/* Общие стили сервиса (совет №68: страница не должна тащить 55 КБ CSS
   в каждом ответе — файл кешируется браузером и Caddy, HTML худеет).
   Здесь только то, что одинаково на всех экранах; частное остаётся в
   {% block extra_style %} своего шаблона. Jinja тут запрещена: файл
   отдаётся как статика и через шаблонизатор не проходит. */

/* hidden должен прятать. Любой наш класс с display (.two-toggle — flex,
   .photo-thumbs — grid и так далее) сильнее правила [hidden]{display:none}
   из браузера, потому что авторские стили важнее пользовательских агентских.
   Из-за этого кнопки «Вдохновиться/Копировать» висели на экране, хотя
   референс ещё не загружен. Одно правило чинит это для всего сервиса. */
[hidden] { display: none !important; }

:root {
    color-scheme: light dark;
    --bg: #FFFFFF;
    --surface: #FFFFFF;
    --surface-2: #F5F4F8;
    --border: #E5E3EC;
    --text: #14121C;
    --text-muted: #63607A;
    --link: #5B3FD6;
    --accent: #7A5AF8;
    --accent-strong: #6947E8;
    --accent-soft: #EFEBFF;
    --accent-text: #FFFFFF;
    --good: #10B981;
    --good-soft: #D1FAE5;
    /* Цвет ТЕКСТА на своей мягкой подложке: сам --good/--bad там не
       дотягивает до читаемости (зелёное на зелёном давало 2.2 при норме 4.5). */
    --good-text: #047857;
    --bad-text: #B91C1C;
    --bad: #DC2626;
    --bad-soft: #FEF2F2;
    --bad-border: #FECACA;
    /* Жёлтое «внимание»: не сбой сервиса, а предупреждение человеку —
       например, что выбранная вручную категория расходится с фото. */
    --warn-soft: #FFFBEB;
    --warn-border: #FDE68A;
    --warn-text: #92400E;
    --accent-line: #C9BFF7;
    /* Свет за страницей и отсвет под знаком. Мягкие и почти прозрачные:
    задача — убрать ощущение плоской заливки, а не покрасить экран. */
    --bg-glow: radial-gradient(1500px 900px at 50% -260px, rgba(122, 90, 248, .16), transparent 72%);
    --accent-cast: rgba(122, 90, 248, .35);
    --panel-sheen: rgba(122, 90, 248, .045);
    --glow-sm: 0 2px 10px rgba(122, 90, 248, .35);
    --glow-lg: 0 8px 30px rgba(122, 90, 248, .28);

    /* --- Система величин (совет №81) -------------------------------------
       Один набор значений на весь сервис. До этого радиусы, тени и отступы
       писались числами по месту, и близкие элементы получались разной формы:
       где 8, где 10, где 12 пикселей скругления. Новые блоки собираются из
       этих токенов, а не из свежепридуманных чисел. */
    --radius: 20px;      /* крупные панели и карточки */
    --radius-sm: 12px;   /* поля, кнопки-прямоугольники, плашки */
    --radius-xs: 8px;    /* мелочь: миниатюры, чипы-квадраты */
    --radius-pill: 999px;

    /* Высота шапки. Нужна не для самой шапки (её задаёт содержимое), а тем,
       кто липнет к верху страницы под ней: без этого «липкая» панель встаёт
       на 8 px от края экрана и при прокрутке наезжает на шапку. */
    --topbar-h: 72px;

    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-5: 24px;
    --space-6: 32px;

    /* Шкала размеров текста: между ступенями ничего не бывает.
       Верх шкалы был слишком низким: от 11 до 22 пикселей — вся страница
       умещалась в один голос, и глазу было негде остановиться. Мелкие
       ступени не трогаем (на них завязаны кнопки и метки), а верхние
       разводим, чтобы заголовку было куда вырасти. */
    --text-xs: 11px;
    --text-sm: 12.5px;
    --text-md: 13.5px;
    --text-base: 15px;
    --text-lg: 18px;
    --text-xl: 26px;
    --text-display: 34px;

    /* Тени задаются в теме: на тёмном фоне мягкая серая тень не видна вовсе,
       поэтому там она глубже. */
    --shadow-sm: 0 1px 2px rgba(10, 10, 10, .06);
    --shadow-md: 0 6px 22px rgba(10, 10, 10, .10);
    --shadow-lg: 0 24px 60px rgba(10, 10, 10, .22);
  }
  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
      --bg: #0E0D13;
      --surface: #16151F;
      --surface-2: #1E1C2A;
      --border: #2C2A3A;
      --text: #F5F4FA;
      --text-muted: #A09DB4;
      --link: #9B84FF;
      --accent: #7A5AF8;
      --accent-strong: #9B84FF;
      --accent-soft: #1E1A38;
      --accent-text: #FFFFFF;
      --good: #34D399;
      --good-soft: #0F2E22;
    /* Цвет ТЕКСТА на своей мягкой подложке: сам --good/--bad там не
       дотягивает до читаемости (зелёное на зелёном давало 2.2 при норме 4.5). */
    --good-text: #34D399;
    --bad-text: #F87171;
      --bad: #F87171;
      --bad-soft: #2A1620;
      --bad-border: #5A2C3A;
      --warn-soft: #2A2416;
      --warn-border: #4D4218;
      --warn-text: #E0B341;
      --accent-line: #3B3168;
      --bg-glow: radial-gradient(1500px 900px at 50% -260px, rgba(124, 99, 232, .30), transparent 72%);
      --accent-cast: rgba(122, 90, 248, .55);
      --panel-sheen: rgba(155, 132, 255, .055);
      --glow-sm: 0 2px 14px rgba(122, 90, 248, .55);
      --glow-lg: 0 8px 34px rgba(122, 90, 248, .45);
      --shadow-sm: 0 1px 2px rgba(0, 0, 0, .5);
      --shadow-md: 0 6px 22px rgba(0, 0, 0, .55);
      --shadow-lg: 0 24px 60px rgba(0, 0, 0, .7);
    }
  }
  /* Ручной переключатель темы — побеждает системную настройку в обе стороны,
     независимо от порядка правил (селектор с атрибутом специфичнее :root). */
  :root[data-theme="dark"] {
    --bg: #0E0D13;
    --surface: #16151F;
    --surface-2: #1E1C2A;
    --border: #2C2A3A;
    --text: #F5F4FA;
    --text-muted: #A09DB4;
    --link: #9B84FF;
    --accent: #7A5AF8;
    --accent-strong: #9B84FF;
    --accent-soft: #1E1A38;
    --accent-text: #FFFFFF;
    --good: #34D399;
    --good-soft: #0F2E22;
    --good-text: #34D399;
    --bad-text: #F87171;
    --bad: #F87171;
    --bad-soft: #2A1620;
    --bad-border: #5A2C3A;
    --warn-soft: #2A2416;
    --warn-border: #4D4218;
    --warn-text: #E0B341;
    --accent-line: #3B3168;
    --bg-glow: radial-gradient(1500px 900px at 50% -260px, rgba(124, 99, 232, .30), transparent 72%);
    --accent-cast: rgba(122, 90, 248, .55);
    --panel-sheen: rgba(155, 132, 255, .055);
    --glow-sm: 0 2px 14px rgba(122, 90, 248, .55);
    --glow-lg: 0 8px 34px rgba(122, 90, 248, .45);
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, .5);
    --shadow-md: 0 6px 22px rgba(0, 0, 0, .55);
    --shadow-lg: 0 24px 60px rgba(0, 0, 0, .7);
  }
  :root[data-theme="light"] {
    --bg: #FFFFFF;
    --surface: #FFFFFF;
    --surface-2: #F5F4F8;
    --border: #E5E3EC;
    --text: #14121C;
    --text-muted: #63607A;
    --link: #5B3FD6;
    --accent: #7A5AF8;
    --accent-strong: #6947E8;
    --accent-soft: #EFEBFF;
    --accent-text: #FFFFFF;
    --good: #10B981;
    --good-soft: #D1FAE5;
    /* Цвет ТЕКСТА на своей мягкой подложке: сам --good/--bad там не
       дотягивает до читаемости (зелёное на зелёном давало 2.2 при норме 4.5). */
    --good-text: #047857;
    --bad-text: #B91C1C;
    --bad: #DC2626;
    --bad-soft: #FEF2F2;
    --bad-border: #FECACA;
    --warn-soft: #FFFBEB;
    --warn-border: #FDE68A;
    --warn-text: #92400E;
    --accent-line: #C9BFF7;
    /* Свет за страницей и отсвет под знаком. Мягкие и почти прозрачные:
    задача — убрать ощущение плоской заливки, а не покрасить экран. */
    --bg-glow: radial-gradient(1500px 900px at 50% -260px, rgba(122, 90, 248, .16), transparent 72%);
    --accent-cast: rgba(122, 90, 248, .35);
    --panel-sheen: rgba(122, 90, 248, .045);
    --glow-sm: 0 2px 10px rgba(122, 90, 248, .35);
    --glow-lg: 0 8px 30px rgba(122, 90, 248, .28);
    --shadow-sm: 0 1px 2px rgba(10, 10, 10, .06);
    --shadow-md: 0 6px 22px rgba(10, 10, 10, .10);
    --shadow-lg: 0 24px 60px rgba(10, 10, 10, .22);
  }
  * { box-sizing: border-box; }
  body {
    margin: 0; background: var(--bg); color: var(--text);
    background-image: var(--bg-glow); background-repeat: no-repeat;
    background-attachment: fixed;
    /* Совет №68: system-ui в запасе — если Google Fonts не открылись (плохая
       мобильная сеть, блокировщик), страница остаётся в человеческом шрифте,
       а не в Times New Roman. */
    font-family: 'Inter', system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
    font-size: 15px; line-height: 1.45;
  }
  a { color: var(--link); }

  header.topbar {
    display: flex; align-items: center; gap: 10px;
    padding: 16px 24px; border-bottom: 1px solid var(--border);
    /* z-index 100, а не 10. Меню аккаунта раскрывается ВНУТРИ шапки, то есть
       в её слое: собственные z-index меню ничего не решают, наружу спорит вся
       шапка целиком. С десяткой её накрывала любая липкая панель страницы —
       открытое меню оказывалось под «выделено: 0». Сто — выше всего, что
       рисует страница, и ниже окон, подсказок и нижней панели разделов. */
    background: var(--surface); position: sticky; top: 0; z-index: 100;
  }
  /* Тонкая акцентная полоса под шапкой: сайт начинается с цвета, а не с
     серой линии. Один пиксель, но он держит верх экрана. */
  header.topbar::after {
    content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 1px;
    background: linear-gradient(90deg, transparent, var(--accent), transparent);
    opacity: .55; pointer-events: none;
  }
  .logo-mark {
    /* Знак — самый маленький элемент на экране и единственный постоянный:
       пусть светится, а не лежит плоским квадратом. */
    box-shadow: var(--glow-sm);
  }
  .logo-mark {
    width: 30px; height: 30px; border-radius: 10px; background: var(--accent);
    color: #fff; display: flex; align-items: center; justify-content: center;
    font-weight: 800; font-size: 14px; flex-shrink: 0;
  }

  /* Звезда — своя фигура, а не эмодзи ⭐. У эмодзи на айфоне, андроиде и в
     Windows три разных рисунка, и ни один не красится в цвет акцента; своя
     одинакова везде и наследует currentColor. */
  .spark { width: 1.05em; height: 1.05em; vertical-align: -.16em; flex-shrink: 0; }
  /* Обёртка, в которую скрипт складывает текст со звездой. Ничего не меняет в
     потоке: остаётся обычным строчным текстом на своём месте. */
  .stars-text { display: contents; }
  .spark path { fill: currentColor; }
  /* Там, где звезда стоит рядом с числом, она наследует цвет строки, а на
     акцентной подложке — цвет текста на акценте. Отдельного правила не нужно:
     currentColor делает это сам. */
  .brand-name { font-weight: 800; font-size: 15px; letter-spacing: -.01em; }
  /* ---- Знак ----
     Имя в двух уголках кадрирования: своя форма без отдельной картинки.
     Уголки рисуем псевдоэлементами, чтобы знак оставался текстом — он
     масштабируется, читается поиском и не требует загрузки файла. */
  .brand-logo {
    position: relative; display: inline-block; flex-shrink: 0;
    font-size: 17px; font-weight: 800; letter-spacing: -.03em; white-space: nowrap;
    padding: 4px 9px; line-height: 1.15;
  }
  .brand-logo::before, .brand-logo::after {
    content: ""; position: absolute; width: .42em; height: .42em;
    border: .12em solid var(--accent-strong);
  }
  .brand-logo::before { left: 0; top: 0; border-right: 0; border-bottom: 0; }
  .brand-logo::after { right: 0; bottom: 0; border-left: 0; border-top: 0; }

  /* Знак над формой генерации: это первый экран сервиса и первый кадр
     будущего ролика, поэтому имя здесь крупнее, чем в шапке. */
  .panel-brand { margin: 2px 0 16px; text-align: center; }
  .panel-brand + h2 { margin-top: 0; }
  /* Кегль плавает по ширине: на узком экране знак упирался бы в поля, на
     широком — мельчил. Верхняя граница — чтобы он не перерастал форму. */
  .panel-brand .brand-logo { font-size: clamp(38px, 10.5vw, 64px); padding: .1em .22em; }
  .panel-brand-tag {
    display: block; margin-top: 10px; font-size: 13.5px; font-weight: 600;
    color: var(--text-muted); letter-spacing: .01em;
  }
  @media (max-width: 760px) {
    .panel-brand-tag { font-size: 12.5px; }
  }
  .brand-tag { color: var(--text-muted); font-size: 12px; margin-left: 4px; }
  .brand-link { display: flex; align-items: center; gap: 10px; text-decoration: none; color: inherit; }

  .nav-tabs { display: flex; align-items: center; gap: 4px; margin-left: auto; flex-wrap: nowrap; }
  .nav-tabs a {
    text-decoration: none; color: var(--text-muted); font-size: 13px; font-weight: 600;
    padding: 8px 14px; border-radius: 999px; transition: background .15s, color .15s;
    white-space: nowrap;
  }
  .nav-tabs a:hover { color: var(--text); background: var(--surface-2); }
  .nav-tabs a.active { color: var(--accent-text); background: var(--accent); }
  .nav-tabs a.cta-btn { color: var(--accent-text); background: var(--accent); font-weight: 700; }
  .nav-tabs a.cta-btn:hover { background: var(--accent-strong); color: var(--accent-text); }
  .nav-tabs .tag {
    font-size: 9.5px; text-transform: uppercase; letter-spacing: .03em;
    background: var(--good-soft); color: var(--good-text); border-radius: 999px;
    padding: 1px 6px; margin-left: 5px; font-weight: 700; vertical-align: 1px;
  }
  .nav-tabs .tag.tag-paid { background: var(--accent); color: var(--accent-text); }
  .nav-tabs .divider { width: 1px; height: 22px; background: var(--border); margin: 0 4px; }

  /* ---------------------------------------------------------------------
     Меню вошедшего — панель, а не строка ссылок.

     Строкой оно почти нигде не помещалось и всё равно пряталось за бургер,
     а из строки не видно ни сколько звёзд на счету, ни что сколько стоит.
     Теперь панель одинаковая на телефоне и на компьютере: карточка баланса,
     рабочие разделы плитками с ценой, редкое — тихим списком.
     --------------------------------------------------------------------- */
  .nav-tabs.nav-menu {
    display: none; position: absolute; top: calc(100% + 6px); right: 16px; left: auto;
    flex-direction: column; align-items: stretch; gap: 12px; margin-left: 0;
    width: 340px; max-width: calc(100vw - 32px);
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius-sm); box-shadow: var(--shadow-lg);
    padding: 12px; z-index: 20;
    /* Меню длиннее экрана — листается само, а не уезжает под нижнюю панель. */
    max-height: calc(100dvh - 88px); overflow-y: auto; overscroll-behavior: contain;
  }
  .nav-tabs.nav-menu.open { display: flex; }
  @media (max-width: 700px) {
    /* На телефоне панель во всю ширину: пальцем целиться проще, и карточка
       баланса помещается развёрнутой. Снизу оставляем место под нижнюю панель
       (52 px + безопасная зона) — иначе последний пункт под неё не достать. */
    .nav-tabs.nav-menu {
      right: 12px; left: 12px; width: auto; max-width: none;
      /* Панель заканчивается НАД нижней панелью разделов, а не уезжает под
         неё: 78 px шапка + 53 px нижняя панель + зазор. Остальное листается
         внутри самой панели. */
      max-height: calc(100dvh - 78px - 61px - env(safe-area-inset-bottom, 0px));
    }
  }

  /* Карточка баланса — первое, что видно в меню: звёзды это и есть товар,
     а «Пополнить» раньше лежало предпоследним пунктом списка. */
  .nav-card {
    display: flex; flex-direction: column; gap: 12px;
    padding: 12px; border-radius: 16px;
    background: var(--surface-2); border: 1px solid var(--border);
  }
  .nav-tabs.nav-menu .nav-card-user {
    display: flex; align-items: center; gap: 11px; min-width: 0; min-height: 44px;
    padding: 0; border-radius: 0; background: none; text-decoration: none; color: inherit;
  }
  .nav-tabs.nav-menu .nav-card-user:hover { background: none; }
  .nav-tabs.nav-menu .nav-card-user:hover .nav-card-mail { text-decoration: underline; }
  .nav-avatar {
    width: 36px; height: 36px; flex-shrink: 0; border-radius: 12px;
    background: var(--accent-soft); border: 1px solid var(--accent-line);
    color: var(--accent-strong); font-size: 14px; font-weight: 800;
    display: flex; align-items: center; justify-content: center;
  }
  .nav-card-who { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex-grow: 1; }
  .nav-card-mail {
    font-size: 13px; font-weight: 700; color: var(--text);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .nav-card-role { font-size: 11.5px; color: var(--text-muted); }
  .nav-card-money { display: flex; align-items: flex-end; gap: 10px; }
  .nav-card-bal { display: flex; flex-direction: column; gap: 1px; flex-grow: 1; min-width: 0; }
  .nav-card-cap {
    font-size: 10.5px; font-weight: 700; letter-spacing: .05em;
    text-transform: uppercase; color: var(--text-muted);
  }
  .nav-card-sum { display: flex; align-items: baseline; gap: 5px; flex-wrap: wrap; }
  .nav-card-sum b { font-size: 26px; font-weight: 800; letter-spacing: -.03em; line-height: 1; }
  .nav-card-sum i { font-style: normal; font-size: 14px; }
  .nav-card-sum small { font-size: 11.5px; color: var(--text-muted); }
  .nav-tabs.nav-menu .nav-topup {
    flex-shrink: 0; height: 44px; padding: 0 20px; border-radius: var(--radius-pill);
    background: var(--accent); color: var(--accent-text);
    font-size: 13.5px; font-weight: 700; text-decoration: none;
    display: inline-flex; align-items: center;
  }
  .nav-tabs.nav-menu .nav-topup:hover { background: var(--accent-strong); color: var(--accent-text); }

  /* На компьютере карточка баланса сжимается: подпись «Баланс» не нужна —
     строка и так короткая, а панель не должна занимать пол-экрана. */
  @media (min-width: 701px) {
    .nav-card { padding: 11px; gap: 10px; }
    .nav-card-cap { display: none; }
    .nav-card-sum b { font-size: 19px; }
    .nav-card-sum i { font-size: 12px; }
    .nav-tabs.nav-menu .nav-topup { height: 34px; padding: 0 15px; font-size: 12.5px; }
    .nav-tabs.nav-menu .nav-tile { min-height: 96px; padding: 11px; }
    .nav-tabs.nav-menu .nav-row { min-height: 38px; font-size: 12.5px; }
  }

  /* --- Звёзд не хватает ---------------------------------------------------
     Человек упёрся в отказ и открыл меню. Показываем это состояние честно:
     карточка баланса краснеет, платные разделы закрыты замком и ведут на
     пополнение, а бесплатные функции наоборот подсвечены — это единственное,
     чем можно заняться прямо сейчас. */
  .nav-card.low { background: var(--bad-soft); border-color: var(--bad-border); }
  .nav-card.low .nav-avatar {
    background: var(--bad-soft); border-color: var(--bad-border); color: var(--bad);
  }
  .nav-card.low .nav-card-cap,
  .nav-card.low .nav-card-sum b,
  .nav-card.low .nav-card-sum i,
  .nav-card.low .nav-card-sum small { color: var(--bad); }

  .nav-tabs.nav-menu .nav-tile.locked { opacity: .55; }
  .nav-tabs.nav-menu .nav-tile.locked:hover { opacity: .8; border-color: var(--accent-line); }
  .nav-tile-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 6px; }
  .nav-tile-lock { color: var(--text-muted); flex-shrink: 0; }
  /* Единственная работающая плитка в этом состоянии — её и видно. */
  .nav-tabs.nav-menu .nav-tile.free.stands-out {
    border-color: var(--good); background: var(--good-soft);
  }
  .nav-tabs.nav-menu .nav-tile.free.stands-out .nav-tile-name { color: var(--good-text); }

  .nav-cap {
    font-size: 10.5px; font-weight: 700; letter-spacing: .06em;
    text-transform: uppercase; color: var(--text-muted); padding: 0 4px; margin-bottom: -6px;
  }

  /* Рабочие разделы — плитками: по ним ходят каждый день, и пальцем в плитку
     попадаешь не глядя. Цена прямо на плитке, чтобы не открывать раздел
     ради вопроса «сколько это стоит». */
  /* grid-auto-rows: 1fr — обе строки плиток одной высоты. Без этого строка с
     длинной подписью («от 4 ⭐ за карточку» в две строки) выше соседней, и
     сетка перестаёт читаться сеткой. */
  .nav-tiles {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-auto-rows: 1fr; gap: 8px;
  }
  .nav-tabs.nav-menu .nav-tile {
    display: flex; flex-direction: column; justify-content: space-between; gap: 10px;
    min-height: 110px; padding: 12px; border-radius: 16px;
    background: var(--surface-2); border: 1px solid var(--border);
    text-decoration: none; color: var(--text);
    transition: border-color .15s, background .15s, transform .12s;
  }
  .nav-tabs.nav-menu .nav-tile:hover { border-color: var(--accent-line); background: var(--surface-2); color: var(--text); }
  .nav-tabs.nav-menu .nav-tile:active { transform: scale(.985); }
  .nav-tile .ic { color: var(--accent-strong); }
  .nav-tile.free .ic { color: var(--good); }
  .nav-tile-name {
    font-size: 14px; font-weight: 700; letter-spacing: -.01em; line-height: 1.2;
    white-space: normal; overflow-wrap: anywhere;
  }
  .nav-tile-price {
    font-size: 11.5px; font-weight: 600; color: var(--text-muted); white-space: normal;
  }
  .nav-tile.free .nav-tile-price { color: var(--good); }
  /* Раздел, на котором человек сейчас стоит. */
  .nav-tabs.nav-menu .nav-tile.on {
    background: var(--accent); border-color: var(--accent); color: var(--accent-text);
  }
  .nav-tile.on .ic { color: var(--accent-text); }
  .nav-tile.on .nav-tile-name { font-weight: 800; }
  .nav-tile.on .nav-tile-price { color: var(--accent-text); opacity: .7; }

  /* Редкое — тихим списком под плитками. */
  .nav-list { display: flex; flex-direction: column; }
  .nav-tabs.nav-menu .nav-row {
    display: flex; align-items: center; gap: 12px; min-height: 46px;
    padding: 0 10px; border-radius: 12px;
    font-size: 13.5px; font-weight: 600; color: var(--text); text-decoration: none;
    white-space: normal;
  }
  .nav-tabs.nav-menu .nav-row .ic { color: var(--text-muted); flex-shrink: 0; }
  .nav-tabs.nav-menu .nav-row > span:first-of-type { flex-grow: 1; }
  .nav-tabs.nav-menu .nav-row:hover { background: var(--surface-2); color: var(--text); }
  .nav-tabs.nav-menu .nav-row.on { background: var(--accent-soft); color: var(--accent-strong); }
  .nav-tabs.nav-menu .nav-row.on .ic { color: var(--accent-strong); }
  .nav-tabs.nav-menu .nav-row.danger { color: var(--bad); }
  .nav-tabs.nav-menu .nav-row.danger .ic { color: var(--bad); }
  .nav-tabs.nav-menu .nav-row .tag { margin-left: 0; }
  .nav-sep { height: 1px; background: var(--border); margin: 7px 10px; }
  /* Меню шапки (бургер) — п. "оптимизировать шапку": когда строке с
     разделами не хватает места, прячем её целиком за кнопку справа, слева
     остаётся только логотип (без текста бренда, чтобы не мешал). Порог
     подобран так, чтобы на обычном desktop-окне строка ещё помещалась
     целиком (даже у админа, с самым длинным набором пунктов), а на более
     узких окнах/планшетах/телефонах уже не наезжала друг на друга. */
  /* Счётчик задач рядом с логотипом: видно, что сервис считает, даже если
     человек ушёл со страницы генерации. */
  .queue-chip {
    display: inline-flex; align-items: center; gap: 6px; margin-left: 12px;
    padding: 5px 11px; border-radius: 999px; border: 1px solid var(--border);
    background: var(--surface-2); color: var(--text); font-size: 12px; font-weight: 600;
    text-decoration: none; white-space: nowrap;
  }
  .queue-chip:hover { border-color: var(--text); }
  .queue-chip-dot {
    width: 7px; height: 7px; border-radius: 50%; background: var(--accent-strong);
    animation: queuePulse 1.4s ease-in-out infinite;
  }
  @keyframes queuePulse { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }

  /* Совет №72: баланс звёзд — в шапке, а не в глубине кабинета. Человек
     тратит звёзды на каждой генерации и должен видеть, сколько осталось,
     не открывая меню. Нажатие ведёт на пополнение. */
  .star-chip {
    position: relative; display: inline-flex; align-items: center; gap: 5px;
    margin-left: 8px; padding: 6px 12px; border-radius: 999px;
    border: 1px solid var(--accent-line); background: var(--accent-soft);
    color: var(--accent-strong);
    font-size: 12.5px; font-weight: 800; text-decoration: none; white-space: nowrap;
    transition: border-color .15s, box-shadow .15s;
  }
  .star-chip:hover { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
  .star-chip.low { border-color: var(--accent-strong); color: var(--accent-strong); }
  /* Всплывающее «−1» при списании: иначе звёзды уходят незаметно, и человек
     узнаёт о расходе только из истории. */
  .star-delta {
    position: absolute; left: 50%; top: -2px; transform: translateX(-50%);
    font-size: 12px; font-weight: 800; pointer-events: none;
    animation: starDelta 1.5s ease-out forwards;
  }
  .star-delta.minus { color: var(--accent-strong); }
  .star-delta.plus { color: #16a34a; }
  @keyframes starDelta {
    0% { opacity: 0; transform: translate(-50%, 4px); }
    18% { opacity: 1; transform: translate(-50%, -6px); }
    100% { opacity: 0; transform: translate(-50%, -20px); }
  }
  @media (max-width: 700px) {
    .queue-chip { margin-left: 6px; padding: 4px 8px; font-size: 11px; }
    /* На телефоне баланс уже стоит в нижней панели — второй раз в шапке он
       только отнимает место у названия раздела. Всплывающее «−1» тогда
       показываем над нижней звездой. */
    .star-chip { display: none; }
    .bottom-nav a { overflow: visible; }
    /* На узком экране шапка и так плотная: когда появляется чип «в работе»,
       он выдавливал переключатель темы за правый край и страница начинала
       ездить вбок. Разрешаем чипу сжиматься и прятать лишний текст. */
    header.topbar { padding-inline: 14px; gap: 6px; }
    .queue-chip { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
    .brand-link { min-width: 0; }
    .brand-tag { display: none; }
    /* На телефоне остаётся сам знак — раньше тут не оставалось ничего. */
    .brand-logo { font-size: 15.5px; }
  }

  .nav-current-wrap { display: none; margin-left: auto; align-items: center; gap: 8px; }
  .nav-current-tab {
    font-size: 12.5px; font-weight: 700; color: var(--text-muted); white-space: nowrap;
    overflow: hidden; text-overflow: ellipsis; max-width: 40vw;
  }
  /* У вошедшего меню всегда панель, значит и кнопка к ней нужна всегда —
     не только на узком экране. */
  .topbar.has-menu .nav-toggle { display: flex; }
  .topbar.has-menu .nav-current-wrap { display: flex; }
  .nav-toggle {
    display: none; width: 38px; height: 38px; flex-shrink: 0;
    align-items: center; justify-content: center; background: var(--surface-2);
    border: 1px solid var(--border); border-radius: 10px; cursor: pointer;
    font-size: 18px; color: var(--text); line-height: 1;
  }
  .nav-toggle:hover { background: var(--surface); }
  /* Переключатель темы — тумблер с солнцем и луной по краям.
     Раньше здесь была кнопка с эмодзи, показывавшая тему, НА которую
     переключит клик. Без подписи это читалось наоборот: человек видел луну
     на тёмной теме и думал, что тёмная и так включена. У тумблера состояние
     показывает ручка, а обе иконки на виду — видно и где стоишь, и куда
     переедешь.

     Сама кнопка — только область нажатия, она прозрачная и на телефоне
     вырастает до 44 px по высоте. Капсулу рисует ::before и держит свои
     30 px: правило «всё, по чему жмут пальцем, не ниже 44» иначе растянуло
     бы тумблер в овал. */
  .theme-toggle {
    position: relative; margin-left: 8px; flex-shrink: 0; box-sizing: border-box;
    width: 56px; height: 34px; padding: 0; border: 0; background: none;
    cursor: pointer; line-height: 0;
  }
  .theme-toggle::before {
    content: ""; position: absolute; left: 0; top: 50%; transform: translateY(-50%);
    width: 56px; height: 30px; border-radius: var(--radius-pill);
    background: var(--surface-2); border: 1px solid var(--border);
    box-sizing: border-box; transition: border-color .16s;
  }
  .theme-toggle:hover::before { border-color: var(--accent-line); }
  .theme-toggle:focus-visible { outline: none; }
  .theme-toggle:focus-visible::before { outline: 2px solid var(--accent); outline-offset: 2px; }
  .tt-side {
    position: absolute; top: 50%; transform: translateY(-50%); width: 28px; height: 28px;
    display: flex; align-items: center; justify-content: center;
    color: var(--text-muted); transition: color .16s;
  }
  .tt-sun { left: 0; }
  .tt-moon { right: 0; }
  /* Ручка едет поверх иконок: та, что под ней, гасится — значит выбрана. */
  .tt-knob {
    position: absolute; top: 50%; left: 3px; width: 24px; height: 24px; border-radius: 50%;
    background: #fff; transform: translateY(-50%); transition: left .18s ease;
    box-shadow: var(--shadow-sm), 0 0 0 1px rgba(0, 0, 0, .08);
  }
  .theme-toggle[aria-checked="true"] .tt-knob { left: 29px; }
  .theme-toggle[aria-checked="true"] .tt-sun { color: var(--text); }
  .theme-toggle[aria-checked="true"] .tt-moon { color: var(--surface-2); }
  .theme-toggle[aria-checked="false"] .tt-sun { color: var(--surface-2); }
  .theme-toggle[aria-checked="false"] .tt-moon { color: var(--text); }
  @media (prefers-reduced-motion: reduce) { .tt-knob { transition: none; } }
  @media (max-width: 1180px) {
    .brand-tag { display: none; }
    .nav-current-wrap { display: flex; }
    .nav-toggle { display: flex; }
    .nav-tabs:not(.nav-menu) {
      display: none; position: absolute; top: calc(100% + 6px); right: 16px; left: 16px;
      flex-direction: column; align-items: stretch; gap: 2px; margin-left: 0;
      background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm);
      padding: 8px; box-shadow: var(--shadow-md); z-index: 20; max-width: 320px;
    }
    .nav-tabs:not(.nav-menu).open { display: flex; }
    .nav-tabs:not(.nav-menu) a { padding: 11px 12px; }
    .nav-tabs .divider { display: none; }
  }

  /* Порог в пикселях помогает только на глаз: у админа пунктов меню больше,
     чем у продавца, и строка перестаёт помещаться раньше — страница начинает
     ездить вбок на вполне широком экране. Поэтому меру знает не медиазапрос, а
     сама страница: скрипт ниже вешает .is-compact, когда строка не влезла.
     Медиазапрос выше остаётся как запасной вариант без JS. */
  .topbar.is-compact .brand-block { display: none; }
  .topbar.is-compact .nav-current-wrap { display: flex; }
  .topbar.is-compact .nav-toggle { display: flex; }
  .topbar.is-compact .nav-tabs:not(.nav-menu) {
    display: none; position: absolute; top: calc(100% + 6px); right: 16px; left: auto;
    flex-direction: column; align-items: stretch; gap: 2px; margin-left: 0;
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm);
    padding: 8px; box-shadow: var(--shadow-md); z-index: 20; width: 300px; max-width: calc(100vw - 32px);
  }
  .topbar.is-compact .nav-tabs:not(.nav-menu).open { display: flex; }
  .topbar.is-compact .nav-tabs:not(.nav-menu) a { padding: 11px 12px; }
  .topbar.is-compact .nav-tabs .divider { display: none; }

  /* Форма — то, ради чего сюда приходят, а галерея — уже сделанное. При
     колонке в 340 пикселей поле загрузки фото выходило меньше миниатюры
     готовой карточки рядом. Даём форме 420 и растягиваем страницу: на
     широком экране места хватает обеим. */
  .layout {
    max-width: 1320px; margin: 0 auto; padding: 24px;
    display: grid; grid-template-columns: 420px 1fr; gap: 24px;
    align-items: start;
  }
  @media (max-width: 1100px) {
    .layout { max-width: 1180px; grid-template-columns: 360px 1fr; }
  }
  @media (max-width: 860px) {
    .layout { grid-template-columns: 1fr; }
  }

  /* Одна колонка. Ширину держим по тексту, а не по экрану: на мониторе в
     2560 строка во всю ширину не читается — глаз теряет начало следующей. */
  .layout-wide { max-width: 1120px; grid-template-columns: minmax(0, 1fr); }
  @media (max-width: 1100px) { .layout-wide { max-width: 1120px; grid-template-columns: minmax(0, 1fr); } }

  /* Страница, которую читают подряд: справка, инструкции, разбор. Колонка
     уже и стоит по центру — иначе текст жмётся к левому краю широкого
     экрана, а справа висит пустота. */
  .layout-read { max-width: 780px; grid-template-columns: minmax(0, 1fr); }
  @media (max-width: 1100px) { .layout-read { max-width: 780px; grid-template-columns: minmax(0, 1fr); } }

  /* Вход и регистрация. Форма и то, ради чего её заполняют, рядом: пустой
     экран с полем «пароль» посередине ничего не обещает. */
  .layout-auth {
    max-width: 960px; grid-template-columns: minmax(0, 1fr);
    padding-top: 40px; justify-items: center;
  }
  @media (max-width: 1100px) { .layout-auth { max-width: 960px; grid-template-columns: minmax(0, 1fr); } }

  /* Шапка страницы: одна и та же на всех разделах, чтобы человек всегда знал,
     где он и что тут можно сделать. */
  .page-head {
    display: flex; align-items: flex-end; justify-content: space-between;
    gap: 16px; flex-wrap: wrap; margin-bottom: var(--space-5);
  }
  .page-head-text { min-width: 0; }
  .page-head h1, .page-head h2 {
    margin: 0; font-size: var(--text-display); line-height: 1.12;
    letter-spacing: -.025em; font-weight: 800; text-wrap: balance;
  }
  .page-head .page-lead {
    margin: 10px 0 0; color: var(--text-muted); font-size: 15px; line-height: 1.55;
    max-width: 62ch;
  }
  .page-head-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
  .page-head-actions .primary { margin-top: 0; }
  @media (max-width: 700px) {
    .page-head h1, .page-head h2 { font-size: 26px; }
    .page-head .page-lead { font-size: 14px; }
  }

  /* Сетка карточек раздела. Колонки подстраиваются сами, ряды — одной высоты,
     чтобы соседние карточки не разъезжались на пиксель. */
  .page-grid {
    display: grid; gap: var(--space-4);
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    align-items: start;
  }
  .page-grid.cols-2 { grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); }
  .page-grid .span-2 { grid-column: 1 / -1; }
  @media (max-width: 700px) {
    .page-grid, .page-grid.cols-2 { grid-template-columns: minmax(0, 1fr); }
  }

  /* Заголовок внутри карточки: подпись сверху мелким, сам заголовок — крупно.
     Это отделяет «что это» от «что здесь сейчас». */
  .card-eyebrow {
    font-size: 11px; letter-spacing: .08em; text-transform: uppercase;
    color: var(--text-muted); font-weight: 700; margin: 0 0 6px;
  }

  /* Пустое состояние. Раньше пустой раздел выглядел как сломанный: заголовок
     и ничего под ним. Теперь он объясняет, что здесь появится и как. */
  .empty-state {
    display: flex; flex-direction: column; align-items: center; text-align: center;
    gap: 10px; padding: 44px 24px;
    background: var(--surface-2); border: 1px solid var(--border);
    border-radius: var(--radius);
  }
  .empty-state .empty-mark {
    width: 52px; height: 52px; border-radius: var(--radius-sm);
    display: grid; place-items: center;
    background: var(--accent-soft); color: var(--accent-strong);
  }
  .empty-state h3 { margin: 0; font-size: 17px; }
  .empty-state p { margin: 0; color: var(--text-muted); font-size: 14px; max-width: 46ch; line-height: 1.5; }
  .empty-state .primary { margin-top: 6px; }
  /* На телефоне отступы складывались один в другой: 24 у страницы, 24 у
     панели, 16 у блока внутри — и содержимое оказывалось в 64 пикселях от
     края экрана, а сами блоки выглядели узкой колонкой посередине. На
     маленьком экране каждый вложенный уровень отступа стоит дорого, поэтому
     ужимаем их разом: поля слева и справа одинаковые, а места под текст и
     кнопки становится заметно больше. */
  @media (max-width: 700px) {
    .layout { padding: 16px 12px 24px; }
    .panel { padding: 14px 12px; }
    .upload-zone { padding: 14px 10px; }
  }

  .panel {
    background-color: var(--surface-2);
    background-image: linear-gradient(180deg, var(--panel-sheen) 0%, transparent 140px);
    border: 1px solid var(--border);
    border-radius: var(--radius); padding: var(--space-5);
  }
  /* Раньше форма генерации была "прилипающей" (sticky) слева, пока справа
     скроллится галерея — по просьбе убрали: теперь форма уезжает вверх при
     скролле как обычный блок, а уже потом идут карточки (п. "сначала блок
     с генерацией уезжает, потом карточки"). На мобильном (одна колонка)
     sticky-блок к тому же наезжал на галерею при скролле — без sticky
     карточки теперь просто идут лентой ниже формы, без наложений. */
  /* Заголовок раздела внутри карточки. Раньше он был набран как служебная
     метка — 13 пикселей, капслоком, приглушённым цветом: самый мелкий и
     самый бледный текст на странице стоял в самом заметном месте, и глаз
     цеплялся не за него, а за абзац под ним. Теперь это обычный заголовок,
     а капслоком набирается только надстрочная подпись (.card-eyebrow) —
     там она уместна, потому что ничего не называет, а лишь помечает. */
  .panel h2 {
    font-size: var(--text-lg); letter-spacing: -.01em;
    color: var(--text); margin: 0 0 12px; font-weight: 700; line-height: 1.3;
  }
  .panel .card-eyebrow + h2 { margin-top: 0; }

  label.field-label { display: block; font-size: 13px; font-weight: 600; margin: 14px 0 5px; }
  label.field-label:first-of-type { margin-top: 0; }
  input[type=file], input[type=text], input[type=email], input[type=password], input[type=color], input[type=number], textarea, select {
    width: 100%; box-sizing: border-box; padding: 10px 12px;
    border: 1px solid var(--border); border-radius: var(--radius-sm); font-size: 13.5px;
    font-family: inherit; background: var(--surface); color: var(--text);
  }
  textarea { min-height: 76px; resize: vertical; }
  input[type=file] { padding: 8px 10px; }
  input[type=color] { padding: 3px; height: 40px; cursor: pointer; }
  input[type=number] { width: auto; }

  .field-hint { font-size: 11.5px; color: var(--text-muted); margin: 4px 0 0; line-height: 1.4; }

  /* Живая проверка полей формы прямо во время ввода (п.38 чек-листа) —
     дополняет браузерный required/minlength текстом под полем, не заменяя
     его: сообщение появляется по мере набора и исчезает, когда всё верно. */
  .field-error {
    font-size: 11.5px; color: var(--bad); margin: 4px 0 0; line-height: 1.4; display: none;
  }
  .field-error.show { display: block; }
  input.field-invalid { border-color: var(--bad); }

  /* Ссылка «назад» — это кнопка, по которой на телефоне надо попадать пальцем.
     Строчка в 12,5px даёт высоту цели около 17px; добираем отступами до 32. */
  .back-link {
    font-size: 12.5px; color: var(--text-muted); text-decoration: none;
    display: inline-flex; align-items: center; min-height: 32px; padding: 4px 0;
    margin-bottom: 6px;
  }
  /* На телефоне цель должна быть не мельче 40 px — это тот же порог, что и у
     остальных кнопок. */
  @media (max-width: 700px) { .back-link { min-height: 40px; padding: 8px 0; margin-bottom: 2px; } }
  .back-link:hover { color: var(--text); }

  /* FAQ / база знаний (п.75 чек-листа) — аккордеон на нативных <details>,
     без JS: клавиатурно доступен и работает без скриптов. */
  .faq-section { margin-bottom: 22px; }
  .faq-section:last-child { margin-bottom: 0; }
  .faq-section-title {
    font-size: 13px; text-transform: uppercase; letter-spacing: .04em;
    color: var(--text-muted); font-weight: 700; margin: 0 0 10px;
  }
  .faq-item {
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    background: var(--surface); margin-bottom: 8px; overflow: hidden;
  }
  .faq-item summary {
    padding: 12px 14px; font-size: 13.5px; font-weight: 600; cursor: pointer;
    list-style: none; display: flex; justify-content: space-between; gap: 10px; align-items: baseline;
  }
  .faq-item summary::-webkit-details-marker { display: none; }
  .faq-item summary::after { content: "+"; color: var(--text-muted); font-weight: 700; flex-shrink: 0; }
  .faq-item[open] summary::after { content: "–"; }
  .faq-item summary:hover { background: var(--surface-2); }
  .faq-item summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
  .faq-item-body {
    padding: 0 14px 14px; font-size: 13px; color: var(--text-muted); line-height: 1.5;
  }

  /* Ознакомительные подсказки для новых пользователей (первое использование
     раздела) — закрываются по кнопке и больше не показываются в этом
     браузере (localStorage), совсем как переключатель темы. Не блокируют
     работу и не требуют похода на сервер. */
  .onb-hint {
    display: flex; gap: 10px; align-items: flex-start; background: var(--accent-soft);
    border: 1px solid var(--accent); border-radius: var(--radius-sm);
    padding: 11px 14px; margin-bottom: 16px; font-size: 12.5px; line-height: 1.5; color: var(--text);
  }
  .onb-hint-text { flex: 1; }
  .onb-hint > .ic { color: var(--accent-strong); flex-shrink: 0; margin-top: 1px; }
  .onb-hint-close {
    background: none; border: none; cursor: pointer; color: var(--text-muted);
    font-size: 16px; line-height: 1; padding: 0 2px; flex-shrink: 0;
  }
  .onb-hint-close:hover { color: var(--text); }

  button.primary {
    margin-top: 16px; width: 100%; background: var(--accent); color: var(--accent-text); border: none;
    /* Не таблетка: у кнопок форма прямоугольная со скруглением, таблетка
       осталась только у чипов и меток — так кнопку не путают с меткой. */
    border-radius: var(--radius-sm); padding: 13px 20px; font-size: 14px; font-weight: 700; cursor: pointer;
    transition: background .15s;
  }
  button.primary:hover { background: var(--accent-strong); }

  /* Ссылка, которая ведёт себя как главная кнопка («Написать в поддержку»,
     «Пополнить баланс»). Раньше такие ссылки получали класс .primary и
     оставались подчёркнутым текстом: правило было написано под button. */
  a.primary {
    display: inline-block; width: auto; text-decoration: none;
    background: var(--accent); color: var(--accent-text); border: none;
    border-radius: var(--radius-sm); padding: 12px 20px;
    font-size: 14px; font-weight: 700; line-height: 1.2;
    transition: background .15s;
  }
  a.primary:hover { background: var(--accent-strong); color: var(--accent-text); text-decoration: none; }

  /* Кнопка внутри сообщения-плашки (например, «Сделать заново» у свежего
     отчёта) — выглядит как ссылка, но отправляет форму. */
  .flash-link {
    background: none; border: none; padding: 0; font: inherit; color: inherit;
    text-decoration: underline; cursor: pointer; font-weight: 700;
  }

  .flash {
    background: var(--bad-soft); color: var(--bad-text); border: 1px solid var(--bad-border);
    border-radius: var(--radius-sm); padding: 11px 16px; margin-bottom: 16px; font-size: 13px;
    grid-column: 1 / -1;
  }
  /* Получилось — зелёная. Красным красим только то, что пошло не так. */
  .flash-ok {
    background: var(--good-soft); color: var(--good-text); border-color: var(--good);
  }

  /* Сообщение о действии, которое не перезагружает страницу (карточка ушла в
     корзину). Внизу экрана — там, где сейчас смотрят, а не в начале страницы. */
  .toast {
    position: fixed; left: 50%; transform: translateX(-50%);
    bottom: max(16px, env(safe-area-inset-bottom)); z-index: 60;
    max-width: min(420px, calc(100vw - 32px));
    background: var(--surface-2); color: var(--text);
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    box-shadow: var(--shadow-lg); padding: 11px 16px; font-size: 13px;
    transition: opacity .3s;
  }
  .toast a { color: var(--link); font-weight: 700; }
  .toast.gone { opacity: 0; }

  /* Окно "не хватает звёзд" (совет №74) — докупить, не уходя со страницы */
  .topup-backdrop {
    position: fixed; inset: 0; background: rgba(10,10,10,.55); z-index: 3000;
    display: flex; align-items: center; justify-content: center; padding: 16px;
  }
  .topup-modal {
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    max-width: 440px; width: 100%; max-height: calc(100vh - 32px); overflow-y: auto;
    padding: 22px; box-shadow: var(--shadow-lg);
  }
  .topup-head { display: flex; align-items: flex-start; gap: 12px; margin-bottom: 6px; }
  .topup-head h3 { margin: 0; font-size: 17px; font-weight: 800; letter-spacing: -.01em; flex: 1; }
  .topup-close {
    background: none; border: none; color: var(--text-muted); font-size: 20px; line-height: 1;
    cursor: pointer; padding: 0 2px;
  }
  .topup-text { margin: 0 0 4px; font-size: 13px; color: var(--text-muted); }
  .topup-balance { margin: 0 0 16px; font-size: 13px; color: var(--text-muted); }
  .topup-balance b { color: var(--text); }
  .topup-list { display: flex; flex-direction: column; gap: 8px; }
  .topup-pkg {
    display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
    background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm);
    padding: 12px 14px; cursor: pointer; font: inherit; color: var(--text);
    transition: border-color .15s, background .15s;
  }
  .topup-pkg:hover { border-color: var(--accent-strong); background: var(--accent-soft); }
  .topup-pkg-main { flex: 1; min-width: 0; }
  .topup-pkg-stars { font-size: 14px; font-weight: 700; }
  .topup-pkg-per { font-size: 12px; color: var(--text-muted); }
  .topup-pkg-price { font-size: 14px; font-weight: 700; white-space: nowrap; }
  .topup-empty { font-size: 13px; color: var(--text-muted); margin: 0 0 12px; }
  .topup-all {
    display: block; text-align: center; margin-top: 14px; font-size: 13px; color: var(--link);
  }

  /* Совет №51: экран ошибки вместо alert() с текстом из лога. Что случилось,
     что делать и кнопка, которой это делают, — в одном месте. */
  .job-error {
    background: var(--bad-soft); border: 1px solid var(--bad-border); border-radius: var(--radius);
    padding: 16px 18px; margin-bottom: 16px; grid-column: 1 / -1;
  }
  .job-error-title { font-size: 14px; font-weight: 700; color: var(--bad); margin-bottom: 4px; }
  .job-error-hint { font-size: 13px; color: var(--text); line-height: 1.45; }
  .job-error-stars { font-size: 12px; color: var(--text-muted); margin-top: 6px; }
  .job-error-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; align-items: center; }
  .job-error-btn {
    background: var(--accent); color: var(--accent-text); border: none; border-radius: var(--radius-sm);
    padding: 8px 16px; font-size: 13px; font-weight: 700; cursor: pointer; text-decoration: none;
    display: inline-block;
  }
  .job-error-btn.secondary { background: none; color: var(--text-muted); border: 1px solid var(--border); }
  .job-error-tech {
    margin-top: 10px; font-size: 11.5px; color: var(--text-muted); word-break: break-word;
  }
  .job-error-tech summary { cursor: pointer; }

  /* Совет №58: подтверждение необратимого действия — публикация заменяет все
     фото карточки на площадке, и это видно покупателям. */
  .confirm-backdrop {
    position: fixed; inset: 0; background: rgba(10,10,10,.55); z-index: 3000;
    display: flex; align-items: center; justify-content: center; padding: 16px;
  }
  .confirm-modal {
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    max-width: 520px; width: 100%; max-height: calc(100vh - 32px); overflow-y: auto;
    padding: 22px; box-shadow: var(--shadow-lg); text-align: left;
  }
  /* Широкое окно — для того, что раньше было целой панелью страницы: формы
     генерации, разбора конкурентов, истории. */
  .confirm-modal.is-wide { max-width: 720px; }
  .confirm-modal h3 { margin: 0 0 8px; font-size: 17px; font-weight: 800; letter-spacing: -.01em; }
  /* У окна, выросшего из раздела, заголовок и крестик стоят в одной строке:
     содержимое длинное, и кнопка «закрыть» должна быть видна сразу. */
  .modal-head {
    display: flex; align-items: center; gap: 12px; margin-bottom: 14px;
    padding-bottom: 12px; border-bottom: 1px solid var(--border);
  }
  .modal-head h3 { margin: 0; flex: 1; }
  .modal-x {
    width: 34px; height: 34px; min-height: 0; margin: 0; padding: 0; flex-shrink: 0;
    display: grid; place-items: center; cursor: pointer;
    border-radius: 50%; border: 1px solid var(--border);
    background: var(--surface-2); color: var(--text-muted); font-size: 15px;
  }
  .modal-x:hover { border-color: var(--bad); color: var(--bad); }
  .confirm-text { margin: 0 0 14px; font-size: 13.5px; line-height: 1.5; color: var(--text); }
  .confirm-note { margin: 12px 0 0; font-size: 12px; color: var(--text-muted); }
  .confirm-strip { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
  .confirm-thumb {
    position: relative; width: 54px; height: 72px; border-radius: 8px; overflow: hidden;
    border: 1px solid var(--border); flex-shrink: 0;
  }
  .confirm-thumb.is-main { border-color: var(--accent-strong); border-width: 2px; }
  .confirm-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
  .confirm-thumb-tag {
    position: absolute; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,.65); color: #fff;
    font-size: 8.5px; font-weight: 700; text-align: center; padding: 2px 0; letter-spacing: .02em;
  }
  .confirm-more { font-size: 12px; color: var(--text-muted); }
  .confirm-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 18px; flex-wrap: wrap; }

  /* Совет №96: полоска оценки. Один вопрос, три кнопки, необязательное поле —
     всё, что больше, люди закрывают не читая. */
  .feedback-bar {
    grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center;
    gap: var(--space-2) var(--space-3); padding: 10px 14px; margin-bottom: var(--space-4);
    background: var(--accent-soft); border: 1px solid var(--accent-line);
    border-radius: var(--radius-sm); font-size: var(--text-md);
  }
  .feedback-q { font-weight: 700; }
  .feedback-form { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); flex: 1 1 320px; }
  .feedback-btn {
    display: inline-flex; align-items: center; gap: 6px; background: var(--surface);
    border: 1px solid var(--border); border-radius: var(--radius-pill); padding: 6px 12px;
    font-size: var(--text-sm); font-weight: 700; cursor: pointer; color: var(--text);
  }
  .feedback-btn:hover { border-color: var(--accent-strong); }
  .feedback-comment { margin: 0; flex: 1 1 200px; font-size: var(--text-sm); }
  .feedback-skip {
    background: none; border: none; color: var(--text-muted); font-size: var(--text-sm);
    cursor: pointer; text-decoration: underline;
  }
  @media (max-width: 560px) { .feedback-label { display: none; } }

  /* Совет №4: полоска первых шагов. Не «геймификация», а ответ на вопрос
     «что дальше»: три пункта, из них максимум один активен. */
  .onb-steps {
    grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center;
    gap: var(--space-2) var(--space-4); list-style: none; margin: 0 0 var(--space-4); padding: 10px 14px;
    background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm);
    font-size: var(--text-md);
  }
  .onb-step { display: flex; align-items: center; gap: var(--space-2); }
  .onb-step a { font-weight: 700; }
  .onb-step.is-done { color: var(--text-muted); }
  .onb-mark {
    width: 20px; height: 20px; border-radius: 50%; flex-shrink: 0;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: var(--text-xs); font-weight: 800;
    background: var(--surface); border: 1px solid var(--border); color: var(--text-muted);
  }
  .onb-step.is-done .onb-mark { background: var(--good-soft); color: var(--good-text); border-color: var(--good); }

  /* Совет №50: «пока вас не было». Хорошая новость, а не тревога — поэтому
     зелёная, спокойная и без кнопки-крестика: она исчезнет сама. */
  .away-note {
    grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px;
    background: var(--good-soft); border: 1px solid var(--good); border-radius: var(--radius-sm);
    padding: 11px 14px; margin-bottom: 16px; font-size: 13px;
  }
  .away-emoji { color: var(--good); font-weight: 800; }
  .away-text { flex: 1 1 240px; line-height: 1.45; }
  .away-link { font-weight: 700; text-decoration: none; color: var(--link); white-space: nowrap; }

  /* Совет №30: предупреждение о сроке хранения. Спокойное, не тревожное —
     это не ошибка, а напоминание забрать свою работу. */
  .retention-note {
    grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center; gap: 10px 16px;
    background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm);
    padding: 11px 14px; margin-bottom: 16px; font-size: 13px;
  }
  .retention-text { flex: 1 1 260px; color: var(--text); line-height: 1.45; }
  .retention-actions { display: flex; align-items: center; gap: 8px; }
  .retention-btn {
    background: var(--accent); color: var(--accent-text); border-radius: var(--radius-sm); padding: 8px 14px;
    font-size: 12.5px; font-weight: 700; text-decoration: none; white-space: nowrap;
  }
  .retention-close {
    background: none; border: none; color: var(--text-muted); font-size: 18px; line-height: 1;
    cursor: pointer; padding: 0 4px;
  }

  /* Совет №62: на телефоне четыре главных раздела живут внизу экрана, под
     большим пальцем, а не прячутся за бургером. Бургер остаётся — в нём всё
     остальное (поддержка, админка, выход). */
  .bottom-nav { display: none; }
  .bottom-nav a {
    flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 3px; padding: 7px 4px 6px; min-height: 52px; text-decoration: none;
    color: var(--text-muted); font-size: 10.5px; font-weight: 600; position: relative;
  }
  .bottom-nav a.active { color: var(--text); }
  /* Совет №86: иконки набора вместо эмодзи — одинаковые на любой системе
     (эмодзи на части машин просто не рисуются) и красятся вместе с текстом. */
  .bottom-nav .bn-icon { font-size: 18px; line-height: 1; display: flex; }
  .ic { display: inline-block; vertical-align: -0.18em; flex: 0 0 auto; }
  button .ic, a .ic { margin-right: 4px; }
  .bottom-nav a.active .bn-icon { transform: translateY(-1px); }
  .bottom-nav .bn-badge {
    position: absolute; top: 4px; left: 50%; margin-left: 4px;
    background: var(--accent); color: var(--accent-text); border-radius: 999px;
    font-size: 9px; font-weight: 800; padding: 1px 5px; line-height: 1.4;
  }

  @media (max-width: 700px) {
    .bottom-nav {
      display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 900;
      background: var(--surface); border-top: 1px solid var(--border);
      padding-bottom: env(safe-area-inset-bottom, 0px);
    }
    /* Панель перекрывает низ страницы — оставляем под неё место, иначе
       последняя кнопка формы всегда оказывается под ней. */
    body { padding-bottom: 64px; }
    /* Совет №64: всё, по чему нужно попасть пальцем, — не меньше 44 px.
       Мелкие ссылки-действия в карточках получают увеличенную область
       нажатия, оставаясь визуально такими же. */
    button, .primary, .ghost-btn, select, input[type="text"], input[type="email"],
    input[type="password"], input[type="number"] { min-height: 44px; }
    /* Раньше этим ссылкам хватало 32 px — «они же мелкие и редкие». На
       телефоне они как раз главные: скачать, сравнить, убрать. Проверка
       tests/mobile_check.py требует 40 px по всем элементам управления, и это
       честнее: в 32 px пальцем попадают не с первого раза. */
    .tile-download, .tile-edit-toggle, .tile-compare, .tile-pin-btn,
    .tile-more > summary, .gal-chip {
      min-height: 40px; display: inline-flex; align-items: center; padding-block: 8px;
    }
    .topup-pkg, .retention-btn, .job-error-btn { min-height: 44px; }
    /* Чип «в работе» в шапке — тоже ссылка, по которой жмут пальцем. */
    .queue-chip { min-height: 40px; }
    .brand-link { min-height: 40px; display: inline-flex; align-items: center; }
    .onb-step a { min-height: 40px; display: inline-flex; align-items: center; }
    .nav-toggle { min-width: 40px; min-height: 40px; }
    /* Область нажатия растёт до 44 px, а капсула внутри остаётся 30 px. */
    .theme-toggle { margin-left: 6px; height: 44px; }
  }

  /* --- Статус одной формы и одного цвета (совет №87) --------------------
     Раньше каждый раздел изображал состояние по-своему: где пульсирующая
     точка, где цветная плашка, где просто серый текст. Одинаковое состояние
     должно выглядеть одинаково везде — тогда его узнают, не читая.

     Форма несёт тот же смысл, что и цвет: это важно и для тех, кто не
     различает красный и зелёный, и просто для беглого взгляда. */
  .status {
    display: inline-flex; align-items: center; gap: 6px;
    border-radius: var(--radius-pill); padding: 3px 10px 3px 8px;
    font-size: var(--text-xs); font-weight: 700; line-height: 1.6; white-space: nowrap;
    border: 1px solid transparent;
  }
  .status-mark {
    width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 8px; font-weight: 800; line-height: 1;
  }

  /* В очереди — серый пунктир: работа принята, но ещё не началась. */
  .status-queued { background: var(--surface-2); color: var(--text-muted); border-color: var(--border); }
  .status-queued .status-mark { border: 1.5px dashed currentColor; }

  /* В работе — пульсирующая точка: единственное состояние, которое движется. */
  .status-running { background: var(--accent-soft); color: var(--text); border-color: var(--accent-line); }
  .status-running .status-mark { background: var(--accent-strong); animation: statusPulse 1.4s ease-in-out infinite; }

  /* Готово — сплошная галочка. */
  .status-done { background: var(--good-soft); color: var(--good-text); border-color: var(--good); }
  .status-done .status-mark { background: currentColor; color: var(--surface); }
  .status-done .status-mark::after { content: '✓'; }

  /* Ошибка — крестик, и всегда рядом кнопка действия (см. .job-error). */
  .status-error { background: var(--bad-soft); color: var(--bad-text); border-color: var(--bad-border); }
  .status-error .status-mark { background: currentColor; color: var(--surface); }
  .status-error .status-mark::after { content: '✕'; }

  @keyframes statusPulse { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }

  /* --- Скелетоны (совет №84) --------------------------------------------
     Пустой серый прямоугольник на месте будущей картинки ощущается быстрее
     крутящегося спиннера при том же времени ожидания: видно форму того, что
     сейчас появится. Миниатюры (совет №46) грузятся быстро, но на мобильном
     интернете и при трёхстах карточках эта секунда заметна.

     Заготовка включается классом is-loading, который вешает скрипт ниже, и
     снимается, когда картинка догрузилась. Важно, что по умолчанию её нет:
     если скрипт не отработает, галерея просто покажет картинки как раньше, а
     не останется пустой. */
  .is-loading {
    background-image: linear-gradient(100deg, var(--surface-2) 30%, var(--border) 45%,
                                      var(--surface-2) 60%);
    background-size: 250% 100%;
    animation: skeletonSlide 1.4s ease-in-out infinite;
  }
  @keyframes skeletonSlide {
    0% { background-position: 150% 0; }
    100% { background-position: -50% 0; }
  }
  .is-loading > img { opacity: 0; }
  .tile-image img, .photo-slot img { transition: opacity .25s ease; }

  /* Движение — только там, где оно что-то объясняет, и только если человек
     не просил систему его выключить (совет №85). */
  @media (prefers-reduced-motion: reduce) {
    .is-loading, .status-running .status-mark, .queue-chip .status-mark { animation: none; }
    .tile-image img, .photo-slot img, .toast { transition: none; }
  }

  .conn-lost-toast {
    position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%);
    background: var(--bad-soft); color: var(--bad-text); border: 1px solid var(--bad-border);
    border-radius: 999px; padding: 10px 18px; font-size: 12.5px; font-weight: 600;
    box-shadow: var(--shadow-md); z-index: 2000; max-width: calc(100vw - 32px);
  }

  /* Шаги формы разведены в отдельные блоки: фото, что за товар, настройки.
     Одна длинная простыня не показывала, что сначала, а что потом. */
  .gen-step {
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    padding: var(--space-4); margin-top: var(--space-3); background: var(--surface);
  }
  .gen-step:first-of-type { margin-top: var(--space-2); }
  @media (max-width: 700px) {
    /* Ещё один уровень отступа на телефоне — это минус 32 пикселя ширины у
       каждой строки внутри шага. */
    .gen-step { padding: var(--space-3) 10px; }
  }
  .gen-step-title {
    font-size: 13px; font-weight: 700; margin-bottom: var(--space-2);
  }

  /* Номер шага. Кружок перед заголовком, а не плашка в углу: в углу его
     не видно на телефоне, где блок и так узкий. */
  /* Без flex и gap: заголовок шага бывает и <label>, у которого своё
     display, и тогда gap не срабатывал — кружок прилипал к тексту. Отступ
     живёт на самом кружке и работает в обоих случаях одинаково. */
  .gen-step-head {
    font-size: 13px; font-weight: 700; color: var(--text);
    margin: 0 0 var(--space-2);
  }
  /* inline-flex, а не inline: у строчного span ширина игнорируется, и кружок
     схлопывался в полоску рядом с «Фото товара». */
  .step-num {
    display: inline-flex; align-items: center; justify-content: center;
    flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%;
    background: var(--accent); color: #fff;
    font-size: 12px; font-weight: 800; line-height: 1; text-align: center;
    margin-right: 8px; vertical-align: middle;
  }
  .step-num-lg { width: 26px; height: 26px; font-size: 14px; }
  /* Пока шаг заперт, номер не должен зазывать. Но и белым по серому его
     писать нельзя: в тёмной теме это 2.63:1, то есть нечитаемо. Поэтому не
     заливка, а обводка — приглушённо и при этом разборчиво. */
  .gen-step.is-locked .step-num {
    background: var(--surface-2); color: var(--text-muted);
    box-shadow: inset 0 0 0 1px var(--border);
  }
  /* Пока фото нет и товар не распознан, настраивать нечего: любая настройка
     здесь — выбор вслепую, а нажатая кнопка — списанная звезда впустую.
     Блок гасим и делаем неактивным целиком (inert выключает и клавиатуру). */
  .gen-step.is-locked { opacity: .45; }
  .gen-step.is-locked .gen-step-lock { display: block; }
  .gen-step-lock {
    display: none; font-size: 12px; color: var(--text-muted);
    margin: 0 0 var(--space-2);
  }

  .gallery-head { display: flex; align-items: baseline; justify-content: space-between;
    margin-bottom: 14px; flex-wrap: wrap; gap: 6px 10px; }
  .gallery-head h2 {
    margin: 0; font-size: 22px; font-weight: 800; text-transform: none;
    letter-spacing: -.02em; color: var(--text); line-height: 1.2;
  }
  .gallery-count { font-size: 12px; color: var(--text-muted); }

  /* Совет №88: пустое состояние стоит внутри панели, у которой уже есть
     рамка. Вторая рамка вокруг него ничего не отделяет — только дробит
     экран. Оставляем подложку и воздух. */
  .empty {
    color: var(--text-muted); font-size: 13px; padding: 32px 20px; text-align: center;
    background: var(--surface); border-radius: var(--radius-sm);
  }
  /* Совет №6: пустое состояние — не сообщение о том, что ничего нет, а
     объяснение, зачем этот раздел нужен, и одно понятное действие. */
  .empty-rich { display: flex; flex-direction: column; align-items: center; gap: var(--space-2); }
  .empty-title { font-size: var(--text-lg); font-weight: 800; color: var(--text); }
  .empty-rich p { margin: 0; max-width: 46ch; line-height: 1.5; }
  .empty-hint { color: var(--text-muted); }
  .empty-btn {
    margin-top: var(--space-2); background: var(--accent); color: var(--accent-text);
    border-radius: var(--radius-pill); padding: 10px 20px; font-size: var(--text-md);
    font-weight: 800; text-decoration: none; min-height: 44px; display: inline-flex;
    align-items: center;
  }

  .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(228px, 1fr)); gap: 16px; }

  .tile {
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    overflow: hidden; display: flex; flex-direction: column;
    transition: border-color .16s, box-shadow .16s;
  }
  /* Наведение поднимает плитку из ряда: это единственный элемент галереи, с
     которым что-то делают, и он должен отзываться. Сдвиг по вертикали не
     берём — в сетке из тридцати карточек он читается как дрожание. */
  .tile:hover { border-color: var(--accent-line); box-shadow: var(--shadow-md); }
  .tile-image {
    aspect-ratio: 3 / 4; background: var(--surface-2); position: relative; overflow: hidden;
  }
  .tile-image.checker {
    background-color: var(--surface);
    background-image:
      linear-gradient(45deg, var(--surface-2) 25%, transparent 25%),
      linear-gradient(-45deg, var(--surface-2) 25%, transparent 25%),
      linear-gradient(45deg, transparent 75%, var(--surface-2) 75%),
      linear-gradient(-45deg, transparent 75%, var(--surface-2) 75%);
    background-size: 20px 20px;
    background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
  }
  .tile-image img { width: 100%; height: 100%; object-fit: cover; display: block; }
  .tile-badge {
    position: absolute; top: 8px; left: 8px; background: var(--accent); color: var(--accent-text);
    font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em;
    border-radius: 999px; padding: 3px 9px; z-index: 2;
  }
  .tile-image img.tile-source {
    position: absolute; bottom: 8px; right: 8px; width: 42px; height: 42px;
    border-radius: 10px; border: 2px solid var(--surface); object-fit: cover; z-index: 2;
  }
  .tile-body { padding: 12px 14px 14px; flex: 1; display: flex; flex-direction: column; gap: 8px; }
  .tile-prompt { font-size: 12.5px; color: var(--text); line-height: 1.4; }
  .tile-meta { font-size: 11px; color: var(--text-muted); }

  /* Задание и чек-лист в плитке — свёрнутыми. Оба нужны редко: задание
     человек только что написал сам, чек-лист смотрят, когда карточка не
     понравилась. Развёрнутыми они делали плитку вдвое выше, и до кнопок
     «Скачать» и «Изменить» приходилось прокручивать. */
  .tile-fold > summary {
    list-style: none; cursor: pointer; display: flex; align-items: center; gap: 6px;
    font-size: 11px; font-weight: 700; color: var(--text-muted);
    text-transform: uppercase; letter-spacing: .03em;
    /* По этой строке жмут пальцем — значит не ниже 40 px, как и все
       остальные элементы управления (tests/mobile_check.py). */
    min-height: 40px;
  }
  .tile-fold > summary::-webkit-details-marker { display: none; }
  .tile-fold > summary::after {
    content: "⌄"; margin-left: auto; font-size: 13px; line-height: 1;
    transition: transform .15s;
  }
  .tile-fold[open] > summary::after { transform: rotate(180deg); }
  .tile-fold > summary:hover { color: var(--text); }
  .tile-fold > *:not(summary) { margin-top: 6px; }

  .checklist { margin-top: auto; padding-top: 8px; border-top: 1px solid var(--border); }
  .cl-head { font-size: 11px; font-weight: 700; color: var(--text-muted); margin-bottom: 5px; text-transform: uppercase; letter-spacing: .03em; }
  .cl-row { display: flex; flex-wrap: wrap; gap: 5px; }
  .cl-chip {
    display: inline-flex; align-items: center; gap: 4px; font-size: 10.5px;
    padding: 3px 7px; border-radius: 999px; background: var(--surface); color: var(--text-muted);
  }
  .cl-chip.ok { background: var(--good-soft); color: var(--good-text); }

  .tile-download {
    align-self: flex-start; font-size: 11.5px; font-weight: 700; text-decoration: none;
    color: var(--link); margin-top: auto; padding-top: 8px;
  }
  .tile-download:hover { text-decoration: underline; }
  /* Совет №24: строка «Скачать: WB / Ozon / оригинал» под карточкой. */
  .tile-download-row {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px;
    margin-top: auto; padding-top: 8px;
  }
  .tile-download-label { font-size: 11.5px; color: var(--text-muted); }
  .tile-download-row .tile-download { margin: 0; padding-top: 0; }

  /* Главные действия под готовой картинкой — кнопками одного размера, а не
     разнокалиберными строчками: пальцем в строку попадаешь через раз.
     Зелёная — та, что стоит денег (правка), остальные спокойные. */
  /* Сетка, а не ряд с переносом: у колонок одинаковая ширина, у строк —
     одинаковая высота, и это не зависит от длины надписей. Колонок ровно
     столько, сколько влезает целиком: на телефоне карточка широкая и кнопки
     встают по две, в узкой колонке галереи — по одной. Раньше кнопки жались
     до 105 пикселей, и «Изменить · 2 ⭐» обрезалось на полуслове. */
  /* «Скачать» с выбором размера. Отдельная строка «Ещё размеры: WB / Ozon /
     оригинал» под кнопками дублировала само «Скачать» и растягивала плитку
     на три лишние строки. Раскрывается своим <details> — без скрипта. */
  .dl-menu { position: relative; min-width: 0; }
  .dl-menu > summary { list-style: none; width: 100%; }
  .dl-menu > summary::-webkit-details-marker { display: none; }
  .dl-menu-box {
    position: absolute; z-index: 20; top: calc(100% + 4px); left: 0; min-width: 160px;
    display: flex; flex-direction: column; padding: 5px;
    border-radius: var(--radius-sm); border: 1px solid var(--border);
    background: var(--surface); box-shadow: var(--shadow-lg);
  }
  .dl-menu-box a {
    padding: 8px 10px; border-radius: 8px; font-size: var(--text-sm); font-weight: 600;
    color: var(--text); text-decoration: none; white-space: nowrap;
  }
  .dl-menu-box a:hover { background: var(--surface-2); color: var(--accent-strong); }

  .tile-actions {
    /* 124 px не помещались в две колонки на плитке ~250 px — четыре кнопки
       вставали столбиком, и ряд переставал читаться сеткой. */
    display: grid; grid-template-columns: repeat(auto-fit, minmax(106px, 1fr));
    gap: 5px; margin-top: auto; padding-top: 10px; align-content: flex-start;
  }
  /* Все кнопки ряда одного роста: разный рост уже был ошибкой и стережётся
     проверкой tests/card_actions2_check.py. Платное действие отличается
     заливкой, а не размером. */
  .tile-action-btn {
    height: 38px; padding: 0 12px; border-radius: var(--radius-sm);
    border: 1px solid var(--border); background: var(--surface); color: var(--text);
    font-family: inherit; font-size: var(--text-sm); font-weight: 700; cursor: pointer;
    /* Высоту строки задаём явно: у эмодзи (⭐ в цене правки) шрифт свой и выше
       текстового, и кнопка с ценой вырастала на несколько пикселей выше
       соседних — ряд переставал читаться сеткой. */
    line-height: 1.2;
    text-align: center; text-decoration: none;
    display: inline-flex; align-items: center; justify-content: center;
    /* Длинная подпись не должна ломать сетку: кнопка не переносится и не
       распирает строку, а в крайнем случае обрезается многоточием. Без
       min-width: 0 кнопка не сжимается уже своего текста и уезжает на
       отдельную строку — ряд сразу перестаёт читаться сеткой. */
    min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .tile-action-btn:hover { border-color: var(--accent-strong); background: var(--accent-soft); }
  /* Зелёная кнопка ряда — та же геометрия, что и у соседей. Иначе общее
     правило button.primary (крупнее шрифт, шире отступы) перебивает класс
     ряда по весу селектора, и кнопка с ценой вырастает выше остальных. */
  .tile-action-btn.primary {
    background: var(--accent); color: var(--accent-text);
    /* Общее правило button.primary (отдельная крупная кнопка формы) по весу
       селектора перебивает класс ряда: свой шрифт, свои отступы, отступ
       сверху в 16 пикселей и убранная рамка. В ряду от этого зелёная кнопка
       съезжала вниз и вылезала за край карточки. Возвращаем геометрию ряда. */
    margin: 0; width: auto; min-width: 0;
    border: 1px solid var(--accent);
    border-radius: var(--radius-sm);
    padding: 0 12px; font-size: var(--text-sm); line-height: 1.2;
    height: 38px; min-height: 0; align-self: center;
  }
  .tile-action-btn.primary:hover { background: var(--accent-strong); border-color: var(--accent-strong); }
  @media (max-width: 700px) {
    /* Пальцем — не меньше 40 пикселей (tests/mobile_check.py). */
    .tile-action-btn, .tile-action-btn.primary { height: 44px; }
  }

  /* Мелкое действие текстом — «закрепить», «убрать», «отозвать»: не кнопка по
     виду, потому что рядом уже стоят настоящие кнопки, и ещё одна рамка
     сделала бы ряд шумным. Живёт здесь, а не в generation.css: этим классом
     пользуются и другие страницы, и каталог элементов. */
  .tile-pin-form { align-self: flex-start; margin-top: 6px; }
  .tile-pin-btn {
    font-size: 11.5px; font-weight: 700; background: none; border: none; padding: 0;
    cursor: pointer; color: var(--text-muted); font-family: inherit;
  }
  .tile-pin-btn:hover { color: var(--text); text-decoration: underline; }
  .tile-pin-btn.active { color: var(--accent-strong); }

  .tile-ref { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--text-muted); }
  .tile-ref-thumb { width: 28px; height: 28px; border-radius: 8px; object-fit: cover; border: 1px solid var(--border); flex-shrink: 0; }

  .tile-image img.zoomable { cursor: zoom-in; }

  /* Совет №21: «было → стало» одним ползунком. Самый убедительный экран
     сервиса — им же делятся, поэтому открываем его крупно, поверх страницы. */
  .compare-overlay {
    position: fixed; inset: 0; background: rgba(0,0,0,.85); z-index: 2500;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 14px; padding: 20px;
  }
  .compare-box {
    position: relative; max-width: min(820px, 100%); max-height: calc(100vh - 140px);
    border-radius: var(--radius); overflow: hidden; touch-action: none; user-select: none;
    background: #000; line-height: 0; cursor: ew-resize;
  }
  .compare-box img {
    display: block; max-width: 100%; max-height: calc(100vh - 140px); width: auto; height: auto;
  }
  .compare-reveal {
    position: absolute; inset: 0; overflow: hidden; width: 50%;
  }
  .compare-reveal img {
    position: absolute; top: 0; left: 0; max-width: none; max-height: none;
    width: var(--cmp-w, auto); height: var(--cmp-h, auto);
  }
  .compare-handle {
    position: absolute; top: 0; bottom: 0; left: 50%; width: 2px; background: #fff;
    box-shadow: 0 0 0 1px rgba(0,0,0,.25); pointer-events: none;
  }
  .compare-handle::after {
    content: '↔'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
    width: 34px; height: 34px; border-radius: 50%; background: #fff; color: #0A0A0A;
    font-size: 15px; font-weight: 700; line-height: 34px; text-align: center;
  }
  .compare-label {
    position: absolute; top: 10px; padding: 4px 10px; border-radius: 999px;
    background: rgba(0,0,0,.6); color: #fff; font-size: 11px; font-weight: 700;
    letter-spacing: .02em; line-height: 1.6; pointer-events: none;
  }
  .compare-label.before { left: 10px; }
  .compare-label.after { right: 10px; }
  .compare-range { width: min(480px, 90vw); accent-color: var(--accent); }
  .compare-hint { color: rgba(255,255,255,.7); font-size: 12px; text-align: center; }
  .compare-close {
    position: absolute; top: 16px; right: 16px; width: 36px; height: 36px; border-radius: 50%;
    border: none; background: rgba(255,255,255,.15); color: #fff; font-size: 16px; cursor: pointer;
  }
  .tile-compare {
    align-self: flex-start; font-size: 11.5px; font-weight: 700; margin-top: 6px;
    background: none; border: none; padding: 0; cursor: pointer; color: var(--link);
  }
  .tile-compare:hover { text-decoration: underline; }
  /* Совет №22: макет выдачи маркетплейса. */
  .shelf-overlay {
    position: fixed; inset: 0; background: rgba(0,0,0,.8); z-index: 2500;
    display: flex; align-items: center; justify-content: center; padding: var(--space-4);
    overflow-y: auto;
  }
  .shelf-box {
    background: var(--bg); border-radius: var(--radius); padding: var(--space-5);
    max-width: 880px; width: 100%; max-height: calc(100vh - 32px); overflow-y: auto;
  }
  .shelf-head { font-size: var(--text-lg); font-weight: 800; margin-bottom: var(--space-4); }
  .shelf-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-3); }
  .shelf-tile { font-size: var(--text-xs); line-height: 1.4; }
  .shelf-pic {
    aspect-ratio: 3 / 4; border-radius: var(--radius-xs); overflow: hidden;
    background: var(--surface-2); margin-bottom: 6px;
  }
  .shelf-pic img { width: 100%; height: 100%; object-fit: cover; display: block; }
  /* Своя карточка выделена только тонкой рамкой: если подсветить её ярко,
     проверка «заметна ли она» потеряет смысл. */
  .shelf-tile.is-mine .shelf-pic { outline: 2px solid var(--accent-strong); outline-offset: -2px; }
  .shelf-price { font-size: var(--text-md); font-weight: 800; }
  .shelf-title { color: var(--text-muted); }
  .shelf-tile.is-mine .shelf-title { color: var(--text); font-weight: 700; }
  .shelf-rating { color: var(--text-muted); }
  .shelf-note { margin-top: var(--space-4); font-size: var(--text-xs); color: var(--text-muted); }
  @media (max-width: 700px) {
    .shelf-grid { grid-template-columns: repeat(2, 1fr); }
    .shelf-box { padding: var(--space-4); }
  }

  /* «Поделиться» встречается и кнопкой в ряду действий, и отдельной ссылкой
     на старых экранах. Ряд действий одевает её сам (.tile-action-btn), а без
     него она остаётся ссылкой — поэтому правило не трогает оформление там,
     где кнопка уже одета. */
  .tile-share:not(.tile-action-btn) {
    align-self: flex-start; font-size: 11.5px; font-weight: 700; margin-top: 6px;
    background: none; border: none; padding: 0; cursor: pointer; color: var(--link);
  }
  .tile-share:not(.tile-action-btn):hover { text-decoration: underline; }
  .tile-share:disabled { opacity: .6; cursor: default; }

  /* Совет №94: сезонная подсказка. */
  .season-note {
    grid-column: 1 / -1; display: flex; align-items: flex-start; gap: 10px;
    padding: 12px 14px; border-radius: var(--radius-sm); border: 1px solid var(--accent-strong);
    background: var(--accent-soft); font-size: 13px; line-height: 1.45;
  }
  .season-emoji { font-size: 16px; line-height: 1.3; display: inline-flex; color: var(--accent-strong); }
  .season-text { flex: 1; }
  .season-idea { display: block; color: var(--text-muted); margin-top: 3px; }
  .season-use {
    flex: 0 0 auto; cursor: pointer; font: inherit; font-weight: 700; font-size: 12.5px;
    padding: 8px 14px; border-radius: 999px; border: 1px solid var(--text);
    background: var(--surface); color: var(--text);
  }
  .season-use:hover { background: var(--text); color: var(--surface); }
  .season-hide {
    flex: 0 0 auto; background: none; border: none; cursor: pointer; padding: 4px;
    color: var(--text-muted); font-size: 14px;
  }
  @media (max-width: 560px) {
    .season-note { flex-wrap: wrap; }
    .season-use { width: 100%; }
  }

  /* Совет №54: спокойная плашка о замедлении — не ошибка и не тревога. */
  .slow-note {
    grid-column: 1 / -1; display: flex; align-items: center; gap: 8px;
    padding: 10px 14px; border-radius: var(--radius-sm); border: 1px solid var(--border);
    background: var(--surface-2); color: var(--text); font-size: 13px; line-height: 1.45;
  }
  .slow-dot {
    width: 8px; height: 8px; border-radius: 50%; background: var(--accent-strong);
    flex: 0 0 auto; animation: statusPulse 1.4s ease-in-out infinite;
  }

  .lightbox-overlay {
    position: fixed; inset: 0; background: rgba(0,0,0,.86); z-index: 1000;
    display: flex; align-items: center; justify-content: center;
    overflow: hidden; touch-action: none; cursor: zoom-out;
  }
  .lightbox-overlay img {
    max-width: 92vw; max-height: 92vh; user-select: none;
    transition: transform .05s linear; will-change: transform;
    box-shadow: 0 20px 60px rgba(0,0,0,.5); border-radius: 8px;
  }
  .lightbox-close {
    position: absolute; top: 18px; right: 22px; width: 40px; height: 40px;
    border-radius: 999px; background: rgba(255,255,255,.12); color: #fff;
    display: flex; align-items: center; justify-content: center;
    font-size: 20px; cursor: pointer; z-index: 1001; border: none;
  }
  /* Кнопки «посмотреть» в крупном просмотре. Под плиткой они занимали три
     строки, а нужны ровно в тот момент, когда карточку рассматривают. Цвета
     фиксированные: подложка просмотра всегда тёмная, от темы не зависит. */
  .lightbox-tools {
    position: absolute; bottom: 58px; left: 50%; transform: translateX(-50%);
    display: flex; gap: 8px; flex-wrap: wrap; justify-content: center;
    z-index: 1001; max-width: min(92vw, 520px);
  }
  .lightbox-tool {
    margin: 0; padding: 9px 16px; min-height: 40px; width: auto;
    border-radius: 999px; border: 1px solid rgba(255,255,255,.35);
    background: rgba(0,0,0,.55); color: #fff;
    font-family: inherit; font-size: 13px; font-weight: 700; cursor: pointer;
  }
  .lightbox-tool:hover { background: rgba(255,255,255,.2); border-color: #fff; }
  .lightbox-hint {
    position: absolute; bottom: 18px; left: 50%; transform: translateX(-50%);
    color: rgba(255,255,255,.72); font-size: 12px; z-index: 1001; text-align: center;
    max-width: min(92vw, 420px); background: rgba(0,0,0,.45); border-radius: 999px;
    padding: 6px 14px; line-height: 1.35;
  }
  /* Совет №63: листать результаты пальцем, как в галерее телефона. Стрелки
     нужны мыши, свайп — пальцу, счётчик — обоим: без него непонятно, сколько
     всего вариантов и где ты находишься. */
  .lightbox-nav {
    position: absolute; top: 50%; transform: translateY(-50%); width: 46px; height: 46px;
    border-radius: 999px; background: rgba(255,255,255,.12); color: #fff; border: none;
    display: flex; align-items: center; justify-content: center; cursor: pointer;
    font-size: 22px; z-index: 1001;
  }
  .lightbox-nav:hover { background: rgba(255,255,255,.24); }
  .lightbox-nav.prev { left: 14px; }
  .lightbox-nav.next { right: 14px; }
  .lightbox-count {
    position: absolute; top: 18px; left: 50%; transform: translateX(-50%);
    color: rgba(255,255,255,.85); font-size: 12px; font-weight: 800; z-index: 1001;
    font-variant-numeric: tabular-nums; background: rgba(0,0,0,.45);
    border-radius: 999px; padding: 5px 12px;
  }
  @media (hover: none) {
    /* На телефоне стрелки только закрывают картинку собой — там листают пальцем. */
    .lightbox-nav { display: none; }
  }

  /* ПОЛОСА ФОНОВОЙ ЗАДАЧИ.

     Плитка ожидания (.magic-tile ниже) — заготовка будущей карточки, и место
     ей только там, где эта карточка появится. Работа, которая ничего не
     создаёт в галерее — публикация фото, отчёт, разбор конкурентов, — живёт
     этой полосой: она стоит в заголовке своего раздела, ничего собой не
     закрывает и не притворяется результатом. */
  .task-strip {
    position: relative; overflow: hidden;
    display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
    padding: 12px 16px 14px; margin: 0 0 14px;
    border-radius: var(--radius-sm); border: 1px solid var(--accent-line);
    background: var(--accent-soft);
  }
  .task-strip-spin {
    width: 16px; height: 16px; flex-shrink: 0; border-radius: 50%;
    border: 2px solid var(--accent-line); border-top-color: var(--accent-strong);
    animation: task-spin 900ms linear infinite;
  }
  @keyframes task-spin { to { transform: rotate(360deg); } }
  .task-strip-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 220px; }
  .task-strip-title { font-size: var(--text-base); font-weight: 700; }
  .task-strip-stage {
    font-size: var(--text-md); color: var(--text-muted); transition: opacity .18s;
  }
  .task-strip-stage.is-fading { opacity: 0; }
  .task-strip-eta {
    font-size: var(--text-md); font-weight: 700; color: var(--text);
    font-variant-numeric: tabular-nums; white-space: nowrap;
  }
  .task-strip-eta:empty { display: none; }
  /* Главное, что должна сказать полоса: ждать её не нужно. */
  .task-strip-note { font-size: var(--text-xs); color: var(--text-muted); white-space: nowrap; }
  .task-strip-line {
    position: absolute; left: 0; right: 0; bottom: 0; height: 3px;
    background: var(--accent-line);
  }
  .task-strip-line i {
    display: block; height: 100%; width: 40%; border-radius: var(--radius-pill);
    background: var(--accent-strong);
    animation: task-slide 1.6s ease-in-out infinite;
  }
  /* Когда вариантов несколько, доля готового известна — полоса перестаёт
     бегать и показывает настоящий прогресс. */
  .task-strip-line.is-real i { animation: none; width: 0; transition: width .4s; }
  @keyframes task-slide {
    0% { transform: translateX(-100%); }
    100% { transform: translateX(250%); }
  }
  @media (prefers-reduced-motion: reduce) {
    .task-strip-spin { animation-duration: 2.4s; }
    .task-strip-line i { animation: none; width: 100%; opacity: .5; }
  }

  /* Тот же индикатор, но когда раздел с полосой уполз за экран: маленькая
     плашка в углу, чтобы человек не гадал, идёт ли ещё работа. */
  .task-dock {
    position: fixed; right: 16px; bottom: 16px; z-index: 60;
    display: flex; align-items: center; gap: 10px;
    padding: 10px 14px; border-radius: var(--radius-pill);
    border: 1px solid var(--accent-line); background: var(--surface-2);
    box-shadow: var(--shadow-md); font-size: var(--text-md); color: var(--text);
    cursor: pointer; max-width: min(320px, calc(100vw - 32px));
  }
  .task-dock:hover { border-color: var(--accent-strong); }
  .task-dock b { font-weight: 700; }
  .task-dock span.task-dock-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }


  /* ПОМЕТКИ И КНОПКИ ПОВЕРХ ПЛИТКИ РЕЗУЛЬТАТА.

     Жили в generation.css, который грузит только страница «Генерация». Но
     плитка — общий компонент: та же разметка стоит на странице товара в
     кабинете продавца, и там звёздочка, крестик и метка типа рисовались
     системными серыми кнопками. Место общего — в app.css. */
  /* Совет №34: наведение на миниатюру исходника показывает «как было». */
  .tile-image img.tile-source { cursor: zoom-in; transition: transform .12s; }
  .tile-image img.tile-source:hover { transform: scale(1.08); }
  .tile-image.showing-before img.tile-source { outline: 2px solid var(--accent-strong); }

  /* Справа сверху теперь стоит крестик удаления — метка типа отодвигается
     левее ровно на его ширину, чтобы они не наезжали друг на друга. */
  .tile-output-badge {
    position: absolute; top: 8px; right: 50px; background: var(--surface); color: var(--text);
    font-size: 9.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em;
    border-radius: 999px; padding: 3px 8px; z-index: 2; border: 1px solid var(--border);
  }
  .tile-image video { width: 100%; height: 100%; object-fit: cover; display: block; }

  /* Закрепление карточки (п.72 чек-листа) — закреплённые сортируются в
     начало списка на сервере (ORDER BY pinned DESC), плюс визуально
     выделяем рамкой, чтобы было видно с первого взгляда. */
  .tile.pinned { border-color: var(--accent-strong); box-shadow: inset 0 0 0 1px var(--accent-strong); }
  /* Избранное — единственное, что осталось поверх картинки: это пометка на
     самой карточке, а не действие над ней. Видно всегда, а не по наведению:
     на телефоне наведения нет, а «появляется, если долго держать» — не
     поведение, о котором догадываются. */
  /* Левый нижний угол: сверху уже стоят метки («2 варианта», тип карточки),
     справа внизу — миниатюра исходника. */
  .tile-fav-form { position: absolute; left: 8px; bottom: 8px; z-index: 3; margin: 0; }
  .tile-fav {
    width: 34px; height: 34px; min-width: 34px; min-height: 34px; aspect-ratio: 1;
    box-sizing: border-box; border-radius: 50%; padding: 0; line-height: 1;
    display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
    border: 1px solid rgba(255, 255, 255, .35); background: rgba(10, 10, 10, .55);
    color: #fff; font-size: 16px;
  }
  .tile-fav:hover { border-color: #fff; }
  /* Кнопка лежит на тёмной полупрозрачной подложке поверх картинки, а не на
     фоне страницы, поэтому цвет здесь свой и от темы не зависит: --accent на
     этой подложке давал 4.38 при норме 4.5. */
  .tile-fav.on { color: #B9A6FF; border-color: #B9A6FF; }

  .quality-note {
    font-size: 11.5px; line-height: 1.4; color: var(--text); background: var(--surface-2);
    border: 1px solid var(--border); border-left: 3px solid var(--accent-strong);
    border-radius: 8px; padding: 6px 9px;
  }

  /* Удаление — крестиком на самой картинке, в углу напротив звёздочки.
     В ряду рабочих кнопок ему не место: туда попадают пальцем случайно. */
  .tile-drop-form { position: absolute; right: 8px; top: 8px; z-index: 3; margin: 0; }
  .tile-del {
    width: 34px; height: 34px; min-width: 34px; min-height: 34px; aspect-ratio: 1;
    box-sizing: border-box; border-radius: 50%; padding: 0; line-height: 1;
    display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
    border: 1px solid rgba(255, 255, 255, .35); background: rgba(10, 10, 10, .55);
    color: #fff; font-size: 14px;
  }
  .tile-del:hover { color: var(--bad); border-color: var(--bad); }
  @media (max-width: 700px) {
    /* На телефоне всё, по чему жмут пальцем, — не меньше 40 пикселей
       (tests/mobile_check.py). */
    .tile-del, .tile-fav {
      width: 40px; height: 40px; min-width: 40px; min-height: 40px;
    }
    .tile-output-badge { right: 56px; }
  }

  /* "Магия" — анимированная заглушка-плейсхолдер на месте результата, пока job в фоне (п.3/9). */
  .magic-tile {
    aspect-ratio: 3 / 4; border-radius: var(--radius); border: 1px solid var(--border);
    position: relative; overflow: hidden; display: flex; flex-direction: column;
    align-items: center; justify-content: center; gap: 8px; text-align: center; padding: 20px;
    background: linear-gradient(115deg, var(--surface-2) 20%, var(--surface) 40%, var(--accent-soft) 50%, var(--surface) 60%, var(--surface-2) 80%);
    background-size: 250% 100%; animation: magic-shimmer 2.2s ease-in-out infinite;
  }
  @keyframes magic-shimmer { 0% { background-position: 0% 0; } 100% { background-position: -250% 0; } }
  /* Под анимацией — размытый исходник: видно, какой именно товар сейчас
     делается, особенно когда в очереди несколько. Картинка отдельным слоем,
     чтобы размытие не задело текст поверх неё. */
  .magic-tile .magic-source {
    position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
    filter: blur(14px) saturate(1.1); transform: scale(1.12); opacity: .45;
  }
  .magic-tile .magic-emoji,
  .magic-tile .magic-text,
  .magic-tile .magic-progress { position: relative; z-index: 1; }
  .magic-tile.has-source .magic-text,
  .magic-tile.has-source .magic-progress {
    text-shadow: 0 1px 6px var(--surface), 0 0 14px var(--surface);
  }
  .magic-tile .magic-emoji {
    font-size: 26px; animation: magic-pulse 1.4s ease-in-out infinite;
    display: inline-flex; color: var(--accent-strong);
  }
  .magic-tile .magic-emoji .spark { width: 26px; height: 26px; vertical-align: 0; }
  @keyframes magic-pulse { 0%, 100% { transform: scale(1); opacity: .85; } 50% { transform: scale(1.15); opacity: 1; } }
  .magic-tile .magic-text { font-size: 12.5px; font-weight: 600; color: var(--text); }
  .magic-tile .magic-progress { font-size: 11.5px; color: var(--text-muted); }
  /* Совет №15: вместо одного крутящегося спиннера — понятный рассказ о том,
     что сейчас происходит, и честная оценка остатка (совет №42). */
  .magic-tile .magic-stage {
    font-size: 12px; color: var(--text); font-weight: 600; min-height: 1.3em;
    transition: opacity .25s;
  }
  .magic-tile .magic-stage.is-fading { opacity: 0; }
  .magic-tile .magic-bar {
    width: 70%; max-width: 180px; height: 4px; border-radius: 999px;
    background: var(--surface-2); overflow: hidden; margin-top: 2px;
  }
  .magic-tile .magic-bar i {
    display: block; height: 100%; width: 0; border-radius: 999px;
    background: var(--accent-strong); transition: width .4s ease;
  }
  .magic-tile .magic-eta { font-size: 11px; color: var(--text-muted); }

  /* Задача, которая ещё ждёт своей очереди. Отличается от считающейся
     намеренно: без мерцания и без пульсации — иначе три одинаково живых
     плитки читаются как три идущие генерации, и человек ждёт результата
     сразу от всех. Здесь честно: эта стоит и ждёт. */
  .magic-tile.is-queued {
    animation: none;
    background: var(--surface-2);
    border-style: dashed;
  }
  .magic-tile.is-queued .magic-source { opacity: .22; filter: blur(16px) grayscale(.35); }
  .magic-tile.is-queued .magic-text { color: var(--text-muted); }
  .magic-tile.is-queued .queue-num {
    position: relative; z-index: 1;
    width: 30px; height: 30px; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--surface); border: 1px solid var(--border);
    font-size: 13px; font-weight: 700; color: var(--text-muted);
    font-variant-numeric: tabular-nums;
  }
  .magic-tile.is-queued .queue-note {
    position: relative; z-index: 1; max-width: 90%; line-height: 1.35;
  }
  /* Точки этапов: сколько шагов пройдено из скольких. */
  .magic-tile .magic-dots { display: flex; gap: 5px; }
  .magic-tile .magic-dots i {
    width: 6px; height: 6px; border-radius: 50%; background: var(--border);
    transition: background .25s;
  }
  .magic-tile .magic-dots i.on { background: var(--accent-strong); }

  /* --- Ожидание поверх исходника ------------------------------------------
     Минута — это долго, и всё это время человек гадает, взяли ли вообще его
     товар. Показываем именно его фото: кадр лежит размытым, по нему сверху
     вниз идёт полоса акцентного цвета, и за ней картинка становится чёткой.
     Цикл повторяется, пока идёт задача.

     Слои: .magic-source — размытый низ, .magic-reveal — чёткая копия,
     открывающаяся сверху вниз через clip-path, .magic-scan — сама полоса.
     Собирает их скрипт (MagicWait), потому что плашка стоит на шести
     страницах. Если исходника нет (видео, серия, синхронизация) — остаётся
     прежнее мерцание, здесь оно и есть запасной вариант. */
  .magic-tile.magic-photo {
    animation: none; background: var(--surface-2);
    justify-content: flex-end; align-items: stretch; gap: 6px; padding: 14px;
    text-align: left;
  }
  .magic-tile.magic-photo .magic-source {
    filter: blur(10px) saturate(.5) brightness(.62); opacity: 1;
  }
  .magic-tile.magic-photo::after {
    /* Подложка под подписями: поверх фотографии текст иначе не читается ни на
       светлом кадре, ни на тёмном. */
    content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 58%;
    background: linear-gradient(to top, rgba(0, 0, 0, .82) 12%, rgba(0, 0, 0, 0) 100%);
    pointer-events: none;
  }
  .magic-reveal {
    position: absolute; inset: 0; overflow: hidden;
    clip-path: inset(0 0 100% 0);
    animation: magic-reveal 3.2s cubic-bezier(.45, 0, .2, 1) infinite;
  }
  .magic-reveal-img {
    position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  }
  /* Пауза в конце цикла короткая: если держать проявленный кадр долго, плашка
     выглядит готовой, хотя генерация ещё идёт. */
  @keyframes magic-reveal {
    0%   { clip-path: inset(0 0 100% 0); }
    80%  { clip-path: inset(0 0 0 0); }
    100% { clip-path: inset(0 0 0 0); }
  }
  .magic-scan {
    position: absolute; left: 0; right: 0; height: 64px; top: -66px; pointer-events: none;
    /* Плавный сход в прозрачность даёт мягкий шлейф без color-mix — оно
       поддержано не везде, а без него градиент просто не нарисуется.
       Нижняя грань — тонкая чёткая линия: именно она читается как «граница,
       до которой уже готово». */
    background: linear-gradient(to bottom, transparent 0%, var(--accent) 100%); opacity: .75;
    border-bottom: 2px solid var(--accent-strong);
    animation: magic-scan 3.2s cubic-bezier(.45, 0, .2, 1) infinite;
  }
  @keyframes magic-scan {
    0%   { top: -66px; opacity: .75; }
    80%  { top: 100%; opacity: .75; }
    86%  { opacity: 0; }
    100% { top: 100%; opacity: 0; }
  }
  /* Что именно делается — чипом в углу, чтобы не спорить с подписью этапа. */
  .magic-tile.magic-photo .magic-text {
    position: absolute; left: 12px; top: 12px; z-index: 2;
    height: 24px; padding: 0 10px; border-radius: var(--radius-pill);
    background: rgba(0, 0, 0, .55); border: 1px solid var(--accent-line);
    color: #fff; font-size: 10.5px; font-weight: 700; text-shadow: none;
    display: inline-flex; align-items: center; align-self: flex-start;
  }
  .magic-tile.magic-photo .magic-stage,
  .magic-tile.magic-photo .magic-dots,
  .magic-tile.magic-photo .magic-bar,
  .magic-tile.magic-photo .magic-eta { position: relative; z-index: 2; }
  .magic-tile.magic-photo .magic-stage { color: #fff; font-size: 12.5px; text-shadow: none; }
  .magic-tile.magic-photo .magic-eta { color: rgba(255, 255, 255, .72); text-shadow: none; }
  .magic-tile.magic-photo .magic-bar { width: 100%; max-width: none; background: rgba(255, 255, 255, .22); }

  @media (prefers-reduced-motion: reduce) {
    .magic-tile, .magic-tile .magic-emoji { animation: none; }
    .magic-tile .magic-stage, .magic-tile .magic-bar i { transition: none; }
    /* Без движения кадр просто наполовину проявлен: понятно, что работа идёт. */
    .magic-reveal { animation: none; clip-path: inset(0 0 45% 0); }
    .magic-scan { animation: none; top: calc(55% - 66px); }
  }

  button.primary[disabled], button.primary.is-busy {
    opacity: .55; cursor: not-allowed; pointer-events: none;
  }

  /* Общий стиль "окошка" загрузки файла (drag&drop) — используется на нескольких
     страницах (Генерация, Бесплатные функции, Замена товара, Серия карточек),
     поэтому вынесено сюда, в общий блок, а не продублировано на каждой странице. */
  /* Пока фото нет, весь экран бессмысленный — и загрузка должна быть на нём
     главным пятном, а не строчкой с иконкой. Подсвет под звездой отмечает
     единственное место, где сейчас что-то делают. */
  .upload-zone {
    position: relative; overflow: hidden;
    border: 1.5px dashed var(--accent-line); border-radius: var(--radius);
    min-height: 230px; padding: 28px 20px; text-align: center; cursor: pointer;
    transition: border-color .18s, box-shadow .18s, transform .18s;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
    background: radial-gradient(120% 140% at 50% -10%, var(--accent-soft) 0%, var(--surface) 68%);
  }
  /* Пятно света внутри зоны — то же свечение, что и за страницей, только
     ближе и плотнее: это единственное место на экране, которому позволено
     светиться в полную силу. */
  .upload-zone::before {
    content: ""; position: absolute; left: 50%; top: -30%;
    width: 300px; height: 300px; transform: translateX(-50%);
    background: radial-gradient(circle, var(--accent-cast) 0%, transparent 65%);
    opacity: .5; pointer-events: none;
  }
  .upload-zone > * { position: relative; }
  .upload-zone:hover, .upload-zone.dragover {
    border-color: var(--accent); box-shadow: var(--glow-lg);
  }
  .upload-zone.dragover { transform: scale(1.01); }
  .upload-zone input[type=file] { display: none; }
  .upload-zone .upload-icon {
    font-size: 0; margin: 0; color: var(--accent-strong);
    filter: drop-shadow(0 0 18px var(--accent-cast));
  }
  .upload-zone .upload-icon .spark { width: 46px; height: 46px; }
  .upload-zone .upload-icon .ic { width: 44px; height: 44px; margin: 0; }
  .upload-zone .upload-title {
    font-size: 20px; font-weight: 800; letter-spacing: -.015em; line-height: 1.25;
  }
  .upload-zone .upload-hint {
    font-size: var(--text-sm); color: var(--text-muted); margin-top: 0;
    line-height: 1.45; max-width: 280px;
  }

  .upload-zone-sm { padding: 12px; }
  .upload-zone-sm .upload-icon { font-size: 18px; margin-bottom: 2px; }
  .upload-zone-sm .upload-icon .ic { width: 26px; height: 26px; }

  .upload-status { margin-top: 10px; }
  .shimmer-bar {
    width: 100%; height: 6px; border-radius: 999px; background: var(--surface-2);
    overflow: hidden; position: relative;
  }
  .shimmer-fill {
    position: absolute; top: 0; left: -40%; width: 40%; height: 100%;
    border-radius: 999px;
    background: linear-gradient(90deg, transparent, var(--accent), var(--accent-strong), var(--accent), transparent);
    animation: shimmer-slide 1.1s ease-in-out infinite;
  }
  @keyframes shimmer-slide {
    0% { left: -40%; }
    100% { left: 100%; }
  }
  .upload-status-text {
    font-size: 11.5px; color: var(--text-muted); margin-top: 6px; text-align: center;
    animation: pulse-text 1.6s ease-in-out infinite;
  }
  @keyframes pulse-text { 0%, 100% { opacity: .55; } 50% { opacity: 1; } }

  /* Превью загруженных файлов — одно на весь сервис. Раньше страницы делились
     на две породы: в «Генерации» крупные плитки 3:4 с круглым крестиком, а на
     остальных — марки 64×64 с крестиком в 16 пикселей, в который пальцем
     попадаешь через раз. Теперь везде крупные: загруженное видно, а крестик
     круглый и с честной областью нажатия. */
  .photo-thumbs {
    display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-top: 10px;
    align-items: start;
  }
  .photo-thumbs:empty { display: none; }
  .photo-thumb {
    position: relative; width: 100%; height: auto; aspect-ratio: 3 / 4;
    border-radius: 10px; overflow: hidden; border: 1px solid var(--border);
    background: var(--surface-2);
  }
  .photo-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
  /* Крестик круглый на любой ширине: правило «кнопка не ниже 44px» из
     мобильной вёрстки иначе растягивает его в овал. Видимый кружок остаётся
     небольшим, а область нажатия расширена невидимой рамкой до пальцевых 44. */
  .photo-thumb .remove-thumb {
    position: absolute; top: 4px; right: 4px;
    width: 26px; height: 26px; min-width: 26px; min-height: 26px; aspect-ratio: 1;
    box-sizing: border-box; border-radius: 50%; padding: 0; border: none; line-height: 1;
    display: flex; align-items: center; justify-content: center;
    background: rgba(0, 0, 0, .62); color: #fff; font-size: 13px; cursor: pointer;
  }
  .photo-thumb .remove-thumb::after {
    content: ""; position: absolute; top: -9px; right: -9px; bottom: -9px; left: -9px;
  }
  .photo-thumb.add-more {
    display: flex; align-items: center; justify-content: center; cursor: pointer;
    background: var(--surface-2); color: var(--text-muted); font-size: 20px; font-weight: 700;
    aspect-ratio: 3 / 4;
  }
  /* Один файл (референс, фон-картинка, образец) — показываем крупно: это и
     есть то, что человек только что выбрал, и он должен видеть, что выбрал
     именно это. */
  .photo-thumbs.thumbs-single { grid-template-columns: minmax(0, 220px); }
  .photo-thumbs.thumbs-single .photo-thumb { aspect-ratio: auto; }
  .photo-thumbs.thumbs-single .photo-thumb img {
    height: auto; max-height: 300px; object-fit: contain; background: var(--surface-2);
  }

  /* Кнопка-ссылка вторичного действия (напр. "Перенести карточку из генерации"). */
  .secondary-btn {
    width: 100%; padding: 10px 12px; border-radius: var(--radius-sm); border: 1px solid var(--border);
    background: var(--surface); color: var(--text); font-size: 13px; font-weight: 600; cursor: pointer;
    text-align: center; margin-top: 4px;
  }
  .secondary-btn:hover { border-color: var(--accent-strong); background: var(--accent-soft); }

  /* Модальное окно-каталог (выбор своей карточки из "Генерации"). */
  .picker-overlay {
    position: fixed; inset: 0; background: rgba(0,0,0,.55); z-index: 1000;
    display: flex; align-items: center; justify-content: center; padding: 24px;
  }
  .picker-box {
    background: var(--bg); border-radius: var(--radius); max-width: 640px; width: 100%;
    max-height: 82vh; overflow-y: auto; padding: 18px; position: relative;
    box-shadow: var(--shadow-lg);
  }
  .picker-box h3 { margin: 0 0 12px; font-size: 15px; }
  .picker-close { min-height: 0;
    position: absolute; top: 10px; right: 12px; width: 40px; height: 40px; border-radius: 999px;
    background: var(--surface-2); border: none; font-size: 18px; cursor: pointer; color: var(--text);
  }
  .picker-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 10px; }
  .picker-card {
    border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; cursor: pointer;
    background: var(--surface); transition: border-color .15s;
  }
  .picker-card:hover { border-color: var(--accent-strong); }
  .picker-card img { width: 100%; aspect-ratio: 3/4; object-fit: cover; display: block; }
  .picker-card .picker-card-time { font-size: 11px; color: var(--text-muted); padding: 4px 6px; }
  .picker-empty { font-size: 13px; color: var(--text-muted); padding: 12px 0; }

  /* Превью уже выбранной карточки-образца (из "Генерации") — сверху формы,
     подогнано под ширину панели настроек. */
  /* Выбранный образец показываем крупно: марка 56×56 не давала понять, ту ли
     карточку взяли, а решение по ней и принимается. Крестик — кружком поверх
     картинки, как у остальных превью на сервисе. */
  .picked-card-box {
    position: relative; border: 1px solid var(--border); border-radius: var(--radius-sm);
    padding: 0; background: var(--surface); margin-top: 6px; overflow: hidden;
    max-width: 220px;
  }
  .picked-card-box img {
    display: block; width: 100%; height: auto; max-height: 300px; object-fit: contain;
    background: var(--surface-2);
  }
  .picked-card-box .picked-card-info {
    font-size: var(--text-sm); color: var(--text-muted); padding: 8px 10px; line-height: 1.35;
  }
  .picked-card-box .picked-card-clear {
    position: absolute; top: 6px; right: 6px;
    width: 26px; height: 26px; min-width: 26px; min-height: 26px; aspect-ratio: 1;
    box-sizing: border-box; border: none; border-radius: 50%; padding: 0; line-height: 1;
    display: flex; align-items: center; justify-content: center;
    background: rgba(0, 0, 0, .62); color: #fff; font-size: 13px; cursor: pointer;
  }
  .picked-card-box .picked-card-clear::before { content: ""; position: absolute; inset: -9px; }
  /* Тот же блок, но как пояснение, а не выбор: в «Серии» он просто говорит,
     с какой карточки копируется стиль, — крупная картинка там занимала бы
     пол-экрана, не добавляя смысла. */
  .picked-card-box.picked-card-note {
    display: flex; align-items: center; gap: 10px; padding: 10px; max-width: none;
  }
  .picked-card-box.picked-card-note img {
    width: 56px; height: 56px; object-fit: cover; border-radius: 8px; flex-shrink: 0;
  }
  .picked-card-box.picked-card-note .picked-card-info { padding: 0; }

  /* Пилюли/переключатели и блок ИИ-анализа — общие для нескольких страниц
     мастеров (Генерация, Серия карточек и т.п.), выносим сюда по той же
     причине, что и .upload-zone выше: страница не должна незаметно терять
     стиль только из-за того, что забыла продублировать блок CSS. */
  .pill-group { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 4px; }
  .pill-btn {
    padding: 7px 13px; border-radius: 999px; border: 1px solid var(--border);
    background: var(--surface); color: var(--text-muted); font-size: 12px; font-weight: 700; cursor: pointer;
  }
  .pill-btn.active { background: var(--accent); color: var(--accent-text); border-color: var(--accent); }

  /* Вариант результата для типа «Фото»: список с миниатюрой-схемой.
     Раньше это были кнопки-таблетки, и по одним названиям («В окружении»,
     «Повседневная сцена») человек не понимал, чем варианты отличаются. */
  /* Категория не совпала с тем, что ИИ увидел на фото. Не ошибка и не запрет —
     предупреждение: делаем по-выбранному, но деньги спишутся. Поэтому жёлтый
     «внимание», а не красный «отказ». */
  .cat-warn {
    margin-top: 8px; padding: 9px 11px; border-radius: var(--radius-sm);
    background: var(--warn-soft); border: 1px solid var(--warn-border);
    color: var(--text); font-size: var(--text-sm); line-height: 1.45;
  }
  .cat-warn-head { font-weight: 800; margin-bottom: 3px; }

  /* Текст от прошлого товара убрали — говорим об этом и даём вернуть. */
  .text-dropped {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    margin: 6px 0 0; font-size: var(--text-sm); color: var(--text-muted);
  }
  .text-dropped-undo {
    border: 1px solid var(--border); background: var(--surface); color: var(--text);
    border-radius: var(--radius-pill); padding: 3px 10px; font-family: inherit;
    font-size: var(--text-sm); font-weight: 700; cursor: pointer;
  }
  .text-dropped-undo:hover { border-color: var(--accent); }
  .cat-warn-fix {
    margin-top: 7px; padding: 5px 11px; border-radius: var(--radius-pill);
    border: 1px solid var(--border); background: var(--surface); color: var(--text);
    font-family: inherit; font-size: var(--text-sm); font-weight: 700; cursor: pointer;
  }
  .cat-warn-fix:hover { border-color: var(--accent); }
  .cat-warn-pay {
    margin: 6px 0 0; text-align: center; font-size: var(--text-sm); font-weight: 700;
    color: var(--warn-text);
  }

  /* Блок стоит первым в панели «Фото», сразу под выбором типа: отделяем его
     от того, что идёт ниже, а не от переключателя над ним. */
  #sceneBox { margin-bottom: 14px; padding-bottom: 12px; border-bottom: 1px solid var(--border); }
  .scene-category-row { margin-bottom: 10px; }
  .scene-empty { margin: 6px 0 0; font-size: var(--text-sm); color: var(--text-muted); }
  /* Кадр выбирают глазами, а не читают: схемы стоят сеткой и крупно, а не
     миниатюрами в 38 пикселей сбоку от строки списка. Колонок столько,
     сколько влезает целиком — на телефоне две, в широкой колонке четыре. */
  .scene-list {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
    grid-auto-rows: 1fr; gap: 7px; margin-top: 6px;
  }
  .scene-item {
    display: flex; flex-direction: column; align-items: stretch; gap: 7px;
    width: 100%; text-align: left;
    padding: 8px; border: 1px solid var(--border); border-radius: var(--radius-sm);
    background: var(--surface); color: var(--text); font-family: inherit; cursor: pointer;
  }
  .scene-item:hover { border-color: var(--accent-line); }
  .scene-item.active {
    border-color: var(--accent); background: var(--accent-soft);
    box-shadow: inset 0 0 0 1px var(--accent);
  }
  .scene-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
  .scene-name { font-size: var(--text-sm); font-weight: 700; line-height: 1.25; }
  /* Пояснение к кадру в сетке не помещается и мельчит — оно уходит в подсказку
     при наведении (title), а видно остаётся название. */
  .scene-note { display: none; }
  .scene-sketch {
    width: 100%; height: auto; aspect-ratio: 3 / 4; flex-shrink: 0;
    border-radius: var(--radius-xs);
  }
  .scene-sketch .sc-bg { fill: var(--accent-soft); }
  .scene-sketch .sc-p { fill: var(--accent-strong); }
  .scene-sketch .sc-n { fill: var(--border); }
  .scene-sketch .sc-s {
    fill: none; stroke: var(--text-muted); stroke-width: 2.5; stroke-linecap: round;
  }
  .scene-sketch .sc-thick { stroke-width: 6; }
  .scene-sketch .sc-on { stroke: var(--accent-soft); stroke-width: 2; }

  .two-toggle { display: flex; gap: 6px; margin-top: 4px; }
  .two-toggle .pill-btn { flex: 1; text-align: center; }

  .analysis-box {
    margin-top: 12px; padding: 12px 14px; border-radius: var(--radius-sm);
    background: var(--accent-soft); border: 1px solid var(--border); font-size: 12.5px;
  }
  .analysis-row { margin-bottom: 6px; display: flex; align-items: center; gap: 6px; }
  .analysis-label { color: var(--text-muted); font-weight: 600; flex-shrink: 0; }
  .analysis-positioning { margin-top: 6px; line-height: 1.4; }
  .analysis-edit-input {
    flex: 1; min-width: 0; font-size: 12.5px; font-family: inherit; color: var(--text);
    background: var(--surface); border: 1px solid var(--border); border-radius: 6px;
    padding: 4px 8px;
  }
  .analysis-edit-input:focus { outline: none; border-color: var(--accent-strong); }
  @media (max-width: 700px) {
    /* На телефоне подпись и поле не помещаются в одну строку: подпись уезжает
       наверх, поле занимает всю ширину — длинное название видно целиком.
       flex:1 здесь снимаем: в колонке он растягивает поле по высоте и
       перебивает высоту, которую ставит скрипт под текст. */
    .analysis-row { flex-direction: column; align-items: stretch; gap: 3px; }
    .analysis-label { font-size: var(--text-xs); }
    .analysis-edit-input { flex: none; width: 100%; }
  }
  .analysis-error { color: var(--bad); font-size: 11.5px; margin-top: 6px; }

  .copy-warning {
    margin-top: 6px; font-size: 11px; color: var(--bad); line-height: 1.4;
    background: var(--bad-soft); border: 1px solid var(--bad-border); border-radius: var(--radius-sm);
    padding: 8px 10px;
  }

  .advanced-toggle {
    display: flex; align-items: center; justify-content: space-between; width: 100%;
    margin-top: 16px; padding: 10px 14px; border-radius: var(--radius-sm); border: 1px solid var(--border);
    background: var(--surface); color: var(--text); font-size: 13px; font-weight: 700; cursor: pointer;
  }
  .advanced-toggle:hover { border-color: var(--accent-strong); }
  .advanced-toggle-arrow { transition: transform .15s; color: var(--text-muted); }
  .advanced-toggle.open .advanced-toggle-arrow { transform: rotate(180deg); }
  .advanced-settings-box { margin-top: 4px; }

  .tile-edit-toggle {
    align-self: flex-start; font-size: 11.5px; font-weight: 700; margin-top: 6px;
    background: none; border: none; padding: 0; cursor: pointer; color: var(--link);
  }
  .tile-edit-toggle:hover { text-decoration: underline; }
  .tile-edit-box { margin-top: 8px; }
  .tile-edit-form { display: flex; flex-direction: column; gap: 6px; }
  .tile-edit-form textarea { margin: 0; font-size: 12.5px; }
  .tile-edit-submit { margin: 0; padding: 7px 12px; width: auto; font-size: 12px; align-self: flex-start; }

  /* «Показать ещё»: галерея грузится порциями, чтобы телефон не разбирал
     полсотни карточек разом. */
  .gallery-more {
    display: block; margin: 14px auto 0; padding: 12px 18px; max-width: 320px;
    text-align: center; border-radius: var(--radius-pill); border: 1px solid var(--border);
    background: var(--surface); color: var(--text); text-decoration: none;
    font-size: var(--text-md); font-weight: 700;
  }
  .gallery-more:hover { border-color: var(--accent); }


  /* ======================================================================
     ВХОД И РЕГИСТРАЦИЯ
     Раньше здесь была одна панель в 380 пикселей посреди пустого экрана:
     человек, впервые открывший сервис, видел поле «пароль» и ничего о том,
     куда он попал. Теперь экран состоит из двух частей — форма и короткий
     ответ на «что я получу». На телефоне смысловая часть уходит вниз:
     тому, кто уже пришёл вводить пароль, она не нужна первой.
     ====================================================================== */
  .auth {
    display: grid; grid-template-columns: minmax(0, 420px) minmax(0, 1fr);
    gap: 48px; align-items: center; width: 100%;
  }
  .auth-form { min-width: 0; }
  .auth-card {
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius); padding: 28px;
    box-shadow: var(--shadow-md);
  }
  .auth-card h1 { margin: 0 0 4px; font-size: 24px; letter-spacing: -.01em; }
  .auth-card .auth-lead { margin: 0 0 20px; color: var(--text-muted); font-size: 14px; line-height: 1.5; }
  .auth-card .primary { width: 100%; margin-top: 18px; }
  .auth-card form + .field-hint, .auth-card .auth-alt { margin-top: 14px; }
  .auth-sep {
    display: flex; align-items: center; gap: 12px;
    margin: 18px 0 14px; color: var(--text-muted); font-size: 12px;
  }
  .auth-sep::before, .auth-sep::after {
    content: ""; flex: 1; height: 1px; background: var(--border);
  }
  .auth-foot {
    margin: 16px 0 0; padding-top: 14px; border-top: 1px solid var(--border);
    font-size: 13px; color: var(--text-muted); text-align: center;
  }
  .auth-foot a { font-weight: 700; }
  .auth-soon { color: var(--text-muted); text-align: center; }

  /* Галочки согласия при регистрации: вся строка — зона нажатия. */
  .reg-check {
    display: flex; gap: 10px; align-items: flex-start; margin: 12px 0 0; cursor: pointer;
    font-size: 13px; line-height: 1.45; color: var(--text);
  }
  .reg-check input { width: 18px; height: 18px; margin: 1px 0 0; flex: 0 0 auto; accent-color: var(--accent); }
  .reg-check a { font-weight: 700; }
  .auth-card .primary:disabled { opacity: .45; cursor: default; }

  /* Строка внизу страницы: продавец и документы. */
  .site-foot {
    grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 0 16px; align-items: center;
    margin-top: 36px; padding-top: 6px; border-top: 1px solid var(--border);
    font-size: 12px; color: var(--text-muted);
  }
  .site-foot a {
    color: var(--text-muted); text-decoration: underline; text-underline-offset: 2px;
    display: inline-flex; align-items: center; min-height: 40px;   /* пальцем попасть */
  }
  .site-foot a:hover { color: var(--text); }

  /* Смысловая часть. Не реклама — короткий список того, что сервис делает,
     теми же словами, что и на самой странице генерации. */
  .auth-side { min-width: 0; }
  .auth-side-brand { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; }
  .auth-side-brand .logo-mark { width: 44px; height: 44px; font-size: 20px; border-radius: var(--radius-sm); }
  .auth-side-brand b { font-size: 19px; letter-spacing: -.01em; }
  .auth-side h2 {
    margin: 0 0 12px; font-size: 30px; line-height: 1.18; letter-spacing: -.02em;
    text-wrap: balance;
  }
  .auth-side > p {
    margin: 0 0 24px; color: var(--text-muted); font-size: 15px; line-height: 1.55; max-width: 44ch;
  }
  .auth-points { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
  .auth-points li { display: flex; gap: 11px; align-items: flex-start; font-size: 14px; line-height: 1.5; }
  .auth-points .spark { color: var(--accent-strong); width: 17px; height: 17px; margin-top: 2px; }
  .auth-points b { display: block; }
  .auth-points span { color: var(--text-muted); }

  @media (max-width: 900px) {
    .auth { grid-template-columns: minmax(0, 1fr); gap: 28px; max-width: 420px; }
    /* Форма первой: сюда чаще приходят за входом, а не за знакомством. */
    .auth-side { order: 2; }
    .auth-side h2 { font-size: 22px; }
    .auth-side > p { margin-bottom: 18px; }
  }
  @media (max-width: 700px) {
    .layout-auth { padding-top: 20px; }
    .auth-card { padding: 20px 16px; }
  }

  /* ======================================================================
     ТАБЛИЦА ДАННЫХ
     По страницам таблицы были собраны инлайновыми стилями — каждая со своими
     отступами и своей линией. Один класс: заголовок тише строк, цифры в
     колонках выровнены по разряду, на узком экране таблица едет вбок сама,
     а не растягивает страницу.
     ====================================================================== */
  .data-table-wrap { overflow-x: auto; margin: 0 -2px; }
  .data-table {
    width: 100%; border-collapse: collapse; font-size: 13px;
    font-variant-numeric: tabular-nums;
  }
  .data-table th {
    text-align: left; font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
    color: var(--text-muted); font-weight: 700; padding: 0 10px 8px;
    border-bottom: 1px solid var(--border); white-space: nowrap;
  }
  .data-table td {
    padding: 10px; border-bottom: 1px solid var(--border); line-height: 1.45;
    vertical-align: top;
  }
  .data-table tr:last-child td { border-bottom: none; }
  .data-table tbody tr:hover td { background: var(--surface-2); }
  .data-table .num { text-align: right; white-space: nowrap; }
  .data-table .nowrap { white-space: nowrap; }
  .data-table .dim { color: var(--text-muted); }

  /* ======================================================================
     БАЛАНС
     Главная цифра страницы денег. Раньше она была строчкой обычного текста
     наравне с примечаниями — человек искал её глазами.
     ====================================================================== */
  .balance-hero {
    display: flex; flex-wrap: wrap; align-items: center; gap: 20px 32px;
    padding: 22px 24px; border-radius: var(--radius);
    border: 1px solid var(--accent-line);
    background: radial-gradient(130% 180% at 0% 0%, var(--accent-soft) 0%, var(--surface-2) 62%);
  }
  .balance-main { min-width: 0; }
  .balance-num {
    display: flex; align-items: center; gap: 10px;
    font-size: 38px; font-weight: 800; letter-spacing: -.02em; line-height: 1;
    font-variant-numeric: tabular-nums;
  }
  .balance-num .spark { width: 30px; height: 30px; color: var(--accent-strong); }
  .balance-sub { margin: 8px 0 0; color: var(--text-muted); font-size: 13.5px; line-height: 1.5; }
  .balance-side { flex: 1 1 260px; min-width: 0; font-size: 13px; line-height: 1.5; color: var(--text-muted); }
  @media (max-width: 700px) {
    .balance-hero { padding: 16px; gap: 14px; }
    .balance-num { font-size: 30px; }
  }

  /* Ряд коротких фактов под заголовком раздела. */
  .fact-row { display: flex; flex-wrap: wrap; gap: 8px; }
  .fact {
    display: flex; flex-direction: column; gap: 2px;
    padding: 9px 13px; border-radius: var(--radius-sm);
    background: var(--surface); border: 1px solid var(--border);
  }
  .fact b { font-size: 15px; font-variant-numeric: tabular-nums; }
  .fact span { font-size: 11.5px; color: var(--text-muted); }

  /* ======================================================================
     ВТОРИЧНАЯ КНОПКА
     .ghost-btn стоит в разметке 38 раз, а правила для неё не было ни одного:
     браузер рисовал системную серую кнопку — светло-серую на тёмном фоне, с
     мелким текстом и чужими скруглениями. Она выглядела как чужеродный
     элемент и на тёмной теме читалась хуже всего на странице.
     ====================================================================== */
  .ghost-btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    padding: 10px 16px; min-height: 40px; width: auto;
    border-radius: var(--radius-sm); border: 1px solid var(--border);
    background: var(--surface); color: var(--text);
    font: inherit; font-size: 13px; font-weight: 700; line-height: 1.2;
    cursor: pointer; text-decoration: none;
    transition: border-color .15s, background .15s, color .15s;
  }
  .ghost-btn:hover {
    border-color: var(--accent-strong); background: var(--accent-soft); color: var(--text);
  }
  .ghost-btn:disabled, .ghost-btn[disabled] {
    opacity: .5; cursor: not-allowed; border-color: var(--border); background: var(--surface);
  }
  /* Опасное вторичное действие — красная только рамка и текст: заливка сделала
     бы её заметнее главной кнопки рядом. */
  .ghost-btn.danger { color: var(--bad-text); border-color: var(--bad-border); }
  .ghost-btn.danger:hover { background: var(--bad-soft); border-color: var(--bad); color: var(--bad-text); }
  /* Кнопка внутри строки таблицы или ряда меток: та же кнопка, только ниже.
     До этого такие кнопки писались инлайном, каждая своим размером — по
     админке их набралось десять штук в пяти разных размерах. */
  .ghost-btn.sm { padding: 5px 10px; min-height: 30px; font-size: 12px; }

  /* Поле выбора файла. Рамку вокруг него мы рисовали, а системную кнопку
     «Choose File» внутри — нет: на тёмной теме она оставалась светло-серой
     плашкой с английской надписью. Красим её как вторичную кнопку. */
  input[type=file]::file-selector-button {
    margin-right: 10px; padding: 7px 13px;
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    background: var(--surface-2); color: var(--text);
    font: inherit; font-size: 12.5px; font-weight: 700; cursor: pointer;
    transition: border-color .15s, background .15s;
  }
  input[type=file]::file-selector-button:hover {
    border-color: var(--accent-strong); background: var(--accent-soft);
  }

