/* ============================================================================
   Общие компоненты дизайн-системы.
   Здесь живёт то, что принадлежит НЕ странице, а системе: полотно страницы,
   атмосфера, обвязка сайта (хедер / мобильное меню) и примитивы словаря
   (docs/COMPONENTS.md): .button, .chip, .filter-chip, .badge--role.

   Порядок подключения на странице строго такой:
     waifuhunt-fonts.css → tokens.css → components.css → <страница>.css

   ⚠️ ДЕТОКС 2026-08-10. Страницы на дизайн-системе БОЛЬШЕ НЕ ГРУЗЯТ
   waifuhunt-theme.css / waifuhunt-archive.css / waifuhunt-motion.css.
   Пока грузили — легаси-тема красила ЛЮБОЙ `<header>`, `input`, `a`, `h1`
   через !important, и каждая страница держала при себе правила паритета;
   их набралось на пять файлов. Здесь этих правил больше нет: если что-то
   выглядит не так, чинить нужно САМО правило, а не важность.
   Легаси-тему по-прежнему грузят непереведённые страницы (admin, versions,
   dialogue, guild + четыре посадочные страницы оверлея) — файлы не удалять
   до этапа 4/6.

   ⚠️ `<main>` переведённой страницы несёт класс `wh-page` — на нём полотно
   (ширина, центровка, поля). Без него страница разъедется на всю ширину.
   Файл в реестре scripts/tokenized-css.txt — Stop hook гоняет stylelint.
   ============================================================================ */

/* ── Полотно страницы ──
   Раньше ширину и поля всем страницам задавал `main {…!important}` из
   waifuhunt-theme.css. Теперь их задаёт класс: полотно — решение дизайн-
   системы, а не побочный эффект легаси-темы. Полноширинные страницы
   (главная) перебивают это своим правилом без всякой войны важностей. */
.wh-page {
  width: min(100%, 1240px);
  margin-inline: auto;
  padding: clamp(var(--s-8), 5vw, var(--s-20)) var(--s-5) var(--s-20);
}

/* ── Атмосфера страницы ──
   Тёплый свет сверху — лампа над стойкой гильдии, тот же источник, что и на
   арте зала. Ставится на само полотно (а не на body::before), чтобы не
   зависеть от порядка загрузки и не спорить с фоном непереведённых страниц.
   ⚠️ Прежняя атмосфера темы (шесть парящих неоновых карт + аврора) снята
   намеренно: ВСЕ шесть переведённых страниц независимо друг от друга гасили
   ей прозрачность — от 0.4 до 0.1. Когда каждый потребитель приглушает
   элемент, элемент не нужен. Разметку карт больше не создаёт и
   js/waifuhunt-ui.js (см. createBackground). */
.wh-page::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: var(--z-behind);
  pointer-events: none;
  background:
    radial-gradient(120% 62% at 50% -12%, rgba(155, 106, 70, 0.16), transparent 62%),
    radial-gradient(80% 50% at 84% 108%, rgba(44, 3, 46, 0.34), transparent 60%);
}

/* ── Вскрытие блоков при прокрутке ──
   Класс вешает js/waifuhunt-ui.js, `is-visible` приходит от
   IntersectionObserver. ⚠️ Скрытая фаза ОБЯЗАНА само-истекать: в Яндекс.
   Браузере на Android в энергосбережении rAF не отдаёт кадр, и без страховки
   блок навсегда остаётся невидимым, но кликабельным (баг входа 2026-07).
   Поэтому whRevealSafety — step-end анимация, которая через 1.2с показывает
   элемент сама, без единого кадра JS. */
@keyframes whRevealHold {
  0%   { opacity: 0; }
  99%  { opacity: 0; }
  100% { opacity: 1; }
}
@keyframes whRevealIn {
  from { opacity: 0; transform: translateY(var(--s-3)); }
  to   { opacity: 1; transform: none; }
}
body.wh-enhanced .wh-page .wh-reveal { animation: whRevealHold 1.2s step-end both; }
body.wh-enhanced .wh-page .wh-reveal.is-visible { animation: whRevealIn var(--dur-3) var(--ease-out) both; }

/* Типографский маркер: «эта строка набрана моноширинной». Не компонент —
   ставится вторым классом рядом с BEM-именем: class="log__when mono".
   Цифры — табличные, иначе столбец дат пляшет по ширине. */
.mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
}

