/* ============================================================
   synapsedev Mini App v2 — Design System
   Light + Dark themes, faithful to MiniappKiro.html mockup
   ============================================================ */

/* === Reset & Base === */
*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  background: var(--bg);
  overscroll-behavior-y: none;
}

body {
  margin: 0;
  font-family: var(--font, 'Hanken Grotesk', 'Wix Madefor Text', 'Inter', -apple-system, system-ui, sans-serif);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  min-width: 0;
  overflow-x: hidden;
  background: var(--bg);
  color: var(--ink);
}

button,
input,
textarea,
select {
  font: inherit;
  color: inherit;
}

button {
  border: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* Keyboard focus only — never on tap/click (ТЗ 125) */
button:focus,
textarea:focus {
  outline: none;
}

button:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--accent, #73c8a5) 60%, transparent);
  outline-offset: 2px;
}

/* Button press (ТЗ 82): quick scale feedback, 80-120ms, back to 1 on release */
button:not(.filter-item):not(.tab):not(.switch):not(.media-mode):active {
  transform: scale(0.97);
  transition: transform var(--dur-fast) var(--ease);
}

/* Card press (ТЗ 83): no jumps — background/border/scale .99 only */
.row:active,
.model-popup-item:active,
.settings-row:active {
  transform: scale(0.99);
  transition: transform var(--dur-fast) var(--ease);
}

/* === Fonts === */
:root {
  /* Claude parity (owner, 2026-10-10): claude.ai is set in Styrene (chrome) +
     Tiempos Text (Claude's own replies, serif). Both are commercial, so the
     closest free relatives are used — Hanken Grotesk for Styrene and Source
     Serif 4 for Tiempos (the pairing every Claude-lookalike theme ships).
     Hanken Grotesk has NO Cyrillic, so the Russian UI falls through to the
     next family: Wix Madefor Text, a Cyrillic grotesque of the same build. */
  --font: 'Hanken Grotesk', 'Wix Madefor Text', 'Inter', -apple-system, system-ui, sans-serif;
  --font-display: 'Hanken Grotesk', 'Wix Madefor Text', 'Inter', system-ui, sans-serif;
  --font-ui: 'Hanken Grotesk', 'Wix Madefor Text', 'Inter', system-ui, sans-serif;
  --font-serif: 'Source Serif 4', Georgia, 'Times New Roman', serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, monospace;

  /* Motion system (ТЗ 79-80): centralized durations + easings */
  --dur-fast: 120ms;
  --dur-normal: 200ms;
  --dur-slow: 320ms;
  --ease: cubic-bezier(.2, .8, .2, 1);
  --ease-sheet: cubic-bezier(.16, 1, .3, 1);
}

/* ============================================================
   THEMES
   ============================================================ */
:root {
  /* Light */
  --bg: #F7F8F6;
  --card: #FFFFFF;
  --line: #D9DED9;
  --danger: #A65D5D;
  /* card / element borders */
  --line-soft: #E0E3E0;
  /* nav bar border */
  --line-soft2: #E0E4E0;
  /* nav selected border */
  --ink: #17201D;
  --ink-2: #707872;
  --ink-3: #918686;
  /* brand lettering darker than body ink (owner: «надпись SYNAPSEDEV темнее») */
  --ink-strong: #050807;

  --accent: #73c8a5;
  --accent-bg: #EDF7F2;
  --accent-line: #CFE5DA;
  --accent-ink: #245B46;
  --accent-deep: #4B896F;

  --soft: #EDEFEC;
  /* assistant bubble */
  --user-bubble: #68706A;
  --user-bubble-ink: #FFFFFF;

  --nav-sel-bg: #F9FAF8;
  --nav-sel-line: #E0E4E0;

  --avatar-bg: #E7F2ED;
  --progress-bg: #E7F2ED;
  --progress-fill: #73c8a5;

  --filter-bg: #E7F2ED;
  --filter-line: #D9DED9;
  --filter-item-bg: #FFFFFF;
  --filter-item-line: #D9DED9;
  --filter-sel: #73c8a5;
  --filter-sel-line: #73c8a5;

  --picker-bg: #FFFFFF;
  --picker-line: #D9E1DC;
  --picker-shadow: 0 10px 28px rgba(23, 61, 45, 0.10), 0 1px 2px rgba(0, 0, 0, 0.04);
  --picker-sel: #E8F4EE;
  --picker-sel-ink: #245B46;
  --picker-other: #40514A;
  --picker-check: #4B896F;

  --icon-1: #5BAE8C;
  /* quick action icons */
  --icon-2: #4F9A7B;
  /* new chat icon */
  --chip-ink: #474747;
  /* plus icon */

  --send-bg: #73c8a5;
  --send-ink: #FFFFFF;
  --attach-bg: #FFFFFF;
  --attach-line: #D8DED9;

  --banner-bg: #FFFFFF;

  --plan-card: #FFFFFF;
  --plan-card-line: rgba(0, 0, 0, 0.05);
  --plan-track: #E7F2ED;
  --plan-fill: #73C8A5;
  --plan-ink: #707872;

  --toast-bg: #343935;
  --toast-ink: #FFFFFF;

  --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.04);
  --shadow-action: 0 1px 2px rgba(0, 0, 0, 0.04);

  color-scheme: light;
}

[data-theme-effective="dark"] {
  --bg: #171513;
  --card: #211E1C;
  --line: #3A332F;
  --danger: #A65D5D;
  --line-soft: #3A332F;
  --line-soft2: #5A4A40;
  --ink: #FFF7F0;
  --ink-2: #A89F99;
  --ink-3: #A89F99;
  /* dark theme keeps brand at full light ink - darkening only applies to light */
  --ink-strong: #FFF7F0;

  --accent: #C2956A;
  --accent-bg: #2B2724;
  --accent-line: #5A4A40;
  --accent-ink: #FFF7F0;
  --accent-deep: #C2956A;

  --soft: #2B2724;
  --user-bubble: #8F5A3C;
  --user-bubble-ink: #FFF7F0;

  --nav-sel-bg: #8F5A3C;
  --nav-sel-line: #8F5A3C;

  --avatar-bg: #C2956A;
  --progress-bg: #2B2724;
  --progress-fill: #C2956A;

  --filter-bg: #2B2724;
  --filter-line: #3A332F;
  --filter-item-bg: #211E1C;
  --filter-item-line: #3A332F;
  --filter-sel: #8F5A3C;
  --filter-sel-line: #5A4A40;

  --picker-bg: #2B2724;
  --picker-line: #5A4A40;
  --picker-shadow: 0 10px 28px rgba(0, 0, 0, 0.45);
  --picker-sel: #8F5A3C;
  --picker-sel-ink: #FFF7F0;
  --picker-other: #A89F99;
  --picker-check: #FFF7F0;

  --icon-1: #C2956A;
  --icon-2: #C2956A;
  --chip-ink: #C2956A;

  --send-bg: #C2956A;
  --send-ink: #171513;
  --attach-bg: #2B2724;
  --attach-line: #5A4A40;

  --banner-bg: #211E1C;

  --plan-card: #8F5A3C;
  --plan-card-line: transparent;
  --plan-track: #2B2724;
  --plan-fill: #FFF7F0;
  --plan-ink: #FFF7F0;

  --toast-bg: #FFF7F0;
  --toast-ink: #171513;

  --shadow-card: 0 2px 8px rgba(0, 0, 0, 0.3);
  --shadow-action: 0 1px 2px rgba(0, 0, 0, 0.2);

  color-scheme: dark;
}

/* ============================================================
   LAYOUT
   ============================================================ */

/* Safe-area helpers */
.app {
  position: relative;
  max-width: 480px;
  height: 100dvh;
  height: calc(var(--vh, 100dvh) - var(--kb, 0px));
  min-height: 100dvh;
  min-height: calc(100dvh - var(--kb, 0px));
  margin: 0 auto;
  padding: calc(4px + var(--top, 0px)) 10px calc(16px + var(--bottom, 0px) / 2);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  /* Card opacity for the history stage push: padding gaps must not reveal
     the stage INSIDE the shifted card — same --bg as body, so nothing
     changes while the drawer is closed. */
  background: var(--bg);
  /* Keyboard open/close (ТЗ 48-49). Аудит плавности 2026-10-11 (D4): transition по
     height/min-height УБРАН. Это анимация layout-свойства: пока она шла (0.25s), длинный
     тред пересобирался каждый кадр, а высоту .app за один ход клавиатуры переписывают
     десятки раз (--vh от Telegram и --kb от visualViewport). Замер на стенде (4x, 60
     баблов): проезд вьюпорта — 26 кадров «между» концами и 11 длинных задач на 750мс;
     проезд --kb — 62 промежуточных кадра, 12 задач на 735мс, 68 пересчётов раскладки.
     Плавность подъёма теперь даёт сам жест клавиатуры и композер (автовысота поля +
     поджатие ленты), а не перерисовка всего .app. Один layout вместо пятнадцати. */
}

/* Offline banner (ТЗ 89) */
.offline-banner {
  margin: 4px 4px 6px;
  padding: 9px 12px;
  border-radius: 12px;
  background: color-mix(in srgb, var(--danger, #a65d5d) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--danger, #a65d5d) 35%, transparent);
  color: var(--danger, #a65d5d);
  font-size: 13px;
  font-weight: 600;
  text-align: center;
}

.offline-banner.hide {
  display: none !important;
}

/* Maintenance state is server-authoritative and normally hidden. */
.maintenance-banner {
  margin: 4px 4px 8px;
  padding: 10px 12px;
  border-radius: 12px;
  background: color-mix(in srgb, #c98232 16%, transparent);
  border: 1px solid color-mix(in srgb, #c98232 42%, transparent);
  color: var(--ink-strong, var(--ink));
  font-size: 13px;
  font-weight: 600;
  line-height: 1.35;
  text-align: center;
}

.maintenance-banner[hidden] {
  display: none !important;
}

/* === Header === */
.header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 0 6px;
}

.header-left {
  display: flex;
  align-items: baseline;
  gap: 8px;
  min-width: 0;
}

.brand {
  font-family: var(--font-display);
  font-weight: 760;
  font-size: clamp(16px, 4.4vw, 18px);
  letter-spacing: -0.03em;
  color: var(--ink-strong, var(--ink));
  display: flex;
  align-items: center;
  gap: 6px;
  line-height: 1;
}

.status {
  font-size: 11px;
  color: var(--ink-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Status lives only in the DOM (thinking-phases read it to pick a phase);
   it must never render — it used to leak «Готово» / «Ищу в интернете» between
   the mode pills and in the profile. v1 (miniapp.html) hides it the same way.
   !important: miniapp-responsive.css styles .status as a visible pill and must
   not resurface it. History repeats: every time it reappeared it was an old
   cached stylesheet resurfacing a .status pill (display:inline-flex etc.),
   or code writing display:inline back onto the node. The v2.html span carries
   an inline display:none now — HTML is served no-store and cannot be
   resurrected by a stale CSS cache. This belt-and-suspenders rule matches
   everything (inline display:*, !important, attribute selectors). */
.status,
#status {
  display: none !important;
  visibility: hidden !important;
  opacity: 0 !important;
  width: 0 !important;
  height: 0 !important;
  min-width: 0 !important;
  min-height: 0 !important;
  padding: 0 !important;
  border: 0 !important;
  margin: 0 !important;
  font-size: 0 !important;
  line-height: 0 !important;
  position: absolute !important;
  pointer-events: none !important;
  content-visibility: hidden !important;
}

/* Inline styles beat !important in stylesheets (except the property the
   inline rule itself sets); these re-kill the box if a stale sheet or a
   script ever flips display back via style.display.
   NOTE: #status now carries an inline display:none in v2.html itself —
   that is the primary defense (HTML is served no-store). */
span#status[style] {
  width: 0 !important;
  height: 0 !important;
  min-width: 0 !important;
  min-height: 0 !important;
  position: absolute !important;
  pointer-events: none !important;
}

.header-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* Theme toggle (sun/moon) — header right, like mockup profile screens */
.theme-toggle {
  width: 34px;
  height: 34px;
  border-radius: 17px;
  border: 0;
  background: var(--card);
  display: grid;
  place-items: center;
  color: var(--accent);
  flex-shrink: 0;
}

.theme-toggle svg {
  width: 20px;
  height: 20px;
  stroke: var(--accent);
}

/* === Main Panel === */
.main-panel {
  position: relative;
  /* Own stacking context: internal z-indexes (floating buttons, menus inside
     the panel) can never paint above global overlays (drawer 600, sheets 700,
     toast 800). */
  isolation: isolate;
  z-index: 0;
  /* No plate: interface sits directly on the canvas — no bg, no border,
     no rounded card, no shadow (owner request 2026-08-27). */
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* Only the thread may bleed into the app's safe-area padding. The app still
   clips at its physical edge; profile/drawer keep their own scroll/clipping.
   Peek uses the same geometry as settled chat, so edge-back cannot jump. */
html.in-chat .main-panel,
html.edge-back-active .main-panel {
  overflow: visible;
}

html.in-chat .messages,
html.edge-back-active .messages {
  min-height: 0;
  margin-top: calc(-1 * (var(--top, 0px) + 4px));
}

/* New chat button — top-right inside panel, like mockup */
.new-chat-btn {
  position: absolute;
  top: 12px;
  right: 12px;
  z-index: 5;
  height: 30px;
  padding: 0 14px;
  border-radius: 15px;
  border: 0;
  background: var(--accent-bg);
  box-shadow: var(--shadow-action);
  display: grid;
  place-items: center;
  color: var(--icon-2);
}

.new-chat-btn svg {
  width: 16px;
  height: 16px;
  stroke: var(--icon-2);
}

/* History button — top-left inside panel (ChatGPT-style) */
.history-btn {
  position: absolute;
  top: 12px;
  left: 12px;
  z-index: 5;
  height: 30px;
  padding: 0 12px;
  border-radius: 15px;
  border: 0;
  background: var(--accent-bg);
  box-shadow: var(--shadow-action);
  display: grid;
  place-items: center;
  color: var(--icon-2);
}

.history-btn svg {
  width: 16px;
  height: 16px;
  stroke: var(--icon-2);
}

.history-group {
  padding: 14px 6px 4px;
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.pin-mark {
  font-size: 11px;
  opacity: 0.75;
}

.row-rename-input {
  display: block;
  width: 100%;
  border: 1px solid var(--accent);
  border-radius: 8px;
  background: var(--card);
  color: var(--ink);
  font-size: 16px;
  font-weight: 700;
  padding: 6px 8px;
  outline: 0;
  box-sizing: border-box;
}

/* Row actions menu (miniapp-v2-nav.js). Kept from main: the live nav module
   does not create this menu, but main's tracked nav still does. */
.row-menu {
  position: fixed;
  z-index: 650;
  min-width: 168px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18);
  padding: 4px;
  display: flex;
  flex-direction: column;
}

.row-menu button {
  text-align: left;
  padding: 10px 12px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--ink);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.row-menu button:hover {
  background: var(--soft);
}

.row-menu button[data-act="del"] {
  color: var(--danger, #a65d5d);
}

/* Spec §20: with Reduce Motion the drawer still STATE-communicates (instant
   open/close, no slide travel, no fade lag) so the user keeps orientation. */
@media (prefers-reduced-motion: reduce) {
  .history-overlay,
  .history-overlay.open,
  .history-overlay::before,
  .history-panel,
  .history-overlay.open .history-panel,
  .app {
    transition-duration: 1ms !important;
  }
}

/* === Chat history overlay — per expert advice (spec §11): one motion system.
   Panel slide and backdrop dim share ONE duration token (--hist-commit-ms is
   set from JS at drag-release = remaining path / release velocity, so the
   settle continues the finger's motion instead of teleporting). Default
   button-driven timings: open 380ms / close 260ms on --hist-curve (retuned
   2026-10-10 for ChatGPT parity). */
:root { --hist-commit-ms: 260ms; --hist-curve: cubic-bezier(.32, .72, 0, 1); }

/* NOTE (owner, 2026-11-03): stage push is BACK by request («история уходит
   визуально ниже чата, как у chatgpt и claude») — but done on the right
   element this time: only .app moves (translate+scale+radius), the history
   is a flat full-height panel ON THE STAGE beneath it. The 2026-10-09 removal
   targeted a version that dragged the whole interface; .app is one overflow-
   hidden column, its fixed/absolute children travel with the card. */

.history-overlay {
  position: fixed;
  inset: 0;
  z-index: 600;
  background: transparent;
  visibility: hidden;
  pointer-events: none;
  transition: visibility 0s linear var(--hist-commit-ms);
}

/* Dim veil covers the stage region the chat card vacated — from this stacking
   level it ALSO veils the chat sliver (overlay z-600 sits above .app), which is
   the ChatGPT look: faint scrim on the card edge, not a blackened panel.
   --dim is written per-frame during drag; .open flips it via the class so the
   fade shares --hist-commit-ms. */
.history-overlay::before {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.06);
  opacity: var(--dim, 0);
  transition: opacity var(--hist-commit-ms) var(--hist-curve);
  pointer-events: none;
}

.history-overlay.open {
  visibility: visible;
  pointer-events: auto;
  transition: visibility 0s;
  --dim: 1;
}

/* Interactive drag (Блок 3): no transitions while the finger moves the card */
.history-overlay.dragging {
  visibility: visible;
  pointer-events: auto;
}
.history-overlay.dragging::before,
.history-overlay.dragging .history-panel {
  transition: none;
}
body.hist-drag .app {
  transition: none;
}

/* The chat card slides right and shrinks — the drawer lives beneath it. */
.app {
  transition: transform var(--hist-commit-ms) var(--hist-curve),
              border-radius var(--hist-commit-ms) var(--hist-curve),
              box-shadow var(--hist-commit-ms) var(--hist-curve);
}
/* Owner, 2026-11-05: chat dim removed by request («убери затемнение чата») —
   the pushed card stays bright; the only depth cue is the .history-seam
   shadow strip at the card edge (below). The 40% veil inside .app banded
   on WebViews («полоски») and is gone. */
/* First-frame compositor hint while the drawer animates only — a permanent
   promoted layer on the root column would cost memory on every view. */
body.hist-open .app,
body.hist-drag .app {
  will-change: transform, border-radius;
}
/* ChatGPT card-peek (owner, 2026-11-03): the chat NEVER resizes — it stays a
   full-height, full-width surface and only slides right. The card look comes
   from the rounded left edge + shadow. (clip-path inset was tried and
   reverted same-day: the clipped strips revealed the dark stage beneath —
   «чёрные полоски» — so full-size card + radius + box-shadow stays.) */
body.hist-open .app {
  /* Recess geometry (owner, 2026-11-05): the card's LEFT edge lands exactly
     at the panel's right edge. Panel width is capped min(78vw, 320px) —
     the pre-«wide niche» size the owner approved («размер до этого меня
     устраивал»). Since the panel is pinned to the column's left edge (see
     .history-panel left below), the translate is simply the panel width —
     always rightward, never negative on wide screens. */
  /* Mobile recess (phones): the card slides right by the panel width —
     its left edge lands on the panel's edge, the sliver keeps rounded
     corners. Tablets/PCs get the sidebar model below instead. */
  transform: translate3d(min(78vw, 320px), 0, 0);
  border-radius: 36px;
}

/* Tablet/PC (owner, 2026-11-05: «всё серое и по центру»): NO pushed
   card — the chat column simply clears the docked sidebar. It shifts
   right ONLY when the centered 480px column would sit beneath the
   320px panel (small tablets), never left, and keeps square edges —
   no giant grey expanse, no hidden greeting. */
@media (min-width: 768px) {
  body.hist-open .app {
    transform: translate3d(max(0px, min(78vw, 320px) - max(0px, 50vw - 240px)), 0, 0);
    border-radius: 0;
  }
  /* The recess-corner crescents wrap a pushed card — there is none
     on desktop, so the wedge squares stay off. */
  .history-overlay .history-notch {
    display: none;
  }
}

/* Owner, 2026-11-03: «в чате видны пузырики и надпись» — the thinking card's
   goo balls + phase caption read as stray artifacts on the sliver. Hidden
   while the drawer is open; the card returns when it closes.
   PHONES ONLY (owner, 2026-11-05): on tablets/PCs the chat is a readable
   column, not a sliver — the greeting must stay. */
@media (max-width: 767px) {
  body.hist-open #thinkingCard {
    opacity: 0 !important;
  }
}
/* Ambient hides removed 2026-11-05: the aurora/dust layer is gone for good
   (owner: «плавающие шары полностью убраны»), and the docked brand name is
   back under the drawer too (owner: «надпись synapse можно вернуть») — the
   occlusion watcher still covers real overlays on its own. */
#brandText { transition: opacity .18s ease; }

.history-panel {
  /* Отступ врезных линий шкафа (владелец, 2026-10-11: «тоненькая, концы не
     доходят до краёв» + «ещё чуть покороче»). Одна переменная на три линии —
     заголовок, поле поиска, полоса профиля — чтобы они не разъехались. */
  --hairline-inset: 24px;
  /* Плоская шторка на всю высоту (владелец, 2026-11-03, скрин ChatGPT):
     закругление живёт на чат-карточке, панель прижата к рамке экрана. */
  position: absolute;
  top: 0;
  bottom: 0;
  /* Dock at the WINDOW's left edge — a real desktop sidebar on tablets
     and PCs (owner: the column-pinned version floated mid-window with a
     dead stage margin). Phones are unaffected (edge = 0). */
  left: 0;
  /* Panel width back to the pre-«wide niche» cap (owner, 2026-11-05:
     «история столько места занимает — убери растяжение»): 78% of the
     screen on phones, never wider than 320px on desktop-class widths.
     The card's left edge lands at this edge (see .app translate). */
  width: min(78vw, 320px);
  height: auto;
  background: var(--card);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  /* Seam shadow ON THE PANEL (owner, 2026-11-05: «не в ту сторону тень»)
     lives in .history-panel::after below — painted ABOVE panel content so
     selected-row highlights sit under it (owner: «кнопка залезает на
     тень»). */
  /* Subtle emerge: while the app card uncovers the stage the panel parallaxes
     into place instead of teleporting — reads as "it was under the chat". */
  /* Drawer slide (owner, 2026-11-07): the panel travels its FULL width —
     starts off-screen and slides in, never fades. In lockstep with the
     app card on mobile; on desktop it arrives on its own (dock model). */
  transform: translate3d(-100%, 0, 0);
  transition: transform var(--hist-commit-ms) var(--hist-curve);
  will-change: transform, opacity;
  backface-visibility: hidden;
  contain: layout paint;
}

.history-overlay.open .history-panel {
  transform: translate3d(0, 0, 0);
}

/* Seam shadow ON THE PANEL (owner, 2026-11-05): the chat card is the
   foreground layer sliding over history, so its shadow falls onto the
   panel surface — a blurred inset rim along the panel's right edge.
   A dedicated ::after strip (pointer-events:none, z above rows) keeps it
   ABOVE list content: a cast shadow darkens whatever is near the edge,
   selected-row highlights included — they no longer «залезают на тень».
   Rides the panel edge automatically (no JS) and fades with panel
   opacity during drags. */
.history-panel::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  width: 22px;
  pointer-events: none;
  z-index: 5;
  box-shadow: inset -16px 0 22px -14px rgba(0, 0, 0, 0.20);
}


.history-library {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;
  -ms-overflow-style: none;
  padding: 8px 12px calc(14px + env(safe-area-inset-bottom, 0px));
}

.history-library::-webkit-scrollbar {
  display: none;
}

.history-lib-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
}

.history-lib-cell {
  position: relative;
  display: block;
}

.history-lib-thumb {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  border-radius: 12px;
  overflow: hidden;
  background: var(--soft);
  padding: 0;
}

.history-lib-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.history-lib-thumb-chat {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 24px;
  height: 24px;
  border-radius: 8px;
  border: 0;
  display: grid;
  place-items: center;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  padding: 0;
}

.history-lib-thumb-chat svg {
  width: 13px;
  height: 13px;
}

.history-lib-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.history-lib-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px;
  border-radius: 12px;
  background: transparent;
  color: var(--ink);
  text-align: left;
  transition: background var(--dur-fast) var(--ease);
}

.history-lib-row:active,
.history-lib-row:hover {
  background: var(--soft);
}

.history-lib-row-ic {
  width: 38px;
  height: 38px;
  border-radius: 10px;
  background: var(--accent-bg);
  color: var(--accent-ink);
  display: grid;
  place-items: center;
  flex-shrink: 0;
}

.history-lib-row-ic svg {
  width: 18px;
  height: 18px;
}

/* Office-tile icons carry their own fill — the tinted badge box steps back. */
.history-lib-row-ic.is-office {
  background: none;
  color: inherit;
}

.history-lib-row-ic.is-office svg {
  width: 38px;
  height: 38px;
}

.history-lib-row-thumb {
  width: 38px;
  height: 38px;
  border-radius: 10px;
  overflow: hidden;
  background: var(--soft);
  flex-shrink: 0;
  display: block;
}

.history-lib-row-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.history-lib-copy {
  flex: 1;
  min-width: 0;
}

.history-lib-copy strong {
  display: block;
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.history-lib-copy small {
  display: block;
  font-size: 11px;
  color: var(--ink-3);
  margin-top: 1px;
}

.history-lib-chev {
  color: var(--ink-3);
  flex-shrink: 0;
  display: grid;
  place-items: center;
}

.history-lib-chev svg {
  width: 14px;
  height: 14px;
}

.history-lib-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 44px 18px;
  text-align: center;
  color: var(--ink-3);
}

.history-lib-empty svg {
  width: 30px;
  height: 30px;
  opacity: 0.6;
}

.history-lib-empty p {
  margin: 0;
  font-size: 12.5px;
  max-width: 220px;
  line-height: 1.5;
}

.history-lib-empty.is-error {
  color: var(--danger);
}

.history-lib-skel {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
}

.history-lib-skel span {
  aspect-ratio: 1;
  border-radius: 12px;
  background: var(--soft);
  animation: libSkel 1.2s ease-in-out infinite;
}

.history-lib-skel span:nth-child(2n) {
  animation-delay: 0.15s;
}

.history-lib-skel span:nth-child(3n) {
  animation-delay: 0.3s;
}

@keyframes libSkel {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.45; }
}

@media (prefers-reduced-motion: reduce) {
  .history-lib-skel span {
    animation: none;
  }
}


/* ============================================================
   CHAT VIEW
   ============================================================ */
.view {
  display: none;
}

.view.on {
  display: flex;
  flex-direction: column;
  min-height: 0;
  flex: 1;
}

/* Profile scrolls inside the panel — scrollbar hidden so content never shifts */
/* The profile scrollport reaches the screen edges, just like the feed.
   Scrollable padding protects resting content; no hard clipping at the header. */
html:not(.in-chat) .main-panel { overflow: visible; }
.header { position: relative; z-index: 40; flex-shrink: 0; }
#profile.view {
  margin-top: calc(-1 * (var(--top, 0px) + 4px + var(--profile-head-h, 38px)));
  margin-bottom: calc(-1 * var(--app-pad-b, 16px));
  padding-top: calc(var(--top, 0px) + 4px + var(--profile-head-h, 38px));
  padding-bottom: calc(var(--app-pad-b, 16px) + 12px);
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;
  -ms-overflow-style: none;
}

#profile.view::-webkit-scrollbar {
  display: none;
}

/* overflow:hidden children must not collapse in the flex column */
#profile.view>* {
  flex-shrink: 0;
}

/* Chat container */
.chat {
  position: relative; /* anchor for the floating + menu overlay */
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.messages {
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  /* was 58px to clear the two floating top pills; those moved into the header,
     so the tall top gap is gone (dead space) — normal breathing room instead. */
  padding: 20px 18px 16px;
  /* Низ ленты уходит до кромки приложения: composer и подпись бренда лежат
     оверлеями, поэтому текст едет под них, а не обрывается на 8px выше подписи
     (владелец 2026-10-11: «текст должен уходить ещё ниже, даже ниже надписи»).
     --app-pad-b и --bottom-zone-h публикует miniapp-v2-composer.js: первый —
     насколько тянуть ленту в паддинг .app, второй — высота всей нижней зоны
     (composer + подпись + safe-area). Нижний padding держит последнюю строку
     выше поля ввода: без него оверлей сделал бы конец ответа недостижимым. */
  margin-bottom: calc(-1 * var(--app-pad-b, 16px));
  /* The overlay does not shorten the scrollport. Reserve its measured height
     plus a small reading gap, so the last line can scroll above the composer. */
  padding-bottom: calc(var(--bottom-zone-h, 112px) + 24px);
  display: flex;
  flex-direction: column;
  gap: 26px;
  -webkit-overflow-scrolling: touch;
}

/* Flex-column children default to flex-shrink:1 — tall media entries (image +
   action buttons) got squeezed to fit the container and their overflow:hidden
   clipped the buttons, with scrollHeight never exceeding the box (no scroll).
   Children of a scroll container must keep their natural height. */
.messages > * {
  flex-shrink: 0;
}

.messages::-webkit-scrollbar {
  width: 4px;
}

.messages::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--line) 80%, transparent);
  border-radius: 99px;
}

/* Invisible filler below the pinned exchange: lets scroll reach far enough that
   the newest user message can sit at the top of the viewport even when the answer
   is shorter than the screen. Removed on manual scroll (full history restore). */
.pin-spacer {
  flex-shrink: 0;
  visibility: hidden;
}

.messages.is-empty,
.messages:has(>.empty:only-child) {
  overflow: hidden;
  overscroll-behavior: none;
}

/* === Empty state (ТЗ 20-22): brand, question, suggestions === */
.empty {
  margin: 0 auto;
  color: var(--ink-3);
  font-size: 14px;
  text-align: center;
  padding: 30px 20px;
}

/* Owner (2026-09-24): the greeting must sit DEAD CENTER of the chat feed —
   both axes. Auto margins center it in the flex column (the feed's only child
   while empty); padding-bottom offsets the composer's visual weight so the
   block reads as centered on the visible screen, not on the raw box. */
.empty-state {
  margin: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 24px 20px;
  gap: 10px;
  transition: opacity var(--dur-normal) var(--ease);
}

.messages.typing .empty-state {
  opacity: 0;
  pointer-events: none;
}

.empty-brand {
  font-family: var(--font-display);
  font-weight: 760;
  font-size: 22px;
  letter-spacing: -0.05em;
  color: var(--ink);
  position: relative;
}

/* Dot sits outside the layout box — otherwise the brand text centers as
   "dot + gap + text" and lands ~8px right of the real center. */
.empty-brand::before {
  content: '';
  position: absolute;
  right: 100%;
  margin-right: 8px;
  top: 50%;
  transform: translateY(-50%);
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--accent);
}

