/* ==========================================================================
   Каркас новой внутренней части.
   ==========================================================================
   Всё меряется токенами из /css/tokens.css. Своих чисел здесь нет, кроме
   тех, что объяснены рядом.

   Главное решение по вёрстке: телефон и широкий экран получают РАЗНУЮ
   навигацию, а не одну и ту же в двух позах.

     телефон        плавающая полоса снизу, три вкладки. Второй уровень
                    (пара, подписка, выход) спрятан внутрь вкладок.
     широкий экран  колонка слева: пара сверху, разделы, выход внизу.
                    Второй уровень виден сразу, прятать его некуда и незачем.

   Переносить полосу-пилюлю на десктоп смысла нет: она придумана под большой
   палец, а на широком экране палец до неё не дотягивается.
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }

/* РАЗМЕР ШРИФТА РЕШАЕМ МЫ, А НЕ БРАУЗЕР.
   Владелец: «на сайте всё крупнее, чем в приложении». Шкала у нас совпадает с
   приложением до пикселя, и дело было не в ней: мобильные браузеры сами
   раздувают текст в блоках, которые считают длинными (font boosting у Chrome,
   text autosizing у Safari). Приложение так не делает никогда, поэтому одни и
   те же 15 пикселей выглядели на телефоне разными.

   Правило живёт здесь, а не в общих токенах: токены читает и боевой сайт, а
   там вёрстка под это давно подстроена, и менять ей размеры вслепую нельзя. */
html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  background: var(--c-bg);
  color: var(--c-text);
  font-family: var(--f-sans);
  -webkit-font-smoothing: antialiased;
  min-height: 100dvh;
}


/* ==========================================================================
   ФОН: три комнаты
   ==========================================================================
   Перенесено из приложения, lib/theme/widgets.dart, класс AmbientBackground.
   Числа те же, пересчитаны из системы координат Flutter в проценты.

   Комнат ровно три, и у каждой своя работа. Комментарий автора приложения
   объясняет, почему их три: «раньше каждый экран передавал свой цвет свечения,
   и их набралось девять, выбранных по одному тем, кто делал экран. Это палитра,
   а не система: цвет что-то значит, только если он значит одно и то же везде».

     тихая     без свечения. Списки, настройки, всё, что читают, а не чувствуют
     угли      золото сверху, как лампа над столом. Играть
     цветение  розовое. Мы, кинк, пара

   Alignment(0, -0.92) во Flutter это середина по горизонтали и 4% сверху:
   (1 + (-0.92)) / 2. Радиус 0.95 считается от КОРОТКОЙ стороны, отсюда min().
   ========================================================================== */

body::before,
body::after {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
}

/*
 * Свечение сверху. Цвет и сила приходят от текущей вкладки.
 *
 * ПЕРЕХОДА ЗДЕСЬ БОЛЬШЕ НЕТ, и это правка по живой жалобе, а не по вкусу.
 *
 * Стояло `transition: background`, то есть плавная смена ЦЕЛОГО ГРАДИЕНТА на
 * слое во весь экран, 450 миллисекунд. Беда в том, что перезапускает такой
 * переход не только смена комнаты: шторка при открытии правит у `body`
 * встроенный стиль (прокрутку, а с недавних пор и отступ), body пересчитывает
 * свои псевдоэлементы, значение градиента пересобирается, и переход начинается
 * заново. Свечение уходит и возвращается за полсекунды.
 *
 * ИМЕННО ТАК ЭТО И ВЫГЛЯДЕЛО. Владелец, трижды и всё точнее: «мигает весь экран
 * сзади», «мигание осталось», и наконец решающее: «будто обновляется сзади,
 * ТЕМНЕЕТ буквально на долю секунды». Свечение светлое; пропав, оно
 * читается ровно как затемнение, а не как вспышка. Виньетка ниже
 * (`body::after`) перехода не имеет вовсе и не мигала никогда, что и было
 * подсказкой, которую я дважды прошёл мимо.
 *
 * ПРОВЕРЕНО НА ЖИВОМ: НЕ ПОМОГЛО. Переход был снят, мигание осталось и в
 * Chrome, и в Safari. Значит причина не здесь, и держать снятым то, что я снял
 * по неверной догадке, нельзя: перелив комнаты был решением, а не случайностью.
 * Возвращаю. Причину мигания ищу щупом, а не рассуждением.
 */
body::before {
  background: radial-gradient(
    circle calc(.95 * min(100vw, 100dvh)) at 50% 4%,
    rgba(var(--room-rgb, 212, 175, 55), var(--room-a, 0)) 0%,
    transparent 58%);
  transition: background var(--m-expressive) var(--m-ease);
}

/* Виньетка и точечная сетка. Виньетка притемняет только дальние УГЛЫ: большой
   радиус и поздняя остановка, иначе левый и правый края превращаются в чёрные
   полосы за светлым содержимым. Сетка: шаг 22, точка радиусом 1, белый при 2%. */
body::after {
  background:
    radial-gradient(circle calc(1.45 * min(100vw, 100dvh)) at 50% 45%,
      transparent 82%, rgba(0, 0, 0, .2) 100%),
    radial-gradient(rgba(250, 250, 250, .02) 1px, transparent 1px);
  background-size: auto, 22px 22px;
}

.frame, .nav-phone { position: relative; z-index: 1; }

/* Пока идёт первая проверка входа, содержимое скрыто: показывать чужой
   экран до ответа нельзя. Но именно содержимое, а не всё подряд, иначе при
   молчащем Firebase человек остаётся с чёрным прямоугольником навсегда, и
   понять, сломалось оно или думает, невозможно. */
/**
 * ГОСТЮ содержимое прячем, и только ему.
 *
 * Гость сейчас уедет на вход, и мелькнувшая внутрянка это обещание, которого
 * страница не сдержит.
 *
 * А ВОТ ЗАГРУЗКУ ПРЯТАТЬ БОЛЬШЕ НЕЛЬЗЯ. Раньше здесь пряталось и её
 * содержимое, а вместо него посреди экрана пульсировали три точки. Теперь в
 * содержимом с первого кадра лежит огонёк (scripts/next-splash.js), и старое
 * правило гасило ровно его: разметка была на месте, а на экране стояли те же
 * три точки. Поймал снимок с ВЫКЛЮЧЕННЫМИ скриптами, потому что с включёнными
 * каркас успевает нарисовать свою заглушку, и подмены не видно.
 */
body[data-state="gostj"] #content { visibility: hidden; }
/* Обёртка показа из router.js: своего блока не даёт, раскладка экранов прежняя. */
#content > .ehkran-mesto { display: contents; }


/* ---------- ОБЩАЯ РАСКЛАДКА ---------- */

.frame {
  min-height: 100dvh;
  display: grid;
  /* Телефон: одна колонка, полоса плавает поверх содержимого.
     minmax(0, 1fr), а не 1fr: у ячейки сетки минимальная ширина по умолчанию
     равна её содержимому, и одна широкая строка внутри распирала страницу.
     Полоса фильтров из пяти чипов делала экран 506 пикселей вместо 390, и
     третий столбец обложек уезжал за край. */
  grid-template-columns: minmax(0, 1fr);
}

.frame__content {
  /* Снизу место под плавающую полосу: её высота, отступ и безопасная зона. */
  padding: 0 var(--s-4) calc(64px + var(--s-6) + env(safe-area-inset-bottom, 0px));
  width: 100%;
  min-width: 0;   /* см. выше: без этого содержимое не даёт себя сжать */
  overflow-x: clip;
}

/* Поле списка сверху space4 (16), как ListView в lib/me/me_screen.dart.
   Было 24, и это первая порция лишнего воздуха. */
.screen { padding-top: var(--s-4); }

/* Шапка экрана: надзаголовок, 8, заголовок, 8, подводка, 20 до первого блока.
   Числа из описания приложения, раздел «компоновка экрана». */
.head { margin-bottom: var(--s-5); }
/* Надзаголовок дословно из components.dart, класс Eyebrow: labelSmall,
   моно, в верхнем регистре, трекинг 2.2, цвет onSurfaceVariant. У меня стоял
   faint, он на ступень темнее, и надзаголовки читались как выключенные. */
.eyebrow {
  font: 600 var(--t-label-s) var(--f-mono);
  letter-spacing: var(--tr-eyebrow);
  color: var(--c-muted);
  text-transform: uppercase;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.title {
  font-family: var(--f-serif);
  font-weight: 400;
  font-size: 28px;
  line-height: 34px;
  margin: var(--s-2) 0 0;
}
.sub {
  font: 400 var(--t-body-m) var(--f-sans);
  color: var(--c-muted);
  margin: var(--s-2) 0 0;
  max-width: 56ch;
}

/* Между блоками space5 (20): шаг из me_screen.dart. */
.stack { display: flex; flex-direction: column; gap: var(--s-5); }

.sect {
  font: 500 var(--t-headline-s) var(--f-serif);
  margin: var(--s-8) 0 var(--s-4);
}


/* ---------- ВЕРХНЯЯ ПОЛОСА: ПРЕМИУМ И ЛИЦО ---------- */

/* Полоса липкая: состояние подписки и дверь наверх должны быть под рукой на
   любой высоте прокрутки, а не только в самом верху. На сайте это делает
   шапка, и человек уже привык, что там всегда написано, премиум он или нет. */
.topbar {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3) 0;
  /* Фона у полосы НЕТ намеренно. Раньше она красила верх сплошным
     цветом и этим срезала верхнюю часть свечения: оно начиналось не
     от края экрана, как в приложении, а из-под полосы. Бейдж и лицо
     и так со своими подложками, полосе красить нечего. */
  background: none;
  pointer-events: none;
}
/* Полоса не ловит нажатия (иначе она перехватывала бы их у содержимого
   под собой), а её содержимое ловит. */
.topbar > * { pointer-events: auto; }
.topbar__gap { flex: 1; pointer-events: none; }
.topbar__face {
  width: 40px; height: 40px; border-radius: 50%;
  border: 1px solid var(--c-border-strong);
  background: var(--c-surface-high);
  object-fit: cover; display: block;
  cursor: pointer;
  transition: border-color var(--m-standard) var(--m-ease);
}
.topbar__face:hover { border-color: rgba(212, 175, 55, .55); }


/* ==========================================================================
   ВИТРИНА ИГР
   ==========================================================================
   Перенесено из приложения, lib/play/game_grid.dart. Устройство бенто:
   полоса фильтров, одна большая плитка, дальше плотная сетка по три в ряд.

   Ключевое правило оттуда, дословно из кода: «рамки нет. Плитка это чистая
   картинка, отделённая от фона мягким свечением своего цвета, а не контуром».
   Поэтому здесь нигде нет border, только box-shadow цветом игры.

   Значков состояния на плитках тоже нет: там сказано «всё бесплатно, а
   приложение целиком 18+, значит метка на плитке лишняя».
   ========================================================================== */

/* ---------- фильтры ---------- */

.chips {
  display: flex;
  gap: var(--s-2);
  overflow-x: auto;
  scrollbar-width: none;
  padding-bottom: 2px;
  margin-bottom: var(--s-4);
}
.chips::-webkit-scrollbar { display: none; }

.chip {
  flex: 0 0 auto;
  height: 34px;
  display: inline-flex;
  align-items: center;
  padding: 0 var(--s-3);
  border-radius: var(--r-ctl, 14px);
  border: 1px solid var(--c-border);
  background: var(--c-surface-high);
  color: var(--c-muted);
  font: 600 var(--t-label-l) var(--f-sans);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  white-space: nowrap;
  transition: background var(--m-micro) var(--m-ease),
              color var(--m-micro) var(--m-ease),
              box-shadow var(--m-micro) var(--m-ease);
}
.chip:focus { outline: none; }
.chip:focus-visible { outline: 2px solid var(--c-gold); outline-offset: 2px; }
/* Выбранный чип плавает в мягком золотом пузыре: золото при 40%, размытие 18,
   разлёт -4. Числа из приложения. */
.chip[aria-pressed="true"] {
  background: var(--g-flame);
  /* Волосина по краю: отсчёт заливки от той же коробки, по которой рисуем. */
  background-origin: border-box;
  border-color: transparent;
  color: var(--c-on-gold);
  box-shadow: 0 0 18px -4px rgba(212, 175, 55, .4);
}

/* ---------- плитки ---------- */

/* По три в ряд, как на телефоне в приложении. Владелец там записал: «мельче,
   по три в ряд на плотном экране читается нормально». */
.arts {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-3);
}

.art {
  position: relative;
  display: block;
  text-decoration: none;
  color: inherit;
  overflow: hidden;
  /*
   * СБРОС КНОПКИ ЖИВЁТ ЗДЕСЬ, а не у каждого, кто плиткой пользуется.
   *
   * Плитка бывает и ссылкой (игры, «Мы», монетка), и кнопкой (тесты, курсы). У
   * кнопки браузер рисует свою рамку, и её надо снимать. Снимали её в
   * `.ts-плитка`, то есть в СВОЁМ классе тестов, и курсы, у которых свой класс,
   * получили светлую рамку вокруг каждой ячейки. Владелец увидел это сразу и
   * назвал причину точнее меня: «ты же делал модуль общего стиля, чтобы каждый
   * раз новые не делать».
   *
   * Забыть это может каждый следующий, поэтому чинится не место, а модуль:
   * теперь плитка выглядит одинаково, чем бы её ни сделали. Ссылке эти строки
   * не мешают: у неё и так нет ни рамки, ни отступов.
   */
  border: 0; padding: 0; margin: 0;
  font: inherit; text-align: left; cursor: pointer;
  appearance: none;
  border-radius: var(--r-lg);
  background: var(--c-surface-high);
  aspect-ratio: 0.8;              /* портретная, как в сетке приложения */
  /* Свечение своего цвета вместо рамки: цвет при 22%, размытие 18,
     разлёт -4, сдвиг вниз 6. */
  box-shadow: 0 6px 18px -4px rgba(var(--ac), .22);
  transition: transform var(--m-micro) var(--m-ease),
              box-shadow var(--m-standard) var(--m-ease);
  -webkit-tap-highlight-color: transparent;
}
.art:hover { box-shadow: 0 6px 26px -4px rgba(var(--ac), .34); }
.art:active { transform: scale(.96); }   /* нажатие 0.96, как Pressable */
.art:focus { outline: none; }
.art:focus-visible { outline: 2px solid rgb(var(--ac)); outline-offset: 3px; }

.art img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover; display: block;
}

/* Затемнение снизу: прозрачное до 42%, дальше к чёрному при 87%. */
.art__veil {
  position: absolute; inset: 0;
  background: linear-gradient(to bottom, transparent 42%, rgba(0, 0, 0, .87) 100%);
}

.art__text {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: var(--s-2);
  display: block;
}
.art__name {
  display: block;
  font: 600 var(--t-label-l) var(--f-sans);
  color: #fff;
  /* Две строки даже на большой плитке. Правило про одну строку с многоточием
     существует, чтобы чужое длинное имя не ломало ряд, а обрезать
     «Пей или делай» на витрине это ровно обратное тому, зачем оно есть. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ---------- большая плитка ---------- */

.art--big {
  grid-column: 1 / -1;
  aspect-ratio: auto;
  height: 160px;
  border-radius: var(--r-xl);
  box-shadow: 0 6px 34px -6px rgba(var(--ac), .30);
  margin-bottom: var(--s-3);
}
.art--big:hover { box-shadow: 0 6px 44px -6px rgba(var(--ac), .42); }
.art--big .art__text { padding: var(--s-5); }
.art--big .art__eyebrow {
  display: block;
  font: 600 var(--t-label-s) var(--f-mono);
  letter-spacing: 2.4px;
  color: var(--c-gold);
  text-transform: uppercase;
  margin-bottom: var(--s-1);
}
.art--big .art__name {
  font: 400 var(--t-headline-m) var(--f-serif);
}
.art--big .art__note {
  display: block;
  font: 400 var(--t-body-m) var(--f-sans);
  color: rgba(255, 255, 255, .7);
  margin-top: var(--s-1);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}


/* ---------- НАВИГАЦИЯ: ТЕЛЕФОН ---------- */

.nav-phone {
  position: fixed;
  left: 50%;
  bottom: calc(var(--s-4) + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  z-index: 40;

  height: 64px;
  display: flex;
  align-items: center;
  gap: var(--s-1);
  padding: 0 var(--s-2);

  border-radius: var(--r-pill);
  background: rgba(20, 14, 14, .92);
  border: 1px solid var(--c-border);
  box-shadow: 0 0 24px rgba(0, 0, 0, .4);
  backdrop-filter: blur(18px);
}

.nav-phone__item {
  appearance: none;
  -webkit-appearance: none;
  -webkit-tap-highlight-color: transparent;
  background: none;
  border: 0;
  cursor: pointer;
  color: inherit;

  min-width: 84px;
  padding: var(--s-1) var(--s-3);
  display: flex;
  flex-direction: column;
  align-items: center;
  border-radius: var(--r-pill);
}
.nav-phone__item:focus { outline: none; }
.nav-phone__item:focus-visible {
  outline: 2px solid rgba(212, 175, 55, .9);
  outline-offset: 2px;
}

/* Пузырь вокруг значка активной вкладки: высота 34, золото 16%, рамка 30%. */
.nav-phone__bubble {
  height: 34px;
  min-width: 52px;
  display: grid;
  place-items: center;
  border-radius: var(--r-pill);
  border: 1px solid transparent;
  transition: background var(--m-standard) var(--m-ease),
              border-color var(--m-standard) var(--m-ease);
}
.nav-phone__item[aria-current="page"] .nav-phone__bubble {
  background: rgba(212, 175, 55, .16);
  border-color: rgba(212, 175, 55, .30);
}

/* Значки золотые со своим свечением и НЕ перекрашиваются: неактивный это тот
   же значок тише, а не серая копия. Правило из приложения. */
/* Значок идёт в ПОЛНУЮ силу. Приглушается только неактивная вкладка, и
   только она: в приложении 0.42 стоит у AppGlyph(active: false), а это
   исключительно нижняя полоса и колонка. Я по ошибке приглушил весь набор, и
   все значки настроек вышли тусклыми. */
.glyph { width: 22px; height: 22px; display: block; opacity: 1; transition: opacity var(--m-standard) var(--m-ease); }

/* Вот здесь, и только здесь, значок тише. */
.nav-phone__item .glyph,
.nav-wide__item .glyph { opacity: .42; }
.nav-phone__item[aria-current="page"] .glyph,
.nav-wide__item[aria-current="page"] .glyph { opacity: 1; }
/* В полосе значки крупнее: 28, как в приложении. */
.nav-phone .glyph { width: 28px; height: 28px; }

.nav-phone__label {
  font: 600 var(--t-label-s) var(--f-mono);
  letter-spacing: var(--tr-label-s);
  margin-top: 3px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 78px;
  color: var(--c-faint);
  text-transform: uppercase;
}
.nav-phone__item[aria-current="page"] .nav-phone__label { color: var(--c-text); }


/* ---------- НАВИГАЦИЯ: ШИРОКИЙ ЭКРАН ---------- */

.nav-wide { display: none; }


/* ---------- ЧАСТИ ---------- */

/* Карточка под СТРОКАМИ. Полей нет намеренно: в приложении Card идёт с
   margin: zero и без внутренних полей, а отступ несёт сама строка (ListTile,
   space4 по горизонтали). Так разделители идут во всю ширину карточки, а не
   висят с зазором, и над первой строкой не набегает лишних 16 пикселей.
   Радиус radiusLg (20), рамка outline при 16 процентах: theme.dart. */
.card {
  background: var(--c-card-bg);
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  padding: 0;
  overflow: hidden;
}

/*
 * Карточка, которая НАЖИМАЕТСЯ. Раз внутри каркаса нажатие открывает шторку, а
 * не ведёт на адрес, элементом обязана быть кнопка, а не ссылка: ссылка без
 * `href` выпадает из обхода клавиатурой, а с `href="#"` дёргает адресную
 * строку. Сброс собран ЗДЕСЬ, один раз на весь каркас, а не в каждом экране:
 * ровно на этом уже обожглись с плитками, где браузерная рамка кнопки лезла
 * белой обводкой, и владелец сказал прямо: «ты же делал модуль общего стиля».
 */
/**
 * Имя внутри текста задания, цветом того, о ком строка.
 *
 * Из приложения: «глаз попадает на то, о ком строка, ещё до того, как
 * предложение прочитано». Цвет ставится прямо в разметке общим модулем
 * `js/games/names.js`, поэтому здесь только насыщенность.
 *
 * ЖИВЁТ В КАРКАСЕ, А НЕ У СЕКСОПОЛИИ, откуда переехало 07.09.2026. Там оно
 * стояло как `.sx-задание .имя`, а разметку делает ОБЩИЙ модуль, которого зовут
 * одиннадцать экранов: в сексополии имя было жирным, в остальных десяти играх
 * нет, и заметить это можно было только положив два экрана рядом.
 */
.imya { font-weight: 600; }   /* цвет не трогаем: он приходит атрибутом */

button.card {
  width: 100%;
  margin: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  appearance: none;
  cursor: pointer;
}

/* Шаг между блоками, когда над блоком нет надзаголовка (у надзаголовка свой
   отступ). space5 (20), как SizedBox между блоками в me_screen.dart. Это
   единственный способ отодвинуть блок: чисел в разметке экрана быть не должно. */
.gap { margin-top: var(--s-5); }

/* Надзаголовок раздела: space5 (20) сверху, space3 (12) до карточки. Ритм
   из lib/me/me_screen.dart. Класс, а не число в экране. */
.eyebrow--sect { margin: var(--s-5) 0 var(--s-3); }
/*
 * Золотая надпись: в приложении `Eyebrow(gold: ...)`, и цвет там несёт
 * смысл. Золото стоит над «Пробовали», серое над «Вернуть»: одна и та же
 * шторка говорит про хорошее и про отмену, и цвет говорит это раньше слов.
 */
.eyebrow--gold { color: var(--c-gold); }

/* Карточка под СОДЕРЖИМЫМ: числа, абзац, кнопки. Здесь поля нужны, потому
   что внутри не строки. Второго вида хватает на всё, третьего заводить не
   надо. */
.card--pad { padding: var(--s-4); }

.tile {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  width: 100%;
  text-align: left;
  text-decoration: none;
  color: inherit;
  background: var(--c-card-bg);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  padding: var(--s-3) var(--s-4);
  cursor: pointer;
  transition: border-color var(--m-standard) var(--m-ease),
              transform var(--m-micro) var(--m-ease);
}
.tile:hover { border-color: rgba(212, 175, 55, .35); }
/* Нажатие: 0.96 за 140 мс, как в приложении. */
.tile:active { transform: scale(.96); }
.tile__glyph { width: 30px; height: 30px; flex: 0 0 auto; }
.tile__body { min-width: 0; flex: 1; }
/* Строками, а не в подбор: это span внутри span, и без display они
   слипаются в «СексополияНастольное приключение». */
.tile__name { display: block; font: 600 var(--t-title-s) var(--f-sans); }
.tile__note { display: block; font: 400 var(--t-body-s) var(--f-sans); color: var(--c-muted); margin-top: 2px; }

/* Лестница жара. Цвет уровня обязан совпадать с тем, который он носит внутри
   игры, поэтому берётся из токенов, а не подбирается на месте. */
.heat { display: flex; gap: 3px; flex: 0 0 auto; }
.heat i { width: 6px; height: 6px; border-radius: 50%; background: var(--c-dim); }
.heat i.on-1 { background: var(--c-heat-1); }
.heat i.on-2 { background: var(--c-heat-2); }
.heat i.on-3 { background: var(--c-heat-3); }
.heat i.on-4 { background: var(--c-heat-4); }
.heat i.on-5 { background: var(--c-heat-5); }

.faces { display: flex; align-items: center; }
.faces img {
  width: 40px; height: 40px; border-radius: 50%;
  border: 2px solid var(--c-bg); background: var(--c-surface-high);
  object-fit: cover; display: block;
  /* Позиционируется, чтобы работал порядок наложения ниже. */
  position: relative;
}
/*
 * НАХЛЁСТ В ДОЛЯХ ПРИЛОЖЕНИЯ, а не на глаз. Там лицо 34, а второе кладут со
 * сдвигом 22 (couple_strip.dart), то есть перекрытие 12 из 34, чуть больше
 * трети. У нас лицо 40, и та же доля даёт 14, а не 12: со старым числом пара
 * стояла почти рядом, а не сложенной стопкой. Число одно на все три места,
 * иначе полосы разъедутся между экранами.
 */
.faces img + img { margin-left: -14px; }

/*
 * ПЕРВЫЙ ЛЕЖИТ ПОВЕРХ ВТОРОГО, а не наоборот, и это НЕ вкусовщина.
 *
 * В приложении полоса пары рисует их стопкой (`couple_strip.dart`): второго
 * кладут со сдвигом 22, а первого сверху и без сдвига. По потоку же позже
 * идущий накрывает раньше идущего, то есть ровно наоборот, и у нас нахлёст
 * получался зеркальным.
 *
 * Владелец объяснил, зачем в приложении именно так: «первый он, и он сверху на
 * ней, это сделано специально, чтобы были видны крестик и нолик». Значок сидит
 * в правом нижнем углу лица, то есть КАК РАЗ В МЕСТЕ НАХЛЁСТА: накрытый первый
 * терял свой крестик целиком.
 *
 * ТОЛЬКО ПРЯМЫЕ ДЕТИ, и это не педантизм. Первая редакция писала просто
 * `.faces img:first-child`, а в варианте со значком картинка лежит ВНУТРИ своей
 * коробки и там тоже первая: единицу получали обе, у первого коробка, у второго
 * картинка. Коробки со значением `auto` стопки не заводят, поэтому обе единицы
 * сходились в одном слое, а при равных числах побеждает тот, кто позже в
 * разметке, то есть второй. Порядок оставался прежним, и проверка это поймала.
 */
.faces > img:first-child,
.faces > .faces__odno:first-child { z-index: 1; }

/* Лицо со значком: значок прибит к своей коробке, а не к картинке. У картинки
   нет своего места в потоке, она лежит в ряду внахлёст, и значок на ней уехал
   бы вместе с этим нахлёстом. Заводится только там, где значок передали
   (сейчас это крестики-желания); остальные игры получают прежнюю разметку. */
.faces__odno { position: relative; display: block; }
.faces--sZnakom .faces__odno + .faces__odno { margin-left: -14px; }
.faces--sZnakom .faces__odno + .faces__odno img { margin-left: 0; }

/* Значок сидит в правом нижнем углу лица, на своей подложке: без неё крест по
   светлой части снимка теряется. */
.faces__znak {
  position: absolute;
  right: -2px; bottom: -2px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: var(--c-bg);
  border: 1.5px solid var(--c-bg);
  box-shadow: 0 0 0 1px rgba(255, 255, 255, .06);
}

.badge {
  display: inline-block;
  font: 600 var(--t-label-s) var(--f-mono);
  letter-spacing: var(--tr-label-s);
  text-transform: uppercase;
  padding: 3px 9px;
  border-radius: var(--r-tag, 10px);
  border: 1px solid rgba(212, 175, 55, .30);
  background: rgba(212, 175, 55, .12);
  color: var(--c-gold);
}
.badge--plain { border-color: var(--c-border); background: none; color: var(--c-faint); }
/* Бейдж состояния это ссылка, а не украшение: он должен выглядеть нажимаемым. */
a.badge { text-decoration: none; transition: border-color var(--m-standard) var(--m-ease), color var(--m-standard) var(--m-ease); }
a.badge:hover { border-color: rgba(212,175,55,.55); color: var(--c-text); }
a.badge--go:hover { color: var(--c-on-gold); filter: brightness(1.06); }
/* Дверь наверх рядом с бейджем «бесплатно»: без премиума человек должен
   видеть и где он, и куда идти, а не догадываться по тому, чего ему не
   показали. */
.badge--go {
  text-decoration: none;
  margin-left: var(--s-2);
  background: var(--g-flame);
  /* Волосина по краю: отсчёт заливки от той же коробки, по которой рисуем. */
  background-origin: border-box;
  color: var(--c-on-gold);
  border-color: transparent;
}

.btn {
  appearance: none; -webkit-appearance: none;
  -webkit-tap-highlight-color: transparent;
  /* Флекс, а не строчный блок. Без этого значок и подпись внутри кнопки
     встают по БАЗОВОЙ ЛИНИИ текста, и подпись оказывается ниже середины
     значка. Ровно это и было видно в окне выбора оплаты. Заодно только
     теперь начинают работать gap и justify-content, до этого они были
     пустым звуком. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-3);
  font: 600 var(--t-label-l) var(--f-sans);
  border-radius: var(--r-ctl, 14px);
  padding: 13px var(--s-6);
  cursor: pointer;
  border: 1px solid var(--c-border-strong);
  background: none;
  color: var(--c-text);
  transition: transform var(--m-micro) var(--m-ease), border-color var(--m-standard) var(--m-ease);
}
.btn:active { transform: scale(.96); }
.btn:hover { border-color: rgba(212, 175, 55, .45); }

/*
 * ЗАПЕРТАЯ КНОПКА ОБЯЗАНА ВЫГЛЯДЕТЬ ЗАПЕРТОЙ.
 *
 * Этого правила здесь НЕ БЫЛО ВОВСЕ, и золотая `disabled` выглядела точно как
 * живая: тот же цвет, та же тень, то же нажатие под пальцем. Владелец:
 * «на мобайле жму кнопку начать игру, чего-то не реагирует». Кнопка и правда
 * была заперта, ждала, пока второй допишет задания, но сказать об этом ей было
 * нечем.
 *
 * Гасим и снимаем отклик на нажатие, а не прячем: спрятанная кнопка читается
 * как «этого действия здесь нет», а погашенная как «ещё не время». Почему не
 * время, говорит строка рядом с ней, это дело экрана.
 */
.btn[disabled] {
  opacity: .42;
  cursor: default;
}
.btn[disabled]:active { transform: none; }
.btn[disabled]:hover { border-color: var(--c-border-strong); }
.btn--gold {
  background: var(--g-flame);
  /* Волосина по краю: отсчёт заливки от той же коробки, по которой рисуем. */
  background-origin: border-box;
  color: var(--c-on-gold);
  border-color: transparent;
  box-shadow: var(--sh-gold);
}

.muted { color: var(--c-muted); font: 400 var(--t-body-s) var(--f-sans); }
.error { color: var(--c-danger); font: 400 var(--t-body-s) var(--f-sans); }


/* ==========================================================================
   ШИРОКИЙ ЭКРАН
   ==========================================================================
   Не та же полоса в другой позе, а другая навигация.

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

@media (min-width: 1024px) {

  .nav-phone { display: none; }

  /* Ширина колонки ОДНИМ числом. Её знает не только сетка каркаса: игровой
     экран стоит fixed поверх всего и должен начинаться там, где колонка
     кончается, иначе он накрывает её собой. Два числа в двух местах
     разъедутся на первом же изменении. */
  .frame {
    --kolonka: 264px;
    grid-template-columns: var(--kolonka) minmax(0, 1fr);
  }

  .frame__content {
    /* Полосы снизу больше нет, значит и место под неё не нужно. */
    padding: 0 var(--s-8) var(--s-12);

    /**
     * ВТОРАЯ КОЛОНКА ЗАДАНА ЯВНО, а не выведена из порядка соседей.
     *
     * Рельс слева рисует nav.js, и до его прихода содержимое остаётся ЕДИНСТВЕННЫМ
     * ребёнком сетки. Первый ребёнок садится в первую колонку, а первая
     * колонка это та самая 264-пиксельная полоска рельса: заглушка сжималась
     * до двухсот с небольшим и прижималась к левому краю монитора. Ровно это
     * владелец и увидел, и увидел он это только теперь, потому что раньше на
     * том месте вообще ничего не рисовалось.
     *
     * Порядок детей это не то, на что можно опираться, когда часть детей
     * появляется позже.
     */
    grid-column: 2;
  }

  /* Предел ширины есть, но щедрый: при 900 на экране в 1440 половина справа
     оставалась пустой, а обложки сжимались в два узких столбца. Приложение
     рисует на телефон, здесь ширину задаём сами и по своему экрану.

     Число живёт в переменной, потому что предел нужен ДВОИМ: экрану и верхней
     полосе. Полоса лежит рядом с экраном, а не внутри, и без общего предела
     на широком мониторе аватар уезжал к дальнему краю, а заголовок оставался
     на месте. */
  .frame__content { --kol: 1240px; }

  /* margin-inline: auto, а не просто max-width. Без него остаток ширины
     целиком уходил вправо: на 2560 это почти 900 пикселей пустоты одним
     куском, ровно то, на что жаловался владелец. */
  .screen,
  .topbar {
    max-width: var(--kol);
    margin-inline: auto;
    width: 100%;
  }

  /* Верх списка тот же, что и на телефоне: space4. Ступень не меняет ритм,
     она меняет только ширину. */
  .screen {
    padding-top: var(--s-4);
  }

  .nav-wide {
    display: flex;
    flex-direction: column;
    gap: var(--s-2);
    position: sticky;
    top: 0;
    height: 100dvh;
    padding: var(--s-6) var(--s-4);
    border-right: 1px solid var(--c-border);
    background: rgba(20, 14, 14, .35);
  }

  /*
   * Имя стало кнопкой и ведёт на витрину. Значит нужен сброс: браузер даёт
   * кнопке свой шрифт, серую рамку и выравнивание по центру, и без сброса
   * логотип превратился бы в серую пилюлю посреди колонки.
   */
  .nav-wide__brand {
    display: block;
    width: 100%;
    text-align: left;
    background: none;
    border: 0;
    cursor: pointer;
    color: var(--c-text);
    font-family: var(--f-serif);
    font-size: 20px;
    line-height: 26px;
    letter-spacing: .2px;
    padding: 0 var(--s-2) var(--s-2);
  }

  /* Шапка пары. На телефоне она живёт внутри вкладки «Мы», здесь видна
     всегда: место есть, и это первое, что хочется видеть. */
  .nav-wide__couple {
    display: flex; align-items: center; gap: var(--s-3);
    padding: var(--s-3) var(--s-2);
    border-radius: var(--r-md);
    text-decoration: none; color: inherit;
    transition: background var(--m-standard) var(--m-ease);
  }
  .nav-wide__couple:hover { background: rgba(255, 255, 255, .03); }
  .nav-wide__names { min-width: 0; }
  .nav-wide__names b { display: block; font: 600 var(--t-title-s) var(--f-sans);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .nav-wide__names span { display: block; font: 400 var(--t-body-s) var(--f-sans); color: var(--c-faint); }

  .nav-wide__sep { height: 1px; background: var(--c-border); margin: var(--s-2) var(--s-2); }

  .nav-wide__item {
    appearance: none; -webkit-appearance: none;
    background: none; border: 0; cursor: pointer; color: inherit;
    display: flex; align-items: center; gap: var(--s-3);
    padding: var(--s-3);
    border-radius: var(--r-md);
    font: 600 var(--t-title-s) var(--f-sans);
    text-align: left;
    transition: background var(--m-standard) var(--m-ease);
  }
  .nav-wide__item:hover { background: rgba(255, 255, 255, .04); }
  .nav-wide__item:focus { outline: none; }
  .nav-wide__item:focus-visible { outline: 2px solid rgba(212, 175, 55, .9); outline-offset: 2px; }
  .nav-wide__item .glyph { width: 22px; height: 22px; }
  .nav-wide__item[aria-current="page"] {
    background: rgba(212, 175, 55, .12);
    border: 1px solid rgba(212, 175, 55, .28);
    padding: calc(var(--s-3) - 1px);
  }

  /*
   * Подпись блока быстрых ссылок. Такая же, как на боевой панели, где над
   * пунктами стоят «главное» и «аккаунт»: она отделяет спину каркаса (три
   * раздела и Premium) от того, что просто под рукой.
   *
   * Отступ сверху больше, чем снизу: подпись принадлежит тому, что НИЖЕ неё, и
   * равные поля читались бы как разрыв посреди списка.
   */
  .nav-wide__label {
    font: 600 var(--t-label-s) var(--f-mono);
    letter-spacing: var(--tr-label-s);
    text-transform: uppercase;
    color: var(--c-faint);
    padding: var(--s-3) var(--s-3) var(--s-2);
    margin-top: var(--s-2);
  }
  /*
   * Две быстрые ссылки уводят наружу и потому написаны `<a>`, а не кнопкой.
   * Своего вида им не заводим, они берут тот же `.nav-wide__item`; снять с них
   * надо ровно одно, подчёркивание, которое кнопке снимать было не от чего.
   */
  a.nav-wide__item { text-decoration: none; }

  .nav-wide__foot { margin-top: auto; display: flex; flex-direction: column; gap: var(--s-2); }
  .nav-wide__quiet {
    font: 400 var(--t-body-s) var(--f-sans);
    color: var(--c-faint);
    background: none; border: 0; padding: var(--s-2) var(--s-3);
    text-align: left; cursor: pointer; border-radius: var(--r-sm);
  }
  .nav-wide__quiet:hover { color: var(--c-text); background: rgba(255, 255, 255, .04); }

  /* Плитки разделов на широком экране идут в два столбца: в один они
     превращались в километровый список с воздухом по бокам. */
  .grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3); }

  /* Плиток в ряду больше: на телефоне их три, потому что там ширина телефона.
     Сама плитка та же, портретная и с тем же свечением, меняется только
     сколько их помещается. */
  .arts { grid-template-columns: repeat(4, 1fr); gap: var(--s-4); }
  .art--big { height: 240px; }
}

@media (min-width: 1440px) {
  .frame { --kolonka: 288px; }
  .frame__content { --kol: 1400px; }
  .grid-2 { grid-template-columns: repeat(3, 1fr); }
  .arts { grid-template-columns: repeat(5, 1fr); }
  .art--big { height: 280px; }
}

/* ==========================================================================
   БОЛЬШИЕ МОНИТОРЫ
   ==========================================================================
   Этих ступеней в приложении НЕТ и взяться им неоткуда: приложение рисует
   телефон. Ступени мои, по ходовым размерам больших мониторов: 2560x1440
   (типичные 32 дюйма) и 3840x2160. Правило продолжает то, что уже работает
   ниже: растёт число обложек в ряду, а не размер каждой, иначе на большом
   экране получается витрина из пяти огромных плиток и та же пустота, только
   дороже.

   Предел ширины остаётся: строка текста во всю ширину 32 дюймов нечитаема.
   Но он щедрый, и колонка отцентрована, поэтому запас делится поровну, а не
   висит справа. */

@media (min-width: 1800px) {
  .frame { --kolonka: 320px; }
  .frame__content { --kol: 1680px; }
  .arts { grid-template-columns: repeat(6, 1fr); }
  .art--big { height: 320px; }
  .grid-2 { grid-template-columns: repeat(4, 1fr); }
}

@media (min-width: 2400px) {
  .frame__content { --kol: 2040px; }
  .arts { grid-template-columns: repeat(7, 1fr); }
  .art--big { height: 360px; }
}

/* Кому не нужна анимация, тому её и не показываем. */
@media (prefers-reduced-motion: reduce) {
  * { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}


/* ==========================================================================
   ШТОРКА СНИЗУ
   ==========================================================================
   Размеры из приложения, lib/theme/sheets.dart: скругление верхних углов 28,
   затемнение чёрное при 55%.

   Одна на все случаи. Как только шторок станет две, они разойдутся по
   высоте, скруглению и скорости, и это будет видно.
   ========================================================================== */

.sheet-scrim {
  position: fixed;
  inset: 0;
  /* Клавиатура отодвигает шторку ПОЛЕМ, а не поднятым краем.

     Сначала здесь стоял `bottom: var(--кб-низ)`, и низ затемнения уезжал вверх
     вместе со шторкой. Кнопки это спасло, но открыло полосу между затемнением
     и краем экрана, а в ней светилась комната морского боя. Владелец: «под
     клавиатурой вижу картинку неба».

     Поле снизу двигает шторку ровно так же, но само затемнение при этом
     остаётся во весь экран и накрывает всё, включая эту полосу. Величину меряет
     `sheet.js` через `visualViewport`, там же и разбор. */
  padding-bottom: var(--kb-niz, 0px);
  /* Видимая часть на iOS не только короче, но и СДВИНУТА вниз внутри
     вёрсточной; без этой поправки верх затемнения оказывается выше экрана.
     Величину меряет `sheet.js`, там же и разбор. */
  top: var(--kb-verkh, 0px);
  z-index: 60;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgba(0, 0, 0, .55);
  opacity: 0;
  transition: opacity var(--m-standard) var(--m-ease);
}
.sheet-scrim.on { opacity: 1; }

.sheet {
  width: 100%;
  max-width: 560px;
  /* Выше оставшейся видимой части шторка быть не может: подняв низ над
     клавиатурой, но оставив прежнюю высоту, мы бы просто выпихнули её верх за
     край экрана вместе с заголовком. `86dvh` остаётся запасным на случай, когда
     замера нет. */
  max-height: min(86dvh, calc(var(--kb-vyisota, 86dvh) - var(--s-6)));
  /* Столбиком, чтобы содержимое умело прокручиваться отдельно от заголовка,
     а нижние кнопки могли прилипнуть к краю шторки. */
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  background: var(--c-surface-high);
  border-radius: var(--r-sheet) var(--r-sheet) 0 0;
  padding: var(--s-3) var(--s-5) calc(var(--s-6) + env(safe-area-inset-bottom, 0px));
  transform: translateY(16px);
  transition: transform var(--m-expressive) var(--m-ease);
}
.sheet-scrim.on .sheet { transform: translateY(0); }

/* Ручка нужна не для красоты: она говорит, что это тянется вниз. Без неё
   ищут крестик. */
.sheet__grip {
  width: 36px; height: 4px;
  border-radius: var(--r-pill);
  background: var(--c-dim);
  margin: 0 auto var(--s-4);
}
.sheet__title {
  font: 400 var(--t-headline-s) var(--f-serif);
  margin-bottom: var(--s-4);
}
.sheet__body { padding-bottom: var(--s-2); }

/*
 * Выход из шторки, выросшей во весь экран. В обычном виде его нет и не надо:
 * там есть ручка, скругления и тычок мимо, три знака того, что это разговор, а
 * не страница. Во весь экран не остаётся ни одного, и без стрелки шторка
 * становится страницей без выхода. Показывается правилом ниже, в мобильной
 * ветке: на широком экране шторка карточкой посередине и во весь экран не
 * растёт вовсе.
 *
 * СЕЛЕКТОР ИЗ ДВУХ КЛАССОВ, И ЭТО НЕ ПРИДИРКА. Одного `.sheet__назад` не
 * хватает: кнопка носит ещё и общий `.back-btn`, у которого `display:
 * inline-flex`, и он объявлен НИЖЕ по файлу. При равном весе побеждает
 * последний, и стрелка была видна всегда, в том числе у обычной шторки, где
 * она лишняя. Поймал это щуп, из кода не видно ничего.
 */
.sheet .sheet__nazad { display: none; }

/*
 * ШТОРКА С ПОЛЕМ ВВОДА, НА ТЕЛЕФОНЕ, ВО ВЕСЬ ЭКРАН.
 *
 * Владелец: «нужно, чтобы шторка до самого верха поднималась на мобайле».
 * Клавиатура съедает больше половины экрана, и в остатке надо уместить
 * заголовок, поле, кубики и две кнопки. Во весь экран места хватает всем, и
 * кнопки перестают жаться к самой клавиатуре.
 *
 * Класс вешает `sheet.js` и только тем шторкам, внутри которых есть поле: там
 * же и разбор, почему признак именно такой, а не «мобильный экран».
 *
 * Прокручивается ТЕЛО, а не вся шторка: иначе при прокрутке уезжал бы
 * заголовок, а вместе с ним и счёт «3 / 15», по которому и понятно, где ты в
 * обходе.
 */
@media (max-width: 1023px) {
  /*
   * ГЛУХОЙ ФОН, А НЕ ПОЛУПРОЗРАЧНОЕ ЗАТЕМНЕНИЕ. Владелец: «пусть тогда
   * полностью в один цвет на мобайле».
   *
   * Обычное затемнение это чёрное при 55%, и сквозь него видно страницу: для
   * короткого разговора это правильно, там важно не потерять место, откуда
   * пришёл. Шторка во весь экран не разговор, а работа: пятнадцать заданий
   * пишут не отвлекаясь, и просвечивающая сквозь неё комната морского боя
   * только мешает. Цвет берём ТОТ ЖЕ, что у самой шторки, и полоса под
   * клавиатурой перестаёт читаться отдельной вещью.
   */
  .sheet-scrim--glukhaya { background: var(--c-surface-high); }

  .sheet--vyisokaya {
    height: 100%;
    max-height: none;
    border-radius: 0;
    padding-top: var(--s-5);
  }
  .sheet--vyisokaya .sheet__grip { display: none; }
  /* Ручка ушла, тычку мимо некуда попасть: выход теперь стрелкой, слева
     сверху, там же, где он на всех наших экранах. */
  .sheet--vyisokaya .sheet__nazad {
    display: inline-flex;
    align-self: flex-start;
    margin-bottom: var(--s-3);
  }
  .sheet--vyisokaya .sheet__body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
  }
}

/* На широком экране шторка снизу читается странно: там ничего не тянут
   пальцем. Ставим её посередине карточкой. */
@media (min-width: 1024px) {
  .sheet-scrim { align-items: center; }
  .sheet {
    border-radius: var(--r-xl);
    max-height: 80dvh;
    padding: var(--s-5) var(--s-6) var(--s-6);
  }
  .sheet__grip { display: none; }
}


/* ---------- СТРОКИ НАСТРОЕК ---------- */

.row {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--c-border);
  /* space3 сверху и снизу, space4 по бокам: listTileTheme в theme.dart.
     Минимальная высота 56 это однострочный ListTile, ниже строка выглядит
     тесной, выше начинается воздух. */
  padding: var(--s-3) var(--s-4);
  min-height: 56px;
  color: inherit;
  cursor: pointer;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}
