/* Оформление Mini App. Вынесено из index.html 24.08.

   Причина не косметическая. index.html отдаётся с `Cache-Control:
   no-store` (webapp/server.py::no_cache_html) — сознательно, иначе
   вебвью Telegram показывает вчерашнюю версию после деплоя. Значит весь
   файл скачивается заново при КАЖДОМ открытии приложения. На момент
   выноса это 1329 KiB (383 KiB gzip), и 120 KiB из них — вот эти стили,
   которые между деплоями не меняются вовсе.

   no-store стоит только на `/` и `*.html`. Всё, что лежит в отдельном
   .css/.js, StaticFiles отдаёт с ETag: браузер спрашивает «изменилось?»
   и получает 304 без тела. После деплоя ETag другой — файл перекачается
   сам, устареть эта копия не может.

   Порядок правки не изменился: это ровно тот же текст, что был внутри
   <style>, ни одно правило не тронуто при выносе. */

  :root {
    --tabbar-h: 58px;
    --sidebar-w: 232px;
    --tg-injected-safe-bottom: 0px;
    --tg-injected-safe-top: 0px;
    --safe-area-bottom: max(env(safe-area-inset-bottom, 0px), var(--tg-injected-safe-bottom, 0px));
    --safe-area-top: max(env(safe-area-inset-top, 0px), var(--tg-injected-safe-top, 0px));
    --bg: var(--tg-theme-bg-color, #f2f4f8);
    --card: var(--tg-theme-secondary-bg-color, #ffffff);
    --text: var(--tg-theme-text-color, #0f1728);
    --muted: var(--tg-theme-hint-color, #637085);
    /* --accent drives interactive elements (buttons, focus rings) and
       deliberately keeps following the user's own Telegram theme — fighting
       that would feel broken inside the real client. --brand is the
       opposite on purpose: a fixed identity color for decorative/branded
       surfaces (hero card, wordmark, chart line) that stays constant
       regardless of the viewer's theme, so the product has a visual
       signature of its own instead of reading as "default Telegram blue"
       the moment someone's outside a themed chat. */
    /* Запасное значение поднято до AA (#2481cc давал 3.75 на светлом
       поле). Правится ТОЛЬКО запасное: сам --accent приходит от клиента
       Telegram и остаётся его выбором -- переопределять его этот проект
       не должен. Тёмная тема получает своё запасное ниже. */
    --accent: var(--tg-theme-button-color, #2073b6);
    --accent-text: var(--tg-theme-button-text-color, #ffffff);
    /* Brand identity, redefined 19.07: was a generic indigo/teal pair that
       read as "default SaaS", not "trading terminal you'd trust with
       money" (direct owner feedback). Gold/amber is the one color humans
       already read as "value" without any learning curve — --amber (a
       secondary warm accent before this pass) and --brand are now the
       SAME hue on purpose, unified into one signature instead of two
       similar-but-different golds competing next to each other. */
    /* Значения светлой темы подняты до порога WCAG AA (4.5:1) 19.08 --
       Фаза 2 плана UI_QUALITY_PLAN_RU.md. Подобраны минимальным сдвигом
       светлоты при сохранении тона и насыщенности, и проверены сразу на
       ДВУХ фонах, на которых встречаются, -- карточка #ffffff и поле
       #f2f4f8; по одному белому цифры выходили мягче и на поле цвет всё
       равно не проходил. Каждый заодно проверен как ФОН под белым
       текстом (кнопки «Купить», «Запустить»): там теперь 4.96.
       Прежние значения и их контраст: #b7791f 3.31, #0f8a7a 3.86,
       #17a56a 2.88, #e5484d 3.55. Пересчитать -- tools/uiref/
       contrast_audit.py. */
    --brand: #b45309;
    --brand-2: #0e7e6f;
    --green: #128052;
    --red: #db1f25;
    --amber: #b45309;
    --violet: #7c3aed;
    --cyan: #0891b2;
    /* Разведение ролей (19.08, Фаза 2). Токен цвета служил сразу двумя
       вещами -- ЦВЕТОМ ТЕКСТА («+21,05 $») и СПЛОШНОЙ ЗАЛИВКОЙ под белым
       текстом (кнопка «Купить», бейдж). На тёмной теме одно значение обе
       роли закрыть не может по арифметике: чтобы читаться на тёмном
       фоне, цвет должен быть светлым, а чтобы под ним читался белый
       текст -- тёмным. Поэтому заливка получает свой токен.
       В светлой теме значения совпадают: #128052 даёт 4.50 как текст и
       4.96 под белым, разводить нечего. */
    --green-solid: #128052;
    --red-solid: #db1f25;
    --border: rgba(125, 140, 165, .16);
    --shadow: 0 1px 2px rgba(16,24,40,.06), 0 10px 24px -10px rgba(16,24,40,.16);
    --shadow-sm: 0 1px 2px rgba(16,24,40,.05);
    --field: color-mix(in srgb, var(--bg) 72%, var(--card));
    /* Type system (see the Google Fonts <link> above for what's loaded):
       Unbounded for display moments only (hero P&L, wordmark, screen
       titles) — used sparingly, it's too characterful for body copy;
       Golos Text for everything else; JetBrains Mono for every financial
       figure specifically (.num class below) — real monospace on numbers
       is the cheapest, most reliable "serious data tool" signal there is.
       Every stack ends in the same system fallback the app used before
       this pass, so a slow/blocked font request degrades gracefully. */
    --font-display: 'Unbounded', -apple-system, "Segoe UI", Roboto, system-ui, sans-serif;
    --font-body: 'Golos Text', -apple-system, "Segoe UI", Roboto, system-ui, sans-serif;
    --font-mono: 'JetBrains Mono', ui-monospace, "SF Mono", Menlo, Consolas, monospace;

    /* Шкалы (Фаза 1 плана docs/product/UI_QUALITY_PLAN_RU.md, 19.08).
       До этого прохода в файле было 21 значение кегля (включая 13.5 /
       12.5 / 11.5px), 20 значений отступа и 14 радиусов -- не шкала, а
       то, что наросло за месяцы правок. Половинные кегли занимали почти
       половину всех объявлений.

       Значения ниже -- ЕДИНСТВЕННЫЙ разрешённый источник размеров.
       Новый литерал в px вместо var() -- это ошибка ревью, даже если он
       совпадает с одной из ступеней: смысл шкалы в том, что менять
       размер можно в одном месте. */

    /* Текст. Семь ступеней. */
    --text-xs: 11px;
    --text-sm: 12px;
    --text-base: 13px;
    --text-md: 14px;
    --text-lg: 16px;
    --text-xl: 22px;   /* заголовки оверлеев и онбординга */
    --text-2xl: 26px;  /* логотип на главной */

    /* Цифры -- ОТДЕЛЬНОЕ семейство, намеренно не сведённое с текстовым.
       Это моноширинные финансовые показатели: у них своя роль, и
       подгонять их под шкалу подписей значило бы ломать смысл. */
    --figure-sm: 20px;   /* .metric .v */
    --figure-md: 26px;   /* .metric.hero .v */
    --figure-lg: 42px;   /* крупная цифра баланса */

    /* Отступы. Чётная сетка -- владелец выбрал мягкий вариант
       (убрать нечётные, ступени не укрупнять). */
    --space-1: 2px;
    --space-2: 4px;
    --space-3: 6px;
    --space-4: 8px;
    --space-5: 10px;
    --space-6: 12px;
    --space-7: 14px;
    --space-8: 16px;
    --space-10: 20px;
    --space-12: 24px;
    --space-16: 32px;
    --space-20: 40px;

    /* Радиусы. */
    --radius-sm: 4px;
    --radius-md: 8px;
    --radius-lg: 12px;   /* карточка -- решение владельца 19.08 (было 10px) */
    --radius-xl: 16px;
    --radius-pill: 999px;
    --radius-circle: 50%;
  }
  @media (prefers-color-scheme: dark) {
    :root {
      --bg: var(--tg-theme-bg-color, #090a0f);
      --card: var(--tg-theme-secondary-bg-color, #0e111a);
      --text: var(--tg-theme-text-color, #ffffff);
      --muted: var(--tg-theme-hint-color, #8a8f98);
      --brand: #f0b429;
      --brand-2: #2dd4c0;
      --amber: #f0b429;
      --green: #10b981;
      --red: #f43f5e;
      --violet: #a78bfa;
      --cyan: #22d3ee;
      --accent: var(--tg-theme-button-color, #f0b429);
      --green-solid: #059669;
      --red-solid: #e11d48;
      --accent-text: var(--tg-theme-button-text-color, #0a0b0e);
      --border: #1c2333;
      --shadow: none;
      --shadow-sm: none;
      --field: color-mix(in srgb, var(--bg) 55%, var(--card));
    }
  }
  :root[data-theme="dark"] {
    --bg: var(--tg-theme-bg-color, #090a0f);
    --card: var(--tg-theme-secondary-bg-color, #0e111a);
    --text: var(--tg-theme-text-color, #ffffff);
    --muted: var(--tg-theme-hint-color, #8a8f98);
    --brand: #f0b429; --brand-2: #2dd4c0; --amber: #f0b429;
    --green: #10b981; --red: #f43f5e;
    --violet: #a78bfa; --cyan: #22d3ee;
    --green-solid: #059669; --red-solid: #e11d48;
    --accent: var(--tg-theme-button-color, #f0b429);
    --accent-text: var(--tg-theme-button-text-color, #0a0b0e);
    --border: #1c2333;
    --shadow: none;
    --shadow-sm: none;
    --field: color-mix(in srgb, var(--bg) 55%, var(--card));
  }
  :root[data-theme="light"] {
    --bg: var(--tg-theme-bg-color, #f2f4f8);
    --card: var(--tg-theme-secondary-bg-color, #ffffff);
    --text: var(--tg-theme-text-color, #0f1728);
    --muted: var(--tg-theme-hint-color, #637085);
    --brand: #b45309; --brand-2: #0e7e6f; --amber: #b45309;
    --green: #128052; --red: #db1f25;
    --violet: #7c3aed; --cyan: #0891b2;
    --green-solid: #128052; --red-solid: #db1f25;
    --shadow: 0 1px 2px rgba(16,24,40,.06), 0 10px 24px -10px rgba(16,24,40,.16);
    --shadow-sm: 0 1px 2px rgba(16,24,40,.05);
    --field: color-mix(in srgb, var(--bg) 72%, var(--card));
  }
  * { box-sizing: border-box; margin: 0; }
  /* Double-tap-to-zoom is a browser default meant for reading regular web
     pages, not an app UI made of buttons/menus -- without this, tapping
     any button twice in quick succession (a normal thing to do) zooms the
     whole screen instead. touch-action:manipulation turns off that one
     gesture specifically while leaving real pinch-zoom alone (unlike
     user-scalable=no in the viewport meta, which kills zoom entirely --
     worse for accessibility). html+body covers screens with no buttons
     at all (e.g. mid-load) too, not just interactive elements. */
  html, body { touch-action: manipulation; }
  /* Browsers do NOT inherit body's font-family into form controls by
     default (button/input/select/textarea/optgroup use the platform UI
     font instead) -- without this, every button and input on every screen
     silently renders in the OS system font instead of Golos Text, next to
     regular text that DOES use it. This is almost certainly the "разные
     шрифты" the owner is seeing, not a missing design decision -- the type
     system itself was already deliberate (see the --font-* comment below),
     it just never reached form controls. */
  button, input, select, textarea, optgroup { font-family: inherit; }
  /* Disabled buttons had zero visual difference from enabled ones anywhere
     in the app -- no .linkbtn/.mrow/etc class had a :disabled rule, so a
     button that does nothing when tapped (no market selected yet, a job
     already running, ...) looked exactly as clickable as one that works,
     with no feedback for why nothing happened. One rule at the element
     level instead of touching every button class individually -- covers
     every current and future disabled button the same way. */
  button:disabled { opacity: .42; cursor: not-allowed; filter: saturate(.6); }
  button:disabled:active { transform: none !important; }
  /* Same gap, same fix, for number inputs (18.08) -- added for #size/#tfSize
     locking to a selected strategy's own risk.size_usd (see
     syncStrategySizeFields()): a disabled input with no visual cue reads as
     a normal editable field that just silently ignores keystrokes, which is
     worse than the invisible-disabled-button bug above -- that one only
     hid "why didn't this do anything", this one could read as "I set my
     own trade size" when the value on screen is actually the strategy's. */
  input:disabled, select:disabled { opacity: .55; cursor: not-allowed; }
  body {
    background: var(--bg);
    color: var(--text);
    font: var(--text-lg)/1.5 var(--font-body);
    -webkit-font-smoothing: antialiased;
    /* worst case is the backtest screen: runbar stacked directly above the
       persistent tabbar, both fixed to the bottom — pad for both so
       content never scrolls under either one, even on screens that only
       show the tabbar this is a harmless bit of extra bottom space. */
    padding: var(--space-6) var(--space-7) calc(112px + var(--tabbar-h) + var(--safe-area-bottom));
    max-width: 560px;
    margin: 0 auto;
  }
  .icon { width: 20px; height: 20px; flex: none; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
  .icon-sm { width: 16px; height: 16px; vertical-align: -3px; }
  /* Трек B, третья попытка (16.08, владелец: "чуть-чуть лучше, но всё
     равно далеко от Binance/OKX, всё ещё нейрослоп"). .card — базовый
     контейнер, задействован буквально на каждом экране приложения;
     первые две попытки трека B трогали только .ico, .tone-* и .moretile
     (декоративные иконки навигации), сам .card ни разу не был затронут —
     то есть львиная доля визуальной поверхности приложения (все
     карточки контента, не только меню) оставалась на старом "мягкое
     плавающее приложение" языке (18px скругление, мягкая тень) и тянула
     общее впечатление обратно. Не только цвет/скругление были не тем
     рычагом: сам факт "каждый блок — своя плавающая карточка с тенью"
     эмоционально читается как consumer-app, а не терминал. Тень убрана
     в пользу тонкой границы (плоская поверхность вровень с фоном, тот
     же приём что уже применён к .moretile), скругление сжато до 10px
     (в one-line с .moretile/.navitem/.ico — теперь один согласованный
     язык геометрии по всему приложению, не два разных). padding чуть
     плотнее (16px → 14px) — по всему приложению это реально освобождает
     вертикальное место под более высокую информационную плотность
     (Binance/OKX packs заметно больше данных на экран, не только другой
     цвет). */
  .card {
    background: var(--card);
    border-radius: var(--radius-lg);
    padding: var(--space-7);
    margin-bottom: var(--space-6);
    border: 1px solid var(--border);
    box-shadow: none;
  }
  .step {
    display: flex; align-items: center; gap: var(--space-5);
    font-weight: 700; font-size: var(--text-lg); letter-spacing: -.1px;
    margin-bottom: var(--space-6);
  }
  .step .icon { color: var(--brand); }
  /* Numbered badge on the two backtest steps that make up the primary
     path (Данные → Стратегия) -- reads as "step 1 of 2" without touching
     the icon vocabulary already used everywhere else on this screen. */
  .stepnum {
    display: inline-flex; align-items: center; justify-content: center;
    width: 19px; height: 19px; border-radius: var(--radius-pill); flex: none;
    background: color-mix(in srgb, var(--brand) 16%, var(--field));
    color: var(--brand); font: 750 var(--text-xs)/1 var(--font-mono);
  }
  select, input[type=number], input[type=text], input[type=date], textarea {
    width: 100%;
    background: var(--field);
    color: var(--text);
    border: 1px solid transparent;
    border-radius: var(--radius-lg);
    padding: var(--space-6);
    /* 16px is the iOS Safari threshold: any smaller and focusing a form
       field auto-zooms the whole page (jarring, disorienting) — this
       applies to every <select>/<input> in the app via this base rule. */
    font-size: var(--text-lg);
    appearance: none;
    transition: border-color .12s;
  }
  textarea { font: inherit; resize: vertical; min-height: 64px; }
  /* Фаза 2 (доступность). Раньше здесь стояло безусловное outline: none --
     единственное правило фокуса во всём файле, и оно фокус УБИРАЛО. Теперь
     обводку снимаем только там, где её и не ждут: при наведении мышью
     (:focus-visible не сработает), а с клавиатуры она остаётся -- см.
     общее правило :focus-visible ниже. */
  select:focus, input:focus, textarea:focus { border-color: var(--accent); }
  select:focus:not(:focus-visible),
  input:focus:not(:focus-visible),
  textarea:focus:not(:focus-visible) { outline: none; }

  /* Видимый фокус с клавиатуры -- по всему приложению.
     До Фазы 2 у кнопок, ссылок и плиток не было НИКАКОГО стиля фокуса, а
     ПК-раскладка в приложении есть: пройти интерфейс с клавиатуры было
     нельзя, потому что не видно, где находишься. Это прямое нарушение
     WCAG 2.4.7, то есть буквы "AAA" в цели проекта.
     :focus-visible, а не :focus -- чтобы обводка появлялась при работе с
     клавиатуры и не появлялась на клик мышью. outline (а не border или
     box-shadow) не влияет на раскладку и сам повторяет border-radius
     элемента во всех современных браузерах. */
  :focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
  }
  select {
    text-overflow: ellipsis;
    padding-right: 34px;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%238b98ab' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 12px center;
  }
  .linkbtn {
    background: none; border: none; color: var(--accent);
    font-size: var(--text-md); font-weight: 600; cursor: pointer; padding: var(--space-4) var(--space-1) 0;
  }
  /* Mini-presentation recall chip (26.07): deliberately quieter than a
     normal .linkbtn (muted, not accent-colored) -- it's a "read this
     again if you want" affordance, not a call to action competing with
     the screen's real content. Originally borderless/backgroundless plain
     text -- on a real screen every OTHER element on Home sits inside a
     rounded card or pill (the mode chip right above it, every tile below
     it), so text with no boundary at all read as an orphaned, unstyled
     scrap rather than "quiet" (found live on a phone 26.07, owner:
     "блоки... выглядят не аккуратно"). A muted pill borrows the same
     shape language as .modechip without borrowing its color/emphasis. */
  .intro-recall-chip {
    display: inline-flex; align-items: center; gap: var(--space-3);
    color: var(--muted); font-size: var(--text-sm); font-weight: 600;
    background: var(--field); border: 1px solid var(--border); border-radius: var(--radius-md, 8px);
    cursor: pointer; padding: var(--space-3) var(--space-6); margin: 0 0 var(--space-6);
  }
  .intro-recall-chip .icon { color: var(--muted); }
  /* Zero-flicker startup rules for pre-rendered state from html attributes */
  html.intro-home-dismissed #introCard { display: none !important; }
  html.intro-home-dismissed .intro-recall-chip[data-key="home"] { display: inline-flex !important; }
  html.mode-real #homeHeroDemo { display: none !important; }
  html.mode-real #homeHeroReal { display: block !important; }
  /* Сброс кнопочного оформления для компонентов, ставших <button> в
     Фазе 2. Раньше .tab и .moretile были <div> с cursor: pointer -- то
     есть недостижимы с клавиатуры и невидимы для скринридера (49 штук:
     переключатели вкладок и вся плиточная навигация). Тег заменён на
     button, потому что это и есть кнопка по смыслу, и тогда клавиатура,
     фокус и озвучивание работают сами, без tabindex и обработчиков
     keydown вручную.
     Но button приносит СВОЁ оформление -- фон, рамку, шрифт, цвет, поля,
     -- и без сброса вид бы изменился. Сброс общий, а не в каждом
     правиле, чтобы следующий такой компонент не забыли. Стоит ВЫШЕ
     собственных правил .tab/.moretile: специфичность одинаковая, значит
     побеждает то, что ниже, -- их собственные фон и рамка. */
  /* .preset сюда НЕ входит намеренно: он был <button> и до Фазы 2, то
     есть уже имел кнопочное оформление, под которое подогнаны его
     собственные правила. Добавление его в этот сброс подняло кегль с
     UA-шных 13.33px до наследуемых 15px и удлинило экран «Бэктест» на
     243 пикселя -- поймано визуальным гейтом. */
  .tab, .moretile, .navitem {
    font: inherit;
    color: inherit;
    background: none;
    border: 0;
    margin: 0;
    appearance: none; -webkit-appearance: none;
  }
  /* .navitem в потоке был блочным <div> во всю ширину, а <button> --
     inline-block и сжимается по содержимому. Без width раскладка бы
     поехала. text-align: left -- потому что кнопка по умолчанию
     центрирует, а внутри .navitem лежит блок с названием и подписью,
     которые должны остаться прижатыми влево (.preset свой text-align
     задаёт сам, ему это правило не нужно). */
  .navitem { width: 100%; text-align: left; }

  /* Кнопка центрирует своё содержимое по вертикали, <div> прижимал его
     кверху. В полосе вкладок высоту задаёт самая длинная подпись («По
     подписке» переносится на две строки), поэтому однострочные подписи
     после смены тега опустились ровно на 10.5px -- гейт показал это
     полосой различий на шести телефонных кадрах.
     Центрирование у <button> зашито в UA и не снимается ни
     align-items, ни align-content, ни display: block (проверено все
     четыре). Снимается только тем, что кнопка сама становится
     контейнером с явным выравниванием: grid + align-content: start
     возвращает текст ровно на прежние 10px.
     .moretile и .navitem этого не требуют -- у них свой display: flex,
     который и так вытесняет UA-шный анонимный бокс. */
  .tab { display: grid; align-content: start; }

  .tabs {
    display: flex; gap: 0; margin-bottom: var(--space-6);
    background: var(--field); border-radius: var(--radius-lg); padding: var(--space-2);
  }
  .tab {
    flex: 1; text-align: center;
    color: var(--muted); border-radius: var(--radius-lg);
    padding: var(--space-4) var(--space-2); font-size: var(--text-md); font-weight: 600; cursor: pointer;
    transition: color .12s;
  }
  .tab.active {
    color: var(--text); background: var(--card);
    box-shadow: 0 1px 3px rgba(16,24,40,.12);
  }
  .presets { display: grid; gap: var(--space-4); }
  .preset {
    text-align: left;
    background: var(--field);
    border: none;
    border-radius: var(--radius-xl);
    padding: var(--space-6);
    color: var(--text);
    cursor: pointer;
    position: relative;
  }
  .preset.active { box-shadow: inset 0 0 0 1.5px var(--accent); background: color-mix(in srgb, var(--accent) 7%, var(--field)); }
  .preset b { font-size: var(--text-md); display: block; padding-right: 26px; }
  .preset span { color: var(--muted); font-size: var(--text-base); display: block; margin-top: var(--space-1); }
  /* Кнопка выбора стратегии в списке «Мои» (Фаза 2). Раньше кликом
     ловилась вся строка, а строка -- <div> с тремя настоящими кнопками
     внутри, и сделать её саму кнопкой нельзя: вложенные кнопки невалидны.
     Поэтому кнопкой стало НАЗВАНИЕ. Выглядеть должна ровно как обычный
     текст, отсюда полный сброс кнопочного оформления; ширина во всю
     строку -- чтобы область нажатия осталась прежней. Кнопки действий
     лежат поверх (position: absolute и позже в разметке), так что клики
     по иконкам достаются им, а не ей. */
  .preset-pick {
    display: block; width: 100%;
    font: inherit; color: inherit; text-align: left;
    background: none; border: 0; padding: 0; margin: 0;
    appearance: none; -webkit-appearance: none;
    cursor: pointer;
  }

  .preset .del {
    position: absolute; top: 8px; right: 8px;
    background: none; border: none; color: var(--muted);
    font-size: var(--text-lg); cursor: pointer; padding: var(--space-2);
  }
  .empty { color: var(--muted); font-size: var(--text-base); padding: var(--space-3) var(--space-1); }
  .row { display: flex; gap: var(--space-5); }
  .row > div { flex: 1; }
  label.small { color: var(--muted); font-size: var(--text-sm); font-weight: 500; display: block; margin: 0 0 var(--space-3) var(--space-1); }
  /* The screen's one primary action -- everything above (dataset,
     strategy) exists to get here, so it gets the most visual weight on
     the page after the result hero itself: real gradient + depth instead
     of a flat fill, a glossy top highlight like native iOS/MT5 buttons,
     and its own icon so it reads as "launch", not just "submit". */
  #run {
    width: 100%;
    display: flex; align-items: center; justify-content: center; gap: var(--space-5);
    background: linear-gradient(165deg, color-mix(in srgb, var(--accent) 88%, #fff), var(--accent) 55%, color-mix(in srgb, var(--accent) 84%, #000));
    color: var(--accent-text);
    border: 0; border-radius: var(--radius-xl);
    padding: var(--space-8); font-size: var(--text-lg); font-weight: 700; letter-spacing: -.1px;
    cursor: pointer;
    box-shadow:
      inset 0 1px 0 rgba(255,255,255,.28),
      0 6px 20px -2px color-mix(in srgb, var(--accent) 50%, transparent),
      0 2px 6px color-mix(in srgb, var(--accent) 30%, transparent);
    transition: box-shadow .15s, transform .1s;
  }
  #run .run-ico { width: 19px; height: 19px; fill: currentColor; stroke: none; }
  #run:disabled {
    opacity: .5; box-shadow: none;
    background: var(--field); color: var(--muted);
  }
  #run:active { transform: translateY(1px); box-shadow: inset 0 1px 0 rgba(255,255,255,.28), 0 2px 8px color-mix(in srgb, var(--accent) 35%, transparent); }
  @media (prefers-reduced-motion: no-preference) {
    #run:not(:disabled) { animation: runGlow 2.6s ease-in-out infinite; }
  }
  @keyframes runGlow {
    0%, 100% { box-shadow: inset 0 1px 0 rgba(255,255,255,.28), 0 6px 20px -2px color-mix(in srgb, var(--accent) 50%, transparent), 0 2px 6px color-mix(in srgb, var(--accent) 30%, transparent); }
    50% { box-shadow: inset 0 1px 0 rgba(255,255,255,.28), 0 8px 26px -2px color-mix(in srgb, var(--accent) 68%, transparent), 0 2px 6px color-mix(in srgb, var(--accent) 30%, transparent); }
  }
  /* Reveal-on-appear (29.07, owner: "как у топовых бирж" -- microanimations
     as content appears). Applied only to STATIC top-level cards (hero,
     quick-action grid, promo shell, pulse tiles) that exist once in the
     markup and are never innerHTML-replaced -- .in-view is added once and
     never removed, so a card that was already revealed on a previous visit
     to the screen just stays visible, it does not re-play every time you
     navigate back. Deliberately NOT applied to individual dynamically
     re-rendered rows (pulsecard/preset/evrow etc.) -- those get replaced
     wholesale on every poll/refresh, so animating them every ~20s would
     read as a flicker, not a premium touch. observeReveal() (JS) is called
     each time a screen becomes active; IntersectionObserver keeps working
     even for elements that were `display:none` at observe()-time and only
     become visible later (once real data arrives and JS flips their
     display), so call order relative to data-loading is not order-sensitive. */
  .reveal {
    opacity: 0; transform: translateY(10px);
    transition: opacity .45s ease, transform .45s ease;
  }
  .reveal.in-view { opacity: 1; transform: translateY(0); }
  @media (prefers-reduced-motion: reduce) {
    .reveal, .reveal.in-view { opacity: 1; transform: none; transition: none; }
  }
  /* Skeleton loading shapes (29.07) -- a shimmering placeholder the same
     size/shape as the content that will replace it, instead of a bare
     "Загрузка…" line. Built from the app's own theme tokens (--field/
     --muted), not hardcoded greys, so it matches both themes automatically
     the same way every other component here already does. */
  @keyframes skeletonShimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
  .skel {
    display: block; border-radius: var(--radius-md);
    /* --field alone (the .skel-pulsecard shelf it sits on) was too close in
       tone to --card to read as a distinct shape (confirmed live 29.07 --
       correct DOM/size but visually blended into the card). Base tone
       mixes in real --muted for actual contrast against both --card and
       --field; the shimmer sweep goes lighter still from that base. */
    background: linear-gradient(90deg,
      color-mix(in srgb, var(--muted) 30%, var(--field)) 30%,
      color-mix(in srgb, var(--muted) 55%, var(--field)) 50%,
      color-mix(in srgb, var(--muted) 30%, var(--field)) 70%);
    background-size: 240% 100%;
    animation: skeletonShimmer 1.5s ease-in-out infinite;
  }
  @media (prefers-reduced-motion: reduce) {
    .skel { animation: none; opacity: .5; }
  }
  .skel-pulsecard { background: var(--field); border-radius: var(--radius-lg); padding: var(--space-5) var(--space-6); }
  .skel-pulsecard .skel + .skel { margin-top: var(--space-4); }
  #status { text-align: center; color: var(--muted); font-size: var(--text-base); min-height: 18px; margin-top: var(--space-3); }
  /* Секундарная группа (Параметры + Стресс-тест): свёрнута по умолчанию,
     плоская -- без тени и фона карточки -- чтобы явно читаться как
     необязательный, дополнительный шаг рядом с основным путём
     Данные → Стратегия → Запустить. */
  .advgroup {
    border: 1.5px dashed var(--border);
    border-radius: var(--radius-xl);
    margin-bottom: var(--space-6);
    background: color-mix(in srgb, var(--field) 55%, transparent);
  }
  .advgroup summary {
    display: flex; align-items: center; gap: var(--space-5);
    padding: var(--space-6) var(--space-8);
    color: var(--muted); font-size: var(--text-md); font-weight: 650;
    cursor: pointer; list-style: none;
  }
  .advgroup summary::-webkit-details-marker { display: none; }
  .advgroup summary .icon { width: 17px; height: 17px; flex: none; }
  .advgroup summary .chev { margin-left: auto; transition: transform .15s; }
  .advgroup[open] summary .chev { transform: rotate(90deg); }
  .advgroup[open] summary { border-bottom: 1px solid var(--border); }
  .advgroup-body { padding: var(--space-1) var(--space-8) var(--space-2); }
  .advgroup .card {
    box-shadow: none; background: transparent;
    padding: var(--space-7) 0; margin-bottom: 0;
    border-top: 1px solid var(--border);
  }
  .advgroup .card:first-child { border-top: none; padding-top: var(--space-6); }
  .advgroup .step { font-size: var(--text-md); color: var(--muted); }
  .advgroup .step .icon { color: var(--muted); }
  /* Внутри клиента Telegram главное действие уезжает в нативную
     MainButton (Фаза 3), и собственная полоса становится лишней -- иначе
     на экране два одинаковых «Запустить тест» подряд. Класс ставится из
     JS и только при непустом initData: вне клиента нативной кнопки нет,
     и полоса обязана остаться, иначе экран лишится кнопки запуска
     вообще. */
  html.tg-native-cta .runbar { display: none; }
  /* Отступ снизу рассчитан на две полосы -- собственную с кнопкой и
     навигацию. Когда первая уехала в нативную кнопку, её доля отступа
     превращается в мёртвое пустое место под контентом, тем более что
     Telegram и так ужимает окно под MainButton. Оставляем долю только
     навигации. */
  html.tg-native-cta body {
    padding-bottom: calc(var(--space-10) + var(--tabbar-h) + var(--safe-area-bottom));
  }

  .runbar {
    position: fixed; left: 0; right: 0; bottom: calc(var(--tabbar-h) + var(--safe-area-bottom));
    background: color-mix(in srgb, var(--bg) 82%, transparent);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    padding: var(--space-5) var(--space-7);
    max-width: 560px; margin: 0 auto;
    z-index: 50;
  }
  /* Persistent bottom tab bar — primary navigation (MT5-style: always
     visible, one tap between top-level sections, no back-button needed).
     Sits below .runbar so the two stack cleanly on the backtest screen,
     the only place both are ever visible at once. */
  /* Тост -- неблокирующее подтверждение (Фаза 3).

     Заменяет tg.showAlert ТОЛЬКО там, где сообщение подтверждает уже
     случившееся: «Скопировано», «Куплено», «Отправлено на модерацию».
     Ошибки намеренно остаются блокирующим окном: его нужно закрыть
     рукой, то есть человек точно его увидел. На денежных экранах это
     принципиально -- сообщение о непрошедшем платеже не должно
     раствориться само через три секунды, пока пользователь смотрит в
     другую сторону.

     Живёт над нижней навигацией и над безопасной зоной. pointer-events
     выключены: тост ничего не перекрывает по нажатию, под ним можно
     продолжать работать -- в этом и смысл неблокирующего сообщения. */
  .toast {
    position: fixed; left: 50%; transform: translateX(-50%) translateY(8px);
    bottom: calc(var(--tabbar-h) + var(--safe-area-bottom) + var(--space-6));
    z-index: 300;
    max-width: min(92vw, 420px);
    background: var(--card); color: var(--text);
    border: 1px solid var(--border); border-radius: var(--radius-lg);
    box-shadow: var(--shadow);
    padding: var(--space-5) var(--space-7);
    font-size: var(--text-base); line-height: 1.35;
    opacity: 0; pointer-events: none;
    transition: opacity .16s ease, transform .16s ease;
  }
  .toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
  @media (prefers-reduced-motion: reduce) {
    .toast { transition: none; }
  }

  .tabbar {
    position: fixed; left: 0; right: 0; bottom: 0;
    height: calc(var(--tabbar-h) + var(--safe-area-bottom));
    padding-bottom: var(--safe-area-bottom);
    display: flex;
    background: var(--card);
    /* --card vs --bg can be nearly the same shade in some real Telegram
       themes (found live: a couple of first-time users didn't notice the
       tab bar at all) -- a real cast shadow above it plus a stronger
       border makes it read as its own lifted surface instead of blending
       into the page, in both themes (--shadow-sm is already redefined per
       theme, same trick .card already uses elsewhere). */
    box-shadow: 0 -1px 0 var(--border), 0 -6px 16px -8px rgba(0,0,0,.22);
    border-top: 1.5px solid var(--border);
    max-width: 560px; margin: 0 auto;
    z-index: 60;
  }
  .tabbar-item {
    flex: 1;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: var(--space-2);
    background: none; border: none; cursor: pointer;
    /* Plain --muted reads a bit too quiet specifically here -- this is
       primary navigation, not secondary copy, so it gets its own slightly
       higher-contrast mix instead of the app-wide muted tone. */
    color: color-mix(in srgb, var(--muted) 78%, var(--text));
    padding: var(--space-3) var(--space-1) 0;
  }
  .tabbar-item .icon { width: 21px; height: 21px; }
  .tabbar-item span { font-size: var(--text-xs); font-weight: 600; letter-spacing: -.1px; }
  .tabbar-item.active { color: var(--accent); }
  /* Desktop layout (owner: the browser/Telegram Web build looked "как в
     моб." stretched into a huge blank window -- there was no wide-viewport
     CSS path at all, just this single 560px mobile column). data-layout is
     set on <html> by the picker overlay (#layoutSelect) below, defaulting
     to a guess from viewport width on first launch. Reuses every existing
     screen/component unchanged -- only repositions the two fixed bars
     (tabbar -> left sidebar, runbar alongside it) and widens the column,
     the same "wrap, don't rewrite" approach used throughout this app. */
  .home-main, .home-rail { display: contents; }

  @media (min-width: 900px) {
    html[data-layout="desktop"] body {
      max-width: 900px;
      margin-left: var(--sidebar-w, 232px);
      margin-right: 0;
      padding: var(--space-16) var(--space-20) 90px;
    }
    /* Главный экран на ПК (11.08, ПК-плотность) */
    html[data-layout="desktop"] #screen-home.active {
      display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--space-8);
    }
  html[data-layout="desktop"] #screen-home > .homehead,
  html[data-layout="desktop"] #screen-home > #introCard,
  html[data-layout="desktop"] #screen-home > .intro-recall-chip,
  html[data-layout="desktop"] #screen-home > #homeBadgeRow,
  html[data-layout="desktop"] #screen-home > #adBannerSlot,
  html[data-layout="desktop"] #screen-home > footer {
    flex: 1 1 100%;
  }
  html[data-layout="desktop"] .home-main {
    display: block; flex: 1 1 450px; min-width: 0;
  }
  html[data-layout="desktop"] .home-rail {
    display: block; flex: 0 0 320px; min-width: 0;
  }
  /* Бэктест на ПК (11.08, ПК-плотность, пункт 2 плана): тот же приём,
     что и на Главном, но обёрнут только вокруг вывода результата
     (results/statsCard/tradesCard/runsCard), не всего экрана -- форма
     настройки теста выше (пресеты, конструктор условий, датасет) уже
     одна колонка не просто так, это последовательный ввод, а не сводка
     для сравнения, превращать её в две колонки не нужно. Разбивка:
     "анализ" слева (итог + график + детальная статистика) шире, потому
     что графику нужна ширина; "журнал" справа (сделки + прошлые
     запуски) уже, потому что это построчные списки, которым ширина не
     нужна -- та же ширина рельса (320px), что у Пульса рынка на
     Главном, для единого ритма по экранам. */
  .bt-output-row, .bt-main, .bt-rail { display: contents; }
  html[data-layout="desktop"] .bt-output-row {
    display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--space-8);
  }
  html[data-layout="desktop"] .bt-main {
    display: block; flex: 1 1 460px; min-width: 0;
  }
  html[data-layout="desktop"] .bt-rail {
    display: block; flex: 0 0 320px; min-width: 0;
  }
  /* Найдено живьём на ПК (16.08, продолжение ПК-плотности): у вернувшегося
     пользователя без запуска в ЭТОЙ вкладке .bt-main целиком скрыт
     (см. syncBtOutputColumns), но .bt-rail всё ещё стоял на своём парном
     flex-basis 320px -- "Последние запуски" оставались узкой карточкой у
     левого края с пустым местом и справа, и снизу, а "2 trades" переносило
     на вторую строку без всякой необходимости. .solo -- переключается той
     же функцией, ровно когда видна только одна из двух колонок -- отдаёт
     видимой колонке всю ширину строки вместо зарезервированной парной.
     Обе половины правила безвредны сами по себе: скрытая колонка (display:
     none) в раскладке не участвует, какой бы flex-basis у неё ни был. */
  html[data-layout="desktop"] .bt-output-row.solo .bt-main,
  html[data-layout="desktop"] .bt-output-row.solo .bt-rail {
    flex: 1 1 100%;
  }
  /* Отчёт «Проверка трейдера» на Кошельке (11.08, тот же обход экранов
     на класс проблемы «узкая колонка на широком экране»): в отличие от
     Бэктеста здесь нет отдельной функции синхронизации -- весь отчёт
     собирается одним вызовом walRenderReport(), поэтому пустой рельс
     (обрезанная история без открытых позиций) обрабатывается прямо в
     JS -- функция просто не оборачивает одну карточку в .wal-output-row,
     если второй колонки не будет. Тот же рельс 320px, что у Бэктеста и
     Пульса рынка на Главном, для единого ритма. */
  .wal-output-row, .wal-main, .wal-rail { display: contents; }
  html[data-layout="desktop"] .wal-output-row {
    display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--space-8);
  }
  html[data-layout="desktop"] .wal-main {
    display: block; flex: 1 1 460px; min-width: 0;
  }
  html[data-layout="desktop"] .wal-rail {
    display: block; flex: 0 0 320px; min-width: 0;
  }
  /* Копитрейдинг-калькулятор (11.08, тот же обход экранов): «Кого
     копируем» и «Во что обойдётся» -- решающие карточки -- в
     .cp-main; «Что скопировалось» и расчёт для реальных денег --
     второстепенные, в .cp-rail. Баннер о неполном покрытии, кнопка
     подписки и дисклеймер сознательно ВНЕ этой строки (см. комментарий
     в cpRenderPlan) -- те же 320px рельса, что у Бэктеста и Кошелька. */
  .cp-output-row, .cp-main, .cp-rail { display: contents; }
  html[data-layout="desktop"] .cp-output-row {
    display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--space-8);
  }
  html[data-layout="desktop"] .cp-main {
    display: block; flex: 1 1 460px; min-width: 0;
  }
  html[data-layout="desktop"] .cp-rail {
    display: block; flex: 0 0 320px; min-width: 0;
  }
  /* Данные и рынки (16.08, продолжение обхода 11.08 -- этот экран тогда
     пропущен, найдено живым скриншотом на 1440px: четыре карточки одной
     колонкой с пустым местом справа). Тот же приём -- Поиск рынков и
     Живой стакан (обоим нужна ширина) в .data-main; Скачать историю и
     Записать стакан (простые формы) в .data-rail, тот же рельс 320px. */
  .data-output-row, .data-main, .data-rail { display: contents; }
  html[data-layout="desktop"] .data-output-row {
    display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--space-8);
  }
  html[data-layout="desktop"] .data-main {
    display: block; flex: 1 1 460px; min-width: 0;
  }
  html[data-layout="desktop"] .data-rail {
    display: block; flex: 0 0 320px; min-width: 0;
  }
  /* Демо-трейдинг (19.08, тот же обход узких экранов -- найдено живым
     скриншотом 1440px: счёт + история сделок + форма новой сессии одной
     колонкой). Счёт и форма новой сессии -- обеим нужна ширина -- в
     .paper-main; история сделок в .paper-rail, тот же рельс 320px и тот
     же журнальный жанр, что у "Сделки" на Бэктесте/Торговле (там список
     сделок тоже живёт в узком рельсе, не в основном потоке). */
  .paper-output-row, .paper-main, .paper-rail { display: contents; }
  html[data-layout="desktop"] .paper-output-row {
    display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--space-8);
  }
  html[data-layout="desktop"] .paper-main {
    display: block; flex: 1 1 460px; min-width: 0;
  }
  html[data-layout="desktop"] .paper-rail {
    display: block; flex: 0 0 320px; min-width: 0;
  }
  /* Задания и очки (11.08, тот же обход экранов): еженедельный цикл
     «заработать/потратить» (Очки, Регулярные, Задания, Магазин) -- в
     .quest-main; разовая настройка рефералки (Пригласи друга,
     Партнёрство) -- в .quest-rail. Единственный экран из этой волны,
     где сплит написан прямо в HTML, а не собирается JS-функцией
     (см. комментарий над .quest-output-row в разметке) -- поэтому
     здесь порядок карточек на телефоне меняется, а не только на ПК;
     проверено отдельным скриншотом телефона. */
  .quest-output-row, .quest-main, .quest-rail { display: contents; }
  html[data-layout="desktop"] .quest-output-row {
    display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--space-8);
  }
  html[data-layout="desktop"] .quest-main {
    display: block; flex: 1 1 460px; min-width: 0;
  }
  html[data-layout="desktop"] .quest-rail {
    display: block; flex: 0 0 320px; min-width: 0;
  }
  /* Голосования (11.08, тот же обход экранов): каталог -- в широкую
     .ev-main; «Мои события» -- в узкую .ev-rail (320px). «Предложить
     новое» вне строки, как и раньше на всю ширину -- см. комментарий
     над .ev-output-row в разметке. */
  .ev-output-row, .ev-main, .ev-rail { display: contents; }
  html[data-layout="desktop"] .ev-output-row {
    display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--space-8);
  }
  html[data-layout="desktop"] .ev-main {
    display: block; flex: 1 1 460px; min-width: 0;
  }
  html[data-layout="desktop"] .ev-rail {
    display: block; flex: 0 0 320px; min-width: 0;
  }
  /* Скринер, Магазин очков и Маркетплейс стратегий на ПК (11.08, «во
     всём должны быть лучше конкурентов, особенно ПК»): у `.evrow` одна
     кнопка внутри `.evactions` наследует общее `.btn-sm { flex: 1 }` и
     растягивается на всю ширину строки -- на 900px-колонке это
     заметная плашка почти во весь экран под одной-тремя строчками
     текста, ровно тот же класс проблемы, что был на Главном/Бэктесте
     до плотности. Правило нарочно СКОУПЛЕНО на конкретные списки
     (используются функциями с ровно одним `.evactions`-блоком и одной
     кнопкой на строку), а не на общий `.evrow`/`.evactions` -- тот же
     компонент переиспользуют административные очереди (по 2-3 кнопки +
     многострочные описания + иногда поле ввода причины отказа), где
     "текст слева, кнопка справа в одну строку" не подходит и было бы
     риском, не улучшением. */
  html[data-layout="desktop"] #scrResults .evrow,
  html[data-layout="desktop"] #shopList .evrow,
  html[data-layout="desktop"] #mpCatalogList .evrow,
  html[data-layout="desktop"] #mpMySubsList .evrow,
  html[data-layout="desktop"] #mpMyListingsList .evrow {
    display: flex; align-items: center; gap: var(--space-8); padding: var(--space-7) 0;
  }
  html[data-layout="desktop"] #scrResults .evrow-body,
  html[data-layout="desktop"] #shopList .evrow-body,
  html[data-layout="desktop"] #mpCatalogList .evrow-body,
  html[data-layout="desktop"] #mpMySubsList .evrow-body,
  html[data-layout="desktop"] #mpMyListingsList .evrow-body {
    flex: 1 1 auto; min-width: 0;
  }
  html[data-layout="desktop"] #scrResults .evactions,
  html[data-layout="desktop"] #shopList .evactions,
  html[data-layout="desktop"] #mpCatalogList .evactions,
  html[data-layout="desktop"] #mpMySubsList .evactions,
  html[data-layout="desktop"] #mpMyListingsList .evactions {
    margin-top: 0; flex: 0 0 auto;
  }
  html[data-layout="desktop"] #scrResults .evactions .btn-sm,
  html[data-layout="desktop"] #shopList .evactions .btn-sm,
  html[data-layout="desktop"] #mpCatalogList .evactions .btn-sm,
  html[data-layout="desktop"] #mpMySubsList .evactions .btn-sm,
  html[data-layout="desktop"] #mpMyListingsList .evactions .btn-sm {
    flex: none; min-width: 190px;
  }
  html[data-layout="desktop"] .tabbar {
    left: 0; right: auto; top: 0; bottom: 0;
    width: var(--sidebar-w, 232px); height: 100%;
    max-width: none; margin: 0;
    flex-direction: column; align-items: stretch; justify-content: flex-start;
    gap: var(--space-2); padding: 28px var(--space-7);
    border-top: none;
    box-shadow: 1px 0 0 var(--border), 6px 0 16px -8px rgba(0,0,0,.18);
  }
  html[data-layout="desktop"] .tabbar-item {
    flex: none; flex-direction: row; justify-content: flex-start;
    gap: var(--space-6); padding: var(--space-6) var(--space-7); border-radius: var(--radius-lg);
    transition: background-color .12s;
  }
  html[data-layout="desktop"] .tabbar-item:hover { background: var(--field); }
  html[data-layout="desktop"] .tabbar-item span { font-size: var(--text-md); }
  html[data-layout="desktop"] .runbar {
    left: var(--sidebar-w, 232px); right: 0;
    max-width: none; margin: 0; padding: var(--space-5) var(--space-20);
  }
}
  /* Mouse hover feedback (28.07, owner: "удобство и особенно для
     пользователей с ПК" -- audited live at 1440px and found exactly ONE
     :hover rule in the whole app, the sidebar nav item above. Every card/
     tile/button had a real, considered :active (touch-press) state but
     nothing at all told a mouse user something was clickable before they
     committed to the click -- the single biggest concrete "feels like a
     phone app in a box" gap on desktop. `(hover: hover) and (pointer:
     fine)` scopes this to real mice/trackpads only, so a touchscreen
     tap never gets stuck showing a "hovered" card until the next tap
     elsewhere (the classic mobile sticky-hover bug). One shared
     brightness bump for cards/tiles (works uniformly regardless of each
     component's own background/gradient) plus a small lift for the
     handful of gradient-filled primary buttons, rather than bespoke
     colors per component -- cheaper to keep consistent as new components
     are added later. */
  @media (hover: hover) and (pointer: fine) {
    .hometile[data-nav]:hover, .moretile:hover, .modecard:hover,
    .navitem:hover, .preset:not(.active):hover, .promocard:hover,
    .hero:hover {
      filter: brightness(1.08);
    }
    #run:not(:disabled):hover, .hero-cta:hover, .bigbtn:not(:disabled):hover,
    .btn-sm:not(:disabled):hover, #resToPaper:hover {
      filter: brightness(1.08);
      transform: translateY(-1px);
    }
    .btn-tonal:hover, #shareResult:hover {
      background: color-mix(in srgb, var(--accent) 16%, transparent);
    }
    .linkbtn:hover, .promoGo:hover {
      text-decoration: underline;
    }
    .iconbtn:hover {
      background: var(--field); border-radius: var(--radius-md);
    }
    .tab:not(.active):hover {
      color: var(--text);
    }
  }
  /* Desktop-only (28.07, continuation of the same PC-shell pass): the
     ready-made preset list on Backtest is the single most-viewed list in
     the app (selectPreset(0) is called at boot -- this tab is active by
     default) and was stacked full-width at 900px, each card far wider
     than its own text needed. Same .preset cards, same click-to-select
     behavior (selectPreset() only toggles a class, no layout-order
     assumption) -- just let them reflow into columns. 280px minimum (not
     moregrid's 150px) because these cards carry a full sentence of
     description, not just an icon+label -- narrower would wrap every
     description to 4+ lines. Mobile's single-column .presets rule above
     is untouched. */
  html[data-layout="desktop"] .presets {
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  }
  .metrics { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4); }
  /* :last-child:nth-child(odd) only correctly detects "alone in the final
     row" for an EXACTLY-2-column grid (rows fill in pairs, so an odd total
     count always leaves the last item alone at an odd position) -- it does
     not generalize to the desktop override below. Left as-is, a 4-column
     desktop grid with e.g. 3 items (finCostTiles' count is server-driven,
     not fixed) would wrongly stretch item 3 across all 4 columns while
     items 1-2 stay normal width -- a worse, inconsistent-looking row than
     just leaving the last row uneven. Desktop cancels the rule outright;
     mobile (2 columns, where the trick is actually valid) keeps it. */
  .metric:last-child:nth-child(odd) { grid-column: 1 / -1; }
  html[data-layout="desktop"] .metric:last-child:nth-child(odd) { grid-column: auto; }
  /* Desktop-only (28.07): six different screens reuse .metrics with six
     different tile counts (4 on Paper/quick-backtest, ~10 on the admin
     stats/economy screens, a server-driven count on the financial model
     screen) -- all were stuck at a hardcoded 2 columns, sparse at 900px+
     (confirmed live on Paper: 4 tiles in a 2x2 square instead of one
     tidy row). auto-fit (not auto-fill, see .moregrid/.presets above)
     specifically so a single lone tile (Quests' "Мои очки") still
     stretches to fill the row on its own, not just sit in column 1. */
  html[data-layout="desktop"] .metrics {
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  }
  .metric {
    background: var(--field);
    border-radius: var(--radius-xl); padding: var(--space-6);
  }
  .metric .v { font: 750 var(--figure-sm)/1.2 var(--font-mono); letter-spacing: -.2px; font-variant-numeric: tabular-nums; }
  .metric .k { color: var(--muted); font-size: var(--text-sm); font-weight: 500; margin-top: var(--space-1); }
  .pos { color: var(--green); } .neg { color: var(--red); }
  #chart { width: 100%; height: 150px; margin-top: var(--space-6); }
  /* trades as list rows, not a table: six data points per trade never fit
     a 360px-wide table (verified by screenshot earlier today) — a two-line
     row (title+PnL, then meta) scales to any width and reads like every
     mobile trading app's history screen */
  .traderow {
    padding: var(--space-4) 0;
    border-top: 1px solid var(--border);
    transition: background 0.15s ease;
  }
  .traderow:active {
    background: rgba(255, 255, 255, 0.03);
  }
  .traderow:first-child { border-top: none; }
  .tr-main { display: flex; align-items: baseline; gap: var(--space-5); }
  .tr-title {
    flex: 1; min-width: 0; font-size: var(--text-md); font-weight: 600;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .tr-pnl { flex: none; font: 750 var(--text-md)/1.2 var(--font-mono); font-variant-numeric: tabular-nums; }
  .tr-meta { color: var(--muted); font-size: var(--text-sm); margin-top: var(--space-1); }
  /* Events' location bit was the last spot in real (non-admin, non-share-
     text) UI still using a raw "📍" glyph instead of the sprite -- found
     scanning the file for leftover emoji after the earlier warning-icon
     sweep. Scoped to .evrow specifically since .tr-meta itself is reused
     at other sizes (.pulsecard .tr-meta is 11px) this icon was never
     tuned for. */
  .evrow .tr-meta .icon { width: 12px; height: 12px; vertical-align: -1.5px; margin-right: var(--space-1); }
  /* The single most "here's the money" moment in the product — the
     backtest result. Display font + real monospace digits + biggest type
     on the page on purpose (owner's own framing: this screen should set
     the tone for everything else). */
  #resHero { padding: var(--space-2) 0 var(--space-1); }
  #resHero .bignum {
    font: 800 42px/1.1 var(--font-mono); letter-spacing: -1px;
    font-variant-numeric: tabular-nums;
  }
  .chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    scrollbar-width: none;
    -ms-overflow-style: none;
    -webkit-overflow-scrolling: touch;
  }
  .chips::-webkit-scrollbar { display: none; }
  #resHero .chips { margin-top: var(--space-4); }

  .chip2 {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    background: var(--card, var(--field));
    border: 1px solid var(--border);
    border-radius: var(--radius-md, 8px);
    padding: var(--space-2) var(--space-3);
    font-size: 12.5px;
    font-weight: 600;
    color: var(--muted);
    white-space: nowrap;
    text-decoration: none;
    transition: all 0.16s cubic-bezier(0.16, 1, 0.3, 1);
  }
  button.chip2 {
    height: 34px;
    padding: 0 var(--space-3);
    cursor: pointer;
    user-select: none;
    outline: none;
  }
  button.chip2:hover:not(:disabled) {
    color: var(--text-bright);
    border-color: color-mix(in srgb, var(--brand) 40%, var(--border));
    background: color-mix(in srgb, var(--card) 90%, #ffffff);
  }
  button.chip2:active:not(:disabled) {
    transform: scale(0.97);
  }
  button.chip2.active,
  .chip2.active {
    background: color-mix(in srgb, var(--brand) 16%, var(--card));
    border-color: var(--brand);
    color: var(--text-bright);
    font-weight: 700;
    box-shadow: 0 2px 8px rgba(212, 175, 55, 0.16), inset 0 0 0 1px rgba(212, 175, 55, 0.2);
  }
  .chip2.pos { color: var(--green); background: color-mix(in srgb, var(--green) 10%, transparent); border-color: color-mix(in srgb, var(--green) 25%, transparent); }
  .chip2.neg { color: var(--red); background: color-mix(in srgb, var(--red) 10%, transparent); border-color: color-mix(in srgb, var(--red) 25%, transparent); }
  .chip2.warn { color: var(--amber); background: color-mix(in srgb, var(--amber) 15%, transparent); border-color: color-mix(in srgb, var(--amber) 30%, transparent); }
  .metric.hero {
    background: linear-gradient(135deg, color-mix(in srgb, var(--brand) 14%, var(--field)), var(--field));
    border: 1px solid color-mix(in srgb, var(--brand) 20%, var(--border));
  }
  .metric.hero .v { font-family: var(--font-mono); font-size: var(--figure-md); color: var(--brand); }
  /* --amber now equals --brand (see :root — unified into one gold identity
     19.07), so .hero and .hero.amber render identically today; kept as two
     classes rather than merged so a future deliberately-different "warning"
     tone stays a one-line change, not a search-and-replace across call sites. */
  .metric.hero.amber {
    background: linear-gradient(135deg, color-mix(in srgb, var(--amber) 16%, var(--field)), var(--field));
    border-color: color-mix(in srgb, var(--amber) 22%, var(--border));
  }
  .metric.hero.amber .v { color: var(--amber); }
  .evrow { padding: var(--space-6) 0; border-top: 1px solid var(--border); }
  .evrow:first-child { border-top: none; }
  .evrow .tr-title { white-space: normal; overflow-wrap: break-word; }
  .evrow .tr-meta { margin-top: var(--space-2); }
  .evrow .evactions { display: flex; gap: var(--space-4); margin-top: var(--space-5); }
  /* Home "market pulse" card: same row component as the screener list
     (consistency, not a new visual language) but tappable as a whole row
     (screener keeps its explicit "Торговать" button — home stays denser).
     Two independent sources, deliberately (25.07): #homePulseSpotlight is
     the most-traded market right now (screener.top_active — always has an
     answer, doesn't depend on anything having moved), #homePulseBody is
     the movers list (screener.scan — can legitimately be empty on a quiet
     day). Splitting them means a quiet day still shows something live. */
  #homePulseBody .evrow, #homePulseSpotlight .evrow { cursor: pointer; }
  #homePulseSpotlight .evrow.pulse-featured { padding: var(--space-7) 0 var(--space-8); }
  #homePulseSpotlight .evrow.pulse-featured .tr-title { font-size: var(--text-lg); font-weight: 700; }
  #homePulseSpotlight .evrow.pulse-featured .tr-pnl {
    font: 650 var(--text-sm)/1.2 var(--font-body); text-transform: uppercase; letter-spacing: .3px;
    color: var(--brand); flex: none;
  }
  .btn-sm {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    flex: 1;
    height: 38px;
    padding: 0 var(--space-4);
    border: 1px solid var(--border);
    border-radius: var(--radius-md, 8px);
    font-size: 13px;
    font-weight: 650;
    cursor: pointer;
    white-space: nowrap;
    transition: all 0.16s cubic-bezier(0.16, 1, 0.3, 1);
    user-select: none;
    outline: none;
  }
  .btn-sm:active:not(:disabled) { transform: scale(0.97); }
  .btn-sm.ok {
    background: color-mix(in srgb, var(--green) 14%, var(--card, var(--field)));
    border-color: color-mix(in srgb, var(--green) 35%, transparent);
    color: var(--green);
  }
  .btn-sm.ok:hover:not(:disabled) {
    background: color-mix(in srgb, var(--green) 24%, var(--card, var(--field)));
    border-color: var(--green);
    color: var(--text-bright);
    box-shadow: 0 2px 8px rgba(16, 185, 129, 0.2);
  }
  .btn-sm.no {
    background: color-mix(in srgb, var(--red) 14%, var(--card, var(--field)));
    border-color: color-mix(in srgb, var(--red) 35%, transparent);
    color: var(--red);
  }
  .btn-sm.no:hover:not(:disabled) {
    background: color-mix(in srgb, var(--red) 24%, var(--card, var(--field)));
    border-color: var(--red);
    color: var(--text-bright);
    box-shadow: 0 2px 8px rgba(239, 68, 68, 0.2);
  }
  .btn-sm:disabled { opacity: .45; cursor: default; }

  /* Segmented Controls (Pari-Mutuel & Market Filters) */
  .seg {
    display: flex;
    background: var(--field);
    border: 1px solid var(--border);
    border-radius: var(--radius-md, 8px);
    padding: 3px;
    gap: 3px;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .seg::-webkit-scrollbar { display: none; }
  .seg-btn {
    flex: 1 0 auto;
    min-width: fit-content;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    height: 32px;
    padding: 0 10px;
    font-size: 12px;
    font-weight: 600;
    color: var(--muted);
    background: transparent;
    border: 1px solid transparent;
    border-radius: 6px;
    cursor: pointer;
    white-space: nowrap;
    transition: all 0.16s ease;
    user-select: none;
    outline: none;
  }
  .seg-btn:hover:not(:disabled) {
    color: var(--text-bright);
    background: rgba(255, 255, 255, 0.04);
  }
  .seg-btn.active {
    background: var(--card, #1c1d22);
    border-color: color-mix(in srgb, var(--brand) 40%, var(--border));
    color: var(--text-bright);
    font-weight: 700;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
  }

  .chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 8px;
    border-radius: 6px;
    background: var(--field);
    border: 1px solid var(--border);
    color: var(--muted);
    font-size: var(--text-xs);
    font-weight: 600;
  }
  .badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 2px 8px;
    border-radius: 5px;
    font-size: 11px;
    font-weight: 650;
    line-height: 1.2;
    text-transform: uppercase;
    letter-spacing: 0.03em;
  }
  .badge.open {
    background: color-mix(in srgb, var(--green) 16%, transparent);
    color: var(--green);
    border: 1px solid color-mix(in srgb, var(--green) 35%, transparent);
  }
  .wagerChipBtn {
    height: 26px;
    padding: 0 8px;
    border-radius: 6px;
    border: 1px solid var(--border);
    background: var(--field);
    color: var(--muted);
    font-size: 11.5px;
    font-weight: 600;
    font-family: var(--font-mono);
    cursor: pointer;
    transition: all 0.14s ease;
    user-select: none;
    outline: none;
  }
  .wagerChipBtn:hover {
    color: var(--text-bright);
    border-color: color-mix(in srgb, var(--brand) 50%, var(--border));
    background: color-mix(in srgb, var(--brand) 10%, var(--field));
  }
  .wagerChipBtn:active {
    transform: scale(0.95);
  }

  .adbanner {
    display: block; position: relative; border-radius: var(--radius-lg);
    overflow: hidden; background: var(--field);
  }
  .adbanner img { display: block; width: 100%; max-height: 90px; object-fit: cover; }
  .adbanner .adtag {
    position: absolute; top: 6px; right: 6px; font-size: var(--text-xs);
    color: #fff; background: rgba(0,0,0,.45); padding: var(--space-1) var(--space-3);
    border-radius: var(--radius-pill); letter-spacing: .02em;
  }
  .authbar {
    position: sticky; top: 0; z-index: 99;
    background: var(--red-solid); color: #fff;
    padding: var(--space-4) var(--space-6); font-size: var(--text-base); line-height: 1.35;
  }
  .authbar .icon { width: 14px; height: 14px; vertical-align: -2.5px; margin-right: var(--space-2); flex: none; }
  .votebar {
    display: flex; height: 26px; border-radius: 6px; overflow: hidden;
    font: 650 var(--text-xs)/1 var(--font-mono); margin-top: var(--space-3);
    font-variant-numeric: tabular-nums;
    border: 1px solid var(--border);
  }
  .votebar .vy {
    background: color-mix(in srgb, var(--green) 18%, var(--card, var(--field)));
    color: var(--green); display: flex; align-items: center;
    padding: 0 var(--space-3); min-width: fit-content;
  }
  .votebar .vn {
    background: color-mix(in srgb, var(--red) 18%, var(--card, var(--field)));
    color: var(--red); display: flex; align-items: center;
    justify-content: flex-end; padding: 0 var(--space-3); min-width: fit-content;
  }
  #resToPaper {
    width: 100%; margin-top: var(--space-7);
    display: flex; align-items: center; justify-content: center; gap: var(--space-4);
    background: var(--accent); color: var(--accent-text);
    border: none; border-radius: var(--radius-lg);
    padding: var(--space-6); font-size: var(--text-lg); font-weight: 650; cursor: pointer;
    box-shadow: 0 4px 14px color-mix(in srgb, var(--accent) 30%, transparent);
  }
  #resToPaper:active { transform: translateY(1px); }
  .statgroup { margin-bottom: var(--space-7); }
  .statgroup:last-of-type { margin-bottom: 0; }
  .sg-title {
    color: var(--muted); font-size: var(--text-sm); font-weight: 700;
    text-transform: uppercase; letter-spacing: .5px; margin-bottom: var(--space-2);
  }
  .statrow {
    display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-6);
    padding: var(--space-3) 0; border-top: 1px solid var(--border); font-size: var(--text-md);
  }
  .statrow span { color: var(--muted); }
  .statrow b { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-weight: 650; text-align: right; }

  /* Copy candidate rows (16.08, usability pass): reuse .statrow's look
     but these ARE clickable (tap -> fills the address field above and
     runs the calc) unlike every other .statrow on the page, which are
     inert. `cursor:pointer` alone is invisible on touch, and the row
     was otherwise pixel-identical to the 40+ read-only stat rows
     elsewhere, so nothing told a first-time user these were tappable.
     Reuses the .chev affordance .navitem already uses for the same
     "this row does something" signal, instead of inventing a new one. */
  .cp-cand-row { cursor: pointer; }
  .cp-cand-score { display: flex; align-items: center; gap: var(--space-2); }
  .cp-cand-row .chev { color: var(--muted); opacity: .6; }

  /* Backtest results ("Детальная статистика", 26.07): was ~20 plain
     label/value rows stacked in one long column -- the product's own
     "most money moment" screen reading like a spreadsheet dump, not a
     terminal. Scoped to #statGroups specifically (statRowsHtml() is only
     ever called from renderStatGroups(), nothing else) so the shared
     .statrow/.statgroup classes used elsewhere (partner stats, tier
     counts, real-mode wallet state) are untouched -- this is a display
     change for one screen's data, not a redefinition of a shared
     component. */
  #statGroups .statgroup {
    display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--space-4); margin-bottom: var(--space-6);
  }
  #statGroups .sg-title { grid-column: 1 / -1; margin-bottom: 0; }
  #statGroups .statrow {
    flex-direction: column; align-items: flex-start; gap: var(--space-1);
    background: var(--field); border-radius: var(--radius-lg); padding: var(--space-4) var(--space-5);
    border-top: none;
  }
  #statGroups .statrow span { font-size: var(--text-xs); }
  #statGroups .statrow b { font-size: var(--text-lg); text-align: left; }
  #statGroups .statrow.stat-row-solo {
    grid-column: 1 / -1; flex-direction: row; align-items: center;
    justify-content: space-between; gap: var(--space-6);
  }
  .banner {
    background: color-mix(in srgb, var(--accent) 10%, transparent);
    border-radius: var(--radius-xl); padding: 14px 16px; font-size: var(--text-base); margin-top: var(--space-6); line-height: 1.48;
  }
  .banner b { color: var(--accent); }
  /* Same currentColor sprite icon used everywhere else, sized to sit on the
     banner's own text baseline (design-language audit, 16.08) -- these
     warnings used to open with a raw "⚠️" glyph, the one spot in the app
     still leaning on emoji instead of the Feather-style sprite. */
  .banner .icon { width: 14px; height: 14px; vertical-align: -2.5px; margin-right: var(--space-2); flex: none; }
  #shareResult {
    width: 100%; margin-top: var(--space-6);
    display: flex; align-items: center; justify-content: center; gap: var(--space-4);
    background: color-mix(in srgb, var(--accent) 10%, transparent);
    color: var(--accent);
    border: none; border-radius: var(--radius-lg);
    padding: var(--space-6); font-size: var(--text-md); font-weight: 600;
    cursor: pointer;
  }
  #shareResult:active { background: color-mix(in srgb, var(--accent) 18%, transparent); }
  footer { color: var(--muted); font-size: var(--text-sm); text-align: center; padding: var(--space-8) var(--space-5) 0; opacity: .85; }
  #results, #builder, #mine { display: none; }
  /* ------------------------------- screens -------------------------------- */
  .screen { display: none; }
  .screen.active { display: block; }
  @media (prefers-reduced-motion: no-preference) {
    /* Дизайн-мандат "трек B" (16.08, референс владельца — тёмная точная
       геометрия + характер анимаций): дефолтный ease браузера ускоряется
       и замедляется симметрично, поэтому любой переход на нём читается
       как "обычный CSS", не как что-то спроектированное -- один и тот же
       cubic-bezier(.16,1,.3,1) ("expo-out", тот же принцип, что у Linear/
       Stripe/Vercel) быстро стартует и подробно, precision-точно гасится
       к концу — именно эта асимметрия и даёт ощущение "спроектированного"
       движения, а не иная длительность. Пробный, самый дешёвый и
       обратимый шаг трека B — один параметр в двух местах (здесь и
       .rowIn ниже), до одобрения владельцем не тиражировать на остальные
       transition/animation в файле. */
    .screen.active { animation: screenIn .22s cubic-bezier(.16,1,.3,1); }
  }
  @keyframes screenIn {
    from { opacity: 0; transform: translateY(6px); }
    to { opacity: 1; transform: none; }
  }
  /* Дизайн-проход 21.07: живые списки вместо мгновенного появления —
     каждая строка (.evrow/.metric/.tier/.statgroup/.preset) слегка
     всплывает при монтировании. Работает без единой строчки JS: эти
     элементы всегда заново создаются через innerHTML при каждой загрузке
     экрана, так что CSS-анимация естественно переигрывается сама. nth-child
     задержка — лёгкий каскад, не более 8 строк подряд (дальше появляются
     одновременно, чтобы длинные списки не растягивались по времени). */
  @media (prefers-reduced-motion: no-preference) {
    /* Тот же expo-out, что у .screenIn выше — см. комментарий там. */
    .evrow, .metric, .tier, .statgroup, .preset {
      animation: rowIn .22s cubic-bezier(.16,1,.3,1) backwards;
    }
    .evrow:nth-child(2), .metric:nth-child(2), .tier:nth-child(2), .statgroup:nth-child(2), .preset:nth-child(2) { animation-delay: .03s; }
    .evrow:nth-child(3), .metric:nth-child(3), .tier:nth-child(3), .statgroup:nth-child(3), .preset:nth-child(3) { animation-delay: .06s; }
    .evrow:nth-child(4), .metric:nth-child(4), .tier:nth-child(4), .statgroup:nth-child(4), .preset:nth-child(4) { animation-delay: .09s; }
    .evrow:nth-child(5), .metric:nth-child(5), .tier:nth-child(5), .statgroup:nth-child(5), .preset:nth-child(5) { animation-delay: .12s; }
    .evrow:nth-child(n+6), .metric:nth-child(n+6), .tier:nth-child(n+6), .statgroup:nth-child(n+6), .preset:nth-child(n+6) { animation-delay: .14s; }
  }
  @keyframes rowIn {
    from { opacity: 0; transform: translateY(5px); }
    to { opacity: 1; transform: none; }
  }
  /* Праздничный "поп" для достижений/покупок (бейджи, задания, тарифы) --
     навешивается JS-ом (celebrate()) на конкретный элемент в момент
     успеха, не постоянный класс. */
  @media (prefers-reduced-motion: no-preference) {
    .celebrate-pop { animation: celebratePop .5s cubic-bezier(.34,1.56,.64,1); }
  }
  @keyframes celebratePop {
    0% { transform: scale(1); }
    35% { transform: scale(1.12); }
    60% { transform: scale(.97); }
    100% { transform: scale(1); }
  }
  @keyframes livePulse {
    0%, 100% { opacity: 1; } 50% { opacity: .35; }
  }
  .livedot {
    display: inline-block; width: 8px; height: 8px; border-radius: var(--radius-pill);
    background: var(--green); margin-right: var(--space-3); vertical-align: 1px;
    animation: livePulse 1.4s ease-in-out infinite;
  }
  .spin {
    display: inline-block; width: 14px; height: 14px; vertical-align: -2px;
    border: 2px solid rgba(255,255,255,.35); border-top-color: #fff;
    border-radius: var(--radius-circle); margin-right: var(--space-3);
    animation: spin .7s linear infinite;
  }
  @keyframes spin { to { transform: rotate(360deg); } }
  .featrow { display: flex; align-items: flex-start; gap: var(--space-6); margin: var(--space-6) 0; font-size: var(--text-md); line-height: 1.45; }
  .featrow .icon { color: var(--accent); width: 18px; height: 18px; margin-top: var(--space-1); }
  .mrow { position: relative; }
  .mrow .chip {
    position: absolute; top: 10px; right: 10px;
    font-size: var(--text-xs); font-weight: 700; font-family: var(--font-mono);
    color: var(--brand-2); background: color-mix(in srgb, var(--brand-2) 14%, var(--card));
    border-radius: var(--radius-pill); padding: var(--space-1) var(--space-4);
  }
  /* Shared by the Data/Screener/paper-history "ticker" figures below --
     mono + teal is this app's one consistent signal for "this number is
     live market data", same brand-2 identity as .mrow.active above. */
  .mono-figure {
    font-family: var(--font-mono); font-variant-numeric: tabular-nums;
    color: var(--brand-2); font-weight: 600;
  }
  .mrow.market b { padding-right: 74px; }
  .iconbtn { display: inline-flex; align-items: center; justify-content: center; }
  .iconbtn .icon { width: 15px; height: 15px; }
  .topbar {
    position: sticky; top: 0; z-index: 40;
    display: flex; align-items: center; gap: var(--space-5);
    margin: -12px -14px var(--space-5);
    padding: calc(var(--space-4) + var(--safe-area-top)) var(--space-6) var(--space-4);
    background: color-mix(in srgb, var(--bg) 88%, transparent);
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
    border-bottom: 1px solid var(--border);
  }
  .topbar .linkbtn {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 6px 12px;
    font-size: var(--text-sm); font-weight: 600;
    color: var(--text);
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    box-shadow: var(--shadow-sm);
    flex: none;
    cursor: pointer;
    text-decoration: none;
    transition: background 0.15s ease, transform 0.1s ease;
  }
  .topbar .linkbtn:active {
    transform: scale(0.97);
  }
  .topbar .linkbtn .icon {
    width: 13px; height: 13px;
    color: var(--muted);
  }
  .topbar b {
    font: 700 17px/1.25 var(--font-display);
    letter-spacing: -.2px;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    margin: 0;
  }
  .homehead {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    padding: calc(var(--space-4) + var(--safe-area-top)) var(--space-1) var(--space-3);
    margin-bottom: var(--space-2);
    flex-wrap: wrap;
    row-gap: var(--space-3);
  }
  .homehead-brand {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-width: 0;
  }
  .homehead-brand h1 {
    font: 800 20px/1.2 var(--font-display);
    letter-spacing: -0.3px;
    display: flex;
    align-items: center;
    gap: 7px;
    margin: 0;
    white-space: nowrap;
  }
  .homehead-brand h1 .icon {
    width: 22px;
    height: 22px;
    color: var(--brand);
    flex-shrink: 0;
  }
  .homehead-brand h1 .brand-logo {
    width: 24px;
    height: 24px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.16);
  }
  .homehead .beta-badge {
    font-size: 10px;
    padding: 2px 6px;
    border-radius: var(--radius-sm, 6px);
    background: rgba(255, 170, 0, 0.15);
    color: var(--amber);
    font-weight: 700;
    letter-spacing: 0.5px;
    flex-shrink: 0;
  }
  .homehead-actions {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-left: auto;
    flex-shrink: 0;
  }
  .homehead-actions .feedback-btn,
  .homehead-actions .modechip,
  .homehead-actions .settings-icon-btn {
    height: 30px;
    margin: 0 !important;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    vertical-align: middle;
    border-radius: var(--radius-pill);
    transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
  }
  .homehead-actions .feedback-btn {
    padding: 0 10px;
    font-size: 11.5px;
    font-weight: 600;
    gap: 5px;
    color: var(--brand);
    background: color-mix(in srgb, var(--brand) 12%, transparent);
    border: 1px solid color-mix(in srgb, var(--brand) 28%, transparent);
    cursor: pointer;
  }
  .homehead-actions .feedback-btn .icon {
    width: 13px;
    height: 13px;
  }
  /* Current-account-mode pill -- always visible on home, tap opens the
     mode picker. Dot color = mode: green (demo, no real money) vs brand
     gold (real money involved). */
  .modechip {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 0 10px; border-radius: var(--radius-md, 8px); cursor: pointer;
    border: 1px solid var(--border); background: var(--field);
    color: var(--text); font-size: 12px; font-weight: 650; font-family: inherit;
    height: 30px; box-sizing: border-box; margin: 0;
  }
  .modechip::before { content: ""; width: 6px; height: 6px; border-radius: var(--radius-circle); background: var(--green); flex-shrink: 0; }
  .modechip.real::before { background: var(--brand); }
  .homehead-actions .settings-icon-btn {
    width: 30px;
    padding: 0;
    border: 1px solid var(--border);
    background: var(--field);
    color: var(--muted);
    cursor: pointer;
  }
  .homehead-actions .settings-icon-btn .icon {
    width: 15px;
    height: 15px;
  }
  .homehead-actions .settings-icon-btn:hover {
    color: var(--text);
    border-color: var(--border-hover, var(--border));
  }
  /* Collapsible "what is Safe" explainer -- native <details>, styled as a
     quiet inline question link with the ⓘ affordance the owner asked for. */
  .safehelp summary {
    cursor: pointer; color: var(--accent); font-size: var(--text-md); font-weight: 600;
    list-style: none;
  }
  .safehelp summary::-webkit-details-marker { display: none; }
  .safehelp summary::before { content: "ⓘ "; }
  .homehead p { color: var(--muted); font-size: var(--text-md); margin-top: var(--space-3); max-width: 42ch; }
  /* Deliberately always-dark regardless of app theme — a "vault panel", not
     a bright SaaS gradient blob. Gold as a thin accent border + corner glow
     on near-black reads as premium; gold as a solid fill reads as a cheap
     "get rich quick" banner — this is the one card in the app where that
     distinction was worth getting exactly right (owner's own framing: this
     is the "here's your money" moment). */
  /* Stars-exclusive cosmetic badges (webapp/shop.py cosmetic_badge items) --
     shown once owned, so the showcase itself is a quiet upsell: a visitor
     without one sees what a "founder"/"oracle" looks like on someone else's
     home screen before ever opening the shop. */
  .badgerow { display: flex; gap: var(--space-3); flex-wrap: wrap; margin-bottom: var(--space-6); }
  .badgechip {
    display: inline-flex; align-items: center; gap: var(--space-3);
    padding: var(--space-3) var(--space-5) var(--space-3) var(--space-4); border-radius: var(--radius-md, 8px); font-size: var(--text-base); font-weight: 650;
    background: color-mix(in srgb, var(--brand) 14%, var(--field));
    color: var(--brand); border: 1px solid color-mix(in srgb, var(--brand) 30%, transparent);
  }
  .badgechip .emoji { font-size: var(--text-md); line-height: 1; }
  /* Deliberately modest (24.07 redesign): this used to be a full-height
     "vault" hero card, the single biggest thing on the home screen. Owner
     flagged that as backwards — balance is important but passive, it
     doesn't help decide what to do right now — so it shrank to a single
     compact row and the freed-up space above the fold went to the market
     pulse card (live movers), which actually helps with that decision. */
  .hero {
    display: flex; flex-direction: column; gap: var(--space-6);
    border-radius: var(--radius-xl); padding: 18px 18px 16px; margin-bottom: var(--space-6);
    background:
      radial-gradient(140% 160% at 100% 0%, color-mix(in srgb, var(--brand) 24%, transparent), transparent 55%),
      linear-gradient(160deg, #151821, #0a0b0e);
    color: #f5f3ee; cursor: pointer; user-select: none;
    border: 1px solid color-mix(in srgb, var(--brand) 32%, transparent);
    box-shadow: 0 1px 2px rgba(0,0,0,.12), 0 8px 24px -6px rgba(0,0,0,.6);
  }
  .hero:active { transform: scale(.995); }
  /* The balance/label row on its own (was the whole .hero's flex row
     before the CTA button below was added) -- keeping the CTA out of
     this row matters: an early version put it beside the balance
     (Binance-style) and it squeezed .hero-info enough to push the RU
     label/sub text into 3-4 lines and clip mid-word (found live) --
     the exact class of bug .hero-sub's own 3-line-clamp comment below
     already documents happening once before, from a different cause. */
  .hero-top { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: start; gap: var(--space-5); }
  .hero-info { min-width: 0; overflow-wrap: break-word; }
  .hero-label {
    font-size: var(--text-xs); font-weight: 700; opacity: .9;
    letter-spacing: .8px; text-transform: uppercase; color: var(--brand);
    line-height: 1.35; margin-bottom: 6px;
  }
  .hero-sub {
    font-size: 13px; opacity: .82; margin-top: 0; line-height: 1.5;
    color: var(--muted);
    /* Used to be a single nowrap+ellipsis line capped at a fixed ch count --
       kept clipping different real strings mid-word (the RU tagline at
       46ch, then a real-mode status sentence, found live on a phone
       26.07). Moved to a 2-line clamp -- still not enough: the real-mode
       "complete" status sentence is 69ch and the "in_progress" one is
       74ch (REAL_MODE_STATUS_TEXT), both still clipping mid-word at 2
       lines on a real phone screenshot (found again live, same day).
       3 lines gives real headroom for the longest of these strings
       instead of chasing one more line every time a longer status
       string turns up; the "complete" copy was also shortened
       separately (see homeRealSub) for extra margin. */
    display: -webkit-box; -webkit-line-clamp: 3; line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
  }
  .hero-right { display: flex; align-items: flex-start; gap: var(--space-4); min-width: 0; }
  /* Трек B, четвёртая попытка (16.08, "типографика/масштаб") — было
     17px, визуально в одной весовой категории с подписями вокруг,
     хотя это самое важное число на всей карточке. */
  .hero-balance {
    font: 800 var(--text-xl)/1.15 var(--font-mono); letter-spacing: -.3px;
    font-variant-numeric: tabular-nums; white-space: nowrap;
  }
  .hero .chev { opacity: .75; flex: none; margin-top: 4px; }
  /* Own full-width row below .hero-top (see the comment on .hero-top for
     why it's not squeezed in beside the balance). Same accent gradient
     language as #run (the app's one other primary action), scaled down
     to hero-card size. Deliberately NOT data-nav -- see its click handler
     for why (bubbling into the ancestor .hero card's own click behavior
     would silently override it). */
  .hero-cta {
    display: flex; align-items: center; justify-content: center; gap: var(--space-3); width: 100%;
    margin-top: 6px;
    background: linear-gradient(165deg, color-mix(in srgb, var(--accent) 88%, #fff), var(--accent) 55%, color-mix(in srgb, var(--accent) 84%, #000));
    color: var(--accent-text);
    border: 0; border-radius: var(--radius-md);
    padding: 12px 18px; font-size: var(--text-md); font-weight: 650; letter-spacing: -.01em;
    cursor: pointer; white-space: nowrap; min-height: 46px;
    box-shadow: inset 0 1px 0 rgba(255,255,255,.28), 0 3px 10px -2px color-mix(in srgb, var(--accent) 45%, transparent);
  }
  .hero-cta .icon { width: 15px; height: 15px; fill: currentColor; stroke: none; }
  .hero-cta:active { transform: translateY(1px); box-shadow: inset 0 1px 0 rgba(255,255,255,.28); }

  @media (max-width: 420px) {
    .hero-top { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--space-4); }
    .hero-info { flex: 1; min-width: 0; }
    .hero-right { flex: none; justify-content: flex-end; align-items: center; gap: var(--space-2); margin-left: var(--space-2); }
    .hero-balance, #screen-home .hero-balance { font-size: 18px !important; letter-spacing: -0.2px; text-align: right; }
    .hero-sub { overflow-wrap: anywhere; }
    .hero-cta { white-space: normal; min-height: 46px; line-height: 1.25; margin-top: 6px; }
  }

  /* Home screen density pass (26.07 first cut, reworked 28.07). The 26.07
     version put spotlight/last-run/open-position/quests in a 2-column
     grid with a hardcoded min-height (92px, tuned for spotlight's 2-line
     carousel card) applied to every tile alike, plus a "solo" class that
     stretched the odd one out to full width whenever the visible count
     was odd. That hardcoded height meant a genuinely one-line tile (last
     run, open position) sat mostly empty whenever it did NOT happen to be
     the odd one out — exactly the "куча свободного места" the owner
     flagged in "Последний тест" (28.07), and a symptom of forcing
     dissimilar content (a scrollable carousel vs. a single status line)
     into the same fixed box. Fix: stop pairing them at all. Пульс рынка
     and the movers tile keep the .hometile card (each needs its own full
     width for the scroll-snap carousel); the three status lines (last
     run/open position/quests) became plain full-width .navitem rows —
     the same icon+label+chevron component the rest of the app already
     uses for single-line, height-hugging status/nav rows (see
     Settings/Ещё) — so there is no min-height, no pairing, and no
     balancing function needed anymore. */
  .hometile {
    background: var(--card); border-radius: var(--radius-lg); padding: 16px;
    margin-bottom: var(--space-6); display: flex; flex-direction: column;
    border: 1px solid var(--border); box-shadow: none; cursor: default;
  }
  .hometile[data-nav] { cursor: pointer; user-select: none; }
  .hometile[data-nav]:active { transform: scale(.98); }
  .hometile .h {
    display: flex; align-items: center; gap: var(--space-3); font-size: var(--text-xs); font-weight: 700;
    color: var(--muted); text-transform: uppercase; letter-spacing: .4px;
  }
  .hometile .h .icon { width: 14px; height: 14px; }
  .hometile .main { font-size: var(--text-md); font-weight: 650; margin-top: 10px; line-height: 1.45; }
  .hometile .main.empty { color: var(--muted); font-weight: 500; }

  /* Pulse tiles gone dynamic (26.07, owner: "нужно чтобы такие вкладки как
     пульс рынка были динамичными, а не статичными... чтобы их можно было
     листать"). Two changes, same mechanism for both spotlight and movers:
     (1) native horizontal scroll-snap carousel instead of a single static
     card / vertical list, so more than one market is reachable by swipe;
     (2) a live-polling refresh (see startHomePulsePolling() in the script
     below) plus a small pulsing dot + "обновлено Nс" ticker, so return
     visits (or just sitting on the screen) show it's actually alive, not
     a snapshot taken once at page load. */
  .pulse-live { display: flex; align-items: center; gap: var(--space-3); margin-left: auto; text-transform: none; letter-spacing: 0; font-weight: 600; }
  .pulse-live .dot { width: 6px; height: 6px; border-radius: var(--radius-circle); background: var(--green); animation: pulse-live-blink 2s ease-in-out infinite; }
  @keyframes pulse-live-blink { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }
  .pulse-carousel {
    display: flex; gap: var(--space-5); overflow-x: auto; scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch; scrollbar-width: none; margin-top: var(--space-4);
    min-width: 0; width: 100%;
  }
  .pulse-carousel::-webkit-scrollbar { display: none; }
  .pulse-carousel > * { scroll-snap-align: start; flex: none; }
  /* .empty (the "no data yet" placeholder, no explicit width of its own
     like .pulsecard has) inherited flex-item auto-sizing from the rule
     above -- a flex item's content defaults to its max-content (no-wrap)
     width, so the placeholder text ran off the right edge of the card
     instead of wrapping, in both spotlight and movers (confirmed live
     28.07, pre-dating this Home pass). Giving it width: 100% makes it
     behave like every other carousel child and wrap normally. */
  .pulse-carousel > .empty { width: 100%; }
  .spotlight-carousel { margin-top: var(--space-4); }
  .spotlight-carousel > .pulsecard { width: 100%; }
  .movers-carousel > .pulsecard { width: 200px; }
  .pulsecard {
    background: var(--field); border-radius: var(--radius-lg); padding: var(--space-5) var(--space-6); cursor: pointer;
  }
  /* .tr-main/.tr-title/.tr-pnl are shared with the old full-width evrow
     list (~350px, title+tag comfortably side by side) -- reused here at
     roughly half that width. Side-by-side there meant the title, which is
     the one thing worth reading, got ellipsis-squeezed down to almost
     nothing by the tag next to it (found 26.07 on a real device: the
     market name was effectively invisible). Stack instead of row, and let
     the title wrap up to 2 lines instead of truncating to 1. */
  .pulsecard .tr-main { flex-direction: column; align-items: flex-start; gap: var(--space-1); }
  .pulsecard .tr-title {
    font-size: var(--text-base); font-weight: 650; line-height: 1.3; white-space: normal;
    display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  }
  .pulsecard.pulse-featured .tr-title { font-size: var(--text-lg); }
  /* Трек B, четвёртая попытка (16.08, владелец: "типографика/масштаб").
     Цена/изменение — единственные цифры на этой карточке, ради которых
     она вообще существует, но раньше были мельче заголовка (11-11.5px
     против 13px) — визуально не то, ради чего открыли карточку, читалось
     как подпись, не как факт. Цифры вперёд: .tr-pnl и цена внутри
     .tr-meta увеличены и утяжелены, заголовок рынка остаётся контекстом,
     не главным элементом. */
  .pulsecard .tr-pnl { font-size: var(--text-lg); font-weight: 700; margin-top: var(--space-1); }
  .pulsecard .tr-meta { font-size: var(--text-xs); color: var(--muted); margin-top: var(--space-2); }
  .pulsecard .tr-meta .mono-figure { font-size: var(--text-base); }
  .pulsecard.flash { animation: pulsecard-flash 1.1s ease-out; }
  @keyframes pulsecard-flash {
    0% { background: color-mix(in srgb, var(--brand) 30%, var(--field)); }
    100% { background: var(--field); }
  }
  /* Promo/engagement carousel (Binance/OKX reward-banner pattern) -- own
     card skin, distinct from .pulsecard's neutral market-data look, but
     shares the .pulse-carousel scroll-snap container and .pulse-dots
     indicator as-is (setupPulseCarouselDots() doesn't care which card
     class it's pointed at). */
  .promo-carousel > .promocard { width: 100%; }
  .promocard {
    background: color-mix(in srgb, var(--brand) 12%, var(--field));
    border: 1px solid color-mix(in srgb, var(--brand) 26%, transparent);
    border-radius: var(--radius-lg); padding: var(--space-6);
    display: flex; align-items: center; justify-content: space-between; gap: var(--space-5); cursor: default;
  }
  .promocard-text { min-width: 0; }
  .promocard-title { font-size: var(--text-base); font-weight: 700; line-height: 1.3; }
  .promocard-sub { font-size: var(--text-sm); color: var(--muted); margin-top: var(--space-1); }
  .promocard .promoGo { flex: none; color: var(--brand); font-weight: 700; font-size: var(--text-base); padding: var(--space-2) 0; }
  .pulse-dots { display: flex; gap: var(--space-3); justify-content: center; margin-top: var(--space-4); }
  .pulse-dot { width: 5px; height: 5px; border-radius: var(--radius-circle); background: var(--border); transition: background .15s, width .15s; }
  .pulse-dot.active { background: var(--brand); width: 14px; border-radius: var(--radius-sm); }

  .navmenu { margin-bottom: var(--space-7); }
  /* Two-tier home menu (19.07): frequent destinations (Бэктест/Данные)
     keep the full card treatment; everything reached "from time to time"
     (Скринер/Задания/Тарифы/Предложить событие/Админ) is visually
     quieter — same tap targets, less weight, under its own label — so the
     eye isn't asked to treat 7-8 flat rows as equally important. Same
     idea Instagram uses for bottom-tab vs profile-menu items. */
  .navgroup-label {
    color: var(--muted); font-size: var(--text-sm); font-weight: 700;
    text-transform: uppercase; letter-spacing: .5px; margin: var(--space-2) var(--space-1) var(--space-4);
  }
  .navmenu-secondary .navitem { padding: var(--space-6) var(--space-7); margin-bottom: var(--space-3); box-shadow: none; }
  .navmenu-secondary .navitem .ico { width: 34px; height: 34px; border-radius: var(--radius-md); }
  .navmenu-secondary .navitem .ico .icon { width: 17px; height: 17px; }
  .navmenu-secondary .navitem b { font-size: var(--text-md); font-weight: 600; }

  /* "Ещё" screen (26.07): compact icon grid replacing 10 full-width rows.
     Tone classes (tone-brand/tone-amber/tone-red/...) reused as-is from
     .navitem's .ico -- same per-destination color coding, just packed
     3-across instead of stacked, closer to how Binance/OKX lay out a
     "More"/discover menu than a settings-style list. */
  /* margin-bottom -- found live 11.08 (owner, dark theme): every other
     top-level block on Home has its own bottom margin (.card 12px, .hero/
     .navitem/.hometile 10px) but this one had none, so the icon row sat
     flush against whatever card followed it -- readable as "touching" once
     the two backgrounds are similar (unnoticeable in light theme's high-
     contrast shadows, obvious in dark). 12px matches .card's convention,
     the type it touches most often (promo card right below it on Home). */
  /* Трек B, пятая попытка (16.08). Четвёртая попытка (список строк
     вместо сетки плиток) владелец отклонил жёстко, живьём на телефоне:
     "список выглядит ещё хуже, чем плитки" — и прямо распорядился:
     вернуть сетку плиток как было, перерисовать иконки в стиле
     сакральной геометрии. Возвращена сетка `.moregrid`/`.moretile` (тот
     же плоский язык, что и `.card` — см. третью попытку, эта часть не
     под вопросом была). Новое — `.geoframe`: декоративный слой ПОД
     обычной функциональной иконкой внутри `.ico`, один переиспользуемый
     SVG-символ (`#i-geoframe` в defs выше) — два концентрических круга,
     8 расходящихся лучей через 45°, повёрнутый на 45° квадрат-ромб в
     центре, тот же мотив "круг + лучи + ромб", что и в исходном
     референсе владельца (генеративная графика @psaltix). Не 20 разных
     мандал вручную — один узор, тон/цвет наследуется от .tone-*, как и
     раньше. */
  .moregrid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-5); margin-bottom: var(--space-6); }
  html[data-layout="desktop"] .moregrid {
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  }
  .moretile {
    background: var(--card); border-radius: var(--radius-lg); padding: var(--space-7) var(--space-4) var(--space-6);
    display: flex; flex-direction: column; align-items: center; gap: var(--space-4);
    text-align: center; cursor: pointer; user-select: none; min-width: 0;
    border: 1px solid var(--border); box-shadow: none;
  }
  .moretile:active { transform: scale(.97); }
  .ico {
    width: 46px; height: 46px; border-radius: var(--radius-md); flex: none;
    display: flex; align-items: center; justify-content: center;
    position: relative;
  }
  .ico .geoframe, .onboarding-icon .geoframe {
    position: absolute; inset: 0; width: 100%; height: 100%;
    stroke-width: 1; opacity: .55; pointer-events: none;
  }
  .ico .icon:not(.geoframe) { width: 18px; height: 18px; position: relative; }
  .moretile b { font-size: var(--text-sm); font-weight: 650; line-height: 1.25; }

  /* First-run onboarding: a fixed overlay above everything else, not part
     of the .screen/showScreen() router — it has to block interaction with
     the app underneath, which a normal screen swap doesn't do. */
  .onboarding-overlay {
    position: fixed; inset: 0; z-index: 500;
    background: var(--bg);
    display: flex; flex-direction: column;
    padding: var(--space-10) var(--space-10) calc(var(--space-10) + var(--safe-area-bottom));
    max-width: 560px; margin: 0 auto;
  }
  .onboarding-step { display: none; flex: 1; flex-direction: column; }
  .onboarding-step.active { display: flex; }
  .onboarding-icon {
    width: 64px; height: 64px; border-radius: var(--radius-xl);
    background: color-mix(in srgb, var(--brand) 14%, transparent);
    color: var(--brand);
    display: flex; align-items: center; justify-content: center;
    position: relative;
    margin-bottom: var(--space-10);
  }
  .onboarding-icon .icon:not(.geoframe) { width: 28px; height: 28px; position: relative; }
  .onboarding-step h2 { font-size: var(--text-xl); font-weight: 800; letter-spacing: -.3px; margin-bottom: var(--space-5); }
  .onboarding-step p { color: var(--muted); font-size: var(--text-lg); line-height: 1.55; }
  .onboarding-step ul { margin-top: var(--space-7); padding-left: 0; list-style: none; display: flex; flex-direction: column; gap: var(--space-6); }
  .onboarding-step li { display: flex; gap: var(--space-5); align-items: flex-start; font-size: var(--text-lg); }
  .onboarding-step li .icon { color: var(--brand); flex: none; margin-top: var(--space-1); }
  .onboarding-dots { display: flex; gap: var(--space-3); justify-content: center; margin: var(--space-8) 0; }
  .onboarding-dot { width: 7px; height: 7px; border-radius: var(--radius-circle); background: var(--field); transition: background .15s, width .15s; }
  .onboarding-dot.active { background: var(--brand); width: 20px; border-radius: var(--radius-sm); }
  .onboarding-check {
    display: flex; align-items: flex-start; gap: var(--space-5);
    background: var(--field); border-radius: var(--radius-xl); padding: var(--space-6) var(--space-7);
    margin: var(--space-2) 0 var(--space-8); font-size: var(--text-md); color: var(--text);
  }
  .onboarding-check input { width: 18px; height: 18px; flex: none; margin-top: var(--space-1); accent-color: var(--accent); }
  .onboarding-actions { display: flex; gap: var(--space-5); align-items: center; }
  .onboarding-skip { color: var(--muted); font-size: var(--text-md); font-weight: 600; background: none; border: none; padding: var(--space-4); cursor: pointer; }

  /* Account mode picker: same fixed-overlay pattern as onboarding, shown
     once right after it (and again from Settings). Higher z-index than
     the tabbar so it fully covers it, like onboarding does. */
  .modeselect-overlay {
    position: fixed; inset: 0; z-index: 480;
    background: var(--bg);
    display: flex; flex-direction: column;
    padding: var(--space-12) var(--space-10) calc(var(--space-10) + var(--safe-area-bottom));
    max-width: 560px; margin: 0 auto;
    /* The real-mode screen outgrew one viewport (status rows + wallet
       balances + explainer) -- without this the bottom buttons are
       simply unreachable, reported live by the owner 22.07. */
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }
  .modeselect-overlay h2 { font-size: var(--text-xl); font-weight: 800; letter-spacing: -.3px; margin-bottom: var(--space-2); }
  /* Точечный фикс 10.09 (0.2): заголовок шапки модалки переносится, а не
     уезжает под кнопку ✕. h2 во flex-шапке держит min-width:auto и не
     сжимается, а .btn-sm тянет flex:1 — длинный заголовок вылезал из
     своего бокса и рисовался под кнопкой (скриншот 10.09: cpQuickFollowModal
     «Следовать за китом», modeRealInfo «Реальный режим»). Правило только
     про шапки .modeselect-overlay: h2 и вложенная logo-обёртка забирают
     свободное место и переносятся, кнопка ✕ жмётся к контенту. */
  .modeselect-overlay h2 { min-width: 0; overflow-wrap: break-word; }
  .modeselect-overlay > div:first-child > h2,
  .modeselect-overlay > div:first-child > div > h2 { flex: 1 1 auto; }
  .modeselect-overlay > div:first-child > div { min-width: 0; flex: 1 1 auto; }
  .modeselect-overlay > div:first-child > button { flex: none; }
  /* Тот же класс 10.09 (0.2): баланс-карта спот-терминала
     (renderTradingStartForm, paper + trading). Подпись «Свободно на
     кошельке» упиралась в кнопку «Пополнить» встык (замер: gap 0px):
     кнопка через .btn-sm тянула flex:1 вместо контента. Кнопка жмётся к
     контенту, текст получает воздух. */
  #spotDepositBtn { flex: none; }
  .modeselect-overlay > p { color: var(--muted); font-size: var(--text-md); line-height: 1.5; margin-bottom: 22px; }
  /* Входная анимация оверлеев (09.08). Смена экрана (.screen.active →
     screenIn) и строки списков (rowIn) её имеют с 21.07, а два
     полноэкранных оверлея — онбординг и выбор режима/раскладки — были
     единственными крупными поверхностями, возникавшими мгновенно, рывком
     поверх всего. Такой скачок читается как сбой отрисовки, а не как
     переход, и заметен именно там, где цена ошибки выше всего: первый
     запуск и выбор режима счёта.
     Тот же словарь, что у screenIn, с двумя отличиями: чуть длиннее
     (.2s против .16s — поверхность во весь экран, на короткой длительности
     переход не читается) и лёгкое сжатие вместо вертикального сдвига —
     translateY на полноэкранной панели выглядит прыжком, а не появлением.
     Оба оверлея переключаются через display:none → flex, поэтому анимация
     переигрывается сама при каждом показе, без единой строчки JS — тот же
     приём, что уже работает для rowIn. */
  @media (prefers-reduced-motion: no-preference) {
    .onboarding-overlay, .modeselect-overlay { animation: overlayIn .2s ease; }
  }
  @keyframes overlayIn {
    from { opacity: 0; transform: scale(.985); }
    to { opacity: 1; transform: none; }
  }
  .modecard {
    display: flex; align-items: center; gap: var(--space-7);
    background: var(--card); border-radius: var(--radius-lg);
    padding: 18px var(--space-8); margin-bottom: var(--space-6);
    cursor: pointer; user-select: none; box-shadow: none;
    text-align: left; border: 1px solid var(--border); width: 100%; color: var(--text);
  }
  .modecard:active { transform: scale(.99); }
  .modecard .ico {
    width: 48px; height: 48px; border-radius: var(--radius-xl); flex: none;
    display: flex; align-items: center; justify-content: center;
    position: relative;
  }
  .modecard .ico .geoframe {
    position: absolute; inset: 0; width: 100%; height: 100%;
    stroke-width: 1; opacity: .55; pointer-events: none;
  }
  .modecard .ico .icon:not(.geoframe) {
    width: 22px; height: 22px; position: relative; z-index: 1;
  }
  .modecard b { font-size: var(--text-lg); display: block; letter-spacing: -.1px; }
  .modecard .sub { color: var(--muted); font-size: var(--text-base); display: block; margin-top: var(--space-2); line-height: 1.4; }
  .modecard-badge {
    background: color-mix(in srgb, var(--muted) 18%, var(--field));
    color: var(--muted); font-size: var(--text-xs); font-weight: 700;
    letter-spacing: .3px; text-transform: uppercase;
    border-radius: var(--radius-pill); padding: var(--space-2) var(--space-4); margin-left: auto; flex: none;
    /* 09.08, найдено живым скриншотом: карточка — flex с align-items:center,
       и бейдж отжимает описание, из-за чего в «Реальном» текст переносится
       на 3 строки (в «Демо» без бейджа — на 2). Центрированный против
       трёхстрочного блока бейдж повисал где-то между строк, не совпадая ни
       с заголовком, ни с чем-либо ещё. Тот же класс проблемы и то же
       решение, что уже приняты выше для .topbar (align-items: flex-start
       из-за двухстрочных заголовков): выравниваем бейдж по первой строке,
       то есть по заголовку, к которому он и относится. */
    align-self: flex-start; margin-top: var(--space-1);
  }
  .modeselect-back { margin-top: auto; }
  .bigbtn {
    width: 100%;
    background: var(--accent); color: var(--accent-text);
    border: 0; border-radius: var(--radius-xl);
    padding: var(--space-8); font-size: var(--text-lg); font-weight: 650;
    cursor: pointer;
    box-shadow: 0 4px 14px color-mix(in srgb, var(--accent) 35%, transparent);
  }
  .bigbtn:disabled { opacity: .45; box-shadow: none; }
  .bigbtn:active:not(:disabled) { transform: translateY(1px); }
  /* Второстепенное действие рядом с главным: «сбросить счёт» не должно
     выглядеть так же зазывающе, как «обновить». */
  .bigbtn.ghost {
    background: transparent; color: var(--muted);
    border: 1px solid var(--border); box-shadow: none;
  }
  .navitem {
    display: flex; align-items: center; gap: var(--space-6);
    background: var(--card);
    border-radius: var(--radius-lg); padding: var(--space-7) var(--space-8); margin-bottom: var(--space-5);
    cursor: pointer; user-select: none;
    border: 1px solid var(--border); box-shadow: none;
  }
  .navitem:active { transform: scale(.99); }
  .navitem .ico {
    width: 44px; height: 44px; border-radius: var(--radius-md); flex: none;
    display: flex; align-items: center; justify-content: center;
    position: relative;
  }
  .navitem .ico .geoframe {
    position: absolute; inset: 0; width: 100%; height: 100%;
    stroke-width: 1; opacity: 0.55; pointer-events: none;
  }
  .navitem .ico .icon:not(.geoframe) {
    width: 18px; height: 18px; position: relative; z-index: 1;
  }
  /* Settings screen: crisp hierarchy, inset navitem cards, and structured spacing */
  #screen-settings .card {
    margin-bottom: var(--space-4);
  }
  #screen-settings .card .step {
    margin-bottom: var(--space-4);
    font-size: 15px;
    font-weight: 650;
    color: var(--text);
  }
  #screen-settings .card .step .icon {
    color: var(--brand);
  }
  #screen-settings .navitem {
    background: var(--field);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: var(--space-6) var(--space-6);
    margin-bottom: 0;
    transition: background 0.15s ease, border-color 0.15s ease;
  }
  #screen-settings .navitem:hover {
    background: color-mix(in srgb, var(--field) 80%, var(--card));
  }
  #screen-settings .navitem + .navitem {
    margin-top: var(--space-4);
  }
  #screen-settings .navitem .navtext b {
    font-size: var(--text-base);
    font-weight: 600;
  }
  #screen-settings .navitem .navtext .sub {
    font-size: var(--text-sm);
    color: var(--muted);
    margin-top: 2px;
  }
  /* Дизайн-мандат "трек B", вторая попытка (16.08, владелец: "это совсем
     не похоже на тот референс", "выглядело дёшево и сгенерировано, так и
     выглядит"). Первая попытка (обводка в цвете тона вместо сплошной
     заливки) не сработала по существу: КАЖДАЯ иконка навигации всё ещё
     получала свой отдельный яркий цвет (красный/жёлтый/зелёный/
     фиолетовый) — вот это и есть настоящий generic-AI-mockup сигнал,
     разноцветные бейджи-«весёлые кнопки», не форма заливки. Референс
     (генеративная графика @psaltix) — монохромная белая линия на чёрном,
     ни одного цветного пятна. Убираем радужную систему тонов для
     навигационных иконок целиком — один нейтральный монохромный вид для
     всех (`color: var(--text)`, тонкая обводка от --border, почти без
     заливки), различие между пунктами меню теперь несёт форма иконки
     (звезда/флаг/стрелка), не цвет. Цвет остаётся ТАМ, где он что-то
     означает по делу — прибыль/убыток (--green/--red на реальных цифрах
     P&L), акцентная кнопка (--accent/--brand на CTA) — это не тронуто,
     речь только про декоративную категоризацию пунктов меню, которая
     реального смысла не несла. border-radius сжат до 8px (был 14-18px
     до первой попытки, 10-12px после неё) — ближе к прямому углу, но не
     до нуля, чтобы не выбиваться из остального `.card`-языка приложения
     раньше, чем он тоже пройдёт этот пересмотр. */
  .tone-muted {
    background: color-mix(in srgb, var(--muted) 12%, transparent);
    color: var(--muted);
    border: 1px solid color-mix(in srgb, var(--muted) 24%, transparent);
  }
  .tone-brand {
    background: color-mix(in srgb, var(--brand) 12%, transparent);
    color: var(--brand);
    border: 1px solid color-mix(in srgb, var(--brand) 28%, transparent);
  }
  .tone-green {
    background: color-mix(in srgb, var(--green) 12%, transparent);
    color: var(--green);
    border: 1px solid color-mix(in srgb, var(--green) 28%, transparent);
  }
  .tone-amber {
    background: color-mix(in srgb, var(--amber) 12%, transparent);
    color: var(--amber);
    border: 1px solid color-mix(in srgb, var(--amber) 28%, transparent);
  }
  .tone-red {
    background: color-mix(in srgb, var(--red) 12%, transparent);
    color: var(--red);
    border: 1px solid color-mix(in srgb, var(--red) 28%, transparent);
  }
  .tone-violet {
    background: color-mix(in srgb, var(--violet) 12%, transparent);
    color: var(--violet);
    border: 1px solid color-mix(in srgb, var(--violet) 28%, transparent);
  }
  .tone-cyan {
    background: color-mix(in srgb, var(--cyan) 12%, transparent);
    color: var(--cyan);
    border: 1px solid color-mix(in srgb, var(--cyan) 28%, transparent);
  }
  .tone-teal {
    background: color-mix(in srgb, var(--brand-2) 12%, transparent);
    color: var(--brand-2);
    border: 1px solid color-mix(in srgb, var(--brand-2) 28%, transparent);
  }
  /* Live order book depth rows -- a background bar sized to each level's
     relative volume, MT5-DOM style, without animating anything (a value
     just changing on the next poll reads as "live" on its own; a
     transition on top would fight the update instead). */
  .obrow {
    position: relative; display: flex; justify-content: space-between;
    padding: var(--space-2) var(--space-3); font-size: var(--text-sm); font-family: var(--font-mono);
    font-variant-numeric: tabular-nums; border-radius: var(--radius-sm); overflow: hidden;
    margin-top: var(--space-1);
  }
  .obrow .obbar { position: absolute; inset: 0; z-index: 0; }
  .obrow.bid .obbar { background: color-mix(in srgb, var(--green) 16%, transparent); }
  .obrow.ask .obbar { background: color-mix(in srgb, var(--red) 16%, transparent); }
  .obrow > span { position: relative; z-index: 1; }
  .obrow .obprice { color: var(--brand-2); font-weight: 600; }
  .obrow .obqty { color: var(--muted); }
  .navitem .navtext { min-width: 0; }
  .navitem b { font-size: var(--text-lg); display: block; letter-spacing: -.1px; }
  .navitem .sub { color: var(--muted); font-size: var(--text-base); display: block; margin-top: var(--space-1); }
  .navitem .chev { margin-left: auto; color: var(--muted); flex: none; opacity: .6; }
  .chev { width: 18px; height: 18px; }
  .rule {
    background: var(--field);
    border-radius: var(--radius-lg); padding: var(--space-5); margin-bottom: var(--space-4);
  }
  .rule-row { display: flex; gap: var(--space-3); margin-bottom: var(--space-3); }
  .rule-row:last-of-type { margin-bottom: 0; }
  .rule select, .rule input {
    padding: var(--space-4) var(--space-5); font-size: var(--text-lg); border-radius: var(--radius-md);
    flex: 1 1 0; min-width: 0;
    text-overflow: ellipsis;
  }
  .rule-row-op select { flex: 1.15 1 0; }
  .rule-row-op input { flex: 1 1 0; }
  .rule-row-op input.win { flex: 0.9 1 0; }
  .rule .del {
    background: none; border: none; color: var(--muted);
    font-size: var(--text-lg); cursor: pointer;
    flex: 0 0 auto; width: 30px; padding: 0;
  }
  .grouphead { display: flex; align-items: center; justify-content: space-between; margin: var(--space-7) 0 var(--space-4); }
  .grouphead b { font-size: var(--text-lg); letter-spacing: -.1px; }
  .emptystate { text-align: center; padding: var(--space-10) var(--space-5) var(--space-7); }
  .emptystate .icon { width: 34px; height: 34px; color: var(--muted); opacity: .45; }
  .emptystate p { color: var(--muted); font-size: var(--text-base); margin: var(--space-4) auto var(--space-6); max-width: 34ch; }
  .btn-tonal {
    display: inline-flex; align-items: center; gap: var(--space-4);
    background: color-mix(in srgb, var(--accent) 10%, transparent);
    color: var(--accent); border: none; border-radius: var(--radius-lg);
    padding: var(--space-5) var(--space-8); font-size: var(--text-md); font-weight: 600; cursor: pointer;
  }
  .btn-tonal:active { background: color-mix(in srgb, var(--accent) 18%, transparent); }
  .grouphead select { width: auto; padding: var(--space-3) var(--space-5); font-size: var(--text-base); }
  .addrule {
    width: 100%; background: none;
    border: 1.5px dashed color-mix(in srgb, var(--accent) 35%, transparent);
    border-radius: var(--radius-lg); color: var(--accent); padding: var(--space-5);
    font-size: var(--text-md); font-weight: 600; cursor: pointer; margin-bottom: var(--space-2);
  }
  .mrow {
    text-align: left; width: 100%;
    background: var(--field); border: none;
    border-radius: var(--radius-lg); padding: var(--space-5) var(--space-6); margin-top: var(--space-3);
    color: var(--text); cursor: pointer; font-size: var(--text-base); font-weight: 500;
  }
  /* --brand-2 (teal) instead of --accent here on purpose -- same reasoning
     as --brand's own comment at the top of this file: --accent tracks
     whatever blue/whatever the viewer's own Telegram theme happens to be,
     so a "your market is selected" state drawn in --accent reads as generic
     Telegram chrome. This one interaction (picking a market to test against)
     is the single most "data terminal" moment on the whole Data screen --
     worth a deliberate brand color instead of an inherited one. */
  .mrow.active { box-shadow: inset 0 0 0 1.5px var(--brand-2); }
  .mrow small { color: var(--muted); display: block; font-weight: 400; }
  .runs { font-size: var(--text-base); width: 100%; border-collapse: collapse; }
  .runs td { padding: var(--space-3) var(--space-2); border-top: 1px solid var(--border); }
  .runs td:first-child { color: var(--muted); white-space: nowrap; }
  .tier {
    background: var(--field);
    border-radius: var(--radius-xl); padding: var(--space-6); margin-bottom: var(--space-4);
  }
  /* Desktop-only (28.07, continuation of the PC-shell pass): tiers were
     stacked full-width, needing scroll to compare -- exactly the kind of
     side-by-side comparison every SaaS pricing page puts in a row on a
     wide screen. `align-items: start` is required, not decorative --
     tiers have different bullet-list lengths (Free has fewer limits
     listed than Elite), and CSS Grid's default `stretch` would have
     reintroduced the exact "shorter card stretched to match its taller
     row-neighbor" bug already found and fixed once today on Home. margin
     -bottom: 0 under the grid so gap (not margin) owns the spacing --
     otherwise a card's own 8px margin stacks on top of the grid gap,
     doubling it at every row boundary. */
  html[data-layout="desktop"] #tiersList {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: var(--space-5); align-items: start;
  }
  html[data-layout="desktop"] .tier { margin-bottom: 0; }
  /* Тарифы на ПК (11.08, поправлено 16.08): при ровно 3 колонках (~1440px)
     4-я карточка (Free/Plus/Pro/Elite) оставалась одна в новой строке с
     пустым местом на две трети экрана справа; auto-fit не растягивает её
     (треки 2-3 заняты строкой выше). Первая версия ловила это чистым CSS
     (:last-child:nth-child(3n+1)) -- жёстко зашитым под ИМЕННО 3 колонки.
     На более узком ПК-окне (найдено живьём на 820px) auto-fit сам
     схлопывается в 1 колонку -- Free/Plus/Pro/Elite и так все в один
     ряд каждая, орфана нет по определению, но старый селектор всё равно
     СРАБАТЫВАЛ (matches по позиции в DOM, не по факту переноса строки) и
     сжимал Elite до 266px посреди пустоты, пока три соседние карточки
     были на всю ширину. syncTiersGridSolo() (см. loadTiers) высчитывает
     число РЕАЛЬНЫХ колонок из getComputedStyle и включает .tier-solo,
     только когда последняя карточка действительно одна в своей строке --
     при любом числе колонок, не только трёх. */
  html[data-layout="desktop"] .tier.tier-solo {
    grid-column: 1 / -1; max-width: 266px; margin: 0 auto;
  }
  .tier.current { box-shadow: inset 0 0 0 1.5px var(--accent); }
  .tier .head { display: flex; align-items: center; gap: var(--space-4); margin-bottom: var(--space-3); }
  .tier .head b { font-size: var(--text-lg); }
  .tier .badge {
    background: var(--accent); color: var(--accent-text);
    font-size: var(--text-xs); font-weight: 700; border-radius: var(--radius-pill);
    padding: var(--space-1) var(--space-4);
  }
  .tier ul { margin: 0; padding-left: 18px; font-size: var(--text-base); color: var(--muted); }
  .tier li { margin-bottom: var(--space-1); }
  .tier li.tier-feature { color: var(--text); font-weight: 600; margin-bottom: var(--space-2); }
  .tier.tier-beta-muted {
    opacity: 0.55;
    background: rgba(255, 255, 255, 0.02);
    border: 1px dashed var(--border);
  }
  .tier.tier-beta-muted .tier-feature {
    color: var(--muted);
  }
  .tier-beta-note {
    margin-top: var(--space-3);
    padding: var(--space-2) var(--space-3);
    background: rgba(255, 255, 255, 0.03);
    border: 1px dashed var(--border);
    border-radius: var(--radius-sm);
    font-size: var(--text-xs);
    color: var(--muted);
    line-height: 1.4;
  }
  .fieldhelp { color: var(--muted); font-size: var(--text-sm); line-height: 1.5; margin-top: 6px; }
  /* A glossary of many terms reads as a wall of text if run together in one
     paragraph (found by re-checking the metrics glossary once it grew past
     ~10 entries over the session) — each entry gets its own block with a
     hairline separator, same visual language as .statrow's border-top. */
  .glossary { margin-top: var(--space-2); }
  .glossary-item {
    color: var(--muted); font-size: var(--text-sm); line-height: 1.45;
    padding: var(--space-4) 0; border-top: 1px solid var(--border);
  }
  .glossary-item:first-child { border-top: none; padding-top: 0; }
  details.more { margin-top: var(--space-3); }
  details.more summary {
    color: var(--accent); font-size: var(--text-base); font-weight: 600;
    cursor: pointer; list-style: none; user-select: none;
  }
  details.more summary::-webkit-details-marker { display: none; }
  /* content's leading space used to be a plain, breakable space -- the
     browser could (and on a real device, did: Справка screen, a summary
     line landing right at the wrap width) break the line right there,
     leaving the ▾/▴ marker stranded alone on its own line. \00a0 is a
     non-breaking space: same visual gap, no break opportunity, so the
     marker always stays attached to the summary's last word. */
  details.more summary::after { content: "\00a0▾"; font-size: var(--text-xs); }
  details.more[open] summary::after { content: "\00a0▴"; }
  details.more .fieldhelp { margin-top: var(--space-2); }
  .rulehint {
    color: var(--muted); font-size: var(--text-xs); line-height: 1.3; margin-top: var(--space-2);
  }
  .rulehint:empty { display: none; }
  .summarybox {
    background: color-mix(in srgb, var(--accent) 8%, var(--card));
    border-radius: var(--radius-lg); padding: var(--space-6); font-size: var(--text-base);
    color: var(--text); margin-bottom: var(--space-6); line-height: 1.45;
  }
  .summarybox b { color: var(--accent); }
  .tabhelp { color: var(--muted); font-size: var(--text-sm); margin: -4px 0 var(--space-5); }
  #introCard p { font-size: var(--text-md); line-height: 1.55; }
  #introCard p + p { margin-top: var(--space-4); }


  /* Quick Top Picks for Copy Trading */
  .cp-top-picks {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-3);
    margin: var(--space-3) 0 var(--space-4);
  }
  .cp-pick-card {
    background: var(--field);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: var(--space-4) var(--space-2);
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: var(--space-2);
    cursor: pointer;
    transition: transform .12s ease, border-color .15s ease, background .15s ease;
    user-select: none;
    min-width: 0;
  }
  .cp-pick-card:hover {
    border-color: var(--brand);
    background: var(--card);
  }
  .cp-pick-card:active {
    transform: scale(0.96);
  }
  .cp-pick-card .ico {
    width: 36px;
    height: 36px;
    border-radius: var(--radius-circle);
  }
  .cp-pick-card b {
    font-size: var(--text-xs);
    font-weight: 700;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    display: block;
  }
  .cp-pick-card .cp-pick-meta {
    font-size: 11px;
    color: var(--muted);
    line-height: 1.2;
  }
  .cp-pick-card .cp-pick-score {
    font-size: 10px;
    font-weight: 700;
    padding: 1px 6px;
    border-radius: var(--radius-circle);
    background: color-mix(in srgb, var(--green) 18%, transparent);
    color: var(--green);
  }