.empty-title {
  font-size: 19px;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--ink);
}

.empty-sub {
  font-size: 13.5px;
  color: var(--ink-3);
  max-width: 260px;
  line-height: 1.5;
}

.empty-suggestions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  margin-top: 10px;
}

.suggestion {
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink);
  border-radius: 999px;
  padding: 8px 14px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}

.suggestion:active {
  transform: scale(0.97);
}

/* === Bubbles === */
.bubble {
  max-width: 85%;
  padding: 11px 13px;
  border-radius: 16px;
  background: var(--soft);
  color: var(--ink);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  -webkit-user-select: text !important;
  user-select: text !important;
  -webkit-touch-callout: default !important;
  touch-action: auto !important;
  cursor: text;
  font-size: 14px;
  line-height: 1.45;
  word-break: break-word;
  transition: transform var(--dur-normal) var(--ease);
}

.bubble.reply-dragging {
  transition: none;
}

/* Quoted message inside a user bubble (Блок 4) */
.reply-quote {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-bottom: 7px;
  padding: 6px 8px;
  border-left: 3px solid rgba(255, 255, 255, 0.55);
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.12);
}

.reply-quote-title {
  font-size: 11px;
  font-weight: 700;
  opacity: 0.85;
}

.reply-quote-text {
  font-size: 12px;
  opacity: 0.8;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}

/* Web-search source cards (Блок 5) */
.sources-cards {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 10px;
}

.source-card {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--card);
  color: var(--ink);
  text-decoration: none;
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}

.source-card:active {
  border-color: var(--accent);
  transform: scale(0.985);
}

.source-card-index {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--accent-bg);
  color: var(--accent-ink);
  font-size: 11px;
  font-weight: 700;
}

.source-card-body {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.source-card-body strong {
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.source-card-body small {
  font-size: 11px;
  color: var(--ink-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.bubble.user {
  align-self: flex-end;
  background: var(--user-bubble);
  color: var(--user-bubble-ink);
  border-radius: 20px 20px 6px 20px;
}

/* AI response: no card, free full width (ТЗ 25-26) */
.bubble.assistant {
  align-self: stretch;
  max-width: 100%;
  background: transparent;
  padding: 0;
  border-radius: 0;
  cursor: default;
}

/* The reply is set in the interface face (2026-11-14): the serif reply was a
   copy of Claude's look and clashed with the sans chrome, tables and lists
   on a phone screen. Code and tables keep their own faces. */
.bubble.assistant .message-content {
  font-family: var(--font-ui);
  font-size: 15px;
  line-height: 1.6;
}

.bubble.assistant .message-content code,
.bubble.assistant .message-content pre,
.bubble.assistant .message-content kbd,
.bubble.assistant .message-content .code-block,
.bubble.assistant .message-content .code-head {
  font-family: var(--font-mono);
}

.bubble.system {
  color: var(--danger);
  font-size: 13px;
}

.meta {
  font-size: 11px;
  opacity: 0.7;
  margin-bottom: 3px;
  font-weight: 650;
}

/* Assistant tag: brand logo + readable model name (chat.js _metaModel). */
.meta-logo {
  display: inline-flex;
  width: 12px;
  height: 12px;
  margin-right: 5px;
  vertical-align: -2px;
}
.meta-logo svg {
  width: 100%;
  height: 100%;
}

/* «Вы» above the user's own bubble repeated what the alignment and colour
   already say. */
.bubble.user > .meta {
  display: none;
}

/* Streaming meta carries the animated brand star instead of the ✦ glyph. */
.meta.is-thinking {
  display: flex;
  align-items: center;
  gap: 5px;
}

.meta.is-thinking .syn-star {
  width: 12px;
  height: 12px;
}

/* Thinking indicator (ТЗ 32-33): ✦ Synapse · Думаю… + pulsing dots + phase line */
.thinking-card {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  align-self: flex-start;
  max-width: 100%;
  max-height: 140px;
  overflow: hidden;
  padding: 0;
  background: transparent;
  color: var(--ink-3);
  font-size: 13px;
  transition: opacity var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease), max-height var(--dur-normal) var(--ease), visibility 0s;
}

/* Exit isn't a display-snap: the card fades, rises a touch and collapses —
   the feed closes the gap smoothly instead of jumping. visibility flips at
   the end so the hidden box never intercepts touches. */
.thinking-card.hide {
  display: flex !important;
  opacity: 0;
  transform: translateY(-6px) scale(.98);
  max-height: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease), max-height var(--dur-normal) var(--ease), visibility 0s linear var(--dur-normal);
}

.thinking-brand {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: var(--ink);
  font-weight: 650;
}

/* Synapse star → logo morph, v2 (liquid): the ✦ implodes into a point, two
   blobs pull apart dragging a gooey neck — the metaball pair from the
   project logo (feGaussianBlur + alpha contrast glues them while they're
   close) — they breathe, merge back into one blob, and the star reassembles.
   3.4s loop in the thinking card, streaming meta and deck-progress head. */
.syn-star {
  position: relative;
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
  color: var(--accent);
}

.syn-star > svg {
  display: block;
  width: 100%;
  height: 100%;
}

.syn-star path {
  fill: currentColor;
  transform-box: view-box;
  transform-origin: 50% 50%;
  animation: synStarBlade 3.4s var(--ease) infinite;
}

/* Implode vectors — each blade dives toward the centre to become the core
   the blobs split from; on return they fly back out along the same axis. */
.syn-star .syn-blades path:nth-of-type(1) { --bx: 0px; --by: 5px; animation-delay: 0s; }
.syn-star .syn-blades path:nth-of-type(2) { --bx: -5px; --by: 0px; animation-delay: 40ms; }
.syn-star .syn-blades path:nth-of-type(3) { --bx: 0px; --by: -5px; animation-delay: 80ms; }
.syn-star .syn-blades path:nth-of-type(4) { --bx: 5px; --by: 0px; animation-delay: 120ms; }

@keyframes synStarBlade {
  0%, 7% { transform: translate(0, 0) scale(1); opacity: 1; }
  19%, 84% { transform: translate(var(--bx), var(--by)) scale(0.2); opacity: 0; }
  97%, 100% { transform: translate(0, 0) scale(1); opacity: 1; }
}

/* The two logo blobs — travel in viewBox units; the goo filter on their
   parent g stretches a liquid neck whenever they sit close. */
.syn-star .syn-ball {
  fill: currentColor;
  transform-box: fill-box;
  transform-origin: center;
  animation: synBall 3.4s var(--ease) infinite;
}

.syn-star .syn-ball.b1 { --gx: -6px; }
.syn-star .syn-ball.b2 { --gx: 6px; animation-delay: 60ms; }

@keyframes synBall {
  0%, 14% { transform: translateX(0) scale(0.3); opacity: 0; }
  20% { transform: translateX(0) scale(0.9); opacity: 1; }
  38% { transform: translateX(var(--gx)) scale(1); opacity: 1; }
  49% { transform: translateX(calc(var(--gx) * 1.12)) scale(1); }
  60% { transform: translateX(var(--gx)) scale(1); opacity: 1; }
  84% { transform: translateX(0) scale(0.9); opacity: 1; }
  94%, 100% { transform: translateX(0) scale(0.25); opacity: 0; }
}

/* Compare progress (owner 2026-11-13): instead of one orbiting ball per
   model (chaotic at 3–4), the logo pair animates as usual and a row of dots —
   one per model — fills in as each answer lands. */
.syn-cmp {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  margin-left: 8px;
  vertical-align: middle;
}

.syn-cmp i {
  width: 5px;
  height: 5px;
  border-radius: 999px;
  background: currentColor;
  opacity: 0.22;
  transition: opacity var(--dur-normal) var(--ease), transform var(--dur-normal) var(--ease);
}

.syn-cmp i.on {
  opacity: 0.9;
  transform: scale(1.15);
}

/* Static (completed) state — the star stops blooming but keeps its shape. */
.syn-star.is-idle path { animation: none; opacity: 0.9; transform: none; }
.syn-star.is-idle .syn-ball { animation: none; opacity: 0; }
.syn-star.is-idle { animation: none; }

/* Phase line (Блок 9): what the AI is doing right now.
   Stable-height rule (2026-10-20): the row is ALWAYS reserved (fixed
   line-height, visibility instead of display:none when empty) so phase
   text arriving/leaving mid-thinking can not grow/shrink the card and
   shift the feed. */
.thinking-phase {
  flex-basis: 100%;
  min-height: 15px;
  line-height: 15px;
  font-size: 11.5px;
  color: var(--ink-3);
  margin-top: -3px;
  animation: phaseIn var(--dur-normal) var(--ease);
}

.thinking-phase:empty {
  visibility: hidden;
}

@keyframes phaseIn {
  from {
    opacity: 0;
    transform: translateY(2px);
  }
}

@keyframes miniapp-wave {

  0%,
  65%,
  100% {
    transform: translateY(0);
    opacity: 0.35;
  }

  32% {
    transform: translateY(-4px);
    opacity: 1;
  }
}

.thinking-timer {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-3);
  margin-left: auto;
  /* tabular digits: per-second ticks must not wiggle the row width */
  font-variant-numeric: tabular-nums;
}

.thinking-timer:empty {
  display: none;
}

/* Reasoning stream inside the thinking card (build 20261115a,
   ThinkingReasoning port): settled provider-reasoning sentences stack
   above a shimmering live tail. The box is bottom-packed — newest lines
   stay visible, older ones clip at the top under a fade mask. */
.thinking-reason {
  flex-basis: 100%;
  min-width: 0;
}

.thinking-reason.hide {
  display: none !important;
}

.thinking-card.has-reason {
  max-height: 320px;
}

.thinking-reason-box {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  max-height: 176px;
  overflow: hidden;
}

.thinking-reason-box.capped {
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 16px);
  mask-image: linear-gradient(to bottom, transparent 0, #000 16px);
}

.thinking-reason-lines {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}

.thinking-reason-lines {
  gap: 5px;
}

.thinking-reason-line {
  margin: 0;
  font-size: 12px;
  line-height: 1.5;
  color: var(--ink-3);
  overflow-wrap: anywhere;
  animation: reasonLineIn .28s var(--ease) both;
}

@keyframes reasonLineIn {
  from {
    opacity: 0;
    transform: translateY(3px);
  }
}

.thinking-reason-tail {
  margin: 5px 0 0;
  font-size: 12px;
  line-height: 1.5;
  color: var(--ink-3);
  overflow-wrap: anywhere;
}

.thinking-reason-tail.hide {
  display: none !important;
}

@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .thinking-card:not(.hide) .thinking-reason-tail:not(.hide):not(:empty) {
    background-image: linear-gradient(100deg, var(--ink-3) 30%, var(--ink) 50%, var(--ink-3) 70%);
    background-size: 230% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    color: transparent;
    animation: thinkingShimmer 1.15s linear infinite;
  }
}

@media (prefers-reduced-motion: reduce) {
  .thinking-reason-line {
    animation: none !important;
  }
}

.model-attribution {
  display: block;
  margin: 0 0 7px;
  color: var(--accent);
  font: 500 10px/1.35 var(--font-ui);
  letter-spacing: 0.02em;
}

.message-content>*:first-child {
  margin-top: 0;
}

.message-content>*:last-child {
  margin-bottom: 0;
}

/* === Markdown content (ТЗ 29): headings, lists, quotes, tables, code === */
.message-content p {
  margin: 0 0 10px;
}

.message-content h1,
.message-content h2,
.message-content h3,
.message-content h4 {
  margin: 16px 0 8px;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.3;
  color: var(--ink);
}

.message-content h1 {
  font-size: 19px;
}

.message-content h2 {
  font-size: 17px;
}

.message-content h3 {
  font-size: 15.5px;
}

.message-content h4 {
  font-size: 14.5px;
}

.message-content ul,
.message-content ol {
  margin: 0 0 10px;
  padding-left: 20px;
}

.message-content li {
  margin: 3px 0;
}

.message-content blockquote {
  margin: 0 0 10px;
  padding: 2px 0 2px 12px;
  border-left: 3px solid var(--accent);
  color: var(--ink-2);
}

.message-content table {
  border-collapse: collapse;
  margin: 10px 0;
  width: 100%;
  font-size: 13px;
}

.message-content th,
.message-content td {
  border: 1px solid var(--line);
  padding: 6px 8px;
  text-align: left;
}

.message-content th {
  background: var(--soft);
  font-weight: 650;
}

.message-content hr {
  border: 0;
  border-top: 1px solid var(--line);
  margin: 14px 0;
}

.message-content code {
  font: 12px/1.5 var(--font-mono);
  background: var(--accent-bg);
  color: var(--accent-ink);
  padding: 1px 5px;
  border-radius: 6px;
  overflow-wrap: anywhere;
}

/* === Code block with copy (ТЗ 30) === */
.code-block {
  margin: 12px 0;
  border: 1px solid var(--line);
  border-radius: 12px;
  overflow: hidden;
  background: var(--accent-bg);
}

.code-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 6px 10px;
  background: color-mix(in srgb, var(--accent-bg) 55%, var(--card));
  border-bottom: 1px solid var(--line);
}

.code-lang {
  font: 11px var(--font-mono);
  color: var(--ink-3);
  text-transform: lowercase;
}

.code-copy {
  margin-left: auto;
  border: 0;
  background: transparent;
  color: var(--ink-3);
  font: 12px var(--font-ui);
  padding: 3px 8px;
  border-radius: 7px;
  cursor: pointer;
}

.code-copy:active {
  transform: scale(0.97);
}

.code-copy.done {
  color: var(--accent-ink);
}

.code-block pre {
  margin: 0;
  padding: 12px;
  overflow-x: auto;
}

.code-block pre code {
  display: block;
  background: transparent;
  color: var(--accent-ink);
  padding: 0;
  white-space: pre;
}

/* Collapse/expand code block (Блок 6) */
.code-toggle {
  width: 22px;
  height: 22px;
  margin-left: 4px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--ink-3);
  display: grid;
  place-items: center;
  cursor: pointer;
  flex-shrink: 0;
}

.code-toggle::before {
  content: '';
  width: 12px;
  height: 12px;
  background-color: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m18 15-6-6-6 6'/%3E%3C/svg%3E") center/contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m18 15-6-6-6 6'/%3E%3C/svg%3E") center/contain no-repeat;
  transition: transform var(--dur-normal) var(--ease);
}

.code-block.collapsed .code-toggle::before {
  transform: rotate(180deg);
}

.code-block.collapsed pre {
  display: none;
}

.code-block.collapsed .code-head {
  border-bottom: 0;
}

/* Copy button on bubbles (core script injects .msg-copy) */
.bubble-actions {
  display: flex;
  gap: 4px;
  margin-top: 6px;
  opacity: 0;
  transition: opacity var(--dur-normal) var(--ease);
}

.bubble-actions,
.bubble:active .bubble-actions,
.bubble-actions:focus-within {
  opacity: 1;
}

.msg-copy {
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--ink-3);
  display: grid;
  place-items: center;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}

.msg-regen {
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--ink-3);
  display: grid;
  place-items: center;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}

.msg-regen::before {
  content: '';
  width: 14px;
  height: 14px;
  background-color: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 12a9 9 0 1 1-2.64-6.36'/%3E%3Cpath d='M21 3v6h-6'/%3E%3C/svg%3E") center/contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 12a9 9 0 1 1-2.64-6.36'/%3E%3Cpath d='M21 3v6h-6'/%3E%3C/svg%3E") center/contain no-repeat;
}

.msg-share {
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--ink-3);
  display: grid;
  place-items: center;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}

.msg-share::before {
  content: '';
  width: 14px;
  height: 14px;
  background-color: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 3v12'/%3E%3Cpath d='M7 8l5-5 5 5'/%3E%3Cpath d='M5 13v6a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-6'/%3E%3C/svg%3E") center/contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 3v12'/%3E%3Cpath d='M7 8l5-5 5 5'/%3E%3Cpath d='M5 13v6a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-6'/%3E%3C/svg%3E") center/contain no-repeat;
}

.msg-share:hover {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent-bg) 60%, transparent);
}

/* Error bubble (ТЗ 88): generic message + retry, no technical details */
.bubble.error {
  align-self: stretch;
  background: transparent;
  padding: 0;
  border-radius: 0;
  color: var(--danger, #a65d5d);
}

.bubble.error .message-content {
  color: var(--ink-3);
  font-size: 13.5px;
}

.retry-btn {
  margin-top: 8px;
  padding: 7px 14px;
  border: 0;
  border-radius: 999px;
  background: var(--card);
  color: var(--ink);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.retry-btn:active {
  transform: scale(0.97);
}

.msg-copy::before {
  content: '';
  width: 14px;
  height: 14px;
  background-color: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M16 1H4a2 2 0 0 0-2 2v14h2V3h12V1zm3 4H8a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2zm0 16H8V7h11v14z'/%3E%3C/svg%3E") center/contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M16 1H4a2 2 0 0 0-2 2v14h2V3h12V1zm3 4H8a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2zm0 16H8V7h11v14z'/%3E%3C/svg%3E") center/contain no-repeat;
}

.msg-copy:hover {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent-bg) 60%, transparent);
}

.bubble.user .msg-copy {
  color: rgba(255, 255, 255, 0.7);
}

/* Message attachments (core script injects .message-attachments) */
.message-attachments {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 6px;
}

/* Owner: thumbnails everywhere must be frameless - a soft fill, rounded corners
   and no 1px outline (it read as a "рамка" around every preview). */
.message-attachment {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 8px;
  border-radius: 12px;
  background: color-mix(in srgb, var(--accent) 8%, var(--soft));
  border: 0;
  font-size: 11px;
  max-width: 220px;
}

.message-attachment-thumb {
  width: 56px;
  height: 56px;
  border-radius: 12px;
  overflow: hidden;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  background: transparent;
  font: 700 8px/1 var(--font);
  color: var(--ink-3);
}

.message-attachment-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 12px;
}

.message-attachment-copy {
  min-width: 0;
  overflow: hidden;
}

.message-attachment-copy strong {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Lecture "Конспект" action chip (rendered into #fileChips) */
.file-chip-conspect .conspect-btn {
  border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--line));
  background: color-mix(in srgb, var(--accent) 15%, var(--card));
  color: var(--ink);
  border-radius: 8px;
  padding: 3px 8px;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  flex-shrink: 0;
}

.file-chip-conspect .conspect-x {
  border: 0;
  background: transparent;
  color: var(--ink-3);
  cursor: pointer;
  font-size: 13px;
  padding: 0 2px;
  flex-shrink: 0;
}

/* Audio transcript: collapsible card under the attachment chip */
.msg-transcript {
  flex-basis: 100%;
  max-width: 100%;
  border: 1px solid color-mix(in srgb, var(--accent) 20%, var(--line));
  border-radius: 12px;
  background: color-mix(in srgb, var(--accent) 5%, var(--card));
  font-size: 11px;
  overflow: hidden;
}

.msg-transcript summary {
  list-style: none;
  cursor: pointer;
  padding: 6px 10px;
  display: flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--ink-2, var(--ink));
}

.msg-transcript summary::-webkit-details-marker {
  display: none;
}

.msg-transcript summary::after {
  content: '▾';
  margin-left: auto;
  flex-shrink: 0;
  opacity: .6;
  transition: transform var(--dur-fast) var(--ease);
}

.msg-transcript[open] summary::after {
  transform: rotate(180deg);
}

.msg-transcript-text {
  padding: 0 10px 8px;
  border-top: 1px solid var(--line);
  padding-top: 8px;
  margin-top: 2px;
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 240px;
  overflow-y: auto;
  color: var(--ink);
  line-height: 1.45;
}

/* Collapsed «Thought for Xs» under an assistant answer (build 20261115a):
   same disclosure anatomy as .msg-transcript, quieter — reasoning is
   supporting material, not content. */
.msg-thought {
  flex-basis: 100%;
  max-width: 100%;
  margin-top: 8px;
  font-size: 11.5px;
}

.msg-thought-head {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 3px 2px;
  color: var(--ink-3);
  user-select: none;
}

.msg-thought-head::-webkit-details-marker {
  display: none;
}

.msg-thought-label {
  font-weight: 600;
}

.msg-thought-chev {
  margin-left: auto;
  flex-shrink: 0;
  opacity: .55;
  transition: transform var(--dur-fast) var(--ease);
}

.msg-thought[open] .msg-thought-chev {
  transform: rotate(180deg);
}

.msg-thought-body {
  margin-top: 4px;
  padding: 2px 0 2px 10px;
  border-left: 2px solid var(--line-soft);
  max-height: 220px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

.msg-thought-body p {
  margin: 0 0 6px;
  color: var(--ink-3);
  font-size: 11.5px;
  line-height: 1.5;
  overflow-wrap: anywhere;
}

.msg-thought-body p:last-child {
  margin-bottom: 0;
}

.message-attachment-copy small {
  display: block;
  color: var(--ink-3);
  margin-top: 1px;
}

/* === Media entries === */
/* Owner: media must look clean - NO frame around the picture and NO cropping.
   .bubble.assistant resets padding/radius with higher specificity — match it,
   then drop the border/padding/card background this bubble used to have, so the
   generated picture sits in the chat by itself with only rounded corners.
   No radius + no overflow:hidden on the BOX: both clipped the meta line
   («Изображение · model») in the top-left corner — owner report 2026-09-25.
   The picture rounds its OWN corners (14px), that is enough. */
.bubble.assistant.media-entry {
  max-width: 100%;
  background: transparent !important;
  border: 0;
  border-radius: 0;
  padding: 0;
  overflow: visible;
}

/* width:auto is the point: the box follows the image's own aspect ratio instead
   of being stretched to the bubble width and letterboxed with grey bands
   (that read as "обрезается"). max-height keeps tall images inside the screen. */
.media-entry img {
  display: block;
  width: auto;
  max-width: 100%;
  max-height: min(420px, 62vh);
  margin: 0 auto;
  border-radius: 14px;
  object-fit: contain;
  background: transparent;
}

/* Video has no intrinsic box until metadata loads, so it keeps the full width and
   letterboxes TRANSPARENTLY (no grey panel) - preload="metadata" paints frame 1. */
.media-entry video {
  display: block;
  width: 100%;
  max-height: min(420px, 62vh);
  border-radius: 14px;
  object-fit: contain;
  background: transparent;
}

.media-entry a {
  display: inline-block;
  margin-top: 9px;
  color: var(--accent);
  font-size: 12px;
}

/* ============================================================
   GENERATIVE PENDING STATE — ChatGPT-style dot-matrix shimmer
   (owner reference screens, 2026-09-25). NO card, NO frame, NO
   skeleton strips: a quiet grid of tiny ACCENT-colored dots on the
   chat background; TWO soft light orbs breathe across it — they fly
   together, swell (вдох), collide in a bright flash and scatter
   (выдох); the dots under the light GROW + BRIGHTEN (a dot mask
   lights them up). One muted phase line floats ABOVE the
   field; no dots/timer footer (the reference has none). The grid is
   FINE (7px — hi-res halftone) and each corner loses its corner dot
   to a bg-colored disc, so the silhouette reads softly rounded
   (owner 2026-09-25). Colors are
   the active theme's accent — light: green ink, dark: warm gold;
   tier accents ride on --accent automatically. Fresh results still
   materialize with the blur-up focus + color settle.
   ============================================================ */
.media-gen {
  --gen-size: min(88%, 330px);
  --gen-aspect: 1 / 1;
  --gen-grid: 7px;
  /* Field dots (dim base) vs orb light (bright accent wash). */
  --gen-dot: color-mix(in srgb, var(--accent) 24%, transparent);
  --gen-wave: var(--accent);
  width: var(--gen-size);
  margin: 4px 2px 6px;
}

[data-theme-effective="dark"] .media-gen {
  --gen-dot: color-mix(in srgb, var(--accent) 32%, transparent);
}

/* Phase line ABOVE the field, muted, exactly like the reference. */
.media-gen-phase {
  margin: 0 2px 10px;
  font-size: 13px;
  color: var(--ink-3);
  animation: phaseIn var(--dur-normal) var(--ease, ease);
}

/* Soft appearance right after the request (opacity/blur/scale, ease-out).
   The class is age-gated in JS (<10s) so poll-tick re-renders of an older
   pending row never replay the intro. */
.media-gen.media-gen-in {
  animation: mediaGenIn 0.45s ease-out both;
}

@keyframes mediaGenIn {
  from { opacity: 0; filter: blur(8px); transform: scale(0.98); }
  to { opacity: 1; filter: blur(0); transform: scale(1); }
}

/* The dim dot field: static paint, zero per-frame work. In-flow block so
   it sits below the phase line; aspect mirrors the coming result. */
.media-gen-canvas {
  position: relative;
  width: 100%;
  aspect-ratio: var(--gen-aspect);
  overflow: hidden;
  background-image: radial-gradient(circle, var(--gen-dot) 0.9px, transparent 1.1px);
  background-size: var(--gen-grid) var(--gen-grid);
  background-position: center;
}

/* The breathing lights: ONE smooth glow per orb, two orbs on one layer
   (::after is taken by the corner shave). Owner 2026-09-26: «мягкий,
   яркий, плавный свет; НЕ разделяй шарик на два» — so each orb is a
   single wide wash with a long gentle falloff (bright core, no hard
   rim, no drifting sheen), rendered THROUGH the large-dot mask: the
   dots GROW under the light (reference). Only background-position/size
   animate (compositor-friendly); overlap at center doubles the light. */
.media-gen-canvas::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image:
    radial-gradient(circle closest-side, var(--gen-wave) 0%, color-mix(in srgb, var(--gen-wave) 72%, transparent) 24%, color-mix(in srgb, var(--gen-wave) 34%, transparent) 52%, color-mix(in srgb, var(--gen-wave) 10%, transparent) 76%, transparent 100%),
    radial-gradient(circle closest-side, var(--gen-wave) 0%, color-mix(in srgb, var(--gen-wave) 72%, transparent) 24%, color-mix(in srgb, var(--gen-wave) 34%, transparent) 52%, color-mix(in srgb, var(--gen-wave) 10%, transparent) 76%, transparent 100%);
  background-size: 185px 158px, 175px 150px;
  background-repeat: no-repeat;
  background-position: 12% 22%, 88% 76%;
  -webkit-mask-image: radial-gradient(circle, #000 1.9px, transparent 2.9px);
  -webkit-mask-size: var(--gen-grid) var(--gen-grid);
  -webkit-mask-position: center;
  mask-image: radial-gradient(circle, #000 1.9px, transparent 2.9px);
  mask-size: var(--gen-grid) var(--gen-grid);
  mask-position: center;
  animation: genOrbs 18s ease-in-out infinite;
}

/* Organic choreography (owner: «большие шары долетают до стенок и углов,
   летают без закономерности, стукаются и отталкиваются», 2026-09-26).
   Two layers = orb A, orb B, ONE smooth glow each (no sheen split —
   owner: «вырви глазно»). They wander irregular anti-phased paths
   touching walls/corners, graze at 38%, collide in a big flash at 50%
   and rebound crossed; 100% mirrors 0% (seamless loop). */
@keyframes genOrbs {
  0%   { background-position: 12% 22%, 88% 76%; background-size: 185px 158px, 175px 150px; }
  7%   { background-position: 24% 40%, 78% 62%; background-size: 170px 145px, 160px 136px; }
  14%  { background-position: 40% 20%, 62% 82%; background-size: 195px 165px, 185px 158px; }
  21%  { background-position: 60% 34%, 44% 66%; background-size: 165px 140px, 168px 142px; }
  28%  { background-position: 82% 26%, 20% 78%; background-size: 182px 154px, 176px 148px; }
  34%  { background-position: 70% 52%, 34% 50%; background-size: 170px 143px, 163px 138px; }
  38%  { background-position: 57% 47%, 45% 54%; background-size: 185px 155px, 178px 150px; }
  44%  { background-position: 52% 48%, 49% 52%; background-size: 200px 168px, 195px 164px; }
  50%  { background-position: 50% 49%, 50% 51%; background-size: 260px 220px, 250px 212px; }
  56%  { background-position: 40% 60%, 62% 42%; background-size: 192px 162px, 187px 158px; }
  63%  { background-position: 20% 40%, 82% 60%; background-size: 178px 150px, 172px 145px; }
  70%  { background-position: 8% 55%, 93% 44%; background-size: 186px 158px, 182px 154px; }
  77%  { background-position: 22% 82%, 78% 16%; background-size: 172px 144px, 166px 140px; }
  84%  { background-position: 45% 68%, 55% 34%; background-size: 163px 137px, 158px 134px; }
  91%  { background-position: 26% 36%, 74% 64%; background-size: 182px 154px, 176px 147px; }
  100% { background-position: 12% 22%, 88% 76%; background-size: 185px 158px, 175px 150px; }
}

/* Rounded silhouette (owner: «убери по одной точке у углов — должно
   слегка закругление создаваться», 2026-09-25): a bg-colored disc in
   each corner shaves the corner dot(s) off the dim field AND the
   lights (::after paints above ::before), so the square reads softly rounded.
   Pure static paint — no animation, survives reduced-motion. The 6px
   radius always swallows the nearest grid dot (centered 7px grid puts
   it within ~5px of the corner) but never reaches the next one. */
.media-gen-canvas::after {
  content: '';
  position: absolute;
  inset: 0;
  background-image:
    radial-gradient(circle at 0 0, var(--bg) 6px, transparent 6.8px),
    radial-gradient(circle at 100% 0, var(--bg) 6px, transparent 6.8px),
    radial-gradient(circle at 0 100%, var(--bg) 6px, transparent 6.8px),
    radial-gradient(circle at 100% 100%, var(--bg) 6px, transparent 6.8px);
}

/* Final reveal: the picture MATERIALIZES FROM THE DOT MATRIX (owner:
   «готовая картинка расходится из точек», 2026-09-25). The result renders
   under a one-shot veil — a field of bg-colored dots with holes, so the
   picture peeks through like a halftone preview; the tiles then GROW
   (dots disperse outward) and the veil fades, handing over to the blur-up
   focus settle. Pure CSS, one 0.8s run, then the veil is inert (opacity 0). */
.media-gen-reveal {
  position: relative;
  border-radius: 14px;
  overflow: hidden;
}

.media-gen-reveal > img,
.media-gen-reveal > video {
  display: block;
  width: 100%;
}

.media-gen-veil {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: var(--bg);
  -webkit-mask-image: radial-gradient(circle, #000 1px, transparent 1.6px);
  -webkit-mask-size: 7px 7px;
  -webkit-mask-position: center;
  mask-image: radial-gradient(circle, #000 1px, transparent 1.6px);
  mask-size: 7px 7px;
  mask-position: center;
  animation: genDotReveal 0.9s var(--ease, ease) both;
}

/* Hold the halftone peek briefly, THEN disperse (owner: «в последнюю
   секунду») — the picture first reads as dots, then the dots fly apart. */
@keyframes genDotReveal {
  0% { opacity: 1; -webkit-mask-size: 7px 7px; mask-size: 7px 7px; }
  35% { opacity: 1; -webkit-mask-size: 9px 9px; mask-size: 9px 9px; }
  100% { opacity: 0; -webkit-mask-size: 34px 34px; mask-size: 34px 34px; }
}

/* And the picture itself still snaps into focus underneath (~0.7s). */
.media-entry img.media-materialize,
.media-entry video.media-materialize {
  animation: mediaMaterialize 0.7s var(--ease, ease) both;
}

@keyframes mediaMaterialize {
  0% { opacity: 0; filter: blur(20px) saturate(0.75) brightness(1.05); transform: scale(0.98); }
  55% { opacity: 1; }
  100% { opacity: 1; filter: blur(0) saturate(1) brightness(1); transform: scale(1); }
}

/* Reduced motion: a still, dim dot field — the wave is decoration and
   hidden rather than frozen mid-frame; the phase line keeps working
   (UI, not decoration). */
@media (prefers-reduced-motion: reduce) {
  .media-gen,
  .media-gen.media-gen-in {
    animation: none;
    box-shadow: none;
  }

  .media-gen-canvas::before {
    animation: none;
    opacity: 0;
  }

  .media-gen-veil {
    animation: none;
    opacity: 0;
  }

  .media-entry img.media-materialize,
  .media-entry video.media-materialize {
    animation: none;
    opacity: 1;
    filter: none;
  }
}

.media-pending {
  color: var(--accent-ink);
}

.media-error {
  color: var(--danger);
}

.media-caption {
  margin-bottom: 8px;
  color: var(--accent-ink);
  font-size: 12px;
  font-weight: 700;
}

.media-actions {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-top: 8px;
}

/* Media action row: EXACT chat (.msg-copy/.msg-share) visual language —
   26×26 quiet glyphs, no frames, accent on press. The name lives in
   title/aria-label. (.media-actions-media re-added below for old builds.) */
.media-ico {
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--ink-3);
  display: grid;
  place-items: center;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}

.media-ico svg {
  width: 14px;
  height: 14px;
  display: block;
}

.media-ico:hover,
.media-ico:active {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent-bg) 60%, transparent);
}

.media-ico.is-busy {
  color: var(--ink-3);
  background: transparent;
  pointer-events: none;
}

.media-ico .media-ico-spin {
  animation: mediaIcoSpin 0.9s linear infinite;
}

@keyframes mediaIcoSpin {
  to {
    transform: rotate(360deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .media-ico .media-ico-spin {
    animation-duration: 2.4s;
  }
}

/* Compact viewports: touch rows get slightly larger targets (Apple HIG 44pt
   would stack five buttons; 34px keeps one row) with the same quiet look. */
@media (max-width: 380px) {
  .media-ico {
    width: 34px;
    height: 34px;
  }

  .media-ico svg {
    width: 16px;
    height: 16px;
  }
}

/* Legacy pill classes (.media-edit etc.) are gone — the old colored-pill look
   is intentionally dead: no served shell loads it anymore (v1 HTML is routed
   to v2), and the new renderer emits .media-ico buttons. */

/* === Media viewer (fullscreen image overlay) ===
   Was completely unstyled — bare buttons under the picture. Now a real
   overlay: dimmed backdrop, centered image, bottom bar with chat-style
   (.msg-copy/.msg-share) icon buttons. z-index fits the app scale
   (model popup 650/700, toast 800). */
.media-viewer {
  position: fixed;
  inset: 0;
  z-index: 700;
  background: rgba(0, 0, 0, 0.82);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: calc(20px + var(--top, 0px)) 16px calc(18px + var(--bottom, 0px));
}

.media-viewer img {
  display: block;
  min-width: 0;
  min-height: 0;
  max-width: 100%;
  max-height: min(70dvh, 560px);
  border-radius: 14px;
  object-fit: contain;
  box-shadow: 0 12px 48px rgba(0, 0, 0, 0.45);
  animation: mediaViewerIn var(--dur-normal) var(--ease, ease);
}

@keyframes mediaViewerIn {
  from {
    opacity: 0;
    transform: scale(0.96);
  }

  to {
    opacity: 1;
    transform: scale(1);
  }
}

.media-viewer-bar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-top: 14px;
}

.media-viewer-btn {
  position: relative;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--ink-3);
  display: grid;
  place-items: center;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
  overflow: hidden;
  /* Text labels stay for AT / tooltip only: the visible face is the mask glyph. */
  text-indent: -9999px;
  font-size: 14px;
  line-height: 1;
}

.media-viewer-btn::before {
  content: '';
  position: absolute;
  width: 14px;
  height: 14px;
  background-color: currentColor;
  -webkit-mask: var(--glyph) center/contain no-repeat;
  mask: var(--glyph) center/contain no-repeat;
}

#mediaViewerDownload {
  --glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 3v12'/%3E%3Cpath d='M7 10l5 5 5-5'/%3E%3Cpath d='M5 21h14'/%3E%3C/svg%3E");
}