/* Сообщение об ошибке рядом с тем, что её вызвало (см. showError в base.js).
   Раньше на это был alert() браузера — модальное окно с адресом сервера в
   заголовке, которое выглядит как поломка сайта. */
.form-error {
  margin: 10px 0 0; padding: 11px 14px; border-radius: var(--radius-sm);
  background: var(--warn-soft); border: 1px solid var(--warn-border);
  color: var(--warn-text); font-size: 13px; line-height: 1.5;
}

/* ---------------------------------------------------------------------------
   Экскурсия по экрану (static/tour.js, templates/_tour.html).

   Затемняем страницу и оставляем светлым один элемент — не копию, а его
   самого: подсветка живёт поверх настоящей разметки, поэтому объяснение
   нельзя разойтись с интерфейсом. Карточка с текстом всегда внизу, под
   пальцем, и на компьютере тоже: так она не прыгает по экрану от шага к шагу
   и не закрывает то, что подсвечивает.
--------------------------------------------------------------------------- */
/* Прокрутку НЕ блокируем: экскурсия сама возит страницу к нужному месту,
   а с overflow: hidden она бы упёрлась в первый экран. */
body.tour-on { scroll-behavior: smooth; }

.tour { position: fixed; inset: 0; z-index: 2400; }
.tour-veil { position: absolute; inset: 0; }