/* ============================================================
   GEOMETRIC RADAR & SKELETON LOADER ANIMATIONS
   ============================================================ */
@keyframes geoPulse {
  0% { transform: scale(0.92); opacity: 0.4; }
  50% { transform: scale(1.08); opacity: 1; filter: drop-shadow(0 0 10px rgba(37, 99, 235, 0.45)); }
  100% { transform: scale(0.92); opacity: 0.4; }
}

@keyframes spinSlow {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

@keyframes skeletonGlow {
  0% { background-position: -200% 0; }
  100% { background-position: 200% 0; }
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

.loader-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: var(--space-6) var(--space-4);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  text-align: center;
  gap: var(--space-3);
  box-shadow: var(--shadow-sm);
  margin: var(--space-3) auto 0;
  width: 100%;
  box-sizing: border-box;
  animation: fadeIn 0.2s ease-out;
}

.loader-card.fullscreen,
.screen-loading {
  min-height: calc(100vh - 120px);
  min-height: calc(100svh - 120px);
  display: flex;
  align-items: center;
  justify-content: center;
  margin: var(--space-4) auto;
  border: none;
  background: transparent;
  box-shadow: none;
}

.loader-radar {
  position: relative;
  width: 48px;
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto;
  color: var(--brand);
}

.loader-radar .geoframe {
  width: 48px;
  height: 48px;
  animation: spinSlow 8s linear infinite;
}

.loader-radar .pulse-dot {
  position: absolute;
  width: 8px;
  height: 8px;
  border-radius: var(--radius-circle);
  background: var(--brand);
  animation: geoPulse 1.5s ease-in-out infinite;
}

.loader-text {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--muted);
  letter-spacing: 0.01em;
  text-align: center;
}

