/* Оформление каталога целиком своё, без библиотек: палитра, основа страницы, небольшой набор служебных
   классов для разметки и компоненты — кнопки, метки, списки, окна снизу, галерея, анкета, фильтры.
   Панель владельца оформлена отдельно (admin/) */

/* Имена и заголовки набраны шрифтом Prata (лицензия SIL OFL 1.1, её текст лежит рядом с файлами). Шрифт хранится
   у нас, с чужих серверов ничего не грузится. Латиницу страница запрашивает заранее (preload в index.html),
   кириллицу браузер скачивает, только когда этим шрифтом набран русский текст */
@font-face {
  font-family: "Prata"; font-style: normal; font-weight: 400; font-display: swap;
  src: url("vendor/fonts/prata-latin-400-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Prata"; font-style: normal; font-weight: 400; font-display: swap;
  src: url("vendor/fonts/prata-cyrillic-400-normal.woff2") format("woff2");
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

/* Палитра, одинаковая у всех клиентов: полуночный синий фон, золотой акцент, тёплый белый текст. Фон почти
   чёрный, но с синевой (так попросил заказчик): холодный глубокий синий оттеняет тёплое золото, а чистый чёрный
   сделал бы золото плоским. Фон, окна и плитки одного оттенка и отличаются только светлостью. Фон повторён
   в theme.js — при смене менять в обоих местах */
:root {
  color-scheme: dark;
  --bg: #090e1a;
  --bg-rgb: 9, 14, 26;
  --surface: #131a2a;
  --surface-2: #1b2438;
  --text: #f1eee8;
  --text-rgb: 241, 238, 232;
  --muted: rgba(var(--text-rgb), .66);
  --line: rgba(var(--text-rgb), .15);
  --gold: #c9a55c;
  --gold-rgb: 201, 165, 92;
  --gold-light: #e2c588;
  --on-gold: #17130a;
  --blue: #5b93ff;
  --green: #198754;
  /* Линия, которая сама несёт смысл (рамка плитки, дорожка ползунка): заметнее обычной разделительной */
  --line-strong: rgba(var(--text-rgb), .38);
  --blue-deep: #2b68d9;
  --radius: .5rem;
  /* Текст интерфейса — родной шрифт устройства: San Francisco, Segoe UI, Roboto. «Segoe UI» назван раньше
     system-ui: в Windows с китайским, японским или корейским языком системы system-ui даёт шрифт со слабой
     латиницей и кириллицей. Насыщенность только 400, 600 и 700: начертания 500 у Segoe UI нет */
  --font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji";
  /* Имена, города и заголовки окон. Пока Prata не загрузился (или не загрузился вовсе), работает шрифт
     с засечками самого устройства: New York, Georgia или Noto Serif */
  --font-display: "Prata", ui-serif, Georgia, "Noto Serif", serif;
  /* У Prata одно начертание. Вернуть всё к шрифту интерфейса: --font-display: var(--font-ui); --display-weight: 700 */
  --display-weight: 400;
  --fs-micro: .6875rem;   /* 11px: метки на фото в списке */
  --fs-caption: .8125rem; /* 13px: метки услуг, статусы, подписи параметров */
  --fs-name: 1.0625rem;   /* 17px: имя в списке */
  --fs-title: 1.375rem;   /* 22px: город в шапке, заголовки окон и пустых экранов */
  --fs-display: 1.625rem; /* 26px: имя в анкете, «Выберите город» */
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --app-width: 520px;
}

/* Основа страницы */

*, *::before, *::after { box-sizing: border-box; }
* { -webkit-tap-highlight-color: transparent; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  font-family: var(--font-ui);
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.5;
  color: var(--text);
  background: var(--bg);
  overflow-x: hidden;
}
h1, h2 { margin: 0 0 .5rem; font-weight: 600; line-height: 1.2; }
p { margin: 0 0 1rem; }
dl { margin: 0 0 1rem; }
a { color: var(--gold); }
img, svg { vertical-align: middle; }
button, input, select { margin: 0; font-family: inherit; font-size: inherit; line-height: inherit; }
button { border-radius: 0; text-transform: none; }
button:not(:disabled) { cursor: pointer; }
label { display: inline-block; }
small, .small { font-size: .875em; }
/* Служебные классы (d-flex и другие) идут с !important и стоят ниже: одиночный селектор им проигрывал, и скрытые
   шапка и кнопка города оставались на экране. Удвоенный селектор сильнее любого класса */
[hidden][hidden] { display: none !important; }

/* Каталог рассчитан на телефон: на широком экране остаётся той же колонкой по центру */
body, .page, .cta, .city-sheet, .gallery.zoom {
  max-width: var(--app-width);
  margin-inline: auto;
}
body.locked { overflow: hidden; }
/* Полосы прокрутки не рисуем (Telegram на компьютере): они ложились поверх фото и сдвигали список при открытии
   окна. Прокрутка колесом и пальцем работает как раньше */
html, .page, .offcanvas-body { scrollbar-width: none; }
html::-webkit-scrollbar, .page::-webkit-scrollbar, .offcanvas-body::-webkit-scrollbar { display: none; }
main { padding-bottom: calc(1.5rem + var(--safe-bottom)); }

/* Служебные классы разметки. Названия короткие и говорящие: d-flex — «в ряд», mt-3 — отступ сверху и т. д.
   Все с !important: класс в разметке должен побеждать правило компонента */

.d-block { display: block !important; }
.d-flex { display: flex !important; }
.flex-column { flex-direction: column !important; }
.flex-wrap { flex-wrap: wrap !important; }
.flex-grow-1 { flex-grow: 1 !important; }
.flex-shrink-0 { flex-shrink: 0 !important; }
.align-items-start { align-items: flex-start !important; }
.align-items-center { align-items: center !important; }
.align-items-baseline { align-items: baseline !important; }
.justify-content-between { justify-content: space-between !important; }
.justify-content-end { justify-content: flex-end !important; }
.gap-1 { gap: .25rem !important; }
.gap-2 { gap: .5rem !important; }
.gap-3 { gap: 1rem !important; }

.m-2 { margin: .5rem !important; }
.m-3 { margin: 1rem !important; }
.mt-1 { margin-top: .25rem !important; }
.mt-2 { margin-top: .5rem !important; }
.mt-3 { margin-top: 1rem !important; }
.mt-4 { margin-top: 1.5rem !important; }
.mt-auto { margin-top: auto !important; }
.mb-0 { margin-bottom: 0 !important; }
.mb-1 { margin-bottom: .25rem !important; }
.mb-2 { margin-bottom: .5rem !important; }
.mb-3 { margin-bottom: 1rem !important; }
.mb-4 { margin-bottom: 1.5rem !important; }
.ms-1 { margin-left: .25rem !important; }
.me-1 { margin-right: .25rem !important; }
.me-auto { margin-right: auto !important; }
.p-0 { padding: 0 !important; }
.px-0 { padding-inline: 0 !important; }
.px-3 { padding-inline: 1rem !important; }
.py-1 { padding-block: .25rem !important; }
.py-2 { padding-block: .5rem !important; }
.py-3 { padding-block: 1rem !important; }
.py-5 { padding-block: 3rem !important; }
.pt-0 { padding-top: 0 !important; }
.pt-1 { padding-top: .25rem !important; }
.pt-2 { padding-top: .5rem !important; }
.pt-3 { padding-top: 1rem !important; }
.pt-4 { padding-top: 1.5rem !important; }
.pb-0 { padding-bottom: 0 !important; }

.w-100 { width: 100% !important; }
.h-100 { height: 100% !important; }
.min-w-0 { min-width: 0; }
.overflow-hidden { overflow: hidden !important; }
.position-relative { position: relative !important; }
.position-absolute { position: absolute !important; }
.top-0 { top: 0 !important; }
.top-50 { top: 50% !important; }
.bottom-0 { bottom: 0 !important; }
.start-0 { left: 0 !important; }
.start-50 { left: 50% !important; }
.end-0 { right: 0 !important; }
.translate-middle { transform: translate(-50%, -50%) !important; }
.sticky-top { position: sticky; top: 0; z-index: 1020; }
.fixed-bottom { position: fixed; right: 0; bottom: 0; left: 0; z-index: 1030; }

.fs-6 { font-size: 1rem !important; }
.fw-normal { font-weight: 400 !important; }
.fw-semibold { font-weight: 600 !important; }
.text-center { text-align: center !important; }
.text-nowrap { white-space: nowrap !important; }
.text-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.text-break { word-wrap: break-word !important; word-break: break-word !important; }
.text-decoration-none { text-decoration: none !important; }
.text-reset { color: inherit !important; }
.text-body { color: var(--text) !important; }
.text-body-secondary { color: var(--muted) !important; }
.text-success { color: var(--green) !important; }
.text-gold { color: var(--gold-light); }
.ws-pre-line { white-space: pre-line; }

/* Роли шрифта. Имя девушки, город и заголовки экранов набраны шрифтом с засечками; цены, параметры, метки
   и кнопки — шрифтом интерфейса. Поддельный жирный не рисуем: у Prata одно начертание */
.t-display, .t-title, .offcanvas-title, .model-title, .no-photo {
  font-family: var(--font-display); font-weight: var(--display-weight); font-synthesis: none; font-variant-numeric: lining-nums;
}
.t-display { font-size: var(--fs-display); line-height: 1.2; text-wrap: balance; }
.t-title, .offcanvas-title { font-size: var(--fs-title); line-height: 1.25; text-wrap: balance; }
/* Мелкая строка прописными над заголовком: город над именем в анкете */
.eyebrow { margin-bottom: .25rem; font-size: .75rem; font-weight: 600; line-height: 1.3; letter-spacing: .09em; text-transform: uppercase; color: var(--muted); }
/* Цифры одной ширины: цены, счётчик фото и значения фильтров не дёргаются, когда меняются */
.price, .rate-price, .facts dd, .counter, [data-value] { font-variant-numeric: tabular-nums; }
/* Последняя строка не из одного слова там, где браузер это умеет */
#listEnd p, #empty p, #failed p { text-wrap: balance; }

.bg-body { background-color: var(--bg) !important; }
.bg-body-secondary { background-color: var(--surface-2) !important; }
.bg-body-tertiary { background-color: var(--surface) !important; }
.bg-transparent { background-color: transparent !important; }
.border-0 { border: 0 !important; }
.border-top { border-top: 1px solid var(--line) !important; }
.border-bottom { border-bottom: 1px solid var(--line) !important; }
.rounded { border-radius: var(--radius) !important; }
.rounded-3 { border-radius: .75rem !important; }
.rounded-circle { border-radius: 50% !important; }
.rounded-pill { border-radius: 50rem !important; }
.rounded-top-4 { border-top-left-radius: 1rem !important; border-top-right-radius: 1rem !important; }
.visually-hidden {
  position: absolute !important; width: 1px !important; height: 1px !important; padding: 0 !important; margin: -1px !important;
  overflow: hidden !important; clip: rect(0, 0, 0, 0) !important; white-space: nowrap !important; border: 0 !important;
}

/* Сетка списка: ряд из колонок с промежутками. row-cols-2 — по две в ряд, col-N — ширина в двенадцатых долях */
.row { --gutter: 1.5rem; display: flex; flex-wrap: wrap; margin: calc(-1 * var(--gutter)) calc(-.5 * var(--gutter)) 0; }
.row > * { flex-shrink: 0; width: 100%; max-width: 100%; padding-inline: calc(var(--gutter) * .5); margin-top: var(--gutter); }
.g-3 { --gutter: 1rem; }
.col { flex: 1 0 0%; }
.row-cols-2 > * { flex: 0 0 auto; width: 50%; }
.col-5 { flex: 0 0 auto; width: 41.66666667%; }
.col-7 { flex: 0 0 auto; width: 58.33333333%; }
.col-8 { flex: 0 0 auto; width: 66.66666667%; }
.col-9 { flex: 0 0 auto; width: 75%; }
.col-10 { flex: 0 0 auto; width: 83.33333333%; }
.col-12 { flex: 0 0 auto; width: 100%; }
/* Блок с заданным соотношением сторон: содержимое растянуто на всю его площадь */
.ratio { position: relative; width: 100%; }
.ratio::before { content: ""; display: block; padding-top: var(--ratio, 100%); }
.ratio > * { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }

/* Кнопки */

.btn {
  display: inline-block; padding: .375rem .75rem; border: 1px solid transparent; border-radius: var(--radius); background: transparent;
  color: var(--text); font-size: 1rem; font-weight: 400; line-height: 1.5; text-align: center; text-decoration: none; vertical-align: middle;
  user-select: none; transition: background-color .15s ease-in-out, border-color .15s ease-in-out;
}
/* Рамка фокуса с клавиатуры — сплошная и светлая: полупрозрачная золотая терялась на тёмном фоне */
.btn:focus-visible { outline: 2px solid var(--gold-light); outline-offset: 2px; }
.btn-overlay:focus-visible { outline-color: #fff; }
.btn:disabled { opacity: .65; pointer-events: none; }
/* Высота под палец: крупные кнопки 50px */
.btn-lg { padding: .75rem 1rem; border-radius: .75rem; font-size: 1.25rem; }
/* Главная кнопка — золотой акцент экрана */
.btn-primary { border-color: var(--gold); background: var(--gold) linear-gradient(135deg, var(--gold-light), var(--gold)); color: var(--on-gold); }
.btn-primary:active { filter: brightness(.9); }
/* Нейтральная кнопка: значки в шапке, второстепенные действия. Нажатая — рамка и лёгкая заливка золотом */
.btn-soft { border-color: var(--line); background: var(--surface); }
.btn-soft:active { border-color: var(--gold); background: rgba(var(--gold-rgb), .14); }
/* Круглая кнопка поверх фото */
.btn-overlay {
  display: inline-flex; align-items: center; justify-content: center; width: 2.75rem; height: 2.75rem; padding: 0;
  background: rgba(0, 0, 0, .45); color: #fff; font-size: 1.25rem; line-height: 1;
}
.btn-overlay:active { background: rgba(0, 0, 0, .65); }
/* Крестик закрытия окна */
.btn-close {
  box-sizing: content-box; width: 1em; height: 1em; padding: .25em; border: 0; border-radius: var(--radius); opacity: .5;
  background: transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M18 6 6 18'/%3e%3cpath d='m6 6 12 12'/%3e%3c/svg%3e") center / 1.375em auto no-repeat;
}
/* Квадратная кнопка со значком в шапке: фильтры и язык */
.icon-btn { display: inline-flex; align-items: center; justify-content: center; flex: none; width: 2.75rem; height: 2.75rem; padding: 0; font-size: 1.125rem; }
.filter-dot { position: absolute; top: .4rem; right: .4rem; width: .5rem; height: .5rem; border-radius: 50%; background: var(--gold); }
/* Значки Lucide: контур в цвет текста. Нажатие всегда достаётся кнопке, а не значку внутри неё */
.icon {
  flex: none; width: 1em; height: 1em; vertical-align: -.125em; pointer-events: none;
  fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
.icon-solid { fill: currentColor; }

/* Метки */

.badge {
  display: inline-block; padding: .35em .65em; border-radius: var(--radius); color: #fff; font-size: var(--fs-caption); font-weight: 600; line-height: 1;
  text-align: center; white-space: nowrap; vertical-align: baseline;
}
.text-bg-success { background-color: var(--green) !important; color: #fff !important; }
.text-bg-dark { background-color: rgba(33, 37, 41, var(--bg-opacity, 1)) !important; color: #fff !important; }
.bg-opacity-75 { --bg-opacity: .75; }
/* «Новенькая» — синяя, чтобы не спорить с золотым VIP */
/* Синий темнее, чем у галочки: белый текст на светлом синем читался плохо */
.badge-new { background: var(--blue-deep); color: #fff; }
/* VIP — тем же золотом, что и весь каталог */
.vip-badge { background: linear-gradient(135deg, var(--gold-light), var(--gold)); color: var(--on-gold); font-weight: 700; letter-spacing: .08em; }
.now-dot { display: inline-block; width: .5em; height: .5em; margin-right: .375em; border-radius: 50%; background: currentColor; vertical-align: .08em; }
/* «Проверено» — синяя галочка: так её привыкли видеть */
/* Галочка приклеена к имени отступом, а не пробелом: по пробелу Safari переносил её на отдельную строку */
.verified { margin-left: .3em; color: var(--blue); white-space: nowrap; }
/* Имя в анкете не выравнивается по строкам (balance): в Safari из-за него галочка уезжала под короткое имя */
.product-name { text-wrap: wrap; }
/* Рядом с крупным именем в открытой анкете галочка чуть меньше букв, иначе она спорит с именем */
h1 .verified .icon { width: .7em; height: .7em; vertical-align: 0; }

/* Сообщение над списком и в анкете */
.alert { position: relative; padding: 1rem; margin-bottom: 1rem; border: 1px solid transparent; border-radius: var(--radius); }

/* Заготовки на время загрузки: серые блоки с мерцанием */
.placeholder { display: inline-block; min-height: 1em; vertical-align: middle; cursor: wait; background-color: currentcolor; opacity: .5; }
.placeholder-glow .placeholder { animation: placeholder-glow 2s ease-in-out infinite; }
@keyframes placeholder-glow { 50% { opacity: .2; } }
/* Заготовка той же высоты, что строка имени: сетка не прыгает, когда приходит каталог */
.skel-title { min-height: 1.375rem; }
.skel-price { min-height: 1.5rem; }

/* Поля */

.form-label { margin-bottom: .5rem; }
.form-text { margin-top: .25rem; font-size: .875em; color: var(--muted); }
.form-select {
  display: block; width: 100%; min-height: 2.75rem; padding: .5625rem 2.25rem .5625rem .75rem; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--bg) url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23dee2e6' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='m6 9 6 6 6-6'/%3e%3c/svg%3e") right .625rem center / 1.125rem no-repeat;
  color: var(--text); font-size: 1rem; font-weight: 400; line-height: 1.5; -webkit-appearance: none; appearance: none;
}
.form-select:focus { outline: 0; border-color: var(--gold); }
.form-select:focus-visible { outline: 2px solid var(--gold-light); outline-offset: 1px; }

/* Шапка: логотип, фильтры, язык */

.brand { min-width: 0; min-height: 2.75rem; }
/* Высота логотипа 36px, а у файла точек втрое больше: он чёткий и на экранах с тройной плотностью.
   В совсем узкой шапке логотип уменьшается целиком, а не обрезается */
.brand-logo { display: block; width: auto; height: 2.25rem; max-width: 100%; object-fit: contain; object-position: left center; }
.dropdown-toggle { white-space: nowrap; }
.dropdown-toggle::after {
  content: ""; display: inline-block; margin-left: .255em; vertical-align: .255em;
  border-top: .3em solid; border-right: .3em solid transparent; border-bottom: 0; border-left: .3em solid transparent;
}

/* Список языков открывается под кнопкой с глобусом */
.dropdown, .lang-menu { position: relative; }
.dropdown-menu {
  position: absolute; top: calc(100% + .25rem); right: 0; z-index: 1000; display: none; min-width: 9rem; padding: .5rem 0; margin: 0;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); color: var(--text); font-size: 1rem; text-align: left;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .5);
}
.dropdown-menu.show { display: block; }
.dropdown-item {
  display: flex; align-items: center; justify-content: space-between; gap: .75rem; width: 100%; padding: .625rem 1rem; border: 0; border-radius: 0; background: transparent; color: var(--text);
  font-weight: 400; text-align: inherit; text-decoration: none; white-space: nowrap;
}
.dropdown-item.active { background: rgba(var(--gold-rgb), .14); color: var(--gold-light); font-weight: 600; }

/* Карточки анкет в списке */

.card { position: relative; display: flex; flex-direction: column; min-width: 0; word-wrap: break-word; border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg); color: var(--text); }
.card-body { flex: 1 1 auto; padding: 1rem; }
.card-title { margin-bottom: .5rem; }
.card-text:last-child { margin-bottom: 0; }
.model-photo { --ratio: 133.3333%; }
/* Единая рамка для всех фото и заставок видео: вертикальная 3:4 в списке и в анкете. Снимок любого размера
   заполняет её целиком и обрезается по краям; обрезка держится ближе к верху кадра, чтобы не срезать лицо.
   Целиком, без обрезки, фото открывается нажатием */
.model-photo img, .slide img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: center 25%; transition: filter .2s ease-out; }
/* Тонкая золотая рамка у фото и золотая цена */
.model-card .model-photo { transition: opacity .12s ease-out; }
/* Рамка нарисована поверх фото, а не вокруг него: у обычной border на скруглённых углах сквозь сглаживание
   просвечивал светлый снимок, и линия выглядела размытой */
.model-card .model-photo::after { content: ""; position: absolute; inset: 0; border: 1px solid var(--gold); border-radius: inherit; pointer-events: none; }
.model-card .price { color: var(--gold-light); font-size: 1rem; font-weight: 600; }
/* Метки на фото в списке мельче обычных: в колонке в пол-экрана крупные закрывали заметную часть снимка */
.model-photo .badge { padding: .3em .6em; font-size: var(--fs-micro); }
/* Значок видео — залитый треугольник «Воспроизвести» в круглой плашке. Он крупнее текста меток и чуть сдвинут
   вправо: у треугольника визуальный центр левее геометрического */
.model-photo .video-badge { display: inline-flex; align-items: center; justify-content: center; width: 1.375rem; height: 1.375rem; padding: 0 0 0 .0625rem; font-size: .625rem; line-height: 1; }
.model-out .model-photo img, .model-out .no-photo { opacity: .5; }
/* У карточек меняется прозрачность, а не размер: иначе сетка дёргается, когда прокрутка начинается с карточки */
.model-card:active .model-photo { opacity: .85; }
/* Имя в одну строку: длинное обрезается многоточием, возраст и галочка остаются на виду */
.model-title { display: flex; align-items: baseline; min-width: 0; font-size: var(--fs-name); line-height: 1.3; letter-spacing: .01em; }
.model-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.model-age, .model-title .verified { flex: none; }
.model-title .verified .icon { width: .9em; height: .9em; vertical-align: -.06em; }
/* На экране обычной плотности (Telegram на компьютере) тонкие штрихи Prata в мелком имени рвутся: там имя
   в списке набрано шрифтом интерфейса. На телефонах плотность выше, и это правило не срабатывает */
@media (max-resolution: 1.49dppx) {
  .model-title { font-family: var(--font-ui); font-size: 1rem; font-weight: 600; line-height: 1.25; letter-spacing: 0; }
  .model-title .verified .icon { width: 1em; height: 1em; vertical-align: -.125em; }
}
/* Заглушка вместо фото: приглушённая, в тон палитре; оттенок зависит от анкеты, чтобы карточки различались.
   Буква — тем же шрифтом, что и имена: читается как монограмма, а не как пустой аватар */
.no-photo {
  display: grid; place-items: center; width: 100%; height: 100%; font-size: 4rem; line-height: 1;
  background: linear-gradient(135deg, hsl(var(--hue) 25% 22%), hsl(calc(var(--hue) + 15) 30% 13%)); color: rgba(var(--gold-rgb), .85);
}
/* Конфиденциальность: размытое фото и подсказка поверх него */
.blur-img { filter: blur(16px) saturate(.85); transform: scale(1.06); }
.blur-note { pointer-events: none; max-width: 90%; text-align: center; }

/* Анкета открывается поверх списка, выше закреплённой шапки */

.page { position: fixed; inset: 0; z-index: 1035; overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; }
/* Анкета получает фокус при открытии, рамка вокруг всего экрана при этом не нужна */
.page:focus { outline: 0; }
/* Кнопка «назад» вне Telegram висит и над фото, и над текстом: плотная подложка с размытием, чтобы текст под ней
   не просвечивал */
.back-btn {
  position: fixed; top: .75rem; left: max(.75rem, calc(50% - var(--app-width) / 2 + .75rem)); z-index: 2;
  border-color: var(--line); background: rgba(var(--bg-rgb), .78); -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
}

.gallery { position: relative; }
.slides { display: flex; overflow-x: auto; overscroll-behavior-x: contain; scroll-snap-type: x mandatory; scrollbar-width: none; }
.slides::-webkit-scrollbar { display: none; }
/* Высота ограничена, чтобы имя и метки попадали на первый экран */
.slide {
  flex: none; width: 100%; aspect-ratio: 3 / 4; max-height: 62vh; scroll-snap-align: start;
  /* Быстрый взмах листает одно фото, а не пролетает несколько */
  scroll-snap-stop: always;
  background: var(--surface) center 25% / cover no-repeat;
  /* Подсказка размытого фото стоит по центру своего слайда, а размытие не залезает на соседний */
  position: relative; overflow: hidden;
}
/* На совсем низких экранах фото ещё ниже, иначе имя уходит под нижнюю панель. Порог низкий намеренно: при
   высоте окна 500–640 рамка от такого правила становилась шире своей высоты и резала портрет в полоску */
/* У недоступной анкеты под именем ещё и пояснение: фото ниже на его высоту */
.gallery-out .slide { max-height: calc(62vh - 5rem); }
/* У анкеты без фото рамка низкая: буква на пол-экрана только отодвигала бы имя и параметры */
.gallery-empty .slide { aspect-ratio: 16 / 9; max-height: 30vh; }
@media (max-height: 460px) {
  .slide { max-height: 48vh; }
  .gallery-out .slide { max-height: calc(48vh - 5rem); }
}

/* Видео в галерее: заставка с кнопкой, по нажатию её сменяет проигрыватель */
.video-play {
  position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: .5rem; padding: 1rem; border: 0; background: transparent; color: #fff;
}
.video-icon {
  display: flex; align-items: center; justify-content: center; width: 4rem; height: 4rem;
  border-radius: 50%; background: rgba(0, 0, 0, .55); font-size: 2rem;
}
/* Треугольник «воспроизвести» зрительно тяжелее слева: сдвигаем, чтобы в круге он стоял по центру */
.video-icon .icon { transform: translateX(6%); }
.video-note { padding: .25rem .75rem; border-radius: 1rem; background: rgba(0, 0, 0, .6); font-size: .875rem; }
.video-note:empty { display: none; }
.slide.started .video-play, .slide.started img { display: none; }
.slide video { display: block; width: 100%; height: 100%; object-fit: contain; background: #000; }
/* Пока идёт видео, счётчик и точки не должны лежать на его кнопках */
.gallery:has(.slide.started) .dots, .gallery:has(.slide.started) .counter { display: none; }

/* Стрелки и точки: видно, что фото несколько. Стрелки нужны только мыши: на сенсорном экране фото
   листают жестом, и стрелки лишь закрывали бы снимок */
.gallery-nav {
  position: absolute; top: 50%; z-index: 1; width: 2.5rem; height: 2.5rem; padding: 0;
  font-size: 1.125rem; line-height: 1; transform: translateY(-50%);
}
@media (hover: none), (pointer: coarse) {
  .gallery-nav { display: none; }
}
.gallery-prev { left: .5rem; }
.gallery-next { right: .5rem; }
/* Точки лежат на тёмной подложке, иначе на светлом фото их не видно; стоят на одной линии со счётчиком */
.dots {
  position: absolute; left: 50%; bottom: 1rem; transform: translateX(-50%); display: flex; align-items: center; gap: .375rem;
  height: 1.5rem; padding: 0 .5rem; border-radius: 1rem; background: rgba(0, 0, 0, .45); pointer-events: none;
}
.dot { width: .375rem; height: .375rem; border-radius: 50%; background: rgba(255, 255, 255, .45); }
.dot.active { background: #fff; }
.gallery.zoom .dots { bottom: calc(1rem + var(--safe-bottom)); }

/* Фото на весь экран, без обрезки. Слой выше нижней панели с кнопкой */
.gallery.zoom { position: fixed; inset: 0; z-index: 1031; background: #000; }
.gallery.zoom .slides { height: 100%; }
.gallery.zoom .slide { aspect-ratio: auto; max-height: none; height: 100%; background: #000 !important; }
/* Целиком и по центру, даже если в рамке владелец сдвинул кадр */
.gallery.zoom .slide img { object-fit: contain; object-position: center !important; transition: none; }
/* Увеличенное фото двигается пальцем: галерея под ним не листается, браузер жесты себе не забирает */
.gallery.zoom .slides { touch-action: pan-x; }
.gallery.zoomed .slides { overflow-x: hidden; touch-action: none; }
.gallery.zoom .gallery-tag { display: none; }
.gallery.zoom .counter { margin-bottom: calc(1rem + var(--safe-bottom)) !important; }
.zoom-close { display: none; position: absolute; top: .5rem; right: .5rem; }
.gallery.zoom .zoom-close { display: inline-flex; }

.product-body { padding-bottom: calc(6.5rem + var(--safe-bottom)); overflow-wrap: break-word; }
/* Длинный текст «О себе» чуть спокойнее основного: самыми яркими остаются имя и цены */
.about { color: rgba(var(--text-rgb), .88); text-wrap: pretty; }
/* Метки анкеты: услуги, формат встречи, способы оплаты. Залитая с галочкой — «есть, включено», контурная —
   «по договорённости». Они не нажимаются, поэтому не похожи на кнопки; длинное название переносится */
.tag {
  display: inline-flex; align-items: center; gap: .375rem; max-width: 100%; min-height: 2rem; padding: .3125rem .75rem;
  border: 1px solid transparent; border-radius: 1rem; background: var(--surface-2); color: var(--text);
  font-size: var(--fs-caption); line-height: 1.25; overflow-wrap: anywhere;
}
.tag .icon { color: var(--gold); font-size: .75rem; stroke-width: 2.5; }
.tag-outline { border-color: rgba(var(--text-rgb), .22); background: transparent; color: rgba(var(--text-rgb), .86); }
.tag-price { color: var(--gold-light); white-space: nowrap; }
/* Длинный текст «О себе» свёрнут до шести строк; кнопка под ним раскрывает его целиком */
.about.clamped { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 6; overflow: hidden; }
/* Высота под палец остаётся, а лишнее поле снизу уходит в отступ следующего раздела: иначе после «Read more» зазор вдвое больше, чем между разделами */
.more-btn { min-height: 2.75rem; margin-bottom: -.75rem; padding: 0; border: 0; background: none; color: var(--muted); font-size: var(--fs-caption); font-weight: 400; }
/* Заголовки разделов анкеты: мелкие золотые прописные */
.section-title { font-size: .75rem; font-weight: 600; letter-spacing: .09em; text-transform: uppercase; color: var(--gold); }
/* Параметры анкеты: ровная сетка, подпись над значением */
/* Три колонки на любом телефоне: с «сколько поместится» на экране 360px выходило две, и сетка разваливалась */
.facts { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .75rem .5rem; }
@media (max-width: 21.5rem) {
  .facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.facts dt { font-size: var(--fs-caption); font-weight: 400; color: var(--muted); -webkit-hyphens: auto; hyphens: auto; }
.facts dd { margin: 0; font-weight: 600; -webkit-hyphens: auto; hyphens: auto; }

/* Тарифы — плитки: срок мелкими прописными, под ним цена, в углу кружок выбора — видно, что это выбор, а не
   ещё один набор меток. Сетка из шести долей: три плитки в ряду занимают по две доли, две — по три, а неполный
   последний ряд делит всю ширину поровну, без пустого места. Выбор читается из самой радиокнопки (input:checked),
   поэтому работает в любом браузере */
.rates { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: .5rem; }
.rate { grid-column: span 2; display: flex; cursor: pointer; }
.rates[data-cols="2"] .rate { grid-column: span 3; }
.rates[data-cols="1"] .rate, .rates[data-cols="2"] .rate:last-child:nth-child(odd), .rates[data-cols="3"] .rate:last-child:nth-child(3n + 1) { grid-column: 1 / -1; }
.rates[data-cols="3"] .rate:nth-last-child(2):nth-child(3n + 1), .rates[data-cols="3"] .rate:nth-last-child(2):nth-child(3n + 1) + .rate { grid-column: span 3; }
.rate-box {
  position: relative; flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .25rem;
  min-height: 4.75rem; padding: .875rem .375rem .625rem; border: 1px solid var(--line-strong); border-radius: .75rem; background: var(--surface);
  text-align: center; transition: border-color .15s ease-out, background-color .15s ease-out;
}
.rate-box::after {
  content: ""; position: absolute; top: .4375rem; right: .4375rem; width: .875rem; height: .875rem; border: 1.5px solid var(--line-strong); border-radius: 50%;
}
.rate-name { max-width: 100%; font-size: .75rem; line-height: 1.3; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); overflow-wrap: anywhere; }
.rate-price { font-size: 1.0625rem; font-weight: 600; line-height: 1.2; white-space: nowrap; }
.rate:active .rate-box { background: var(--surface-2); }
/* Вторая линия рамки идёт внутрь плитки: снаружи между рамкой и тенью на скруглённых углах просвечивал тёмный фон */
.rate input:checked + .rate-box { border-color: var(--gold); background: rgba(var(--gold-rgb), .14); box-shadow: inset 0 0 0 1px var(--gold); }
.rate input:checked + .rate-box::after {
  border-color: var(--gold);
  background: var(--gold) url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2317130a' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M20 6 9 17l-5-5'/%3e%3c/svg%3e") center / .5625rem no-repeat;
}
.rate input:checked + .rate-box .rate-name, .rate input:checked + .rate-box .rate-price { color: var(--gold-light); }
.rate input:focus-visible + .rate-box { outline: 2px solid var(--gold-light); outline-offset: 3px; }

/* Нижняя панель: кнопка связи. Текст уходит под неё плавно, а не обрезается чертой */
.cta { padding: .75rem 1rem calc(.75rem + var(--safe-bottom)); background: var(--bg); }
.cta::before {
  content: ""; position: absolute; left: 0; right: 0; bottom: 100%; height: 1.25rem; pointer-events: none;
  background: linear-gradient(to top, var(--bg), rgba(var(--bg-rgb), 0));
}

/* Окна снизу: выбор города и фильтры. Высота по содержимому, но не выше экрана. Открываются классом show */

.offcanvas {
  position: fixed; right: 0; bottom: 0; left: 0; z-index: 1045; display: flex; flex-direction: column; max-height: 88vh; max-height: 88dvh;
  padding-bottom: var(--safe-bottom); background: var(--surface); color: var(--text);
  /* Окно приподнято над страницей: свой цвет, светлая кромка по скруглённому верху и тень */
  box-shadow: inset 0 1px 0 rgba(var(--text-rgb), .14), 0 -12px 32px rgba(0, 0, 0, .5);
  outline: 0; visibility: hidden; transform: translateY(100%); transition: transform .3s ease-in-out;
}
.offcanvas.show { visibility: visible; transform: none; }
/* Полоска-ручка: за неё и за шапку окно тянут вниз, чтобы закрыть */
.sheet-grabber { flex: none; padding: .5rem 0 .125rem; touch-action: none; }
.sheet-grabber::before { content: ""; display: block; width: 2.25rem; height: .25rem; margin: 0 auto; border-radius: 2px; background: rgba(var(--text-rgb), .3); }
.offcanvas.dragging { transition: none; }
.offcanvas-header { display: flex; align-items: center; padding: .5rem 1rem 1rem; touch-action: none; }
.offcanvas-title { margin-bottom: 0; }
/* Строки берут цвет окна; выбранная сохраняет свою золотую заливку */
.offcanvas-body { flex-grow: 1; padding: 1rem; overflow-y: auto; }
.city-sheet .btn-close { padding: .875rem; margin: -.625rem -.625rem -.625rem auto; }
.offcanvas-backdrop { position: fixed; top: 0; left: 0; z-index: 1040; width: 100vw; height: 100vh; background: #000; opacity: 0; transition: opacity .15s linear; }
.offcanvas-backdrop.show { opacity: .6; }
/* Скрытая подложка и закрытое окно не должны ловить нажатия */
.offcanvas-backdrop:not(.show), .offcanvas:not(.show) { pointer-events: none; }
/* Закрытое окно прячем только после того, как оно уедет вниз, иначе оно исчезало бы мгновенно */
@media (prefers-reduced-motion: no-preference) {
  .offcanvas:not(.show) { transition: transform .3s ease-in-out, visibility 0s linear .3s; }
}

/* Окно фильтров. Ползунок с двумя ручками собран из двух обычных, наложенных друг на друга: нажатия ловят
   только ручки, а закрашенный отрезок между ними рисует подложка */
.filter-footer { padding: .75rem 1rem; }
.range2 { position: relative; height: 2.75rem; --from: 0%; --to: 100%; }
.range2::before {
  content: ""; position: absolute; left: 1.375rem; right: 1.375rem; top: 50%; height: 4px; margin-top: -2px; border-radius: 2px;
  background: linear-gradient(to right, var(--line-strong) var(--from), var(--gold) var(--from), var(--gold) var(--to), var(--line-strong) var(--to));
}
.range2 input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; background: none; pointer-events: none; -webkit-appearance: none; appearance: none; }
.range2 input:focus { outline: 0; }
.range2 input::-webkit-slider-runnable-track { height: 100%; background: none; }
.range2 input::-moz-range-track { background: none; }
/* Ручка ловит палец на площади 44 на 44, а нарисована кружком 24: попасть легко, выглядит аккуратно */
.range2 input::-webkit-slider-thumb {
  -webkit-appearance: none; pointer-events: auto; width: 2.75rem; height: 2.75rem; margin-top: 0; border: 0; border-radius: 50%; cursor: grab;
  background: radial-gradient(circle, var(--gold-light) 0 10px, var(--surface) 10.5px 12px, var(--gold) 12.5px 13px, transparent 13.5px);
}
.range2 input::-moz-range-thumb {
  pointer-events: auto; width: 2.75rem; height: 2.75rem; border: 0; border-radius: 50%; cursor: grab;
  background: radial-gradient(circle, var(--gold-light) 0 10px, var(--surface) 10.5px 12px, var(--gold) 12.5px 13px, transparent 13.5px);
}
.range2 input:focus-visible::-webkit-slider-thumb {
  background: radial-gradient(circle, var(--gold-light) 0 10px, var(--surface) 10.5px 12px, var(--gold) 12.5px 13px, rgba(var(--gold-rgb), .45) 13.5px 17px, transparent 17.5px);
}

/* Появление анкеты. Сам .page не сдвигаем: внутри него закреплённая нижняя панель */
@keyframes fade-in { from { opacity: 0; } }
@keyframes rise-in { from { opacity: 0; transform: translateY(16px); } }
.page:not([hidden]) { animation: fade-in .18s ease-out; }
.page.entering .product-body { animation: rise-in .26s cubic-bezier(.2, .8, .2, 1); }

/* Тем, кто просил систему убавить движение, — без анимаций и переходов */
@media (prefers-reduced-motion: reduce) {
  .page:not([hidden]), .page.entering .product-body, .placeholder-glow .placeholder { animation: none; }
  .btn, .offcanvas, .offcanvas-backdrop, .model-card .model-photo, .model-photo img, .slide img, .rate-box { transition: none; }
}

/* Отклик на наведение мыши (Telegram на компьютере). На сенсорном экране наведения нет, и такие правила там
   «залипали» бы после касания */
@media (hover: hover) and (pointer: fine) {
  .btn-soft:hover, .rate:hover .rate-box { border-color: rgba(var(--gold-rgb), .5); }
  .btn-primary:hover { filter: brightness(1.06); }
  .btn-close:hover { opacity: .85; }
  .model-card:hover .model-photo { opacity: .92; }
  .dropdown-item:hover { background: var(--surface-2); }
}

/* ТЕСТ: промо-блок Monthly Pass над списком (см. PROMO_TEST в app.js) */
.promo {
  position: relative; display: grid; gap: .625rem; padding: .875rem; border-radius: .75rem;
  background: radial-gradient(120% 90% at 100% 0%, rgba(var(--gold-rgb), .16), transparent 60%), var(--surface);
}
/* Золотая рамка поверх блока, как у фото анкет */
.promo::after { content: ""; position: absolute; inset: 0; border: 1px solid rgba(var(--gold-rgb), .55); border-radius: inherit; pointer-events: none; }
.promo-head { display: flex; align-items: center; justify-content: space-between; gap: .75rem; }
.promo-title { margin: 0; font-family: var(--font-display); font-size: 1.375rem; font-weight: var(--display-weight); font-synthesis: none; line-height: 1.2; color: var(--gold-light); }
.promo-save {
  flex: none; padding: .25rem .625rem; border-radius: 2rem; background: var(--gold) linear-gradient(135deg, var(--gold-light), var(--gold));
  color: var(--on-gold); font-size: .75rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
}
.promo-common { margin: -.375rem 0 .125rem; color: var(--muted); font-size: var(--fs-caption); }
/* Варианты — как плитки тарифов: выбранный обведён золотом и подсвечен */
.promo-tiers { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .5rem; }
.promo-tier {
  min-height: 2.5rem; padding: .25rem; border: 1px solid var(--line-strong); border-radius: .625rem; background: transparent;
  color: var(--muted); font: inherit; font-size: .8125rem; white-space: nowrap;
}
/* Число часов — шрифтом имён, но чуть крупнее подписи: в большом размере единица Prata походила на «l» */
.promo-tier b { font-family: var(--font-display); font-size: 1.0625rem; font-weight: var(--display-weight); font-synthesis: none; font-variant-numeric: lining-nums; color: var(--text); }
.promo-tier[aria-checked="true"] { border-color: var(--gold); background: rgba(var(--gold-rgb), .14); box-shadow: inset 0 0 0 1px var(--gold); color: var(--gold-light); }
.promo-tier[aria-checked="true"] b { color: var(--gold-light); }
.promo-tier:focus-visible { outline: 2px solid var(--gold-light); outline-offset: 2px; }
/* Преимущества варианта: строки с галочкой, всегда на две строки — блок не прыгает при переключении */
/* Если преимущества занимают одну строку, она стоит посередине отведённого места: пустота делится поровну над и
   под ней, а не копится перед кнопкой */
.promo-perks { display: flex; flex-wrap: wrap; align-content: center; gap: .25rem .875rem; min-height: 2.75rem; margin: 0; padding: 0; list-style: none; font-size: .8125rem; line-height: 1.5; }
.promo-perks li { display: inline-flex; align-items: center; gap: .375rem; }
.promo-perks .icon { color: var(--gold); }
