/* style.css — все стили SPA. Чистый CSS без фреймворков (без Bootstrap).
   Тема: тёплый оранжевый акцент, светлый фон, мягкие тени, скруглённые углы.

   Брейкпоинт адаптивности: 780px (см. @media (min-width: 780px) ближе к
   концу файла).
   - До 779px включительно — мобильный вид: нижняя фиксированная панель
     .app-nav (Новый заказ/Заказы/Баланс), в шапке — только логотип/тема/
     бургер (бургер целиком, все 7 пунктов, включая Баланс/Username).
   - Начиная с 780px — десктопный вид: нижняя панель .app-nav скрывается,
     навигация переезжает в шапку целиком (бургер с сокращённым списком
     + Баланс/Username отдельно, см. .topbar-quick-item), контент/футер/
     кнопки связи занимают освободившееся место.
   ДО 2026-08-15 брейкпоинт был 1367px (сознательный выбор — держать iPad
   Pro 12.9", 1366px CSS-ширины в обеих ориентациях, в мобильном виде,
   несмотря на широкий экран). После переноса нижней панели на 780px это
   больше не так — iPad Pro и любой планшет шире 779px теперь получает
   десктопный вид навигации.

   ПЛОТНОСТЬ ИНТЕРФЕЙСА: все размерные величины (font-size, padding, margin,
   высоты полей/кнопок/иконок, gap, --radius-*) уменьшены примерно на 13%
   относительно исходных — тот же эффект, что даёт просмотр страницы в Safari
   на iOS при масштабе ~85% (пропорционально мельче ВСЁ разом, а не просто
   уменьшенный шрифт). Применено ГЛОБАЛЬНО, на всех брейкпоинтах разом — файл
   не использует rem/переменную-масштаб (все размеры исторически заданы
   абсолютным px по месту использования), поэтому это точечная правка каждого
   значения, а не единый множитель. Контейнерные ширины (max-width карточек/
   контента, брейкпоинты медиа-запросов) и толщины бордеров/эффекты теней —
   НЕ трогали: это композиция страницы и декоративные эффекты, а не то, что
   определяет "плотность" интерфейса. */

/* ---------- Базовые переменные темы ---------- */
:root {
  --color-accent: #f5a623;
  --color-accent-dark: #d98c0f;
  --color-accent-light: #fdf1dc;
  --color-bg: #faf9f7;
  --color-surface: #ffffff;
  --color-text: #2b2b2b;
  --color-text-muted: #8a8a8a;
  --color-border: #ececec;
  --color-danger: #e0503c;
  --color-success: #3fa96a;
  /* Только для тумблера "показать/скрыть id" (форма заказа, home.js) —
     синего в клиентской теме больше нигде нет (акцент оранжевый), но
     здесь цвет нужен намеренно другой — просто отличаться от обычного
     текста, не совпадать с фирменным акцентом/статусами. */
  --color-info: #2f6fed;
  --color-info-dark: #2557c2;

  --shadow-soft: 0 2px 10px rgba(30, 20, 0, 0.06), 0 1px 2px rgba(30, 20, 0, 0.04);
  --shadow-lift: 0 8px 24px rgba(30, 20, 0, 0.10);

  /* Четвёртый цвет статуса ("Partial") — остальные три уже есть выше
     (accent — жёлтый/оранжевый, success — зелёный, danger — красный). */
  --color-partial: #7c3aed;
  --color-partial-light: #f2e9fb;

  /* Только для кнопки-ярлыка "на конкретную услугу" (.quick-link-btn-service,
     см. ниже) — розово-малиновый, свободный от остальной палитры сайта:
     не оранжевый (обычные кнопки категорий), не красно-оранжевый Tron-
     свечения ("Новые услуги"), не синий/фиолетовый/зелёный/жёлтый (заняты
     выше и админскими бейджами услуг). Та же трёхступенчатая структура
     accent/-dark/-light, что и у --color-accent. */
  --color-feature: #e0459a;
  --color-feature-dark: #c73584;
  --color-feature-light: #fbe6f2;

  /* Светлые "плашечные" оттенки success/danger — раньше были захардкожены
     прямо в местах использования (.auth-error/.auth-success, .order-banner,
     .status-pill, .order-card-tint-*), теперь вынесены сюда переменными:
     тёмной теме (body.dark-theme ниже) достаточно переопределить эти два
     значения, а не искать и чинить каждое место по отдельности. */
  --color-success-light: #e9f8ee;
  --color-danger-light: #fdecea;

  /* --density-scale — единая точка управления "плотностью" интерфейса.
     0.87 — текущее состояние (глобальное уменьшение всех размерных величин
     ~13%, см. историю ниже), 1.0 — полноразмерный оригинал ДО этого
     уменьшения. Все calc(Npx * var(--density-scale)) ниже — N это
     значение, которое было ДО уменьшения плотности (не текущее видимое).
     Введена задним числом (2026-08-21) — раньше уменьшение плотности было
     сделано точечной правкой ~90 объявлений без единого множителя (см.
     коммит e4a3bde), эта переменная позволяет в будущем поменять плотность
     всего сайта разом одним числом, не переписывая файл заново. Значение
     0.87 подобрано не приблизительно, а сверкой: по всем ~130 точечным
     правкам коммита e4a3bde исходное_значение×0.87, округлённое до целого
     px, СОВПАДАЕТ с тем, что реально применили — то есть 0.87 это не
     "примерно 13%" из комментария того коммита, а точный факт. */
  --density-scale: 0.87;

  /* Крупное мягкое скругление в стиле iOS 26 "Liquid Glass" — раньше было
     10/14/20px (--radius-sm/--radius-md/--radius-lg), карточки/панели
     (.auth-card, .page-card, .order-card и т.п.) уже сидели на --radius-lg
     и почти попадали в нужный диапазон (~16-20px), а вот поля ввода,
     select, кнопки-таблетки и подобные средние элементы (--radius-sm)
     были заметно угловатее — 10px. Подняли ВСЕ три ступени, сохранив
     саму трёхступенчатую иерархию (sm < md < lg), но сблизив их —
     Liquid Glass, в отличие от прежнего стиля, тяготеет к единообразно
     мягким скруглениям по всему интерфейсу, а не к резкому контрасту
     между "почти прямоугольным полем" и "сильно скруглённой карточкой".
     Мелкие бейджи/пилюли (999px/50%) этой переменной не пользуются
     и трогать их не нужно — они и так полностью круглые.
     Далее (глобальное уменьшение плотности ~13%) — были 16/18/22px.
     Ещё позже — увеличено на ~25-30% (14/16/19 → 18/20/24) для более
     мягкого/современного вида по всему сайту, та же трёхступенчатая
     иерархия, тот же принцип пропорций — уже ПОСЛЕ уменьшения плотности,
     этот множитель их не затрагивал, поэтому база для --density-scale
     ниже пересчитана заново (текущее/0.87), а не взята из старого
     16/18/22px. */
  --radius-sm: calc(20.69px * var(--density-scale));
  --radius-md: calc(22.99px * var(--density-scale));
  --radius-lg: calc(27.59px * var(--density-scale));

  --bottom-nav-height: calc(64px * var(--density-scale));
  /* Было 60px, затем 64px (значение ДО уменьшения плотности, использовано
     как база выше) — увеличено вместе с логотипом брендов (.logo-image
     ниже вырос с 24px до 48px — правило общее, применяется и к
     rublevkalike.ru/.com, и к 1millionlikes.me, оба сейчас показывают
     реальный файл логотипа, не текст-заглушку), чтобы вокруг него осталась
     комфортная вертикальная "воздушная" зона, а не логотип впритык к краям
     шапки. Раньше это значение было ОПАСНО менять — .auth-screen считала
     свою высоту как calc(100vh - var(--topbar-height)), и любое расхождение
     (например, из-за баннера о переходе, вставшего между шапкой и
     .auth-screen) срывало вёрстку экрана логина. Это уже исправлено:
     .auth-screen теперь просто flex: 1 внутри .public-shell (см.
     .public-shell ниже) и сама подстраивается под ЛЮБУЮ фактическую высоту
     шапки — менять --topbar-height теперь безопасно.
     После глобального уменьшения плотности (~13%): 63 - 42 = 21px запаса,
     по ~10px сверху и снизу (было 72 - 48 = 24px, по 12px). */
  --topbar-height: calc(72px * var(--density-scale));
}

/* ---------- Тёмная тема ----------
   Класс на body (переключается кликом по кнопке в шапке, см. #theme-toggle
   в app.js/style.css ниже), а не отдельная media-query prefers-color-scheme —
   так пользователь сам решает, а не браузер/ОС за него; выбор запоминается
   в localStorage (см. THEME_STORAGE_KEY в app.js) и переживает перезаход.
   Раз ВСЁ оформление сайта уже построено на переменных из :root выше —
   тёмная тема это просто их переопределение под селектором body.dark-theme,
   без единого нового правила для конкретных элементов (кроме буквально
   одного места выше — .status-filter-btn.active, — где раньше был
   захардкожен #fff вместо переменной, см. комментарий там же).
   Фон — мягкий тёмно-серый, нейтральный (без явного оттенка в сторону
   жёлтого/коричневого — раньше --color-bg/--color-surface были слегка
   тёплыми, R>G>B во всех каналах, что на практике читалось как заметная
   желтизна фона; теперь R=G=B, чистый нейтральный серый), не чёрный —
   глазам легче. Акцент — тот же фирменный оранжевый, но чуть ярче/светлее исходного:
   на тёмном фоне менее яркий/насыщенный оранжевый (--color-accent-dark)
   читается плохо, поэтому местами они меняются ролями (см. ниже).

   --color-accent-light — ИСКЛЮЧЕНИЕ из "оранжевый только для текста/кнопок":
   в светлой теме это тёплая кремовая ПОДЛОЖКА (радиальный градиент на
   экране логина, плашка баланса, описание услуги, выбранный способ
   оплаты, жёлтый статус-пилюль) — то есть везде используется именно как
   ФОН, не текст. На тёмном фоне та же самая тёплая подложка читалась как
   мутное ржавое пятно (особенно в радиальном градиенте позади карточки
   входа — самый заметный случай). Поэтому здесь это НЕ тёплый, а
   нейтральный тёмно-серый оттенок — фон/подложка остаётся нейтральной
   независимо от темы, а сам оранжевый акцент (--color-accent/-dark) как
   и раньше остаётся только в тексте/кнопках/бордерах поверх неё. */