.skeleton-box {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  margin: var(--space-2) auto 0;
}

.skeleton-line {
  height: 14px;
  border-radius: var(--radius-sm);
  background: linear-gradient(90deg, var(--line) 25%, color-mix(in srgb, var(--brand) 15%, var(--line)) 50%, var(--line) 75%);
  background-size: 200% 100%;
  animation: skeletonGlow 1.8s infinite linear;
}

.skeleton-line.w-100 { width: 100%; }
.skeleton-line.w-80 { width: 80%; }
.skeleton-line.w-65 { width: 65%; margin: 0 auto; }
.skeleton-line.w-50 { width: 50%; }

.skeleton-card {
  padding: var(--space-4);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.skeleton-avatar {
  width: 36px;
  height: 36px;
  border-radius: var(--radius-circle);
  background: linear-gradient(90deg, var(--line) 25%, color-mix(in srgb, var(--brand) 15%, var(--line)) 50%, var(--line) 75%);
  background-size: 200% 100%;
  animation: skeletonGlow 1.8s infinite linear;
  flex-shrink: 0;
}
.skeleton-pill {
  height: 28px;
  width: 72px;
  border-radius: var(--radius-pill);
  background: linear-gradient(90deg, var(--line) 25%, color-mix(in srgb, var(--brand) 15%, var(--line)) 50%, var(--line) 75%);
  background-size: 200% 100%;
  animation: skeletonGlow 1.8s infinite linear;
  flex-shrink: 0;
}

@media (prefers-reduced-motion: reduce) {
  .skeleton-line, .skeleton-avatar, .skeleton-pill { animation: none; opacity: 0.6; }
}

/* ==========================================================================
   SACRED GEOMETRY & QUANTUM TERMINAL DESIGN SYSTEM
   ========================================================================== */

.sacred-card {
  position: relative;
  background: linear-gradient(135deg, color-mix(in srgb, var(--card) 95%, var(--brand) 5%), var(--card));
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.08);
  padding: var(--space-4) var(--space-5);
  margin-bottom: var(--space-3);
  transition: transform 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
  overflow: hidden;
}
.sacred-card:active {
  transform: scale(0.99);
}