/* Дырка в затемнении — огромная тень вокруг рамки. Так подсвеченный элемент
   остаётся кликабельным и не перерисовывается копией. */
.tour-ring {
  position: absolute; border-radius: 14px; pointer-events: none;
  box-shadow: 0 0 0 4px var(--accent), 0 0 0 9999px rgba(10, 8, 20, .66);
  transition: top .2s, left .2s, width .2s, height .2s;
}

.tour-card {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: 24px; width: min(560px, calc(100vw - 32px));
  box-sizing: border-box; background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 16px 18px 18px; box-shadow: 0 18px 48px rgba(10, 8, 20, .35);
}
.tour-dots { display: flex; gap: 5px; margin-bottom: 13px; }
.tour-dots span {
  flex: 1 1 0; height: 3px; border-radius: 999px; background: var(--border);
}
.tour-dots span.on { background: var(--accent); }
.tour-card-head { display: flex; align-items: baseline; gap: 12px; }
.tour-title { flex-grow: 1; min-width: 0; font-size: 16.5px; font-weight: 800; letter-spacing: -.01em; }
.tour-skip {
  flex-shrink: 0; font: inherit; font-size: 12.5px; color: var(--text-muted);
  background: none; border: none; padding: 6px 2px; cursor: pointer; min-height: 32px;
}
.tour-skip:hover { color: var(--text); }
.tour-text { margin: 7px 0 0; font-size: 13px; line-height: 1.55; color: var(--text-muted); }
.tour-foot { display: flex; gap: 9px; margin-top: 15px; }
.tour-back, .tour-next {
  font: inherit; font-weight: 800; border-radius: var(--radius-sm);
  padding: 12px 18px; min-height: 44px; cursor: pointer;
}
.tour-back {
  flex: 0 0 104px; font-size: 13px; background: var(--surface-2);
  border: 1px solid var(--border); color: var(--text);
}
.tour-back:disabled { opacity: .45; cursor: default; }
.tour-next {
  flex-grow: 1; font-size: 14px; border: none;
  background: var(--accent); color: var(--accent-text);
}