body.dark-theme {
  --color-accent: #ffb648;
  --color-accent-dark: #f5a623;
  --color-accent-light: #332f2b;
  --color-bg: #1a1a1a;
  --color-surface: #242424;
  --color-text: #f2ede6;
  --color-text-muted: #a6a29c;
  --color-border: #3a3632;
  --color-danger: #ff6b57;
  --color-success: #4ecb84;
  --color-info: #5b93f7;
  --color-info-dark: #2f6fed;

  /* На тёмном фоне тени почти не читаются (нет светлого фона, от которого
     тёмная тень отличалась бы) — вместо "мягкой тени" карточки отличаются
     от фона своим --color-surface (светлее --color-bg), а тень остаётся
     лёгким дополнением, не основным приёмом. */
  --shadow-soft: 0 2px 10px rgba(0, 0, 0, 0.35), 0 1px 2px rgba(0, 0, 0, 0.25);
  --shadow-lift: 0 8px 24px rgba(0, 0, 0, 0.45);

  --color-partial: #b39bfb;
  --color-partial-light: #2f2540;

  --color-success-light: #1c3324;
  --color-danger-light: #3a201c;

  --color-feature: #f06bb5;
  --color-feature-dark: #e0459a;
  --color-feature-light: #3d1f31;
}

/* ---------- Сброс и база ---------- */
* {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
}

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  background: var(--color-bg);
  color: var(--color-text);
  -webkit-font-smoothing: antialiased;
}

button,
input {
  font-family: inherit;
}

a {
  color: inherit;
  text-decoration: none;
}

#app {
  min-height: 100vh;
}

/* ================================================================
   ЭКРАН ЛОГИНА / РЕГИСТРАЦИИ
   ================================================================ */

/* .public-shell — обёртка неавторизованных экранов (логин, восстановление
   пароля): шапка + .auth-screen, тем же flex-column приёмом, что и
   .app-shell для залогиненного интерфейса ниже. Раньше вместо этого
   .auth-screen сама считала себе высоту как "100vh минус высота шапки"
   (min-height: calc(100vh - var(--topbar-height))) — расчёт держался на
   предположении, что перед .auth-screen ВСЕГДА стоит только .topbar и
   ничего между ними больше не появится. Предположение оказалось хрупким
   (в реальности между ними одно время стоял ещё и информационный баннер —
   см. git-историю), поэтому заменено на flex: 1 у .auth-screen ниже — сам
   разбирается, сколько места реально осталось, независимо от того, что
   именно (и какой высоты) стоит между шапкой и .auth-screen. */
.public-shell {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.auth-screen {
  flex: 1;
  /* Без min-height: 0 flex-элемент по умолчанию не может сжаться меньше
     содержимого (min-height: auto) — с центрированной карточкой это не
     страшно на практике, но обнуляем явно, чтобы flex считал доступное
     место корректно в любом случае. */
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: calc(24px * var(--density-scale));
  /* Было "circle at 15% 15%" — центр глубоко в верхнем левом углу
     ПОЛНОШИРИННОГО (проверено: .auth-screen растянут ровно на 100% ширины
     экрана, никакого сужения/сдвига контейнера нет) блока. На узких
     экранах разница малозаметна, но на широких десктопных мониторах
     (2000px+) такая точка приходится всего в 300-400px от левого края —
     то есть заметно ЛЕВЕЕ центрированной карточки входа, а вся правая
     часть экрана (где на самом деле light card) остаётся почти чистым
     --color-bg (#faf9f7 — этот цвет ПОЧТИ белый, поэтому и читается как
     "фон отсутствует"). Симметрии тут никогда не было — она и не могла
     появиться при фиксированной точке привязки в процентах от одного угла:
     чем шире экран, тем сильнее видимый перекос в сторону этого угла.
     center вместо 15% 15% — гарантирует одинаковое расстояние до обеих
     боковых границ НЕЗАВИСИМО от ширины экрана (симметрия по построению,
     а не только "на глаз" при какой-то конкретной ширине), и совпадает
     с точкой, где и так центрируется сама карточка входа (justify-content:
     center выше) — тёплое пятно оказывается точно за карточкой. */
  background:
    radial-gradient(circle at center, var(--color-accent-light), transparent 45%),
    var(--color-bg);
}

/* Оборачивает крупный заголовок (.auth-hero) и саму карточку формы —
   .auth-screen центрирует ЭТОТ блок целиком как один flex-item (см.
   .auth-screen выше), а здесь уже раскладываем заголовок и карточку
   друг под другом. width: 100% + max-width как у .auth-card — иначе
   узкая карточка "теряется" по центру широкого .auth-hero (там текст
   шире 380px на больших экранах). */
.auth-screen-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
}

/* Крупный заголовок над формой входа (см. renderAuthHeroHtml в app.js) —
   для узнаваемости бренда с первого экрана, по образцу старого дизайна
   PerfectPanel. max-width — длинная RU-формулировка не должна растягиваться
   на всю ширину широкого десктопного экрана в одну строку. */
.auth-hero {
  max-width: 560px;
  margin-bottom: calc(32px * var(--density-scale));
  text-align: center;
}

.auth-hero-title {
  margin: 0;
  font-size: calc(32px * var(--density-scale));
  font-weight: 800;
  line-height: 1.25;
  color: var(--color-text);
}

.auth-hero-subtitle {
  margin: calc(10px * var(--density-scale)) 0 0;
  font-size: calc(16px * var(--density-scale));
  font-weight: 500;
  color: var(--color-text-muted);
}

/* #auth-card-slot — обёртка вокруг .auth-card, введённая вместе с блоком
   "Попробовать бесплатно" (см. renderLogin() в app.js), чтобы переключение
   вкладок Вход/Регистрация (login.js: container.innerHTML = this.render())
   не стирало заголовок и сам free-trial-widget заодно с формой. БЕЗ
   явного width:100% здесь — реальный баг, найденный сразу после того фикса:
   .auth-screen-inner центрирует детей (align-items: center), то есть
   flex-item без своей явной ширины считает её "по контенту" (shrink-to-fit),
   а .auth-card внутри задаёт себе width:100% ОТ ЭТОГО flex-item — вложенная
   percentage-width от shrink-to-fit родителя схлопывалась до ширины полей
   формы, карточка входа становилась заметно уже 380px и уже соседнего
   free-trial-widget (у того такой проблемы не было — он сам прямой
   flex-item, без лишней обёртки). width: 100% здесь возвращает
   #auth-card-slot к прежнему поведению (растягивается на всю ширину
   .auth-screen-inner, дальше .auth-card сам ограничивает себя max-width). */
#auth-card-slot {
  width: 100%;
  display: flex;
  justify-content: center;
}

.auth-card {
  width: 100%;
  max-width: 380px;
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lift);
  padding: calc(32px * var(--density-scale)) calc(28px * var(--density-scale));
}

/* Блок "Попробовать бесплатно" (см. free-trial-widget.js) — визуально тот
   же язык, что и .auth-card (карточка со скруглением/тенью), та же
   max-width (одинаковая ширина обоих блоков — см. задачу). Вставляется
   между .auth-hero и #auth-card-slot (см. renderLogin() в app.js), поэтому
   margin-bottom вместо gap родителя — .auth-screen-inner не флексбокс с
   gap, а обычный column с центрированием. */
.free-trial-widget {
  width: 100%;
  max-width: 380px;
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lift);
  padding: 24px;
  margin-bottom: 20px;
  text-align: center;
}

.free-trial-title {
  margin: 0 0 6px;
  font-size: 18px;
  font-weight: 800;
  color: var(--color-text);
}

.free-trial-form .field {
  text-align: left;
}

.free-trial-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 4px;
}

/* .btn-primary по умолчанию width:100% (одна кнопка на всю ширину карточки
   входа) — здесь их две в ряд, поэтому переопределяем на flex: 1 (растут
   поровну) с разумным минимумом, чтобы не схлопывались в кашу на узких
   экранах раньше, чем сработает flex-wrap выше. */
.free-trial-actions .btn-primary {
  width: auto;
  flex: 1 1 180px;
}

/* Собственного логотипа внутри .auth-card больше нет — он теперь виден
   в постоянной шапке (.topbar) над этим экраном, см. .auth-screen выше
   и renderPublicTopbarHtml() в app.js; дублировать его внутри карточки
   было бы избыточно (два логотипа подряд). */
.auth-subtitle {
  text-align: center;
  color: var(--color-text-muted);
  font-size: calc(14px * var(--density-scale));
  margin-bottom: calc(24px * var(--density-scale));
}

.auth-tabs {
  display: flex;
  background: var(--color-bg);
  border-radius: var(--radius-md);
  padding: calc(4px * var(--density-scale));
  margin-bottom: calc(24px * var(--density-scale));
}