.sacred-card::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--brand), transparent);
  opacity: 0.6;
}

/* Whale Alpha Card */
.whale-card {
  position: relative;
  background: color-mix(in srgb, var(--card) 90%, #000 10%);
  border: 1px solid rgba(240, 180, 41, 0.22);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  margin-bottom: var(--space-3);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06), 0 4px 16px rgba(0, 0, 0, 0.3);
  transition: all 0.2s ease;
}
.whale-card:hover {
  border-color: var(--brand);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 6px 24px rgba(240, 180, 41, 0.12);
}

.whale-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-3);
  min-width: 0;
  gap: var(--space-2);
}

.whale-identity {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
  flex: 1;
  overflow: hidden;
}

.whale-hex-badge {
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--brand) 18%, transparent);
  border: 1px solid var(--brand);
  color: var(--brand);
  font-size: 14px;
  flex-shrink: 0;
}

.whale-name {
  font-weight: 700;
  font-size: var(--text-sm);
  color: var(--text);
  letter-spacing: 0.02em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.whale-live-tag {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 2px 6px;
  border-radius: 6px;
  background: rgba(23, 165, 106, 0.15);
  color: var(--green);
  border: 1px solid rgba(23, 165, 106, 0.3);
}

.whale-live-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--green);
  box-shadow: 0 0 8px var(--green);
  animation: geoPulse 1.4s ease-in-out infinite;
}