/* ── Обвязка сайта: хедер, мобильное меню ──
   Разметка общая (SiteHeader/MobileNav), оформление — здесь и только здесь. */
.hidden { display: none; }

/* Подпись для экранного диктора и только для него. Не `display: none` — так её
   не прочитают вовсе; нужен именно вырез размером в пиксель. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: var(--s-0);
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.maintenance-owner-banner {
  padding: var(--s-2) var(--s-4);
  background: var(--warn-bg);
  border-bottom: 1px solid var(--warn-line);
  color: var(--warn);
  font-size: var(--t--1);
  text-align: center;
}
.maintenance-owner-banner.hidden { display: none; }

/* Шапка — планка над стойкой: тёмный лак и латунная нить по нижней кромке,
   та же деталь, что по краю стойки и ящика картотеки в зале.
   Липкая: единственная точка ориентации игрока не должна уезжать вверх
   (инвариант 11 ТЗ). На липкость завязан `scroll-margin-top` заголовков в
   doc.css — если снимать sticky, править и там.
   ⚠️ z-index — из токен-шкалы. Легаси-тема ставила 4200 !important, и
   оверлей деталки новостей (--z-overlay = 50) не мог её накрыть. */
.site-header {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-4) clamp(var(--s-4), 4vw, var(--s-10));
  background: var(--bg-raised);
  border-bottom: 1px solid color-mix(in srgb, var(--brass) 52%, transparent);
}
.header-left { display: flex; align-items: center; gap: var(--s-3); }
.site-header .logo {
  font-family: var(--font-display);
  font-variation-settings: 'opsz' 24;
  font-size: var(--t-2);
  font-weight: var(--w-semi);
  line-height: var(--lh-snug);
  color: var(--ink);
  text-decoration: none;
}
.desktop-nav { display: flex; gap: var(--s-5); }
.desktop-nav .nav-link {
  font-family: var(--font-label);
  font-size: var(--t--2);
  font-weight: var(--w-semi);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--ink-3);
  text-decoration: none;
  padding: var(--s-2) 0;
  border-bottom: 2px solid transparent;
}
.desktop-nav .nav-link:hover { color: var(--ink); }
/* Активный раздел — латунное подчёркивание, тот же приём, что у вкладок
   разделов внутри страниц. `is-active` вешает js/waifuhunt-ui.js (клиентом),
   `aria-current` может стоять и в разметке — учитываем оба. */
.desktop-nav .nav-link[aria-current="page"],
.desktop-nav .nav-link.is-active {
  color: var(--accent-text);
  border-bottom-color: var(--accent);
}

.mobile-nav-btn {
  display: none;
  width: var(--s-10);
  height: var(--s-10);
  background: transparent;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-1);
  color: var(--ink-2);
  font-size: var(--t-1);
  cursor: pointer;
}

.mobile-nav-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  background: var(--bg-void);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-2) var(--ease-out);
}
.mobile-nav-overlay.show { opacity: 0.72; pointer-events: auto; }

.mobile-nav-panel {
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  z-index: var(--z-overlay);
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  width: min(320px, 86vw);
  padding: var(--s-5);
  background: var(--bg-raised);
  border-right: 1px solid var(--line);
  box-shadow: var(--sh-3);
  transform: translateX(-102%);
  /* visibility прячет закрытую панель от таба и скринридера;
     задержка = длительности выезда, чтобы не мигало при закрытии. */
  visibility: hidden;
  transition: transform var(--dur-2) var(--ease-out), visibility 0s var(--ease-out) var(--dur-2);
}
.mobile-nav-panel.open {
  transform: translateX(0);
  visibility: visible;
  transition: transform var(--dur-2) var(--ease-out), visibility 0s;
}
.mobile-nav-top { display: flex; align-items: center; justify-content: space-between; }
.mobile-nav-title {
  font-family: var(--font-label);
  font-size: var(--t--2);
  font-weight: var(--w-semi);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--ink-4);
}
.mobile-nav-close {
  width: var(--s-8);
  height: var(--s-8);
  display: grid;
  place-items: center;
  background: transparent;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-pill);
  color: var(--ink-2);
  font-size: var(--t-1);
  cursor: pointer;
}
.mobile-nav-links { display: grid; gap: var(--s-2); }
.mobile-nav-link {
  font-family: var(--font-label);
  font-size: var(--t--1);
  font-weight: var(--w-semi);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--ink-2);
  text-decoration: none;
  padding: var(--s-3);
  border: 1px solid var(--line-subtle);
  border-radius: var(--r-1);
}
.mobile-nav-link:hover { background: var(--bg-hover); color: var(--ink); }
body.mobile-nav-lock { overflow: hidden; }

