  :root{
    /* Три ступени текста: --text читают подряд, --muted это пояснения и
       подписи, --muted2 — то, чего как бы ещё нет (подсказка в пустом поле,
       «здесь появится…»).

       --muted потемнел 02.09.2026: был #5A6B80, владелец нашёл его слишком
       светлым рядом с почти чёрным --text. Число это живёт не только здесь —
       копии стоят там, куда эту таблицу не дотянуть: страница «идут работы»
       (lib/maintenance-page.ts), страницы редиректа с паролем и заглушками
       (redirect/password-page.ts, redirect/stub-page.ts), витрина тарифов в
       предпросмотре панели (admin PlansPreview.tsx) и подпись под точкой на
       графике (site/app.js, она рисуется в SVG числом). Меняете здесь —
       поищите старое значение по всему проекту, иначе половина сайта
       разъедется по оттенкам. */
    --bg:#EAF5FF; --card:#FFFFFF; --text:#0F1E2E; --muted:#354051; --muted2:#93A6B9;
    --border:#DCEAF6; --border2:#E7F1F8;

    /* синий — основной бренд-цвет, кнопки/акценты действий */
    --primary:#2F6FED; --primary-dark:#1E52C4; --primary-50:#EBF3FF; --primary-100:#D6E7FF;

    /* переменная называется --teal исторически (её трогает app.js/Color Lab),
       но теперь это ЗЕЛЁНЫЙ — используется для всего, что связано с Динамическим QR */
    --teal:#22C55E; --teal-dark:#159348; --teal-50:#E8FBEF;

    /* новый акцент — точечные оранжевые вставки */
    --orange:#F97316; --orange-dark:#DD5F0A; --orange-50:#FFF2E5; --orange-100:#FFE0BF;

    /* зелёный из макета — генерация и выбор режима */
    --gen:#5FC752; --gen-2:#7CD66A; --gen-soft:#F1FAEF; --gen-border:#A9DFA0;
    /* сиреневый — выбранный тип QR */
    --pick:#AA8BCF; --pick-2:#8E6BB8AF;

    --green:#16A34A; --red:#DC2626; --fav:#F43F5E;
    /* тот же красный, но с насыщенностью на 40% ниже (S 72% → 43% при том же тоне
       и светлоте) — для тонких рамок, где чистый --red слишком кричит */
    --red-soft:#B84A4A;
    /* падение показателя — не ошибка и не опасное действие, а просто факт, и
       краснеть ему незачем: направление уже сказано стрелкой и знаком минус.
       Отдельная переменная, а не прямой --muted, — чтобы смысл «отрицательная
       динамика» остался виден в правиле и цвет менялся в одном месте */
    --neg:var(--muted);
    /* Три ступени скругления. --r — большие карточки и панели (карточка
       генератора, тариф, полосы на главной, окна); --r-md — карточки и плитки
       в рядах: код в «Моих QR», папка, плитка типа, подсказка ассистента;
       --r-sm — кнопки и поля ввода.
       02.09.2026 владелец попросил углы карточек на 30% острее: --r был 20px,
       --r-md появился тогда же вместо 16px, вписанных в каждое правило по
       отдельности. --r-sm не трогали — кнопки и поля остались как были. */
    --r:14px; --r-md:11px; --r-sm:8px;
    --shadow:0 1px 2px rgba(20,50,90,.05), 0 2px 8px rgba(20,50,90,.05);
    --shadow-md:0 10px 28px rgba(20,50,90,.10);
    --topbar-h:64px;
    --sidebar-w:240px;

    /* Фон страницы вынесен в переменную не ради красоты: этими же слоями
       закрашивает себя язычок «ещё типы» на кромке рамки выбора типа. Оба
       фона закреплены за экраном (background-attachment:fixed), поэтому
       совпадают попиксельно при любой прокрутке — язычок читается как выемка
       в кромке карточки, а не как белая нашлёпка поверх страницы. Меняете
       фон — правьте здесь, язычок подхватит сам. */
    --page-bg:
      radial-gradient(1000px 560px at 6% -8%, rgba(47,111,237,.15), transparent 60%),
      radial-gradient(900px 520px at 102% 18%, rgba(34,197,94,.16), transparent 55%),
      radial-gradient(680px 420px at 55% 112%, rgba(249,115,22,.10), transparent 55%),
      linear-gradient(165deg, var(--bg) 0%, #EEFBF3 55%, #ECFCE9 100%);
  }
  *{margin:0;padding:0;box-sizing:border-box}
  html,body{height:100%;overflow:hidden}
  body{
    font-family:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;
    font-synthesis-weight:none;
    color:var(--text); font-size:14px; line-height:1.5;
    -webkit-font-smoothing:antialiased;
    background:var(--page-bg);
    background-attachment:fixed;
  }

  svg{display:block}
  /* color:inherit тут обязателен наравне с font-family. Кнопкам и полям
     браузер даёт собственный чёрный (#000), а не наш --text (#0F1E2E), и
     подпись, которой не проставили цвет вручную, выпадает из палитры сайта —
     так было у карточек «Статический / Динамический QR» и у полей поиска.
     Правило стоит первым и без классов, поэтому любой явный color в стилях
     ниже его перебивает. */
  button{font-family:inherit;color:inherit;cursor:pointer}
  input,select,textarea{font-family:inherit;font-size:14px;color:inherit}

  /* Рабочая область во весь экран, а не «экран минус шапка»: шапка лежит
     поверх неё стеклом, и место под собой больше не занимает. Начало
     содержимого опускают отступы сверху у сайдбара и <main> ниже. */
  .app{display:flex;height:100vh;min-width:1180px}

  /* ============ TOPBAR — матовое стекло ============ */
  /* Полоса не стоит над содержимым, а лежит на нём: экран прокручивается под
     ней, и карточки уходят под стекло, слегка просвечивая сквозь него. Отсюда
     fixed вместо sticky.

     Белого в стекле — 60,5%, блюр 14,3px (02.09.2026, по просьбе владельца:
     78% и 26px выглядели почти сплошным стеклом, 55% и 13px — чуть жиже, чем
     он хотел; оба числа подняты от них на десятую). Ниже этого белое перестаёт
     держать шапку светлой над тёмным содержимым, и подписи в ней теряются;
     блюр слабее — и уехавший текст читается сквозь стекло буквами, а не
     мягким пятном цвета. saturate добавляет цвет обратно: один блюр вымывает
     его до серого.

     Отступы сверху у <main> и сайдбара тоже часть этой конструкции — без них
     содержимое начиналось бы под полосой. Липкие блоки внутри <main> (полоса
     чужого аккаунта, телефон предпросмотра) при этом не трогаем: браузер
     считает их прилипание от края содержимого, то есть уже с учётом отступа —
     они встают там же, где стояли. */
  .topbar{
    height:var(--topbar-h);position:fixed;top:0;left:0;right:0;z-index:50;min-width:1180px;
    background:rgba(255,255,255,.605);
    -webkit-backdrop-filter:blur(14.3px) saturate(1.7);
    backdrop-filter:blur(14.3px) saturate(1.7);
    border-bottom:1px solid var(--border);box-shadow:0 1px 2px rgba(20,50,90,.04);
  }
  /* полоса тянется на всю ширину: логотип держится левого угла, кнопки — правого */
  .topbar-inner{height:100%;width:100%;padding:0 28px;display:flex;align-items:center;justify-content:space-between;gap:24px;position:relative}
  /* Логотип — ссылка на главную, поэтому гасим подчёркивание и цвет ссылки. */
  .topbar-logo{display:flex;align-items:center;gap:10px;flex-shrink:0;text-decoration:none;color:inherit}
  /* Знак сервиса — тёмные дуги на прозрачном (29.09.2026; README, «Знак сервиса»):
     квадрат без скругления и обрезки — у прежнего красного круга они прятали
     углы картинки. */
  .logo-img{width:36px;height:36px;display:block}
  /* Словесный знак — сразу справа от знака (11.10.2026, решение владельца из
     трёх вариантов: знак 36, слово 24, зазор 10 — высота строчных букв около
     половины знака, и пара читается одним целым). Цвет — тот же, что у дуг
     знака. На 1 точку выше середины — так слово на глаз стоит ровно по
     центру знака. */
  .brand-word{
    position:relative;top:-1px;
    font-size:24px;line-height:1;font-weight:800;letter-spacing:-.02em;color:#0F1E2E;white-space:nowrap;
  }
  .topbar-right{display:flex;align-items:center;gap:28px;flex-shrink:0}
  .topbar-links{display:flex;align-items:center;gap:22px}
  /* «Тарифы» и «FAQ» в шапке, как и пункты меню ниже, — ссылки, а не кнопки (15.10.2026,
     navGo в app.js): без подчёркивания и с высотой строки кнопки, какими были до того. */
  .topbar-link{background:none;border:none;color:var(--muted);font-size:13.5px;font-weight:600;padding:6px 2px;transition:color .15s;text-decoration:none;line-height:normal}
  .topbar-link:hover{color:var(--text)}
  /* «Тарифы» в шапке и в шторке прячутся у того, кто работает в чужом аккаунте:
     покупать ему там нечего (Р-391, проверка роботом, заход 12). display:flex у
     .nav-item сильнее браузерного [hidden]{display:none} — без этой строки
     пункт шторки остался бы на месте. */
  .topbar-link[hidden],.nav-item[hidden]{display:none}
  .topbar-actions{display:flex;align-items:center;gap:10px}
  .topbar .btn-ghost{color:var(--text);border:1.5px solid var(--border);padding:7px 14px}
  .topbar .btn-ghost:hover{background:var(--primary-50);color:var(--primary);border-color:var(--primary-100)}
  /* Имя (а то и почта) вошедшего задаёт ширину правого блока, и запас под знак
     перестаёт сходиться. Длинное имя обрезаем многоточием. */
  .topbar .auth-actions .btn{display:block;max-width:150px;text-align:center;overflow:hidden;text-overflow:ellipsis}

  /* ============ SIDEBAR ============ */
  .sidebar{
    width:var(--sidebar-w);flex-shrink:0;background:rgba(255,255,255,.82);backdrop-filter:blur(10px);border-right:1px solid var(--border);
    display:flex;flex-direction:column;height:100%;overflow-y:auto;
    /* Отступ под стеклянную шапку: она лежит поверх, и без него первый пункт
       меню оказался бы под ней. Белая подложка сайдбара при этом уходит под
       стекло до самого верха — так шапка и слева остаётся белой. */
    padding-top:var(--topbar-h);
  }
  .nav{padding:20px 12px 14px;display:flex;flex-direction:column;gap:2px}
  .nav-divider{height:1px;background:var(--border2);margin:10px 4px}
  /* Пункт меню — ссылка на адрес раздела (15.10.2026: правой кнопкой открывается в новой
     вкладке); подчёркивания нет, высота строки — как у кнопки, которой он был. */
  .nav-item{
    display:flex;align-items:center;gap:11px;padding:10px 12px;border-radius:12px;border:none;background:transparent;
    color:var(--muted);font-size:14px;font-weight:500;text-align:left;transition:all .15s;width:100%;position:relative;
    text-decoration:none;line-height:normal;
  }
  .nav-item svg{width:19px;height:19px;flex-shrink:0}
  .nav-item:hover{background:#F0F6FF;color:var(--text)}
  .nav-item.active{background:var(--primary-50);color:var(--primary);font-weight:600}
  .nav-item[data-nav="ai"]::after{
    content:'AI'; margin-left:auto; background:var(--orange-50); color:#960ADD66;
    font-size:9.5px; font-weight:800; letter-spacing:.03em; padding:2px 7px; border-radius:999px; flex-shrink:0;
  }
  /* Разделы аккаунта у гостя. Не скрыты, а притушены: так видно, что в сервисе
     есть, а нажатие открывает вход — молчаливая серая кнопка выглядела бы
     поломкой. Курсор остаётся обычным, потому что кнопка всё-таки работает. */
  .nav-item.locked{color:var(--muted2);opacity:.72}
  .nav-item.locked svg{opacity:.6}
  /* Своего облачка у пункта нет: data-tip показывает общая подсказка .tipx,
     и второе, на ::before, выскакивало под первым тем же текстом. */
  .nav-item.locked:hover{background:#F5F7FA;color:var(--muted2)}

  /* ---- кнопка «Feedback» ----
     С 01.10.2026 вместо «Нашёл баг? Сообщи нам» с жучком (решение владельца):
     книжка контуром и две строки — «Feedback» и «Ideas & bug reports». Стоит
     последней в меню и намеренно тише его пунктов: сервис человек открыл
     делать коды, а не писать нам. Поэтому в покое ни фона, ни рамки, книжка
     серым контуром, а буквы бледные — не чёрные (слово владельца: «не надо,
     чтобы сильно притягивала на себя внимание»). Цвет и движение появляются
     только под мышкой и при открытом окне: книжка раскрывается, и над ней по
     одной прорисовываются дуги радуги из цветов сайта.

     Наведение — только там, где есть мышь (`@media (hover:hover)`): на
     телефоне :hover залипает после касания, и закрытое окно оставляло бы
     книжку раскрытой. Там то же движение играет в миг нажатия — класс .open,
     его ставит openBugBox. Классы рисунка — с приставкой fb-: стили сайта
     общие на все экраны, и голое «.arc» однажды задело бы чужое. */
  .bug-card{
    --fb-ic:#A9B8C8;          /* контур книжки в покое — серо-голубой */
    display:flex;align-items:center;gap:11px;width:100%;text-align:left;
    margin:14px 0 2px;padding:9px 12px;border-radius:10px;
    /* Рамка не убрана, а прозрачна: её ширина остаётся в расчёте, и фокус с
       клавиатуры не сдвигает кнопку. */
    border:1px solid transparent;background:transparent;
    transition:background .2s, border-color .15s, box-shadow .15s;
  }
  .bug-card[hidden]{display:none}
  .bug-card.open{background:rgba(235,243,255,.75);--fb-ic:#7F95AD}
  .bug-card:focus-visible{outline:none;border-color:var(--primary);box-shadow:0 0 0 3px var(--primary-100)}
  .bug-art{flex-shrink:0;width:36px;height:36px;color:var(--fb-ic);transition:color .2s}
  .bug-art svg{width:100%;height:100%;overflow:visible}
  .bug-card-text{display:flex;flex-direction:column;min-width:0}
  .bug-card-title{font-weight:600;font-size:13px;line-height:1.25;color:#7D8C9C;transition:color .2s}
  .bug-card-sub{font-size:11.5px;line-height:1.3;color:#A9B8C8;transition:color .2s}
  .bug-card.open .bug-card-title,.bug-card:focus-visible .bug-card-title{color:#5A6B7E}
  .bug-card.open .bug-card-sub,.bug-card:focus-visible .bug-card-sub{color:var(--muted2)}

  /* Книжка раскрывается: обложка сжимается к корешку (x=19), из него же
     разворачивается левая страница, а вся книжка отъезжает вправо, чтобы
     разворот встал посередине. Обратный ход — те же шаги в обратном порядке. */
  .fb-bk{transform-box:view-box;transition:transform .36s ease .05s}
  .fb-cover,.fb-inner{transform-box:view-box;transform-origin:19px 0}
  .fb-cover{transition:transform .17s ease-out .17s}
  .fb-inner{transform:scaleX(0);transition:transform .17s ease-in}
  .bug-card.open .fb-bk{transform:translateX(13px);transition:transform .36s ease}
  .bug-card.open .fb-cover{transform:scaleX(0);transition:transform .17s ease-in}
  .bug-card.open .fb-inner{transform:scaleX(1);transition:transform .17s ease-out .17s}
  /* Дуги радуги: у каждой длина 1 (pathLength в разметке), и сдвиг штриха от
     1 к 0 рисует её слева направо. Начинают, пока книжка ещё раскрывается, —
     вся радуга готова примерно через 0,85 с (владелец просил на четверть
     быстрее первого варианта). Уходят разом и быстро. */
  .fb-arc{stroke-dasharray:1;stroke-dashoffset:1;transition:stroke-dashoffset .15s ease}
  .bug-card.open .fb-arc{stroke-dashoffset:0;transition:stroke-dashoffset .41s cubic-bezier(.3,.7,.3,1)}
  .bug-card.open .fb-a1{transition-delay:.26s}
  .bug-card.open .fb-a2{transition-delay:.31s}
  .bug-card.open .fb-a3{transition-delay:.36s}
  .bug-card.open .fb-a4{transition-delay:.41s}
  .bug-card.open .fb-a5{transition-delay:.46s}
  /* То же под мышкой — правила те же, что у .open выше; правишь одно — правь оба. */
  @media (hover:hover){
    .bug-card:hover{background:rgba(235,243,255,.75);--fb-ic:#7F95AD}
    .bug-card:hover .bug-card-title{color:#5A6B7E}
    .bug-card:hover .bug-card-sub{color:var(--muted2)}
    .bug-card:hover .fb-bk{transform:translateX(13px);transition:transform .36s ease}
    .bug-card:hover .fb-cover{transform:scaleX(0);transition:transform .17s ease-in}
    .bug-card:hover .fb-inner{transform:scaleX(1);transition:transform .17s ease-out .17s}
    .bug-card:hover .fb-arc{stroke-dashoffset:0;transition:stroke-dashoffset .41s cubic-bezier(.3,.7,.3,1)}
    .bug-card:hover .fb-a1{transition-delay:.26s}
    .bug-card:hover .fb-a2{transition-delay:.31s}
    .bug-card:hover .fb-a3{transition-delay:.36s}
    .bug-card:hover .fb-a4{transition-delay:.41s}
    .bug-card:hover .fb-a5{transition-delay:.46s}
  }

  /* ---- свёрнутый вид: одна книжка, надпись выезжает по наведению ----
     Второй вид кнопки, включается в панели («Система → Фидбэк», «Вид кнопки
     на компьютере»). Занимает угол меню вместо целой строки.

     Только там, где есть мышь: на телефоне (и на любом сенсорном экране)
     наводить нечем, свёрнутая кнопка осталась бы навсегда безымянным
     значком — поэтому оба условия в @media, а не одно про ширину.

     Ширина кнопки анимируется, надпись при этом держит свои 150 точек
     (`flex:0 0`) и просто уходит за обрез. Иначе на узкой кнопке она сжалась
     бы в столбик по букве и кнопка запрыгала бы по высоте. 62 = книжка 36,
     поля по 12 и рамка по точке: уже — и обрез съел бы край книжки. */
  @media (min-width:901px) and (hover:hover){
    .bug-card.compact{
      width:62px;align-self:flex-start;overflow:hidden;
      transition:width .22s ease, border-color .15s, background .2s, box-shadow .15s;
    }
    /* Прозрачность поверх обреза: правый отступ кнопки входит в её область
       видимого, и первые точки надписи выглядывали из-за книжки тонкой чёрточкой.
       Появляется надпись с задержкой — сперва кнопка успевает разъехаться. */
    .bug-card.compact .bug-card-text{flex:0 0 150px;opacity:0;transition:opacity .12s ease}
    /* Открытое окошко держит кнопку раскрытой: она — то место, откуда оно
       выехало, и схлопываться под ним ей нельзя. */
    .bug-card.compact:hover,.bug-card.compact.open{width:100%}
    .bug-card.compact:hover .bug-card-text,.bug-card.compact.open .bug-card-text{
      opacity:1;transition:opacity .18s ease .1s;
    }
  }

  /* ---- окошко «Feedback» ----
     Место на компьютере считает placeBugBox в app.js: окно встаёт справа от
     кнопки, вплотную к меню. Здесь только вид и телефонный вариант. */
  .bug-box{
    position:fixed;z-index:70;width:330px;max-width:calc(100vw - 24px);
    background:var(--card);border:1px solid var(--border);border-radius:12px;
    box-shadow:var(--shadow-md);padding:16px 14px 14px;
    display:flex;flex-direction:column;gap:11px;
  }
  .bug-box[hidden]{display:none}
  /* Радужная полоска по верхнему краю. Цвета — те же пять цветов сайта, но
     смешанные с белым на 40 %: насыщенность ниже, светлее (слово владельца —
     «уменьшить насыщенность и чуть-чуть добавить яркости, чтобы была не такой
     заметной»). Скругление — внутреннее у рамки окна (12 − 1). */
  .bug-box-stripe{
    position:absolute;left:0;right:0;top:0;height:4px;border-radius:11px 11px 0 0;
    background:linear-gradient(90deg,#F88C9E,#FBAB73,#7ADC9E,#82A9F4,#CCB9E2);
  }
  .bug-box-head{display:flex;align-items:center;gap:8px}
  .bug-box-rainbow{width:26px;height:16px;flex-shrink:0}
  /* Заголовок — цветом надписей бокового меню (.nav-item), а не почти чёрным:
     окно должно быть спокойным (просьба владельца 01.10.2026). */
  .bug-box-head b{flex:1;min-width:0;font-family:'Manrope','Inter',sans-serif;font-size:15px;font-weight:800;color:var(--muted)}
  /* Крестик — 32 точки, как у всех окон сайта (.modal-x): при 20 на телефоне в
     него не попасть пальцем (Р-422, проверка роботом, заход 13, 20.09.2026).
     Фона до наведения нет — вид окна от этого не меняется, растёт только цель. */
  .bug-box-x{
    flex-shrink:0;width:32px;height:32px;display:flex;align-items:center;justify-content:center;
    background:none;border:none;padding:0;color:var(--muted2);border-radius:9px;line-height:0;
  }
  .bug-box-x svg{width:16px;height:16px}
  .bug-box-x:hover{color:var(--text);background:var(--border2)}
  /* «Идея» / «Баг»: переключатель-«таблетка», нажата всегда одна половина
     (setBugKind). Выбран владельцем 01.10.2026 из трёх вариантов как спокойный, но
     узнаваемый: маленький, слева, по ширине коротких слов, без синего. Белый
     ползунок переезжает под выбранное; выбранную половину видно ещё и по рисунку —
     лампочка загорается жёлтым, жучок краснеет и подрастает на четверть.
     Половины равные (1fr 1fr), поэтому ползунку хватает «половины дорожки». */
  .bug-kinds{
    position:relative;display:inline-grid;grid-template-columns:1fr 1fr;align-self:flex-start;
    background:#F3F5F8;border-radius:999px;padding:2px;
  }
  .bug-kinds-thumb{
    position:absolute;top:2px;bottom:2px;left:2px;width:calc(50% - 2px);
    background:#fff;border-radius:999px;box-shadow:0 1px 2px rgba(20,50,90,.12);
    transition:transform .25s cubic-bezier(.3,.7,.3,1);
  }
  .bug-kinds[data-kind="bug"] .bug-kinds-thumb{transform:translateX(100%)}
  .bug-kind{
    position:relative;z-index:1;display:flex;align-items:center;justify-content:center;gap:5px;
    min-height:26px;padding:4px 14px;border:none;border-radius:999px;background:none;
    color:#A3B2C2;font-size:12px;font-weight:600;line-height:1.3;transition:color .2s;
  }
  .bug-kind svg{width:15px;height:15px;flex-shrink:0;overflow:visible;transition:transform .2s ease,color .2s}
  .bug-kind:hover{color:#5A6B7E}
  .bug-kind.on{color:#5A6B7E}
  .bug-kind:focus-visible{outline:none;box-shadow:0 0 0 3px var(--primary-100)}
  .bug-kind-glow{opacity:0;transition:opacity .2s}
  .bug-kind.on .bug-kind-glow{opacity:1}
  .bug-kind-bug.on svg{color:#E5485F;transform:scale(1.25)}
  .bug-box textarea{
    width:100%;resize:vertical;min-height:96px;padding:10px 12px;
    border:1.5px solid var(--border);border-radius:12px;background:#fff;color:var(--text);
    outline:none;transition:border-color .15s,box-shadow .15s;
  }
  /* В поле стоят — рамка чуть темнее, без синей подсветки (просьба владельца
     01.10.2026): мигающего курсора хватает, чтобы видеть, куда пишешь. */
  .bug-box textarea:focus{border-color:#C9D6E3}
  .bug-box-foot{display:flex;align-items:center;justify-content:space-between;gap:10px}
  /* Строка замечания стоит над подвалом и во всю ширину; пустая не оставляет
     после себя даже отступа — иначе окно дышало бы на пустом месте. */
  .bug-box-note{font-size:11.5px;color:var(--muted2);min-width:0}
  .bug-box-note:empty{display:none}
  .bug-box-foot .btn{flex-shrink:0}
  /* ---- приложенные картинки ----
     Кнопка со скрепкой намеренно тише «Отправить»: приложить картинку — дело
     доброй воли, а уйти сообщение должно и без неё. */
  .bug-clip{
    display:inline-flex;align-items:center;gap:6px;flex-shrink:0;
    background:none;border:1px solid var(--border);border-radius:9px;
    padding:5px 9px;color:var(--muted2);font-size:12px;line-height:1;
    transition:color .15s,border-color .15s,background .15s;
  }
  .bug-clip[hidden]{display:none}
  .bug-clip svg{width:15px;height:15px}
  /* Наведение — серым, как у кнопок «идея / баг»: синего в окне нет, кроме «Отправить». */
  .bug-clip:hover{color:var(--muted);border-color:#C9D6E3;background:#F6F8FB}
  .bug-clip:focus-visible{outline:none;border-color:var(--primary);box-shadow:0 0 0 3px var(--primary-50)}
  /* Скрепка и подсказка «Ctrl + V» стоят рядом слева, «Отправить» — справа. */
  .bug-box-tools{display:flex;align-items:center;gap:8px;min-width:0}
  /* Подсказка — просто серый текст, не кнопка: тише скрепки, чтобы не спорить
     с ней за внимание. Без мыши её нет (телефон, планшет): клавиш там нет. */
  .bug-paste{font-size:12px;line-height:1;color:var(--muted2);white-space:nowrap;user-select:none;-webkit-user-select:none}
  .bug-paste[hidden]{display:none}
  @media (hover:none),(pointer:coarse){
    .bug-paste{display:none}
  }
  .bug-shots{display:flex;flex-wrap:wrap;gap:8px}
  .bug-shots[hidden]{display:none}
  .bug-shot{
    position:relative;width:56px;height:56px;border-radius:9px;overflow:hidden;
    border:1px solid var(--border);background:var(--border2);
  }
  .bug-shot img{width:100%;height:100%;object-fit:cover;display:block}
  .bug-shot-x{
    position:absolute;top:2px;right:2px;width:18px;height:18px;padding:0;
    border:none;border-radius:50%;background:rgba(17,17,17,.62);color:#fff;
    display:flex;align-items:center;justify-content:center;line-height:0;
  }
  .bug-shot-x svg{width:11px;height:11px}
  .bug-shot-x:hover{background:rgba(17,17,17,.85)}
  /* Перетаскивание подсвечивает всё окно, а не полосу картинок: полосы может
     не быть вовсе, и целиться человеку было бы некуда. */
  .bug-box.drop{border-color:var(--primary);box-shadow:0 0 0 3px var(--primary-50),var(--shadow-md)}
  /* «Спасибо!» накрывает окно целиком, над словом по одной прорисовываются дуги
     радуги — и окно закрывается само (см. sendBugReport). Здесь анимация, а не
     переход: до показа слой скрыт (display:none), и переходу не от чего идти. */
  .bug-thanks{
    /* 11 — внутреннее скругление рамки окна (12 − 1): при 12 из-под углов
       выглядывали концы радужной полоски. */
    position:absolute;inset:0;border-radius:11px;background:var(--card);
    display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;
    font-family:'Manrope','Inter',sans-serif;font-size:18px;font-weight:800;color:var(--text);
  }
  .bug-thanks[hidden]{display:none}
  .bug-thanks svg{width:120px;height:62px;overflow:visible}
  .bug-thanks .fb-arc{animation:fb-draw .55s cubic-bezier(.3,.7,.3,1) both}
  .bug-thanks .fb-a2{animation-delay:.07s}
  .bug-thanks .fb-a3{animation-delay:.14s}
  .bug-thanks .fb-a4{animation-delay:.21s}
  .bug-thanks .fb-a5{animation-delay:.28s}
  @keyframes fb-draw{from{stroke-dashoffset:1}to{stroke-dashoffset:0}}
  /* Тому, кто просил систему не двигать картинки, книжка и радуга появляются
     сразу, без хода. */
  @media (prefers-reduced-motion:reduce){
    .bug-card .fb-bk,.bug-card .fb-cover,.bug-card .fb-inner,.bug-card .fb-arc,.bug-kind svg,.bug-kinds-thumb{transition:none!important}
    .bug-thanks .fb-arc{animation:none!important;stroke-dashoffset:0}
  }

  .sidebar-footer{margin-top:auto;padding:16px;border-top:1px solid var(--border2)}
  /* Полоса «занято из тарифа» над карточкой профиля. Тонкая и без рамки: это
     сводка, а не кнопка, и спорить за внимание с меню и именем ей незачем. */
  .plan-usage{margin-bottom:12px}
  .plan-usage[hidden]{display:none}
  .plan-usage-top{display:flex;align-items:baseline;justify-content:space-between;gap:8px;
    font-size:11.5px;font-weight:600;color:var(--muted);margin-bottom:6px}
  /* Подпись и значок (i) — одной группой: иначе space-between развёл бы их по
     разным краям строки, и значок встал бы посреди пустоты. */
  .plan-usage-name{display:flex;align-items:center;gap:4px;min-width:0}
  /* Сжимается и обрезается подпись, а число видно целиком: ради него строка и
     стоит, и «6 из 5…» было бы хуже, чем обрезанное слово слева. */
  .plan-usage-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .plan-usage-count{flex:none;white-space:nowrap;font-weight:700;color:var(--text)}
  .plan-usage-bar{height:4px;border-radius:999px;background:var(--border2);overflow:hidden}
  /* width:0 — начало отсчёта: без него блок занимал бы всю дорожку, пока число
     не пришло, а первый переход не проигрывался бы вовсе. */
  .plan-usage-fill{display:block;height:100%;width:0;border-radius:999px;background:var(--primary);transition:width .25s ease}
  /* Почти под потолок — оранжевый, под самый — красный: число рядом человек
     прочитает не всегда, а цвет полосы виден краем глаза. */
  .plan-usage.is-warn .plan-usage-fill{background:var(--orange)}
  .plan-usage.is-full .plan-usage-fill{background:var(--red)}
  /* Колонка: строка «кто вошёл» и под ней кнопка на витрину тарифов. */
  .user-box{display:flex;flex-direction:column;gap:9px;padding:10px;border-radius:14px;background:linear-gradient(135deg,var(--primary-50),var(--teal-50))}
  /* Кнопка, а не просто строка: нажатие открывает «Настройки». */
  .user-id{display:flex;align-items:center;gap:10px;width:100%;min-width:0;padding:0;border:none;background:transparent;text-align:left}
  .user-id:disabled{cursor:default}   /* у гостя открывать нечего */
  .user-id:not(:disabled):hover .user-name{color:var(--primary)}
  /* min-width:0 — иначе длинное имя не сжимается, а распирает строку и
     сплющивает кружок в овал; flex:none у кружка нужен по той же причине. */
  .user-id-text{display:block;min-width:0}
  .avatar{flex:none;width:34px;height:34px;border-radius:50%;background:#2b2b2d;color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:13px}
  /* Фотография из аккаунта Google (23.09.2026). Фон снимаем: у картинки с
     прозрачными углами из-под круга иначе торчит тёмное. `cover` — потому что
     не всякая фотография квадратная, а кружок обязан остаться кружком. */
  .avatar.has-photo{background:none;overflow:hidden}
  .avatar img{width:100%;height:100%;object-fit:cover;display:block}
  /* Цвета проставлены явно: строка лежит внутри кнопки, а браузер гасит текст
     выключенной кнопки — у гостя «Вы не вошли» выцветало до нечитаемого. */
  .user-name{display:block;font-weight:600;font-size:13px;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .user-plan{display:flex;align-items:center;gap:4px;font-size:11.5px;color:var(--muted);min-width:0}
  /* display:flex сильнее браузерного [hidden]{display:none} — без этих строк
     пустая строка тарифа и кнопка «Upgrade» остались бы на экране. */
  .user-plan[hidden],.user-upgrade[hidden]{display:none}
  /* Подпись целиком, а сжимается и обрезается название — оно длиннее и его
     переименовывают в админке. */
  .user-plan .plan-text{flex:none}
  /* Имя класса своё, не `.plan-name`: так называется заголовок карточки на
     витрине тарифов (20 px, Manrope) — тот стиль ниже по файлу и перебивал
     этот, название тарифа в углу вырастало вдвое. */
  .user-plan-name{font-size:11.5px;font-weight:700;color:var(--primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  /* Зелёная точка = тариф сейчас действует. */
  .plan-dot{flex:none;width:6px;height:6px;border-radius:50%;background:var(--teal)}
  .user-upgrade{padding:7px 12px;font-size:12.5px}

  /* ============ MAIN ============ */
  /* Колонка, а не просто прокручиваемый блок: подвал стоит внутри main
     последним, и колонка позволяет прижать его к низу окна на коротких экранах
     (жалоба, FAQ, пустые «Мои QR»). Иначе он приезжал бы к самому содержимому
     и висел посреди пустого места. Содержимое длиннее окна — подвал просто
     уходит под него, как раньше: `margin-top:auto` при нехватке места равен
     нулю. */
  /* scrollbar-gutter — место под полосу прокрутки занято всегда, даже когда
     раздел короткий и прокрутки нет. Без этого полоса появлялась только на
     длинных разделах и съедала ~15px: рабочая область гуляла по ширине, а при
     переключении разделов содержимое дёргалось вбок (замечание владельца,
     31.08.2026). Телефонов не касается: там полоса лежит поверх содержимого
     и места не занимает. */
  /* padding-top — место под стеклянную шапку: содержимое начинается под ней,
     а при прокрутке уходит за неё и просвечивает сквозь стекло.
     scroll-padding-top — та же высота для подмотки: без него всё, что сайт
     подводит к верху экрана сам (страница списка кодов, начало партии,
     подсвеченное незаполненное поле), вставало бы ровно под шапку и оказалось
     бы наполовину за стеклом. */
  main{flex:1;min-width:0;height:100%;overflow-y:auto;display:flex;flex-direction:column;scrollbar-gutter:stable;padding-top:var(--topbar-h);scroll-padding-top:var(--topbar-h)}
  /* `flex:0 0 auto` обязателен: без него флекс-колонка ужимала бы высокий
     экран под свою высоту, и низ содержимого обрезался бы вместо прокрутки.
     `width:100%` — тоже: боковые `margin:auto` отменяют растягивание по
     ширине, и без явной ширины экран съёжился бы по содержимому. */
  .screen{display:none;flex:0 0 auto;width:100%;padding:32px 40px 60px;max-width:1232px;margin:0 auto}
  .screen.active{display:block}
  .screen-header{margin-bottom:17px}
  /* Заголовок с действием справа. flex-start, а не center: заголовок с
     подзаголовком выше кнопки, и по центру она уезжала бы к подписи, теряя
     связь с названием. flex-wrap — для узкого экрана: кнопка уходит под
     заголовок, а не сжимает его в столбик из двух букв. */
  .screen-header-act{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;flex-wrap:wrap}
  h1{font-size:27px;font-weight:800;letter-spacing:-.02em;font-family:'Manrope','Inter',sans-serif;color:#0F1E2EF7}
  .subtitle{color:var(--muted);margin-top:5px;font-size:14px}

  .card{background:var(--card);border:1px solid var(--border);border-radius:var(--r);box-shadow:var(--shadow)}

  /* ============ BUTTONS ============ */
  /* единый радиус скругления — одинаковая "острота" для всех кнопок сайта;
     исключение — .segment (Статический/Динамический QR), он выглядит как переключатель */
  .btn{
    display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:10px 20px;border-radius:var(--r-sm);
    font-size:13.5px;font-weight:700;letter-spacing:-.005em;border:1px solid transparent;transition:all .15s;white-space:nowrap;
  }
  .btn svg{width:16px;height:16px}
  .btn-primary{background:linear-gradient(135deg,var(--primary),#4C8CFF);color:#fff;box-shadow:0 6px 16px rgba(47,111,237,.28)}
  .btn-primary:hover{filter:brightness(1.05);transform:translateY(-1px);box-shadow:0 10px 22px rgba(47,111,237,.36)}
  /* зелёная кнопка генерации */
  /* Подпись переносится, когда в строку не встаёт, а сама кнопка не шире места,
     которое ей дали: с поднятым в браузере минимальным размером шрифта
     «Generate a dynamic QR Code» занимала 368 точек в строке шириной 328 —
     хвост подписи уходил за правый край экрана, и страница ехала вбок
     (Р-469, проверка роботом, заход 14, 21.09.2026). При обычном шрифте места
     хватает и кнопка остаётся однострочной; при крупном она станет в две
     строки — это и есть задуманное. */
  .btn-generate{
    background:linear-gradient(100deg,var(--gen),var(--gen-2));color:#fff;padding:12px 39px;
    box-shadow:0 6px 16px rgba(95,199,82,.28);
    max-width:100%;min-width:0;white-space:normal;text-wrap:balance;text-align:center;overflow-wrap:anywhere;
  }
  .btn-generate:hover{filter:brightness(1.04);transform:translateY(-1px);box-shadow:0 10px 22px rgba(95,199,82,.34)}
  .btn-secondary{background:#fff;color:var(--primary);border-color:var(--primary-100)}
  .btn-secondary:hover{background:var(--primary-50);border-color:var(--primary)}
  .btn-teal{background:linear-gradient(135deg,var(--teal),#4ADE80);color:#fff;box-shadow:0 6px 16px rgba(34,197,94,.28)}
  .btn-teal:hover{transform:translateY(-1px);box-shadow:0 10px 22px rgba(34,197,94,.36)}
  /* необратимое действие в модалке подтверждения — заливкой, чтобы не спутать с «Отмена» */
  .btn-danger{background:var(--red);color:#fff;box-shadow:0 6px 16px rgba(220,38,38,.24)}
  .btn-danger:hover{filter:brightness(1.06);transform:translateY(-1px);box-shadow:0 10px 22px rgba(220,38,38,.3)}
  .btn-ghost{background:transparent;color:var(--muted);padding:7px 10px}
  .btn-ghost:hover{background:#F0F6FF;color:var(--text)}
  .btn-lg{padding:12px 22px;font-size:14px}
  .btn-block{width:100%}
  .btn:disabled{opacity:.4;cursor:not-allowed;pointer-events:none;box-shadow:none!important;transform:none!important}
  /* Выключено «мягко»: выглядит ровно как :disabled выше, но кнопка остаётся
     живой для мыши и пальца. Нужно там, где на серой кнопке висит объяснение,
     почему она серая (data-tip): настоящая disabled не отдаёт событий мыши
     вовсе, и подсказку на ней никто никогда не увидит. Само нажатие отбивает
     обработчик — .off рисует, но не запрещает. Класс, а не [aria-disabled]:
     тем же атрибутом выключена галочка «Применить оформление», а у неё свой
     вид на каждое состояние, и общее правило погасило бы ей зелёный. */
  .btn.off{opacity:.4;cursor:not-allowed;box-shadow:none!important;transform:none!important}
  /* Подсветку под мышью снимаем вручную: pointer-events кнопке оставлены, и без
     этой строки выключенная кнопка отвечала бы на наведение как живая. Сдвиг и
     тень сняты выше через !important. */
  .btn.off:hover{filter:none}
  /* Ещё мягче: кнопка выглядит обычной, но нажимать её сейчас бесполезно —
     курсор перечёркнут, подсказка называет причину, а нажатие всё-таки доходит
     и делает единственное полезное, что может, — подсвечивает незаполненное.
     Так выключена «Сгенерировать» на пустой форме: гасить её нельзя, иначе
     нечем было бы вызвать эту самую подсветку. Приподнимание под мышью снимаем —
     «жмите меня» и перечёркнутый курсор на одной кнопке спорят друг с другом. */
  .btn.blocked{cursor:not-allowed}
  .btn.blocked:hover{filter:none;transform:none}

  /* ============ TOOLTIP ============ */
  /* Подсказка — светлая карточка того же рода, что и всё остальное на экране:
     тёмный прямоугольник посреди белого списка читался как ошибка, а не как
     пояснение. Появляется не мгновенно: задержка в .12s отсекает облачка,
     которые человек вызывает случайно, проводя мышью через ряд кнопок, а
     .16s проявления не дают ей выпрыгнуть. Прячется без задержки — уводя
     курсор, человек уже принял решение. */
  .tip{position:relative;display:inline-flex}
  .tip .tip-box{
    position:absolute;bottom:calc(100% + 10px);left:50%;transform:translateX(-50%) translateY(4px);
    background:#fff;color:var(--text);font-size:12.5px;line-height:1.5;padding:10px 13px;border-radius:12px;
    border:1px solid var(--border);
    width:280px;opacity:0;pointer-events:none;transition:opacity .16s ease,transform .16s ease;z-index:100;
    box-shadow:0 10px 28px rgba(15,30,46,.14);font-weight:500;
  }
  /* Уголок — тот же белый треугольник; тонкая грань по краю дорисована тенью:
     рамку у собранного из бордеров треугольника иначе не получить. */
  .tip .tip-box::after{
    content:'';position:absolute;top:100%;left:50%;transform:translateX(-50%);
    border:6px solid transparent;border-top-color:#fff;filter:drop-shadow(0 1px 0 var(--border));
  }
  /* Со значком — отдельным классом, а не по умолчанию: подсказка «Подробнее»
     на экране создания значка не имеет, и flex ей ни к чему.
     align-items:flex-start — иначе кружок «i» уезжал бы в центр двух строк текста. */
  .tip .tip-box.has-ico{display:flex;align-items:flex-start;gap:8px}
  .tip-ico{width:15px;height:15px;flex-shrink:0;margin-top:2px;opacity:.75}
  /* :focus — ради телефона: наводить там нечем, а подсказка нужна не меньше.
     Обёртке проставлен tabindex, поэтому тап по ней открывает подсказку, а тап
     мимо — закрывает. Клавиатуре это заодно даёт тот же доступ, что и мыши. */
  .tip:hover .tip-box,.tip:focus .tip-box{opacity:1;transform:translateX(-50%) translateY(0);transition-delay:.12s}

  /* ---- подсказки на data-tip ----
     Одно облачко на всю страницу, которое JS переставляет к нужной кнопке.
     Системный title рисует браузер: тёмный квадрат без переносов, всплывающий
     через секунду и только под мышью. Здесь тот же светлый вид, что и у .tip,
     но без обёртки вокруг каждой кнопки. */
  .tipx{
    /* Выше модалок (900), но ниже тостов (1000): подсказка нужна и на кнопке
       внутри окна, а перекрывать сообщение о сохранении ей нечего. */
    position:fixed;z-index:950;max-width:280px;padding:9px 12px;border-radius:12px;
    background:#fff;color:var(--text);border:1px solid var(--border);
    box-shadow:0 10px 28px rgba(15,30,46,.14);
    font-size:12.5px;line-height:1.45;font-weight:500;
    pointer-events:none;opacity:0;transform:translateY(4px);
    transition:opacity .16s ease,transform .16s ease;
    /* Длинные адреса рвём по любому месту: ссылка без пробелов иначе
       растягивала бы облачко за край экрана. */
    overflow-wrap:anywhere;
  }
  .tipx.show{opacity:1;transform:none}
  /* Уголок — повёрнутый квадрат: половина рамки у него лишняя, она пересекала
     бы карточку насквозь. */
  .tipx-arrow{
    position:absolute;width:9px;height:9px;background:#fff;
    border:1px solid var(--border);transform:rotate(45deg);
    bottom:-5.5px;border-top:0;border-left:0;
  }
  .tipx.is-below .tipx-arrow{bottom:auto;top:-5.5px;border:1px solid var(--border);border-bottom:0;border-right:0}

  /* ============ CREATE SCREEN ============ */
  /* Полоса типов — два ряда одной сетки. Основной ряд стоит одной строкой и
     делит её ширину поровну: числом колонок правит --type-cols, его ставит
     renderTypeGrid по числу плиток первого ряда — добавится плитка, строка
     сожмётся сама, без правки этого места. Квадратность плиток
     (aspect-ratio) — пожелание, а не запрет: на узком окне подпись вроде
     «Бизнес-карточка» вытянет плитку выше квадрата, и это лучше обрезанного
     слова.

     Второй ряд (.row2 — Audio, WhatsApp, соцсети, Email, Телефон, Локация,
     Текст) до нажатия на язычок .type-more спрятан display:none, а не
     прозрачностью: сетка честно схлопывается до одной строки. Он и остаётся
     свёрнутым по умолчанию: это витрина «а ещё умеем», и держать её всегда
     открытой значило бы удвоить полосу ради типов, которыми пользуются реже.
     С 12.09.2026 рядов по одиннадцать плиток в каждом (Audio переехал вниз),
     но равнять их и впредь никто не обязан: колонок столько, сколько плиток
     в первом ряду, и второй просто встаёт под ним. */
  .type-grid{
    display:grid;grid-template-columns:repeat(var(--type-cols,11),minmax(0,1fr));
    gap:8px;margin-bottom:17px;
  }
  .type-grid-frame:not(.expanded) .type-card.row2{display:none}
  .type-grid-frame.expanded .type-card.row2{animation:typeRowIn .18s ease}
  @keyframes typeRowIn{from{opacity:0;transform:translateY(-5px)}to{opacity:1;transform:none}}
  /* Плитка бывает и ссылкой на витрину своего типа (renderTypeGrid), поэтому
     здесь снято подчёркивание: у ссылки оно от браузера, у кнопки его нет, а
     выглядеть они обязаны одинаково — человек не должен видеть в полосе типов
     две разные вещи. */
  .type-card{
    background:#fff;border:1px solid var(--border);border-radius:var(--r-md);padding:10px 5px;text-align:center;aspect-ratio:1;
    display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;transition:all .15s;color:var(--muted);position:relative;
    text-decoration:none;
  }
  /* Гнездо под значок шире, чем выше, и это не опечатка: значки обрезаны по
     самому рисунку (см. ICONS в app.js), поэтому по высоте они выходят
     одинаковыми, а по ширине — какими нарисованы. Самый широкий, YouTube,
     занимает 1.39 высоты; в квадратном гнезде его обрезало бы по бокам, а с
     запасом по ширине все значки встают на одну высоту. */
  .type-card svg{width:28px;height:19px}
  .type-card span{font-size:11px;font-weight:600;color:var(--text);line-height:1.25;text-wrap:balance}
  .type-card:hover{border-color:var(--primary-100);box-shadow:var(--shadow-md);transform:translateY(-2px)}
  .type-card.selected{border-color:transparent;background:linear-gradient(180deg,var(--pick),var(--pick-2));color:#fff;box-shadow:0 10px 24px rgba(147,106,186,.32)}
  .type-card.selected span{color:#fff}

  /* тонкая рамка вокруг всего выбора типа QR */
  .type-grid-frame{position:relative;border:1px solid var(--border);border-radius:15px;padding:14px;margin-bottom:17px;background:rgba(255,255,255,.55)}
  .type-grid-frame .type-grid{margin-bottom:0}

  /* Язычок «ещё типы» — не нашлёпка под рамкой, а выемка в её нижней кромке:
     кромка ныряет вниз трапецией и возвращается обратно.

     Сама кнопка — прямоугольник заметно выше выемки: он поднимается на 15px
     внутрь рамки, в её нижний отступ. Это площадь для мыши: попасть в полоску
     15px высотой трудно, а поле над выемкой всё равно пустое — плитки до него
     не достают. Видимого у кнопки только одно — полукруглое свечение над
     кромкой, продолжение белого градиента из выемки; к краям прямоугольника
     оно сходит на нет, поэтому углов не видно и обрезать их незачем.

     Выемку рисует .type-more-notch, и на ней держится вся иллюзия:
       * фон — тот же, что у рамки: полупрозрачная белая плёнка поверх фона
         страницы (--page-bg), оба закреплены за экраном и потому совпадают.
         Этот же фон закрашивает отрезок кромки над выемкой;
       * поверх фона — белый градиент (::before): у дна почти белый, к устью
         сходит почти на нет, дальше вверх его подхватывает свечение кнопки.
         Плоская заливка выглядела вырезанной из бумаги, градиент сажает
         выемку на место;
       * форму режет clip-path, а линию рисует та же кривая внутри <svg>,
         вдвое толще: наружную половину штриха срезает тот же clip-path, и
         наружу выходит ровно 1px — как у самой рамки;
       * координаты у clip-path и у svg общие (размер в пикселях, viewBox
         один к одному), поэтому кривую правят в двух местах одинаково.
     Глубина выемки (15px) меньше отступа рамки снизу (17px) — язычок не
     наезжает на карточку формы. Стоит справа, под последней плиткой. */
  .type-more{
    position:absolute;right:34px;bottom:-16px;width:62px;height:31px;
    display:flex;align-items:flex-end;justify-content:center;
    border:0;padding:0;color:var(--muted);cursor:pointer;transition:color .15s;
    background:radial-gradient(30px 8px at 50% 48%, rgba(255,255,255,.22), rgba(255,255,255,0) 100%);
  }
  .type-more:hover{color:var(--primary)}
  .type-more-notch{
    position:absolute;left:0;bottom:0;width:62px;height:16px;
    background:linear-gradient(rgba(255,255,255,.55),rgba(255,255,255,.55)),var(--page-bg);
    background-attachment:fixed;
    clip-path:path('M0 0 L9 10.5 Q11 15 16.5 15 H45.5 Q51 15 53 10.5 L62 0 Z');
  }
  .type-more-notch::before{
    content:'';position:absolute;inset:0;
    background:linear-gradient(to top,rgba(255,255,255,.95),rgba(255,255,255,.22));
  }
  .type-more-edge{position:absolute;inset:0;fill:none;stroke:var(--border);stroke-width:2;transition:stroke .15s}
  .type-more:hover .type-more-edge{stroke:var(--primary-100)}
  .type-more-arrow{position:relative;width:15px;height:15px;transition:transform .2s}
  .type-grid-frame.expanded .type-more-arrow{transform:rotate(180deg)}

  .create-layout{display:flex;flex-direction:column;gap:17px}
  .qr-settings-row{display:grid;grid-template-columns:320px 1fr;gap:17px;align-items:start}

  /* ====== РЕЖИМ ПРАВКИ ОФОРМЛЕНИЯ ======
     Тот же экран «Создать QR» без полей содержимого: у сохранённого кода
     содержимое не меняется. Прячем именно выбор типа и карточку формы вместе
     с кнопкой генерации — редактор ниже остаётся ровно тем же. */
  body:not(.design-edit) .js-edit-only{display:none}
  /* Оформление партии правится в том же редакторе, но формат и кнопка
     скачивания там лишние: скачивать нечего — кодов ещё нет. (Названия в
     редакторе нет вовсе, ни у партии, ни у одиночного кода — см. .gen-row.) */
  body.batch-design .fmt-row,
  body.batch-design .demo-note{display:none}
  body.design-edit .type-grid-frame,
  body.design-edit .form-card{display:none}
  /* Правка собранной страницы — карточки или списка соцсетей — тот же режим, но
     с открытым конструктором: у этих типов содержимое как раз и меняют после
     печати, ради этого они и сделаны. Выбор типа остаётся скрытым — код уже
     выпущен, и стать из карточки Wi-Fi он не может, — а вместе с ним и
     подвкладки: «своя ссылка» здесь означала бы стереть страницу исподтишка, и
     для этого есть отдельный путь. */
  body.design-edit.page-edit .form-card{display:block}
  body.page-edit #form-vcard .subtabs,
  body.page-edit #form-menu .subtabs,
  body.page-edit #formTitle{display:none}
  /* Полоса режима лежит внутри той же карточки формы, и вместе с ней вернулась
     бы кнопка «Сгенерировать». Генерировать здесь нечего: код уже выпущен, и
     правка уходит кнопкой «Сохранить изменения» внизу экрана. */
  body.page-edit .mode-zone{display:none}
  /* Замена файла у кода с видео, аудио, PDF, файлом или меню — тот же режим,
     что и правка собранной страницы, только вместо конструктора открыта
     площадка загрузки типа. Правила зеркалят page-edit и по тем же причинам:
     тип уже выпущенного кода не меняется, генерировать нечего, а подвкладка
     «ссылка на онлайн-меню» здесь означала бы стереть файл исподтишка. */
  body.design-edit.media-edit .form-card{display:block}
  body.media-edit #formTitle{display:none}
  body.media-edit .mode-zone{display:none}
  body.media-edit #form-menu .subtabs{display:none}
  .edit-actions{display:flex;justify-content:flex-end;align-items:center;gap:12px;flex-wrap:wrap}
  /* Правая колонка ряда «превью + настройки»: вкладки и под ними кнопки правки.
     Колонка тянется на высоту ряда, кнопки прижаты к её низу — и кончаются на
     одной линии с карточкой превью. Раньше они стояли отдельной строкой под всем
     рядом и падали ниже высокого превью с рамкой (слово владельца 18.09.2026).
     Выше превью оказываются только вкладки — тогда кнопки просто под ними. */
  .settings-col{display:flex;flex-direction:column;gap:17px;align-self:stretch;min-width:0}
  .settings-col .edit-actions{margin-top:auto}

  /* ====== ЗОНА «РЕЖИМ + ГЕНЕРАЦИЯ» ======
     Сначала выбор статический/динамический, затем кнопка генерации. */
  .mode-zone{margin-top:8px;padding-top:8px}
  /* У видео и аудио слева от телефона стоит одна площадка загрузки, а корпус
     высокий — под площадкой оставалось пустое поле, и кнопка генерации висела
     ниже телефона: низ карточки читался ступенькой. Поднимаем полосу с кнопкой
     в это самое пустое поле, ровно на её собственную высоту вместе с зазором
     (43px кнопки + 8px внутреннего отступа полосы + 8px зазора = 59px, то есть
     margin-top с +8 на −51), — и кнопка заканчивается на одной линии с низом
     телефона.
     Работает, пока левый столбец ниже телефона: у этих двух типов там всегда
     одно поле. Остальных типов правило не касается — там левый столбец длинный
     и поднимать кнопку некуда. Одноколоночную раскладку отменяем ниже. */
  #form-video.active ~ .mode-zone,
  #form-audio.active ~ .mode-zone{margin-top:-51px}
  /* С загруженным файлом левый столбец уже не короче телефона: под площадкой встают плашка
     файла, тумблер «Show file name in player» с полем и зелёная строка «можно заменить» — и
     поднятая кнопка наезжала на эту строку (снимок владельца 20.10.2026). Тогда подъём снимаем:
     кнопка встаёт под поля обычным отступом. Пока файла нет, всё как было. */
  #form-video.active:has(.file-chip.show) ~ .mode-zone,
  #form-audio.active:has(.file-chip.show) ~ .mode-zone{margin-top:8px}
  .more-link{color:var(--primary);font-weight:700;text-decoration:underline;text-underline-offset:2px}

  .mode-cards{display:flex;flex-wrap:wrap;gap:12px;margin-bottom:16px;align-items:stretch}
  .mode-card{
    display:flex;align-items:center;gap:12px;text-align:left;font-family:inherit;background:#fff;
    border:1px solid var(--border);border-radius:12px;padding:9px 16px;transition:all .15s;
  }
  .mode-card:hover{border-color:var(--gen)}
  .mode-card.active{background:var(--gen-soft);border-color:var(--gen)}
  .mc-check{
    width:16px;height:16px;border-radius:50%;border:1.5px solid #C6D6EA;background:#fff;flex-shrink:0;transition:all .15s;
  }
  .mode-card.active .mc-check{border-color:var(--gen);background:var(--gen);box-shadow:inset 0 0 0 3px #fff}
  .mc-title{font-size:14px;font-weight:700;white-space:nowrap}
  .mc-desc{font-size:12.5px;color:var(--muted);line-height:1.4}
  /* «динамическая» карточка несёт описание внутри, но рамку тянуть на всю
     ширину незачем — она заканчивается там же, где текст */
  /* Скругление то же, что у соседней карточки (12px, а не 10): карточки стоят
     рядом, и на телефоне, где они встают друг под друга, разная «острота»
     углов читалась как разный вид элемента (30.09.2026). */
  .mode-card-dyn{flex:0 1 auto;max-width:100%;border-radius:12px;border-color:var(--gen-border);background:#FFFFFF28;padding:9px 18px}
  .mode-card-dyn.active{background:var(--gen-soft)}

  /* ---- пара рисунков в подсказке «Подробнее» на динамической карточке ----
     Уменьшенная копия картинки из блока «Чем отличается статический QR от
     динамического», который лежит под генератором на страницах типов
     (блоки/общие-блоки.html — там же исходные кадры). Здесь она без списков
     под собой: подсказка объясняет словами, рисунки показывают то же самое
     глазами. Слева статический — код на цепи с замком и перечёркнутая
     аналитика, справа динамический — сменяющиеся цели и живые столбики; тем же
     порядком, что и карточки режима под подсказкой.
     Половины тянутся по ширине окошка (minmax(0,1fr)), а не заданы числом:
     подсказка сужается на узком экране, и рисунки сужаются вместе с ней.
     Круг — 8 секунд, четыре цели по 2 секунды; рычаги живут своим кругом ровно
     в 2 секунды и отходят, когда цель трогается. Меняете длительность — меняйте
     в обоих местах, иначе рычаг потянется к пустоте.
     Пока подсказка закрыта, кадры стоят: крутить анимацию в невидимом окошке
     значит впустую греть телефон. Трогается с места вместе с показом. */
  /* Облачко меряется по карточке, а не по слову «Подробнее»: правой стороной
     оно встаёт на правый край карточки, а «Подробнее» — последнее, что в
     карточке есть, поэтому облачко оказывается ровно над ним и при этом никогда
     не вылезает за край экрана (со словом посередине вылезало: картинок стало
     две, и облачко стало шире). Точкой отсчёта поэтому служит карточка, а не
     само слово. */
  .mode-card-dyn{position:relative}
  .mode-tip{position:static}
  .mode-tip .tip-box{left:auto;right:0;width:320px;max-width:100%;transform:translateY(4px);text-align:center;color:var(--muted)}
  .mode-tip:hover .tip-box,.mode-tip:focus .tip-box{transform:translateY(0)}
  .mode-tip .tip-box::after{left:auto;right:48px;transform:none}
  .ma-defs{position:absolute;width:0;height:0;overflow:hidden}
  .ma-pair{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:12px;margin-top:12px}
  .ma{display:block;width:100%;height:auto;--ma-dur:8s}
  .ma-qr{filter:drop-shadow(0 2px 6px rgba(20,50,90,.13))}
  /* Подпись лежит на коде плашкой — как полоса «sample» на превью генератора:
     белое поле, тонкая грань, мягкая тень. Код под ней остаётся настоящим, но
     сканировать эту миниатюру никто и не станет. */
  .ma-tag{filter:drop-shadow(0 2px 6px rgba(15,30,46,.22))}
  /* левая половина: замок на цепи еле качается — статический код не мёртвый,
     он просто прикован к одной ссылке */
  .ma-chain{transform-box:fill-box;transform-origin:50% 0%;animation:ma-sway 4.6s ease-in-out infinite alternate}
  @keyframes ma-sway{from{transform:rotate(-1.4deg)}to{transform:rotate(1.4deg)}}
  /* правая половина: цели приезжают одна за другой, рычаги подключают их,
     столбики статистики дышат */
  .ma-slot{opacity:0;animation:ma-slot var(--ma-dur) linear infinite}
  .ma-slot-2{animation-delay:2s}
  .ma-slot-3{animation-delay:4s}
  .ma-slot-4{animation-delay:6s}
  @keyframes ma-slot{
    0%      {opacity:0;transform:translateX(240px);animation-timing-function:cubic-bezier(.18,.72,.3,1)}
    2%      {opacity:1;animation-timing-function:cubic-bezier(.18,.72,.3,1)}
    4.5%    {opacity:1;transform:translateX(0)}
    21%     {opacity:1;transform:translateX(0);animation-timing-function:cubic-bezier(.55,0,.8,.4)}
    23%     {opacity:1;animation-timing-function:cubic-bezier(.55,0,.8,.4)}
    25%     {opacity:0;transform:translateX(-240px)}
    100%    {opacity:0;transform:translateX(-240px)}
  }
  .ma-arm{animation:ma-arm 2s linear infinite}
  @keyframes ma-arm{
    0%,15%   {transform:translateY(-7px)}
    22%,82%  {transform:translateY(0)}
    89%,100% {transform:translateY(-7px)}
  }
  .ma-arm-dn{animation:ma-arm-dn 2s linear infinite}
  @keyframes ma-arm-dn{
    0%,15%   {transform:translateY(7px)}
    22%,82%  {transform:translateY(0)}
    89%,100% {transform:translateY(7px)}
  }
  .ma-live{opacity:0;animation:ma-live 2s linear infinite}
  @keyframes ma-live{0%,20%{opacity:0}26%,80%{opacity:.45}86%,100%{opacity:0}}
  .ma-bar{transform-box:fill-box;transform-origin:50% 100%;animation:ma-bar 2.6s ease-in-out infinite alternate}
  @keyframes ma-bar{from{transform:scaleY(.72)}to{transform:scaleY(1)}}
  .ma-bar-2{animation-duration:3.1s;animation-delay:-.4s}
  .ma-bar-3{animation-duration:2.3s;animation-delay:-.9s}
  .ma-bar-4{animation-duration:3.5s;animation-delay:-.2s}
  .ma-bar-5{animation-duration:2.9s;animation-delay:-1.1s}
  .ma-bar-6{animation-duration:2.1s;animation-delay:-.6s}
  /* Стоп-кадр, пока на «Подробнее» не навели. Правило обязано идти после
     сокращённого animation, иначе оно бы его сбросило. */
  .ma-chain,.ma-slot,.ma-arm,.ma-arm-dn,.ma-live,.ma-bar{animation-play-state:paused}
  .mode-tip:hover .ma-chain,.mode-tip:hover .ma-slot,.mode-tip:hover .ma-arm,
  .mode-tip:hover .ma-arm-dn,.mode-tip:hover .ma-live,.mode-tip:hover .ma-bar,
  .mode-tip:focus .ma-chain,.mode-tip:focus .ma-slot,.mode-tip:focus .ma-arm,
  .mode-tip:focus .ma-arm-dn,.mode-tip:focus .ma-live,.mode-tip:focus .ma-bar{animation-play-state:running}
  /* Тем, кто выключил движение в системе: тот же рисунок неподвижно — первая
     цель подключена, полоса «живого» горит, столбики стоят. */
  @media (prefers-reduced-motion:reduce){
    .ma-chain,.ma-slot,.ma-arm,.ma-arm-dn,.ma-live,.ma-bar{animation:none}
    .ma-slot{opacity:0}
    .ma-slot-1{opacity:1}
    .ma-live{opacity:.45}
  }

  /* ---- строка «сгенерировать» и «как назвать» ----
     Название стоит справа от кнопки, а не отдельной полосой: назвать код и
     нажать «Сгенерировать» человек делает подряд. Пунктир вместо сплошной
     рамки — тем же приёмом, каким название было оформлено под превью: поле
     не обязательное (пустое означает имя по содержимому, defaultName в
     app.js), и вставать в один ряд с полями, которые заполнить надо, ему
     нельзя. Ширина повторяет кнопку слева, чтобы полоса читалась одним
     предметом, а не тремя.
     Ширина задана числом: кнопка меряется по своей подписи, а подпись живая
     (у части типов syncBuilderMode пишет в неё другой текст) — привязать одно
     к другому средствами CSS нечем. При переводе сайта проверить заново.
     Поле стоит внутри карточки формы, поэтому в правке сохранённого кода
     скрывается вместе с ней само — так и задумано: переименовывают код
     карандашом на его карточке в «Моих QR». */
  .gen-row{display:flex;flex-wrap:wrap;align-items:center;gap:12px}
  /* Обёртка поля с дискеткой (с 15.10.2026, saveCodeName в app.js): ширину
     в строке держит она, поле занимает её целиком. Справа у поля запас под
     дискетку всегда, а не только пока она видна, — иначе длинное название
     прыгало бы под пальцем в ту секунду, когда она появляется. */
  .gen-name{position:relative;display:flex;flex:0 1 233px;min-width:150px}
  .gen-name[hidden]{display:none}
  .gen-name .gen-name-input{padding-right:40px}
  .gen-name-save{
    position:absolute;right:5px;top:50%;transform:translateY(-50%);
    width:32px;height:32px;padding:0;display:grid;place-items:center;
    border:0;border-radius:9px;background:transparent;color:var(--primary);cursor:pointer;
    transition:background .15s;
  }
  .gen-name-save[hidden]{display:none}
  .gen-name-save:hover{background:var(--primary-50)}
  .gen-name-save:focus-visible{outline:2px solid var(--primary);outline-offset:1px}
  .gen-name-save svg{width:18px;height:18px}
  .gen-name-save .ico-done{display:none}
  .gen-name-save.done{color:var(--green);background:transparent;cursor:default}
  .gen-name-save.done .ico-disk{display:none}
  .gen-name-save.done .ico-done{display:block;animation:nameSaved .25s ease-out}
  @keyframes nameSaved{from{transform:scale(.4);opacity:0}to{transform:none;opacity:1}}
  .gen-name-input{
    flex:1;min-width:0;
    /* Высоты здесь нет намеренно: она набирается сама и совпадает с карточками
       «Статический / Динамический» строкой выше — те же поля 9px, тот же шрифт
       14px, та же межстрочная. Числом её задавать нельзя: карточки на узком
       окне становятся выше (описание переносится), и подогнанное под широкий
       экран число разъехалось бы с ними. */
    padding:9px 16px;line-height:normal;
    font-family:inherit;font-size:14px;font-weight:700;color:var(--text);
    background:transparent;border:1.5px dashed var(--border);border-radius:12px;
    outline:none;transition:all .15s;
  }
  .gen-name-input::placeholder{color:var(--muted2);font-weight:600}
  .gen-name-input:hover{border-color:var(--primary-100)}
  .gen-name-input:focus{border-style:solid;border-color:var(--primary);box-shadow:0 0 0 3px var(--primary-50)}
  /* Подпись на рамке поля: видна, пока в поле есть текст, — подсказка внутри с первым
     знаком пропадает (21.10.2026, решение владельца; разметка — index.html). Фон —
     как у карточки формы, иначе пунктир рамки шёл бы сквозь буквы. */
  .gen-name-label{
    position:absolute;left:12px;top:0;transform:translateY(-50%);
    padding:0 5px;background:var(--card);color:var(--muted2);
    font-size:11px;font-weight:600;line-height:1;white-space:nowrap;
    pointer-events:none;opacity:0;transition:opacity .15s,color .15s;
  }
  .gen-name:has(.gen-name-input:not(:placeholder-shown)) .gen-name-label{opacity:1}
  .gen-name:has(.gen-name-input:focus) .gen-name-label{color:var(--primary)}
  .gen-name-input[hidden] ~ .gen-name-label{display:none}

  .form-card{padding:26px}
  .form-section-title{font-size:15px;font-weight:700;margin-bottom:16px;display:flex;align-items:center;gap:8px;font-family:'Manrope','Inter',sans-serif}
  /* «Как это работает» рядом с заголовком (пока только у письма — setType в
     app.js). Вид маленькой ссылки, а не кнопки: она ничего не делает, кроме
     облачка-подсказки, и спорить с жирным заголовком ей не положено. */
  .how-tip{border:0;background:none;padding:0;cursor:pointer;
    font:inherit;font-size:12.5px;font-weight:600;color:var(--primary)}
  .how-tip:hover{color:var(--primary-dark);text-decoration:underline}
  /* Место под палец (Р-12, проверка роботом 13.09.2026): на телефоне подсказка была
     75×19 точек при норме не меньше 24. Вид ссылки прежний — растёт только высота. */
  @media (max-width:900px){
    .how-tip{display:inline-flex;align-items:center;min-height:32px}
    /* Та же норма у галочки с подписью («Скрытая сеть» у Wi-Fi, галочки в
       профиле): строка была 22 точки в высоту. Ширина зоны нажатия остаётся
       по решению владельца 04.09.2026 — галочка с подписью, не вся колонка.
       Р-50 Д8, проверка роботом, заход 3, 17.09.2026. */
    .check-row{min-height:24px}
    /* И у переключателей оформления — «Градиент», «Цвет центра глаза», «Градиент
       рамки»: на телефоне строка была 21 точка при том же пределе 24
       (Р-81 и Р-92, проверка роботом, заход 4, 17.09.2026; у всех трёх
       переключателей беда одна, поэтому и правило одно). Сам переключатель
       остаётся прежнего размера — подрастает только зона нажатия вокруг него. */
    .grad-check{min-height:24px}
  }
  .field{margin-bottom:16px}
  .field label{display:block;font-size:13px;font-weight:600;margin-bottom:6px;color:#33424F}
  /* Подпись под полем: слева ошибка, справа счётчик символов. Строка есть всегда,
     даже пустая, — иначе появление ошибки дёргало бы кнопки модалки вниз. */
  .field-foot{display:flex;align-items:center;gap:10px;min-height:18px;margin-top:6px;font-size:12px}
  .field-error{flex:1;min-width:0;color:var(--red);font-weight:600}
  .field-counter{flex-shrink:0;color:var(--muted2);font-variant-numeric:tabular-nums}
  .field-counter.at-limit{color:var(--orange-dark);font-weight:700}
  /* Строка под полем с потолком длины: слева — про вставку, которая не
     поместилась, справа — счётчик «набрано / потолок». Появляется с 90 %
     потолка (решение владельца 21.09.2026, Р-464) и до этого места не
     занимает: строку рисует сам скрипт (syncFieldCap в app.js), и пока она
     скрыта, поля под ней не сдвигаются. Название кода стоит в одной строке с
     кнопкой генерации, и его строка встаёт под всей этой строкой, а не третьим
     столбиком в ней — место выбирает fieldCapBox. */
  .field-cap{display:flex;align-items:baseline;gap:10px;margin-top:6px;font-size:12px}
  .field-cap[hidden]{display:none}
  .field-cut{flex:1;min-width:0;color:var(--muted)}
  .field-cap .field-counter{margin-left:auto}
  .hint{font-size:12px;color:var(--muted);margin-top:5px;text-wrap:pretty}
  .hint-info{display:flex;align-items:flex-start;gap:6px}
  .hint-info svg{width:14px;height:14px;flex-shrink:0;margin-top:1px;color:var(--muted2)}
  .input,select.input,textarea.input{
    width:100%;padding:10px 14px;border:1px solid var(--border);border-radius:12px;background:#fff;color:var(--text);
    transition:all .15s;outline:none;
  }
  .input:focus{border-color:var(--primary);box-shadow:0 0 0 3px rgba(47,111,237,.14)}
  .input::placeholder{color:var(--muted2)}
  .input[readonly]{background:#F8FAFC;color:var(--muted)}
  .input:disabled{background:#F1F4F8;color:var(--muted2);cursor:not-allowed}
  /* ---- «это не похоже на адрес» ----
     Красная рамка на поле соцсети, пока в нём не ссылка (см. markBadUrl в
     app.js). Красный, а не жёлтый: в отличие от пустого поля, здесь написанное
     придётся исправить — кнопка с таким адресом на странице никуда не ведёт. */
  .input.is-bad-url{border-color:var(--red)}
  .input.is-bad-url:focus{border-color:var(--red);box-shadow:0 0 0 3px rgba(220,38,38,.13)}
  /* ---- «вот это и не заполнено» ----
     Вспышка по нажатию «Сгенерировать» на пустой форме. Уведомление в углу
     называет нехватку словами, но глазами человек в этот момент смотрит на
     кнопку, а не в угол; жёлтое пятно доводит взгляд до самого поля. Жёлтый, а
     не красный: человек ничего не сломал, ему просто нечего пока сохранять.

     Гаснет само, за полторы секунды, и след после себя не оставляет: цвет и
     рамка в последних кадрах возвращаются к собственным значениям поля — те и
     другие заданы только в начальных кадрах. Поэтому одна анимация годится и
     полю ввода, и площадке загрузки, у которых рамка и фон разные. Снимает
     класс сам JS по окончании анимации (markEmptyField). */
  @keyframes needsFill{
    0%,60%{background:#FFF8E1;border-color:#F0B429;box-shadow:0 0 0 5px rgba(240,180,41,.24)}
    100%{box-shadow:0 0 0 0 rgba(240,180,41,0)}
  }
  .needs-fill{animation:needsFill 1.5s ease-out}
  textarea.input{resize:vertical;min-height:80px;font-family:inherit}
  .row-2{display:grid;grid-template-columns:1fr 1fr;gap:14px}

  .qr-form{display:none}
  .qr-form.active{display:block}

  /* По ширине текста, а не колонки (решение владельца, 30.08.2026): растянутая
     на весь экран зелёная полоса читалась плашкой раздела, а это подсказка. */
  .dyn-note{
    display:none;background:var(--teal-50);border:1px solid #BDEECB;color:#0E7A45;border-radius:12px;
    padding:10px 14px;font-size:12.5px;margin-top:14px;align-items:center;gap:8px;
    width:fit-content;max-width:100%;
  }
  .dyn-note svg{width:15px;height:15px;flex-shrink:0}
  body.dynamic-mode .dyn-note{display:flex}
  /* Заметка объясняет, чем динамический код лучше статического, — довод для
     того, кто этого ещё не знает. Через месяц после регистрации человек это
     уже проверил руками, и строка под каждой формой только занимает место.
     Правило стоит после dynamic-mode: одинаковая сила селекторов, побеждает
     последнее. Класс ставит lockGuestSections в app.js. */
  body.hide-dyn-notes .dyn-note{display:none}

  /* «иллюстративный QR» — предупреждение для типов без реального хостинга (PDF/Файл/Видео/Меню-файл) */
  .demo-note{
    display:none;background:#FFF7ED;border:1px solid #FCD9B4;color:#9A5B10;border-radius:12px;
    padding:10px 14px;font-size:12.5px;margin-top:14px;align-items:center;gap:8px;text-align:left;
  }
  .demo-note svg{width:15px;height:15px;flex-shrink:0}
  .demo-note.show{display:flex}

  /* dropzone */
  .dropzone{
    border:1.5px dashed #C6D6EA;border-radius:11px;padding:34px 20px;text-align:center;background:#FAFCFF;
    transition:all .15s;cursor:pointer;
  }
  .dropzone:hover,.dropzone.drag{border-color:var(--primary);background:var(--primary-50)}
  .dropzone .dz-icon{width:42px;height:42px;margin:0 auto 10px;border-radius:50%;background:var(--primary-50);color:var(--primary);display:flex;align-items:center;justify-content:center}
  .dropzone .dz-icon svg{width:20px;height:20px}
  .dropzone .dz-title{font-weight:600;font-size:13.5px}
  .dropzone .dz-title u{color:var(--primary);text-decoration:none;font-weight:600}
  .dropzone .dz-sub{font-size:12px;color:var(--muted);margin-top:4px}
  .file-chip{
    display:none;margin-top:12px;align-items:center;gap:12px;background:#F6FAFF;border:1px solid var(--border);
    border-radius:14px;padding:10px 14px;
  }
  .file-chip.show{display:flex}
  /* position:static — у карточки папки в «Моих QR» свой .fc-icon, прижатый к углу (position:absolute
     ниже), и без этого значок плашки улетал из неё в угол окна (замечено 20.10.2026). */
  .file-chip .fc-icon{position:static;width:36px;height:36px;border-radius:10px;background:var(--primary-50);color:var(--primary);display:flex;align-items:center;justify-content:center;flex-shrink:0}
  .file-chip .fc-icon svg{width:17px;height:17px}
  /* Кружок загрузки вместо значка, пока файл едет на сервер (showChip в app.js,
     20.10.2026) — тот же вид, что у ожидания графиков (.chart-wait-spin). */
  .fc-spin{
    width:17px;height:17px;border-radius:50%;
    border:2px solid var(--primary-100);border-top-color:var(--primary);animation:chart-spin .8s linear infinite;
  }
  .file-chip .fc-name{font-weight:600;font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .file-chip .fc-size{font-size:12px;color:var(--muted)}
  .file-chip .fc-remove{margin-left:auto;background:none;border:none;color:var(--muted);padding:4px;border-radius:6px}
  .file-chip .fc-remove:hover{background:#E2ECF7;color:var(--text)}
  .file-chip .fc-remove svg{width:15px;height:15px}
  /* У аудио и видео плашка файла стоит в одну строку с тумблером «Show file name in player»
     (решение владельца 20.10.2026): плашка короче, тумблер справа. Строка переносится сама — где
     обоим не хватает места (телефон, узкая колонка), тумблер встаёт под плашку, и вбок ничего не
     едет. Отступ сверху — у каждого из двух, а не у строки: пока файла нет, оба скрыты, и пустая
     строка места не занимает. У остальных типов в строке одна плашка — как было. */
  .file-row{display:flex;flex-wrap:wrap;align-items:center;column-gap:18px}
  .file-row .file-chip{flex:1 1 180px;min-width:0}
  /* Метка-переключатель сидит внутри .field, а там у label свои правила (блок, кегль, отступ
     снизу, у конструктора — flex с зазором): возвращаем ей вид .sw-row. Зона нажатия —
     переключатель с подписью, не больше (width:fit-content у .sw-row). */
  .field label.sw-row.pn-sw{display:flex;align-items:center;gap:12px;margin:12px 0 0;font-size:inherit;font-weight:inherit;color:inherit}
  .pn-sw .sw{margin-top:0}
  .pn-sw .sw-text b{white-space:nowrap}
  .pn-input{margin-top:10px}
  .pn-input[hidden]{display:none}
  /* social chips */
  .chip-row{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:4px}
  .chip{
    padding:7px 15px;border-radius:999px;border:1px solid var(--border);background:#fff;font-size:12.5px;font-weight:600;
    color:var(--muted);transition:all .15s;
  }
  .chip:hover{border-color:var(--primary-100);color:var(--text)}
  .chip.selected{background:var(--primary);border-color:var(--primary);color:#fff}

  /* ====== ПОДВКЛАДКИ ТИПА ======
     Выбор пути внутри типа: «Меню», «Бизнес-карточка», «Соцсети» — собрать
     страницу у нас или дать готовый файл либо ссылку. Вид один на все три
     (решение владельца, 30.08.2026): это главная развилка экрана, и читаться
     она должна с первого взгляда. Серое корыто, выбранная — белой приподнятой
     плиткой. Перенос по строкам — чтобы длинная тройка «Меню» не выезжала за
     край телефона горизонтальной прокруткой. */
  .subtabs{
    display:inline-flex;flex-wrap:wrap;max-width:100%;gap:5px;padding:5px;margin-bottom:18px;
    background:#E9EEF5;border-radius:14px;box-shadow:inset 0 1px 2px rgba(15,30,46,.07);
  }
  .subtabs button{
    border:none;background:transparent;border-radius:10px;padding:10px 18px;
    font-size:13.5px;font-weight:700;color:var(--muted);transition:all .15s;
  }
  .subtabs button:hover{color:var(--text)}
  .subtabs button.active{background:#fff;color:var(--primary);box-shadow:0 2px 8px rgba(15,30,46,.14)}
  /* На телефоне поля по бокам съедены рамкой карточки: без этого «Собрать
     карточку» и «Своя ссылка» перестают помещаться в строку и встают друг под
     другом. Тройке «Меню» две строки всё равно не миновать — там слишком
     длинные подписи. */
  @media (max-width:520px){
    .subtabs button{padding:10px 13px;font-size:13px}
  }

  /* Галочка с подписью. width:fit-content — не украшение, а зона нажатия:
     метка-<label> ловит клик по всей своей площади, а на широком экране
     (сетка интересов в профиле, поле «Скрытая сеть») она растягивалась во всю
     колонку. За коротким «Госсектор» оставалось полколонки пустоты, которая
     ничем не отмечена и всё равно переключала галочку — владелец 04.09.2026
     попросил обрезать зону до самой галочки с подписью. fit-content, а не
     inline-flex: метка бывает и ячейкой сетки, и элементом флекса, а там
     inline-flex приводится обратно к блочному и снова растягивается. */
  .check-row{
    display:flex;align-items:center;gap:9px;width:fit-content;max-width:100%;
    font-size:13.5px;font-weight:500;cursor:pointer;user-select:none;
  }
  .check-row input{width:16px;height:16px;accent-color:var(--primary)}
  /* Внутри .field общее «.field label{display:block}» весомее .check-row и
     роняло галочку на строку текста без промежутка (Wi-Fi, «Hidden network»). */
  .field label.check-row{display:flex}

  /* ============ КОНСТРУКТОРЫ СТРАНИЦ ============
     Общая обвязка двух конструкторов — бизнес-карточки и страницы соцсетей.
     Классы с приставкой vc- достались от карточки, которая появилась первой,
     но правила здесь ничьи: форма, ряд заготовок цвета, кружок фотографии и
     колонка с телефоном у обеих страниц одни и те же. Своё у каждой — только
     сама страница внутри экрана (.vc-page и .sp-page ниже). */
  /* Поля слева, телефон справа и он же липкий: список полей длинный, а смотреть
     на результат нужно на каждой строке, а не в конце. */
  /* Ширину колонки телефона не задаём числом: у аудио рамка уменьшена (см.
     .phone-mount-sm), и любое фиксированное число подошло бы только одной из
     двух величин — вторая либо вылезла бы за колонку, либо оставила рядом с
     собой пустую полосу. Колонка по содержимому меряет сам корпус, а zoom
     уменьшает и его внешний размер тоже. */
  .vcard-editor{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:34px;align-items:start}
  .vcard-fields{min-width:0}
  /* Ряд над формой: в нём остался только выбор оформления (цвет карточки, стиль
     меню). Заголовка «Что будет на карточке» здесь больше нет — человек и так
     стоит в конструкторе и видит поля перед собой, — поэтому прижимаем выбор
     вправо: там он и стоял, когда слева был заголовок. */
  .vc-sec-head{display:flex;align-items:center;justify-content:flex-end;gap:12px;margin-bottom:16px}
  .vcard-fields .field label{display:flex;align-items:center;gap:6px}
  /* Имя короче остальных полей: строка на всю ширину карточки под три слова
     выглядит опечаткой в вёрстке. Ширина — ровно колонка соседнего ряда,
     чтобы правый край совпал с «Компанией» под ним. */
  .vc-field-half .input{max-width:calc(50% - 7px)}

  .vc-photo-row{display:flex;align-items:center;gap:16px}
  .vc-photo-box{
    width:84px;height:84px;flex-shrink:0;border-radius:50%;border:1px dashed var(--border);background:#F7FBFE;
    display:flex;align-items:center;justify-content:center;color:var(--muted2);overflow:hidden;
  }
  /* По готовому фото нажимают, чтобы поправить кадр, — курсор об этом говорит. */
  .vc-photo-box.filled{border-style:solid;border-color:var(--border2);cursor:pointer}
  .vc-photo-box.filled:hover{border-color:var(--primary);box-shadow:var(--shadow)}
  .vc-photo-box svg{width:28px;height:28px}
  .vc-photo-box img{width:100%;height:100%;object-fit:cover}
  .vc-photo-btns{display:flex;flex-direction:column;gap:8px;width:152px}
  .vc-photo-btns .btn{justify-content:center}

  /* Повторяемая строка: подпись слева узкой колонкой, значение — на всё
     остальное, крестик держится края. Ширины фиксированные, чтобы строки
     телефонов, почт и адресов стояли в одну линию друг под другом. */
  .vc-rows-field{margin-bottom:22px}
  .vc-rows{display:flex;flex-direction:column;gap:8px}
  .vc-rows:not(:empty){margin-bottom:10px}
  .vc-row{display:grid;grid-template-columns:136px minmax(0,1fr) 34px;gap:8px;align-items:center}
  .vc-row.vc-row-social{grid-template-columns:38px minmax(0,1fr) 34px}
  .vc-row .input{height:38px;padding:0 11px}
  .vc-row-ico{
    width:38px;height:38px;border:1px solid var(--border2);border-radius:10px;background:#fff;
    display:flex;align-items:center;justify-content:center;
  }
  .vc-row-ico img{width:20px;height:20px}
  .vc-row-del{
    width:34px;height:34px;border:none;background:none;border-radius:9px;color:var(--muted2);
    display:flex;align-items:center;justify-content:center;transition:all .15s;
  }
  .vc-row-del svg{width:15px;height:15px}
  .vc-row-del:hover{background:#FEF2F2;color:var(--red)}
  .vc-add{
    display:inline-flex;align-items:center;gap:7px;border:1px dashed var(--border);background:#fff;border-radius:10px;
    padding:8px 14px;font-size:12.5px;font-weight:600;color:var(--primary);transition:all .15s;
  }
  .vc-add svg{width:14px;height:14px}
  .vc-add:hover{background:var(--primary-50);border-color:var(--primary-100)}
  /* Строк одного вида карточка держит двенадцать (VC_ROWS_MAX в app.js и в
     server/src/domain/page.ts). На потолке кнопка гаснет и говорит «12 of 12»,
     а не отправляет тринадцатую строку на сервер за отказом (Р-465, проверка
     роботом, заход 14, 21.09.2026). */
  .vc-add:disabled{color:var(--muted);background:var(--border2);border-style:solid;cursor:not-allowed}
  .vc-add:disabled:hover{background:var(--border2);border-color:var(--border)}
  .vc-add-count{color:var(--muted2);font-weight:600;font-variant-numeric:tabular-nums}
  .vc-add-count:empty{display:none}

  .vc-social-pick{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px}
  .vc-social-pick button{
    width:40px;height:40px;padding:0;border:1px solid var(--border);border-radius:11px;background:#fff;
    display:flex;align-items:center;justify-content:center;transition:all .15s;
  }
  .vc-social-pick button:hover{border-color:var(--primary);transform:translateY(-1px);box-shadow:var(--shadow)}
  .vc-social-pick button.used{opacity:.4}
  .vc-social-pick img{width:22px;height:22px}
  /* На телефоне плитки растягиваются на всю ширину строки: при жёстких 40px справа
     оставалась полоса пустоты (решение владельца 16.10.2026, только телефон). В ряд
     их столько же, сколько влезало прежних, — колонка не уже 40px, а остаток строки
     делится между плитками поровну; значок растёт вместе с плиткой. */
  @media (max-width:640px){
    .vc-social-pick{display:grid;grid-template-columns:repeat(auto-fill,minmax(40px,1fr))}
    .vc-social-pick button{width:auto;height:auto;aspect-ratio:1}
    .vc-social-pick img{width:58%;height:58%}
  }

  /* Заготовки оформления. Кнопка — просто краска шапки: подпись под каждым
     квадратом превратила бы выбор цвета в чтение списка, а название и так
     висит подсказкой. Выбранная обведена кольцом снаружи, а не рамкой внутри:
     внутренняя обводка съедала бы сам цвет, который и выбирают. */
  .vc-themes{display:flex;gap:8px}
  .vc-theme{
    width:30px;height:30px;padding:0;border:none;border-radius:9px;
    box-shadow:inset 0 0 0 1px rgba(15,30,46,.13);transition:transform .15s,box-shadow .15s;
  }
  .vc-theme:hover{transform:translateY(-1px)}
  .vc-theme.active{box-shadow:inset 0 0 0 1px rgba(15,30,46,.13),0 0 0 2px #fff,0 0 0 4px var(--primary)}

  /* ============ РАМКА СМАРТФОНА ============
     Корпус нарисован стилями, без единой картинки. Металл на экране — это не
     один серый цвет, а чередование бликов и теней вдоль грани: полосы в
     градиенте ободка и есть отражения комнаты, из-за них грань читается
     круглой. Экран уходит под самый верх, а часы и значки лежат поверх
     содержимого — ровно так, как это выглядит на телефоне в руках. */
  /* Телефон поднят к строке подвкладок — выше, чем начинается колонка полей:
     он высокий, и каждый выигранный десяток пикселей — это ещё одна строка
     карточки, видная без прокрутки. Отступ равен высоте подвкладок вместе с
     их нижним полем (36 + 18). */
  .vcard-preview-col{position:sticky;top:12px;margin-top:-54px}
  /* Форма без подвкладок (видео и аудио). Подвкладок над колонкой полей тут
     нет, но пустое место в карточке есть — строка с названием типа занята
     одним коротким словом слева, а справа над телефоном пустует. Поднимаем
     корпус к самому верху карточки, к её внутреннему полю (26px + название
     ≈ 40), и на столько же увеличиваем сам телефон (zoom у .phone-mount-sm
     ниже) — низ остаётся на месте, вровень с кнопкой генерации, а экран
     становится крупнее. Просьба владельца 02.09.2026. */
  .vcard-preview-flat{margin-top:-40px}
  .phone{
    position:relative;width:296px;margin:0 auto;
    /* тень снимается с силуэта, а не с прямоугольника: у корпуса скруглены
       углы, и box-shadow подсветил бы пустые уголки за ними */
    filter:drop-shadow(0 20px 36px rgba(15,30,46,.24)) drop-shadow(0 3px 8px rgba(15,30,46,.14));
  }
  /* Кнопки лежат под ободком — так они выходят из-под грани, а не наклеены на неё. */
  .phone-btn{position:absolute;z-index:0;background:linear-gradient(180deg,#cfd5db,#8e959e 28%,#6c737c 72%,#b2b9c1)}
  .phone-btn-mute {left:-2px;top:104px;width:3px;height:26px;border-radius:2px 0 0 2px}
  .phone-btn-volup{left:-2px;top:146px;width:3px;height:48px;border-radius:2px 0 0 2px}
  .phone-btn-voldn{left:-2px;top:202px;width:3px;height:48px;border-radius:2px 0 0 2px}
  .phone-btn-power{right:-2px;top:172px;width:3px;height:70px;border-radius:0 2px 2px 0}
  .phone-rim{
    position:relative;z-index:1;border-radius:52px;padding:3px;
    background:linear-gradient(138deg,#f4f6f8 0%,#99a1ab 8%,#e9ecf0 17%,#757c86 27%,#ced3d9 38%,#8e949e 50%,#e4e8ed 62%,#787f89 74%,#d9dde2 85%,#8c939d 93%,#f1f3f6 100%);
  }
  /* Чёрная кромка между металлом и картинкой. Тонкая светлая линия внутри —
     стык стекла с корпусом, без неё экран выглядит наклеенным сверху. */
  .phone-bezel{border-radius:49px;padding:11px;background:#07090c;box-shadow:inset 0 0 0 1px rgba(255,255,255,.08)}
  .phone-screen{
    position:relative;border-radius:39px;overflow:hidden;background:#fff;aspect-ratio:9/19.5;
    /* Цвет часов и полоски внизу подбирает JS под цвет шапки карточки:
       белые часы на белой шапке пропали бы (см. renderVcardPreview). */
    --phone-ink:#0F1E2E;
  }
  .phone-scroll{position:absolute;inset:0;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:none}
  .phone-scroll::-webkit-scrollbar{display:none}
  /* На сенсорном экране долистанное до конца превью отдаёт прокрутку странице:
     мимо рамки телефона пальцу почти негде листать (просьба владельца 16.10.2026).
     Внутри одного движения пальца браузер прокрутку не передаёт — страница поедет
     со следующего. С мышью превью по-прежнему не уводит страницу из-под себя. */
  @media (hover:none),(pointer:coarse){
    .phone-scroll{overscroll-behavior:auto}
  }
  /* Полоса красится цветом шапки карточки: страница под часами прокручивается,
     и без своего фона белые часы однажды оказались бы на белом листе. */
  .phone-status{
    position:absolute;top:0;left:0;right:0;height:34px;z-index:3;pointer-events:none;
    display:flex;align-items:center;justify-content:space-between;padding:0 19px 0 21px;
    font-size:11.5px;font-weight:700;letter-spacing:.01em;
    color:var(--phone-ink);background:var(--phone-band,#fff);
  }
  .phone-status-icons{display:flex;align-items:center;gap:5px}
  .ps-signal{width:16px;height:10.6px}
  .ps-wifi{width:14px;height:10.5px}
  .ps-batt{width:23px;height:10.6px}
  .phone-island{
    position:absolute;top:9px;left:50%;transform:translateX(-50%);z-index:4;
    width:84px;height:25px;border-radius:999px;background:#000;
  }
  .phone-cam{
    position:absolute;right:7px;top:50%;transform:translateY(-50%);width:9px;height:9px;border-radius:50%;
    background:radial-gradient(circle at 34% 30%,#2b3c55,#05070b 68%);box-shadow:inset 0 0 0 1px rgba(255,255,255,.09);
  }
  /* Полоска жеста всегда лежит на белом листе карточки — он тянется до низа
     экрана, — поэтому цвет у неё постоянный и от шапки не зависит. */
  .phone-home{
    position:absolute;bottom:7px;left:50%;transform:translateX(-50%);z-index:4;
    width:106px;height:4px;border-radius:999px;background:#0F1E2E;opacity:.42;
  }
  /* Уменьшение — только у аудио и только рядом с формой. Дело не в экономии
     места как таковой: у аудио вся форма — одна зона загрузки, и рамка в
     полный рост перетягивала на себя весь экран, выглядя главнее того, ради
     чего человек сюда пришёл. У карточки и соцсетей полей много, телефон с
     ними не соперничает и остаётся в натуральную величину.

     Уменьшает zoom, а не transform: transform оставил бы на месте пустой след
     прежнего размера, и колонку пришлось бы подпирать отрицательными полями.
     Ниже 1180px телефон уходит под форму и возвращается в натуральную величину
     — см. соответствующий @media.

     .666, а не прежние .6 (02.09.2026): ровно на те 40 пикселей, на которые
     колонка поднялась к верху карточки (margin-top у .vcard-preview-flat
     выше). Числа связаны: меняешь одно — пересчитывай второе, иначе низ
     телефона разойдётся с кнопкой генерации. */
  .phone-mount-sm .phone{zoom:.666}

  /* Блик стекла. Держим его слабым и только в верхнем углу: сильнее — и он
     начнёт мешать читать саму карточку, ради которой всё и затевалось. */
  .phone-glare{
    position:absolute;inset:0;z-index:5;pointer-events:none;border-radius:39px;mix-blend-mode:screen;
    background:linear-gradient(128deg,rgba(255,255,255,.30) 0%,rgba(255,255,255,.09) 15%,rgba(255,255,255,0) 33%);
    box-shadow:inset 0 0 0 1px rgba(255,255,255,.10);
  }

  /* ============ САМА КАРТОЧКА ВНУТРИ ЭКРАНА ============
     Разметка неинтерактивная намеренно: это превью, а не страница. Телефон и
     почта здесь не ссылки — иначе клик по превью уводил бы человека в почтовый
     клиент прямо посреди настройки. */
  .vc-page{min-height:100%;display:flex;flex-direction:column;background:#fff;color:#16202B;user-select:none}
  .vc-head{position:relative;padding:42px 16px 32px;text-align:center}
  /* ВАЖНО ДЛЯ БЭКЕНДА: этот блик — часть самой страницы, а не превью.
     Когда появятся настоящие страницы (сервер отдаёт их по короткому коду),
     тот же градиент обязан быть и там, теми же числами: угол 128°, белый
     .30 → .09 на 15% → прозрачный на 33%. Цвет блика намеренно не
     настраивается — он должен читаться одинаково на любой краске шапки.
     Правило общее у карточки и у страницы соцсетей: шапка у них — одна и та
     же цветная плашка, и разъехаться этим двум бликам нельзя. */
  .vc-head::before,.sp-head::before{
    content:'';position:absolute;inset:0;pointer-events:none;
    background:linear-gradient(128deg,rgba(255,255,255,.30) 0%,rgba(255,255,255,.09) 15%,rgba(255,255,255,0) 33%);
  }
  .vc-head > *{position:relative}
  .vc-avatar{
    width:88px;height:88px;margin:0 auto;border-radius:50%;overflow:hidden;background:rgba(255,255,255,.16);
    box-shadow:0 0 0 3px rgba(255,255,255,.25);display:flex;align-items:center;justify-content:center;
    font-size:26px;font-weight:700;letter-spacing:.02em;
  }
  .vc-avatar img{width:100%;height:100%;object-fit:cover}
  /* Запасной значок держим вполовину кружка: во всю ширину он упирается в
     край и читается не значком, а обрезанной картинкой. */
  .vc-avatar svg{width:40px;height:40px}
  /* Длинное слово без пробелов («Immobilienverwaltungsgesellschaft», ник) переносится
     внутри своей строки — здесь, у соцстраницы и у меню ниже, как на настоящих
     страницах (Р-19, проверка роботом, 14.09.2026). */
  .vc-name,.vc-company,.vc-role,.vc-desc,.vc-sub,.sp-name,.sp-desc{overflow-wrap:anywhere}
  .vc-name{font-size:19px;font-weight:700;letter-spacing:-.01em;margin-top:15px}
  .vc-ghost{opacity:.55;font-weight:600}
  .vc-company{font-size:13.5px;font-weight:600;margin-top:4px;opacity:.95}
  .vc-role{font-size:12px;margin-top:2px;opacity:.78}
  /* Ряд кнопок переносится, а подписи ужимаются — так же, как на самой странице
     после скана (`.vc-acts` в redirect/vcard-page.ts). Без переноса при поднятом
     в браузере размере шрифта «Call» и «Directions» вылезали за корпус
     телефончика и оказывались срезаны, хотя настоящая страница при том же
     шрифте цела (Р-470, проверка роботом, заход 14, 21.09.2026). */
  .vc-acts{display:flex;flex-wrap:wrap;justify-content:center;gap:14px;margin-top:20px}
  .vc-act{display:flex;flex-direction:column;align-items:center;gap:6px;font-size:10.5px;opacity:.95;
    min-width:0;max-width:100%;text-align:center;overflow-wrap:anywhere}
  .vc-act i{width:42px;height:42px;border-radius:13px;display:flex;align-items:center;justify-content:center;color:#fff}
  .vc-act svg{width:19px;height:19px}
  /* Белый лист «наезжает» на шапку скруглённым краем — так он читается как
     отдельная карточка поверх цвета, а не как продолжение цветного блока. */
  .vc-body{flex:1;background:#fff;border-radius:22px 22px 0 0;margin-top:-20px;padding:20px 16px 34px}
  /* Верхний отступ больше нижнего: сверху текст упирается в скруглённый край
     белого листа, и вплотную к этой дуге он выглядит подвёрстанным к ней. */
  .vc-desc{font-size:12.5px;line-height:1.5;margin:14px 0 10px;text-align:center}
  .vc-item{display:flex;gap:12px;padding:11px 0;border-top:1px solid #EDF1F5}
  .vc-item:first-child{border-top:none}
  .vc-ico{flex-shrink:0;width:22px;color:#8FA0B2}
  .vc-ico svg{width:20px;height:20px}
  .vc-vals{min-width:0;flex:1}
  .vc-val{font-size:12.5px;font-weight:600;line-height:1.35;overflow-wrap:anywhere}
  /* Подписи строк и заголовок ссылок — #66788A, как на настоящей визитке
     (vcard-page.ts): прежний #93A6B9 давал 2,5:1 к белому, темнее по решению
     владельца 14.09.2026. Это копия страницы после скана, а не подпись сайта —
     правило «серый подписей сайта не темнить» к ней не относится. */
  .vc-sub{font-size:10.5px;color:#66788A;margin-top:1px}
  .vc-val + .vc-val,.vc-sub + .vc-val{margin-top:9px}
  .vc-links-title{font-size:10.5px;color:#66788A;margin:16px 0 9px}
  .vc-links{display:flex;flex-wrap:wrap;gap:10px}
  .vc-links img{width:30px;height:30px}
  .vc-save{
    margin-top:22px;height:42px;border-radius:13px;color:#fff;font-size:12.5px;font-weight:700;
    display:flex;align-items:center;justify-content:center;gap:8px;
  }
  .vc-save svg{width:16px;height:16px}

  /* ============ ПОДПИСЬ СЕРВИСА ВНУТРИ ЭКРАНА ============
     «Made with QRPlace» внизу собранной у нас страницы — на бесплатном тарифе
     (`madeWithHtml` в site/app.js, server/src/redirect/made-with.ts, решение
     владельца 30.09.2026). Копия подписи со страницы после скана, и правится
     только вместе с ней: числа страницы поделены на 1.45, как вся остальная
     разметка превью. Цвет тот же — на белом листе 5,5:1, у плееров свой ниже. */
  .qp-made{
    display:flex;align-items:center;justify-content:center;gap:4px;
    font-size:9px;line-height:1;color:#5B6B7C;
  }
  .qp-made svg{width:10px;height:10px;flex:none}
  /* Карточка: подпись на белом листе под кнопкой. С ней низ листа перестроен —
     кнопка ближе к последней строке контактов, под подписью меньше воздуха. */
  .vc-body .qp-made{padding:26px 0 0}
  .vc-page.qp-signed .vc-body{padding-bottom:12px}
  .vc-page.qp-signed .vc-save{margin-top:15px}

  /* ============ СТРАНИЦА СОЦСЕТЕЙ ВНУТРИ ЭКРАНА ============
     Тот же строй, что у карточки: цветная шапка, поверх неё скруглённым краем
     наезжает белый лист. Разница в том, что на листе стоит: не строки
     контактов, а кнопки профилей во всю ширину. По такой кнопке метятся
     пальцем с телефона в руке, и мельче её делать нечем.
     Разметка неинтерактивная намеренно: это превью, а не страница. */
  .sp-page{min-height:100%;display:flex;flex-direction:column;background:#F1F3F6;color:#16202B;user-select:none}
  .sp-head{position:relative;padding:42px 18px 34px;text-align:center}
  .sp-head > *{position:relative}
  .sp-avatar{
    width:96px;height:96px;margin:0 auto;border-radius:50%;overflow:hidden;background:rgba(255,255,255,.16);
    box-shadow:0 0 0 3px rgba(255,255,255,.25);display:flex;align-items:center;justify-content:center;
    font-size:28px;font-weight:700;letter-spacing:.02em;
  }
  .sp-avatar img{width:100%;height:100%;object-fit:cover}
  .sp-avatar svg{width:44px;height:44px}
  .sp-name{font-size:20px;font-weight:700;letter-spacing:-.01em;margin-top:15px}
  /* Описание живёт в шапке, а не на белом листе: здесь это подпись к лицу, а
     не отдельный раздел, и оторванное от фотографии оно читается как чужой
     текст, случайно попавший наверх списка кнопок. */
  .sp-desc{font-size:12px;line-height:1.5;margin-top:9px;opacity:.9}
  /* Лист «наезжает» на шапку скруглённым краем — position обязателен: шапка
     позиционирована (position:relative под блик), и без своего position лист
     уходит под неё вместе со скруглением, а стык выходит прямой линией.
     Лист не белый, а чуть серый: кнопки на нём тоже светлые, и на чистом белом
     между ними и листом почти нет ступеньки — ряд читается сплошным пятном. */
  .sp-body{
    position:relative;flex:1;background:#F1F3F6;border-radius:22px 22px 0 0;
    margin-top:-20px;padding:26px 16px 34px;
    display:flex;flex-direction:column;gap:10px;
  }
  /* Подпись прижата к низу листа: лист на телефоне занимает экран целиком, и
     лишний воздух у страницы с тремя кнопками деться никуда не может — он либо
     над подписью, либо под ней. Владелец выбрал «под подписью меньше»
     (30.09.2026), и на самой странице так же. */
  .sp-body .qp-made{padding:21px 0 0;margin-top:auto}
  .sp-page.qp-signed .sp-body{padding-bottom:14px}
  .sp-link{display:flex;align-items:center;gap:12px;padding:9px 12px;border-radius:14px;color:#fff}
  /* Белая плитка под значком. Половина логотипов чёрные — X, Threads, TikTok, —
     и на тёмной краске кнопки от них остался бы силуэт. Плитка заодно равняет
     значки, нарисованные в разном масштабе. */
  /* Белая плитка со значком не перекрашивается затемнением — как на самой
     странице (redirect/social-page.ts; «ЗНАЧКИ СОЦСЕТЕЙ» ниже). */
  .sp-link i{
    flex-shrink:0;width:34px;height:34px;border-radius:10px;background:#fff;
    display:flex;align-items:center;justify-content:center;color-scheme:only light;
  }
  .sp-link i img{width:21px;height:21px}
  /* ---- ЗНАЧКИ СОЦСЕТЕЙ: затемнение их не перекрашивает (29.09.2026) ----
     «Затемнение сайтов» Chrome на телефоне перекрашивает и картинки: у
     чёрно-белых значков (X, Threads, GitHub) от этого пропадал сам рисунок —
     оставался пустой белый кружок, а у Signal и TikTok портились цвета.
     Фирменный значок перекрашивать нельзя вовсе, поэтому каждый значок из
     site/social — `only light`, где бы он ни стоял (редактор, превью, логотипы
     в оформлении). Страницы после сканирования — то же в redirect/social-page.ts
     и redirect/vcard-page.ts. Остальную страницу браузер затемняет как прежде. */
  img[src^="/social/"]{color-scheme:only light}
  .sp-link span{font-size:13px;font-weight:700;letter-spacing:-.01em}
  .sp-empty{font-size:12px;line-height:1.5;color:#93A6B9;text-align:center;padding:24px 12px}

  /* ============ КОНСТРУКТОР МЕНЮ: ФОРМА ============ */
  /* Переключатель стиля страницы — в строке заголовка, там же, где у карточки
     стоят цветовые заготовки: это выбор вида будущей страницы, а не поле. */
  .mn-style-pick{display:inline-flex;border:1px solid var(--border);border-radius:10px;overflow:hidden}
  .mn-style-pick button{border:none;background:#fff;padding:7px 14px;font-size:12.5px;font-weight:600;color:var(--muted)}
  .mn-style-pick button.active{background:var(--primary-50);color:var(--primary)}
  .mn-style-pick button + button{border-left:1px solid var(--border)}
  /* Строка инструментов: вкладки «Заведение / Блюда» слева, «Загрузить пример»
     справа. Вкладки того же вида, что подвкладки типов, но мельче: это второй
     уровень, и спорить с первым он не должен. */
  .mn-tools{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:18px;flex-wrap:wrap}
  .mn-tabs{display:inline-flex;border:1px solid var(--border);border-radius:12px;overflow:hidden}
  .mn-tabs button{border:none;background:#fff;padding:8px 16px;font-size:13px;font-weight:600;color:var(--muted)}
  .mn-tabs button.active{background:var(--primary-50);color:var(--primary)}
  .mn-tabs button + button{border-left:1px solid var(--border)}
  /* Обложка — прямоугольник, а не квадрат карточки: она ляжет лентой поверх
     страницы, и рамка выбора должна обещать именно это. */
  .mn-cover-box.vc-photo-box{width:112px;height:70px;border-radius:12px}
  .mn-cat-edit{border:1px solid var(--border);border-radius:14px;background:#fff;padding:14px;margin-bottom:14px}
  .mn-cat-line{display:flex;gap:8px;align-items:center;margin-bottom:4px}
  .mn-cat-line .input{height:38px;padding:0 11px;font-weight:600}
  .mn-item-edit{display:flex;gap:10px;padding:12px 0;border-top:1px dashed var(--border);margin-top:12px}
  .mn-item-edit:first-of-type{border-top:none;margin-top:4px}
  .mn-item-fields{flex:1;min-width:0;display:flex;flex-direction:column;gap:6px}
  .mn-item-line{display:flex;gap:8px;align-items:center}
  .mn-item-fields .input{height:38px;padding:0 11px}
  .mn-item-line .mn-price{width:92px;flex-shrink:0}
  /* На узком телефоне (до 374 точек) название блюда — на свою строку, цена с крестиком под ним:
     на 320 точках цена держала свои 92 px, названию доставалось ~40, и «Espresso» читалось как
     «Es» (Р-73 П4, проверка роботом, заход 3, 17.09.2026). Именно медиа-правило, а не перенос
     по месту: на 390 переносился один крестик, а строка там и так читается (название ~110 px). */
  @media (max-width:374px){
    .mn-item-line{flex-wrap:wrap}
    .mn-item-line > .input:first-child{flex:1 1 100%}
  }
  /* Гнездо фотографии блюда. Пустое — пунктир со значком, как гнездо фото
     карточки; с фотографией — сама фотография и крестик в углу. */
  .mn-dish-photo{
    position:relative;flex-shrink:0;width:54px;height:54px;border-radius:11px;padding:0;
    border:1.5px dashed var(--border2);background:#F8FAFC;color:var(--muted2);
    display:flex;align-items:center;justify-content:center;overflow:visible;transition:all .15s;
  }
  .mn-dish-photo:hover{border-color:var(--primary);color:var(--primary)}
  .mn-dish-photo.filled{border-style:solid;border-color:var(--border2)}
  .mn-dish-photo svg{width:22px;height:22px}
  .mn-dish-photo img{width:100%;height:100%;object-fit:cover;border-radius:9px}
  .mn-dish-x{
    position:absolute;top:-7px;right:-7px;width:20px;height:20px;border-radius:50%;
    background:#fff;border:1px solid var(--border);color:var(--muted);
    display:flex;align-items:center;justify-content:center;box-shadow:var(--shadow);
  }
  .mn-dish-x svg{width:11px;height:11px}
  .mn-dish-x:hover{background:#FEF2F2;color:var(--red)}

  /* ============ МЕНЮ ВНУТРИ ЭКРАНА ============
     Не заготовка, а уменьшенная копия того, что соберёт сервер
     (server/src/redirect/menu-page.ts): те же краски, тот же строй, размеры
     ужаты в 1.45 раза. Правится только вместе с той страницей. Разметка
     неинтерактивна: полоса категорий и кнопки внизу нарисованы, но не
     нажимаются — это превью, а не меню. */
  .mn-page{min-height:100%;display:flex;flex-direction:column;background:#fff;color:#16202B;user-select:none}
  .mn-cover{height:138px;overflow:hidden;flex-shrink:0}
  .mn-cover img{width:100%;height:100%;object-fit:cover;display:block}
  /* Полоса с названием заведения нарочно узкая (владелец, 30.08.2026): каждый
     её лишний пиксель отбирает место у фотографии зала, а она и есть первое,
     что видит гость. Отступы срезаны на десятую часть высоты полосы сверху и
     столько же снизу — те же доли, что и на настоящей странице. */
  .mn-head{padding:12px 18px 10px;text-align:center;background:#FAFBFC;border-bottom:1px solid #E7EAEF}
  /* Без обложки шапка начинается под часами телефона — им нужна своя полоса. */
  .mn-page > .mn-head:first-child{padding-top:44px}
  /* Длинное слово переносится внутри своей строки (Р-19) — тот же список, что на
     странице меню. */
  .mn-name,.mn-desc,.mn-chips span,.mn-cat,.mn-item-desc,.mn-footer,.mn-wifi-box b,.mn-btn span,.mn-item-price{overflow-wrap:anywhere}
  .mn-name{font-size:20px;font-weight:700;letter-spacing:-.01em;line-height:1.2}
  .mn-desc{font-size:11px;line-height:1.5;margin-top:7px;color:#5B6572}
  .mn-chips{display:flex;flex-wrap:wrap;justify-content:center;gap:6px;padding:13px 14px 2px}
  .mn-chips span{
    padding:5px 11px;border-radius:999px;border:1px solid #DCE1E8;background:#F5F7FA;
    font-size:10px;font-weight:600;cursor:pointer;
  }
  .mn-chips span:active{background:#E9EDF2}
  .mn-sec{padding:16px 14px 0}
  .mn-cat{font-size:13px;font-weight:700;letter-spacing:-.01em;margin-bottom:9px}
  .mn-items{display:flex;flex-direction:column}
  .mn-item{display:flex;gap:10px;align-items:flex-start}
  .mn-item-body{flex:1;min-width:0}
  /* Цене тесно рядом с названием — она уходит на вторую строку к правому краю,
     а не вылезает за карточку (Р-20, проверка роботом, 14.09.2026; подробно — у
     .mn-item-top в menu-page.ts). */
  .mn-item-top{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:8px}
  .mn-item-name{flex:1 1 auto;width:min-content;min-width:0;overflow-wrap:break-word;font-weight:600}
  .mn-item-price{margin-left:auto;max-width:100%;text-align:right;font-variant-numeric:tabular-nums}
  .mn-item-desc{color:#5B6572}
  /* стиль «cards»: карточки с рамкой и тенью */
  .mn-cards .mn-items{gap:9px}
  .mn-cards .mn-item{border:1px solid #E2E6EC;border-radius:11px;background:#fff;box-shadow:0 3px 10px rgba(15,30,46,.06);padding:9px}
  .mn-cards .mn-item-photo{flex-shrink:0;width:64px;height:64px;border-radius:8px;overflow:hidden}
  .mn-cards .mn-item-photo img{width:100%;height:100%;object-fit:cover;display:block}
  .mn-cards .mn-item-name{font-size:12px}
  .mn-cards .mn-item-price{font-size:11px;font-weight:700}
  .mn-cards .mn-item-desc{font-size:10px;line-height:1.45;margin-top:4px}
  /* стиль «list»: строгий список с тонкими линиями */
  .mn-list .mn-item{padding:9px 1px}
  .mn-list .mn-item + .mn-item{border-top:1px solid #ECEEF2}
  .mn-list .mn-item-photo{flex-shrink:0;width:39px;height:39px;border-radius:50%;overflow:hidden}
  .mn-list .mn-item-photo img{width:100%;height:100%;object-fit:cover;display:block}
  .mn-list .mn-item-name{font-size:11px}
  .mn-list .mn-item-price{font-size:10.5px;font-weight:600}
  .mn-list .mn-item-desc{font-size:9.5px;line-height:1.45;margin-top:2px}
  /* Серый заголовка раздела — 4,9:1 к белому, как на странице меню (Р-37). */
  .mn-list .mn-cat{font-size:9px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:#687180;margin-bottom:3px}
  .mn-contacts{display:flex;flex-wrap:wrap;justify-content:center;gap:7px;padding:21px 14px 0;margin-top:auto;align-items:flex-start}
  .mn-btn{
    display:flex;align-items:center;gap:6px;padding:7px 12px;border-radius:999px;
    border:1px solid #D6DCE4;background:#fff;font-size:10.5px;font-weight:600;
    color:#16202B;text-decoration:none;cursor:pointer;list-style:none;
  }
  .mn-btn::-webkit-details-marker{display:none}
  .mn-btn:active{background:#F0F3F7}
  .mn-btn svg{width:12.5px;height:12.5px;flex-shrink:0}
  .mn-wifi{max-width:100%;min-width:0}
  span.mn-btn{cursor:default}
  .mn-wifi-box{
    margin-top:6px;padding:8px 10px;border-radius:9px;background:#F4F6F9;
    display:flex;flex-direction:column;gap:4px;font-size:10px;
  }
  .mn-wifi-box div{display:flex;justify-content:space-between;gap:10px}
  /* Подписи Wi-Fi и надпись внизу — серый заголовков «List», как на странице
     меню (решение владельца 14.09.2026: прежние #7A8290 и #8A93A0 — 3,6 и 3,1). */
  .mn-wifi-box span{color:#687180}
  /* Пароль и кнопка копирования стоят вместе, у правого края строки. Кнопка
     размером с палец — на настоящей странице это 40 px (владелец, 30.08.2026:
     в маленькую не попасть), здесь те же 40, ужатые в 1.45 раза. */
  .mn-wifi-box .mn-wifi-val{display:inline-flex;align-items:center;gap:7px;color:#16202B;min-width:0}
  .mn-wifi-box b{min-width:0}
  .mn-copy{
    display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;
    width:28px;height:28px;margin:-4px -4px -4px 0;padding:0;border-radius:8px;
    border:1px solid #DCE1E8;background:#fff;color:#5B6572;cursor:pointer;
  }
  .mn-copy svg{width:14px;height:14px;display:block}
  .mn-copy:hover{background:#EDF1F6;color:#16202B}
  /* Скопировано — значок на полторы секунды становится зелёной галочкой. */
  .mn-copy .mn-copy-ok{display:none}
  .mn-copy.done{color:var(--green)}
  .mn-copy.done:hover{background:transparent;color:var(--green)}
  .mn-copy.done .mn-copy-ico{display:none}
  .mn-copy.done .mn-copy-ok{display:block}
  .mn-footer{text-align:center;padding:17px 17px 28px;font-size:12px;font-style:italic;color:#687180}
  .mn-pad{padding-bottom:25px}
  /* С подписью надпись заведения поднята к контактам, а низ страницы держит
     сама подпись — как на странице после скана. */
  .mn-page .qp-made{padding:6px 0 14px}
  .mn-page.qp-signed .mn-footer{padding-top:10px;padding-bottom:8px}
  .mn-page.qp-signed .mn-pad{padding-bottom:10px}

  /* ============ СТРАНИЦА АУДИО ВНУТРИ ЭКРАНА ============
     Не заготовка и не набросок, а уменьшенная копия того, что соберёт сервер
     (server/src/redirect/audio-page.ts): те же краски, тот же строй, те же
     скругления. Разойдись они — человек подобрал бы одно, а напечатал код на
     другое, и узнал бы об этом уже с телефона в руках.

     Правится это место только вместе с той страницей. Разметка неинтерактивна:
     кнопки нарисованы, но не нажимаются — это превью, а не плеер. */
  .aud-page{
    min-height:100%;display:flex;align-items:center;justify-content:center;
    padding:18px;color:#EDEAFB;user-select:none;
    background:
      radial-gradient(115% 85% at 12% 4%, #4B1D8F 0%, rgba(75,29,143,0) 58%),
      radial-gradient(100% 75% at 92% 12%, #B0257A 0%, rgba(176,37,122,0) 55%),
      radial-gradient(120% 90% at 78% 100%, #0F5C8C 0%, rgba(15,92,140,0) 60%),
      linear-gradient(168deg,#100B24 0%,#0B0A1B 55%,#080714 100%);
  }
  .aud-card{
    width:100%;padding:24px 20px 18px;border-radius:24px;
    background:rgba(255,255,255,.055);border:1px solid rgba(255,255,255,.12);
    box-shadow:0 18px 44px rgba(0,0,0,.42), inset 0 1px 0 rgba(255,255,255,.10);
  }
  /* Кружок с полосками вместо обложки: слушать здесь нечего, но пустой квадрат
     на месте картинки читался бы как незагрузившаяся обложка, а полоски прямо
     говорят, что за содержимое внутри. */
  .aud-art{
    width:96px;height:96px;margin:0 auto 20px;border-radius:50%;
    display:flex;align-items:flex-end;justify-content:center;gap:5px;padding-bottom:41px;
    background:
      radial-gradient(80% 80% at 30% 22%, rgba(255,255,255,.16) 0%, rgba(255,255,255,0) 60%),
      linear-gradient(150deg,#7C3AED 0%,#C0248A 52%,#2563EB 100%);
    box-shadow:0 12px 30px rgba(124,58,237,.42), inset 0 1px 0 rgba(255,255,255,.28);
  }
  .aud-art i{display:block;width:4.5px;border-radius:3px;background:rgba(255,255,255,.94)}
  /* Полоски лежат низким рядом — так плеер выглядит на паузе, а именно её и
     видит человек, открывший страницу: играть без его нажатия браузер не даст. */
  .aud-art i:nth-child(1){height:7px}
  .aud-art i:nth-child(2){height:14px}
  .aud-art i:nth-child(3){height:10px}
  .aud-art i:nth-child(4){height:17px}
  .aud-art i:nth-child(5){height:9px}
  /* Заголовок записи — .ttl страницы (20.10.2026: своё поле кода, тумблер «Show file name in
     player»); числа страницы ужаты так же, как кружок и кнопка. Пустой — строки нет. */
  .aud-ttl{margin:-4px 0 13px;font-size:13px;font-weight:600;line-height:1.35;text-align:center;overflow-wrap:anywhere}
  .aud-track{height:5px;border-radius:3px;background:rgba(255,255,255,.20)}
  .aud-time{
    display:flex;justify-content:space-between;margin-top:6px;
    font-size:10px;letter-spacing:.02em;color:rgba(237,234,251,.62);
    font-variant-numeric:tabular-nums;
  }
  .aud-play{
    width:52px;height:52px;margin:16px auto 0;border-radius:50%;
    display:flex;align-items:center;justify-content:center;
    background:#fff;color:#141024;box-shadow:0 8px 22px rgba(0,0,0,.42);
  }
  /* Размер и положение — доля от круга страницы (30 из 68), чтобы превью и
     сама страница показывали одну кнопку. Про отсутствие сдвига вправо см.
     комментарий в audio-page.ts: фигура смещена уже в самом пути. */
  .aud-play svg{width:23px;height:23px}
  .aud-dl{
    display:flex;align-items:center;justify-content:center;gap:7px;
    margin-top:20px;padding:10px;border-radius:12px;
    font-size:12.5px;font-weight:500;letter-spacing:.01em;
    background:rgba(255,255,255,.09);border:1px solid rgba(255,255,255,.14);
  }
  .aud-dl svg{width:14px;height:14px}
  /* У плееров подпись стоит не на стеклянной карточке, а под ней, у нижнего края
     страницы (решение владельца 30.09.2026): карточка принадлежит записи, а не
     нам. Привязана к низу, чтобы не сдвигать карточку с середины экрана, — как
     на самой странице. Цвет светлее: фон тёмный. */
  .aud-page,.vid-page{position:relative}
  .aud-page .qp-made,.vid-page .qp-made{position:absolute;left:0;right:0;bottom:12px;color:#A8B4C2}

  /* ============ СТРАНИЦА ВИДЕО ВНУТРИ ЭКРАНА ============
     Уменьшенная копия server/src/redirect/video-page.ts — на тех же правах,
     что и страница аудио выше: правится только вместе с ней.

     Фон и карточка те же, что у аудио, — а вот своих кнопок здесь нет совсем.
     Управление ролику рисует сам браузер (почему именно так, разобрано в
     video-page.ts), и превью показывает ровно это: настоящий <video controls>,
     а не нарисованную полосу. Рисовать её было бы враньём вдвойне — системная
     полоса выглядит по-разному в Safari, Chrome и Firefox, и любой наш
     набросок разошёлся бы с тем, что человек увидит после скана. */
  /* Поля здесь узкие не по вкусу, а следом за страницей: там на телефоне они
     срезаны ради ширины лежачего ролика (см. @media в video-page.ts). Доля
     экрана та же — около 6% на сторону, — просто экран нарисованный и меньше
     настоящего. */
  .vid-page{
    min-height:100%;display:flex;align-items:center;justify-content:center;
    padding:7px;color:#EDEAFB;user-select:none;
    background:
      radial-gradient(115% 85% at 12% 4%, #4B1D8F 0%, rgba(75,29,143,0) 58%),
      radial-gradient(100% 75% at 92% 12%, #B0257A 0%, rgba(176,37,122,0) 55%),
      radial-gradient(120% 90% at 78% 100%, #0F5C8C 0%, rgba(15,92,140,0) 60%),
      linear-gradient(168deg,#100B24 0%,#0B0A1B 55%,#080714 100%);
  }
  .vid-card{
    width:100%;padding:8px 8px 10px;border-radius:24px;
    background:rgba(255,255,255,.055);border:1px solid rgba(255,255,255,.12);
    box-shadow:0 18px 44px rgba(0,0,0,.42), inset 0 1px 0 rgba(255,255,255,.10);
  }
  /* Соотношение сторон приходит от самого файла (--ar ставит renderVideoPreview),
     до выбора файла — 16:9. Вторая граница ширины считается от 300px высоты:
     стоячий ролик 9:16 при ширине карточки вытянулся бы на 410px и вытолкнул
     кнопку «скачать» за нижний край экрана. Ужимается рамка, а не картинка в
     ней, — поэтому чёрных полей по бокам не появляется.

     На настоящей странице та же граница считается от высоты окна (58vh):
     здесь высота известна заранее — это экран нарисованного телефона. */
  .vid-stage{
    position:relative;width:100%;margin:0 auto;
    max-width:calc(300px * var(--ar,1.7778));
    aspect-ratio:var(--ar,1.7778);
    /* 16px — это 24px скругления карточки минус 8px её поля: углы картинки
       садятся в углы карточки без зазора, как и на самой странице. */
    border-radius:16px;overflow:hidden;
    /* Пока ролик не выбран, вместо картинки — тёмная плашка в красках самой
       страницы, а не чёрный прямоугольник: чёрный читался бы как не
       загрузившееся видео, а не как пустое место под него. */
    background:linear-gradient(150deg,#241A44,#141026);
    box-shadow:0 12px 28px rgba(0,0,0,.42), inset 0 0 0 1px rgba(255,255,255,.09);
  }
  /* Ролик показан настоящий — тот самый файл, который человек только что
     залил, и с теми же кнопками, что достанутся посетителю. Играть его прямо
     здесь можно, и это не оплошность: сам он не заговорит, автозапуска нет, —
     а нажать «play» и проверить свой же ролик до того, как код напечатан, ровно
     то, зачем превью и нужно. */
  .vid-stage video{display:block;width:100%;height:100%;object-fit:contain;background:#000}
  /* Заголовок ролика под картинкой — .ttl страницы видео, ужатый, как у звука выше. */
  .vid-ttl{margin:10px 3px 0;font-size:12.5px;font-weight:600;line-height:1.35;text-align:center;overflow-wrap:anywhere}
  .vid-dl{
    display:flex;align-items:center;justify-content:center;gap:7px;
    margin-top:12px;padding:10px;border-radius:12px;
    font-size:12.5px;font-weight:500;letter-spacing:.01em;
    background:rgba(255,255,255,.09);border:1px solid rgba(255,255,255,.14);
  }
  .vid-dl svg{width:14px;height:14px}

  /* ============ SETTINGS ============ */
  .settings-card{max-width:760px}
  /* Карточки аккаунта — одна колонна с тем же шагом 17px, что и соседние
     составные экраны (.settings-col, .create-layout). Отступ принадлежит
     контейнеру: внутренние поля карточек и их padding остаются прежними. */
  #settings-pane-account.active{display:flex;flex-direction:column;gap:17px}
  /* Вся колонна раздела — заголовок, вкладки и карточка — по центру рабочей
     области, той же шириной, что и карточка: прижатая влево узкая карточка
     под заголовком во всю ширину читалась как перекос (замечание владельца,
     01.09.2026, вместе с «Массовой генерацией»). Вкладки едут тоже —
     подчёркнутый ряд, оставшийся во всю ширину, висел бы над карточкой
     сбоку. Ряд вкладок в генераторе не трогаем — там колонка своя. */
  #screen-settings .screen-header,
  #screen-settings .cust-tab-row,
  #screen-settings .cust-tab-body{max-width:760px;margin-left:auto;margin-right:auto}
  .input-edit-row{display:flex;align-items:center;gap:8px}
  .input-edit-row .input{flex:1}
  /* Имя в «Настройках → Account» (решение владельца 15.10.2026): поле с карандашом — до
     середины карточки; на телефоне половина карточки не вмещает обычное имя — там во
     всю ширину. Пока карандаш не нажат, поле только показывает имя: щелчок по нему
     ничего не меняет, поэтому и рамка не загорается, как у поля для ввода. */
  .name-edit-row{width:50%}
  @media (max-width:640px){ .name-edit-row{width:auto} }
  .name-edit-row .input[readonly]{cursor:default}
  .name-edit-row .input[readonly]:focus{border-color:var(--border);box-shadow:none}
  .icon-edit-btn{
    width:40px;height:40px;flex-shrink:0;border:1px solid var(--border);border-radius:12px;background:#fff;color:var(--muted);
    display:flex;align-items:center;justify-content:center;transition:all .15s;
  }
  .icon-edit-btn:hover{background:var(--primary-50);color:var(--primary);border-color:var(--primary-100)}
  .icon-edit-btn svg{width:16px;height:16px}
  .settings-divider{height:1px;background:var(--border2);margin:22px 0}
  /* «Где вы вошли» — по строке на сеанс: устройство, адрес и время, справа
     «Завершить» у всех, кроме этого устройства (renderSessions в app.js). */
  .session-list{display:flex;flex-direction:column;gap:10px}
  .session-row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px;border:1px solid var(--border);border-radius:12px}
  .session-main{min-width:0}
  .session-dev{font-size:14px;font-weight:600;overflow-wrap:anywhere}
  .session-you{font-size:12px;color:var(--primary);font-weight:600;margin-left:8px}
  .session-meta{font-size:12.5px;color:var(--muted);margin-top:2px;overflow-wrap:anywhere}
  .session-empty{font-size:13px;color:var(--muted)}
  /* Карточка удаления аккаунта: заголовок красный, чтобы кнопку не нажимали
     по инерции вместе с остальными настройками. */
  .settings-danger .form-section-title{color:var(--red)}
  .text-link-btn{background:none;border:none;color:var(--primary);font-size:13.5px;font-weight:600;padding:0}
  .text-link-btn:hover{text-decoration:underline}
  .plan-row{display:flex;align-items:center;justify-content:space-between;gap:16px}
  .plan-current{font-size:14px;font-weight:700}
  /* Состояние подписки под названием тарифа («Настройки → Оплата»): цена,
     плашка статуса, дата продления. Плашки трёх цветов — спокойная, тревожная
     и серая; слова в них приходят из словаря, цвет решает renderBilling. */
  .billing-state{display:flex;flex-wrap:wrap;gap:6px 10px;align-items:center;margin-top:8px;font-size:13px;color:var(--muted)}
  .billing-pill{display:inline-block;padding:2px 9px;border-radius:999px;font-size:12px;font-weight:700;background:var(--primary-50);color:var(--primary);white-space:nowrap}
  .billing-pill.ok{background:#e8f7ee;color:#1a7f4b}
  .billing-pill.warn{background:#fff4e5;color:#b45309}
  .billing-load-error{white-space:normal;border-radius:8px}
  .billing-pill.muted{background:var(--border2);color:var(--muted)}
  /* Две кнопки в строке тарифа: «Отменить переход» появляется рядом со «Сменить
     тариф», только когда переход назначен. На телефоне они уходят в столбик. */
  .plan-row-actions{display:flex;flex-wrap:wrap;gap:8px;justify-content:flex-end}
  .plan-row-actions[hidden]{display:none}
  .billing-portal-row{margin-top:12px}
  /* Сколько денег вернулось и когда — строкой под самим платежом: одного слова
     «Возвращён частично» человеку нечем сверить с выпиской банка (Р-405,
     проверка роботом, заход 12, 20.09.2026). Строка идёт во всю ширину и без
     черты над собой — так она читается частью своего платежа, а не соседним
     платежом. */
  .settings-table tr.pay-with-refund td{border-bottom:none}
  .settings-table tr.billing-refund td{padding-top:0;font-size:12px;color:var(--muted)}
  /* Узкий телефон (320 точек): кнопки стоят второй колонкой той же строки и
     забирают половину ширины — «Renews on September 17, 2026» ломалось на три
     строки, а справа от текста оставалось пустое место под кнопкой (Р-407).
     Колонкой строка тарифа читается целиком, кнопки уходят под неё. */
  @media (max-width:360px){
    .plan-row{flex-direction:column;align-items:stretch;gap:12px}
    .plan-row-actions{justify-content:flex-start}
    /* Плашка «Partly refunded» в клетке шириной 80 точек одной строкой не
       помещается (колонки истории равной ширины) и раздвигала страницу вбок —
       на узком телефоне разрешаем ей перенос. */
    .settings-table td .billing-pill{white-space:normal}
  }
  /* Кнопка в портал Paddle получает [hidden] от renderBilling, а display:inline-flex
     у .btn сильнее браузерного [hidden]{display:none} — без этой строки она
     показывалась бы и тому, кому в портале нечего делать. */
  .btn[hidden]{display:none}
  .settings-table td .billing-pill{font-size:11.5px}
  .settings-table th{padding:11px 4px;border-bottom:1px solid var(--border);font-size:12.5px;font-weight:700;color:var(--muted);text-align:left}
  .settings-table td.settings-table-empty{text-align:center;color:var(--muted2);padding:24px 4px}

  /* ============ ХРАНИЛИЩЕ ============ */
  /* Полоса занятости. Тот же язык, что у полосы кодов в меню (.plan-usage):
     обычная — синяя, от 80% — оранжевая, впритык — красная. */
  .storage-usage-top{display:flex;align-items:baseline;justify-content:space-between;gap:8px;
    font-size:13.5px;font-weight:700;margin-bottom:8px;flex-wrap:wrap}
  .storage-usage-free{color:var(--muted);font-weight:500;font-size:12.5px;white-space:nowrap}
  .storage-track{height:8px;border-radius:999px;background:var(--border2);overflow:hidden}
  /* Как и у полос разрезов: длину ставит скрипт после раскладки (app.js). */
  .storage-fill{display:block;height:100%;width:0;border-radius:999px;background:var(--primary);transition:width .25s ease}
  .storage-usage.is-warn .storage-fill{background:var(--orange)}
  .storage-usage.is-full .storage-fill{background:var(--red)}
  /* Предупреждение под полосой — тем же цветом, что и сама полоса: одно
     состояние, один цвет. */
  .storage-note{margin-top:10px;font-size:12.5px;font-weight:600}
  .storage-note.is-warn{color:var(--orange-dark)}
  .storage-note.is-full{color:var(--red)}
  .storage-empty{margin-top:14px}

  /* Разбивка по типам: цветная точка, тип, справа — счёт. Цвета точек — из
     палитры сайта, по одному на категорию (ключи отдаёт сервер). */
  .storage-cat-row{display:flex;align-items:center;gap:10px;padding:6px 0;font-size:13.5px}
  .storage-cat-dot{width:9px;height:9px;border-radius:50%;flex-shrink:0;background:var(--muted2)}
  .storage-cat-dot.video{background:var(--primary)}
  .storage-cat-dot.image{background:var(--teal)}
  .storage-cat-dot.audio{background:var(--orange)}
  .storage-cat-dot.document{background:var(--pick)}
  .storage-cat-dot.logo{background:var(--fav)}
  .storage-cat-name{font-weight:600;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .storage-cat-val{margin-left:auto;color:var(--muted);font-variant-numeric:tabular-nums;white-space:nowrap}

  /* Самые тяжёлые файлы — таблица: файл, какой QR-код держит (у логотипа —
     какие коды им пользуются или прочерк), тип, вес. Раскладка фиксированная
     (ширины в colgroup): имена файлов и кодов пишет человек, любая длина
     обрезается многоточием, целиком её показывает облачко-подсказка. Вес виден
     всегда целиком. */
  .storage-table{width:100%;border-collapse:collapse;table-layout:fixed}
  /* Отступ между колонками 8px — ровно столько, чтобы заголовок третьей колонки
     (до 12.09.2026 — «Тип кода») влезал на 375px без обрезания. */
  .storage-table th{padding:8px 8px 8px 0;border-bottom:1px solid var(--border);
    font-size:12.5px;font-weight:700;color:var(--muted);text-align:left;white-space:nowrap;
    overflow:hidden;text-overflow:ellipsis}
  .storage-table td{padding:9px 8px 9px 0;border-bottom:1px solid var(--border2);
    font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .storage-table tr:last-child td{border-bottom:0;padding-bottom:0}
  .storage-table .num{text-align:right;padding-right:0;font-variant-numeric:tabular-nums}
  .storage-table td.num{font-weight:600;color:var(--muted)}
  .storage-table td.is-name{font-weight:600}
  .storage-table td.is-none{color:var(--muted2)}

  .storage-limit-line{font-size:13.5px;margin-bottom:4px}
  .storage-limit-line:last-child{margin-bottom:0}
  /* На телефоне кнопке «Сменить тариф» тесно рядом со строками лимитов —
     перенос вниз вместо ломания строк на «до 20 / МБ». */
  .storage-plan-row{flex-wrap:wrap}

  /* ============ КОМАНДА И РАБОЧИЙ АККАУНТ ============ */
  /* Полоса «вы в чужом аккаунте». Спокойная, синяя: это не тревога, а
     напоминание, и висеть ей часами. Красной она становится только когда
     доступа больше нет и человек смотрит не на те коды, что ожидал. */
  .ws-strip{
    position:sticky;top:0;z-index:20;padding:10px 28px;font-size:13px;font-weight:600;
    background:var(--primary-50);border-bottom:1px solid var(--primary-100);color:var(--primary-dark);
    display:flex;align-items:center;gap:10px;
  }
  /* display:flex сильнее браузерного [hidden]{display:none} — без этой строки
     полоса не пряталась бы, когда человек вернулся в свой аккаунт. */
  .ws-strip[hidden]{display:none}
  .ws-strip.warn{background:#FFF7ED;border-bottom-color:#FCD9B4;color:#9A5B10}
  .ws-strip svg{width:16px;height:16px;flex-shrink:0}
  .ws-strip .ws-strip-sub{font-weight:500;color:inherit;opacity:.75}

  .ws-item{
    display:flex;align-items:center;gap:12px;padding:12px 14px;border:1px solid var(--border);
    border-radius:12px;margin-bottom:8px;background:#fff;
  }
  .ws-item.on{border-color:var(--primary);background:var(--primary-50)}
  .ws-item.off{opacity:.65}
  .ws-item-main{flex:1;min-width:0}
  .ws-item-name{font-size:13.5px;font-weight:700}
  .ws-item-sub{font-size:12px;color:var(--muted);margin-top:2px}
  .ws-item-here{font-size:12.5px;font-weight:700;color:var(--primary)}

  .team-sum{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:4px}
  .team-seats{font-size:13px;color:var(--muted)}
  .team-row{
    display:flex;align-items:center;gap:12px;padding:12px 0;border-bottom:1px solid var(--border2);
  }
  .team-row:last-child{border-bottom:none}
  .team-row-main{flex:1;min-width:0}
  .team-row-name{font-size:13.5px;font-weight:650;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .team-row-sub{font-size:12px;color:var(--muted);margin-top:2px}
  /* Через `select.`, а не одним классом: у базового `select.input` ширина
     100%, и класс её не перебивает. Пока не перебивал — список ролей занимал
     всю строку, имя человека сжималось в ноль, а «убрать» уезжало за край
     карточки. */
  select.team-role{width:150px;flex-shrink:0}
  .team-tag{
    font-size:11.5px;font-weight:700;padding:3px 9px;border-radius:999px;flex-shrink:0;
    background:var(--border2);color:var(--muted);
  }
  .team-tag.wait{background:#FFF7ED;color:#9A5B10}
  .team-tag.off{background:#FDECEC;color:var(--red)}
  .team-del{background:none;border:none;color:var(--muted2);padding:6px;border-radius:8px;flex-shrink:0}
  .team-del:hover{background:#FDECEC;color:var(--red)}
  .team-del svg{width:16px;height:16px}
  .team-empty{font-size:13px;color:var(--muted);padding:14px 0}
  .team-invite{display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap}
  .team-invite .field{flex:1;min-width:220px;margin:0}
  .team-invite .field.narrow{flex:0 0 170px;min-width:170px}

  @media (max-width:520px){
    /* На телефоне имя с почтой и выпадающий список ролей рядом не помещаются:
       на имя остаётся сотня точек, и человека в собственной команде не
       узнать. Строка разворачивается в два этажа — сверху кто, снизу что ему
       можно и кнопка «убрать». */
    .team-row{flex-wrap:wrap}
    .team-row-main{flex:1 1 100%}
    select.team-role{flex:1;width:auto}
    .team-invite .field.narrow{flex:1 1 100%}
  }

  /* ============ КЛЮЧИ API (вкладка настроек, 23.09.2026) ============ */
  /* Кнопка вкладки «API» прячется атрибутом hidden (apiKeysSyncTab в app.js), а
     display:inline-flex у .cust-tab сильнее браузерного [hidden]{display:none} —
     без этой строки вкладку видели бы все. */
  .cust-tab[hidden]{display:none}
  /* Строка под заголовком — пояснение к нему, вплотную, как у «Команды». */
  .api-keys-lead{margin-top:-8px}
  .api-keys-docs{margin-top:8px;font-size:14px;font-weight:600}
  .api-keys-docs a{color:var(--primary);text-decoration:none}
  .api-keys-docs a:hover{text-decoration:underline}
  .api-keys-note{margin:16px 0 0}
  .api-keys-note-act{margin-top:10px}
  /* «Ваши ключи» и «Create key» одной строкой; значок (i) у серой кнопки —
     вплотную к ней, в той же строке, чтобы на телефоне ничего не ехало вбок. */
  .api-keys-head{margin-bottom:12px}
  .api-keys-head .form-section-title{margin:0}
  .api-keys-create{display:flex;align-items:center;gap:6px;flex-shrink:0}
  /* Строка ключа — тем же видом, что строка сеанса во вкладке «Аккаунт». */
  .api-key-list{display:flex;flex-direction:column;gap:10px}
  .api-key-row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px;border:1px solid var(--border);border-radius:12px}
  .api-key-main{min-width:0}
  .api-key-name{font-size:14px;font-weight:600;overflow-wrap:anywhere}
  .api-key-meta{font-size:12.5px;color:var(--muted);margin-top:2px;overflow-wrap:anywhere}
  .api-key-row .btn{flex-shrink:0}
  /* Начало ключа и сам ключ — моноширинным: их сверяют по знакам с тем, что
     вписано в настройки программы. */
  .api-key-prefix{font-family:ui-monospace,'SFMono-Regular',Menlo,monospace;color:var(--text)}
  /* Шаги окна «Create key» — колонкой, как тело окна у форм (.modal > .auth-form):
     иначе потолок высоты окна до тела не доходит и середине нечем прокручиваться. */
  .api-key-step{display:flex;flex-direction:column;min-height:0}
  .api-key-step[hidden]{display:none}
  /* Поле с ключом: только для чтения, но не серое, как прочие такие поля, —
     ключ читают и сверяют. Высоту под весь ключ ставит apiKeysShowSecret, ключ
     переносится в любом месте. Кегль не задаём: на телефоне поля — 16px, иначе
     iOS Safari увеличивает страницу при касании поля. */
  .input.api-key-secret{min-height:0;resize:none;overflow-y:auto;color:var(--text);
    font-family:ui-monospace,'SFMono-Regular',Menlo,monospace;line-height:1.45;word-break:break-all}
  .api-key-copy-row{display:flex;align-items:center;flex-wrap:wrap;gap:6px 10px;margin-top:10px}
  .api-key-copy-row .hint{margin-top:0}
  /* Доступ ключа в окне «Create key» (заход 2 API, 23.09.2026). Строки — те же,
     что у переключателей окна настроек кода (.sw-row + .sw-text: зона нажатия —
     кружок с подписью, а не пустая строка), только вместо тумблера кружок
     выбора — родной radio цвета сайта, как галочка у .check-row. Подпись группы
     — тем же видом, что подпись поля «Key name» над ней. Кружок стоит на
     середине первой строки: 14px × 1.5 = 21px строки, 16px кружка. */
  .api-key-access{border:0;margin:0 0 16px;padding:0;min-width:0}
  .api-key-access legend{display:block;padding:0;margin-bottom:8px;font-size:13px;font-weight:600;color:#33424F}
  .api-key-access .sw-row + .sw-row{margin-top:10px}
  .api-key-access input{flex-shrink:0;width:16px;height:16px;margin:3px 0 0;accent-color:var(--primary);cursor:pointer}
  /* Пока едет ответ на «Create», выбор заперт (apiKeysBusyModal) — и виден
     запертым, как кнопки подвала окна. */
  .api-key-access:disabled .sw-row{opacity:.5;cursor:default}
  .api-key-access:disabled input{cursor:default}

  /* ============ PREVIEW ============ */
  .preview-card{padding:24px;text-align:center}
  .preview-title{font-size:15px;font-weight:700;margin-bottom:18px;text-align:left}
  /* min-height держит высоту карточки постоянной: разные рамки дают композиции разной высоты */
  .qr-stage{display:flex;align-items:center;justify-content:center;min-height:336px;margin-bottom:16px}
  /* ---- Рамка НЕ верстается в CSS: код, карточка и подпись целиком рисуются на холсте
     тем же paintFrame(), что собирает PNG и SVG. Здесь остаётся только тень —
     drop-shadow повторяет силуэт холста, поэтому корректно обходит вырезы билета,
     хвост облака и «таблетку» бейджа. ---- */
  .qr-wrap{position:relative;display:inline-block;line-height:0}
  .qr-wrap canvas{
    display:block;
    filter:drop-shadow(0 2px 5px rgba(15,30,46,.10)) drop-shadow(0 12px 26px rgba(15,30,46,.13));
    transition:filter .18s ease, opacity .18s ease;
  }
  /* ---- «обработка» при пересборке кода: сам холст на 0.5с гаснет и слегка
     расплывается (тень при этом не трогаем — второй drop-shadow держит форму
     рамки), а вокруг расходится тихий пульс. Пульс кладём поверх наложением
     qr-scan — его положение и скругление задаёт JS той же геометрией, что и
     у самого кода, см. drawPreview() в app.js. ---- */
  .qr-wrap.processing canvas{
    filter:blur(1.5px) contrast(.6) drop-shadow(0 2px 5px rgba(15,30,46,.10)) drop-shadow(0 12px 26px rgba(15,30,46,.13));
    opacity:.7;
  }
  .qr-scan{position:absolute;pointer-events:none;border-radius:inherit}
  .qr-scan::before{
    content:'';position:absolute;inset:0;border-radius:inherit;
    box-shadow:0 0 0 0 rgba(47,111,237,.4);
    opacity:0;
  }
  .qr-wrap.processing .qr-scan::before{animation:qrProcessPulse .5s ease-out}
  @keyframes qrProcessPulse{
    0%{box-shadow:0 0 0 0 rgba(47,111,237,.45);opacity:1}
    70%{box-shadow:0 0 0 14px rgba(47,111,237,0);opacity:.35}
    100%{box-shadow:0 0 0 18px rgba(47,111,237,0);opacity:0}
  }
  /* ---- Полоса «sample» на стоковом коде ----
     Пока в превью наш пример (до первой генерации), поверх кода лежит полоса
     «sample»: без неё стоковый код принимают за свой. Показ ведёт
     syncSampleBadge() в app.js, положение — drawPreview(): блок встаёт ровно
     на квадрат кода в процентах от холста, поэтому не съезжает, когда канвас
     ужат на телефоне. Подсказка открывается наведением, а на телефоне — тапом
     на одну секунду (класс open ставит и снимает по таймеру app.js): липкого
     состояния у неё нет нарочно, решение владельца — после нажатия подсказка
     не должна оставаться ни на компьютере, ни на телефоне. Внутри подсказки —
     мини-копия кнопки генерации и стрелка, указывающая на неё. */
  .qr-sample{position:absolute;display:none;pointer-events:none;z-index:4}
  .qr-sample.show{display:block}
  /* `color-scheme: only light` — полоса часть картинки кода, а не страницы
     (29.09.2026): «затемнение сайтов» Chrome на телефоне белую плашку делало
     тёмной, а тёмные буквы «sample» не трогало — полоса становилась чёрным
     пятном на светлом коде (код — холст, его затемнение не перекрашивает).
     `only light` запрещает браузеру перекрашивать один этот элемент; остальную
     страницу он затемняет как прежде (решение владельца 21.09.2026 — затемнять
     не мешаем). То же у значков соцсетей — ниже, «ЗНАЧКИ СОЦСЕТЕЙ». */
  .qr-sample-band{
    position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:64%;
    display:block;pointer-events:auto;cursor:help;color-scheme:only light;
    background:#fff;border:1px solid rgba(15,30,46,.16);border-radius:5px;
    box-shadow:0 3px 14px rgba(15,30,46,.22);
  }
  /* Логотип живёт в центре кода (свой ещё и открывается нажатием — bindPreviewLogoTap
     в app.js) — полоса уступает ему середину и уходит вниз, подсказка за ней. Любому
     логотипу, и значку соцсети тоже (logo-on, 21.10.2026); облачко «нажмите на
     логотип» над полосой не всплывает (data-tip-off, tipTargetOf). */
  .qr-wrap.logo-on .qr-sample-band{top:71%}
  .qr-sample-band svg{display:block;width:100%;height:auto}
  .qr-sample-band text{font-family:'Manrope','Inter',sans-serif;font-weight:700;font-size:18px;fill:#0F1E2E}
  .qr-sample-tip{
    position:absolute;left:50%;bottom:calc(58% + 12px);transform:translateX(-50%) translateY(4px);
    width:224px;padding:12px 14px;
    background:#fff;color:var(--text);border:1px solid var(--border);border-radius:12px;
    box-shadow:0 10px 28px rgba(15,30,46,.14);
    font-size:13px;font-weight:500;line-height:1.45;text-align:center;
    display:flex;flex-direction:column;align-items:center;gap:9px;
    opacity:0;pointer-events:none;transition:opacity .16s ease,transform .16s ease;z-index:5;
  }
  .qr-wrap.logo-on .qr-sample-tip{bottom:calc(37% + 12px)}
  .qr-sample-tip::after{
    content:'';position:absolute;top:100%;left:50%;transform:translateX(-50%);
    border:6px solid transparent;border-top-color:#fff;filter:drop-shadow(0 1px 0 var(--border));
  }
  /* Наведение — только там, где оно по-настоящему есть (hover:hover): на
     телефоне Chrome «прилепляет» :hover после тапа, и подсказка не гасла бы. */
  @media (hover:hover){
    .qr-sample-band:hover + .qr-sample-tip{opacity:1;transform:translateX(-50%) translateY(0);transition-delay:.12s}
  }
  .qr-sample.open .qr-sample-tip{opacity:1;transform:translateX(-50%) translateY(0)}
  /* Мини-копия зелёной кнопки — картинка, не кнопка: настоящая на этом же экране. */
  .qr-sample-btn{
    display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:7px 13px;
    border-radius:9px;background:linear-gradient(100deg,var(--gen),var(--gen-2));color:#fff;
    font-size:11.5px;font-weight:700;box-shadow:0 4px 10px rgba(95,199,82,.3);
  }
  .qr-sample-btn svg{width:13px;height:13px;flex-shrink:0}
  .qr-sample-arrow{width:20px;height:22px;color:var(--gen);margin-top:-3px}
  /* Название кода стояло здесь, подписью под превью, до 02.09.2026. Теперь оно
     одно и живёт в строке с кнопкой генерации — см. .gen-name-input. */
  /* Формат и «Скачать» лежат в одной строке разметки, но раскладываются по-разному:
     на десктопе кнопка уходит под форматы во всю ширину (высоты в колонке кода вдоволь,
     а главное действие экрана крупным читается лучше), на телефоне встаёт справа
     от форматов — см. блок 900px. Отсюда flex-basis:100%: это и есть перенос строки. */
  .fmt-row{display:flex;flex-wrap:wrap;justify-content:center;align-items:stretch;gap:20px 10px}
  .fmt-btn{
    display:flex;align-items:center;gap:9px;font-family:inherit;background:#fff;border:1px solid var(--border);
    border-radius:12px;padding:7px 14px;font-size:13px;font-weight:700;color:var(--text);transition:all .15s;
  }
  .fmt-btn .fmt-dot{width:15px;height:15px;border-radius:50%;border:1.5px solid #C6D6EA;background:#fff;flex-shrink:0;transition:all .15s}
  .fmt-btn.on{background:var(--gen-soft);border-color:var(--gen)}
  .fmt-btn.on .fmt-dot{border-color:var(--gen);background:var(--gen);box-shadow:inset 0 0 0 3px #fff}
  #btnDownload{flex:1 0 100%;padding:12px 22px;font-size:14px}
  /* «Разместить QR» — своей строкой под «Скачать». Белая, а не цветная: код
     уже готов и уже скачан, это следующий шаг, а не соперник зелёной кнопке.
     Картинка внутри крупнее обычной иконки, потому что она и объясняет
     действие: код, стрелка, лист с местом под код. */
  .btn-place{
    flex:1 0 100%;background:#fff;color:var(--text);border-color:var(--border);
    padding:9px 22px;font-size:13.5px;
  }
  .btn-place:hover{background:var(--primary-50);border-color:var(--primary)}
  .btn-place .btn-place-pic{width:42px;height:24px;flex:none}
  /* Та же фраза, что в облачке кнопки, — для тех, кому наводить нечем.
     Облачко (data-tip) живая кнопка показывает только под мышью: палец не
     наводит, а нажимает. Поэтому под мышью эта строка прячется — облачко
     скажет то же самое и ровно тогда, когда об этом спросили. */
  .fmt-note{flex:1 0 100%;text-align:center;font-size:12px;line-height:1.35;color:var(--muted)}
  @media (hover:hover){ .fmt-note{display:none} }

  /* customize — вкладки настроек, справа от QR */
  /* position — опора для номера пресета в правом нижнем углу карточки (.preset-no ниже). */
  .customize{padding:24px;text-align:left;position:relative}
  /* Заголовок, «Применить» и «Сохранить в пресеты» — одна строка. Галочка стоит
     при заголовке: она про то, что настроено под ним. Пресеты прижаты к правому
     краю не через justify-content, а своим полем: в строке три вещи, и
     space-between развёл бы их по углам, оторвав галочку от заголовка.
     Перенос разрешён — на узком экране им втроём не хватает места (см. @media). */
  .cust-head{display:flex;align-items:center;flex-wrap:wrap;gap:10px;margin-bottom:18px}
  .cust-head .preview-title{margin-bottom:0}
  .cust-head .btn-preset-save{margin-left:auto}
  .btn-sm{padding:7px 13px;font-size:12.5px}
  .btn-sm svg{width:14px;height:14px}
  /* «Сохранить в пресеты» — намеренно тихая кнопка: это не действие экрана, а «убрать
     текущее оформление про запас». Синей рамкой она перетягивала внимание с вкладок,
     под которыми лежат сами настройки, поэтому рамка серая и коробка меньше.
     Радиус общий для всех кнопок сайта (см. блок BUTTONS) — его не трогаем. */
  .btn-preset-save{
    background:#fff;color:var(--muted);border-color:#D8DFE7;
    padding:6px 11px;font-size:12px;
  }
  .btn-preset-save svg{width:13px;height:13px}
  .btn-preset-save:hover{background:#F6FAFE;border-color:var(--muted2);color:var(--text)}
  /* Секунда зелёного после сохранения. Нажатие само по себе ничего на экране не
     двигает — вкладка «Пресеты» может быть даже не открыта, — и без ответа прямо
     под пальцем оно выглядит как «не сработало». Заливкой, а не рамкой: тихая
     серая кнопка меняет рамку слишком незаметно, чтобы это прочиталось вспышкой.
     :hover перебиваем — иначе с мышью на кнопке зелёного не увидеть. */
  .btn-preset-save.saved,
  .btn-preset-save.saved:hover{background:var(--teal-50);border-color:#8FE3AC;color:var(--teal-dark)}
  /* Короткая подпись пресетной кнопки живёт только на телефоне (см. @media 900px) */
  .btn-label-sm{display:none}

  /* ---- «Применить оформление» ----
     Не кнопка-действие, а показание: совпадает ли вид на экране с тем, что стоит
     на сохранённом коде. Отсюда три состояния и один и тот же корпус, что у
     «Сохранить в пресеты», — обе тихие, обе про запас, обе не спорят с вкладками.

     Зелёная = совпадает, применять нечего, поэтому не нажимается. Серая = на коде
     ещё прежний вид, кнопка живая. Бледная = кода ещё нет (не нажата
     «Сгенерировать»), и применять не к чему; повод объясняет подсказка.

     Выключаем через aria-disabled, а не через disabled: выключенная кнопка в
     браузере не отдаёт событий мыши вовсе, и подсказка на ней (data-tip, одно
     облачко на страницу — см. showTip в app.js) не показалась бы никогда —
     то есть человек так и не узнал бы, почему кнопка бледная. */
  .btn-apply-design{
    background:#fff;color:var(--muted);border-color:#D8DFE7;
    padding:6px 11px;font-size:12px;gap:7px;
  }
  /* display у .btn сильнее браузерного [hidden]{display:none}: без этой строки
     кнопка стоит на экране и у гостя, и на правке готового кода, где её быть
     не должно (то же место грабель, что и у .ws-strip выше). */
  .btn-apply-design[hidden]{display:none}
  .btn-apply-design svg{width:13px;height:13px}
  .btn-apply-design:hover{background:#F6FAFE;border-color:var(--muted2);color:var(--text)}
  /* Зелёный — тот же, что у кнопки «Сгенерировать QR-код»: на этом экране это
     единственный зелёный, и второй его оттенок читался бы как другое состояние,
     а не как «готово». Заливкой, а не рамкой: серая и зелёная кнопки должны
     различаться через весь экран, боковым зрением. */
  .btn-apply-design.on,
  .btn-apply-design.on:hover{
    background:linear-gradient(100deg,var(--gen),var(--gen-2));
    border-color:var(--gen);color:#fff;cursor:default;
  }
  .btn-apply-design.idle,
  .btn-apply-design.idle:hover{
    background:#fff;border-color:#E4E9EF;color:var(--muted2);cursor:default;
  }
  .cust-tab-row{display:flex;gap:4px;flex-wrap:wrap;border-bottom:1px solid var(--border2);margin-bottom:14px}
  .cust-tab{
    position:relative;
    display:inline-flex;align-items:center;gap:6px;background:none;
    border:none;border-bottom:2px solid transparent;margin-bottom:-1px;padding:10px 12px;
    font-size:13.5px;font-weight:600;color:var(--muted);transition:all .15s;white-space:nowrap;
  }
  .cust-tab svg{width:15px;height:15px;flex-shrink:0}
  .cust-tab:hover{color:var(--text)}
  .cust-tab.active{color:var(--primary);border-bottom-color:var(--primary)}
  /* Вспышка «пресет добавился». Значок держится внутри границ вкладки: на телефоне
     строка вкладок — это контейнер с overflow, и всё, что торчит за край, там срезается.
     Место под него зарезервировано всегда: выезжай он поверх подписи, на секунду
     вместо «Пресеты» читалось бы «Пресет» — а это ровно та секунда, ради которой
     значок и появляется. Лишние 20px в строку вкладок влезают.
     Правила стоят после .active намеренно — на активной вкладке вспышка должна
     перебивать синий, иначе на ней же её и не видно. */
  .cust-tab[data-pane="preset"]{padding-right:32px}
  .tab-bump{
    position:absolute;top:50%;right:6px;padding:1px 5px;border-radius:999px;
    background:var(--teal);color:#fff;font-size:10.5px;font-weight:800;line-height:1.45;
    opacity:0;transform:translateY(-50%) scale(.7);transition:opacity .18s,transform .18s;pointer-events:none;
  }
  .cust-tab.bumped{color:var(--teal)}
  .cust-tab.bumped .tab-bump{opacity:1;transform:translateY(-50%) scale(1)}
  .cust-tab-body{min-height:90px}
  .cust-pane{display:none}
  /* Содержимое вкладки не подменяется рывком, а проявляется сверху вниз — тем
     же приёмом, что ответ в «Частых вопросах» (.faq-a ниже): у элемента с
     display:none начального состояния нет, и переход тут не работает, а
     короткая анимация работает. 0,18 с — столько же, сколько у окна. */
  .cust-pane.active{display:block;animation:custPaneIn .18s ease}
  @keyframes custPaneIn{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}
  .color-row{display:flex;gap:14px}
  .color-item{display:flex;align-items:center;gap:9px;font-size:13px;font-weight:500}
  .color-item input[type=color]{
    width:36px;height:36px;border:1px solid var(--border);border-radius:11px;padding:2px;background:#fff;cursor:pointer;flex-shrink:0;
  }
  .color-item .hex-input{
    width:78px;height:32px;border:1px solid var(--border);border-radius:8px;padding:0 8px;font-size:12.5px;font-family:inherit;
    font-weight:600;letter-spacing:.02em;text-transform:uppercase;color:var(--text);background:#fff;
  }
  .color-item .hex-input:focus{outline:none;border-color:var(--primary)}
  .color-item .color-label{color:var(--muted);font-weight:500}
  /* градиент: чекбокс над цветами; цвета градиента (код+второй) сгруппированы и визуально отделены от фона */
  /* Переключатели оформления («Градиент», «Цвет центра глаза», «Градиент рамки»):
     тот же тумблер, что в настройках кода (.sw), а не системная галочка.
     display:flex здесь нельзя — метка растягивалась на всю строку, и нажатие
     где угодно справа от текста переключало настройку. inline-flex обнимает
     только тумблер с подписью; сверху display:block от обёртки не нужен,
     перенос строки после метки даёт сама следующая .color-row.

     Зона нажатия — тумблер вместе с подписью, ровно как у .sw-row и
     .check-row в остальном сайте (решение владельца, 04.09.2026). Промежуточный
     заход в тот же день, когда нажимался один тумблер без подписи, отменён:
     правило по всему сайту должно быть одно, и объяснять человеку, что здесь
     подпись нажимается, а там нет, нечем. Лишнего в зоне не остаётся всё равно
     — inline-flex обнимает пару «тумблер + подпись» и дальше не тянется. */
  .grad-check{display:inline-flex;align-items:center;gap:10px;margin-bottom:16px;font-size:13.5px;font-weight:600;color:var(--text);cursor:pointer;user-select:none;vertical-align:top}
  .grad-check .sw{margin-top:0;width:36px;height:21px}
  .grad-check .sw-track::after{width:15px;height:15px}
  .grad-check .sw input:checked + .sw-track::after{transform:translateX(15px)}
  .color-group{background:#F6F8FB;border:1px solid var(--border);border-radius:12px;padding:14px}
  .bg-row{margin-top:14px;padding-left:15px}
  #gradTypeRow,#frameGradTypeRow{margin-top:14px;min-height:0}
  .grad-swatch{width:34px;height:34px;border-radius:8px;display:block;box-shadow:inset 0 0 0 1px rgba(15,30,46,.08)}
  .opt-row{display:flex;gap:10px;flex-wrap:wrap;min-height:90px}
  /* Чекбоксы цвета глаз/центра отделены от плиток форм глаз чуть заметнее,
     чем стандартный отступ .opt-row — иначе они лепятся прямо к кнопкам. */
  .eye-opt-row{margin-bottom:18px}
  #eyeColorRow{margin-bottom:18px}
  .opt-btn{
    border:1px solid var(--border);background:#fff;border-radius:14px;padding:16px;display:flex;flex-direction:column;
    align-items:center;gap:8px;min-width:98px;transition:all .15s;color:var(--text);
  }
  .opt-btn span{font-size:12.5px;font-weight:600;color:var(--text)}
  .opt-btn:hover{border-color:var(--primary-100)}
  .opt-btn.selected{border-color:var(--primary);background:var(--primary-50);color:var(--primary)}
  .opt-btn.selected span{color:var(--primary)}
  .opt-btn svg{width:38px;height:38px}
  /* Тип градиента — только свотч, без подписи, поэтому кнопка меньше обычной опции.
     Правило идёт после .opt-btn: специфичность та же, побеждает последнее. */
  .grad-type-btn{padding:6px;min-width:0;gap:0;border-radius:11px}
  /* «Глаза» — только иконка, без подписи */
  .opt-btn.opt-icon{padding:18px;min-width:88px;min-height:88px;justify-content:center}
  .opt-btn.opt-icon svg{width:52px;height:52px}
  /* Кнопка рамки показывает саму рамку, поэтому картинка вытянутая, а не квадратная,
     и поля вокруг неё меньше обычных: на такой кнопке пустое место только мешает
     разглядеть, чем рамки отличаются друг от друга. */
  .opt-btn.opt-frame{padding:9px 10px;min-width:82px;min-height:96px;justify-content:center}
  .opt-btn.opt-frame svg{width:46px;height:46px}
  .opt-btn .frame-thumb{width:62px;height:78px;object-fit:contain;display:block}
  /* Рамка, снятая с выбора, но уже стоящая в этом коде: выбрать её заново
     нельзя, поэтому она помечена и стоит особняком в конце ряда. */
  .opt-btn.frame-retired{position:relative;border-style:dashed;opacity:.8;padding-bottom:20px}
  .opt-btn.frame-retired::after{content:'снята';position:absolute;left:0;right:0;bottom:5px;font-size:9px;font-weight:700;letter-spacing:.03em;color:var(--muted2);text-align:center}

  /* ---- настройки SVG-рамки ---- */
  /* Подсказка вместо пустоты, пока рамка не выбрана: настройки ниже скрыты,
     и без строки вкладка читается как «тут только плитки и всё». */
  .frame-hint{
    margin-top:18px;padding:13px 15px;border:1px dashed var(--border2);border-radius:12px;
    font-size:13px;line-height:1.45;color:var(--muted);
  }
  .frame-opts{margin-top:18px;padding-top:18px;border-top:1px solid var(--border)}
  .frame-text{margin-top:16px}
  .frame-lbl{display:block;font-size:11.5px;font-weight:600;color:var(--muted);margin-bottom:6px}
  .frame-input{
    width:100%;padding:9px 11px;border:1px solid var(--border);border-radius:10px;
    font:inherit;font-size:13px;color:var(--text);background:#fff;
  }
  .frame-input:focus{outline:none;border-color:var(--primary)}
  /* Текст и шрифт — в одну строку; размер идёт ниже в той же колоночной сетке,
     поэтому его поле ровняется по ширине поля текста, а не тянется во всю панель. */
  .frame-text-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}
  .frame-text-grid + .frame-text-grid{margin-top:12px}
  /* ---- пресеты: сетка плиток с миниатюрой оформления ---- */
  /* Ряд пресетов не выше трёх строк плиток: полсотни пресетов растягивали
     страницу на два экрана (13.09.2026). Дальше — прокрутка внутри ряда.
     Отступ сверху и справа — под крестики, они торчат за плитку. */
  .preset-grid{display:flex;gap:10px;flex-wrap:wrap;max-height:360px;overflow-y:auto;padding:8px 8px 2px 0}
  .preset-tile{position:relative}
  .preset-btn{
    border:1px solid var(--border);background:#fff;border-radius:14px;padding:10px;
    display:flex;align-items:center;justify-content:center;width:92px;height:104px;transition:all .15s;
  }
  .preset-btn:hover{border-color:var(--primary-100)}
  .preset-tile.active .preset-btn{border-color:var(--primary);background:var(--primary-50)}
  .preset-btn img{max-width:100%;max-height:100%;object-fit:contain;display:block}
  /* Крестик удаления показываем только при наведении: плитка — про картинку, а не про кнопки */
  .preset-del{
    position:absolute;top:-6px;right:-6px;width:22px;height:22px;border-radius:50%;
    background:#fff;border:1px solid var(--border);color:var(--muted);
    display:flex;align-items:center;justify-content:center;box-shadow:var(--shadow);
    opacity:0;transition:opacity .15s,color .15s,border-color .15s;
  }
  .preset-del svg{width:11px;height:11px}
  .preset-tile:hover .preset-del,.preset-del:focus{opacity:1}
  .preset-del:hover{color:var(--red);border-color:#F3C9C9;background:#FEF2F2}
  /* Номер пресета (01.10.2026, решение владельца): по нему программа клиента создаёт коды
     через API, поэтому он есть только у тех, у кого есть API (presetTileNo в app.js). Подпись
     «Preset 7» мелко и серо в правом нижнем углу карточки «Настройка QR», только пока мышь на
     плитке этого пресета или на ней фокус с клавиатуры. Постоянно не висит и на самих плитках не
     стоит; правила «на телефоне видна всегда» нет намеренно — наводить там нечем. В разметке подпись
     идёт сразу после своей плитки, поэтому её зажигает сосед слева (+), а опора её места —
     карточка (.customize), а не ряд: ряд и вкладка не позиционированы. Нажатие проходит
     сквозь подпись. */
  .preset-no{
    position:absolute;right:18px;bottom:8px;
    font-size:11.5px;line-height:1;font-weight:500;color:var(--muted2);white-space:nowrap;
    pointer-events:none;opacity:0;transition:opacity .15s;
  }
  .preset-tile:hover + .preset-no,.preset-tile:has(.preset-btn:focus-visible) + .preset-no{opacity:1}
  .preset-empty{font-size:13px;color:var(--muted);line-height:1.55;max-width:420px}
  .seg-mini{display:inline-flex;background:var(--primary-50);border-radius:999px;padding:3px;gap:3px}
  .seg-mini button{border:none;background:transparent;padding:6px 13px;border-radius:999px;font-size:12px;font-weight:600;color:var(--muted)}
  .seg-mini button.active{background:#fff;color:var(--text);box-shadow:var(--shadow)}
  .dropzone-logo{padding:18px}
  .logo-row{display:flex;align-items:center;gap:10px;margin-top:12px}
  /* Плитка с загруженной картинкой — та же, что в библиотеке, и снимается тем же
     крестиком. Показывается только гостю: в аккаунте логотип уже стоит плиткой
     в библиотеке, и вторая копия рядом была бы дублем. Крестик здесь видно
     сразу, без наведения: другого способа убрать логотип у гостя нет.
     Имя файла нужно только для внутренней логики (снимок/восстановление
     оформления), поэтому остаётся скрытым. */
  .logo-preview{padding:4px;background:#F6FAFF;cursor:default}
  .logo-preview img{width:100%;height:100%;object-fit:contain;display:block}
  .logo-row .logo-tile-del{opacity:1}
  .logo-name{display:none}

  /* Библиотека логотипов аккаунта: плитки над зоной загрузки. Стоит выше
     дропзоны намеренно — уже загруженное чаще нужно, чем новое. */
  .logo-lib{margin-bottom:14px}
  .logo-lib-head{display:flex;align-items:baseline;justify-content:space-between;margin-bottom:8px}
  .logo-lib-head>span:first-child{font-size:12px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.04em}
  .logo-lib-count{font-size:12px;color:var(--muted2)}
  .logo-lib-grid{display:flex;flex-wrap:wrap;gap:8px}
  .logo-tile{position:relative;width:56px;height:56px}
  .logo-tile-pick{width:100%;height:100%;padding:4px;border:1px solid var(--border);border-radius:12px;background:#F6FAFF;cursor:pointer;display:block}
  .logo-tile-pick img{width:100%;height:100%;object-fit:contain;display:block}
  .logo-tile.selected .logo-tile-pick{border-color:var(--brand);box-shadow:0 0 0 2px rgba(71,102,169,.18)}
  .logo-tile-del{position:absolute;top:-6px;right:-6px;width:20px;height:20px;border-radius:50%;border:1px solid var(--border);background:#fff;color:var(--muted);font-size:14px;line-height:1;cursor:pointer;opacity:0;transition:opacity .12s}
  .logo-tile:hover .logo-tile-del,.logo-tile-del:focus{opacity:1}
  .logo-tile-del:hover{color:var(--danger,#D14343);border-color:currentColor}
  /* На телефоне наводить нечем: крестики пресетов и логотипов были невидимы,
     но нажимались, и случайное касание угла плитки стирало пресет
     (13.09.2026). Где наведения нет — крестик виден всегда. */
  @media (hover:none){
    .preset-del,.logo-tile-del{opacity:1}
    /* Пальцем крестик в 20 точек не поймать, а промах по нему — нажатие на саму
       плитку, то есть чужой логотип на коде. На телефоне карточка логотипа и её
       крестик крупнее: крестик — 24 точки, как остальные мелкие цели сайта
       (Р-100, проверка роботом, заход 4). Плитки лежат рядом с переносом строки,
       поэтому вширь библиотека от этого не растёт. */
    .logo-tile{width:64px;height:64px}
    .logo-tile-del{width:24px;height:24px;top:-8px;right:-8px;font-size:15px}
    /* Крестик пресета — той же меркой (Р-112, проверка роботом, заход 4):
       22 точки пальцем не поймать, а промах по крестику — нажатие на саму
       плитку, то есть чужое оформление на коде. Отступ .preset-grid сверху и
       справа — 8 точек, ровно под вынос крестика, и вширь ряд не растёт. */
    .preset-del{width:24px;height:24px;top:-8px;right:-8px}
    .preset-del svg{width:12px;height:12px}
  }
  /* Пояснения под библиотекой: «N логотипов держат место» и «логотип убран из
     библиотеки, но стоит на коде». */
  .logo-lib-note{font-size:12px;color:var(--muted);line-height:1.5;margin-top:8px;max-width:420px}

  /* Готовые логотипы соцсетей — те же плитки, что в библиотеке, но без крестика:
     удалять из набора сайта нечего. Значки круглые с прозрачными углами, поэтому
     отступ внутри плитки чуть больше библиотечного: вплотную к рамке круг
     смотрелся бы крупнее квадратных картинок в соседнем блоке. */
  .logo-social{margin-bottom:14px}
  .logo-social .logo-tile-pick{padding:6px}
  .logo-guest-hint{margin-top:12px;font-size:12px;line-height:1.5;color:var(--muted2)}
  /* Правило под пипетками вкладки «Цвет»: серым, без рамки — это пояснение,
     а не предупреждение об ошибке. */
  .color-hint{margin-top:16px;font-size:12px;line-height:1.5;color:var(--muted2)}
  /* Та же строка, когда выбранные цвета код не прочитать: правило перестаёт быть
     справкой и становится предупреждением — см. syncColorWarning в app.js. */
  .color-hint.warn{color:#B42318;font-weight:600}

  /* ============ MY QR ============ */
  .toolbar{display:flex;align-items:center;gap:14px;margin-bottom:14px}
  /* Поиск стоит в строке фильтров, у правого края: он сужает ту же выборку,
     что переключатель и типы, и разлучать их через всю сетку папок незачем.
     flex:0 1 340px, а не flex:1 — с ненулевым grow строка отдала бы поиску
     весь свободный остаток, и margin-left:auto перестал бы его прижимать. */
  .search-box{position:relative;flex:0 1 340px;margin-left:auto}
  .search-box svg{position:absolute;left:12px;top:50%;transform:translateY(-50%);width:16px;height:16px;color:var(--muted2)}
  .search-box input{width:100%;padding:10px 13px 10px 38px;border:1px solid var(--border);border-radius:999px;outline:none;transition:all .15s;background:#fff}
  .search-box input:focus{border-color:var(--primary);box-shadow:0 0 0 3px rgba(47,111,237,.14)}
  .filter-chips{display:flex;gap:6px;flex-wrap:wrap}
  .fchip{
    display:inline-flex;align-items:center;gap:6px;padding:8px 16px;border-radius:999px;border:1px solid var(--border);
    background:#fff;font-size:12.5px;font-weight:600;color:var(--muted);
    transition:color .15s,border-color .15s,background-color .15s;
  }
  .fchip svg{width:15px;height:15px}
  .fchip:hover{color:var(--text)}
  /* У активного чипа из перехода убран background-color — и это не вкусовщина.
     Заливка здесь градиентная, то есть background-image, а он в переходах не
     интерполируется и встаёт сразу. Анимировался при этом background-color
     (#fff → прозрачный), и пока он ехал, движок рисовал градиент по прямоугольнику,
     не применяя border-radius: вокруг скруглённого чипа на 150 мс вылезали синие
     острые углы. Сняли фон из списка — чип перекрашивается разом, углы на месте.
     В базовом правиле background-color остаётся: там заливка простая (белый
     фон чипа), градиента над ней нет и ломаться нечему. */
  .fchip.active{
    background:linear-gradient(135deg,var(--primary),#4C8CFF);border-color:transparent;color:#fff;
    transition:color .15s,border-color .15s;
  }
  /* счётчик внутри чипа */
  .fchip-count:empty{display:none}
  .fchip-count{
    min-width:18px;padding:0 5px;height:18px;border-radius:999px;background:#EDF2F8;color:var(--muted);
    font-size:11px;font-weight:800;display:inline-flex;align-items:center;justify-content:center;
  }
  .fchip.active .fchip-count{background:rgba(255,255,255,.28);color:#fff}
  /* «Избранное» стоит в ряду разделов рядом с «Корзиной», и чип у него такой же
     серый: цветная рамка и розовая заливка на ховере делали из раздела кнопку
     со значением, хотя он такой же переключатель списка. Цвет остался ровно
     там, где помогает искать глазами, — на сердечке. В активном состоянии
     сердечко белеет вместе с текстом: розовое на синей заливке не читается. */
  .fchip-fav svg{color:var(--fav)}
  .fchip-fav.active svg{color:inherit}
  .fchip-trash:hover{color:var(--red);border-color:#F3C9C9}
  .fchip-trash.active{background:linear-gradient(135deg,#64748B,#94A3B8)}
  /* ---- папки: карточки над списком ---- */
  /* Чипом папка выглядела как ещё один фильтр в общем ряду, хотя это раздел
     хранения. У карточки есть место на то, что о папке нужно знать сразу:
     сколько внутри кодов, как называется и когда заведена.

     Четыре в ряд на полной ширине — потолок в пять папок укладывается в две
     строки, и сетка не съедает список.

     Число колонок считает сама сетка, а не медиазапросы: auto-fill с нижней
     границей в 250px даёт четыре на полной ширине, три примерно с 1115px и две
     ниже 900px. Порогами это пришлось бы угадывать, и на промежуточных ширинах
     карточки прыгали бы с четырёх сразу на две. auto-fill, а не auto-fit:
     при двух папках пустые дорожки остаются, и карточка держит свою четверть,
     а не растягивается на пол-экрана. */
  .folder-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:8px;margin-bottom:11px}
  .folder-grid[hidden]{display:none}
  /* Радиус тот же, что у карточек кодов: папка и код лежат на одном экране
     друг под другом, и разные скругления читались бы как разные разделы. */
  .folder-card{
    position:relative;background:#fff;border:1px solid var(--border);border-radius:var(--r-md);
    padding:14px 16px;box-shadow:var(--shadow);cursor:pointer;transition:all .15s;
    text-align:left;min-width:0;min-height:84px;
  }
  .folder-card:hover{box-shadow:var(--shadow-md);border-color:var(--primary-100)}
  .folder-card:focus-visible{outline:none;border-color:var(--primary);box-shadow:0 0 0 3px var(--primary-100)}
  /* Открытая папка — заливка, а не только рамка: рамку уже занимает наведение,
     и по ней одной не понять, какая из четырёх сейчас показана. */
  .folder-card.is-active{border-color:var(--primary);background:var(--primary-50)}
  .folder-card.is-active:hover{border-color:var(--primary)}
  /* Открытое меню выходит за карточку и должно лежать поверх соседних. */
  .folder-card.open{z-index:30}
  /* Значок в правом нижнем углу и вне потока: своей строкой сверху он добавлял
     карточке 40px высоты, ничего не сообщая — что это папка, и так видно по
     сетке над списком. В углу он остаётся опознавательным знаком, а карточка
     стала ниже на треть. */
  .fc-icon{
    position:absolute;right:12px;bottom:10px;
    display:flex;width:30px;height:30px;border-radius:9px;background:var(--primary-50);color:var(--primary);
    align-items:center;justify-content:center;flex-shrink:0;pointer-events:none;
  }
  .fc-icon svg{width:16px;height:16px}
  .folder-card.is-active .fc-icon{background:#fff}
  /* Имя — первое, что читают: папку узнают по названию, а не по тому, сколько
     в ней лежит. Троеточие висит в правом верхнем углу вне потока — место под
     него освобождает отступ здесь, иначе длинное имя уезжает под кнопку. */
  .fc-name{
    font-weight:700;font-size:14.5px;color:var(--text);padding-right:26px;
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  }
  /* Значки вместо слов «кодов» и «создана»: на карточке в четверть экрана два
     существительных занимали строку целиком, ничего к числу и дате не добавляя.
     Расшифровка — в title и aria-label каждой пары. */
  /* Отступ справа — под значок в углу: без него дата подлезала бы под него. */
  .fc-meta{display:flex;align-items:center;gap:12px;margin-top:14px;min-width:0;padding-right:38px}
  .fc-stat{
    display:inline-flex;align-items:center;gap:5px;flex-shrink:0;
    font-size:12px;font-weight:600;color:var(--muted2);white-space:nowrap;
  }
  /* Дата длиннее счётчика и уступает ему: ужимается многоточием она, а число
     кодов остаётся читаемым на любой ширине карточки. */
  .fc-stat:last-child{flex-shrink:1;min-width:0;overflow:hidden;text-overflow:ellipsis}
  .fc-stat svg{width:13px;height:13px;flex-shrink:0;color:var(--muted2)}
  .fc-dots{
    position:absolute;top:12px;right:12px;
    display:flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;
    border:1px solid transparent;background:none;border-radius:7px;color:var(--muted2);
    transition:all .15s;
  }
  .fc-dots svg{width:15px;height:15px}
  .fc-dots:hover,.folder-card.open .fc-dots{background:#F1F5FA;border-color:var(--border);color:var(--text)}
  .folder-card.is-active .fc-dots:hover,.folder-card.is-active.open .fc-dots{background:#fff}
  /* Меню падает из-под самой кнопки «⋯», а не из-под карточки: между ними
     иначе оказывалась вся высота карточки, и связь кнопки с меню терялась.
     Селектор с родителем — не для красоты: общее правило .qr-menu стоит в файле
     ниже и при равном весе перебивало бы одноклассовый .folder-menu. */
  .folder-card .folder-menu{
    top:40px;right:10px;left:auto;min-width:194px;
    /* Ширину ограничиваем и экраном тоже: на телефоне карточка папки уже меню,
       и фиксированные 230px уводили его за край. */
    max-width:min(230px,calc(100vw - 32px));
  }
  /* Меню шире карточки и раскрывается от её правого края влево. У карточки в
     левой колонке оно при этом уходит за край экрана — там раскрываем вправо.
     Какой случай сейчас, считает JS: колонок в ряду от одной до четырёх. */
  .folder-card .folder-menu.to-right{left:10px;right:auto}
  .folder-card.open .folder-menu{display:block}

  /* ---- где мы сейчас: строка над сеткой папок ----
     Появляется, когда открыта папка или корзина. Стрелка стоит первой и
     отдельной кнопкой: выйти на уровень выше — самое частое действие внутри
     папки, и искать его в меню или у имени человек не должен. */
  .folder-path{display:flex;align-items:center;gap:10px;margin-bottom:8px;min-width:0}
  .folder-path[hidden]{display:none}
  .fp-back{
    display:flex;align-items:center;justify-content:center;width:34px;height:34px;flex-shrink:0;padding:0;
    border:1px solid var(--border);background:#fff;border-radius:11px;color:var(--muted2);
    box-shadow:var(--shadow);transition:all .15s;
  }
  .fp-back svg{width:18px;height:18px}
  .fp-back:hover{border-color:var(--primary-100);color:var(--primary);box-shadow:var(--shadow-md)}
  .fp-back:focus-visible{outline:none;border-color:var(--primary);box-shadow:0 0 0 3px var(--primary-100)}
  /* Путь до открытой папки: родители — кнопками, открытая — обычным текстом
     справа. Серые и тоньше, чем имя: это дорога, а не место, где мы сейчас.
     Ужимаются они первыми (flex-shrink) — упереться в край должен путь, а не
     название открытой папки. */
  .fp-crumbs{display:inline-flex;align-items:center;gap:6px;min-width:0;flex-shrink:3}
  .fp-crumb{
    max-width:170px;padding:3px 7px;border:1px solid transparent;background:none;
    border-radius:8px;font-family:inherit;font-size:14px;font-weight:700;color:var(--muted2);
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:all .15s;
  }
  .fp-crumb:hover{background:var(--primary-50);color:var(--primary)}
  .fp-crumb:focus-visible{outline:none;border-color:var(--primary);box-shadow:0 0 0 3px var(--primary-100)}
  .fp-sep{font-size:14px;font-weight:600;color:#C4D2DF;flex-shrink:0}
  /* «…» вместо имени: у спрятанных уровней имён в строке нет, они в меню. */
  .fp-more{position:relative;display:inline-flex;flex-shrink:0}
  .fp-dots{display:flex;align-items:center;justify-content:center;padding:5px 6px}
  .fp-dots svg{width:15px;height:15px}
  .fp-more.open .fp-dots{background:#F1F5FA;border-color:var(--border);color:var(--text)}
  /* Меню раскрывается вправо от «…»: оно стоит в левой половине строки, и
     влево (как у карточек) ушло бы за край экрана. */
  .fp-more.open .fp-menu{display:block}
  .fp-menu{left:0;right:auto;min-width:180px}

  /* Значок папки тот же, что в углу карточки, и та же плитка под ним: строка
     сверху и карточка, из которой в неё пришли, — про одну и ту же папку. */
  .fp-here{display:inline-flex;align-items:center;gap:8px;min-width:0}
  .fp-icon{
    display:flex;align-items:center;justify-content:center;width:28px;height:28px;flex-shrink:0;
    border-radius:9px;background:var(--primary-50);color:var(--primary);
  }
  .fp-icon svg{width:16px;height:16px}
  /* Корзина — серая, тем же серым, что и её включённый чип: это раздел
     хранения, а не опасность сама по себе. Красное здесь только у действия,
     и только когда до него дотянулись, — см. .sel-btn.danger:hover. */
  .fp-icon.is-trash{background:#F1F5F9;color:#64748B}
  .fp-name{
    font-weight:800;font-size:15px;color:var(--text);
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  }
  /* «Очистить» — к правому краю: слева читают, где находятся, справа
     распоряжаются. Кнопка та же, что в панели выделенного (.sel-btn.danger),
     и не случайно: обе про одно и то же удаление навсегда, и стоят они через
     строку друг от друга — разный вид у них читался бы как разное действие. */
  .fp-clear{margin-left:auto;flex-shrink:0}

  /* Строка папки в списках выбора: имя, под ним путь до неё. Путь мельче и
     серым — это подпись, по которой отличают два одинаковых «Меню», а не
     название, которое ищут глазами. */
  .fp-line{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .fp-sub{
    display:block;margin-top:1px;font-size:11px;font-weight:600;color:var(--muted2);
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  }

  /* ---- вид кода и тип содержимого ---- */
  /* Стоят под папками и над списком: это свойства самих кодов, а не разделы,
     и действуют они внутри выбранной папки так же, как снаружи.

     Обёртка нужна, чтобы поиск пережил появление панели выделенного: панель
     подменяет собой только .list-filters, а поиск лежит рядом с ними обоими
     и остаётся на месте. */
  .list-bar{display:flex;align-items:center;gap:12px;margin-bottom:11px;flex-wrap:wrap}
  .list-filters{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
  .list-filters[hidden]{display:none}
  /* Сегменты, а не три отдельных чипа: положения взаимоисключающие, и общая
     рамка вокруг них говорит об этом раньше, чем человек попробует. */
  .seg-switch{display:inline-flex;background:#EDF3FA;border:1px solid var(--border);border-radius:999px;padding:3px;gap:2px}
  .seg-opt{
    padding:7px 15px;border:none;background:none;border-radius:999px;
    font-size:12.5px;font-weight:600;color:var(--muted);transition:all .15s;white-space:nowrap;
  }
  .seg-opt:hover{color:var(--text)}
  .seg-opt.active{background:#fff;color:var(--primary);box-shadow:0 1px 3px rgba(20,50,90,.10)}

  .type-filter{position:relative}
  .type-filter-btn{
    display:inline-flex;align-items:center;gap:7px;padding:8px 14px;
    border:1px solid var(--border);border-radius:999px;background:#fff;
    font-size:12.5px;font-weight:600;color:var(--muted);transition:all .15s;
  }
  .type-filter-btn svg{width:15px;height:15px;flex-shrink:0}
  .type-filter-btn .chev{width:12px;height:12px;transition:transform .15s}
  .type-filter.open .type-filter-btn .chev{transform:rotate(180deg)}
  .type-filter-btn:hover{border-color:var(--primary-100);background:var(--primary-50);color:var(--text)}
  /* Отмеченные типы — синим: выключенный фильтр от включённого должно быть
     видно, не открывая список. */
  .type-filter.has-pick .type-filter-btn{border-color:var(--primary);background:var(--primary-50);color:var(--primary)}
  .type-menu{
    display:none;position:absolute;top:calc(100% + 6px);left:0;z-index:40;
    min-width:210px;max-height:320px;overflow-y:auto;padding:6px;background:#fff;
    border:1px solid var(--border);border-radius:14px;box-shadow:0 12px 32px rgba(15,30,46,.16);
  }
  .type-filter.open .type-menu{display:block}
  .type-opt{
    display:flex;align-items:center;gap:9px;width:100%;padding:8px 10px;border:none;background:none;
    border-radius:9px;font-family:inherit;font-size:13px;font-weight:600;color:var(--text);
    text-align:left;transition:background .15s,color .15s;
  }
  .type-opt:hover{background:var(--primary-50);color:var(--primary)}
  .type-opt-box{
    display:flex;align-items:center;justify-content:center;width:17px;height:17px;flex-shrink:0;
    border:1.5px solid var(--border);border-radius:5px;background:#fff;color:#fff;transition:all .15s;
  }
  .type-opt-box svg{width:11px;height:11px;opacity:0}
  .type-opt.on .type-opt-box{background:var(--primary);border-color:var(--primary)}
  .type-opt.on .type-opt-box svg{opacity:1}
  .type-opt.on{color:var(--primary)}
  .type-opt-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .type-menu-sep{height:1px;background:var(--border2);margin:5px 6px}
  /* У сортировки выбран всегда ровно один пункт, поэтому и рамка у кнопки
     подсвечена только когда порядок не тот, что по умолчанию: подсвеченная
     всегда, она перестала бы что-либо сообщать. */
  .sort-filter .type-menu{min-width:190px}
  /* Кружок вместо квадрата: у типов отмечают сколько угодно, у порядка — ровно
     один, и форма галочки говорит об этом до первого клика. */
  .sort-filter .type-opt-box{border-radius:50%}
  .type-menu-empty{padding:10px;font-size:12.5px;color:var(--muted2);line-height:1.4}

  /* ---- выбор папки на «Аналитике» ----
     Та же кнопка с выпадающим списком, что у типов и сортировки: обе решают,
     по чему считать то, что под ними, и выглядеть должны одинаково. Разница
     одна — сверху поиск, потому что папок бывает сотня, а типов десять. */
  /* Раскрывается от правого края: кнопка стоит в правой половине карточки,
     и список, растущий вправо, уехал бы за экран. */
  .folder-filter .type-menu{left:auto;right:0;min-width:280px;max-width:min(360px,90vw);padding:0}
  /* Длинное название папки обрезаем, а не растягиваем им кнопку: справа от неё
     ещё галочка и период, и кнопка на пол-карточки выдавила бы их за край. */
  .folder-filter #statsFolderLabel{max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  /* Кнопка стоит вплотную к кнопке периода и обязана быть с ней одного роста:
     у .type-filter-btn отступы на пиксель больше, и рядом это видно. */
  .chart-tools .type-filter-btn{padding:7px 13px}
  /* Список прокручивается под закреплённым поиском, а не вместе с ним. */
  .fmenu-search{
    position:sticky;top:0;z-index:1;padding:8px;background:#fff;
    border-bottom:1px solid var(--border2);border-radius:14px 14px 0 0;
  }
  .fmenu-search svg{position:absolute;left:20px;top:50%;transform:translateY(-50%);width:15px;height:15px;color:var(--muted2)}
  .fmenu-search input{
    width:100%;padding:8px 11px 8px 33px;border:1px solid var(--border);border-radius:999px;
    font-family:inherit;font-size:13px;outline:none;background:#fff;transition:all .15s;
  }
  .fmenu-search input:focus{border-color:var(--primary);box-shadow:0 0 0 3px rgba(47,111,237,.14)}
  .fmenu-list{padding:6px}
  /* Строка папки — та же, что в окне «Перенести в папку»: имя, путь под ним,
     число кодов справа. Отличие одно: выбранная подсвечена, потому что здесь
     выбор остаётся на экране, а не выполняет действие и закрывается. */
  .fmenu-list .folder-pick-item{min-height:42px;padding:8px 10px;font-size:13.5px}
  .fmenu-list .folder-pick-item.on{background:var(--primary-50);color:var(--primary)}
  .fmenu-list .folder-pick-item.on>svg{color:var(--primary)}
  .fmenu-sep{height:1px;background:var(--border2);margin:5px 6px}

  /* ---- панель выделенного ---- */
  /* Появляется только с первой галочкой и на месте переключателя, а не над ним:
     полоса, которая всегда висит над списком и почти всегда бесполезна, только
     съедает высоту. Своей подложки у неё нет — рамка вокруг двух кнопок внутри
     и без того обрамлённого экрана ничего к ним не прибавляла, а высоту строки
     задирала. Действия стоят слева, там же, где только что был переключатель:
     глаз ищет их на прежнем месте, а не через всю строку. */
  .sel-bar{display:flex;align-items:center;gap:14px;min-width:0}
  .sel-bar[hidden]{display:none}
  .sel-all{display:inline-flex;align-items:center;gap:8px;font-size:13px;font-weight:700;color:var(--text);cursor:pointer;white-space:nowrap}
  .sel-all input{width:18px;height:18px;accent-color:var(--primary);cursor:pointer;margin:0}
  .sel-count{font-size:12.5px;font-weight:600;color:var(--primary)}
  .sel-hint{color:var(--muted);font-weight:600}
  .sel-actions{display:flex;gap:8px;flex-wrap:wrap}
  .sel-btn{
    display:inline-flex;align-items:center;gap:7px;padding:8px 14px;border-radius:var(--r-sm);
    border:1px solid var(--border);background:#fff;font-size:12.5px;font-weight:600;color:var(--muted);transition:all .15s;
  }
  .sel-btn svg{width:15px;height:15px}
  .sel-btn:hover{border-color:var(--primary-100);color:var(--primary);background:var(--primary-50)}
  .sel-btn.danger:hover{border-color:#F3C9C9;color:var(--red);background:#FEF2F2}
  .sel-clear{
    display:flex;align-items:center;justify-content:center;width:28px;height:28px;flex-shrink:0;padding:0;
    border:none;background:none;border-radius:8px;color:var(--muted);transition:all .15s;
  }
  .sel-clear svg{width:16px;height:16px}
  .sel-clear:hover{background:#fff;color:var(--text)}
  /* Панель выделенного на ноутбуке. Её кнопки шире переключателя, которому она
     уступила место, и в строке шириной 1366 px поиск переносился на вторую
     строку, а список уезжал вниз на 45 px — хотя поиск обещан «на месте»
     (проверка «Моих QR», 12.09.2026). Панель берёт всё, что осталось от
     поиска, а поиск на время выделения ужимается. На телефоне у обоих своя
     строка — правила ниже 900 px. */
  @media (min-width:901px){
    .sel-bar{flex:1 1 0}
    .list-bar:has(.sel-bar:not([hidden])) .search-box{flex-basis:220px}
  }

  /* ---- метка срока работы под названием ---- */
  .sched-tag{display:inline-flex;align-items:center;font-size:11.5px;font-weight:700;padding:1px 7px;border-radius:999px;white-space:nowrap}
  .sched-wait{background:var(--orange-50);color:var(--orange-dark)}
  .sched-over{background:#FEF2F2;color:var(--red)}
  .sched-on{background:var(--teal-50);color:var(--teal-dark)}
  /* Спокойная, без цвета беды: «Открыли 35 из 50» — не поломка, а счёт мест
     (seatsBadge в app.js, ответ владельца 8.5 от 20.09.2026). Рядом с красной
     «Лимит исчерпан» она бы с ней спорила. */
  .sched-idle{background:var(--border2);color:var(--muted)}

  /* Метка «лежит в папке» — там же, в строке под названием. Появляется только
     когда код показан вне своей папки: в находках поиска и в «Избранном».
     Вид приглушённый, как у всей строки: это справка «где искать», а не
     предупреждение, и спорить за внимание со сроком работы ей нечем. */
  .qr-folder-tag{
    display:inline-flex;align-items:center;gap:4px;max-width:170px;
    padding:1px 8px 1px 6px;border-radius:999px;background:var(--border2);color:var(--muted);
    font-size:11.5px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  }
  .qr-folder-tag svg{width:12px;height:12px;flex-shrink:0}

  /* «Создать папку» словом, а не плюсиком: плюс в ряду фильтров читался как
     «ещё фильтры». Пунктир отделяет «создать» от «отфильтровать» — по заливке
     она чип, но делает не то же самое. */
  .fchip-add{
    flex-shrink:0;
    border-style:dashed;border-color:var(--primary-100);color:var(--primary);background:var(--primary-50);
  }
  .fchip-add svg{width:16px;height:16px}
  .fchip-add:hover{background:var(--primary);border-color:var(--primary);color:#fff;border-style:solid}
  /* Лимит исчерпан — кнопка гаснет, но остаётся живой: тап по ней объясняет,
     почему больше нельзя. Выключенная кнопка на телефоне сказать этого не может. */
  .fchip-add.is-full{border-color:var(--border);color:var(--muted2);background:#F6F9FC;border-style:dashed}
  .fchip-add.is-full:hover{background:#EDF2F8;color:var(--muted);border-color:var(--border)}

  .toolbar .btn-primary{margin-left:auto}


  .qr-list{display:flex;flex-direction:column;gap:8px}
  /* «Ещё N — на вкладке Динамические» над списком (renderMyList). Ссылкой, а не
     плашкой: это подсказка, где остальные коды, а не предупреждение. Так же
     выглядят «Ещё N — вне этой папки» и «Ещё N — в корзине» у поиска (.list-hint,
     18.09.2026): класс свой, потому что .list-other-kind значит именно «соседняя
     вкладка» — по нему её ищет и набор проверок. */
  .list-other-kind,.list-hint{
    align-self:flex-start;border:none;background:none;padding:2px 2px 2px 4px;
    font-size:12.5px;font-weight:600;color:var(--primary);cursor:pointer;text-align:left;
  }
  .list-other-kind:hover,.list-hint:hover{text-decoration:underline}

  /* ---- страницы списка ----
     Номера по центру, счёт и размер страницы — справа под ними. Ряд кнопок
     стоит по центру, потому что к нему тянется рука; счёт и «показывать по» —
     это справка и настройка, их читают глазами и трогают редко. */
  .qr-pager{
    display:flex;flex-direction:column;align-items:center;gap:10px;
    margin:15px 0 6px;
  }
  /* Полосу прячет атрибут hidden, но display:flex у класса перебивает его —
     без этой строки она оставалась на экране с числами прошлого списка. */
  .qr-pager[hidden]{display:none}
  .pager-pages{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:center}
  .pager-btn{
    display:inline-flex;align-items:center;justify-content:center;
    min-width:42px;height:42px;padding:0 10px;
    border:1px solid var(--border);border-radius:12px;background:#fff;
    font-size:14px;font-weight:700;color:var(--muted);
    transition:all .15s;
  }
  .pager-btn svg{width:17px;height:17px}
  .pager-btn:hover:not(:disabled):not(.on){background:var(--primary-50);border-color:var(--primary-100);color:var(--text)}
  /* Выключенная стрелка остаётся на месте и не исчезает: ряд кнопок, который
     на первой странице короче, чем на второй, прыгает под рукой. */
  .pager-btn:disabled{opacity:.4;cursor:default}
  .pager-btn.on{
    background:var(--orange);border-color:var(--orange);color:#fff;
    box-shadow:0 2px 8px rgba(249,115,22,.28);
  }
  .pager-gap{color:var(--muted2);font-weight:700;padding:0 2px;user-select:none}

  .pager-side{display:flex;align-items:center;gap:16px;flex-wrap:wrap;justify-content:center}
  .pager-count{font-size:12.5px;color:var(--muted)}
  .pager-per{display:inline-flex;align-items:center;gap:8px;font-size:12.5px;color:var(--muted)}
  .pager-per select{
    padding:6px 10px;border:1px solid var(--border);border-radius:var(--r-sm);
    background:#fff;font-size:12.5px;font-weight:600;color:var(--text);
  }
  /* 16px вместо общих 20px — углы на 20% острее. Число здесь своё, а не в
     переменной: --r держит скругление всех карточек сайта, и подвинуть его
     значило бы заодно подвинуть аналитику, модалки и панели. */
  /* flex-wrap здесь не про телефон, а про промежуточные ширины окна. Шесть
     колонок помещаются в строку примерно от 1360px; ниже сжималась только
     колонка с названием — остальные держат фиксированную ширину, — и на 1000px
     она схлопывалась в ноль: имя и ссылка исчезали совсем.

     Перенос вместо порога в медиазапросе: ряд кнопок уходит на свою строку ровно
     тогда, когда перестал помещаться, а не на заранее угаданной ширине. */
  .qr-row{
    display:flex;align-items:center;gap:18px;flex-wrap:wrap;background:#fff;border:1px solid var(--border);border-radius:var(--r-md);
    padding:16px 20px;box-shadow:var(--shadow);transition:all .15s;
    /* якорь для кнопки «⋯» и её меню в правом верхнем углу */
    position:relative;
  }
  /* Код вне своего срока: сейчас он не работает, и список обязан это показать
     раньше, чем человек проверит его телефоном. Гасим превью, как в корзине. */
  .qr-row.is-paused .qr-mini{filter:grayscale(1);opacity:.75}

  /* ---- галочка выделения ---- */
  /* Видна всегда, а не по наведению: на телефоне наводить нечем, и о самой
     возможности выделять коды человек узнаёт только по ней. */
  .qr-check{display:flex;align-items:center;flex-shrink:0;cursor:pointer;padding:2px}
  .qr-check input{width:18px;height:18px;accent-color:var(--primary);cursor:pointer;margin:0}
  .qr-row:hover{box-shadow:var(--shadow-md);border-color:var(--primary-100)}
  /* Открытое меню выходит за карточку и должно лежать поверх соседних:
     карточки идут в потоке, и без подъёма следующая перекрыла бы список папок. */
  .qr-row.menu-open{z-index:30}
  .qr-row.is-trashed{opacity:.72;background:#FBFCFD}
  .qr-row.is-trashed .qr-mini{filter:grayscale(1)}
  /* Код на паузе после конца подписки — бледный, как в корзине: он сейчас не
     работает, и список показывает это раньше всякого чтения. Серую миниатюру
     даёт общий .is-paused в классах строки. Бледнеет содержимое, а не строка
     целиком: кнопки «Снять с паузы» и «Удалить» остаются в полную силу — это
     то, ради чего человек сюда пришёл, и глушить их нельзя (решение владельца
     от 31.08.2026). */
  .qr-row.is-onpause{background:#FBFCFD}
  .qr-row.is-onpause > :not(.qr-row-actions){opacity:.72}

  /* ── Заблокированный код ──
     Мягко красная карточка и красный восклицательный знак у типа кода. Знак и
     есть всё объяснение: текст висит на нём подсказкой (BLOCKED_TIP в app.js),
     в самой строке его нет — список и без того плотный.

     Ниже правил паузы нарочно: код может быть и заблокирован, и на паузе разом,
     а перевешивать должна блокировка — пауза снимается кнопкой, блокировка нет. */
  .qr-row.is-blocked{background:#FFEAE7;border-color:#F0AEA8}
  .qr-blocked-mark{
    display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;
    width:17px;height:17px;padding:0;border:0;background:none;
    color:var(--red);cursor:help;
  }
  .qr-blocked-mark svg{width:17px;height:17px}

  /* ── Пауза после конца подписки ──
     Оранжевая карточка над списком (renderPauseBanner в app.js): появляется,
     только когда подписка кончилась и кодов больше бесплатного лимита. */
  .pause-note{
    display:flex;align-items:center;gap:16px;
    background:var(--orange-50);border:1px solid var(--orange-100);
    border-radius:14px;padding:14px 16px;margin-bottom:14px;
  }
  .pause-note-body{flex:1 1 auto;min-width:0}
  .pause-note-title{font-size:14px;font-weight:700;color:var(--orange-dark);margin-bottom:4px}
  .pause-note-text{font-size:13px;color:var(--text);line-height:1.5}
  /* Счётчик — отдельной строкой под текстом, числа жирные: сколько кодов
     включено и сколько ещё можно, человек должен видеть сразу, не выискивая
     в абзаце. Синим, а не оранжевым — чтобы строка не сливалась с карточкой
     (решение владельца от 31.08.2026). Про продление текста нет нарочно:
     это говорит кнопка рядом, второй раз писать незачем. */
  .pause-note-count{margin-top:8px;font-size:13px;line-height:1.5;color:var(--primary)}
  .pause-note-count b{font-weight:800}
  .pause-note-btn{flex-shrink:0}

  /* ── Предупреждения об аккаунте ──
     Карточка над списком, которую можно закрыть крестиком: место в хранилище
     на исходе и то, что появится здесь дальше (renderNotices в app.js). Того
     же оранжевого, что и карточка паузы: это одного рода сообщения — «сделайте
     что-нибудь, пока не поздно», — и разноцветными им быть незачем. */
  .note-card{
    display:flex;align-items:center;gap:12px;
    background:var(--orange-50);border:1px solid var(--orange-100);
    border-radius:14px;padding:12px 14px;margin-bottom:14px;
  }
  .note-card-body{flex:1 1 auto;min-width:0}
  .note-card-title{font-size:14px;font-weight:700;color:var(--orange-dark);margin-bottom:3px}
  .note-card-text{font-size:13px;color:var(--text);line-height:1.5}
  .note-card-close{
    flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;
    width:28px;height:28px;padding:0;border:0;border-radius:8px;
    background:none;color:var(--orange-dark);opacity:.65;cursor:pointer;transition:all .15s;
  }
  .note-card-close:hover{opacity:1;background:var(--orange-100)}
  .note-card-close svg{width:15px;height:15px}
  /* Кнопка «Пауза» на работающем коде подсвечена тем же оранжевым, что и
     карточка над списком: это одно правило, и выглядеть оно должно одним. */
  .pause-btn{border-color:var(--orange-100);color:var(--orange-dark)}
  .pause-btn:hover:not(:disabled){border-color:var(--orange);background:var(--orange-50);color:var(--orange-dark)}
  /* Без рамки, подложки и внутреннего отступа: белая карточка под белым кодом
     ничего не отделяла — фон списка и так белый, — а рамка с padding съедали
     14px из 83, то есть шестую часть картинки. Сняли их, и код занял коробку
     целиком, не сдвинув при этом ни одной соседней колонки.

     100px, а не 83: колонка с названием, ссылкой и короткой ссылкой набирает
     около сотни, и превью просто добирает высоту соседа — строка от этого не
     растёт. У статических кодов короткой ссылки нет, их карточка ниже, и там
     высоту задаёт как раз превью: обе становятся одного роста, а список —
     ровным. Дальше сотни не идём: следующий шаг двигает уже саму строку. */
  .qr-mini{
    width:100px;height:100px;flex-shrink:0;
    display:grid;place-items:center;
  }
  /* Композиция с рамкой не квадратная (под кодом ещё полоса с подписью), поэтому
     холст вписываем целиком, а не растягиваем на квадрат: растянутая рамка
     показывала бы код не тем, что скачается. Ширину ставит paintMini долей от
     коробки, высоту досчитывает браузер по пропорции холста. */
  .qr-mini canvas{display:block;height:auto}
  /* Нижняя граница, а не ноль: до неё колонка сжимается, дальше строка
     переносит кнопки. 240px — это примерно «Меню ресторана «Белая…» и короткий
     адрес под ним, то есть минимум, по которому код ещё узнаётся. */
  .qr-row-info{flex:1.4;min-width:240px}
  .qr-row-sub{font-size:12.5px;color:var(--muted);margin-top:3px;display:flex;align-items:center;gap:6px}
  .qr-row-sub .sep{width:3px;height:3px;border-radius:50%;background:var(--muted2)}
  /* Дата создания — кнопка ради касания на телефоне (renderMyList в app.js,
     20.10.2026), а выглядит прежним текстом строки. */
  .qr-date{font:inherit;color:inherit;background:none;border:0;padding:0;margin:0;cursor:default;white-space:nowrap}

  /* название кода — редактируется прямо в списке */
  .qr-name-wrap{display:flex;align-items:center;gap:2px;max-width:100%;min-width:0}
  /* Ширину держит колонка, а не число: жёсткие 280px обрезали название
     многоточием посреди пустого места — колонка к тому времени была уже шире.
     min-width:0 обязателен, иначе флекс-элемент не сжимается уже текста и
     многоточия не будет вовсе. */
  .qr-row-name{
    font-weight:700;font-size:14.5px;color:var(--text);cursor:text;border-radius:6px;
    padding:2px 5px;margin:-2px 0 -2px -5px;transition:background .15s;outline:none;
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%;min-width:0;flex:0 1 auto;
  }
  .qr-row-name:hover{background:#F1F5FA}
  .qr-row-name:focus-visible{background:#F1F5FA;box-shadow:0 0 0 2px var(--primary-100)}
  /* Карандаш тоже не должен занимать место, пока его не видно: 22px из той же
     колонки. Ширину ему возвращает наведение — вместе с ней имя на эти 22px
     ужимается, но в этот момент курсор уже на карточке и читают её целиком. */
  .qr-name-pencil{
    display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:0;height:18px;padding:0;
    border:none;background:none;color:var(--muted2);border-radius:6px;opacity:0;overflow:hidden;
    transition:opacity .15s,width .15s,background .15s,color .15s;
  }
  .qr-name-pencil svg{width:10.5px;height:10.5px}
  .qr-name-wrap:hover .qr-name-pencil,.qr-row-name:focus-visible + .qr-name-pencil{opacity:1;width:18px}
  .qr-name-pencil:hover{background:#E4EBF3;color:var(--text)}
  .qr-name-input{
    font:inherit;font-weight:700;font-size:14.5px;color:var(--text);background:#fff;
    border:1.5px solid var(--primary);border-radius:6px;padding:2px 5px;margin:-2px 0 -2px -6.5px;outline:none;
    box-shadow:0 0 0 3px rgba(47,111,237,.14);min-width:140px;max-width:320px;width:100%;
  }
  /* Бейдж скрыт прозрачностью, но место в строке занимал: 75px из колонки
     уходили в пустоту, и название обрывалось многоточием при свободном месте
     справа от него. Сворачиваем в ноль ширины, пока он не понадобился — тогда
     он раздвигает строку сам, а имя ужимается ровно на время показа. */
  .qr-name-saved{
    display:inline-flex;align-items:center;gap:3px;margin-left:0;font-size:11px;font-weight:700;color:#0E7A45;
    opacity:0;transform:translateY(2px);pointer-events:none;white-space:nowrap;
    max-width:0;overflow:hidden;flex-shrink:0;
    transition:opacity .18s,transform .18s,max-width .18s,margin-left .18s;
  }
  .qr-name-saved svg{width:11px;height:11px}
  .qr-name-saved.show{opacity:1;transform:none;max-width:90px;margin-left:4px}

  /* строка со ссылкой + кнопка «Изменить» */
  .qr-link-row{display:flex;align-items:center;gap:8px;margin-top:8px;min-width:0}
  .qr-link-ico{width:14px;height:14px;flex-shrink:0;color:var(--muted2)}
  /* Та же история, что с названием: 340px обрезали ссылку раньше, чем кончалось
     место в строке. Теперь её ширину задаёт свободный остаток строки. */
  .qr-link-text{
    font-size:12.5px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
    flex:0 1 auto;min-width:0;
  }
  /* Без tabular-nums: в Inter он делает моноширинным и дефис, и «my-site» читалось
     как «my - site». Выравнивать в строке адреса нечего (30.09.2026). */
  /* Один карандаш без подписи: слово «Изменить» стояло вплотную к адресу и
     отбирало у него place — а сам адрес здесь и есть то, что читают. Квадрат
     26×26 держит палец на телефоне; смысл — в title и aria-label. */
  /* Серый и мельче соседей: карандаш стоит вплотную к адресу, а синяя заливка
     тянула взгляд сильнее, чем сам адрес, который здесь и читают. 21x21 вместо
     26 — те же 20%, что сняты у карандаша названия. Цвет возвращается на
     наведении: тогда видно, что это кнопка, а не значок. */
  .link-edit{
    display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;
    width:21px;height:21px;padding:0;border:1px solid var(--border);background:#F6F9FC;
    color:var(--muted);border-radius:7px;transition:all .15s;
  }
  .link-edit svg{width:11px;height:11px}
  .link-edit:hover{background:#EAF0F7;border-color:var(--muted2);color:var(--text)}
  /* Обычная стрелка: перечёркнутый круг читался как «сюда нельзя», хотя кнопка
     не запрещает действие, а объясняет, почему его нет — на статическом коде
     менять нечего. Объясняет это подсказка, а не значок у курсора. */
  .link-edit.is-disabled{background:#F1F4F8;border-color:var(--border);color:var(--muted2);cursor:default}
  /* оборачиваем неактивную кнопку в .tip, чтобы навести и увидеть причину */
  .link-edit-tip{display:inline-flex;flex-shrink:0}
  .link-edit-tip .tip-box{width:250px;bottom:calc(100% + 8px)}
  /* ---- короткая ссылка кода ---- */
  /* Второй строкой под адресом цели и мельче него: сверху — куда код ведёт
     (это меняется), снизу — как он называется в мире (это не меняется никогда).
     Моноширинные цифры, чтобы код ссылки читался посимвольно: его диктуют
     по телефону и перепечатывают с бумаги. */
  .qr-short-row{display:flex;align-items:center;gap:6px;margin-top:5px;min-width:0}
  .qr-short-ico{display:inline-flex;flex-shrink:0;color:var(--muted2)}
  .qr-short-ico svg{width:13px;height:13px}
  .qr-short-text{
    font-size:12px;font-weight:600;color:var(--primary);letter-spacing:.01em;
    font-variant-numeric:tabular-nums;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;
  }
  /* Короткая ссылка открывает свой код в новой вкладке (renderMyList в app.js,
     решение владельца 20.10.2026). Вид прежний — она и так синяя; черта под
     мышью говорит «нажми», рамка фокуса — только с клавиатуры. */
  a.qr-short-text{text-decoration:none;cursor:pointer;border-radius:3px}
  a.qr-short-text:hover{text-decoration:underline;text-underline-offset:2px}
  a.qr-short-text:focus-visible{outline:2px solid var(--primary);outline-offset:1px}
  .qr-short-copy{
    display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;
    width:21px;height:21px;padding:0;border:1px solid var(--border);background:#F6F9FC;
    color:var(--muted);border-radius:7px;transition:all .15s;
  }
  .qr-short-copy svg{width:11px;height:11px}
  .qr-short-copy:hover{background:#EAF0F7;border-color:var(--muted2);color:var(--text)}

  /* На десктопе обёртки как будто нет: счётчик остаётся прямым ребёнком
     строки и держит свою колонку. Собирает его в отдельную строку только телефон. */
  .qr-row-meta{display:contents}
  .qr-row-scans{width:130px;flex-shrink:0}
  /* 19.5px вместо 15 — цифры на 30% крупнее: счётчик и есть то, ради чего в
     этот список заходят чаще всего, а он был мельче названия кода. */
  .scan-num{font-weight:700;font-size:19.5px;line-height:1.15}
  .scan-label{font-size:11.5px;color:var(--muted)}
  /* У статического кода в этой ячейке одна подпись — числа у него нет. Раньше
     на месте числа стоял прочерк: сам по себе он не значит ничего, а весом и
     размером спорил с настоящими числами соседних строк (30.09.2026). Место
     ячейки держим прежним, подпись встаёт по середине — сетка, а не flex,
     чтобы выравнивание по правому краю на узких экранах работало как было. */
  .qr-row-scans.is-nostats{display:grid;align-content:center;min-height:38px}
  .qr-row-actions{display:flex;gap:6px;margin-left:auto}
  /* Корзина отделена от соседей лишним просветом. Она стоит последней, вплотную
     к «Скачать», и промах на шесть точек отправляет код в корзину вместо файла.
     Цветом её не выделяем: красная кнопка в покое кричала бы на каждой строке
     списка (решение владельца 30.09.2026 — «просто отделим чутка»). */
  .qr-row-actions .icon-btn.danger{margin-left:6px}

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

     Поэтому ниже 1360px ряд кнопок занимает свою строку целиком, а бейдж со
     счётчиком собираются в группу и прижимаются к правому краю. Так счётчик
     стоит справа на любой ширине — от карточки телефона до полной строки. */
  @media (max-width:1360px) and (min-width:901px){
    .qr-row-meta{display:flex;align-items:center;gap:18px;margin-left:auto;flex-shrink:0}
    .qr-row-scans{width:auto;text-align:right;min-width:88px}
    .qr-row-actions{flex-basis:100%;margin-left:0;justify-content:flex-end;margin-top:2px}
  }
  .icon-btn{
    display:inline-flex;align-items:center;gap:6px;padding:8px 13px;border-radius:var(--r-sm);border:1px solid var(--border);
    background:#fff;font-size:12.5px;font-weight:600;color:var(--muted);transition:all .15s;
  }
  .icon-btn svg{width:14px;height:14px}
  /* Кнопка без подписи — квадрат в высоту соседей, а не обрезок прямоугольника:
     значок в нём стоит по центру, и ряд кнопок читается ровным. Такими идут
     корзина, скачивание и срок работы. */
  .icon-btn.is-icon{width:34px;height:34px;padding:0;justify-content:center;flex-shrink:0}
  .icon-btn.is-icon svg{width:16px;height:16px}
  .icon-btn:hover:not(:disabled){border-color:var(--primary-100);color:var(--primary);background:var(--primary-50)}
  /* У кода с заданным сроком кнопка подсвечена: иначе о расписании на карточке
     не сказано ничего, пока срок не наступил и бейдж не появился. */
  .icon-btn.is-set{border-color:var(--orange-100);background:var(--orange-50);color:var(--orange-dark)}
  .icon-btn.is-set:hover:not(:disabled){border-color:var(--orange);background:var(--orange-50);color:var(--orange-dark)}
  .sched-tip{display:inline-flex;flex-shrink:0}
  .sched-tip .tip-box{width:250px}
  /* Курсор обычный, по той же причине, что и у «Изменить»: причину называет
     подсказка, а не значок у стрелки. */
  .icon-btn:disabled{opacity:.45;cursor:default}
  /* Неактивный Stats объясняется таким же облачком, как «Изменить». Обёртка .tip
     подменяет собой кнопку как элемент раскладки, поэтому размеры держит она. */
  .stats-tip{display:inline-flex;flex-shrink:0}
  .stats-tip .icon-btn{width:100%}
  .stats-tip .tip-box{width:250px}
  .icon-btn.danger:hover:not(:disabled){border-color:#F3C9C9;color:var(--red);background:#FEF2F2}
  /* сердечко «в избранное» */
  .fav-btn{
    display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;flex-shrink:0;
    border:1px solid var(--border);border-radius:var(--r-sm);background:#fff;color:var(--muted2);transition:all .15s;
  }
  .fav-btn svg{width:17px;height:17px;fill:none}
  /* Розовой рамки и розовой заливки у кнопки нет: она стоит в ряду одинаковых
     серых кнопок действий, и подкрашенная целиком читалась как «нажата». Цвет
     здесь несёт само сердечко — красный контур при наведении и красное
     сердечко у того, что уже в избранном. */
  .fav-btn:hover{color:var(--fav)}
  .fav-btn.on{color:var(--fav)}
  /* Залито намеренно: контур того же цвета появляется и при наведении, и без
     заливки «в избранном» не отличалось бы от «сейчас под курсором». */
  .fav-btn.on svg{fill:currentColor}
  .empty-state{text-align:center;padding:60px 20px;color:var(--muted)}
  /* «Не загрузилось» — не то же, что «пусто», и выглядит иначе: строка причины
     тёмная (её надо прочитать, а не проскользить взглядом) и под ней кнопка.
     Без кнопки человеку оставалась бы перезагрузка страницы, о которой ему
     никто не говорит. */
  .empty-fail{color:var(--text);font-weight:500}
  .empty-fail .fail-why{display:block;margin-top:6px;font-weight:400;color:var(--muted)}
  .empty-fail .btn{margin-top:16px}

  /* ---- «⋯» на карточке: перенос кода в папку ---- */
  /* Кнопки нет, пока не создано ни одной папки, — её рисует JS только тогда.
     Стоит в правом верхнем углу: на десктопе это свободное поле над кнопками
     действий, на телефоне — угол рядом с названием. */
  .qr-menu-wrap{position:absolute;top:10px;right:12px}
  .qr-menu-btn{
    display:flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;
    border:1px solid transparent;background:none;color:var(--muted2);border-radius:8px;transition:all .15s;
  }
  .qr-menu-btn svg{width:16px;height:16px}
  .qr-menu-btn:hover,.qr-menu-wrap.open .qr-menu-btn{background:#F1F5FA;border-color:var(--border);color:var(--text)}
  .qr-menu{
    display:none;position:absolute;top:calc(100% + 6px);right:0;z-index:40;
    min-width:212px;max-width:260px;padding:6px;background:#fff;border:1px solid var(--border);
    border-radius:14px;box-shadow:0 12px 32px rgba(15,30,46,.16);
  }
  /* Меню строки кода — не выше двух третей экрана, дальше список папок прокручивается
     внутри. Оно перечисляет все папки аккаунта, и у клиента с сотней папок вытягивалось
     на 4 600 точек — на телефоне пять экранов вниз, «Убрать из папки» в самом конце
     (Р-192, проверка роботом, заход 6, 18.09.2026). Меню карточки папки и «крошек»
     пути короткие — их правило не трогает. */
  .qr-menu-wrap .qr-menu{max-height:min(460px,66vh);overflow-y:auto;overscroll-behavior:contain}
  .qr-menu-wrap.open .qr-menu{display:block}
  .qr-menu-title{
    font-size:11px;font-weight:800;letter-spacing:.03em;text-transform:uppercase;
    color:var(--muted2);padding:6px 10px 7px;
  }
  /* Черта между правкой оформления и списком папок: это разные по смыслу
     действия, и вперемешку они читались бы одним списком. */
  .qr-menu-sep{height:1px;background:var(--border2);margin:5px 6px 1px}
  .qr-menu-item{
    display:flex;align-items:center;gap:9px;width:100%;padding:8px 10px;border:none;background:none;
    border-radius:9px;font-family:inherit;font-size:13px;font-weight:600;color:var(--text);
    text-align:left;transition:background .15s,color .15s;
  }
  .qr-menu-item>svg{width:16px;height:16px;flex-shrink:0;color:var(--muted2)}
  .qr-menu-item:hover{background:var(--primary-50);color:var(--primary)}
  .qr-menu-item:hover>svg{color:var(--primary)}
  .qr-menu-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  /* Галочка у папки, в которой код лежит сейчас: иначе из меню не понять,
     перенос это или код уже там. */
  .qr-menu-check{width:15px;height:15px;flex-shrink:0;opacity:0;color:var(--primary)}
  .qr-menu-item.on{color:var(--primary)}
  .qr-menu-item.on>svg,.qr-menu-item.on .qr-menu-check{color:var(--primary);opacity:1}
  /* Последний пункт, отделённый чертой: «убрать из папки» на карточке и «удалить
     папку» в меню чипа. Оба уводят из текущего состояния, поэтому и выглядят так же. */
  .qr-menu-clear,.qr-menu-danger{margin-top:4px;border-top:1px solid var(--border2);border-radius:0 0 9px 9px;padding-top:10px;color:var(--muted)}
  .qr-menu-clear:hover,.qr-menu-danger:hover{background:#FEF2F2;color:var(--red)}
  .qr-menu-clear:hover>svg,.qr-menu-danger:hover>svg{color:var(--red)}

  /* ============ ANALYTICS ============ */
  /* Полоса «цифры не приехали». Стоит над самими цифрами и жёлтая, а не
     красная: это не поломка аккаунта и не ошибка человека, а «данных сейчас
     нет — попробуйте ещё раз». Красный на этом экране означал бы падение
     показателей, а не отказ связи. */
  .stats-fail{
    display:flex;align-items:center;gap:14px;flex-wrap:wrap;
    background:#FFFBEB;border:1px solid #FDE68A;border-radius:12px;
    padding:13px 16px;margin-bottom:14px;color:#78350F;font-size:14px;line-height:1.45;
  }
  .stats-fail .fail-text{flex:1;min-width:200px}
  .stats-fail .fail-text b{font-weight:600}
  .stats-fail .btn{flex:none}
  /* display:flex сильнее браузерного [hidden]{display:none} — без этой строки
     пустая жёлтая полоса висела бы над цифрами всегда, а не только когда
     цифры не приехали. */
  .stats-fail[hidden]{display:none}
  /* Ожидание цифр. Пока запрос статистики в пути, экран носит класс stats-busy
     (app.js, «ОЖИДАНИЕ ЦИФР»): числа, график, разрезы и таблица бледнеют, а на
     фоне графика появляется «Считаем сканирования» с крутящимся значком — одна
     короткая надпись на месте графика, так решил владелец 11.09.2026. Всё — с
     задержкой 0,2 с: быстрый ответ проходит, не мигнув. Шапка графика с
     периодом и папкой не бледнеет: ими во время ожидания и пользуются. */
  .chart-stage{position:relative}
  .chart-wait{
    position:absolute;inset:0;display:flex;align-items:center;justify-content:center;gap:9px;
    pointer-events:none;visibility:hidden;opacity:0;
    font-size:13.5px;font-weight:600;color:var(--text);text-align:center;
  }
  .stats-busy .chart-wait{visibility:visible;opacity:1;transition:opacity .15s .2s,visibility 0s .2s}
  /* Под замком бесплатного тарифа надпись размылась бы вместе с заготовкой. */
  .pay-lock.on .chart-wait{display:none}
  .chart-wait-spin{
    width:16px;height:16px;flex:none;border-radius:50%;
    border:2px solid var(--primary-100);border-top-color:var(--primary);animation:chart-spin .8s linear infinite;
  }
  @keyframes chart-spin{to{transform:rotate(360deg)}}
  .stats-busy .stats-grid .stat-value,.stats-busy .stats-grid .stat-trend,.stats-busy .stats-grid .stat-alltime,
  .stats-busy .chart-body,.stats-busy .analytics-grid{opacity:.4;transition:opacity .15s .2s}
  .stats-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:11px;margin-bottom:14px}
  .stat-card{padding:20px 22px}
  .stat-top{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}
  /* inline-flex — ради значка (i) рядом с подписью: он живёт внутри неё, чтобы
     не стать третьим предметом в строке и не разъехаться со значком карточки
     (у .stat-top space-between). */
  .stat-label{display:inline-flex;align-items:center;gap:5px;font-size:12.5px;font-weight:600;color:var(--muted)}
  .stat-icon{width:34px;height:34px;border-radius:11px;display:flex;align-items:center;justify-content:center}
  .stat-icon svg{width:16px;height:16px}
  .si-indigo{background:var(--primary-50);color:var(--primary)}
  .si-teal{background:var(--teal-50);color:var(--teal)}
  .si-amber{background:var(--orange-50);color:var(--orange-dark)}
  .si-green{background:#EAF7EF;color:var(--green)}
  .stat-value{font-size:26px;font-weight:800;letter-spacing:-.02em}
  /* Стрелка держится за первую строку подписи: по центру она оказывалась рядом
     со второй, когда «+156.3% к прошлому периоду» переносился на три строки. */
  .stat-trend{font-size:12px;font-weight:600;margin-top:4px;display:flex;align-items:flex-start;gap:4px;line-height:1.25}
  .trend-up{color:var(--green)}
  .trend-down{color:var(--neg)}
  .stat-trend svg{width:12px;height:12px;flex-shrink:0;margin-top:2px}
  /* Значок вместо подписи «к прошлому периоду». Кнопка, а не span: на телефоне
     наведения нет, а тап по кнопке даёт фокус — облачко открывается и по нему. */
  .info-dot{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;
    padding:0;border:0;background:none;cursor:pointer;color:var(--muted2);flex-shrink:0;
    outline:none;border-radius:50%;position:relative}
  /* Значок, который показывают не всем: своё display сильнее браузерного [hidden]. */
  .info-dot[hidden]{display:none}
  /* Зона нажатия вдвое шире самого кружка — 30 точек против 15 (решение
     владельца 21.09.2026, ответ на вопрос 14.5 захода 14). Сам значок на вид
     прежний: попасть в него пальцем трудно именно тому, кому подписи нужнее
     всех. Невидимая область лежит ПОВЕРХ соседей и в строке места не занимает:
     значок покрупнее сдвинул бы текст рядом, а на телефоне добавил бы
     горизонтальную прокрутку. Вылет вверх и влево прокрутки не создаёт, а
     справа и снизу он меньше отступов карточек, в которых значок живёт. */
  .info-dot::before{
    content:'';position:absolute;top:50%;left:50%;width:30px;height:30px;
    transform:translate(-50%,-50%);border-radius:50%;
  }
  .info-dot:hover,.info-dot:focus{color:var(--muted)}
  /* Кольцо только для клавиатуры: по тапу и клику фокус тоже приходит, но там
     облачко само показывает, что кнопка сработала. */
  .info-dot:focus-visible{box-shadow:0 0 0 2px var(--primary-100)}
  .info-dot svg{width:13px;height:13px;margin-top:0}
  /* Своего облачка у значка нет: data-tip уже показывает общая подсказка
     .tipx, и второе, на ::after, выскакивало рядом с ней вторым экземпляром
     того же текста. */
  .stat-trend{position:relative}
  /* Строка под числом на «Аналитике»: период и стрелка с процентом (setTrend
     в app.js, проверка «Аналитики» 12.09.2026). Период серый, цвет — только
     у стрелки с процентом; на узкой карточке длинный период («12 Jun 2026 —
     12 Sep 2026») переносится, а стрелка с процентом и значком уходят на
     следующую строку целиком. */
  .stat-trend{flex-wrap:wrap;row-gap:1px}
  .stat-period{color:var(--muted);margin-right:3px}
  .trend-val{display:inline-flex;align-items:flex-start;gap:4px;white-space:nowrap}
  .trend-none{color:var(--muted)}
  /* «За всё время: N» под «Всего сканирований» (setAllTime в app.js, проверка
     «Аналитики» 12.09.2026): под замком «Всего» — за всё время, после оплаты —
     за период, и прежнее число остаётся строкой ниже. */
  .stat-alltime{font-size:12px;font-weight:600;color:var(--muted);margin-top:3px;line-height:1.25}

  .chart-card{padding:22px 24px;margin-bottom:14px}
  .chart-head{display:flex;align-items:center;justify-content:space-between;gap:14px;margin-bottom:16px}
  .chart-head h3{font-size:15px;font-weight:700}
  .chart-tools{display:flex;align-items:center;gap:12px;min-width:0}
  /* Галочка «Уникальные» цветом совпадает со своей линией на графике —
     это и есть вся легенда: подпись рядом, цвет тот же. */
  .uniq-check{font-size:12.5px;font-weight:600;color:var(--muted);white-space:nowrap}
  .uniq-check input{accent-color:#f7c441}
  .uniq-check:hover{color:var(--text)}
  .period-btn{
    display:inline-flex;align-items:center;gap:7px;padding:7px 13px;
    border:1px solid var(--border);border-radius:999px;background:#fff;
    font-size:12.5px;font-weight:600;color:var(--muted);transition:all .15s;
  }
  .period-btn:hover{border-color:var(--primary-100);background:var(--primary-50);color:var(--text)}
  .period-btn .chev{width:12px;height:12px;flex-shrink:0}
  .period-btn .period-cal{display:none;width:19px;height:19px}   /* значок — только на телефоне */
  .chart-empty{display:flex;align-items:center;justify-content:center;color:var(--muted);font-size:13.5px;text-align:center}
  /* pan-y: по графику протягивают палец, чтобы прочитать день, и браузер иначе
     принимает это движение за прокрутку. Вертикальную оставляем — страница длинная.
     none у выделения: при протяжке иначе подсвечиваются подписи оси. */
  .chart-body{width:100%;touch-action:pan-y;user-select:none;-webkit-user-select:none}

  .analytics-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:11px;margin-bottom:14px}
  .panel{padding:20px 22px}
  .panel h3{font-size:14px;font-weight:700;margin-bottom:16px}
  /* Значок (i) после слов — в заголовке панели и в подзаголовке «Аналитики». */
  .panel h3 .info-dot,.subtitle-hint .info-dot{margin-left:6px;vertical-align:-2px}
  /* …и на строке заголовка поля: у почты в «Настройки → Аккаунт» (Р-148, решение
     владельца 18.09.2026, заход 5). Кружок тот же, а зона нажатия — 24×24 под
     палец; отрицательные поля по вертикали не дают ей раздвинуть строку подписи. */
  .field label .info-dot{width:24px;height:24px;margin:-6px 0 -6px 1px;vertical-align:middle}
  /* Шапка «Лучших кодов»: название со значком и единица над столбцом чисел. */
  .panel-head{display:flex;align-items:baseline;justify-content:space-between;gap:10px;margin-bottom:16px}
  .panel .panel-head h3{margin-bottom:0}
  .panel-unit{font-size:12px;font-weight:600;color:var(--muted);white-space:nowrap;padding-right:4px}
  /* Devices / OS / Browser в одной карточке. На десктопе места хватает на все три
     списка сразу, поэтому сегменты скрыты, а карточка занимает вторую колонку в
     обеих строках сетки — рядом с ней Countries и Top QR встают друг под друга. */
  .panel-stack{grid-row:span 2}
  .panel-stack .seg-row{display:none}
  .panel-stack .seg-pane+.seg-pane{margin-top:24px}
  .bar-row{margin-bottom:13px}
  .bar-row:last-child{margin-bottom:0}
  .bar-top{display:flex;justify-content:space-between;font-size:13px;margin-bottom:5px}
  .bar-top .bl{font-weight:600}
  .bar-top .bv{color:var(--muted);font-weight:500;font-variant-numeric:tabular-nums}
  .bar-track{height:7px;border-radius:999px;background:var(--primary-50);overflow:hidden}
  /* Полоса рождается нулевой: длину ей ставит barList уже после того, как
     браузер её разложил (app.js). Без этого полоса появлялась сразу готовой,
     и переход, написанный тут же, не проигрывался ни разу. */
  .bar-fill{height:100%;width:0;border-radius:999px;transition:width .6s cubic-bezier(.2,.8,.3,1)}

  /* fixed: без него длинное имя раздвигает таблицу, а не обрезается многоточием */
  .top-table{width:100%;border-collapse:collapse;table-layout:fixed}
  .top-table td{padding:11px 4px;border-bottom:1px solid var(--border2);font-size:13.5px;vertical-align:middle}
  .top-table tr:last-child td{border-bottom:none}
  /* Строка ведёт на статистику своего кода */
  .top-table tr[data-qr]{cursor:pointer}
  .top-table tr[data-qr]:hover .top-name{color:var(--primary)}
  .rank{width:26px;height:26px;border-radius:9px;background:var(--primary-50);color:var(--primary);font-weight:700;font-size:12px;display:flex;align-items:center;justify-content:center}
  .rank.r1{background:linear-gradient(135deg,var(--primary),#4C8CFF);color:#fff}
  .rank.r2{background:var(--primary-100);color:var(--primary)}
  /* Ширины крайних колонок заданы явно: при table-layout:fixed незаданные делят
     остаток поровну, и колонка с числом забирала полстроки у названия. */
  .top-rank-cell{width:36px}
  .top-empty,.panel-empty{color:var(--muted);font-size:13px}
  .panel-empty{padding:2px 0 4px}
  /* Ссылка на источник стран под их списком — условие лицензии DB-IP, тише подписей.
     Надпись мелкая, а зона нажатия — не ниже 24 точек: на телефоне в строку высотой
     14 точек пальцем не попасть (набор робота, «мелкие цели»). */
  .geo-credit{margin:7px 0 -5px;font-size:11.5px;line-height:1.4;color:var(--muted)}
  .geo-credit a{display:inline-flex;align-items:center;min-height:24px;color:inherit;text-decoration:none}
  .geo-credit a:hover{text-decoration:underline}
  .top-name{font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .top-scans{width:76px;font-weight:700;text-align:right;font-variant-numeric:tabular-nums}

  /* ============ ЗАКРЫТАЯ АНАЛИТИКА ============ */
  /* Бесплатный тариф видит только «Всего сканов» и «Active QR». Остальное уходит
     под блюр, а поверх встаёт замок с предложением старшего тарифа.

     Блок не убран и не заменён серой заглушкой намеренно: под блюром стоит
     заготовка с выдуманными числами — так человек видит, что именно он получит
     за деньги, а не пустое место, о котором нечего думать. Своих данных под
     блюром нет: на бесплатном тарифе сервер их не присылает вовсе.

     Класс `on` ставит setStatsLock в app.js по ответу сервера; сама по себе
     разметка ничего не закрывает. */
  .pay-lock{position:relative}
  .pay-over{display:none}
  .pay-lock.on .pay-blur{
    filter:blur(5px) saturate(.75);
    /* Под блюром живые кнопки — период, вкладки, строки таблицы. Без этого по
       ним можно попасть вслепую, а замазанные надписи выделялись бы мышью. */
    pointer-events:none;user-select:none;
  }
  .pay-lock.on .pay-over{display:flex;justify-content:center;position:absolute;inset:0;z-index:6;padding:88px 16px 0}
  /* Замок липнет к верху экрана: закрытая область выше экрана, и на середине
     прокрутки объяснение уезжало бы за край — оставался бы один туман. */
  .pay-box{
    position:sticky;top:calc(var(--topbar-h) + 26px);align-self:flex-start;
    max-width:400px;text-align:center;background:var(--card);border:1px solid var(--border);
    border-radius:13px;padding:22px 24px 20px;box-shadow:0 18px 40px rgba(15,30,46,.16);
  }
  .pay-ico{
    width:46px;height:46px;border-radius:15px;margin:0 auto 13px;
    display:flex;align-items:center;justify-content:center;
    background:linear-gradient(135deg,var(--primary-50),var(--teal-50));color:var(--primary);
  }
  .pay-ico svg{width:22px;height:22px}
  .pay-text{font-size:14.5px;font-weight:600;line-height:1.45;margin-bottom:16px}
  .pay-text b{color:var(--primary)}
  /* Не во всю карточку: кнопка шире надписи над ней читалась как главное на
     экране, хотя решение человек принимает по надписи. min() — чтобы на самом
     узком телефоне кнопка ужалась вместе с карточкой, а не вылезла из неё. */
  .pay-btn{width:auto;min-width:min(240px,100%);justify-content:center}
  .pay-btn svg{width:16px;height:16px}
  /* Карточки чисел сверху: закрыто значение, подпись остаётся — человек должен
     видеть, чего именно он не видит. Замочек в углу говорит, что это не сбой
     отрисовки: без него замазанное число читается как поломка. */
  .stat-card.pay-fake{position:relative}
  .stat-card.pay-fake .stat-value,.stat-card.pay-fake .stat-trend{
    filter:blur(6px);pointer-events:none;user-select:none;
  }
  .stat-card.pay-fake::after{
    content:'';position:absolute;right:20px;bottom:20px;width:16px;height:16px;opacity:.75;
    background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2393A6B9' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='4' y='10.5' width='16' height='10' rx='2.5'/%3E%3Cpath d='M8 10.5V7.8a4 4 0 0 1 8 0v2.7'/%3E%3C/svg%3E") center/contain no-repeat;
  }

  /* Выдуманное число под замком — не текстом страницы (проверка «Аналитики»,
     12.09.2026): поиск по странице, экранный диктор и автоперевод находили
     его в разметке, хотя глазам его закрывало размытие. Текст ячейки под
     замком скрыт, а пятно рисует CSS из data-sample в разметке; «/ ""» прячет
     нарисованное и от диктора (браузер без этой записи берёт первую). */
  .stat-card.pay-fake .stat-value{visibility:hidden}
  .stat-card.pay-fake .stat-value::before{visibility:visible;content:attr(data-sample);content:attr(data-sample) / ""}
  /* «Пример» у замочка в углу: рядом стоит настоящее «Всего сканирований» со
     строкой «за всё время», и выдумка без подписи читалась как правда. */
  .stat-sample{display:none}
  .stat-card.pay-fake .stat-sample{
    display:block;position:absolute;right:42px;bottom:19px;
    font-size:12px;font-weight:600;line-height:18px;color:var(--muted2);
  }
  /* Цифры не приехали — полоса отказа над карточками (renderStatsFailed). Замок
     остаётся от прошлого ответа, но у выдуманного числа вместо пятна и «примера»
     — прочерк, как у остальных: «пример» рядом с «данные не загрузились»
     читался бы как ответ. */
  .stats-fail:not([hidden]) + .stats-grid .stat-card.pay-fake .stat-value{visibility:visible;filter:none}
  .stats-fail:not([hidden]) + .stats-grid .stat-card.pay-fake .stat-value::before{content:none}
  .stats-fail:not([hidden]) + .stats-grid .stat-sample{display:none}

  /* ============ ПРОБНАЯ ПОЛНАЯ АНАЛИТИКА (22.10.2026) ============ */
  /* Решение владельца, вид утверждён по макету (Макеты/пробная-аналитика). На
     бесплатном тарифе под синей кнопкой замка — белая «Start … free trial» и
     серая строка под ней; пока проба идёт — плашка со сроком справа от названия
     графика; сразу после нажатия — окно «Full analytics is on». Что когда
     показывать, решают setStatsLock и statsTrial* в app.js. */
  .pay-trial{margin-top:16px;padding-top:15px;border-top:1px solid var(--border)}
  .pay-trial[hidden]{display:none}
  /* Ширина — как у синей кнопки над ней; рамка 2px и поля на точку меньше —
     ростом она с синюю, а не тоньше её. */
  .pay-trial .btn{width:auto;min-width:min(240px,100%);justify-content:center;border-width:2px;padding:9px 19px}
  .pay-trial-text{font-size:13.5px;font-weight:500;line-height:1.45;color:var(--muted);margin-top:9px;text-wrap:balance}
  /* Название графика и плашка — одной группой слева; не встают рядом — плашка
     уходит под название, а не за край карточки. */
  .chart-title-row{display:flex;align-items:center;gap:12px;min-width:0;flex-wrap:wrap;row-gap:8px}
  /* Плашка — как кнопка периода (.period-btn), но ничего не делает: подсказка с
     датой конца по наведению и касанию (data-tip, aria-disabled). */
  .trial-chip{
    display:inline-flex;align-items:center;gap:6px;padding:5px 11px;
    border:1px solid var(--border);border-radius:999px;background:#fff;
    font-size:12.5px;font-weight:600;line-height:1.5;color:var(--muted);white-space:nowrap;cursor:default;
  }
  .trial-chip[hidden]{display:none}
  .trial-chip svg{width:14px;height:14px;color:var(--primary);flex:none}
  .trial-chip .trial-dot{color:var(--muted2)}
  .trial-chip b{font-weight:700;color:var(--text)}
  /* Последние три дня — число красным (statsTrialChip ставит класс). */
  .trial-chip b.is-ending{color:var(--red)}
  /* С плашкой название и инструменты графика вместе не всегда встают в строку
     (узкое окно, кнопка папки): инструменты переносятся строкой ниже, к правому
     краю, а не сжимают плашку. */
  .chart-head:has(.trial-chip:not([hidden])){flex-wrap:wrap;row-gap:10px}
  .chart-head:has(.trial-chip:not([hidden])) .chart-tools{margin-left:auto}
  /* Окно «Full analytics is on»: по центру, значок — как у замка (.pay-ico),
     линия пульса — как у карточки «Total scans». */
  .trial-on{position:relative;align-items:center;text-align:center;padding:30px 26px 24px;overflow-y:auto}
  .trial-on-x{position:absolute;top:14px;right:14px}
  .trial-on-ico{
    width:48px;height:48px;border-radius:15px;margin:0 auto 14px;flex-shrink:0;
    display:flex;align-items:center;justify-content:center;
    background:linear-gradient(135deg,var(--primary-50),var(--teal-50));color:var(--primary);
  }
  .trial-on-ico svg{width:24px;height:24px}
  .trial-on-title{font-family:'Manrope','Inter',sans-serif;font-size:19px;font-weight:800;letter-spacing:-.01em;margin-bottom:10px;text-wrap:balance}
  .trial-on-text{margin:0 0 6px;font-size:14px;line-height:1.55;color:var(--muted)}
  .trial-on-text b{color:var(--text)}
  .trial-on-btn{margin-top:14px;min-width:min(200px,100%);justify-content:center}

  /* ============ ВЫГРУЗКА СКАНОВ В ТАБЛИЦУ (24.10.2026) ============
     Кнопка «Export» справа от заголовка «Аналитики» и рядом с правкой в
     статистике кода, окно выгрузки и окно замка — по утверждённому макету
     («Макеты/выгрузка-сканов»). Логика — app.js, «ВЫГРУЗКА СКАНОВ В ТАБЛИЦУ». */
  .xp-head{display:flex;align-items:center;justify-content:space-between;gap:12px}
  .xp-head h1{margin:0;min-width:0}
  .xp-head .xp-btn{flex:none}
  .xp-actions{display:flex;flex-wrap:wrap;gap:10px}
  #screen-qrstats .screen-header-act>.xp-actions{min-width:auto}
  .xp-btn svg{width:17px;height:17px}
  /* Значок — по тарифу: стрелка «скачать» или замок (класс is-locked ставит statsExportSync). */
  .xp-btn .xp-ico-lock,.xp-btn.is-locked .xp-ico-dl{display:none}
  .xp-btn.is-locked .xp-ico-lock{display:block}
  .xp-modal{max-width:480px}
  .xp-group{margin-bottom:18px}
  .xp-group .sw-row + .sw-row{margin-top:12px}
  /* «Every scan» за периодом старше срока сырых сканов — серым, пояснение вместо подписи. */
  .xp-group .sw-row.xp-off{opacity:.55;cursor:default}
  .xp-group .sw-row.xp-off input{cursor:default}
  .xp-fmt-hint{font-size:12.5px;color:var(--muted);margin-top:8px}
  .xp-modal .modal-note{margin:0 0 16px}
  .xp-modal .bulk-progress{margin:0 0 4px}
  .xp-foot{align-items:center}
  .xp-count{margin-right:auto;font-size:13px;font-weight:600;color:var(--muted);font-variant-numeric:tabular-nums}
  .xp-foot .btn-primary svg{width:16px;height:16px}
  /* Пока файл собирается, на кнопке вместо стрелки — крутилка (класс is-busy у окна). */
  .xp-spin{display:none;width:14px;height:14px;border-radius:50%;border:2px solid rgba(255,255,255,.45);border-top-color:#fff;animation:xpSpin .8s linear infinite}
  .xp-modal.is-busy .xp-spin{display:inline-block}
  .xp-modal.is-busy .xp-go-ico{display:none}
  @keyframes xpSpin{to{transform:rotate(360deg)}}
  @media (prefers-reduced-motion:reduce){ .xp-spin{animation-duration:2.4s} }
  .xp-lock{position:relative;align-items:center;text-align:center;padding:30px 26px 26px;overflow-y:auto}
  .xp-lock-x{position:absolute;top:14px;right:14px}
  .xp-lock-ico{
    width:48px;height:48px;border-radius:15px;margin:0 auto 14px;flex-shrink:0;
    display:flex;align-items:center;justify-content:center;
    background:linear-gradient(135deg,var(--primary-50),var(--teal-50));color:var(--primary);
  }
  .xp-lock-ico svg{width:24px;height:24px}
  .xp-lock-title{font-family:'Manrope','Inter',sans-serif;font-size:19px;font-weight:800;letter-spacing:-.01em;margin-bottom:10px;text-wrap:balance}
  .xp-lock-text{margin:0 0 6px;font-size:14px;line-height:1.55;color:var(--muted);text-wrap:balance}
  .xp-lock-text b{color:var(--text)}
  .xp-lock-btn{margin-top:14px;min-width:min(220px,100%);justify-content:center}
  .xp-lock-btn svg{width:16px;height:16px}
  @media (max-width:640px){
    .xp-head .xp-btn{padding:8px 14px}
    /* Число строк — своей строкой: иначе «Download» уезжает вниз один, вдали от «Cancel». */
    .xp-count{flex-basis:100%}
  }
  @media (max-width:900px){
    /* На телефоне инструменты графика при плашке — всегда своей строкой, как на
       «Аналитике» с кнопкой папки (#screen-analytics .chart-head): на «Статистике
       QR» название, плашка, галочка и календарик в одну строку не встают. */
    .chart-head:has(.trial-chip:not([hidden])) .chart-tools{flex:1 1 100%;margin-left:0;justify-content:space-between}
  }
  @media (max-width:640px){
    .pay-trial{margin-top:14px;padding-top:13px}
    .pay-trial-text{font-size:13px;margin-top:8px}
    .chart-title-row{gap:8px}
    .trial-chip{font-size:12px;padding:4px 10px}
  }

  /* ============ QR STATS ============ */
  /* Кнопка «Назад» — единственный выход с этого экрана, поэтому она заметно
     крупнее обычной ссылки: стрелка 19px и своя высота под палец. */
  .back-link{display:inline-flex;align-items:center;gap:9px;background:none;border:none;color:var(--muted);font-size:14.5px;font-weight:700;margin-bottom:14px;padding:8px 14px 8px 10px;border-radius:999px}
  .back-link:hover{background:#EFF5FF;color:var(--text)}
  .back-link svg{width:19px;height:19px}
  /* Что с кодом сейчас — в шапке, теми же метками, что в «Моих QR»
     (codeStateTags в app.js): заблокирован, на паузе, лимит, срок. Кнопкой —
     на телефоне нажатие показывает объяснение. */
  /* Метка шапки переносится, если не влезла: у метки мест внутри целая фраза
     с двумя числами («Открыли 1 250 из 5 000»), и `white-space:nowrap` общей
     `.sched-tag` увёл бы её за край телефона — той же бедой, что Р-253. */
  .stats-state{border:0;margin-left:6px;vertical-align:1px;line-height:1.45;cursor:help;white-space:normal;text-align:left}

  /* Имя кода без единого пробела — артикул или строка из таблицы партии
     («Table_12_Terrace_Harbour_Entrance_4f8a1»): на 390 точках беда
     начиналась с 32 знаков, на 320 — с 26. Заголовок резало краем, и страницу
     можно было утащить пальцем вбок (Р-253, проверка роботом, заход 8).
     `min-width:0` обязателен вместе с переносом: колонка шапки — элемент
     флекса, а он по умолчанию не ужимается уже своего самого длинного слова,
     и переносить внутри неё было бы нечего. */
  #screen-qrstats .screen-header-act>div{min-width:0}
  #statsTitle{overflow-wrap:anywhere}

  /* ============ AI ADVISOR — экран Lumi ============ */
  /* Свои цвета только здесь: советник — единственное место сайта, где к
     фирменному синему добавлен сиреневый. Он и делает экран «про ИИ», не
     заставляя перекрашивать остальной сайт. */
  /* ============ ОКНО LUMI (24.10.2026) ============
     Советник — окно поверх любого раздела, а не свой экран (решение владельца:
     человек держит подсказку сбоку и делает по ней). Вид — «компактное» из трёх
     показанных: маленькая шапка, сообщения без огоньков и значка «You». Место
     и размер ставит app.js (блок «ОКНО LUMI») строчными left/top/width/height;
     без места (запасной вид) оно стоит в правом нижнем углу над своим круглым
     значком (.lumi-launcher: 64 px + 20 px от края). Над окнами сайта
     (.modal-backdrop — 900), но под окошком «Contact support» (920), окном
     отказа (930), подсказками (950) и уведомлениями (1000): по шагам Lumi
     человек работает и внутри окон, а подсказка (i) в самом Lumi должна
     вставать поверх него. Свои цвета Lumi — синий бренда плюс сиреневый —
     живут переменными внутри окна и наружу не выходят. */
  .lumi-win{--lumi-1:#2F6FED;--lumi-2:#7A5CF0;--lumi-3:#C084FC;
    position:fixed;z-index:910;right:20px;bottom:96px;width:380px;height:min(560px,calc(100vh - 150px));
    container-type:inline-size;container-name:lumi;
    display:flex;flex-direction:column;overflow:hidden;
    background:#fff;border:1px solid #DCD5F5;border-radius:16px;
    box-shadow:0 18px 44px rgba(40,30,110,.20),0 3px 10px rgba(40,30,110,.10)}
  .lumi-win[hidden]{display:none}
  /* Включён ещё и чат Chatwoot: значок Lumi стоит над его кнопкой, окно — выше обоих. */
  body.sc-on .lumi-win{bottom:172px;height:min(540px,calc(100vh - 226px))}
  /* Пока окно тянут или двигают, текст страницы под мышью не выделяется. */
  body.lumi-dragging{user-select:none;-webkit-user-select:none}
  /* Ручки — внутри окна: выступающие наружу резал overflow окна вместе со
     скруглением, и край ловился полоской в три-четыре точки, а угол — только
     в стороне от самого угла (живая проверка 24.10.2026). Угол — квадрат 18 px. */
  .lumi-grip{position:absolute;z-index:3}
  .lumi-grip[data-edge="n"]{top:0;left:18px;right:18px;height:7px;cursor:ns-resize}
  .lumi-grip[data-edge="s"]{bottom:0;left:18px;right:18px;height:7px;cursor:ns-resize}
  .lumi-grip[data-edge="e"]{right:0;top:18px;bottom:18px;width:7px;cursor:ew-resize}
  .lumi-grip[data-edge="w"]{left:0;top:18px;bottom:18px;width:7px;cursor:ew-resize}
  .lumi-grip[data-edge="ne"]{top:0;right:0;width:18px;height:18px;cursor:nesw-resize}
  .lumi-grip[data-edge="sw"]{bottom:0;left:0;width:18px;height:18px;cursor:nesw-resize}
  .lumi-grip[data-edge="nw"]{top:0;left:0;width:18px;height:18px;cursor:nwse-resize}
  .lumi-grip[data-edge="se"]{bottom:0;right:0;width:18px;height:18px;cursor:nwse-resize}
  .lumi-hide{flex-shrink:0;width:28px;height:28px;padding:0;border:0;border-radius:8px;background:transparent;color:var(--muted);display:flex;align-items:center;justify-content:center;cursor:pointer;transition:background .15s,color .15s}
  .lumi-hide:hover{background:rgba(122,92,240,.10);color:var(--lumi-2)}
  .lumi-hide:focus-visible{outline:2px solid var(--lumi-2);outline-offset:1px}
  .lumi-hide svg{width:16px;height:16px}
  /* «Contact support» в шапке (25.10.2026): в рамке, как «Start again», но серой — главное в
     окне разговор, а не выход из него. Подпись стоит, пока помещается рядом с «Lumi QR Code
     advisor»; не помещается — от кнопки остаётся конверт, подпись — в title и aria-label. Конверт:
     окно ужато мышью уже 360 px, рядом встала «Start again» (разговор начат) при окне уже 460 px
     и всегда на телефоне (слово владельца). Решает ширина самого окна, а не экрана. */
  .lumi-support{flex-shrink:0;display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border:1px solid var(--border);border-radius:999px;background:rgba(255,255,255,.75);font:inherit;font-size:11.5px;font-weight:600;color:var(--muted);cursor:pointer;white-space:nowrap;transition:all .15s}
  .lumi-support svg{width:14px;height:14px;flex-shrink:0}
  .lumi-support:hover{border-color:var(--lumi-2);color:var(--lumi-2);background:#fff;box-shadow:0 3px 10px rgba(122,92,240,.16)}
  .lumi-support:focus-visible{outline:2px solid var(--lumi-2);outline-offset:1px}
  @container lumi (max-width:360px){
    .lumi-support{width:28px;height:28px;padding:0;justify-content:center;border-color:transparent;background:transparent}
    .lumi-support span{display:none}
    .lumi-support svg{width:17px;height:17px}
  }
  @container lumi (max-width:460px){
    .chat-head:has(> .chat-reset:not([hidden])) .lumi-support{width:28px;height:28px;padding:0;justify-content:center;border-color:transparent;background:transparent}
    .chat-head:has(> .chat-reset:not([hidden])) .lumi-support span{display:none}
    .chat-head:has(> .chat-reset:not([hidden])) .lumi-support svg{width:17px;height:17px}
  }
  @media (max-width:640px){
    .lumi-support{width:28px;height:28px;padding:0;justify-content:center;border-color:transparent;background:transparent}
    .lumi-support span{display:none}
    .lumi-support svg{width:17px;height:17px}
  }

  /* Круглый значок Lumi в правом нижнем углу (25.10.2026, решение владельца: на месте кнопки
     чата). Тот же огонёк, что в шапке окна, только крупный: ядро с бликом и свечение вокруг,
     значка внутри нет (см. ниже, «Огонёк Lumi»). Размер и место — как были у кнопки чата (64 px,
     20 px от края). Слоем ниже окон сайта (900) и шторки меню на телефоне (55): открытое окно и
     шторка его закрывают. У открытого окна на шарике — стрелка «свернуть»: значок и сворачивает. */
  .lumi-launcher{--lumi-1:#2F6FED;--lumi-2:#7A5CF0;--lumi-3:#C084FC;
    position:fixed;right:20px;bottom:20px;z-index:54;width:64px;height:64px;padding:0;border:0;border-radius:50%;
    display:flex;align-items:center;justify-content:center;background:none;color:#fff;cursor:pointer}
  .lumi-launcher[hidden]{display:none}
  .lumi-launcher::before{content:'';position:absolute;inset:-6px;border-radius:50%;
    background:radial-gradient(circle,rgba(122,92,240,.42),transparent 68%);transition:opacity .2s;pointer-events:none}
  .lumi-launcher-orb{position:relative;overflow:hidden;width:56px;height:56px;border-radius:50%;
    background:linear-gradient(160deg,var(--lumi-1),var(--lumi-2));
    box-shadow:inset 0 -4px 10px rgba(20,20,60,.28),0 8px 22px rgba(70,50,190,.30);
    transition:transform .2s ease,box-shadow .2s ease;animation:lumiPulse 3.6s ease-in-out infinite}
  .lumi-launcher-orb::before{content:'';position:absolute;inset:0;border-radius:50%;
    background:conic-gradient(from 210deg,var(--lumi-1),var(--lumi-2),var(--lumi-3),var(--lumi-1))}
  .lumi-launcher-orb::after{content:'';position:absolute;left:22%;top:16%;width:34%;height:26%;border-radius:50%;
    background:linear-gradient(180deg,rgba(255,255,255,.9),rgba(255,255,255,.15));filter:blur(.4px)}
  .lumi-launcher-down{position:absolute;width:24px;height:24px;opacity:0;transform:translateY(-3px);transition:opacity .15s,transform .2s}
  .lumi-launcher.open .lumi-launcher-down{opacity:1;transform:none}
  .lumi-launcher.open .lumi-launcher-orb::after{opacity:.35}
  @media (hover:hover){
    .lumi-launcher:hover .lumi-launcher-orb{transform:scale(1.05);box-shadow:inset 0 -4px 10px rgba(20,20,60,.28),0 10px 28px rgba(70,50,190,.38)}
  }
  .lumi-launcher:focus-visible{outline:none}
  .lumi-launcher:focus-visible .lumi-launcher-orb{box-shadow:0 0 0 3px #fff,0 0 0 5px var(--lumi-2)}
  /* Включён ещё и чат Chatwoot (его кнопка в самом углу) — значок Lumi встаёт над ней. */
  body.sc-on .lumi-launcher{bottom:96px}
  /* Под шторкой меню на телефоне и под окнами сайта значка не видно и так (слой), но на
     телефоне он ложился бы на кнопки окошка Feedback внизу — прячем, как кнопку Chatwoot. */
  body.drawer-open .lumi-launcher{display:none}
  /* Нижнюю строку страницы (подвал) на телефоне значок не закрывает: запас снизу. */
  @media (max-width:640px){ body.lumi-btn main{padding-bottom:84px} }

  /* Огонёк Lumi: ядро с бликом и мягкое свечение вокруг. Значка внутри нет
     нарочно — ни одна иконка не читается как «собеседник», а пятно света
     читается. С 24.10.2026 огонёк один — в шапке окна (ниже), у сообщений
     ленты их больше нет (вид «компактное», решение владельца). Всё движение
     выключается вместе с остальной анимацией — см. конец блока. */
  .lumi-orb{position:relative;width:38px;height:38px;flex-shrink:0;display:flex;align-items:center;justify-content:center}
  .lumi-orb::before{
    content:'';position:absolute;inset:-8px;border-radius:50%;
    background:radial-gradient(circle,rgba(122,92,240,.5),transparent 68%);
  }
  .lumi-orb-core{
    position:relative;overflow:hidden;width:100%;height:100%;border-radius:50%;
    background:linear-gradient(160deg,var(--lumi-1),var(--lumi-2));
    box-shadow:inset 0 -3px 8px rgba(20,20,60,.28), 0 3px 10px rgba(90,70,200,.28);
  }
  /* Цвет внутри шарика — отдельным слоем под бликом. Слой неподвижен: сам
     шарик не крутится и не переливается (владелец, 05.09.2026 — пробовали и
     то, и другое, оба не понравились). Живёт вокруг него свет, а не он сам. */
  .lumi-orb-core::before{
    content:'';position:absolute;inset:0;border-radius:50%;
    background:conic-gradient(from 210deg,var(--lumi-1),var(--lumi-2),var(--lumi-3),var(--lumi-1));
  }
  .lumi-orb-core::after{
    content:'';position:absolute;left:22%;top:16%;width:34%;height:26%;border-radius:50%;
    background:linear-gradient(180deg,rgba(255,255,255,.9),rgba(255,255,255,.15));
    filter:blur(.4px);
  }
  /* Живой — только огонёк в шапке окна, и живёт сам шарик: его яркость
     медленно колеблется, то ярче, то приглушённее. Ничего больше не движется —
     ни подсветка вокруг, ни цвет внутри. К этому пришли перебором 05.09.2026:
     вращение цвета, бегущий блик, расходящаяся волна и дыхание подсветки
     владельцу по очереди не подошли. До 24.10.2026 жил большой огонёк над
     разговором на экране советника; экрана больше нет, жизнь перешла в шапку. */
  .lumi-win .chat-head .lumi-orb{width:20px;height:20px}
  .lumi-win .chat-head .lumi-orb::before{inset:-5px}
  .lumi-win .chat-head .lumi-orb-core{animation:lumiPulse 3.6s ease-in-out infinite}

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

  /* Шапка разговора: кто отвечает и кнопка начать заново. Имя собеседника
     стоит здесь, а не подписью под каждым сообщением, — иначе оно повторялось
     бы в ленте столько раз, сколько было ответов. */
  /* Полоса стеклянная, тем же приёмом, что верхняя полоса сайта: свой цвет,
     прозрачность и блюр. Лента прокручивается под ней и видна сквозь стекло —
     до 05.09.2026 она под шапкой обрывалась ровным краем (владелец). Отсюда и
     position:absolute: в потоке полоса отодвигала бы ленту вниз, и заезжать
     под неё было бы нечему. Отступ сверху у ленты равен высоте полосы. */
  .chat-head{position:absolute;top:0;left:0;right:0;z-index:2;display:flex;align-items:center;gap:8px;padding:7px 8px 7px 13px;cursor:move;touch-action:none;
    background:rgba(245,243,251,.66);
    -webkit-backdrop-filter:blur(14.3px) saturate(1.7);
    backdrop-filter:blur(14.3px) saturate(1.7);
    border-bottom:1px solid var(--border2)}
  .chat-head-text{display:flex;align-items:baseline;gap:7px;line-height:1.25;min-width:0;flex:1}
  .chat-head-text b{font-size:13.5px}
  .chat-head-text span{font-size:11.5px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

  /* Лента прокручивается внутри карточки, и её края режут содержимое: длинный
     ответ обрывался прямой линией поперёк пузыря (владелец, 05.09.2026:
     «переход всё равно присутствует»). Сверху этим занимается стеклянная
     шапка — текст уходит под неё и виден размытым, а не отрезанным. Снизу
     растворяем край сами: полоса — 26 пикселей, вровень с отступом ленты
     (24 px), так что пока прокручивать нечего, она приходится на пустое поле.
     Верхний отступ 84 px — высота шапки плюс воздух. */
  .chat-messages{position:relative;flex:1;min-height:0;padding:56px 12px 18px;display:flex;flex-direction:column;gap:10px;overflow-y:auto;
    -webkit-mask-image:linear-gradient(180deg,#000 calc(100% - 26px),transparent 100%);
    mask-image:linear-gradient(180deg,#000 calc(100% - 26px),transparent 100%)}
  .msg{display:flex;gap:12px;max-width:90%}
  /* display:flex сильнее браузерного [hidden]{display:none} — без этой строки
     приветствие показалось бы сразу, а не по очереди (см. aiHello). */
  .msg[hidden]{display:none}
  .msg-user{align-self:flex-end;flex-direction:row-reverse}
  /* overflow-wrap:anywhere — про длинное слово без пробелов: адрес страницы,
     немецкое составное слово, набранное подряд имя сети Wi-Fi. Без него такое
     слово вылезало из пузыря и уводило всю ленту вбок, а на телефоне 320 конец
     собственной фразы человека уезжал за край экрана (Р-432, проверка роботом,
     заход 13, 20.09.2026). */
  .msg-bubble{padding:9px 13px;border-radius:15px;font-size:13.5px;line-height:1.55;min-width:0;overflow-wrap:anywhere}
  .msg-user .msg-bubble{background:linear-gradient(135deg,var(--primary),#4C8CFF);color:#fff;border-bottom-right-radius:5px}
  .msg-ai .msg-bubble{
    background:linear-gradient(180deg,#F5F8FF,#F1F1FE);
    border:1px solid #E5E7FA;border-bottom-left-radius:5px;
    box-shadow:0 2px 10px rgba(60,60,140,.05);
  }

  /* Появление сообщения: всплывает снизу. Ответ Lumi вдобавок раскрывается
     по частям — сначала вводная фраза словами, потом сценарии один за другим
     (см. aiRender). Задержки задаются переменной --i прямо в разметке. */
  @keyframes lumiIn{from{opacity:0;transform:translateY(12px) scale(.985)}to{opacity:1;transform:none}}
  @keyframes lumiBlockIn{from{opacity:0;transform:translateY(9px)}to{opacity:1;transform:none}}
  @keyframes lumiWord{from{opacity:0;filter:blur(4px)}to{opacity:1;filter:none}}
  /* Колебания яркости шарика. Размер и положение не трогаем — только свет:
     чуть приглушённый шарик разгорается и снова гаснет. Размах небольшой
     (владелец, 05.09.2026): заметно краем глаза, но не мигает. */
  @keyframes lumiPulse{0%,100%{filter:brightness(.96)}50%{filter:brightness(1.14) saturate(1.03)}}
  @keyframes lumiSweep{from{background-position:200% 0}to{background-position:-200% 0}}
  .msg-in{animation:lumiIn .34s cubic-bezier(.2,.8,.3,1) both}
  .lumi-word{display:inline-block;animation:lumiWord .34s ease-out both;animation-delay:calc(var(--i) * .028s)}
  .lumi-block{animation:lumiBlockIn .4s cubic-bezier(.2,.8,.3,1) both;animation-delay:calc(.18s + var(--i) * .13s)}

  /* «Думает»: огонёк и бегущий блик по строке — вместо трёх точек. Строка
     словами, а не многоточием: человек читает, что происходит. */
  .typing{display:flex;align-items:center;gap:10px;padding:9px 13px;background:linear-gradient(180deg,#F5F8FF,#F1F1FE);border:1px solid #E5E7FA;border-radius:18px;border-bottom-left-radius:5px;width:fit-content}
  .typing i{width:7px;height:7px;border-radius:50%;background:linear-gradient(135deg,var(--lumi-1),var(--lumi-3));animation:bounce 1.2s infinite}
  .typing i:nth-child(2){animation-delay:.15s}
  .typing i:nth-child(3){animation-delay:.3s}
  .typing span{
    font-size:13px;font-weight:600;
    background:linear-gradient(90deg,var(--muted2) 20%,var(--lumi-2) 50%,var(--muted2) 80%) 0 0/200% 100%;
    -webkit-background-clip:text;background-clip:text;color:transparent;
    animation:lumiSweep 1.9s linear infinite;
  }
  @keyframes bounce{0%,60%,100%{transform:translateY(0);opacity:.5}30%{transform:translateY(-5px);opacity:1}}

  .chat-footer{position:relative;border-top:1px solid var(--border2);padding:10px 12px;background:#FCFDFF}
  .chat-input-row{display:flex;gap:8px}
  .chat-input-row input{flex:1;min-width:0;padding:9px 14px;border:1px solid var(--border);border-radius:999px;outline:none;transition:all .15s}
  .chat-input-row input:focus{border-color:var(--lumi-2);box-shadow:0 0 0 3px rgba(122,92,240,.15)}
  .send-btn{width:38px;height:38px;border-radius:50%;background:linear-gradient(135deg,var(--lumi-1),var(--lumi-2));color:#fff;border:none;display:flex;align-items:center;justify-content:center;transition:all .15s;flex-shrink:0}
  .send-btn:hover{transform:translateY(-1px);box-shadow:0 8px 18px rgba(122,92,240,.35)}
  .send-btn svg{width:16px;height:16px}

  /* Строка под полем: слева предупреждение про личные данные, справа счётчик
     оставшихся сообщений. Оба висят на одной строке и на узком экране
     переносятся, а не сжимаются в столбик из обрубков. */
  /* Проверка «я не робот» перед первым вопросом гостя: пустой контейнер не
     занимает места, виджет появляется в нём только на время проверки. */
  .chat-robot{display:flex;justify-content:center;margin-top:8px}
  .chat-robot:empty{display:none}
  /* В строке под полем с 25.10.2026 только счётчик вопросов (строку про ИИ владелец снял): он
     справа, а без счётчика строки нет вовсе — пустая полоса не висит под полем. */
  .chat-note{display:flex;justify-content:flex-end;align-items:center;gap:4px 12px;flex-wrap:wrap;margin-top:7px;font-size:11px;line-height:1.4;color:var(--muted)}
  .chat-note:not(:has(> :not([hidden]))){display:none}
  /* «Начать заново» — в рамке (владелец, 05.09.2026): на стекле шапки голая
     надпись не читалась как кнопка. */
  .chat-reset{flex-shrink:0;padding:4px 10px;border:1px solid #E4DAFB;border-radius:999px;background:rgba(255,255,255,.75);font:inherit;font-size:11.5px;font-weight:600;color:var(--lumi-2);cursor:pointer;white-space:nowrap;transition:all .15s}
  .chat-reset:hover{border-color:var(--lumi-2);background:#fff;box-shadow:0 3px 10px rgba(122,92,240,.16)}
  .chat-left{display:inline-flex;align-items:center;gap:5px;white-space:nowrap;color:#7B8797}
  .chat-left b{font-weight:700;color:inherit}
  .chat-left.is-out b{color:var(--red-soft)}
  .chat-left[hidden]{display:none}

  /* Ответ советника абзацами: сценарий — абзац с кнопкой под ним, а не карточка. */
  .msg-bubble p{margin:0}
  .msg-bubble p+p{margin-top:10px}
  .ai-item{margin-top:13px;padding-top:13px;border-top:1px solid rgba(60,60,140,.09)}
  .ai-item b{font-weight:700}
  /* Значок к названию сценария: приходит от советника. Чуть крупнее строки —
     оживляет абзац, но с названием не спорит. */
  .ai-emo{font-size:15.5px;line-height:1}
  /* Кнопка под сценарием зелёная — тем же цветом, что кнопка «Сгенерировать»
     (владелец, 05.09.2026). Она туда и ведёт: в генератор с выбранным типом.
     Сиреневый остаётся у Lumi — у огонька, шапки и кнопок под отказом. */
  .ai-pick{display:inline-flex;align-items:center;gap:6px;margin-top:9px;padding:7px 14px;border-radius:999px;border:1px solid var(--gen-border);background:var(--gen-soft);color:var(--teal-dark);font:inherit;font-size:12.5px;font-weight:600;cursor:pointer;transition:all .15s}
  .ai-pick:hover{background:linear-gradient(100deg,var(--gen),var(--gen-2));border-color:transparent;color:#fff;box-shadow:0 6px 14px rgba(95,199,82,.3)}
  /* Повтор уже показанного сценария: только название и кнопка, в одну строку. */
  .ai-item-again{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
  .ai-item-again .ai-pick{margin-top:0}
  /* Кнопка под отказом «вопросы кончились»: регистрация или витрина тарифов. */
  .ai-cta{display:inline-flex;align-items:center;gap:7px;margin-top:11px;padding:9px 17px;border-radius:999px;border:0;background:linear-gradient(135deg,var(--lumi-1),var(--lumi-2));color:#fff;font:inherit;font-size:13px;font-weight:700;cursor:pointer;transition:all .15s}
  .ai-cta:hover{transform:translateY(-1px);box-shadow:0 8px 18px rgba(122,92,240,.32)}
  /* Ответ «как сделать» (с 14.10.2026): шаги нумерованным списком под
     вступлением, а не абзацем — человек идёт по ним глазами, держа экран
     сайта рядом. Номер — в кружке тех же цветов, что значок «Lumi · AI
     Advisor» над разговором: сиреневый Lumi на светлом, без заливки, — шаг
     читается, а не кричит. Текст шага — обычной строкой пузыря. Шаги
     проявляются по очереди, как сценарии (класс lumi-block и --i в разметке). */
  .ai-steps{list-style:none;counter-reset:ai-step;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}
  .msg-bubble p+.ai-steps,.msg-bubble .ai-steps+p{margin-top:10px}
  .ai-steps li{counter-increment:ai-step;position:relative;padding-left:31px;min-height:22px}
  .ai-steps li::before{
    content:counter(ai-step);position:absolute;left:0;top:1px;
    width:21px;height:21px;box-sizing:border-box;border-radius:50%;
    display:flex;align-items:center;justify-content:center;
    background:linear-gradient(135deg,var(--primary-50),#F3EBFF);border:1px solid #E4DAFB;
    color:var(--lumi-2);font-size:11.5px;font-weight:700;line-height:1;
  }
  /* Адрес поддержки под ответом «не знаю — напишите нам»: один адрес, без
     слов — фразу на языке человека пишет советник. В рамке, как «Начать
     заново»: голый адрес на светлом пузыре не читался бы как то, на что
     нажимают. */
  .ai-human a,.ai-human button{display:inline-flex;align-items:center;gap:7px;max-width:100%;padding:6px 13px;border:1px solid #E4DAFB;border-radius:999px;background:#fff;color:var(--lumi-2);font:inherit;font-size:13px;font-weight:600;text-decoration:none;overflow-wrap:anywhere;cursor:pointer;transition:all .15s}
  .ai-human a:hover,.ai-human button:hover{border-color:var(--lumi-2);box-shadow:0 3px 10px rgba(122,92,240,.16)}
  .ai-human button svg{width:15px;height:15px;flex-shrink:0}


  /* Кому движение мешает, тот выключил его в системе: экран должен остаться
     рабочим, а не просто менее нарядным. Гасим всё разом — и свечение, и
     проявление сообщений. */
  @media (prefers-reduced-motion: reduce){
    .lumi-win .chat-head .lumi-orb-core,.msg-in,.lumi-word,.lumi-block,.typing i,.typing span{animation:none}
    .typing span{color:var(--muted);-webkit-text-fill-color:currentColor}
  }

  /* На телефоне окно не двигают и не тянут: оно встаёт внизу во всю ширину,
     а место и размер, запомненные на компьютере, здесь не действуют —
     отсюда !important поверх строчных стилей app.js. */
  @media (max-width:640px){
    .lumi-win,body.sc-on .lumi-win{left:8px!important;right:8px!important;top:auto!important;bottom:8px!important;
      width:auto!important;height:min(76vh,600px)!important;border-radius:14px}
    .lumi-grip{display:none}
    .chat-head{cursor:default}
    .msg{max-width:94%}
  }

  /* ============ TOASTS ============ */
  #toasts{position:fixed;bottom:24px;right:24px;display:flex;flex-direction:column;gap:10px;z-index:1000}
  /* В углу — кнопка чата (ниже, «ЧАТ ПОДДЕРЖКИ»): уведомления встают над ней, а не на неё. */
  body.sc-on #toasts{bottom:100px}
  /* Круглый значок Lumi в углу (25.10.2026) — так же; с ним и с кнопкой чата — над обоими. */
  body.lumi-btn #toasts{bottom:100px}
  body.lumi-btn.sc-on #toasts{bottom:176px}
  /* Окно Lumi стоит в том же углу — уведомления встают левее него (на телефоне —
     над ним), а не на его поле ввода. Отступ считает app.js (lumiSyncToasts). */
  body.lumi-corner #toasts{right:var(--lumi-toast-right,24px)}
  body.lumi-above #toasts{bottom:var(--lumi-toast-bottom,24px)}
  /* Плашка светлая, в тон сайту — вариант Б из четырёх показанных (решение
     владельца 30.09.2026). До этого была тёмная (#12213A): на светлых экранах
     сайта она читалась чужой заплатой, а цветной кружок значка на ней почти не
     отличался от соседнего. Тёмной её обратно не делать, не спросив.
     Все цвета ниже держат к своему фону не меньше 4,5:1 — плашка часто несёт
     единственное объяснение отказа, и прочитать её должны на любом экране. */
  .toast{
    display:flex;align-items:center;gap:11px;background:#F4F9FF;color:#0F1E2E;border:1px solid #CFE3F8;
    padding:13px 18px;border-radius:14px;
    /* Две тени, а не одна: светлая плашка всплывает и над голубой страницей, и
       над белой карточкой настроек — ближняя отделяет край, дальняя держит
       высоту. У тёмной хватало одной, её отделял сам цвет. */
    box-shadow:0 12px 28px rgba(20,60,120,.18),0 2px 6px rgba(20,60,120,.10);
    font-size:13.5px;font-weight:500;min-width:280px;max-width:380px;
    animation:toastIn .3s cubic-bezier(.2,.9,.3,1.2);
  }
  /* Длительность парная с TOAST_FADE_MS в app.js — см. комментарий у toast(). */
  .toast.hide{animation:toastOut 1.25s forwards}
  .toast .t-icon{width:26px;height:26px;border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0}
  .toast .t-icon svg{width:13px;height:13px}
  .toast.success .t-icon{background:#E2F4E9;color:#0F6E33}
  .toast.info .t-icon{background:#DEEBFD;color:#1B4FC4}
  .toast.error .t-icon{background:#FBE8E8;color:#B91C1C}
  .toast .t-sub{font-size:12px;color:#566B85;font-weight:400;margin-top:2px}
  /* Выход рядом с отказом (toast(…, action) в app.js): обводка по светлой
     плашке, цель пальцу не меньше 32 px. Р-229, проверка роботом, заход 7. */
  /* Обводка кнопки не бледнее 3:1 к плашке (и к ней же при наведении): на
     тёмной плашке она была белой в 45 % — столько же. Светлее взять нельзя,
     иначе кнопка перестаёт читаться кнопкой. */
  .toast .t-act{margin-top:9px;min-height:32px;padding:6px 14px;border-radius:9px;border:1px solid #5C87BA;
    background:transparent;color:#0F1E2E;font:inherit;font-size:12.5px;font-weight:600;cursor:pointer}
  .toast .t-act:hover{background:#E4EFFC}
  .toast .t-act:focus-visible{outline:2px solid #1B4FC4;outline-offset:2px}
  @keyframes toastIn{from{opacity:0;transform:translateY(14px) scale(.96)}to{opacity:1;transform:none}}
  @keyframes toastOut{to{opacity:0;transform:translateX(20px)}}

  /* ============ ЧАТ ПОДДЕРЖКИ (Chatwoot, 23.10.2026) ============
     Своя кнопка до нажатия (site/support-chat.js): тот же круг 64 px в том же углу (20 px от
     края), что у кнопки Chatwoot (её стили приходят с чат-сервера, sdk.css), — подмена после
     загрузки не прыгает; цвет окна в настройках Chatwoot — тот же --primary. Слоем ниже окон
     (900) и шторки меню на телефоне (55): открытое окно и шторка кнопку закрывают. Кнопку
     Chatwoot (слой у неё выше всего) под нашими окнами прячем — на телефоне она легла бы на
     их кнопки внизу. */
  .sc-launcher{
    position:fixed;right:20px;bottom:20px;z-index:54;width:64px;height:64px;padding:0;
    display:flex;align-items:center;justify-content:center;border:0;border-radius:50%;
    background:var(--primary);color:#fff;cursor:pointer;
    box-shadow:0 8px 24px rgba(20,50,90,.22);transition:box-shadow .15s;
  }
  .sc-launcher svg{width:24px;height:24px;fill:currentColor;transition:opacity .15s}
  @media (hover:hover){ .sc-launcher:hover{box-shadow:0 10px 30px rgba(20,50,90,.34)} }
  .sc-launcher:focus-visible{outline:2px solid var(--primary-dark);outline-offset:3px}
  .sc-launcher.is-loading{cursor:progress}
  .sc-launcher.is-loading svg{opacity:.45}
  .sc-launcher.is-loading::after{
    content:'';position:absolute;inset:7px;border-radius:50%;
    border:2px solid rgba(255,255,255,.3);border-top-color:#fff;animation:scSpin .8s linear infinite;
  }
  @keyframes scSpin{to{transform:rotate(360deg)}}
  @media (prefers-reduced-motion:reduce){ .sc-launcher.is-loading::after{animation-duration:2.4s} }
  /* Записка «окно чата не открылось» — над кнопкой, светлая, как уведомления. */
  .sc-note{
    position:fixed;right:20px;bottom:96px;z-index:54;width:300px;max-width:calc(100vw - 40px);
    display:flex;align-items:flex-start;gap:8px;padding:12px 10px 12px 16px;border-radius:14px;
    background:#F4F9FF;color:#0F1E2E;border:1px solid #CFE3F8;font-size:13.5px;line-height:1.45;
    box-shadow:0 12px 28px rgba(20,60,120,.18),0 2px 6px rgba(20,60,120,.10);
  }
  .sc-note[hidden]{display:none}
  .sc-note-text{margin:0;flex:1;min-width:0;overflow-wrap:anywhere}
  .sc-note-text a{color:var(--primary-dark);font-weight:600}
  .sc-note-close{
    flex-shrink:0;width:28px;height:28px;margin:-4px 0 0;padding:0;border:0;border-radius:8px;
    display:flex;align-items:center;justify-content:center;background:transparent;color:#566B85;cursor:pointer;
  }
  .sc-note-close svg{width:11px;height:11px;stroke:currentColor;stroke-width:1.8;stroke-linecap:round}
  .sc-note-close:hover{background:#E4EFFC}
  .sc-note-close:focus-visible{outline:2px solid #1B4FC4;outline-offset:1px}
  /* Под шторкой меню на телефоне — тоже: кнопка Chatwoot выше всего и легла бы на шторку и на
     кнопку «Send» окошка Feedback, которое на телефоне встаёт внизу во всю ширину (проверено
     живьём против настоящего Chatwoot 23.10.2026). */
  body.modal-open #cw-bubble-holder,body.drawer-open #cw-bubble-holder{display:none}
  /* На телефоне колонка во всю ширину, и кнопка чата легла бы на самый низ страницы (подпись
     про DENSO WAVE под подвалом): запас снизу, чтобы прокруткой его можно было поднять над ней. */
  @media (max-width:640px){ body.sc-on main{padding-bottom:84px} }

  /* ============ ДЛЯ ЭКРАННОГО ДИКТОРА И КЛАВИАТУРЫ (13.09.2026) ============ */
  /* Текст, которого не видно, но который читает диктор: причина выключенной
     кнопки, название открытого раздела. */
  .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
  /* «Перейти к содержимому» — первое, куда попадает Tab. Спрятана за верхним
     краем, показывается только на фокусе с клавиатуры. */
  .skip-link{
    position:fixed;top:-60px;left:12px;z-index:1000;padding:10px 16px;border-radius:12px;
    background:var(--primary);color:#fff;font-weight:600;font-size:14px;text-decoration:none;
    box-shadow:0 10px 28px rgba(20,50,90,.2);transition:top .15s;
  }
  .skip-link:focus{top:12px;outline:none}
  main:focus{outline:none}

  /* ============ МОДАЛЬНОЕ ОКНО ============ */
  .modal-backdrop{
    position:fixed;inset:0;z-index:900;display:none;align-items:center;justify-content:center;padding:24px;
    background:rgba(15,30,46,.45);backdrop-filter:blur(2px);
    opacity:0;transition:opacity .18s,display .18s allow-discrete;
    /* Сколько высоты экрана отдано полям подложки сверху и снизу: от него
       считают потолок высоты .modal и окна со своим потолком ниже. */
    --modal-room:48px;
  }
  /* На компьютере окно встаёт по центру рабочей области — правее сайдбара и
     ниже шапки, над карточками экрана, а не по центру всего окна браузера:
     иначе оно сидело на 120px левее карточек (24.10.2026, замечание
     владельца по окну «Full analytics is on»). Подложка по-прежнему затемняет
     весь экран, сдвинуты только её поля. На телефоне сайдбар — шторка, шапка
     узкая, и окно остаётся по центру экрана. */
  @media (min-width:901px){
    .modal-backdrop{
      padding:calc(var(--topbar-h) + 24px) 24px 24px calc(var(--sidebar-w) + 24px);
      --modal-room:calc(var(--topbar-h) + 48px);
    }
  }
  .modal-backdrop.open{display:flex;opacity:1}
  /* Пока подложка гаснет, она ещё на экране — и без этой строки ловила бы
     нажатия те две десятых секунды, что человек уже считает окно закрытым. */
  .modal-backdrop:not(.open){pointer-events:none}
  body.modal-open{overflow:hidden}
  /* Потолок высоты и прокрутка внутри — у КАЖДОГО окна, а не по списку.
     Раньше это правило выдавалось поимённо (настройки кода, папки, период,
     обрезка, вход, «Оформление выделенных»), и каждое следующее окно снова
     оказывалось без него: на телефоне 320×640 с поднятым минимальным размером
     шрифта окно «Delete account» вырастало до 805 точек — заголовок с
     крестиком выше края, «Cancel» и «Delete account» ниже, а прокрутить нечего
     ни подложке, ни окну; при увеличении 300 % то же случалось с окном
     скачивания (Р-467, проверка роботом, заход 14, 21.09.2026). --modal-room — поля
     подложки сверху и снизу (на компьютере с шапкой). Шапка и кнопки
     стоят на месте, прокручивается середина. */
  .modal{
    width:100%;max-width:520px;background:var(--card);border-radius:var(--r);box-shadow:0 24px 60px rgba(10,25,50,.28);
    overflow:hidden;transform:translateY(8px) scale(.985);transition:transform .2s cubic-bezier(.2,.9,.3,1.1);
    display:flex;flex-direction:column;
    max-height:calc(100vh - var(--modal-room));max-height:calc(100dvh - var(--modal-room));
  }
  .modal-head,.modal-foot{flex-shrink:0}
  /* У окна смены пароля и удаления аккаунта между окном и телом стоит форма —
     браузеру нужны границы пары «почта + пароль». Колонка нужна и ей, иначе
     потолок до тела окна не доходит и прокручиваться нечему. */
  .modal > .auth-form{display:flex;flex-direction:column;min-height:0}
  .modal-backdrop.open .modal{transform:none}
  /* Плавное появление было написано двумя строками выше ещё в самом начале — и
     не работало ни разу: подложка живёт с display:none, а у невидимого элемента
     нет начального состояния, от которого браузеру плясать. Окно выскакивало
     готовым. Начальное состояние задаём явно (@starting-style), а display в
     переходе (allow-discrete) держит подложку на экране, пока она гаснет, —
     иначе окно пропадало бы мгновенно при закрытии. Браузер постарше правила
     не поймёт и покажет как раньше, мгновенно: поломкой это не будет. */
  @starting-style{
    .modal-backdrop.open{opacity:0}
    .modal-backdrop.open .modal{transform:translateY(8px) scale(.985)}
  }
  /* Окно на одно поле: 520px под строку названия — это пустая половина экрана. */
  .modal-sm{max-width:420px}
  .modal-head{display:flex;align-items:flex-start;gap:16px;padding:22px 24px 0}
  .modal-title{font-size:17px;font-weight:800;letter-spacing:-.01em;font-family:'Manrope','Inter',sans-serif}
  .modal-sub{font-size:12.5px;color:var(--muted);margin-top:3px}
  .modal-x{
    margin-left:auto;flex-shrink:0;width:32px;height:32px;display:flex;align-items:center;justify-content:center;
    border:none;background:#F1F5FA;color:var(--muted);border-radius:9px;transition:all .15s;
  }
  .modal-x svg{width:15px;height:15px}
  .modal-x:hover{background:#E4EBF3;color:var(--text)}
  .modal-body{padding:18px 24px 4px;overflow-y:auto;min-height:0}
  .modal-note{font-size:12.5px;line-height:1.5;border-radius:12px;padding:11px 14px;margin-bottom:18px}
  .modal-note.ok{background:var(--teal-50);border:1px solid #BDEECB;color:#0E7A45}
  .modal-note.warn{background:#FFF7ED;border:1px solid #FCD9B4;color:#9A5B10}
  /* Окно удаления папки говорит двумя абзацами: что уйдёт в корзину и что станет
     с кодами на бумаге (Р-165, проверка роботом, заход 6, 18.09.2026). */
  #fdNote p{margin:0}
  #fdNote p+p{margin-top:8px}
  /* Окно удаления пресета: абзац о программе, которая создаёт коды этим пресетом через API,
     стоит первым и есть не всегда (номера пресетов, 01.10.2026; askDeletePreset). */
  #pdApi:not([hidden]) + p{margin-top:8px}
  /* Ответ сервера, когда сохранить не удалось. Пустой блок места не занимает:
     :empty убирает и отступ, иначе под заметкой всегда висела бы дыра. */
  .modal-error{font-size:12.5px;line-height:1.45;color:var(--neg);margin-top:-8px;margin-bottom:14px}
  .modal-error:empty{display:none}
  /* Кнопки переносятся, когда в строку не встают: на узком окне с поднятым
     размером шрифта пара «Cancel» + «Delete account» была шире окна, и
     «Cancel», прижатая справа, уезжала за левый край (Р-467, проверка роботом,
     заход 14). При обычном шрифте ряд как был — переносить нечего. */
  .modal-foot{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:10px;padding:6px 24px 22px}
  /* подтверждение — в окне нет полей, только вопрос и предупреждение: 520px тут пусто */
  .confirm-modal{max-width:430px}

  /* ---- окно отказа: опасная ссылка или файл (18.10.2026, refusalShow в app.js) ----
     Поверх любого открытого окна: оно и так последнее в разметке, а слой выше всех окон —
     страховка, если разметку переставят. Под ним окошко Feedback (70), окно Lumi (910) и
     окошко «Contact support» (920): отказ «картинка опасна» встаёт поверх набранного. */
  #refusalModal{z-index:930}

  /* ---- окошко «Contact support» (25.10.2026, supportOpen в app.js) ----
     Слоем выше окна Lumi (910): открытое кнопкой из шапки Lumi, оно его накрывает, а не
     прячется под ним. Картинки — теми же превью и скрепкой, что в окошке Feedback. */
  #supportModal{z-index:920}
  .support-modal{max-width:480px}
  /* .modal-head и .modal-foot — flex и сильнее браузерного [hidden]: без этой строки они
     оставались видны под «спасибо». */
  .support-modal .modal-head[hidden],.support-modal .modal-foot[hidden]{display:none}
  .support-modal textarea.input{min-height:120px}
  .sup-tools{display:flex;align-items:center;gap:8px;margin-top:-4px}
  .bug-shots + .sup-tools{margin-top:10px}
  #supShots{margin-top:-4px}
  .sup-lumi{margin:12px 0 0;font-size:12.5px;line-height:1.45;color:var(--muted)}
  .sup-lumi[hidden]{display:none}
  .support-modal .chat-robot{margin-top:12px}
  .support-modal .modal-error{margin:12px 0 0}
  .support-modal .modal-error:empty{display:none}
  .support-modal.drop{box-shadow:0 0 0 3px var(--primary-100),0 24px 60px rgba(10,25,50,.28)}
  .sup-done{display:flex;flex-direction:column;align-items:center;text-align:center;padding:34px 24px 26px}
  .sup-done[hidden]{display:none}
  .sup-done-mark{display:flex;align-items:center;justify-content:center;width:52px;height:52px;border-radius:50%;background:#E2F4E9;color:#0F6E33}
  .sup-done-mark svg{width:26px;height:26px}
  .sup-done-title{margin:16px 0 0;font-size:20px;font-weight:700;line-height:1.3;color:var(--text)}
  .sup-done p{margin:6px 0 0;font-size:14px;line-height:1.55;color:var(--muted)}
  .sup-done .btn{margin-top:22px;min-width:120px}
  .refusal-text{margin:0 0 14px;font-size:14px;line-height:1.55;color:var(--text);overflow-wrap:anywhere}
  .refusal-text a{color:var(--primary);font-weight:600}
  /* Подпись Google — серым подписей сайта (--muted2, темнее не делать) и мелко, без
     значка и вида кнопки: так велел владелец («не делать заметной»). Под кнопкой,
     в самом низу окна. */
  .refusal-credit{margin:-12px 0 0;padding:0 24px 14px;font-size:11.5px;line-height:1.4;color:var(--muted2)}
  .refusal-credit[hidden]{display:none}
  .refusal-credit a{display:inline-flex;align-items:center;min-height:24px;color:inherit;text-decoration:none}
  .refusal-credit a:hover{text-decoration:underline}

  /* ---- смена платного тарифа ---- */
  /* Раскладка счёта: слева за что, справа сколько. Итог отделён чертой и
     набран крупнее — на него смотрят первым делом. */
  .pc-rows{display:grid;gap:9px}
  .pc-row{display:flex;align-items:baseline;justify-content:space-between;gap:16px;font-size:13.5px;color:var(--muted)}
  .pc-row b{font-weight:700;color:var(--text);white-space:nowrap}
  .pc-total{border-top:1px solid var(--border2);padding-top:11px;margin-top:2px;font-size:15px;font-weight:800;color:var(--text)}
  .pc-total b{font-size:16px}
  .pc-note{margin-top:14px;font-size:12.5px;line-height:1.5;border-radius:12px;padding:11px 14px;
    background:var(--teal-50);border:1px solid #BDEECB;color:#0E7A45}
  .pc-next{margin-top:14px;font-size:12.5px;color:var(--muted)}
  .pc-hint{margin-top:8px;font-size:12.5px;line-height:1.5;color:var(--muted2)}
  /* Понижение считать нечего — там один абзац обычным текстом. */
  .pc-text{font-size:14px;line-height:1.6}

  /* ---- срок работы кода ---- */
  /* Пара полей на телефоне встаёт в столбик — это делает .row-2, общее
     правило форм. Здесь остаётся только запас снизу. */
  .sched-row{margin-bottom:2px}

  /* ---- настройки кода: лимит, пароль, срок ---- */
  /* Разделы разделены линией, а не карточками: карточка в модальном окне
     внутри модального окна — уже коробка в коробке, и глубина мешает читать
     то, ради чего окно открыли. */
  .cfg-sec{padding:14px 0;border-bottom:1px solid var(--border)}
  .cfg-sec:first-child{padding-top:0}
  .cfg-sec:last-of-type{border-bottom:none;padding-bottom:16px}
  /* Окно не выше экрана: шапка с крестиком и «Сохранить» стоят на месте,
     прокручивается середина — как у «Оформления выделенных» (Р-105) и окон со
     списком папок. С тремя включёнными разделами окно выходило выше телефона
     на 320 точек и телефона лёжа: .modal-backdrop центрирует, .modal режет
     лишнее — крестик уходил за верхний край, «Сохранить» за нижний, и
     сохранить было нечем; на обычном телефоне окно помещалось впритык и
     переполнялось от второй строки подписи (Р-202, проверка роботом,
     заход 6, 18.09.2026). Календарь показывается в верхнем слое браузера и
     поэтому не раздвигает эту прокрутку и не прячется под границами окна. */
  #cfgModal .modal{
    display:flex;flex-direction:column;
    max-height:calc(100vh - var(--modal-room));max-height:calc(100dvh - var(--modal-room));
  }
  #cfgModal .modal-head,#cfgModal .modal-foot{flex-shrink:0}
  #cfgModal .modal-body{overflow-y:auto;min-height:0;overscroll-behavior:contain}

  /* Переключатель. Не чекбокс: чекбокс отвечает на вопрос «отметить?», а здесь
     вопрос другой — «включено или выключено», и ответ на него виден издалека
     положением, а не галочкой.

     width:fit-content — зона нажатия, а не вёрстка. Метка-<label> ловит клик по
     всей площади, и на широком экране строка тянулась во всю карточку: подпись
     кончалась на трети, а оставшиеся две трети пустоты всё равно переключали
     настройку. Владелец 04.09.2026 попросил обрезать зону до переключателя с
     его подписью. На телефоне ничего не изменилось — там подпись занимает
     строку целиком, и обрезать нечего. */
  .sw-row{
    display:flex;align-items:flex-start;gap:12px;width:fit-content;max-width:100%;
    cursor:pointer;user-select:none;
  }
  .sw{position:relative;flex-shrink:0;width:40px;height:23px;margin-top:1px}
  .sw input{position:absolute;inset:0;width:100%;height:100%;margin:0;opacity:0;cursor:pointer}
  .sw-track{
    position:absolute;inset:0;border-radius:999px;background:#D6DFEA;pointer-events:none;
    transition:background .16s;
  }
  .sw-track::after{
    content:'';position:absolute;top:3px;left:3px;width:17px;height:17px;border-radius:50%;
    background:#fff;box-shadow:0 1px 3px rgba(15,30,46,.28);transition:transform .16s;
  }
  .sw input:checked + .sw-track{background:var(--primary)}
  .sw input:checked + .sw-track::after{transform:translateX(17px)}
  .sw input:focus-visible + .sw-track{box-shadow:0 0 0 3px rgba(47,111,237,.22)}
  .sw-text{display:flex;flex-direction:column;gap:2px;min-width:0}
  .sw-text b{font-size:14px;font-weight:700;color:var(--text)}
  .sw-text span{font-size:12.5px;line-height:1.45;color:var(--muted)}

  /* Поля включённого раздела. Отступ слева ровно под ширину переключателя:
     так видно, что они принадлежат ему, а не следующему разделу. */
  .cfg-pane{margin:12px 0 0 52px}
  @media (max-width:520px){ .cfg-pane{margin-left:0} }
  .cfg-pane-title{display:flex;align-items:center;gap:6px;font-size:13px;font-weight:600;color:#33424F;margin-bottom:8px}
  .info-dot.cfg-tip{margin-left:2px;vertical-align:-2px}
  .cfg-tip .tip-ico{width:15px;height:15px;color:var(--muted2)}
  .cfg-tip .tip-box{width:230px}

  /* ---- выбор даты и времени ---- */
  /* Кнопка вместо поля: пока дату не выбрали, показывать пустое поле не за чем —
     оно обещает ввод с клавиатуры, а вводится здесь только время, и то внутри
     календаря. */
  /* «Начало» и «Конец» идут друг под другом, а не в две колонки. В окне
     шириной 520 px на колонку остаётся меньше двухсот пикселей, и «30 дек
     2026, 23:59» там обрезается многоточием — то есть человек перестаёт
     видеть как раз время, которое сам вписал. */
  .cfg-pane .row-2{grid-template-columns:1fr;gap:10px}
  .cfg-pane .row-2 > .field{min-width:0;margin-bottom:0}
  .dt-row{display:flex;align-items:center;gap:4px;min-width:0}
  .dt-wrap{position:relative;flex:1;min-width:0}
  .dt-btn{
    /* min-width:0 обязателен: без него кнопка не сжимается ниже своей надписи,
       и «30 сен 2026, 23:59» выдавливает крестик за край окна. */
    display:flex;align-items:center;gap:7px;width:100%;min-width:0;min-height:42px;padding:9px 10px;
    border:1px solid var(--border);border-radius:12px;background:#F6F9FC;cursor:pointer;
    font-size:13px;font-weight:600;color:var(--muted);text-align:left;transition:all .15s;
  }
  .dt-btn svg{width:15px;height:15px;flex-shrink:0;color:var(--muted2)}
  .dt-btn span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .dt-btn:hover{border-color:var(--primary-100);background:var(--primary-50)}
  .dt-btn.is-set{background:#fff;color:var(--text)}
  .dt-btn.is-set svg{color:var(--primary)}
  /* Крестик стоит снаружи кнопки, а не внутри: «убрать дату» — это не часть
     выбора даты, и промахнуться одним по другому нельзя. */
  .dt-clear{
    flex-shrink:0;width:32px;height:32px;display:flex;align-items:center;justify-content:center;
    border:1px solid var(--border);border-radius:10px;background:#fff;color:var(--muted2);
    cursor:pointer;transition:all .15s;
  }
  .dt-clear svg{width:13px;height:13px}
  .dt-clear:hover{border-color:#F3C7C7;background:#FEF2F2;color:var(--red)}

  /* Календарь показывается в browser top layer через Popover API. Он остаётся
     рядом с кнопкой в DOM (порядок Tab не меняется), но больше не входит в
     scrollable overflow .modal-body и не обрезается окном. */
  .dt-pop{
    position:fixed;z-index:951;inset:auto;margin:0;width:266px;max-width:calc(100vw - 16px);
    max-height:calc(100vh - 16px);max-height:calc(100dvh - 16px);overflow-y:auto;padding:12px;
    background:#fff;border:1px solid var(--border);border-radius:14px;
    box-shadow:0 16px 40px rgba(10,25,50,.18);
  }
  .dt-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}
  .dt-title{font-size:13.5px;font-weight:700;color:var(--text)}
  .dt-nav{
    width:28px;height:28px;display:flex;align-items:center;justify-content:center;
    border:none;background:transparent;color:var(--muted);border-radius:8px;cursor:pointer;transition:all .15s;
  }
  .dt-nav svg{width:15px;height:15px}
  .dt-nav:hover{background:var(--primary-50);color:var(--primary)}
  .dt-week,.dt-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:2px}
  .dt-week span{text-align:center;font-size:11px;font-weight:700;color:var(--muted2);padding:4px 0}
  .dt-day{
    height:32px;border:none;background:transparent;border-radius:9px;cursor:pointer;
    font-size:13px;font-weight:600;color:var(--text);transition:all .12s;
  }
  .dt-day:hover{background:var(--primary-50);color:var(--primary)}
  .dt-day.is-empty{pointer-events:none}
  /* Сегодня — точкой снизу, а не заливкой: заливка есть у выбранного дня,
     и два одинаково закрашенных дня в сетке спорят друг с другом. */
  .dt-day.is-today{position:relative}
  .dt-day.is-today::after{
    content:'';position:absolute;left:50%;bottom:4px;transform:translateX(-50%);
    width:4px;height:4px;border-radius:50%;background:var(--primary);
  }
  .dt-day.is-sel{background:var(--primary);color:#fff}
  .dt-day.is-sel::after{background:#fff}
  .dt-time{display:flex;align-items:center;gap:10px;margin-top:10px;padding-top:10px;border-top:1px solid var(--border)}
  .dt-time label{font-size:12.5px;font-weight:600;color:#33424F}
  /* Время набирается четырьмя цифрами, поэтому поле узкое и по центру:
     широкое поле обещало бы, что туда можно вписать что-то ещё. */
  .dt-time-input{width:88px;margin-left:auto;text-align:center;font-variant-numeric:tabular-nums}
  .dt-foot{margin-top:10px}
  .dt-done{width:100%;padding:9px 14px;font-size:13.5px}
  /* Число сканирований с шагами по краям: на телефоне попасть пальцем в
     стрелочки системного type=number невозможно, а лимит правят именно
     с телефона — стоя у стойки, когда промокоды кончаются. */
  .num-step{display:flex;align-items:center;max-width:260px;border:1px solid var(--border);border-radius:12px;background:#fff;overflow:hidden}
  .num-btn{
    flex-shrink:0;width:42px;height:42px;display:flex;align-items:center;justify-content:center;
    border:none;background:transparent;color:var(--primary);cursor:pointer;transition:background .15s;
  }
  .num-btn:hover{background:var(--primary-50)}
  .num-btn svg{width:16px;height:16px}
  .num-input{
    flex:1;min-width:0;height:42px;border:none;outline:none;background:transparent;
    text-align:center;font:inherit;font-size:14px;font-weight:600;color:var(--text);
    font-variant-numeric:tabular-nums;
  }
  /* Системные стрелки убираем: свои кнопки шагают по круглым числам, а эти —
     по единице, и рядом они бы спорили друг с другом. */
  .num-input::-webkit-outer-spin-button,.num-input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
  .num-input[type=number]{-moz-appearance:textfield}

  /* Пароль с «глазиком»: владелец видит его в любой момент — он для того и
     хранится обратимо, чтобы его можно было продиктовать гостю. */
  .pw-field{position:relative}
  .pw-field .input{padding-right:44px}
  .pw-eye{
    position:absolute;top:50%;right:6px;transform:translateY(-50%);
    width:32px;height:32px;display:flex;align-items:center;justify-content:center;
    border:none;background:transparent;color:var(--muted2);border-radius:8px;cursor:pointer;transition:all .15s;
  }
  .pw-eye:hover{background:#F1F5FA;color:var(--muted)}
  .pw-eye.on{color:var(--primary)}
  .pw-eye svg{width:17px;height:17px}
  /* Тот же «глазик» у паролей окна входа — с пометкой data-pw-toggle, механизм
     в auth.js (Р-147, решение владельца 18.09.2026, заход 5). Зона нажатия —
     44×44 под палец, а квадрат подсветки прежний, 32×32 (поля + фон по
     содержимому). Поле и так отдаёт кнопке свои правые 44px, поэтому набранное
     под значок не заезжает. На компьютере поле ниже кнопки (39 против 44), и
     она выступает за него на пару прозрачных точек — строку это не раздвигает:
     кнопка лежит поверх поля, а не рядом. Открытый пароль — синий
     перечёркнутый глаз: нажатие его спрячет. */
  .pw-eye[data-pw-toggle]{right:0;width:44px;height:44px;padding:6px;background-clip:content-box;border-radius:14px}
  .pw-eye[data-pw-toggle][aria-pressed="true"]{color:var(--primary)}
  .pw-eye[data-pw-toggle]:focus-visible{outline:2px solid var(--primary);outline-offset:-6px}
  .pw-eye-slash{display:none}
  .pw-eye[aria-pressed="true"] .pw-eye-slash{display:inline}
  /* Edge рисует у поля пароля свой глаз — рядом с нашим он был бы вторым. */
  .pw-field .input::-ms-reveal{display:none}
  .modal-foot .btn:disabled{opacity:.5;cursor:default}

  /* ---- выбор папки для выделенных кодов ---- */
  /* Список, а не выпадашка: показать все папки сразу дешевле, чем прятать за
     ещё одним нажатием. Папок бывают десятки — тогда прокручивается сам список
     (окна ниже), а шапка окна остаётся на месте. */
  .folder-pick{display:flex;flex-direction:column;gap:2px;padding-bottom:18px}
  /* Окна со списком папок — «Переместить папку» и «Перенести в папку» у
     выделенных. На платных тарифах папок сколько угодно (`folders_max` −1), и уже
     при полутора десятках окно вырастало выше экрана: .modal-backdrop его
     центрирует, а .modal режет лишнее, — шапка с крестиком уходила за верхний
     край, а последние пункты «Вынести наверх» и «Без папки» — за нижний, и
     достать их было нечем (Р-167, проверка роботом, заход 6, 18.09.2026; у
     засеянного big — 40 папок). Прокручивается список, как у окна периода и
     «Оформления выделенных» (Р-105). */
  #folderMoveModal .modal,#bulkFolderModal .modal{
    display:flex;flex-direction:column;
    max-height:calc(100vh - var(--modal-room));max-height:calc(100dvh - var(--modal-room));
  }
  #folderMoveModal .modal-head,#bulkFolderModal .modal-head{flex-shrink:0}
  #folderMoveModal .modal-body,#bulkFolderModal .modal-body{overflow-y:auto;min-height:0}
  .folder-pick-item{
    display:flex;align-items:center;gap:10px;width:100%;min-height:46px;padding:10px 14px;
    border:none;border-radius:12px;background:none;text-align:left;
    font-family:inherit;font-size:14px;font-weight:600;color:var(--text);transition:background .15s,color .15s;
  }
  .folder-pick-item>svg{width:17px;height:17px;flex-shrink:0;color:var(--muted2)}
  .folder-pick-item:hover{background:var(--primary-50);color:var(--primary)}
  .folder-pick-item:hover>svg{color:var(--primary)}
  .folder-pick-n{font-size:11.5px;font-weight:800;color:var(--muted2);flex-shrink:0}
  /* Последний пункт, отделённый чертой: «без папки» уводит из всех папок сразу —
     тот же смысл и тот же вид, что у «Убрать из папки» в меню карточки. */
  .folder-pick-clear{margin-top:4px;border-top:1px solid var(--border2);border-radius:0 0 12px 12px;color:var(--muted)}
  /* «Вынести наверх» тоже отделено чертой, но красным не подсвечивается:
     это не удаление, а такое же место, как любая папка из списка выше. */
  .folder-pick-up{margin-top:4px;border-top:1px solid var(--border2);border-radius:0 0 12px 12px}
  .folder-pick-clear:hover{background:#FEF2F2;color:var(--red)}
  .folder-pick-clear:hover>svg{color:var(--red)}

  /* ---- выбор периода ---- */
  /* Семь строк, два поля дат и кнопки не влезают в экран телефона 375×667: окно
     вырастало до 758px и уезжало за оба края сразу. Шапка и кнопки остаются на
     месте, прокручивается только список. */
  .period-modal{max-width:420px;display:flex;flex-direction:column;max-height:calc(100vh - var(--modal-room));max-height:calc(100dvh - var(--modal-room))}
  .period-modal .modal-head,.period-modal .modal-foot{flex-shrink:0}
  .period-modal .modal-body{overflow-y:auto;min-height:0}
  .period-list{display:flex;flex-direction:column;gap:2px;margin-bottom:4px}
  .period-opt{
    display:flex;align-items:center;justify-content:space-between;gap:10px;
    width:100%;min-height:46px;padding:10px 14px;border:none;border-radius:12px;
    background:none;text-align:left;font-size:14px;font-weight:600;color:var(--text);transition:background .15s;
  }
  .period-opt:hover{background:#F3F7FC}
  /* Галочка занимает место всегда — иначе при выборе строка дёргалась бы вбок. */
  .period-opt svg{width:16px;height:16px;flex-shrink:0;color:var(--primary);opacity:0}
  .period-opt.active{background:var(--primary-50);color:var(--primary)}
  .period-opt.active svg{opacity:1}
  .period-name{display:flex;align-items:center;gap:8px}
  /* Запертый глубиной тарифа пункт: сам серый, но рамка с именем тарифа
     остаётся цветной — она и есть предложение, ради которого пункт виден. */
  .period-opt.locked{color:var(--muted);cursor:default}
  .period-opt.locked:hover{background:none}
  .period-badge{
    font-size:11px;font-weight:700;line-height:1;color:#2F6FED;
    border:1px solid #A8C3F2;border-radius:6px;padding:3px 6px 2px;
  }
  /* Своей строки с объяснением у запертого пункта нет: почему он заперт,
     говорит облачко .tipx прямо у него (data-tip ставит openPeriodModal). */
  .period-custom{padding:14px 2px 0;margin-top:6px;border-top:1px solid var(--border2)}
  .period-custom .modal-note{margin:12px 0 0}

  /* ---- обрезка логотипа ---- */
  /* Не выше экрана и с прокруткой внутри: кадр 320 px с шапкой и кнопками — около 560 px, а
     телефон лёжа даёт 390. Общее .modal центрируется и режет лишнее (overflow:hidden), и до
     17.09.2026 заголовок уходил вверх, «Применить» — вниз, и достать его было нечем: фото
     на телефоне лёжа не ставилось (Р-72 П3, проверка роботом, заход 3). --modal-room — поля
     фона сверху и снизу (.modal-backdrop), как у .period-modal. */
  .crop-modal{max-width:400px;max-height:calc(100vh - var(--modal-room));max-height:calc(100dvh - var(--modal-room));overflow-y:auto}
  .crop-stage-wrap{display:flex;justify-content:center;padding:4px 0 18px}
  /* Кадр ужимается под узкое окно: на телефоне в 320 точек квадрат 320 px в окно
     не влезал, у окна появлялась прокрутка вбок, и края, которые уедут в
     логотип, человек не видел (Р-97, проверка роботом, заход 4). Точки самого
     кадра при этом прежние — сдвиг пальца переводит в них bindCropDrag. */
  .crop-canvas{
    display:block;max-width:100%;height:auto;background:#F1F5FA;cursor:grab;touch-action:none;
    box-shadow:0 0 0 1px rgba(15,30,46,.08);transition:border-radius .15s;
  }
  .crop-canvas:active{cursor:grabbing}
  .crop-canvas.shape-square{border-radius:16px}
  .crop-canvas.shape-circle{border-radius:50%}
  .crop-shape-row{display:flex;gap:10px;margin-bottom:18px}
  .crop-shape-btn{
    flex:1;display:flex;align-items:center;justify-content:center;gap:7px;font-family:inherit;
    border:1.5px solid var(--border);background:#fff;color:var(--muted);font-size:13px;font-weight:700;
    padding:10px;border-radius:12px;transition:all .15s;
  }
  .crop-shape-btn svg{width:17px;height:17px}
  .crop-shape-btn:hover{border-color:var(--primary-100);color:var(--text)}
  .crop-shape-btn.active{border-color:var(--primary);background:var(--primary-50);color:var(--primary)}
  .crop-zoom-row{display:flex;align-items:center;gap:10px;padding:0 2px 18px;color:var(--muted2)}
  .crop-zoom-row svg{width:15px;height:15px;flex-shrink:0}
  .crop-zoom-row svg:last-child{width:18px;height:18px}
  .crop-zoom-row input[type=range]{flex:1;accent-color:var(--primary);cursor:pointer}

  /* ---- FAQ ----
     Страница-раздел, а не модалка. Разметку целиком собирает renderFaq
     (site/app.js) — там же лежат сами вопросы и объяснение решений.

     Вид взят у блока «Вопросы и ответы» на главной (`блоки/main.html`,
     класс `.b-faq`) — 07.09.2026, по просьбе владельца: раздел и блок стоят
     на одном сайте, и человек, пришедший в раздел из-под генератора, не
     должен думать, что попал в другое место. Оттуда волосяные линейки вместо
     карточек, кружок с плюсом вместо галочки и подпись темы капителью.

     Не взяты две вещи. Поиск и чипы остаются: раздел — это место, куда
     приходят с готовым вопросом, а блок работает без скриптов (сервер их
     вырезает) и такого себе позволить не может. И блок держит первый вопрос
     открытым, а здесь открыт первый видимый — их выбирает поиск.

     Ширина и левый край — как у всех остальных экранов, своего потолка у
     раздела нет. Раньше колонка была уже (820, потом 1040) и стояла по центру:
     одинокий столбец текста во всю ширину монитора читать тяжело, а прижатый
     к левому краю он выглядел обрывком вёрстки. С двумя колонками причина
     ушла — каждая и так выходит около 540 точек, — а разница осталась бы
     видна: переход с генератора на FAQ сдвигал содержимое вправо и сужал его,
     и это первое, что заметил владелец. Заголовок по той же причине встал
     влево, как на других экранах и как в блоке на главной. */
  #screen-faq .screen-header{margin-bottom:22px}

  /* ---- поиск ---- */
  .faq-search{position:relative;margin-bottom:8px}
  .faq-search>svg{position:absolute;left:17px;top:50%;transform:translateY(-50%);width:18px;height:18px;color:var(--muted2);pointer-events:none}
  .faq-search input{
    width:100%;padding:13px 44px 13px 46px;border:1px solid var(--border);border-radius:14px;
    background:var(--card);color:var(--text);box-shadow:var(--shadow);outline:none;
    transition:border-color .15s,box-shadow .15s;
  }
  .faq-search input::placeholder{color:var(--muted2)}
  .faq-search input:focus{border-color:var(--primary);box-shadow:0 0 0 3px var(--primary-50)}
  /* Крестик появляется только при набранном тексте: пустая кнопка «очистить»
     рядом с пустым полем — обещание действия, которого нет. */
  .faq-search-x{
    position:absolute;right:11px;top:50%;transform:translateY(-50%);width:28px;height:28px;
    display:none;align-items:center;justify-content:center;border:none;background:transparent;
    color:var(--muted2);border-radius:50%;transition:all .15s;
  }
  .faq-search-x svg{width:14px;height:14px}
  .faq-search-x:hover{background:var(--primary-50);color:var(--primary)}
  .faq-search.has-text .faq-search-x{display:flex}

  /* ---- чипы разделов ---- */
  /* Переносятся на новую строку, а не уезжают прокруткой вбок: на телефоне
     горизонтальная прокрутка внутри страницы читается как поломка. */
  .faq-chips{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:26px}
  .faq-chip{
    padding:7px 14px;border:1px solid var(--border);background:var(--card);color:var(--muted);
    border-radius:999px;font-size:13px;font-weight:700;transition:all .15s;
  }
  .faq-chip:hover{border-color:var(--primary-100);color:var(--text)}
  .faq-chip.active{background:var(--primary);border-color:var(--primary);color:#fff;box-shadow:0 6px 16px rgba(47,111,237,.24)}

  /* ---- две колонки ----
     Сорок закрытых вопросов в один столбик дают почти четыре экрана заголовков;
     разложенные надвое — вдвое меньше. Ровно тот же расчёт, что и у блока на
     главной, только тем здесь семь, а не четыре, и делить их на глаз нельзя:
     вопрос добавят, и колонки разъедутся. Место разреза считает `faqSplit`
     (`site/app.js`) по числу вопросов, а темы в колонки идут подряд, не
     вперемешку, — тогда чтение сверху вниз по левой колонке и дальше по правой
     совпадает с порядком тем, и `display:contents` в одну колонку возвращает
     тот же порядок, ничего не переставляя.

     Сетка, а не `columns`: многоколоночная вёрстка пересчитывает баланс на
     каждом раскрытии ответа, и соседняя тема прыгает из колонки в колонку под
     рукой у человека.

     Одна колонка везде, где список сузили (чип или поиск: `is-one` ставит
     `faqFilter`) и где колонке в 340 точек просто не хватит места. Порог 1180
     записан второй раз в `faqWide` (site/app.js) — по нему считается сама
     раскладка, и разойтись им нельзя. */
  .faq-cols{display:grid;grid-template-columns:1fr;gap:0 64px;align-items:start}
  .faq-col{display:contents}
  @media (min-width:1180px){
    .faq-cols:not(.is-one){grid-template-columns:1fr 1fr}
    .faq-cols:not(.is-one) .faq-col{display:block}
  }

  /* ---- раздел ---- */
  .faq-group[hidden]{display:none}
  .faq-group + .faq-group{margin-top:38px}
  .faq-group-head{display:flex;align-items:center;gap:9px;margin:0 0 6px}
  .faq-group-ico{
    width:22px;height:22px;flex-shrink:0;display:flex;align-items:center;justify-content:center;
    border-radius:7px;background:var(--primary-50);color:var(--primary);
  }
  .faq-group-ico svg{width:13px;height:13px}
  /* Капитель с разрядкой — подпись темы, а не заголовок над карточкой: она
     служит указателем для глаза, а вес в этой раскладке несут сами вопросы. */
  .faq-group-title{
    font-size:12.5px;font-weight:800;text-transform:uppercase;letter-spacing:.12em;
    color:var(--primary);font-family:'Manrope','Inter',sans-serif;
  }

  /* ---- вопрос ---- */
  /* Волосяные линейки вместо карточки: у восьми тем на экране восемь коробок с
     тенями спорили бы друг с другом, а линейка держит список и молчит. */
  .faq-list{border-top:1px solid var(--border)}
  .faq-item{border-bottom:1px solid var(--border)}
  .faq-item[hidden]{display:none}
  .faq-q{
    width:100%;display:flex;align-items:flex-start;gap:16px;text-align:left;
    padding:16px 2px;border:none;background:transparent;color:var(--text);
    font-size:15px;font-weight:700;line-height:1.4;letter-spacing:-.012em;transition:color .15s;
  }
  .faq-q:hover{color:var(--primary)}
  .faq-qt{flex:1;text-wrap:balance}
  .faq-sign{
    width:26px;height:26px;flex-shrink:0;margin-top:-1px;display:flex;align-items:center;justify-content:center;
    border-radius:50%;border:1px solid var(--border);background:var(--card);color:var(--primary);
    transition:transform .25s,background .25s,border-color .25s,color .25s;
  }
  .faq-sign svg{width:11px;height:11px}
  /* Плюс поворачивается в крестик — знак сам говорит, что нажатие закроет. */
  .faq-item.open .faq-sign{transform:rotate(45deg);background:var(--primary);border-color:var(--primary);color:#fff}
  .faq-item.open .faq-q{padding-bottom:4px}
  /* Ответ показывается и прячется через display, а не высотой: ответы разной
     длины, и общий потолок высоты для анимации пришлось бы ставить наугад —
     на длинном он обрезал бы текст. Появление смягчено сдвигом. */
  .faq-a{display:none;padding:2px 42px 20px 2px;font-size:14px;line-height:1.65;color:var(--muted);text-wrap:pretty}
  .faq-item.open .faq-a{display:block;animation:faqOpen .18s ease}
  @keyframes faqOpen{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}
  .faq-a b{color:var(--text);font-weight:700}

  /* ---- пустой поиск и «остались вопросы» ---- */
  .faq-none{
    text-align:center;color:var(--muted);font-size:13.5px;line-height:1.6;padding:34px 22px;
    background:var(--card);border:1px solid var(--border);border-radius:var(--r);box-shadow:var(--shadow);
  }
  .faq-none b{display:block;color:var(--text);font-size:15px;margin-bottom:4px}
  /* Карточка «Не нашли ответ?» стоит под обеими колонками, поэтому у неё свой
     отступ сверху: у крайних тем он уже съеден линейкой последнего вопроса. */
  .faq-ask{
    margin-top:44px;text-align:center;padding:26px 22px;border-radius:var(--r);
    border:1px solid var(--primary-100);background:linear-gradient(135deg,var(--primary-50),#fff);
  }
  .faq-ask b{display:block;font-size:16px;font-weight:800;letter-spacing:-.015em;font-family:'Manrope','Inter',sans-serif;margin-bottom:5px}
  .faq-ask p{color:var(--muted);font-size:13.5px;margin-bottom:12px}
  /* Ссылка почты — кнопкой в рамке, как в блоке на главной: там это единственный
     призыв на весь раздел, и подчёркнутый текст его недодавал. */
  .faq-ask a{
    display:inline-flex;align-items:center;gap:8px;padding:10px 16px;border-radius:10px;
    border:1px solid var(--primary-100);background:var(--card);
    color:var(--primary);font-weight:700;font-size:14px;text-decoration:none;transition:all .15s;
  }
  .faq-ask a:hover{border-color:var(--primary);background:var(--primary-50)}
  @media (max-width:720px){
    .faq-a{padding-right:2px}
  }

  .hidden{display:none!important}

  /* ---- размер подписи на рамке: ползунок ----
     У range нет своей шкалы, поэтому текущий кегль печатаем цифрой рядом с подписью.
     Бегунок стилизуем руками: системный — 16px, в него не попасть пальцем. */
  .frame-size-row{margin-top:12px}
  .frame-size-val{color:var(--text);font-weight:700;margin-left:2px}
  .frame-range{-webkit-appearance:none;appearance:none;width:100%;height:30px;background:transparent;cursor:pointer}
  .frame-range::-webkit-slider-runnable-track{height:6px;border-radius:3px;background:var(--border)}
  .frame-range::-webkit-slider-thumb{
    -webkit-appearance:none;width:22px;height:22px;margin-top:-8px;border-radius:50%;
    background:var(--primary);border:3px solid #fff;box-shadow:0 2px 6px rgba(20,50,90,.28);
  }
  .frame-range::-moz-range-track{height:6px;border-radius:3px;background:var(--border)}
  .frame-range::-moz-range-thumb{width:22px;height:22px;border-radius:50%;background:var(--primary);border:3px solid #fff}

  /* ---- размер кода: ползунок на три положения ----
     Стоит в «Форме», под кнопками модулей. От ползунка кегля отличается тем, что
     значений всего три и они именованные, поэтому под дорожкой стоит своя шкала:
     цифра «1» человеку ничего не сказала бы, а «чуть меньше» — говорит. */
  .shrink-row{margin-top:14px;padding-top:14px;border-top:1px solid var(--border)}
  /* Дорожка намеренно куда короче карточки: положений всего три, и на всю ширину
     панели бегунок пришлось бы возить через полэкрана ради одного шага.
     min-width — чтобы на узкой панели не схлопнулась до нечитаемой. */
  .shrink-slider{width:30%;min-width:132px}
  /* насечки — тремя точками прямо на дорожке, чтобы дискретность была видна
     до того, как за бегунок возьмутся; крайние вжаты внутрь на свой радиус,
     иначе половина точки уходит за скруглённый край дорожки */
  .shrink-range::-webkit-slider-runnable-track{
    background-color:var(--border);
    background-image:
      radial-gradient(circle 2px at center,var(--muted) 99%,transparent 100%),
      radial-gradient(circle 2px at center,var(--muted) 99%,transparent 100%),
      radial-gradient(circle 2px at center,var(--muted) 99%,transparent 100%);
    background-repeat:no-repeat;
    background-size:6px 6px;
    /* 8px = половина бегунка (11px) минус половина плитки (3px): точки встают
       ровно под центры трёх положений, а не по краям дорожки */
    background-position:8px center, center, calc(100% - 8px) center;
  }
  .shrink-scale{
    display:flex;justify-content:space-between;
    font-size:11px;color:var(--muted);margin-top:-4px;
  }
  /* подписи целятся в свои положения бегунка, а не в края блока: у крайних
     половина ширины свисала бы за дорожку и строка расползалась бы по карточке */
  .shrink-scale span{flex:0 0 auto}
  .shrink-scale span:first-child{margin-left:-2px}
  .shrink-scale span:last-child{margin-right:-2px}

  /* подпись для тача в зоне загрузки — показывается только в мобильной вёрстке */
  .dz-tap{display:none}
  /* по логотипу на превью можно нажать: откроется редактор его положения */
  .qr-wrap.has-logo{cursor:pointer}

  /* ================================================================
     МОБИЛЬНАЯ ВЁРСТКА
     Отдельной мобильной разметки нет — те же узлы меняют раскладку.
     Три элемента существуют только здесь и на десктопе спрятаны:
     бургер, затемнение под шторкой и липкая полоса с кодом.
  ================================================================ */

  /* ---- элементы только для телефона: по умолчанию скрыты ---- */
  .burger{
    display:none;flex-direction:column;justify-content:center;gap:5px;
    width:44px;height:44px;padding:0 9px;flex-shrink:0;
    background:none;border:none;border-radius:12px;transition:background .15s;
  }
  /* Полоски — картинкой цвета --text, а не заливкой (29.09.2026). Chrome на
     телефоне с «затемнением сайтов» (и такие же режимы других браузеров) тёмную
     заливку считает фоном и оставляет тёмной на потемневшей шапке — кнопка меню
     пропадала совсем (контраст 1,06). Картинку он перекрашивает, как и знак
     рядом: полоски белеют. Без затемнения вид прежний до точки. */
  .burger span{display:block;height:2px;border-radius:2px;transition:transform .2s,opacity .2s;
    background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1' preserveAspectRatio='none'%3E%3Crect width='1' height='1' fill='%230F1E2E'/%3E%3C/svg%3E") center/100% 100% no-repeat}
  .burger:hover{background:var(--primary-50)}
  /* открытая шторка — бургер складывается в крестик */
  body.drawer-open .burger span:nth-child(1){transform:translateY(7px) rotate(45deg)}
  body.drawer-open .burger span:nth-child(2){opacity:0}
  body.drawer-open .burger span:nth-child(3){transform:translateY(-7px) rotate(-45deg)}

  .drawer-backdrop{display:none}
  .drawer-extra{display:none}

  /* Не панель, а мягкая засветка: код по центру, под ним белое сходит на нет.
     Жёсткой границы нет намеренно — иначе поверх текста настроек появляется
     вторая «шапка» и экран выглядит поделённым пополам. */
  .sticky-preview{
    display:none;position:fixed;top:var(--topbar-h);left:0;right:0;z-index:45;
    justify-content:center;padding:12px 0 46px;
    /* Плотное белое держится до низа кода (68% высоты полосы) и только под ним
       сходит на нет — иначе у нижних углов кода просвечивают настройки. */
    background:linear-gradient(180deg,
      #fff 0%, #fff 68%,
      rgba(255,255,255,.6) 85%,
      rgba(255,255,255,0) 100%);
    transform:translateY(-100%);opacity:0;transition:transform .2s ease,opacity .2s;
    /* Полоса перекрывает настройки, поэтому тапы обязана пропускать насквозь:
       своего интерактива в ней нет. */
    pointer-events:none;
  }
  .sticky-preview #qrMini{width:88px;height:88px;flex-shrink:0}

  /* ---- до 1180px: снять замки ширины ---- */
  /* Ровно из-за них страница была шире окна на любом устройстве: макет
     упирался в 1180px и не сжимался. Планшетную доводку делаем отдельно. */
  @media (max-width:1180px){
    .app,.topbar{min-width:0}
    .screen{padding:28px 24px 56px}

    /* Телефон на узком окне уходит под поля и перестаёт быть липким: в одну
       колонку липкий блок закрывал бы собой то, что человек в этот момент
       заполняет. Смотреть на карточку он спускается сам. */
    .vcard-editor{grid-template-columns:1fr;gap:28px}
    /* Подъём к подвкладкам снимаем: в одну колонку он затянул бы телефон под
       последнее поле формы. */
    .vcard-preview-col{position:static;margin-top:0}
    /* Здесь телефон стоит один посреди колонки, ни с чем не соперничая, —
       уменьшать его больше не за чем. */
    .phone-mount-sm .phone{zoom:1}
    /* Подъём кнопки генерации к низу телефона (видео и аудио) тоже снимаем:
       в одну колонку пустого поля рядом с корпусом нет, и кнопка наехала бы
       на сам телефон. */
    #form-video.active ~ .mode-zone,
    #form-audio.active ~ .mode-zone{margin-top:8px}
  }

  /* ================= ТЕЛЕФОН ================= */
  @media (max-width:900px){
    /* 100vh на телефоне меряется без адресной строки, и низ экрана срезается.
       dvh следует за ней; строка выше остаётся запасом для старых браузеров. */
    .app{height:100vh}
    .app{height:100dvh}

    /* ---- шапка: лого, название, бургер ---- */
    .topbar-inner{padding:0 12px;gap:10px;justify-content:flex-start}
    .topbar-right{display:none}          /* тарифы, FAQ, язык и вход переехали в шторку */
    .logo-img{width:32px;height:32px}
    .brand-word{font-size:21px}         /* та же пропорция, что на компьютере: 21 к 32 */
    .burger{display:flex;margin-left:auto}

    /* ---- сайдбар превращается в шторку ---- */
    .sidebar{
      position:fixed;top:var(--topbar-h);bottom:0;left:0;z-index:60;
      /* height:100% с десктопа меряется от окна, а не от пары top/bottom, и шторка
         уезжала на высоту шапки ниже экрана — плашка аккаунта оказывалась за краем */
      height:auto;
      /* Отступ под стеклянную шапку с десктопа здесь не нужен: шторка и так
         начинается под ней (top), и он отбил бы первый пункт меню вниз. */
      padding-top:0;
      width:82vw;max-width:300px;background:#fff;backdrop-filter:none;
      transform:translateX(-100%);transition:transform .22s ease,visibility 0s linear .22s;
      box-shadow:0 12px 40px rgba(15,30,46,.18);
      /* Закрытая шторка — вне пути Tab: без visibility:hidden тридцать её
         кнопок стояли на пути к генератору, а фокус уезжал за край экрана
         (13.09.2026). Задержка — чтобы шторка не пропадала до конца выезда. */
      visibility:hidden;
    }
    body.drawer-open .sidebar{transform:translateX(0);visibility:visible;transition:transform .22s ease}
    .drawer-backdrop{
      display:block;position:fixed;inset:var(--topbar-h) 0 0;z-index:55;
      background:rgba(15,30,46,.42);opacity:0;pointer-events:none;transition:opacity .22s;
    }
    body.drawer-open .drawer-backdrop{opacity:1;pointer-events:auto}
    .drawer-extra{display:flex;flex-direction:column;gap:2px;padding:0 12px}
    .drawer-auth{display:flex;gap:8px;padding:8px 0 2px}
    .drawer-auth .btn{flex:1;justify-content:center;min-height:44px}
    .nav-item{min-height:46px}

    /* Сбоку от карточки на телефоне места нет — окно встаёт снизу во всю
       ширину, поверх шторки меню (у неё z-index 60). Место, посчитанное
       openBugBox для компьютера, здесь перебиваем: !important — потому что оно
       приходит в style у самого элемента. */
    .bug-box{
      left:8px!important;right:8px!important;bottom:8px!important;top:auto!important;
      width:auto!important;max-width:none;
    }
    .bug-box textarea{min-height:104px}
    .bug-box-foot .btn{min-height:44px}
    /* Половины переключателя «идея / баг» — повыше и пошире под палец, но
       «таблеткой», а не кнопками во всю ширину. */
    .bug-kind{min-height:36px;padding:6px 18px}
    /* Превью крупнее, а крестик на нём — не 18 точек: пальцем в такой не попасть,
       и вместо «убрать не ту картинку» человек промахивался по самой картинке
       (Р-426, проверка роботом, заход 13, 20.09.2026). Заодно на 72 точках
       видно, что именно приложено, — на 56 снимок экрана не разобрать. */
    .bug-shot{width:72px;height:72px}
    .bug-shot-x{width:28px;height:28px;top:3px;right:3px}
    .bug-shot-x svg{width:13px;height:13px}

    /* ---- общая плотность ---- */
    .screen{padding:16px 14px 40px}
    .screen-header{margin-bottom:16px}
    h1{font-size:22px}

    /* ---- FAQ ---- */
    /* Ответ на телефоне занимает всю ширину карточки: правый отступ под
       значок-стрелку тут стоил бы полстроки текста. */
    .faq-q{padding:14px;font-size:14px}
    .faq-a{padding:6px 14px 16px}
    .faq-chips{margin-bottom:20px}
    .faq-search input{padding:12px 42px 12px 44px}
    .form-card,.customize,.preview-card{padding:16px}
    .create-layout{gap:14px}
    /* код и настройки встают друг под друга — ради этого и нужна липкая полоса */
    .qr-settings-row{grid-template-columns:1fr;gap:14px}
    /* без min-width:0 элемент сетки не сжимается уже своего содержимого, и колонка
       раздувалась до 599px при экране в 375 — внутрь уходила горизонтальная прокрутка */
    .qr-settings-row>*{min-width:0}
    /* Обе кнопки правки — во всю ширину и в один ряд: по краю экрана прижатая
       вправо пара выглядела бы случайной, а попадать пальцем нужно в обе. */
    .settings-col{gap:14px}
    .edit-actions{gap:10px}
    .edit-actions .btn{flex:1 1 auto;justify-content:center;min-height:46px}

    /* ---- полоса типов: четыре с половиной в экран, дальше прокруткой ----
       Обрезанная пятая плитка и есть подсказка, что полосу можно тянуть. */
    .type-grid-frame{padding:10px;border-radius:13px;margin-bottom:16px}
    .type-grid{
      display:flex;gap:8px;justify-content:flex-start;
      overflow-x:auto;scroll-snap-type:x proximity;
      -webkit-overflow-scrolling:touch;scrollbar-width:none;
    }
    .type-grid::-webkit-scrollbar{display:none}
    .type-card{flex:0 0 calc((100% - 36px)/4.5);scroll-snap-align:start;padding:8px 4px;gap:5px}
    .type-card svg{width:26px;height:18px}   /* то же гнездо, что на десктопе, чуть уже плитка */
    .type-card span{font-size:10px;line-height:1.15;overflow-wrap:anywhere}
    .type-card:hover{transform:none;box-shadow:none}   /* hover на тач-экране залипает */
    /* Скрытого ряда на телефоне нет: плитки row2 стоят в хвосте той же
       прокручиваемой полосы, а язычок не показывается. Селектор нарочно
       повторяет десктопное правило скрытия — перебить его можно только той же
       силой, и это правило обязано стоять в файле позже него. */
    .type-grid-frame:not(.expanded) .type-card.row2{display:flex}
    /* Анимации появления второму ряду здесь не нужно: он никуда не прячется,
       а пометку .expanded на телефоне ставит не язычок (его нет), а выбор типа
       из второго ряда (setType → toggleTypeRow) — и одиннадцать плиток,
       стоящих на виду, моргали на ровном месте. Правило той же силы, что
       десктопное, и стоит позже него — иначе не перебьёт. */
    .type-grid-frame.expanded .type-card.row2{animation:none}
    .type-more{display:none}

    /* ---- форма ---- */
    /* 16px — не вкусовщина: при меньшем кегле iOS Safari зумит страницу на фокусе */
    .input,select.input,textarea.input{font-size:16px;padding:12px 14px}
    .row-2{grid-template-columns:1fr}

    /* Строка карточки на телефоне ломается надвое: подпись сверху, значение
       снизу. В три колонки поле значения ужималось до пары сантиметров, а
       именно в него и вписывают самое длинное — почту и адрес. */
    /* Строки разъезжаются сильнее, чем половинки одной строки: иначе «Рабочий»
       под чужим номером читается как подпись к нему, а не как начало следующей. */
    .vc-rows{gap:18px}
    .vc-row{grid-template-columns:minmax(0,1fr) 34px;grid-template-areas:'label del' 'value value';gap:6px 8px}
    .vc-row > :nth-child(1){grid-area:label}
    .vc-row > :nth-child(2){grid-area:value}
    .vc-row > :nth-child(3){grid-area:del}
    .vc-row.vc-row-social{grid-template-columns:38px minmax(0,1fr) 34px;grid-template-areas:none}
    .vc-row.vc-row-social > :nth-child(1),
    .vc-row.vc-row-social > :nth-child(2),
    .vc-row.vc-row-social > :nth-child(3){grid-area:auto}
    .vc-row .input{height:auto}
    .vc-photo-btns{width:auto;flex:1}
    /* На телефоне ужимать имя не от чего — колонка и так одна. */
    .vc-field-half .input{max-width:none}

    .mode-cards{gap:8px}
    .mode-card{min-height:48px;flex:1 1 100%}
    /* описание уводим под заголовок: в строку оно ужималось до столбика в три слова */
    .mode-card-dyn{min-width:0;flex-wrap:wrap;padding:11px 14px}
    .mode-card-dyn .mc-desc{flex:1 0 100%;padding-left:28px}
    /* Описание уехало на свою строку, и «Подробнее» больше не у правого края
       карточки — облачко встаёт по её середине. */
    .mode-tip .tip-box{left:0;right:0;width:auto;max-width:340px;margin-inline:auto}
    .mode-tip .tip-box::after{left:50%;right:auto;transform:translateX(-50%)}
    .btn{min-height:44px}
    .btn-lg{min-height:50px}
    /* главное действие экрана — на 15% крупнее остальных кнопок и по центру.
       width:fit-content нужен, чтобы auto-поля центрировали: блочный flex занял бы всю строку. */
    .btn-generate{display:flex;width:fit-content;margin:0 auto;min-height:58px;font-size:16px;padding:15px 28px}
    .btn-generate svg{width:19px;height:19px}
    /* Рядом эти двое на телефоне не встают: кнопка и так занимает почти всю
       ширину. Название уходит под неё во всю строку. */
    .gen-row{flex-direction:column;align-items:stretch}
    .gen-name{flex:none;width:100%;min-width:0}
    .gen-name-input{min-height:48px;font-size:16px;text-align:center}
    /* Текст по центру — запас под дискетку с обеих сторон, иначе центр съехал бы. */
    .gen-name .gen-name-input{padding-inline:50px}
    .gen-name-save{width:42px;height:42px;right:4px}

    /* ---- зона загрузки вместо перетаскивания ----
       Пальцем файл в браузер не перетащить, поэтому на телефоне это просто широкая
       кнопка выбора файла: иконка слева, подпись и лимит — справа. */
    .dz-drag{display:none}
    .dz-tap{display:inline}
    .dropzone{
      display:grid;grid-template-columns:auto 1fr;column-gap:12px;align-items:center;
      text-align:left;padding:12px 14px;min-height:60px;
      border-style:solid;border-color:var(--primary-100);background:var(--primary-50);
    }
    .dropzone .dz-icon{grid-row:1/3;margin:0;width:38px;height:38px;background:#fff}
    .dropzone .dz-title{font-size:14.5px;font-weight:700;color:var(--primary)}
    .dropzone .dz-sub{margin-top:2px}
    .dropzone-logo{padding:12px 14px}
    /* «Разместить QR»: то же правило, что и для .dz-drag выше, только зоны там
       свои. «Перетащите документ сюда» на телефоне — обещание, которого не
       сдержать: пальцем файл в браузер не перетаскивают. Остаются список
       форматов и кнопка выбора — по ним и так понятно, что делать. */
    .place-drop-title,.place-stage-drop-title{display:none}

    /* ---- карточка кода ---- */
    .qr-stage{min-height:0;margin-bottom:12px}
    .qr-wrap canvas{max-width:100%;height:auto}
    /* PNG / SVG / PDF / «Скачать» в одну строку — только на телефоне: там каждая
       лишняя полоса по высоте отжимает код за верхний край. Кегль форматов ужат,
       иначе кнопке не оставалось места и она сваливалась на вторую строку —
       ровно то, от чего строку и объединяли. Скругление форматов подогнано под
       кнопку: рядом друг с другом разные углы видны сразу.

       Третий формат (PDF, 03.09.2026) съел последний запас: поля кнопок, точку и
       промежуток пришлось ужать ещё на пару пикселей, чтобы строка сходилась на
       360px — это самый узкий экран, который сегодня встречается всерьёз. На
       совсем древних 320px «Скачать» встаёт своей строкой; четыре вещи туда не
       помещаются никакими полями, и уж лучше лишняя полоса, чем кнопка в четверть
       слова. Высота 44px везде остаётся — это размер, в который попадает палец. */
    .fmt-row{gap:8px 6px}
    .fmt-btn{min-height:44px;padding:7px 7px;gap:5px;font-size:12.5px;border-radius:var(--r-sm)}
    .fmt-btn .fmt-dot{width:13px;height:13px}
    #btnDownload{flex:1 1 auto;min-width:96px;min-height:44px;padding:10px 8px;font-size:13.5px}
    /* Колонкой — чтобы порядок кусков карточки задавался здесь, а не разметкой.
       Раньше этим поднимали название кода над самим кодом; с 02.09.2026
       название стоит выше, в карточке формы, и поднимать больше нечего. */
    .preview-card{display:flex;flex-direction:column}

    /* ---- настройки: все вкладки в одной строке ---- */
    /* Заголовок и «Сохранить в пресеты» — одной строкой, как на десктопе.
       Высота 40px вместо общих для телефона 44: кнопка второстепенная, но палец
       по ней всё равно попадает — она широкая, промахнуться можно только по высоте. */
    .cust-head{gap:8px}
    .cust-head .btn-preset-save{min-height:40px;padding:6px 10px;flex-shrink:0}
    /* «Применить» — третья вещь в той же строке, и палец по ней попадает чаще,
       чем по пресетам: ею отвечают на каждую подобранную мелочь. Высота та же,
       что у соседки, иначе строка выглядит ступенькой. */
    .cust-head .btn-apply-design{min-height:40px;padding:6px 10px;flex-shrink:0}
    /* Вкладки НЕ прокручиваются: при горизонтальной прокрутке половина вкладок
       («Цвет», «Рамка», «Пресеты») оказывалась за краем экрана, а полосы прокрутки
       на телефоне нет — настройки просто не находили. Иконка встаёт над подписью:
       так вкладка занимает ширину слова, и все шесть влезают в 375px.
       overflow-x оставлен страховкой на случай, если вкладок станет больше. */
    .cust-tab-row{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;-webkit-overflow-scrolling:touch;gap:0}
    .cust-tab-row::-webkit-scrollbar{display:none}
    .cust-tab{
      flex:1 1 auto;min-width:0;flex-direction:column;justify-content:center;
      gap:3px;padding:8px 4px;font-size:12px;min-height:46px;
    }
    .cust-tab svg{width:17px;height:17px}
    /* На телефоне вкладка — столбик «иконка над подписью», и справа от подписи
       места нет: резерв снимаем, значок уходит в верхний угол, рядом с иконкой.
       Подпись он там не задевает — она ниже. */
    .cust-tab[data-pane="preset"]{padding-right:4px}
    .tab-bump{top:1px;right:0;padding:0 4px;font-size:9.5px;transform:translateY(3px) scale(.7)}
    .cust-tab.bumped .tab-bump{transform:translateY(0) scale(1)}
    /* Плитки рамок — ровно четыре в ряд: при фиксированных 82px в строку влезали
       три, и четвёртая уезжала вниз одна. Ширину задаём долей строки, а миниатюра
       тянется за плиткой — поэтому правило работает и на 320px, и на планшете.
       30px в calc — это три промежутка по 10px между четырьмя плитками. */
    .opt-btn.opt-frame{flex:0 0 calc((100% - 30px)/4);min-width:0;padding:8px 6px;min-height:0}
    /* Плитки форм модулей — тоже четыре в ряд, тем же приёмом, что и рамки: семь
       штук ложатся в две строки вместо четырёх, и вкладка перестаёт быть длиннее
       экрана. Ради этого ужаты поля, значок и кегль подписи: при 98px минимума в
       строку влезали две плитки, а на 70px «Rounded» кеглем 12.5px не помещался.
       Подпись держим в одну строку — перенос сделал бы плитки разной высоты. */
    #shapeRow .opt-btn{flex:0 0 calc((100% - 30px)/4);min-width:0;padding:9px 3px;gap:5px}
    #shapeRow .opt-btn svg{width:30px;height:30px}
    #shapeRow .opt-btn span{font-size:10.5px;white-space:nowrap}
    .opt-btn .frame-thumb{width:100%;height:auto;aspect-ratio:62/78}
    .opt-btn.opt-frame svg{width:100%;height:auto;max-width:46px;aspect-ratio:1}
    /* «Второй цвет» в строку не влезал и вылезал за карточку. Слева от него остаётся
       «Код» (и «Рамка» во вкладке рамки) — этого хватает, чтобы понять, что это пара.
       Одной подписи мало: пара всё равно была шире карточки, поэтому ужаты и поля.
       flex-wrap — страховка: при другой длине подписи строка перенесётся, а не вылезет. */
    #fg2Item .color-label,#frame2Item .color-label{display:none}
    .color-row{flex-wrap:wrap;gap:10px 12px}
    /* Поле должно вмещать семь символов вида #4766A9 кеглем 16px — мельче нельзя,
       iOS зумит страницу на фокусе. Ради этих 90px ужаты образец, отступы и подпись:
       иначе пара уезжала на вторую строку. */
    /* рамка вокруг пары съедала 28px по бокам — на 375px это и была та нехватка,
       из-за которой второй цвет уезжал на следующую строку */
    .color-group{padding:10px 8px}
    .bg-row{padding-left:0}
    .color-item{min-width:0;gap:5px}
    .color-item input[type=color]{width:30px;height:30px;border-radius:9px}
    .color-item .hex-input{width:90px;height:38px;font-size:16px;padding:0 6px}
    .color-label{font-size:11px}
    /* текст на рамке и шрифт — в один ряд */
    .frame-text-grid{grid-template-columns:1fr 1fr}
    .frame-range{height:34px}
    .frame-range::-webkit-slider-thumb{width:26px;height:26px;margin-top:-10px}
    .frame-range::-moz-range-thumb{width:26px;height:26px}
    /* бегунок на телефоне крупнее, значит и крайние положения его центра сдвинуты:
       10px = половина бегунка (13px) минус половина плитки (3px) */
    .shrink-range::-webkit-slider-runnable-track{background-position:10px center, center, calc(100% - 10px) center}
    /* На телефоне тянут пальцем, а не мышью — дорожку оставляем длиннее десктопной:
       короче она была бы точнее, но попадать в положения стало бы неудобно. */
    .shrink-slider{width:60%;min-width:0}

    /* ---- липкое мини-превью ---- */
    .sticky-preview{display:flex}
    .sticky-preview.on{transform:translateY(0);opacity:1}

    /* ================= АНАЛИТИКА ================= */

    /* ---- четыре главные цифры ----
       Сетку 2×2 оставляем: в горизонтальной ленте, как у полосы типов, половина
       цифр ушла бы за край, а это главные числа экрана — их не листают, на них
       смотрят. Ужимаем сами карточки: вчетвером они занимали 328px, то есть
       40% первого экрана до первого содержимого. */
    .stats-grid{grid-template-columns:repeat(2,1fr);gap:10px;margin-bottom:14px}
    .stat-card{padding:13px 14px}
    .stat-top{margin-bottom:6px}
    .stat-label{font-size:11.5px}
    .stat-icon{width:28px;height:28px;border-radius:9px}
    .stat-icon svg{width:14px;height:14px}
    .stat-value{font-size:21px}
    /* Резерв под вторую строку держали, пока под числом стояло «к прошлому
       периоду» — оно переносилось не у всех карточек, и ряды выходили рваными
       (147/147/165/165). Теперь там значок, подписи везде в одну строку, и
       резерв равен его высоте: ряд ровный, а карточка на 11px ниже. */
    .stat-trend{font-size:11px;margin-top:3px;min-height:15px}
    .stat-alltime{font-size:11px;margin-top:2px}

    /* ---- график ---- */
    .chart-card{padding:14px 12px 10px;margin-bottom:14px}
    .chart-head{margin-bottom:8px;gap:10px}
    .chart-head h3{font-size:14px}
    .chart-tools{gap:10px;flex-shrink:0}
    /* «Scans over time», выбор папки, галочка и календарик вчетвером в одну
       строку не встают ни на одном телефоне: инструменты уходят под заголовок
       на свою строку целиком. Только на «Аналитике» — на «Статистике QR»
       кнопки папки нет, там втроём места хватает до самых узких экранов
       (см. @media 360px). Перенос внутри самой строки — страховка на длинное
       имя папки: пусть календарик съедет вниз, чем строка вылезет за карточку. */
    #screen-analytics .chart-head{flex-wrap:wrap}
    #screen-analytics .chart-head .chart-tools{flex:1 1 100%;flex-wrap:wrap;row-gap:10px;justify-content:space-between}
    /* Ростом кнопка папки равна кнопке периода — они стоят в одном ряду. */
    .chart-tools .type-filter-btn{min-height:44px;padding:8px 12px}
    .folder-filter #statsFolderLabel{max-width:96px}
    /* В своей строке кнопка стоит слева — и список раскрываем от левого края:
       от правого он уехал бы за край экрана. Ширину даём по экрану, а не по
       кнопке: имена папок длиннее слова «Все коды». */
    .folder-filter .type-menu{left:0;right:auto;min-width:0;max-width:none;width:min(320px,calc(100vw - 40px))}
    .fmenu-search input{font-size:16px;min-height:42px}   /* мельче — iOS зумит на фокусе */
    /* Название периода в строку не влезает — вместе с заголовком и галочкой это
       больше ширины экрана. Остаётся значок календаря: окно с периодами всё
       равно открывается, а выбранный период виден в нём первой же строкой.
       44px — размер, по которому попадают пальцем. */
    .period-btn{width:44px;height:44px;padding:0;justify-content:center;border-radius:14px}
    .period-btn .period-cal{display:block}
    .period-btn .js-period-label,.period-btn .chev{display:none}
    .uniq-check{font-size:12px;gap:7px}
    .uniq-check input{width:19px;height:19px}   /* мельче — не попасть пальцем */

    /* ---- панели со списками ---- */
    .pay-lock.on .pay-over{padding-top:52px}
    .pay-box{top:calc(var(--topbar-h) + 12px);padding:18px 18px 16px;border-radius:11px}
    .pay-text{font-size:13.5px;margin-bottom:14px}
    .stat-card.pay-fake::after{right:13px;bottom:12px;width:14px;height:14px}
    .stat-card.pay-fake .stat-sample{right:32px;bottom:10px;font-size:11px;line-height:16px}

    .analytics-grid{grid-template-columns:1fr;gap:12px;margin-bottom:14px}
    .panel{padding:14px 16px}
    .panel h3{font-size:13.5px;margin-bottom:12px}
    .panel-head{margin-bottom:12px}
    .bar-row{margin-bottom:11px}
    /* Три списка в одной карточке переключаются сегментами, и подписи-заголовки
       внутри больше не нужны — их роль играют сами сегменты.
       grid-row возвращаем в auto: в одной колонке span 2 оставлял пустую строку. */
    .panel-stack{grid-row:auto}
    .panel-stack .seg-row{
      display:flex;gap:0;border-bottom:1px solid var(--border2);margin:0 0 12px;
    }
    .panel-stack .seg-tab{
      flex:1 1 0;min-width:0;background:none;border:none;
      border-bottom:2px solid transparent;margin-bottom:-1px;
      padding:8px 4px;min-height:44px;font-size:12.5px;font-weight:600;color:var(--muted);
    }
    .panel-stack .seg-tab.active{color:var(--primary);border-bottom-color:var(--primary)}
    .panel-stack .seg-pane{display:none}
    .panel-stack .seg-pane.active{display:block}
    .panel-stack .seg-pane+.seg-pane{margin-top:0}
    .panel-stack .seg-pane h3{display:none}
    /* Заголовки со значком (i) на телефоне спрятаны — значок встаёт в ряд сегментов. */
    .panel-stack .seg-row .info-dot{align-self:center;margin:0 4px 0 8px}

    /* ---- топ кодов ----
       Строка — единственный способ уйти в статистику кода с этого экрана.
       48px по высоте она даёт и так, поля не трогаем. */
    .top-table tr[data-qr]:active .top-name{color:var(--primary)}
    .top-table tr[data-qr]:hover .top-name{color:inherit}   /* :hover на тач-экране залипает */

    /* ---- выбор периода ---- */
    .period-opt{min-height:50px;font-size:14.5px}
    /* Поля дат на телефоне открывают родной календарь; 16px — чтобы iOS не
       зумил страницу на фокусе, как и у остальных полей. */
    .period-custom input[type=date]{min-height:48px;font-size:16px}

    /* ================= МОИ QR ================= */

    /* ---- панель над списком ----
       Поиск уехал вниз, к фильтрам, и делить верхнюю строку больше не с чем:
       разделы занимают её целиком, «Создать QR» идёт под ними во всю ширину.
       Четыре чипа рядом с кнопкой ужимались бы в три строки по половине экрана.
       Слово «QR» с кнопки на телефоне не снимаем — места ей теперь хватает. */
    .toolbar{display:flex;flex-wrap:wrap;gap:10px}
    /* Разделы переносим, а не прокручиваем: из уехавшего за край о «Корзине»
       никто бы не догадался — полосы прокрутки на телефоне нет. */
    .filter-chips{flex:1 1 100%;gap:8px}
    .toolbar .btn-primary{flex:1 1 100%;margin-left:0;justify-content:center;min-height:44px}
    .fchip{padding:9px 14px;min-height:38px}
    .fchip-add{padding:9px 13px}

    /* ---- строка фильтров ----
       Поиск идёт первым: искать начинают с него. С 24.10.2026 рядом с ним стоит
       порядок («Newest first»), а поиск берёт всё, что осталось, — подсказка в
       нём короткая, «Search» (syncSearchHint в app.js). Остальные фильтры —
       строкой ниже (правила «вид, тип и порядок»). */
    .list-bar{gap:10px}
    .search-box{flex:1 1 0;min-width:0;order:-3;margin-left:0}
    .search-box input{font-size:16px;min-height:44px}   /* мельче — iOS зумит на фокусе */

    /* ---- папки ----
       Четыре карточки в ряд на 375px дают колонку в 84px — в неё не помещается
       ни «Летняя акция», ни дата. Делим на две: карточка становится шире экрана
       пополам, и всё, что на ней написано, читается целиком. */
    .folder-grid{grid-template-columns:repeat(2,1fr);gap:10px;margin-bottom:14px}
    .folder-card{padding:12px 13px}
    .folder-card:hover{box-shadow:var(--shadow);border-color:var(--border)}  /* hover на тач-экране залипает */
    .folder-card.is-active:hover{border-color:var(--primary)}
    .fc-icon{right:10px;bottom:9px;width:26px;height:26px;border-radius:8px}
    .fc-icon svg{width:14px;height:14px}
    .fc-name{font-size:13.5px;padding-right:24px}
    .fc-meta{gap:10px;margin-top:4px;padding-right:32px}
    .fc-stat{font-size:11px;gap:4px}
    .fc-stat svg{width:12px;height:12px}
    /* Дата — своей строкой: в карточку на пол-экрана рядом с двумя счётчиками
       она влезала огрызком «12 Se…» вплотную к значку в углу (проверка «Моих
       QR», 12.09.2026). */
    .fc-meta{flex-wrap:wrap;row-gap:3px}
    .fc-stat:last-child{flex:1 0 100%;overflow:visible}
    /* Зона нажатия у точек — 28×28: в 24 пальцем не попасть, а расти наружу
       некуда, там край карточки. */
    .fc-dots{top:9px;right:9px;width:28px;height:28px}
    .fc-dots:hover{background:none;border-color:transparent;color:var(--muted2)}
    .folder-card.open .fc-dots{background:#F1F5FA;border-color:var(--border);color:var(--text)}
    /* Пункты меню карточки папки («Переименовать», «Переместить», «Удалить папку»)
       и меню «Перенести в» у кода — 44px под палец: в 32 пальцем задевался
       соседний пункт (Р-168, проверка роботом, заход 6, 18.09.2026). */
    .folder-card .folder-menu .qr-menu-item,.qr-menu-wrap .qr-menu-item{min-height:44px}
    /* Строка «где мы сейчас»: кнопка вырастает до 38px — в 34 пальцем
       попадаешь через раз, а место под неё есть, строка всё равно пустая. */
    .folder-path{gap:8px;margin-bottom:10px}
    .fp-back{width:38px;height:38px}
    .fp-back:hover{border-color:var(--border);color:var(--muted2);box-shadow:var(--shadow)}
    /* Путь на телефоне не показываем: «Кафе / Меню / Обед /» съело бы экран
       целиком, а имени открытой папки не осталось бы места. Наружу здесь ведёт
       стрелка, по шагу за нажатие. */
    .fp-crumbs{display:none}
    .fp-icon{width:26px;height:26px;border-radius:8px}
    .fp-icon svg{width:14px;height:14px}
    .fp-name{font-size:14px}

    /* ---- вид, тип и порядок ----
       Две строки (решение владельца 24.10.2026): в первой — поиск и порядок
       («Newest first»), во второй — «All | Static | Dynamic» и «All types».
       Обёртка фильтров на телефоне растворяется (display:contents), и её кнопки
       встают в общую строку с поиском — порядок задаёт order, перенос — пустой
       ::after во всю ширину между порядком и видом.

       До этого все три кнопки стояли одной лентой с прокруткой вбок (решение
       30.09.2026), но лента с overflow обрезает всё, что из неё выходит, — и
       выпавшие меню типов и порядка открывались невидимыми: нажатие на «All
       types» и «Newest first» на телефоне будто ничего не делало (заметил
       владелец 24.10.2026). Ленты больше нет, и меню снова видно. Не влезла
       вторая строка (самые узкие телефоны) — «All types» переносится ниже, а не
       уезжает вбок.

       Панель выделенного по-прежнему подменяет фильтры целиком (hidden у
       обёртки — display:none), порядок уходит вместе с ними. */
    .list-filters:not([hidden]){display:contents}
    .list-bar::after{content:'';flex:1 1 100%;order:-1;height:0}
    .list-bar:has(.list-filters[hidden])::after{display:none}
    .list-bar #sortFilter{order:-2}
    .list-bar{row-gap:10px;column-gap:8px}
    .seg-switch{flex:0 0 auto;min-width:0}
    .list-filters > .type-filter{flex:0 0 auto}
    /* Ширину каждой половине даёт её слово, а не равная доля: делённые поровну,
       «Статические» и «Динамические» упирались в одну и ту же границу, и
       второе обрывалось многоточием при свободном месте у первого. */
    .seg-opt{flex:0 1 auto;min-width:0;padding:8px 10px;min-height:38px;font-size:12px;
      overflow:hidden;text-overflow:ellipsis}
    .type-filter{flex:0 0 auto}
    .type-filter-btn{min-height:40px;padding:8px 12px}
    /* У кнопки справа меню упирается в правый край экрана — раскрываем от него.
       Кнопке, перенесённой к левому краю («Сначала новые»), край меню выбирает
       сайт по месту на экране (fitMenuToScreen в app.js, Р-163). */
    .type-menu{left:auto;right:0;min-width:190px}
    /* «Ещё N — на вкладке …» — строка-кнопка под палец: при 19 px в высоту по ней
       промахивались (Р-162, проверка роботом, заход 6, 18.09.2026). */
    .list-other-kind,.list-hint{padding:8px 4px}

    /* ---- панель выделенного ----
       Действия уходят на свою строку целиком: рядом со счётчиком две кнопки
       ужимались до нечитаемых огрызков. */
    .sel-bar{flex:1 1 100%;flex-wrap:wrap;gap:10px;position:relative;padding-right:34px}
    .sel-count{margin-right:auto}
    .sel-actions{flex:1 1 100%}
    .sel-btn{flex:1 1 0;justify-content:center;min-height:42px;padding:8px 10px}
    /* «Очистить» в строке корзины — та же кнопка, но правило выше ей не годится:
       в панели выделенного кнопок две-три и они делят строку поровну, а эта
       стоит одна и, растянувшись, съела бы имя раздела. Стоит здесь, а не
       рядом с .fp-clear: правило .sel-btn идёт следом и иначе перебивало бы
       его само собой. */
    .fp-clear{flex:0 0 auto;min-height:38px}
    /* Крестик в угол: в потоке он делил бы строку со счётчиком и сдвигал его. */
    .sel-clear{position:absolute;right:0;top:0}
    /* «Select all» — под палец: подпись с галочкой была 20 px в высоту, мельче галочки строки
       (Р-178, проверка роботом, заход 6, 18.09.2026). */
    .sel-all{min-height:32px}
    .sel-all input{width:20px;height:20px}

    /* ---- строка списка превращается в карточку ----
       В одну линию тут не помещается ничего: на 375px строка растягивала экран
       до 722px. Раскладку разбираем на сетку — превью слева во всю высоту шапки
       карточки, справа от него название, тип с датой и бейдж; ссылка и кнопки
       идут ниже во всю ширину, им нужна вся строка.

       Счётчик сканирований прижат к правому краю карточки — там же, где он
       стоит на десктопе. Отдельную колонку под него на 375px не выделить: она
       съедала у названия 88px из 183, и «Меню ресторана «Белая лошадь»…»
       обрывалось на втором слове. Поэтому он делит строку с бейджем, но уходит
       вправо до самого края — глаз находит его там же, где привык. */
    .qr-row{
      display:grid;
      grid-template-columns:auto auto minmax(0,1fr);
      grid-template-areas:
        "chk mini name"
        "chk mini sub"
        "chk mini meta"
        "link link link"
        "short short short"
        "acts acts acts";
      align-items:center;
      column-gap:11px;row-gap:5px;padding:14px 12px;
    }
    /* Галочка — своя колонка во всю высоту шапки карточки: рядом с названием
       она делила бы строку с «⋯», а под превью ей пришлось бы отдать ещё ряд. */
    .qr-check{grid-area:chk;align-self:center}
    .qr-check input{width:20px;height:20px}
    .qr-row:hover{box-shadow:var(--shadow);border-color:var(--border)}  /* hover на тач-экране залипает */
    /* display:contents распускает обёртки: их дети становятся ячейками сетки
       карточки напрямую. Иначе название, тип и ссылка остались бы заперты
       в одной колонке и ссылке не досталось бы полной ширины. */
    .qr-row-info{display:contents}
    .qr-row-meta{display:flex;align-items:center;gap:9px;flex-wrap:wrap;grid-area:meta}
    /* Подсказка у «нет статистики» — она же кнопка для пальца: на телефоне
       навести нечем, а тап по обычному тексту облачко не откроет. */
    .qr-row-scans .scan-label[data-tip]{cursor:pointer}
    /* На телефоне превью стоит в колонке рядом со стопкой «имя + подпись +
       счётчик» и центрируется по ней: 88px — её полная высота, то есть предел,
       за которым карточка начала бы расти. */
    .qr-mini{grid-area:mini;align-self:center;width:88px;height:88px}
    .qr-name-wrap{grid-area:name}
    /* Кнопка «⋯» висит абсолютом в углу и в поток не входит — место под неё
       освобождает отступ у строки названия, иначе длинное имя уезжает под неё. */
    .qr-row.has-menu .qr-name-wrap{padding-right:30px}
    .qr-row-name{max-width:100%;font-size:15px}
    /* Карандаш появляется по наведению, а наводить на телефоне нечем — показываем
       постоянно: без него по названию не догадаться, что его можно переименовать. */
    /* Второй селектор — не лишний: правило «карандаш видно по наведению» выше
       специфичнее одиночного класса, а тач-браузер честно ставит :hover на
       тронутую карточку и возвращал кнопке настольные 18px прямо под пальцем. */
    .qr-name-pencil,.qr-name-wrap:hover .qr-name-pencil{opacity:1;width:32px;height:32px;margin:-5px -6px -5px 0}
    .qr-name-pencil svg{width:14px;height:14px}
    .qr-name-input{font-size:16px;min-height:36px}   /* мельче — iOS зумит на фокусе */
    /* Тип, дата и метка срока в одну строку на 375px не встают — разрешаем
       переносить: метка уходит под них и остаётся читаемой целиком. */
    .qr-row-sub{grid-area:sub;margin-top:0;flex-wrap:wrap;row-gap:3px}
    /* Под палец — полями, без сдвига строки: подсказку даты открывает касание. */
    .qr-date{padding:6px 0;margin:-6px 0}
    /* margin-left:auto уводит счётчик к правому краю карточки: колонка с
       названием — последняя в сетке и кончается ровно на внутреннем поле
       карточки. Столбиком, а не строкой: «128 340 сканирований» одной строкой
       рядом с бейджем не помещается и уносило бы счётчик на второй ряд. */
    .qr-row-scans{width:auto;margin-left:auto;text-align:right}
    .scan-num{font-size:18px}
    .scan-label{font-size:11px}

    /* ---- ссылка ---- */
    .qr-link-row{grid-area:link;margin-top:9px}
    .qr-short-row{grid-area:short;margin-top:6px}
    .qr-short-copy{width:34px;height:34px}
    .qr-short-copy svg{width:14px;height:14px}
    /* flex:1 растягивал текст ссылки на всю строку, и карандаш улетал к правому
       краю карточки — от адреса, который он правит, его отделяла половина
       экрана. Ужимаем текст по содержимому: кнопка встаёт сразу за ним. */
    .qr-link-text{max-width:none;flex:0 1 auto;min-width:0}
    /* Пальцу строка в 16 точек мала: зона нажатия у короткой ссылки растёт полями
       вверх и вниз, а отрицательный отступ возвращает строке прежнюю высоту. */
    a.qr-short-text{padding:8px 0;margin:-8px 0}
    /* Пальцем в 27px не попасть: на телефоне обе кнопки правки растут до 34 —
       это тот же размер, что у значков в ряду действий ниже. Серый цвет и мелкий
       рисунок внутри остаются, растёт только зона нажатия. */
    .link-edit{width:34px;height:34px;padding:0}
    .link-edit svg{width:14px;height:14px}
    /* Подсказку у заблокированной кнопки прижимаем к правому краю карточки:
       по центру она вылезала за экран, потому что кнопка стоит у самого края. */
    .link-edit-tip .tip-box{
      width:min(230px,66vw);left:auto;right:0;transform:translateY(4px);
    }
    .link-edit-tip:hover .tip-box,.link-edit-tip:focus .tip-box{transform:translateY(0)}
    .link-edit-tip .tip-box::after{left:auto;right:16px;transform:none}

    /* ---- кнопки ---- */
    /* Отделяем чертой: без неё кнопки сливались с текстом карточки в общий столбик.
       Подписи тянутся на равные доли, иконки держат квадрат — так строка занимает
       всю ширину и по каждой кнопке легко попасть пальцем. */
    .qr-row-actions{
      grid-area:acts;margin-left:0;margin-top:11px;padding-top:11px;
      border-top:1px solid var(--border2);gap:8px;
    }
    /* Шесть кнопок в одну строку: пять значков по 42px и «Stats» по своей
       ширине — 290px плюс пять зазоров по 6px это 320 из 322. Подписи не тянем:
       растянутый «Stats» забирал лишние 40px и сбрасывал корзину на вторую
       строку одну, без соседей. Перенос всё равно разрешён — на экранах уже
       360px эти 320 уже не помещаются. */
    .qr-row-actions{flex-wrap:wrap;gap:6px}
    .qr-row-actions .icon-btn{flex:0 1 auto;justify-content:center;min-height:42px;padding:8px 10px}
    .qr-row-actions .icon-btn.is-icon{flex:0 0 42px;width:42px;height:42px;padding:0}
    /* В корзине кнопок всего две, и подписи у обеих — там строку делят поровну.
       У кода на паузе кнопок тоже две — правило то же. */
    .qr-row.is-trashed .qr-row-actions .icon-btn{flex:1 1 0}
    .qr-row.is-onpause .qr-row-actions .icon-btn{flex:1 1 0}
    /* Телефон: оранжевая карточка складывается в столбик, кнопка во всю ширину. */
    .pause-note{flex-direction:column;align-items:stretch}
    .pause-note-btn{width:100%}
    .qr-row-actions .sched-tip{flex:0 0 42px}
    .qr-row-actions .sched-tip .icon-btn{width:100%}
    /* Облачко у срока прижимаем к правому краю: кнопка стоит ближе к концу
       ряда, и по центру подсказка вылезала бы за экран. */
    .sched-tip .tip-box{
      width:min(250px,72vw);left:auto;right:0;transform:translateY(4px);
    }
    .sched-tip:hover .tip-box,.sched-tip:focus .tip-box{transform:translateY(0)}
    .sched-tip .tip-box::after{left:auto;right:16px;transform:none}
    /* Обёртка подсказки стоит в ряду вместо кнопки, поэтому долю ширины делит она,
       а кнопка внутри растягивается на всю обёртку: неактивный Stats у статического
       кода должен занимать столько же, сколько активный у динамического. */
    .qr-row-actions .stats-tip{flex:0 1 auto}
    .qr-row-actions .stats-tip .icon-btn{width:100%}
    /* Облачко прижимаем к правому краю: Stats стоит в конце подписей, ближе
       к правому краю карточки, и по центру подсказка вылезала бы за экран. */
    .stats-tip .tip-box{
      width:min(250px,72vw);left:auto;right:0;transform:translateY(4px);
    }
    .stats-tip:hover .tip-box,.stats-tip:focus .tip-box{transform:translateY(0)}
    .stats-tip .tip-box::after{left:auto;right:16px;transform:none}
    .fav-btn{width:42px;height:42px}
    .fav-btn svg{width:19px;height:19px}
    /* «Восстановить» и «Удалить навсегда» вдвоём шире карточки на 375px —
       кегль ужимаем только им, у остальных кнопок запас есть. */
    .qr-row.is-trashed .icon-btn{font-size:12px;padding:8px 6px}

    .empty-state{padding:40px 18px}
  }

  /* Строка «Настройка QR» держит теперь три вещи вместо двух, и на телефоне они
     в неё не помещаются. Уступают по очереди, и подписи с кнопок не снимаются:
     значок без слова — загадка, а обе эти кнопки нажимают редко и наугад их
     искать не станут.

       ниже 520px — «Сохранить в пресеты» сокращается до «В пресеты»;
       ниже 400px — заголовок занимает строку целиком, обе кнопки уезжают
                    под него (решение владельца: лучше лишняя строка высоты,
                    чем кнопка без подписи).

     Переносит их сам flex-wrap, а строку заголовку даёт flex-basis: без него
     вниз уехала бы одна кнопка, та, что не влезла, и они разошлись бы по
     разным строкам. */
  @media (max-width:520px){
    .btn-preset-save .btn-label{display:none}
    .btn-preset-save .btn-label-sm{display:inline}
  }
  @media (max-width:400px){
    .cust-head .preview-title{flex-basis:100%}
  }

  /* Узкие экраны (320px и уже): шести подписям вкладок не хватает места и они
     встают вплотную друг к другу. Мельче кегль — вернулся зазор между словами. */
  @media (max-width:360px){
    .cust-tab{font-size:11.5px;padding:8px 2px}
    /* «Настройка QR» + галочка + «В пресеты» здесь снова впритык: заголовок короче
       не сделать, а переносить кнопки под него — терять целую строку. Сжимаем сами
       кнопки; подписи к этому месту уже сокращены выше (см. @media 640 и 520). */
    .cust-head .btn-preset-save{font-size:11.5px;padding:6px 8px;gap:6px}
    .cust-head .btn-apply-design{padding:6px 8px}
    /* Вторая строка фильтров «Моих QR» — «All | Static | Dynamic» и «All types» —
       на 320 точках не помещалась на 11 px, и «All types» уезжал на третью строку.
       Поджимаем отступы внутри кнопок, слова те же. */
    .seg-opt{padding:8px 8px}
    .list-filters .type-filter-btn{padding:8px 10px;gap:6px}
    /* «Scans over time», галочка и календарик в одну строку тут уже не встают.
       Инструменты уходят на свою строку целиком — заголовок остаётся цельным,
       а галочка с кнопкой делят строку под ним. Это про «Статистику QR»:
       на «Аналитике», где в ряду ещё и выбор папки, то же самое сделано
       для всех телефонов сразу — в блоке для 900px. */
    .chart-head{flex-wrap:wrap}
    .chart-head .chart-tools{flex:1 1 100%;justify-content:space-between}
    /* На 320 точках витрины с конструктором и файлом ехали вбок (Р-08, проверка
       роботом 13.09.2026). Корпус телефона в превью — 296px при 258px ширины
       карточки: колонка сетки (1fr меряет содержимое) тянулась под него, а за ней
       и поля. Колонка теперь не шире карточки, а телефон уменьшен zoom-ом — как у
       аудио (.phone-mount-sm): zoom уменьшает и внешний размер. Подпись главной
       кнопки «Generate a dynamic QR Code» в одну строку здесь не встаёт — переносим. */
    .vcard-editor{grid-template-columns:minmax(0,1fr)}
    .vcard-preview-col .phone{zoom:.85}
    /* Ряд над полями («Fill with example» и заготовки цвета) в 258px не встаёт и при
       justify-content:flex-end уезжал за левый край — заготовки переносим строкой ниже. */
    .vc-sec-head{flex-wrap:wrap}
    .btn-generate{max-width:100%;white-space:normal;text-wrap:balance;text-align:center;padding:14px 18px}
    /* Полоса типов на 320 точках: четыре с половиной плитки дают подписи 42px, и
       слова длиннее семи букв рвутся посередине — «Busines / s card», «Instagra / m»,
       «Pinteres / t» (overflow-wrap:anywhere держит их в плитке ценой переноса;
       Р-69 Э4, проверка роботом, заход 3, 17.09.2026). Плиток в экран — три с
       четвертью: подпись получает ~62px, как на 390, где все подписи целы, а
       обрезанная четвёртая плитка по-прежнему подсказывает, что полосу можно
       тянуть. Формула та же, что у общего правила (900px): ширина минус зазоры
       по 8px между видимыми плитками, делённая на их число. */
    .type-card{flex-basis:calc((100% - 26px)/3.25)}
  }

  /* ================= АККАУНТ =================
     Отдельного оформления у окна входа нет — оно собрано из тех же .modal,
     .field и .btn, что и остальные окна сайта. Здесь только то, чего в них
     не было: переключение панелей, ссылки под кнопкой и вход через Google. */
  .auth-actions{display:flex;align-items:center;gap:10px}
  .auth-modal{max-width:420px}
  /* Телефон лёжа — 390 точек в высоту, а окно входа с отказом занимает 450:
     общее .modal режет лишнее (overflow:hidden) и не прокручивается, и за
     нижним краем оставалась ссылка «Забыли пароль?» — та самая, на которую
     зовёт отказ после пяти промахов, — а за верхним заголовок с крестиком.
     Шапка остаётся на месте, прокручивается только середина, как у окна
     выбора периода (.period-modal) и «Оформления выделенных» (Р-128,
     проверка роботом, заход 5, 17.09.2026). */
  .auth-modal{display:flex;flex-direction:column;
              max-height:calc(100vh - var(--modal-room));max-height:calc(100dvh - var(--modal-room))}
  .auth-modal .modal-head{flex-shrink:0}
  /* Внизу окна теперь всегда ссылки под кнопкой формы — стандартных 4px
     модалки им мало, окно упирается в них. */
  .auth-modal .modal-body{padding-bottom:22px;overflow-y:auto;min-height:0}
  /* «Вход» и «Регистрация» — по центру окна (решение владельца 02.09.2026).
     Крестик остаётся справа, поэтому заголовок отодвигаем слева ровно на его
     ширину вместе с отступом (32 + 16): иначе «по центру» уехало бы влево. */
  .auth-modal .modal-head > div:first-child{flex:1;min-width:0;text-align:center;padding-left:48px}
  /* Панели лежат в одном окне и подменяют друг друга: так при переходе
     «вход → регистрация» окно не прыгает и не теряет фокус. */
  .auth-pane{display:none}
  .auth-pane.active{display:block}
  /* Форма здесь только затем, чтобы браузер видел границы пары «почта +
     пароль» (см. комментарий в index.html) — собственных отступов у неё быть
     не должно, их задают поля внутри. */
  .auth-form{margin:0}
  .auth-links{display:flex;flex-wrap:wrap;justify-content:space-between;gap:10px;margin-top:14px}
  /* Ссылки «Забыли пароль?», «Создать аккаунт» и остальные — строки в 16px при
     кнопках в 44: на телефоне в них не попадали (проверка 13.09.2026). Место
     под палец — 44px высоты, а отрицательные поля возвращают строке прежнее
     положение на экране. */
  .auth-links .text-link-btn{min-height:44px;display:inline-flex;align-items:center;padding:0 6px;margin:-12px -6px}
  /* Единственная ссылка на панели по краю смотрится случайной — ставим её по центру. */
  .auth-links.auth-links-center{justify-content:center}
  /* Кнопка регистрации — не на всю ширину окна, чтобы не выглядеть как единственно
     важное действие: остальные ссылки на панели равны ей по весу. */
  .auth-btn-narrow{display:block;width:auto;min-width:0;margin:0 auto;padding-left:26px;padding-right:26px}

  /* ---- проверка «я не робот» на регистрации ----
     Сам виджет рисует Cloudflare, его вид и размер не наши. Наше здесь — отступ
     до кнопки и запас высоты: без него форма дёргалась бы в тот момент, когда
     виджет дорисовывается. Скрытая рамка места не занимает, поэтому у формы без
     заведённых ключей вид прежний. */
  /* Виджет у Cloudflare фиксированной ширины и уже формы — ставим по центру,
     как и кнопку под ним, иначе он прижимается к левому краю случайной
     заплаткой. */
  .auth-turnstile{display:flex;justify-content:center;margin:14px 0 4px;min-height:65px}
  .auth-turnstile[hidden]{display:none}

  /* ---- требования к паролю под полем регистрации ----
     Столбиком, по требованию на строку (решение владельца 02.09.2026): в
     строку они на узком окне ломались «два и один» и выглядели случайностью.
     Меняет состояние только галочка — текст всё время серый, чтобы взгляд
     ловил разницу в одном месте, а не в трёх строках сразу. */
  .pw-rules{display:flex;flex-direction:column;gap:3px;margin-top:8px;font-size:12px;color:var(--muted)}
  /* Без этой строки скрытый список всё равно виден: display:flex по классу
     сильнее, чем display:none, который браузер сам вешает на hidden. */
  .pw-rules[hidden]{display:none}
  .pw-rule{display:inline-flex;align-items:center;gap:6px}
  /* Галочка рисуется маской, а не картинкой и не буквой: цвет задаётся одной
     строкой, а на любой системе она выглядит одинаково — символ «галочка»
     шрифтом кое-где подменяется на эмодзи. */
  .pw-rule::before{
    content:'';flex:none;width:12px;height:12px;background:var(--muted2);transition:background .15s;
    -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6L9 17l-5-5'/%3E%3C/svg%3E") center/12px 12px no-repeat;
            mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6L9 17l-5-5'/%3E%3C/svg%3E") center/12px 12px no-repeat;
  }
  .pw-rule.ok::before{background:var(--green)}
  .auth-gate-text{font-size:14px;line-height:1.6;color:var(--text);margin:0 0 20px}
  .auth-text{font-size:14px;line-height:1.55;color:var(--text);margin:0 0 10px}
  .auth-text.auth-muted{color:var(--muted);font-size:13px}
  .auth-divider{display:flex;align-items:center;gap:12px;margin:14px 0 16px;color:var(--muted2);font-size:12.5px}
  .auth-divider::before,.auth-divider::after{content:'';flex:1;height:1px;background:var(--border)}
  /* Строка «Продолжая, вы принимаете Условия…» внизу окна (10.10.2026): мелко и
     тихо, но читаемым серым, а не бледным — это условия договора, а не
     украшение. Ссылки подчёркнуты и того же цвета, чтобы строка не спорила с
     кнопкой окна. */
  .auth-legal{margin:18px 0 0;font-size:12px;line-height:1.5;color:var(--muted);text-align:center}
  .auth-legal a{color:inherit;text-decoration:underline;text-underline-offset:2px;white-space:nowrap}
  /* Вход через Google — главный способ входа (решение владельца 02.09.2026),
     поэтому он стоит первым в окне и обведён рамкой: без неё кнопка читалась
     как строчка-ссылка под формой, а не как равноценный вход. Заливки нет
     намеренно — синей остаётся одна кнопка на окне, та, что отправляет форму. */
  .auth-google{
    display:flex;align-items:center;justify-content:center;gap:10px;text-decoration:none;
    border:1.5px solid var(--border);color:var(--text);font-weight:700;
  }
  .auth-google:hover{background:#F0F6FF;color:var(--text);border-color:var(--primary-100)}
  /* Гостю градиент под именем ни к чему: подсвечивать нечего, а блок должен
     читаться как приглашение войти, а не как чужой профиль. */
  .user-box.guest{background:var(--border2)}
  .user-box.guest .avatar{background:var(--muted2)}
  /* Пояснение, зачем понадобился аккаунт: это не ошибка и не предупреждение,
     поэтому свой спокойный синий, а не оранжевый .warn. */
  .modal-note.info{background:var(--primary-50);border:1px solid var(--primary-100);color:var(--primary-dark)}

  /* ================= ТАРИФЫ =================
     Витрина: переключатель периода, четыре карточки и таблица сравнения.
     Всё содержимое приходит из базы, поэтому здесь только раскладка — числа,
     подписи и состав строк меняются в админке и вёрстку не трогают. */
  .plans-header{margin-bottom:14px}
  .plans-header .subtitle{max-width:720px}
  .plans-loading{color:var(--muted);padding:40px 0;text-align:center}
  .plans-error{
    max-width:560px;margin:24px auto;padding:18px 20px;text-align:center;
    background:var(--card);border:1px solid var(--border);border-radius:var(--r);color:var(--muted);
  }

  /* ---- переключатель «месяц / год» ---- */
  .plans-period{display:flex;flex-direction:column;align-items:center;gap:8px;margin-bottom:26px}
  .plans-switch{
    display:inline-flex;gap:4px;padding:4px;background:#fff;
    border:1px solid var(--border);border-radius:999px;box-shadow:var(--shadow);
  }
  .plans-switch button{
    border:none;background:transparent;color:var(--muted);font-size:13px;font-weight:700;
    padding:8px 18px;border-radius:999px;transition:all .15s;white-space:nowrap;
  }
  .plans-switch button:hover{color:var(--text)}
  .plans-switch button.active{background:var(--primary);color:#fff;box-shadow:0 4px 12px rgba(47,111,237,.26)}
  /* Выгода от года — рядом с переключателем, а не внутри кнопки: кнопка должна
     оставаться одинаковой ширины при переключении, иначе она прыгает. */
  .plans-save{
    display:inline-flex;align-items:center;padding:5px 11px;border-radius:999px;
    background:var(--teal-50);border:1px solid #BDEECB;color:#0F7A3C;font-size:12px;font-weight:700;
  }
  .plans-note{font-size:12.5px;color:var(--muted2);text-align:center;max-width:540px}
  /* Полоса над карточками: почему кнопок сейчас нет. Два случая — человек
     работает в чужом аккаунте (тариф купится ему самому, а не тому аккаунту) и
     подписку сейчас не сменить (назначен переход, назначена отмена, пауза,
     неоплата). Рядом кнопка в то место, где это меняют. Спокойный синий, а не
     оранжевый: это не ошибка, а объяснение (Р-390, Р-391, проверка роботом,
     заход 12). */
  .plans-ws{
    display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:12px;
    max-width:720px;margin:0 auto 20px;padding:13px 16px;border-radius:var(--r);
    background:var(--primary-50);border:1px solid var(--primary-100);color:var(--primary-dark);
    font-size:13px;line-height:1.5;text-align:center;
  }
  .plans-ws .btn{flex:none}

  /* ---- карточки ---- */
  .plans-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:11px;align-items:stretch}
  /* min-width:0 — карточка не шире своей колонки в сетке, как у таблицы
     сравнения. Без него карточка тянулась под самую длинную строку внутри
     («CANCEL SUBSCRIPTION» прописными): на телефоне 320 с поднятым минимальным
     размером шрифта все четыре занимали 14…343 в экране 320, и страница ехала
     вбок (Р-469, проверка роботом, заход 14, 21.09.2026). */
  .plan-card{
    position:relative;display:flex;flex-direction:column;min-width:0;padding:24px 20px 20px;
    background:var(--card);border:1px solid var(--border);border-radius:var(--r);box-shadow:var(--shadow);
  }
  /* Выделенный тариф не «больше», а заметнее: рост карточки ломал бы сетку на
     узких экранах, а рамка и подложка читаются одинаково при любой ширине. */
  .plan-card.featured{border-color:var(--primary);box-shadow:0 12px 30px rgba(47,111,237,.16)}
  /* Пришли сюда по кнопке замка аналитики: показываем ту самую карточку, которую
     замок назвал, — на широком экране четыре колонки видны разом, и одной
     прокрутки мало (ответ владельца 8.7, 21.09.2026). Гаснет само. */
  .plan-card.plan-pointed{
    border-color:var(--primary);
    box-shadow:0 0 0 3px rgba(47,111,237,.28),0 12px 30px rgba(47,111,237,.16);
    transition:box-shadow .3s ease,border-color .3s ease;
  }
  .plan-badge{
    position:absolute;top:-11px;left:20px;padding:4px 12px;border-radius:999px;
    background:linear-gradient(135deg,var(--primary),#4C8CFF);color:#fff;
    font-size:11.5px;font-weight:800;letter-spacing:.01em;box-shadow:0 4px 12px rgba(47,111,237,.3);
  }
  .plan-name{font-family:'Manrope','Inter',sans-serif;font-size:20px;font-weight:800;letter-spacing:-.02em}
  .plan-tagline{margin-top:6px;font-size:12.5px;line-height:1.5;color:var(--muted);min-height:38px}
  .plan-price{display:flex;align-items:baseline;gap:6px;margin-top:14px}
  .plan-price b{font-family:'Manrope','Inter',sans-serif;font-size:34px;font-weight:800;letter-spacing:-.03em}
  /* Надпись на месте цены, пока цена страны едет (Р-396): одна короткая строка,
     без плашек и пояснений (правило владельца о виде ожидания, 11.09.2026).
     Она лежит в том же теге, что и цена, — тогда место под неё ровно такое же,
     как под цену, и карточка не подпрыгивает, когда цена приедет. Мелкий
     размер задаётся внутри, а высоту строки держит крупный снаружи. */
  .plan-price-wait span{font-size:13px;font-weight:600;letter-spacing:0;color:var(--muted)}
  /* Копейки мельче целых: крупным читается сама цена, а «,99» — уточнение,
     которое не должно с ней спорить. Размер в em, чтобы на узких экранах, где
     цена ужимается, копейки ужимались вместе с ней. */
  .plan-price b small{font-size:.52em;letter-spacing:-.01em}
  .plan-price span{font-size:12.5px;color:var(--muted);font-weight:600}
  /* Строка под ценой держит высоту всегда: у бесплатного тарифа её нет, и без
     запаса кнопки в соседних карточках стояли бы на разной высоте. */
  .plan-price-sub{margin-top:4px;font-size:12px;color:var(--muted2);min-height:17px}
  /* «Налог включён» — под ценой и только там, где налог есть (Р-397). Строка
     про деньги, а не подпись оформления: цвет тот же, что у строки выше. */
  .plan-price-tax{margin-top:2px;font-size:12px;color:var(--muted2)}
  /* Прописные — у кнопки выбора тарифа, и только у неё: на витрине это главное
     действие, и капитель отделяет его от обычных кнопок сайта. Регистр делает
     стиль, а не текст, — на перевод фраза должна уходить обычной строкой. */
  /* Подпись переносится, а не вылезает из кнопки: карточка теперь не шире своей
     колонки (см. .plan-card), и длинной подписи прописными («CANCEL
     SUBSCRIPTION» при крупном шрифте) нужно место для второй строки. */
  .plan-cta{margin-top:16px;width:100%;text-transform:uppercase;letter-spacing:.03em;white-space:normal;text-align:center}
  /* Кнопки у карточки нет — нажимать нечего: правило захода 11 «кнопка, чей
     единственный исход — отказ, не показывается». Вместо неё строка тем же
     местом и той же высоты, чтобы карточки не разъехались, и без прописных:
     это объяснение, а не действие. */
  .plan-cta-off{
    display:flex;align-items:center;justify-content:center;min-height:38px;padding:10px 14px;
    border-radius:var(--r-sm);background:var(--border2);color:var(--muted);
    font-size:12.5px;font-weight:700;line-height:1.3;
    text-transform:none;letter-spacing:0;white-space:normal;text-align:center;
  }
  .plan-feats{list-style:none;margin-top:18px;padding-top:16px;border-top:1px solid var(--border2);display:grid;gap:9px}
  .plan-feats li{display:flex;align-items:flex-start;gap:8px;font-size:13px;line-height:1.45;color:var(--text)}
  .plan-feats svg{width:15px;height:15px;flex-shrink:0;margin-top:1px;color:var(--teal)}

  /* ---- таблица сравнения ---- */
  .plans-compare{margin-top:44px}
  .plans-compare h2{
    font-family:'Manrope','Inter',sans-serif;font-size:20px;font-weight:800;
    letter-spacing:-.02em;margin-bottom:14px;
  }
  .plans-table-wrap{
    background:var(--card);border:1px solid var(--border);border-radius:var(--r);
    box-shadow:var(--shadow);overflow-x:auto;
  }
  .plans-table{width:100%;border-collapse:collapse;min-width:720px}
  .plans-table th,.plans-table td{padding:12px 16px;text-align:center;font-size:13px}
  .plans-table th:first-child,.plans-table td:first-child{text-align:left;width:34%;min-width:230px}
  .plans-table thead th{
    background:var(--card);border-bottom:1px solid var(--border);
    font-size:13.5px;font-weight:800;color:var(--text);
  }
  /* Первая колонка не уезжает при боковой прокрутке: на телефоне таблица шире
     экрана, и без этого клетка «да» оказывается без названия строки. На широком
     окне прокручивать нечего, и правило ничего не меняет. */
  .plans-table thead th:first-child,
  .plans-table tbody tr:not(.plans-section-row) td:first-child{
    position:sticky;left:0;z-index:1;background:var(--card);
  }
  .plans-table thead th small{display:block;font-size:11.5px;font-weight:600;color:var(--muted)}
  .plans-table tbody tr + tr td{border-top:1px solid var(--border2)}
  .plans-table tbody tr:hover td,
  .plans-table tbody tr:not(.plans-section-row):hover td:first-child{background:#F7FBFF}
  /* position:relative — якорь для облачка подсказки у значка «i»: оно
     привязано к клетке, иначе у крайних колонок вылезало бы за таблицу. */
  .plans-row-title{position:relative;color:var(--text);font-weight:600;display:flex;align-items:center;gap:6px}
  .plans-section-row td{
    background:var(--primary-50);font-family:'Manrope','Inter',sans-serif;
    font-size:13.5px;font-weight:800;color:var(--primary-dark);letter-spacing:-.01em;
    padding-left:0;
  }
  /* Название раздела прилипает к левому краю вместе с первой колонкой: полоса
     раздела тянется на всю ширину таблицы, и при боковой прокрутке заголовок
     иначе уезжает, оставляя пустую синюю строку. */
  .plans-section-label{position:sticky;left:0;display:inline-block;padding-left:16px}
  /* Короткие значения («10 000», «2 ГБ») не переносим: разорванное число
     читается как два. Длинную фразу, наоборот, переносим — иначе одна клетка
     растягивает таблицу шире экрана. Границу ставит plansCell() в app.js. */
  .plans-cell{color:var(--muted);white-space:nowrap}
  .plans-cell.wrap{white-space:normal;display:inline-block;min-width:140px;max-width:220px}
  .plans-yes{color:var(--teal);display:inline-flex}
  .plans-yes svg{width:17px;height:17px}
  .plans-no{color:var(--muted2)}
  /* Колонка выделенного тарифа подсвечена во всю высоту таблицы — так же, как
     он выделен среди карточек. */
  .plans-table .col-featured{background:#F4F8FF}
  .plans-table thead th.col-featured{background:#EDF4FF}
  .plans-foot{margin-top:16px;font-size:12.5px;line-height:1.6;color:var(--muted);max-width:860px}

  @media (max-width:1180px){
    .plans-grid{grid-template-columns:repeat(2,1fr);gap:10px}
    .plan-tagline{min-height:0}
  }
  @media (max-width:900px){
    .plans-grid{grid-template-columns:1fr}
    .plans-period{align-items:stretch}
    .plans-switch{width:100%;justify-content:center}
    /* Переключатель решает, какую цену человек видит, — и на телефоне он такая
       же цель для пальца, как кнопка тарифа (44 точки). До 20.09.2026 кнопки
       были 32 точки высотой при 44 у соседних (Р-392, проверка роботом,
       заход 12). */
    .plans-switch button{flex:1;padding:12px 18px;min-height:44px}
    /* Два числа, которые держат таблицу читаемой на телефоне шириной 320 (Р-388,
       проверка роботом, заход 12). Прилипшая первая колонка закрывает собой
       левый край окна всегда, поэтому колонка тарифа обязана помещаться в
       остаток: 320 − 180 = 140 точек. До 20.09.2026 первая колонка занимала
       34 % от 640 (218 точек), и последнюю колонку нельзя было долистать
       целиком ни в одном положении прокрутки — под словом «Max» стояло
       «99/month» вместо «$27.99/month», а в клетках «nlimited» и «expiry».
       Теперь у неё своя ширина (180), а вся таблица — 560: колонка тарифа
       выходит около 100 точек и помещается целиком. Ширину нельзя отдать
       содержимому (`width:auto`): по самой длинной строке оно просит 259 точек,
       и становится только хуже. Прилипание при этом сохраняется — без него
       клетка «да» осталась бы без названия строки. */
    .plans-table{min-width:560px}
    .plans-table th,.plans-table td{padding:10px 12px}
    .plans-table th:first-child,.plans-table td:first-child{width:180px;min-width:180px}
    .plans-section-row td{padding-left:0}
    .plans-section-label{padding-left:12px}
  }

  /* ============ МАССОВАЯ ГЕНЕРАЦИЯ ============ */
  /* Карточки-шаги одной колонкой. Ширину держим уже экрана: строки текста
     на всю ширину монитора не читаются, а решения здесь принимают по
     тексту — что за колонка, где заголовок, сколько строк попало.
     Колонка стоит по центру, и заголовок раздела едет вместе с ней: узкая
     колонка, прижатая влево под заголовком во всю ширину, на широком
     мониторе читалась как перекос (замечание владельца, 01.09.2026).
     Тем же приёмом стоят FAQ и «Настройки». */
  .bulk-steps{display:flex;flex-direction:column;gap:11px;max-width:920px;margin-left:auto;margin-right:auto}
  #screen-bulk .screen-header{max-width:920px;margin-left:auto;margin-right:auto}
  .bulk-step,.bulk-result,.bulk-recovery{padding:22px 24px}
  .bulk-step.is-hidden{display:none}
  /* Шаг, до которого ещё не дошли: виден, но выключен. Прятать нельзя —
     человек должен видеть весь путь целиком, иначе непонятно, сколько
     ещё спросят и стоит ли начинать. */
  .bulk-step.is-locked{opacity:.45;pointer-events:none;filter:grayscale(.3)}

  .bulk-head{display:flex;align-items:center;gap:12px;margin-bottom:14px}
  .bulk-head h2{font-size:17px;font-weight:800;letter-spacing:-.015em;color:var(--text)}
  .bulk-num{
    display:inline-flex;align-items:center;flex-shrink:0;padding:4px 11px;border-radius:999px;
    background:var(--primary-50);color:var(--primary);font-size:11.5px;font-weight:800;
    letter-spacing:.01em;text-transform:uppercase;
  }
  .bulk-num-done{background:var(--teal-50);color:var(--teal-dark)}

  .bulk-hint{font-size:12.5px;line-height:1.55;color:var(--muted);margin-top:8px;max-width:70ch;text-wrap:pretty}
  .bulk-hint:empty{display:none}
  .bulk-hint.is-warn{color:var(--orange-dark)}
  .bulk-sub{font-size:12px;font-weight:700;color:var(--muted2);text-transform:uppercase;
            letter-spacing:.03em;margin:16px 0 9px;text-wrap:balance}
  .bulk-sub:first-child{margin-top:0}

  .bulk-template{display:flex;align-items:center;gap:12px;margin-top:14px;flex-wrap:wrap}
  .bulk-template-name{font-size:12.5px;color:var(--muted2);font-family:ui-monospace,'SFMono-Regular',Menlo,monospace}

  /* ---- шаг 1: тип партии и нарисованный пример таблицы ----
     Две колонки: слева вид кодов, кнопки типа и пример для скачивания, справа
     тот же пример, нарисованный листом Excel (решение владельца 21.09.2026:
     пустое место справа занято примером). Левая колонка узкая намеренно —
     визитке нужно семь колонок, и всё, что не занято кнопками, отдано ей;
     своей рамки и подложки у примера нет (22.09.2026) — он и так стоит внутри
     карточки шага, а вторая рамка забирала у таблицы ширину. */
  .bulk-step1{display:grid;grid-template-columns:minmax(0,330px) minmax(0,1fr);gap:8px 20px;align-items:start}
  .bulk-step1-main{min-width:0}
  .bulk-types{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}
  .bulk-types:empty{display:none}
  .bulk-type{
    display:inline-flex;align-items:center;gap:7px;padding:8px 12px;border:1.5px solid var(--border);
    border-radius:12px;background:#fff;font-family:inherit;font-size:13.5px;font-weight:700;color:var(--text);
    cursor:pointer;transition:border-color .15s,background .15s,color .15s;
  }
  .bulk-type svg{width:17px;height:17px;flex-shrink:0;color:var(--muted2)}
  .bulk-type:hover{border-color:var(--primary)}
  .bulk-type.active{border-color:var(--primary);background:var(--primary-50);color:var(--primary-dark)}
  .bulk-type.active svg{color:var(--primary)}
  /* Пока партия создаётся, тип и вид заперты — как и сама кнопка «Create»
     (Р-472, 22.09.2026). Выключенная кнопка обязана и выглядеть выключенной:
     иначе человек жмёт и не понимает, почему ничего не происходит. Заперты они
     на время ответа сервера, дальше отпускаются (renderBulkStep1). */
  .bulk-type:disabled,.seg-opt:disabled{opacity:.45;cursor:default}
  .bulk-type:disabled:hover{border-color:var(--border)}
  .bulk-type.active:disabled:hover{border-color:var(--primary)}
  .seg-opt:disabled:hover{color:var(--muted)}

  .bulk-sample{min-width:0;padding-top:2px}
  .bulk-sample:empty{display:none}
  .bulk-sample-cap{font-size:11.5px;font-weight:800;color:var(--muted2);text-transform:uppercase;letter-spacing:.03em;margin-bottom:8px}
  .bulk-sample-scroll{overflow-x:auto;scrollbar-width:thin;border:1px solid var(--border);border-radius:8px;background:#fff}
  .bulk-sample-table{width:100%;border-collapse:collapse;table-layout:fixed;font-size:11.5px;line-height:1.35}
  .bulk-sample-table th,.bulk-sample-table td{
    padding:6px 8px;border-right:1px solid var(--border2);border-bottom:1px solid var(--border2);
    text-align:left;vertical-align:top;
  }
  .bulk-sample-table tr:last-child>*{border-bottom:none}
  .bulk-sample-table tr>*:last-child{border-right:none}
  /* Буквы колонок и номера строк — серой каймой, как в самом Excel. */
  .bulk-sample-table thead th,.bulk-sample-table tbody th{
    background:#F1F5FA;color:var(--muted2);font-weight:700;font-size:10.5px;text-align:center;vertical-align:middle;
  }
  .bulk-sample-table thead th:first-child{width:26px}
  .bulk-sample-table tbody th{padding-left:0;padding-right:0}
  .bulk-sample-table td{color:var(--muted)}
  .bulk-sample-table tr.is-head td{font-weight:800;color:var(--text)}
  /* Название — уже, ссылка или текст — шире: иначе адрес рвётся на три строки. */
  .bulk-sample-table[data-cols="2"] thead th:nth-child(2){width:34%}
  /* Семь колонок визитки: должности и компании места больше, имени с фамилией
     и сайту — меньше (у них короткие записи). */
  .bulk-sample-table[data-cols="7"]{font-size:10px}
  .bulk-sample-table[data-cols="7"] th,.bulk-sample-table[data-cols="7"] td{padding:6px 4px}
  .bulk-sample-table[data-cols="7"] thead th:nth-child(2){width:12%}
  .bulk-sample-table[data-cols="7"] thead th:nth-child(3){width:13%}
  .bulk-sample-table[data-cols="7"] thead th:nth-child(4){width:15%}
  .bulk-sample-table[data-cols="7"] thead th:nth-child(5){width:15%}
  .bulk-sample-table[data-cols="7"] thead th:nth-child(6){width:14%}
  .bulk-sample-table[data-cols="7"] thead th:nth-child(7){width:15%}
  /* Сайту хватает ровно на «example.com»: уже — и он ломается посреди слова. */
  .bulk-sample-table[data-cols="7"] thead th:nth-child(8){width:16%}
  /* Подписи колонок не рвутся посреди слова («Должно-сть»), записи рвутся только там, где иначе не
     влезть; у почты место переноса — после «@» (<wbr> в bulkSampleHtml). */
  .bulk-sample-table tr.is-head td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .bulk-sample-table td{overflow-wrap:break-word}

  /* Строка про лимит тарифа. Стоит внизу первой карточки, а не всплывает
     ошибкой после разбора файла: узнать «на вашем тарифе нельзя» после
     получаса возни с таблицей — худший момент из возможных. */
  .bulk-limit{margin-top:14px;padding:10px 14px;border-radius:12px;background:var(--primary-50);
              font-size:12.5px;font-weight:600;color:var(--primary-dark)}
  .bulk-limit:empty{display:none}
  .bulk-limit.is-off{background:var(--orange-50);color:var(--orange-dark)}
  .link-btn{background:none;border:none;padding:0;font:inherit;color:var(--primary);
            font-weight:700;text-decoration:underline;text-underline-offset:2px}

  /* ---- карточка «партия — с тарифа …» на тарифе без партии ----
     Решение владельца 22.10.2026 (макет «Макеты/карточка-партии/вариант-2.html»):
     замок партии был мелкой оранжевой строкой внизу первого шага, человек мог
     её не заметить и пройти мастер до конца. Теперь между первым и вторым шагом
     стоит отдельная карточка: заголовок с именем тарифа, строка «что это даёт»,
     «See pricing» и полоса-анимация — таблица едет, строка вспыхивает, код
     вылетает и ныряет в папку. Показывает и заводит её bulkPromoShow (bulk.js).
     Всё, что движется, — только transform и opacity. Фон — сине-зелёный
     градиент, насыщеннее фона страницы: карточка-предложение должна отличаться
     от карточек шагов; слева вверху синий пригашен — под ним заголовок. */
  .bulk-promo{position:relative;isolation:isolate;padding:20px 24px 22px;overflow:hidden;
    border-color:rgba(47,111,237,.20);
    box-shadow:0 1px 2px rgba(20,50,90,.05),0 14px 34px rgba(47,111,237,.13);
    background:
      radial-gradient(420px 260px at 0% 0%, rgba(47,111,237,.20), transparent 70%),
      radial-gradient(380px 260px at 100% 30%, rgba(34,197,94,.26), transparent 70%),
      radial-gradient(360px 160px at 55% 120%, rgba(249,115,22,.13), transparent 70%),
      linear-gradient(118deg,#DCE8FF 0%,#E6F0FF 38%,#E4F7EC 74%,#D7F4E1 100%)}
  .bulk-promo[hidden]{display:none}
  /* еле заметная сетка точек — «стол» под листом; под текстом почти гаснет */
  .bulk-promo::before{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
    background-image:radial-gradient(rgba(47,111,237,.16) 1px, transparent 1.3px);background-size:18px 18px;
    -webkit-mask-image:linear-gradient(180deg,rgba(0,0,0,.18) 0,rgba(0,0,0,.18) 64px,#000 110px);
            mask-image:linear-gradient(180deg,rgba(0,0,0,.18) 0,rgba(0,0,0,.18) 64px,#000 110px)}
  .bulk-promo-top{position:relative;z-index:1;display:flex;align-items:center;justify-content:space-between;gap:20px}
  /* В макете заголовок в одну строку (nowrap); имя тарифа приходит из панели и
     может быть длинным — поэтому перенос разрешён: пока строка влезает, вид тот же. */
  .bulk-promo-text{min-width:0}
  .bulk-promo-title{font-family:'Manrope','Inter',sans-serif;font-size:21px;font-weight:800;letter-spacing:-.02em;line-height:1.25;color:var(--text);text-wrap:balance}
  .bulk-promo-sub{font-size:14.5px;line-height:1.45;color:var(--muted);margin-top:3px;text-wrap:pretty}
  /* Кнопка стоит ровно над папкой: ширина коробки = 2 × (отступ папки 16 + половина папки 60),
     коробка прижата к правому краю — середина кнопки над серединой папки. */
  .bulk-promo-cta{flex:0 0 152px;display:flex;justify-content:center}
  .bulk-promo-top .btn{flex-shrink:0}

  /* Полоса конвейера; своего фона у неё нет — лежит прямо на градиенте карточки. */
  .bulk-promo-stage{position:relative;z-index:1;height:136px;margin-top:10px;overflow:hidden}
  /* лист таблицы — белый, как бумага на столе */
  .bulk-promo-sheet{position:absolute;left:0;top:10px;bottom:12px;width:310px;border:1px solid rgba(47,111,237,.14);border-radius:9px;
    background:#fff;overflow:hidden;
    box-shadow:0 1px 0 rgba(255,255,255,.9) inset,0 2px 4px rgba(20,50,90,.06),0 14px 28px rgba(30,82,196,.14)}
  .bulk-promo-cols{display:grid;grid-template-columns:26px 90px 1fr;height:100%}
  .bulk-promo-shead{position:absolute;left:0;right:0;top:0;height:24px;z-index:3;background:#F1F5FA;border-bottom:1px solid var(--border)}
  .bulk-promo-shead>span{display:flex;align-items:center;padding:0 8px;font-size:11px;font-weight:800;color:var(--text);
    border-right:1px solid var(--border2);white-space:nowrap;overflow:hidden}
  .bulk-promo-shead>span:first-child{justify-content:center;padding:0}
  .bulk-promo-shead>span:last-child{border-right:none}
  .bulk-promo-sbody{position:absolute;left:0;right:0;top:24px;bottom:0;overflow:hidden;
    -webkit-mask-image:linear-gradient(180deg,transparent 0,#000 9px,#000 72%,transparent);mask-image:linear-gradient(180deg,transparent 0,#000 9px,#000 72%,transparent)}
  .bulk-promo-row{position:absolute;left:0;right:0;top:0;height:26px;will-change:transform}
  .bulk-promo-row>.bulk-promo-cells{position:relative;z-index:2;height:100%;border-bottom:1px solid var(--border2)}
  .bulk-promo-cells>span{display:flex;align-items:center;padding:0 8px;font-size:11.5px;color:var(--muted);
    border-right:1px solid var(--border2);white-space:nowrap;overflow:hidden}
  .bulk-promo-cells>span:first-child{justify-content:center;padding:0;background:#F1F5FA;color:var(--muted2);font-size:10.5px;font-weight:700}
  .bulk-promo-cells>span:last-child{border-right:none}
  .bulk-promo-hl{position:absolute;inset:0 0 1px 0;z-index:1;background:var(--teal-50);
    box-shadow:inset 0 0 0 1.5px var(--teal);border-radius:2px;opacity:0;will-change:opacity}
  .bulk-promo-pulse{position:absolute;inset:-1px 0 0 0;z-index:3;border:2px solid var(--teal);border-radius:4px;
    opacity:0;will-change:transform,opacity;pointer-events:none}
  /* «печатная головка» — зелёная засечка на правом краю листа */
  .bulk-promo-mark{position:absolute;left:0;top:0;z-index:4;width:4px;height:18px;border-radius:3px;background:var(--teal);
    box-shadow:0 0 0 3px rgba(34,197,94,.16);opacity:.35;will-change:opacity}
  /* дорожка конвейера между листом и папкой */
  .bulk-promo-rail{position:absolute;height:2px;overflow:hidden;opacity:.9}
  .bulk-promo-dots{position:absolute;left:-16px;right:-16px;top:0;height:2px;will-change:transform;
    background:repeating-linear-gradient(90deg,rgba(47,111,237,.30) 0 6px,transparent 6px 14px)}
  /* Папка: тень < задняя стенка < коды внутри < летящий код < передняя стенка.
     Все части — одного размера и места: качаются вокруг одной точки (низ по центру).
     У самой папки нет ни z-index, ни transform — части встают в общий порядок с летящим кодом. */
  .bulk-promo-folder{position:absolute;right:16px;bottom:10px;width:120px;height:105px}
  .bulk-promo-fpart{position:absolute;inset:0;transform-origin:50% 100%}
  .bulk-promo-fshadow{position:absolute;left:-16px;right:-16px;bottom:-14px;height:26px;z-index:1;border-radius:50%;
    background:radial-gradient(closest-side,rgba(30,82,196,.30),rgba(30,82,196,0))}
  .bulk-promo-fback{z-index:2}
  .bulk-promo-fback svg{width:120px;height:105px;overflow:visible}
  .bulk-promo-stickers{z-index:3}
  .bulk-promo-stk{position:absolute;left:0;top:0;width:32px;height:32px;will-change:transform}
  .bulk-promo-stk svg{width:32px;height:32px;filter:drop-shadow(0 -1px 4px rgba(20,50,90,.14))}
  .bulk-promo-ffront{z-index:6}
  .bulk-promo-flap{position:absolute;left:0;right:0;top:calc(36px * var(--fs, 1));bottom:0;border-radius:8px 8px 10px 10px;
    background:linear-gradient(180deg,#6FA3FF 0%,#4C8CFF 28%,var(--primary) 100%);
    box-shadow:inset 0 1.5px 0 rgba(255,255,255,.55),inset 0 -10px 18px rgba(20,60,160,.25),0 10px 22px rgba(30,82,196,.30);
    transform:perspective(420px) rotateX(-16deg);transform-origin:50% 100%;
    display:flex;align-items:center;justify-content:center;overflow:hidden}
  .bulk-promo-glyph{width:20px;height:20px;opacity:.85}
  .bulk-promo-sheen{position:absolute;inset:0;opacity:0;background:linear-gradient(180deg,rgba(255,255,255,.6),rgba(255,255,255,0) 70%)}
  /* счётчик = номер «QR Name» последнего кода, ушедшего в папку */
  .bulk-promo-counter{position:absolute;right:150px;bottom:13px;z-index:7;display:inline-flex;align-items:baseline;gap:5px;
    padding:3px 10px;border-radius:999px;background:#fff;border:1px solid var(--border);
    font-size:11.5px;color:var(--muted);box-shadow:0 1px 2px rgba(20,50,90,.05);white-space:nowrap}
  .bulk-promo-counter b{display:inline-block;font-size:12.5px;font-weight:800;color:var(--teal-dark);font-variant-numeric:tabular-nums;min-width:2ch;text-align:right}
  .bulk-promo-plus{position:absolute;left:0;top:0;z-index:7;font-size:12px;font-weight:800;color:var(--teal-dark);
    padding:1px 7px;border-radius:999px;background:var(--teal-50);border:1px solid #BFEFD0;opacity:0;will-change:transform,opacity}
  /* летящий код: внешняя обёртка — полёт, внутренняя карточка — «печать» из края строки */
  .bulk-promo-fly{position:absolute;left:0;top:0;width:30px;height:30px;margin:-15px 0 0 -15px;z-index:5;
    opacity:0;will-change:transform,opacity}
  .bulk-promo-qr{width:30px;height:30px;transform-origin:0 50%;will-change:transform}
  .bulk-promo-qr svg{width:30px;height:30px;filter:drop-shadow(0 3px 6px rgba(15,30,46,.16))}
  .bulk-promo-qr .bulk-promo-eye,.bulk-promo-qr .bulk-promo-dot{will-change:opacity}

  /* Телефон и узкое окно — та же точка перелома, что у шагов (820px): текст и
     кнопка стопкой; лист уже, папка и счётчик меньше; папка сверху справа,
     счётчик под ней. */
  @media (max-width:820px){
    .bulk-promo{padding:18px 16px}
    .bulk-promo::before{
      -webkit-mask-image:linear-gradient(0deg,#000 0,#000 128px,rgba(0,0,0,.18) 170px);
              mask-image:linear-gradient(0deg,#000 0,#000 128px,rgba(0,0,0,.18) 170px)}
    .bulk-promo-top{flex-direction:column;align-items:stretch;gap:14px}
    .bulk-promo-title{font-size:20px;line-height:1.25}
    .bulk-promo-sub{font-size:14px;margin-top:5px}
    .bulk-promo-cta{flex:none;display:block}
    .bulk-promo-top .btn{min-height:44px}
    .bulk-promo-stage{height:128px;margin-top:14px}
    .bulk-promo-sheet{top:8px;width:min(310px,calc(100% - 116px))}
    .bulk-promo-cols{grid-template-columns:22px 82px minmax(0,1fr)}
    .bulk-promo-shead>span,.bulk-promo-cells>span{display:block;line-height:24px;padding:0 6px;overflow:hidden;text-overflow:ellipsis;font-size:11px}
    .bulk-promo-cells>span{line-height:25px}
    .bulk-promo-shead>span:first-child,.bulk-promo-cells>span:first-child{text-align:center;padding:0;font-size:10px}
    .bulk-promo-folder{--fs:.7;width:84px;height:73.5px;right:6px;top:16px;bottom:auto}
    .bulk-promo-fback svg{width:84px;height:73.5px}
    .bulk-promo-fshadow{left:-8px;right:-8px;bottom:-9px;height:16px}
    .bulk-promo-glyph{width:15px;height:15px}
    .bulk-promo-counter{right:0;bottom:4px;width:96px;justify-content:center;padding:2px 8px;font-size:10.5px}
    .bulk-promo-counter b{font-size:11.5px;min-width:0}
    .bulk-promo-plus{font-size:11px;padding:0 6px}
  }
  /* кнопка во всю ширину — только на телефоне; на промежуточной ширине она по левому краю */
  @media (max-width:560px){
    .bulk-promo-top .btn{width:100%}
  }
  /* «Уменьшить движение»: bulkPromoMake рисует один неподвижный кадр и не крутит его. */
  @media (prefers-reduced-motion:reduce){
    .bulk-promo-row,.bulk-promo-fly,.bulk-promo-stk,.bulk-promo-dots,.bulk-promo-qr,.bulk-promo-plus{will-change:auto}
    .bulk-promo .btn-primary:hover{transform:none}
  }

  /* ---- приём файла ---- */
  .bulk-drop{
    display:flex;flex-direction:column;align-items:center;gap:8px;padding:30px 20px;
    border:1.5px dashed var(--border);border-radius:11px;background:#FAFCFF;text-align:center;
    transition:border-color .15s,background .15s;
  }
  .bulk-drop.is-over{border-color:var(--primary);background:var(--primary-50)}
  .bulk-drop.is-disabled{background:#F1F4F8;border-color:var(--border);cursor:not-allowed}
  .bulk-drop.is-disabled .bulk-pick{opacity:.55;cursor:not-allowed}
  .bulk-drop svg{width:30px;height:30px;color:var(--muted2)}
  .bulk-drop-title{font-size:14.5px;font-weight:700;color:var(--text)}
  .bulk-drop-sub{font-size:12.5px;color:var(--muted)}
  .bulk-pick{margin-top:6px;cursor:pointer}

  /* Подтверждение разбора. Зелёная полоса, а не просто имя файла: человек
     отдал таблицу и должен увидеть, что её приняли и поняли — сколько
     строк, сколько колонок, в какой кодировке. */
  .bulk-ok{
    display:flex;align-items:center;gap:12px;margin-top:16px;padding:12px 14px;
    border:1px solid var(--gen-border);border-radius:14px;background:var(--gen-soft);
  }
  .bulk-ok svg{width:20px;height:20px;flex-shrink:0;color:var(--green)}
  .bulk-ok>div{flex:1;min-width:0}
  .bulk-ok-title{font-size:13.5px;font-weight:800;color:var(--text)}
  .bulk-ok-sub{font-size:12px;color:var(--muted);margin-top:2px;word-break:break-word}

  /* Колонки файла с примером значения — по ним человек и понимает, что где. */
  .bulk-cols{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}
  .bulk-col{
    display:flex;align-items:baseline;gap:8px;max-width:100%;padding:8px 12px;
    border:1px solid var(--border2);border-radius:11px;background:#F7FBFF;
  }
  .bulk-col-letter{font-size:12px;font-weight:800;color:var(--primary);flex-shrink:0}
  .bulk-col-sample{
    font-size:12.5px;color:var(--muted);font-family:ui-monospace,'SFMono-Regular',Menlo,monospace;
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:280px;
  }

  /* На телефоне плашки идут одной лентой с прокруткой внутри карточки, а не
     столбиком: тридцать колонок по плашке в ряд вытягивали шаг «Таблица» на
     полтора-три экрана, и до выбора колонок приходилось долго листать (Р-276,
     решение владельца 21.09.2026). Вертикальную прокрутку страницы лента не
     отбирает — вверх-вниз она не ездит вовсе (overflow-y: hidden), а сцепление
     на краю отключает overscroll-behavior. На компьютере всё как было.
     До дальних плашек можно дойти клавишами: фокус ленте ставит
     bulkColsReach (bulk.js), дальше её везут стрелки. */
  @media (max-width:640px){
    .bulk-cols{
      flex-wrap:nowrap;overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain;
      scrollbar-width:thin;padding-bottom:4px;
    }
    .bulk-col{flex:0 0 auto}
    .bulk-cols:focus-visible{outline:2px solid var(--primary);outline-offset:2px;border-radius:12px}
  }

  /* ---- поля ---- */
  .bulk-grid2{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px 20px}
  /* Поля визитки — по три в ряд. У сетки свой display, и встроенное правило
     браузера для hidden он перебивает — отсюда строка ниже. */
  .bulk-grid3{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px 20px}
  .bulk-grid2[hidden],.bulk-grid3[hidden]{display:none}
  .bulk-card-note{grid-column:1/-1;margin-top:0}
  /* Плашка предела тарифа одной полосой: значок, строка и «Upgrade» справа
     (решение владельца 24.10.2026). По длине текста и не шире половины экрана
     (50vw, слово владельца; половина колонки на 1440 рвала «14 / of 5») — во всю
     ширину она читалась как баннер, а не как пометка у кнопки; на телефоне половина
     слишком узка, там — во всю строку (ниже, @media 900px). Перенос — ровными
     строками (balance), чтобы число не оставалось одно на второй строке. */
  .gen-limit{
    margin:10px 0 0;padding:10px 12px 10px 14px;border-radius:12px;
    width:fit-content;max-width:min(50vw,100%);display:flex;align-items:center;gap:12px;
    background:var(--warn-bg, #fff4e6);color:var(--warn-fg, #b45309);
    font-size:13px;line-height:1.45;
  }
  .gen-limit[hidden]{display:none}
  .gen-limit-ico{flex:none;width:20px;height:20px}
  /* Выход на тарифы рядом с текстом отказа (Р-216, проверка роботом, заход 7, 18.09.2026).
     У .btn свой display, и встроенное правило браузера для hidden он перебивает. */
  .gen-limit-text{flex:1;min-width:0;text-wrap:balance}
  .gen-limit-up{flex:none}
  .gen-limit-up[hidden]{display:none}
  @media (max-width:900px){
    .gen-limit{max-width:none}
  }

  .bulk-field{display:flex;flex-direction:column;gap:6px;min-width:0}
  /* Без этого атрибут hidden на поле не работает вовсе: display:flex выше
     перебивает встроенное правило браузера, и скрытое поле остаётся на экране. */
  .bulk-field[hidden]{display:none}
  .bulk-field label{font-size:12.5px;font-weight:700;color:var(--text)}
  .bulk-select,.bulk-input{
    width:100%;padding:10px 12px;border:1.5px solid var(--border);border-radius:var(--r-sm);
    background:#fff;font-family:inherit;font-size:13.5px;color:var(--text);transition:border-color .15s;
  }
  .bulk-select:focus,.bulk-input:focus{outline:none;border-color:var(--primary)}
  .bulk-range{display:flex;align-items:flex-end;gap:16px;flex-wrap:wrap}
  .bulk-range .bulk-field{width:150px}
  .bulk-count{
    padding:9px 14px;border-radius:999px;background:var(--gen-soft);border:1px solid var(--gen-border);
    font-size:12.5px;font-weight:700;color:var(--teal-dark);
  }
  .bulk-count.is-empty{background:#FEF2F2;border-color:#F3C9C9;color:var(--red)}

  /* ---- оформление ---- */
  .bulk-design{display:flex;gap:26px;align-items:flex-start;flex-wrap:wrap}
  /* Нижняя граница колонки — 280 точек, но не шире самой карточки: на 320-точечном
     экране под неё остаётся около 256, и жёсткие 280 выпихивали шаг «Оформление» за
     край страницы (замечено при заходе правок 5, 22.09.2026). */
  .bulk-design-pick{flex:1;min-width:min(280px,100%)}
  .bulk-design-preview{flex-shrink:0;width:210px}
  /* Подпись над превью центрируем по коду: холст стоит по центру колонки,
     и подпись, прижатая к левому краю, висела над пустотой рядом с ним. */
  .bulk-design-preview{text-align:center}
  /* С 22.09.2026 (решение владельца) над превью стоит не название ряда, а
     напоминание сделать тестовое сканирование — и пишется оно тем же
     начертанием, что «Standard design» слева: капсом в разрядку, как всякая
     подпись ряда на этом шаге (его слово того же дня — «надо писать в том же
     стиле, что и Standard design»). Своей перебивки у напоминания поэтому
     нет: шаг «Оформление» подписан одинаково слева и справа, и напоминание —
     такая же подпись ряда, а не текст помельче. */
  /* «Уменьшите количество информации…» — строка над самим кодом, когда у
     самого густого кода партии 90 квадратиков по стороне и больше (решение
     владельца 22.09.2026; показывает её bulkDenseWarn). Цвет — та же
     предупреждающая пара --warn-fg/--warn-bg, что у плашки предела тарифа
     (.gen-limit выше): на своём фоне она даёт 4,6:1, а оранжевый
     --orange-dark соседних подсказок — 3,7:1 на белом, и для строки, которая
     решает судьбу тиража, этого мало. Прижата влево: колонка превью центрует
     всё по коду, а предложение в несколько строк по центру читается рвано. */
  .bulk-dense-note{
    margin:0 0 10px;padding:8px 10px;border-radius:10px;text-align:left;
    background:var(--warn-bg, #fff4e6);color:var(--warn-fg, #b45309);
    font-size:12px;line-height:1.45;font-weight:600;
  }
  .bulk-dense-note[hidden]{display:none}
  .bulk-design-preview canvas{display:block;margin:0 auto}
  .bulk-preset-row{display:flex;gap:12px;flex-wrap:wrap;align-items:flex-end}
  .bulk-preset-row[hidden]{display:none}
  /* Плитка — сам код и ничего вокруг. Карточка с рамкой и подписью под ней
     соревновалась за внимание с тем, что в ней нарисовано, а нарисован там
     ровно тот вид, который уедет в архив: подпись к нему ничего не добавляла.
     Своей высоты у плитки нет — композиция с рамкой выше квадратной, и
     подгонять её под квадрат нельзя. */
  .bulk-preset{
    display:block;padding:3px;border:0;border-radius:11px;background:none;
    outline:2px solid transparent;outline-offset:-1px;transition:outline-color .15s,background .15s;
  }
  .bulk-preset:hover{background:var(--primary-50)}
  .bulk-preset.selected{outline-color:var(--primary);background:var(--primary-50)}
  .bulk-preset-cv{display:block;border-radius:8px}
  .bulk-design-open{margin-top:14px}
  .bulk-logo-row{display:flex;gap:9px;flex-wrap:wrap;align-items:center}
  .bulk-logo{
    display:flex;align-items:center;justify-content:center;width:52px;height:52px;padding:6px;
    border:1.5px solid var(--border);border-radius:12px;background:#fff;color:var(--muted2);transition:all .15s;
  }
  .bulk-logo svg{width:18px;height:18px}
  .bulk-logo img{max-width:100%;max-height:100%;object-fit:contain}
  .bulk-logo:hover{border-color:var(--primary-100)}
  .bulk-logo.selected{border-color:var(--primary);background:var(--primary-50);color:var(--primary)}
  /* «Не менять логотипы» и «Убрать все логотипы» в окне «Оформление выделенных» —
     словами, той же высоты, что плитки логотипов рядом (слово владельца 18.09.2026). */
  .bulk-logo.bulk-logo-text{width:auto;padding:0 16px;font-size:13px;font-weight:600;white-space:nowrap}
  .bulk-logo.bulk-logo-text:not(.selected){color:var(--muted)}

  /* ---- окно «Оформление выделенных» ---- */
  /* Окно растёт на ряд плиток пресетов: у нового аккаунта их пять, у Free на
     потолке — полсотни, и окно вырастало выше экрана целиком. Общее .modal
     центрируется и режет лишнее (overflow:hidden), поэтому заголовок с
     крестиком уходил за верхний край, а «Логотип», «Применить» и «Отмена» — за
     нижний, и достать их было нечем: ни мышью, ни пальцем (Р-105, проверка
     роботом, заход 4; на телефоне 320 точек — уже с пятью пресетами). Шапка и
     кнопки остаются на месте, прокручивается только середина — как у окна
     выбора периода (.period-modal) и окна обрезки. Сам ряд пресетов — не выше
     трёх строк плиток, как .preset-grid на «Создать QR»: иначе до ряда
     логотипов пришлось бы листать полсотни плиток. */
  #bulkDesignModal .modal{
    display:flex;flex-direction:column;
    max-height:calc(100vh - var(--modal-room));max-height:calc(100dvh - var(--modal-room));
  }
  #bulkDesignModal .modal-head,#bulkDesignModal .modal-foot{flex-shrink:0}
  #bulkDesignModal .modal-body{overflow-y:auto;min-height:0}
  #bdsPresetRow{max-height:312px;overflow-y:auto}

  /* ---- подтверждение ---- */
  /* Таблица просит свою ширину, но не уже карточки: длинная строка уезжает в
     ленту (ниже), короткая по-прежнему занимает карточку целиком. */
  .bulk-preview-table{width:max-content;min-width:100%;border-collapse:collapse;font-size:12.5px;margin-bottom:4px}
  .bulk-preview-table th{
    text-align:left;padding:8px 10px;border-bottom:1px solid var(--border);
    font-size:11.5px;font-weight:700;color:var(--muted2);text-transform:uppercase;letter-spacing:.02em;
  }
  .bulk-preview-table td{padding:9px 10px;border-bottom:1px solid var(--border2);color:var(--text);
                         white-space:nowrap}
  .bulk-preview-table .bulk-row-n{width:64px;color:var(--muted2);font-weight:700}
  .bulk-preview-table .bulk-row-v{color:var(--muted);font-family:ui-monospace,'SFMono-Regular',Menlo,monospace}
  /* Строка сверки едет вбок одной лентой — на любой ширине экрана (решение
     владельца 22.09.2026, Р-488; до этого дня лента работала только до 640 px,
     а шире строка обрывалась многоточием, и достать её хвост было нечем: у
     записи визитки на 1440 пряталась больше трети — вся почта и весь сайт).
     Резать «…» нельзя: сверка стоит ровно ради того, чтобы увидеть, что
     строки не съехали, а различаются строки как раз хвостом — номер точки в
     ссылке, метки utm, почта и сайт в визитке. Переносом (как было с
     19.09.2026, Р-284) запись визитки разворачивалась на пять-шесть строчек, и
     две строки тиража занимали пол-экрана. Лента прокручивается внутри
     карточки: вбок едет таблица, а не страница. Клавишами до хвоста строки
     дотягивается bulkStripReach — второго механизма не заводить. */
  .bulk-preview-wrap{
    overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain;
    /* Знак «строку можно потянуть» (пожелание владельца 22.09.2026): мягкая
       тень у правого края, пока лента не доехала до конца. Два слоя фона:
       верхний — заслонка цвета карточки, приклеенная к содержимому (local),
       нижний — сама тень, приклеенная к окну ленты (scroll). Лента доехала —
       заслонка встала ровно на тень и погасила её; тянуть нечего (короткая
       таблица) — тень не показывается вовсе. Знак сделан фоном, а не блоком
       поверх: фон не перехватывает нажатия, не просит своего шага табуляции
       и не добавляет экранному диктору лишнего узла. Прозрачный конец
       заслонки записан белым с нулевой прозрачностью, а не словом
       transparent: иначе браузер гасит её через прозрачный чёрный и у края
       появляется серая полоса. */
    background:
      linear-gradient(to left, var(--card) 20px, rgba(255,255,255,0) 40px) right center / 40px 100% no-repeat local,
      linear-gradient(to left, rgba(15,30,46,.16), rgba(15,30,46,0)) right center / 18px 100% no-repeat scroll;
  }
  /* Номер строки не уезжает вместе со строкой (пожелание владельца
     22.09.2026): номер — то, ради чего сверку в этот день и переделывали, а
     видеть номер и хвост строки одновременно иначе нельзя. Фон колонки
     непрозрачный — сквозь прозрачный номер ехало бы содержимое соседней
     ячейки. */
  .bulk-preview-table th:first-child,
  .bulk-preview-table .bulk-row-n{position:sticky;left:0;z-index:1;background:var(--card)}

  .bulk-errors{margin-bottom:14px;padding:12px 14px;border:1px solid #F3C9C9;border-radius:13px;background:#FEF2F2}
  .bulk-errors-title{font-size:12.5px;font-weight:800;color:var(--red);margin-bottom:6px}
  .bulk-errors ul{margin:0;padding-left:18px;font-size:12.5px;line-height:1.6;color:var(--text)}
  .bulk-problems{
    margin:14px 0;padding:12px 14px;border-radius:13px;background:var(--orange-50);
    border:1px solid var(--orange-100);font-size:12.5px;line-height:1.6;color:var(--orange-dark);font-weight:600;
  }
  /* Выход на тарифы под причинами (Р-302, заход 9): своя строка, цель не
     меньше 24 px — на телефоне по ней попадают пальцем. */
  .bulk-problems .bulk-exit{margin:6px 0 0}
  .bulk-problems .bulk-exit .link-btn{display:inline-flex;align-items:center;min-height:24px}
  /* Секция с включателем внутри карточки шага. Отбивка сверху и линия —
     чтобы необязательная настройка читалась как приложение к карточке, а не
     как ещё одно обязательное поле. */
  .bulk-sec{padding:16px 0 0;margin-top:16px;border-top:1px solid var(--border);border-bottom:none}
  .bulk-sec + .bulk-sec{margin-top:0}
  .bulk-sec:last-child{padding-bottom:0}
  .bulk-sec .sw-text b{display:flex;align-items:center;gap:6px}
  .bulk-sec .cfg-pane{margin-left:52px}
  /* Подпись о первой строке стоит под панелью диапазона и видна и при
     выключенном тумблере — с тем же отступом, что поля (Р-266, заход 9). */
  .bulk-sec .bulk-range-note{margin-left:52px}
  .bulk-sec .cfg-pane .bulk-input{max-width:340px}
  /* В окне настроек одного кода даты стоят друг под другом — там узко.
     В карточке партии места хватает на оба поля в строку, но не на всю её
     ширину: кнопка даты во весь экран выглядит как поле для текста. */
  .bulk-sec .cfg-pane .row-2{grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;max-width:520px}
  /* На телефоне два поля даты в строку не помещаются: выбранная дата резалась
     до «25 S…», и человек не видел, с какого дня заработает тираж
     (12.09.2026). Здесь та же одна колонка, что в окне настроек одного кода. */
  @media (max-width:640px){
    .bulk-sec .cfg-pane .row-2{grid-template-columns:1fr}
  }
  .bulk-head .info-dot{margin-left:-4px}

  /* Папка: поля отлипают от переключателя и не тянутся во всю карточку —
     название папки шириной в экран выглядело как поле для абзаца текста. */
  .bulk-folder-field{margin-top:14px;max-width:340px}
  .bulk-folder-field[hidden]{display:none}

  .bulk-go{margin-top:16px}

  /* ---- результат и прогресс ---- */
  .bulk-result{border-color:var(--gen-border);background:linear-gradient(180deg,var(--gen-soft),#fff 120px)}
  .bulk-result[hidden]{display:none}
  .bulk-recovery{border-color:var(--orange-100);background:linear-gradient(180deg,var(--orange-50),#fff 120px)}
  .bulk-recovery[data-state="recovering"]{border-color:var(--primary-100);background:linear-gradient(180deg,var(--primary-50),#fff 120px)}
  .bulk-recovery[hidden]{display:none}
  .bulk-recovery[data-state="unknown"] .bulk-num{background:var(--orange-50);color:var(--orange-dark)}
  .bulk-recovery-reason{margin-top:10px;font-size:12px;line-height:1.5;color:var(--muted2);overflow-wrap:anywhere}
  .bulk-recovery-reason[hidden]{display:none}
  .bulk-result-actions{display:flex;gap:10px;flex-wrap:wrap;margin-top:16px}
  /* Полоса прогресса нужна и на экране, и в модалке скачивания: собирается
     архив в браузере, и без неё долгая пачка выглядит как зависание. */
  .bulk-progress{display:flex;align-items:center;gap:12px;margin-top:16px}
  .bulk-progress[hidden]{display:none}
  .bulk-progress-bar{flex:1;height:8px;border-radius:999px;background:var(--border2);overflow:hidden}
  .bulk-progress-fill{height:100%;width:0;border-radius:999px;background:var(--primary);transition:width .2s}
  .bulk-progress-text{font-size:12px;font-weight:700;color:var(--muted);white-space:nowrap}

  /* ---- окно скачивания: формат и размер ----
     Подпись слева, управление справа: строк всего две, и в столбик они дали бы
     окну лишнюю высоту на ровном месте. На узком экране колонки схлопываются. */
  .dl-grid{display:grid;grid-template-columns:auto 1fr;align-items:center;gap:20px 16px}
  .dl-label{font-size:13px;font-weight:700;color:var(--muted)}
  .dl-fmts{display:flex;gap:8px;flex-wrap:wrap}
  .dl-fmt{
    min-width:72px;padding:9px 16px;border-radius:var(--r-sm);border:1.5px solid var(--border);
    background:#fff;color:var(--text);font-size:13.5px;font-weight:800;letter-spacing:-.005em;
    transition:all .15s;
  }
  .dl-fmt:hover{border-color:var(--primary);color:var(--primary)}
  .dl-fmt.on,.dl-fmt.on:hover{background:var(--primary);border-color:var(--primary);color:#fff}
  /* Посреди сборки выбор закрыт (dlLockChoice в bulk.js, Р-291): наведение не зовёт нажать. */
  .dl-fmt:disabled{pointer-events:none}
  .dl-range:disabled,.dl-px:disabled{cursor:default}

  /* Дорожка с заливкой до бегунка. Заливку задаёт renderDlModal переменной
     --dl-fill: одинаково её не рисует ни один браузер — в Firefox есть
     ::-moz-range-progress, в Chrome нет ничего. */
  .dl-size{padding-top:2px;display:flex;gap:10px;align-items:flex-start}
  .dl-size-track{flex:1 1 auto;min-width:0}
  /* Поле точного размера не тянется: длиннее четырёх цифр в него не влезет, а
     растянутое на полокна оно спорило бы со шкалой за главную роль. Ширина
     одна и на телефоне — там оно тем более не должно занимать целую строку. */
  .dl-px-wrap{flex:0 0 auto;width:84px}
  .dl-px{
    width:100%;height:34px;margin-top:-4px;   /* середина поля на середине дорожки */
    padding:0 8px;border:1.5px solid var(--border);border-radius:var(--r-sm);
    background:#fff;color:var(--text);
    font:inherit;font-size:14px;font-weight:800;text-align:center;
    transition:border-color .15s;
  }
  .dl-px:focus{outline:none;border-color:var(--primary)}
  .dl-px::placeholder{font-weight:600;color:var(--muted2)}
  /* Подсказка с границами. Отступы и скругление держим всегда, чтобы жёлтая
     подложка на время мигания ничего не сдвигала. */
  .dl-px-wrap{text-align:center}
  .dl-px-hint{
    display:inline-block;font-size:11px;color:var(--muted);
    margin-top:2px;padding:1px 5px;border-radius:6px;
  }
  /* Пять миганий на «Скачать», когда в поле число вне 500–2000. Цвет тот же,
     каким этот экран предупреждает про незаполненную папку (.gen-limit), —
     жёлтое предупреждение на сайте одно. Переход внутри цикла мгновенный
     (49.9% → 50%): плавное затухание читалось бы как подсветка, а не как
     «посмотрите сюда». */
  .dl-px-hint.warn{animation:dlPxWarn .36s 5}
  @keyframes dlPxWarn{
    0%,49.9%{background:#fff4e6;color:#b45309;font-weight:800}
    50%,100%{background:transparent;color:var(--muted);font-weight:400}
  }
  .dl-range{-webkit-appearance:none;appearance:none;display:block;width:100%;height:26px;background:transparent;cursor:pointer}
  .dl-range::-webkit-slider-runnable-track{
    height:7px;border-radius:999px;
    background:linear-gradient(90deg,var(--primary) 0 var(--dl-fill,0%),var(--primary-100) var(--dl-fill,0%) 100%);
  }
  .dl-range::-webkit-slider-thumb{
    -webkit-appearance:none;width:20px;height:20px;margin-top:-6.5px;border-radius:50%;
    background:var(--primary);border:3px solid #fff;box-shadow:0 2px 6px rgba(20,50,90,.28);
  }
  .dl-range::-moz-range-track{height:7px;border-radius:999px;background:var(--primary-100)}
  .dl-range::-moz-range-progress{height:7px;border-radius:999px;background:var(--primary)}
  .dl-range::-moz-range-thumb{width:20px;height:20px;border-radius:50%;background:var(--primary);border:3px solid #fff}
  /* Ползунок, отменённый полем. Рабочим он остаётся — касание вернёт размер
     шкале, — но перестаёт быть цветным: два «выбранных» числа на экране разом
     заставляли бы гадать, какое из них уедет в файл. */
  .dl-range.off::-webkit-slider-runnable-track{background:var(--border)}
  .dl-range.off::-webkit-slider-thumb{background:var(--muted2);box-shadow:none}
  .dl-range.off::-moz-range-track{background:var(--border)}
  .dl-range.off::-moz-range-progress{background:var(--border)}
  .dl-range.off::-moz-range-thumb{background:var(--muted2)}
  /* Шкала целится в положения бегунка, а не в края блока: у крайних подписей
     половина ширины свисала бы за дорожку и строка расползалась бы по окну. */
  .dl-scale{display:flex;justify-content:space-between;font-size:11px;color:var(--muted);margin-top:-1px}
  .dl-scale span{flex:0 0 auto;transition:color .15s}
  .dl-scale span.on{color:var(--primary);font-weight:800}
  /* «Скачать» — главное действие окна: зелёная, крупнее соседа и по центру
     подвала. Три колонки, а не флекс с автополями: так центр держится за
     середину окна, а не за остаток места после «Отмена». */
  /* Подписей под ползунком больше нет, а место, которое они держали, нужно:
     без него шкала «500px 1000 2000 4000» упирается прямо в кнопку. */
  #dlModal .modal-body{padding-bottom:14px}
  #dlModal .modal-foot{display:grid;grid-template-columns:1fr auto 1fr;align-items:center}
  #dlModal .modal-foot .btn-ghost{justify-self:start}
  #dlModal .modal-foot .btn-generate{min-height:40px;font-size:15px;padding:9px 46px}

  @media (max-width:520px){
    .dl-grid{grid-template-columns:1fr;gap:6px 0}
    .dl-label{margin-top:12px}
    .dl-fmt{flex:1 1 0;min-width:0;min-height:44px;padding:9px 8px}
    /* В строку крупная кнопка и «Отмена» на телефоне не встают ровно:
       боковые колонки выходят разной ширины и центр съезжает. Ставим друг под друга. */
    #dlModal .modal-foot{grid-template-columns:1fr;gap:8px;justify-items:center}
    #dlModal .modal-foot .btn-ghost{order:2;justify-self:center}
    /* На телефоне кнопка остаётся 44px: ниже этого палец начинает промахиваться,
       и весь сайт держит тот же нижний предел (см. .btn в блоке телефона). */
    #dlModal .modal-foot .btn-generate{min-height:44px}
  }

  @media (max-width:820px){
    .bulk-step,.bulk-result,.bulk-recovery{padding:18px 16px}
    .bulk-grid2,.bulk-grid3{grid-template-columns:1fr}
    /* Пример таблицы встаёт под кнопку; семь колонок визитки на телефоне едут
       вбок внутри своей рамки — страница при этом не прокручивается. */
    .bulk-step1{grid-template-columns:minmax(0,1fr)}
    .bulk-sample{margin-top:14px}
    .bulk-sample-table[data-cols="7"]{min-width:620px}
    .bulk-range .bulk-field{width:calc(50% - 8px)}
    .bulk-design-preview{width:100%;display:flex;flex-direction:column;align-items:center}
    .bulk-col-sample{max-width:180px}
  }

  /* ============ РАЗМЕСТИТЬ QR ============ */
  /* Экран из двух частей: слева столешница с документом, справа узкая
     панель. Столешница тёмная намеренно — белый лист на белом фоне теряет
     края, а именно по краям человек и целится кодом. */

  /* Пустой экран — тонкая пунктирная рамка во всю ширину колонки, без фона.
     Раньше здесь стояла карточка в 640 точек по центру, и всё, что под ней
     (полоса «ещё нет кода», текст витрины под поиск), приходилось ужимать до
     той же ширины — иначе торчало из-под неё с обеих сторон. Теперь наоборот:
     ширину держит рамка, а содержимое стоит по центру внутри неё. Вся страница
     легла на одну сетку, и узких исключений на этом экране больше нет.

     Пунктир — он же и подсказка: файл можно бросить на всю эту площадь, а не
     целиться в маленький прямоугольник. */
  .place-start{
    position:relative;padding:34px 24px;
    /* Пунктир заметно темнее обычной рамки карточек: у тех есть белый фон и
       тень, они видны и без контраста, а эта рамка стоит на светлом фоне
       страницы одна. В цвете var(--border) её попросту не видно. */
    border:1.5px dashed #B4CEE6;border-radius:var(--r);
    transition:border-color .15s,background .15s;
  }
  .place-start.drag{border-color:var(--primary);background:var(--primary-50)}
  /* Содержимое рамки — прежние 640 по центру: читаемая ширина не изменилась,
     изменилась только рамка вокруг. */
  .place-start>.place-drop{max-width:640px;margin:0 auto}
  /* Стрелка стоит слева от содержимого, в поле, которое иначе просто пустует.
     Разметка — в index.html. Абсолютная, чтобы содержимое осталось ровно по
     середине: встань стрелка в поток, она сдвинула бы его вправо. */
  .place-arrow{
    position:absolute;right:calc(50% + 332px);top:26px;width:132px;height:auto;
    color:#A9C3E0;pointer-events:none;
  }
  /* Как только слева перестало хватать места, стрелка уходит: на узком окне
     она полезла бы на текст, а на телефоне ей и целиться некуда. */
  @media (max-width:1279px){ .place-arrow{display:none} }
  /* Своей рамки у зоны броска больше нет — её роль взяла .place-start выше.
     Две пунктирные рамки одна в другой читались бы как брак вёрстки. */
  .place-drop{
    display:flex;flex-direction:column;align-items:center;gap:8px;padding:14px 0;
    text-align:center;
  }
  .place-drop-title{font-size:15px;font-weight:700;color:var(--text)}
  .place-drop-sub{font-size:12.5px;color:var(--muted)}
  .place-pick{margin-top:8px;cursor:pointer}
  .place-drop-note{margin-top:10px;font-size:12px;color:var(--muted2)}

  /* ---- ролик на входе ----
     Вместо иконки — короткий круг на месте: на столешницу приезжает лист, мышь
     кладёт на него код, код прилипает (направляющие оранжевые, рамка синяя —
     ровно как в работе), готовый файл уезжает вправо, слева заходит следующий.
     Разметка сцены — в index.html, рядом с .place-drop.

     Всё держится на одном таймере: у каждой части свой @keyframes, но круг у
     всех один и тот же — --pl-dur. Одно число меняет темп целиком, и части не
     разъезжаются. Ключевые проценты круга:
        8–44 %   код летит к листу,
        44 %     касание: летящий код гаснет, лежащий зажигается — они в одной
                 точке, поэтому подмены не видно,
        44–64 %  направляющие, рамка, зелёная галочка,
        72–86 %  лист уезжает вправо,
        86 %     невидимый прыжок за левый край и сброс всего, что появилось,
        86–100 % заходит чистый лист.
     Двигаются только transform и opacity — браузер такое считает на видеокарте
     и не перерисовывает страницу. */
  .place-anim{
    --pl-dur:2.4s;
    display:block;width:320px;height:auto;max-width:100%;margin-bottom:6px;
    border-radius:12px;box-shadow:0 8px 22px rgba(20,50,90,.16);
  }
  .place-anim .pl-sheet{filter:drop-shadow(0 3px 6px rgba(0,0,0,.42))}
  .place-anim .pl-fly{filter:drop-shadow(0 4px 5px rgba(6,16,28,.45))}

  .pl-sheet{animation:pl-sheet var(--pl-dur) linear infinite}
  @keyframes pl-sheet{
    0%,72%{transform:translateX(0);animation-timing-function:cubic-bezier(.5,0,.85,.4)}
    86%{transform:translateX(180px)}
    /* сотая доля процента — тот самый прыжок за левый край: лист в этот момент
       за пределами столешницы, и глазу переезд не достаётся */
    86.01%{transform:translateX(-180px);animation-timing-function:cubic-bezier(.16,.72,.28,1)}
    100%{transform:translateX(0)}
  }

  /* рука с кодом: дуга сверху справа, будто код взяли с панели */
  .pl-hand{animation:pl-hand var(--pl-dur) linear infinite}
  @keyframes pl-hand{
    0%,8%{transform:translate(128px,-52px);animation-timing-function:cubic-bezier(.34,.5,.5,1)}
    30%{transform:translate(44px,-18px);animation-timing-function:cubic-bezier(.4,0,.25,1)}
    44%,100%{transform:translate(0,0)}
  }
  .pl-fly{transform-box:fill-box;transform-origin:50% 50%;animation:pl-fly var(--pl-dur) linear infinite}
  @keyframes pl-fly{
    0%,8%{opacity:0;transform:rotate(-7deg) scale(1.12)}
    13%{opacity:1}
    32%{transform:rotate(-4deg) scale(1.09)}
    43.9%{opacity:1;transform:rotate(0deg) scale(1)}
    44%,100%{opacity:0;transform:rotate(0deg) scale(1)}
  }
  /* точка отсчёта — остриё стрелки, поэтому нажатие «поджимает» курсор к нему */
  .pl-cursor{transform-box:fill-box;transform-origin:0 0;animation:pl-cursor var(--pl-dur) linear infinite}
  @keyframes pl-cursor{
    0%,8%{opacity:0;transform:scale(1)}
    13%,41%{opacity:1;transform:scale(1)}
    45%{transform:scale(.82)}
    49%,54%{opacity:1;transform:scale(1) translate(0,0)}
    58%{opacity:1}
    68%,100%{opacity:0;transform:scale(1) translate(16px,-18px)}
  }

  /* место, куда код ляжет: подсказка появляется, пока код ещё в полёте */
  .pl-target{animation:pl-target var(--pl-dur) linear infinite}
  @keyframes pl-target{0%,10%{opacity:0}18%,43.9%{opacity:1}44%,100%{opacity:0}}

  .pl-qr-set{transform-box:fill-box;transform-origin:50% 50%;animation:pl-set var(--pl-dur) linear infinite}
  @keyframes pl-set{
    0%,43.9%{opacity:0;transform:scale(1)}
    44%{opacity:1;transform:scale(1)}
    47%{transform:scale(1.08)}
    52%{transform:scale(1)}
    86%{opacity:1}
    86.01%,100%{opacity:0}
  }

  .pl-guides{animation:pl-guides var(--pl-dur) linear infinite}
  @keyframes pl-guides{0%,43.9%{opacity:0}44%,56%{opacity:1}64%,100%{opacity:0}}

  .pl-sel{animation:pl-sel var(--pl-dur) linear infinite}
  @keyframes pl-sel{0%,43.9%{opacity:0}45%,62%{opacity:1}70%,100%{opacity:0}}

  .pl-ok{transform-box:fill-box;transform-origin:50% 50%;animation:pl-ok var(--pl-dur) linear infinite}
  @keyframes pl-ok{
    0%,52%{opacity:0;transform:scale(.4)}
    57%{opacity:1;transform:scale(1.2)}
    62%{transform:scale(1)}
    86%{opacity:1;transform:scale(1)}
    86.01%,100%{opacity:0;transform:scale(.4)}
  }

  /* Кому система запретила двигать картинки (у части людей от такого кружится
     голова), тому показываем последний кадр: лист с уже поставленным кодом. */
  @media (prefers-reduced-motion:reduce){
    .place-anim *{animation:none!important}
    .place-anim .pl-hand,.place-anim .pl-target,.place-anim .pl-guides,.place-anim .pl-sel{display:none}
    .place-add-arrow{animation:none}
  }

  .place-work{
    display:grid;grid-template-columns:minmax(0,1fr) 296px;gap:14px;
    height:calc(100vh - var(--topbar-h) - 196px);min-height:460px;
  }
  .place-work[hidden]{display:none}

  /* ---- столешница ---- */
  .place-stage{
    position:relative;border-radius:var(--r);overflow:hidden;touch-action:none;cursor:grab;
    background:radial-gradient(120% 120% at 50% 0%, #33465C 0%, #1D2C3D 60%, #16222F 100%);
    box-shadow:inset 0 1px 3px rgba(0,0,0,.25);
  }
  .place-stage:active{cursor:grabbing}
  .place-stage canvas{display:block;width:100%;height:100%}
  .place-hint{
    position:absolute;left:50%;bottom:18px;transform:translateX(-50%);
    padding:8px 14px;border-radius:999px;background:rgba(15,30,46,.72);
    color:#fff;font-size:12.5px;font-weight:600;white-space:nowrap;pointer-events:none;
  }
  .place-hint[hidden]{display:none}
  /* На узкой столешнице (телефон стоя) подсказке внизу места нет: она
     ложилась поверх кнопок масштаба и страниц (Р-354, проверка роботом,
     заход 10, 19.09.2026). Там она — у верхнего края. */
  @media (max-width:560px){ .place-hint{top:14px;bottom:auto} }

  /* ---- пустая столешница ----
     Документа ещё нет, а экран уже рабочий: так он встречает того, кто пришёл
     сюда с готовым кодом с «Создать QR». Карточка стоит по центру и углов не
     занимает — в углу висит сам код (.place-wait). */
  .place-stage-drop{
    position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
    width:min(78%,360px);display:flex;flex-direction:column;align-items:center;gap:7px;
    padding:26px 20px;border:2px dashed rgba(255,255,255,.28);border-radius:11px;text-align:center;
    transition:border-color .15s,background .15s;
  }
  .place-stage-drop[hidden]{display:none}
  .place-stage-drop.drag{border-color:#fff;background:rgba(255,255,255,.08)}
  .place-stage-drop svg{width:54px;height:45px;margin-bottom:2px;color:#fff;opacity:.6}
  .place-stage-drop-title{font-size:15px;font-weight:700;color:#fff}
  .place-stage-drop-sub{font-size:12.5px;color:rgba(255,255,255,.62)}
  .place-stage-drop .place-pick{margin-top:9px}

  /* ---- код, который ждёт документа ----
     Верхний левый угол: внизу столешницы живут зум и перелистывание, и хотя
     без документа их не видно, место под ними менять не стоит — код там
     оказался бы на пути у кнопок, едва документ появится. */
  .place-wait{
    position:absolute;left:14px;top:14px;display:flex;flex-direction:column;align-items:center;gap:7px;
    padding:10px;border-radius:12px;background:rgba(255,255,255,.94);box-shadow:0 4px 14px rgba(0,0,0,.22);
  }
  .place-wait[hidden]{display:none}
  .place-wait-row{display:flex;gap:6px}
  .place-wait-thumb{width:54px;height:54px;border-radius:6px;background:#fff;object-fit:contain}
  /* Пустой квадрат — то мгновение, пока картинка кода ещё читается. */
  .place-wait-thumb.is-blank{background:var(--primary-50)}
  .place-wait-text{max-width:132px;font-size:11.5px;font-weight:700;line-height:1.3;color:var(--muted);text-align:center}
  /* Пока код ждёт, рамка документа встаёт по центру свободного места под его
     карточкой, а не всей столешницы: на низкой столешнице (телефон 320 точек,
     ноутбук 1280×720) карточка наезжала на рамку и закрывала её значок
     (Р-323, проверка роботом, заход 10, 19.09.2026). 68 точек — половина
     высоты угла с карточкой (14 + 111) и зазор. */
  .place-stage:has(.place-wait:not([hidden])) .place-stage-drop{top:calc(50% + 68px)}

  /* Кнопки поверх холста: масштаб слева внизу, страницы справа внизу — обе
     группы у нижнего края, чтобы не перекрывать сам документ. */
  .place-tools,.place-pager{
    position:absolute;bottom:14px;display:flex;align-items:center;gap:2px;padding:4px;
    border-radius:12px;background:rgba(255,255,255,.94);box-shadow:0 4px 14px rgba(0,0,0,.22);
  }
  .place-tools{left:14px}
  .place-pager{right:14px;gap:6px;padding:4px 10px;font-size:12.5px;font-weight:700;color:var(--text)}
  .place-tools[hidden],.place-pager[hidden]{display:none}
  .place-tool{
    display:flex;align-items:center;justify-content:center;min-width:28px;height:28px;padding:0 7px;
    border:none;border-radius:8px;background:none;color:var(--text);font-size:12.5px;font-weight:700;
    transition:background .12s;
  }
  .place-tool:hover:not(:disabled){background:var(--primary-50);color:var(--primary-dark)}
  .place-tool:disabled{color:var(--muted2);cursor:default}
  .place-tool svg{width:15px;height:15px}
  .place-tool-val{min-width:52px}

  .place-busy{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(22,34,47,.55)}
  .place-busy[hidden]{display:none}
  .place-spinner{
    width:34px;height:34px;border-radius:50%;
    border:3px solid rgba(255,255,255,.25);border-top-color:#fff;animation:place-spin .8s linear infinite;
  }
  @keyframes place-spin{to{transform:rotate(360deg)}}
  /* Вышли из аккаунта, пока собирался файл прежнего человека: до конца сборки
     раздел не показывает ничего, кроме заголовка, и ничего не даёт нажать —
     ни его документа, ни его кодов, ни полосы (placeForget в place.js;
     Р-348, проверка роботом, заход 10, 19.09.2026). */
  #screen-place.is-forgetting>:not(.screen-header){visibility:hidden}

  /* ---- панель ---- */
  .place-panel{
    display:flex;flex-direction:column;gap:14px;min-width:0;overflow-y:auto;
    padding:16px;background:var(--card);border:1px solid var(--border);border-radius:var(--r);box-shadow:var(--shadow);
  }
  /* display:flex сильнее браузерного [hidden]{display:none} — без своей строки
     блок «Файл для фона» стоял бы в панели и тогда, когда документа ещё нет. */
  .place-doc{display:flex;align-items:center;gap:10px}
  .place-doc[hidden]{display:none}
  .place-doc-text{min-width:0;flex:1}
  .place-doc-label{font-size:11px;font-weight:700;letter-spacing:.02em;color:var(--muted2);text-transform:uppercase;margin-bottom:2px}
  .place-doc-name{font-size:13px;font-weight:700;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  /* Имя режется посередине: начало — многоточием, хвост с версией и типом
     файла виден всегда (placeShowDocName; Р-358, проверка роботом, заход 10,
     19.09.2026). pre — чтобы пробел на стыке двух половин не пропадал. */
  .place-doc-name{display:flex}
  .place-doc-name-head{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:pre}
  .place-doc-name-tail{flex:none;white-space:pre}
  .place-doc-meta{font-size:11.5px;color:var(--muted)}
  .place-doc-swap{
    flex:none;padding:5px 10px;border:1px solid var(--border);border-radius:9px;background:#fff;
    font-size:12px;font-weight:700;color:var(--primary);cursor:pointer;transition:background .12s;
  }
  .place-doc-swap:hover{background:var(--primary-50)}

  .place-block{display:flex;flex-direction:column;gap:10px;padding-top:14px;border-top:1px solid var(--border2)}
  .place-block[hidden]{display:none}
  .place-block-head{display:flex;align-items:center;gap:6px;font-size:12px;font-weight:800;
                    letter-spacing:.02em;text-transform:uppercase;color:var(--muted)}
  /* Облачко подсказки привязано к строке заголовка, а не к самому значку —
     тем же приёмом, что и на карточках «Аналитики». Облачко шире значка в
     десяток раз, и, посчитанное от него, оно вылезало за панель и добавляло
     всему экрану горизонтальную прокрутку на телефоне. От строки оно
     получает её ширину и переносит текст внутри. */
  .place-block-head{position:relative}
  /* Своего облачка на ::after у значка давно нет — его показывает общая
     подсказка .tipx (см. .info-dot), — поэтому отдавать строке заголовка ещё и
     точку отсчёта значка больше незачем: от неё считается увеличенная зона
     нажатия, и без своей точки отсчёта она встала бы посреди заголовка
     (22.09.2026). Ширину облачка правила ниже больше ни на что не влияют. */
  .place-block-head .info-dot{position:relative}
  .place-block-head .info-dot::after{
    left:0;right:0;width:auto;max-width:none;
    white-space:normal;text-align:left;text-transform:none;letter-spacing:0;
  }

  .place-items{display:flex;flex-direction:column;gap:6px}
  .place-empty{padding:10px;border-radius:10px;background:var(--border2);font-size:12px;color:var(--muted);text-align:center}
  .place-item{
    display:flex;align-items:center;gap:9px;padding:6px;border:1.5px solid transparent;border-radius:11px;
    background:#F7FAFF;cursor:pointer;transition:border-color .12s,background .12s;
  }
  .place-item:hover{background:var(--primary-50)}
  .place-item.is-sel{border-color:var(--primary);background:var(--primary-50)}
  /* Код с другой страницы виден в списке, но приглушён: он существует и
     уедет в файл, просто сейчас его не видно на холсте. */
  .place-item.is-other{opacity:.6}
  /* Миниатюра вдвое крупнее прежней (01.09.2026): по картинке в 30 px нельзя
     было разобрать, тот ли это код, — а в списке их бывает несколько, и
     отличаются они как раз рисунком. Растёт в обе стороны: код квадратный, и
     от одной высоты осталась бы рамка с пустыми полями. */
  .place-item-thumb{width:60px;height:60px;flex:none;border-radius:6px;background:#fff;object-fit:contain}
  .place-item-body{min-width:0;flex:1}
  .place-item-name{font-size:12.5px;font-weight:700;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .place-item-sub{font-size:11px;color:var(--muted)}
  .place-item-del{
    flex:none;display:flex;align-items:center;justify-content:center;width:22px;height:22px;
    border:none;border-radius:6px;background:none;color:var(--muted2);transition:color .12s,background .12s;
  }
  .place-item-del:hover{background:#fff;color:var(--red)}
  .place-item-del svg{width:13px;height:13px}
  /* Под палец — крупнее: 22 точки мельче нормы в 24, а промах по крестику
     попадает в строку и перелистывает к коду (Р-322, проверка роботом,
     заход 10, 19.09.2026). Место есть: строка высотой в миниатюру, 60 точек. */
  @media (hover:none), (pointer:coarse){
    .place-item-del{width:36px;height:36px;border-radius:8px}
    .place-item-del svg{width:15px;height:15px}
  }
  .place-add{width:100%;justify-content:center;cursor:pointer}
  /* Стрелка под кнопкой. Качается на пять пикселей — этого хватает, чтобы
     её заметили боковым зрением, и мало, чтобы она мешала работать. */
  .place-add-arrow{display:block;width:26px;height:auto;margin:-4px auto 0;
    color:var(--primary);opacity:.7;animation:place-arrow-bob 1.6s ease-in-out infinite}
  .place-add-arrow[hidden]{display:none}
  @keyframes place-arrow-bob{0%,100%{transform:translateY(2px)}50%{transform:translateY(-3px)}}

  /* Код из «Моих QR» поиском (placePickSync в place.js). Список выпадает
     окошком поверх панели и открывается только по нажатию на поле: стоя в
     потоке, он вытягивал панель вниз, и «Скачать» уходило за экран (слово
     владельца 24.10.2026). Окошко — fixed, а не absolute: панель прокручивается
     сама и обрезала бы его своей прокруткой. Место и высоту ставит
     placePickPlace, ниже шапки (50) и шторки телефона. */
  .place-mine{display:flex;flex-direction:column;gap:6px}
  .place-mine[hidden]{display:none}
  .place-mine-label{font-size:11.5px;font-weight:700;color:var(--muted)}
  .place-mine-field{position:relative}
  .place-mine-field svg{position:absolute;left:10px;top:50%;width:15px;height:15px;margin-top:-7.5px;color:var(--muted2);pointer-events:none}
  .place-mine-field .place-input{padding-left:32px;font-weight:600}
  .place-mine-field .place-input::placeholder{color:var(--muted2);font-weight:500}
  /* У поля type=search свой крестик очистки — оставляем, но без синей обводки WebKit. */
  .place-mine-field .place-input::-webkit-search-decoration{-webkit-appearance:none}
  .place-mine-list{
    position:fixed;z-index:40;display:flex;flex-direction:column;gap:2px;padding:4px;
    border:1px solid var(--border);border-radius:11px;background:#fff;box-shadow:0 12px 32px rgba(15,23,42,.16);
    overflow-y:auto;overscroll-behavior:contain;
  }
  .place-mine-list[hidden]{display:none}
  .place-mine-head{padding:4px 6px 2px;font-size:11px;font-weight:700;color:var(--muted2)}
  .place-mine-row{
    display:flex;align-items:center;gap:9px;width:100%;padding:5px 6px;border:none;border-radius:8px;
    background:none;text-align:left;cursor:pointer;transition:background .12s;
  }
  .place-mine-row:hover,.place-mine-row.is-active{background:var(--primary-50)}
  .place-mine-row.is-busy{opacity:.55;cursor:progress}
  .place-mine-thumb{display:flex;align-items:center;justify-content:center;width:40px;height:40px;flex:none;border-radius:6px;background:#fff;border:1px solid var(--border2);overflow:hidden}
  .place-mine-thumb canvas{display:block;height:auto}
  .place-mine-body{display:flex;flex-direction:column;min-width:0;flex:1}
  .place-mine-name{font-size:12.5px;font-weight:700;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .place-mine-sub{font-size:11px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .place-mine-tag{flex:none;max-width:84px;padding:2px 6px;border-radius:6px;font-size:10.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .place-mine-tag.is-on{background:#E7F7EE;color:#137A43}
  .place-mine-tag.is-off{background:#FFF4DB;color:#8A5A00}
  .place-mine-none,.place-mine-more{padding:6px;font-size:11.5px;color:var(--muted)}
  /* Под палец строка выше: 40 точек миниатюры — уже норма, но промах между
     строками на телефоне частый. */
  @media (hover:none), (pointer:coarse){ .place-mine-row{min-height:48px} }

  .place-field{display:flex;flex-direction:column;gap:4px}
  .place-field label{font-size:11.5px;font-weight:700;color:var(--muted)}
  .place-input{
    width:100%;padding:8px 10px;border:1.5px solid var(--border);border-radius:10px;background:#fff;
    font-size:13px;font-weight:700;color:var(--text);outline:none;transition:border-color .12s;
  }
  .place-input:focus{border-color:var(--primary)}
  .place-range{width:100%;accent-color:var(--primary)}
  /* Под палец ползунок выше: в 16 точек (мельче нормы в 24) палец проскакивал
     мимо дорожки (Р-338, проверка роботом, заход 10, 19.09.2026). */
  @media (hover:none), (pointer:coarse){ .place-range{height:32px} }
  .place-xy{display:grid;grid-template-columns:1fr 1fr;gap:8px}

  /* Девять якорей одной сеткой: точка внутри кнопки стоит там же, куда
     уедет код, — подписи такому не нужны. */
  .place-anchors{display:grid;grid-template-columns:repeat(3,1fr);gap:4px;margin-top:2px}
  .place-anchors button{
    position:relative;height:26px;border:1px solid var(--border);border-radius:7px;background:#fff;
    transition:background .12s,border-color .12s;
  }
  .place-anchors button:hover{border-color:var(--primary);background:var(--primary-50)}
  .place-anchors button::after{
    content:"";position:absolute;top:50%;left:50%;width:6px;height:6px;margin:-3px 0 0 -3px;
    border-radius:2px;background:var(--muted2);
  }
  .place-anchors button:hover::after{background:var(--primary)}

  .place-save{margin-top:auto;width:100%;justify-content:center}

  /* ---- «ещё нет кода» ----
     Полоса под всем разделом, и её видит только гость: у вошедшего коды есть.
     Стоит внизу нарочно — сначала человек должен увидеть сам инструмент, за
     которым пришёл, а предложение завести код читается уже после. */
  .place-cta{
    display:flex;flex-direction:column;align-items:center;gap:10px;
    margin-top:13px;padding:18px 20px;border-radius:var(--r);text-align:center;
    background:var(--primary-50);border:1px solid var(--primary-100);
  }
  .place-cta[hidden]{display:none}
  .place-cta-text{font-size:13.5px;font-weight:600;color:var(--text)}
  /* Ширину полоса не подгоняет ни под что: и пустой экран, и рабочая область
     теперь во всю ширину колонки (см. .place-start выше). Раньше здесь стояло
     исключение на 640 — оно исчезло вместе с узкой карточкой. */

  @media (max-width:1100px){
    .place-work{grid-template-columns:minmax(0,1fr) 260px}
  }
  @media (max-width:820px){
    /* На телефоне панель уходит под холст: холсту нужна вся ширина, иначе
       прицелиться кодом нечем. */
    .place-work{display:flex;flex-direction:column;height:auto;min-height:0}
    .place-stage{height:58vh;min-height:320px}
    .place-panel{overflow:visible}
    .place-save{margin-top:6px;min-height:46px}
    /* Поля и скругление рамки — как у соседних полос на телефоне. У самой зоны
       броска своих полей почти нет: их держит рамка вокруг. */
    .place-start{padding:20px 14px;border-radius:11px}
    .place-drop{padding:8px 0}
    /* Панель на телефоне и так стоит сразу под холстом — показывать пальцем
       на кнопку, которая занимает всю ширину, незачем. */
    .place-add-arrow{display:none}
  }
  /* Низкое окно — телефон лёжа: столешница не выше видимой части под шапкой.
     Иначе её 460 точек при 326 видимых не помещались целиком никогда, а кнопки
     масштаба и страниц внизу уходили за экран (Р-334, проверка роботом, заход
     10, 19.09.2026). svh — окно с показанной строкой адреса: так столешница
     помещается и тогда, когда строка адреса не спряталась. */
  @media (min-width:821px) and (max-height:540px){
    .place-work{height:calc(100vh - var(--topbar-h) - 16px);height:calc(100svh - var(--topbar-h) - 16px);min-height:240px}
  }
  @media (max-width:820px) and (max-height:540px){
    .place-stage{height:calc(100vh - var(--topbar-h) - 16px);height:calc(100svh - var(--topbar-h) - 16px);min-height:240px}
  }

  /* ================= ПОДВАЛ =================
     Три ссылки в строку — Terms, Privacy и почта поддержки. Стоит в <main>
     после экранов, поэтому виден на любом из них и уезжает вместе с
     прокруткой: прибивать его к низу окна не стоит — на телефоне он съедал бы
     полосу у экрана, где и так тесно.

     Отделён от содержимого только тонкой линией: подвал здесь служебный, и
     плашка на всю ширину перетягивала бы на себя внимание с самого сервиса. */
  /* `margin-top:auto` — то самое, что держит подвал у нижнего края, пока
     содержимого мало. Правое и левое auto оставлены: подвал по-прежнему
     центрируется под ту же ширину, что и экран. */
  .site-footer{max-width:1232px;margin:auto auto 0;padding:0 40px 28px;width:100%}
  .site-footer-links{
    display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:6px 10px;
    padding-top:18px;border-top:1px solid var(--border2);
    font-size:13px;color:var(--muted);
  }
  .site-footer-links a{color:var(--muted);text-decoration:none}
  .site-footer-links a:hover{color:var(--primary);text-decoration:underline;text-underline-offset:2px}
  .site-footer-sep{color:var(--muted2)}
  /* Переключатель языков: строка под ссылками подвала, теми же буквами.
     Текущий язык — не ссылка и темнее остальных. Сервер оставляет строку
     пустой, пока язык на сервере один, и тогда её нет вовсе — иначе под
     подвалом висел бы лишний отступ. */
  .site-footer-langs{
    display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:6px 10px;
    margin-top:8px;font-size:13px;color:var(--muted);
  }
  .site-footer-langs:empty{display:none}
  .site-footer-langs a{color:var(--muted);text-decoration:none}
  .site-footer-langs a:hover{color:var(--primary);text-decoration:underline;text-underline-offset:2px}
  .site-footer-langs .is-current{color:var(--text);font-weight:600}
  /* Строка о товарном знаке. Ниже ссылок и мельче их: это сноска, а не пункт
     меню, — читают её раз в жизни, но стоять она обязана. */
  .site-footer-note{
    margin:9px 0 0;text-align:center;font-size:11.5px;line-height:1.5;color:var(--muted2);
  }

  /* ================= TERMS И PRIVACY =================
     Страница договора: одна колонка текста в карточке. Ширина ограничена не
     сеткой экрана, а длиной строки — читать юридический текст во всю ширину
     монитора невозможно.

     Разметку внутри .legal-body собирает сервер из того, что владелец набрал в
     админке (см. server/src/domain/legal.ts). Набор тегов там закрыт, поэтому
     здесь перечислено всё, что вообще может прийти.

     До 04.09.2026 правила были поделены с карточкой текста витрины под
     поиск (seo-body). Карточек больше нет: под генератором стоят блоки
     (site/blocks/, стили в /blocks.css), а договор остался единственным
     местом, где эта разметка показывается. */
  .legal-body{
    background:var(--card);border:1px solid var(--border);border-radius:var(--r);
    font-size:15px;line-height:1.65;color:var(--text);
  }
  /* Договор — отдельная страница, карточка на экране одна, и ширину ей задаёт
     длина строки: ограничивать нечего, кроме самой строки. У витрины иначе —
     см. ниже, там карточка стоит в ряду других. */
  .legal-body{max-width:800px;padding:28px 32px}
  .legal-body h2{font-size:19px;font-weight:700;letter-spacing:-.01em;margin:26px 0 10px}
  .legal-body h3{font-size:16px;font-weight:650;margin:20px 0 8px}
  .legal-body h2:first-child,.legal-body h3:first-child{margin-top:0}
  .legal-body p{margin:0 0 12px}
  /* Пустая строка — Enter, нажатый на пустом абзаце. Своей высоты у пустого
     <p> нет: без этой строки от пустой строки остался бы один отступ между
     абзацами, и набранное в панели не совпало бы со страницей. Высота — в
     `em`, чтобы на телефоне уменьшилась вместе с кеглем. */
  .legal-body p.gap{height:1.65em}
  /* Отступ списка держит пункт, а не сам список, и пункт объявлен flow-root —
     обе строки написаны ради картинки сбоку, и без неё выглядели бы причудой.

     Картинку в боковом поле владелец волен растянуть шире поля: текст тогда
     обтекает её, и это нормальная вёрстка. Но обтекание двигает только строки —
     коробка пункта остаётся во всю ширину и уезжает под картинку, а вместе с
     ней и метка пункта. Галочка и номер оказывались на фотографии, тогда как
     текст шёл правильно, рядом; владелец это и увидел 02.09.2026.

     `flow-root` заставляет коробку пункта обойти картинку целиком, а не одними
     строками, — метка едет вместе с текстом. Слово `list-item` рядом
     обязательно: без него пункт перестал бы быть пунктом и потерял бы номер.
     Старый браузер записи из двух слов не поймёт и пропустит всю строку —
     пункт останется прежним, метка снова уедет под картинку, но текст уцелеет;
     это здесь и есть правильный отказ.

     Отступ переехал со списка на пункт затем, чтобы метке было где стоять
     после такого обхода: слева от обойдённого пункта остаётся один промежуток
     до картинки, и метка прижималась бы к ней вплотную. На обычном списке без
     картинки оба написания дают одно и то же место. */
  .legal-body ul,.legal-body ol{margin:0 0 14px;padding-left:0}
  .legal-body li{margin-bottom:6px;padding-left:22px;display:flow-root list-item}
  /* Пункт списка редактор хранит абзацем внутри пункта: собственный отступ ему
     здесь не нужен, иначе список расползается вдвое. */
  .legal-body li>p{margin:0}
  .legal-body li>ul,.legal-body li>ol{margin:6px 0 0}
  /* ---- список галочками ----
     Выбирается в редакторе админки («Пометка пунктов»), в дереве лежит словом
     `tick` / `tick-green`, класс сюда приводит сервер. Тот же значок, что в
     готовой полосе «QR-код в цветах вашего бренда» (.brand-tick): текст витрины
     стоит на одной странице с этими полосами, и список точками рядом с ними
     выглядит недоделанным, а не строгим.

     Значок рисует стиль, а не разметка: в пункте лежит текст владельца, и
     дописывать в него наш <span> означало бы мешать наше с набранным. Отсюда
     же `position:absolute` вместо `display:flex` у пункта — при флексе вложенный
     список встал бы сбоку от текста, а не под ним.

     Квадратик прижат к первой строке пункта, а не к его середине: у пункта в
     три строки значок иначе уезжает на вторую. Высота строки та же, что у
     всего текста (1.65), потому и считается от неё. */
  .legal-body ul.ticks{list-style:none;padding-left:0}
  .legal-body ul.ticks>li{position:relative;padding-left:29px;margin-bottom:10px}
  .legal-body ul.ticks>li::before{
    content:'';position:absolute;left:0;top:calc((1.65em - 19px) / 2);
    width:19px;height:19px;border-radius:6px;
    background-color:var(--primary-50);
    background-image:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%232F6FED' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'><path d='M20 6L9 17l-5-5'/></svg>");
    background-repeat:no-repeat;background-position:center;background-size:11px 11px;
  }
  /* Зелёная галочка — второе и последнее значение. Класс назван дважды, чтобы
     правило заведомо перевешивало синее выше, а не зависело от порядка строк. */
  .legal-body ul.ticks.ticks-green>li::before{
    background-color:var(--teal-50);
    background-image:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23159348' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'><path d='M20 6L9 17l-5-5'/></svg>");
  }
  .legal-body a{color:var(--primary);text-decoration:underline;text-underline-offset:2px}
  /* Цвет у жирного не задан намеренно — он наследуется. Внутри серого куска
     (.dim ниже) жирное слово обязано посереть вместе с остальным: «Серым» —
     кнопка цвета, и выделенное ею красится целиком, что бы внутри ни было.
     Стояло наоборот, тёмным (тёмный жирный внутри серого — приём из готовой
     полосы «.brand-list b»), но там тёмный жирный не внутри серого, а рядом с
     ним: так набирают и здесь, не помечая серым сам заголовок строки. Владелец
     на прежнее поведение пожаловался 02.09.2026 — снаружи оно выглядело как
     «кнопка не работает». */
  .legal-body strong{font-weight:650}
  /* Серый кусок — кнопка «Серым» в редакторе админки. Оттенок общий с готовыми
     полосами сайта (.why-text, .brand-lead): своего цвета владелец не
     выбирает — страниц двадцать, и произвольные цвета на них разъехались бы.
     Собирает <span class="dim"> сервер, снаружи остальных меток (см.
     renderInline в server/src/domain/legal.ts): жирное внутри серого серым и
     станет, а вот ссылка обязана остаться синей — свой цвет у неё задан
     правилом выше, и работает он, только пока <a> лежит внутри серого. */
  .legal-body .dim{color:var(--muted)}
  /* Выравнивание строки: кнопки «по центру» и «справа» в редакторе админки.
     Слева текст стоит и так, поэтому своего класса у левого края нет. */
  .legal-body .ta-center{text-align:center}
  .legal-body .ta-right{text-align:right}
  .legal-body>:last-child{margin-bottom:0}
  .legal-loading,.legal-empty{color:var(--muted)}

  /* ================= ВИТРИНА ПОД ПОИСК =================
     /wifi-qr-code-generator и остальные адреса из server/src/domain/seo-pages.ts.
     Экран тот же, что и у генератора, — добавляются два куска текста, и стоят
     они по разные стороны от инструмента намеренно.

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

     Рассказ — под генератором, блоками (site/blocks/, стили в /blocks.css):
     его читает тот, кому он действительно нужен, и ровно он же нужен
     поисковику, чтобы отличить эту страницу от двадцати соседних. */
  .seo-lead{
    max-width:760px;margin:10px 0 0;
    font-size:15.5px;line-height:1.6;color:var(--muted);
  }

  /* ================= БЛОКИ ПОД ГЕНЕРАТОРОМ =================
     Зона .site-blocks — сосед экрана, а не его часть: экран (.screen)
     ограничен 1232 точками и полями, а секции должны идти во всю ширину
     колонки. Собирает её сервер (server/src/lib/site-blocks.ts) и ставит в
     метку seo:blocks:* сразу за </section> экрана. Показывается она, только
     когда её экран открыт; в окне правки панели зона стоит одна, без экранов,
     — для неё пометка data-preview.

     Всё оформление самих блоков живёт в них же: каждый блок приезжает файлом
     со своей разметкой и стилями, сервер привязывает стили к блоку и кладёт
     их одним <style> в начало зоны. Здесь только место и видимость.

     hidden на зоне и на блоке ставит скрипт сайта (syncBlocksVisibility в
     app.js): вошедшему свои блоки не показываются, общие — по их
     переключателю; уход с витрины на другую плитку прячет её свои блоки.
     Правило с !important — чтобы стили блока (display:grid и прочее) не
     перебили пометку. */
  main > .site-blocks{display:none;width:100%;flex:0 0 auto;margin:0;background:#fff}
  main > .screen.active + .site-blocks,
  .site-blocks[data-preview]{display:block}
  .site-blocks > .blk{display:block}
  .site-blocks[hidden],
  .site-blocks > .blk[hidden]{display:none !important}


  /* ================= ЖАЛОБА НА КОД =================
     Две колонки: слева объяснение, справа форма. Объяснение стоит именно
     слева и именно текстом — сюда приходит человек, которому уже подсунули
     чужой код, и первое, что ему нужно, это понять, туда ли он попал и что с
     его жалобой будет дальше. Форма без этого выглядела бы анкетой в никуда.

     Колонки равные не делаем: читаемая строка текста короче, чем удобное поле
     ввода, а главное здесь — форма. */
  .report-cols{
    display:grid;grid-template-columns:minmax(0,1fr) minmax(0,520px);
    gap:32px;align-items:start;max-width:1060px;
  }
  .report-about{font-size:14.5px;line-height:1.7;color:var(--muted);max-width:480px}
  .report-about p{margin:0 0 14px}
  .report-about p:last-child{margin-bottom:0}
  /* Главное, чего от нас ждут и чего мы не делаем: жалоба не гасит код сама.
     Сказать это до отправки честнее, чем объясняться потом, поэтому плашка
     стоит в самой карточке, под кнопкой, а не в тексте рядом. */
  .report-note{
    background:var(--orange-50);border:1px solid var(--orange-100);color:var(--orange-dark);
    border-radius:12px;padding:12px 14px;margin:16px 0 0;font-size:13px;line-height:1.55;
  }
  .report-req{color:var(--red);font-weight:700}
  /* Почему жалобу прямо сейчас не отправить: не доехал список причин или не
     пришёл скрипт проверки «я не робот». Красная, а не оранжевая: это не
     оговорка, а остановка, и человеку нужен другой путь — он тут же, в тексте
     (Р-415, A6, проверка роботом, заход 13, 20.09.2026). */
  .report-trouble{
    background:#FEF2F2;border:1px solid #F3C9C9;color:var(--red);
    border-radius:12px;padding:12px 14px;margin:0 0 16px;font-size:13px;line-height:1.55;
  }
  /* Место под галочку Cloudflare. Высота задана заранее: виджет приезжает
     чужим скриптом с задержкой, и без неё кнопка прыгала бы вниз под пальцем. */
  .report-turnstile{margin:0 0 16px;min-height:65px}
  .report-submit{width:100%;justify-content:center}
  /* Заявление о добросовестности (DSA ст. 16, 19.10.2026) — серой строкой под кнопкой, до
     оранжевой плашки: читается до отправки, но не спорит с кнопкой. */
  .report-faith{margin:10px 0 0;line-height:1.5}

  /* Ответ вместо формы. Ставится на её место, а не рядом: дело сделано, и
     повторять его не нужно — второй раз человек попадёт сюда по кнопке. */
  .report-done{text-align:center}
  .report-done-icon{
    width:52px;height:52px;margin:0 auto 16px;border-radius:50%;
    background:var(--teal-50);color:var(--teal-dark);
    display:flex;align-items:center;justify-content:center;
  }
  .report-done-icon svg{width:26px;height:26px}
  .report-done-title{font-size:18px;font-weight:700;margin-bottom:8px}
  .report-done-text{font-size:14px;line-height:1.6;color:var(--muted);margin-bottom:20px}

  @media (max-width:1180px){
    .site-footer{padding:0 24px 24px}
  }
  @media (max-width:1000px){
    /* Одна колонка: ниже этой ширины поле ввода в правой колонке становится
       уже, чем набираемая в нём ссылка. Форма при этом поднимается наверх, а
       объяснение уходит под неё: человек пришёл сюда жаловаться, и три абзаца
       перед первым полем на телефоне — это три экрана прокрутки до дела. */
    .report-cols{grid-template-columns:minmax(0,1fr);gap:22px;max-width:560px}
    .report-form-col{order:-1}
    .report-about{max-width:none}
  }
  @media (max-width:900px){
    .site-footer{padding:0 14px 20px}
    /* Место под палец (Р-04, проверка роботом 13.09.2026): ссылки подвала и языков
       были по 19,5 точки в высоту при норме не меньше 24. Высоту даёт сама ссылка —
       буквы и порядок строк те же. */
    .site-footer-links a,.site-footer-langs a{display:inline-flex;align-items:center;min-height:32px}
    .legal-body{font-size:14.5px}
    .legal-body{padding:20px 18px}
    .seo-lead{font-size:14.5px}
  }

  /* ================= ПРОМЕЖУТКИ НА ТЕЛЕФОНЕ =================
     02.09.2026 промежутки между карточками и между блоками экрана уменьшены
     на 30% — но только на компьютере и планшете (решение владельца). На
     телефоне запас оставлен прежним: список там идёт в одну колонку, карточки
     нажимают пальцем, и в плотном ряду легко попасть не по тому коду.

     Здесь возвращены прежние числа для тех правил, у которых своего
     телефонного значения не было и они брали его с широкого экрана. Блок стоит
     в самом конце файла нарочно: часть уменьшенных значений объявлена ниже
     основного телефонного блока (@media 900px) и перебила бы их оттуда. */
  @media (max-width:900px){
    .qr-list{gap:12px}
    .bulk-steps{gap:16px}
    .place-work{gap:20px}
    .place-cta{margin-top:18px}
    .toolbar{margin-bottom:20px}
    .list-bar{margin-bottom:16px}
    .cust-tab-row{margin-bottom:20px}
    .plans-header{margin-bottom:20px}
    .plans-grid{gap:14px}
    .qr-pager{margin:22px 0 8px}
  }

  /* ================================================================
     НАЖАТИЕ, ТАЧ-ЭКРАН И ПЕРЕЕЗЖАЮЩАЯ ПЛИТКА — 30.09.2026

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

  /* ---- нажатие видно ----
     Кнопка уходит под палец на 4 % и возвращается. Раньше нажатие не
     показывала ни одна кнопка сайта: между «нажал» и «экран ответил»
     оставалась пустота, и на медленной связи человек жал второй раз.
     0.96 — ровно столько: ниже 0.95 движение уже кривляется.
     Отдельной строки про длительность нет: у всех перечисленных переход
     уже описан на своём месте и укладывается в 0,15 с. */
  .btn:not(:disabled):not(.off):active,
  .icon-btn:not(:disabled):active,
  .fav-btn:not(:disabled):active,
  .seg-opt:not(:disabled):active,
  .subtabs button:not(:disabled):active,
  .cust-tab:not(:disabled):active,
  .chip:not(:disabled):active,
  .fchip:not(:disabled):active,
  .type-filter-btn:not(:disabled):active,
  .type-card:active,
  .mode-card:not(:disabled):active,
  .nav-item:not(:disabled):active{transform:scale(.96)}
  /* У этих двух перехода трансформации не было — дописываем только её,
     остальные свойства оставляем как были. */
  .fchip{transition:color .15s,border-color .15s,background-color .15s,transform .15s}

  /* ---- наведение — не для пальца ----
     На сенсорном экране :hover остаётся на кнопке после нажатия и висит,
     пока не коснёшься в другом месте. Приподнятая кнопка, застрявшая в
     воздухе, читается как выбранная, а зелёная рамка на карточке режима —
     как «этот режим включён». Поэтому там, где мыши нет, подъём, усиленную
     тень и подсветку рамки снимаем: ответом на касание служит нажатие
     выше. Образец правила стоит в разделе про плитки типов. */
  @media (hover:none){
    .btn-primary:hover,.btn-generate:hover,.btn-teal:hover,.btn-danger:hover,
    .type-card:hover,.vc-social-pick button:hover,.vc-theme:hover,
    .send-btn:hover,.ai-cta:hover{transform:none}
    .btn-primary:hover{filter:none;box-shadow:0 6px 16px rgba(47,111,237,.28)}
    .btn-generate:hover{filter:none;box-shadow:0 6px 16px rgba(95,199,82,.28)}
    .btn-teal:hover{box-shadow:0 6px 16px rgba(34,197,94,.28)}
    .btn-danger:hover{filter:none;box-shadow:0 6px 16px rgba(220,38,38,.24)}
    .mode-card:not(.active):hover{border-color:var(--border)}
  }

  /* ---- белая плитка переключателя переезжает, а не перемигивает ----
     Было так: у прежней половины белая плитка гасла, у новой проявлялась,
     и ни один пиксель не двигался. Глазу не за чем проследить — «Мои QR»
     выглядели подменёнными, а не переключёнными. Плитка теперь одна на всю
     группу и переезжает под выбранную подпись.

     Мест шесть, и правило на все одно: вид кодов в «Моих QR», вид и папка
     в партии, развилки визитки и меню, строка вкладок оформления (там
     плитка — та самая синяя черта под подписью).

     Половины разной ширины («Статические» — 65 точек, «Динамические» — 83;
     решение владельца: делить поровну нельзя, длинное слово обрывается),
     поэтому место плитки замеряется у самой кнопки. Двигает её movePill в
     app.js — пока скрипт её не поставил, группа живёт по-старому: класс
     .has-pill появляется вместе с плиткой. */
  .seg-switch,.subtabs,.cust-tab-row{position:relative}
  .seg-pill{
    position:absolute;left:0;top:0;width:0;height:0;pointer-events:none;z-index:0;
    transition:transform .22s cubic-bezier(.2,.9,.3,1),width .22s cubic-bezier(.2,.9,.3,1);
  }
  /* Первая установка и возвращение на экран — без переезда: плитке неоткуда
     ехать, и полёт из левого угла был бы вознёй на пустом месте. */
  .seg-pill.no-move{transition:none}
  .seg-switch > .seg-opt,.subtabs > button,.cust-tab-row > .cust-tab{position:relative;z-index:1}
  .seg-switch .seg-pill{background:#fff;border-radius:999px;box-shadow:0 1px 3px rgba(20,50,90,.10)}
  .subtabs .seg-pill{background:#fff;border-radius:10px;box-shadow:0 2px 8px rgba(15,30,46,.14)}
  .cust-tab-row .seg-pill{background:var(--primary);border-radius:2px 2px 0 0}
  /* Цвет подписи остаётся на месте: движение — не единственный признак
     выбранного, иначе остановленная анимация оставляла бы группу без ответа. */
  .seg-switch.has-pill .seg-opt.active{background:none;box-shadow:none}
  .subtabs.has-pill button.active{background:none;box-shadow:none}
  .cust-tab-row.has-pill .cust-tab.active{border-bottom-color:transparent}

  /* Вид кодов в «Моих QR» (All / Static / Dynamic) выделен сильнее остальных
     переключателей: это главный выбор над списком, а светлая капсула терялась
     рядом с белыми кнопками фильтров (решение владельца 24.10.2026, из шести
     показанных вариантов — «голубая подложка»). Насыщенно-голубая капсула в
     рамке, выбранное — белая плитка с тенью и жирной синей надписью.
     Переключатели массовой генерации остаются прежними. */
  #kindSwitch{background:#D6E7FF;border:1.5px solid #A9C8FA;padding:4px;box-shadow:0 2px 10px rgba(47,111,237,.14)}
  #kindSwitch .seg-pill{box-shadow:0 2px 8px rgba(20,60,140,.22),0 0 0 1.5px rgba(47,111,237,.35)}
  #kindSwitch .seg-opt{font-size:13.5px;padding:8px 19px;color:#2B4A78}
  #kindSwitch .seg-opt:hover{color:var(--text)}
  #kindSwitch .seg-opt.active{color:var(--primary);font-weight:700}
  /* На телефоне — размеры общего правила (@media 900px выше: слово целиком,
     плотнее), здесь стоят позже и иначе перебили бы их. */
  @media (max-width:900px){
    #kindSwitch .seg-opt{padding:8px 12px;font-size:12.5px}
  }
  /* 320 точек: вид и «All types» должны встать в одну строку (@media 360px выше). */
  @media (max-width:360px){
    #kindSwitch{padding:3px}
    #kindSwitch .seg-opt{padding:8px 8px;font-size:12px}
  }

  /* ---- кому движение мешает ----
     Просил браузер поменьше движения — всё, что добавлено выше, выключено.
     Видно при этом ровно то же самое: цвет, плитка и длина полос остаются,
     пропадает только дорога между состояниями. */
  @media (prefers-reduced-motion:reduce){
    .btn:active,.icon-btn:active,.fav-btn:active,.seg-opt:active,.subtabs button:active,
    .cust-tab:active,.chip:active,.fchip:active,.type-filter-btn:active,
    .type-card:active,.mode-card:active,.nav-item:active{transform:none}
    .seg-pill{transition:none}
    .bar-fill,.plan-usage-fill,.storage-fill{transition:none}
    .modal-backdrop,.modal{transition:none}
    .cust-pane.active{animation:none}
  }