.whale-event-title {
  font-size: var(--text-sm);
  font-weight: 600;
  line-height: 1.35;
  color: var(--text);
  margin-bottom: var(--space-3);
}

.whale-metrics-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: var(--space-3);
  border-top: 1px dashed var(--border);
  gap: var(--space-3);
  flex-wrap: wrap;
}

.whale-amount-group {
  display: flex;
  flex-direction: column;
}

.whale-amount-label {
  font-size: 10px;
  text-transform: uppercase;
  color: var(--muted);
  letter-spacing: 0.05em;
  font-weight: 600;
}

.whale-amount-val {
  font-family: var(--font-mono);
  font-size: var(--text-md);
  font-weight: 700;
  color: var(--brand);
}

.whale-outcome-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 3px 8px;
  border-radius: var(--radius-sm);
  font-size: 11px;
  font-weight: 700;
  font-family: var(--font-mono);
  letter-spacing: 0.02em;
}
.whale-outcome-chip.buy {
  background: rgba(23, 165, 106, 0.12);
  color: var(--green);
  border: 1px solid rgba(23, 165, 106, 0.28);
}
.whale-outcome-chip.sell {
  background: rgba(231, 86, 90, 0.12);
  color: var(--red);
  border: 1px solid rgba(231, 86, 90, 0.28);
}