@media (max-width: 760px) {
  .desktop-nav { display: none; }
  .mobile-nav-btn { display: grid; place-items: center; }
}

/* ── Ссылки ──
   Цвет ссылки приходит НАСЛЕДУЕМОЙ переменной `--link-ink`: её объявляет
   контейнер (бумага, гнездо, полка), а не каждая ссылка отдельно. У `color`
   тогда вообще нет войны специфичностей — новый вид ссылки чинится одной
   строкой на контейнере.
   ⚠️ Базовое правило намеренно завёрнуто в `:where()` — специфичность 0,0,0.
   Любое правило компонента или страницы перебивает его без важности. Раньше
   здесь стоял `a[class]:not()×4 … !important` — паритет с легаси-темой,
   которая красила ЛЮБУЮ ссылку золотом. Темы больше нет, паритета не нужно.
   Голые ссылки без класса (правовые документы, инвариант 14) теперь тоже
   попадают под правило — раньше `[class]` их пропускал. */
/* ⚠️ ОБА правила в `:where()` — и порядок между ними значим.
   Голое `a { color: inherit }` имеет специфичность 0,0,1 и МОЛЧА перебивает
   правило ниже: ссылки в документах теряли чернила и наследовали цвет абзаца.
   В `:where()` у обоих 0,0,0, побеждает то, что ниже, — а любое правило
   компонента (0,1,0 и выше) перебивает оба без всякой важности. */
:where(a) { color: inherit; }
:where(.wh-page a) {
  color: var(--link-ink, inherit);
  text-decoration: none;
  transition: color var(--dur-1) var(--ease-out);
}
:where(.wh-page a:hover) { color: var(--link-ink-hover, var(--link-ink, inherit)); }

/* ── Кнопка (канон словаря: .button, три размера) ──
   Форма принята парным сравнением 2026-08-09, вариант Б: скругление 10px,
   подпись Manrope обычным регистром, разрядки нет. Стрелка ставится в
   разметке только у основного действия и означает движение, а не украшение. */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  font-family: var(--font-body);
  font-weight: var(--w-semi);
  letter-spacing: 0;
  line-height: var(--lh-flat);
  border: 1px solid transparent;
  border-radius: var(--r-btn);
  cursor: pointer;
  user-select: none;
}
/* ⚠️ `display` из компонента перебивает `[hidden]` (правило UA слабее любого
   авторского) — кнопка со снятым `hidden` продолжала показываться. Гард нужен
   каждому компоненту, который задаёт display и переключается атрибутом. */
.button[hidden] { display: none; }
.button svg { width: 1em; height: 1em; fill: none; stroke: currentColor; stroke-width: 1.8; }
.button--lg { padding: var(--btn-lg-y) var(--btn-lg-x); font-size: var(--btn-lg-t); }
.button--md { padding: var(--btn-md-y) var(--btn-md-x); font-size: var(--btn-md-t); }
.button--sm { padding: var(--btn-sm-y) var(--btn-sm-x); font-size: var(--btn-sm-t); }

.button--primary {
  --link-ink: var(--accent-ink);
  --link-ink-hover: var(--accent-ink);
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}
.button--primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }

.button--ghost {
  --link-ink: var(--ink-2);
  --link-ink-hover: var(--ink);
  background: transparent;
  border-color: var(--line-strong);
  color: var(--ink-2);
}
.button--ghost:hover { background: var(--bg-hover); color: var(--ink); }

.button--danger {
  --link-ink: var(--danger);
  background: var(--danger-bg);
  border-color: var(--danger-line);
  color: var(--danger);
}
.button--danger:hover { border-color: var(--danger); }

/* Кнопка НА БУМАГЕ. Латунь рассчитана на тёмный фон, на светлом листе она
   выцветает — там основное действие берёт цвет чернил регистра. */
.button--paper {
  --link-ink: var(--leaf);
  --link-ink-hover: var(--leaf);
  background: var(--leaf-ink);
  border-color: var(--leaf-ink);
  color: var(--leaf);
}
.button--paper:hover { background: var(--bg-void); border-color: var(--bg-void); }

.button:active { transform: translateY(1px); }
.button:disabled,
.button[aria-busy="true"] {
  opacity: 0.55;
  cursor: not-allowed;
  transform: none;
}

