/* Язык интерфейса: белое поле, весь текст одного размера, квадратные фото,
   один розовый акцент. Ничего не кричит — говорят фотографии.

   Правила, которые держат этот файл в узде:
   · цвет берётся только из переменной. Тёмная тема ниже переопределяет токены
     и ни одного правила — иначе через месяц половина экранов останется белой;
   · размер текста один — 15px. Крупнее только два заголовка, мельче только
     подпись. Третий размер сюда не добавлять, он сразу читается как чужой;
   · движение короткое и в одну сторону: вперёд справа, назад слева. Всё, что
     длиннее 380мс, воспринимается как тормоз, а не как анимация.

   Классы делятся на три семьи, и ломать их нельзя: ядро (.scr, .head, .nav),
   прототип (.slotcard, .fieldbox, .soc-word) и старый фронт (.item, .card,
   .fab). Все три живут в разных файлах у разных владельцев. */

:root {
  --bg: #FFFFFF;          /* поле: под ним ничего нет */
  --card: #FFFFFF;        /* поднятое: нижнее меню, шапка */
  --ink: #121212;         /* имя, заголовок, цифра */
  --text: #4D4D4D;        /* основной текст */
  --muted: #878787;       /* подпись, второстепенное */
  --hair: #EDEDED;        /* линия между строками списка */
  --soft: #F2F2F2;        /* заливка карточки, поля, вторичной кнопки */
  --pink: #FF3BA0;
  --pink-p3: #FF3BA0;     /* тот же розовый; звонче он становится в @supports ниже */
  --on-pink: #000000;     /* по розовому пишем чёрным: белое на нём слепнет */
  --skel: #E8E8E8;        /* блик скелета — единственный цвет светлее поля */

  /* Старые имена тех же токенов: их пишут прямо в разметке scr-*.js, и
     переименование молча покрасило бы половину плашек в прозрачное. */
  --strong: var(--ink);
  --fill: var(--soft);

  --t-tap: 120ms;         /* отклик на палец */
  --t-base: 220ms;        /* смена состояния */
  --t-in: 420ms;          /* появление */
  --t-scr: 380ms;         /* переход экрана; та же цифра лежит в core.js: ANIM */
  --e: cubic-bezier(.2,.7,.2,1);

  --r: 24px;              /* кнопки и пилюли */
  --r-box: 8px;           /* карточки и поля */

  /* Onest не грузим со стороны: мини-апп открывается в метро, и один внешний
     файл шрифта стоит дороже, чем разница в начертании. Если он есть в
     системе — возьмётся он, нет — системный гротеск. */
  --font: "Onest", system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

/* Широкий розовый включаем через @supports, а не второй строкой `background:
   var(--pink-p3)` следом за обычной. Подстановка переменной считается уже после
   каскада: там, где color(display-p3) не понимают, значение объявляется
   негодным на месте — и свойство сбрасывается в прозрачное, а не откатывается к
   предыдущей строке. На старом вебвью андроида это красило все розовые кнопки в
   пустоту. Здесь же негодным может стать только сам токен, а его читают лишь
   те браузеры, которые прошли проверку. */
@supports (color: color(display-p3 1 0 0)) {
  :root {
    --pink: color(display-p3 1 0.23 0.62);
    --pink-p3: color(display-p3 1 0.23 0.62);
  }
}

/* Тёмная тема — не инверсия: розовый и серый остаются теми же, меняется только
   поле. Класс вешает ядро по теме телеграма, а вне телеги — по системной. */
body.dark {
  --bg: #121212;
  --card: #121212;
  --ink: #FFFFFF;
  --text: #E5E5E5;
  --muted: #878787;
  --hair: #262626;
  --soft: #1E1E1E;
  --skel: #2C2C2C;
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

body {
  margin: 0; background: var(--bg); color: var(--text);
  font-family: var(--font); font-size: 15px; line-height: 1.45; font-weight: 410;
  -webkit-font-smoothing: antialiased;
  overflow: hidden;                       /* прокручивается слой экрана, а не страница */
  overscroll-behavior: none;              /* иначе телеграм закрывает мини-апп рывком вниз */
}

/* ── слои экранов ──────────────────────────────────────────────────────────
   Экраны лежат друг на друге, потому что на переходе их всегда двое. Отсюда
   же высота в 100dvh: со 100vh в мобильной сафари нижнее меню уезжает под
   адресную строку. */
.app {
  position: relative; overflow: hidden;
  height: 100vh; height: 100dvh;
  max-width: 560px; margin: 0 auto;
}

.scr {
  position: absolute; inset: 0;
  overflow-y: auto; overflow-x: hidden;
  -webkit-overflow-scrolling: touch; overscroll-behavior: contain;
  background: var(--bg);
  padding: 0 16px calc(24px + env(safe-area-inset-bottom));
}

/* Поле по краям держит слой, поэтому .pad из прототипа отвечает только за
   воздух сверху — иначе отступ удваивается на каждом втором экране. */
.pad { padding-top: 6px; }

/* Уезжающий слой перестаёт ловить нажатия сразу: полсекунды после «назад»
   палец попадал бы в кнопку экрана, которого уже нет. */
.scr-in, .scr-out { will-change: transform, opacity; }
.scr-out { pointer-events: none; }

.scr-in { animation: scr-in var(--t-scr) var(--e) both; }
.scr-out { animation: scr-out var(--t-scr) var(--e) both; }
.scr-in.scr-back { animation-name: scr-in-back; }
.scr-out.scr-back { animation-name: scr-out-back; }

/* Уходящий слой не улетает целиком, а отстаёт на четверть ширины и гаснет:
   так видно, что он остался позади, а не пропал. */
@keyframes scr-in       { from { transform: translate3d(100%, 0, 0) }  to { transform: none } }
@keyframes scr-out      { from { transform: none; opacity: 1 } to { transform: translate3d(-28%, 0, 0); opacity: .5 } }
@keyframes scr-in-back  { from { transform: translate3d(-100%, 0, 0) } to { transform: none } }
@keyframes scr-out-back { from { transform: none; opacity: 1 } to { transform: translate3d(28%, 0, 0); opacity: .5 } }

/* Заставка живёт до первого экрана. */
.boot { height: 70vh; display: flex; align-items: center; justify-content: center; }

/* Шарик — знак продукта, поэтому единственное место с собственными цветами:
   в тёмной теме он тот же самый, как и любой логотип. Ниже так же живут два
   свечения: это тот же розовый с прозрачностью, которого в градиенте
   переменной не задать. */
.orb {
  width: 44px; height: 44px; border-radius: 50%; display: inline-block; flex: 0 0 auto;
  background:
    radial-gradient(circle farthest-side at 62% 88%, rgba(255,140,205,.45) 0%, rgba(255,140,205,0) 50%),
    radial-gradient(circle farthest-side at 33% 24%, #FFF 0%, #FFD2E9 9%, #FF8AC6 24%, #FF3BA0 43%, #D00074 66%, #5E0033 100%);
  animation: orb 1.6s var(--e) infinite;
}
@keyframes orb { 50% { transform: scale(.9); opacity: .75 } }

/* ── шапка ─────────────────────────────────────────────────────────────────
   Сетка 44 / всё / 44: заголовок стоит по центру даже когда слева пусто.
   Отрицательные поля возвращают подложке ширину слоя — липкая шапка с
   прозрачными краями показывала бы текст, ползущий по бокам. */
.head {
  position: sticky; top: 0; z-index: 6;
  display: grid; grid-template-columns: 44px 1fr 44px; align-items: center;
  min-height: 46px; margin: 0 -16px; padding: 4px 16px 8px;
  background: var(--card);
}
.h-back, .h-right { display: flex; align-items: center; height: 44px; background: none; color: var(--ink); padding: 0; }
.h-back { justify-content: flex-start; }
.h-right { justify-content: flex-end; }
.h-title {
  text-align: center; font-weight: 500; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ic { width: 22px; height: 22px; display: block; }

/* Старая шапка со словом «сенси» — её ещё показывает часть экранов. */
.top { display: flex; align-items: center; gap: 10px; padding: 14px 0 10px; }
.top .orb { width: 22px; height: 22px; animation: none; }
.top .nm { font-weight: 500; color: var(--ink); }

/* ── нижнее меню ───────────────────────────────────────────────────────────
   Иконки без подписей: четыре слова внизу забирают строку у ленты, а иконки
   вкладок человек запоминает за один вечер. Активная — розовая, и это
   единственный розовый на экране, когда карточек нет. */
.nav {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 20;
  display: flex; justify-content: space-around; align-items: center;
  padding: 6px max(12px, calc((100% - 536px) / 2)) calc(6px + env(safe-area-inset-bottom));
  background: var(--card); border-top: 1px solid var(--hair);
}
.nav-b {
  position: relative; display: flex; align-items: center; justify-content: center;
  background: none; color: var(--muted); padding: 10px 16px; border-radius: var(--r);
  transition: color var(--t-base) var(--e), transform var(--t-tap) var(--e);
}
.nav-b .ic { width: 24px; height: 24px; }
.nav-b[aria-current="true"] { color: var(--pink); }
.nav-b:active { transform: scale(.92); }
.nav .dot {
  position: absolute; top: 7px; right: 12px;
  width: 6px; height: 6px; border-radius: 50%; background: var(--pink);
}

/* Меню перекрывает низ слоя, поэтому содержимое под ним просит запас. */
.has-tabs { padding-bottom: calc(78px + env(safe-area-inset-bottom)); }

/* Старое текстовое меню — ещё встречается в неперенесённых кусках. */
.tabs {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 20;
  display: flex; justify-content: space-around; align-items: center;
  padding: 8px 16px calc(8px + env(safe-area-inset-bottom));
  background: var(--card); border-top: 1px solid var(--hair);
}
.tabs button { background: none; color: var(--muted); font-size: 13px; padding: 6px 10px; border-radius: var(--r); }
.tabs button[aria-current="true"] { color: var(--ink); font-weight: 500; }
.tabs .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--pink); display: inline-block; margin-left: 4px; vertical-align: top; }

/* ── типографика ───────────────────────────────────────────────────────────
   Два заголовка на весь продукт. Отрицательный трекинг — чтобы крупная строка
   не рассыпалась на буквы. */
h1, .h1s { font-size: 26px; font-weight: 500; letter-spacing: -.025em; line-height: 1.1; color: var(--ink); margin: 0 0 8px; }
h2, .h2s { font-size: 21px; font-weight: 500; letter-spacing: -.02em; line-height: 1.16; color: var(--ink); margin: 0 0 8px; }
p { margin: 0 0 12px; }
.p0 { margin: 0; }
.mut { color: var(--muted); }
.sm { font-size: 13px; }
.strong { color: var(--ink); }
.w5 { font-weight: 500; }
.row { display: flex; align-items: center; gap: 10px; }
.sp { flex: 1; min-width: 0; }
.mt { margin-top: 16px } .mt2 { margin-top: 24px }
.mt6 { margin-top: 6px } .mt10 { margin-top: 10px } .mt16 { margin-top: 16px } .mt22 { margin-top: 22px }
.mb6 { margin-bottom: 6px } .mb10 { margin-bottom: 10px } .mb16 { margin-bottom: 16px } .mb22 { margin-bottom: 22px }
.hr, .divider { height: 1px; background: var(--hair); margin: 16px 0; border: 0; }

/* ── кнопки и пилюли ───────────────────────────────────────────────────────
   Скругление 24 при высоте 34–46 делает из кнопки пилюлю, и это осознанно:
   прямые углы в продукте остались только у фотографий. */
button { font-family: var(--font); font-size: 15px; border: 0; cursor: pointer; color: inherit; }

.btn {
  display: inline-block; text-align: center; text-decoration: none;
  border-radius: var(--r); padding: 8px 16px; font-size: 15px; font-weight: 500;
  background: var(--pink); color: var(--on-pink);
  transition: transform var(--t-tap) var(--e), opacity var(--t-base) var(--e);
}
.btn:active { transform: scale(.965); }
.btn[disabled] { opacity: .45; }
.btn.fill { background: var(--soft); color: var(--text); }
.btn.wide { display: block; width: 100%; padding: 13px; }
/* .sm пришло из старого фронта, .sm2 — из прототипа; это одна и та же мелкая
   кнопка, и разводить их размерами значило бы завести второй язык. */
.btn.sm, .btn.sm2 { padding: 5px 12px; font-size: 14px; }
.btn:focus-visible { outline: 2px solid var(--pink); outline-offset: 2px; }

.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chip {
  border-radius: var(--r); padding: 8px 14px; font-size: 14px;
  background: var(--soft); color: var(--text);
  transition: background var(--t-base) var(--e), color var(--t-base) var(--e), transform var(--t-tap) var(--e);
}
.chip[aria-pressed="true"] { background: var(--pink); color: var(--on-pink); }
.chip:active { transform: scale(.96); }

/* Значок «проверена» лежит в одном ряду с тусовками, а розовая заливка в этом
   продукте значит ровно одно — «выбрано». Залитой пилюлей значок читался как
   нажатый фильтр, хотя нажать его нельзя. Контур с галочкой отличается от обеих
   пилюль сразу: у невыбранной серая заливка, у выбранной розовая, у значка —
   ни одной. Розовая тут только галочка: розовый текст на белом мелким кеглем
   уже плохо читается. */
.chip.ok {
  display: inline-flex; align-items: center; gap: 5px;
  background: transparent; color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--pink);
  cursor: default;
}
.chip.ok svg { color: var(--pink); flex: 0 0 auto; }
.chip.ok:active { transform: none; }   /* не кнопка — и вжиматься под пальцем не должна */

.badge { font-size: 12px; color: var(--muted); background: var(--soft); border-radius: var(--r); padding: 2px 8px; }
.badge.pink { background: var(--pink); color: var(--on-pink); }

/* Плавающая кнопка над меню — единственный способ выставить статус из ленты. */
.fab {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(74px + env(safe-area-inset-bottom)); z-index: 21;
  border-radius: var(--r); padding: 12px 24px; font-size: 15px; font-weight: 500;
  background: var(--pink); color: var(--on-pink);
  box-shadow: 0 10px 26px rgba(255,59,160,.4);
}
.fab:active { transform: translateX(-50%) scale(.96); }

/* ── поля ──────────────────────────────────────────────────────────────────
   16px, а не 15: на меньшем сафари при фокусе увеличивает всю страницу, и
   человек дописывает имя в зуме. Это единственное исключение из общего кегля.
   Ползунок и выбор файла из правила выключены нарочно: заливка и внутренние
   отступы превращают системный слайдер в серую плашку. */
input:not([type="range"]):not([type="file"]):not([type="checkbox"]),
textarea, .field, .fieldbox {
  width: 100%; font-family: var(--font); font-size: 16px; line-height: 1.4;
  color: var(--ink); background: var(--soft);
  border: 0; border-radius: var(--r-box); padding: 12px 14px;
  transition: box-shadow var(--t-base) var(--e);
}
textarea { resize: none; }
input::placeholder, textarea::placeholder { color: var(--muted); }
input:focus, textarea:focus { outline: 0; box-shadow: inset 0 0 0 2px var(--pink); }
div.fieldbox, span.fieldbox { min-height: 0; }

/* ── ползунки фильтров ─────────────────────────────────────────────────────
   Системный ползунок красит только «от начала до ручки», а вилке возраста нужна
   закрашенная середина — двумя системными она и выглядела сломанной: нижняя
   полоса розовая во всю ширину, верхняя пустая. Поэтому полосу и заливку рисуем
   сами, и одним и тем же способом для обоих ползунков: два разных ползунка на
   одном экране читаются как две разные вещи, хотя это одна.
   Устройство: .rngbox — полоса, на ней лежат один или два прозрачных input, а
   видимое — .bar с заливкой внутри. Ползунок вне .rngbox не живёт.
   Пальцем ловятся только ручки (pointer-events): иначе верхний ползунок накрыл
   бы нижний целиком и до второй ручки было бы не добраться. */
.rngbox {
  --th: 22px;                /* ручка; её половина — вылет полосы за края */
  --a: 0; --b: 1;            /* доли 0..1, где стоят ручки; ставит js */
  position: relative; height: 28px; margin: 0 0 18px;
}
.rngbox .bar {
  position: absolute; left: 0; right: 0; top: 50%; height: 5px;
  transform: translateY(-50%);
  border-radius: 999px; background: var(--soft);
}
/* Центр ручки ходит не от края до края, а от своей половины до неё же с другой
   стороны. Заливку считаем по той же арифметике, иначе на концах она уезжает
   от ручки на десяток пикселей. */
.rngbox .bar i {
  position: absolute; top: 0; bottom: 0; border-radius: 999px;
  background: var(--pink); background: var(--pink-p3);
  left: calc(var(--th) / 2 + var(--a) * (100% - var(--th)));
  right: calc(var(--th) / 2 + (1 - var(--b)) * (100% - var(--th)));
}
/* У одиночного ползунка (.one) заливка идёт от самого края полосы: слева от
   единственной ручки нет второй, и серый огрызок в начале читался бы как
   недоделка. Под ручкой в самом левом положении он всё равно спрятан. */
.rngbox.one .bar i { left: 0; }
.rng {
  position: absolute; left: 0; top: 0; width: 100%; height: 100%; margin: 0;
  -webkit-appearance: none; appearance: none;
  background: transparent; pointer-events: none; cursor: pointer;
}
.rng::-webkit-slider-runnable-track { height: 100%; background: transparent; border: 0; }
.rng::-moz-range-track { height: 100%; background: transparent; border: 0; }
/* Фаерфокс красит пройденную часть сам — тут это была бы вторая полоса поверх нашей. */
.rng::-moz-range-progress { background: transparent; }
.rng::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: var(--th); height: var(--th);
  margin-top: calc((28px - var(--th)) / 2);   /* трек во всю высоту: вебкит сам ручку не центрует */
  border-radius: 50%; border: 2px solid var(--bg);
  background: var(--pink); background: var(--pink-p3);
  box-shadow: 0 1px 4px rgba(0,0,0,.2);
  pointer-events: auto; cursor: grab;
}
.rng::-moz-range-thumb {
  width: var(--th); height: var(--th);
  border-radius: 50%; border: 2px solid var(--bg);
  background: var(--pink);
  box-shadow: 0 1px 4px rgba(0,0,0,.2);
  pointer-events: auto; cursor: grab;
}
/* Ручки сошлись в одной точке — верхняя накрывает нижнюю, и одна из двух
   становится недоступной пальцу. Класс вешает js, когда пара стоит у правого
   края: оттуда уехать может только нижняя. */