.whale-copy-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border-radius: 6px;
  font-size: 12px;
  font-weight: 700;
  background: linear-gradient(135deg, var(--brand), color-mix(in srgb, var(--brand) 80%, #000));
  color: #0d1117;
  border: 1px solid rgba(255, 255, 255, 0.2);
  box-shadow: 0 2px 8px rgba(240, 180, 41, 0.3);
  cursor: pointer;
  transition: all 0.15s ease;
}
.whale-copy-btn:active {
  transform: scale(0.96);
}

/* Quick Artifact Cards for Home (Sacred Geometry Symmetrical 3x3 Grid) */
.quick-artifact-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-5);
  margin-top: var(--space-3);
  margin-bottom: var(--space-5);
}
html[data-layout="desktop"] .quick-artifact-grid {
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}

.quick-artifact-card {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 8px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 14px 10px 12px;
  cursor: pointer;
  user-select: none;
  min-width: 0;
  transition: all 0.16s ease;
  overflow: hidden;
}
.quick-artifact-card:hover {
  border-color: var(--brand);
}
.quick-artifact-card:active {
  transform: scale(0.97);
  border-color: var(--brand);
}

.quick-artifact-card .ico {
  margin-bottom: 2px;
}

.quick-artifact-title {
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: 700;
  line-height: 1.35;
  color: var(--text);
}