.auth-tab {
  flex: 1;
  text-align: center;
  padding: calc(10px * var(--density-scale)) 0;
  border-radius: var(--radius-sm);
  font-size: calc(14px * var(--density-scale));
  font-weight: 600;
  color: var(--color-text-muted);
  cursor: pointer;
  border: none;
  background: transparent;
  transition: background 0.15s ease, color 0.15s ease;
}

.auth-tab.active {
  background: var(--color-surface);
  color: var(--color-text);
  box-shadow: var(--shadow-soft);
}

.field {
  margin-bottom: calc(16px * var(--density-scale));
}

.field label {
  display: block;
  font-size: calc(13px * var(--density-scale));
  font-weight: 600;
  color: var(--color-text-muted);
  margin-bottom: calc(6px * var(--density-scale));
}

/* Строка "подпись поля + кнопка-тумблер" (сейчас только у order-service,
   см. home.js) — flex вместо обычного block-label, чтобы кнопка легла
   рядом с текстом подписи, а не под ним. flex-wrap: wrap — на очень узких
   экранах кнопка аккуратно переносится под подпись отдельной строкой,
   а не сжимает/обрезает текст в одну сплошную кашу. margin-bottom с
   .field label переносится сюда же (сама label внутри — margin-bottom: 0),
   чтобы отступ до <select> не задваивался. */
.field-label-row {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 8px;
  margin-bottom: 5px;
}

.field-label-row label {
  margin-bottom: 0;
}

.field-label-link {
  border: none;
  background: none;
  padding: 0;
  font: inherit;
  font-size: 11px;
  font-weight: 600;
  color: var(--color-info);
  cursor: pointer;
}

.field-label-link:hover {
  color: var(--color-info-dark);
}

.field input,
.field select,
.field textarea {
  width: 100%;
  padding: calc(12px * var(--density-scale)) calc(14px * var(--density-scale));
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border);
  background: var(--color-bg);
  font-size: calc(15px * var(--density-scale));
  color: var(--color-text);
  outline: none;
  font-family: inherit;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

/* textarea раньше вообще не попадала под правило выше (только input/select) —
   первый textarea на клиенте появился с тикетами (views/tickets.js), до
   этого его на клиенте просто не было нигде. resize: vertical — можно
   вручную растянуть выше заданных rows, но не шире поля (см. width: 100%
   выше) — горизонтальный resize сломал бы вёрстку .page-card. */
.field textarea {
  resize: vertical;
}

.field input:focus,
.field select:focus,
.field textarea:focus {
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px var(--color-accent-light);
}

/* Кольцо фокуса — это АКТИВНОЕ СОСТОЯНИЕ поля (не фоновая подложка), его
   оставляем оранжевым и в тёмной теме, но --color-accent-light там теперь
   нейтральный тёмно-серый (см. body.dark-theme в начале файла — исключение
   именно для подложек, не для этого случая), поэтому здесь отдельно
   задаём полупрозрачное оранжевое свечение поверх текущего --color-accent,
   а не переиспользуем эту переменную. */
body.dark-theme .field input:focus,
body.dark-theme .field select:focus,
body.dark-theme .field textarea:focus {
  box-shadow: 0 0 0 3px rgba(255, 182, 72, 0.3);
}

.field input[readonly] {
  color: var(--color-text-muted);
  cursor: default;
}

/* Автокомплит поиска услуги (#order-search, home.js) — выпадающий список
   совпадений под полем. .order-search-field (обёртка вокруг label+input,
   она же .field) — position: relative, дропдаун абсолютно позиционирован
   относительно неё, а не всей карточки заказа. */
.order-search-field {
  position: relative;
}

.order-search-dropdown {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 20;
  margin-top: 4px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lift);
  max-height: 280px;
  overflow-y: auto;
}

.order-search-dropdown-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  padding: 9px 13px;
  border: none;
  background: none;
  font: inherit;
  font-size: 13px;
  color: var(--color-text);
  text-align: left;
  cursor: pointer;
}

.order-search-dropdown-row:hover {
  background: var(--color-accent-light);
}

.order-search-dropdown-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.order-search-dropdown-price {
  flex-shrink: 0;
  color: var(--color-text-muted);
  font-size: 12px;
}

.order-search-dropdown-empty {
  padding: 10px 13px;
  color: var(--color-text-muted);
  font-size: 12px;
}

/* Honeypot-поле (см. support-modal.js) — спрятано позиционированием за
   пределы экрана, а не display:none/visibility:hidden: некоторые боты
   умеют распознавать именно эти два свойства и пропускать такое поле,
   position:absolute за пределами viewport'а — нет. Реальному пользователю
   не видно и не мешает табом (tabindex="-1" в самой разметке). */