.rngbox.lo-up .rng:first-of-type { z-index: 2; }
/* Обводка вокруг ползунка обвела бы прозрачный прямоугольник во всю ширину,
   поэтому фокус показываем кольцом на самой ручке. */
.rng:focus-visible { outline: 0; }
.rng:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 4px rgba(255,59,160,.3); }
.rng:focus-visible::-moz-range-thumb { box-shadow: 0 0 0 4px rgba(255,59,160,.3); }

/* Полоса шагов анкеты: три деления, но без цифр — счёт идёт словами рядом. */
.prog { height: 3px; background: var(--soft); border-radius: 999px; overflow: hidden; margin: 8px 0 18px; }
.prog i { display: block; height: 100%; background: var(--pink); transition: width var(--t-in) var(--e); }

/* ── карточки ──────────────────────────────────────────────────────────────
   .card из старого фронта и .slotcard из прототипа — одно и то же; держим их
   одним правилом, чтобы выбранный вариант выглядел одинаково в квизе и в ленте. */
.card, .slotcard {
  background: var(--soft); border-radius: var(--r-box); padding: 14px 16px;
  transition: background var(--t-base) var(--e), transform var(--t-tap) var(--e);
}
.card { margin-bottom: 8px; }
.card.on, .slotcard.on { background: var(--pink); color: var(--on-pink); }
.card.on .mut, .card.on .strong, .slotcard.on .mut, .slotcard.on .strong { color: var(--on-pink); }
.card.off, .slotcard.off { opacity: .42; }
.card:active, .slotcard:active { transform: scale(.985); }