/* P3-1: offline PDF fallback — печать слайда средствами клиента, когда
   серверный Chromium недоступен. Кнопка видна только в doc-режиме вьюера. */
#mediaViewerPrint {
  --glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9V3h12v6'/%3E%3Cpath d='M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2'/%3E%3Cpath d='M6 14h12v7H6z'/%3E%3C/svg%3E");
}

#mediaViewerPrint.doc-viewer-print.hide {
  display: none;
}

#mediaViewerShare {
  --glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 3v12'/%3E%3Cpath d='M7 8l5-5 5 5'/%3E%3Cpath d='M5 13v6a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-6'/%3E%3C/svg%3E");
}

#mediaViewerClose {
  --glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round'%3E%3Cpath d='M18 6L6 18'/%3E%3Cpath d='M6 6l12 12'/%3E%3C/svg%3E");
}

.media-viewer-btn:hover,
.media-viewer-btn:active {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent-bg) 60%, transparent);
}

.media-viewer-btn.is-busy {
  pointer-events: none;
}

.media-viewer-btn.is-busy::before {
  display: none;
}

.media-viewer-btn .media-ico-spin {
  width: 14px;
  height: 14px;
  animation: mediaIcoSpin 0.9s linear infinite;
}

/* Light theme: the glyphs sit on a light dim backdrop — same tokens keep
   them visible because color-mix rides the shell variables. */

/* === Media mode bar (image/video) — hidden unless media models present === */
.media-mode-bar {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--line);
  background: var(--bg);
}

.media-mode {
  min-width: 88px;
  min-height: 32px;
  border: 0;
  border-radius: 9px;
  background: transparent;
  color: var(--ink-3);
  font-family: var(--font-ui);
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
}

.media-mode.on {
  background: var(--accent-bg);
  border-color: var(--accent-line);
  color: var(--accent-ink);
}

.media-mode:disabled {
  cursor: not-allowed;
  opacity: 0.45;
}

.media-note {
  padding: 9px 12px;
  border-bottom: 1px solid var(--line);
  color: var(--ink-3);
  font-size: 11px;
}

/* === Quick Actions (Веб-поиск / Добавить файл) — vertical stack like mockup === */
/* Floating overlay at the bottom of .chat (reparented there at init): the
   thread keeps its full height behind it, the strip itself is transparent and
   tap-through - only the solid pills cover the text and take clicks (owner:
   «меню не должно сплошной панелью перекрывать текст, кнопки пусть»). */
.quick-actions {
  position: absolute;
  left: 14px;
  right: auto;
  /* Devin-parity: якорь к строке [+], а не к верхней кромке composer'а —
     нижний край меню «заходит» на поле ввода (toolbar 44px + padding 8px +
     зазор 4px), как в Devin Desktop, где попап перекрывает composer. */
  bottom: 56px;
  z-index: 9;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0;
  width: min(236px, calc(100% - 28px));
  max-height: max(80px, calc(var(--vh, 100dvh) - var(--top, 0px) - var(--bottom, 0px) - 120px));
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;
  padding: 6px;
  pointer-events: auto;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: 0 14px 36px rgba(0, 0, 0, 0.34), 0 2px 10px rgba(0, 0, 0, 0.16);
}

.quick-actions:not(.hide) {
  animation: qaIn var(--dur-normal) ease-out;
}

@keyframes qaIn {
  from {
    opacity: 0;
    transform: translateY(10px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.quick-action {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 34px;
  padding: 0 10px;
  border-radius: 8px;
  border: 0;
  background: transparent;
  cursor: pointer;
  font-family: var(--font-ui);
  font-size: 13.5px;
  font-weight: 400;
  color: var(--ink);
  flex-shrink: 0;
  text-align: left;
}

.quick-action:hover {
  background: var(--soft);
}

/* Devin-style: иконки монохромные (цвет текста с лёгким приглушением),
   тонкий штрих — не акцентные */
.quick-action svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  color: var(--ink);
  stroke: var(--ink);
  stroke-width: 1.6;
  opacity: 0.8;
}

.quick-action.hide {
  display: none !important;
}

/* web toggle on state */
.quick-action.on {
  background: var(--accent-bg);
  border-color: var(--accent-line);
  color: var(--accent-ink);
}

.quick-action.on svg {
  color: var(--accent-ink);
  stroke: var(--accent-ink);
  opacity: 1;
}

/* === Multi-step task plan (composer-flush layer, like .deck-progress) === */
/* The card lives INSIDE .composer right above .composer-box: no bottom
   border, square bottom corners — its side borders continue into the box
   frame below, so an open to-do reads as one outline around the composer. */
.task-card {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: 10px 12px 11px;
  border: 1px solid var(--line);
  border-bottom: none;
  border-radius: 16px 16px 0 0;
  background:
    linear-gradient(rgba(127, 127, 127, 0.12), rgba(127, 127, 127, 0.12)),
    color-mix(in srgb, var(--card) 94%, transparent);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  color: var(--ink);
  max-height: min(38vh, 320px);
  overflow: hidden;
  opacity: 1;
  transform: translateY(0);
  visibility: visible;
  pointer-events: auto;
  transition:
    max-height .28s ease,
    opacity .24s ease,
    transform .28s ease,
    padding .28s ease,
    visibility 0s;
}

.task-card.hide {
  max-height: 0;
  opacity: 0;
  padding-top: 0;
  padding-bottom: 0;
  border-width: 0;
  transform: translateY(12px);
  visibility: hidden;
  pointer-events: none;
  transition:
    max-height .28s ease,
    opacity .24s ease,
    transform .28s ease,
    padding .28s ease,
    visibility 0s linear .28s;
}

/* An open flush layer above the box turns the pair into one frame: the box
   drops its top corners so the layer's side borders run on uninterrupted.
   ~ (not +) keeps working when a hidden sibling sits in between. */
.deck-progress:not(.hide) ~ .composer-box,
.task-card:not(.hide) ~ .composer-box {
  border-radius: 0 0 18px 18px;
}

/* Two stacked layers: the middle one stays square on both ends. */
.deck-progress:not(.hide) ~ .task-card:not(.hide) {
  border-radius: 0;
}

/* Collapsed state keeps the header and the completed/total count visible. */
.task-card.collapsed .task-card-progress,
.task-card.collapsed .task-card-steps {
  display: none;
}

.task-card-head {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 18px;
}

.task-card-kicker {
  font: 650 12px/1.2 var(--font-ui);
  letter-spacing: 0.02em;
}

.task-card-count {
  color: var(--ink-3);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}

.task-card-toggle {
  margin-left: auto;
  width: 24px;
  height: 24px;
  padding: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ink-3);
  display: grid;
  place-items: center;
}

.task-card-toggle svg {
  width: 14px;
  height: 14px;
  transition: transform var(--dur-fast) var(--ease);
}

/* The open card shows the small down-chevron to collapse. */
.task-card.collapsed .task-card-toggle svg {
  transform: rotate(180deg);
}

.task-card-toggle:hover,
.task-card-toggle:focus-visible {
  background: var(--soft);
  color: var(--ink);
}

.task-card-progress {
  height: 2px;
  border-radius: 2px;
  background: var(--line);
  overflow: hidden;
}

.task-card-progress > span {
  display: block;
  width: 0;
  height: 100%;
  background: var(--accent);
  transition: width 0.25s var(--ease);
}

.task-card-steps {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 6px;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.task-step {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: 12.5px;
  line-height: 1.35;
  color: var(--ink-3);
}

.task-step-mark {
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
  margin-top: 1px;
  border: 1.5px solid var(--line);
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 9px;
  font-weight: 700;
  color: var(--accent-ink);
}

.task-step.pending {
  color: var(--ink-3);
}

.task-step.active {
  color: var(--ink);
}

.task-step.active .task-step-mark {
  border-color: var(--accent);
  animation: taskStepPulse 1.5s var(--ease) infinite;
}

@keyframes taskStepPulse {
  0%,
  100% {
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 14%, transparent);
  }
  50% {
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 22%, transparent);
  }
}

.task-step.done {
  color: var(--ink-2);
}

.task-step.done .task-step-mark {
  background: var(--accent);
  border-color: var(--accent);
}

.task-step.done .task-step-mark::after {
  content: "✓";
}

.task-step.done .task-step-text {
  text-decoration: line-through;
  text-decoration-color: var(--line);
}

@media (max-height: 560px) {
  .task-card {
    max-height: 34vh;
  }
}

@media (prefers-reduced-motion: reduce) {
  .task-card,
  .task-card:not(.hide) {
    animation: none;
    transition: none;
  }

  .task-card-toggle svg,
  .task-step.active .task-step-mark {
    animation: none;
    transition: none;
  }
}

/* === Composer === */
.composer {
  padding: 0 14px 8px;
  /* Оверлей у нижней кромки .chat — как .quick-actions выше: лента сохраняет
     полную высоту и уезжает ПОД поле ввода (владелец 2026-10-11: «текст должен
     проходить за composer до низу»), а не упирается в его верхнюю кромку. Цена
     оверлея — нижний padding ленты на высоту composer (--composer-h). */
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 6;
}

/* БЛЮРА В ЗОНЕ COMPOSER'А НЕТ — ни на окне, ни под ним (владелец 2026-10-11,
   после первого захода: «убери этот чёртов блюр на composer'е, текст должен
   уходить под него, а не обрезаться за composer'ом»). Слой над текстом с
   backdrop-filter выедал фон начисто: то, что проходило за окном, не размывалось,
   а исчезало — и кромка окна читалась линией обрезки. Гасит текст ТОЛЬКО рампа
   .chat::after, и она же создаёт фейд, так что текст видно уходящим под окно. */


/* Physical screen edges, not the Telegram content-safe toolbar region.
   The scrollport bleeds behind chrome, while padding keeps the first/last
   message reachable. These sibling overlays affect only passing app content,
   NEVER native time/icons. Small blur + theme-aware 14% scrim; a gradient
   masks the effect (not the feed), so there is no extra text clipping.
   Absolute positioning follows the app/keyboard height and view transitions;
   fixed positioning could attach to an animated ancestor or miss keyboard top. */
#chat .chat::before,
#chat .chat::after {
  content: '';
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  width: min(100vw, 480px);
  pointer-events: none;
  background: color-mix(in srgb, var(--bg) 14%, transparent);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}

#chat .chat::before {
  top: calc(-1 * (var(--top, 0px) + 4px) - 8px);
  height: clamp(28px, calc(max(var(--tg-safe-area-inset-top, 0px), env(safe-area-inset-top, 0px)) + 24px), 84px);
  z-index: 5;
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, rgba(0,0,0,.65) 35%, rgba(0,0,0,.18) 70%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 0%, rgba(0,0,0,.65) 35%, rgba(0,0,0,.18) 70%, transparent 100%);
}

#chat .chat::after {
  bottom: calc(-1 * var(--app-pad-b, 16px) - 8px);
  height: clamp(28px, calc(max(var(--tg-safe-area-inset-bottom, 0px), env(safe-area-inset-bottom, 0px)) + 24px), 64px);
  z-index: 5;
  -webkit-mask-image: linear-gradient(to top, #000 0%, rgba(0,0,0,.65) 35%, rgba(0,0,0,.18) 70%, transparent 100%);
  mask-image: linear-gradient(to top, #000 0%, rgba(0,0,0,.65) 35%, rgba(0,0,0,.18) 70%, transparent 100%);
}

/* Selection copy button (floating above the highlighted text) */
.selection-copy-btn {
  position: fixed;
  z-index: 850;
  padding: 9px 16px;
  border-radius: 999px;
  border: 0;
  background: var(--accent);
  color: var(--accent-ink);
  font: 700 13px/1 var(--font-ui);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.22);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  animation: selCopyIn var(--dur-fast, 120ms) var(--ease);
}

@keyframes selCopyIn {
  from {
    opacity: 0;
    transform: translateY(4px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Messages always allow text selection */
.messages,
.messages * {
  -webkit-user-select: text;
  user-select: text;
  -webkit-touch-callout: default;
}

/* Load older messages (ТЗ 114): windowing for long chats */
.load-older-btn {
  align-self: center;
  margin: 0 0 8px;
  padding: 7px 16px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--card);
  color: var(--ink-2);
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
}

.load-older-btn:active {
  transform: scale(0.97);
}

/* === Composer box: input row on top, toolbar below === */
.scroll-bottom-btn {
  position: absolute;
  top: -46px;
  right: 22px;
  z-index: 6;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 0;
  background: var(--card);
  color: var(--ink);
  display: grid;
  place-items: center;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.14);
  cursor: pointer;
  opacity: 1;
  transform: scale(1);
  transition: opacity var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease), visibility 0s;
}

.scroll-bottom-btn svg {
  width: 18px;
  height: 18px;
}

/* Floats out with a fade+shrink instead of vanishing on display:none;
   stays mounted-but-inert while hiding (absolute — no layout shift). */
.scroll-bottom-btn.hide {
  display: grid !important;
  opacity: 0;
  transform: scale(.6);
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease), visibility 0s linear var(--dur-fast);
}

.scroll-bottom-btn:active {
  transform: scale(.86);
}

.composer-box {
  border: 1px solid var(--line);
  border-radius: 18px;
  background: color-mix(in srgb, var(--card) 97%, transparent);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  transition: border-color var(--dur-normal) var(--ease), box-shadow var(--dur-normal) var(--ease);
}

.composer-box:focus-within {
  border-color: color-mix(in srgb, var(--accent) 55%, var(--line));
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 10%, transparent);
}

.composer-input {
  padding: 7px 8px 0;
}

.composer-input textarea {
  display: block;
  width: 100%;
  box-sizing: border-box;
  border: 0;
  background: transparent;
  color: var(--ink);
  /* 16px prevents iOS auto-zoom on focus (keyboard zoom hides the toolbar) */
  font-size: 16px;
  line-height: 1.4;
  resize: none;
  outline: 0;
  min-height: 30px;
  max-height: 220px;
  /* Native autogrow (composer.js skips its per-keystroke measure when
     supported): content-sized height, same 220px/28dvh ceiling the JS
     path computed as min(220, innerHeight*0.28). */
  field-sizing: content;
  max-height: min(220px, 28dvh);
  padding: 3px 0;
  margin: 0;
  scrollbar-width: none;
  -ms-overflow-style: none;
  transition: opacity var(--dur-normal) var(--ease);
}

.composer-input textarea::-webkit-scrollbar {
  display: none;
}

.composer-input textarea::placeholder {
  color: var(--ink-3);
}

/* The field is narrow next to the model pill: a long placeholder used to
   wrap onto a hidden second line, leaving a lone first word («Опишите»).
   While empty, keep it on one line and fade the edge instead. */
.composer-input textarea:placeholder-shown {
  white-space: nowrap;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 22px), transparent);
  mask-image: linear-gradient(90deg, #000 calc(100% - 22px), transparent);
}

.composer-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 44px;
  padding: 4px 8px 6px;
}

.composer-toolbar-right {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
  /* D5: the toolbar gained a third child (the improve star), and space-between
     would park it in the middle. The auto margin keeps the group at the right
     end no matter how many children sit on the left. */
  margin-left: auto;
}

/* Improve star in the toolbar (D5): same 28px round control, different slot —
   no extra row grows when a draft appears, so the input box height is stable. */
.composer-toolbar > .improve-star {
  margin-left: 8px;
}

/* Model selector — plain text + arrow (like mockup "name ▾") */
.model-trigger {
  display: flex;
  align-items: center;
  gap: 6px;
  background: transparent;
  color: var(--ink-3);
  font-size: 13px;
  font-weight: 400;
  white-space: nowrap;
  padding: 6px 4px;
  min-width: 0;
  max-width: 130px;
}

.model-trigger>span:first-child {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.model-trigger-arrow {
  display: flex;
  flex-shrink: 0;
  color: var(--ink-3);
  transition: transform var(--dur-normal) var(--ease);
}

.model-trigger-arrow svg {
  width: 14px;
  height: 14px;
  stroke: var(--ink-3);
}

.model-trigger.open .model-trigger-arrow {
  transform: rotate(180deg);
}

/* Attach button (files module injects #fileAttachPick here) */
.composer-actions {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
}

.image-attach {
  width: 31px;
  height: 31px;
  border-radius: 50%;
  border: 1px solid var(--attach-line);
  background: var(--attach-bg);
  color: var(--chip-ink);
  flex-shrink: 0;
  padding: 0;
  display: grid;
  place-items: center;
  cursor: pointer;
}

/* Replace the module's 📎/text with a proper lucide plus icon (mask follows color) */
.image-attach span {
  display: none !important;
}

.image-attach::before {
  content: '';
  width: 16px;
  height: 16px;
  background-color: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round'%3E%3Cpath d='M12 5v14M5 12h14'/%3E%3C/svg%3E") center/contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round'%3E%3Cpath d='M12 5v14M5 12h14'/%3E%3C/svg%3E") center/contain no-repeat;
}

/* Send button — circular arrow-up, bottom-right */
.send-btn {
  width: 31px;
  height: 31px;
  border-radius: 50%;
  border: 0;
  background: var(--send-bg);
  display: grid;
  place-items: center;
  color: var(--send-ink);
  padding: 0;
  flex-shrink: 0;
  transition: background var(--dur-fast) var(--ease), opacity var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}

.send-btn:active {
  transform: scale(.86);
}

.send-btn svg {
  width: 22px;
  height: 22px;
  display: block;
  fill: var(--send-ink);
}

.send-btn:disabled {
  opacity: 0.35;
}

/* Stop generation (ТЗ 35): ■ replaces ↑ while answering */
.send-btn.busy {
  background: var(--accent);
}

.send-stop {
  display: block;
  width: 11px;
  height: 11px;
  border-radius: 2px;
  background: var(--send-ink);
}

/* Streaming cursor (ТЗ 34): blinking ▍ while generating */
/* Живой хвост стрима — плоский текст до оседания абзаца (см. _twPaint). */
.message-content .tw-raw {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.stream-cursor {
  display: inline-block;
  width: 2px;
  height: 1.05em;
  margin-left: 1px;
  background: var(--accent);
  vertical-align: text-bottom;
  animation: cursorBlink 0.6s ease-in-out infinite;
}

/* Мягкое появление свежего текста стрима (2026-10-15, «как ChatGPT»):
   каждые только что напечатанные ТОП-ноды самого элемента (не весь контент)
   проходят короткую антиалиасную выдержку — fade-in + микро-сдвиг вверх.
   Только opacity/transform, без reflow; класс снимается по animationend,
   так что постоянный DOM чистый, и осевшие блоки никогда не переанимируются.
   reduce-motion обнуляет длительность (текст появится мгновенно). */
.tw-fade {
  animation: twFade var(--dur-normal, 240ms) var(--ease) both;
}
@keyframes twFade {
  from {
    opacity: 0.1;
    transform: translateY(2px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Посимвольная печать стрима (2026-11-11): каждая свежая буква — span.tw-l в
   .tw-live (выделенный узел, живёт вне markdown-пересборки хвоста): fade-in +
   slide-up + сход blur. Сдвиг — через top на position:relative, а не transform:
   так буквы остаются обычным inline-текстом (не ломается ни кернинг, ни
   перенос слов — inline-block спаны давали бы и то, и другое). Через ~0.5с
   буквы складываются обратно в markdown-рендер (см. _twPaint), DOM чистый. */
/* Живой край — tight tracking по спеке (переносится на textNode-ы тоже,
   при промоушне трекинг уходит вместе с .tw-live). */
.tw-live {
  letter-spacing: -0.015em;
}
.tw-l {
  position: relative;
  animation: twLetter 340ms cubic-bezier(.2, .7, .3, 1) both;
}
/* Пока идёт посимвольная печать, сами буквы отмечают позицию набора — старую
   бликующую каретку прячем (она всегда стоит сразу после .tw-live в хосте).
   Когда живые буквы сложатся в markdown, .tw-live уходит — каретка
   возвращается до _twFlush. */
.tw-live + .stream-cursor { display: none; }
@keyframes twLetter {
  from {
    opacity: 0;
    top: .45em;
    filter: blur(5px);
    color: color-mix(in srgb, currentColor 30%, var(--accent));
  }
  to {
    opacity: 1;
    top: 0;
    filter: blur(0);
    color: inherit;
  }
}

@keyframes cursorBlink {

  0%,
  100% {
    opacity: 1;
  }

  50% {
    opacity: 0;
  }
}

/* Plus toggle — toolbar left, reveals quick actions */
.attach-toggle {
  width: 31px;
  height: 31px;
  border-radius: 50%;
  border: 0;
  background: var(--attach-bg);
  color: var(--chip-ink);
  display: grid;
  place-items: center;
  cursor: pointer;
  padding: 0;
  flex-shrink: 0;
}

.attach-toggle svg {
  width: 20px;
  height: 20px;
  display: block;
  fill: var(--chip-ink);
}

.attach-toggle.on svg {
  fill: var(--accent-ink);
}

.attach-toggle.on {
  background: var(--accent-bg);
  border-color: var(--accent-line);
  color: var(--accent-ink);
}

/* Composer extra tools (quota / improve) — always hidden, kept for JS compat */
.composer-tools {
  display: none;
}

.quota {
  font-size: 12px;
  color: var(--ink-3);
  flex-shrink: 0;
}

.improve-btn {
  padding: 7px 11px;
  border-radius: 11px;
  border: 0;
  background: var(--accent-bg);
  color: var(--accent-ink);
  font-size: 13px;
  font-weight: 650;
  cursor: pointer;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.improve-btn::before {
  content: '';
  width: 14px;
  height: 14px;
  background-color: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='black'%3E%3Cpath d='M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z'/%3E%3C/svg%3E") center/contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='black'%3E%3Cpath d='M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z'/%3E%3C/svg%3E") center/contain no-repeat;
}

.improve-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.improve-loader {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  vertical-align: middle;
  margin-right: 5px;
}

.improve-loader i {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent);
  animation: miniapp-wave 1.2s ease-in-out infinite;
}

.improve-loader i:nth-child(2) {
  animation-delay: var(--dur-fast);
}

.improve-loader i:nth-child(3) {
  animation-delay: var(--dur-slow);
}

/* === Model Picker Popup (opens above toolbar, right-aligned to input box) === */
/* === Model bottom sheet (ТЗ 12-15): slide-up panel + backdrop + drag handle === */
.model-backdrop {
  position: fixed;
  inset: 0;
  z-index: 650;
  background: rgba(0, 0, 0, .65);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-slow) var(--ease);
}

.model-backdrop:not(.hide) {
  opacity: 1;
  pointer-events: auto;
}

.model-backdrop.hide {
  display: block !important;
  opacity: 0;
  pointer-events: none;
}

.model-popup {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 700;
  width: 100%;
  max-width: 480px;
  margin: 0 auto;
  background: var(--picker-bg);
  border: 1px solid var(--picker-line);
  border-bottom: 0;
  border-radius: 28px 28px 0 0;
  box-shadow: var(--picker-shadow);
  padding: 0 0 calc(12px + var(--bottom, 0px));
  max-height: min(420px, 62dvh);
  /* Flex column + clipped overflow: header and search stay pinned, only
     .model-popup-list scrolls. (.hide forces display:block while dismissed —
     harmless, the sheet is off-screen then.) */
  display: flex;
  flex-direction: column;
  overflow: hidden;
  transform: translateY(100%);
  visibility: hidden;
  transition: transform var(--dur-slow) var(--ease-sheet), visibility var(--dur-slow) var(--ease);
}

.model-popup:not(.hide) {
  transform: translateY(0);
  visibility: visible;
}

.model-popup.hide {
  display: block !important;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(100%);
}

/* Picker-only motion pass (20261112g): the shared .model-popup slide uses
   --ease-sheet, which covers ~90% of the distance in the first third of the
   duration — on the model list that reads as a snap, not a glide. Give ONLY
   #modelPopup/#modelBackdrop Kamil's plan-card curve (0.38s
   cubic-bezier(.32,.72,0,1), same as .plan-more) plus a soft content fade so
   the sheet settles while the rows materialise. Sibling sheets sharing
   .model-popup/.model-backdrop (#docPopup, #mediaParamsPopup, the documents
   sheets) keep the original timing untouched. */
#modelPopup.model-popup {
  transition: transform 0.38s cubic-bezier(0.32, 0.72, 0, 1), visibility 0.38s var(--ease);
}

#modelBackdrop.model-backdrop {
  transition: opacity 0.38s var(--ease);
}

#modelPopup:not(.hide) .model-popup-header,
#modelPopup:not(.hide) .model-search,
#modelPopup:not(.hide) .model-popup-list {
  animation: modelPickerIn 0.3s var(--ease) both;
}

#modelPopup:not(.hide) .model-search {
  animation-delay: 0.07s;
}

#modelPopup:not(.hide) .model-popup-list {
  animation-delay: 0.12s;
}

@keyframes modelPickerIn {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
}

.model-handle {
  width: 36px;
  height: 4px;
  border-radius: 2px;
  background: var(--ink-3);
  opacity: 0.4;
  margin: 10px auto 6px;
}

[data-theme-effective="dark"] .model-popup {
  border-radius: 28px 28px 0 0;
}

.model-popup::-webkit-scrollbar {
  display: none;
}

.model-popup {
  scrollbar-width: none;
  -ms-overflow-style: none;
}