@media (max-width: 700px) {
  .tour-card {
    left: 0; right: 0; bottom: 0; transform: none; width: auto;
    border-radius: var(--radius) var(--radius) 0 0; border-bottom: none;
    padding-bottom: calc(18px + env(safe-area-inset-bottom));
  }
  .tour-title { font-size: 15.5px; }
  .tour-text { font-size: 12.5px; }
}

/* ---------------------------------------------------------------------------
   Подсказка-вопросик у названия раздела (макрос tip в _macros.html).

   Текст, который нужен один раз — откуда взялось название товара, что будет
   со строками, чей дизайн можно грузить, — висел абзацем под каждым полем.
   Читают его в первый день и больше никогда, а место он занимал всегда и
   отодвигал вниз то, ради чего пришли. Теперь он за знаком вопроса.
--------------------------------------------------------------------------- */
/* Строка заголовка поля: название слева, рядом вопросик, справа — кнопка
   вроде «↻ Другие». Без неё блочный <label> уводит вопросик на строку ниже. */
.field-label-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin: 14px 0 5px; }
.field-label-row .field-label { margin: 0; }
/* Вопросик — часть заголовка, а не отдельный элемент строки: прижимаем его
   к названию, иначе space-between разводит их по разным краям. */
.field-label-row .tip-wrap { margin-right: auto; }