.field-honeypot {
  position: absolute;
  left: -9999px;
  top: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* Ссылка + кнопка "Копировать" в одну строку (views/referrals.js) —
   input flex: 1 забирает всё свободное место, кнопка своей ширины рядом
   не переносится на мобильных (min-width: 0 на input обязателен, иначе
   flex-item не сжимается уже своей intrinsic-ширины и ломает раскладку). */
.referral-link-row {
  display: flex;
  gap: 8px;
  align-items: center;
}

.referral-link-row input {
  flex: 1;
  min-width: 0;
}

.referral-link-row .btn-primary {
  flex-shrink: 0;
  width: auto;
}

.field-hint {
  margin: calc(6px * var(--density-scale)) 0 0;
  font-size: calc(12px * var(--density-scale));
  color: var(--color-text-muted);
}

/* Первый видимый чекбокс на клиенте (drip-feed в форме заказа, home.js) —
   тот же визуальный язык, что и .checkbox-label в админке (admin.css),
   только на клиентских цветовых токенах. */
.checkbox-label {
  display: flex;
  align-items: center;
  gap: calc(6px * var(--density-scale));
  font-size: calc(13px * var(--density-scale));
  font-weight: 500;
  color: var(--color-text);
  cursor: pointer;
}

.checkbox-label input {
  width: auto;
}

/* Обёртка чекбокса — НЕ .field: .field input (ниже) задаёт всем полям
   width:100%/padding, рассчитанные на текстовые/числовые поля — если
   чекбокс окажется потомком .field (было так у #order-dripfeed-field до
   фикса), в части браузеров (особенно мобильных, где нативный чекбокс
   реально уважает заданные размеры, в отличие от десктопного Chromium)
   это визуально раздувает чекбокс в несколько раз. Тот же приём, что уже
   в админке (admin.css) — .field-checkboxes всегда СОСЕД .field, а не
   вложен в него. */
.field-checkboxes {
  display: flex;
  flex-direction: column;
  gap: calc(6px * var(--density-scale));
}

/* Обёртка блока drip-feed (#order-dripfeed-field) — не .field (см.
   .field-checkboxes выше, чекбокс внутри не должен быть потомком .field),
   но нужен тот же отступ снизу, что и у обычных .field, для единого
   вертикального ритма формы. */
.order-dripfeed-block {
  margin-bottom: calc(16px * var(--density-scale));
}

.btn-primary {
  width: 100%;
  padding: calc(13px * var(--density-scale)) 0;
  border: none;
  border-radius: var(--radius-sm);
  background: var(--color-accent);
  color: #fff;
  font-size: calc(15px * var(--density-scale));
  font-weight: 700;
  cursor: pointer;
  box-shadow: var(--shadow-soft);
  transition: background 0.15s ease, transform 0.1s ease;
}

.btn-primary:hover {
  background: var(--color-accent-dark);
}

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

.btn-primary:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.auth-error {
  background: var(--color-danger-light);
  color: var(--color-danger);
  border-radius: var(--radius-sm);
  padding: calc(10px * var(--density-scale)) calc(12px * var(--density-scale));
  font-size: calc(13px * var(--density-scale));
  margin-bottom: calc(16px * var(--density-scale));
  display: none;
}

.auth-error.visible {
  display: block;
}

.auth-success {
  background: var(--color-success-light);
  color: var(--color-success);
  border-radius: var(--radius-sm);
  padding: calc(10px * var(--density-scale)) calc(12px * var(--density-scale));
  font-size: calc(13px * var(--density-scale));
  margin-bottom: calc(16px * var(--density-scale));
  display: none;
}

.auth-success.visible {
  display: block;
}

.auth-hint {
  color: var(--color-text-muted);
  font-size: 11px;
  line-height: 1.5;
  text-align: center;
  margin-bottom: 14px;
  display: none;
}

.auth-hint.visible {
  display: block;
}

.auth-hint a {
  color: var(--color-info);
  text-decoration: underline;
}

.auth-hint a:hover {
  color: var(--color-info-dark);
}

.auth-forgot-link {
  text-align: center;
  margin-top: calc(16px * var(--density-scale));
  font-size: calc(13px * var(--density-scale));
}

.auth-forgot-link a {
  color: var(--color-text-muted);
  text-decoration: underline;
}

.auth-forgot-link a:hover {
  color: var(--color-accent-dark);
}

/* ================================================================
   ЗАЛОГИНЕННЫЙ ИНТЕРФЕЙС: шапка + контент + нижняя навигация
   ================================================================ */

.app-shell {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* ---- Шапка ---- */
.topbar {
  height: var(--topbar-height);
  background: var(--color-surface);
  box-shadow: var(--shadow-soft);
  position: sticky;
  top: 0;
  z-index: 10;
}

.topbar-inner {
  height: 100%;
  max-width: 1100px;
  margin: 0 auto;
  padding: 0 calc(16px * var(--density-scale));
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.logo {
  font-size: calc(18px * var(--density-scale));
  font-weight: 700;
  letter-spacing: -0.01em;
  /* Кликабелен (ведёт на главную, см. navigate('/') в app.js) — курсор
     явно намекает на это, иначе выглядел бы как обычный статичный текст. */
  cursor: pointer;
}

.logo span {
  color: var(--color-accent);
}

/* Логотип-картинка (когда для домена задан logoUrl в getSiteLocale()) —
   заменяет текстовый .logo, но должен вписываться в ту же строку шапки
   по высоте. Было 24px, увеличено вдвое до 48px (см. --topbar-height
   выше — шапка подросла вместе с логотипом), затем уменьшено вместе со
   всем интерфейсом (~13%) до 42px. width не задаём — img без явной
   ширины сохраняет исходные пропорции файла сама, растягивать через
   width: auto тут не нужно, это и есть поведение по умолчанию. */
.logo-image {
  height: calc(48px * var(--density-scale));
  display: block;
}

/* .topbar-left — логотип + переключатель темы как ОДНА группа слева.
   .topbar-inner устроен как justify-content: space-between ровно на ДВА
   элемента (левая группа / .topbar-right), поэтому логотип и кнопку темы
   нельзя просто положить в .topbar-inner по отдельности — так они
   разъехались бы на всю ширину шапки вместо того, чтобы идти вплотную
   друг к другу слева. На публичных экранах (renderPublicTopbarHtml) этот
   же .topbar-left — единственный ребёнок .topbar-inner, там space-between
   просто не с чем сравнивать, группа остаётся у левого края как обычно. */
.topbar-left {
  display: flex;
  align-items: center;
  gap: calc(10px * var(--density-scale));
}

/* Переключатель темы — СЛЕВА в шапке, сразу после логотипа (см. разметку
   в renderPublicTopbarHtml()/renderShell() в app.js), по аналогии с
   PerfectPanel. Круглая кнопка-иконка — тот же визуальный язык, что и у
   .btn-burger (справа в шапке), просто на другом краю. */
.theme-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: calc(36px * var(--density-scale));
  height: calc(36px * var(--density-scale));
  /* Без этого сброса иконка смещена от центра круга — браузерный UA-стиль
     <button> подставляет свой, не всегда симметричный padding, и при
     глобальном box-sizing: border-box (см. выше) это реально сдвигает
     контент-бокс (где центрирует flex) внутри уже фиксированного круга.
     Тот же баг и тот же фикс, что и у .btn-burger ниже — обнаружено там же. */
  padding: 0;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text-muted);
  border-radius: 999px;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease;
}

.theme-toggle:hover {
  border-color: var(--color-accent);
  color: var(--color-accent-dark);
}

.theme-toggle svg {
  width: calc(18px * var(--density-scale));
  height: calc(18px * var(--density-scale));
}

/* Кружок-счётчик непрочитанных сообщений от админа (см.
   refreshTicketsUnreadBadge в app.js) — рядом с переключателем темы, только
   в авторизованной шапке (renderShell) — не появляется на renderPublicTopbarHtml
   (тот тоже использует renderThemeToggleHtml(), но бейдж рисуется отдельной
   разметкой прямо в renderShell, не внутри самой функции). hidden при 0.
   Тот же визуальный язык (цвет/форма), что и .admin-nav-badge в админке. */
.header-unread-badge {
  display: inline-block;
  min-width: 17px;
  padding: 2px 6px;
  border-radius: 999px;
  background: var(--color-danger);
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  line-height: 1.3;
  text-align: center;
  /* Теперь <button>, а не <span> (клик ведёт в "Поддержку", см. app.js) —
     сбрасываем стандартные стили браузерной кнопки, чтобы выглядела как
     и раньше, просто с курсором-указателем. */
  border: none;
  cursor: pointer;
  font-family: inherit;
}

/* [hidden] у браузера — тоже display:none, но с той же специфичностью
   (0,1,0), что и .header-unread-badge выше: чей селектор в стиле идёт
   позже в каскаде, тот и побеждает. Без этого явного переопределения
   display:inline-block из правила выше побеждал бы атрибут hidden —
   кружок с "0" молча оставался бы виден вместо того, чтобы пропасть
   (реальный баг, найден 2026-08-10 при визуальной проверке). */
.header-unread-badge[hidden] {
  display: none;
}

.topbar-right {
  display: flex;
  align-items: center;
  gap: calc(10px * var(--density-scale));
}

/* ---- Бургер-меню (кнопка + выпадающий список в шапке) ---- */
.burger-menu {
  position: relative;
}

.btn-burger {
  display: flex;
  align-items: center;
  justify-content: center;
  width: calc(36px * var(--density-scale));
  height: calc(36px * var(--density-scale));
  /* Без этого сброса иконка смещена от центра круга — браузерный UA-стиль
     <button> подставляет свой, не всегда симметричный padding, и при
     глобальном box-sizing: border-box (см. выше) это реально сдвигает
     контент-бокс (где центрирует flex) внутри уже фиксированного круга. */
  padding: 0;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text);
  border-radius: 999px;
  cursor: pointer;
}

.btn-burger svg {
  width: calc(18px * var(--density-scale));
  height: calc(18px * var(--density-scale));
}

.btn-burger:hover {
  border-color: var(--color-accent);
  color: var(--color-accent-dark);
}

.burger-dropdown {
  position: absolute;
  top: calc(100% + calc(8px * var(--density-scale)));
  right: 0;
  min-width: 180px;
  background: var(--color-surface);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lift);
  padding: calc(6px * var(--density-scale));
  flex-direction: column;
  /* Выше топбара/контента (topbar сам не задаёт z-index больше 10) —
     выпадающее меню не должно оказаться под чем-то на странице. */
  z-index: 20;
}

/* БАГ, который тут был: "display: flex;" стоял прямо в .burger-dropdown
   безусловно — он побеждал встроенный браузерный [hidden] { display: none }
   (та же специфичность 0,1,0, но правило автора идёт позже в каскаде),
   поэтому меню визуально было развёрнуто ВСЕГДА, что бы ни делал JS с
   атрибутом hidden. Правильно — включать flex ТОЛЬКО когда hidden снят:
   тогда при hidden браузер сам скрывает элемент через display:none,
   а без hidden срабатывает именно этот display:flex. */
.burger-dropdown:not([hidden]) {
  display: flex;
}

.burger-item {
  /* .burger-item вешается и на <a> (пункты меню/"Поддержка"), и на <button>
     ("Выйти" — см. getMenuItemsHtml/data-action="logout" в app.js: это
     действие, а не переход по ссылке) — здесь же сбрасываем стандартные
     стили браузерной <button>, чтобы оба выглядели одинаково. Для <a>
     эти свойства и так были фактическим поведением по умолчанию, так что
     на неё эта строка визуально не влияет. */
  display: block;
  width: 100%;
  text-align: left;
  border: none;
  background: none;
  font-family: inherit;
  cursor: pointer;
  padding: calc(10px * var(--density-scale)) calc(12px * var(--density-scale));
  border-radius: var(--radius-sm);
  font-size: calc(14px * var(--density-scale));
  font-weight: 600;
  color: var(--color-text);
}

.burger-item:hover {
  background: var(--color-bg);
}

/* "Выйти" — единственный пункт-ДЕЙСТВИЕ в меню (остальные — переходы),
   поэтому и цветом при наведении отличается: то же красное предупреждение,
   что раньше было у отдельной кнопки .btn-logout в шапке. Специфичность
   выше, чем у .burger-item:hover — побеждает его. */
.menu-item[data-action="logout"]:hover {
  color: var(--color-danger);
}

/* Сумма баланса и логин пользователя — оба ВСЕГДА оранжевые (что внутри
   .topbar-quick-item в шапке на десктопе, что внутри бургера на любой
   ширине, включая мобильную) независимо от hover/active пункта-обёртки:
   это не обычная текстовая подпись пункта, а конкретное значение
   (сумма/логин), которое должно выделяться среди остальных пунктов. */
.balance-value-number,
.menu-username {
  color: var(--color-accent);
}

/* ---- Баланс/Username в шапке (см. renderTopbarQuickItemsHtml в app.js) ----
   Отдельно от бургера видны ТОЛЬКО с ширины ≥780px (см. @media ниже). На
   более узких ширинах скрыты целиком (display: none по умолчанию), там
   Баланс/Username видны только внутри бургера (см. .menu-item-quick-dup
   и getMenuItemsHtml в app.js). */
.topbar-quick-item {
  display: none;
  border: none;
  background: none;
  font-family: inherit;
  cursor: pointer;
  padding: 0;
  font-size: calc(14px * var(--density-scale));
  font-weight: 600;
  color: var(--color-text-muted);
  white-space: nowrap;
}

.topbar-quick-item:hover,
.topbar-quick-item.active {
  color: var(--color-accent-dark);
}

/* ---- Контентная область ---- */
.app-content {
  flex: 1;
  width: 100%;
  max-width: 1100px;
  margin: 0 auto;
  /* Нижний паддинг контента должен ровно совпадать с полной высотой
     .app-nav (bottom-nav-height + 17px базового запаса + safe-area),
     иначе последние строки контента прячутся под панелью либо остаётся
     лишний пустой хвост. Числа должны быть теми же, что и в .app-nav ниже. */
  padding: calc(20px * var(--density-scale)) calc(16px * var(--density-scale)) calc(var(--bottom-nav-height) + calc(20px * var(--density-scale)) + env(safe-area-inset-bottom) + calc(24px * var(--density-scale)));
}