.quick-artifact-desc {
  font-size: 11px;
  color: var(--muted);
  line-height: 1.35;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Real Closed Trades History & Ledger */
.real-trade-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  transition: border-color 0.15s ease;
}
.real-trade-card:hover {
  border-color: var(--brand);
}
.real-trade-title {
  color: var(--text);
  font-weight: 600;
  font-size: var(--text-sm);
  text-decoration: none;
  line-height: 1.35;
  display: inline-block;
  transition: color 0.15s ease;
}
.real-trade-title:hover {
  color: var(--brand);
  text-decoration: underline;
}
.real-trade-breakdown {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-top: var(--space-2);
  padding-top: var(--space-2);
  border-top: 1px dashed var(--border);
  font-size: 11px;
  color: var(--muted);
}

/* Preset Strategy Cards */
.strategy-preset-card {
  position: relative;
  background: color-mix(in srgb, var(--card) 94%, var(--brand) 6%);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  margin-bottom: var(--space-2);
  transition: all 0.15s ease;
  cursor: pointer;
}
.strategy-preset-card:hover {
  border-color: var(--brand);
}
.strategy-preset-card.active {
  border-color: var(--brand);
  box-shadow: 0 0 0 1px var(--brand), inset 0 1px 0 rgba(255, 255, 255, 0.1);
}

/* Sacred Geometry Strategy Cards for Marketplace & Subscriptions */
.sacred-strategy-card {
  position: relative;
  background: linear-gradient(135deg, color-mix(in srgb, var(--card) 94%, var(--brand) 6%), var(--card));
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.08);
  padding: var(--space-4) var(--space-5);
  margin-bottom: var(--space-4);
  transition: transform 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
  overflow: hidden;
}
.sacred-strategy-card:hover {
  border-color: var(--brand);
  box-shadow: 0 6px 24px rgba(240, 180, 41, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.12);
}
.sacred-strategy-card::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--brand), transparent);
  opacity: 0.8;
}

.strategy-card-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}

.strategy-card-identity {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.strategy-geo-badge {
  position: relative;
  width: 38px;
  height: 38px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--brand) 16%, transparent);
  border: 1px solid var(--brand);
  color: var(--brand);
  flex-shrink: 0;
}
.strategy-geo-badge .geoframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0.45;
}

.strategy-card-title {
  font-family: var(--font-display, "Unbounded", sans-serif);
  font-size: var(--text-base);
  font-weight: 700;
  color: var(--text);
  line-height: 1.3;
}

.strategy-author-tag {
  font-size: 11px;
  color: var(--muted);
  display: flex;
  align-items: center;
  gap: var(--space-1);
  margin-top: 2px;
}

.strategy-card-desc {
  font-size: var(--text-sm);
  color: var(--text-dim, #99aab5);
  line-height: 1.45;
  margin-bottom: var(--space-4);
}

.strategy-metrics-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: rgba(0, 0, 0, 0.22);
  border: 1px solid rgba(255, 255, 255, 0.05);
  border-radius: var(--radius-md);
  margin-bottom: var(--space-4);
}
@media (min-width: 480px) {
  .strategy-metrics-grid {
    grid-template-columns: repeat(4, 1fr);
  }
}

.strategy-metric-cell {
  display: flex;
  flex-direction: column;
}

.strategy-metric-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
  font-weight: 600;
  margin-bottom: 2px;
}

.strategy-metric-val {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--text);
}
.strategy-metric-val.pos {
  color: var(--green);
}
.strategy-metric-val.brand {
  color: var(--brand);
}

.strategy-card-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding-top: var(--space-2);
  border-top: 1px dashed var(--border);
}

.strategy-sub-count {
  font-size: 12px;
  color: var(--muted);
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.strategy-card-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.btn-1click-trade {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  font-size: 12px;
  font-weight: 700;
  background: linear-gradient(135deg, var(--brand), color-mix(in srgb, var(--brand) 80%, #000));
  color: #0d1117;
  border: 1px solid rgba(255, 255, 255, 0.2);
  box-shadow: 0 2px 8px rgba(240, 180, 41, 0.3);
  cursor: pointer;
  transition: all 0.15s ease;
  text-decoration: none;
}
.btn-1click-trade:active {
  transform: scale(0.96);
}

/* Modal for Publishing Backtest */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.75);
  backdrop-filter: blur(4px);
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4) var(--space-4) calc(var(--space-4) + var(--safe-area-bottom));
}
.modal-card {
  position: relative;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  max-width: 480px;
  width: 100%;
  max-height: 90vh;
  overflow-y: auto;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
  padding: var(--space-5);
}

/* === Real Copy Trading UX & Clean Sacred Architecture === */
.real-kpi-card {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.03) 0%, rgba(0, 0, 0, 0.2) 100%), var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  position: relative;
  overflow: hidden;
}

.real-kpi-main {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-bottom: var(--space-3);
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  margin-bottom: var(--space-3);
}

.real-kpi-label {
  font-size: var(--text-xs);
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: 600;
}

.real-kpi-val {
  font-family: var(--font-mono);
  font-size: 22px;
  font-weight: 700;
  line-height: 1.2;
}

.real-kpi-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

.real-kpi-cell {
  background: rgba(255, 255, 255, 0.02);
  border: 1px solid rgba(255, 255, 255, 0.04);
  border-radius: var(--radius-sm);
  padding: var(--space-2);
  text-align: center;
}

.real-kpi-cell-label {
  font-size: 10px;
  color: var(--muted);
  margin-bottom: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.real-kpi-cell-val {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--text);
}

.real-kpi-toggle-btn {
  background: transparent;
  border: none;
  color: var(--brand);
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 0;
}

.real-kpi-toggle-btn:focus-visible,
.real-trade-summary:focus-visible,
.collapsible-header:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

/* Open Positions Asset Card */
.real-pos-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  margin-top: var(--space-2);
  transition: border-color 0.2s ease;
}

.real-pos-card:hover {
  border-color: rgba(240, 180, 41, 0.4);
}

.real-pos-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

.real-pos-title {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text);
  line-height: 1.35;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.real-pos-title:hover {
  color: var(--brand);
}

.real-pos-metrics {
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: rgba(255, 255, 255, 0.02);
  border-radius: var(--radius-sm);
  padding: var(--space-2);
  font-size: 12px;
  margin-bottom: var(--space-2);
}

.real-pos-pnl-pill {
  font-family: var(--font-mono);
  font-weight: 700;
  padding: 2px 8px;
  border-radius: var(--radius-sm);
  font-size: 11px;
}

.real-pos-pnl-pill.win {
  background: rgba(46, 213, 115, 0.12);
  color: var(--green);
  border: 1px solid rgba(46, 213, 115, 0.25);
}

.real-pos-pnl-pill.loss {
  background: rgba(255, 71, 87, 0.12);
  color: var(--red);
  border: 1px solid rgba(255, 71, 87, 0.25);
}

.real-pos-leader-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 11px;
  font-weight: 600;
  color: var(--brand);
  background: rgba(240, 180, 41, 0.1);
  border: 1px solid rgba(240, 180, 41, 0.25);
  border-radius: var(--radius-sm);
  padding: 1px 7px;
}

.real-pos-live-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  color: var(--text);
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 2px 7px;
}

.pulse-dot-live {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--green);
  box-shadow: 0 0 6px var(--green);
  display: inline-block;
  animation: pulseLiveDot 1.8s infinite;
}

@keyframes pulseLiveDot {
  0% { transform: scale(0.9); opacity: 0.7; }
  50% { transform: scale(1.25); opacity: 1; }
  100% { transform: scale(0.9); opacity: 0.7; }
}

/* TP / SL Target Bar on Open Positions */
.real-pos-tpsl {
  background: rgba(255, 255, 255, 0.02);
  border: 1px solid rgba(255, 255, 255, 0.05);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
  margin-top: var(--space-2);
}

.real-pos-tpsl-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 10.5px;
  margin-bottom: 6px;
  font-family: var(--font-mono);
}

.tpsl-track {
  position: relative;
  height: 4px;
  background: rgba(255, 255, 255, 0.08);
  border-radius: 9999px;
  overflow: visible;
  margin: 8px 0 4px;
}

.tpsl-track-gradient {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  border-radius: 9999px;
  background: linear-gradient(to right, rgba(239, 68, 68, 0.7) 0%, rgba(240, 180, 41, 0.4) 40%, rgba(0, 230, 118, 0.7) 100%);
}