.tip-wrap {
  position: relative; display: inline-flex; align-items: center;
  vertical-align: middle; margin-left: 17px;
}
/* Кнопка — прозрачные 40×40 под палец, кружок рисует .tip-dot внутри.
   Наоборот не выходит: в мобильной вёрстке любая кнопка тянется до 44 px по
   высоте, и круг 18×18 превращался в вертикальный овал. Отрицательные поля
   возвращают кнопке место 18 px в строке, а видимый отступ от заголовка
   задаёт margin-left у обёртки. */
.tip-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; min-width: 0; min-height: 0; margin: -11px;
  padding: 0; border: none; background: none; font: inherit; cursor: pointer;
}
.tip-dot {
  display: flex; align-items: center; justify-content: center; flex: 0 0 auto;
  width: 18px; height: 18px; border-radius: 50%; aspect-ratio: 1;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--text-muted);
  font-size: 11px; font-weight: 800; line-height: 1;
}
.tip-btn:hover .tip-dot, .tip-btn[aria-expanded="true"] .tip-dot {
  border-color: var(--accent); background: var(--accent-soft); color: var(--accent-strong);
}
.tip-text {
  position: absolute; left: -6px; top: calc(100% + 7px); z-index: 30;
  display: block; width: max-content; max-width: min(320px, calc(100vw - 32px));
  box-sizing: border-box; padding: 10px 13px; border-radius: var(--radius-sm);
  background: var(--text); color: var(--surface);
  font-size: 12.5px; font-weight: 400; line-height: 1.5; white-space: normal;
  box-shadow: 0 10px 28px rgba(10, 8, 20, .28);
}
.tip-text[hidden] { display: none; }