/* ---- Футер (см. renderFooterHtml в app.js) — на ВСЕХ экранах: и внутри
   .app-shell (после .app-content), и на неавторизованных .public-shell
   (после .auth-screen). Оба родителя — flex-column с flex: 1 у
   предыдущего элемента (.app-content/.auth-screen), поэтому футер сам
   собой прижимается к низу экрана, даже если контента мало — без
   position: absolute/sticky. ---- */
.site-footer {
  padding: calc(24px * var(--density-scale)) calc(16px * var(--density-scale));
  text-align: center;
  border-top: 1px solid var(--color-border);
}

.site-footer-inner {
  max-width: 1100px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(8px * var(--density-scale));
}

.footer-copyright {
  font-size: calc(12px * var(--density-scale));
  color: var(--color-text-muted);
}

.footer-links {
  display: flex;
  gap: calc(16px * var(--density-scale));
}

.footer-link {
  font-size: calc(12px * var(--density-scale));
  color: var(--color-text-muted);
}

/* Некликабельный плейсхолдер ("Правила" — реальной страницы пока нет,
   см. renderFooterHtml) специально БЕЗ hover-цвета — иначе выглядел бы
   как рабочая ссылка, хотя клик по нему ничего не делает. */
a.footer-link:hover {
  color: var(--color-accent-dark);
}

/* Внутри залогиненного интерфейса (.app-shell) на мобильном/планшетном
   виде под футером ещё есть фиксированная нижняя навигация (.app-nav,
   см. ниже) — без явного отступа она перекрывала бы нижнюю часть футера.
   На неавторизованных экранах (.public-shell) такой панели нет вовсе,
   поэтому отступ добавлен ИМЕННО для футера внутри .app-shell, не для
   .site-footer вообще. На десктопе (см. медиа-запрос в конце файла)
   .app-nav скрыт целиком — там этот отступ обнуляется. */
.app-shell .site-footer {
  margin-bottom: calc(var(--bottom-nav-height) + calc(20px * var(--density-scale)) + env(safe-area-inset-bottom));
}

/* ---- Навигация: по умолчанию (мобильный вид) — фиксированная снизу ---- */
.app-nav {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  /* ИСПРАВЛЕНИЕ (проблема 4): раньше здесь был min-height + меньший
     safe-area-паддинг (12px) — этого на практике не хватило на iPad:
     показания браузера по safe-area-inset-bottom там занижены/нулевые
     в части сценариев (домашний индикатор, полноэкранный Safari), из-за
     чего нижние пункты меню всё равно частично перекрывались системной
     зоной. Теперь высота панели задаётся ЯВНО и детерминированно:
     фиксированная высота "иконки+подписи" (--bottom-nav-height) плюс
     ЩЕДРЫЙ базовый отступ поверх системной зоны (было 20px, после
     глобального уменьшения плотности — 17px), плюс сама safe-area (когда
     браузер её всё же сообщает). height (а не min-height) + padding-bottom
     с той же safe-area-частью гарантируют, что кликабельные .nav-item
     (внутри содержимого коробки, без учёта padding) всегда остаются строго
     НАД нижним краем экрана независимо от того, вернул браузер
     safe-area-inset-bottom корректно или нет. */
  height: calc(var(--bottom-nav-height) + calc(20px * var(--density-scale)) + env(safe-area-inset-bottom));
  padding-bottom: calc(calc(20px * var(--density-scale)) + env(safe-area-inset-bottom));
  background: var(--color-surface);
  box-shadow: 0 -2px 10px rgba(30, 20, 0, 0.06);
  display: flex;
  z-index: 10;
}

.nav-item {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  font-size: calc(11px * var(--density-scale));
  font-weight: 600;
  color: var(--color-text-muted);
  transition: color 0.15s ease;
}

.nav-item svg {
  width: calc(22px * var(--density-scale));
  height: calc(22px * var(--density-scale));
}

.nav-item.active {
  color: var(--color-accent);
}

/* Сумма баланса на кнопке "Баланс" нижней навигации (см. formatBalanceForNav
   в app.js) — ВСЕГДА оранжевая, независимо от .nav-item.active (активна
   вкладка "Баланс" сейчас или нет): это не обычная подпись пункта меню,
   а конкретная цифра, которая должна выделяться среди остальных серых
   подписей, поэтому со своим собственным цветом, а не просто наследует
   .nav-item/.nav-item.active. */
.nav-balance-value {
  color: var(--color-accent);
}

/* ---- Карточки-заглушки для view'ов ---- */
.page-card {
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-soft);
  padding: calc(32px * var(--density-scale)) calc(24px * var(--density-scale));
  text-align: center;
}

/* .page-card сама по себе без отступа снизу — раньше на экране всегда
   был ровно один .page-card за раз (загрузка/ошибка/пусто — взаимоисключающие
   ветки в home.js/orders.js/balance.js), стык было видеть негде. Первый
   реальный случай ДВУХ соседних .page-card одновременно — views/settings.js
   ("Аккаунт: username" + карточка смены пароля) — без отступа они визуально
   сливались в одну карточку. Тот же приём, что и у .payment-method-item +
   .payment-method-item в этом же файле — отступ через смежный селектор,
   а не через margin-bottom на самом .page-card (не задваивать отступ, если
   когда-нибудь после карточки пойдёт что-то ещё, не карточка). */
.page-card + .page-card {
  margin-top: 14px;
}

.page-card .page-icon {
  width: calc(56px * var(--density-scale));
  height: calc(56px * var(--density-scale));
  margin: 0 auto calc(16px * var(--density-scale));
  color: var(--color-accent);
}

.page-card h2 {
  margin: 0 0 calc(8px * var(--density-scale));
  font-size: calc(20px * var(--density-scale));
}

.page-card p {
  margin: 0;
  color: var(--color-text-muted);
  font-size: calc(14px * var(--density-scale));
  line-height: 1.5;
}

/* Раньше растягивалась на всю ширину контента и была неоправданно крупной
   для простого "текущий баланс: X" — уменьшили примерно в 5 раз по площади,
   но получилось СЛИШКОМ мелко (плохо читалось). Значения ниже — золотая
   середина между исходным огромным (padding 28px 24px, сумма 34px) и
   прошлым слишком мелким (padding 8px 20px, сумма 18px) вариантами,
   дополнительно уменьшенная (~13%) вместе со всем интерфейсом. width:
   fit-content + margin: 0 auto по-прежнему центрируют её по горизонтали
   (не во всю ширину), text-align — по той же причине, что и раньше (иначе
   label/value повисли бы слева внутри неширокой карточки). */
.balance-hero {
  background: linear-gradient(135deg, var(--color-accent) 0%, #f7b94e 100%);
  color: #fff;
  border-radius: var(--radius-lg);
  padding: calc(14px * var(--density-scale)) calc(22px * var(--density-scale));
  margin: 0 auto calc(16px * var(--density-scale));
  box-shadow: var(--shadow-soft);
  width: fit-content;
  max-width: 100%;
  text-align: center;
}

.balance-hero .label {
  font-size: calc(12px * var(--density-scale));
  opacity: 0.85;
  font-weight: 600;
}

.balance-hero .value {
  font-size: calc(24px * var(--density-scale));
  font-weight: 800;
  margin-top: 2px;
}

/* ================================================================
   ФОРМА СОЗДАНИЯ ЗАКАЗА (вкладка "Заказать")
   ================================================================ */

.order-form-card {
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-soft);
  padding: calc(24px * var(--density-scale)) calc(20px * var(--density-scale));
}

/* Кнопки-ярлыки категорий над формой заказа (см. renderQuickLinksHtml в
   home.js) — по образцу старого PerfectPanel (оранжевые закруглённые
   кнопки-пилюли в ряд), но на переменных текущей темы (--color-accent*),
   а не отдельной хардкоженной палитрой. flex-wrap БЕЗ фиксированного
   числа колонок — перенос строк чисто по ширине контента, естественно
   даёт разное количество в ряду на desktop/tablet/mobile само по себе,
   без явных brekpoint-правил "N кнопок в ряду". Блок не рендерится
   вовсе (см. renderQuickLinksHtml), если кнопок нет, поэтому здесь нет
   :empty-обработки — margin-bottom просто не появляется в DOM. */
.quick-links {
  display: flex;
  flex-wrap: wrap;
  /* Раньше кнопки прижимались к левому краю (flex-start по умолчанию) —
     центрируем весь ряд (и каждый перенесённый ряд по себе тоже, это
     всегда так работает у justify-content на flex-wrap) по горизонтали,
     тот же приём, что и у .orders-filters. */
  justify-content: center;
  gap: 9px;
  margin-bottom: 14px;
}

.quick-link-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: none;
  border-radius: 999px;
  background: var(--color-accent-light);
  color: var(--color-accent-dark);
  padding: 9px 16px;
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  line-height: 1.3;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, transform 0.1s ease;
  /* Ширину каждой кнопки в норме определяет сам её текст (fit-content) —
     перенос на новую строку решает flex-wrap у родителя .quick-links, а
     не обрезание содержимого одной кнопки. max-width здесь — ТОЛЬКО
     страховка от горизонтального оверфлоу страницы на экстремально
     длинном тексте (проверено эмпирически: ~50 символов на 375px реально
     вылезали за край экрана без этого ограничения) — типичные названия
     (до ~40-50 символов) в него укладываются свободно и не обрезаются,
     см. .quick-link-text ниже. min() с vw — предел пропорционален
     реальной ширине экрана, а не фиксированный px, который на маленьких
     телефонах обрезал бы куда раньше, чем на десктопе. Было 90vw — на
     iPhone SE (375px) это давало 337.5px, чуть меньше естественной
     ширины вполне типичного 37-символьного примера (~359px) — обрезало
     то, что обрезаться не должно было. Поднято сначала до 95vw, потом до
     98vw — на 375px даже 95vw (356.25px) не хватало буквально 3px до
     нужных ~359px, тот же типичный пример всё ещё чуть обрезался. 420px-
     потолок сам по себе уже достаточен для защиты от оверфлоу (проверено
     стресс-тестом на ~54 символах), поэтому vw-множитель можно сделать
     почти предельным. */
  max-width: min(420px, 98vw);
}