@keyframes popIn {
  from {
    opacity: 0;
    transform: translateY(6px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.model-popup-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 2px 14px 10px;
  font-family: var(--font);
  font-size: 13px;
  font-weight: 700;
  color: var(--ink);
}

.model-popup-list {
  display: flex;
  flex-direction: column;
  padding: 0 12px 6px;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;
  -ms-overflow-style: none;
  touch-action: pan-y;
}

.model-popup-list::-webkit-scrollbar {
  display: none;
}

/* Search field pinned between the sheet header and the scrolling list */
.model-search {
  position: relative;
  padding: 0 14px 8px;
  flex-shrink: 0;
}

.model-search svg {
  position: absolute;
  left: 24px;
  top: calc(50% - 4px);
  transform: translateY(-50%);
  width: 14px;
  height: 14px;
  color: var(--ink-3);
  pointer-events: none;
}

.model-search input {
  width: 100%;
  box-sizing: border-box;
  padding: 8px 12px 8px 30px;
  border: 1px solid var(--picker-line);
  border-radius: 10px;
  background: var(--soft);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 13px;
  outline: 0;
}

.model-search input:focus {
  border-color: color-mix(in srgb, var(--accent) 55%, var(--picker-line));
}

.model-search input::placeholder {
  color: var(--ink-3);
}

.model-empty {
  padding: 18px 20px;
  text-align: center;
  font-size: 13px;
  color: var(--ink-3);
}

.model-cat {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 10px 10px 4px;
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-3);
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
}

.model-cat-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.model-cat-num {
  font-weight: 600;
  opacity: 0.7;
}

.model-cat-chev {
  margin-left: auto;
  display: flex;
  flex-shrink: 0;
  transition: transform var(--dur-fast) var(--ease);
}

.model-cat-chev svg {
  width: 12px;
  height: 12px;
  stroke: var(--ink-3);
}

.model-cat.closed .model-cat-chev {
  transform: rotate(-90deg);
}

/* Family sub-headers inside the chat group (Qwen, DeepSeek, …) */
.model-cat.model-sub {
  padding: 7px 10px 3px 18px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: none;
  color: var(--ink-2);
}

.model-subgrp {
  padding-left: 8px;
}

/* Group open/close animation: grid-template-rows 0fr↔1fr animates real
   height (display:none can't transition). .model-grp-in is the single
   grid item that clips the collapsing rows; visibility flips to hidden
   only after the collapse finishes, and back to visible instantly on
   open. Reduced-motion users get the old instant toggle via the global
   transition-duration kill-switch.
   20261112i: --grp-dur is set by composer.js at constant speed (duration
   ∝ height), so a tall group no longer sweeps past faster than a short one.
   The curve is near-linear with soft ends: the drawer curve crammed ~3/4 of
   the travel into the first quarter of the duration and read as a snap. */
.model-grp {
  display: grid;
  grid-template-rows: 1fr;
  transition: grid-template-rows var(--grp-dur, var(--dur-normal)) cubic-bezier(0.3, 0.1, 0.7, 0.9), visibility 0s;
}

.model-grp-in {
  min-height: 0;
  overflow: hidden;
}

.model-grp.closed {
  grid-template-rows: 0fr;
  visibility: hidden;
  transition: grid-template-rows var(--grp-dur, var(--dur-normal)) cubic-bezier(0.3, 0.1, 0.7, 0.9), visibility 0s linear var(--grp-dur, var(--dur-normal));
}

/* Picker rows/headers are divs — suppress the Android system tap-flash
   that `button` already opts out of (incl. inner spans, cursor:pointer
   is inherited so the highlight can land on them). */
.model-cat, .model-cat *,
.model-popup-item, .model-popup-item * {
  -webkit-tap-highlight-color: transparent;
}

.model-popup-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 10px;
  border-radius: 10px;
  cursor: pointer;
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: 500;
  color: var(--picker-other);
  transition: background var(--dur-fast) var(--ease);
  touch-action: manipulation;
  min-height: 38px;
  /* ~170 rows but only ~10 fit the sheet: offscreen rows skip layout/paint
     entirely, so the grid-template-rows group animation re-renders just the
     visible slice per frame instead of the whole catalog. contain-intrinsic
     only feeds the (hidden) scrollbar estimate until a row renders once. */
  content-visibility: auto;
  contain-intrinsic-size: auto 46px;
}

.model-popup-item:hover {
  background: color-mix(in srgb, var(--accent-bg) 50%, var(--picker-bg));
}

.model-popup-item.on {
  background: var(--picker-sel);
  color: var(--picker-sel-ink);
  font-weight: 600;
}

.model-popup-item.on::before {
  content: '\2713';
  font-size: 14px;
  color: var(--picker-check);
  font-weight: 700;
  flex-shrink: 0;
}

.model-popup-item:not(.on)::before {
  content: '';
  width: 14px;
  flex-shrink: 0;
}

/* Brand logomark to the left of the model name: monochrome svg painted in the
   row's own text color (currentColor), fixed box so every row aligns. Wider
   wordmarks (ltx, xai) scale down inside the box without distorting. */
.model-popup-item .model-ico {
  width: 17px;
  height: 17px;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  user-select: none;
}

.model-popup-item .model-ico svg {
  width: 100%;
  height: 100%;
  display: block;
}

/* Capability badges: context window size and vision support, dimmed and pushed
   to the right edge of the row. */
.model-popup-item .model-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.model-popup-item .model-tags {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
  padding-left: 8px;
}

.model-tag {
  font-size: 10.5px;
  font-weight: 600;
  line-height: 1.2;
  color: var(--ink-3);
  opacity: 0.7;
  white-space: nowrap;
  letter-spacing: 0.01em;
}

.model-popup-item.on .model-tag {
  color: var(--picker-sel-ink);
  opacity: 0.6;
}

/* Sections inside picker (if used) */
.model-popup-section-title {
  padding: 6px 10px 4px;
  font: 800 9px/1.3 var(--font-ui);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
}

/* === «Параметры» sheet (#mediaParamsPopup) — same shell as the model picker.
   One field row per declared capability; options are chips, «Авто» first. === */
#mediaParamsPopup.model-popup {
  max-height: min(480px, 72dvh);
  padding-bottom: 0;
}

.media-params-fields {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 4px 2px 10px;
}

.media-params-field {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.media-params-label {
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 650;
  letter-spacing: 0.02em;
  color: var(--ink-3);
}

.media-params-opts {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.media-params-opt {
  height: 30px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: 15px;
  background: var(--card);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
}

.media-params-opt.on {
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  background: color-mix(in srgb, var(--accent) 16%, var(--card));
  color: var(--accent-ink);
}

.media-params-opt:focus-visible {
  outline: none;
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent);
}

.media-params-opt:active {
  transform: scale(0.96);
}

.media-params-fixed {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  height: 30px;
  padding: 0 12px;
  border-radius: 15px;
  background: var(--soft);
  color: var(--ink-2);
  font-family: var(--font-ui);
  font-size: 12.5px;
  font-weight: 600;
}


/* === «Слайды» sheet (#docPopup) — sibling of the model picker.
   KIMI-style: pinned category tabs, template gallery, deck params. === */
#docPopup.model-popup {
  max-height: min(600px, 82dvh);
  padding-bottom: 0;
}

#docPopup .model-popup-list {
  padding: 0 12px 8px;
}

/* Category tabs: horizontal scroll rail under the header (KIMI's All/Work/…). */
.doc-tabs {
  display: flex;
  gap: 6px;
  padding: 2px 12px 8px;
  overflow-x: auto;
  scrollbar-width: none;
  touch-action: pan-x;
  flex: 0 0 auto;
}

.doc-tabs::-webkit-scrollbar {
  display: none;
}

.doc-tab {
  appearance: none;
  flex: 0 0 auto;
  height: 30px;
  padding: 0 13px;
  border: 1px solid var(--line);
  border-radius: 15px;
  background: transparent;
  color: var(--ink-2);
  font-family: var(--font-ui);
  font-size: 12.5px;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}

.doc-tab.on {
  background: color-mix(in srgb, var(--accent) 16%, var(--card));
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  color: var(--accent-ink);
}

.doc-tab:active {
  transform: scale(0.97);
}

/* Template gallery: 2-up grid of palette thumbs (KIMI's design cards). */
.doc-templates {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  padding: 2px 2px 10px;
}

.doc-templates.solo {
  grid-template-columns: 1fr;
}

.doc-tpl {
  appearance: none;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 0;
  border: 0;
  border-radius: 12px;
  background: var(--card);
  font-family: var(--font-ui);
  text-align: left;
  cursor: pointer;
  overflow: hidden;
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}

.doc-tpl.on {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}

.doc-tpl:active {
  transform: scale(0.98);
}

/* === Custom Palette Modal (KIMI Photo 4) === */
.custom-palette-modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(0, 0, 0, 0.75);
  backdrop-filter: blur(8px);
}

.custom-palette-modal.hide {
  display: none !important;
}

.custom-palette-card {
  width: 100%;
  max-width: 360px;
  background: #1e1e1e;
  border: 1px solid #333333;
  border-radius: 20px;
  padding: 20px;
  box-shadow: 0 16px 40px rgba(0, 0, 0, 0.5);
  color: #ffffff;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.custom-palette-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.custom-palette-title {
  font-size: 16px;
  font-weight: 700;
  color: #f0f0f0;
}

.custom-palette-close {
  background: none;
  border: none;
  color: #888;
  font-size: 20px;
  line-height: 1;
  padding: 4px;
  cursor: pointer;
}

.custom-palette-close:hover {
  color: #fff;
}

.custom-palette-preview {
  width: 100%;
  height: 90px;
  border-radius: 12px;
  border: 1px solid #333;
  padding: 12px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  box-sizing: border-box;
}

.custom-palette-preview-header {
  height: 14px;
  width: 60%;
  border-radius: 4px;
}

.custom-palette-preview-body {
  height: 8px;
  width: 85%;
  border-radius: 3px;
  opacity: 0.6;
}

.custom-palette-preview-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.custom-palette-preview-chip {
  height: 12px;
  width: 28px;
  border-radius: 4px;
}

.custom-palette-rows {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.custom-palette-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.custom-palette-label {
  font-size: 13.5px;
  font-weight: 500;
  color: #cccccc;
}

.custom-palette-control {
  display: flex;
  align-items: center;
  gap: 8px;
}

.custom-palette-picker-btn {
  position: relative;
  width: 32px;
  height: 32px;
  border-radius: 8px;
  border: 1px solid #444;
  overflow: hidden;
  cursor: pointer;
  display: grid;
  place-items: center;
}

.custom-palette-picker-btn input[type="color"] {
  position: absolute;
  inset: -10px;
  width: 60px;
  height: 60px;
  cursor: pointer;
  opacity: 0;
}

.custom-palette-hex {
  width: 84px;
  height: 32px;
  background: #2a2a2a;
  border: 1px solid #3a3a3a;
  border-radius: 8px;
  color: #fff;
  font-family: var(--font-mono, monospace);
  font-size: 12px;
  text-align: center;
  text-transform: uppercase;
}

.custom-palette-apply-btn {
  width: 100%;
  height: 42px;
  background: var(--accent, #3b82f6);
  border: none;
  border-radius: 12px;
  color: #fff;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  margin-top: 4px;
  transition: opacity var(--dur-normal) var(--ease);
}

.custom-palette-apply-btn:hover {
  opacity: 0.9;
}

/* === Color Popover (KIMI Photo 3) === */
.color-popover {
  position: absolute;
  z-index: 100;
  width: 250px;
  background: #1c1c1e;
  border: 1px solid #333333;
  border-radius: 16px;
  padding: 12px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.6);
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.color-popover {
  opacity: 1;
  transform: translateY(0) scale(1);
  transform-origin: top center;
  transition: opacity var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease), visibility 0s;
}

.color-popover.hide {
  display: flex !important;
  opacity: 0;
  transform: translateY(-6px) scale(.96);
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease), visibility 0s linear var(--dur-fast);
}

.color-presets-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}

.color-preset-card {
  height: 46px;
  border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  padding: 6px;
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}

.color-preset-card:hover {
  transform: scale(1.02);
  border-color: rgba(255, 255, 255, 0.3);
}

.color-preset-card.on {
  outline: 2px solid var(--accent);
}

.color-preset-top {
  height: 14px;
  width: 100%;
  border-radius: 3px;
  margin-bottom: 4px;
}

.color-preset-bottom {
  display: flex;
  height: 12px;
  gap: 4px;
}

.color-preset-bottom span {
  flex: 1;
  border-radius: 2px;
}

.color-popover-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 2px;
}

.color-popover-shuffle {
  width: 36px;
  height: 36px;
  border-radius: 10px;
  background: #2c2c2e;
  border: 1px solid #3a3a3c;
  color: #aaa;
  display: grid;
  place-items: center;
  cursor: pointer;
}

.color-popover-shuffle:hover {
  color: #fff;
  background: #3a3a3c;
}

.color-popover-custom-btn {
  flex: 1;
  height: 36px;
  background: #2c2c2e;
  border: 1px solid #3a3a3c;
  border-radius: 10px;
  color: #eee;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}.color-popover-custom-btn:hover {
  background: #3a3a3c;
  color: #fff;
}

/* Saved custom palettes (see miniapp-documents.js savePaletteFav) - compact shelf in the popover, capped at 12 entries. */
.color-favs {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.color-favs-title {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #8e8e93;
}

.color-favs-grid {
  max-height: 96px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

.color-fav-card {
  position: relative;
  border-color: rgba(255, 255, 255, 0.22);
}

.color-fav-card::after {
  content: '★';
  position: absolute;
  top: 3px;
  right: 5px;
  font-size: 9px;
  color: rgba(255, 255, 255, 0.75);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
}
/* === KIMI-Style Realistic Slide Cards (Photo 2) === */
.doc-tpl {
  position: relative;
  display: flex;
  flex-direction: column;
  border-radius: 12px;
  background: var(--card);
  border: 0;
  overflow: hidden;
  cursor: pointer;
  text-align: left;
  transition: transform var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}

.doc-tpl:hover {
  border-color: var(--accent);
}

.doc-tpl.on {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent);
}

.doc-tpl-thumb {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  border-bottom: 1px solid var(--line);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 8px 10px;
  box-sizing: border-box;
}

.doc-slide-mini {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  height: 100%;
  position: relative;
  z-index: 2;
}

.doc-slide-mini-kicker {
  font-size: 5.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin-bottom: 2px;
  opacity: 0.8;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.doc-slide-mini-title {
  font-size: 9px;
  font-weight: 800;
  line-height: 1.15;
  margin-bottom: 3px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.doc-slide-mini-desc {
  font-size: 6px;
  line-height: 1.25;
  opacity: 0.7;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.doc-slide-mini-bottom {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  margin-top: 4px;
}

.doc-slide-mini-stat {
  display: flex;
  flex-direction: column;
}

.doc-slide-mini-stat-val {
  font-size: 11px;
  font-weight: 900;
  line-height: 1;
  font-family: var(--font-mono, monospace);
}

.doc-slide-mini-stat-label {
  font-size: 5px;
  font-weight: 600;
  opacity: 0.75;
}

.doc-slide-mini-badge {
  font-size: 6px;
  padding: 1px 4px;
  border-radius: 3px;
  font-weight: 600;
}

.doc-tpl-thumb-freestyle {
  background: linear-gradient(135deg, #ffeedd 0%, #ffe4e6 50%, #e0e7ff 100%);
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}

.doc-freestyle-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px 12px;
  background: rgba(30, 30, 30, 0.85);
  backdrop-filter: blur(4px);
  color: #ffffff;
  border-radius: 20px;
  font-size: 11px;
  font-weight: 600;
  border: 1px solid rgba(255, 255, 255, 0.15);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}

.doc-freestyle-btn:hover {
  transform: scale(1.04);
  background: rgba(20, 20, 20, 0.95);
}

.doc-freestyle-btn svg {
  width: 13px;
  height: 13px;
}

.doc-tpl-name {
  padding: 8px 10px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.doc-tpl.on .doc-tpl-name {
  color: var(--accent);
}

/* === Composer context row (inside the input window): web chip + improve star === */
.composer-ctx {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 10px 0;
}

.composer-ctx.hide {
  display: none !important;
}

.composer-ctx-spacer {
  flex: 1;
}

/* All chips inside composer-ctx have border-radius, clean background and NO border / outline */
.composer-ctx button,
.composer-ctx .slides-chip,
.composer-ctx .web-chip,
.composer-ctx .reply-chip,
.composer-ctx .media-params-chip {
  border: none !important;
  outline: none !important;
  box-shadow: none !important;
}

.web-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 26px;
  padding: 0 9px 0 8px;
  border-radius: 13px;
  border: none;
  outline: none;
  background: var(--accent-bg);
  color: var(--accent-ink);
  font-family: var(--font-ui);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}

.web-chip svg {
  width: 13px;
  height: 13px;
  flex-shrink: 0;
}

.web-chip .web-chip-x {
  width: 10px;
  height: 10px;
  opacity: 0.7;
}

/* Reply chip (Блок 4): quote context in the composer */
.reply-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 62%;
  height: 26px;
  padding: 0 9px 0 8px;
  border-radius: 13px;
  border: none;
  outline: none;
  background: var(--card);
  color: var(--ink-2);
  font-family: var(--font-ui);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  flex-shrink: 0;
}

.reply-chip span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.reply-chip svg {
  width: 13px;
  height: 13px;
  flex-shrink: 0;
}

.reply-chip .reply-chip-x {
  width: 10px;
  height: 10px;
  opacity: 0.7;
}

/* Media params chip (Параметры генерации): same pill language as .web-chip —
   accent tint, no border; the second span carries chosen values (« · 16:9 · 1080p»). */
.media-params-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  max-width: 62%;
  height: 26px;
  padding: 0 9px 0 8px;
  border-radius: 13px;
  border: none;
  outline: none;
  background: var(--accent-bg);
  color: var(--accent-ink);
  font-family: var(--font-ui);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  flex-shrink: 0;
}

.media-params-chip svg {
  width: 13px;
  height: 13px;
  flex-shrink: 0;
}

.media-params-chip span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.media-params-chip #mediaParamsChipValues {
  opacity: 0.75;
  font-weight: 550;
}

.media-params-chip.hide {
  display: none !important;
}


.reply-chip.hide {
  display: none !important;
}

.improve-star {
  width: 28px;
  height: 28px;
  border-radius: 14px;
  border: 1px solid transparent;
  background: transparent;
  display: grid;
  place-items: center;
  color: var(--icon-1);
  cursor: pointer;
  flex-shrink: 0;
}

.improve-star svg {
  width: 16px;
  height: 16px;
  stroke: var(--icon-1);
}

.improve-star:active {
  transform: scale(0.9);
}

.improve-star.busy svg {
  animation: starSpin 0.9s linear infinite;
  opacity: 0.65;
}

@keyframes starSpin {
  to {
    transform: rotate(360deg);
  }
}

/* Send button glows with accent ring while web search is active */
.send-btn.web-on {
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 55%, transparent), 0 3px 10px rgba(0, 0, 0, 0.12);
}

/* === File chips (inside the composer box, above the textarea) —
   horizontal strip (owner request 2026-10-19): one scrollable row, rounded
   thumbs, remove buttons overlay the media; no vertical stacking. === */
.file-chips {
  display: flex;
  flex-wrap: nowrap;
  gap: 8px;
  margin: 0;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  -ms-overflow-style: none;
  padding: 2px;
}

.file-chips::-webkit-scrollbar {
  display: none;
}

.file-chips.hide {
  display: none !important;
}

.file-chip {
  flex-shrink: 0;
  position: relative;
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 6px 9px;
  border: 0;
  border-radius: 12px;
  background: var(--accent-bg);
  font-size: 12px;
  max-width: 200px;
  min-width: 0;
}

.file-chip-icon {
  flex-shrink: 0;
  display: inline-grid;
  place-items: center;
  color: var(--accent-ink);
}

.file-chip-icon svg {
  width: 17px;
  height: 17px;
  display: block;
}

.file-chip-thumb {
  position: relative;
  border-radius: 12px;
  overflow: hidden;
  padding: 0;
  width: 64px;
  height: 64px;
}

.file-chip-img {
  width: 64px;
  height: 64px;
  object-fit: cover;
  border-radius: 12px;
  display: block;
}

.file-chip-remove {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 0;
  background: rgba(0, 0, 0, 0.6);
  color: #fff;
  font-size: 12px;
  line-height: 1;
  display: grid;
  place-items: center;
  cursor: pointer;
  position: absolute;
  top: 3px;
  right: 3px;
  flex-shrink: 0;
  z-index: 1;
}

/* Failed upload: error state + retry action */
.file-chip-error {
  background: color-mix(in srgb, var(--danger, #a65d5d) 12%, var(--card));
  border: 1px solid color-mix(in srgb, var(--danger, #a65d5d) 35%, transparent);
}

.file-chip-error .file-chip-size.is-error {
  color: var(--danger, #a65d5d);
  font-size: 10.5px;
  max-width: 130px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.file-chip-retry {
  border: 0;
  border-radius: 8px;
  padding: 3px 8px;
  font-size: 11px;
  font-weight: 600;
  background: var(--danger, #a65d5d);
  color: #fff;
  flex-shrink: 0;
}

/* === Image reference chip === */
.image-reference-chip {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  margin-bottom: 8px;
  border: 0;
  border-radius: 12px;
  background: var(--accent-bg);
  max-width: 100%;
}

.image-reference-chip.hide {
  display: none !important;
}

.image-reference-chip img {
  width: 48px;
  height: 48px;
  object-fit: cover;
  border-radius: 8px;
  flex-shrink: 0;
}

.image-reference-copy {
  flex: 1;
  min-width: 0;
  overflow: hidden;
}

.image-reference-copy strong {
  display: block;
  font-size: 12px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.image-reference-copy small {
  display: block;
  font-size: 10px;
  color: var(--ink-3);
}

.image-reference-chip button {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: 0;
  background: rgba(0, 0, 0, 0.15);
  color: var(--ink);
  font-size: 14px;
  display: grid;
  place-items: center;
  cursor: pointer;
  flex-shrink: 0;
}

/* === Attach preview === */
.attach-preview {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0 0 8px;
}

.attach-preview.hide {
  display: none !important;
}

.attach-chip {
  position: relative;
  border-radius: 10px;
  overflow: hidden;
  border: 1px solid var(--line);
  background: var(--accent-bg);
}

.attach-chip img {
  display: block;
  width: 64px;
  height: 64px;
  object-fit: cover;
}

.attach-chip.file {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 6px 10px;
  font-size: 11px;
  max-width: 180px;
}

.attach-chip .attach-icon {
  flex-shrink: 0;
}

.attach-chip .attach-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 650;
}

.attach-chip .attach-size {
  color: var(--ink-3);
  flex-shrink: 0;
  font-size: 9px;
}

.attach-remove {
  position: absolute;
  top: 2px;
  right: 2px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 0;
  background: rgba(0, 0, 0, 0.6);
  color: #fff;
  font-size: 12px;
  display: grid;
  place-items: center;
  cursor: pointer;
}

/* === Drag state === */
.composer.dragging .composer-box {
  border-color: var(--accent);
  background: var(--accent-bg);
}

/* === Motion polish (Блок 6) === */

/* View switch (chat <-> profile) */
.view.on {
  animation: viewIn var(--dur-normal) var(--ease);
}

@keyframes viewIn {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
}

/* The newest message bubble settles in */
.bubble.fresh {
  animation: bubbleIn var(--dur-normal) var(--ease);
}

/* Streaming answer fades in as the typing starts */
.bubble.streaming {
  animation: bubbleIn var(--dur-normal) var(--ease);
}

@keyframes bubbleIn {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
}

/* Session-open cascade (owner, 2026-11-05: «появление плавным и
   анимированным»): chat.js вешает .cascade на #messages на время смены чата;
   каждый бабл несёт --mi, проиндексированный С КОНЦА списка — чат открывается
   у нижнего края, поэтому видимый хвост ведёт волну, а старые ряды тянутся
   за ним. Длинный ход (380мс) при коротком шаге (50мс) — движение соседних
   баблов перекрывается и читается одной волной, а не тиками. */
#messages.cascade .bubble {
  animation: bubbleIn 380ms var(--hist-curve) both;
  animation-delay: calc(30ms + var(--mi, 0) * 50ms);
}

/* Modals (key reveal / share sheet / confirm): soft entrance instead of the
   display snap. Class `open` is put by miniapp-modal.js on the root; the EXIT
   stays instant on purpose — an async exit would disagree with the synchronous
   isOpen() and with the Telegram-WebView invariants documented in modal.js.
   Both animations are composited (opacity / transform only). */
/* The modal scrollport hides its scrollbar — a visible track shifted the
   profile-picker layout and looked like a stray page scrollbar in sheets
   (owner, 2026-11-05). */
#synapse-modal-content {
  scrollbar-width: none;
  -ms-overflow-style: none;
}

#synapse-modal-content::-webkit-scrollbar {
  display: none;
}

#synapse-modal-root.open {
  animation: modalBackdropIn var(--dur-normal) var(--ease);
}

#synapse-modal-root.open #synapse-modal-content {
  animation: modalContentIn var(--dur-normal) var(--ease);
}

@keyframes modalBackdropIn {
  from {
    opacity: 0;
  }
}

@keyframes modalContentIn {
  from {
    opacity: 0;
    transform: translateY(12px) scale(0.985);
  }
}

@media (prefers-reduced-motion: reduce) {

  #synapse-modal-root.open,
  #synapse-modal-root.open #synapse-modal-content {
    animation: none;
  }
}

/* Smooth interactive feedback across controls */
.quick-action,
.minor,
.row,
.model-popup-item,
.history-btn,
.new-chat-btn,
.attach-toggle,
.send-btn,
.avatar-btn,
.scroll-bottom-btn,
.web-chip,
.improve-star,
.media-params-chip,
.history-close,
.back-btn,
.confirm-btn,
.code-copy,
.msg-copy,
.msg-regen,
.msg-share,
.retry-btn,
.load-older-btn {
  transition: transform var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease), opacity var(--dur-fast) var(--ease);
}

@media (hover: hover) {
  .row:hover {
    background: var(--soft);
  }
}

/* Composer box: soft glow while typing */
.composer-box {
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-normal) var(--ease);
}

.composer-box:focus-within {
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}

/* Главная панель, история, попапы, профиль: вертикальный скролл — браузеру,
   горизонтальные жесты — нам (drawer/reply-drag/edge-back). ОБЯЗАТЕЛЬНО на самих
   скролл-контейнерах: иначе браузер забирает горизонталь и убивает pointercancel'ем.
   ВАЖНО (контракт, аудит P2-10): .messages/.bubble НИЖЕ переопределяются на
   touch-action:auto !important (iOS-фикс выделения текста, см. строки ~964 и ~6262) —
   это осознанное исключение: в чате горизонталь живёт на non-passive touchmove
   reply-drag'а (preventDefault после latch), а НЕ на pointer-событиях.
   Новые горизонтальные жесты в чате добавлять в тот же touchmove-канал. */
.main-panel,
.history-panel,
.model-popup,
#profile.view,
.messages,
#sessions {
  touch-action: pan-y;
}

/* ============================================================
   PROFILE VIEW
   ============================================================ */

/* Banner card — like mockup (radius 4px!) */
.profile-banner {
  background: var(--banner-bg);
  border-radius: 8px;
  padding: 10px 11px 12px;
  margin-top: 12px;
}

.banner-top {
  display: flex;
  align-items: center;
  gap: 11px;
}

.profile-avatar {
  width: 52px;
  height: 52px;
  border-radius: 4px;
  background: var(--avatar-bg);
  display: grid;
  place-items: center;
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 700;
  color: var(--ink);
  flex-shrink: 0;
  overflow: hidden;
  /* Accent ring in EVERY theme (owner, 2026-11-05: «обводка в цвет темы во
     всех темах») — tier themes layer their pulse on top via box-shadow
     keyframes below. */
  box-shadow: 0 0 0 2px var(--accent);
}

.profile-avatar.has-photo {
  background-size: cover;
  background-position: center;
  color: transparent;
}

.profile-info {
  flex: 1;
  min-width: 0;
}

.profile-name {
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: 700;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.profile-handle {
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--ink-2);
  margin-top: 3px;
}

.profile-tier {
  text-align: right;
  flex-shrink: 0;
  margin-left: 8px;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 4px;
}

.tier-label {
  font-family: var(--font-display);
  font-size: 14px;
  font-weight: 700;
  color: var(--ink);
}

.tier-caption {
  font-size: 11px;
  color: var(--ink-2);
  margin-top: 3px;
}

/* Progress bar — inside banner bottom */
.profile-progress {
  margin-top: 12px;
  cursor: pointer;
}

.progress-bar {
  position: relative;
  height: 9px;
  border-radius: 4px;
  background: var(--progress-bg);
  overflow: hidden;
}

.progress-fill {
  height: 100%;
  border-radius: 4px;
  background: var(--progress-fill);
  transition: width var(--dur-slow) var(--ease);
}

/* П.6 (2026-10-15): тап по бару — точный остаток токенов цифрами прямо в баре.
   Надпись живёт в самом #bar (0-текст по умолчанию, высоту не двигает), mono,
   трафарет-подложка (text-shadow по кругу) сохраняет читаемость и на заполненной,
   и на пустой заливке. Курсор-подсказка: бар ведёт себя как кнопка. */
.progress-bar {
  cursor: pointer;
}
/* Число живёт отдельным слоем на всю ширину ТРЕКА (.progress-bar), а не в
   заливке — иначе центрирование съезжает к краю при малой заполненности. */
.progress-bar .bar-amount {
  position: absolute;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: flex-start;
  padding-left: 6px;
  font-family: var(--font-mono);
  font-size: 8.5px;
  line-height: 9px;
  font-weight: 700;
  letter-spacing: 0.2px;
  color: var(--ink);
  white-space: nowrap;
  pointer-events: none;
}

.progress-bar .bar-amount.tick {
  display: flex;
  text-shadow:
    0 0 2px var(--bg), 0 0 2px var(--bg),
    1px 0 2px var(--bg), -1px 0 2px var(--bg),
    0 1px 2px var(--bg), 0 -1px 2px var(--bg);
  animation: barTick var(--dur-fast) var(--ease);
}
@keyframes barTick {
  from { opacity: 0; }
}

.progress-meta {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  margin-top: 6px;
}

.progress-meta .pm-left,
.progress-meta .pm-right {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--ink-2);
}