/* Витрина бесплатных инструментов на странице «Бесплатные функции». */
.free-tools {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
}
.free-tool {
  display: flex; flex-direction: column; text-decoration: none; color: var(--text);
  box-sizing: border-box; background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 15px 16px; transition: border-color .15s;
}
.free-tool:hover { border-color: var(--accent); }
.free-tool-name { font-size: 15px; font-weight: 800; }
.free-tool-text { font-size: 12.5px; line-height: 1.5; color: var(--text-muted); margin-top: 6px; }
.free-tool-go { font-size: 12.5px; font-weight: 800; color: var(--accent-strong); margin-top: 11px; }

/* Значок инструмента: тот же набор, что везде, — линия 1.75 и currentColor,
   поэтому он сам подстраивается под тему. */
.free-tool-mark {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; border-radius: var(--radius-sm); margin-bottom: 10px;
  background: var(--accent-soft); color: var(--accent-strong);
}


/* Приглашение в команду — ждёт ответа, поэтому стоит над списком разделов
   кабинета и выделено рамкой акцента. */
.acc-invite {
  border: 1px solid var(--accent-line); background: var(--accent-soft);
  border-radius: var(--radius); padding: 14px 16px; margin: 0 0 12px;
}

/* --- Вход через ВКонтакте ------------------------------------------------ */
.vk-gift {
  display: flex; align-items: center; gap: 12px; margin: 4px 0 16px; padding: 12px 14px;
  border-radius: var(--radius-sm); background: var(--surface-2); border: 1px solid var(--accent-line);
  font-size: 13px; line-height: 1.45;
}
.vk-gift b { font-size: 20px; font-weight: 800; color: var(--link); white-space: nowrap; }
.vk-btn {
  display: flex; align-items: center; justify-content: center; gap: 10px; width: 100%;
  min-height: 50px; margin: 12px 0 0; padding: 12px 16px; border: 0; border-radius: var(--radius-sm);
  background: #0077FF; color: #fff; font: inherit; font-size: 15px; font-weight: 800; cursor: pointer;
}
.vk-btn:disabled { opacity: .4; cursor: default; }
.vk-btn:focus-visible { outline: 3px solid var(--accent-line); outline-offset: 2px; }
.vk-mark {
  display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px;
  border-radius: 7px; background: #fff; color: #0077FF; font-size: 11px; font-weight: 800;
}
.vk-fine { margin: 10px 0 0; font-size: 12px; line-height: 1.45; color: var(--text-muted); text-align: center; }
.email-login { margin-top: 18px; border-top: 1px solid var(--border); padding-top: 12px; }
.email-login > summary { cursor: pointer; font-size: 13px; font-weight: 700; color: var(--text-muted); min-height: 40px;
  display: flex; align-items: center; }