/* Статус — та самая фраза, ради которой всё затевалось. Единственный крупный
   текст в продукте, который не заголовок. */
.status-card {
  background: var(--pink); color: var(--on-pink);
  border-radius: var(--r-box); padding: 18px 18px 20px;
}
.status-card .phrase { font-size: 22px; font-weight: 500; letter-spacing: -.02em; line-height: 1.14; margin: 0; }
.status-card .meta { margin: 10px 0 0; font-size: 14px; opacity: .72; }

/* Строка списка: фото, две строки текста, действие справа. Строки обрезаются
   многоточием только при display:block — inline-элемент его игнорирует. */
.item { display: flex; align-items: center; gap: 12px; padding: 12px 0; border-bottom: 1px solid var(--hair); }
.item:last-child { border-bottom: 0; }
.item .ph { width: 52px; height: 52px; }
.item .txt { flex: 1; min-width: 0; }
.item .txt .l1, .item .txt .l2, .l1, .l2 { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.l1 { color: var(--ink); font-weight: 500; }
.l2 { color: var(--muted); font-size: 14px; }
.item .btn, .item .badge, .item .chip { flex: 0 0 auto; }

/* Имена. Телеграм отдаёт их как написал человек — «Артур», «Азалия», — а весь
   продукт говорит строчными, и одно заглавное имя посреди ленты читается как
   чужая вставка. Правим только показ: в базе имя остаётся своим, и наружу — в
   бот, в приглашение другу, в жалобу — уходит нетронутым. Строчить имя в
   данных нельзя: человек написал его о себе, это не наш текст.
   Класс вешается на строку имени; .l1 — первая строка строки списка, там имя
   и стоит. */
.nm, .l1 { text-transform: lowercase; }

/* Имя и значок проверки в одной строке, но обрезается только имя.
   ПОЧЕМУ флекс, а не один блок с многоточием: многоточие съедает конец строки,
   а значок стоит как раз в конце — на 375px выходило «азалия, 20 проверен…».
   Значок и есть причина, по которой карточке верят; терять его первым — ровно
   наоборот тому, зачем он там. Имя обрезать можно: лицо рядом.
   Арифметика узкая: на 375px под имя со значком остаётся около 158px (карточка
   минус фото 48 и кнопка «я с вами»), а «азалия, 20 проверена» просит ровно
   столько же. Поэтому значок в этой строке чуть теснее обычного — иначе на
   каждом втором имени вылезало бы многоточие из-за пяти пикселей. */
.nmrow { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.nmrow > .nm { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nmrow > .badge, .nmrow > .chip { flex: 0 0 auto; padding: 2px 7px; }

/* Две строки и только потом многоточие. Одной строки хватает подписи, но не
   фразе статуса: обрезка приходилась ровно на место и компанию — «куда-нибудь
   с подр…», — то есть на всё, ради чего фразу и читают. Длинное имя заведения
   без пробелов переносим внутри слова, иначе оно распирает карточку. */
.clamp2 {
  display: -webkit-box; -webkit-box-orient: vertical;
  -webkit-line-clamp: 2; line-clamp: 2;
  overflow: hidden; overflow-wrap: anywhere;
}

.empty { text-align: center; padding: 44px 16px; color: var(--muted); }
.empty b { display: block; color: var(--ink); font-weight: 500; margin-bottom: 6px; font-size: 17px; }

/* Реплики: экрана переписки у нас нет, но мэтч иногда показывает последнюю
   строку — пусть выглядит как реплика, а не как абзац. */
.bub { display: block; max-width: 78%; padding: 9px 13px; border-radius: 16px; }
.bub.them { background: var(--soft); align-self: flex-start; }
.bub.me { background: var(--pink); color: var(--on-pink); align-self: flex-end; }

/* ── фотографии ────────────────────────────────────────────────────────────
   Углы прямые: скругление фотографии — это уже дизайн поверх лица, а лицо
   здесь и есть содержание. Круглыми остаются только аватарки в строках. */
.ph { position: relative; overflow: hidden; background: var(--soft); flex: 0 0 auto; display: block; }
.ph img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.av { border-radius: 50%; }
.sq { width: 100%; aspect-ratio: 1/1; }

/* Сетка фото анкеты: два в ряд, вертикальные 3/4 — телефон снимает так же.
   Зазор 3px, а не 8: это одна стена фотографий, а не четыре карточки. */
.grid2, .grid3 { display: grid; gap: 3px; }
.grid2 { grid-template-columns: 1fr 1fr; }
.grid3 { grid-template-columns: repeat(3, 1fr); }
.grid2 > *, .grid3 > * { aspect-ratio: 3/4; }
.grid3 > * { aspect-ratio: 1/1; }

/* Скелет вместо пустого места: фото с телефона летит секунд десять, и всё это
   время человек должен видеть, что оно летит, а не что кнопка не сработала. */
.skel {
  display: block; border-radius: 0;
  background: linear-gradient(90deg, var(--soft) 25%, var(--skel) 37%, var(--soft) 50%);
  background-size: 300% 100%; animation: skel 1.3s linear infinite;
}
@keyframes skel { from { background-position: 150% 0 } to { background-position: -150% 0 } }

/* ── лента людей ───────────────────────────────────────────────────────────
   Карточка занимает весь экран между шапкой и кнопками: одно фото, одно
   решение. Всё, что тянут пальцем, снимается со слоя на свой — иначе браузер
   перерисовывает под ней весь экран на каждом кадре. */
[data-card] {
  touch-action: none; user-select: none; -webkit-user-select: none;
  will-change: transform; cursor: grab;
}
[data-card]:active { cursor: grabbing; }
[data-card] .ph { border-radius: var(--r-box); }

/* Подсказки решения проявляются по ходу пальца — их непрозрачность ведёт js,
   переход здесь только сглаживает возврат карточки на место. */
[data-hint] { transition: opacity var(--t-tap) var(--e); border-radius: var(--r); }

/* ── мэтч ──────────────────────────────────────────────────────────────────
   Единственная крупная анимация в продукте. Всё остальное спокойное ровно для
   того, чтобы эта читалась как событие. Фото съезжаются с двух сторон, розовое
   свечение вспыхивает под ними, текст поднимается следом. */
.soc-pair, .pair2 { position: relative; }
.soc-pair::before, .pair2::before {
  content: ""; position: absolute; left: -30%; right: -30%; top: -35%; bottom: -35%;
  background: radial-gradient(circle at 50% 45%, rgba(255,59,160,.42) 0%, rgba(255,59,160,.08) 42%, transparent 68%);
  animation: match-glow 900ms var(--e) both;
}
.soc-pair [data-side="l"], .pair2 .ph:first-child { animation: match-l 620ms var(--e) both; }
.soc-pair [data-side="r"], .pair2 .ph:last-child { animation: match-r 620ms var(--e) 90ms both; }
.soc-word, .word { animation: match-up 520ms var(--e) 240ms both; }
.soc-word:nth-of-type(2), .word:nth-of-type(2) { animation-delay: 320ms; }
.soc-word:nth-of-type(3), .word:nth-of-type(3) { animation-delay: 400ms; }

@keyframes match-l { from { opacity: 0; transform: translate3d(-26px, 0, 0) } to { opacity: 1; transform: none } }
@keyframes match-r { from { opacity: 0; transform: translate3d(26px, 0, 0) } to { opacity: 1; transform: none } }
@keyframes match-glow { 0% { opacity: 0; transform: scale(.7) } 45% { opacity: 1; transform: scale(1.04) } 100% { opacity: .75; transform: scale(1) } }
@keyframes match-up { from { opacity: 0; transform: translate3d(0, 14px, 0) } to { opacity: 1; transform: none } }

/* ── всплывашка ────────────────────────────────────────────────────────────
   Живёт вне слоёв экранов: она переживает переход и не уезжает вместе с ним. */
.toast {
  position: fixed; left: 50%; bottom: calc(24px + env(safe-area-inset-bottom)); z-index: 60;
  background: var(--ink); color: var(--bg); border-radius: var(--r);
  padding: 11px 20px; font-size: 14px; max-width: 88vw;
  transform: translate(-50%, 90px); opacity: 0; pointer-events: none;
  transition: transform 340ms var(--e), opacity 340ms var(--e);
}
.toast.show { transform: translate(-50%, 0); opacity: 1; }

.fade { animation: fade 300ms var(--e) both; }
@keyframes fade { from { opacity: 0; transform: translate3d(0, 10px, 0) } to { opacity: 1; transform: none } }

/* Кому движение мешает — тому его нет совсем. Ядро в этом режиме ещё и
   снимает старый слой сразу, так что экран просто сменяется. */
@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; scroll-behavior: auto !important; }
}