/* ── Фильтры ленты: текстовые рубрики, не коробки ── */
.filter-chip,
.creator-mode {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  font-family: var(--font-label);
  font-size: var(--t--2);
  font-weight: var(--w-semi);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--ink-3);
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  border-radius: var(--r-0);
  padding: var(--s-1) 0;
  cursor: pointer;
  user-select: none;
}
@media (hover: hover) {
  .filter-chip:hover,
  .creator-mode:hover { color: var(--ink); }
}
.filter-chip.is-active,
.creator-mode.is-active {
  color: var(--accent-text);
  border-bottom-color: var(--accent);
}

/* ── Чип: рубрика набором, не коробкой ──
   Рамка осталась одному случаю — технической метке (версия, формат файла):
   там она читается штампом каталога, а не украшением. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  font-family: var(--font-label);
  font-size: var(--t--2);
  font-weight: var(--w-semi);
  letter-spacing: var(--ls-kicker);
  text-transform: uppercase;
  color: var(--ink-3);
  border: 0;
  border-radius: var(--r-0);
  padding: 0;
  background: transparent;
}

.chip--framed {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--ls-label);
  text-transform: none;
  color: var(--info);
  border: 1px solid var(--info-line);
  padding: var(--s-1) var(--s-2);
}

/* Бейдж роли — тоже набор: золотой/синий глиф + слово капителью, а не
   капсула: служебная метка не должна светить ярче имени автора. */
.badge--role {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  padding: var(--s-0);
  border: 0;
  border-radius: var(--r-0);
  background: transparent;
  box-shadow: none;
  font-family: var(--font-label);
  font-size: var(--t--2);
  font-weight: var(--w-semi);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  line-height: var(--lh-tight);
}
.badge--role svg { width: 11px; height: 11px; display: block; fill: currentColor; }
.badge--owner { color: var(--role-owner); }
.badge--moder { color: var(--role-moder); }

/* ── Жетон ранга ──
   Ранг выражается МАТЕРИАЛОМ оправы, а не цветом акцента (ТЗ §0.4). Металл
   рисуется бликом (--plate-*), объём даёт --sh-plate: без тени кружок
   читается цветной точкой, а не жетоном.
   Жетон оверлея аккаунта — другой предмет (армейский на цепочке) и живёт
   под своим корнем; пересечения имён больше нет. */
.guild-token {
  flex: none;
  display: grid;
  place-items: center;
  width: var(--s-8);
  height: var(--s-8);
  padding: var(--s-0);
  border-radius: var(--r-pill);
  box-shadow: var(--sh-plate);
  font-family: var(--font-label);
  /* Литера на 32-пиксельном жетоне: ниже --t-0 и легче --w-bold она тонет в
     блике металла — проверено на бронзе и платине, там перепад самый злой. */
  font-size: var(--t-0);
  font-weight: var(--w-bold);
  line-height: var(--lh-flat);
  color: var(--accent-ink);
  background-image: var(--plate-g);
}
.guild-token--g { background-image: var(--plate-g); color: var(--lamp); }
.guild-token--f { background-image: var(--plate-f); }
.guild-token--e { background-image: var(--plate-e); }
.guild-token--d { background-image: var(--plate-d); }
.guild-token--c { background-image: var(--plate-c); }
.guild-token--b { background-image: var(--plate-b); }
.guild-token--a { background-image: var(--plate-a); }
.guild-token--s { background-image: var(--plate-s); }
/* SS — ступень РЕДКОСТИ, а не ранга гильдии: жетона с такой литерой не
   выдают, но карточка ранга SS его показывает (буква редкости = ранг
   карточки). Литера длиннее одной — кружок ей узок, поэтому кегль ниже. */
.guild-token--ss { background-image: var(--plate-ss); font-size: var(--t--2); }
.guild-token--founder { background-image: var(--plate-ss); }
.guild-token--lg { width: var(--s-14); height: var(--s-14); font-size: var(--t-2); }

/* ── Бумага: предмет на поверхности, а не поле во всю страницу ──
   Светлое на странице — это ЛИСТ, который лежит на видимой поверхности.
   Прошлая «плита во всю полосу» читалась вставкой ровно потому, что лежала
   ни на чём: под светлым прямоугольником обязана быть видна поверхность,
   вокруг — поля, под ним — контактная тень.
   Классу нужен ВТОРОЙ класс с фоном и размерами (.leaf, .slip и т.п.):
   .paper даёт только фактуру и чернила.
   ⚠️ Четыре сигнала, одного шума мало: волокно (--fibre), пятна (--blots),
   потемнение по периметру и прогиб (--sh-leaf). */