.email-login form { margin-top: 8px; }
.vk-hello { text-align: center; }
.vk-avatar { width: 72px; height: 72px; border-radius: 50%; object-fit: cover; margin: 0 auto 12px; display: block;
  background: var(--surface-2); }
.vk-allow { display: flex; flex-direction: column; align-items: stretch; gap: 10px; margin-top: 16px; }
.vk-allow .ghost-btn { width: 100%; margin: 0; min-height: 44px; }
.vk-widget { min-height: 30px; display: flex; justify-content: center; }
.vk-done { display: none; padding: 12px 14px; border-radius: var(--radius-sm); background: var(--good-soft);
  border: 1px solid var(--good); color: var(--good-text); font-size: 13.5px; font-weight: 700; }
.vk-done.show { display: block; }

/* «Прислать карточку в ВК?» — после готовой карточки */
.vk-prompt {
  grid-column: 1 / -1; display: flex; gap: 14px; align-items: center; flex-wrap: wrap;
  padding: 12px 14px; border-radius: var(--radius-sm); border: 1px solid #BFD9FF; background: #F0F6FF;
}
.vk-prompt img { width: 56px; height: 72px; object-fit: cover; border-radius: 8px; flex: none; }
.vk-prompt-text { flex: 1 1 200px; font-size: 13.5px; line-height: 1.45; color: #10243e; }
.vk-prompt-text b { display: block; font-size: 14.5px; }
.vk-prompt-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.vk-prompt-actions form { margin: 0; }
.vk-prompt-actions .ghost-btn { margin: 0; min-height: 40px; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .vk-prompt { background: #0f2033; border-color: #1f4a7a; }
  :root:not([data-theme="light"]) .vk-prompt-text { color: #dbe8f7; }
}
:root[data-theme="dark"] .vk-prompt { background: #0f2033; border-color: #1f4a7a; }
:root[data-theme="dark"] .vk-prompt-text { color: #dbe8f7; }

/* «Сообщить о проблеме» — окно снизу на телефоне, по центру на компьютере */
.report-sheet {
  width: min(460px, 100%); max-width: 100%; margin: auto auto 0; padding: 18px 16px 20px;
  border: 0; border-radius: 20px 20px 0 0; background: var(--surface); color: var(--text);
  box-shadow: 0 -10px 40px rgba(0,0,0,.2);
}
@media (min-width: 700px) { .report-sheet { margin: auto; border-radius: 20px; } }
.report-sheet::backdrop { background: rgba(20, 18, 28, .45); }
.report-sheet h3 { margin: 0 0 4px; font-size: 18px; font-weight: 800; }
.report-lead { margin: 0 0 12px; font-size: 13px; color: var(--text-muted); }
.report-opt { font-weight: 400; color: var(--text-muted); }
.report-sheet textarea { width: 100%; margin: 0 0 10px; }
.report-add {
  display: flex; align-items: center; justify-content: center; gap: 6px; min-height: 44px; margin: 0;
  border: 1px dashed var(--border); border-radius: var(--radius-sm); cursor: pointer;
  font-size: 13.5px; font-weight: 700; color: var(--link);
}
.report-thumbs { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.report-thumbs img { width: 56px; height: 56px; object-fit: cover; border-radius: 8px; border: 1px solid var(--border); }
.report-sheet .primary { width: 100%; margin: 12px 0 0; min-height: 46px; }
.report-sheet .ghost-btn { width: 100%; margin: 8px 0 0; min-height: 44px; }
.report-note { margin: 6px 0 0; }

/* «Откуда вы о нас узнали?» */
.src-ask { margin-top: 18px; padding: 14px; border: 1px solid var(--border); border-radius: var(--radius-sm); text-align: left; }
.src-ask > b { display: block; font-size: 14px; margin-bottom: 10px; }
.src-chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 0; }
.src-chip {
  width: auto; margin: 0; min-height: 40px; padding: 8px 14px; border-radius: var(--radius-pill);
  border: 1px solid var(--border); background: var(--surface); color: var(--text);
  font: inherit; font-size: 13px; font-weight: 700; cursor: pointer;
}
.src-chip.is-on { border-color: var(--accent-strong); background: var(--surface-2); color: var(--link); }
.src-chip:disabled { cursor: default; }
.src-chip:disabled:not(.is-on) { opacity: .5; }
.src-thanks { margin: 10px 0 0; font-size: 13px; font-weight: 700; color: var(--good-text); }
.job-error-actions .job-error-btn { width: auto; margin: 0; }

/* Счётчик знаков под описанием для площадки. */
.desc-count { margin: 4px 0 8px; font-size: 12px; color: var(--text-muted); text-align: right; font-variant-numeric: tabular-nums; }
.desc-count.is-near { color: var(--warn-text, #B45309); font-weight: 700; }

/* Код ошибки под текстом: короткая метка, которую легко продиктовать поддержке. */
.job-error-code { margin-top: 6px; font-size: 12px; color: var(--text-muted); }
.job-error-code b { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--text); letter-spacing: .02em; }

/* Админ смотрит кабинет продавца: жёлтая полоса сверху, кнопки действий серые. */
.va-bar {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 9px 16px; background: #FDE68A; color: #713F12; font-size: 13px; font-weight: 600;
  position: relative; z-index: 1001;
}
.va-bar b { font-weight: 800; }
.va-form { margin: 0; }
.va-exit {
  margin: 0; width: auto; min-height: 0; padding: 6px 14px; border: none; border-radius: 999px;
  background: #713F12; color: #FEF3C7; font: inherit; font-size: 12.5px; font-weight: 800; cursor: pointer;
}
.is-viewas form[method="post"]:not(.va-form) button[type="submit"],
.is-viewas form[method="post"]:not(.va-form) button:not([type]),
.is-viewas form[method="post"]:not(.va-form) input[type="submit"] {
  filter: grayscale(1); opacity: .5; cursor: not-allowed;
}
.is-viewas form[method="post"]:not(.va-form) button.primary[type="submit"]::before { content: "🔒 "; }
.va-toast {
  position: fixed; left: 50%; bottom: calc(76px + env(safe-area-inset-bottom, 0px)); transform: translateX(-50%);
  z-index: 5000; background: #14121C; color: #fff; font-size: 13px; font-weight: 600;
  padding: 10px 16px; border-radius: 999px; box-shadow: var(--shadow-lg); white-space: nowrap;
}

/* «Подключите бота ФокусМП в ВК» (вариант Б) */
.vk-bot {
  margin-top: 18px; padding: 16px; text-align: left; border-radius: var(--radius);
  background: var(--surface-2); border: 1px solid var(--border);
}
.vk-bot h3 { margin: 0 0 10px; font-size: 16px; font-weight: 800; }
.vk-bot-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.vk-bot-list li { display: flex; gap: 10px; align-items: flex-start; font-size: 13.5px; line-height: 1.45; color: var(--text-muted); }
.vk-bot-list b { color: var(--text); }
.vk-bot-ico {
  flex: 0 0 30px; height: 30px; border-radius: 9px; display: grid; place-items: center;
  background: var(--accent-soft); font-size: 15px;
}
.vk-bot-btn {
  display: flex; align-items: center; justify-content: center; gap: 9px; width: 100%; box-sizing: border-box;
  min-height: 48px; border-radius: var(--radius-sm); background: #0077FF; color: #fff !important;
  font-weight: 800; font-size: 15px; text-decoration: none; margin-top: 14px;
}
.vk-bot-btn:hover { background: #0066DB; }
.vk-bot-btn[hidden] { display: none; }
.vk-bot-logo {
  font-size: 11px; font-weight: 900; letter-spacing: .02em; background: #fff; color: #0077FF;
  border-radius: 6px; padding: 3px 5px; line-height: 1;
}
.vk-bot-how { margin: 8px 0 0; font-size: 12.5px; color: var(--text-muted); text-align: center; }
.vk-bot .vk-allow { margin-top: 0; gap: 0; }
.vk-bot .vk-done { margin-top: 14px; }
.vk-skip { width: 100%; margin-top: 12px; border-color: transparent; color: var(--text-muted); }
.vk-skip.is-go { border-color: var(--accent); color: var(--accent-strong); }
.vk-prompt .vk-allow { margin-top: 0; }
.vk-prompt .vk-bot-btn { margin-top: 0; min-height: 42px; font-size: 14px; padding: 0 16px; }
.src-banner { position: relative; grid-column: 1 / -1; margin: 0 0 14px; background: var(--surface); }
.src-x {
  position: absolute; top: 8px; right: 8px; width: 30px; height: 30px; min-height: 0; margin: 0; padding: 0;
  border: none; border-radius: 50%; background: var(--surface-2); color: var(--text-muted); cursor: pointer; font-size: 13px;
}

/* Название из слов (_title_builder.html) */
.tb { margin: 0 0 14px; }
.tb textarea.tb-input { min-height: 0; height: auto; }
.tb-input { display: block; width: 100%; resize: none; font: inherit; font-size: 16px; line-height: 1.35; padding: 10px 12px; border: 1.5px solid var(--border, #DCD5FA);
  border-radius: 12px; background: var(--surface, #fff); color: inherit; }
.tb-input:focus { outline: none; border-color: var(--accent, #6D4CF5); box-shadow: 0 0 0 3px rgba(109,76,245,.15); }
.tb-count { margin: 4px 0 6px; font-size: 12px; color: var(--text-muted); text-align: right; font-variant-numeric: tabular-nums; }
.tb-count.is-near { color: var(--warn-text, #B45309); font-weight: 700; }
.tb-lead { margin: 0 0 6px; font-size: 13px; color: var(--text-muted); }
.tb-chips { display: flex; flex-wrap: wrap; gap: 7px; }
.tb .tb-chip { width: auto; flex: 0 0 auto; margin: 0; border: 1.5px dashed #9E8EF0; background: transparent; color: var(--link, #5B3FD6);
  border-radius: 999px; padding: 6px 12px; font: inherit; font-size: 13px; font-weight: 700; cursor: pointer;
  min-height: 34px; }
.tb .tb-chip::before { content: "+ "; }
.tb .tb-chip.is-in { border-style: solid; background: #E4F7EE; border-color: #7FD3AE; color: #11744F; }
.tb .tb-chip.is-in::before { content: "✓ "; }
.tb-chip:focus-visible { outline: 2px solid var(--accent, #6D4CF5); outline-offset: 2px; }
.tb-warn { margin: 8px 0 0; font-size: 13px; color: var(--warn-text, #B45309); font-weight: 600; }