.row:last-child { border-bottom: 0; }

/* Строка, по которой не нажимают: она показывает, а не ведёт. */
.row--flat { cursor: default; }

/* Состояния имени в строке. Классами, а не цветом по месту: иначе следующий
   такой же случай напишут вручную и чуть другим оттенком. */
.row__name--empty { color: var(--c-faint); }
.row__name--danger { color: var(--c-danger); }
.row:hover .row__name { color: var(--c-gold); }
.row__body { min-width: 0; flex: 1; }
/* Имя, которое кто-то ввёл, идёт в ОДНУ строку с многоточием: правило из
   приложения, оно не даёт чужому длинному имени сломать ряд. */
.row__name {
  display: block;
  font: 600 var(--t-title-m) var(--f-sans);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  transition: color var(--m-standard) var(--m-ease);
}
.row__note {
  display: block;
  font: 400 var(--t-body-s) var(--f-sans);
  color: var(--c-muted);
  margin-top: 2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.row__face {
  width: 48px; height: 48px; border-radius: 50%;
  object-fit: cover; display: block;
  background: var(--c-surface-alt);
  border: 2px solid var(--c-dim);
}
.row__face--him { border-color: var(--c-player-1); }
.row__face--her { border-color: var(--c-player-2); }
.row__go { color: var(--c-faint); flex: 0 0 auto; }

/**
 * Строка, у которой ДЕЙСТВИЙ НЕСКОЛЬКО.
 *
 * Обычная строка это одна кнопка целиком, и пока действие одно, так и надо. Но
 * у своей доски их три: играть, править, убрать. Кнопка внутри кнопки браузером
 * не собирается, поэтому строка становится рядом из трёх: тело растягивается и
 * держит главное нажатие, хвост это два значка.
 *
 * Пара к `.row`, а не к одной игре: список с правкой и удалением встретится
 * ещё не раз, и второй такой же, написанный на месте, разойдётся с этим.
 */
.row__zhatj {
  flex: 1;
  min-width: 0;
  display: block;
  padding: 0;
  background: none;
  border: 0;
  text-align: left;
  color: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.row__zhatj:hover .row__name { color: var(--c-gold); }

/* Значок-кнопка в хвосте строки. Сорок пикселей это минимальная цель под
   палец: меньше промахиваются, а «удалить» промахом это худшее из промахов. */
.row__kn {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: none;
  border: 0;
  border-radius: var(--r-sm);
  color: var(--c-faint);
  cursor: pointer;
  transition: color var(--m-standard) var(--m-ease),
              background var(--m-standard) var(--m-ease);
}
.row__kn:hover { color: var(--c-text); background: var(--c-surface-alt); }
.row__kn--opasno:hover { color: var(--c-danger); }

/* Премиум это строка с пилюлей, а не абзац. Из приложения, и там записано
   почему: «человек приходит сюда узнать, включено или нет, а абзац это
   плохой способ сказать да». */
.pill {
  flex: 0 0 auto;
  font: 600 var(--t-label-s) var(--f-mono);
  letter-spacing: 1px;
  text-transform: uppercase;
  padding: 4px var(--s-3);
  border-radius: var(--r-tag, 10px);
  background: var(--c-surface-alt);
  color: var(--c-muted);
  border: 1px solid transparent;
}
.pill--on {
  background: rgba(212, 175, 55, .14);
  border-color: rgba(212, 175, 55, .5);
  color: var(--c-gold);
}

.btn--danger { border-color: rgba(244, 63, 94, .5); color: var(--c-danger); }
.btn--danger:hover { border-color: var(--c-danger); }

/* Выбор языка: сетка, а не выпадающий список. Языков десять, и все они
   должны быть видны разом, иначе человек не знает, есть ли его. */
.langs { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--s-2); }
@media (min-width: 640px) { .langs { grid-template-columns: repeat(3, 1fr); } }
.lang {
  display: block; text-align: left; text-decoration: none;
  padding: var(--s-3);
  border-radius: var(--r-sm);
  border: 1px solid var(--c-border);
  background: var(--c-surface);
  color: var(--c-muted);
  font: 400 var(--t-body-m) var(--f-sans);
  cursor: pointer;
}
.lang:hover { border-color: rgba(212, 175, 55, .45); color: var(--c-text); }
.lang[aria-current="true"] {
  border-color: rgba(212, 175, 55, .5);
  background: rgba(212, 175, 55, .12);
  color: var(--c-gold);
}

/* Сетка лиц в шторке. Те же двенадцать и тот же приём с цветом, что на
   приветствии: цвет выбранного и есть ответ про пол. */
.faces-pick { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--s-3); margin: var(--s-4) 0; }
.faces-pick button {
  aspect-ratio: 1; padding: 0; cursor: pointer;
  border-radius: 50%; overflow: hidden;
  background: var(--c-surface-high);
  border: 1px solid var(--c-border-strong);
  opacity: .55;
  -webkit-tap-highlight-color: transparent;
  transition: opacity var(--m-standard) var(--m-ease), border-color var(--m-standard) var(--m-ease);
}
.faces-pick img { width: 100%; height: 100%; object-fit: cover; display: block; }
.faces-pick button.on { opacity: 1; border-width: 2.2px; }
.faces-pick button.him.on { border-color: var(--c-player-1); box-shadow: 0 0 16px -2px var(--c-player-1); }
.faces-pick button.her.on { border-color: var(--c-player-2); box-shadow: 0 0 16px -2px var(--c-player-2); }

/* Выборщик лиц: ТРИ столбца, как в приложении, когда лица одного места.
   Двенадцать вперемешку (место ещё не выбрано) идут по четыре: иначе шторка
   вытягивается на четыре ряда. */
.faces-pick--six { grid-template-columns: repeat(3, 1fr); }

/* Поле ввода в шторке. Шестнадцать пикселей это не про красоту: Safari на
   iPhone приближает страницу при фокусе на поле мельче. */
.sheet-input {
  width: 100%; box-sizing: border-box;
  margin-top: var(--s-2);
  padding: var(--s-3) var(--s-4);
  font-size: var(--t-input-min);
  font-family: var(--f-sans);
  color: var(--c-text);
  background: var(--c-surface-alt);
  border: 1px solid var(--c-border-strong);
  border-radius: var(--r-sm);
  outline: none;
  caret-color: var(--c-gold);
  transition: border-color var(--m-standard) var(--m-ease);
}
.sheet-input:focus { border-color: var(--c-gold); }
.sheet-input::placeholder { color: var(--c-dim); }

/* Карточка настроек берёт приподнятую поверхность, а рамка это граница при
   16%, как в палитре приложения: карточка отделяется тоном, а не контуром. */
.card { background: var(--c-surface); border-color: var(--c-border); }


/* ==========================================================================
   ПОДДЕРЖКА
   ==========================================================================
   Источник: {язык}/cabinet/support/index.html. В приложении этого раздела
   нет вовсе, поэтому здесь я держусь боевой страницы, а цвета и отступы беру
   из наших токенов. */

/* Заголовок переписки: тема обращения бывает длинной, крупный заголовок
   экрана под неё не годится. */
.title--sm { font-size: 22px; line-height: 28px; }

/* ---------- лента писем ---------- */

.msgs { display: flex; flex-direction: column; gap: var(--s-3); }

.msg {
  max-width: 82%;
  align-self: flex-start;
  background: var(--c-surface-high);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  padding: var(--s-3) var(--s-4);
}

/* Своё письмо справа и тёплое, как s-msg-user на бою. */
.msg--mine {
  align-self: flex-end;
  background: rgba(212, 175, 55, .10);
  border-color: rgba(212, 175, 55, .28);
}

/* Подпись есть только у поддержки: своё письмо не нужно подписывать своим
   именем, это и так видно по стороне. Так же на бою. */
.msg__who {
  font: 600 var(--t-label-s) var(--f-mono);
  letter-spacing: var(--tr-label-s);
  color: var(--c-gold);
  text-transform: uppercase;
  margin-bottom: var(--s-1);
}

.msg__text {
  font: 400 var(--t-body-m) var(--f-sans);
  color: var(--c-text);
  white-space: pre-wrap;
  word-break: break-word;
}
.msg__text a { color: var(--c-gold); }

.msg__time {
  margin-top: var(--s-1);
  font: 400 var(--t-label-s) var(--f-mono);
  color: var(--c-faint);
}

/* ---------- ответ ---------- */

.reply { margin-top: var(--s-5); }
.reply__foot {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-top: var(--s-3);
  flex-wrap: wrap;
}
/* Счётчик знаков прижат к дальнему краю: он справочный, не для чтения. */
.reply__foot .muted { margin-left: auto; }

textarea.sheet-input { resize: vertical; line-height: 1.45; }

.closed-note {
  margin-top: var(--s-5);
  text-align: center;
  padding: var(--s-4);
  border: 1px dashed var(--c-border);
  border-radius: var(--r-md);
}

/* ---------- список обращений ---------- */

/* Начало последнего письма второй строкой. Одна строка, дальше многоточие:
   это напоминание, о чём разговор, а не сам разговор. */
.row__preview {
  margin-top: var(--s-1);
  color: var(--c-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  display: block;
}

/* Точка непрочитанного. Числа нет намеренно: важно только «тебе ответили»,
   как и на боевой странице. */
.dot-new {
  flex: 0 0 auto;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--c-gold);
  box-shadow: 0 0 10px rgba(212, 175, 55, .8);
}

.empty { text-align: center; padding: var(--s-8) var(--s-4); }
.empty__title {
  font: 600 var(--t-title-s) var(--f-sans);
  color: var(--c-text);
  margin-bottom: var(--s-2);
}
.empty__sub { font: 400 var(--t-body-s) var(--f-sans); color: var(--c-faint); }

/* ---------- новое обращение ---------- */

/* Вместо <select> три чипа в столбик: выпадающий список это чужой для нас
   элемент, а тем всего три и они разной длины. */
.chips--stack {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  overflow: visible;
  margin-top: var(--s-3);
}
.chips--stack .chip { width: 100%; text-align: left; }

.form-error {
  margin-top: var(--s-3);
  padding: var(--s-3) var(--s-4);
  border-radius: var(--r-md);
  border: 1px solid rgba(244, 63, 94, .4);
  background: rgba(244, 63, 94, .08);
  color: var(--c-danger);
  font: 400 var(--t-body-s) var(--f-sans);
}
.form-error[hidden] { display: none; }


/* ==========================================================================
   НАСТРОЙКИ: СТРОКА ЗАГОЛОВКА И КРАСНАЯ ПИЛЮЛЯ
   ==========================================================================
   В приложении на экране «Я» сверху обычная строка AppBar, а не засечное
   полотно с надзаголовком и подводкой (lib/me/me_screen.dart). Полотно и
   давало тот лишний воздух, на который жаловался владелец. */

.head--bar { margin-bottom: var(--s-4); }
.head--bar .title { margin: 0; }

/* Выход живёт внутри строки с почтой, поэтому он пилюля, а не кнопка во всю
   ширину: строка отвечает на один вопрос, «чья это учётная запись», и выход
   это её же вторая половина. */
.pill--danger {
  border: 1px solid rgba(244, 63, 94, .45);
  background: rgba(244, 63, 94, .10);
  color: var(--c-danger);
  cursor: pointer;
  flex: 0 0 auto;
}
.pill--danger:hover { border-color: var(--c-danger); background: rgba(244, 63, 94, .16); }


/* ==========================================================================
   ЗНАЧКИ НАСТРОЕК
   ==========================================================================
   Встроенный рисунок вместо картинки: цвет наследуется от текста, поэтому
   красная строка красит и свой значок сама. Размер 22, как у нашего .glyph,
   чтобы строки не разъезжались там, где рядом стоят оба набора. */

.ico {
  width: 22px; height: 22px;
  display: block;
  flex: 0 0 auto;
}


/* ==========================================================================
   ИМЯ РАЗДЕЛА В ВЕРХНЕЙ ПОЛОСЕ
   ==========================================================================
   Сквозное правило: имя раздела стоит в полосе, а не отдельным блоком под
   ней. Своих заголовков у экранов нет. Причина в том, что имя раздела и так
   написано на вкладке внизу, а повторять его отдельной строкой, да ещё с
   надзаголовком над ней, значит отдавать две строки высоты на то, что и без
   них понятно.

   Начертание то же, что у AppBar в приложении: titleLarge, засечный, 20/26,
   w500 (lib/theme/theme.dart, appBarTheme titleTextStyle). */

/*
 * Заголовок полосы ведёт на витрину ТОЛЬКО когда в нём стоит имя. Класс вешает
 * `поставитьЗаголовок` в nav.js, и он же снимает: подпись раздела нажимать
 * незачем, она описывает то, что уже открыто.
 */