.paper {
  position: relative;
  color: var(--leaf-ink);
}
/* ⚠️ Слои: ::before и ::after — ПОСЛЕДНИЕ в дереве, при равном z-index они
   рисуются поверх содержимого, и фактура выбеливает собственный текст.
   Поэтому фактура прибита к --z-base, а содержимое поднято выше. */
.paper::before,
.paper::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: var(--z-base);
  pointer-events: none;
}
/* Масштаб и сила шума — часть примитива, а не настройка страницы: в полную силу
   волокно читается доской, а не бумагой (поймано на записке Архивариуса). */
.paper::before {
  background-image: var(--fibre), var(--blots);
  background-size: 520px 520px, 900px 900px;
  mix-blend-mode: multiply;
  opacity: 0.3;
}
/* ⚠️ Волокно анизотропно, и плитка в 520px рассчитана на ШИРОКИЙ лист. На
   узком и высоком (обложка дела ~320px) она укладывается меньше одного раза
   по горизонтали и повторяется по вертикали — бумага читается брашированным
   металлом. Модификатор мельчит зерно, а не гасит фактуру: масштаб остаётся
   свойством примитива, страница его не настраивает. */
.paper--narrow::before {
  background-size: 240px 240px, 420px 420px;
  opacity: 0.24;
}
.paper::after {
  box-shadow: var(--sh-paper);
}
.paper > * { position: relative; z-index: var(--z-sticky); }
.paper h1, .paper h2, .paper h3 { color: var(--leaf-ink); }
.paper p, .paper li { color: var(--leaf-ink-2); }
/* Кикер и моношрифт на бумаге — чернила, а не латунь: на светлом листе
   акцентный металл выбивается из набора. */
.paper .section-label, .paper .kicker, .paper .mono { color: var(--leaf-ink-3); }
.paper { --link-ink: var(--leaf-ink); --link-ink-hover: var(--leaf-ink); }

/* ── Гнездо картотеки: пустое место под арт ──
   Не «серая плашка», а вырез в дереве: внутри темно, по периметру латунная
   нить, снизу светлая кромка. Подпись обязана говорить, ЧТО сюда придёт. */
.cell {
  position: relative;
  aspect-ratio: 3 / 4;
  overflow: hidden;
  background: linear-gradient(180deg, #120211, #1c0620);
  box-shadow: var(--sh-cell);
}
/* ⚠️ display:block обязателен: инлайновая картинка стоит на базовой линии
   строки, и под ней остаётся место под нижние выносные элементы — гнездо
   становится на 5–7px выше карты и обрезает её. Та же грабля уже ловилась на
   кнопке с иконкой. */
.cell > img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: 50% 12%; }
/* ⚠️ Внутренняя тень гнезда рисуется ПОД содержимым, поэтому вставленный
   портрет закрывал её целиком — и вырез в дереве превращался в плитку.
   Кромку кладём отдельным слоем поверх карты. */
.cell::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  box-shadow: var(--sh-cell);
}
.cell__empty {
  display: grid;
  place-content: center;
  justify-items: center;
  gap: var(--s-3);
  height: 100%;
  padding: var(--s-4);
  text-align: center;
}
/* Печать приходит и файлом (<img>), и инлайновым <svg> из src/icons —
   стилизуем оба, иначе гнездо пустеет в одном из двух случаев. */
.cell__empty img { width: 34px; height: 34px; opacity: 0.34; filter: invert(1) sepia(1) saturate(3) hue-rotate(-12deg); }
.cell__empty svg { width: 34px; height: 34px; fill: var(--brass); opacity: 0.5; }
.cell__empty span {
  font-family: var(--font-mono);
  font-size: var(--t--2);
  color: var(--lamp-4);
  letter-spacing: 0;
  line-height: var(--lh-snug);
}

/* ── Латунная табличка: на ней написано, что внутри ящика ── */
.plate-label {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-2) var(--s-5);
  background-image: linear-gradient(180deg, #d8b184, #a97c4c 55%, #7d5a2c);
  box-shadow: var(--sh-brass);
  color: var(--accent-ink);
  font-family: var(--font-label);
  font-size: var(--t--2);
  font-weight: var(--w-semi);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  line-height: var(--lh-flat);
}