.progress-meta .pm-left b {
  font-weight: 700;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.progress-meta .pm-right {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Content card — no plate: profile content sits directly on the canvas */
.profile-content {
  margin-top: 12px;
}

/* Filter bar */
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  padding: 4px;
  margin: 10px;
  background: var(--filter-bg);
  border: 1px solid var(--filter-line);
  border-radius: 21px;
}

.filter-item {
  flex: 1 1 auto;
  height: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 18px;
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 500;
  color: var(--ink-2);
  cursor: pointer;
  background: var(--filter-item-bg);
  border: 1px solid var(--filter-item-line);
  transition: background var(--dur-normal) var(--ease), color var(--dur-normal) var(--ease), border-color var(--dur-normal) var(--ease);
  white-space: nowrap;
  padding: 0 8px;
}

.filter-item.active {
  background: var(--filter-sel);
  border-color: var(--filter-sel-line);
  color: var(--ink);
  font-weight: 600;
}

/* Profile panels (switched by filter) */
.profile-panels {
  padding: 0 14px 4px;
}

.profile-panel {
  display: none;
}

.profile-panel.active {
  display: block;
}

/* Audit Block 4: spend log in the profile («Журнал» panel). Compact rows:
   model + local time on the left, tokens in/out on the right. */
.usage-log-empty {
  margin: 6px 4px;
  color: var(--ink-3);
  font-size: 13px;
  line-height: 1.45;
}

.usage-log {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.usage-log-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 9px 12px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 12px;
  font-family: var(--font-ui);
}

.usage-log-main {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.usage-log-main strong {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.usage-log-meta {
  font-size: 11px;
  color: var(--muted);
}

.usage-log-fail {
  color: var(--danger, #d33);
  font-weight: 600;

}

.usage-log-tokens {
  flex: 0 0 auto;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-2);
  white-space: nowrap;
}





/* ============================================================
   SHARED UI ELEMENTS (inside profile panels)
   ============================================================ */

.details-group {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 12px;
  overflow: hidden;
}

.sub {
  font-size: 13px;
  color: var(--ink-2);
  margin: 2px 0 10px;
}

.settings-note {
  margin: 0 2px 10px;
}

.minor {
  border: 0;
  border-radius: 11px;
  background: var(--card);
  color: var(--ink);
  font-size: 13px;
  font-weight: 650;
  padding: 9px 11px;
  cursor: pointer;
  width: 100%;
  text-align: center;
}

/* «Сбросить ключ» revokes the key — it must not look like its neighbours. */
#forget.minor {
  color: var(--danger, #a65d5d);
}

.minor:hover {
  border-color: var(--accent-line);
  background: var(--accent-bg);
}

/* Compare summary */
.compare-summary {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 9px;
  margin: 0 0 10px;
  padding: 10px 11px;
  border: 1px solid var(--accent-line);
  border-radius: 12px;
  background: var(--accent-bg);
}

.compare-summary span {
  font: 700 9px/1 var(--font-ui);
  letter-spacing: 0.08em;
  color: var(--accent);
}

.compare-summary strong {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font: 700 12px/1.3 var(--font-ui);
  color: var(--ink);
}

.compare-summary .minor {
  padding: 7px 8px;
  font-size: 11px;
  width: auto;
}

/* Версия сборки: тусклая тех-надпись в углу раздела «Сравнение».
   Намеренно не кнопка и не в общем фокусе — но это скрытый триггер
   входа в админку (удержание 10 с, см. miniapp-admin.js). */
.ver-corner {
  display: block;
  margin: 14px 2px 0;
  text-align: right;
  font: 500 10px/1 var(--font-mono);
  letter-spacing: 0.04em;
  color: var(--ink-3);
  opacity: 0.55;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}

/* Model rows */
.model {
  display: flex;
  gap: 9px;
  align-items: center;
  padding: 10px 0;
  border-top: 1px solid var(--line);
}

.model label {
  flex: 1;
  font-size: 13px;
  color: var(--ink);
}

.model input {
  accent-color: var(--accent);
}

.chip {
  padding: 5px 9px;
  border: 1px solid var(--accent-line);
  border-radius: 999px;
  background: var(--accent-bg);
  font-size: 11px;
  color: var(--accent-ink);
}

.chip.on {
  background: var(--accent-bg);
  border-color: var(--accent-line);
  color: var(--accent-ink);
}

/* Recommendations */
.recommendation {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 9px;
  padding: 10px 11px;
  border: 1px solid var(--accent-line);
  border-radius: 12px;
  background: var(--accent-bg);
  margin-bottom: 8px;
}

.recommendation .rec-models {
  font: 700 11px/1.35 var(--font-ui);
  color: var(--accent-ink);
}

.recommendation button {
  white-space: nowrap;
}

/* Session rows */
.row {
  width: 100%;
  min-height: 53px;
  padding: 10px 14px;
  background: transparent;
  text-align: left;
  border: 0;
  border-top: 1px solid var(--line);
  cursor: pointer;
  color: var(--ink);
}


.row strong {
  display: block;
}

.row span {
  display: block;
  font-size: 12px;
  color: var(--ink-3);
  margin-top: 2px;
}

.row:first-child {
  border-top: 0;
}


/* Ref link */
.ref {
  font: 12px var(--font-mono);
  word-break: break-all;
  color: var(--accent-ink);
  display: block;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
  margin-bottom: 10px;
}

/* Stats */
.stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  border: 1px solid var(--line);
  border-radius: 12px;
  overflow: hidden;
  margin-top: 10px;
}

.stat {
  text-align: center;
  padding: 12px 6px;
}

.stat+.stat {
  border-left: 1px solid var(--line);
}

.stat b {
  display: block;
  font-size: 17px;
  letter-spacing: -0.04em;
  color: var(--ink);
}

.stat span {
  font-size: 11px;
  color: var(--ink-3);
}

/* Plans — radio-select pricing rows (П.8, 2026-11-11): bare rows on the
   canvas, selected row expands with features + CTA. Monochrome on tokens —
   inherits every accent palette and both themes. */
.plans {
  display: block;
}

.plan-row {
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr auto;
  column-gap: 12px;
  align-items: start;
  padding: 13px 12px;
  border: 1px solid var(--line);
  border-radius: 15px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: border-color var(--dur-normal) var(--ease),
    box-shadow var(--dur-normal) var(--ease),
    background var(--dur-normal) var(--ease);
}

.plan-row + .plan-row {
  margin-top: 9px;
}

.plan-row:active {
  transform: none;
}

.plan-row.sel {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}

/* Radio marker */
.plan-radio {
  width: 21px;
  height: 21px;
  margin-top: 1px;
  border-radius: 50%;
  border: 2px solid color-mix(in srgb, var(--ink-3) 40%, transparent);
  display: grid;
  place-items: center;
  transition: border-color var(--dur-normal) var(--ease);
}

.plan-radio::after {
  content: '';
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: var(--accent);
  transform: scale(0);
  transition: transform 0.22s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.plan-row.sel .plan-radio {
  border-color: var(--accent);
}

.plan-row.sel .plan-radio::after {
  transform: scale(1);
}

/* Name + description */
.plan-row-main {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.plan-row-main strong {
  font-size: 15px;
  font-weight: 650;
  color: var(--ink);
  display: flex;
  align-items: center;
  gap: 7px;
  flex-wrap: wrap;
}

.plan-row-main small {
  font-size: 12px;
  color: var(--ink-2);
}

.plan-chip {
  font: 700 9.5px/1 var(--font-ui);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 4px 8px;
  border-radius: 999px;
  color: var(--accent-ink);
  background: color-mix(in srgb, var(--accent) 22%, transparent);
}

.plan-chip.plan-chip-current {
  color: var(--ink-2);
  background: var(--soft);
}

/* Price */
.plan-row-price {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 1px;
}

.plan-row-price b {
  font-size: 16px;
  font-weight: 700;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.plan-row-price small {
  font-size: 11px;
  color: var(--ink-3);
}

/* Expanding detail — selected row only */
.plan-more {
  grid-column: 1 / -1;
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  transition: grid-template-rows 0.38s cubic-bezier(0.32, 0.72, 0, 1),
    opacity 0.28s var(--ease);
}

.plan-row.sel .plan-more {
  grid-template-rows: 1fr;
  opacity: 1;
}

.plan-more-in {
  overflow: hidden;
  min-height: 0;
}

.plan-more-body {
  padding: 16px 2px 2px;
}

.plan-feats {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 8px;
}

.plan-feats li {
  position: relative;
  padding-left: 22px;
  font-size: 13px;
  color: var(--ink-2);
  line-height: 1.4;
}

.plan-feats li::before {
  content: '✓';
  position: absolute;
  left: 2px;
  top: 0;
  font-size: 12px;
  font-weight: 700;
  color: var(--accent);
}

/* Staggered feature reveal on expand */
.plan-row.sel .plan-feats li {
  animation: plan-feat-in 0.3s var(--ease) both;
}

.plan-row.sel .plan-feats li:nth-child(2) { animation-delay: 0.05s; }
.plan-row.sel .plan-feats li:nth-child(3) { animation-delay: 0.1s; }
.plan-row.sel .plan-feats li:nth-child(4) { animation-delay: 0.15s; }
.plan-row.sel .plan-feats li:nth-child(5) { animation-delay: 0.2s; }
.plan-row.sel .plan-feats li:nth-child(6) { animation-delay: 0.25s; }

@keyframes plan-feat-in {
  from { opacity: 0; transform: translateY(5px); }
  to   { opacity: 1; transform: none; }
}

.plan-more-hr {
  height: 1px;
  margin: 14px 0 12px;
  background: var(--line-soft);
}

.plan-cta {
  width: 100%;
  padding: 11px;
  border: 0;
  border-radius: 12px;
  background: var(--ink);
  color: var(--bg);
  font-weight: 650;
  font-size: 13.5px;
  cursor: pointer;
}

.plan-cta.plan-cta-secondary {
  background: var(--soft);
  color: var(--ink);
  border: 1px solid var(--line);
}

@media (prefers-reduced-motion: reduce) {
  .plan-more { transition: none; }
  .plan-radio::after { transition: none; }
  .plan-row.sel .plan-feats li { animation: none; }
}

/* Settings grid */
.settings-grid {
  display: grid;
  gap: 8px;
}

/* Palette dots (settings) */
.palette-label {
  font-size: 12px;
  color: var(--ink-2);
  font-weight: 600;
  margin-bottom: 6px;
  display: block;
}

.palette-row {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}

.palette-dot {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 2px solid transparent;
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease);
}

.palette-dot:hover {
  border-color: var(--ink);
}

/* ============================================================
   HEADER PROFILE ACCESS (ТЗ 108-109) — avatar opens profile, back returns
   ============================================================ */
.avatar-btn {
  width: 34px;
  height: 34px;
  border-radius: 17px;
  border: 0;
  background: var(--card);
  display: grid;
  place-items: center;
  color: var(--ink);
  font-weight: 700;
  font-size: 14px;
  flex-shrink: 0;
  cursor: pointer;
}

/* Back arrow in header (ChatGPT pattern) + profile title */
.profile-title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(16px, 4.4vw, 18px);
  letter-spacing: -0.02em;
  color: var(--ink);
}

.profile-title.hide {
  display: none !important;
}

.brand.hide {
  display: none !important;
}

.back-btn {
  width: clamp(30px, 8.4vw, 34px);
  height: clamp(30px, 8.4vw, 34px);
  border-radius: 50%;
  border: 0;
  background: color-mix(in srgb, var(--accent-bg) 62%, transparent);
  -webkit-backdrop-filter: blur(8px) saturate(1.4);
  backdrop-filter: blur(8px) saturate(1.4);
  display: grid;
  place-items: center;
  padding: 0;
  color: var(--ink-2);
  flex-shrink: 0;
  cursor: pointer;
  /* Owner 2026-11-11: align flush with the 12px control rail (hamburger pill).
     The left ~10px of the button now overlaps the 22px edge strip — safe,
     because the edge-back touchstart excludes .back-btn explicitly (ux.js). */
  margin-left: 2px;
  margin-right: 2px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
  transition: border-color var(--dur-fast, 120ms) var(--ease), background var(--dur-fast, 120ms) var(--ease), transform var(--dur-fast, 120ms) var(--ease);
}

.back-btn.hide {
  display: none !important;
}

.back-btn:not(.hide) {
  animation: backIn var(--dur-normal, 200ms) var(--ease, cubic-bezier(.2, .8, .2, 1));
}

@keyframes backIn {
  from {
    opacity: 0;
    transform: translateX(6px);
  }

  to {
    opacity: 1;
    transform: translateX(0);
  }
}

.back-btn:hover {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
  background: color-mix(in srgb, var(--accent) 7%, var(--card));
}

.back-btn:active {
  transform: scale(0.94);
}

.back-btn svg {
  width: clamp(17px, 4.8vw, 20px);
  height: clamp(17px, 4.8vw, 20px);
  stroke: currentColor;
  display: block;
}

/* ============================================================
   GATE (API key entry)
   ============================================================ */
.gate {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: var(--bg);
  display: grid;
  place-items: center;
  padding: 20px;
}

.gate-card {
  width: 100%;
  max-width: 340px;
  text-align: center;
}

.gate-input {
  width: 100%;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--card);
  color: var(--ink);
  font-size: 16px;
  margin-bottom: 12px;
  outline: 0;
}

.gate-input:focus {
  border-color: var(--accent);
}

.gate-btn {
  width: 100%;
  padding: 12px;
  border: 0;
  border-radius: 12px;
  background: var(--accent);
  color: var(--bg);
  font-weight: 700;
  font-size: 14px;
  cursor: pointer;
}

/* ============================================================
   TOAST / KEY REVEAL / MISC
   ============================================================ */
.toast {
  position: fixed;
  bottom: calc(140px + var(--bottom, 0px));
  left: 50%;
  z-index: 800;
  transform: translate(-50%, 8px);
  opacity: 0;
  background: var(--toast-bg);
  color: var(--toast-ink);
  padding: 10px 14px;
  border-radius: 12px;
  font-size: 13px;
  font-weight: 600;
  transition: transform var(--dur-normal) var(--ease), opacity var(--dur-normal) var(--ease);
  pointer-events: none;
  max-width: calc(100% - 30px);
}

.toast.show,
.toast.on {
  transform: translate(-50%, 0);
  opacity: 1;
}

.toast.out {
  transform: translate(-50%, -4px);
  opacity: 0;
}

/* === Delete confirmation sheet (ТЗ 60) === */
.confirm-backdrop {
  position: fixed;
  inset: 0;
  z-index: 650;
  background: rgba(0, 0, 0, .65);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-normal) var(--ease);
}

.confirm-backdrop:not(.hide) {
  opacity: 1;
  pointer-events: auto;
}

.confirm-backdrop.hide {
  display: block !important;
  opacity: 0;
  pointer-events: none;
}

.confirm-sheet {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 700;
  width: 100%;
  max-width: 480px;
  margin: 0 auto;
  background: var(--card);
  border: 1px solid var(--line);
  border-bottom: 0;
  border-radius: 28px 28px 0 0;
  padding: 20px 20px calc(16px + var(--bottom, 0px));
  box-shadow: 0 -12px 40px rgba(0, 0, 0, 0.18);
  transform: translateY(100%);
  visibility: hidden;
  transition: transform var(--dur-slow) var(--ease-sheet), visibility var(--dur-slow) var(--ease);
}

.confirm-sheet:not(.hide) {
  transform: translateY(0);
  visibility: visible;
}

.confirm-sheet.hide {
  display: block !important;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(100%);
}

.confirm-sheet-title {
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--ink);
}

.confirm-sheet-text {
  margin-top: 6px;
  font-size: 13.5px;
  color: var(--ink-3);
}

.confirm-sheet-actions {
  display: flex;
  gap: 10px;
  margin-top: 18px;
}

.confirm-btn {
  flex: 1;
  height: 44px;
  border-radius: 12px;
  border: 0;
  background: var(--soft);
  color: var(--ink);
  font-size: 14px;
  font-weight: 650;
  cursor: pointer;
}

.confirm-btn:active {
  transform: scale(0.97);
}

.confirm-btn.danger {
  background: var(--danger, #a65d5d);
  border-color: transparent;
  color: #fff;
}

/* Buy button spinner (ТЗ 67) */
.btn-spinner {
  display: inline-block;
  width: 13px;
  height: 13px;
  border: 2px solid rgba(255, 255, 255, 0.35);
  border-top-color: #fff;
  border-radius: 50%;
  vertical-align: -2px;
  margin-right: 6px;
  animation: btnSpin 0.7s linear infinite;
}

@keyframes btnSpin {
  to {
    transform: rotate(360deg);
  }
}

/* Masked API key (ТЗ 70) */
.key-reveal-card code.masked {
  letter-spacing: 0.3em;
  user-select: none;
  cursor: pointer;
}

/* === Settings (ТЗ 68, 71) === */
.settings-list {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 16px;
  overflow: hidden;
}

.settings-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 13px 14px;
  min-height: 58px;
}

.settings-row+.settings-row {
  border-top: 1px solid var(--line);
}

.settings-copy strong {
  display: block;
  font-size: 14px;
  color: var(--ink);
}

.settings-copy span {
  display: block;
  font-size: 12px;
  color: var(--ink-3);
  margin-top: 2px;
}

.switch {
  --sw-w: clamp(40px, 11vw, 44px);
  --sw-h: clamp(24px, 6.6vw, 26px);
  width: var(--sw-w);
  height: var(--sw-h);
  border-radius: calc(var(--sw-h) / 2);
  border: 1px solid var(--line);
  background: var(--soft);
  position: relative;
  flex-shrink: 0;
  cursor: pointer;
  transition: background var(--dur-normal) var(--ease);
  padding: 0;
}

.switch::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: calc(var(--sw-h) - 6px);
  height: calc(var(--sw-h) - 6px);
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.28), 0 0 0 0.5px rgba(0, 0, 0, 0.08);
  transition: transform var(--dur-normal) var(--ease);
}

.switch.on {
  background: var(--accent);
  border-color: transparent;
}

.switch.on::after {
  transform: translateX(calc(var(--sw-w) - var(--sw-h)));
}

/* Inline «Авто» switch (model-picker header, next to the title):
   a small label + the settings-switch track (sliding ::after knob).
   The mode is shared with the «Авто» pill. */
.auto-toggle {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  border: 0;
  background: transparent;
  padding: 0;
  flex-shrink: 0;
  cursor: pointer;
}

.auto-toggle-label {
  font-family: var(--font);
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-3);
  line-height: 1;
  white-space: nowrap;
  transition: color var(--dur-normal) var(--ease);
}

.auto-toggle.on .auto-toggle-label {
  color: var(--ink);
}

.auto-toggle-track {
  --sw-w: 32px;
  --sw-h: 19px;
  position: relative;
  width: var(--sw-w);
  height: var(--sw-h);
  border-radius: calc(var(--sw-h) / 2);
  border: 1px solid var(--line);
  background: var(--soft);
  transition: background var(--dur-normal) var(--ease), border-color var(--dur-normal) var(--ease);
}

.auto-toggle-track::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: calc(var(--sw-h) - 6px);
  height: calc(var(--sw-h) - 6px);
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.28), 0 0 0 0.5px rgba(0, 0, 0, 0.08);
  transition: transform var(--dur-normal) var(--ease);
}

.auto-toggle.on .auto-toggle-track {
  background: var(--accent);
  border-color: transparent;
}

.auto-toggle.on .auto-toggle-track::after {
  transform: translateX(calc(var(--sw-w) - var(--sw-h)));
}

/* Theme control (light / dark / system): dropdown */
.theme-drop {
  position: relative;
  flex-shrink: 0;
}

.theme-drop-btn {
  display: flex;
  align-items: center;
  gap: 7px;
  height: 38px;
  padding: 0 11px 0 8px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--soft);
  color: var(--ink);
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: border-color var(--dur-normal, 200ms) var(--ease), background var(--dur-normal, 200ms) var(--ease), transform var(--dur-fast, 120ms) var(--ease);
}

.theme-drop-btn:active {
  transform: scale(0.97);
}

.theme-drop.open .theme-drop-btn {
  border-color: color-mix(in srgb, var(--accent) 55%, var(--line));
  background: var(--card);
}

.theme-drop-ic {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  color: var(--accent);
  flex-shrink: 0;
}

.theme-drop-ic svg {
  width: 14px;
  height: 14px;
}

.theme-drop-caret {
  width: 14px;
  height: 14px;
  color: var(--ink-3);
  flex-shrink: 0;
  transition: transform var(--dur-normal, 200ms) var(--ease);
}

.theme-drop.open .theme-drop-caret {
  transform: rotate(180deg);
}

.theme-drop-menu {
  position: fixed;
  z-index: 650;
  min-width: 176px;
  padding: 6px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--card);
  box-shadow: var(--shadow-card);
  visibility: hidden;
  opacity: 0;
}

.theme-drop-menu.open {
  visibility: visible;
  opacity: 1;
}

.theme-drop-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  height: 38px;
  padding: 0 10px;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: var(--ink);
  font-family: inherit;
  font-size: 14px;
  font-weight: 550;
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background var(--dur-fast, 120ms) var(--ease), color var(--dur-fast, 120ms) var(--ease);
}

.theme-drop-item:active {
  background: var(--soft);
}

.theme-drop-item-ic {
  width: 18px;
  height: 18px;
  display: grid;
  place-items: center;
  color: var(--ink-3);
  flex-shrink: 0;
  transition: color var(--dur-fast, 120ms) var(--ease);
}

.theme-drop-item-ic svg {
  width: 15px;
  height: 15px;
}

.theme-drop-item.on {
  color: var(--accent);
}

.theme-drop-item.on .theme-drop-item-ic {
  color: var(--accent);
}

.theme-drop-check {
  width: 15px;
  height: 15px;
  margin-left: auto;
  color: var(--accent);
  opacity: 0;
  flex-shrink: 0;
  transition: opacity var(--dur-fast, 120ms) var(--ease);
}

.theme-drop-item.on .theme-drop-check {
  opacity: 1;
}

.settings-danger {
  margin-top: 18px;
  padding: 0;
  border: 0;
  background: transparent;
}

.danger-btn {
  width: 100%;
  height: 42px;
  border-radius: 11px;
  border: 0;
  background: color-mix(in srgb, var(--danger, #a65d5d) 16%, transparent);
  color: var(--danger, #a65d5d);
  font-size: 14px;
  font-weight: 650;
  cursor: pointer;
}

.danger-btn:active {
  transform: scale(0.98);
}

/* Key reveal */
.key-reveal {
  position: fixed;
  z-index: 700;
  inset: 0;
  background: rgba(0, 0, 0, 0.85);
  padding: 20px;
  display: grid;
  place-items: center;
  pointer-events: auto;
}

.key-reveal.hide {
  display: none !important;
  pointer-events: none !important;
  visibility: hidden !important;
}

.key-reveal-card {
  width: min(100%, 460px);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 18px;
  padding: 18px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.3);
}

.key-reveal-card code {
  display: block;
  margin: 12px 0;
  padding: 12px;
  border-radius: 10px;
  background: var(--accent-bg);
  color: var(--accent-ink);
  overflow-wrap: anywhere;
  font: 12px/1.45 var(--font-mono);
}

.key-reveal-card .minor {
  margin-top: 8px;
}

.dk-balance {
  margin: 12px 0 4px;
  padding: 11px 12px;
  border-radius: 10px;
  background: var(--accent-bg);
  border: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.dk-balance-label {
  font-size: 10.5px;
  color: var(--ink-3);
  text-transform: uppercase;
  letter-spacing: 0.4px;
}

.dk-balance-value {
  font-size: 21px;
  color: var(--accent-ink);
  font-weight: 750;
  font-variant-numeric: tabular-nums;
  line-height: 1.15;
}

.dk-balance-plan {
  font-size: 11.5px;
  color: var(--ink-3);
}

#desktopKeyValue {
  cursor: pointer;
}

/* Theme corner (legacy profile theme button) */
.theme-corner {
  display: none;
}

/* ============================================================
   UTILITIES
   ============================================================ */
.hide {
  display: none !important;
}

/* ============================================================
   LOADING SPLASH (ТЗ 5) — black screen + pulsing dot on open
   ============================================================ */
#splash {
  position: fixed;
  inset: 0;
  z-index: 300;
  background: #000;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
}

.splash-brand {
  font-family: var(--font-display);
  font-weight: 760;
  font-size: 20px;
  letter-spacing: -0.05em;
  color: #f2f3f1;
}

.splash-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #e8eae7;
  animation: splashPulse 1s ease-in-out infinite;
}

@keyframes splashPulse {

  0%,
  100% {
    opacity: 0.3;
  }

  50% {
    opacity: 1;
  }
}

#splash.splash-out {
  animation: splashOut var(--dur-slow) ease forwards;
}

@keyframes splashOut {
  to {
    opacity: 0;
    visibility: hidden;
  }
}

.status:empty {
  display: none;
}

/* STICKY HEADER (ТЗ 10) removed by owner request 2026-09-27: the translucent
   "slab" over the back button read as a visual glitch. Header stays transparent
   at every scroll position now. */

/* ============================================================
   NEW CHAT FADE (ТЗ 19) — messages fade out before switching
   ============================================================ */
.messages {
  transition: opacity var(--dur-normal) var(--ease);
}

.messages.leaving {
  opacity: 0;
}

/* ============================================================
   SKELETON LOADING (ТЗ 86-87) — faint shimmer
   ============================================================ */
.skeleton {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 4px 2px;
}

.skeleton-bubble {
  align-self: flex-start;
  width: 82%;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.skeleton-bubble.short {
  width: 60%;
}

.skeleton-bubble i {
  display: block;
  height: 11px;
  border-radius: 6px;
  background: var(--soft);
  animation: shimmer 1.6s ease-in-out infinite;
}

.skeleton-bubble i:nth-child(2) {
  width: 88%;
}

.skeleton-bubble i:nth-child(3) {
  width: 64%;
}

@keyframes shimmer {

  0%,
  100% {
    opacity: 0.55;
  }

  50% {
    opacity: 1;
  }
}

/* ============================================================
   REDUCED MOTION (ТЗ 85) — minimize all animation
   ============================================================ */
@media (prefers-reduced-motion: reduce) {

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  .skeleton-bubble i {
    opacity: 0.6 !important;
  }

  .stream-cursor {
    display: none !important;
  }

  .tw-fade {
    animation: none !important;
  }

  .tw-l {
    animation: none !important;
  }
}

/* Persisted app preference mirrors the system accessibility floor. Keep a
   tiny transition for transitionend-based gesture cleanup, no travel delay. */
html.reduce-motion { --dur-fast: 0.01ms; --dur-normal: 0.01ms; --dur-slow: 0.01ms; }
html.reduce-motion *, html.reduce-motion *::before, html.reduce-motion *::after {
  animation-duration: 0.01ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.01ms !important;
  transition-delay: 0s !important;
  scroll-behavior: auto !important;
}
html.reduce-motion .stream-cursor { display: none !important; }

/* Safe interactive geometry; the scroller still bleeds to physical edges. */
.app {
  padding-left: max(10px, env(safe-area-inset-left, 0px), var(--tg-safe-area-inset-left, 0px));
  padding-right: max(10px, env(safe-area-inset-right, 0px), var(--tg-safe-area-inset-right, 0px));
}
.settings-copy { min-width: 0; overflow-wrap: anywhere; }
.settings-row > .switch, .settings-row > .theme-drop { flex-shrink: 0; }
.settings-row { gap: 12px; }
.composer-tools-right { min-width: 0; }
.model-trigger { min-width: 0; max-width: min(45vw, 210px); }
#modelTriggerLabel { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (max-height: 420px) {
  .app { min-height: 0; }
  .model-popup, .confirm-sheet, .ex-chat-sheet { max-height: calc(var(--vh, 100dvh) - 16px); overflow-y: auto; }
}

/* ============================================================
   RESPONSIVE
   ============================================================ */
@media (max-width: 360px) {
  .app {
    padding-inline: 6px;
  }

  .bubble {
    max-width: 93%;
  }

  .quick-action {
    font-size: 12px;
    padding: 0 10px;
  }

  .filter-item {
    font-size: 10px;
  }
}

@media (orientation: landscape) and (max-height: 500px) {
  .app {
    padding-bottom: calc(8px + var(--bottom, 0px));
  }
}

@media (hover: none) {

  .quick-action:active,
  .model-trigger:active,
  .send-btn:active,
  .image-attach:active,
  .filter-item:active,
  .avatar-btn:active,
  .back-btn:active {
    transform: scale(0.97);
  }
}

@media print {

  .composer,
  .quick-actions,
  .header-actions {
    display: none !important;
  }

  .app {
    padding-bottom: 0;
  }
}

/* ============================================================
   THEME SWITCH — snappy-attack ease: the interface itself flows
   into the new theme, ONLY colors change (background-color,
   color, border-color, fill). 220ms, start instant, tail smooth —
   feels fast and pleasant, no paint-heavy properties, no stutter.
   ============================================================ */
html.theme-flow,
html.theme-flow body,
html.theme-flow .app,
html.theme-flow .composer-box,
html.theme-flow .history-panel,
html.theme-flow .profile-banner,
html.theme-flow .settings-row,
html.theme-flow .model-popup,
html.theme-flow .bubble.user,
html.theme-flow .filter-item,
html.theme-flow .switch,
html.theme-flow .theme-drop-btn,
html.theme-flow .history-btn,
html.theme-flow .new-chat-btn,
html.theme-flow .back-btn {
  transition-property: background-color, color, border-color, fill;
  transition-duration: var(--dur-slow);
  transition-timing-function: cubic-bezier(.22, 1, .36, 1);
}

@media (prefers-reduced-motion: reduce) {

  html.theme-flow,
  html.theme-flow *,
  html.theme-flow *::before,
  html.theme-flow *::after {
    transition: none;
  }
}

/* ============================================================
   RESPONSIVE SCALE — sizes follow viewport width, no stretching.
   Small screens get proportionally smaller buttons/type/icons.
   ============================================================ */
body {
  font-size: clamp(12.5px, 3.4vw, 15px);
}

.app {
  max-width: min(100%, 480px);
}

.brand {
  font-size: clamp(15px, 4.2vw, 18px);
}

.header {
  padding-bottom: clamp(4px, 1.2vw, 6px);
}

.avatar-btn,
.theme-toggle {
  width: clamp(28px, 7.8vw, 34px);
  height: clamp(28px, 7.8vw, 34px);
}

.theme-toggle svg {
  width: clamp(17px, 4.8vw, 20px);
  height: clamp(17px, 4.8vw, 20px);
}

.avatar-btn {
  font-size: clamp(12px, 3.4vw, 14px);
}

.history-btn,
.new-chat-btn {
  height: clamp(26px, 7.4vw, 30px);
  padding: 0 clamp(10px, 3vw, 14px);
}

.history-btn svg,
.new-chat-btn svg {
  width: clamp(14px, 4vw, 16px);
  height: clamp(14px, 4vw, 16px);
}