.quick-link-btn:hover {
  background: var(--color-accent);
  color: #fff;
}

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

/* Иконка соцсети перед текстом кнопки (см. renderSocialIconSvg в
   public/js/social-icons.js) — flex-shrink: 0, чтобы длинный текст рядом
   никогда не сжимал саму иконку. Свой цвет не задаём — fill="currentColor"
   в самом SVG уже наследует color кнопки (тот же оранжевый, что и текст,
   в состоянии покоя и на hover), поэтому однотонная иконка автоматически
   остаётся "в цвет темы", а не фирменного цвета платформы. */
.quick-link-btn .social-icon {
  flex-shrink: 0;
}

/* Текст кнопки — всегда в одну строку. Для типичных названий (умещаются
   в max-width у .quick-link-btn) ellipsis никогда не включается — кнопка
   просто по ширине текста. Для экстремально длинных — обрезается
   многоточием по достижении max-width, а не переносится на вторую
   строку и не вылезает за край. min-width: 0 обязателен: flex-элемент
   по умолчанию не может сжаться уже своего содержимого (min-width: auto),
   без явного обнуления overflow/text-overflow тут же не сработали бы. */
.quick-link-text {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

/* Регистрация углового custom property — без этого браузер не умеет плавно
   интерполировать conic-gradient(from var(--qlb-angle)) между кадрами
   @keyframes (обычный CSS custom property типа string никогда не
   анимируется само по себе, тут прямо нужен зарегистрированный <angle>). */
@property --qlb-angle {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}

/* Кнопка "Новые услуги" (см. isNewServicesButton в quick-link-buttons.js/
   NEW_SERVICES_CATEGORY_ID в home.js) — та же обычная .quick-link-btn, плюс
   эффект "бегущий световой блик по контуру" (четвёртая визуальная правка —
   до этого пробовали бегущее кольцо на JS/SVG-комете, статичный ореол по
   всему периметру, статичные полосы сверху/снизу; финальная версия —
   чистый CSS, без mountCometTrail/SVG, см. историю в git log). Один и тот
   же класс/эффект и для авторизованных (кнопка над формой заказа), и для
   гостя (new-services-widget.js — гостевая кнопка тоже несёт классы
   quick-link-btn quick-link-btn-new).
   Два независимых слоя:
   1. Постоянное мягкое рассеянное свечение вокруг всей кнопки — обычный
      box-shadow на самом элементе, статичный, НЕ участвует в анимации.
      Непрозрачность уменьшена примерно до 20% от изначальной (0.35/0.22 →
      0.07/0.04) — визуально забивало бегущую полосу (пункт 2), сам
      geometry (blur/spread) не менялся, чтобы свечение осталось таким же
      мягким/растянутым, просто заметно тише.
   2. Тонкая яркая полоса, непрерывно бегущая по контуру, — ::before чуть
      больше кнопки, залитый conic-gradient (красно-оранжевый фирменный
      градиент, не фиолетовый), обрезанный маской ТОЛЬКО до тонкого кольца
      по краю (content-box вычитается из padding-box через
      mask-composite: exclude — тот же приём, что и в самой первой версии
      этого эффекта). Крутится не transform: rotate на всём слое (это
      вращало бы даже сам чётко очерченный овал маски), а поворотом угла
      старта градиента через var(--qlb-angle) — так пятно яркости само
      "едет" по кольцу равномерно, независимо от формы pill. */
.quick-link-btn-new {
  position: relative;
  box-shadow:
    0 0 10px 1px rgba(255, 59, 48, 0.07),
    0 0 22px 6px rgba(255, 138, 0, 0.04);
}

.quick-link-btn-new::before {
  content: '';
  position: absolute;
  inset: -2px;
  border-radius: inherit;
  padding: 2px;
  --qlb-angle: 0deg;
  background: conic-gradient(
    from var(--qlb-angle),
    transparent 0%,
    transparent 70%,
    rgba(255, 59, 48, 0.55) 78%,
    #ff8a00 85%,
    rgba(255, 59, 48, 0.65) 90%,
    transparent 100%
  );
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  animation: quick-link-tron-spin 3.5s linear infinite;
  pointer-events: none;
}

@keyframes quick-link-tron-spin {
  to {
    --qlb-angle: 360deg;
  }
}

@media (prefers-reduced-motion: reduce) {
  .quick-link-btn-new::before {
    animation: none;
  }
}

/* Кнопка "на конкретную услугу" (см. QuickLinkButton.serviceId, точечная
   реклама одной услуги, не целой категории) — та же обычная .quick-link-btn
   (форма/паттерн не меняются), просто другой цвет заливки — без Tron-
   свечения (то уникальная примета именно "Новых услуг", размывать её на
   вторую кнопку не стали, см. обсуждение при проектировании). */
.quick-link-btn-service {
  background: var(--color-feature-light);
  color: var(--color-feature-dark);
}

.quick-link-btn-service:hover {
  background: var(--color-feature);
  color: #fff;
}

/* Переключатель "Разовый заказ"/"Подписка" (см. #order-kind-field в
   home.js) — тот же визуальный приём сегментированного переключателя,
   что и .auth-tabs/.auth-tab на экране логина, но отдельными классами:
   тот компонент привязан к конкретному экрану логина по смыслу имени,
   здесь — к форме заказа, смешивать не стали, хотя стили и совпадают. */
.order-kind-toggle {
  display: flex;
  background: var(--color-bg);
  border-radius: var(--radius-md);
  padding: calc(4px * var(--density-scale));
}

.order-kind-btn {
  flex: 1;
  text-align: center;
  padding: calc(10px * var(--density-scale)) 0;
  border-radius: var(--radius-sm);
  font-size: calc(14px * var(--density-scale));
  font-weight: 600;
  color: var(--color-text-muted);
  cursor: pointer;
  border: none;
  background: transparent;
  transition: background 0.15s ease, color 0.15s ease;
}

.order-kind-btn.active {
  background: var(--color-surface);
  color: var(--color-text);
  box-shadow: var(--shadow-soft);
}

/* Поле "Указать свою" скорости (см. #order-speed-custom в home.js) —
   появляется ПОД селектом пресетов внутри того же .field, нужен
   собственный отступ сверху (обычный .field + .field между двумя
   отдельными полями здесь не подходит — это одно поле, просто с двумя
   элементами управления). */
#order-speed-custom {
  margin-top: calc(8px * var(--density-scale));
}

.order-description,
.method-description {
  background: var(--color-accent-light);
  border-radius: var(--radius-sm);
  padding: calc(12px * var(--density-scale)) calc(14px * var(--density-scale));
  margin-bottom: calc(16px * var(--density-scale));
}

.order-description strong {
  display: block;
  font-size: calc(12px * var(--density-scale));
  font-weight: 700;
  color: var(--color-accent-dark);
  margin-bottom: calc(4px * var(--density-scale));
}

.order-description p {
  margin: 0;
  font-size: calc(14px * var(--density-scale));
  line-height: 1.5;
  color: var(--color-text);
}

.method-description {
  font-size: calc(14px * var(--density-scale));
  line-height: 1.5;
  color: var(--color-text);
}

/* Глобально ссылки наследуют цвет текста и без подчёркивания (см. базовый
   `a` в начале файла) — внутри блока с описанием способа оплаты это сделало
   бы кликабельную ссылку неотличимой от обычного текста, поэтому здесь
   явно возвращаем акцентный цвет и подчёркивание. */
.order-description a,
.method-description a {
  color: var(--color-accent-dark);
  text-decoration: underline;
}

/* ================================================================
   СПОСОБЫ ПОПОЛНЕНИЯ (вкладка "Баланс")
   ================================================================ */

.balance-methods-card h2 {
  margin: 0 0 calc(16px * var(--density-scale));
  font-size: calc(18px * var(--density-scale));
}

/* Один способ оплаты — ОДНА карточка целиком (название + форма суммы или
   инструкция внутри неё же, см. renderMethodRow в balance.js) — раньше
   это были ДВЕ отдельные подложки (карточка названия + отдельная
   .order-form-card в соседнем слоте), теперь одна общая рамка/тень на
   весь блок, без внутреннего разделения линией. Способы больше не
   кликабельны (ни у yookassa, ни у manual/crypto нечего "открывать" —
   всё видно сразу), поэтому никакого cursor/hover-состояния тут нет.
   margin-bottom между карточками способов — раньше на .payment-method-row
   (отдельной обёртки для пары карточка+слот, которой теперь нет), теперь
   прямо здесь через смежный селектор. */
.payment-method-item {
  padding: calc(14px * var(--density-scale)) calc(16px * var(--density-scale));
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border);
  background: var(--color-bg);
  box-shadow: var(--shadow-soft);
}

.payment-method-item + .payment-method-item {
  margin-top: calc(12px * var(--density-scale));
}

.payment-method-name {
  font-weight: 700;
  font-size: calc(15px * var(--density-scale));
  margin-bottom: calc(8px * var(--density-scale));
}