.tpsl-current-pin {
  position: absolute;
  top: 50%;
  width: 10px;
  height: 10px;
  background: #fff;
  border: 2px solid var(--brand);
  border-radius: 50%;
  transform: translate(-50%, -50%);
  box-shadow: 0 0 6px rgba(240, 180, 41, 0.8);
  z-index: 2;
}

.real-pos-fill-progress {
  height: 4px;
  background: rgba(255, 255, 255, 0.08);
  border-radius: 9999px;
  overflow: hidden;
  margin-top: 4px;
}

.real-pos-fill-bar {
  height: 100%;
  background: var(--brand);
  border-radius: 9999px;
}

/* Pending Orders Cards */
.pending-order-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  margin-top: var(--space-2);
  transition: border-color 0.2s ease;
}

.pending-order-card:hover {
  border-color: rgba(240, 180, 41, 0.4);
}

/* Expandable Real Trade Card */
.real-trade-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  margin-bottom: var(--space-2);
  transition: all 0.2s ease;
  overflow: hidden;
}

.real-trade-card:hover {
  border-color: rgba(255, 255, 255, 0.15);
}

.real-trade-summary {
  padding: var(--space-3);
  cursor: pointer;
  user-select: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.real-trade-top {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--space-2);
}

.real-trade-title {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text);
  line-height: 1.35;
  text-decoration: none;
  flex: 1;
  min-width: 0;
}

.real-trade-title:hover {
  color: var(--brand);
}

.real-trade-pnl-badge {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  font-weight: 700;
  flex-shrink: 0;
  padding: 2px 8px;
  border-radius: var(--radius-sm);
}

.real-trade-pnl-badge.win {
  color: var(--green);
  background: rgba(46, 213, 115, 0.1);
}

.real-trade-pnl-badge.loss {
  color: var(--red);
  background: rgba(255, 71, 87, 0.1);
}

.real-trade-bottom {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 11px;
  color: var(--muted);
  gap: var(--space-2);
  flex-wrap: wrap;
}

.real-trade-chevron {
  transition: transform 0.2s ease;
  color: var(--muted);
  width: 14px;
  height: 14px;
  pointer-events: none;
}

.real-trade-card.expanded .real-trade-chevron {
  transform: rotate(180deg);
  color: var(--brand);
}

.real-trade-details-panel {
  display: none;
  padding: var(--space-3);
  background: rgba(0, 0, 0, 0.25);
  border-top: 1px solid rgba(255, 255, 255, 0.05);
  animation: fadeIn 0.15s ease;
}

.real-trade-card.expanded .real-trade-details-panel {
  display: block;
}

.real-trade-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.real-trade-field {
  display: flex;
  flex-direction: column;
}

.real-trade-field-label {
  font-size: 10px;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 2px;
}

.real-trade-field-val {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
  word-break: break-word;
}

@media (max-width: 380px) {
  .real-trade-grid {
    gap: 6px 8px;
  }
  .real-trade-field-val {
    font-size: 11px;
  }
}

/* Collapsible Section Header */
.collapsible-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  cursor: pointer;
  user-select: none;
  padding: var(--space-2) 0;
}

.collapsible-header:hover .step {
  color: var(--brand);
}

.collapsible-icon {
  transition: transform 0.2s ease;
  color: var(--muted);
}

.collapsible-header.collapsed .collapsible-icon {
  transform: rotate(-90deg);
}

.collapsible-body {
  transition: all 0.2s ease;
}

.collapsible-body.collapsed {
  display: none;
}

#safeDepositWeb3Btn.disabled {
  opacity: .55;
  pointer-events: none;
}

/* === MOBILE UX REVOLUT & LINEAR GRADE POLISH === */

/* 1. Sacred Geometry: Intricate concentric mandala motifs behind functional icons */
.ico .geoframe,
.onboarding-icon .geoframe,
.concept-step-ico .geoframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  stroke-width: 1;
  opacity: 0.55;
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.35s ease;
}

.quick-artifact-card:hover .ico .geoframe,
.moretile:hover .ico .geoframe,
.concept-step-card:hover .ico .geoframe {
  opacity: 0.85;
  transform: rotate(45deg);
}

/* 2. Home Hero & Balance Enhancement */
#screen-home .hero {
  border-radius: var(--radius-xl);
  padding: var(--space-6);
  border: 1px solid var(--border);
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
}

#screen-home .hero-balance {
  font-family: var(--font-mono);
  font-size: var(--figure-md, 28px);
  font-weight: 800;
  letter-spacing: -0.5px;
  line-height: 1.15;
}

/* 3. Spot Terminal Revolut-Grade Layout */
#tradingSpotContainer {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

#tradingSpotContainer .card {
  box-shadow: none;
  border-radius: var(--radius-xl);
}

#spotPickYes, #spotPickNo {
  min-height: 52px;
  border-radius: var(--radius-lg);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  cursor: pointer;
  user-select: none;
  transition: all 0.16s cubic-bezier(0.4, 0, 0.2, 1);
}

#spotPickYes.active-yes {
  background: var(--green-solid) !important;
  color: #ffffff !important;
  border-color: var(--green-solid) !important;
  box-shadow: 0 4px 14px rgba(18, 128, 82, 0.35);
}

#spotPickNo.active-no {
  background: var(--red-solid) !important;
  color: #ffffff !important;
  border-color: var(--red-solid) !important;
  box-shadow: 0 4px 14px rgba(219, 31, 37, 0.35);
}

#spotAmountChips {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: var(--space-2);
}

#spotAmountChips .chip2 {
  min-height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: var(--text-base);
  font-weight: 700;
  border-radius: var(--radius-md);
  padding: 0;
}

#spotAmountChips .chip2.active {
  background: var(--card);
  border: 2px solid var(--brand);
  color: var(--brand);
  box-shadow: 0 0 0 1px var(--brand);
}

/* Keep the Buy action in document flow so it cannot cover TP/SL or the tabbar. */
@media (max-width: 600px) {
  .spot-action-bar {
    position: static;
    background: var(--card);
    padding: var(--space-2);
    border-radius: var(--radius-xl);
    border: 1px solid var(--border);
    box-shadow: none;
  }
}

#spotExecuteBuyBtn {
  min-height: 50px;
  font-size: var(--text-md);
  font-weight: 700;
  border-radius: var(--radius-lg);
}

/* 4. Copy Trading Clean Layout */
.cp-whale-card {
  transition: border-color 0.15s ease, transform 0.12s ease;
  border-radius: var(--radius-xl) !important;
}

.cp-whale-card:active {
  transform: scale(0.99);
  border-color: var(--brand);
}

.cpFollowWhaleBtn {
  min-height: 42px;
  min-width: 96px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  border-radius: var(--radius-md);
}

/* Bottom Sheet Presentation for Quick Follow Modal */
@media (max-width: 600px) {
  #cpQuickFollowModal.modeselect-overlay {
    justify-content: flex-end;
    background: rgba(0, 0, 0, 0.68);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    padding: 0;
  }
  #cpQuickFollowModal > * {
    max-width: 100%;
  }
  #cpQuickFollowModal {
    border-radius: 24px 24px 0 0;
  }
}

/* 5. Safe Deposit Modal Ergonomics & No-Overflow Protection */
#safeDepositModal .card {
  border-radius: var(--radius-xl);
}

#safeDepositQrBox {
  background: #ffffff;
  padding: var(--space-3);
  border-radius: var(--radius-lg);
  margin: 0 auto var(--space-3);
  width: 200px;
  height: 200px;
  display: flex;
  align-items: center;
  justify-content: center;
}

#safeDepositAddrText {
  min-width: 0;
  width: 100%;
  box-sizing: border-box;
  overflow-wrap: anywhere;
  word-break: break-all;
  text-align: center;
  background: var(--field);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  font-size: var(--text-sm);
  font-family: var(--font-mono);
}

#safeDepositCopyBtn {
  min-height: 48px;
  font-size: var(--text-md);
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
}

/* Beta Header Badge & Feedback Button */
.beta-badge {
  font-family: var(--font-mono, monospace);
  line-height: 1;
  display: inline-block;
}
.feedback-btn {
  border-radius: var(--radius-sm);
  background: rgba(43, 114, 255, 0.08);
  border: 1px solid rgba(43, 114, 255, 0.2);
}
.feedback-btn:hover {
  background: rgba(43, 114, 255, 0.16);
}

/* Focus Chips (Roles) */
.focus-chip-row {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding: 4px 0 8px;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.focus-chip-row::-webkit-scrollbar { display: none; }
.focus-chip {
  padding: 6px 12px;
  border-radius: var(--radius-md, 8px);
  font-size: 12px;
  font-weight: 600;
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--muted);
  white-space: nowrap;
  cursor: pointer;
  transition: all 0.2s ease;
}
.focus-chip:hover {
  color: var(--text);
  border-color: var(--border-hover, rgba(255,255,255,0.2));
}
.focus-chip.active {
  background: color-mix(in srgb, var(--brand) 16%, var(--card));
  border-color: var(--brand);
  color: var(--text-bright);
}

/* Modern Chip Rows: smooth horizontal scroll without ugly scrollbar */
#cpCatalogFilterChips,
#cpBankrollFilterChips,
#btCategoryChips,
#btPlatformChips,
#btCatalogFilterChips,
#simAmountChips,
.wagerQuickChips,
.modern-chip-row {
  display: flex !important;
  gap: 6px !important;
  overflow-x: auto !important;
  scrollbar-width: none !important;
  -ms-overflow-style: none !important;
  -webkit-overflow-scrolling: touch !important;
  padding-bottom: 2px !important;
}
#cpCatalogFilterChips::-webkit-scrollbar,
#cpBankrollFilterChips::-webkit-scrollbar,
#btCategoryChips::-webkit-scrollbar,
#btPlatformChips::-webkit-scrollbar,
#btCatalogFilterChips::-webkit-scrollbar,
#simAmountChips::-webkit-scrollbar,
.wagerQuickChips::-webkit-scrollbar,
.modern-chip-row::-webkit-scrollbar {
  display: none !important;
  width: 0 !important;
  height: 0 !important;
}

/* Quick amount chip buttons in wagers */
.wagerChipBtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 28px;
  padding: 0 10px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--text-bright);
  cursor: pointer;
  transition: all 0.15s ease;
  user-select: none;
}
.wagerChipBtn:hover {
  background: color-mix(in srgb, var(--brand) 15%, var(--card));
  border-color: var(--brand);
}
.wagerChipBtn:active {
  transform: scale(0.95);
}

/* Focus dimming & highlighting */
[data-focus-active="trading"] [data-focus-tag="algo"],
[data-focus-active="algo"] [data-focus-tag="trading"] {
  opacity: 0.35;
  filter: grayscale(0.5);
  transition: opacity 0.2s ease, filter 0.2s ease;
}
[data-focus-active="trading"] [data-focus-tag="trading"],
[data-focus-active="algo"] [data-focus-tag="algo"] {
  opacity: 1;
  filter: none;
  border-color: var(--brand);
  box-shadow: 0 0 10px rgba(43, 114, 255, 0.12);
  transition: all 0.2s ease;
}
[data-focus-active="trading"] [data-focus-card="algo"] {
  opacity: 0.35;
  transition: opacity 0.2s ease;
}
[data-focus-active="algo"] [data-focus-card="trading"] {
  opacity: 0.35;
  transition: opacity 0.2s ease;
}

/* Persistent Interface Mode Segmented Control (Lite / Pro / All) */
.mode-segmented-control {
  display: flex;
  align-items: center;
  gap: var(--space-2, 4px);
  background: var(--field);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl, 24px);
  padding: 3px;
  margin: var(--space-3) 0 var(--space-4);
  user-select: none;
}
.mode-segment {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 8px 6px;
  border-radius: var(--radius-lg, 18px);
  border: 1px solid transparent;
  background: transparent;
  color: var(--muted);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  line-height: 1.2;
  cursor: pointer;
  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
  min-height: 38px;
  box-sizing: border-box;
}
.mode-segment:active {
  transform: scale(0.97);
}
.mode-segment.active {
  background: var(--card);
  color: var(--text);
  border-color: var(--border);
  box-shadow: var(--shadow-sm, 0 1px 3px rgba(0,0,0,0.1));
  font-weight: 700;
}
.mode-segment.active[data-uimode="lite"] {
  border-color: rgba(240, 180, 41, 0.45);
  color: var(--brand);
}
.mode-segment.active[data-uimode="pro"] {
  border-color: rgba(43, 140, 218, 0.45);
  color: var(--accent);
}
.mode-segment.active[data-uimode="all"] {
  border-color: rgba(23, 165, 106, 0.45);
  color: var(--green);
}
.mode-segment .mode-ico {
  font-size: 14px;
  line-height: 1;
}
.mode-segment .mode-txt {
  white-space: nowrap;
}

/* UI Mode Select Modal Option State */
#uiModeSelectModal .modecard.active-pick {
  border-color: var(--brand);
  background: color-mix(in srgb, var(--brand) 8%, var(--card));
  box-shadow: 0 0 12px rgba(240, 180, 41, 0.2);
}

/* App Mode Filtering Behaviors */
html[data-app-mode="lite"] [data-focus-tag="algo"],
html[data-app-mode="lite"] [data-focus-card="algo"] {
  display: none !important;
}
html[data-app-mode="pro"] [data-focus-tag="algo"],
html[data-app-mode="pro"] [data-focus-card="algo"] {
  opacity: 1;
  filter: none;
  border-color: var(--brand);
}
html[data-app-mode="pro"] [data-focus-tag="trading"] {
  opacity: 0.85;
}

/* Card Alignment Uniformity */
.quick-artifact-card {
  min-height: 100px;
}
.quick-artifact-title {
  min-height: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.moretile {
  min-height: 100px;
}

/* Concept Architecture Steps */
.concept-steps-grid {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-top: var(--space-2);
}
.concept-step-card {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  cursor: pointer;
  transition: transform 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease;
}
.concept-step-card:hover {
  border-color: var(--brand);
  transform: translateY(-1px);
}
.concept-step-badge {
  position: absolute;
  top: -6px;
  left: 12px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--brand);
  color: #fff;
  font-size: 10px;
  font-weight: 800;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.25);
  z-index: 2;
}
.concept-step-ico {
  width: 44px;
  height: 44px;
  flex: none;
  border-radius: var(--radius-md);
  position: relative;
}
.concept-step-ico .geoframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  stroke-width: 1;
  opacity: 0.55;
  pointer-events: none;
}
.concept-step-ico .icon:not(.geoframe) {
  width: 18px;
  height: 18px;
  position: relative;
  z-index: 1;
}
.concept-step-body {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
.concept-step-body b {
  font-size: var(--text-base);
  font-weight: 700;
  color: var(--text);
  line-height: 1.35;
}
.concept-step-body span {
  font-size: var(--text-sm);
  line-height: 1.45;
  color: var(--muted);
}
@media (min-width: 900px) {
  .concept-steps-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-3);
  }
  .concept-step-card {
    flex-direction: column;
    align-items: flex-start;
    padding: var(--space-6) var(--space-5) var(--space-5);
    gap: var(--space-3);
  }
  .concept-step-card .concept-step-badge {
    top: -6px;
    left: 14px;
  }
}

/* === Newcomer Onboarding Checklist Card (Step 1) === */
.newcomer-checklist-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  padding: 16px 18px 14px;
  margin-top: var(--space-4);
  margin-bottom: var(--space-6);
  position: relative;
  overflow: hidden;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
}
.checklist-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 12px;
}
.checklist-title-group {
  display: flex;
  align-items: center;
  gap: 10px;
}
.checklist-title {
  font-size: 15px;
  font-weight: 700;
  color: var(--text);
  line-height: 1.3;
}
.checklist-sub {
  font-size: 12px;
  color: var(--muted);
  line-height: 1.45;
  margin-top: 3px;
}
.checklist-progress-badge {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 700;
  color: var(--brand);
  background: color-mix(in srgb, var(--brand) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--brand) 30%, transparent);
  padding: 3px 9px;
  border-radius: var(--radius-pill);
  letter-spacing: 0.5px;
}
.checklist-progress-badge.all-done {
  color: var(--green);
  background: color-mix(in srgb, var(--green) 12%, transparent);
  border-color: color-mix(in srgb, var(--green) 30%, transparent);
}
.checklist-progress-track {
  width: 100%;
  height: 6px;
  background: color-mix(in srgb, var(--text) 8%, transparent);
  border-radius: var(--radius-pill);
  overflow: hidden;
  margin-bottom: 12px;
}
.checklist-progress-fill {
  height: 100%;
  background: linear-gradient(90deg, var(--brand), var(--brand-2));
  border-radius: var(--radius-pill);
  transition: width 0.35s cubic-bezier(0.4, 0, 0.2, 1);
}
.checklist-progress-fill.all-done {
  background: var(--green);
}
.checklist-items {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.checklist-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  background: color-mix(in srgb, var(--bg) 60%, var(--card));
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: all 0.16s ease;
  user-select: none;
}
.checklist-item:hover {
  border-color: color-mix(in srgb, var(--brand) 45%, transparent);
  background: color-mix(in srgb, var(--card) 92%, var(--brand));
}
.checklist-circle {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 1.5px solid var(--muted);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: all 0.2s ease;
  color: transparent;
}
.checklist-circle .icon {
  width: 12px;
  height: 12px;
}
.checklist-item.done .checklist-circle {
  border-color: var(--green);
  background: var(--green);
  color: #fff;
}
.checklist-item-content {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.checklist-item-title {
  font-size: 13px;
  font-weight: 650;
  color: var(--text);
  line-height: 1.35;
}
.checklist-item.done .checklist-item-title {
  text-decoration: line-through;
  color: var(--muted);
}
.checklist-item-desc {
  font-size: 12px;
  color: var(--muted);
  line-height: 1.4;
}
.checklist-item-btn {
  background: none;
  border: none;
  color: var(--brand);
  font-size: 12px;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 3px;
  padding: 4px 6px;
  cursor: pointer;
  flex-shrink: 0;
}
.checklist-item.done .checklist-item-btn {
  display: none;
}
.checklist-reward-banner {
  margin-top: 12px;
  padding: 10px 14px;
  background: color-mix(in srgb, var(--green) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--green) 25%, transparent);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--green);
  text-align: center;
  animation: fadeIn 0.3s ease;
}

/* Actionable Empty State Cards */
.empty-actionable {
  text-align: center;
  padding: var(--space-6) var(--space-5);
  background: color-mix(in srgb, var(--card) 60%, var(--bg));
  border: 1px dashed var(--border);
  border-radius: var(--radius-lg);
  margin-top: var(--space-3);
}
.empty-actionable .empty-ico {
  font-size: 32px;
  margin-bottom: var(--space-2);
}
.empty-actionable .empty-title {
  display: block;
  font-size: var(--text-base);
  font-weight: 700;
  color: var(--text);
  margin-bottom: var(--space-1);
}
.empty-actionable .empty-desc {
  font-size: var(--text-xs);
  color: var(--muted);
  max-width: 320px;
  margin: 0 auto var(--space-4);
  line-height: 1.45;
}

/* Collapsible sections (e.g. Trade History, Open Positions) */
.collapsible-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  cursor: pointer;
  user-select: none;
  padding: 4px 0;
  -webkit-tap-highlight-color: transparent;
}
.collapsible-header:hover {
  opacity: 0.85;
}
.collapsible-header .collapsible-icon {
  transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
  color: var(--text-muted, #94a3b8);
}
.collapsible-header.collapsed .collapsible-icon {
  transform: rotate(-90deg);
}
.collapsible-body.collapsed {
  display: none !important;
}