.messages {
  padding-left: clamp(10px, 4.2vw, 18px);
  padding-right: clamp(10px, 4.2vw, 18px);
  gap: clamp(16px, 5.2vw, 26px);
}

.bubble {
  font-size: clamp(12.5px, 3.4vw, 14.5px);
  padding: clamp(8px, 2.4vw, 11px) clamp(10px, 3vw, 13px);
}

.meta {
  font-size: clamp(10px, 2.8vw, 11px);
}

.msg-copy,
.msg-regen,
.msg-share {
  width: clamp(22px, 6.2vw, 26px);
  height: clamp(22px, 6.2vw, 26px);
}

.msg-copy::before,
.msg-regen::before,
.msg-share::before {
  width: clamp(12px, 3.4vw, 14px);
  height: clamp(12px, 3.4vw, 14px);
}

.thinking-card {
  font-size: clamp(11.5px, 3.2vw, 13px);
}

.empty-brand {
  font-size: clamp(18px, 5vw, 22px);
}

.empty-brand::before {
  width: clamp(8px, 2.2vw, 9px);
  height: clamp(8px, 2.2vw, 9px);
}

.empty-title {
  font-size: clamp(16px, 4.6vw, 19px);
}

.empty-sub {
  font-size: clamp(12px, 3.3vw, 13.5px);
}

.suggestion {
  font-size: clamp(11.5px, 3.2vw, 13px);
  padding: clamp(6px, 1.8vw, 8px) clamp(11px, 3.2vw, 14px);
}

.quick-action {
  height: clamp(32px, 8.8vw, 34px);
  font-size: clamp(11.5px, 3.2vw, 13px);
}

.quick-action svg {
  width: clamp(15px, 4.2vw, 17px);
  height: clamp(15px, 4.2vw, 17px);
}

.composer {
  padding-left: clamp(10px, 3.2vw, 14px);
  padding-right: clamp(10px, 3.2vw, 14px);
  padding-bottom: clamp(4px, 1.6vw, 8px);
}

.composer-input textarea {
  font-size: 16px;
  min-height: clamp(26px, 7.2vw, 32px);
  padding: clamp(2px, 0.6vw, 3px) 0;
}

.composer-toolbar {
  min-height: clamp(38px, 10.4vw, 46px);
}

.attach-toggle {
  width: clamp(28px, 7.8vw, 33px);
  height: clamp(28px, 7.8vw, 33px);
}

.attach-toggle svg {
  width: clamp(17px, 4.8vw, 20px);
  height: clamp(17px, 4.8vw, 20px);
}

.send-btn {
  width: clamp(28px, 7.8vw, 34px);
  height: clamp(28px, 7.8vw, 34px);
}

.send-btn svg {
  width: clamp(18px, 5vw, 22px);
  height: clamp(18px, 5vw, 22px);
}

.send-stop {
  width: clamp(9px, 2.6vw, 11px);
  height: clamp(9px, 2.6vw, 11px);
}

.model-trigger {
  font-size: clamp(11px, 3vw, 13px);
}

.model-trigger-arrow svg {
  width: clamp(13px, 3.8vw, 16px);
  height: clamp(13px, 3.8vw, 16px);
}

.scroll-bottom-btn {
  width: clamp(28px, 7.8vw, 34px);
  height: clamp(28px, 7.8vw, 34px);
}

.scroll-bottom-btn svg {
  width: clamp(15px, 4.2vw, 18px);
  height: clamp(15px, 4.2vw, 18px);
}

.model-popup-item {
  font-size: clamp(12px, 3.3vw, 13px);
  padding-top: clamp(9px, 2.6vw, 12px);
  padding-bottom: clamp(9px, 2.6vw, 12px);
}

.model-popup-header {
  font-size: clamp(12px, 3.3vw, 13px);
}

.model-cat {
  font-size: clamp(10px, 2.8vw, 11px);
}

.model-search input {
  font-size: clamp(12px, 3.3vw, 13px);
}

.model-handle {
  width: clamp(32px, 9vw, 36px);
}

.profile-name {
  font-size: clamp(17px, 4.8vw, 19px);
}

.profile-handle,
.tier-label,
.tier-caption {
  font-size: clamp(11px, 3vw, 13px);
}

.settings-copy strong {
  font-size: clamp(13px, 3.6vw, 14px);
}

.settings-copy span {
  font-size: clamp(11px, 3vw, 12px);
}

.confirm-btn {
  height: clamp(40px, 11vw, 44px);
  font-size: clamp(13px, 3.6vw, 14px);
}

.confirm-sheet-title {
  font-size: clamp(15px, 4.2vw, 17px);
}

.confirm-sheet-text {
  font-size: clamp(12px, 3.3vw, 13.5px);
}

.retry-btn {
  font-size: clamp(12px, 3.3vw, 13px);
}

.row strong {
  font-size: clamp(13px, 3.6vw, 15px);
}

.row span {
  font-size: clamp(11px, 3vw, 12px);
}

.toast {
  font-size: clamp(11.5px, 3.2vw, 13px);
}

.history-group {
  font-size: clamp(10px, 2.8vw, 11px);
}

/* ============================================================
   LECTURE RECORDING (5th quick action)
   Sheets follow the app z-index scale: backdrop 650, sheet 700,
   toast 800 stays above. Colors are color-mix over theme tokens.
   ============================================================ */

.lecture-backdrop {
  position: fixed;
  inset: 0;
  z-index: 650;
  background: rgba(0, 0, 0, .65);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-slow) var(--ease);
}

.lecture-backdrop:not(.hide) {
  opacity: 1;
  pointer-events: auto;
}

.lecture-backdrop.hide {
  display: block !important;
  opacity: 0;
  pointer-events: none;
}

.lecture-sheet {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 700;
  width: 100%;
  max-width: 480px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 0 16px calc(14px + var(--bottom, 0px));
  max-height: min(560px, 78dvh);
  background: var(--picker-bg);
  border: 1px solid var(--picker-line);
  border-bottom: 0;
  border-radius: 28px 28px 0 0;
  box-shadow: var(--picker-shadow);
  transform: translateY(100%);
  visibility: hidden;
  transition: transform var(--dur-slow) var(--ease-sheet), visibility var(--dur-slow) var(--ease);
}

.lecture-sheet:not(.hide) {
  transform: translateY(0);
  visibility: visible;
}

.lecture-sheet.hide {
  display: flex !important;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(100%);
}

.lecture-handle {
  width: 36px;
  height: 4px;
  border-radius: 2px;
  background: var(--ink-3);
  opacity: .4;
  margin: 10px auto 2px;
  flex-shrink: 0;
}

.lecture-head {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 2px 2px 0;
}

.lecture-title {
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 700;
  color: var(--ink);
  flex: 1;
  min-width: 0;
}

.lecture-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--danger, #a65d5d);
  flex-shrink: 0;
  animation: lecturePulse 1.6s var(--ease) infinite;
}

.lecture-sheet.is-paused .lecture-dot {
  animation: none;
  opacity: .45;
}

@keyframes lecturePulse {

  0%,
  100% {
    transform: scale(1);
    opacity: 1;
  }

  50% {
    transform: scale(.72);
    opacity: .55;
  }
}

.lecture-timer {
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 15px;
  font-weight: 700;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.lecture-level {
  --lecture-level: 0%;
  height: 6px;
  border-radius: 99px;
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  overflow: hidden;
  flex-shrink: 0;
}

.lecture-level i {
  display: block;
  height: 100%;
  width: var(--lecture-level);
  border-radius: 99px;
  background: color-mix(in srgb, var(--accent) 72%, var(--ink));
  transition: width var(--dur-fast) linear;
}

.lecture-status {
  font-size: 12.5px;
  line-height: 1.35;
  color: var(--ink-2);
}

.lecture-status.is-warn {
  color: color-mix(in srgb, var(--danger, #a65d5d) 78%, var(--ink));
}

.lecture-status.is-error {
  color: var(--danger, #a65d5d);
}

/* Result row: after stop the sheet parks here — title + Конспект/Готово.
   Recording controls hide (`.has-result`), the dot stops pulsing. */
.lecture-result {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--card);
}

.lecture-result-text {
  font-family: var(--font-display);
  font-size: 14px;
  font-weight: 650;
  color: var(--ink);
}

.lecture-result-actions {
  display: flex;
  gap: 10px;
}

.lecture-result-actions .lecture-btn {
  flex: 1;
}

.lecture-sheet.has-result .lecture-level,
.lecture-sheet.has-result .lecture-sens,
.lecture-sheet.has-result .lecture-hint {
  display: none;
}


.lecture-transcript {
  flex: 1;
  min-height: 96px;
  overflow-y: auto;
  overscroll-behavior: contain;
  touch-action: pan-y;
  -webkit-overflow-scrolling: touch;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: color-mix(in srgb, var(--accent) 4%, var(--card));
  scrollbar-width: none;
}

.lecture-transcript::-webkit-scrollbar {
  display: none;
}

.lecture-caption {
  margin: 0 0 8px;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--ink);
  word-break: break-word;
}

.lecture-caption:last-child {
  margin-bottom: 0;
}

.lecture-empty {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--ink-3);
}

.lecture-budget {
  font-size: 11.5px;
  color: var(--ink-2);
}

.lecture-budget.is-over {
  color: color-mix(in srgb, var(--danger, #a65d5d) 78%, var(--ink));
}

.lecture-hint {
  margin: 0;
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--ink-3);
}

.lecture-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  padding-top: 2px;
}

.lecture-btn {
  height: 44px;
  border-radius: 14px;
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: 650;
  cursor: pointer;
}

.lecture-btn.is-primary {
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  background: color-mix(in srgb, var(--accent) 16%, var(--card));
  color: var(--accent-ink);
}

.lecture-btn:active {
  transform: scale(0.98);
}

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

  .lecture-level i {
    transition: none;
  }
}

/* Sensitivity control inside the lecture sheet */
.lecture-sens {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 8px;
}

.lecture-sens-label {
  font-size: 12px;
  color: var(--ink-2);
  white-space: nowrap;
}

.lecture-sens-range {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 22px;
  background: transparent;
  cursor: pointer;
}

.lecture-sens-range::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: 99px;
  background: color-mix(in srgb, var(--accent) 22%, var(--line));
}

.lecture-sens-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 18px;
  height: 18px;
  margin-top: -7px;
  border-radius: 50%;
  background: var(--card);
  border: 2px solid color-mix(in srgb, var(--accent) 65%, var(--ink));
}

.lecture-sens-range::-moz-range-track {
  height: 4px;
  border-radius: 99px;
  background: color-mix(in srgb, var(--accent) 22%, var(--line));
}

.lecture-sens-range::-moz-range-thumb {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--card);
  border: 2px solid color-mix(in srgb, var(--accent) 65%, var(--ink));
}

.lecture-sens-value {
  font-family: var(--font-display);
  font-size: 12.5px;
  font-weight: 650;
  color: var(--ink-2);
  min-width: 104px;
  text-align: right;
  white-space: nowrap;
}

/* Language switch in settings (RU/EN, miniapp-i18n.js) */
.lang-switch {
  display: flex;
  gap: 4px;
  padding: 3px;
  border-radius: 11px;
  background: var(--soft);
  border: 1px solid var(--line);
  flex-shrink: 0;
}

.lang-btn {
  min-height: 28px;
  padding: 0 10px;
  border-radius: 8px;
  background: transparent;
  color: var(--ink-2);
  font-family: var(--font-ui);
  font-size: 12px;
  font-weight: 650;
  cursor: pointer;
  white-space: nowrap;
}

.lang-btn.on {
  background: var(--card);
  color: var(--ink);
  box-shadow: 0 1px 2px color-mix(in srgb, var(--ink) 10%, transparent);
}

/* === Shared answer view (deep link startapp=share-<id>): answer sheet + CTA === */
.share-view {
  position: fixed;
  inset: 0;
  z-index: 720;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgba(0, 0, 0, .62);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
}

.share-panel {
  width: 100%;
  max-width: 560px;
  max-height: min(680px, 92dvh);
  display: flex;
  flex-direction: column;
  background: var(--card);
  border: 1px solid var(--line);
  border-bottom: 0;
  border-radius: 24px 24px 0 0;
  box-shadow: 0 -18px 60px rgba(0, 0, 0, .28);
  padding: 16px 18px calc(16px + var(--bottom, 0px));
  animation: share-sheet-in var(--dur-slow, 320ms) var(--ease, ease);
}

@keyframes share-sheet-in {
  from {
    transform: translateY(28px);
    opacity: 0;
  }

  to {
    transform: translateY(0);
    opacity: 1;
  }
}

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

.share-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
  flex-shrink: 0;
}

.share-logo {
  width: 34px;
  height: 34px;
  flex: 0 0 34px;
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--accent) 14%, var(--soft));
  color: var(--accent);
  font-size: 16px;
  overflow: hidden;
}

.share-logo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  border-radius: 10px;
}

.share-head-txt {
  min-width: 0;
}

.share-title {
  font-size: 14.5px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--ink);
}

.share-model {
  font-size: 12px;
  color: var(--ink-3);
  margin-top: 1px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.share-question {
  flex-shrink: 0;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--ink-2);
  background: var(--soft);
  border-left: 2px solid color-mix(in srgb, var(--accent) 55%, transparent);
  border-radius: 8px;
  padding: 8px 10px;
  margin-bottom: 12px;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  word-break: break-word;
}

.share-content {
  flex: 1 1 auto;
  min-height: 60px;
  overflow-y: auto;
  overscroll-behavior: contain;
  touch-action: pan-y;
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--ink);
  word-break: break-word;
  padding-right: 4px;
  margin-bottom: 14px;
}

.share-cta {
  flex-shrink: 0;
  width: 100%;
  min-height: 48px;
  border: 0;
  border-radius: 14px;
  background: var(--send-bg, var(--accent));
  color: var(--send-ink, #fff);
  font-family: inherit;
  font-size: 15px;
  font-weight: 650;
  letter-spacing: -0.01em;
  padding: 12px 16px;
  cursor: pointer;
}

.share-cta:active {
  opacity: .85;
}

.share-close {
  flex-shrink: 0;
  width: 100%;
  min-height: 40px;
  border: 0;
  border-radius: 12px;
  background: transparent;
  color: var(--ink-3);
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 550;
  padding: 8px 16px;
  margin-top: 4px;
  cursor: pointer;
}

.bubble.streaming .msg-share {
  display: none;
}

/* Copy button in the shared-answer head: same small icon as .msg-copy, pinned right */
.share-copy {
  margin-left: auto;
  flex-shrink: 0;
  align-self: center;
}

/* Text selection + partial copy inside the shared-answer sheet */
.share-content,
.share-question {
  -webkit-user-select: text;
  user-select: text;
  -webkit-touch-callout: default;
}

/* === Native text selection unblock for iOS Telegram WebView (build 20260916d).
   Two real blockers, both proven by the miniapp-responsive.css fix that the
   parallel session carried for v1 ("force text selection, override Telegram
   injected CSS"):
   1) touch-action: pan-y on the scroll container suppresses the WebKit
      long-press text-interaction gesture -> containers that host selectable
      text must be touch-action: auto.
   2) WebKit computes user-select per element and treats it as inherited;
      !important across the whole subtree guarantees text stays selectable no
      matter what ancestor or injected stylesheet says. Buttons opt out so tap
      targets are never dragged into a selection. === */
.messages {
  touch-action: auto !important;
  -webkit-user-select: text !important;
  user-select: text !important;
}

.messages,
.messages * {
  -webkit-user-select: text !important;
  user-select: text !important;
  -webkit-touch-callout: default !important;
}

.messages button,
.messages .bubble-actions,
.messages .media-actions,
.messages .load-older-btn,
.messages .retry-btn,
.messages .source-card {
  -webkit-user-select: none !important;
  user-select: none !important;
  -webkit-touch-callout: none !important;
}

.share-content {
  touch-action: auto !important;
}

.share-view,
.share-view * {
  -webkit-user-select: text !important;
  user-select: text !important;
  -webkit-touch-callout: default !important;
}

.share-view button {
  -webkit-user-select: none !important;
  user-select: none !important;
  -webkit-touch-callout: none !important;
}

/* === Telegram fullscreen (Bot API 8.0+) =====================================
   miniapp-fullscreen.js requests native fullscreen at boot and toggles
   html.tg-fullscreen. The Telegram header becomes transparent there, so the
   app draws its own exit control; look mirrors .back-btn.

   Insets: real clients frequently report contentSafeAreaInset as ZEROES in
   fullscreen, and theme() in the html writes contentSafeAreaInset||safeAreaInset
   into --top/--bottom INLINE -> the header ended up under Telegram's floating
   controls (back/search/close). In fullscreen the variables are owned HERE:
   max() of both Telegram CSS vars plus the per-platform floor (--fs-floor,
   set by the module: android 56 / ios 52 / desktop 0). !important on a custom
   property beats the inline style, and max() stays live when Telegram
   refreshes --tg-*-inset vars (rotation etc). */
html.tg-fullscreen {
  --top: max(var(--tg-content-safe-area-inset-top, 0px),
      calc(var(--tg-safe-area-inset-top, 0px) + var(--fs-floor, 0px))) !important;
  --bottom: max(var(--tg-content-safe-area-inset-bottom, 0px),
      var(--tg-safe-area-inset-bottom, 0px)) !important;
}

/* The chat-history panel is its own fixed layer (inset:0) — pad it too,
   otherwise its header/search slide under the same Telegram controls. */
html.tg-fullscreen .history-panel {
  padding-top: var(--top, 0px);
}

/* === Header chrome (owner request 2026-09-19) ===============================
   The header carries ONLY functional controls: #historyBtn moved into
   .header-left (the old brand slot), #new sits on the right (the old shrink
   button slot). The nodes are relocated at runtime by miniapp-fullscreen.js,
   so their ids/handlers/tour targets survive; leaving fullscreen is a swipe
   down over the header, so no exit button exists anymore. */
.header .history-btn,
.header .new-chat-btn {
  position: static;
  top: auto;
  left: auto;
  right: auto;
  z-index: auto;
  flex-shrink: 0;
}

.header .new-chat-btn {
  margin-left: auto;
}

.header-left .history-btn {
  margin-right: 2px;
}

/* "synapsedev" now docks under the text window: absolutely placed inside the
   .app bottom padding gap, out of flow — the composer never moves. Owner
   call 2026-09-25: the strip must hug the mini app floor. On devices with a
   safe-area inset we spend only HALF of it (the lower half is the gesture
   zone), so on iPhone it sits ~19px from the physical bottom; without an
   inset (desktop/Android nav) it is 2px from the floor. Owner call 2026-09-27:
   the composer sits LOWER — bottom padding spends only HALF of the safe-area
   inset (16px + inset/2), so the air above the strip is ~2px on ANY device:
   full inset made the window hover ~19px above the strip on iPhones. */
.brand.brand-footer {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(var(--bottom, 0px) / 2 + 2px);
  /* Подпись рисуется ВЫШЕ нижнего скрима (z 5): текст проходит под ней
     (лента тянется до кромки приложения), а сама надпись остаётся чёткой. */
  z-index: 7;
  display: block;
  text-align: center;
  font-size: 10px;
  font-weight: 600;
  line-height: 12px;
  letter-spacing: 0.08em;
  color: var(--ink-3);
  opacity: 0.62;
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
}

/* ============================================================
   MOTION + ADAPTIVITY PASS (2026-10-01)
   (ниже — блоки edge-back: правка 2026-10-11, «выход из профиля жестом —
   всё скачет»)
   Поведенческий слой поверх существующего UI: ничего не перекрашиваем,
   не перемещаем и не переразмериваем вне адаптации под viewport.
   ============================================================ */

/* --- Motion: единая система на существующих токенах --- */

/* Back gesture (Profile → Chat): экран следует за пальцем без transition.
   Класс ставит JS edge-back на время жеста, снимает на cancel/commit —
   обратный ход и довершение едут штатным ease. */
html.edge-back-active #profile.view {
  margin: 0;
  transition: none;
  /* ПРОФИЛЬ СНИМАЕТСЯ С ПОТОКА, а не чат (правка 2026-10-11). Причина: peek-чат
     абсолютом с top:auto Chromium ставит НЕ в начало панели, а ПОСЛЕ
     in-flow профиля — замер: top 441 / h 387 вместо top 4 / h 824. В конце
     жеста чат вставал на своё место → скачок на пол-экрана.
     inset: 0 даёт РОВНО ту же коробку, что у профиля в потоке: он был
     единственным flex-ребёнком .main-panel (position: relative, без паддинга)
     и занимал всю её content-box — то есть подмена позиции ничего не двигает,
     а чат остаётся в потоке и сразу имеет финальную геометрию.
     ВАЖНО: peek и лист ВСЕГДА делят одну коробку — коробку .main-panel, и
     лист накрывает peek ЦЕЛИКОМ (полоса, в которой из-под листа торчит чат,
     отклонена владельцем: «ужасно выглядит, профиль меньше, чем чат»).
     Поэтому снятие шапки с потока НЕ смещает лист через top: — лист остаётся
     inset: 0, а уехавшее вверх содержимое возвращается паддингом на том же
     значении (см. выше). Замер probe'ом (--top:47): коробка панели на жесте
     и после коммита 51/777, лента до и после коммита 146 — сдвиг 0px. */
  position: absolute;
  /* Cover the enlarged thread's safe-area bleed too: otherwise 110px of
     chat would appear ABOVE the sheet as soon as edge-back latches. */
  inset: calc(-1 * (var(--top, 0px) + 4px)) 0 calc(-1 * var(--app-pad-b, 16px));
  z-index: 1;
  /* Коробка листа выросла вверх на высоту шапки (см. правило header ниже) —
     отдаём её назад паддингом, иначе содержимое профиля уезжает вверх на H
     в самый первый кадр жеста. Паддинг (не top:) — потому что лист обязан
     накрывать peek ЦЕЛИКОМ (полоса 37px над листом = «профиль меньше чата»,
     отклонено владельцем), а паддинг двигает только контент. */
  padding-top: calc(var(--top, 0px) + 4px + var(--eb-header-h, 0px));
  padding-bottom: var(--app-pad-b, 16px);
  background: var(--bg); /* opaque только на время жеста: чат не просвечивает */
  box-shadow: -24px 0 44px rgba(0, 0, 0, 0.22);
  /* Слой поднимается вместе с классом (одним кадром), а не инлайн-стилем
     посреди жеста: переключение композитинга на латче давало микрорванок. */
  will-change: transform;
  backface-visibility: hidden;
}

/* Чат под профилем во время edge-back: та же раскладка, что .view.on —
   БЕЗ абсолюта, иначе его коробка (см. выше) не совпадает с финальной. */
#chat.peek {
  display: flex;
  flex-direction: column;
  min-height: 0;
  flex: 1;
  position: relative;
  z-index: 0;
  pointer-events: none;
}

/* Жест уже показал чат целиком: его вход (viewIn: opacity 0 → 1 + сдвиг)
   на коммите — это повтор анимации поверх пальца, то есть мигание (замер:
   opacity 0.33 сразу после отпускания). Класс ставит edge-back в armPeek,
   снимает — выход ИЗ чата (наблюдатель в модуле), поэтому обычный тап по
   «Чат» анимацию сохраняет. */
#chat.view.on.gesture-settled {
  animation: none;
}

/* Peek обязан стоять в ЦЕЛЕВОЙ ленте, а не только в целевой коробке: в режиме
   чата шапки нет, поэтому верхний отступ ленты держит место под парящие
   пилюли (html.in-chat .messages, miniapp-exclusive.css). Значение ОДНО и то
   же, иначе на коммите лента садится ниже на (--top + 28 − высота шапки):
   замер при --top:47 → 37.6px, то есть и есть «чат скачет» у владельца.
   Синхронность значения сторожит src/tests/miniappGestureSmooth.test.js. */
html.edge-back-active .messages {
  /* Same reachable first-message position as settled chat; only the
     scrollport extends to y=0, not the resting text under native controls. */
  padding-top: calc(var(--top, 0px) + 48px);
  scrollbar-width: none;
  -ms-overflow-style: none;
}

/* На время жеста: профиль (едет за пальцем) над чатом, шапка — над обоими.

   КОММИТ БЕЗ ПЕРЕКЛАДКИ (владелец, 2026-10-11: «чат всё так и скачет при
   выходе из профиля»). Раньше шапка держала свою строку и в потоке, поэтому
   коробка панели на жестe была ПРОФИЛЬНОЙ (top 88.4 / h 739.6), а после
   коммита становилась чатовой (top 51 / h 777) — лента уезжала на 37.6px
   одним кадром (замер probe'ом: --top:47).

   Теперь шапка стоит ровно там же, но строку не занимает: отрицательный
   margin тянет её (и .main-panel за ней) вверх на её собственную высоту, а
   transform возвращает саму шапку на место 1:1. Верх панели совпадает с
   чатовым (выше шапки ничего не двигается: она последний элемент перед
   панелью), коробки peek и листа — целевые, и на коммите не переезжает
   ничего. Высота строки измеряется в armPeek() ДО этого класса
   (--eb-header-h); без неё правило вырождается в прежнее поведение. */
html.edge-back-active .header {
  position: relative;
  z-index: 2;
  margin-top: calc(-1 * var(--eb-header-h, 0px));
  transform: translate3d(0, var(--eb-header-h, 0px), 0);
}

/* Вход профиля остаётся прежним (viewIn); во время edge-back (класс ставит JS)
   анимация входа отключена — состояние приходит из жеста, не со скачком.
   view-under также переживает settle жеста (JS снимает его при РЕАЛЬНОМ
   ре-входе в профиль, а не в reset) — иначе отмена свайпа переигрывала бы
   вход и профиль вспыхивал (аудит жестов 2026-10-01, P1-3). */
#profile.view.on:not(.view-under) {
  animation: viewIn var(--dur-normal) var(--ease);
}

#profile.view.view-under {
  animation: none;
}

/* История-панель едет тем же ease и той же длительностью, что и фон: раньше
   transform (240ms) отставал от затемнения (200ms) — заметный рассинхрон.
   ПРАВИЛО СОЗНАТЕЛЬНО НЕ ПРИМЕНЕНО (аудит жестов 2026-10-01): панель ведёт
   finger-follow параллельной сессии (.history-overlay.dragging выше) — трогать
   её тайминги без владельца нельзя. Эта пара строк — комментарий, не правило:
   лишняя } здесь раньше проглатывала весь хвост CSS (10 правил, включая
   аффорданс edge-back и чипы без окантовки).
*/

/* Возврат из профиля: бренд в шапке приезжает мягко (вход был мгновенным).
   Ключевой кадр переиспользует существующий backIn (fade + slide). */
.brand:not(.hide):not(.brand-footer) {
  animation: brandIn var(--dur-normal) var(--ease);
}

@keyframes brandIn {
  from {
    opacity: 0;
    transform: translateX(-6px);
  }

  to {
    opacity: 1;
    transform: translateX(0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .brand:not(.hide):not(.brand-footer) {
    animation: none;
  }
}

/* --- Edge-back affordance (Profile → Chat) ---
   Оверлей не участвует в layout (pointer-events: none), виден только во время
   жеста: полупрозрачная вуаль + стрелка, следующая за пальцем. */
.edge-back-drag {
  position: fixed;
  inset: 0;
  z-index: 890;
  display: flex;
  align-items: center;
  pointer-events: none;
  opacity: 0;
  background: linear-gradient(90deg, rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0) 22%);
  -webkit-user-select: none;
  user-select: none;
}

.edge-back-label {
  width: 34px;
  height: 34px;
  margin-left: 8px;
  padding: 7px;
  border-radius: 50%;
  background: var(--card);
  color: var(--ink);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);
  box-sizing: border-box;
  will-change: transform;
}

@media (prefers-reduced-motion: reduce) {
  .edge-back-drag {
    display: none;
  }
}

/* --- Chips: ЕДИНЫЙ СТИЛЬ БЕЗ ОКАНТОВКИ ---
   Reference — существующие .web-chip / .slides-chip (26px / r13 / accent-bg /
   border: none). Приводим к тому же принципу остальные чипы-пилюли;
   визуальная концепция (форма, фон, цвет, размеры) не меняется — уходит только
   рамка. Правило идёт ПОСЛЕ всех тем, чтобы перекрыть ихspecific-цвета рамок. */
.suggestion,
.quick-action,
.quick-action.on,
.media-mode,
.media-mode.on,
.filter-item,
.filter-item.active,
.load-older-btn {
  border: none !important;
  outline: none !important;
}

/* Идентичные по роли фолбэк-темы в exclusive-палитре остаются без окантовки */
[data-theme-exclusive] .suggestion,
[data-theme-exclusive] .quick-action,
[data-theme-exclusive] .media-mode,
[data-theme-exclusive] .filter-item {
  border: none !important;
}

/* --- Адаптивность: интерполяция на ЛЮБОМ viewport ---
   Единая переменная горизонтального паддинга: интерполируется и по ширине,
   и по высоте (landscape), вместо трёх фиксированных брейкпоинтов. */
:root {
  --pad-x: clamp(6px, 2.4vw, 14px);
}

@media (orientation: landscape) {
  :root {
    --pad-x: clamp(5px, 1.2vw + 0.6vh, 10px);
  }
}

.composer,
.quick-actions {
  padding-inline: var(--pad-x);
}

/* Ультра-узкие экраны: больше места контенту, меньше рамкам окна */
@media (max-width: 340px) {
  .app {
    padding-inline: 4px;
  }

  .quick-action {
    padding: 0 9px;
    font-size: 12px;
  }

  .web-chip,
  .slides-chip,
  .reply-chip,
  .media-params-chip {
    padding: 0 8px 0 7px;
  }
}

/* Низкие landscape-viewport (телефон в руке, split-screen): компактные ряды
   вместо налезания — composer ниже, верхние пилюли тоньше, таб-бар уже. */
@media (orientation: landscape) and (max-height: 480px) {
  .composer {
    padding-bottom: 6px;
  }

  .composer-input textarea {
    min-height: 26px;
    max-height: 96px;
  }

  .quick-action {
    height: 30px;
    padding: 0 10px;
  }

  .media-mode {
    min-width: 72px;
    min-height: 27px;
    font-size: 11px;
  }

  #app > .tabs .tab {
    min-height: 26px;
    font-size: 11px;
  }
}

/* Filter-bar (профиль): guard переполнения на узких экранах. Базовый грид
   остаётся как есть —Exclusive-стиль и дальше выигрывает (источник ниже). */