/* ---- История платежей: карточки, отступ между ними такой же, как у
   заказов (order-card сам по себе уже даёт margin-bottom) ---- */
.balance-history h2 {
  margin: 0 0 calc(16px * var(--density-scale));
  font-size: calc(18px * var(--density-scale));
}

.order-banner {
  border-radius: var(--radius-sm);
  padding: calc(10px * var(--density-scale)) calc(12px * var(--density-scale));
  font-size: calc(13px * var(--density-scale));
  font-weight: 600;
  margin-bottom: calc(16px * var(--density-scale));
  display: none;
}

.order-banner.visible {
  display: block;
}

.order-banner-success {
  background: var(--color-success-light);
  color: var(--color-success);
}

.order-banner-error {
  background: var(--color-danger-light);
  color: var(--color-danger);
}

/* Ссылка "Пополните баланс"/"Top up your balance" внутри #order-balance-
   warning — тот же голубой токен и без подчёркивания, что и у тумблера
   "показать/скрыть id" (.field-label-link) — цвет здесь уже сам по себе
   сигнализирует кликабельность, подчёркивание поверх фона предупреждения
   (--color-danger-light) было бы лишним. */
#order-balance-topup-link {
  color: var(--color-info);
  text-decoration: none;
}

#order-balance-topup-link:hover {
  color: var(--color-info-dark);
}

/* ================================================================
   СПИСОК ЗАКАЗОВ (вкладка "Заказы")
   ================================================================ */

.orders-filters {
  display: flex;
  flex-wrap: wrap;
  /* Раньше кнопки жались к левому краю (flex-start по умолчанию) —
     выравниваем по центру ряда фильтров. */
  justify-content: center;
  gap: calc(8px * var(--density-scale));
  margin-bottom: calc(16px * var(--density-scale));
}

.status-filter-btn {
  padding: calc(6px * var(--density-scale)) calc(14px * var(--density-scale));
  border-radius: 999px;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text);
  font-size: calc(13px * var(--density-scale));
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}

/* Активный фильтр — сплошная заливка цветом ТЕКСТА темы, текст — цветом
   ФОНА темы (не акцентный оранжевый: тут это выбор одного варианта из
   списка, а не призыв к действию). Раньше цвет текста был захардкожен
   как #fff ("тёмный фон + белый текст") — в тёмной теме это сломалось бы:
   --color-text там сам становится СВЕТЛЫМ (это же основной цвет текста
   страницы), то есть фон кнопки стал бы светлым, а текст остался бы
   жёстко белым — почти невидимым на светлом фоне. var(--color-bg) вместо
   #fff — тот же "инвертированный" вид в обеих темах: цвета bg/text просто
   меняются местами относительно того, что светлее в конкретной теме. */
.status-filter-btn.active {
  background: var(--color-text);
  border-color: var(--color-text);
  color: var(--color-bg);
}

/* "Автозаказы"/"Заказы с восстановлением" (.subscriptions-filter-btn/
   .refill-filter-btn в orders.js) — единственные два фильтра, переключающие
   ВЕСЬ раздел на другой набор данных/предикат, а не просто сужающие список
   заказов по статусу — акцентный цвет в покое (не только при hover/active,
   как у обычных .status-filter-btn) визуально отделяет их от остальных.
   #fff у .active — тот же приём, что и у .btn-primary (сплошная заливка
   ФИКСИРОВАННЫМ акцентным цветом, не завязанным на тему, поэтому белый
   текст безопасен в обеих темах — в отличие от .status-filter-btn.active
   выше, где инвертируются именно ТЕМАТИЧЕСКИЕ text/bg). */
.subscriptions-filter-btn,
.refill-filter-btn {
  border-color: var(--color-accent);
  color: var(--color-accent-dark);
}

.subscriptions-filter-btn:hover,
.refill-filter-btn:hover {
  background: var(--color-accent-light);
}

.subscriptions-filter-btn.active,
.refill-filter-btn.active {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: #fff;
}

.orders-search-field {
  margin-bottom: calc(16px * var(--density-scale));
}

.order-card {
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-soft);
  padding: calc(6px * var(--density-scale)) calc(18px * var(--density-scale));
  margin-bottom: calc(14px * var(--density-scale));
}

.order-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: calc(12px * var(--density-scale));
  padding: calc(10px * var(--density-scale)) 0;
  border-bottom: 1px solid var(--color-border);
}

.order-row:last-child {
  border-bottom: none;
}

.order-label {
  flex: none;
  font-size: calc(13px * var(--density-scale));
  color: var(--color-text-muted);
}

.order-value {
  font-weight: 700;
  font-size: calc(14px * var(--density-scale));
  text-align: right;
  /* Ссылки заказа могут быть длинными и без пробелов — не должны вылезать
     за карточку на узких мобильных экранах. */
  word-break: break-all;
}

.order-value-refund {
  color: var(--color-success);
}

/* Дочерние заказы bundle — визуально "вложены" под родителя: отступ +
   цветной бордер слева, более лёгкая тень, чтобы карточка читалась как
   часть родительской покупки, а не как самостоятельный заказ. */
.order-children {
  margin-left: calc(20px * var(--density-scale));
}

.order-card-child {
  padding-left: calc(14px * var(--density-scale));
  border-left: 3px solid var(--color-accent);
  box-shadow: none;
  background: var(--color-bg);
}

/* Едва заметный оттенок фона всей карточки по статусу — см. normalizeOrderStatus
   в orders.js. Идёт ПОСЛЕ .order-card/.order-card-child в файле, чтобы
   переопределять их фон при той же специфичности селектора. */
.order-card-tint-completed {
  background: var(--color-success-light);
}

.order-card-tint-canceled {
  background: var(--color-danger-light);
}

.status-pill {
  display: inline-block;
  padding: calc(3px * var(--density-scale)) calc(10px * var(--density-scale));
  border-radius: 999px;
  font-size: calc(12px * var(--density-scale));
  font-weight: 700;
  white-space: nowrap;
}

.status-pill-green {
  background: var(--color-success-light);
  color: var(--color-success);
}

.status-pill-yellow {
  background: var(--color-accent-light);
  color: var(--color-accent-dark);
}

.status-pill-purple {
  background: var(--color-partial-light);
  color: var(--color-partial);
}

.status-pill-red {
  background: var(--color-danger-light);
  color: var(--color-danger);
}

/* Кнопка "Обратиться в поддержку" на карточке заказа (orders.js) — своя
   строка ПОСЛЕ обычных .order-row (без border-bottom последней строки, см.
   .order-row:last-child), выровнена вправо, компактный контурный стиль —
   как .status-filter-btn, но с акцентным текстом (это переход-действие,
   а не переключатель фильтра). */
.order-support-btn {
  display: block;
  margin: 9px 0 5px auto;
  padding: 5px 12px;
  border-radius: 999px;
  border: 1px solid var(--color-accent);
  background: none;
  color: var(--color-accent-dark);
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
}

.order-support-btn:hover {
  background: var(--color-accent-light);
}

/* Кнопка "Рефил" — тот же стиль, что и "Обратиться в поддержку" (см. выше),
   рядом с ней (order.refillEligible, см. orders/controller.js). */
.order-refill-btn {
  display: block;
  margin: 9px 0 5px auto;
  padding: 5px 12px;
  border-radius: 999px;
  border: 1px solid var(--color-accent);
  background: none;
  color: var(--color-accent-dark);
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
}

.order-refill-btn:hover {
  background: var(--color-accent-light);
}

.order-refill-btn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

/* Результат попытки рефила — тот же цветовой язык, что и .auth-error/
   .auth-success, но здесь один класс переключает и текст, и цвет разом
   (success/error решает JS по refillRequest.status, см. orders.js), а не
   два раздельных всегда присутствующих в DOM блока, как в .auth-*. */
.order-refill-result {
  display: none;
  margin-top: 7px;
  padding: 7px 10px;
  border-radius: var(--radius-sm);
  font-size: 11px;
  text-align: right;
}

.order-refill-result.visible {
  display: block;
}

.order-refill-result-success {
  background: var(--color-success-light);
  color: var(--color-success);
}

.order-refill-result-error {
  background: var(--color-danger-light);
  color: var(--color-danger);
}

/* "Попробуйте позже" (retry_after/окно между попытками, см. renderOrderCard
   и attachHandlers в orders.js) — не ошибка и не успех, тот же нейтральный
   акцентный цвет, что и у .status-pill-yellow (пилюля статуса "Ожидание") —
   уже устоявшийся в проекте цветовой язык для "не финально, просто ждём". */
.order-refill-result-pending {
  background: var(--color-accent-light);
  color: var(--color-accent-dark);
}

/* ---- Карточка подписки: кнопки "Публикации"/"Отменить подписку" (views/orders.js) ---- */
.subscription-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 9px;
}

.subscription-publications-btn,
.subscription-cancel-btn {
  padding: 5px 12px;
  border-radius: 999px;
  border: 1px solid var(--color-accent);
  background: none;
  color: var(--color-accent-dark);
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
}

.subscription-publications-btn:hover,
.subscription-cancel-btn:hover {
  background: var(--color-accent-light);
}

.subscription-cancel-btn {
  border-color: var(--color-danger);
  color: var(--color-danger);
}

.subscription-cancel-btn:hover {
  background: var(--color-danger-light);
}

.subscription-cancel-btn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.subscription-publications {
  margin-top: 9px;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  background: var(--color-bg);
  font-size: 11px;
}

.subscription-publication-row {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  padding: 4px 0;
}