.topbar__title--vedyot { cursor: pointer; }
.topbar__title {
  font-family: var(--f-serif);
  font-weight: 500;
  font-size: 20px;
  line-height: 26px;
  color: var(--c-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 0 1 auto;
  margin-right: var(--s-1);
}

/* Подводка сразу под полосой, без заголовка над ней. */
.sub--lead { margin-top: 0; margin-bottom: var(--s-4); }

/* Прятать больше нечего: без премиума метка теперь ОДНА, надпись как в шапке
   сайта. Раньше их было две, состояние и приглашение, и на узком экране они
   не помещались рядом с именем раздела. */

/* Пилюля-дверь: то же место, что и у красной, но зовёт, а не предупреждает. */
.pill--go {
  border: 1px solid rgba(212, 175, 55, .45);
  background: rgba(212, 175, 55, .12);
  color: var(--c-gold);
  text-decoration: none;
  cursor: pointer;
  flex: 0 0 auto;
}
.pill--go:hover { border-color: var(--c-gold); background: rgba(212, 175, 55, .18); }

/* ---------- окно удаления ---------- */

/* Треугольник сверху: на боевой странице окно удаления открывается именно
   им, и это единственное место во всей внутрянке, где предупреждающий знак
   такого размера оправдан. */
.warn {
  display: flex;
  justify-content: center;
  color: var(--c-danger);
  margin-bottom: var(--s-4);
}
.danger-text { margin: 0 0 var(--s-5); }

/* Предупреждение внутри шторки.

   КРАСНОЕ, решение владельца 27.08.2026: «чтобы бросалось в глаза». Я сначала
   сделал янтарным, рассудив, что красное во внутрянке значит «отменить
   нельзя», а смену пола отменяют выбором прежнего лица. Владелец решил иначе,
   и довод понятен: правка задевает НЕ ТОЛЬКО ТЕБЯ, а это редкий случай, и
   пропустить его нельзя. Полужирный по той же причине. */
.sheet-warn {
  margin: 0 0 var(--s-4);
  padding: var(--s-3) var(--s-4);
  border-radius: var(--r-sm);
  background: rgba(244, 63, 94, .12);
  border: 1px solid rgba(244, 63, 94, .42);
  color: var(--c-danger);
  font: 600 var(--t-body-s) var(--f-sans);
}
.sheet-warn[hidden] { display: none; }

/* Поле дёргается, когда слово подтверждения набрано не так. Ровно как на
   бою: там на две секунды вешается класс error. */
.shake { animation: drozhj 420ms var(--m-ease); border-color: var(--c-danger) !important; }
@keyframes drozhj {
  0%, 100% { transform: translateX(0) }
  20% { transform: translateX(-6px) }
  40% { transform: translateX(6px) }
  60% { transform: translateX(-4px) }
  80% { transform: translateX(4px) }
}




/* Пометка подписки: корона и слово рядом с именем раздела.

   Ни рамки, ни подложки: владелец 25.08.2026, «просто слово premium, без
   рамки, просто жёлтым цветом, или как вот делают, что просто pro
   приписывают аккуратно». Первый заход был моношириной, и он сказал «выглядит
   плохо». Правильно сказал: моно рядом с засечным именем раздела висит
   отдельной строкой системного вида.

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

   Число 13 моё: между label-s (11) и body-s (13) у нас нет ступени для
   засечного начертания, а 11 засечным мельчит. */
.pro {
  display: flex;
  align-items: center;
  gap: 3px;
  flex: 0 0 auto;
  margin-left: 3px;
  color: var(--c-gold);
  white-space: nowrap;
  text-decoration: none;

  /* Приписка, а не сосед. Она начинается у ПРАВОГО ВЕРХНЕГО угла названия и
     набрана мельче, как ставят надстрочную пометку. Поднимаем на 7: полоса
     высотой 64 центрует содержимое, название занимает 19..45, значит его верх
     на 19, а середина пометки должна прийтись примерно туда же. */
  align-self: center;
  transform: translateY(-7px);
}
.pro b {
  font-family: var(--f-serif);
  font-weight: 500;
  /* Мельче названия: 11 против 20. Приписка не спорит с тем, к чему
     приписана, иначе это не приписка, а второе слово. */
  font-size: 11px;
  line-height: 1;
  letter-spacing: .2px;
}
.pro:hover { color: var(--c-gold-light); }
.pro svg { display: block; flex: 0 0 auto; }


/* ==========================================================================
   КРИПТООКНО
   ==========================================================================
   Перекраска общего модуля js/crypto-payment.js, без единой правки в нём
   самом. Приём взят с боевой страницы цен: она делает то же самое и
   объясняет почему, «мы не форкаем 1378 строк, используемых десятью языками,
   а вставляем свои стили ПОСЛЕ и выигрываем порядком следования».

   Нам порядка мало, потому что модуль вставляет стили в head при первом
   открытии, то есть позже нашего файла. Отсюда важность у правил. */

.crypto-modal-overlay {
  background: var(--c-scrim) !important;
  backdrop-filter: blur(10px) !important;
  -webkit-backdrop-filter: blur(10px) !important;
}

.crypto-modal {
  background: var(--c-surface) !important;
  border: 1px solid var(--c-border) !important;
  border-radius: var(--r-lg) !important;
  font-family: var(--f-sans) !important;
}

.crypto-modal-title {
  font-family: var(--f-serif) !important;
  font-weight: 500 !important;
  letter-spacing: 0 !important;
  color: var(--c-text) !important;
}

/* Сумма: золото, а не зелёный. Зелёный в нашей палитре значит «сделано», а
   не «столько платить». */
.crypto-modal-price,
.crypto-amount-value,
.crypto-status-text.success { color: var(--c-gold) !important; }
.crypto-copy-btn.copied {
  background: var(--c-gold) !important;
  color: var(--c-on-gold) !important;
  border-color: var(--c-gold) !important;
}

.crypto-modal-subtitle,
.crypto-section-label,
.crypto-qr-label,
.crypto-network-fee { color: var(--c-muted) !important; }

/* Счёт не создался. Плашку рисует сам модуль (до 07.10.2026 он молчал и
   только возвращал кнопку), здесь только цвет и скругление как у .form-error. */
.crypto-invoice-error {
  color: var(--c-danger) !important;
  border-radius: var(--r-md) !important;
}

.crypto-modal-close {
  background: none !important;
  border: 1px solid var(--c-border) !important;
  border-radius: var(--r-tag, 10px) !important;
}
.crypto-modal-close:hover { border-color: var(--c-gold) !important; }

/* Выбор сети. Класс строки у модуля crypto-network-item, а выбранная помечена
   классом active: имена подсмотрены в самом модуле, а не угаданы. */
.crypto-network-item {
  background: var(--c-surface-high) !important;
  border: 1px solid var(--c-border) !important;
  border-radius: var(--r-md) !important;
}
.crypto-network-item.active {
  border-color: rgba(212, 175, 55, .55) !important;
  background: rgba(212, 175, 55, .10) !important;
}
.crypto-network-check { border-color: var(--c-border) !important; }
.crypto-network-item.active .crypto-network-check {
  border-color: var(--c-gold) !important;
  background: var(--c-gold) !important;
}
.crypto-network-item.active .crypto-network-check::after {
  background: var(--c-on-gold) !important;
}

/* «Лучший» знак у сети: у модуля он зелёный, а зелёный у нас значит
   «сделано», а не «советуем». */
.crypto-network-badge {
  background: rgba(212, 175, 55, .16) !important;
  color: var(--c-gold) !important;
  border-radius: var(--r-tag, 10px) !important;
}

/* Главная кнопка золотая, как везде у нас. */
.crypto-pay-btn {
  background: var(--g-flame) !important;
  color: var(--c-on-gold) !important;
  border: 0 !important;
  border-radius: var(--r-ctl, 14px) !important;
  font: 600 var(--t-label-l) var(--f-sans) !important;
}
.crypto-pay-btn:hover { filter: brightness(1.06) !important; }

.crypto-copy-btn,
.crypto-change-network-btn,
.crypto-htx-btn,
.crypto-recovery-continue,
.crypto-recovery-new {
  border-radius: var(--r-ctl, 14px) !important;
  border: 1px solid var(--c-border) !important;
  background: none !important;
  color: var(--c-text) !important;
}
.crypto-copy-btn:hover,
.crypto-change-network-btn:hover,
.crypto-htx-btn:hover { border-color: var(--c-gold) !important; color: var(--c-gold) !important; }

.crypto-payment-box,
.crypto-field {
  background: var(--c-surface-alt) !important;
  border: 1px solid var(--c-border) !important;
  border-radius: var(--r-md) !important;
}
.crypto-address-value { font-family: var(--f-mono) !important; color: var(--c-text) !important; }
.crypto-divider { background: var(--c-border) !important; }
.crypto-spinner { border-right-color: transparent !important; }


/* ==========================================================================
   ДЕЙСТВИЯ В ШТОРКЕ
   ==========================================================================
   Сквозное правило: кнопки в шторке идут СТОЛБИКОМ, никогда в ряд.

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

   Порядок внутри списка тоже часть правила: главное действие первым, отказ
   последним. */

/*
 * ШАГИ УСТАНОВКИ НА ДОМАШНИЙ ЭКРАН.
 *
 * Нумерованный список, а не абзац: человек читает инструкцию с телефоном в
 * руке и возвращается к ней глазами после каждого действия. В сплошном тексте
 * он теряет место и начинает сначала.
 *
 * Номера золотом и своей колонкой: без `list-style-position: outside` длинный
 * шаг переносится под номер и колонка ломается.
 */
.pwa-shagi {
  margin: 0;
  padding-left: var(--s-6);
  list-style-position: outside;
  font: 400 var(--t-body-m) var(--f-sans);
  color: var(--c-text);
}
.pwa-shagi li { margin-bottom: var(--s-3); padding-left: var(--s-1); }
.pwa-shagi li:last-child { margin-bottom: 0; }
.pwa-shagi li::marker { color: var(--c-gold); font-weight: 600; }

/*
 * СВОЕЙ ВЁРСТКИ У ПЛИТКИ «УСТАНОВИТЬ» НЕТ, И ЭТО НЕ УПУЩЕНИЕ.
 *
 * Она стоит последней ячейкой в тех же сетках, что игры и разделы «Мы», и берёт
 * тот же класс `.art`, включая сброс кнопки, который в нём живёт. Отдельный
 * класс здесь означал бы ровно одно: однажды соседи изменятся, а она нет.
 *
 * Прежняя редакция была значком под сеткой, со своими `.уст-низ` и
 * `.уст-значок`. Владелец забраковал: «не непонятной стрелочкой, а ячейкой
 * последней». Оба класса удалены, чтобы мёртвые правила не пережили замысел.
 */

.sheet-acts {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  margin-top: var(--s-5);
}
.sheet-acts .btn {
  width: 100%;
  justify-content: center;
}
/* Кнопка со значком слева, как в выборе способа оплаты: значок к краю,
   подпись за ним, и всё равно во всю ширину. */
.sheet-acts .btn.pay__btn { justify-content: flex-start; }

/* Счётчик знаков под полем, к которому относится. Справочная величина, не
   действие, поэтому и живёт отдельно от кнопок. */
.count {
  margin-top: var(--s-2);
  text-align: right;
  font: 400 var(--t-label-s) var(--f-mono);
  letter-spacing: var(--tr-label-s);
  color: var(--c-faint);
}


/* ==========================================================================
   ЦЕНЫ
   ==========================================================================
   Вид взят у варианта B, старой мобильной стены (js/paywall-mobile.js,
   оформление «b»), и взят ДОСЛОВНО, а не воспроизведён на глаз: та же картинка,
   та же высота героя, та же пелена, то же зерно, те же тринадцать цветов.
   Сверено скриптом: своих цветов, которых у B нет, в блоке ровно четыре, и все
   четыре это палитра нашей сквозной кнопки «назад».

   ЗАЧЕМ МЕНЯЛИ. У прежнего экрана заголовком стояло «Тарифы Premium», то есть
   название прейскуранта. Медиана времени на странице была ДВЕ СЕКУНДЫ, шестьдесят
   процентов уходили быстрее трёх. До доказательств (числа, значки доверия,
   «что бесплатно») большинство просто не долистывало, а первый экран говорил
   не о том. У B на его месте обещание вечера.

   ЧТО ОСТАЛОСЬ НАШИМ. Шрифты по своду токенов (Literata и Inter вместо Fraunces
   и Manrope), кнопка «назад» сквозная, полоса прокрутки убрана нашим же приёмом
   (см. `.ln-полотно`), переключатель срока держится палитры B, но заведён нами:
   у B выбирать было нечего.

   СТРАНИЦА НЕ ЕДЕТ САМА. Картинка прибита сверху, полка с кнопкой снизу, едет
   только середина. Так у B, и так же владелец просил на телефоне. Экран лежит
   во весь рост, поэтому он в списке БЕЗ_ВЕРХА и ставит `во-весь-рост` на body:
   без этого страница под ним сохраняет запас хода, и «прибитый» слой уезжает
   вместе с панелью браузера. */

/*
 * ВО ВЕСЬ РОСТ ТОЛЬКО НА ТЕЛЕФОНЕ. На широком у каркаса слева колонка разделов,
 * и слой `position: fixed` накрыл бы её собой. Там страница остаётся обычной:
 * две колонки, кнопка в потоке, прокрутка страницы. Прибитый низ нужен там,
 * где экран маленький и кнопка иначе уезжает за край.
 */
/* Общее для обоих: краски, палитра B и столбик. Положение и высота ниже,
   отдельно для телефона и для широкого. */
.tsenyi {
  /*
   * ЯКОРЬ ДЛЯ СТРЕЛКИ «НАЗАД». Она висит абсолютно над картинкой, а на широком
   * экране сам слой стоит в потоке, и без этой строки ей не за что зацепиться:
   * отсчёт идёт от страницы, и кнопка уезжает в самый левый верхний угол окна,
   * поверх слова LovePlay в колонке разделов. Владелец прислал снимок ровно с
   * этим. На телефоне слой `fixed` и якорем служит сам, поэтому там беды не
   * было видно.
   */
  position: relative;
  display: flex;
  flex-direction: column;
  background: #0F0D0E;
  color: #EDEDEF;
  /* Палитра варианта B, из SKIN.b, дословно. */
  --pw-ac: #E0754F;
  --pw-acsoft: rgba(224, 117, 79, .16);
  --pw-on: #1A0D08;
  --pw-dock: linear-gradient(0deg, #120C0D 72%, rgba(18, 12, 13, 0));
  --pw-scrim: linear-gradient(0deg, #0F0D0E 6%, rgba(15, 13, 14, .55) 52%, rgba(15, 13, 14, .35));
}

@media (max-width: 767px) {
  /*
   * ЗАМОК СТРАНИЦЫ ТОЛЬКО ЗДЕСЬ, И ПОЭТОМУ КЛАСС СВОЙ, А НЕ ОБЩИЙ.
   *
   * Сперва экран ставил сквозной `во-весь-рост`, и это ломало широкий: там он
   * запирал прокрутку страницы, которая как раз должна прокручиваться, потому
   * что слой во весь рост на широком мы не рисуем. Общий класс означает общее
   * поведение, а нам нужно разное, значит и класс нужен свой.
   *
   * Полосу разделов прячем ЗДЕСЬ ЖЕ, хотя `pricing` уже стоит в наборе СТРАНИЦЫ
   * в nav.js. Дублирование намеренное: тот набор говорит «это страница, а не
   * раздел», и полоса по нему прячется у всех страниц разом; здесь же случай
   * жёстче, прибитая снизу кнопка и полоса просто не могут занимать одно место.
   * Заметил владелец на первом же снимке: пилюля стояла поверх содержимого.
   */
  body.na-tsenakh { overflow: hidden; overscroll-behavior: none; }
  body.na-tsenakh .frame { min-height: 0; }
  body.na-tsenakh .frame__content { padding-bottom: 0; }
  body.na-tsenakh .nav-phone { display: none; }

  .tsenyi {
    position: fixed;
    top: 0; left: 0; right: 0;
    /* svh, а не vh: это самое маленькое окно, со строкой браузера на месте.
       При vh экран прыгал бы каждый раз, когда строка уезжает и возвращается.
       `vh` строкой выше остаётся тем, кто про `svh` не знает. */
    height: 100vh;
    height: 100svh;
    z-index: 40;
    overflow: hidden;
  }

  /*
   * ОБЁРТКА ТОЖЕ ВО ВСЮ ВЫСОТУ, и без этого не работает НИЧЕГО.
   *
   * Между слоем экрана и его содержимым стоит `<div id="prBody">`, куда экран
   * пишет разметку. Высота у неё своя, по содержимому, а `flex: 1` у середины
   * считается от РОДИТЕЛЯ: упираясь в обёртку, он превращался в «сколько
   * содержимого, столько и высоты». Отсюда все три беды разом, и все три
   * владелец увидел на первом же снимке: кнопка уехала за нижний край,
   * прокрутке нечего прокручивать, а список полез под нижнюю полосу каркаса.
   *
   * ЭТО УЖЕ БЫЛО, СЛОВО В СЛОВО, НА ЛИНИИ (`.screen--линия > *` ниже по файлу),
   * и там же записано, почему выбором `> *`, а не именем: обёртка это
   * подробность разметки экрана, и завязываться на её `id` значит завести
   * вторую вещь, которую надо помнить.
   */
  .tsenyi > * {
    height: 100%;
    min-height: 0;
    display: flex;
    flex-direction: column;
  }
}

/* Зерно поверх фона: у B оно есть, и без него плоско. */
.tsenyi .pw-grain {
  position: absolute; inset: 0; z-index: 2; pointer-events: none; opacity: .4;
  background-image: radial-gradient(rgba(255, 255, 255, .04) 1px, transparent 1px);
  background-size: 3px 3px;
}

.tsenyi .pw-hero { position: relative; flex: 0 0 auto; height: 212px; overflow: hidden; }
.tsenyi .pw-hero img { width: 100%; height: 100%; object-fit: cover; display: block; }
.tsenyi .pw-hero::after { content: ''; position: absolute; inset: 0; background: var(--pw-scrim); }

/* Заголовок лежит НА картинке, а не под ней: под ней экран превращается в
   товарную карточку. Правило и объяснение от B. */
.tsenyi .pw-htext { position: absolute; left: var(--s-5); right: var(--s-5); bottom: var(--s-4); z-index: 4; }
.tsenyi .pw-h1 { font: 400 var(--t-display-s) var(--f-serif); letter-spacing: -.01em; margin: 0; }
.tsenyi .pw-lead { font: 400 var(--t-body-m) var(--f-sans); color: #9A9AA0; margin: var(--s-2) 0 0; }

/* Кнопка «назад» сквозная, `.back-btn`, здесь только положение над картинкой. */
.tsenyi .back-btn {
  position: absolute;
  top: calc(var(--s-3) + env(safe-area-inset-top, 0px));
  left: var(--s-4);
  z-index: 6;
}

.tsenyi .pw-body { flex: 1; display: flex; flex-direction: column; min-height: 0; position: relative; z-index: 3; }

/* ПОЛОСЫ ПРОКРУТКИ НЕТ. Отступление от B и намеренное: он жил только на
   телефоне, где полоса наложенная. На широком и в Windows системная встаёт
   светлой лентой во весь рост. Приём наш же, с линии (`.ln-полотно`).

   `overscroll-behavior: contain` не украшение: без него докрученная до конца
   середина тянет за собой страницу под ней, и обещание «не едет» ломается
   именно на iOS. У B этого не было, и это была его дыра. */
.tsenyi .pw-scroll {
  flex: 1; overflow-y: auto; min-height: 0;
  padding: 0 var(--s-5);
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.tsenyi .pw-scroll::-webkit-scrollbar { width: 0; height: 0; }
.tsenyi .pw-khvost { height: var(--s-4); }

/* Переключатель срока: наш орган в палитре B. */
.tsenyi .pw-switch {
  display: grid; grid-template-columns: 1fr 1fr; gap: 4px; padding: 4px;
  background: rgba(237, 237, 239, .07);
  border-radius: var(--r-ctl, 16px);
  margin: var(--s-4) 0 var(--s-1);
}
.tsenyi .pw-switch__half {
  appearance: none; border: 0; background: none; color: #9A9AA0;
  font: 600 var(--t-label-l) var(--f-sans);
  padding: var(--s-3) var(--s-2);
  border-radius: 13px; cursor: pointer;
  transition: background var(--m-standard) var(--m-ease), color var(--m-standard) var(--m-ease);
}
.tsenyi .pw-switch__half[aria-pressed="true"] { background: var(--pw-ac); color: var(--pw-on); }

.tsenyi .pw-price { padding: var(--s-4) 0 4px; border-top: 1px solid rgba(237, 237, 239, .07); }
.tsenyi .pw-price .n { font: 600 var(--t-title-s) var(--f-sans); color: #9A9AA0; }
/* Размер цены свой: в шкале нет ступени для крупного засечного числа. */
.tsenyi .pw-price .v { font-family: var(--f-serif); font-weight: 400; font-size: 44px;
  line-height: 1; margin: 6px 0 8px; }
.tsenyi .pw-price .d { font: 400 var(--t-body-m) var(--f-sans); color: #6A6A72; }

/* Список одной вертикальной линией, без ячеек: плитки в две колонки на 390
   режут строки посередине и выглядят самодельно. Правило от B. */
.tsenyi .pw-sect {
  font: 600 var(--t-label-s) var(--f-mono);
  letter-spacing: .16em; text-transform: uppercase;
  color: #6A6A72; margin: var(--s-4) 0 var(--s-2);
}
.tsenyi .pw-i { display: flex; align-items: center; gap: var(--s-4); padding: 11px 0;
  border-top: 1px solid rgba(237, 237, 239, .07); }
.tsenyi .pw-i:first-of-type { border-top: 0; }
.tsenyi .pw-i img { width: 44px; height: 44px; flex: 0 0 auto; display: block; object-fit: contain; }
.tsenyi .pw-i b { display: block; font: 600 var(--t-title-m) var(--f-sans); }

/* Заметка про выписку: ГЛАВНОЕ возражение при оплате 18+. У прежнего экрана
   от неё оставались два слова, зарытые в свёрнутый ответ. */
.tsenyi .pw-note {
  display: flex; gap: var(--s-3); align-items: flex-start;
  margin-top: var(--s-4); padding-top: var(--s-4);
  border-top: 1px solid rgba(237, 237, 239, .07);
  font: 400 var(--t-body-m) var(--f-sans); color: #9A9AA0;
}
.tsenyi .pw-note svg { width: 20px; height: 20px; color: var(--pw-ac); flex: 0 0 auto; margin-top: 1px; }

/* Вопросы. Раскрывашка своя, `details`, а не класс `.open` как у B: у нас она
   уже есть в каркасе и умеет событие `toggle`, по которому шлётся слежение. */
.tsenyi .pw-faq { border-top: 1px solid rgba(237, 237, 239, .07); }
.tsenyi .pw-faq .qr { display: flex; align-items: center; gap: var(--s-3);
  min-height: 44px; padding: var(--s-3) 0; cursor: pointer; list-style: none; }
.tsenyi .pw-faq .qr::-webkit-details-marker { display: none; }
.tsenyi .pw-faq .q { flex: 1; font: 600 var(--t-title-s) var(--f-sans); }
.tsenyi .pw-faq .ch { color: #6A6A72; flex: 0 0 auto; display: flex;
  transition: transform var(--m-standard) var(--m-ease); }
.tsenyi .pw-faq[open] .ch { transform: rotate(180deg); }
.tsenyi .pw-faq .a { font: 400 var(--t-body-s) var(--f-sans); color: #9A9AA0;
  padding-bottom: var(--s-4); }

/* Полка с кнопкой прибита снизу и видна с первого кадра. Прежняя липкая кнопка
   выезжала, только когда главная уезжала вверх, то есть показывалась
   дочитавшим; уходившим за две секунды её не показывали вовсе. */
.tsenyi .pw-dock {
  flex: 0 0 auto;
  padding: var(--s-3) var(--s-5) calc(var(--s-3) + env(safe-area-inset-bottom, 0px));
  background: var(--pw-dock);
  position: relative; z-index: 5;
}
.tsenyi .pw-cta {
  display: flex; align-items: center; justify-content: center;
  width: 100%; min-height: 56px; border: 0; border-radius: var(--r-ctl, 16px);
  cursor: pointer; font: 600 var(--t-label-l) var(--f-sans);
  background: var(--pw-ac); color: var(--pw-on);
}
.tsenyi .pw-cta:active { transform: scale(.985); }
.tsenyi .pw-fine { text-align: center; font: 400 var(--t-body-s) var(--f-sans);
  color: #9A9AA0; margin: var(--s-3) 0 0; }
/* «Как проходит оплата?»: тихая кнопка под пояснением, шаги в шторке. Шторка
   живёт вне .цены (её вешают на body), поэтому её стили под .sheet. */
/* Кнопка, а не подчёркнутая ссылка (владелец 03.10.2026): обводка-пилюля, тише главной. */
.tsenyi .pw-kak {
  display: flex; align-items: center; justify-content: center; gap: var(--s-2);
  margin: var(--s-3) auto 0; min-height: 40px; padding: 0 var(--s-4);
  border: 1px solid var(--c-border-strong); border-radius: 999px; background: none;
  color: var(--c-text); font: 500 var(--t-label-m) var(--f-sans); cursor: pointer;
}
.tsenyi .pw-kak::before { content: "?"; display: grid; place-items: center; width: 18px; height: 18px;
  border-radius: 50%; border: 1px solid currentColor; font-size: 11px; font-weight: 700; opacity: .7; }
.tsenyi .pw-kak:active { transform: scale(.98); }
.pw-shagi { margin: 0; padding: 0 0 0 var(--s-5); display: grid; gap: var(--s-3);
  font: 400 var(--t-body-m) var(--f-sans); color: var(--c-text); }
.pw-shagi li::marker { color: var(--c-muted); font-weight: 600; }
.pw-shagi__snoska { margin: var(--s-4) 0 0; font: 400 var(--t-body-s) var(--f-sans); color: var(--c-muted); }
.pw-shagi__snoska + .pw-shagi__snoska { margin-top: var(--s-2); }
.pw-shagi__pomoshchj { display: inline-block; margin-top: var(--s-4); font: 400 var(--t-body-s) var(--f-sans);
  color: var(--c-muted); text-decoration: underline; text-underline-offset: 3px; margin-bottom: var(--s-3); }

/* ПОЛКА СТОИТ У САМОГО НИЗА. Место под полосу разделов держать не надо: на этом
   экране её нет, и это не своеволие, а её же правило (набор СТРАНИЦЫ в nav.js
   плюс замок выше). Здесь стоял отступ в шестьдесят четыре пикселя под полосу,
   и он отодвигал кнопку от края ради того, чего там нет. */

/* ----- шторка оплаты ----- */

/* Механика шторки наша (`sheet.js`), переодето только тело. */
.pay__za { font: 600 var(--t-title-s) var(--f-sans); margin-bottom: 2px; }
.pay__nota { font: 400 var(--t-body-s) var(--f-sans); color: var(--c-muted);
  margin-bottom: var(--s-4); }
.pay__srazu { text-align: left; color: var(--c-muted); margin-top: var(--s-4); }

/* Ряд способа оплаты: вид `.pw-m` от B, значок наш. */
.pay-row {
  display: flex; align-items: center; gap: var(--s-4);
  width: 100%; min-height: 72px; padding: 13px 0;
  border: 0; border-top: 1px solid var(--c-border);
  background: none; color: inherit; text-align: left; cursor: pointer;
}
.pay-row:first-of-type { border-top: 0; }
.pay-row[disabled] { opacity: .55; }
.pay-row__znak {
  width: 44px; height: 44px; flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%; background: var(--c-surface-high); color: var(--c-gold);
}
.pay-row__tekst { flex: 1; }
.pay-row__tekst b { display: block; font: 600 var(--t-title-m) var(--f-sans); }
.pay-row__tekst span { display: block; font: 400 var(--t-body-s) var(--f-sans);
  color: var(--c-muted); margin-top: 3px; }
.pay-row__shevron { margin-left: auto; color: var(--c-dim); font-size: 20px; }

/* Надзаголовок «Способ оплаты» внутри шторки живёт без класса `.цены`. */
.sheet .pw-sect {
  font: 600 var(--t-label-s) var(--f-mono);
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--c-dim); margin: var(--s-4) 0 var(--s-2);
}

/* ----- широкий ----- */

@media (min-width: 768px) {
  /*
   * НА ШИРОКОМ ЭКРАН НЕ ПРИБИТ, И ПРОКРУТКА ОБЩАЯ. Слой во весь рост накрыл бы
   * колонку разделов слева, а прибивать кнопку к низу там незачем: экран
   * высокий, она и так на виду. Значит середина перестаёт быть отдельным
   * прокручиваемым окном и просто течёт со страницей.
   */
  .tsenyi { max-width: 1100px; margin-inline: auto; }
  .tsenyi .pw-body { display: block; }
  .tsenyi .pw-scroll { flex: none; overflow: visible; min-height: 0; }
  .tsenyi .pw-hero { border-radius: var(--r-lg, 20px); }

  /* Герой выше, поля шире, содержимое в две колонки: слева предложение,
     справа вопросы. Кнопка не растягивается во всю ширину, ей это не идёт. */
  .tsenyi .pw-hero { height: 260px; }
  .tsenyi .pw-htext { left: var(--s-7, 40px); right: var(--s-7, 40px); }
  .tsenyi .pw-h1 { font-size: 32px; line-height: 38px; }
  .tsenyi .pw-scroll { padding: 0 var(--s-7, 40px); }
  .tsenyi .pw-kolonki { display: grid; grid-template-columns: 1fr 1fr;
    gap: var(--s-6); align-items: start; }
  /* На широком нижней полосы нет, значит и место под неё не нужно. */
  .tsenyi .pw-dock { padding: var(--s-4) var(--s-7, 40px) var(--s-5); }
  .tsenyi .pw-cta { max-width: 420px; margin-inline: auto; }
}

/* ----- низкий экран ----- */

@media (max-height: 700px) {
  /* Числа от B: на 390 на 660, когда Safari показывает адресную строку. */
  .tsenyi .pw-hero { height: 152px; }
  .tsenyi .pw-price .v { font-size: 42px; }
  .tsenyi .pw-i { padding: 9px 0; }
  .tsenyi .pw-sect { margin: var(--s-3) 0 6px; }
}


/* ==========================================================================
   ПЕРЕНЕСЕНО ИЗ ПРЕЖНЕГО БЛОКА ЦЕН, БЕЗ ИЗМЕНЕНИЙ
   ==========================================================================
   Эти правила жили внутри блока цен, но пользуются ими ДРУГИЕ экраны, и
   выкинуть их вместе со старой страницей значило бы молча сломать календарь,
   путь пары, инструменты и заголовок каркаса. Проверено поимённо: `.hero` и
   `.hero__title` зовут app.js, calendar-day, calendar-settings, calendar-stats,
   calendar, journey и tools; `.spinner` крутится на кнопке оплаты, пока идёт
   запрос ссылки.

   Мёртвое при этом НЕ переносим: `.wall__note` и `.wall__list` не звал никто,
   их не стало вместе со старой вёрсткой. */

.hero { margin-bottom: var(--s-5); }
.hero__title:first-child { margin-top: 0; }
.hero__title {
  font-family: var(--f-serif);
  font-weight: 400;
  font-size: 30px;
  line-height: 36px;
  margin: var(--s-2) 0 0;
}
.hero__lede {
  font: 400 var(--t-body-m) var(--f-sans);
  color: var(--c-muted);
  margin: var(--s-3) 0 0;
  max-width: 56ch;
}

.spinner {
  display: inline-block;
  width: 16px; height: 16px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: vertushka 700ms linear infinite;
  vertical-align: -3px;
}
@keyframes vertushka { to { transform: rotate(360deg) } }


/* ==========================================================================
   КНОПКА «НАЗАД»
   ==========================================================================
   Общая на весь проект. Из приложения, lib/theme/components.dart,
   CircleBackButton: круг 44 на 44 (в исходнике подписано «iOS HIG minimum
   touch target»), поверхность surfaceContainer при 70 процентах, рамка
   outline при 22, стрелка 20. */

.back-btn {
  width: 44px; height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: rgba(29, 23, 22, .7);
  border: 1px solid rgba(92, 81, 78, .22);
  color: var(--c-text);
  cursor: pointer;
  flex: 0 0 auto;
  transition: border-color var(--m-standard) var(--m-ease),
              background var(--m-standard) var(--m-ease);
}
.back-btn:hover { border-color: rgba(212, 175, 55, .55); background: rgba(29, 23, 22, .9); }
.back-btn:active { transform: scale(.96); }
.back-btn svg { display: block; }

/* На странице, куда пришли и откуда возвращаются, нижней полосы нет: она
   говорит «ты в разделе», а это не раздел. */
body.bez-polosyi .nav-phone { display: none; }

/* На странице, куда пришли за одним делом, верхней полосы нет вовсе.
   Считал по пикселям: ради кнопки «назад» и лица она забирала 64, восьмую
   часть телефонного экрана. Кнопка переехала в строку с заголовком, лицо там
   не нужно, это дверь в раздел «Я». */
body.bez-verkha .topbar { display: none; }
body.bez-polosyi .frame__content { padding-bottom: var(--s-12); }

/*
 * ЭКРАН ВО ВЕСЬ РОСТ: страница под ним не прокручивается ВООБЩЕ.
 *
 * Экран, прибитый к окну, держится за окно, а не за страницу, и стоит на месте
 * ровно до тех пор, пока странице некуда ехать. У каркаса же под ним оставался
 * запас: `min-height: 100dvh` плюс отступ снизу под плавающую полосу, вместе
 * почти сотня пикселей. На телефоне этой сотни хватает: палец тянет страницу,
 * Safari прячет свою панель, видимое окно меняет высоту, и прибитый экран
 * уезжает вместе с ней. Со стороны это выглядит так, будто «кнопка назад
 * исчезла», а дата «приклеилась», хотя не двигалось ни то, ни другое.
 *
 * Класс вешает и снимает сам экран, а не список в навигации: он один знает,
 * когда его сняли, и обязан вернуть страницу как было.
 */
body.vo-vesj-rost { overflow: hidden; overscroll-behavior: none; }
body.vo-vesj-rost .frame { min-height: 0; }
body.vo-vesj-rost .frame__content { padding-bottom: 0; }

/* ---------- переключатель плана ---------- */

/* Два состояния одного вопроса, поэтому это ОДИН орган из двух половин, а не
   две кнопки. Выбранная половина заливается золотом. */
.switch {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s-1);
  padding: var(--s-1);
  margin-bottom: var(--s-5);
  background: var(--c-surface-alt);
  border: 1px solid var(--c-border);
  border-radius: var(--r-ctl, 14px);
}
.switch__half {
  padding: var(--s-2) var(--s-3);
  border: 0;
  border-radius: calc(var(--r-ctl, 14px) - 4px);
  background: none;
  color: var(--c-muted);
  font: 600 var(--t-label-l) var(--f-sans);
  cursor: pointer;
  transition: background var(--m-standard) var(--m-ease),
              color var(--m-standard) var(--m-ease);
}
.switch__half[aria-pressed="true"] {
  background: var(--g-flame);
  color: var(--c-on-gold);
}

/* ==========================================================================
   ЦЕНЫ: ТОЧКИ ПЕРЕЛОМА КАК НА БОЕВОЙ
   ==========================================================================
   Я срисовал десктоп и подогнал телефон на глаз, а у боевой страницы разница
   задана точками, и точки не те, что я взял. Здесь они дословно её:

     подводка скрыта      max-width 767   (@media (max-width:767px){.lede{display:none}})
     две карточки рядом   min-width 768   (@media (min-width:768px){.plans{1fr 1fr}})
     липкая кнопка до     min-width 981   (@media (min-width:981px){.cta-sticky{display:none}})

   Подводка на телефоне убрана намеренно: она длинная, а между заголовком и
   ценой на маленьком экране абзацу не место. */

/* Точки перелома старой страницы цен убраны вместе с ней: карточек `.plans`,
   липкой кнопки и подводки в шапке больше нет, и правила висели бы впустую.
   Точка 768 переехала в блок ЦЕНЫ и делит там предложение и вопросы на две
   колонки. */


/* Заголовок страницы в одной строке с кнопкой «назад»: две строки сведены в
   одну, и наверху больше ничего нет. */
.hero--row {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-bottom: var(--s-5);
}
.hero--row .hero__title {
  margin: 0;
  min-width: 0;
  flex: 1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;

  /* СКВОЗНОЕ ПРАВИЛО: серифный заголовок рядом с органом управления
     поднимается на --сериф-снос, иначе слова стоят ниже стрелки. Почему так и
     откуда число, расписано у токена. */
  transform: translateY(calc(0px - var(--serif-snos, .083em)));
}
/* Лицо в той же строке: тот же кружок, что в полосе, просто живёт здесь. */
.hero__face { margin-left: auto; }


/* ==========================================================================
   ВКЛАДКА «МЫ»
   ==========================================================================
   Сетка плиток тут НЕ своя: берутся готовые .arts и .art с витрины игр. Это
   не экономия, а факт из исходника: в приложении обе сетки построены
   одинаково, по три в ряд с соотношением 0.8
   (lib/couple/couple_screen.dart и lib/play/game_grid.dart).

   Ниже только то, чего в общем наборе ещё не было: шапка пары, блок связки и
   полоса чисел. Своих чисел нет, всё на токенах. */

/* Шапка пары: два лица, имена, отметка о новом у партнёра. В приложении это
   _CoupleHeader, карточка с полем space3 и теми же тремя частями. */
.us-head {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  text-decoration: none;
  color: inherit;
}
.us-head__body { min-width: 0; flex: 1; }
.us-head__name {
  display: block;
  font: 600 var(--t-title-m) var(--f-sans);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/*
 * Разделитель между именами пары. Приглушён нарочно: цвет здесь несут ИМЕНА,
 * каждое своим, и знак между ними не должен спорить с ними за внимание. Тоньше
 * их же начертанием, чтобы «&» не читался как третье имя.
 *
 * Класс общий, потому что общий и компонент (`именаПары` в ui.js): та же строка
 * стоит в шапке «Мы» и в колонке навигации.
 *
 * Токен `--c-faint`, а НЕ `--c-dim`: приглушённый второстепенный текст это
 * первое, а `--c-dim` у нас значит «выключено». Знак между именами читается, он
 * не отключён.
 */
.amp { color: var(--c-faint); font-weight: 400; }
.us-head__note {
  display: block;
  margin-top: 2px;
  font: 400 var(--t-body-s) var(--f-sans);
  color: var(--c-muted);
}

/* Пока пара не связана, это главное действие экрана, и оно стоит отдельно, а
   не среди плиток. */
.us-pair {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--s-3);
  margin-top: var(--s-5);
}
.us-pair__note {
  font: 400 var(--t-body-s) var(--f-sans);
  color: var(--c-muted);
  max-width: 52ch;
}

/* Плитки идут через шаг блока после шапки. */
.us-arts { margin-top: var(--s-5); }

/* Полоса чисел стоит ПОСЛЕ плиток: сюда заходят за разделами, а числа
   приятная деталь. */
.us-stats { display: flex; gap: var(--s-4); }
.us-stat { flex: 1; min-width: 0; }
.us-stat__num {
  font: 400 var(--t-display-s) var(--f-serif);
  color: var(--c-text);
}
.us-stat .eyebrow { margin-top: 2px; }


/* ---------- заглушка нестроенного раздела ---------- */

.soon-card { max-width: 56ch; }
.soon-head {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-bottom: var(--s-3);
}
.soon-list {
  margin: 0;
  padding-left: var(--s-5);
  font: 400 var(--t-body-s) var(--f-sans);
  color: var(--c-muted);
}
.soon-list li { margin: 0 0 var(--s-2); }
.soon-list li:last-child { margin-bottom: 0; }




/* ==========================================================================
   ПОЛОСА НАД ВИТРИНОЙ: СЫГРАНО И ОПЫТ
   ==========================================================================
   Из приложения, lib/play/play_screen.dart, _StreakStrip. Числа оттуда же:
   радиус radiusMd (16), поля space3 по горизонтали и space2 по вертикали,
   значок 22, полоска высотой 6 со скруглением под завязку.

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

/*
 * ПОЛОСА ЭТО КНОПКА, и ведёт она в «путь пары». Значит нужен сброс: браузер
 * даёт кнопке свой шрифт, свою ширину по содержимому и своё выравнивание по
 * центру, и без сброса полоса съёжилась бы в середину экрана.
 *
 * `width: 100%` и `text-align: left` здесь несущие, а не отделка.
 */
.strip {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  width: 100%;
  text-align: left;
  font: inherit;
  color: inherit;
  cursor: pointer;
  padding: var(--s-2) var(--s-3);
  margin-bottom: var(--s-4);
  border-radius: var(--r-md);
  background: var(--c-surface-high);
  border: 1px solid var(--c-border);
  transition: border-color var(--m-standard) var(--m-ease);
}
.strip:hover { border-color: var(--c-gold); }
.strip__go { color: var(--c-faint); flex: 0 0 auto; }
.strip .ico { color: var(--c-gold); flex: 0 0 auto; }
.strip__body { flex: 1; min-width: 0; }
.strip__line { display: flex; align-items: baseline; gap: var(--s-2); }
.strip__played {
  flex: 1;
  min-width: 0;
  font: 600 var(--t-title-m) var(--f-sans);
  color: var(--c-gold);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.strip__lv {
  flex: 0 0 auto;
  font: 400 var(--t-body-s) var(--f-sans);
  color: var(--c-muted);
}

/* Дорожка и заливка. Заливка золотым градиентом, как gradFlame в приложении,
   и она едет плавно, чтобы новый опыт было видно, а не подставлялся молча. */
.strip__bar {
  height: 6px;
  margin-top: var(--s-1);
  border-radius: var(--r-pill);
  background: var(--c-surface-alt);
  overflow: hidden;
}
.strip__bar i {
  display: block;
  height: 100%;
  background: var(--g-flame);
  transition: width var(--m-celebrate) var(--m-ease);
}


/* ==========================================================================
   МОНЕТКА И КУБИК
   ==========================================================================
   Перенесено из приложения, lib/tools. Числа оттуда: монета 132, кубик 76,
   бросок две секунды, четырнадцать полуоборотов.

   Экран инструмента устроен так: подводка сверху, сама вещь по центру, строка
   ответа ПОСТОЯННОЙ высоты, последние три и кнопка внизу. Высота строки
   закреплена намеренно: иначе монета подпрыгивает, когда приходит имя. */

.tool { display: flex; flex-direction: column; min-height: 70vh; }
.tool__note {
  font: 400 var(--t-body-m) var(--f-sans);
  color: var(--c-muted);
  margin: 0 0 var(--s-5);
}
.tool__go { width: 100%; justify-content: center; margin-top: var(--s-4); }

/* Строка ответа. Высота держится всегда, пусть даже сказать пока нечего. */
.tool__said {
  min-height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  font: 400 var(--t-body-m) var(--f-sans);
  color: var(--c-muted);
  margin: var(--s-5) 0 var(--s-3);
}
.tool__said--him,
.tool__said--her {
  font: 500 var(--t-headline-s) var(--f-serif);
}
.tool__said--him { color: var(--c-player-1); }
.tool__said--her { color: var(--c-player-2); }

/* ---------- монетка ---------- */

.coin {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 180px;
}
.coin__face { will-change: transform; transform-style: preserve-3d; }
/* Монета. Всё нарисовано, ничего не взято картинкой: так же в приложении
   (first_turn_coin.dart), и там объяснено почему. Числа оттуда же.

   Тёмное золото 8A6E22 своего токена не имеет: это край отлива, он живёт
   только здесь и больше нигде не встречается. */
.coin__disc {
  display: block;
  width: 132px; height: 132px;
  border-radius: 50%;
  position: relative;
  /* Отлив из точки (-0.3, -0.4), остановки 0, 55, 100. */
  background: radial-gradient(circle at 35% 30%,
    #fde68a 0%, var(--c-gold) 55%, #8a6e22 100%);
  box-shadow: 0 10px 20px rgba(0, 0, 0, .5);
}

/* Чеканный ободок и светлая кромка есть ТОЛЬКО У ПУСТОЙ ЗАГОТОВКИ.

   В приложении это два разных виджета. CoinSide, монета с лицом, не имеет ни
   рамки, ни внутреннего кольца вовсе. CoinBlank имеет оба, и там подписано
   зачем: «ободок и есть то, что на таком размере отличает монету от кружка».
   У пустой отличать нечего, кроме ободка; у той, что с лицом, отличает лицо.

   Я повесил их на обе, и внутри брошенной монеты появилось лишнее кольцо. */
.coin__disc--chekan { border: 1px solid rgba(253, 230, 138, .5); }
.coin__disc--chekan::after {
  content: "";
  position: absolute;
  left: 19%; top: 19%;
  width: 62%; height: 62%;
  border-radius: 50%;
  border: 1px solid rgba(138, 110, 34, .55);
}

/* Монета РАЗМЕРОМ С ЛИЦО: для вопроса «кто ходит первым».

   Вопрос отвечается человеком или монетой, и в приложении на двух чипах из трёх
   стоит лицо (first_turn_picker.dart). Значит и на третьем должна стоять монета,
   а не слово, и ровно того же размера, что лица рядом.

   Своего размера у монеты нет: `.coin__disc` это 132 точки, столько она и
   занимает в броске и в инструменте. Поставленная в чип как есть, она разносит
   его и съедает подпись: «Монета» превращается в «Монет». Увидено на снимке
   стенда, а не на бою.

   Тень и внутреннее кольцо на таком размере только грязнят, поэтому их нет:
   на 22 точках монету от кружка отличает ободок, он и остаётся. */
.coin__disc--chip {
  width: 22px; height: 22px;
  box-shadow: none;
}
.coin__disc--chip::after { display: none; }

/* Со стороной свечение цветом места, поверх обычной тени. */
.coin__disc--him { box-shadow: 0 10px 20px rgba(0, 0, 0, .5), 0 0 30px rgba(92, 181, 255, .35); }
.coin__disc--her { box-shadow: 0 10px 20px rgba(0, 0, 0, .5), 0 0 30px rgba(255, 120, 150, .35); }

/* Лицо на монете: 70 процентов, в кольце своего цвета. Не на всю монету,
   иначе монета перестаёт быть монетой. */
.coin__portrait {
  position: absolute;
  left: 15%; top: 15%;
  width: 70%; height: 70%;
  border-radius: 50%;
  overflow: hidden;
  background: var(--c-surface-alt);
  border: 1.8px solid;
}
.coin__disc--him .coin__portrait { border-color: rgba(92, 181, 255, .95); }
.coin__disc--her .coin__portrait { border-color: rgba(255, 120, 150, .95); }
.coin__portrait img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ---------- лицо игрока ---------- */

/* Из приложения, player_face.dart. Круг в своём цвете, кольцо толще и ярче,
   когда ход его, плюс свечение того же цвета. Одно лицо на весь каркас: им
   подписаны настройки, полоса хода, монета и шторка задания. */
.pface {
  display: inline-block;
  width: var(--litso, 44px);
  height: var(--litso, 44px);
  border-radius: 50%;
  overflow: hidden;
  background: var(--c-surface-alt);
  border: 1.2px solid;
  flex: none;
  line-height: 0;
}
.pface img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pface--him { border-color: rgba(92, 181, 255, .45); }
.pface--her { border-color: rgba(255, 120, 150, .45); }
.pface--khodit { border-width: 1.8px; }
.pface--khodit.pface--him {
  border-color: rgba(92, 181, 255, .95);
  box-shadow: 0 0 10px -2px rgba(92, 181, 255, .45);
}
.pface--khodit.pface--her {
  border-color: rgba(255, 120, 150, .95);
  box-shadow: 0 0 10px -2px rgba(255, 120, 150, .45);
}

/* Монета ПОВЕРХ игры. Та же монета, другое обрамление: во вкладке «Мы» она
   лежит на экране и ждёт нажатия, в игре падает на доску и уходит сама.

   Накрывает ДОСКУ, а не экран: в приложении это Positioned.fill над доской, и
   разница видна, потому что партия начинается там же, где кончился бросок.
   Затемнение чёрное при 55%, число из first_turn_coin.dart. */
.coin-over {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, .55);
  opacity: 0;
  transition: opacity .2s ease;
  cursor: pointer;
}
.coin-over.on { opacity: 1; }
.coin-over__box { display: flex; flex-direction: column; align-items: center; }
/* Монета в накладке своего роста, а не во всю высоту экрана. */
.coin-over .coin { flex: none; min-height: 0; }

/* Имя появляется, когда монета легла, и ни мгновением раньше: до этого места
   на экране нет ответа, только вращение. */
.coin-over__said {
  opacity: 0;
  transition: opacity .2s ease;
  text-align: center;
  margin-top: var(--s-6);
}
.coin-over__said.on { opacity: 1; }
.coin-over__said .tool__said { margin: 0; }
.coin-over__kak {
  font: 400 var(--t-label-s) var(--f-sans);
  color: var(--c-faint);
  margin: var(--s-2) 0 0;
}

/* ---------- кубики ---------- */

/* Чипы числа кубиков стоят посередине: это не фильтр списка, а выбор для
   того, что под ними. */
.chips--mid { justify-content: center; overflow: visible; margin-bottom: var(--s-5); }

/* justify-content, а НЕ text-align. Чип это inline-flex, а на флексе
   text-align не работает: цифра прижималась к левому краю ячейки, и чем шире
   ячейка от min-width, тем заметнее. Та же ошибка, что была в .btn. */
.chips--mid .chip { min-width: 44px; justify-content: center; }

.dice {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--s-3);
  min-height: 180px;

  /* Сторона кубика и отъезд сцены.

     ЕДИНСТВЕННОЕ, что здесь отличается от боя, и отличается намеренно. На бою
     кубик 72 и 42, потому что он лежит ПОВЕРХ ДОСКИ, в её средних пяти
     клетках, и больше туда не влезает. Здесь доски нет: кубик и есть весь
     экран, и в тех же 42 он теряется посреди пустоты. Механика, картинки и
     повороты взяты один в один, размер тут свой.

     Отъезд это не половина стороны, а запас, чтобы куб на развороте не влезал
     зрителю в глаз. Держим его долей: 0.57 повторяет обе боевые пары (72 и
     -40, 42 и -25) с точностью до пиксела и не врёт на любом другом размере.
     Переменной, потому что JS подставляет её прямо в transform. */
  --storona: clamp(76px, 8vw, 96px);
  --tyaga: calc(var(--storona) * -0.57);
}
.dice__row { display: flex; gap: 18px; }

/* ---------- последние три ---------- */

.tl-log {
  display: flex;
  justify-content: center;
  gap: var(--s-2);
  min-height: 26px;
}
.tl-log__i {
  padding: 3px var(--s-3);
  border-radius: var(--r-tag, 10px);
  border: 1px solid var(--c-border);
  font: 400 var(--t-body-s) var(--f-sans);
  color: var(--c-faint);
}
/* Последний бросок ярче: спорят обычно о нём. */
.tl-log__i--now { color: var(--c-gold); border-color: rgba(212, 175, 55, .45); }


/* ---------- комната инструмента ---------- */

/* Предмету на плоском чёрном негде находиться: у каждого инструмента своя
   картинка, лужа света с поверхностью под ней (tool_backdrop.dart).

   Затемнение сверху и снизу держит картинку тёмной: она тёмная по замыслу, но
   рисовальщик иногда выдаёт светлее, а заголовок обязан остаться читаемым. */
.tool-bg {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}
.tool-bg img { width: 100%; height: 100%; object-fit: cover; display: block; }
.tool-bg__veil {
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom,
    rgba(0, 0, 0, .87) 0%, transparent 50%, rgba(0, 0, 0, .87) 100%);
}

/* ---------- кубик, боевой сексопольный ---------- */

/* Скопирован как есть из ru/games/sexopoly/play/index.html: шесть граней с
   теми же картинками b1..b6 и r1..r6, что лежат в /images/ с самого запуска
   игры, тот же фон грани, та же обводка, тот же внутренний тень-скос.

   display: block обязателен обоим. Это span, то есть СТРОЧНЫЙ элемент, а на
   строчном width не делает ничего: коробка осталась бы нулевой, и все шесть
   граней вышли бы нулевой ширины. На бою это div, там вопрос не стоял. */
.die-scene {
  display: block;
  width: var(--storona);
  height: var(--storona);
  perspective: 900px;
}
.die-3d {
  display: block;
  position: relative;
  width: var(--storona);
  height: var(--storona);
  transform-style: preserve-3d;
  transform: translateZ(var(--tyaga));
  transition: transform 0s linear;
}
.die-face {
  position: absolute;
  width: var(--storona);
  height: var(--storona);
  background: rgba(255, 255, 255, .92);
  border: 2px solid rgba(0, 0, 0, .3);
  border-radius: 8px;
  background-size: cover;
  background-position: center;
  box-shadow: inset 0 0 10px rgba(0, 0, 0, .2);
  /* visible, а не hidden: на бою через грань просвечивает дальняя, и куб от
     этого читается стеклянной костью, а не глухой коробкой. */
  backface-visibility: visible;
}

/* Раскладка граней с боя. Половина стороны там записана числом (36 и 21 при
   72 и 42), здесь считается: одно правило на оба размера. */
.face-1 { transform: rotateY(0deg) translateZ(calc(var(--storona) / 2)); }
.face-2 { transform: rotateY(90deg) translateZ(calc(var(--storona) / 2)); }
.face-3 { transform: rotateX(90deg) translateZ(calc(var(--storona) / 2)); }
.face-4 { transform: rotateX(-90deg) translateZ(calc(var(--storona) / 2)); }
.face-5 { transform: rotateY(-90deg) translateZ(calc(var(--storona) / 2)); }
.face-6 { transform: rotateY(180deg) translateZ(calc(var(--storona) / 2)); }

.die-3d.blue .face-1 { background-image: url('/images/b1.png'); }
.die-3d.blue .face-2 { background-image: url('/images/b2.png'); }
.die-3d.blue .face-3 { background-image: url('/images/b3.png'); }
.die-3d.blue .face-4 { background-image: url('/images/b4.png'); }
.die-3d.blue .face-5 { background-image: url('/images/b5.png'); }
.die-3d.blue .face-6 { background-image: url('/images/b6.png'); }
.die-3d.pink .face-1 { background-image: url('/images/r1.png'); }
.die-3d.pink .face-2 { background-image: url('/images/r2.png'); }
.die-3d.pink .face-3 { background-image: url('/images/r3.png'); }
.die-3d.pink .face-4 { background-image: url('/images/r4.png'); }
.die-3d.pink .face-5 { background-image: url('/images/r5.png'); }
.die-3d.pink .face-6 { background-image: url('/images/r6.png'); }

/* Порог с боя, размер свой: см. выше, у .dice. Три в ряд при 76 и зазоре 18
   это 264, в телефонную колонку 358 входит с запасом. */
@media (max-width: 768px) {
  .dice { --storona: 76px; }
}


/* ==========================================================================
   ЗНАКОМСТВО
   ==========================================================================
   Разовый экран на входе для тех, кто так и не назвался. Стили перенесены со
   слайда знакомства (next/js/welcome.js, .onb-id-*): это один и тот же экран,
   и расходиться им нельзя. Значения все токенные, поэтому перенос дословный.
   ========================================================================== */

.hello { display: flex; align-items: center; justify-content: center; min-height: 80vh; }
.hello__in { width: 100%; max-width: 420px; }

.hello__eyebrow {
  font: 600 var(--t-label-s) var(--f-mono);
  letter-spacing: var(--tr-eyebrow);
  color: var(--c-faint);
  text-transform: uppercase;
  margin-bottom: var(--s-2);
}
.hello__title {
  font: 400 var(--t-display-s) var(--f-serif);
  color: var(--c-text);
  margin: 0;
}

.hello__faces {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: var(--s-3);
  margin: var(--s-5) 0;
}
.hello__face {
  position: relative; aspect-ratio: 1; padding: 0; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  -webkit-appearance: none; appearance: none;
  border-radius: 50%; overflow: hidden;
  background: var(--c-surface-high);
  border: 1px solid var(--c-border-strong);
  opacity: .55;
  transition: opacity var(--m-standard) var(--m-ease),
              border-color var(--m-standard) var(--m-ease),
              box-shadow var(--m-standard) var(--m-ease),
              transform var(--m-micro) var(--m-ease);
}
.hello__face img { width: 100%; height: 100%; object-fit: cover; display: block; }
.hello__face:focus { outline: none; }
.hello__face:focus-visible { outline: 2px solid var(--c-gold); outline-offset: 3px; }
.hello__face:active { transform: scale(.96); }
.hello__face.on { opacity: 1; border-width: 2.2px; }

/* Цвет выбранного лица и есть ответ на вопрос о поле. Цвета мест лежат в
   токенах: первый всегда он, второй всегда она. */
.hello__face.him.on { border-color: var(--c-player-1); box-shadow: 0 0 16px -2px var(--c-player-1); }
.hello__face.her.on { border-color: var(--c-player-2); box-shadow: 0 0 16px -2px var(--c-player-2); }

.hello__label {
  font: 600 var(--t-label-s) var(--f-mono);
  letter-spacing: var(--tr-label-s);
  color: var(--c-faint);
  text-transform: uppercase;
  transition: color var(--m-standard) var(--m-ease);
}
.hello__label.him { color: var(--c-player-1); }
.hello__label.her { color: var(--c-player-2); }

/* Шестнадцать пикселей это не про красоту: Safari на iPhone приближает
   страницу при фокусе на поле мельче, и вернуть масштаб можно только руками.
   Число живёт в токенах. */
.hello__name {
  width: 100%; box-sizing: border-box;
  margin-top: var(--s-2);
  padding: var(--s-3) var(--s-4);
  font-size: var(--t-input-min);
  font-family: var(--f-sans);
  color: var(--c-text);
  background: var(--c-surface-alt);
  border: 1px solid var(--c-border-strong);
  border-radius: var(--r-sm);
  outline: none;
  /* Курсор виден: без явного цвета он на тёмном фоне почти не читается, и
     поле выглядит подписью, а не вводом. */
  caret-color: var(--c-gold);
  transition: border-color var(--m-standard) var(--m-ease);
}
.hello__name::placeholder { color: var(--c-dim); }
.hello__name:focus { border-color: var(--c-gold); }

.hello__error {
  margin: var(--s-2) 0 0;
  font: 400 var(--t-body-s) var(--f-sans);
  color: var(--c-danger);
  min-height: 18px;
}
.hello__go { width: 100%; justify-content: center; margin-top: var(--s-3); }
.hello__go[disabled] { opacity: .38; pointer-events: none; }


/* ==========================================================================
   НАСТРОЙКИ ИГРЫ: общий слой
   ==========================================================================
   Перенесено из приложения дословно, чтобы КАЖДЫЙ следующий экран настроек
   лёг сюда без своих чисел. Источники:

     lib/games/sexopoly/sexopoly_setup.dart   порядок блоков и карточка
     lib/games/widgets/choice_row.dart        вопрос из трёх вариантов
     lib/games/widgets/how_to_play.dart       «как играть» одной строкой
     lib/couple/couple_strip.dart             кто играет
     lib/theme/app_tokens.dart                сами числа

   Числа приложения совпали с нашими токенами один в один (space1..space12,
   radiusSm..radiusSheet, длительности), поэтому здесь везде токены, а не
   константы.

   ЧТО ОТСТУПАЕТ ОТ ПРИЛОЖЕНИЯ И ПОЧЕМУ. Там варианты выбора скруглены по
   radiusPill (999), у нас по --r-ctl (14): решение владельца 25.08.2026 про
   прямоугольные пилюли с лёгким скруглением, оно сквозное и старше этого
   экрана.
   ========================================================================== */

/* ---------- шапка игры ---------- */

/* Название в две краски: вторая половина слова курсивом и золотом. Так на
   бою («Sex opoly») и так же в приложении. */
/* СКВОЗНОЕ: заголовок напротив стрелки, а не под ней. Строка со стрелкой
   в одиночку не говорит ничего, а стоит столько же, сколько строка с
   названием. */
.mast__row { display: flex; align-items: center; gap: var(--s-3); }
.mast__row .mast__title {
  flex: 1;
  min-width: 0;
  /* Название игры НЕ ОБРЕЗАЕТСЯ. Раньше здесь стояло nowrap с многоточием, и
     это верно для чужой длинной строки, но название игры не чужая строка:
     «Раздевающие шашки» на телефоне уезжало за край и теряло половину слова.
     Две строки это по-прежнему шапка, а обрезанное название это дефект. */
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  /* Тот же оптический подъём, что у заголовков в .hero--row: серифные буквы
     сидят в своей строке низко, и рядом с кнопкой это видно. */
  transform: translateY(calc(0px - var(--serif-snos, .083em)));
}
.mast__title {
  font: 400 var(--t-display-s) var(--f-serif);
  color: var(--c-text);
  margin: 0;
}
.mast__title em { color: var(--c-gold); font-style: italic; }
.mast__dek {
  font: 400 var(--t-body-m) var(--f-sans);
  color: var(--c-muted);
  margin: var(--s-2) 0 0;
}

/* ---------- карточка настроек ---------- */

/* Не .card: та держит строки и потому без полей. Эта наоборот, поле по
   кругу, и в ней лежат вопросы. Шкура одна и та же. */
.set-card {
  padding: var(--s-4);
  background: var(--c-card-bg);
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
}
/* Ритм между блоками, числа из приложения (sexopoly_setup.dart): после
   названия space5, дальше везде space4. */
.set-card + .set-card,
.set-card + .howto,
.duo + .set-card,
.duo + .howto { margin-top: var(--s-4); }
.mast + .duo,
.mast + .set-card { margin-top: var(--s-5); }

/* ---------- вопрос из нескольких вариантов ---------- */

/* Пояснений под вариантами НЕТ, и это главное в компоненте. В приложении
   объяснено зачем: пояснение под каждым превращало вопрос в абзац и уносило
   кнопку за экран. Что значит вариант, написано в «как играть», внизу. */
.choice + .choice { margin-top: var(--s-4); }
.choice__opts { display: flex; gap: var(--s-2); margin-top: var(--s-2); }

.choice__opt {
  flex: 1 1 0;
  min-width: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: var(--s-3) var(--s-2);
  border-radius: var(--r-ctl, 14px);
  border: 1px solid var(--c-border-strong);
  background: var(--c-surface-high);
  color: var(--c-text);
  font: 600 var(--t-label-l) var(--f-sans);
  white-space: nowrap;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  /* Тот же набор свойств в обоих состояниях, иначе переключение мигает.
     Дословно оттуда же. */
  transition: background var(--m-micro) var(--m-ease),
              border-color var(--m-micro) var(--m-ease),
              color var(--m-micro) var(--m-ease);
}
.choice__opt[aria-pressed="true"] {
  background: var(--g-flame);
  /* Волосина по краю: отсчёт заливки от той же коробки, по которой рисуем. */
  background-origin: border-box;
  border-color: transparent;
  color: var(--c-on-gold);
}
.choice__opt:focus { outline: none; }
.choice__opt:focus-visible { outline: 2px solid var(--c-gold); outline-offset: 2px; }

/* Подпись не переносится и не растит кнопку. В приложении это FittedBox
   scaleDown: подпись УЖИМАЕТСЯ, а не режется. Многоточие здесь было бы не
   переносом, а поломкой: «$1200» превращалось в «$12…», и пять вариантов
   запаса становились неразличимы.

   Поэтому при пяти вариантах кегль и поля меньше. Пять по $2000 это самый
   тесный случай, какой у нас есть: 326 внутри карточки минус четыре зазора
   по 8 даёт 58 на кнопку, и подпись обязана уложиться в них целиком. */
.choice__opt span { min-width: 0; }
.choice__opts--tesno .choice__opt {
  padding-left: 4px;
  padding-right: 4px;
  font-size: 13px;
}
.choice__opt svg { flex: 0 0 auto; opacity: .75; }

/* Много ответов разом: пилюли ТЕ ЖЕ, переносятся на вторую строку.

   Одиночный выбор делит строку поровну, потому что вариантов три-пять и они
   короткие («Нежно», «Остро», «Дико»). Слоты спрашивают шесть категорий с
   настоящими названиями, и делёж поровну ужал бы «Грязные слова» до нечитаемого.
   Здесь пилюля занимает ровно свою ширину, ряд переносится, а вид остаётся
   общим: разными эти вопросы делает число ответов, а не оформление.

   ПОЛЯ ШИРЕ, ЧЕМ У ОДИНОЧНОГО ВЫБОРА, и это правка по живому экрану. Пилюли
   унаследовали боковое поле в 8 точек от вопроса, который делит строку на пять,
   и слово в них стояло впритык к краям, а между рядами зияло: «текст зажат,
   края зажаты, а пустота высокая, выглядит не лаконично» (владелец 02.09.2026).
   Ряду тесно по горизонтали и просторно по вертикали, значит поля надо не
   уменьшать, а перераспределить. */
.choice__opts--mnogo { flex-wrap: wrap; gap: var(--s-2); }
.choice__opts--mnogo .choice__opt {
  flex: 0 1 auto;
  padding: var(--s-3) var(--s-4);
}

/* СВОЙ ЦВЕТ У ВКЛЮЧЁННОЙ ПИЛЮЛИ.

   Золотая заливка одна на все ответы верна там, где ответ один: выбранное видно
   тем, что оно золотое. Когда включено четыре из шести, четыре одинаковых
   золотых пятна не говорят ничего, кроме «включено», а у слотов у каждой
   категории есть свой цвет, и тот же цвет падает на барабане. Пилюля показывает
   его заранее.

   Выключенная держит цвет тонкой каймой и точкой: так видно, чего ждать от
   категории, ещё не включая её. */
.choice__opt[style*="--svoy"] { border-color: color-mix(in srgb, var(--svoy) 45%, var(--c-border-strong)); }
.choice__opt[style*="--svoy"][aria-pressed="true"] {
  background: color-mix(in srgb, var(--svoy) 86%, black);
  border-color: var(--svoy);
  color: #fff;
}

/* ---------- как играть ---------- */

/* ОДНА СТРОКА, пока не попросят. В приложении объяснено зачем: раскрытый
   список из пяти вопросов был самым высоким блоком на экране, и пара, уже
   знающая игру, пролистывала свод правил каждый раз. */
.howto {
  background: var(--c-card-bg);
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  overflow: hidden;
}
.howto__head {
  width: 100%;
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  background: none;
  border: 0;
  color: var(--c-text);
  font: 600 var(--t-title-s) var(--f-sans);
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.howto__head > span:first-of-type { flex: 1; min-width: 0; }
.howto__count {
  font: 600 var(--t-label-s) var(--f-mono);
  letter-spacing: var(--tr-label-s);
  color: var(--c-muted);
  flex: 0 0 auto;
}
.howto__chev {
  flex: 0 0 auto;
  color: var(--c-gold);
  transition: transform var(--m-standard) var(--m-ease);
}
.howto__head[aria-expanded="true"] .howto__chev { transform: rotate(180deg); }
.howto__ico { flex: 0 0 auto; color: var(--c-muted); }

.howto__body { padding: 0 var(--s-4) var(--s-2); }
.howto__body[hidden] { display: none; }

.howto__q {
  width: 100%;
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3) 0;
  background: none;
  border: 0;
  color: var(--c-text);
  font: 600 var(--t-title-s) var(--f-sans);
  text-align: left;
  cursor: pointer;
}
/* Номер двумя знаками: 01, а не 1. Так в приложении, и так столбик слева
   стоит ровно, сколько бы вопросов ни было. */
.howto__num {
  flex: 0 0 auto;
  font: 600 var(--t-label-s) var(--f-mono);
  letter-spacing: var(--tr-label-s);
  color: var(--c-gold);
}
.howto__q > span:nth-of-type(2) { flex: 1; min-width: 0; }
.howto__q .howto__chev { color: var(--c-muted); }
.howto__q[aria-expanded="true"] .howto__chev { color: var(--c-gold); }

.howto__a {
  margin: 0 0 var(--s-3);
  padding-left: calc(var(--s-3) + 18px);
  font: 400 var(--t-body-s) var(--f-sans);
  color: var(--c-muted);
}
.howto__a[hidden] { display: none; }

/* ---------- кнопка внизу ---------- */

/*
 * Пояснение под ползунком настройки: «примерно столько будет закопано». Шло
 * основным цветом и читалось как значимая строка, хотя объясняет соседний
 * ползунок. `--c-muted`, а не `--c-dim`: выключенным оно не является.
 */
.set-podstrochnik {
  margin: var(--s-2) 0 0;
  color: var(--c-muted);
  font: 400 var(--t-body-s) var(--f-sans);
}

/* В приложении она в bottomNavigationBar с полем space4, то есть НЕ уезжает
   со списком. У нас то же самое липкой полосой. */
.set-go {
  position: sticky;
  bottom: 0;
  padding: var(--s-4) 0 calc(var(--s-4) + env(safe-area-inset-bottom, 0px));
  margin-top: var(--s-4);
  background: linear-gradient(to bottom, transparent, var(--c-bg) 40%);
}
.set-go .btn { width: 100%; }

/*
 * ВТОРАЯ КНОПКА: «получи полный доступ».
 *
 * ТОЛЬКО КРАСКА. Ни одного объявления про размер, отступы, скругление или
 * кегль здесь нет и быть не должно: всю геометрию даёт `.btn`, и ровно поэтому
 * кнопка совпадает со стартом до пикселя. Стоит завести тут `padding` или
 * `font-size`, и она разъедется, а заметят это на одном языке из десяти.
 *
 * НЕФРИТ, а не изумруд. Оттенок выбран владельцем из восьми на пробе: он того
 * же ряда, но теплее, и в нашем тёплом чёрном не читается чужой вставкой.
 * Заливкой, а не контуром: кнопка заводится ради заходов на цены, а контурную
 * просто реже замечают.
 *
 * ТЕНЬ ПОВТОРЯЕТ ЗОЛОТУЮ по устройству (`--sh-gold`), только своим цветом:
 * два разных приёма подсветки на соседних кнопках видно сразу.
 */
.set-dostup {
  background: linear-gradient(135deg, #a7f3d0, #15803d);
  /* Волосина по краю: отсчёт заливки от той же коробки, по которой рисуем. */
  background-origin: border-box;
  color: #04231a;
  border-color: transparent;
  box-shadow: 0 6px 26px -6px rgba(21, 128, 61, .38);
}
.set-go .set-dostup { margin-top: var(--s-2); }

/*
 * ТРЕТЬЯ КНОПКА: «получить печатную версию».
 *
 * ТОЛЬКО КРАСКА, как и у второй: геометрию даёт `.btn`, и заводить тут размеры
 * нельзя. С одной оговоркой, и она измерена, а не придумана.
 *
 * КАЙМА СНЯТА СОВСЕМ, А НЕ ПОКРАШЕНА В ПРОЗРАЧНОЕ. Приём соседей
 * (`border-color: transparent` плюс заливка от коробки каймы) на этом градиенте
 * даёт по краю ВИДИМУЮ черту: справа проступает светлый конец градиента
 * (rgb 117,16,47 против заливки rgb 40,16,25), слева тёмный. Владелец увидел её
 * глазами, замер пикселей поперёк края подтвердил и показал, что покраска каймы
 * тёмным лечит только правую сторону. Снятие каймы чистит обе.
 *
 * ОТСТУП ВОЗВРАЩАЕТ ДВА ПИКСЕЛЯ ВЫСОТЫ, которые ушли вместе с каймой: без него
 * кнопка стала бы на два пикселя ниже соседних, то есть та же кривизна с другой
 * стороны. Равенство высот сторожит стенд, поэтому правка `.btn` его разбудит.
 */
.set-pechatj {
  border: 0;
  padding: 14px 25px;
  background: linear-gradient(135deg, #7a1030, #241018);
  color: #f2cbd6;
  box-shadow: 0 6px 26px -8px rgba(122, 16, 48, .45);
}
.set-go .set-pechatj { margin-top: var(--s-2); }

/*
 * СПРЯТАННАЯ КНОПКА ДОЛЖНА ИСЧЕЗАТЬ, а сама по себе она не исчезает: `.btn`
 * объявляет `display: inline-flex`, и авторский стиль бьёт `[hidden]` из
 * набора браузера. Без этой строки кнопка «получи доступ» висела бы у всех, в
 * том числе у заплативших, то есть ровно у тех, кому её показывать нельзя.
 */
.btn[hidden] { display: none; }

/* Пока открыта шторка, липкая кнопка страницы не рисуется: шторка это модальный
   разговор, и главное действие страницы на время его недоступно. Разбор у
   `sheet.js`, там же снимок, ради которого это заведено: на айфоне липкая
   всплывала ПОВЕРХ затемнения, и «Далее» страницы висела над «Далее» шторки. */
body.so-shtorkoy .set-go { visibility: hidden; }

/* ---------- кто играет ---------- */

/* Из lib/couple/couple_strip.dart, и там объяснено, что она заменила: два
   лица, две подписи, два поля со счётчиками, примерно треть первого экрана,
   и всё это спрашивало имена, которые пара уже вбила в прошлые четыре игры.
   Теперь имя живёт в профиле, а полоса его только ПОКАЗЫВАЕТ.

   Поле по бокам 16, сверху и снизу 12: чуть плотнее карточки, потому что
   внутри одна строка, а не вопросы. */
.duo {
  /* Полоса стала КНОПКОЙ (в ней карандаш), а у кнопки своя ширина по
     содержимому и своё выравнивание. Без этого она обрывалась на середине,
     не доходя до края карточки под ней. */
  width: 100%;
  appearance: none;
  -webkit-appearance: none;
  -webkit-tap-highlight-color: transparent;
  text-align: left;
  cursor: pointer;

  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  background: var(--c-card-bg);
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  color: var(--c-text);
}
.duo .faces { flex: 0 0 auto; }
.duo .faces img { width: 34px; height: 34px; }
.duo .faces img + img { margin-left: -14px; }

.duo__names {
  flex: 1;
  min-width: 0;
  font: 600 var(--t-title-s) var(--f-sans);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.duo__him { color: var(--c-player-1); }
.duo__her { color: var(--c-player-2); }
/* Амперсанд приглушён вдвое: он связка, а не имя. */
.duo__amp { color: var(--c-muted); opacity: .5; }


/* Карандаш в полосе пары: он справа и приглушён, потому что это не действие
   экрана, а возможность. Ярче он спорил бы с кнопкой «Начать». */
.duo__pen { flex: 0 0 auto; color: var(--c-faint); transition: color var(--m-standard) var(--m-ease); }
.duo:hover .duo__pen { color: var(--c-gold); }
.duo:focus { outline: none; }
.duo:focus-visible { outline: 2px solid var(--c-gold); outline-offset: 2px; }

/* Строка человека в шторке «кто играет»: подпись, шесть лиц, поле имени. */
.cs-row[hidden] { display: none; }
.cs-row .faces-pick { margin: var(--s-2) 0 var(--s-3); }
.cs-note { margin: 0 0 var(--s-4); }

/* Тумблер в шторке «кто играет»: две половины, каждая красится в цвет своего
   места, когда выбрана. Собран из того же .choice__opt, что и остальные
   вопросы: один и тот же орган управления во всей внутрянке. */
.cs-switch { margin-bottom: var(--s-4); }
.cs-switch__him[aria-pressed="true"] {
  background: none;
  border-color: var(--c-player-1);
  color: var(--c-player-1);
}
.cs-switch__her[aria-pressed="true"] {
  background: none;
  border-color: var(--c-player-2);
  color: var(--c-player-2);
}
/* Точка у своей половины: которая строка твоя, из названия места не следует. */
.cs-switch__mine {
  display: inline-block;
  width: 5px; height: 5px;
  margin-left: 6px;
  vertical-align: middle;
  border-radius: 50%;
  background: currentColor;
}

/* ==========================================================================
   ЭКРАН-ФОРМА

   СКВОЗНОЕ ПРАВИЛО (владелец 27.08.2026): экран настроек это КОЛОНКА, а не
   страница. Общий предел 1240 верен для списков и обложек, но форму он
   разваливает: пять чипов запаса растягиваются во всю ширину монитора, кнопка
   «Начать» становится линейкой, а вопрос и ответ уезжают в разные концы.

   560 это та ширина, в которой строка не рвётся и палец достаёт до любого
   чипа. То же число, что у игрового экрана: настройки и партия должны
   выглядеть одним местом, а не двумя.

   В КОНЦЕ ФАЙЛА: медиа-запрос не добавляет силы правилу, и предел, написанный
   выше `.screen { max-width: var(--кол) }`, проиграл бы ему.
   ========================================================================== */

@media (min-width: 1024px) {
  .screen--forma {
    max-width: 560px;
    margin-inline: auto;
  }
}

/* ==========================================================================
   ОЖИДАНИЕ: ОГОНЁК, ИМЯ И СТРОЧКА

   Из приложения, lib/splash/splash_screen.dart. Огонёк 62, дышит от 0.96 до
   1.04, позади тёплое свечение той же частоты. Строчка меняется каждые 3.2
   секунды и въезжает снизу.

   Раньше здесь стояло слово «Гружу». Оно сообщало ровно то, что человек и так
   видит, и сообщало голосом машины.
   ========================================================================== */

.gruzim {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  /**
   * ВО ВЕСЬ ЭКРАН И ПО ЦЕНТРУ, И НА ТЕЛЕФОНЕ ТОЖЕ.
   *
   * Владелец: «на мобайле повтори с приложения, там очень качественно
   * сделано». В приложении ожидание это ЭКРАН, а не блок в углу страницы:
   * огонёк ровно посередине, под ним имя, под ним фраза. Полторы сотни
   * вычитаются на нижнюю полосу разделов, чтобы центр был центром видимой
   * части, а не той, что под полосой.
   */
  min-height: calc(100svh - 148px);
  padding: var(--s-6);
  text-align: center;
}

.gruzim__ogonj {
  position: relative;
  width: 96px; height: 96px;
  display: grid;
  place-items: center;
  animation: gruzim-dyishit 2.6s ease-in-out infinite;
}
/* Свечение позади огонька: оно и делает его живым, а не значком. */
.gruzim__ogonj::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(245, 208, 169, .30), transparent 68%);
  animation: gruzim-svetit 2.6s ease-in-out infinite;
}
/* С 09.10.2026 в круге маскот, а не огонёк (владелец: «он будет лучше выглядеть»):
   фигурка крупнее значка, 80 из 96. Поза меняется вместе с фразой и коротко
   проявляется (ui.js, сменитьПозу). */
.gruzim__ogonj img { position: relative; width: 80px; height: 80px; }
.gruzim__maskot--voshyol { animation: gruzim-maskot .45s ease-out; }

.gruzim__imya {
  margin: 0;
  font: var(--t-headline-m);
  font-family: var(--f-serif);
  letter-spacing: .01em;
}

.gruzim__stroka {
  margin: 0;
  max-width: 30ch;
  min-height: 2.8em;
  color: var(--c-muted);
  font: var(--t-body-m);
}
.gruzim__stroka--voshla { animation: gruzim-stroka .5s ease-out; }

@keyframes gruzim-dyishit {
  0%, 100% { transform: scale(.96); }
  50%      { transform: scale(1.04); }
}
@keyframes gruzim-svetit {
  0%, 100% { opacity: .55; }
  50%      { opacity: 1; }
}
@keyframes gruzim-stroka {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
@keyframes gruzim-maskot {
  from { opacity: 0; transform: scale(.9); }
  to   { opacity: 1; transform: none; }
}

/* Уважаем «поменьше движения»: маскот остаётся и меняет позу, дыхание и проявление уходят. */
@media (prefers-reduced-motion: reduce) {
  .gruzim__ogonj,
  .gruzim__ogonj::before,
  .gruzim__stroka--voshla,
  .gruzim__maskot--voshyol { animation: none; }
}

/* ==========================================================================
   ЭКРАН-ФОРМА ПОСЕРЕДИНЕ ВЫСОТЫ

   ЗДЕСЬ БЫЛА ПРАВКА, КОТОРУЮ Я ОТКАТИЛ. Я разложил кнопку «назад» строкой над
   заголовком, со словом вместо кружка, и это нарушило сквозное правило
   («Заголовок в одну строку с органом управления», docs/refactor/RULES.md).
   Владелец поправил сразу: стрелка и название стоят в одной строке, и точка.

   Урок не про кнопку, а про диагноз. Жалоба была на ПУСТОТУ, а я тронул то,
   что попалось под руку рядом с пустотой. Пустота лечится компоновкой, чем и
   занимается блок ниже и боковая колонка в saga.css.
   ========================================================================== */

@media (min-width: 900px) {
  /**
   * ЭКРАН-ФОРМА СТОИТ ПОСЕРЕДИНЕ ВЫСОТЫ, а не прижат к верху.
   *
   * Ширину не трогаем: 560 это сквозное правило владельца от 27.08, и оно
   * верное, форму шире делать нельзя. А вот пустота ПОД формой была не
   * решением, а следствием: короткий экран просто висел в верхней трети.
   *
   * `safe center` вместо простого: если содержимое выше экрана, обычное
   * центрирование срезает ему верх, и до первого поля уже не добраться.
   */
  .screen--forma {
    min-height: calc(100svh - 128px);
    display: grid;
    align-content: safe center;
  }
}

/* ==========================================================================
   ЗАГЛУШКА КРУПНЕЕ НА ШИРОКОМ ЭКРАНЕ

   Владелец: «на десктопе оно мелкое очень». Огонёк 62 и имя телефонного кегля
   на мониторе теряются: на телефоне они занимают треть ширины, на мониторе
   пятнадцатую часть.

   Числа не выдуманы: тот же множитель, что у заголовка хаба (19 против 34 с
   боя), приложен к огоньку и имени.

   БЛОК ЧУТЬ НЕ ПОТЕРЯЛСЯ. Первый раз я дописал его в одной команде с заменой,
   которая упала, и цепочка `&&` оборвалась молча: файл остался прежним, а я
   пошёл снимать. Поймал снимок, а не проверка.
   ========================================================================== */

@media (min-width: 900px) {
  .gruzim {
    min-height: calc(100svh - 96px);
    justify-content: center;
    gap: var(--s-3);
  }
  .gruzim__ogonj { width: 150px; height: 150px; }
  .gruzim__ogonj img { width: 120px; height: 120px; }
  .gruzim__imya { font-size: 34px; }
  .gruzim__stroka { max-width: 40ch; font-size: 17px; }
}

/* ==========================================================================
   ДВОЙНОЙ ТАП НЕ ПРИБЛИЖАЕТ

   Владелец: «на мобайле иногда приближает и увеличивает сагу, и тексты уходят
   вправо и влево». Замер показал, что переполнения по ширине НЕТ ни на одном
   экране: документ ровно по окну. Значит увеличивает не вёрстка, а браузер, по
   двойному касанию, и «уходят вправо и влево» это уже поездка увеличенной
   страницы.

   Ловится это одним свойством. `manipulation` отключает ожидание второго
   касания (и заодно снимает те самые триста миллисекунд задержки нажатия), но
   НЕ трогает щипок двумя пальцами: кому надо приблизить, тот приблизит. Прямой
   запрет масштаба в `<meta viewport>` делать нельзя, это отбирает возможность
   у тех, кому она нужна, и iOS его всё равно не слушает.

   Ставим на то, ПО ЧЕМУ БЬЮТ ПАЛЬЦЕМ, а не на всё подряд: страница остаётся
   обычной страницей, а кнопка перестаёт быть мишенью для случайного второго
   касания. Игровые поверхности целиком: там частые нажатия подряд это норма.
   ========================================================================== */

button, a, label, summary, [role="button"], input, select, textarea,
.btn, .chip, .card, .row, .sheet, .sheet-acts,
.sg-igra, .sx-doska, .sg-vyiboryi, .sg-pilyulya {
  touch-action: manipulation;
}

/* ==========================================================================
   СВЯЗКА ПАРЫ
   ==========================================================================
   Шторка, которую открывают откуда угодно: из «Я» и из любой игры, которой
   нужны двое. Вид из приложения (lib/couple/pair_partner.dart): пара лиц,
   заголовок, одна строка о том, что связка одна на всё, свой код и поле для
   чужого. */

.pair { text-align: center; display: flex; flex-direction: column; align-items: center; }
.pair__litsa { margin-bottom: var(--s-4); }
.pair__chto {
  font: 500 var(--t-headline-s) var(--f-serif);
  color: var(--c-text);
  margin: 0 0 var(--s-2);
}
.pair__zachem {
  font: 400 var(--t-body-m) var(--f-sans);
  color: var(--c-muted);
  margin: 0 0 var(--s-5);
}
.pair__moy { width: 100%; justify-content: center; }

/* Два пути, и каждый со своей шапкой. Без них кнопка «мой код» и поле ввода
   читались как два шага одного действия, а это развилка: делает либо одно,
   либо другое, и кто именно, неважно. */
.pair__putj { width: 100%; }
.pair__shapka { margin: 0 0 var(--s-3); }

/* «Или» между путями, чертой с обеих сторон. Так же в приложении
   (lib/couple/pair_partner.dart), и так развилка видна одним взглядом. */
/* Ссылка-приглашение: главный путь связки. */
/* Завеса на время ухода на оплату. Поверх всего, включая полку. */
.k-oplate {
  position: fixed;
  inset: 0;
  z-index: 9999;
  background: var(--c-bg);
}

/* Запасные способы оплаты: тихая строка под главной кнопкой. */
.pay__inache {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--s-4);
  margin: var(--s-2) 0 0;
}
.pay__inache:empty { display: none; }

.pay__inache-ssyilka {
  border: 0;
  background: none;
  padding: var(--s-2) 0;
  min-height: var(--tap);
  color: var(--c-muted);
  font: 400 var(--t-body-s) var(--f-sans);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.pay__inache-ssyilka:active { color: var(--c-text); }

.pair__ssyilka { margin-bottom: var(--s-4); }

/* Коробка ссылки: адрес и значок копирования в один ряд, как у кода. */
.pair__korobka--ssyilka { margin-top: var(--s-3); gap: var(--s-2); }
.pair__korobka--ssyilka .pair__adres { flex: 1; min-width: 0; margin-top: 0; }

.pair__adres {
  margin-top: var(--s-3);
  padding: var(--s-3);
  border: 1px solid var(--c-border);
  border-radius: var(--r-ctl);
  background: var(--c-surface-alt);
  color: var(--c-muted);
  font: 400 var(--t-body-s) var(--f-mono);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
}
.pair__adres:active { background: rgba(212, 175, 55, .12); }

.pair__ili {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  width: 100%;
  margin: var(--s-5) 0;
}
.pair__ili::before,
.pair__ili::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--c-border);
}
.pair__ili span {
  font: 400 var(--t-body-s) var(--f-sans);
  color: var(--c-muted);
}

/* Код и значок копирования одной строкой. Значок ТЯНЕТСЯ ПО ВЫСОТЕ кода
   (`stretch`), а не центрируется рядом: заданной высоты у него нет, и любое
   число тут разъехалось бы с полем при другом размере шрифта. */
.pair__korobka {
  display: flex;
  align-items: stretch;
  gap: var(--s-2);
  width: 100%;
}

/* Код крупно и моноширинно: его читают вслух и переписывают на второй телефон,
   а в таком деле важнее всего не спутать похожие знаки. Сервер их и не даёт:
   в алфавите нет ни нуля, ни единицы, ни O, ни I. */
.pair__kod {
  flex: 1;
  min-width: 0;
  padding: var(--s-4);
  border-radius: var(--r-md, 16px);
  border: 1px dashed rgba(212, 175, 55, .5);
  background: rgba(212, 175, 55, .08);
  color: var(--c-gold-light);
  font: 600 28px/34px var(--f-mono);
  letter-spacing: .22em;
  text-indent: .22em;   /* трекинг последнего знака иначе сдвигает строку влево */
  cursor: pointer;
  user-select: all;
}

/* Значок копирования. Нажатие на код копирует его и без значка, но об этом
   надо было догадаться: владелец 07.09.2026 «копируется при нажатии, но юзеру
   может быть не понятно». Сброс кнопки здесь свой, а не из `.art`: там сброс
   для плитки во всю ширину, у которой ни рамки, ни фона. */
.pair__kopiya {
  flex: 0 0 auto;
  width: 56px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  margin: 0;
  appearance: none;
  border-radius: var(--r-md, 16px);
  border: 1px solid rgba(212, 175, 55, .5);
  background: rgba(212, 175, 55, .08);
  color: var(--c-gold-light);
  cursor: pointer;
}
.pair__kopiya:active { background: rgba(212, 175, 55, .2); }

/* Что делать с кодом дальше. После нажатия на две с половиной секунды
   превращается в «код скопирован» и золотится. */
.pair__kak {
  font: 400 var(--t-body-s) var(--f-sans);
  color: var(--c-muted);
  margin: var(--s-2) 0 0;
}
.pair__kak--gotovo { color: var(--c-gold-light); }

.pair__vvod { display: flex; gap: var(--s-2); width: 100%; }
/*
 * Поле для чужого кода. Общий вид берёт `.sheet-input`, и это не украшение:
 * до 07.09.2026 здесь стоял класс `.field`, которого в каркасе НЕТ ВОВСЕ, и
 * поле выходило белым прямоугольником браузера посреди тёмной шторки. Правил
 * оно не нарушало, просто их не получало: `var()` на несуществующий класс
 * ничего не сообщает, как и класс без правил.
 *
 * Кегль НЕ переопределяем: `.sheet-input` держит шестнадцать пикселей нарочно,
 * иначе Safari на iPhone приближает страницу при фокусе. Своё здесь только
 * моноширинность и трекинг, чтобы код читался знак за знаком.
 */
.pair__pole {
  flex: 1;
  min-width: 0;
  margin-top: 0;   /* отступ `.sheet-input` тут развалил бы строку с кнопкой */
  font-family: var(--f-mono);
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
}
.pair__beda {
  font: 400 var(--t-body-s) var(--f-sans);
  color: var(--c-muted);
  margin: var(--s-3) 0 0;
}

/* Строка под кодом: она сообщает, что мы и правда ждём, а не просто нарисовали
   код и забыли о нём. Тому, кто отдал код, узнать о подключении больше
   неоткуда: сервер ему ничего не шлёт. */
.pair__zhdyom {
  font: 400 var(--t-body-s) var(--f-sans);
  color: var(--c-muted);
  margin: var(--s-3) 0 0;
}

/* ---------- завеса ожидания второго устройства ---------- */

/*
 * ОДНА НА ВСЕ ИГРЫ, где играют вдвоём с двух устройств. Решение владельца
 * 31.08.2026: «как норму для всех игр сквозную». До этого она была написана
 * дважды, у шашек и у морского боя, разными классами; третья копия разошлась
 * бы с первыми двумя незаметно, потому что рядом их никто не видит.
 *
 * Накрывает целиком и размывает: пока второй не вошёл, играть нечем, и
 * полупрозрачная доска под завесой обещала бы обратное.
 */
.veil {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s-5);
  background: var(--c-scrim);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

.veil__box {
  width: 100%;
  max-width: 360px;
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}

.veil__what { font: 500 var(--t-title-m) var(--f-serif); color: var(--c-text); }
.veil__how { font: 400 var(--t-body-s) var(--f-sans); color: var(--c-muted); }


/* ==========================================================================
   ЭКРАН ПАРТИИ: СЛОЙ ВО ВЕСЬ ПРОСВЕТ
   ==========================================================================
   Приём общий, а не одной игры: так стоят крестики и скретч-карты, и так же
   устроены шашки своим `.ck`. Жил он в tictac.css только потому, что
   крестики пришли первыми; вторая игра, взявшая его оттуда, поставила бы
   свою доску в зависимость от чужого файла.
   ========================================================================== */

/*
 * ЭКРАН ПАРТИИ СЛОЕМ ВО ВЕСЬ ПРОСВЕТ, а не блоком внутри каркаса.
 *
 * Приём взят у шашек (`.ck` в checkers.css) и по той же причине: блок внутри
 * каркаса упирается в поля содержимого, в место под нижнюю полосу и в высоту
 * верхней. Доска обрывалась выше нижнего края, полоса хода уезжала за него, и к
 * ней приходилось мотать. Верхней и нижней полос у партии нет (nav.js), так что
 * перекрывать нечего.
 *
 * ВЫСОТУ ДЕРЖИТ САМ СЛОЙ, потому она и не может не поместиться: `inset: 0` это
 * ровно просвет, а не «сто процентов чего-то ещё».
 */
.screen--partiya {
  position: fixed;
  inset: 0;
  z-index: 20;
  /*
   * ШИРИНУ ЗАДАЮТ КРАЯ, А НЕ ЧИСЛО, и `width: auto` тут не для порядка. Каркас
   * на широком экране ставит `.screen { width: 100% }`, а у прибитого элемента
   * заданная ширина сильнее правого края: слой начинался от рельса и всё равно
   * тянулся на все 1440, то есть уезжал за экран правым боком вместе с доской.
   */
  width: auto;
  max-width: none;
  margin: 0;
  padding: env(safe-area-inset-top, 0px) 0 env(safe-area-inset-bottom, 0px);
  overflow: hidden;
}

@media (min-width: 1024px) {
  /*
   * СЛОЙ ПАРТИИ БЕРЁТ ВСЁ, ЧТО ОСТАЛОСЬ ОТ БОКОВОГО РЕЛЬСА, и то же самое
   * делают шашки. Отступ слева считается по ширине рельса, а не выдуман:
   * доска, отмеренная по ширине ОКНА, уехала бы под него.
   */
  .screen--partiya {
    inset: 0 0 0 var(--kolonka, 264px);
    padding: 0 var(--s-6) env(safe-area-inset-bottom, 0px);
    /*
     * СЛОЙ НЕ ЛЕЗЕТ ВЫШЕ БОКОВОГО РЕЛЬСА. Так же поступают шашки, и там это
     * прописано отдельной строкой: `z-index: auto`. С двадцаткой слой ложился
     * поверх всего каркаса, и рельс с навигацией пропадал, хотя во всех
     * остальных играх на десктопе он остаётся.
     */
    z-index: auto;
  }
}

/* ===================== кинк-лист ===================== */
/*
 * Перенос из приложения (`lib/kinks/kink_screen.dart`): карточки строк, карточка
 * согласия с золотой рамкой, совпадения полосами. Своей палитры у экрана нет,
 * всё из токенов каркаса; цвета имён берутся местом, как везде.
 */
.kk__verkh { display: flex; align-items: center; min-height: 44px; }

/* Согласие. Золотая рамка потому, что это не карточка среди прочих: пока оно
   не дано, на экране больше ничего нет. */
.kk-soglasie { border: 1px solid rgba(212,175,55,.30); }
.kk-soglasie__lid { margin: var(--s-2) 0 var(--s-4); color: var(--c-text); }
.kk-punkt { display: flex; gap: var(--s-3); align-items: flex-start; margin-bottom: var(--s-4); }
.kk-punkt__imya { font: 600 var(--t-title-s) var(--f-sans); }
.kk-punkt__nota { margin-top: 2px; font: 400 var(--t-body-s) var(--f-sans); color: var(--c-faint); }

/* Строки пары. Моя нажимается, партнёрская нет и никогда не будет: этот экран
   не может отвечать за них и не имеет права знать, что они ответили. */
.kk-stroka { display: flex; gap: var(--s-3); align-items: center; margin-bottom: var(--s-3); }
.kk-stroka--moya { cursor: pointer; }
.kk-stroka__telo { min-width: 0; flex: 1; }
.kk-stroka__imya { font: 600 var(--t-title-s) var(--f-sans);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.kk-stroka__nota { margin-top: 2px; font: 400 var(--t-body-s) var(--f-sans); color: var(--c-faint); }

.kk-zvatj { margin-bottom: var(--s-3); }
.kk-zvatj__nota { margin: var(--s-2) 0 var(--s-3); color: var(--c-faint);
  font: 400 var(--t-body-s) var(--f-sans); }

/* Совпадения. Заголовок стоит НАД ГРУППОЙ, а не у каждой строки: две сотни
   строк, подписанных каждая «Не терпится», это таблица, а не список. */
.kk-polosa { margin: var(--s-4) 0 var(--s-2); }
.kk-polosa:first-child { margin-top: 0; }
/* Сама строка описана ниже, вместе с точкой и галочкой: здесь только то, что
   к ним не относится. Двух наборов правил на один класс не держим. */
.kk-punkt-stroka:last-child { border-bottom: 0; }
.kk-punkt-stroka__nota { display: block; margin-top: 2px;
  font: 400 var(--t-body-s) var(--f-sans); color: var(--c-faint); }
/* Сделанное тише, но НЕ выключено: это дневник пары, а не вычеркнутый список. */
.kk-punkt-stroka--byilo .kk-punkt-stroka__tekst { color: var(--c-faint); }

.kk-dnevnik { margin-top: var(--s-4); }
.kk-pusto__imya { font: 600 var(--t-title-s) var(--f-sans); }
.kk-pusto__nota { margin-top: var(--s-2); color: var(--c-faint);
  font: 400 var(--t-body-s) var(--f-sans); }

.kk-zabyitj { display: block; width: 100%; margin-top: var(--s-6); }

/* Десктоп: то же, что на бою, но нашими токенами. Колонка не растягивается на
   всю ширину, иначе строка совпадения читается через полэкрана. */
@media (min-width: 900px) {
  .kk-soglasie, .kk-stroka, .kk-zvatj, .kk-sovpalo, .kk-dnevnik, .kk-pusto,
  .kk-zabyitj { max-width: 720px; margin-inline: auto; }
}

/* Спрос перед отметкой: она закрывает строку у обоих, поэтому спрашиваем всегда. */
.kk-spros { text-align: center; }
.kk-spros__chto { font: 600 var(--t-title-s) var(--f-sans); margin-bottom: var(--s-2); }
.kk-spros__nota { color: var(--c-faint); font: 400 var(--t-body-s) var(--f-sans);
  margin-bottom: var(--s-4); }
/*
 * Строка совпадения: точка, текст, галочка.
 *
 * Перенесено из `_Line` приложения вместе с числами: точка 8, отступ до текста
 * 12 (space3), отступ перед галочкой 8 (space2, «длинный заголовок иначе
 * упирается в значок»), галочка 18. Кнопка, поэтому вид кнопки сбрасываем.
 */
.kk-punkt-stroka { display: flex; align-items: flex-start; width: 100%;
  text-align: left; background: none; border: 0;
  border-bottom: 1px solid var(--c-border);
  color: inherit; cursor: pointer; font: inherit; padding: var(--s-3) 0; }
.kk-punkt-stroka__telo { flex: 1; min-width: 0; }
/*
 * Точка цветом СВОЕЙ ЧАСТИ, и ради неё двенадцать оттенков заведены: пара видит
 * с одного взгляда, что четыре верхних строки у неё из одной части. Отступ
 * сверху 6, чтобы она встала по первой строке текста, а не по верху блока.
 */
.kk-tochka { width: 8px; height: 8px; flex: none; border-radius: 50%;
  margin: 6px var(--s-3) 0 0; background: var(--ch); }
/* Отмеченная строка больше не про свою часть, она про сделанное: точка и
   галочка переходят на общий цвет отметки. */
.kk-punkt-stroka--byilo .kk-tochka { background: var(--c-gold); }
.kk-galka { flex: none; margin-left: var(--s-2); color: var(--c-faint);
  display: flex; align-items: center; height: 24px; }
.kk-punkt-stroka--byilo .kk-galka { color: var(--c-gold); }
.kk-punkt-stroka__tekst { display: block; font: 400 var(--t-body-m) var(--f-sans); }
/* День появляется только у отмеченной, разрядкой, как в приложении. */
.kk-punkt-stroka__denj { display: block; margin-top: 2px;
  font: 500 var(--t-label-s) var(--f-sans); letter-spacing: 1.2px;
  color: var(--c-faint); text-transform: uppercase; }

/* Значок пункта согласия: 18 и акцентным цветом, как в приложении. Маской, а
   не картинкой: файлы белые с альфой, и <img> покрасить нечем. */
.kk-znak { width: 18px; height: 18px; flex: none; margin-top: 2px;
  background: var(--c-gold);
  -webkit-mask: var(--zn) no-repeat center / contain;
          mask: var(--zn) no-repeat center / contain; }
/* Полоса совпадений сворачивается: две сотни строк, расписанных сразу, это
   таблица, которую не читают. */
.kk-polosa { display: flex; align-items: center; gap: var(--s-2); width: 100%;
  background: none; border: 0; color: var(--c-faint); cursor: pointer;
  text-align: left; font: inherit; padding: var(--s-2) 0; }
.kk-polosa__znak { margin-left: auto; font-size: 18px; line-height: 1; }
/* Действие показывается только у раскрытой строки: остальные это одна строка. */
.kk-punkt-stroka__deystvie { display: block; margin-top: var(--s-2);
  color: var(--c-gold); font: 600 var(--t-body-s) var(--f-sans); }

/* ==========================================================================
   КИНК-ЛИСТ: СПИСКИ, КОТОРЫЕ ПАРА ПИШЕТ САМА
   ==========================================================================
   Числа из `kink_list_editor.dart`, а не на глаз: значок 46, кружок цвета 34,
   точка ступени 8 с отступом сверху 6, разрядка подписи 1.4.

   Экран НАРОЧНО ПРОСТОЙ, и это записано целью в приложении: пара, пишущая
   «наш зимний список» в полночь, должна встретить поле имени, список и кнопку,
   а не анкету. Поэтому здесь нет ни рамок вокруг разделов, ни колонок. */

.kl-ryad { display: flex; flex-direction: column; gap: var(--s-2); }
.kl-karta { display: flex; align-items: flex-start; gap: var(--s-3);
  width: 100%; text-align: left; cursor: pointer; }
.kl-karta__znak { width: 20px; height: 20px; flex: none; margin-top: 2px;
  background: var(--ch);
  -webkit-mask: var(--zn) no-repeat center / contain;
          mask: var(--zn) no-repeat center / contain; }
.kl-karta__telo { flex: 1; min-width: 0; }
.kl-karta__imya { display: block; font: 600 var(--t-title-s) var(--f-sans); }
.kl-karta__nota { display: block; margin-top: 2px; color: var(--c-faint);
  font: 400 var(--t-body-s) var(--f-sans); }

.kl-pusto__imya { font: 600 var(--t-title-s) var(--f-sans); }
.kl-pusto__nota { margin-top: 2px; color: var(--c-faint);
  font: 400 var(--t-body-s) var(--f-sans); }
/* Предел не отказывает нажатием, а объясняет: кнопки на пределе просто нет. */
.kl-predel { margin-top: var(--s-5); color: var(--c-faint);
  font: 400 var(--t-body-s) var(--f-sans); text-align: center; }
.kl-predel-mal { color: var(--c-faint); font: 500 var(--t-label-s) var(--f-sans); }

.kl-imya { width: 100%; }
.kl-podzag { margin-top: var(--s-5); }

/* Двенадцать значков и двенадцать цветов: тот же набор, что у готовых частей,
   чтобы список пары стоял в том ряду как свой, а не как записка с краю. */
.kl-znachki { display: flex; flex-wrap: wrap; gap: var(--s-2);
  margin-top: var(--s-3); }
.kl-znachok { width: 46px; height: 46px; display: grid; place-items: center;
  border-radius: var(--r-md); background: var(--c-card-bg); cursor: pointer;
  border: 1px solid var(--c-border); }
.kl-znachok--vzyat { border-color: var(--ch); border-width: 1.6px; }
.kl-znachok__ris { width: 24px; height: 24px; background: var(--c-faint);
  -webkit-mask: var(--zn) no-repeat center / contain;
          mask: var(--zn) no-repeat center / contain; }
.kl-znachok--vzyat .kl-znachok__ris { background: var(--ch); }

.kl-tsveta { display: flex; flex-wrap: wrap; gap: var(--s-2);
  margin-top: var(--s-4); }
.kl-tsvet { width: 34px; height: 34px; border-radius: 50%; cursor: pointer;
  background: var(--ts); border: 2px solid transparent; }
.kl-tsvet--vzyat { border-color: var(--c-text); }

.kl-stroka-schyota { display: flex; align-items: baseline; gap: var(--s-3);
  margin-top: var(--s-6); margin-bottom: var(--s-3); }
.kl-stroka-schyota .eyebrow { flex: 1; }

/* Строка списка: точка цветом СТУПЕНИ, подпись ею же, крестик справа. */
.kl-punkt { display: flex; align-items: flex-start; gap: var(--s-3);
  padding: var(--s-3); margin-bottom: var(--s-2); border-radius: var(--r-md);
  background: var(--c-card-bg); border: 1px solid var(--c-border); }
.kl-punkt__tochka { width: 8px; height: 8px; flex: none; margin-top: 6px;
  border-radius: 50%; background: var(--zh); }
.kl-punkt__telo { flex: 1; min-width: 0; text-align: left; background: none;
  border: 0; padding: 0; color: inherit; font: inherit; cursor: pointer; }
.kl-punkt__imya { display: block; font: 400 var(--t-body-m) var(--f-sans);
  color: var(--c-text); }
.kl-punkt__stupenj { display: block; margin-top: 2px; color: var(--zh);
  font: 500 var(--t-label-s) var(--f-sans); letter-spacing: 1.4px;
  text-transform: uppercase; }
.kl-punkt__snyatj { flex: none; background: none; border: 0; padding: 0;
  color: var(--c-faint); cursor: pointer; display: flex; align-items: center;
  height: 24px; }
.kl-punkt__snyatj:hover { color: var(--c-text); }

.kl-dobavitj { width: 100%; margin-top: var(--s-4); }
.kl-vyibrositj { margin-left: auto; background: none; border: 0; padding: 0;
  color: var(--c-faint); cursor: pointer; display: flex; align-items: center; }
.kl-vyibrositj:hover { color: var(--c-danger); }

/* Шторка пункта: два поля ввода, поэтому высокая. Ограничить её половиной
   экрана значит спрятать второе поле под клавиатурой телефона. */
.kl-sh .sheet-input { width: 100%; margin-top: var(--s-2); }
.kl-sh__nota { min-height: 64px; }
.kl-sh__podzag { margin-top: var(--s-4); }
.kl-stupeni { display: flex; gap: var(--s-2); margin-top: var(--s-2); }
.kl-stupenj { flex: 1; padding: var(--s-3) var(--s-2); border-radius: var(--r-md);
  background: var(--c-card-bg); border: 1px solid var(--c-border);
  color: var(--c-text); font: 500 var(--t-body-s) var(--f-sans); cursor: pointer; }
.kl-stupenj--vzyata { border-color: var(--zh); color: var(--zh); }
.kl-sh__gotovo { width: 100%; margin-top: var(--s-5); }

@media (min-width: 900px) {
  .kl-ryad, .kl-imya, .kl-znachki, .kl-tsveta, .kl-stroka-schyota,
  .kl-punkt, .kl-dobavitj, .kl-pusto { max-width: 720px; margin-inline: auto; }
}
/* Вход в свои списки: обычная кнопка, но НАД «Забыть», а не рядом с ней.
   Одна добавляет, вторая стирает всё, и соседство просило бы о промахе. */
.kk-svoi { width: 100%; margin-top: var(--s-4); }
@media (min-width: 900px) { .kk-svoi { max-width: 720px; margin-inline: auto; } }

/* ==========================================================================
   КИНК-ЛИСТ: ЧАСТИ И ОПРОС
   ==========================================================================
   Переписано по `kink_chapters.dart` и `kink_ask.dart`. До этого здесь была
   плитка в два столбца, собранная мной заново, и владелец сравнил её со
   скринами приложения: «кардинально отличаются, прямо радикально». Он прав:
   там СТРОКИ ВО ВСЮ ШИРИНУ со счётом, подписью, отметкой второго и полосой,
   а плитка не несла ничего из этого.

   Числа оттуда: отступ экрана space5=20, между частями space3=12, полоса 3px,
   значок 20, зелёная галочка 14, разрядка счёта 1.4. */

.ka-shapka { display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-2) 0 var(--s-4); }
.ka-shapka__imya { font: 600 var(--t-title-m) var(--f-sans); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.ka-lid { color: var(--c-faint); font: 400 var(--t-body-m) var(--f-sans);
  margin-bottom: var(--s-4); }
/* Своё наверху, а не внизу: пара, пришедшая писать своё, не должна пролистать
   девять чужих частей, чтобы найти это предложение. */
.ka-napisatj { width: 100%; margin-bottom: var(--s-5); }
.ka-razdel { margin: var(--s-8) 0 var(--s-3); }

/* Часть: строка во всю ширину, а не плитка. */
/* Обводка СВОИМ цветом части, а не общим серым: владелец «нету обводок у
   корпусов как в приложении». Серая рамка есть у всего подряд и потому не
   читается как рамка; цвет части делает её видимой и заодно повторяет то,
   что уже несут значок и полоса. */
.ka-chastj { position: relative; padding: var(--s-4); margin-bottom: var(--s-3);
  border-radius: var(--r-lg); background: var(--c-card-bg);
  border: 1px solid color-mix(in srgb, var(--ch) 22%, transparent); }
/*
 * ВЗЯТАЯ ЧАСТЬ ЗАМЕТНА ТРЕМЯ ПРИЗНАКАМИ РАЗОМ.
 *
 * Было 22% рамки против 55%, и владелец эту разницу искал глазами. Три
 * признака это тот же приём, что у ответов в опросе: заливка изнутри цветом
 * части, рамка в полную силу, мягкое свечение под карточкой. Больше трёх на
 * одном слое не читается.
 */
.ka-chastj--vzyata {
  border-color: color-mix(in srgb, var(--ch) 85%, transparent);
  border-width: 1.6px;
  background:
    linear-gradient(color-mix(in srgb, var(--ch) 12%, transparent),
                    color-mix(in srgb, var(--ch) 12%, transparent)),
    var(--c-surface);
  /* Волосина по краю: отсчёт заливки от той же коробки, по которой рисуем. */
  background-origin: border-box;
  box-shadow: 0 0 22px -8px var(--ch);
}
/*
 * И медленная пульсация свечения: выбранное видно боковым зрением, пока палец
 * идёт к следующей карточке. Дышит только тень, ни размер, ни положение: тень
 * не двигает соседей и не заставляет браузер пересчитывать раскладку.
 */
@keyframes ka-dyishit {
  0%, 100% { box-shadow: 0 0 22px -8px var(--ch); }
  50%      { box-shadow: 0 0 30px -4px var(--ch); }
}
.ka-chastj--vzyata { animation: ka-dyishit 2.4s ease-in-out infinite; }
/*
 * Просили меньше движения: остаются заливка и рамка, то есть вся суть. Уходит
 * единственное, что мешает.
 */
@media (prefers-reduced-motion: reduce) {
  .ka-chastj--vzyata { animation: none; }
}
.ka-chastj--zaperta { opacity: .72; }
.ka-chastj__zhatj { display: block; width: 100%; text-align: left; background: none;
  border: 0; padding: 0; color: inherit; font: inherit; cursor: pointer; }
.ka-chastj__verkh { display: flex; align-items: center; gap: var(--s-2); }
.ka-chastj__znak { width: 20px; height: 20px; flex: none; background: var(--ch);
  -webkit-mask: var(--zn) no-repeat center / contain;
          mask: var(--zn) no-repeat center / contain; }
.ka-chastj__imya { flex: 1; min-width: 0; font: 700 var(--t-title-s) var(--f-sans);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Счёт разрядкой, как в приложении: он читается как метка, а не как текст. */
.ka-chastj__schyot { flex: none; color: var(--c-faint); letter-spacing: 1.4px;
  font: 500 var(--t-label-s) var(--f-sans); display: flex; align-items: center; }
.ka-chastj__nota { display: block; margin-top: 2px; color: var(--c-faint);
  font: 400 var(--t-body-s) var(--f-sans); }
/* «Второй тут был»: одна зелёная строка и больше ничем. Обводка его цветом
   говорила бы то же самое второй раз, и рамку носила бы каждая карточка. */
.ka-chastj__ikh { display: flex; align-items: center; gap: 6px; margin-top: var(--s-2);
  color: var(--c-done); font: 500 var(--t-label-s) var(--f-sans); }
.ka-chastj__pravka { position: absolute; top: var(--s-4); right: var(--s-4);
  background: none; border: 0; padding: 0; color: var(--c-faint); cursor: pointer; }
.ka-chastj__snova { width: 100%; margin-top: var(--s-3); }

/* Полоса: три пикселя, цветом своей части. */
.ka-polosa { height: 3px; margin-top: var(--s-3); border-radius: 2px;
  background: color-mix(in srgb, var(--c-text) 10%, transparent); overflow: hidden; }
.ka-polosa > span { display: block; height: 100%; border-radius: 2px;
  background: var(--ch, var(--c-gold)); }
.ka-polosa--khod { --ch: var(--c-player-1); margin: var(--s-2) 0 var(--s-3); }

.ka-poyti { width: 100%; margin-top: var(--s-5); }

/* ----- опрос ----- */
.ka-khod { display: flex; align-items: baseline; justify-content: space-between; }
.ka-khod__schyot { color: var(--c-faint); letter-spacing: 1.4px;
  font: 500 var(--t-label-s) var(--f-sans); text-transform: uppercase; }
.ka-khod__dolya { color: var(--c-faint); font: 500 var(--t-label-s) var(--f-sans); }
.ka-tikho { color: var(--c-faint); font: 400 var(--t-body-s) var(--f-sans);
  margin-bottom: var(--s-6); }
.ka-tikho--niz { margin: var(--s-4) 0 0; }

/*
 * Карта пункта НЕПРОЗРАЧНАЯ и с волоском, как в приложении.
 *
 * Стояло `--c-card-bg`, а это тот же #140e0e, но при 55%: поверх фона
 * #0a0606 выходит #0f0a0a, то есть почти фон, да ещё без рамки. Отсюда
 * «обводка сливается с задником, будто в пустоте висит». В приложении здесь
 * `scheme.surfaceContainer`, непрозрачный, плюс `outline` при 14%.
 */
.ka-karta { padding: var(--s-5); border-radius: var(--r-lg);
  background: var(--c-surface); border: 1px solid var(--c-border);
  margin-bottom: var(--s-6); }
.ka-karta__verkh { display: flex; align-items: center; gap: var(--s-3);
  margin-bottom: var(--s-3); }
.ka-karta__verkh .eyebrow { flex: 1; min-width: 0; }
/* Ступень точкой и словом, цветом самой ступени: одно золото трёх сил. */
.ka-zhar { flex: none; display: flex; align-items: center; gap: 6px; color: var(--zh);
  font: 500 var(--t-label-s) var(--f-sans); letter-spacing: 1.2px; }
.ka-zhar > i { width: 7px; height: 7px; border-radius: 50%; background: var(--zh); }
/* Сам пункт СЕРИФОМ и крупно: это единственное, ради чего открыт экран. */
.ka-karta__tekst { font: 600 var(--t-title-m) var(--f-serif); }
.ka-karta__nota { margin-top: var(--s-2); color: var(--c-faint);
  font: 400 var(--t-body-m) var(--f-sans); }

/* Пять ответов столбцом: пять пилюль в ряд не помещаются, не ужав слова до
   нечитаемости, а столбец разводит концы шкалы дальше всего. */
.ka-otvetyi { display: flex; flex-direction: column; gap: var(--s-2); }
.ka-otvet { display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-3) var(--s-4); border-radius: var(--r-md); cursor: pointer;
  border: 1px solid var(--c-border); background: var(--c-card-bg);
  color: var(--c-text); font: 500 var(--t-body-m) var(--f-sans); text-align: left; }
/*
 * НАВЕДЕНИЕ ТОЛЬКО ТАМ, ГДЕ ОНО ЕСТЬ.
 *
 * Стояло просто `:hover`, и на телефоне оно ЗАЛИПАЕТ: после нажатия кнопка
 * остаётся «под курсором» до касания в другом месте. Экран рисует следующий
 * вопрос, а кнопка на том же месте всё ещё обведена цветом прошлого ответа.
 * Это и была «подсветка, которая сохраняется на последнем выборе»: не
 * состояние в коде, а наведение, которому на сенсорном экране нечем кончиться.
 */
@media (hover: hover) {
  .ka-otvet:hover { border-color: var(--ac); }
}

/*
 * ВСПЫШКА ОТВЕТА: заливка идёт ИЗНУТРИ, а не одной рамкой.
 *
 * Три свойства разом, ровно как `_Row` в приложении: заливка теплеет до
 * цвета ответа при 14%, волосок берёт его же целиком, снизу поднимается
 * свечение при 20%, размытие 18, растяжка -6. Больше трёх на одном слое не
 * читается, и приложение на трёх и останавливается.
 *
 * Заливка отдельным слоем, а не `background`: у кнопки уже есть свой фон, и
 * подмешать к нему цвет переходом можно только через второй слой. Он же
 * даёт затухание в обе стороны, тогда как `--переменная` без `@property`
 * переключается рывком.
 */
.ka-otvet { position: relative; overflow: hidden;
  transition: border-color var(--m-standard) var(--m-ease),
              box-shadow var(--m-standard) var(--m-ease); }
.ka-otvet::before { content: ''; position: absolute; inset: 0;
  border-radius: inherit; background: var(--ac); opacity: 0;
  pointer-events: none;
  transition: opacity var(--m-standard) var(--m-ease); }
.ka-otvet > * { position: relative; }
.ka-otvet--vspyishka::before { opacity: .14; }
.ka-otvet--vspyishka { border-color: var(--ac);
  box-shadow: 0 0 18px -6px var(--ac);
  animation: ka-tolchok 380ms cubic-bezier(.2, .7, .3, 1); }

/*
 * Толчок пальцем: ряд стоит в своём размере, проседает под нажатием и
 * возвращается, перескочив себя примерно на три сотых. В приложении это
 * пружина с отскоком 0.25, запущенная СКОРОСТЬЮ, а не сдвигом цели.
 */
@keyframes ka-tolchok {
  0%   { transform: scale(1); }
  22%  { transform: scale(.972); }
  62%  { transform: scale(1.03); }
  100% { transform: scale(1); }
}

/*
 * Просили меньше движения: цвет остаётся, движение уходит. Это и есть вся
 * поблажка, человеку по-прежнему нужно видеть, что нажатие сработало.
 */
@media (prefers-reduced-motion: reduce) {
  .ka-otvet--vspyishka { animation: none; }
}

/* Шторка отметки: описание пункта и что случится. */
.kk-shtorka__nota { margin: var(--s-2) 0 0; color: var(--c-text);
  font: 400 var(--t-body-m) var(--f-sans); }
.kk-shtorka__chto { margin: var(--s-3) 0 var(--s-5); color: var(--c-faint);
  font: 400 var(--t-body-m) var(--f-sans); }
.kk-shtorka__da { width: 100%; }
.kk-shtorka__net { width: 100%; margin-top: var(--s-2); }
.ka-otvet__znak { display: block; width: 22px; height: 22px; flex: none;
  background: var(--ac);
  -webkit-mask: var(--zn) no-repeat center / contain;
          mask: var(--zn) no-repeat center / contain; }

@media (min-width: 900px) {
  /*
   * ЦЕНТРУЕМ, А НЕ ПРОСТО ОБРЕЗАЕМ ПО ШИРИНЕ. Правило записано выше, над
   * `.screen`: без `margin-inline: auto` остаток ширины уходит вправо одним
   * куском, и колонка липнет к левому краю. Владелец: «там где выбор
   * почему-то прилипло к левому краю, нарушая наши правила next дизайна».
   */
  .ka-shapka, .ka-lid, .ka-napisatj, .ka-razdel, .ka-poyti,
  .ka-khod, .ka-polosa, .ka-tikho, .ka-karta, .ka-otvetyi {
    max-width: 720px; margin-inline: auto;
  }

  /*
   * ЧАСТИ В ДВЕ КОЛОНКИ, а на очень широком в три.
   *
   * Тринадцать карточек в одну колонку это свиток на мониторе, где по высоте
   * влезло бы всё разом, и владелец справедливо спросил, зачем тут листать.
   * Карточка части самодостаточна: значок, имя, счёт, подпись, полоса. Значит
   * её можно ставить рядом с соседней, не теряя ни одного смысла.
   *
   * Порядок читается ПО СТРОКАМ, слева направо: сетка, а не колонки текста.
   * Колонки читались бы сверху вниз, и порядок справочника, который мы
   * бережём в `kink-ask.js`, перестал бы совпадать с тем, что видит глаз.
   */
  .ka-chasti {
    display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3);
    max-width: 1040px; margin-inline: auto; align-items: start;
  }
  .ka-chasti > .ka-chastj { max-width: none; margin-bottom: 0; }
}

@media (min-width: 1400px) {
  .ka-chasti { grid-template-columns: repeat(3, 1fr); max-width: 1240px; }
}

/* ----- кинк-хаб: шапка, строки людей, полосы совпадений ----- */
/* Переписано по `kink_screen.dart`: у меня было «Кинк-лист» и голые числа,
   у них надзаголовок, крупный сериф с золотым словом ВНУТРИ и действие
   справа в каждой строке. Числа оттуда: лицо 40, отступ 12, стрелка 18. */
.kk-verkh { display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-2) 0 var(--s-4); }
.kk-verkh__imya { font: 600 var(--t-title-m) var(--f-sans); }
.kk-kiker { margin-bottom: var(--s-2); }
/* Золотое слово стоит там, куда его ставит язык: заголовок его НЕСЁТ, а не
   заканчивается им. Поэтому подстановка в середину строки, а не приписка. */
.kk-shapka__krupno { font: 400 var(--t-display-s) var(--f-serif);
  margin: 0 0 var(--s-3); }
.kk-shapka__krupno em { font-style: italic; color: var(--c-gold); }
.kk-shapka__podvodka { color: var(--c-faint); font: 400 var(--t-body-m) var(--f-sans);
  margin: 0 0 var(--s-5); }

.kk-stroka { display: flex; align-items: center; gap: var(--s-3);
  width: 100%; text-align: left; }
.kk-stroka--moya { cursor: pointer; }
.kk-stroka__telo { flex: 1; min-width: 0; }
.kk-stroka__imya { display: block; font: 600 var(--t-title-s) var(--f-sans); }
.kk-stroka__nota { display: block; margin-top: 2px; color: var(--c-faint);
  font: 400 var(--t-body-s) var(--f-sans); }
/* Действие словом и стрелкой, цветом своего места: строка обещает переход, и
   обещание надо показать, а не спрятать в нажатии. */
.kk-stroka__khod { flex: none; display: flex; align-items: center; gap: 4px;
  font: 600 var(--t-body-m) var(--f-sans); }

.kk-polosa-imya { margin: var(--s-4) 0 var(--s-2); color: var(--c-gold); }
/* «И ещё N» открывает остаток полосы. Пять видно всегда: свёрнутая полоса
   встречала пару четырьмя серыми заголовками и ничем больше. */
.kk-eshchyo { background: none; border: 0; padding: var(--s-2) 0; cursor: pointer;
  color: var(--c-faint); font: 400 var(--t-body-s) var(--f-sans); }
.kk-eshchyo:hover { color: var(--c-text); }

@media (min-width: 900px) {
  .kk-verkh, .kk-kiker, .kk-shapka__krupno, .kk-shapka__podvodka { max-width: 720px; margin-inline: auto; }
}


/* ================= тесты для пар ================= */

.ts-lid { margin: 0 0 var(--s-5); color: var(--c-muted);
  font: 400 var(--t-body-m) var(--f-sans); }

/*
 * Три входа хаба. Строкой, а не плиткой: у каждого входа есть подпись в две
 * строки и счётчик, и в плитке шириной с половину экрана они не помещаются,
 * не ужав текст до нечитаемого.
 */
.ts-vkhod { display: flex; align-items: center; gap: var(--s-4); width: 100%;
  padding: var(--s-4); margin-bottom: var(--s-3); border-radius: var(--r-lg);
  background: var(--c-surface); border: 1px solid var(--c-border);
  color: var(--c-text); font: inherit; text-align: left; cursor: pointer;
  transition: border-color var(--m-standard) var(--m-ease); }
@media (hover: hover) { .ts-vkhod:hover { border-color: var(--c-border-strong); } }
/* Выключенный вход: экрана ещё нет, и обещать нажатие нечем. */
.ts-vkhod--skoro { opacity: .55; cursor: default; }
@media (hover: hover) { .ts-vkhod--skoro:hover { border-color: var(--c-border); } }

/*
 * Значок входа кружком в цвет смысла: пройти это золото действия, свои
 * результаты это спокойный синий игрока, общие это розовый пары. Цвета из
 * набора, свои сюда не заводятся.
 */
/*
 * Значок входа КАРТИНКОЙ, а не кружком из стилей. Три круга разного оттенка
 * это не три разных места, а один элемент трижды, и рядом с карточками
 * тестов, у каждой из которых свой постер, они читались как недоделка.
 * Цвета внутри картинок те же три: золото действия и два цвета мест.
 */
.ts-vkhod__znak { width: 40px; height: 40px; flex: none; border-radius: 50%;
  object-fit: cover; }

.ts-vkhod__telo { flex: 1; min-width: 0; }
.ts-vkhod__imya { display: block; font: 600 var(--t-title-s) var(--f-sans); }
.ts-vkhod__nota { display: block; margin-top: 2px; color: var(--c-muted);
  font: 400 var(--t-body-s) var(--f-sans); }
.ts-vkhod__schyot { flex: none; color: var(--c-faint); letter-spacing: 1.2px;
  font: 500 var(--t-label-s) var(--f-sans); }

/* ----- список тестов ----- */

.ts-kartochki { display: flex; flex-direction: column; gap: var(--s-3); }
.ts-razdel { margin: var(--s-6) 0 var(--s-3); }

/*
 * Карточка теста. Цвет теста приходит из боевого каталога переменной «--тц»:
 * тот же цвет лежит на постере, и назначить здесь свой значило бы поссорить
 * картинку с рамкой.
 */
.ts-karta { position: relative; border-radius: var(--r-lg);
  background: var(--c-surface);
  border: 1px solid color-mix(in srgb, var(--tts) 22%, transparent); }
.ts-karta--zapert { opacity: .72; }
/* Пройденный тише: он внизу и уже сделан, звать он не должен. */
.ts-karta--proyden { border-color: var(--c-border); }

.ts-karta__zhatj { display: flex; align-items: flex-start; gap: var(--s-3);
  width: 100%; padding: var(--s-4); background: none; border: 0;
  color: inherit; font: inherit; text-align: left; cursor: pointer; }
.ts-karta__poster { flex: none; width: 72px; height: 72px; border-radius: var(--r-md);
  object-fit: cover;
  background: color-mix(in srgb, var(--tts) 14%, transparent); }
.ts-karta__telo { flex: 1; min-width: 0; }
.ts-karta__verkh { display: flex; align-items: baseline; gap: var(--s-2); }
.ts-karta__imya { flex: 1; min-width: 0; font: 700 var(--t-title-s) var(--f-sans); }
/*
 * МЕТКА ОДНОЙ ШИРИНЫ НА ВСЕ ТРИДЦАТЬ КАРТОЧЕК.
 *
 * Владелец: «у всех тестов разного размера пилюли, выглядит криво». Так и было:
 * «7 мин · 36 вопросов», «Пройден» и замок это три разные длины, и правый край
 * списка рвался на каждой строке. Ширина задана числом, длинное подрезается.
 */
.ts-karta__metka { flex: none; width: 8.5em; text-align: right;
  color: var(--c-faint); letter-spacing: 1.2px;
  font: 500 var(--t-label-s) var(--f-sans);
  display: flex; align-items: center; justify-content: flex-end;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ts-karta__nota { display: block; margin-top: 4px; color: var(--c-faint);
  font: 400 var(--t-body-s) var(--f-sans); }
/* «Второй тут был» одной зелёной строкой, как в кинк-листе. */
.ts-karta__ikh { display: flex; align-items: center; gap: 6px; margin-top: var(--s-2);
  color: var(--c-done); font: 500 var(--t-label-s) var(--f-sans); }

/*
 * НА ТЕЛЕФОНЕ ПИЛЮЛИ НЕТ ВОВСЕ.
 *
 * Владелец: «с моба убрать пилюли». Одна ширина на все тридцать карточек чинила
 * рваный правый край, но чинила его ЦЕНОЙ ЗАГОЛОВКА: 8.5em это треть строки на
 * узком экране, и имя теста обрезалось ради подписи «7 мин · 36 вопросов»,
 * которая ни на что не влияет.
 *
 * Поэтому на телефоне метка уходит из строки имени вниз, отдельной тихой
 * строкой под крючком. `display: contents` распускает обёртку `__верх`, и её
 * дети становятся детьми `__тело`, где порядок задаётся числом: имя, крючок,
 * метка, «второй тут был». Ровный левый край делает то же, что делала общая
 * ширина, но ничего не отнимает у заголовка.
 *
 * Десктоп остаётся как был: там 8.5em не мешают, а правый край виден весь.
 */
@media (max-width: 899px) {
  .ts-karta__telo { display: flex; flex-direction: column; }
  .ts-karta__verkh { display: contents; }
  /* В колонке `flex: 1` растянул бы имя по высоте, а не по ширине. */
  .ts-karta__imya { flex: none; }
  .ts-karta__nota { order: 2; }
  .ts-karta__metka { order: 3; width: auto; margin-top: 4px;
    justify-content: flex-start; text-align: left; }
  .ts-karta__ikh { order: 4; }
}

@media (min-width: 900px) {
  /*
   * ЛИД И ЗАГОЛОВОК РАЗДЕЛА ИДУТ ПО ШИРИНЕ СОДЕРЖИМОГО, а не в своей колонке.
   *
   * Раньше здесь стояло `.ts-лид, .ts-вход, .ts-раздел { max-width: 560px;
   * margin-inline: auto }`, и на экранах с плиткой это разъезжалось: шапка шла
   * по всей ширине 1240, а подводка под ней сидела центрованной колонкой в
   * 560. Заголовок слева, текст посередине, сетка снова слева.
   *
   * Колонку теперь держит секция там, где она нужна (`screen--колонка` у хаба
   * и экрана ответа), а на плиточных экранах всё выравнено по одному левому
   * краю: шапка, подводка, заголовок раздела и сама сетка.
   */
  /*
   * Карточки сеткой в две колонки, как части кинка: тридцать штук в одну
   * колонку на мониторе это свиток там, где по высоте влезло бы всё.
   */
  .ts-kartochki { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3);
    max-width: 1040px; margin-inline: auto; align-items: start; }
}
@media (min-width: 1400px) {
  .ts-kartochki { grid-template-columns: repeat(3, 1fr); max-width: 1240px; }
}

/* ----- выбор теста плиткой ----- */

/*
 * Сетка плиток тут НЕ своя: берутся готовые `.arts` и `.art` с витрины игр.
 * Владелец просил «плиткой как в „Я“», и это ровно она. Своя сетка означала бы
 * четвёртый набор правил про то же самое, и разошёлся бы он на первой правке.
 *
 * Тут остаётся только то, чего у витрины игр нет: отступ между сетками,
 * приглушение пройденного и значок в углу.
 */
.ts-plitki { margin-bottom: var(--s-4); }

/*
 * ПЛИТКА ТЕСТА ЭТО КНОПКА, А НЕ ССЫЛКА, и потому ей нужен сброс.
 *
 * На витрине игр и в «Мы» `.art` всегда стоит на `<a>`, поэтому в нём нет ни
 * `border: 0`, ни `padding: 0`: ссылке они не нужны. Здесь нажатие никуда не
 * ведёт, оно открывает шторку с описанием, и правильный тег это `button`, а
 * ему браузер сам рисует рамку, отступ и свой шрифт. Сброс живёт здесь, а не в
 * `.art`: трогать общий компонент ради одного случая значит менять вид витрины
 * игр и «Мы» заодно.
 */
/*
 * Своего у плитки теста не осталось ничего: сброс кнопки переехал в сам модуль
 * `.art`, потому что забыть его может каждый следующий, и курсы забыли. Класс
 * оставлен крючком для отметки «пройден» ниже.
 */
.ts-plitka { }

/* Пройденный тише: он уже сделан и лежит отдельной сеткой под своим заголовком. */
.ts-plitka--proyden { opacity: .72; }
@media (hover: hover) { .ts-plitka--proyden:hover { opacity: 1; } }

/*
 * Значок в углу ставится только когда он что-то меняет: замок у платного и
 * галочка там, где второй уже прошёл. Метки «пройден» тут нет намеренно:
 * пройденные лежат под своим заголовком, и значок сказал бы то же дважды.
 */
.ts-plitka__znachok {
  position: absolute; top: var(--s-2); right: var(--s-2);
  width: 24px; height: 24px; display: flex;
  align-items: center; justify-content: center;
  border-radius: 50%;
  background: rgba(0, 0, 0, .55);
  backdrop-filter: blur(6px);
}
.ts-plitka__znachok--lock { color: var(--c-gold); }
.ts-plitka__znachok--check { color: var(--c-done); }

/*
 * Вторая строка на плитке: у своих результатов там имя типа, у общих
 * приглашение открыть. Одна строка с многоточием, потому что «Тревожно
 * привязанный» и «Надёжный» это разная длина, а ряд обязан остаться ровным.
 */
.ts-plitka__nota {
  display: block; margin-top: 2px;
  color: rgba(255, 255, 255, .72);
  font: 500 var(--t-label-s) var(--f-sans);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/*
 * ХВОСТ ШТОРКИ: кнопки списком там, где кончается чтение.
 *
 * Владелец: «в результатах нету кнопки закрыть, интуитивно не понятно как выйти
 * со шторки, вверху есть крестик но еще в самом низу добавить закрыть и пройти
 * заново, две кнопки списком». Крестик сверху остаётся, но разбор это несколько
 * экранов текста, и дочитав до конца, человек оказывается далеко от него.
 *
 * Черта сверху отделяет действия от текста: без неё кнопки читаются как
 * продолжение разбора.
 */
.ts-khvost {
  margin-top: var(--s-6);
  padding-top: var(--s-5);
  border-top: 1px solid var(--c-border);
  display: flex; flex-direction: column; gap: var(--s-3);
}
.ts-khvost .btn { width: 100%; }

/* ----- шторка «о чём этот тест» ----- */

/*
 * Сюда переехал крючок теста, снятый с плитки. Владелец: «уберём лишние
 * описания, оставим только названия, а на входе в тест сделай шторку, где
 * подробнее, чтобы блок этот не потерять».
 */
.ts-o { text-align: center; }
.ts-o__poster {
  width: 128px; height: 128px; border-radius: var(--r-lg);
  object-fit: cover; margin-bottom: var(--s-4);
  /* Своё свечение теста, тем же цветом, что на плитке и на постере. */
  box-shadow: 0 8px 28px -6px rgba(var(--ac), .45);
}
.ts-o__tekst {
  margin: 0 0 var(--s-4); color: var(--c-text);
  font: 400 var(--t-body-m) var(--f-sans);
}
.ts-o__mera {
  color: var(--c-faint); letter-spacing: 1.2px;
  font: 500 var(--t-label-s) var(--f-sans);
}
.ts-o__ikh {
  margin-top: var(--s-2); color: var(--c-done);
  font: 500 var(--t-label-s) var(--f-sans);
}
.ts-o__voyti { width: 100%; margin-top: var(--s-5); }

/* ----- шторка после последнего ответа ----- */

.ts-konets { text-align: center; }
.ts-konets__tekst {
  margin: 0 0 var(--s-5); color: var(--c-muted);
  font: 400 var(--t-body-m) var(--f-sans);
}
.ts-konets .btn { width: 100%; }
.ts-konets .btn + .btn { margin-top: var(--s-3); }

/* ----- прохождение теста ----- */

.ts-khod { display: flex; align-items: baseline; justify-content: space-between; }
.ts-khod__schyot { color: var(--c-faint); letter-spacing: 1.4px;
  font: 500 var(--t-label-s) var(--f-sans); }
.ts-khod__dolya { color: var(--c-faint); font: 500 var(--t-label-s) var(--f-sans); }

/* У двойных тестов два прохода, и человек обязан видеть, в каком он сейчас. */
.ts-prokhod { margin: var(--s-4) 0 2px; color: var(--c-text);
  font: 600 var(--t-title-s) var(--f-sans); }

/*
 * ВОПРОС В СВОЮ ПИЛЮЛЮ, и полоса прогресса под ним.
 *
 * Владелец: «блок текста в свою пилюлю», «полосу прогресса опустил бы вниз».
 * Раньше вопрос лежал карточкой вровень с колонкой и сливался с фоном, а
 * полоса стояла первой и забирала верх экрана у того, ради чего сюда пришли.
 *
 * Пилюля скруглена сильнее карточки и уже её на десктопе: так видно, что это
 * не блок страницы, а сам вопрос.
 *
 * ЗАЗОР СВЕРХУ У САМОЙ ПИЛЮЛИ, а не у соседа. Здесь стоял только
 * `margin-bottom`, и сверху пилюля держалась на чужом отступе: у двойных тестов
 * над ней лежит `.ts-проход` со своим `margin`, и зазор получался сам собой, а у
 * одиночных над ней сразу шапка, и название теста упиралось в край пилюли.
 * Владелец на скрине: «слово будущее уходит под пилюлю». То есть половина
 * тестов выглядела правильно по совпадению, а не по правилу.
 *
 * Общий отступ `.mast + …` не годится: он перечень имён соседей
 * (`.mast + .duo, .mast + .set-card`), и следующий экран, чей первый блок в
 * перечень не попал, снова встанет вплотную. Свой отступ у блока не зависит от
 * того, что оказалось над ним.
 */
.ts-pilyulya {
  padding: var(--s-5) var(--s-6);
  border-radius: var(--r-xl);
  background: var(--c-surface-high);
  border: 1px solid var(--c-border);
  margin-top: var(--s-5);
  margin-bottom: var(--s-5);
}
.ts-pilyulya__tekst {
  margin: 0;
  font: 600 var(--t-title-m) var(--f-serif);
  color: var(--c-text);
}

/* Полоса и счёт внизу, после кнопок ответа: смотрят на них между делом. */
.ts-niz { margin-top: var(--s-6); }
.ts-niz .ts-khod { margin-top: var(--s-2); }

/*
 * Здесь стояли `.tt-карта` и `.tt-карта__текст` от прежней карточки вопроса.
 * После переименования её в пилюлю правила осиротели, а `.tt-карта` в разметке
 * ЖИВЁТ у крестиков-ноликов: тот же префикс, другой хозяин. То есть мёртвые
 * для тестов правила молча перекрашивали чужую игру. Убраны.
 */

/*
 * Десять ступеней сеткой, а не флексом: у цифр разная ширина, и флекс дал бы
 * десять кнопок разного размера, из которых крайние шире средних.
 */
/*
 * ДВЕ КОЛОНКИ, а не десять в ряд.
 *
 * Десять кнопок на телефоне шириной 390 это по 34 пикселя каждая с зазором,
 * впритык к пределу нажатия, и внутрь помещается только голая цифра. Владелец:
 * «все цыфры в ряд смотряться странно», и позже «просто цыферное обозначение
 * будто странно». Две колонки дают по 170 на кнопку: туда входит и число, и
 * слово-якорь из данных теста.
 */
.ts-shkala { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-2); }
.ts-stupenj { position: relative; overflow: hidden; display: flex;
  align-items: baseline; gap: var(--s-3);
  padding: var(--s-4); border-radius: var(--r-md);
  border: 1px solid var(--c-border); background: var(--c-surface);
  color: var(--c-text); text-align: left; cursor: pointer;
  transition: border-color var(--m-standard) var(--m-ease),
              box-shadow var(--m-standard) var(--m-ease); }
/* Номер в своей ширине: иначе однозначные и двузначные разъезжаются. */
.ts-stupenj__nomer { flex: none; min-width: 1.4em;
  font: 700 var(--t-title-s) var(--f-serif); color: var(--ats); }
/*
 * СЛОВО СТУПЕНИ ЧИТАЕМЫМ ЦВЕТОМ, а не приглушённым.
 *
 * Стояло `--c-muted`, и владелец справедливо сказал: «серые и плохо читаемые,
 * нужно прямо фокусироваться». Наше же правило про цвета: читаемое всегда
 * `--c-text`, приглушённый цвет значит «выключено». А это не подпись к чему-то,
 * это САМ ОТВЕТ: по нему и выбирают, цифра рядом только порядковый номер.
 */
.ts-stupenj__slovo { flex: 1; min-width: 0; color: var(--c-text);
  font: 400 var(--t-body-s) var(--f-sans); }
@media (hover: hover) { .ts-stupenj:hover { border-color: var(--ats); } }

/*
 * Отдельной строки якорей больше НЕТ: слова переехали внутрь самих ступеней,
 * где они и нужны. Подпись под шкалой отвечала на вопрос «что значит край», а
 * человек спрашивает «что значит вот эта кнопка», и отвечать на это надо в ней.
 */

.ts-variantyi { display: flex; flex-direction: column; gap: var(--s-2); }
.ts-variant { position: relative; overflow: hidden;
  padding: var(--s-4); border-radius: var(--r-md);
  border: 1px solid var(--c-border); background: var(--c-surface);
  color: var(--c-text); font: 500 var(--t-body-m) var(--f-sans); text-align: left;
  cursor: pointer; --ats: var(--c-gold);
  transition: border-color var(--m-standard) var(--m-ease),
              box-shadow var(--m-standard) var(--m-ease); }
@media (hover: hover) { .ts-variant:hover { border-color: var(--ats); } }

/*
 * ВСПЫШКА ОТВЕТА, перенесена из кинк-листа целиком. Владелец: «заливка и потом
 * затухание, а то не понятно что выбрал».
 *
 * Заливка идёт ИЗНУТРИ вторым слоем: у кнопки уже есть свой фон, и подмешать к
 * нему цвет переходом иначе нельзя. Он же даёт затухание в обе стороны.
 *
 * ПОСТОЯННОЙ ПОДСВЕТКИ ВЫБРАННОГО НЕТ намеренно: экран сразу показывает
 * следующий вопрос, и оставшийся цвет горел бы уже под ним. Это решение
 * владельца, записанное в кинке, и здесь оно то же.
 */
.ts-stupenj::before, .ts-variant::before { content: ''; position: absolute;
  inset: 0; border-radius: inherit; background: var(--ats); opacity: 0;
  pointer-events: none; transition: opacity var(--m-standard) var(--m-ease); }
.ts-stupenj > *, .ts-variant > * { position: relative; }
.ts-vspyishka::before { opacity: .16; }
.ts-vspyishka { border-color: var(--ats); box-shadow: 0 0 18px -6px var(--ats);
  animation: ka-tolchok 380ms cubic-bezier(.2, .7, .3, 1); }
@media (prefers-reduced-motion: reduce) { .ts-vspyishka { animation: none; } }

/* ----- экран тестов колонкой ----- */

/*
 * КОЛОНКА ЦЕЛИКОМ, А НЕ СПИСОК ИСКЛЮЧЕНИЙ.
 *
 * Раньше на десктопе тут стоял перечень: `.ts-ход, .ts-проход, .tt-карта,
 * .ts-шкала, .ts-варианты { max-width: 560px }`. Он и подвёл. Я переименовал
 * карточку вопроса в `.ts-пилюля`, а перечень остался со старым именем, и
 * вопрос растянулся на все 1240, пока шкала под ним сидела в 560. Владелец
 * прислал скрин: «очень плохая компоновка, много пустоты».
 *
 * Беда была не в числе, а в способе: перечень имён надо помнить, а он живёт в
 * другом файле, чем разметка. Колонку теперь держит САМА СЕКЦИЯ, и всё, что
 * экран положит внутрь, встаёт в неё без единой новой строки CSS.
 *
 * Ширина та же, 560, и довод тот же, что у `.screen--форма`: форму читают
 * сверху вниз, и ширина ей нужна ровно та, в которой строка не рвётся.
 *
 * ПОЧЕМУ НЕ `.screen--форма`. У неё на десктопе стоит `min-height: calc(100svh
 * - 128px)` с `align-content: center`, и это верно для экрана, который сам
 * себе экран. Хаб же вкладывал её ВНУТРЬ своей секции, и правило прижимало
 * три входа к вертикальной середине, оставляя полтысячи пикселей пустоты между
 * лидом и ними. Здесь только ширина, без вертикального центрирования.
 */
@media (min-width: 900px) {
  .screen--kolonka { max-width: 560px; margin-inline: auto; }
}

/* Три входа хаба идут подряд, между ними тот же шаг, что у карточек. */
.ts-vkhodyi { display: flex; flex-direction: column; gap: var(--s-3); }

/* ----- результаты тестов ----- */

/* Состояние связи: одна карточка, один следующий шаг. */
.ts-sostoyanie { text-align: center; }
.ts-sostoyanie__tekst { margin: 0 0 var(--s-2); color: var(--c-text);
  font: 600 var(--t-title-s) var(--f-sans); }
.ts-sostoyanie__nota { margin: 0 0 var(--s-5); color: var(--c-muted);
  font: 400 var(--t-body-m) var(--f-sans); }
.ts-sostoyanie .btn { width: 100%; }

.ts-pusto { text-align: center; }
.ts-pusto__tekst { margin: 0 0 var(--s-5); color: var(--c-muted);
  font: 400 var(--t-body-m) var(--f-sans); }
.ts-pusto .btn { width: 100%; }

/* Карточка результата: та же рамка, что у теста, но с содержимым внутри. */
.ts-karta--itog { padding: var(--s-4); }
.ts-itog__verkh { display: flex; align-items: center; gap: var(--s-3); }
.ts-itog__shapka { flex: 1; min-width: 0; }
.ts-itog__denj { margin-top: 2px; color: var(--c-faint);
  font: 500 var(--t-label-s) var(--f-sans); letter-spacing: 1.2px; }
/* Тип крупно: ради него тест и проходят. */
.ts-itog__tip { margin: var(--s-4) 0 var(--s-2);
  font: 700 var(--t-title-m) var(--f-serif); color: var(--tts); }
.ts-itog__zanovo { width: 100%; margin-top: var(--s-4); }

.ts-grani { display: flex; flex-direction: column; gap: var(--s-3);
  margin-top: var(--s-3); }
.ts-granj__verkh { display: flex; justify-content: space-between;
  margin-bottom: 4px; }
.ts-granj__imya { color: var(--c-muted); font: 500 var(--t-label-s) var(--f-sans); }
/*
 * Два балла грани, свой и партнёра. Цифрами и тихо: разбор рядом ссылается на
 * них словами «вы на 7.2, партнёр на 4.1», и без них фраза повисает. Но
 * главное в разделе текст, поэтому числа идут подписью, а не заголовком.
 */
.ts-granj__mera { flex: none; color: var(--c-faint); letter-spacing: .6px;
  font: 500 var(--t-label-s) var(--f-sans); }
.ts-granj .ka-polosa { --ch: var(--tts); }

/*
 * Общий результат живёт в ШТОРКЕ, а не раскрывается в карточке.
 *
 * Раскрытие на месте казалось удобнее, но на телефоне оно выталкивало соседние
 * карточки вниз, текст уезжал под сгиб, и закрыть его было нечем. Владелец:
 * «результаты должны быть в шторке с кнопкой закрыть сверху, сейчас не удобно».
 */
.ts-obshchiy:empty { display: none; }
.ts-obshchiy { padding: 0; }
/* Крестик прижат к правому верху шторки, поверх её собственного заголовка. */
.ts-obshchiy__zakryitj { position: absolute; top: var(--s-3); right: var(--s-3);
  width: 36px; height: 36px; display: flex; align-items: center;
  justify-content: center; border-radius: 50%;
  background: var(--c-surface-high); border: 1px solid var(--c-border);
  color: var(--c-muted); cursor: pointer; }
@media (hover: hover) {
  .ts-obshchiy__zakryitj:hover { color: var(--c-text); border-color: var(--c-border-strong); }
}
.ts-obshchiy__imya { margin-bottom: var(--s-2);
  font: 700 var(--t-title-m) var(--f-serif); color: var(--tts); }
.ts-obshchiy__mera { font: 700 var(--t-title-l) var(--f-serif); color: var(--tts); }
.ts-obshchiy__podpisj { color: var(--c-faint); letter-spacing: 1.2px;
  font: 500 var(--t-label-s) var(--f-sans); }
.ts-obshchiy__slovo { margin: var(--s-2) 0 0; color: var(--c-text);
  font: 400 var(--t-body-m) var(--f-sans); }

/*
 * Сильные стороны и зона роста списком.
 *
 * Списком, а не абзацем, потому что в данных это и есть список: три-четыре
 * коротких утверждения, каждое про своё. Слить их в абзац значит спрятать
 * границы между ними и заставить перечитывать.
 */
.ts-spisok { margin: var(--s-2) 0 0; padding-left: var(--s-4);
  color: var(--c-text); font: 400 var(--t-body-m) var(--f-sans); }
.ts-spisok li { margin-bottom: var(--s-2); }
.ts-spisok li::marker { color: var(--tts, var(--c-gold)); }

/* Строка ожидания не нажимается: жать в ней нечего. */
.ts-karta__zhatj--tikho { cursor: default; }

@media (min-width: 900px) {
  .ts-sostoyanie, .ts-pusto { max-width: 560px; margin-inline: auto; }
}

@media (min-width: 900px) {
  /* Постер крупнее: карточка вдвое шире, а картинка в ней оставалась 72. */
  .ts-karta__poster { width: 88px; height: 88px; }

  /*
   * Вопрос это главное на экране прохождения, и на широком он должен читаться
   * с расстояния вытянутой руки, а не с телефонного.
   */
  /*
   * ЧЕРЕЗ `font`, А НЕ `font-size`. Токены размеров это ПАРА «размер через
   * интерлиньяж» (`--t-title-l: 20px/26px`), и косая черта имеет смысл только
   * внутри шортката. `font-size: var(--t-title-l)` разворачивается в
   * `font-size: 20px/26px`, что не значение, и объявление молча отбрасывается:
   * вопрос на десктопе так и оставался телефонного размера.
   */
  .ts-pilyulya { padding: var(--s-6) var(--s-7); }
  .ts-pilyulya__tekst { font: 600 var(--t-title-l) var(--f-serif); }

  /* Ступени выше: палец сменился курсором, но цель мельче делать незачем. */
  .ts-stupenj { padding: var(--s-5); }
  .ts-variant { padding: var(--s-4) var(--s-5); }

  /* Тип результата крупно: он тут ответ, а не подпись. */
  .ts-itog__tip { font: 700 var(--t-title-l) var(--f-serif); }
  .ts-obshchiy__mera { font: 700 var(--t-display-s) var(--f-serif); }
}

/* ==================== НАША ЛИНИЯ ==================== */

/*
 * ЧИСЛА ИЗ ПРИЛОЖЕНИЯ, И ТЕПЕРЬ НЕ ТОЛЬКО ГЕОМЕТРИЯ ЛИНИИ.
 *
 * Шкала текста (lib/theme/theme.dart, _textTheme):
 *   titleLarge   сериф 20/26 w500     заголовок экрана
 *   headlineSmall сериф 20/26 w500    заголовок первого захода и шторок
 *   titleSmall   Inter 15/20 w600
 *   bodyMedium   Inter 15/22 w400
 *   bodySmall    Inter 13/18 w400, цвет muted
 *   labelLarge   Inter 14/18 w600     надписи кнопок
 *   labelSmall   МОНО 11/14, трекинг 1.6, цвет muted
 *
 * Последнее и было пропущено в первом заходе: дата над блоком и годовщина
 * набраны МОНОШИРИННЫМ, а не Inter. На экране это заметно сразу.
 *
 * Цвета (lib/theme/palette.dart и ColorScheme.dark):
 *   surfaceContainer        #140E0E   карточки
 *   surfaceContainerHigh    #1D1716
 *   surfaceContainerHighest #282020   поля ввода и подложка лица
 *   outline                 #5C514E   границы
 *   onSurfaceVariant        #A1A1AA   вторичный текст
 *   secondary               #D4AF37   золото
 */

/*
 * У ЛИНИИ СВОЙ ЭКРАН, как у доски в партии.
 *
 * Владелец: «всегда виден таймер и всегда видна кнопка назад, по сути мы
 * скролим только линию, у неё свой лейаут». Так и в приложении: шапка это
 * AppBar, счётчик это bottomNavigationBar, а прокручивается ListView между
 * ними. У меня прокручивалась вся страница, шапка уезжала вверх, счётчик был
 * прибит к ОКНУ и потому центровался иначе, чем полотно. Отсюда и «не
 * отцентровано».
 *
 * Устройство то же, что у `.screen--партия`: слой во весь экран, а на широком
 * он начинается за боковым рельсом и не лезет поверх него.
 */
/*
 * ВЫСОТА ЗАДАНА ЯВНО, А НЕ ЧЕРЕЗ `bottom: 0`, И ЭТО ПРО iPhone.
 *
 * У прибитого слоя `inset: 0` меряется по вёрсточному вьюпорту, а в Safari на
 * телефоне он равен БОЛЬШОМУ: тому, каким экран будет, когда нижняя панель
 * браузера уедет. Панель при этом никуда не уезжает, потому что страница у нас
 * не прокручивается, и низ слоя вместе со счётчиком навсегда оказывался ПОД
 * ней. Владелец: «цифр не видно». `svh` это наименьший вьюпорт, тот, что виден
 * при поднятых панелях, и низ гарантированно над ними. `vh` строкой выше
 * остаётся для браузеров, которые про `svh` не знают.
 */
.screen--liniya { position: fixed; top: 0; left: 0; right: 0; z-index: 20;
  width: auto; height: 100vh; height: 100svh;
  padding: 0 var(--s-4); overflow: hidden; }

/*
 * ОБЁРТКА ТОЖЕ ВО ВСЮ ВЫСОТУ, и это была причина сразу трёх бед.
 *
 * Между слоем экрана и колонкой стоит `<div id="lnP">`, куда экран пишет своё
 * содержимое. Высота у неё была своя, по содержимому, а процент у колонки
 * считается от РОДИТЕЛЯ: `height: 100%` упирался в обёртку и превращался в
 * `auto`. Колонка становилась ростом с содержимое, и отсюда всё разом: счётчик
 * уезжал за нижний край, полотну нечего было прокручивать, плюс висел ниже
 * видимого, а на широком экране под счётчиком зияла пустота.
 *
 * Выбором `> *`, а не именем: обёртка это подробность разметки экрана, и
 * завязываться на её `id` значит поставить вторую вещь, которую надо помнить.
 */
.screen--liniya > * { height: 100%; min-height: 0; }

.ln { position: relative; display: flex; flex-direction: column; height: 100%;
  max-width: 880px; margin-inline: auto; }

/* Заголовок экрана это AppBar, а у него titleLarge: сериф 20/26 w500. Было
   Inter 17, и владелец назвал это «мелкая какая-то». */
.ln-verkh { flex: none; display: flex; align-items: center; gap: var(--s-3);
  padding: calc(var(--s-3) + env(safe-area-inset-top, 0px)) 0 var(--s-2); }
.ln-verkh__imya { font: 500 20px/26px var(--f-serif); color: var(--c-text); }

/*
 * ПРОКРУЧИВАЕТСЯ ТОЛЬКО ПОЛОТНО.
 *
 * ОСОБЕННОСТИ БРАУЗЕРОВ, все три по делу:
 *
 *   `overscroll-behavior: contain` не даёт докрутке утащить за собой страницу.
 *   Без него на iOS полотно, докрученное до конца, начинает тянуть весь экран,
 *   и линия «отлипает» от шапки;
 *
 *   `-webkit-overflow-scrolling` это старый Safari, где без него внутренняя
 *   прокрутка идёт рывками, без инерции;
 *
 *   `scrollbar-gutter` держит место под полосу прокрутки С ОБЕИХ сторон. В
 *   Windows полоса забирает ширину у содержимого, и хребет, который обязан
 *   стоять по середине, уезжал влево ровно на её толщину. На маке и на
 *   телефонах полоса наложенная и места не занимает, поэтому там свойство
 *   ничего не меняет; браузеры, которые его не знают, ведут себя как раньше.
 */
.ln-polotno { position: relative; flex: 1; min-height: 0;
  display: flex; flex-direction: column;
  padding-top: var(--s-4);
  overflow-y: auto; overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  /*
   * ПОЛОСЫ ПРОКРУТКИ НЕТ. Владелец, со скрина десктопа: «нужно убрать полоску».
   * Здесь стояло `scrollbar-gutter: stable both-edges`, и оно не только держало
   * место с обеих сторон, но и оставляло саму полосу видимой: широкую, светлую
   * и во весь рост экрана, прямо поверх правой половины линии.
   *
   * Убрать её можно, потому что линии есть чем сказать, что она продолжается:
   * хребет уходит за оба края, и по нему же идёт биение. Место с обеих сторон
   * при этом больше не нужно, а без него колонка встаёт ровно посередине.
   */
  scrollbar-width: none; }
.ln-polotno::-webkit-scrollbar { width: 0; height: 0; }

/* ----- один день ----- */

.ln-ryad { position: relative; padding-bottom: 30px; }

/*
 * ГДЕ СТОИТ ТОЧКА. Формула приложения (line_row.dart, _SpinePainter):
 *
 *     dotY = (высота ряда - kRowGap + kDateH) / 2
 *
 * то есть ровно середина блока под датой. В первом заходе я поставил её на
 * долю «номер ряда делить на всего», что вообще не про вертикаль внутри ряда:
 * точка висела где попало, и владелец увидел, что линии идут НЕ ОТ ТОЧКИ.
 * kRowGap 30, kDateH 20, отсюда «(100% - 10px) / 2».
 */
.ln-ryad { --uzel: calc((100% - 10px) / 2); }

/*
 * Хребет тремя остановками: половина пути от цвета соседа сверху, свой цвет на
 * высоте точки, половина пути к соседу снизу. Не в полную силу (в приложении
 * alpha 0.6): он то, на чём всё висит, и в полную силу спорил бы с точками.
 */
/*
 * ВВЕРХУ ЛИНИЯ УХОДИТ В НИЧТО, а не обрывается срезом. Владелец: «в приложении
 * линия в самом конце вверху будто замыливается, а не выглядит прямым
 * обрубком». Так и есть: у самой верхней строки верхний край градиента взят
 * ПРОЗРАЧНЫМ (`above == null ? hue.withValues(alpha: 0)`), и линия растворяется
 * на подходе к своей точке.
 *
 * Здесь на месте отсутствующего соседа подставлялся цвет самой строки, и линия
 * начиналась в полную силу прямо от края. Оба края теперь считает разметка:
 * снизу отсутствие соседа значит «продолжай как есть», потому что дальше идёт
 * хвост, а сверху значит «растворись».
 */
.ln-khrebet { position: absolute; inset: 0 auto 0 50%; width: 2px;
  transform: translateX(-1px); pointer-events: none;
  background: linear-gradient(to bottom,
    var(--verkh-khrebta) 0,
    var(--tsvet) var(--uzel),
    var(--niz-khrebta) 100%);
  opacity: .6; }

/*
 * Точка залитая, никогда не пустая: кольцо читается как заготовка, а диск как
 * то, что случилось. Тёмная подложка под ней из приложения: биение проходит
 * ПОД точкой, и без подложки его размытая голова просвечивала сквозь неё.
 * Свечение: радиус +5, alpha .18, размытие 6 (в биении вырастает).
 */
/*
 * ТОЧКА И ЕЁ ОРЕОЛ ЭТО ДВА РАЗНЫХ СЛОЯ, как в приложении, где ореол рисуется
 * своим кругом ДО тёмного ободка и самой точки.
 *
 * Держать ореол тенью самой точки нельзя: точка на биении вырастает в 1.35
 * раза, и тень растянулась бы вместе с ней, вместо того чтобы жить своей
 * жизнью. А живёт она другой: радиус идёт с 11 до 20.1, размытие с 6 до 13,
 * плотность с .18 до .54. Это и есть то, чем точка «отдаёт цвет веткам»:
 * стебель отходит на 15, рука на 30, и оба лежат внутри разлива.
 */
.ln-tochka { position: absolute; left: 50%; top: var(--uzel);
  width: 12px; height: 12px; margin: -6px 0 0 -6px; border-radius: 50%;
  background: var(--tsvet); pointer-events: none;
  box-shadow: 0 0 0 2px #0A0606; }
.ln-tochka--melkaya { width: 8px; height: 8px; margin: -4px 0 0 -4px; }
.ln-oreol { position: absolute; left: 50%; top: var(--uzel);
  width: 22px; height: 22px; margin: -11px 0 0 -11px; border-radius: 50%;
  background: color-mix(in srgb, var(--tsvet) 18%, transparent);
  filter: blur(6px); pointer-events: none; }
.ln-oreol--melkiy { width: 18px; height: 18px; margin: -9px 0 0 -9px; }

/* Стебель: от точки до стойки, только у дня с несколькими блоками. */
.ln-stebelj { position: absolute; top: var(--uzel); height: 1.5px;
  background: color-mix(in srgb, var(--tsvet) 90%, transparent); pointer-events: none; }
.ln-ryad--pravo .ln-stebelj { left: 50%; width: 15px; }
.ln-ryad--levo  .ln-stebelj { right: 50%; width: 15px; }

.ln-stolbets { display: flex; flex-direction: column; width: 50%; }
.ln-ryad--pravo .ln-stolbets { margin-left: 50%; }
.ln-ryad--levo  .ln-stolbets { margin-right: 50%; }

/* Дата: labelSmall, то есть МОНО, с правкой размера и трекинга из line_row. */
.ln-data { height: 20px; color: var(--c-muted);
  font: 600 10.5px/20px var(--f-mono); letter-spacing: 1.1px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ln-ryad--pravo .ln-data { padding-left: 30px; }
.ln-ryad--levo  .ln-data { padding-right: 30px; text-align: right; }

/* ----- сцепка: рука к блоку и кусок стойки ----- */

/*
 * ЗАЗОР МЕЖДУ БЛОКАМИ ОДНОГО ДНЯ ЛЕЖИТ ВНУТРИ БЛОКА, а не между блоками.
 *
 * Здесь стоял `margin-top: 10px`, и стойка рвалась ровно на этих десяти
 * пикселях: она рисуется от края до края СВОЕЙ сцепки, а внешний отступ лежит
 * за её краем. Владелец: «рваные линии там, где от двух штук начинается в одной
 * дате».
 *
 * В приложении это сделано иначе и потому цело: `gapBelow` входит в высоту
 * ячейки (line_row.dart, `_Connected`), стойка идёт через него насквозь, а рука
 * выходит на середине ОСТАВШЕЙСЯ части, `(size.height - gapBelow) / 2`. Ровно
 * это и переносим: зазор становится внутренним отступом, а середина считается
 * без него.
 */
.ln-stsepka { position: relative; --ruka: 50%; }
.ln-stsepka:not(.ln-stsepka--poslednyaya) { padding-bottom: 10px;
  --ruka: calc((100% - 10px) / 2); }
.ln-ryad--pravo .ln-stsepka { padding-left: 30px; }
.ln-ryad--levo  .ln-stsepka { padding-right: 30px; }

/* Рука в цвете ТОГО БЛОКА, к которому идёт, и выходит она с середины блока. */
.ln-ruka { position: absolute; top: var(--ruka); height: 1.5px;
  background: color-mix(in srgb, var(--granj) 90%, transparent); }
.ln-ryad--pravo .ln-ruka { left: 0; width: 30px; }
.ln-ryad--levo  .ln-ruka { right: 0; width: 30px; }
.ln-ryad--pravo .ln-stsepka--vilka .ln-ruka { left: 15px; width: 15px; }
.ln-ryad--levo  .ln-stsepka--vilka .ln-ruka { right: 15px; width: 15px; }

/* Стойка в цвете ТОЧКИ, а не блока: она говорит, что блоки одного дня. */
.ln-stoyka { position: absolute; top: 0; bottom: 0; width: 1.5px;
  background: color-mix(in srgb, var(--tsvet) 60%, transparent); }
.ln-ryad--pravo .ln-stoyka { left: 15px; }
.ln-ryad--levo  .ln-stoyka { right: 15px; }
/* У крайних стойка начинается и кончается на своей руке, а не на краю блока. */
.ln-stsepka--pervaya .ln-stoyka { top: var(--ruka); }
.ln-stsepka--poslednyaya .ln-stoyka { bottom: calc(100% - var(--ruka)); }
.ln-stsepka--pervaya.ln-stsepka--poslednyaya .ln-stoyka { display: none; }

/* ----- блок ----- */

.ln-blok { position: relative; overflow: hidden; display: block; width: 100%;
  background: var(--c-surface); color: var(--c-text);
  border: 1px solid rgba(255,255,255,.07); border-radius: 0;
  font: inherit; text-align: inherit; cursor: pointer; }
.ln-ryad--pravo .ln-blok { border-left: 2px solid var(--granj); }
.ln-ryad--levo  .ln-blok { border-right: 2px solid var(--granj); }

/* Поля головы из приложения: 11 сверху и снизу, 12 со стороны ветки и 10 со
   стороны края экрана, где стоит лицо. */
.ln-golova { display: flex; align-items: center; gap: 6px;
  width: 100%; padding: 11px 10px 11px 12px;
  background: none; border: 0; color: inherit; font: inherit;
  text-align: left; cursor: pointer; }
.ln-ryad--levo .ln-golova { flex-direction: row-reverse;
  padding: 11px 12px 11px 10px; text-align: right; }
.ln-golova__tekst { flex: 1; min-width: 0; color: var(--c-text);
  font: 600 13.5px/1.35 var(--f-sans);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; }
.ln-golova__znak { flex: none; margin-left: 6px;
  color: color-mix(in srgb, var(--c-muted) 55%, transparent);
  font: 500 12px/1 var(--f-mono); }
/* Лицо в наружном углу, у самого края: взгляд вдоль линии читает, чья это
   память, ничего не открывая. Место под него держится и когда лица нет. */
.ln-golova__litso, .ln-golova__mesto { flex: none; width: 26px; height: 26px; margin-left: 8px; }
.ln-ryad--levo .ln-golova__litso, .ln-ryad--levo .ln-golova__mesto { margin: 0 8px 0 0; }

/* Открытый блок ВСЕГДА одной высоты, kOpenH = 152. Длинная заметка
   прокручивается внутри, и ничего написанного не теряется. */
.ln-blok--otkryit .ln-nutro { height: 152px; display: flex; flex-direction: column;
  padding: 0 10px 10px 12px; }
.ln-ryad--levo .ln-blok--otkryit .ln-nutro { padding: 0 12px 10px 10px; }
.ln-zametka { flex: 1; min-height: 0; overflow-y: auto; color: var(--c-muted);
  font: 400 12.5px/1.5 var(--f-sans); }
.ln-ryad--levo .ln-zametka { text-align: right; }
.ln-deystviya { display: flex; margin-top: var(--s-2); }
.ln-ryad--levo .ln-deystviya { justify-content: flex-end; }
.ln-pilyulya { padding: 5px 12px; border-radius: var(--r-pill);
  border: 1px solid color-mix(in srgb, var(--c-border) 35%, transparent);
  background: none; color: var(--c-muted);
  font: 400 11.5px var(--f-sans); cursor: pointer; }

/* День от календаря: рамка в цвет, фона нет, открывать нечего. Текст МОНО. */
.ln-blok--dannyiy { display: flex; align-items: center; gap: 8px;
  padding: 11px 12px; background: transparent;
  border-color: color-mix(in srgb, var(--granj) 30%, transparent); }
.ln-ryad--levo .ln-blok--dannyiy { flex-direction: row-reverse; }
.ln-dannyiy__tekst { flex: 1; min-width: 0; color: var(--c-gold);
  font: 600 10.5px/1.4 var(--f-mono); letter-spacing: .8px; text-transform: uppercase;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; text-align: left; }
.ln-ryad--levo .ln-dannyiy__tekst { text-align: right; }
.ln-dannyiy__plyus { flex: none; color: color-mix(in srgb, var(--c-gold) 55%, transparent);
  font: 400 15px/1 var(--f-sans); }

/* День, с которого считают: подложка золотом на 10 процентов. */
.ln-blok--nachalo { display: flex; align-items: center; gap: 8px;
  padding: 13px 12px;
  background: color-mix(in srgb, var(--c-gold) 10%, transparent); }
.ln-ryad--levo .ln-blok--nachalo { flex-direction: row-reverse; }
.ln-nachalo__tekst { flex: 1; min-width: 0; color: var(--c-gold-light, #FDE68A);
  font: 600 13.5px/1.35 var(--f-sans); text-align: left;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ln-ryad--levo .ln-nachalo__tekst { text-align: right; }
.ln-nachalo__znak { flex: none; color: color-mix(in srgb, var(--c-gold) 60%, transparent);
  font: 400 14px/1 var(--f-sans); }

/* ----- хвост и продолжение ----- */

/*
 * ХВОСТ ТЯНЕТСЯ ДО САМОГО НИЗА. Владелец: «линия начинается не с самого низа».
 * В приложении подножие уходит за край экрана, потому что список всегда выше
 * окна; у нас на широком экране пять записей кончались на середине, и линия
 * повисала в воздухе. `flex: 1` даёт хвосту забрать весь остаток высоты, а 140
 * это его минимум, тот же, что в line_row.dart.
 */
.ln-khvost { flex: 1 0 140px; margin: 0 auto; width: 2px;
  background: linear-gradient(to bottom,
    color-mix(in srgb, var(--tsvet) 60%, transparent), transparent); }
/* Продолжение, наоборот, ровно 160 и не тянется: край окна не конец линии, и
   растянуть его значило бы соврать, что дальше ничего нет. */
.ln-eshchyo { flex: 0 0 160px; margin: 0 auto; width: 2px;
  background: color-mix(in srgb, var(--tsvet) 60%, transparent); }

/* ----- биение ----- */

/*
 * ЧЕГО ЗДЕСЬ НЕ ХВАТАЛО. Владелец: «в длине линии идёт импульс, и когда он
 * проходит сквозь точку, то даёт подсветку веткам». Вспышка точки и промывка
 * блока стояли, а САМОГО ИМПУЛЬСА не было: вспыхивало по очереди, но по линии
 * ничего не ехало, и связь между вспышками читалась только если её знать.
 *
 * Девять секунд ход, три отдыха (_kBeat, _kRest в line_screen.dart). Ход снизу
 * вверх, от дня, с которого считают, к сегодняшнему: линия растёт вверх.
 *
 * ОДИН ЛУЧ НА ВСЮ ЛИНИЮ, а не по лучу на строку. Так это и устроено в
 * приложении: биение там одно, а строке достаётся только вопрос, где оно
 * сейчас. Двести строк с собственной анимацией у каждой стоили бы двухсот
 * дорожек, которые браузер обязан тикать даже за краем экрана.
 */
/*
 * ЛУЧ МЕНЯЕТ ЦВЕТ НА КАЖДОЙ ТОЧКЕ, и это правило приложения дословно: он
 * приходит к точке с цветом предыдущей, ударяет в неё и дальше идёт цветом
 * ЭТОЙ. Строке при этом не нужно знать, где сейчас голова: внутри строки всё
 * ВЫШЕ её точки это её цвет, а всё ниже цвет соседа снизу. Шов между ними
 * приходится ровно на точку, и точка его собой закрывает, поэтому смена
 * читается как «точка передала лучу новый цвет», а не как две полосы встык.
 *
 * ЗДЕСЬ БЫЛ ОДИН БЕЛЫЙ ЛУЧ НА ВСЮ ЛИНИЮ. Я собрал его сложением света поверх
 * хребта, рассчитывая, что цвет он возьмёт у хребта. Хребет шириной два
 * пиксела, белого в луче куда больше, и владелец увидел ровно то, что вышло:
 * «пульсация одного цвета». Правило про цвет так не переносится.
 *
 * Поэтому лучей два НА СТРОКУ, каждый в своём окне: одно от верха строки до
 * точки, второе от точки до низа. Едут они синхронно и в общих координатах
 * полотна, а не своей строки, поэтому складываются в один непрерывный луч:
 * отсюда `--сверху` и `--узел-пкс`, замеренные смещения строки и её точки.
 *
 * Голова горячая (цвет, уведённый к белому на 55%), хвост длиной 280 и тянется
 * ВНИЗ, потому что идут вверх. Размытие 1.2, чтобы снять жёсткий край.
 */
@keyframes ln-udar-vyishe {
  0%   { transform: translateY(calc(var(--putj, 100vh) - var(--sverkhu, 0px))); opacity: 0; }
  3%   { opacity: 1; }
  70%  { opacity: 1; }
  75%  { transform: translateY(calc(-310px - var(--sverkhu, 0px))); opacity: 0; }
  100% { transform: translateY(calc(-310px - var(--sverkhu, 0px))); opacity: 0; }
}
@keyframes ln-udar-nizhe {
  0%   { transform: translateY(calc(var(--putj, 100vh) - var(--sverkhu, 0px)
           - var(--uzel-pks, 0px))); opacity: 0; }
  3%   { opacity: 1; }
  70%  { opacity: 1; }
  75%  { transform: translateY(calc(-310px - var(--sverkhu, 0px)
           - var(--uzel-pks, 0px))); opacity: 0; }
  100% { transform: translateY(calc(-310px - var(--sverkhu, 0px)
           - var(--uzel-pks, 0px))); opacity: 0; }
}
/*
 * БЕЗ СВОЕГО СЛОЯ НАРОЧНО. Поднимешь луч слоем, и он пойдёт ПОВЕРХ точки, а в
 * приложении он проходит под ней, и точка нарочно подложена диском фона: «голова
 * биения поднята почти до белого и размыта, и её свечение поднималось сквозь
 * лицо точки, отчего точка выглядела подсвеченной изнутри и теряла свой край».
 * Порядок в разметке уже верный, хребет, луч, ореол, точка, и трогать его
 * слоями значит переставить то, что и так стоит правильно.
 */
.ln-luch { position: absolute; left: 0; right: 0;
  overflow: hidden; pointer-events: none; }
.ln-luch--vyishe { top: 0; height: var(--uzel); --svet: var(--tsvet); }
.ln-luch--nizhe { top: var(--uzel); bottom: 0; --svet: var(--nizhe); }
.ln-luch__svet { position: absolute; left: 50%; top: 0;
  width: 3px; height: 310px; margin-left: -1.5px; filter: blur(1.2px);
  background: linear-gradient(to bottom,
    transparent 0,
    color-mix(in srgb, #fff 55%, var(--svet)) 12%,
    color-mix(in srgb, var(--svet) 50%, transparent) 42%,
    transparent 100%); }
.ln-luch--vyishe .ln-luch__svet { animation: ln-udar-vyishe 12s linear infinite; }
.ln-luch--nizhe .ln-luch__svet { animation: ln-udar-nizhe 12s linear infinite; }

/*
 * Промывка блока: свет входит со стороны ветки и умирает у дальнего края.
 * Идёт следом за лучом, поэтому такт у неё тот же.
 */
@keyframes ln-volna {
  0%   { opacity: 0; transform: translateX(-100%); }
  1%   { opacity: 1; }
  7%   { opacity: 0; transform: translateX(100%); }
  100% { opacity: 0; transform: translateX(100%); }
}
@keyframes ln-volna-levo {
  0%   { opacity: 0; transform: translateX(100%); }
  1%   { opacity: 1; }
  7%   { opacity: 0; transform: translateX(-100%); }
  100% { opacity: 0; transform: translateX(-100%); }
}
.ln-blok::after { content: ''; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(to right, transparent,
    color-mix(in srgb, var(--granj) 26%, transparent), transparent);
  opacity: 0;
  animation: ln-volna 12s linear infinite;
  animation-delay: calc((var(--takt) + 1) * -12s); }
.ln-ryad--levo .ln-blok::after {
  background: linear-gradient(to left, transparent,
    color-mix(in srgb, var(--granj) 26%, transparent), transparent);
  animation-name: ln-volna-levo; }

/*
 * Точка вспыхивает, когда луч доходит до неё: в приложении её радиус растёт в
 * 1.35 раза, а ореол с 5 до 12 при alpha с .18 до .54.
 *
 * ЗАДЕРЖКА ВСЕГДА ОТРИЦАТЕЛЬНАЯ, и лишний цикл в ней не для красоты:
 * положительная задержка означала бы, что первую половину минуты половина
 * точек просто не горит, потому что их черёд ещё не настал.
 */
@keyframes ln-vspyishka-tochki {
  0%, 44%, 100% { transform: scale(1); }
  50% { transform: scale(1.35); }
}
.ln-tochka { animation: ln-vspyishka-tochki 12s linear infinite;
  animation-delay: calc((var(--takt) - 0.5) * -12s - 12s); }

/*
 * ТОЧКА ОТДАЁТ ЦВЕТ ВЕТКАМ, и делает это РАЗЛИВОМ, а не подсветкой самих веток.
 *
 * В приложении стебель, рука и стойка не анимированы вовсе: у них своя
 * постоянная краска (стебель и рука .9 от цвета, стойка .6). Загораются они
 * потому, что ореол точки на них наползает. Числа его при этом такие, что
 * достаёт он далеко: радиус с 11 до 20, размытие с 6 до 13, плотность с .18 до
 * .54. Гауссово размытие с сигмой 13 расплывается ещё на три сигмы, то есть
 * ветка на 15 и рука на 30 лежат в разливе целиком.
 *
 * Я перенёс это вдвое меньшим (тень 13 пикселей это сигма 6.5, а не 13) и,
 * недосчитавшись света, подсветил ветки отдельной анимацией с осветлением.
 * Вышло две беды разом: в покое ветки стали ТУСКЛЕЕ, потому что анимация
 * держала им прозрачность .9 поверх их собственной, а в пике они белели вместо
 * того, чтобы налиться цветом. Владелец: «не отдаёт цвет в ветки».
 *
 * Размытие здесь `filter: blur()`, а не тень, потому что у фильтра число это
 * прямо сигма, то же самое, что у `MaskFilter.blur` в приложении. Рост идёт
 * масштабом, а масштаб растягивает и размытие, поэтому в пике заявлено 7.12:
 * 7.12 × 1.827 это те самые 13.
 */
@keyframes ln-oreol {
  0%, 44%, 100% { transform: scale(1); filter: blur(6px);
    background: color-mix(in srgb, var(--tsvet) 18%, transparent); }
  50% { transform: scale(1.827); filter: blur(7.12px);
    background: color-mix(in srgb, var(--tsvet) 54%, transparent); }
}
@keyframes ln-oreol-melkiy {
  0%, 44%, 100% { transform: scale(1); filter: blur(6px);
    background: color-mix(in srgb, var(--tsvet) 18%, transparent); }
  50% { transform: scale(1.933); filter: blur(6.72px);
    background: color-mix(in srgb, var(--tsvet) 54%, transparent); }
}
.ln-oreol { animation: ln-oreol 12s linear infinite;
  animation-delay: calc((var(--takt) - 0.5) * -12s - 12s); }
.ln-oreol--melkiy { animation-name: ln-oreol-melkiy; }

@media (prefers-reduced-motion: reduce) {
  .ln-luch { display: none; }
  .ln-blok::after, .ln-tochka, .ln-oreol { animation: none; }
}

/* ----- появление новой записи ----- */

/*
 * Шесть тактов и порядок владельца: линия тянется ВВЕРХ, потом точка, потом
 * ветка вбок, потом блок, потом слова, последним лицо. Доли из приложения:
 * 0.30, 0.48, 0.62, 0.76 от полутора секунд.
 */
@keyframes ln-podjjyom { from { clip-path: inset(100% 0 0 0); } to { clip-path: inset(0 0 0 0); } }
@keyframes ln-rodilasj { from { transform: scale(0); } to { transform: scale(1); } }
@keyframes ln-tyanetsya { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes ln-prishlo { from { opacity: 0; } to { opacity: 1; } }

.ln-ryad--rastyot .ln-khrebet { animation: ln-podjjyom .45s ease-out both; }
.ln-ryad--rastyot .ln-tochka { animation: ln-rodilasj .27s cubic-bezier(.2,.8,.3,1.4) .45s both; }
.ln-ryad--rastyot .ln-ruka,
.ln-ryad--rastyot .ln-stebelj { animation: ln-tyanetsya .21s ease-out .72s both; }
.ln-ryad--pravo.ln-ryad--rastyot .ln-ruka,
.ln-ryad--pravo.ln-ryad--rastyot .ln-stebelj { transform-origin: left center; }
.ln-ryad--levo.ln-ryad--rastyot .ln-ruka,
.ln-ryad--levo.ln-ryad--rastyot .ln-stebelj { transform-origin: right center; }
.ln-ryad--rastyot .ln-blok { animation: ln-prishlo .36s ease-out .93s both; }
.ln-ryad--rastyot .ln-golova__tekst { animation: ln-prishlo .3s ease-out 1.14s both; }
.ln-ryad--rastyot .ln-golova__litso { animation: ln-prishlo .3s ease-out 1.35s both; }

@media (prefers-reduced-motion: reduce) {
  .ln-ryad--rastyot .ln-khrebet, .ln-ryad--rastyot .ln-tochka,
  .ln-ryad--rastyot .ln-ruka, .ln-ryad--rastyot .ln-stebelj,
  .ln-ryad--rastyot .ln-blok, .ln-ryad--rastyot .ln-golova__tekst,
  .ln-ryad--rastyot .ln-golova__litso { animation: none; }
}

/*
 * ЛИЦА БЕРЁМ ОБЩИЕ, `.pface`. Своих правил у линии тут нет и не должно быть:
 * компонент уже собран по PlayerFace приложения, вместе с ободом, подложкой
 * surfaceContainerHighest и свечением на минус два. Я завёл было свои и это
 * была вторая ошибка подряд в одном месте: первая в том, что я передавал в
 * `лицо()` пустой ключ вместо настоящего, и на линии стояла заглушка вместо
 * аватара. Владелец: «аватарки не ставятся как в приложении».
 *
 * На линии лицо СПОКОЙНОЕ (`активно: false`): там не «чей ход», а «кто это
 * написал», и обод в полную силу со свечением означал бы очередь.
 */

/* ----- счётчик на заслонках ----- */

/*
 * ДОСКА-ПЕРЕКИДЫШ, а не просто цифры (lib/theme/flap_count.dart). Владелец:
 * «счётчик внизу почему-то совсем другой». Так и было: я нарисовал золотые
 * числа, а в приложении это табло, у которого есть МОМЕНТ переворота. Цифра
 * сначала одна, потом другая, и между ними такт; бегущая цифра это цифра, на
 * которую не поднимают глаз.
 *
 * Числа оттуда же: ячейка 22 на 29.92 (22 умножить на 1.36), радиус 4, зазор 3
 * внутри группы и 14 между группами, цифра моноширинная 60 процентов высоты
 * ячейки, подпись 9 с трекингом 1.4 в пяти пунктах под ячейкой.
 */
/*
 * СЧЁТЧИК ПРИБИТ К НИЗУ ЭКРАНА ЛИНИИ, а не к окну. Прежнее `position: fixed`
 * и было причиной, по которой он центровался иначе, чем полотно: окно шире
 * содержимого на ширину бокового рельса. Теперь это последняя строка той же
 * колонки, и обе середины совпадают по устройству, а не по совпадению.
 */
.ln-niz { flex: none; position: relative; z-index: 4;
  background: color-mix(in srgb, var(--c-surface) 92%, transparent);
  border-top: 1px solid color-mix(in srgb, var(--c-border) 28%, transparent);
  padding: var(--s-3) var(--s-4) calc(var(--s-3) + env(safe-area-inset-bottom, 0px)); }
.ln-schyot { display: flex; justify-content: center; align-items: flex-start; gap: 14px; }
.ln-gruppa { display: flex; flex-direction: column; align-items: center; }
.ln-tsifryi { display: flex; gap: 3px; }

.ln-zaslonka { position: relative; width: 22px; height: 29.92px;
  border-radius: 4px; overflow: hidden;
  background: linear-gradient(to bottom, #1D1B22 0%, #141317 50%, #0D0C10 100%);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--c-gold) 20%, transparent); }
.ln-zaslonka__verkh, .ln-zaslonka__niz, .ln-zaslonka__list {
  position: absolute; left: 0; right: 0; height: 50%; overflow: hidden;
  font: 600 17.95px/29.92px var(--f-mono); color: #F2EADC; text-align: center; }
.ln-zaslonka__verkh { top: 0; }
.ln-zaslonka__niz { bottom: 0; }
.ln-zaslonka__niz span { display: block; margin-top: -14.96px; }
/* Петля посередине, поверх половин, но под листом. */
.ln-zaslonka::after { content: ''; position: absolute; left: 1px; right: 1px;
  top: 50%; height: 1.2px; background: rgba(0,0,0,.9); }

/*
 * Лист в полёте: та же половина, сплющенная к петле и потемневшая, потому что
 * она отворачивается от света. Верхний падает первые 230 миллисекунд, нижний
 * доезжает вторые: всего 460, как в приложении.
 */
/*
 * ЛИСТ В ПОКОЕ СЛОЖЕН, и вот это была вся беда доски.
 *
 * `transform` здесь не задавался, а это `scaleY(1)`: оба листа лежали поверх
 * своих половин во весь рост и показывали то, что на них написано. А написаны
 * на них РАЗНЫЕ цифры, так устроен поворот: на верхнем старая, на нижнем новая.
 * Пока держался класс поворота, листья были прижаты заливкой анимации; в тот
 * миг, когда класс снимали, оба возвращались в рост, и ячейка замирала со
 * СТАРОЙ цифрой сверху и новой снизу. Владелец назвал это точно: «половина
 * одной цифры внизу и половина другой вверху».
 *
 * Покой это сложенный лист. Тогда видны неподвижные половины, а на них обеих
 * одна и та же новая цифра, ровно как в приложении: там при `from == to` листа
 * не рисуют вовсе (lib/theme/flap_count.dart, `_cell`).
 */
.ln-zaslonka__list { top: 0; transform-origin: center bottom;
  transform: scaleY(0);
  background: linear-gradient(to bottom, #1D1B22 0%, #141317 100%); }
.ln-zaslonka__list--niz { top: 50%; transform-origin: center top;
  transform: scaleY(0);
  background: linear-gradient(to bottom, #141317 0%, #0D0C10 100%); }
.ln-zaslonka__list--niz span { display: block; margin-top: -14.96px; }

/* Лист темнеет, отворачиваясь от света: в приложении чёрным при (1-k)*0.55. */
@keyframes ln-list-verkh {
  from { transform: scaleY(1); filter: brightness(1); }
  to   { transform: scaleY(0); filter: brightness(.45); } }
@keyframes ln-list-niz  {
  from { transform: scaleY(0); filter: brightness(.45); }
  to   { transform: scaleY(1); filter: brightness(1); } }
.ln-zaslonka--vertitsya .ln-zaslonka__list {
  animation: ln-list-verkh .23s ease-in both; }
.ln-zaslonka--vertitsya .ln-zaslonka__list--niz {
  animation: ln-list-niz .23s ease-out .23s both; }
.ln-zaslonka--vertitsya { box-shadow: inset 0 0 0 1px
  color-mix(in srgb, var(--c-gold) 55%, transparent); }

.ln-imya { margin-top: 5px; color: var(--c-gold);
  font: 600 9px/1 var(--f-mono); letter-spacing: 1.4px; text-transform: uppercase; }

@media (prefers-reduced-motion: reduce) {
  .ln-zaslonka--vertitsya .ln-zaslonka__list,
  .ln-zaslonka--vertitsya .ln-zaslonka__list--niz { animation: none; }
}

/*
 * Кнопка добавления: маленькая круглая, в углу под большим пальцем. Прибита к
 * ЭКРАНУ ЛИНИИ, а не к окну, и потому держится своей колонки, а не улетает к
 * краю широкого монитора. Приподнята над счётчиком ровно так же, как в
 * приложении её приподнимает Scaffold над своей нижней полосой.
 */
/*
 * ПЛЮС ЛЕЖИТ ОТДЕЛЬНЫМ СЛОЕМ поверх полотна и не двигается вместе с ним, ровно
 * как `floatingActionButton` у Scaffold: тот тоже не в теле, а над ним.
 *
 * ВЫСОТУ СЧЁТЧИКА МЕРИТ ЭКРАН, а не я. Здесь стояло 78, взятое на глаз, и это
 * было бы верно ровно до первого языка, где подпись под цифрами длиннее, или до
 * первой пары, у которой дней стало пятизначное число. Экран измеряет доску
 * после отрисовки и кладёт число сюда (`screens/line.js`); 78 остаётся запасным
 * на тот кадр, пока измерение ещё не случилось.
 */
.ln { --niz-schyota: 78px; }
.ln-plyus { position: absolute; right: 0; z-index: 5;
  bottom: calc(var(--niz-schyota) + var(--s-4));
  width: 46px; height: 46px; border-radius: 50%; border: 0;
  background: linear-gradient(135deg, #FDE68A, #D4AF37);
  color: #211803; font: 400 26px/1 var(--f-sans); cursor: pointer;
  box-shadow: 0 6px 26px -6px color-mix(in srgb, var(--c-gold) 35%, transparent); }

/* ----- первый заход и формы ----- */

/*
 * Первый заход живёт в том же слое во весь экран, поэтому прокручивается САМ:
 * снаружи `overflow: hidden`, и без этой строки длинная подводка на маленьком
 * телефоне просто обрезалась бы вместе с кнопкой.
 */
.ln-nachalo { max-width: 560px; margin-inline: auto; height: 100%;
  overflow-y: auto; overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding-bottom: calc(var(--s-8) + env(safe-area-inset-bottom, 0px)); }

/* Eyebrow: моно, трекинг 2.2, прописными, вторичным цветом. */
.ln-nadzagolovok { color: var(--c-muted); font: 600 11px/14px var(--f-mono);
  letter-spacing: 2.2px; text-transform: uppercase; }
/* SerifTitle на headlineSmall: сериф 20/26 w500. */
.ln-zagolovok { margin: var(--s-2) 0 var(--s-2);
  font: 500 20px/26px var(--f-serif); color: var(--c-text); }
.ln-podvodka { margin: 0 0 var(--s-5); color: var(--c-muted);
  font: 400 15px/22px var(--f-sans); }
.ln-tikho { margin: var(--s-2) 0 0; color: var(--c-muted);
  font: 400 13px/18px var(--f-sans); }

.ln-forma { display: flex; flex-direction: column; gap: var(--s-4); }

/*
 * КАРТОЧКА СО СТРОКОЙ, а не голое поле даты. Владелец: «полоса с датой, там
 * просто чёрная линия и не совсем будет понятно юзеру». Так и было: я поставил
 * «input type=date», и в тёмной теме он выглядит полосой без единого слова. В
 * приложении это Card с ListTile: значок календаря золотом, крупная строка с
 * датой, подпись под ней и шеврон справа.
 *
 * Card: фон surfaceContainer, радиус 20 (radiusLg), рамка outline на 16
 * процентов. ListTile: поля по 16 по бокам, высота от содержимого.
 */
.ln-stroka { display: flex; align-items: center; gap: var(--s-4); width: 100%;
  padding: 14px var(--s-4); text-align: left;
  background: var(--c-surface); color: var(--c-text);
  border: 1px solid color-mix(in srgb, var(--c-border) 16%, transparent);
  border-radius: var(--r-lg); cursor: pointer; font: inherit; }
.ln-stroka__znachok { flex: none; width: 24px; height: 24px; color: var(--c-gold); }
.ln-stroka__znachok svg { display: block; width: 100%; height: 100%; }
.ln-stroka__telo { flex: 1; min-width: 0; }
.ln-stroka__imya { display: block; color: var(--c-text);
  font: 400 16px/24px var(--f-sans); }
.ln-stroka__nota { display: block; margin-top: 1px; color: var(--c-muted);
  font: 400 15px/22px var(--f-sans);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ln-stroka__shevron { flex: none; width: 20px; height: 20px; color: var(--c-muted); }
.ln-stroka__shevron svg { display: block; width: 100%; height: 100%; }
/*
 * Настоящее поле даты лежит поверх строки и невидимо: нажатие открывает
 * системный выбор, а выглядит это карточкой приложения. Прятать через
 * «display: none» нельзя, тогда его нечем открыть на телефоне.
 */
.ln-stroka input[type="date"] { position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0; border: 0; padding: 0; cursor: pointer; }
.ln-stroka { position: relative; }

/* Поля ввода: заливка surfaceContainerHighest, радиус 12, золотой фокус 1.5. */
.ln-pole { display: flex; flex-direction: column; gap: var(--s-2); }
.ln-pole__imya { color: var(--c-muted); font: 600 11px/14px var(--f-mono);
  letter-spacing: 2.2px; text-transform: uppercase; }
.ln-vvod { width: 100%; padding: 14px var(--s-4);
  border: 0; border-radius: var(--r-sm); background: #282020; color: var(--c-text);
  font: 400 15px/22px var(--f-sans); }
.ln-vvod:focus { outline: 0; box-shadow: inset 0 0 0 1.5px var(--c-gold); }
.ln-vvod--mnogo { resize: vertical; min-height: 92px; line-height: 1.5; }

/* GoldButton: пилюля с градиентом goldLight в gold, поля 20 на 15, тень. */
.ln-knopka { width: 100%; padding: 15px var(--s-5); border: 0;
  border-radius: var(--r-pill);
  background: linear-gradient(135deg, #FDE68A, #D4AF37); color: #211803;
  font: 600 14px/18px var(--f-sans); cursor: pointer;
  box-shadow: 0 6px 26px -6px color-mix(in srgb, var(--c-gold) 35%, transparent); }
.ln-knopka:disabled { opacity: .5; cursor: default; box-shadow: none; }
.ln-knopka--tikhaya { background: none; color: var(--c-muted); box-shadow: none;
  border: 1px solid color-mix(in srgb, var(--c-border) 35%, transparent); }
.ln-snyatj { width: 100%; }

/* ChoiceRow: пилюли, выбранная залита золотым градиентом и текст на ней чёрный. */
.ln-vyibor { display: flex; flex-direction: column; gap: var(--s-2); }
.ln-vyibor__ryad { display: flex; gap: var(--s-2); }
.ln-vyibor__kn { flex: 1; padding: var(--s-3) var(--s-2);
  border-radius: var(--r-pill);
  border: 1px solid color-mix(in srgb, var(--c-border) 18%, transparent);
  background: var(--c-surface); color: var(--c-text);
  font: 600 14px/18px var(--f-sans); cursor: pointer;
  transition: background var(--m-micro, 120ms) linear, color var(--m-micro, 120ms) linear; }
.ln-vyibor__kn--vzyat { border-color: transparent; color: #211803;
  background: linear-gradient(135deg, #FDE68A, #D4AF37); 
  /* Волосина по краю: отсчёт заливки от той же коробки, по которой рисуем. */
  background-origin: border-box; }

/* Кто пишет: две карточки с лицом и именем. */
.ln-kto { display: flex; gap: var(--s-3); }
.ln-kto__kn { flex: 1; display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-3); border-radius: var(--r-md);
  border: 1px solid color-mix(in srgb, var(--c-border) 25%, transparent);
  background: var(--c-surface); color: var(--c-text); font: inherit; cursor: pointer; }
/*
 * ВЫБРАННЫЙ ОБВЕДЁН СВОИМ ЦВЕТОМ. Владелец: «не могу сейчас выбрать аватарку,
 * не кликабельное поле». Нажатие работало, а выбор был НЕ ВИДЕН: я оставил
 * только толщину рамки, 1 против 1.6, и разница в шесть десятых пиксела на
 * тёмном фоне неразличима. В приложении (line_sheet.dart, _Seat) выбранная
 * карточка обводится цветом МЕСТА, синим у него и розовым у неё, и толщина там
 * следствие, а не признак.
 */
.ln-kto__kn--vzyat { border-width: 1.6px; border-color: var(--tsvetMesta); }
.ln-kto__imya { flex: 1; min-width: 0; font: 600 15px/20px var(--f-sans);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: left; }

/* ----- десктоп ----- */

/*
 * ЛИНИЯ НЕ ДОЛЖНА СТАТЬ МЕЛКОЙ. Ловушка тут обратная привычной: линия делится
 * пополам, и наши обычные 560 дали бы блоку 280 минус ветка, то есть меньше,
 * чем на телефоне. Полотну 880: половина 440, и блок выходит ШИРЕ телефонного.
 */
@media (min-width: 900px) {
  /*
   * Слой начинается ЗА БОКОВЫМ РЕЛЬСОМ и не лезет поверх него: то же правило и
   * та же причина, что у `.screen--партия`. С прежним `z-index: 20` он накрыл
   * бы навигацию, которая во всех остальных играх на десктопе остаётся.
   */
  .screen--liniya { inset: 0 0 0 var(--kolonka, 264px); z-index: auto;
    padding: 0 var(--s-6); }

  /*
   * Ширину держит ОДНА колонка на всё, а не каждый кусок по отдельности.
   * Раньше полотно центровалось внутри содержимого, а счётчик, прибитый к окну,
   * внутри окна: две разные середины, и владелец увидел это как «не
   * отцентровано».
   */
  .ln-verkh__imya { font-size: 26px; line-height: 32px; }

  .ln-golova { padding: 14px 12px 14px 16px; }
  .ln-ryad--levo .ln-golova { padding: 14px 16px 14px 12px; }
  .ln-golova__tekst { font-size: 15.5px; }
  .ln-zametka { font-size: 14px; }
  .ln-blok--otkryit .ln-nutro { height: 172px; padding: 0 12px 12px 16px; }
  .ln-ryad--levo .ln-blok--otkryit .ln-nutro { padding: 0 16px 12px 12px; }
  .ln-data { font-size: 11.5px; }
  .ln-dannyiy__tekst { font-size: 11.5px; }
  .ln-nachalo__tekst { font-size: 15px; }
  .ln-blok--dannyiy, .ln-blok--nachalo { padding: 13px 16px; }

  .ln-tochka { width: 14px; height: 14px; margin: -7px 0 0 -7px; }
  .ln-tochka--melkaya { width: 10px; height: 10px; margin: -5px 0 0 -5px; }

  /* Табло крупнее вслед за всем остальным. */
  .ln-zaslonka { width: 28px; height: 38.08px; }
  .ln-zaslonka__verkh, .ln-zaslonka__niz, .ln-zaslonka__list {
    font-size: 22.85px; line-height: 38.08px; }
  .ln-zaslonka__niz span, .ln-zaslonka__list--niz span { margin-top: -19.04px; }
  .ln-imya { font-size: 10px; }

  /* Табло выше, значит и кнопка поднимается: число одно, и живёт оно здесь. */
  .ln { --niz-schyota: 90px; }
  .ln-niz { border-radius: var(--r-md) var(--r-md) 0 0; }
}

/*
 * БОЛЬШИЕ МОНИТОРЫ. Владелец: «на десктопе нету адаптации к большим
 * мониторам». На 2500 пикселях колонка в 880 оставляла по семьсот пустоты с
 * каждой стороны, и линия читалась ниткой посреди поля.
 *
 * Растём ступенями, а не резиной. Резина здесь вредна дважды: линия делится
 * пополам, и на очень широком блок вырос бы в полосу текста в одну строку, а
 * длина руки и стойки числами из приложения, и они не тянутся вместе с
 * колонкой. Ступени берём те же, на которых каркас расширяет свою колонку
 * (1600 и 1900), чтобы линия росла вместе с остальной внутрянкой, а не сама по
 * себе.
 */
@media (min-width: 1600px) {
  /* `margin-inline: auto` повторяется вместе с шириной, а не берётся из
     правила выше: иначе однажды кто-нибудь перенесёт ступень отдельно, и
     колонка прилипнет к левому краю. Стенд центровки требует их парой. */
  .ln { max-width: 1040px; margin-inline: auto; }
  .ln-golova__tekst { font-size: 16.5px; }
  .ln-zametka { font-size: 15px; }
  .ln-nachalo__tekst { font-size: 16px; }
  .ln-data { font-size: 12px; }
  .ln-dannyiy__tekst { font-size: 12px; }
  .ln-blok--otkryit .ln-nutro { height: 184px; }
}

@media (min-width: 1900px) {
  .ln { max-width: 1180px; margin-inline: auto; --niz-schyota: 102px; }
  .ln-verkh__imya { font-size: 30px; line-height: 36px; }
  /* Табло ещё на шаг: на таком экране 28 читается мелко с расстояния. */
  .ln-zaslonka { width: 32px; height: 43.52px; }
  .ln-zaslonka__verkh, .ln-zaslonka__niz, .ln-zaslonka__list {
    font-size: 26.11px; line-height: 43.52px; }
  .ln-zaslonka__niz span, .ln-zaslonka__list--niz span { margin-top: -21.76px; }
  .ln-imya { font-size: 11px; }
  .ln-plyus { width: 56px; height: 56px; font-size: 30px; }
}

/* ==================== СЕКС-КУРСЫ ==================== */

/*
 * Витрина берёт ГОТОВУЮ плитку `.arts`/`.art`, ту же, что игры, «Мы» и тесты:
 * одна высота ячейки, четыре, пять, шесть и семь колонок на десктопе уже
 * описаны, своё свечение из `--ac`. Здесь только то, чего у плитки нет:
 * переключатель рядов, шторка курса и сама читалка.
 */

/*
 * ТРИ СТРОКИ НА ИМЯ КУРСА, а не две. Плитка даёт две, и этого хватает играм с
 * короткими названиями; названия курсов пришли с боя и доходят до двадцати
 * шести знаков: «БДСМ: Доминирование» обрывалось на «Доминирова», без
 * многоточия, то есть выглядело поломкой, а не сокращением.
 */
/*
 * ИМЯ КУРСА НАБИРАЕТСЯ МЕЛЬЧЕ И В ТРИ СТРОКИ, а не в две, как у игр.
 *
 * Названия игр коротки («Шашки», «Слоты»), названия курсов пришли с боя и
 * доходят до двадцати шести знаков. Кегль витрины игр на них не рассчитан:
 * «Тантрический» не влезает в ячейку целиком, и слово рвалось посреди себя.
 * Тринадцать пикселей это то, при котором самое длинное слово раздела
 * помещается в строку.
 *
 * Перенос всё равно оставлен, но как последняя мера: «Доминирование» шире
 * ячейки при любом разумном кегле, и без него оно уезжало за край без всякого
 * многоточия, то есть выглядело поломкой, а не сокращением.
 */
.kr-plitka .art__name { -webkit-line-clamp: 3;
  font-size: 13px; line-height: 17px;
  overflow-wrap: break-word; hyphens: auto; }
/* ----- переключатель рядов ----- */

/*
 * ТРИ КНОПКИ В ОДНУ СТРОКУ И НА УЗКОМ ТЕЛЕФОНЕ. На бою этот же переключатель
 * переносился на 360 пикселях, и владелец назвал это отдельно. Поэтому размер
 * букв уступает раньше, чем строка сломается: `nowrap` плюс две ступени.
 */
.kr-ryadyi { display: flex; gap: 4px; margin: 0 0 var(--s-5);
  padding: 4px; border-radius: 999px;
  background: var(--c-surface); border: 1px solid var(--c-border); }
.kr-ryad { flex: 1; padding: 9px 12px; border: 0; border-radius: 999px;
  background: transparent; color: var(--c-dim); cursor: pointer;
  font: 700 11px/1 var(--f-mono); letter-spacing: .16em; text-transform: uppercase;
  white-space: nowrap; transition: color .25s, background .25s; }
.kr-ryad:hover { color: var(--c-muted); }
.kr-ryad--vzyat { background: linear-gradient(135deg, var(--c-gold-light), var(--c-gold));
  color: #0A0A0A; }
@media (max-width: 560px) {
  .kr-ryad { padding: 9px 6px; font-size: 10px; letter-spacing: .04em; }
}
@media (max-width: 360px) {
  .kr-ryad { padding: 9px 4px; font-size: 9px; letter-spacing: .02em; }
}

/* ----- шторка курса ----- */

.kr-shtorka__verkh { display: block; width: 100%; height: auto;
  border-radius: var(--r-md); margin-bottom: var(--s-4); }
.kr-shtorka__lid { margin: 0 0 var(--s-4); color: var(--c-muted);
  font: 400 14px/1.55 var(--f-sans); }
.kr-metyi { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: var(--s-5); }
.kr-meta { padding: 5px 10px; border-radius: 999px;
  border: 1px solid var(--c-border); color: var(--c-muted);
  font: 600 11px/1 var(--f-mono); letter-spacing: .06em; }
.kr-razdel { margin-bottom: var(--s-3); }

/*
 * Программа курса. Восьмая строка это не украшение: стена, не говорящая, что за
 * ней, просит денег за кота в мешке.
 */
.kr-glavyi { list-style: none; margin: 0 0 var(--s-5); padding: 0;
  display: flex; flex-direction: column; gap: 2px; }
.kr-glava { display: flex; align-items: flex-start; gap: 10px;
  padding: 9px 0; border-bottom: 1px solid var(--c-border); }
.kr-glava:last-child { border-bottom: 0; }
.kr-glava__nomer { flex: none; width: 22px; height: 22px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--c-border); color: var(--c-dim);
  font: 600 11px/1 var(--f-mono); }
.kr-glava--darom .kr-glava__nomer {
  border-color: rgba(var(--ac), .55); color: rgb(var(--ac)); }
.kr-glava__telo { flex: 1; min-width: 0; display: flex; flex-wrap: wrap;
  align-items: baseline; gap: 4px 8px; }
.kr-glava__imya { color: var(--c-text); font: 500 13.5px/1.4 var(--f-sans); }
.kr-glava__metka { color: rgb(var(--ac));
  font: 700 9.5px/1 var(--f-mono); letter-spacing: .14em; text-transform: uppercase; }


/*
 * Хвост шторки курса прилипает к низу: программа это восемь строк, и на
 * телефоне кнопка уходила под край. Человек видел список без единого
 * действия и не знал, что оно есть ниже. Подложка непрозрачная, иначе под
 * кнопкой просвечивают уезжающие строки.
 */
.kr-shtorka .ts-khvost { position: sticky; bottom: calc(var(--s-4) * -1);
  margin-top: var(--s-5); padding-bottom: var(--s-4);
  background: var(--c-surface-high); }
/* ----- читалка ----- */

/*
 * КОЛОНКА ЧТЕНИЯ УЖЕ ЭКРАНА, и центрована. Строка длиной во весь монитор
 * нечитаема: глаз теряет начало следующей. Семьсот шестьдесят это около
 * девяноста знаков нашим кеглем, верхняя граница удобного чтения.
 *
 * `margin-inline: auto` тут не украшение: без него `max-width` липнет к левому
 * краю, и это у меня записано отдельным правилом после живой жалобы.
 */
.kr-kurs { max-width: 760px; margin-inline: auto; }

.kr-verkh { display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-3) 0 var(--s-2); }
.kr-verkh__imya { font: 500 17px/24px var(--f-serif); color: var(--c-text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.kr-oblozhka { position: relative; margin: 0 calc(var(--s-4) * -1) var(--s-6);
  overflow: hidden; }
.kr-oblozhka img { display: block; width: 100%; height: auto; }
/* Затемнение снизу: буквы поверх фотографии иначе не читаются ни на одной. */
.kr-oblozhka__niz { position: absolute; inset: auto 0 0 0;
  padding: var(--s-8) var(--s-4) var(--s-4);
  background: linear-gradient(to top, #0A0606 0%, rgba(10,6,6,.86) 45%, transparent 100%); }
.kr-oblozhka .kr-razdel { display: inline-block; margin: 0 0 6px;
  color: rgb(var(--ac));
  font: 700 10px/1 var(--f-mono); letter-spacing: .28em; text-transform: uppercase; }
.kr-zagolovok { margin: 0; color: var(--c-text);
  font: 500 24px/1.2 var(--f-serif); }
.kr-lid { margin: 6px 0 0; color: var(--c-muted); font: 400 14px/1.5 var(--f-sans); }
.kr-oblozhka .kr-metyi { margin: var(--s-3) 0 0; }

@media (min-width: 900px) {
  .kr-oblozhka { margin-inline: 0; border-radius: var(--r-md); }
  .kr-zagolovok { font-size: 30px; }
  .kr-verkh__imya { font-size: 20px; line-height: 26px; }
}

/* ----- текст главы ----- */

/*
 * ЭТО ЧУЖАЯ РАЗМЕТКА. Тело главы приходит с боя и несёт свои классы: `ch-head`,
 * `ch-img`, `sub-head`, `tip`, `takeaway`, `steps`. Переписывать их значило бы
 * править 270 файлов ради имён, поэтому они описываются здесь, но ТОЛЬКО внутри
 * `.kr-текст`: те же слова живут на боевых страницах со своими стилями, и
 * выпустить их в общий каркас значит столкнуть два раздела.
 */
.kr-tekst { color: var(--c-text); font: 400 15.5px/1.7 var(--f-sans); }
.kr-tekst p { margin: 0 0 var(--s-4); }
.kr-tekst strong { color: var(--c-text); font-weight: 700; }
.kr-tekst em { font-style: italic; }
.kr-tekst ul, .kr-tekst ol { margin: 0 0 var(--s-4); padding-left: 1.35em; }
.kr-tekst li { margin-bottom: 6px; }

.kr-tekst .ch-head { display: flex; align-items: center; gap: 12px;
  margin: var(--s-8) 0 var(--s-5); }
.kr-tekst .ch-head:first-child { margin-top: 0; }
.kr-tekst .ch-num { flex: none; width: 34px; height: 34px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid rgba(var(--ac), .45); color: rgb(var(--ac));
  font: 600 14px/1 var(--f-mono); }
.kr-tekst .ch-label { color: var(--c-dim);
  font: 700 9.5px/1 var(--f-mono); letter-spacing: .22em; text-transform: uppercase; }
.kr-tekst .ch-name { margin-top: 3px; color: var(--c-text);
  font: 500 19px/1.25 var(--f-serif); }
.kr-tekst .ch-img { display: block; width: 100%; height: auto;
  border-radius: var(--r-md); margin: 0 0 var(--s-5); }
.kr-tekst .sub-head { margin: var(--s-6) 0 var(--s-3); color: var(--c-text);
  font: 600 15px/1.35 var(--f-sans); }

/*
 * Врезки. Цвет у них РАЗНЫЙ по смыслу: совет цветом курса, предупреждение
 * тревожным. Одинаковые врезки читаются как одна, и предупреждение теряется.
 */
.kr-tekst .tip, .kr-tekst .warn, .kr-tekst .takeaway, .kr-tekst .steps {
  /* display и color ГАСЯТ НАСЛЕДСТВО: `.warn` в каркасе это окно удаления,
     у него flex и тревожный цвет, и без этих двух строк текст врезки
     краснеет целиком, а подпись уезжает отдельной колонкой. */
  display: block; color: var(--c-text);
  margin: 0 0 var(--s-5); padding: var(--s-4);
  border-radius: var(--r-md); border: 1px solid var(--c-border);
  background: var(--c-surface); font-size: 14.5px; }
.kr-tekst .tip { border-left: 2px solid rgb(var(--ac)); }
.kr-tekst .warn { border-left: 2px solid var(--c-rose); }
.kr-tekst .takeaway { border-left: 2px solid var(--c-gold); }
/* Маркерам списка нужен свой отступ: шестнадцати пикселей им мало, и цифры
   вылезали за рамку врезки. */
.kr-tekst .steps { padding-left: calc(var(--s-4) + 1.5em); }
.kr-tekst .steps li::marker { color: var(--c-muted); }
/* Подпись врезки идёт В СТРОКУ, как на бою: капитель поперёк абзаца рвала
   фразу пополам и на узком экране переносилась на две строки. */
.kr-tekst .tip strong:first-child { color: rgb(var(--ac)); }
.kr-tekst .warn strong:first-child { color: var(--c-rose); }
.kr-tekst .takeaway-label { display: block; margin-bottom: 6px; color: var(--c-gold);
  font: 700 10px/1 var(--f-mono); letter-spacing: .18em; text-transform: uppercase; }

/*
 * ССЫЛКИ НАШИМИ ТОКЕНАМИ. Внутри глав их три вида на все 270 страниц, и две из
 * трёх уже переписаны на внутренние маршруты. Подчёркивание не сплошное, а
 * подложкой: сплошное на 15 пикселях режет строку пополам.
 */
.kr-ssyilka, .kr-tekst a { color: rgb(var(--ac)); text-decoration: none;
  background-image: linear-gradient(rgba(var(--ac), .45), rgba(var(--ac), .45));
  background-size: 100% 1px; background-repeat: no-repeat; background-position: 0 100%;
  transition: background-size .2s, color .2s; }
.kr-ssyilka:hover, .kr-tekst a:hover { color: var(--c-text);
  background-image: linear-gradient(rgba(var(--ac), .9), rgba(var(--ac), .9)); }

/* ----- стена ----- */

/*
 * Стена стоит там, где кончается бесплатное, и говорит, что за ней. Верхний
 * край растворяется в тексте: обрыв встык читается как ошибка загрузки.
 */
.kr-stena { position: relative; margin-top: var(--s-6);
  padding: var(--s-7) var(--s-5) var(--s-6);
  border-radius: var(--r-md); border: 1px solid var(--c-border);
  background: var(--c-surface); }
.kr-stena::before { content: ''; position: absolute; left: 0; right: 0; top: -80px;
  height: 80px; pointer-events: none;
  background: linear-gradient(to bottom, transparent, var(--c-bg)); }
.kr-stena__imya { margin: 0 0 var(--s-3); color: var(--c-text);
  font: 500 20px/1.25 var(--f-serif); }
.kr-stena__tekst { margin: 0 0 var(--s-5); color: var(--c-muted);
  font: 400 14px/1.6 var(--f-sans); }
.kr-stena .btn { width: 100%; }
.kr-stena--mimo { text-align: center; }

@media (min-width: 900px) {
  .kr-tekst { font-size: 16.5px; }
  .kr-stena { padding: var(--s-8) var(--s-7) var(--s-7); }
}

/* ==================== ОЧИСТКА СВОИХ ДАННЫХ ==================== */
/*
 * Экран «Я» → «Очистить данные». Шесть строк с галочками, две кнопки внизу.
 *
 * СТРОКА ЭТО КНОПКА ЦЕЛИКОМ, а не галочка с подписью рядом. Попадать пальцем в
 * квадратик двенадцати пикселей на телефоне значит промахиваться, а промах
 * здесь стоит дорого: человек думает, что снял отметку, а она осталась.
 */
.wipe__zachem { margin: 0 0 var(--s-4); }

.wipe__obeshchanie {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin: 0 0 var(--s-5);
  padding: var(--s-3) var(--s-4);
  border-radius: var(--r-md);
  background: var(--c-surface-high);
  border: 1px solid var(--c-border);
  font: 400 var(--t-body-s) var(--f-sans);
  color: var(--c-text);
}
.wipe__obeshchanie .ico { color: var(--c-done); flex: 0 0 auto; }

.wipe__spisok { padding: 0; overflow: hidden; }

.wipe__stroka {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--c-border);
  padding: var(--s-4);
  cursor: pointer;
  color: inherit;
  font: inherit;
}
.wipe__stroka:last-child { border-bottom: 0; }
.wipe__stroka:disabled { cursor: default; opacity: .45; }

/*
 * Квадратик отмечен цветом ЗОЛОТА, а не красным. Красный тут был бы обещанием
 * беды раньше времени: отметка это ещё не удаление, страшное начинается на
 * кнопке внизу и в подтверждении.
 */
.wipe__galka {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  border-radius: 6px;
  border: 1.5px solid var(--c-border);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background var(--m-micro) var(--m-ease), border-color var(--m-micro) var(--m-ease);
}
.wipe__galka--da { background: var(--c-gold); border-color: var(--c-gold); }
.wipe__galka--da .ico { color: #1a1305; width: 14px; height: 14px; }

.wipe__znak { color: var(--c-muted); flex: 0 0 auto; }
.wipe__telo { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.wipe__imya { font: 500 var(--t-title-s) var(--f-sans); color: var(--c-text); }
.wipe__skoljko { font: 400 var(--t-body-s) var(--f-sans); color: var(--c-muted); }

/*
 * Кнопки СТОЛБИКОМ, а не в ряд: сквозное правило каркаса, и здесь у него есть
 * своя причина. «Очистить выбранное» и «удалить всё» рядом в строке отличались
 * бы только цветом, а промах между ними необратим.
 */
.wipe__niz {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  margin-top: var(--s-5);
}
/* Пара «13px/18px» годится только шортката ради: в свойстве font-size она не
   разбирается, и объявление умирало молча, оставляя мелкому тексту размер
   родителя. */
.wipe__melkim { margin: var(--s-6) 0 0; font: 400 var(--t-body-s) var(--f-sans); }

/* На широком экране список не растягиваем во всю доску: читать глазами. */
@media (min-width: 820px) {
  .wipe__spisok, .wipe__niz, .wipe__obeshchanie, .wipe__zachem, .wipe__melkim {
    max-width: 560px;
    margin-inline: auto;
  }
  .wipe__zachem, .wipe__obeshchanie { margin-inline: auto; }
}

/* ---------- кинк: отбор совпадений по категориям ---------- */

/*
 * Строка отбора, одна на любое число категорий.
 *
 * Высота у неё как у надзаголовка, поэтому она не спорит с карточкой
 * совпадений, над которой стоит. Ряд подписанных чипсов на её месте занял бы
 * две строки на телефоне при девяти категориях и три при двенадцати, то есть
 * фильтр оказался бы выше того куска списка, который фильтрует.
 */
.kk-otbor { display: flex; align-items: center; gap: var(--s-2);
  margin: var(--s-5) 0 var(--s-2); }
.kk-otbor__knopka { flex: 1; min-width: 0; display: flex; align-items: baseline;
  gap: var(--s-2); background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--c-text); text-align: left; }
.kk-otbor__chto { flex: none; }
.kk-otbor__znach { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; font: 600 var(--t-body-s) var(--f-sans); color: var(--c-text); }
.kk-otbor__shevron { flex: none; width: 7px; height: 7px; align-self: center;
  border-right: 1.5px solid var(--c-faint); border-bottom: 1.5px solid var(--c-faint);
  transform: rotate(45deg) translate(-1px, -1px); }
.kk-otbor__sbros { position: relative; flex: none; width: 26px; height: 26px;
  border: 0; border-radius: var(--r-pill); background: var(--c-surface-alt);
  cursor: pointer; }
.kk-otbor__sbros span::before, .kk-otbor__sbros span::after {
  content: ''; position: absolute; left: 8px; top: 12px; width: 10px; height: 1.5px;
  background: var(--c-faint); }
.kk-otbor__sbros span::after { transform: rotate(90deg); }
.kk-otbor__sbros:hover span::before, .kk-otbor__sbros:hover span::after {
  background: var(--c-text); }

/* Совпадения есть, но не в этих категориях. Говорим прямо и даём выход:
   без выхода экран выглядит сломанным, а виноват в этом фильтр. */
.kk-otbor-pusto { text-align: center; }
.kk-otbor-pusto__nota { margin: 0 0 var(--s-2); color: var(--c-faint);
  font: 400 var(--t-body-s) var(--f-sans); }

/* ---------- шторка отбора ---------- */

.kk-kat { display: flex; align-items: center; gap: var(--s-3); width: 100%;
  padding: var(--s-3) var(--s-2); background: none; border: 0;
  border-radius: var(--r-md); cursor: pointer; color: var(--c-text);
  text-align: left; }
.kk-kat:hover { background: var(--c-surface-alt); }
.kk-kat__imya { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; font: 500 var(--t-body-m) var(--f-sans); }
.kk-kat__schyot { flex: none; font: 600 var(--t-label-s) var(--f-sans);
  color: var(--c-faint); font-variant-numeric: tabular-nums; }
/*
 * Галочка пустая до выбора и залитая после: та же пара состояний, что у строк
 * совпадений на этом же экране. Один язык отметки на экран, а не два.
 */
.kk-kat__galka { position: relative; flex: none; width: 18px; height: 18px;
  border-radius: var(--r-pill); border: 1.5px solid var(--c-border); }
.kk-kat--da .kk-kat__galka { border-color: var(--c-gold); background: var(--c-gold); }
.kk-kat--da .kk-kat__galka::after { content: ''; position: absolute;
  left: 5px; top: 2px; width: 5px; height: 9px; border: solid var(--c-on-gold);
  border-width: 0 1.5px 1.5px 0; transform: rotate(45deg); }

.kk-kat-niz { display: flex; align-items: center; gap: var(--s-3);
  margin-top: var(--s-4); }
/* Тихая кнопка тем же приёмом, что «и ещё N» выше: класса для неё в каркасе
   нет, и заводить второй способ показать необязательное действие незачем. */
.kk-kat-vsyo { flex: none; background: none; border: 0; padding: var(--s-2) 0;
  cursor: pointer; color: var(--c-faint); font: 400 var(--t-body-s) var(--f-sans); }
.kk-kat-vsyo:hover { color: var(--c-text); }
.kk-kat-niz .btn { flex: 1; }