@media (max-width: 480px) {
  .filter-bar {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (max-width: 360px) {
  .filter-bar {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* Тап-зоны чипов в композере на маленьких экранах: не ниже 24px — не «сжимаются
   до нечитаемого состояния» на ультра-малом viewport (визуальный размер тот же). */
@media (max-height: 600px) {
  .web-chip,
  .slides-chip,
  .reply-chip,
  .media-params-chip {
    height: 26px;
  }
}

/* Резкий resize/поворот: высоту шелла уже ведёт существующий переход .app
   (height/min-height 0.25s выше — плавная клавиатура, ТЗ 48-49). Ширину
   сознательно НЕ анимируем: смена ориентации должна перестраиваться мгновенно. */

/* AMBIENT BUBBLES removed (owner, 2026-11-05: «плавающие шары полностью
   убраны») — #bgStarfield canvas is gone, miniapp-v2-bg.js unloaded. */

@media (prefers-reduced-motion: reduce) {
  .syn-star, .syn-star path, .syn-star .syn-ball { animation: none; }
}

/* ============================================================
   ПЛАВАЮЩИЕ ПОВЕРХНОСТИ — СТЕКЛО, А НЕ ГЛУХАЯ ПЛАШКА
   (владелец, 2026-10-10: «кнопки не должны обрезать текст плашкой,
   текст должен проходить за ними»).

   Кнопки чата (история слева, новый чат справа) и панель ввода висят
   НАД лентой сообщений. Непрозрачная заливка работала как крышка:
   строка, уехавшая под кнопку, пропадала целиком и читалась как
   «обрезали текст». Теперь заливка полупрозрачная и под ней настоящий
   backdrop-blur — текст проходит за кнопкой и остаётся виден.
   ============================================================ */
.history-btn,
.new-chat-btn {
  background: color-mix(in srgb, var(--accent-bg) 62%, transparent);
  -webkit-backdrop-filter: blur(14px) saturate(1.4);
  backdrop-filter: blur(14px) saturate(1.4);
}

/* Owner 2026-10-13: denser input (97%) with only 3px of backdrop blur.
   Filter is scoped to .composer-box above, never the entire bottom zone. */

/* Без backdrop-filter полупрозрачная заливка кнопок = каша из текста и полей:
   такие движки получают прежнюю непрозрачную плашку. Окно ввода сюда НЕ входит:
   своё стекло оно больше не использует, а непрозрачная заливка опять спрятала бы
   текст, уходящий под него. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .history-btn,
  .new-chat-btn {
    background: var(--accent-bg);
  }
}

/* ============================================================
   «СРАВНЕНИЕ» И «ЖУРНАЛ» — БЕЗ ОБВОДКИ
   (владелец, 2026-10-10: убрать окантовку у чипов и кнопок).
   Тот же принцип, что у .filter-item/.web-chip выше: форма, фон и цвет
   остаются, уходит только рамка.
   ============================================================ */
.chip,
.chip.on,
.compare-summary,
.minor,
.usage-log-row {
  border: none !important;
  outline: none !important;
}

/* ============================================================
   CHATGPT-МОУШН — ящик истории и «печатная машинка» (владелец, 2026-10-10:
   «анимации открывания истории чатов, как у ChatGPT, плавность появления
   текстовой строки, как у ChatGPT»).

   Что здесь и почему:
   1) Строки ящика приезжают за панелью короткой ЛЕСЕНКОЙ, а не все разом.
      Класс .anim-in вешает nav.js ТОЛЬКО на само открытие и снимает его
      через ~0.5s — поэтому фильтрация поиска и «Показать ещё» перерисовывают
      список без переигрывания входа.
   2) Каретка печати переезжает В КОНЕЦ последней строки ответа (это делает
      chat.js), а CSS добавляет только отступ — раньше каретка стояла
      отдельной строкой ПОД абзацем и текст читался как обрыв фразы.
   3) Фазовая строка («Анализирую запрос…» → «Готовлю ответ…») не подменяется
      рывком: каждое новое значение въезжает снизу (phaseSwap, вешает ux.js).
   ============================================================ */

/* Row cascade (owner, 2026-11-04: «больше плавности и анимированности
   появления истории чатов»): каждая строка въезжает чуть позже предыдущей —
   короткая лесенка за панелью, а не один общий блок. Индекс строки лежит в
   --hi (ставит renderSessions, кап 14 — глубокие ряды за «Показать все…» всё
   равно скрыты, так что дешёвых слоёв максимум ~15). Шапка едет с панелью,
   поиск и футер — с маленькой задержкой, чтобы нижний край «догонял» список. */
.history-overlay.anim-in .history-panel .history-header {
  animation: histItemIn var(--dur-normal) var(--hist-curve) both;
}
.history-overlay.anim-in .history-panel .history-search.on {
  animation: histItemIn var(--dur-normal) var(--hist-curve) 60ms both;
}
/* Строки плывут ВОЛНОЙ, а не ступеньками: длинная анимация (340мс) при коротком
   шаге задержки (20мс) даёт соседним рядам перекрытый ход — лесенка читается
   как одно сплошное движение (владелец, 2026-11-05: «плавно и анимированно»). */
.history-overlay.anim-in .history-panel #sessions > * {
  animation: histItemIn 340ms var(--hist-curve) both;
  animation-delay: calc(40ms + var(--hi, 0) * 20ms);
}
.history-overlay.anim-in .history-panel #sessions > .history-expand-wrap {
  animation-delay: calc(40ms + 14 * 20ms);
}
.history-overlay.anim-in .history-panel .history-newchat {
  animation: histItemIn 300ms var(--hist-curve) 110ms both;
}
.history-overlay.anim-in .history-panel .history-profile {
  animation: histItemIn 300ms var(--hist-curve) 150ms both;
}

@keyframes histItemIn {
  from {
    opacity: 0;
    transform: translateY(8px);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

/* 2. Каретка в строке текста */
.bubble.streaming .message-content .stream-cursor {
  margin-left: 2px;
}

/* 3. Фазовая строка мысли въезжает вместо подмены текста */
.thinking-phase.phase-swap {
  animation: phaseSwap var(--dur-normal) var(--hist-curve) both;
}

/* Thinking shimmer: a dark↔light grey wave sweeps the live phase/reasoning
   line while the card is up (text-clip fallback keeps plain --ink-3). */
@keyframes thinkingShimmer {
  from { background-position: 115% 0; }
  to { background-position: -115% 0; }
}
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .thinking-card:not(.hide) .thinking-phase:not(:empty) {
    background-image: linear-gradient(100deg, var(--ink-3) 30%, var(--ink) 50%, var(--ink-3) 70%);
    background-size: 230% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    color: transparent;
    animation: thinkingShimmer 1.15s linear infinite;
  }
  .thinking-card:not(.hide) .thinking-phase.phase-swap:not(:empty) {
    animation: phaseSwap var(--dur-normal) var(--hist-curve) both, thinkingShimmer 1.15s linear infinite;
  }
}

@keyframes phaseSwap {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
}

/* Карточка мыслей появляется мягко, а не вспышкой display */
.thinking-card:not(.hide) {
  animation: phaseIn var(--dur-normal) var(--ease);
}

@media (prefers-reduced-motion: reduce) {

  .history-overlay.anim-in .history-panel .history-header,
  .history-overlay.anim-in .history-panel .history-search.on,
  .history-overlay.anim-in .history-panel .history-newchat,
  .history-overlay.anim-in .history-panel .history-profile,
  .history-overlay.anim-in .history-panel #sessions > *,
  #messages.cascade .bubble,
  .thinking-phase.phase-swap,
  .thinking-card:not(.hide) .thinking-phase,
  .thinking-card:not(.hide) .thinking-phase.phase-swap,
  .thinking-card:not(.hide) {
    animation: none !important;
  }

  /* Instant show/hide for users who asked for no motion — same states, no travel. */
  .thinking-card,
  .scroll-bottom-btn,
  .send-btn,
  .composer-box,
  .color-popover {
    transition-duration: 1ms !important;
  }
}

/* Generated file cards (design pass 2026-10-24, Claude-inspired): restrained
   neutral warm surfaces from the app tokens, thin subtle borders, monochrome
   text-only buttons (no emoji), clear hierarchy name → type/size/version,
   ≥44px hit targets, safe-area sheets, loading/disabled states. */
.file-job-status-card { margin: 8px 0; color: inherit; }
/* flex-wrap lets .synfc-card-actions drop below the title row when its buttons
   do not fit (narrow phones); without it the row overflows and flex-shrink
   collapses .synfc-card-main to width 0 — text renders one char per line. */
.synfc-generated-card { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin: 8px 0; padding: 12px 14px; border: 1px solid color-mix(in srgb, var(--line, #D9DED9) 75%, transparent); border-radius: 14px; background: var(--card, #fff); }
.synfc-generated-card.is-error { border-color: var(--danger, #a65d5d); }
.synfc-card-icon { flex: 0 0 auto; display: grid; place-items: center; width: 36px; height: 36px; color: var(--ink-2, #6f7a74); background: color-mix(in srgb, var(--ink, #17201D) 5%, var(--card, #fff)); border: 1px solid color-mix(in srgb, var(--line, #D9DED9) 60%, transparent); border-radius: 10px; }
.synfc-card-icon svg { width: 19px; height: 19px; }
/* Office cards: real first-page thumbnail (hydrated by fetch→blobURL — Bearer
   auth can't ride on <img src>) over the generic icon it replaces; on error
   the img is removed and the icon shows. 16:9 box matches slide geometry;
   docx/xlsx pages are cropped from the top. */
.synfc-thumbbox { position: relative; width: 64px; height: 36px; overflow: hidden; }
.synfc-thumbbox > svg { width: 36px; height: 36px; }
.synfc-card-thumb { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: top center; border-radius: inherit; background: #fff; }
.synfc-card-thumb:not([src]) { display: none; }
.synfc-kind { display: inline-block; padding: 1px 6px; border-radius: 6px; font-weight: 600; font-size: 11px; line-height: 1.5; }
.synfc-kind-word { color: #185abd; background: rgba(24, 90, 189, .12); }
.synfc-kind-excel { color: #107c41; background: rgba(16, 124, 65, .12); }
.synfc-kind-ppt { color: #c43e1c; background: rgba(196, 62, 28, .12); }
.synfc-card-main { min-width: 0; flex: 1; }
.synfc-card-main strong { display: block; font-size: 14px; font-weight: 600; line-height: 1.35; color: var(--ink, inherit); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.synfc-card-meta { display: block; margin-top: 2px; font-size: 12px; color: var(--ink-2, #888); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.synfc-card-purpose { display: block; margin-top: 4px; font-size: 12.5px; line-height: 1.4; color: var(--ink-2, #888); }
.synfc-card-meta .synfc-badge { display: inline-block; margin-left: 4px; padding: 1px 7px; border-radius: 999px; font-size: 11px; font-weight: 600; background: color-mix(in srgb, var(--ink, #17201D) 5%, var(--card, #fff)); color: var(--ink-2, #6f7a74); border: 1px solid var(--line, #D9DED9); }
.synfc-card-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; }
.synfc-btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-height: 44px; min-width: 44px; padding: 0 14px; border: 1px solid var(--line, #D9DED9); border-radius: 10px; background: var(--card, #fff); color: var(--ink, inherit); font: 500 13px/1 var(--font-ui, inherit); cursor: pointer; text-align: center; transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease); }
.synfc-btn .synfc-btn-ico { font-size: 14px; line-height: 1; }
/* Monochrome actions: no emoji glyph pseudo-elements — labels carry the
   meaning, event delegation still pins to .synfc-btn-* classes. */
.synfc-btn:hover { background: color-mix(in srgb, var(--ink, #17201D) 4%, var(--card, #fff)); border-color: color-mix(in srgb, var(--ink, #17201D) 22%, var(--line, #D9DED9)); }
.synfc-btn:active { background: color-mix(in srgb, var(--ink, #17201D) 8%, var(--card, #fff)); }
.synfc-btn-primary { border-color: transparent; background: var(--accent-ink, #245B46); color: var(--bg, #F7F8F6); font-weight: 600; }
.synfc-btn-primary:hover { border-color: transparent; background: color-mix(in srgb, var(--accent-ink, #245B46) 86%, var(--ink, #17201D)); }
.synfc-btn-primary:active { background: color-mix(in srgb, var(--accent-ink, #245B46) 76%, var(--ink, #17201D)); }
.synfc-btn:disabled, .synfc-btn[aria-disabled="true"] { opacity: .5; cursor: default; pointer-events: none; }
.synfc-btn.is-loading { color: transparent !important; position: relative; pointer-events: none; }
.synfc-btn.is-loading::after { content: ""; position: absolute; width: 14px; height: 14px; border: 2px solid color-mix(in srgb, var(--ink-2, #888) 40%, transparent); border-top-color: var(--accent, #73c8a5); border-radius: 50%; animation: synfcSpin .7s linear infinite; }
@keyframes synfcSpin { to { transform: rotate(360deg); } }
.synfc-btn.is-pending { opacity: .55; cursor: default; }
.synfc-btn:focus-visible { outline: 2px solid var(--accent, #73c8a5); outline-offset: 2px; }

/* B08: anchored job progress + final explanation. Stages are lifecycle facts
   (server stage_seq), so no timer/progress-bar animation is used here. */
.synfc-progress { margin: 6px 0 10px; border: 1px solid var(--line, rgba(127,127,127,.2)); border-radius: 14px; background: var(--surface, color-mix(in srgb, var(--bg, #fff) 94%, #888 6%)); overflow: hidden; }
.synfc-progress-summary { display: flex; align-items: center; justify-content: space-between; gap: 10px; min-height: 44px; padding: 0 12px; cursor: pointer; list-style: none; font: 600 13px/1.3 var(--font-ui, inherit); color: var(--ink, inherit); }
.synfc-progress-summary::-webkit-details-marker { display: none; }
.synfc-progress-summary:focus-visible { outline: 2px solid var(--accent, #73c8a5); outline-offset: -2px; }
.synfc-progress-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.synfc-progress-count { flex: 0 0 auto; font-size: 11.5px; font-weight: 500; color: var(--ink-2, #888); }
.synfc-progress.is-failed { border-color: color-mix(in srgb, var(--danger, #a65d5d) 45%, var(--line, #ddd)); }
.synfc-stages { margin: 0; padding: 0 12px 10px; list-style: none; display: grid; gap: 6px; }
.synfc-stage { display: flex; align-items: flex-start; gap: 8px; font-size: 12.5px; line-height: 1.35; color: var(--ink-2, #888); }
.synfc-stage.is-done { color: var(--ink, inherit); }
.synfc-stage.is-active { color: var(--accent-ink, #245B46); font-weight: 600; }
.synfc-stage-mark { flex: 0 0 18px; text-align: center; }
.synfc-result { margin: 10px 0 2px; padding: 12px; border: 1px solid var(--line, rgba(127,127,127,.2)); border-radius: 16px; background: var(--card, #fff); color: var(--ink, inherit); }
.synfc-result.is-failed { border-color: color-mix(in srgb, var(--danger, #a65d5d) 45%, var(--line, #ddd)); }
.synfc-result.is-partial { border-color: color-mix(in srgb, var(--warning, #b8860b) 40%, var(--line, #ddd)); }
.synfc-result-head { display: flex; align-items: center; gap: 8px; }
.synfc-result-head strong { min-width: 0; font-size: 14px; line-height: 1.3; }
.synfc-result-summary { margin: 6px 0 0; font-size: 13.5px; line-height: 1.45; overflow-wrap: anywhere; }
.synfc-result-changes, .synfc-result-validation, .synfc-result-provenance { margin: 6px 0 0; font-size: 12px; line-height: 1.4; color: var(--ink-2, #888); overflow-wrap: anywhere; }
.synfc-result-label { margin: 9px 0 2px; font-size: 11px; letter-spacing: .04em; text-transform: uppercase; color: var(--muted, #8e8e93); }
.synfc-result-assumptions { margin: 0; padding-left: 18px; font-size: 12px; line-height: 1.4; color: var(--ink-2, #888); }
.synfc-result-error { margin: 8px 0 0; font-size: 12.5px; line-height: 1.4; color: var(--danger, #a65d5d); overflow-wrap: anywhere; }
.synfc-result-files { display: grid; gap: 8px; margin-top: 10px; }
.synfc-result-file { display: flex; align-items: center; gap: 12px; padding: 14px; border: 1px solid color-mix(in srgb, var(--line, #D9DED9) 75%, transparent); border-radius: 14px; background: var(--surface, color-mix(in srgb, var(--bg, #F7F8F6) 94%, #888 6%)); transition: border-color .15s ease, background .15s ease; }
.synfc-result-file:hover { border-color: color-mix(in srgb, var(--ink, #17201D) 26%, var(--line, #D9DED9)); }
.synfc-result-file-icon { display: grid; place-items: center; flex: 0 0 36px; height: 36px; border: 1px solid color-mix(in srgb, var(--line, #D9DED9) 60%, transparent); border-radius: 10px; color: var(--ink-2, #6f7a74); background: color-mix(in srgb, var(--ink, #17201D) 5%, var(--card, #fff)); }
.synfc-result-file-icon svg { width: 25px; height: 25px; }
.synfc-result-file-main { min-width: 0; min-height: 44px; flex: 1 1 auto; padding: 0; border: 0; border-radius: 4px; background: transparent; color: inherit; text-align: left; font: inherit; cursor: pointer; }
.synfc-result-file-main:focus-visible { outline: 2px solid var(--accent, #73c8a5); outline-offset: 4px; }
.synfc-result-file-main strong { display: block; font-size: 13px; line-height: 1.3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.synfc-result-file-meta { display: block; margin-top: 2px; font-size: 11.5px; color: var(--ink-2, #888); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.synfc-result-actions { display: flex; flex-wrap: wrap; gap: 4px; justify-content: flex-end; }
.synfc-result-actions .synfc-btn { padding: 0 10px; border-color: transparent; background: transparent; border-radius: 8px; font-weight: 500; font-size: 12px; }
.synfc-result-actions .synfc-btn::before { content: none; }
.synfc-result-actions .synfc-btn-primary { border-color: var(--line, #ddd); background: var(--card, #fff); color: var(--ink, inherit); }
.synfc-result-retry { margin-top: 8px; }
@media (max-width: 560px) {
  .synfc-result-file { flex-wrap: wrap; }
  .synfc-result-file-main { flex-basis: calc(100% - 56px); }
  .synfc-result-actions { width: 100%; justify-content: flex-start; border-top: 1px solid var(--line-soft, rgba(127,127,127,.12)); padding-top: 8px; }
  .synfc-result-actions .synfc-btn { flex: 1 1 auto; }
}
@media (prefers-reduced-motion: reduce) {
  .synfc-btn { transition: none; }
  .synfc-progress-summary { transition: none; }
}
@media (max-width: 560px) {
  .synfc-generated-card { align-items: flex-start; }
  .synfc-card-actions { width: 100%; margin-left: 48px; justify-content: flex-start; }
  .synfc-card-actions .synfc-btn { flex: 1 1 auto; }
}
/* Sheets opened from file cards: app sheet look + iOS safe area */
.synfc-panel { padding: 16px; padding-bottom: calc(16px + env(safe-area-inset-bottom, 0px)); }
.synfc-panel > strong { display: block; margin-bottom: 10px; overflow-wrap: anywhere; }
.synfc-file-row { flex-wrap: wrap; }
.synfc-file-row > .synfc-btn { min-height: 44px; padding: 0 12px; font-size: 12px; }

/* === Fixed viewport (owner request 2026-10-19): in chat the page NEVER
   scrolls — the app shell owns the full viewport (header + thread + composer
   stay visible, only #messages scrolls, keyboard handled via --kb/visual
   viewport). Applies even to an empty chat, so no page scrollbar ghost. === */
html.in-chat,
html.in-chat body {
  overflow: hidden;
  height: 100%;
  overscroll-behavior: none;
}

/* === File preview viewer (2026-10-21) =====================================
   Text/code is rendered as escaped, highlighted HTML; images and PDF use real
   object URLs; DOCX/XLSX/PPTX arrive as server-rendered HTML inside a
   sandboxed iframe. The viewer follows the app theme and fills the WebView.

   Safe areas: the viewer is its own fixed layer, so it must reuse the app's
   resolved Telegram geometry (--top/--bottom from miniapp-v2-chat.js theme()
   and the fullscreen rule in miniapp-fullscreen.js) instead of relying on
   env(safe-area-inset-*), which is zero in many Telegram WebViews. env() stays
   as a fallback for a standalone iOS browser. */
.synfc-preview { position: fixed; inset: 0; height: calc(var(--vh, 100dvh) - var(--kb, 0px)); z-index: 100000; display: flex; align-items: stretch; justify-content: center; padding: 0; box-sizing: border-box; background: var(--bg, #f7f6f3); }
.synfc-preview-backdrop { display: none; }
.synfc-preview-panel { position: relative; z-index: 1; width: min(100%, 1080px); height: 100%; display: flex; flex-direction: column; overflow: hidden; border: 0; border-radius: 0; background: var(--bg, #f7f6f3); color: var(--ink, #1c1c1e); }
.synfc-preview-head { flex: 0 0 auto; display: grid; grid-template-columns: minmax(82px, auto) minmax(0, 1fr) 44px; align-items: center; gap: 10px; min-height: 64px; padding: calc(10px + max(var(--top, 0px), env(safe-area-inset-top, 0px))) 12px 10px; border-bottom: 1px solid var(--line, rgba(127,127,127,.18)); background: var(--bg, #f7f6f3); }
.synfc-preview-back { display: inline-flex; align-items: center; justify-content: flex-start; gap: 4px; width: 82px; min-width: 82px; height: 44px; box-sizing: border-box; padding: 0 8px 0 2px; border: 0; background: transparent; color: var(--ink, inherit); font: 600 14px/1 var(--font-ui, inherit); cursor: pointer; }
.synfc-preview-back svg { width: 22px; height: 22px; stroke: currentColor; fill: none; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
.synfc-preview-back:focus-visible, .synfc-preview-close:focus-visible { outline: 2px solid var(--accent, #73c8a5); outline-offset: 3px; border-radius: 10px; }
.synfc-preview-heading { min-width: 0; text-align: center; }
.synfc-preview-title { display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; }
.synfc-preview-kind { display: block; margin-top: 4px; font-size: 10px; text-transform: uppercase; letter-spacing: .08em; color: var(--muted, #8e8e93); }
.synfc-preview-close { display: grid; place-items: center; width: 44px; height: 44px; padding: 0; border: 1px solid var(--line, rgba(127,127,127,.3)); border-radius: 13px; background: var(--card, #fff); color: var(--ink, inherit); font: 300 29px/1 var(--font-ui, inherit); cursor: pointer; }
.synfc-preview-body { flex: 1 1 auto; min-height: 0; overflow: auto; overscroll-behavior: contain; padding: 12px max(12px, env(safe-area-inset-left)) calc(96px + max(var(--bottom, 0px), env(safe-area-inset-bottom, 0px))); background: var(--bg, #f7f6f3); }
.synfc-preview-loading { padding: 28px 12px; text-align: center; color: var(--muted, #8e8e93); }
.synfc-preview-note { margin: 12px 4px 0; font-size: 12px; line-height: 1.45; color: var(--muted, #8e8e93); }
.synfc-preview-img { display: block; max-width: 100%; max-height: 100%; margin: 0 auto; object-fit: contain; border-radius: 8px; }
.synfc-preview-frame { display: block; width: 100%; height: 100%; min-height: 420px; border: 0; border-radius: 10px; background: #fff; }
.synfc-preview-switch { display: flex; gap: 8px; margin: 0 4px 10px; }
.synfc-preview-switch .synfc-btn[aria-pressed="true"] { border-color: color-mix(in srgb, var(--ink, #17201D) 30%, var(--line, #D9DED9)); background: color-mix(in srgb, var(--ink, #17201D) 6%, var(--card, #fff)); color: var(--ink, inherit); font-weight: 600; }
.synfc-preview-actions { position: fixed; z-index: 2; left: 50%; bottom: 0; transform: translateX(-50%); display: flex; justify-content: center; gap: 8px; width: min(100%, 1080px); padding: 10px max(12px, env(safe-area-inset-left)) calc(10px + max(var(--bottom, 0px), env(safe-area-inset-bottom, 0px))); border-top: 1px solid var(--line, rgba(127,127,127,.18)); background: color-mix(in srgb, var(--bg, #f7f6f3) 94%, transparent); backdrop-filter: blur(12px); }
.synfc-action { min-width: 132px; min-height: 44px; padding: 0 16px; border: 1px solid var(--line, #D9DED9); border-radius: 10px; background: var(--card, #fff); color: var(--ink, inherit); font: 500 13px/1 var(--font-ui, inherit); cursor: pointer; }
.synfc-action:hover { background: color-mix(in srgb, var(--ink, #17201D) 4%, var(--card, #fff)); border-color: color-mix(in srgb, var(--ink, #17201D) 22%, var(--line, #D9DED9)); }
.synfc-action-primary { border-color: transparent; background: var(--accent-ink, #245B46); color: var(--bg, #F7F8F6); font-weight: 600; }
.synfc-action-primary:hover { background: color-mix(in srgb, var(--accent-ink, #245B46) 86%, var(--ink, #17201D)); }
.synfc-action:focus-visible { outline: 2px solid var(--accent, #73c8a5); outline-offset: 3px; }
.synfc-pdf-pages { display: grid; gap: 20px; padding: 12px; background: color-mix(in srgb, var(--bg, #fff) 90%, #888 10%); border-radius: 10px; }
.synfc-pdf-page { position: relative; min-height: 160px; color: var(--ink-2, #777); text-align: center; }
.synfc-pdf-page canvas { display: block; height: auto; background: white; box-shadow: 0 2px 12px #0002; border-radius: 2px; }
.synfc-pdf-caption { display: block; margin-top: 8px; font-size: 11px; font-variant-numeric: tabular-nums; color: var(--ink-2, #888); }
.synfc-pdf-pages.hide { display: none; }

/* Capability catalogue: each card opens an existing app workflow. */
.history-skills-head h3, .history-skills-history h3 { margin: 0 0 8px; font-size: 16px; color: var(--ink, inherit); }
.history-skills-head p { margin: 0 0 18px; color: var(--ink-2, #888); font-size: 12px; line-height: 1.5; }
.history-skills-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 230px), 1fr)); gap: 10px; }
.history-skill { display: flex; gap: 10px; min-width: 0; padding: 14px; border: 1px solid var(--line, #ddd); border-radius: 14px; background: var(--card, transparent); }
.history-skill-icon { flex: 0 0 30px; display: grid; place-items: start center; color: var(--ink-2, #888); }
.history-skill-icon svg { width: 24px; height: 24px; }
.history-skill-copy { display: flex; flex-direction: column; align-items: flex-start; min-width: 0; }
.history-skill-copy strong { font-size: 13px; line-height: 1.4; color: var(--ink, inherit); }
.history-skill-copy p { margin: 6px 0 10px; font-size: 12px; line-height: 1.5; color: var(--ink-2, #888); overflow-wrap: anywhere; }
.history-skill-action { margin-top: auto; min-height: 44px; padding: 8px 10px; border: 1px solid var(--line, #ddd); border-radius: 9px; background: transparent; color: var(--ink, inherit); font: inherit; font-size: 12px; cursor: pointer; text-align: left; }
.history-skill-action:hover { background: var(--accent-bg, #8881); }
.history-skill-action:focus-visible { outline: 2px solid var(--accent, #73c8a5); outline-offset: 3px; }
.history-skills-history { margin-top: 24px; padding-top: 18px; border-top: 1px solid var(--line, #ddd); }
.synfc-original-panel { padding: 8px 4px; }
.synfc-code { margin: 0; padding: 14px; border-radius: 10px; background: color-mix(in srgb, var(--bg) 88%, #000 12%); color: var(--ink, #1c1c1e); overflow: auto; font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12.5px; line-height: 1.55; tab-size: 2; white-space: pre; }
.synfc-code code { font: inherit; }
.synfc-tok-comment { color: #6b7280; font-style: italic; }
.synfc-tok-string { color: #0a7a4a; }
.synfc-tok-number { color: #b45309; }
.synfc-tok-keyword { color: #7c3aed; font-weight: 600; }
.synfc-tok-tag { color: #0b6bcb; }
.synfc-table-wrap { overflow: auto; border: 1px solid var(--line, rgba(127,127,127,.22)); border-radius: 10px; }
.synfc-table { border-collapse: collapse; min-width: 100%; font-size: 12.5px; }
.synfc-table th, .synfc-table td { border: 1px solid var(--line, rgba(127,127,127,.2)); padding: 6px 9px; text-align: left; vertical-align: top; white-space: nowrap; }
.synfc-table th { position: sticky; top: 0; background: var(--surface, color-mix(in srgb, var(--bg) 90%, #888 10%)); font-weight: 600; }
html[data-theme="dark"] .synfc-tok-comment, html[data-theme-effective="dark"] .synfc-tok-comment, html.dark .synfc-tok-comment { color: #9aa0a6; }
html[data-theme="dark"] .synfc-tok-string, html[data-theme-effective="dark"] .synfc-tok-string, html.dark .synfc-tok-string { color: #7ee2a8; }
html[data-theme="dark"] .synfc-tok-number, html[data-theme-effective="dark"] .synfc-tok-number, html.dark .synfc-tok-number { color: #f6c177; }
html[data-theme="dark"] .synfc-tok-keyword, html[data-theme-effective="dark"] .synfc-tok-keyword, html.dark .synfc-tok-keyword { color: #c4a7ff; }
html[data-theme="dark"] .synfc-tok-tag, html[data-theme-effective="dark"] .synfc-tok-tag, html.dark .synfc-tok-tag { color: #8ab4f8; }
@media (max-width: 640px) {
  .synfc-preview-panel { width: 100%; }
  .synfc-preview-head { grid-template-columns: 82px minmax(0, 1fr) 44px; }
  .synfc-preview-body { padding: 8px 8px calc(96px + max(var(--bottom, 0px), env(safe-area-inset-bottom, 0px))); }
  .synfc-preview-frame { min-height: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .synfc-preview-backdrop { backdrop-filter: none; }
}

/* === Tablet / wide canvas (≥768px) =====================================
   The root cause of the squashed tablet UI: .app is hard-capped at 480px,
   so on a tablet EVERYTHING — header buttons, feed, composer — sat inside
   a phone-width column. The app itself expands to a comfortable ~960px:
   header controls spread to the real edges, the feed widens, the composer
   is edge-to-edge (wider, not narrower), and bottom sheets lift off the
   edge into floating rounded cards. */
@media (min-width: 768px) {
  .app {
    max-width: 960px;
  }
  .model-popup,
  .confirm-sheet,
  .lecture-sheet {
    width: min(560px, calc(100vw - 48px));
    max-width: 560px;
    border-radius: 24px;
    border-bottom: 1px solid var(--picker-line, var(--line));
    bottom: calc(20px + var(--bottom, 0px));
    max-height: min(560px, 74dvh);
  }
}

/* === Motion polish pass (20261102p) =====================================
   What animated well already stays untouched; this fills the gaps found in
   the audit: context chips popped in raw, mode pills snapped between states,
   library lists and cards materialised at once, the media-mode strip and
   note flickered in. All entrances share the app tokens and respect the
   global prefers-reduced-motion gate. */

@keyframes chipIn {
  from { opacity: 0; transform: scale(0.78) translateY(3px); }
}

@keyframes riseIn {
  from { opacity: 0; transform: translateY(7px); }
}

@keyframes fadeSoft {
  from { opacity: 0; }
}

/* Context chips — pop in like a pill, not a flash of text. */
.slides-chip:not(.hide),
.slide-edit-chip:not(.hide),
.web-chip:not(.hide),
.chip:not(.hide),
.file-chip {
  animation: chipIn var(--dur-normal) var(--ease) both;
}

/* Mode pills — the .on state glides instead of snapping colour. */
.media-mode {
  transition:
    background var(--dur-normal) var(--ease),
    border-color var(--dur-normal) var(--ease),
    color var(--dur-normal) var(--ease),
    transform var(--dur-fast) var(--ease);
}

.media-mode-bar:not(.hide) {
  animation: riseIn var(--dur-normal) var(--ease);
}

.media-note:not(.hide) {
  animation: fadeSoft var(--dur-normal) var(--ease);
}

/* Library lists — rows cascade in, capped at 10 items of stagger so a long
   history doesn't delay the tail for a second. */
.history-lib-row,
.history-lib-cell,
.history-skill {
  animation: riseIn var(--dur-normal) var(--ease) both;
}

.history-lib-list > *:nth-child(2) { animation-delay: 30ms; }
.history-lib-list > *:nth-child(3) { animation-delay: 60ms; }
.history-lib-list > *:nth-child(4) { animation-delay: 90ms; }
.history-lib-list > *:nth-child(5) { animation-delay: 120ms; }
.history-lib-list > *:nth-child(6) { animation-delay: 150ms; }
.history-lib-list > *:nth-child(n+7) { animation-delay: 180ms; }

.history-lib-grid > *:nth-child(2) { animation-delay: 30ms; }
.history-lib-grid > *:nth-child(3) { animation-delay: 60ms; }
.history-lib-grid > *:nth-child(4) { animation-delay: 90ms; }
.history-lib-grid > *:nth-child(n+5) { animation-delay: 120ms; }

.history-skills-grid > *:nth-child(2) { animation-delay: 30ms; }
.history-skills-grid > *:nth-child(3) { animation-delay: 60ms; }
.history-skills-grid > *:nth-child(4) { animation-delay: 90ms; }
.history-skills-grid > *:nth-child(5) { animation-delay: 120ms; }
.history-skills-grid > *:nth-child(6) { animation-delay: 150ms; }
.history-skills-grid > *:nth-child(n+7) { animation-delay: 180ms; }

/* Document card lands with the same rise as bubbles. */
.doc-card {
  animation: riseIn var(--dur-normal) var(--ease);
}

/* Source cards inside a fresh answer cascade gently. */
.sources-cards .source-card {
  animation: riseIn var(--dur-normal) var(--ease) both;
}
.sources-cards .source-card:nth-child(2) { animation-delay: 40ms; }
.sources-cards .source-card:nth-child(3) { animation-delay: 80ms; }
.sources-cards .source-card:nth-child(n+4) { animation-delay: 120ms; }

/* Empty state greets once — brand, then suggestions cascade. */
.empty-state {
  animation: fadeSoft var(--dur-slow) var(--ease);
}

.empty-suggestions .suggestion {
  animation: chipIn var(--dur-normal) var(--ease) both;
}
.empty-suggestions .suggestion:nth-child(2) { animation-delay: 60ms; }
.empty-suggestions .suggestion:nth-child(3) { animation-delay: 120ms; }
.empty-suggestions .suggestion:nth-child(4) { animation-delay: 180ms; }

@media (prefers-reduced-motion: reduce) {
  .slides-chip:not(.hide),
  .slide-edit-chip:not(.hide),
  .web-chip:not(.hide),
  .chip:not(.hide),
  .file-chip,
  .media-mode-bar:not(.hide),
  .media-note:not(.hide),
  .history-lib-row,
  .history-lib-cell,
  .history-skill,
  .doc-card,
  .sources-cards .source-card,
  .empty-state,
  .empty-suggestions .suggestion {
    animation: none;
  }
}

/* Image cells in the library grid — press feedback matches rows. */
.history-lib-thumb {
  transition: transform var(--dur-fast) var(--ease);
}
.history-lib-thumb:active {
  transform: scale(0.97);
}

/* ============================================================
   HISTORY DRAWER — consolidated chrome (rebuilt 20261105ag)
   One authoritative block for the drawer face: header, search,
   section tabs, session list, bottom bar. The scattered rules
   that accumulated across the file were removed; this section
   is the only place these selectors are styled.
   DOM hooks (JS): .history-overlay.open/.lib-mode/.times-on/.anim-in,
   .history-libtabs/.history-libtab[.on] (+.history-libtab-count),
   #sessions .row[data-s] > .row-time + strong + span + .row-actions
   (>.row-act/.row-pin/.row-archive/.row-del), .history-expand(-wrap),
   .history-empty, [data-load-more], .history-footer children.
   ============================================================ */

/* ---- Header: brand left, search + collapse icons right ---- */
.history-header {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 16px 12px 12px 20px;
  position: relative;
  flex-shrink: 0;
  color: var(--ink);
}

/* Inset hairline under the top block — ends stay clear of the edges. */
.history-header::after {
  content: '';
  position: absolute;
  left: var(--hairline-inset);
  right: var(--hairline-inset);
  bottom: 0;
  height: 1px;
  background: var(--line);
  pointer-events: none;
}

/* While the search field is open the hairline belongs to it — the field is
   the header's direct sibling (the tab rail sits below the field). */
.history-header:has(+ .history-search.on)::after {
  display: none;
}

.history-header span {
  font-family: var(--font-display);
  font-size: clamp(18px, 5.2vw, 21px);
  font-weight: 760;
  letter-spacing: -0.03em;
  line-height: 1;
}

.history-search-icon,
.history-close {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 0;
  background: var(--soft);
  color: var(--ink-2);
  display: grid;
  place-items: center;
  cursor: pointer;
  flex-shrink: 0;
}

.history-search-icon {
  margin-left: auto;
}

.history-search-icon svg,
.history-close svg {
  width: 18px;
  height: 18px;
  display: block;
}

.history-search-icon.on {
  background: var(--accent-bg);
  color: var(--accent);
}

@media (hover: hover) {
  .history-search-icon:hover,
  .history-close:hover {
    background: var(--line);
    color: var(--ink);
  }
}

/* ---- Collapsible search (header icon reveals the field) ---- */
.history-search {
  display: none;
  padding: 0 12px 10px;
  position: relative;
  flex-shrink: 0;
}

.history-search.on {
  display: block;
  animation: histSearchIn var(--dur-normal) var(--ease);
}

.history-search.on::after {
  content: '';
  position: absolute;
  left: var(--hairline-inset);
  right: var(--hairline-inset);
  bottom: 0;
  height: 1px;
  background: var(--line);
  pointer-events: none;
}

.history-search input {
  width: 100%;
  padding: 9px 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--soft);
  color: var(--ink);
  font-size: 14px;
  outline: 0;
  box-sizing: border-box;
}

.history-search input:focus {
  border-color: color-mix(in srgb, var(--accent) 55%, var(--line));
}

.history-search input::placeholder {
  color: var(--ink-3);
}

@keyframes histSearchIn {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .history-search.on { animation-duration: 1ms; }
}

/* ---- Section tabs: Чаты / Изображения / Файлы / Инструменты ---- */
.history-libtabs {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 4px 10px 12px;
  flex-shrink: 0;
  position: relative;
}

.history-libtabs::after {
  content: '';
  position: absolute;
  left: var(--hairline-inset, 24px);
  right: var(--hairline-inset, 24px);
  bottom: 0;
  height: 1px;
  background: var(--line);
  pointer-events: none;
}

.history-libtab {
  width: 100%;
  /* «Чуть меньше» (owner, 2026-11-05): nav rows and type down a notch. */
  min-height: 40px;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 12px;
  border: 0;
  border-radius: 12px;
  background: transparent;
  color: var(--ink-2);
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: 550;
  letter-spacing: 0.01em;
  text-align: left;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.history-libtab svg {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}

.history-libtab > span:not(.history-libtab-count) {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.history-libtab-count {
  flex-shrink: 0;
  min-width: 22px;
  padding: 2px 7px;
  border-radius: 999px;
  background: var(--soft);
  color: var(--ink-2);
  font-size: 11px;
  font-weight: 700;
  line-height: 1.4;
  text-align: center;
}

.history-libtab.on {
  background: var(--accent-bg);
  color: var(--accent-ink);
}

.history-libtab.on .history-libtab-count {
  background: transparent;
  box-shadow: inset 0 0 0 1px currentColor;
  color: inherit;
}

@media (hover: hover) {
  .history-libtab:hover { background: var(--soft); color: var(--ink); }
  .history-libtab.on:hover { background: var(--accent-bg); color: var(--accent-ink); }
}

.history-libtab:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.history-libtab:active {
  transform: scale(0.99);
}

@media (prefers-reduced-motion: reduce) {
  .history-libtab,
  .history-libtab:active { transition-duration: 1ms; }
}

/* ---- Session list: flat single-line rows, no separators ---- */
.history-panel #sessions {
  flex: 1;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;
  -ms-overflow-style: none;
  padding-bottom: 8px;
}

.history-panel #sessions::-webkit-scrollbar {
  display: none;
}

/* Group labels («Сегодня/Вчера/Ранее») stay hidden — flat ChatGPT list. */
.history-panel #sessions .history-group {
  display: none;
}

.history-panel #sessions .row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: 0;
  align-items: center;
  position: relative;
  width: 100%;
  /* «Чуть меньше» (owner, 2026-11-05): rows and type down a notch. */
  min-height: 38px;
  padding: 7px 16px 7px 14px;
  border: 0;
  background: transparent;
  cursor: pointer;
  color: var(--ink);
  text-align: left;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  touch-action: manipulation;
}

.history-panel #sessions .row > strong {
  grid-column: 2;
  grid-row: 1;
  min-width: 0;
  text-align: left;
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: 550;
  line-height: 1.25;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The «N сообщений» meta line is hidden in the drawer (kept elsewhere). */
.history-panel #sessions .row > span:not(.row-time) {
  display: none;
}

@media (pointer: coarse) {
  .history-panel #sessions .row { min-height: 40px; }
}

/* Active chat: a quiet full-width tint, not a marker dot. */
.history-panel #sessions .row.active {
  background: var(--soft);
}

/* Archived chats: muted rows in the dedicated group. */
.history-panel #sessions .row.archived {
  opacity: 0.6;
}

.history-panel #sessions .row.archived:active {
  opacity: 1;
}

/* Swipe-right reveals the timestamp gutter (iOS-style, .times-on on overlay;
   gutter width carries the gap — titles sit flush when it is collapsed).
   Right padding keeps the stamp off the title edge (owner, 2026-11-05:
   «время прижимается к тексту чата — сдвинь левее»). */
.history-panel #sessions .row > .row-time {
  grid-column: 1;
  grid-row: 1;
  display: block;
  width: 0;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  padding-right: 8px;
  color: var(--ink-3);
  font-family: var(--font-ui);
  font-size: 10.5px;
  font-weight: 600;
  opacity: 0;
  text-align: right;
  transition: width var(--dur-normal) var(--ease), opacity var(--dur-normal) var(--ease);
}

.history-overlay.times-on .history-panel #sessions .row-time {
  width: 40px;
  opacity: 0.85;
}

@media (prefers-reduced-motion: reduce) {
  .history-panel #sessions .row-time { transition-duration: 1ms; }
}

/* Hold-to-reveal row actions (pin/rename/export/archive/delete): hidden in
   every row, shown only on the held row (.acts-on from nav.js long-press). */
.history-panel #sessions .row .row-actions {
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  gap: 2px;
}

.history-panel #sessions .row .row-act {
  width: 28px;
  height: 28px;
  border-radius: 8px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--ink-3);
  display: grid;
  place-items: center;
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.history-panel #sessions .row .row-act svg {
  width: 14px;
  height: 14px;
}

.history-panel #sessions .row.acts-on .row-act,
.history-panel #sessions .row:has(.row-act:focus-visible) .row-act {
  opacity: 1;
  pointer-events: auto;
}

.history-panel #sessions .row.acts-on,
.history-panel #sessions .row.acts-on:hover {
  background: var(--card);
}

.history-panel #sessions .row.acts-on .row-actions {
  background: var(--card);
  border-radius: 10px;
  padding: 2px;
}

@media (pointer: coarse) {
  .history-panel #sessions .row .row-act { width: 32px; height: 32px; }
  .history-panel #sessions .row .row-act svg { width: 16px; height: 16px; }
}

.row-pin.on,
.row-archive.on {
  opacity: 1;
  color: var(--accent);
}

.row-pin.on svg {
  fill: currentColor;
}

.history-panel #sessions .row.confirm .row-del {
  opacity: 1;
  border-color: var(--danger);
  background: var(--danger);
  color: #fff;
}

/* «Показать все…» expander + load-more row: quiet affordance, not a chat. */
.history-expand-wrap {
  padding: 2px 14px;
}

.history-expand {
  background: none;
  border: 0;
  padding: 8px 0;
  color: var(--ink-3);
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

@media (hover: hover) {
  .history-expand:hover { color: var(--ink-2); }
}

.history-panel #sessions .row[data-load-more] > strong,
.history-panel #sessions .row[data-expand-all] > strong,
.history-panel #sessions .row[data-collapse-all] > strong {
  color: var(--ink-3);
  font-size: 13px;
}

.history-empty {
  padding: 20px;
  text-align: center;
  color: var(--ink-3);
}

/* Non-chat tabs swap the session list for the library panel. */
.history-overlay.lib-mode #sessions,
.history-overlay.lib-mode .history-search {
  display: none;
}

/* ---- Bottom bar: «Чат» pill left + round profile button right ---- */
.history-footer {
  position: relative;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 14px calc(12px + var(--bottom, 0px));
}

.history-footer::before {
  content: '';
  position: absolute;
  left: var(--hairline-inset);
  right: var(--hairline-inset);
  top: 0;
  height: 1px;
  background: var(--line);
  pointer-events: none;
}

.history-newchat {
  display: inline-flex;
  width: auto;
  margin: 0;
  height: 42px;
  padding: 0 20px;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border: 0;
  border-radius: 999px;
  background: var(--accent, #2C6BED);
  color: #fff;
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  box-shadow: var(--shadow-action, 0 4px 14px rgba(0, 0, 0, 0.25));
  transition: transform var(--dur-fast) var(--ease);
}

.history-newchat:active {
  transform: scale(0.985);
}

.history-newchat svg {
  width: 16px;
  height: 16px;
}

.history-profile {
  width: auto;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  padding: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
}

@media (hover: hover) {
  .history-profile:hover { background: var(--soft); }
}

.history-profile-avatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--soft);
  display: grid;
  place-items: center;
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 700;
  color: var(--ink);
  flex-shrink: 0;
  overflow: hidden;
  /* Accent ring in EVERY theme (owner, 2026-11-05) — same accent outline as
     .profile-avatar; tier themes animate it via their pulse below. */
  box-shadow: 0 0 0 2px var(--accent);
}

.history-profile-copy,
.history-profile-chev {
  display: none;
}

/* Seam wedges (ChatGPT parity): the card's rounded left corners expose the
   stage — the reference shows PANEL-coloured crescents there instead. Two
   SMALL squares (owner: no stretched full-height strip) ride the card's
   left edge; JS positions them per-frame during drags. */
.history-notch {
  position: absolute;
  /* Anchor: the card's corner arc centre sits at the element's far corner,
     so left = edge − (size − radius) and the ring wraps the arc. Edge =
     column left + translate (same terms as .app above). */
  left: calc(max(0px, 50vw - 240px) + min(78vw, 320px) - 20px);
  width: 56px;
  height: 56px;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--hist-commit-ms) var(--hist-curve);
}
/* Crescent fill + CURVED corner shadow (owner: «углы без тени»): a soft
   dark ring hugging the card's 36px corner arc, fading to panel colour —
   the straight inset seam shadow on the panel edge joins it tangentially,
   so the shadow follows the card silhouette around the corners too. */
.history-notch.t {
  top: -20px;
  background: radial-gradient(circle 56px at 100% 100%, transparent 35.5px, rgba(0, 0, 0, 0.16) 41px, var(--card) 53px);
}
.history-notch.b {
  bottom: -20px;
  background: radial-gradient(circle 56px at 100% 0%, transparent 35.5px, rgba(0, 0, 0, 0.16) 41px, var(--card) 53px);
}

.history-overlay.open .history-notch {
  opacity: 1;
}
/* Drag frames write opacity per-move — transitions would smear the finger. */
.history-overlay.dragging .history-notch {
  transition: none;
}

/* Row context menu (ChatGPT parity, owner 2026-11-05): on a row HOLD a
   floating menu pops inside the history overlay — icon quick-bar, chat
   preview card and the action list. Replaces the inline .acts-on strip. */
.rowmenu-backdrop {
  position: absolute;
  inset: 0;
  z-index: 70;
  background: rgba(0, 0, 0, 0.10);
}

.rowmenu {
  position: absolute;
  z-index: 71;
  display: flex;
  flex-direction: column;
  gap: 8px;
  animation: rowmenu-pop .16s ease;
}

@keyframes rowmenu-pop {
  from { opacity: 0; transform: scale(.92) translateY(-6px); }
  to   { opacity: 1; transform: none; }
}

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


.rm-preview,
.rm-list {
  background: var(--card);
  border-radius: 18px;
  box-shadow: 0 12px 36px rgba(0, 0, 0, 0.16);
  overflow: hidden;
}

.rm-preview { padding: 14px 16px; }

.rm-preview strong {
  display: block;
  font-size: 15px;
  font-weight: 650;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rm-preview p {
  margin: 6px 0 0;
  font-size: 13px;
  line-height: 1.4;
  color: var(--ink-2);
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.rm-act {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 10px 16px;
  border: 0;
  background: none;
  font: inherit;
  font-size: 14px;
  color: var(--ink);
  text-align: left;
  cursor: pointer;
}

.rm-act svg {
  width: 18px;
  height: 18px;
  color: var(--ink-2);
  flex-shrink: 0;
}

.rm-act:active { background: var(--soft); }
.rm-act.danger, .rm-act.danger svg { color: #d43a3a; }
.rm-act + .rm-act { border-top: 1px solid var(--line); }

/* ============================================================
   ai-chat-input port (2026-11-12, vanilla adaptation): pill composer
   that springs open on input, attachment tray sliding out from
   behind the box, morphing control labels, icon-morph send button
   (arrow/mic/stop), voice dictation overlay, FLIP image preview.
   Spring physics identical to the reference component.
   ============================================================ */
:root {
  --spring: cubic-bezier(0.175, 0.885, 0.32, 1.275);
  /* Softer spring for the composer morph itself — gentle overshoot, no
     jelly: the box and its content share one duration via --morph. */
  --spring-soft: cubic-bezier(0.32, 1.18, 0.45, 1);
  --morph: 0.46s var(--spring-soft);
}

/* --- Box: height/radius join the transition list; JS drives height
   inline while .is-pill swaps the column layout for a single row. --- */
.composer-box {
  position: relative;
  z-index: 1;
  transition: border-color var(--dur-normal) var(--ease), box-shadow var(--dur-normal) var(--ease), height var(--morph), border-radius var(--morph);
}

.composer-box.is-pill {
  display: flex;
  align-items: center;
  height: 48px;
  border-radius: 26px;
  overflow: hidden;
}

/* Same clip during the height morph: children hold their final layout
   while the border sweeps over them (accordion reveal, not fly-in). */
.composer-box.box-anim {
  overflow: hidden;
}

/* Morph choreography: the content rides with the border instead of
   teleporting into place. Expanding: the toolbar row and the input row
   ease down/up into their slots; collapsing: the pill-row items slide up
   into the single row. Scoped to .box-anim so it runs only during the
   460 ms morph window, never on plain renders. */
.composer-box.box-anim:not(.is-pill) .composer-toolbar {
  animation: tb-rise var(--morph);
}
.composer-box.box-anim:not(.is-pill) .composer-input {
  animation: input-lift var(--morph);
}
.composer-box.is-pill.box-anim .attach-toggle,
.composer-box.is-pill.box-anim .composer-toolbar-right {
  animation: tb-settle var(--morph);
}
.composer-box.is-pill.box-anim .composer-input {
  animation: input-settle var(--morph);
}
@keyframes tb-rise {
  from { opacity: 0; transform: translateY(-14px); }
}
@keyframes input-lift {
  from { opacity: 0.5; transform: translateY(12px); }
}
@keyframes tb-settle {
  from { opacity: 0; transform: translateY(14px); }
}
@keyframes input-settle {
  from { opacity: 0.5; transform: translateY(-8px); }
}

/* In pill mode the toolbar dissolves into the box's flex row:
   [ + ] [ input ] [ profile? · model · effort · send ] — like the
   reference's collapsed 48px pill. */
.composer-box.is-pill .composer-ctx,
.composer-box.is-pill .file-chips {
  display: none !important;
}

.composer-box.is-pill .composer-input {
  order: 2;
  flex: 1 1 auto;
  min-width: 0;
  padding: 0 4px;
}

.composer-box.is-pill .composer-input textarea {
  min-height: 24px;
  height: 24px !important;
  /* autogrow writes inline px — pill pins the single line */
  padding: 1px 0;
  overflow: hidden;
}

.composer-box.is-pill .composer-toolbar {
  display: contents;
}

.composer-box.is-pill .attach-toggle {
  order: 1;
  margin-left: 8px;
}

.composer-box.is-pill .improve-star {
  display: none !important;
}

.composer-box.is-pill .composer-toolbar-right {
  order: 3;
  margin-left: 0;
  gap: 4px;
  padding-right: 8px;
}

/* Labels never squeeze: overflow:hidden + min-width:0 make them
   shrink-to-zero flex items, so the 48px row was clipping morph text
   from both sides ("Авт…", "ediu"). The textarea's flex:1 + min-width:0
   already absorbs all row pressure — it alone may shrink. */
.composer-box.is-pill .composer-toolbar-right,
.composer-box.is-pill .composer-toolbar-right > * {
  flex: none;
}

/* --- Attachment tray: #fileChips is a sibling BEFORE .composer-box
   (files.js). A rounded-top tab that tucks its bottom edge under the
   box and slides up from behind; JS animates height/padding inline,
   .hide stays mounted-but-inert so the reveal can run. --- */
.composer>.file-chips.tray-host {
  display: flex;
  position: relative;
  z-index: 0;
  box-sizing: border-box;
  margin: 0 10px -9px;
  padding: 0 10px;
  height: 0;
  border: 1px solid var(--line);
  border-bottom: 0;
  border-radius: 16px 16px 0 0;
  background: color-mix(in srgb, var(--soft) 82%, var(--card));
  overflow: hidden;
  visibility: hidden;
  transition: height var(--morph), padding-top var(--morph), padding-bottom var(--morph), visibility 0s linear 0.46s;
}

.composer>.file-chips.tray-host.tray-open {
  padding-top: 8px;
  padding-bottom: 14px;
  visibility: visible;
  transition: height var(--morph), padding-top var(--morph), padding-bottom var(--morph), visibility 0s;
}

.composer>.file-chips.tray-host.hide {
  /* JS owns the collapse — beat the global display:none */
  display: flex !important;
}

.composer>.file-chips.tray-host .file-chip {
  animation: chipIn 0.4s var(--spring) backwards;
}

.composer>.file-chips.tray-host .file-chip:nth-child(2) {
  animation-delay: 35ms;
}

.composer>.file-chips.tray-host .file-chip:nth-child(3) {
  animation-delay: 70ms;
}

.composer>.file-chips.tray-host .file-chip:nth-child(4) {
  animation-delay: 105ms;
}

.composer>.file-chips.tray-host .file-chip:nth-child(5) {
  animation-delay: 140ms;
}

.composer>.file-chips.tray-host .file-chip:nth-child(6) {
  animation-delay: 175ms;
}

.composer>.file-chips.tray-host .file-chip:nth-child(n+7) {
  animation-delay: 210ms;
}

@keyframes chipIn {
  from {
    opacity: 0;
    transform: translateY(10px) scale(0.9);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

.composer>.file-chips.tray-host .file-chip-thumb {
  cursor: zoom-in;
}

/* --- Send icon stack: three icons layered, classes pick the live one.
   setSendUi still toggles .busy; composer.js toggles .send-voice (empty
   draft + mic support) and .send-rec (recording). --- */
.send-btn {
  position: relative;
}

.send-ic {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: opacity 0.3s var(--spring), transform 0.3s var(--spring), filter 0.3s var(--ease);
}

.send-ic-arrow svg {
  width: 22px;
  height: 22px;
  fill: var(--send-ink);
}

.send-ic-mic svg {
  width: 19px;
  height: 19px;
  fill: none;
}

.send-ic-mic,
.send-ic-stop {
  opacity: 0;
  transform: scale(0.5) rotate(45deg);
  filter: blur(1px);
}

.send-btn.send-voice .send-ic-arrow,
.send-btn.send-rec .send-ic-arrow,
.send-btn.busy .send-ic-arrow,
.send-btn.busy .send-ic-mic,
.send-btn.send-rec .send-ic-mic,
.send-btn.send-voice .send-ic-stop {
  opacity: 0;
  transform: scale(0.5) rotate(-45deg);
  filter: blur(1px);
}

.send-btn.send-voice .send-ic-mic {
  opacity: 1;
  transform: none;
  filter: none;
}

.send-btn.busy .send-ic-stop,
.send-btn.send-rec .send-ic-stop {
  opacity: 1;
  transform: none;
  filter: none;
}

/* --- Effort pill (auto-mode level cycler): visible only while auto
   routing is on — updateLabel marks .composer-box.auto-model. Collapses
   by width so the toolbar reflows smoothly. --- */
.effort-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 28px;
  padding: 0 8px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--ink-3);
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  overflow: hidden;
  max-width: 130px;
  opacity: 1;
  transition: max-width 0.3s var(--spring), opacity 0.25s var(--ease), padding 0.3s var(--spring), background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.composer-box:not(.auto-model) .effort-pill {
  max-width: 0;
  padding: 0;
  opacity: 0;
  pointer-events: none;
}

.effort-bars {
  display: inline-flex;
  align-items: flex-end;
  gap: 2px;
  height: 12px;
  flex-shrink: 0;
}

.effort-bars i {
  width: 3px;
  border-radius: 1px;
  background: currentColor;
  opacity: 0.3;
  transition: opacity 0.3s var(--ease), background 0.3s var(--ease);
}

.effort-bars i:nth-child(1) {
  height: 5px;
  opacity: 1;
}

.effort-bars i:nth-child(2) {
  height: 8px;
}

.effort-bars i:nth-child(3) {
  height: 12px;
}

.effort-pill.lv-medium .effort-bars i:nth-child(2),
.effort-pill.lv-high .effort-bars i,
.effort-pill.lv-max .effort-bars i {
  opacity: 1;
}

.effort-pill.lv-max .effort-bars i {
  background: var(--accent);
}

/* --- Morphing label: invisible sizer sets the width, an absolute face
   carries the animated text. Used by #modelTriggerLabel + #effortLabel. --- */
.morph-label {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  white-space: nowrap;
  transition: width 0.3s var(--spring);
}

.morph-label>.ml-sizer {
  visibility: hidden;
  white-space: nowrap;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}

.morph-label>.ml-face {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.morph-label>.ml-face.swap {
  animation: mlSwap 0.3s var(--ease) both;
}

/* Width caps for the two live morph labels — the sizer must clamp before
   measurement or a long model name would stretch the trigger. */
#modelTriggerLabel.morph-label {
  max-width: 118px;
}

.effort-pill .morph-label {
  max-width: 88px;
}

@keyframes mlSwap {
  from {
    opacity: 0;
    transform: scale(0.92);
  }

  to {
    opacity: 1;
    transform: scale(1);
  }
}

/* --- Voice dictation overlay: analyser bars + status line replace the
   toolbar pills while .composer-box.recording is on. --- */
.voice-bars {
  position: absolute;
  left: 16px;
  bottom: 13px;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 3px;
  height: 22px;
}

.voice-bars.hide {
  display: none !important;
}

.voice-bars i {
  width: 3px;
  height: 4px;
  border-radius: 2px;
  background: var(--accent);
  transition: height 0.075s linear;
}

.voice-status {
  margin-left: 8px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-3);
  white-space: nowrap;
}

.voice-status.hide {
  display: none;
}

.composer-box.rec .composer-toolbar> :not(.composer-toolbar-right),
.composer-box.rec .composer-toolbar-right> :not(#send) {
  opacity: 0 !important;
  pointer-events: none !important;
}

.composer-box.rec .composer-toolbar,
.composer-box.rec .composer-toolbar-right {
  pointer-events: none;
}

.composer-box.rec .composer-toolbar-right #send {
  pointer-events: auto;
}

/* --- FLIP image preview (shared-element zoom): geometry is inline,
   only backdrop + shadow live here. --- */
.attach-zoom {
  position: fixed;
  inset: 0;
  z-index: 400;
}

.attach-zoom .az-bg {
  position: absolute;
  inset: 0;
  background: color-mix(in srgb, var(--bg) 68%, transparent);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  opacity: 0;
  transition: opacity 0.35s var(--ease);
}

.attach-zoom.on .az-bg {
  opacity: 1;
}

.attach-zoom img.az-img {
  position: fixed;
  object-fit: cover;
  margin: 0;
}

@media (prefers-reduced-motion: reduce) {

  .composer-box,
  .composer-box.box-anim .composer-toolbar,
  .composer-box.box-anim .composer-input,
  .composer-box.box-anim .attach-toggle,
  .composer-box.box-anim .composer-toolbar-right,
  .composer>.file-chips.tray-host,
  .composer>.file-chips.tray-host.tray-open,
  .send-ic,
  .effort-pill,
  .morph-label,
  .voice-bars i,
  .attach-zoom .az-bg,
  .attach-zoom img.az-img {
    transition-duration: 1ms !important;
    animation-duration: 1ms !important;
  }

  .composer>.file-chips.tray-host .file-chip {
    animation: none !important;
  }
}