.subscription-publication-row + .subscription-publication-row {
  border-top: 1px solid var(--color-border);
}

.subscription-publication-row a {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.subscription-publication-status {
  flex-shrink: 0;
  color: var(--color-text-muted);
}

.subscription-publications-loading,
.subscription-publications-empty,
.subscription-publications-error {
  margin: 0;
  color: var(--color-text-muted);
}

/* Тот же цветовой язык, что и .order-refill-result-success/-error выше. */
.subscription-cancel-result {
  display: none;
  margin-top: 7px;
  padding: 7px 10px;
  border-radius: var(--radius-sm);
  font-size: 11px;
  text-align: right;
}

.subscription-cancel-result.visible {
  display: block;
}

.subscription-cancel-result-success {
  background: var(--color-success-light);
  color: var(--color-success);
}

.subscription-cancel-result-error {
  background: var(--color-danger-light);
  color: var(--color-danger);
}

/* ---- Поддержка / тикеты (views/tickets.js) ---- */
.tickets-header {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 12px;
}

/* Строка списка тикетов — обычная .order-card, только кликабельная целиком
   (курсор-подсказка) — открывает тред, в отличие от карточки заказа. */
.tkt-clickable-card {
  cursor: pointer;
}

/* Тикет с непрочитанным сообщением от админа (см. renderTicketRow в
   views/tickets.js) — лёгкий акцентный фон, тот же приём и тот же токен
   (--color-accent-light), что и у .order-card-tint-* в orders.js. */
.tkt-card-unread {
  background: var(--color-accent-light);
}

.tkt-unread-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-right: 6px;
  border-radius: 50%;
  background: var(--color-danger);
  vertical-align: middle;
}

.tkt-meta-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 7px 0 14px;
  font-size: 12px;
  color: var(--color-text-muted);
}

.tkt-thread {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 17px;
}

.tkt-message {
  max-width: 85%;
  padding: 9px 12px;
  border-radius: var(--radius-md);
}

/* Сообщение клиента — слева, нейтральный фон; сообщение админа — справа,
   лёгкий акцентный оттенок (тот же .color-accent-light, что и у пилюли
   status-pill-yellow) — визуально считывается как "ответ панели". */
.tkt-message-user {
  align-self: flex-start;
  background: var(--color-bg);
}

.tkt-message-admin {
  align-self: flex-end;
  background: var(--color-accent-light);
}

.tkt-message-meta {
  font-size: 10px;
  color: var(--color-text-muted);
  margin-bottom: 4px;
}

.tkt-message-body {
  font-size: 13px;
  white-space: pre-wrap;
  word-break: break-word;
}

.tkt-message-body a {
  color: var(--color-info);
}

/* ================================================================
   ПЛАВАЮЩИЕ КНОПКИ СВЯЗИ (Telegram/WhatsApp) — видны всегда, на любом
   экране (логин, восстановление пароля, основное приложение), поверх
   всего остального. Живут вне #app в index.html, JS их не трогает.
   ================================================================ */
.contact-buttons {
  position: fixed;
  right: calc(16px * var(--density-scale));
  /* На мобильном (до 779px включительно) нижняя навигация фиксирована
     внизу экрана — поднимаем кнопки выше неё, с запасом, чтобы не
     перекрывать её пункты. С 780px (см. медиа-запрос ниже) такой
     навигации уже нет, там достаточно обычного отступа от края. */
  bottom: calc(var(--bottom-nav-height) + calc(16px * var(--density-scale)));
  display: flex;
  flex-direction: column;
  gap: calc(12px * var(--density-scale));
  /* Выше шапки/навигации (у них z-index: 10) — это простые ссылки, а не
     модальное окно, поэтому конфликтовать с интерактивными элементами
     страницы (полями форм, кнопками) им не с чего. */
  z-index: 50;
}

.contact-button {
  width: calc(52px * var(--density-scale));
  height: calc(52px * var(--density-scale));
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  box-shadow: var(--shadow-lift);
  transition: transform 0.15s ease;
}

.contact-button:hover {
  transform: scale(1.06);
}

.contact-button svg {
  width: calc(26px * var(--density-scale));
  height: calc(26px * var(--density-scale));
}

.contact-button-telegram {
  background: #0088cc;
}

.contact-button-whatsapp {
  background: #25d366;
}

/* "Написать в поддержку" — свой, акцентный цвет бренда сайта (а не цвет
   стороннего сервиса, как у Telegram/WhatsApp выше): это единственная
   кнопка в ряду, которая ведёт не наружу, а в наш собственный тикет-чат. */
.contact-button-support {
  background: var(--color-accent);
  /* Кнопка, а не <a> (см. index.html) — сбрасываем стандартные стили
     браузерной <button>, чтобы выглядела идентично соседним ссылкам. */
  border: none;
  cursor: pointer;
  font-family: inherit;
}

/* ================================================================
   МОДАЛКА "НАПИСАТЬ В ПОДДЕРЖКУ" (см. initSupportModal в app.js) —
   первое модальное окно на клиенте: до тикетов такого компонента в
   проекте не было вообще (обычные экраны — полностраничные view).
   ================================================================ */
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 14px;
  /* Выше .contact-buttons (z-index: 50) и шапки/навигации (z-index: 10) —
     модалка должна перекрывать буквально всё на экране, пока открыта. */
  z-index: 100;
}

.modal-card {
  position: relative;
  width: 100%;
  max-width: 400px;
  max-height: 90vh;
  overflow-y: auto;
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lift);
  padding: 24px 21px;
}

.modal-close-btn {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 28px;
  height: 28px;
  border: none;
  background: none;
  color: var(--color-text-muted);
  cursor: pointer;
  font-size: 20px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

.modal-close-btn:hover {
  color: var(--color-text);
}

.modal-card h2 {
  margin: 0 0 14px;
  font-size: 17px;
  padding-right: 21px; /* не наезжать на крестик закрытия */
}

/* Гостевая кнопка "Новые услуги" (new-services-widget.js) — обёртка нужна
   только для центрирования и отступа от карточки входа, сама кнопка несёт
   стили quick-link-btn/quick-link-btn-new (см. .quick-link-btn выше). */
.new-services-guest-wrapper {
  display: flex;
  justify-content: center;
  margin-bottom: 20px;
}

/* Список услуг в попапе "Новые услуги" (.modal-card переиспользуется как
   есть, см. .modal-backdrop/.modal-card выше — тот же паттерн, что и у
   support-modal.js). */
.new-services-modal-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.new-services-modal-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  padding: 9px 0;
}

.new-services-modal-row + .new-services-modal-row {
  border-top: 1px solid var(--color-border);
}

.new-services-modal-name {
  font-size: 13px;
  color: var(--color-text);
}

/* Цена — акцентный красный (тот же цвет, что и у свечения кнопки, см.
   .quick-link-btn-new) и покрупнее — сюда и должен упасть первый взгляд
   ("эффект очень низкой цены"). */
.new-services-modal-price {
  flex-shrink: 0;
  font-size: 14px;
  font-weight: 800;
  color: #ff3b30;
  white-space: nowrap;
}

/* ================================================================
   ЕДИНСТВЕННЫЙ брейкпоинт адаптивности — 780px. До 2026-08-14 здесь было
   два отдельных: 780px (только Баланс/Username в шапке) и 1367px
   (нижняя мобильная панель .app-nav + связанная с ней раскладка —
   выбран был исторически, чтобы держать iPad Pro 12.9", 1366px CSS-
   ширины, в мобильном виде). С тех пор верхнее меню и на 780px, и на
   1367px+ выглядит одинаково (бургер с сокращённым списком + Баланс/
   Username отдельно) — держать одновременно ещё и нижнюю панель ROUTES
   (Новый заказ/Заказы/Баланс, см. app.js) в диапазоне 780-1366px стало
   чистым дублированием той же навигации. Поэтому оба брейкпоинта слиты
   в один — 780px — и нижняя панель теперь скрывается с той же ширины,
   что и вынос Баланса/Username в шапку.
   ================================================================ */
@media (min-width: 780px) {
  /* Баланс/Username — начиная с этой ширины показываются отдельно в
     шапке (по умолчанию, для более узких ширин, .topbar-quick-item скрыт
     целиком, см. базовое правило выше) — а их дубли внутри бургера
     (.menu-item-quick-dup, см. getMenuItemsHtml в app.js), наоборот,
     прячутся, чтобы не показывались дважды. */
  .topbar-quick-item {
    display: inline-block;
  }

  .menu-item-quick-dup {
    display: none;
  }

  /* Без фиксированной нижней панели контенту больше не нужен запас снизу. */
  .app-content {
    padding: calc(32px * var(--density-scale)) calc(24px * var(--density-scale)) calc(40px * var(--density-scale));
  }

  /* Та же причина, что и у .app-content выше: .app-nav скрыт целиком
     (см. ниже), футеру больше не нужен отступ под несуществующую панель. */
  .app-shell .site-footer {
    margin-bottom: 0;
  }

  /* Нижней фиксированной навигации больше нет (см. .app-nav ниже) —
     кнопкам связи незачем подниматься так высоко над краем. */
  .contact-buttons {
    bottom: calc(24px * var(--density-scale));
  }

  /* С этой ширины навигация полностью переезжает в шапку (бургер +
     Баланс/Username отдельно, см. renderShell в app.js) — держать ещё и
     нижнюю панель избыточно и дублирует те же 3 пункта (Новый заказ/
     Заказы/Баланс, см. ROUTES в app.js), поэтому она скрывается целиком. */
  .app-nav {
    display: none;
  }
}
