/* Слой главной страницы.

   Правила, применимые ТОЛЬКО к главной: их классы не встречаются ни на
   одной из десяти внутренних страниц. Раньше лежали в `site.css`, и
   каждая внутренняя страница тянула их зря. Подключается ключом `css`
   из `site.config.json` только для раскладки `landing`.

   ДВА ПРАВИЛА, КУПЛЕННЫЕ ОШИБКАМИ ПРИ ПЕРЕНОСЕ.

   1. Файл грузится ПОСЛЕ `site.css`. Первая попытка вынесла только
      правила верхнего уровня, оставив их `@media`-переопределения
      раньше — приоритет перевернулся, мобильная главная выросла на 3370
      пикселей. Правила переносятся ВМЕСТЕ с обёртками и в исходном
      порядке.
   2. Класс, упомянутый в ТЕКСТЕ комментария, попадал в разбор селектора
      (жадный шаблон подбирал комментарий вместе с ним). Из-за
      `.brand-logo` в пояснении правило смены темы осталось в `site.css`,
      пока его пара уехала сюда, и в тёмной теме показались обе картинки
      моста. Комментарии вырезаются из селектора перед разбором.

   Обе ошибки поймал `scripts/computed-check.mjs`, не глаза и не снимки:
   PNG этой страницы не воспроизводимы между прогонами.               */





.landing-main {
  box-sizing: border-box;
  width: 100%;
  min-width: 0;
  padding: 0;
  overflow-x: clip;
}




/* ---- homepage module ---- */

.index-homepage {
  --index-page: min(100% - 80px, 1200px);
  --index-tone-green-fg: var(--accent-text);
  --index-tone-green-soft: var(--accent-soft);
  --index-tone-amber-fg: var(--warning);
  --index-tone-amber-soft: var(--warning-soft);
  --index-tone-teal-fg: var(--accent-secondary);
  --index-tone-teal-soft: color-mix(in srgb, var(--index-tone-teal-fg) 18%, transparent);
  --index-tone-success-fg: var(--success);
  --index-tone-success-soft: var(--success-soft);
  /* Добавлены при порте раскладок с bridge-indexnow-static (W7a). Значения
     НАШИ: донор после миграции палитры стоит на другом слое, и тащить его
     цвета значило бы внести раскол Q-NET-04 внутрь одной страницы. */
  --index-tone-danger-fg: var(--danger);
  --index-tone-danger-soft: var(--danger-soft);
  --index-tone-muted-fg: var(--text-muted);
  /* Фон СНЯТ намеренно. Прежде статья красила непрозрачную заливку на всю
     страницу — 1440×7331 замером, — и накрывала фоновую систему целиком:
     сетку, пятна и зерно было видно ровно нигде. У WAF непрозрачных полос
     шире 600px нет вовсе, а те, что есть, идут через альфу 0.84.
     Холст красит `body`, атмосферу — слои `.site-bg` и `.site-noise`. */
  color: var(--text-primary);
  /* `clip`, а НЕ `hidden`, и это не косметика. `overflow: hidden` создаёт
     скролл-контейнер, и `animation-timeline: view()` цепляется к нему, а
     не к корню страницы. Контейнер не прокручивается — шкала мертва,
     раскрытие при прокрутке молча не работает: правило применено,
     `animation-name` и `animation-timeline` в computed-стилях стоят, а
     непролистанный блок показан с opacity 1. `clip` обрезает так же, но
     скролл-контейнера НЕ создаёт. Соседний `main.landing-main` уже стоит
     на `overflow: clip visible` — здесь просто не догнали.
     Поймано замером: минимальный пример работал, наша страница нет.

     `display: flow-root` рядом — вторая половина той же правки.
     `overflow: hidden` создавал ещё и блочный контекст, а `clip` его не
     создаёт: отступ ребёнка начал схлопываться наружу, и статья
     укоротилась на 80 пикселей. `flow-root` возвращает контекст, не
     создавая скролл-контейнера. Тоже поймано слепком computed-стилей. */
  display: flow-root;
  overflow: clip;
}




:root[data-theme="proof-light"] .index-homepage {
  --index-tone-teal-fg: var(--accent);
  --index-tone-teal-soft: var(--accent-soft);
}




/* Load-bearing reset: section rhythm comes from flex/grid gap, not element
   margins. Any margin added inside this module needs a two-class selector. */
.index-homepage h1,
.index-homepage h2,
.index-homepage h3,
.index-homepage p { margin: 0; }




.index-homepage h1,
.index-homepage h2,
.index-homepage h3 { letter-spacing: 0; }




.index-content-rail {
  max-width: 760px;
  /* auto, не 0: шапка секции центрируется своим правилом
     (--center → margin-inline: auto), а тело оставалось прижатым к
     левому краю — под центрированным заголовком это читалось как
     дефект (замечание владельца 2026-08-31). Панели механизма изъяна
     не показывали только потому, что сбегают из рельсы через
     max-width: none. */
  margin-inline: auto;
  width: 100%;
}




.index-section {
  width: var(--index-page);
  margin: 0 auto;
  padding: calc(var(--space-12) + var(--space-6)) 0;
  border-bottom: 1px solid var(--border-subtle);
}




.index-section-header { margin-bottom: calc(var(--space-8) + var(--space-2)); }



.index-section-header .index-kicker { margin-bottom: var(--space-3); }




.index-section-header--center {
  margin-inline: auto;
  text-align: center;
}




.index-section-header--center .index-copy { margin-inline: auto; }




.index-section h2 {
  margin-bottom: var(--space-5);
  color: var(--text-primary);
  font-size: clamp(28px, 3vw, 38px);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.015em;
}




.index-section h3 {
  color: var(--text-primary);
  font-size: var(--font-lg);
  font-weight: 600;
}




/* --accent only reaches 3.3:1 on the dark background at 12px; --accent-secondary
   is the lighter indigo in dark and resolves to --accent in light. */
.index-kicker {
  color: var(--accent-secondary);
  font-size: var(--font-xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}




.index-copy {
  max-width: 640px;
  color: var(--text-secondary);
  font-size: var(--font-md);
  line-height: 1.65;
}




/* ---- hero ---- */

.index-hero {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  padding: var(--space-6) var(--space-10) calc(var(--space-12) + var(--space-8));
  border-bottom: 1px solid var(--border-subtle);
  /* Полупрозрачно, чтобы под полосой читалась фоновая система. Приём
     перенят у WAF: их межсекционная полоса идёт через альфу 0.84. */
  background-color: color-mix(in srgb, var(--index-hero-bg, var(--bg-hero)) 84%, transparent);
  isolation: isolate;
}




/* Shared grain overlay — the hero and the closing CTA are the two "panel" surfaces
   that carry it. One rule, one data URI; both hosts set isolation + overflow. */
.index-noise::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E");
  background-size: 180px 180px;
  pointer-events: none;
}




.index-hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(400px, 0.85fr);
  align-items: center;
  gap: 0;
  width: var(--index-page);
  margin: 0 auto;
  padding-top: calc(var(--space-16) + var(--space-8));
  padding-bottom: var(--space-12);
}




/* The title is sized off THIS box, not the viewport — the text column is a grid
   fraction of the page rail, so vw units over-ask and wrap the h1. container-type
   makes 8.6cqi the authoritative measure; the vw declaration above it is the
   fallback for engines without container queries. */
.index-hero-content {
  position: relative;
  z-index: 2;
  container-type: inline-size;
  container-name: hero;
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  padding-inline-end: var(--space-8);
  animation: indexFadeUp 0.7s cubic-bezier(0.16, 1, 0.3, 1) both;
}




.index-title-line {
  display: block;
  white-space: nowrap;
  hyphens: none;
}




/* Акцентная строка заголовка красится БРЕНДОВЫМ акцентом, а не
   `--warning`. Прежде стоял он, то есть семантический цвет предупреждения
   в декоративной роли; у WAF на этот счёт правило прямое — цвета режимов
   не красят ничего, кроме режимов.

   У самого WAF в заголовке акцентного цвета нет вовсе: вторая строка
   приглушена тоном и весом. Здесь так нельзя — у нас выделена СРЕДНЯЯ
   строка, и она не отступление, а подлежащее фразы («The tags nobody / got
   around to writing / are the ones machines read»). Приглушить её значит
   прочитать фразу неверно. Поэтому выделение остаётся, но цветом системы. */
.index-title-accent { color: var(--accent-text); }




.index-actions-note {
  order: 5;
  flex-basis: 100%;
  color: var(--text-secondary);
  font-size: var(--font-sm);
}




.index-bridge-wrap {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 420px;
  animation: indexFadeUp 0.8s 0.15s cubic-bezier(0.16, 1, 0.3, 1) both;
}




.index-bridge-stage {
  position: relative;
  width: 100%;
  max-width: 760px;
  margin-inline-end: calc(var(--space-8) * -1);
}




.index-bridge-frame {
  position: relative;
  display: grid;
  place-items: center;
  overflow: hidden;
  width: min(100%, 760px);
  aspect-ratio: 1100 / 601;
  isolation: isolate;
  background-color: color-mix(in srgb, var(--index-hero-bg) 84%, transparent);
}




.index-title {
  max-width: none;
  color: var(--text-primary);
  /* Ставка кегля подобрана так, чтобы САМАЯ ДЛИННАЯ строка помещалась в
     коробку заголовка. Строки стоят `white-space: nowrap` (композиция из
     трёх строк — замысел, а не случай), поэтому не влезающая строка не
     переносится, а вытекает наружу. При 8.6cqi она вытекала при ЛЮБОЙ
     ширине — на 12%, от 36 до 80 пикселей: на широком экране это уходило
     в пустоту справа и не замечалось, на телефоне обрезалось краем.
     Замер: длина строки ≈ 13.07 × кегль, отсюда потолок ставки 7.65cqi;
     взято 7.5 с запасом на разницу шрифтовых движков.

     Перекалибровка 2026-08-31 (B-94): расчёт выше снят с IBM Plex, а
     28-го гарнитуру сменили на Inter — он шире, фактор 13.64 × кегль
     (замер: строка 673px при кегле 49.35). Потолок стал 100/13.64 =
     7.33cqi; взято 7.2 с тем же запасом. Нижняя граница 18px остаётся
     ниже ставки на 300px-окне (7.2% × 256 = 18.4) — порог не перебивает.

     Нижняя граница — 18px, и она тоже посчитана, а не выбрана. Сперва
     стояло 22px: расчёт вёлся по 360px, где коробка 296. На 320px
     коробка 256, ставка даёт 19.2px, и порог 22 перебивал её обратно:
     строка вылезала на 31px и обрезалась краем. Порог обязан быть НИЖЕ
     того, что ставка даёт на самом узком экране, иначе он сам и есть
     дефект. При 18px строка помещается вплоть до окна в 300px, то есть
     ниже любого живого устройства. */
  font-size: clamp(18px, 3.55vw, 58px);
  font-size: clamp(18px, 7.2cqi, 58px);
  font-weight: 600;
  line-height: 1.06;
  letter-spacing: -0.02em;
  text-wrap: balance;
}




.index-lead {
  max-width: 720px;
  color: var(--text-secondary);
  font-size: clamp(17px, 1.6vw, 20px);
  font-weight: var(--weight-regular);
  line-height: 1.55;
}




/* Hero artwork blend law — three coupled rules, do not touch one alone:
   1. mix-blend-mode lighten (dark) / darken (light)
   2. the PNGs ship pre-normalized; never re-encode or re-crop them
   3. the blend surface must be --index-hero-bg, confined by isolation: isolate */
.index-bridge-image {
  grid-area: 1 / 1;
  display: block;
  width: 100%;
  height: auto;
  max-height: min(48vw, 420px);
  object-fit: contain;
  object-position: center;
  pointer-events: none;
  user-select: none;
  /* Блендов больше нет: арт героя с 2026-09-02 — вырез с прозрачным
     фоном (ai-meta-tags из библиотеки сети), ему нечего вплавлять в
     подложку. lighten/darken были костылём растров со сплошным фоном. */
}




/* Theme swap mirrors the .brand-logo pattern — CSS-toggled, no JS src rewrite. */
.index-bridge-image-light { display: none; }



[data-theme="proof-light"] .index-bridge-image-dark { display: none; }



[data-theme="proof-light"] .index-bridge-image-light { display: block; }




/* ---- icon tiles ---- */

.index-icon-tile {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-md);
  background: var(--bg-panel-soft);
  border: 1px solid var(--border-subtle);
  color: var(--text-secondary);
  flex-shrink: 0;
}




.index-icon-tile svg { width: 20px; height: 20px; }




.index-icon-tile.tone-green {
  background: var(--index-tone-green-soft);
  border-color: color-mix(in srgb, var(--index-tone-green-fg) 28%, var(--border-subtle));
  color: var(--index-tone-green-fg);
}




.index-icon-tile.tone-amber {
  background: var(--index-tone-amber-soft);
  border-color: color-mix(in srgb, var(--index-tone-amber-fg) 32%, var(--border-subtle));
  color: var(--index-tone-amber-fg);
}




.index-icon-tile.tone-teal {
  background: var(--index-tone-teal-soft);
  border-color: color-mix(in srgb, var(--index-tone-teal-fg) 28%, var(--border-subtle));
  color: var(--index-tone-teal-fg);
}




.index-icon-tile.tone-success {
  background: var(--index-tone-success-soft);
  border-color: color-mix(in srgb, var(--index-tone-success-fg) 30%, var(--border-subtle));
  color: var(--index-tone-success-fg);
}




/* ---- capability grid ---- */

.index-parity-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-10) calc(var(--space-12) + var(--space-4));
  max-width: 980px;
  margin: 0 auto;
  padding: 0;
  list-style: none;
}




.index-parity-item {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  min-width: 0;
}




.index-parity-body {
  display: grid;
  gap: var(--space-1);
  min-width: 0;
  padding-top: 2px;
}




.index-parity-title {
  color: var(--text-primary);
  font-size: var(--font-md);
  font-weight: var(--weight-semibold);
  line-height: 1.3;
}




.index-parity-desc {
  color: var(--text-secondary);
  font-size: var(--font-sm);
  line-height: 1.55;
}




/* ---- claim strip — a lighter sibling of .index-flagship ---- */

.index-panel {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
  max-width: 860px;
  margin: 0 auto calc(var(--space-10) + var(--space-2));
  padding: var(--space-6);
  border: 1px solid color-mix(in srgb, var(--warning) 34%, var(--border-subtle));
  border-radius: var(--radius-xl);
  background: color-mix(in srgb, var(--warning-soft) 40%, var(--bg-panel-soft));
}




.index-panel-body { display: grid; gap: var(--space-2); min-width: 0; }




.index-panel-title {
  color: var(--text-primary);
  font-size: var(--font-lg);
  font-weight: 600;
  line-height: 1.3;
}




.index-panel-desc {
  color: var(--text-secondary);
  font-size: var(--font-sm);
  line-height: 1.6;
}




/* ---- FAQ ---- */

.index-faq-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-4);
}




.index-faq-item {
  display: grid;
  gap: var(--space-2);
  min-width: 0;
  padding: var(--space-6);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  background: var(--bg-panel-soft);
}




.index-faq-item h3 {
  color: var(--text-primary);
  font-size: var(--font-md);
  font-weight: 600;
  line-height: 1.3;
}




.index-faq-item p {
  color: var(--text-secondary);
  font-size: var(--font-sm);
  line-height: 1.6;
}




/* ---- final CTA ---- */

/* The hero's bookend: same surface colour, same grain, same accent wash — the
   old --bg-hero-on---bg pairing was a 2-value delta and read as an empty section. */
.index-final {
  position: relative;
  isolation: isolate;
  width: var(--index-page);
  margin: var(--space-16) auto var(--space-20);
  padding: calc(var(--space-16) + var(--space-4)) var(--space-12);
  border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--border-strong));
  border-radius: var(--radius-xl);
  /* Полупрозрачно, чтобы под полосой читалась фоновая система. Приём
     перенят у WAF: их межсекционная полоса идёт через альфу 0.84. */
  background-color: color-mix(in srgb, var(--index-hero-bg, var(--bg-hero)) 84%, transparent);
  background-image: radial-gradient(120% 110% at 50% 0%, var(--accent-soft), transparent 62%);
  text-align: center;
  overflow: hidden;
}




.index-final > .index-icon-tile {
  position: relative;
  z-index: 2;
  width: 56px;
  height: 56px;
  margin: 0 auto var(--space-5);
}




.index-final > .index-icon-tile svg { width: 28px; height: 28px; }




.index-homepage .index-final h2 {
  position: relative;
  z-index: 2;
  margin-bottom: var(--space-4);
  font-size: clamp(26px, 2.8vw, 38px);
  line-height: 1.1;
  letter-spacing: -0.015em;
}




.index-final p {
  position: relative;
  z-index: 2;
  max-width: 54ch;
  margin-inline: auto;
  color: var(--text-secondary);
  font-size: var(--font-md);
  line-height: 1.65;
}




.index-actions--center {
  position: relative;
  z-index: 2;
  justify-content: center;
  margin-top: var(--space-8);
}




/* Three coupled declarations: `.index-final p` caps every paragraph at 54ch and
   16px, which silently defeats the note's 100% flex-basis and keeps it on the
   button row. max-width + shrink restore the wrap; font-size restores the scale. */
.index-actions--center .index-actions-note {
  flex-shrink: 0;
  max-width: none;
  margin-top: var(--space-2);
  font-size: var(--font-sm);
  text-align: center;
}
@media (max-width: 1080px) {

  .index-homepage { --index-page: min(100% - 64px, 1180px); }




  .index-hero-grid {
    grid-template-columns: minmax(0, 1.25fr) minmax(300px, 0.75fr);
    padding-top: calc(var(--space-12) + var(--space-6));
  }




  .index-bridge-wrap { min-height: 360px; }



  .index-bridge-frame { overflow: visible; }



  .index-bridge-stage { margin-inline-end: 0; }
}
/* `, print` — не косметика. Лист A4 в CSS-пикселях 794, а этот перелом
   стоит на 760: тридцати четырёх пикселей не хватает, и на бумагу уходит
   десктопная раскладка, сжатая вдвое. На первом листе герой стоял узкой
   колонкой слева (`350px 300px` при листе в 794), правая половина пустая.
   Условие «или печать» добавляется к «или узкий экран»; на экране не
   меняется ничего. Находка чужого сеанса, B-64. */
@media (max-width: 760px), print {

  .index-homepage { --index-page: min(100% - 32px, 1180px); }




  .index-hero {
    min-height: auto;
    padding: var(--space-4) var(--space-4) var(--space-10);
  }




  .index-hero-grid {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-8);
    padding-top: var(--space-10);
    padding-bottom: 0;
  }




  .index-bridge-wrap {
    order: 2;
    min-height: 220px;
    margin-inline: calc(50% - 50vw);
  }




  .index-bridge-stage { max-width: none; }




  .index-bridge-image {
    width: 100%;
    max-height: 460px;
    max-width: 100%;
  }




  .index-hero-content { order: 1; padding-inline-end: 0; }




  .index-section { padding-block: var(--space-10); }




  .index-faq-grid { grid-template-columns: 1fr; }



  .index-parity-grid { grid-template-columns: 1fr; }




  .index-final { margin: var(--space-10) auto var(--space-12); padding: var(--space-8) var(--space-6); }
}



/* --- Дельта сайта ssl (в эталоне этого блока нет) --------------------- */

/* Заголовок навигационной плитки — ссылка, но красится как заголовок:
   акцентный цвет на восьми плитках подряд читается как список ссылок,
   а не как сетка разделов. Подчёркивание появляется на наведении. */
.index-parity-title a {
  color: inherit;
  text-decoration: none;
}


.index-parity-title a:hover {
  color: var(--accent-secondary);
  text-decoration: underline;
  text-underline-offset: 3px;
}


/* ═══ Появление при прокрутке ═════════════════════════════════════════

   На прокруточной шкале (`animation-timeline: view()`), а не на скрипте.
   Бюджет JS этого репозитория — footer year, переключатель темы и
   пре-пейнт чтение темы; вводить ради раскрытия `IntersectionObserver`
   означало бы его нарушить, а шкала делает то же и лучше: без слушателя
   на каждый кадр.

   ЧЕТЫРЕ ПРАВИЛА, БЕЗ КОТОРЫХ ЭТО ЛОМАЕТСЯ. Сняты со спеки Bridge WAF
   (`docs/design-system.md` §6) и подтверждены здесь замером.

   1. Анимируется СОДЕРЖИМОЕ контейнера, а не сам контейнер. Иначе сквозь
      прозрачную секцию проступает фон сетки, и пока блок не долистан,
      страница выглядит дырявой.
   2. Стартовое состояние задаётся ТОЛЬКО внутри `@supports`. Где браузер
      шкалы не знает, правила нет вовсе и блок виден сразу — деградация
      без потерь, а не «пусто до скролла».
   3. Это СКРАБ, а не одноразовый показ: блок, уходящий обратно под нижнюю
      кромку, снова гаснет. Цена отказа от скрипта, и она принята.
   4. На печати прокруточной шкалы НЕТ. Без `@media print` всё, до чего
      читатель не долистал, уходит в PDF прозрачным. Тем же лечится и
      снимок страницы целиком — см. `scripts/snap.mjs`.

   Про `prefers-reduced-motion` отдельного блока здесь нет намеренно:
   `site.css` уже гасит анимации глобально (`*, *::before, *::after
   { animation: none !important }`), и `!important` перебивает этот слой
   независимо от порядка файлов. Контент остаётся видимым, потому что
   стартовое состояние приходит только из заливки анимации.

   Проверено замером 2026-08-27, а не только рассуждением — поверхность
   опасная: если бы стартовая прозрачность стояла в обычном правиле,
   глушение анимации оставило бы текст невидимым насовсем. Главная,
   58 целей раскрытия:

       prefers-reduced-motion: no-preference   52 прозрачных, 63 анимации
       prefers-reduced-motion: reduce           0 прозрачных,  0 анимаций

   То есть при просьбе убрать движение видно всё и сразу.             */

@supports (animation-timeline: view()) {
  .index-section-header > *,
  .index-parity-item > *,
  .index-faq-item > *,
  .index-final > * {
    animation: indexFadeUp linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 40%;
  }
}

@media print {
  /* Гасятся ВСЕ раскрытия, а не четыре из шести. Прежний список пропускал
     `.index-hero-content` и `.index-bridge-wrap`, у которых раскрытие не
     прокруточное, а временное (`0.7s` и `0.8s` с задержкой). Печать
     снимает страницу с анимациями в начальном состоянии, то есть при
     `opacity: 0`, и картинка героя уходила на лист пустым белым полем в
     420 пикселей высотой. Заголовок при этом печатался — просто повезло с
     порядком, а не по замыслу.

     Найдено просмотром печатного листа, замером не ловилось:
     `getComputedStyle` в режиме печати отдаёт базовые значения, а не то,
     чем страница красится. */
  /* Картинка героя исчезала с листа целиком, и виновата была светлая
     палитра печати. Закон смешения: на тёмной теме `mix-blend-mode:
     lighten` поверх `--index-hero-bg`, на светлой — `darken`. Печать
     делает фон белым, а тема остаётся тёмной, и получается `lighten`
     поверх белого: «светлее белого» не бывает, лист остаётся белым, а на
     месте картинки — пустая полоса в 420 пикселей.

     Поэтому на печати берётся СВЕТЛЫЙ вариант растра и `darken`, как в
     светлой теме. Явно, а не через `data-theme`: атрибут в печати не
     меняется.

     Диагноз дался не сразу: три гипотезы — отложенная загрузка,
     раскодирование, раскрытие по прокрутке — оказались мимо. Замер
     показывал `display: block`, `opacity: 1`, размеры 760x420 и
     загруженный файл; элемент был на месте и не красил ничего. */
  .index-bridge-image-dark { display: none; }
  .index-bridge-image-light { display: block; }

  .index-hero-content,
  .index-bridge-wrap,
  .index-section-header > *,
  .index-parity-item > *,
  .index-faq-item > *,
  .index-final > * {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
}



/* ═══ Раскладки секций, портированные с bridge-indexnow-static ════════

   Сборщик умеет четырнадцать раскладок, но разметка без CSS — это не
   раскладка. На этом сайте описаны стилями были ЧЕТЫРЕ: `cards`, `tiles`,
   `panel`, `hubs`. Попытка поставить `mech` дала секцию с иконкой во весь
   экран — и ни один машинный чекер этого не заметил: переменные
   разрешимы, скобки целы, правил для класса просто нет (`B-53`).

   Здесь структура шести недостающих: `mech`, `verdict`, `tiers`,
   `ledger`, `fit`, `triad`. Донор использует десять раскладок и написал
   им стили; повторять эту работу с нуля незачем.

   ЧТО ИМЕННО ПЕРЕНЕСЕНО: раскладка, отступы, сетки, размеры. НЕ цвета.
   Все `var(--…)` разрешаются нашими токенами; двух недостающих
   (`--index-tone-danger-fg`, `--index-tone-muted-fg`) заведены выше на
   НАШИ значения. Донор после миграции палитры стоит на другом слое, и
   смешивать их значило бы внести раскол Q-NET-04 внутрь одной страницы.

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

.index-verdict {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-5);
  max-width: 900px;
  padding: var(--space-6) var(--space-6) var(--space-6) var(--space-5);
  border: 1px solid color-mix(in srgb, var(--warning) 30%, var(--border-subtle));
  border-inline-start: 3px solid var(--warning);
  border-radius: var(--radius-lg);
  background: var(--bg-elevated);
  box-shadow: var(--shadow-sm);
}
.index-verdict-mark {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-md);
  background: var(--index-tone-amber-soft);
  color: var(--index-tone-amber-fg);
}
.index-verdict-mark svg { width: 22px; height: 22px; }
.index-verdict-body { display: grid; gap: var(--space-4); min-width: 0; }
/* Вердикт крупнее основного текста, но мельче H2: он ответ, а не заголовок. */
.index-verdict-claim {
  color: var(--text-primary);
  font-size: clamp(19px, 2vw, 24px);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -0.01em;
  text-wrap: balance;
}
.index-verdict-note {
  max-width: 62ch;
  color: var(--text-secondary);
  font-size: var(--font-md);
  line-height: 1.65;
}
/* Дословная формулировка области действия. Канон требует её почти слово в
   слово, поэтому она подана как цитата из документа, а не как обычный абзац. */
.index-ledger-statement {
  padding: var(--space-5) var(--space-6);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  background: var(--bg-panel-soft);
  color: var(--text-primary);
  font-size: var(--font-md);
  line-height: 1.6;
}
.index-ledger-note,
.index-chain-close,
.index-mech-note {
  max-width: none;
  color: var(--text-secondary);
  font-size: var(--font-md);
  line-height: 1.65;
}
/* ---- ступени участия ---- */

.index-tier-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  gap: var(--space-5);
  max-width: none;
  margin: 0;
  padding: 0;
  list-style: none;
}
.index-tier {
  display: grid;
  gap: var(--space-3);
  padding: var(--space-6);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  background: var(--bg-elevated);
}
.index-tier-icon {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: var(--radius-md);
  background: var(--index-tone-teal-soft);
  color: var(--index-tone-teal-fg);
}
.index-tier.tone-green .index-tier-icon {
  background: var(--index-tone-green-soft);
  color: var(--index-tone-green-fg);
}
.index-tier-icon svg { width: 20px; height: 20px; }
/* Ярлык ступени — моно: это статус в системе, а не фраза автора. */
.index-tier-label {
  color: var(--index-tone-teal-fg);
  font-size: var(--font-xs);
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.index-tier.tone-green .index-tier-label { color: var(--index-tone-green-fg); }
.index-tier-title {
  color: var(--text-primary);
  font-size: var(--font-xl);
  font-weight: 600;
  line-height: 1.25;
}
.index-tier-desc {
  color: var(--text-secondary);
  font-size: var(--font-md);
  line-height: 1.6;
}
/* ---- кому подходит ---- */

.index-fit-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
  gap: var(--space-5);
  max-width: none;
  margin: 0;
  padding: 0;
  list-style: none;
}
/* Верхняя черта тоном — единственная разметка, отличающая «да» от «нет».
   Смысл при этом несёт сам текст заголовка, цвет только подтверждает: цвет
   в одиночку значение нести не должен. */
.index-fit {
  display: grid;
  gap: var(--space-3);
  align-content: start;
  padding: var(--space-6);
  border: 1px solid var(--border-subtle);
  border-block-start: 3px solid var(--index-tone-muted-fg);
  border-radius: var(--radius-lg);
  background: var(--bg-elevated);
}
.index-fit--success { border-block-start-color: var(--index-tone-success-fg); }
.index-fit--danger { border-block-start-color: var(--index-tone-danger-fg); }
.index-fit-title {
  color: var(--text-primary);
  font-size: var(--font-lg);
  font-weight: 600;
  line-height: 1.3;
}
.index-fit-desc {
  color: var(--text-secondary);
  font-size: var(--font-md);
  line-height: 1.6;
}
/* ---- механизм: файл ключа ---- */

/* `margin-inline: auto` — блок был уже секции и прижимался влево, а панель
   тянулась по рельсу: правая половина оставалась пустой. Видно только на
   странице целиком, кусками секция читалась нормально. */
.index-mech { display: grid; gap: var(--space-5); max-width: 900px; margin-inline: auto; }
.index-mech-panel {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-5);
  align-items: start;
  padding: var(--space-6);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  background: var(--bg-panel-soft);
}
.index-mech-icon {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-md);
  background: var(--index-tone-teal-soft);
  color: var(--index-tone-teal-fg);
}
.index-mech-icon svg { width: 22px; height: 22px; }
.index-mech-core {
  color: var(--text-primary);
  font-size: var(--font-lg);
  line-height: 1.6;
}
/* Путь к файлу — единственный зримый артефакт механизма; он и должен быть
   виден как артефакт, а не как слово в предложении. */
.index-mech-core code {
  padding: 2px 7px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--bg-elevated);
  color: var(--index-tone-teal-fg);
  font-size: 0.92em;
  white-space: nowrap;
}
/* `, print` — не косметика. Лист A4 в CSS-пикселях 794, а этот перелом
   стоит на 760: тридцати четырёх пикселей не хватает, и на бумагу уходит
   десктопная раскладка, сжатая вдвое. На первом листе герой стоял узкой
   колонкой слева (`350px 300px` при листе в 794), правая половина пустая.
   Условие «или печать» добавляется к «или узкий экран»; на экране не
   меняется ничего. Находка чужого сеанса, B-64. */
@media (max-width: 760px), print {
.index-chain-list,
  .index-tier-grid,
  .index-fit-grid { grid-template-columns: 1fr; }
.index-verdict {
    grid-template-columns: 1fr;
    gap: var(--space-4);
    padding: var(--space-5);
  }
.index-mech-panel {
    grid-template-columns: 1fr;
    gap: var(--space-4);
    padding: var(--space-5);
  }
  /* На широком экране 18px против 16 — акцент ведущей фразы. В колонке
     390px тот же перепад делает её доминирующей: абзац переносится вдвое
     чаще соседних и читается как ошибка размера. Видно только на
     мобильном полотне целиком. */
  .index-mech-core { font-size: var(--font-md); }
.index-ledger-statement { padding: var(--space-4) var(--space-5); }
}
@media (max-width: 480px) {
.index-mech-core code { white-space: normal; overflow-wrap: anywhere; }
.index-tier,
  .index-fit,
  .index-step { padding: var(--space-5); }
}
/* Внутри правой колонки читаемая мера уже не нужна: колонка сама её задаёт. */
.index-section--split .index-verdict,
.index-section--split .index-ledger,
.index-section--split .index-mech,
.index-section--split .index-sequence { max-width: none; }
/* Схеме нужна вся ширина колонки, а тексту — нет: длинная строка не читается.
   Поэтому мера остаётся только у абзацев. */
.index-section--split .index-verdict-note,
.index-section--split .index-ledger-note,
.index-section--split .index-chain-close,
.index-section--split .index-mech-note { max-width: 68ch; }
/* ---- плотность карточек ---- */

/* Иконка висела отдельно от заголовка на высоту строки: сетка раздавала
   одинаковый зазор всем детям, включая её. */
.index-tier { gap: var(--space-2); }
.index-tier-icon { margin-bottom: var(--space-2); }
@media (min-width: 1081px) {
.index-fit-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.index-fit-grid > * { min-width: 0; }
}
/* ---- три вопроса ---- */

.index-triad {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
  gap: 0;
  margin: 0;
  padding: 0;
  list-style: none;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  background: var(--bg-elevated);
  overflow: hidden;
}
.td-subject {
  color: var(--text-primary);
  font-size: var(--font-lg);
  font-weight: 600;
}


/* ═══ Переключатель состояний страницы ════════════════════════════════

   Единственный интерактив главной. Переключение делает CSS через
   `:checked` — без JavaScript работает полностью. Бюджет скриптов этого
   репозитория: год в подвале, переключатель темы и пре-пейнт чтение
   темы. Тратить его на то, что умеет разметка, нечем оправдать.

   Панель показывает НЕ интерфейс кабинета, а его результат: какие теги
   у страницы есть, каких нет и какие появились. Мокапы панели — приём
   соседнего домена, и повторять их здесь значило бы строить тот же сайт
   второй раз.

   Моноширинного шрифта тут нет намеренно. Решение владельца от
   2026-08-25 по спутнику SSL: «у людей возникает иллюзия, что это очень
   сложный технический проект». Для маркетологов и агентств — персоны
   этого домена — правило важнее, чем для инженеров эксплуатации.
   Строка узнаётся подложкой и весом, а не костюмом гарнитуры.

   Цветовых ролей две, как задано §4.2 плана: пробел и заполненное.
   «Уже было» не красится вовсе — это нейтральный текст.               */

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.index-demo {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-2) var(--space-2);
  max-width: var(--content-default);
  margin-inline: auto;
  width: 100%;
}

.dm-tab {
  display: block;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--bg-panel);
  /* `--text-primary`, а не `--text-secondary`: вкладка — управляющий
     элемент, её читают, а не проглядывают. Замер W15 дал 4.28 в тёмной и
     3.11 в светлой при норме 4.5. Активность различается рамкой и
     подложкой, этого достаточно. */
  color: var(--text-primary);
  font-size: var(--font-sm);
  font-weight: var(--weight-medium);
  text-align: center;
  cursor: pointer;
  transition: var(--transition);
}

.dm-tab:hover { color: var(--text-primary); border-color: var(--border-strong); }

.dm-input:nth-of-type(1):checked ~ .dm-tab:nth-of-type(1),
.dm-input:nth-of-type(2):checked ~ .dm-tab:nth-of-type(2),
.dm-input:nth-of-type(3):checked ~ .dm-tab:nth-of-type(3) {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--text-primary);
}

/* Радио скрыта визуально, поэтому кольцо фокуса рисуется на подписи —
   иначе клавиатурный обход по переключателю не виден вовсе. */
.dm-input:focus-visible ~ .dm-tab:nth-of-type(1) { outline: 2px solid var(--accent); outline-offset: 2px; }
.dm-input:nth-of-type(2):focus-visible ~ .dm-tab:nth-of-type(2) { outline: 2px solid var(--accent); outline-offset: 2px; }
.dm-input:nth-of-type(3):focus-visible ~ .dm-tab:nth-of-type(3) { outline: 2px solid var(--accent); outline-offset: 2px; }

.dm-stack {
  grid-column: 1 / -1;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  background: var(--bg-panel);
  padding: var(--space-5);
}

.dm-panel { display: none; }

.dm-input:nth-of-type(1):checked ~ .dm-stack > .dm-panel:nth-child(1),
.dm-input:nth-of-type(2):checked ~ .dm-stack > .dm-panel:nth-child(2),
.dm-input:nth-of-type(3):checked ~ .dm-stack > .dm-panel:nth-child(3) { display: block; }

.dm-lines { display: grid; gap: var(--space-2); }

.dm-l {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-3);
  align-items: baseline;
  padding-inline-start: var(--space-5);
  position: relative;
  color: var(--text-secondary);
  font-size: var(--font-sm);
  line-height: var(--leading-snug);
}

.dm-l b { color: var(--text-primary); font-weight: var(--weight-semibold); }

/* Маркер состояния — псевдоэлементом. Отдельным узлом он стоил 600 байт
   на пятнадцати строках, а странице оставалось 1.1 КБ до потолка веса. */
.dm-l::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  top: 0.45em;
  width: 8px; height: 8px;
  border-radius: 50%;
  border: 1px solid var(--text-muted);
}

/* «Нет» приглушается формой маркера и весом имени, но НЕ цветом ниже
   нормы: замер W15 дал 3.77 при 4.5. Строку читают — это перечень того,
   чего у страницы не хватает, а не декоративный фон. */
.dm-gap { color: var(--text-secondary); }
.dm-gap b { color: var(--text-secondary); font-weight: var(--weight-regular); }
.dm-gap::before { border-style: dashed; background: none; }

.dm-new::before { border-color: var(--accent); background: var(--accent); }
.dm-new b { color: var(--accent-hover); }

.dm-have::before { background: var(--text-muted); }

.dm-note {
  margin: var(--space-4) 0 0;
  color: var(--text-muted);
  font-size: var(--font-xs);
}

@media (max-width: 640px) {
  .index-demo { grid-template-columns: 1fr; }
  .dm-stack { padding: var(--space-4); }
}


/* ═══ Схема карточки — только главная ════════════════════════════════

   Общий словарь `viz-` живёт в `site.css`: внутренние страницы `home.css`
   НЕ подключают, и правило оттуда на них молча не применяется. Ровно так
   на bridge-waf.com дважды разваливались шапка и аккордеон FAQ. Здесь —
   только то, что нужно одной схеме одной страницы.                     */

.viz-swatch   { fill: var(--accent); }
.viz-title    { fill: var(--text-primary); font-size: 13px; font-weight: var(--weight-semibold); }

/* Рисунок обязан читаться статикой: анимация подчёркивает то, что уже
   нарисовано, и её отключение ничего не отнимает. `transform-box:
   fill-box` обязателен — иначе `transform-origin` считается от вьюпорта
   и группа уезжает за кадр. */
.viz-src { transform-box: fill-box; transform-origin: center; }

/* Схема ПОКАЗЫВАЕТ СБОРКУ, а не подталкивает источники по очереди.

   Прежде здесь было `vizNudge`: три группы сдвигались вбок и бледнели.
   Движение было, а рассказа не было — сдвиг вбок ничего не сообщает о
   том, что карточка собирается из трёх источников. Приём взят у
   `bridge-waf-static`, где схемы главной демонстрируют механизм
   непрерывным циклом (`drip`, `level`, `gather`, `copy-block`), а не
   украшаются им. Владелец назвал нехватку этого 2026-08-28.

   Цикл 6.4s: источники загораются по очереди, линия дочерчивается к
   карточке, карточка вспыхивает, пауза. То есть глазом читается ровно то,
   что говорит подпись: цвет, гарнитура и снимок собираются в карточку.

   Начальное состояние — В ЗАЛИВКЕ анимации, не в базовом правиле. При
   `animation: none` (печать, просьба убрать движение) всё остаётся
   видимым и полностью непрозрачным: `stroke-dasharray` живёт только
   внутри `@keyframes`, иначе линия исчезла бы навсегда. */

@keyframes viz-src-lit {
  0%, 6%    { opacity: 0.42; }
  16%, 52%  { opacity: 1; }
  66%, 100% { opacity: 0.42; }
}

@keyframes viz-flow-run {
  0%, 40%   { stroke-dasharray: 52; stroke-dashoffset: 52; }
  62%, 100% { stroke-dasharray: 52; stroke-dashoffset: 0; }
}

@keyframes viz-out-lit {
  0%, 56%   { opacity: 0.5; }
  70%, 92%  { opacity: 1; }
  100%      { opacity: 0.5; }
}

.viz-card .viz-src { animation: viz-src-lit 6.4s var(--ease-io, ease-in-out) infinite; }
.viz-card .d1 { animation-delay: 0s; }
.viz-card .d2 { animation-delay: 0.45s; }
.viz-card .d3 { animation-delay: 0.9s; }

.viz-card .viz-flow { animation: viz-flow-run 6.4s linear infinite; }
.viz-card .viz-out  { animation: viz-out-lit 6.4s var(--ease-io, ease-in-out) infinite; }


/* Заполнение пробелов — вторая схема главной, и единственная, которая
   показывает саму функцию продукта в действии.

   Пустые ячейки загораются ПО ОЧЕРЕДИ и гаснут все разом; плотные — те,
   что автор написал сам, — не участвуют вовсе. Это не украшение: цикл
   проговаривает глазом единственную претензию, на которой держится
   домен. Если однажды в анимацию попадёт `.viz-filled`, схема начнёт
   иллюстрировать заявление, которое сайту запрещено делать.

   Пунктир и прозрачная заливка живут ТОЛЬКО в кадрах анимации в той
   части, где они меняются; базовое правило `.viz-gap` остаётся прежним,
   поэтому при `animation: none` схема читается как обычный рисунок
   пробелов. */

@keyframes viz-fill-in {
  0%, 6%    { fill: transparent; }
  16%, 76%  { fill: var(--accent-soft); stroke-dasharray: none; }
  86%, 100% { fill: transparent; }
}

.viz-fill .viz-gap { animation: viz-fill-in 7.2s var(--ease-io, ease-in-out) infinite; }
.viz-fill .f1 { animation-delay: 0.0s; }
.viz-fill .f2 { animation-delay: 0.18s; }
.viz-fill .f3 { animation-delay: 0.36s; }
.viz-fill .f4 { animation-delay: 0.54s; }
.viz-fill .f5 { animation-delay: 0.72s; }
.viz-fill .f6 { animation-delay: 0.9s; }
.viz-fill .f7 { animation-delay: 1.08s; }
.viz-fill .f8 { animation-delay: 1.26s; }
.viz-fill .f9 { animation-delay: 1.44s; }

/* Сетка карточек по ЧИСЛУ элементов, а не по ширине.

   `repeat(auto-fit, minmax(280px, 1fr))` при рельсе в 1200px раскладывает
   три трека и с двумя карточками оставляет треть пустой: карточки жмутся
   влево, а секция читается недоделанной. Видно только на странице
   целиком — по отдельности секция выглядит нормально.

   `:has` даёт точный счёт без правки сборщика и без класса-модификатора. */

.index-fit-grid:has(> :nth-child(2):last-child) {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.index-fit-grid:has(> :nth-child(4):last-child) {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

@media (max-width: 720px) {
  .index-fit-grid:has(> :nth-child(2):last-child),
  .index-fit-grid:has(> :nth-child(4):last-child) { grid-template-columns: 1fr; }
}

/* =============================== ПЕРЕХОДЫ МЕЖДУ СЕКЦИЯМИ — ПОРТ ИЗ WAF
   Перенесено дословно из bridge-waf-static/base.css §4 (волна W3 плана
   home-v4, 2026-08-31): «горизонт» между секциями и смена плотности
   фоновой сетки. Токены --line и --accent у сети общие.
   ------------------------------------------------------------------------- */
/* §4.1 «горизонт»: волосяная линия со световым разрывом, один раз на входе */
.rule-horizon {
  position: relative; height: 1px; border: 0; margin: 0;
  background: linear-gradient(90deg, transparent, var(--line) 18%, var(--line) 82%, transparent);
}
.rule-horizon::after {
  content: "";
  position: absolute; inset: -1px auto auto 0; height: 3px; width: 26%;
  background: linear-gradient(90deg, transparent, var(--accent), transparent);
  opacity: 0; filter: blur(2px);
}
@supports (animation-timeline: view()) {
  .rule-horizon::after {
    animation: rule-sweep linear both;
    animation-timeline: view();
    animation-range: entry 10% cover 30%;
  }
}
@keyframes rule-sweep {
  0%   { transform: translateX(-40%); opacity: 0; }
  25%  { opacity: 0.75; }
  100% { transform: translateX(300%); opacity: 0; }
}

/* §4.2 смена плотности сетки: плотный блок → воздух → плотный блок */
section[data-density] { position: relative; isolation: isolate; }
section[data-density="tight"] { --local-grid: 24px; --local-grid-a: 0.05; }
section[data-density="open"]  { --local-grid: 96px; --local-grid-a: 0.015; }
section[data-density]::before {
  content: "";
  position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background-image:
    linear-gradient(to right, rgba(255, 255, 255, var(--local-grid-a)) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(255, 255, 255, var(--local-grid-a)) 1px, transparent 1px);
  background-size: var(--local-grid) var(--local-grid);
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 22%, #000 78%, transparent);
  mask-image: linear-gradient(to bottom, transparent, #000 22%, #000 78%, transparent);
}
:root[data-theme="proof-light"] section[data-density]::before {
  background-image:
    linear-gradient(to right, rgba(0, 0, 0, var(--local-grid-a)) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(0, 0, 0, var(--local-grid-a)) 1px, transparent 1px);
}

/* ============================== СЕТКА ВСТЫК И ПЕРЕЧНИ — ПОРТ ИЗ WAF
   Перенесено дословно из bridge-waf-static/base.css (волна W4 плана
   home-v4, 2026-08-31): ячейки встык (.cells), перечень названного
   (.harms), полоса фактов (.facts), хвосты и заметки. Токены общие.
   ------------------------------------------------------------------------- */
/* ------------------------------------------------------- сетка встык (ядро) */

/* Приём донора: ячейки стыкуются без зазора, между ними одна волосяная линия,
   скруглений нет. Линия — фон контейнера, проступающий в gap: 1px. */
.cells {
  display: grid;
  gap: var(--hairline);
  background: var(--line);
  border: var(--hairline) solid var(--line);
}
.cells > * {
  background: var(--bg);
  display: flex;
  flex-direction: column;
  /* фон ячейки не участвует в раскрытии: иначе на её месте проступает
     фон-разделитель сетки и полоса читается как дыра */
  padding: var(--space-8);
  min-width: 0;
}
.cells-3 { grid-template-columns: repeat(3, 1fr); }
.cells-4 { grid-template-columns: repeat(4, 1fr); }
.cells-2 { grid-template-columns: repeat(2, 1fr); }

.cell-title {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-family: var(--font-sans);
  font-size: var(--fs-card);
  line-height: var(--lh-card);
  letter-spacing: var(--ls-card);
  font-weight: 400;
  color: var(--text);
}
.cell-title svg { flex: none; width: 18px; height: 18px; color: var(--accent); }
.cell-body {
  margin-top: var(--space-4);
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: var(--muted);
}
/* Хвост ячейки: целое предложение, а не машинная пометка. Моноширинный на
   12px тусклым читается как комментарий в коде и для связного текста не
   годится — отсюда отдельный класс рядом с .cell-note. */
/* Строка под кнопкой: возражение снимается там, где человек решает нажимать.
   Приём взят у bunny.net, где такая строка стоит под каждой кнопкой и
   усиливается от блока к блоку («No credit card required» → «…in seconds»
   → «…Cancel anytime»). У нас средний призыв стоял голым. */
/* Сетка названных угроз. Приём взят у qrator.net: там восемь иконок под
   «…to get protected from» — не таксономия, а перечень того, что случается
   с тобой. Классификация объясняет, перечень узнаётся. Половина имён ведёт
   на свою страницу базы знаний — у qrator такого нет, а у нас это ещё и
   вход вглубь. */
.harms {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: var(--hairline);
  margin: var(--space-8) 0 0;
  padding: 0;
  list-style: none;
  background: var(--line);
  border: var(--hairline) solid var(--line);
}
.harms li {
  background: var(--bg);
  padding: var(--space-4) var(--space-6);
  font: 400 var(--fs-card)/1.3 var(--font-sans);
  color: var(--text);
}
/* Цвет у всех один. Сначала ссылочные пункты были ярче безссылочных — и это
   читалось как «эти важнее», хотя разница лишь в том, что для них написана
   страница. Ссылку помечает подчёркивание, как везде в тексте сайта. */
.harms a { color: inherit; border-bottom: 1px solid var(--line); }
.harms li:hover { background: var(--panel); }
.harms a:hover { color: var(--accent-hover); border-bottom-color: var(--accent-hover); }

.cta-note {
  margin-top: var(--space-4);
  font-size: var(--fs-sm);
  line-height: 1.6;
  color: var(--muted);
}

.cell-tail {
  /* хвост прижат к низу ячейки: в сетке ячейки разной высоты, и без этого
     строки-указатели не встают в одну линию */
  margin-top: auto;
  padding-top: var(--space-6);
  font-size: var(--fs-sm);
  line-height: 1.6;
  color: var(--muted);
}
.cell-tail a { color: var(--text); border-bottom: 1px solid var(--line); }
.cell-tail a:hover { color: var(--accent-hover); border-bottom-color: var(--accent-hover); }

.facts { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--hairline);
  background: var(--line); border-block: var(--hairline) solid var(--line); }
.facts > div { background: var(--bg); padding: var(--space-6) var(--space-8); }
.facts dt { font: 400 var(--fs-label)/1 var(--font-sans); letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--dim); }
.facts dd { margin-top: var(--space-3); font: 400 var(--fs-card)/1.3 var(--font-sans);
  letter-spacing: var(--ls-card); color: var(--text); }

/* Локальная поправка к порту, не правка порта: у WAF перечень заполняет
   сетку целиком, у нас пунктов пять — последний («IndexNow, шаг после
   них») занимает всю нижнюю строку, иначе в сетке остаётся дыра. Смысл
   совпадает с формой: он и в тексте назван отдельной вещью после семейств. */
.harms { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.harms li:last-child:nth-child(odd) { grid-column: 1 / -1; }
@media (max-width: 640px) {
  .harms { grid-template-columns: minmax(0, 1fr); }
  .harms li:last-child:nth-child(odd) { grid-column: auto; }
}

/* Компоненты W4 занимают всю ширину секции, как у WAF их занимает wrap:
   рельса в 760 под центрированной шапкой оставляла сетку узкой полосой.
   На широком экране пять семейств встают одной строкой — дыр нет. */
.cells.index-content-rail,
.harms.index-content-rail,
.facts.index-content-rail,
.cta-note.index-content-rail { max-width: none; }
@media (min-width: 1081px) {
  .harms { grid-template-columns: repeat(5, minmax(0, 1fr)); }
  .harms li:last-child:nth-child(odd) { grid-column: auto; }
}

/* Отзывчивость порта: правило .cells — дословно из WAF base.css @760px;
   .facts у WAF без правила (их статусы короче колонок) — наша полоса
   на 390px вылетала на 223px, отсюда локальная строка рядом. */
@media (max-width: 760px) {
  .cells-3, .cells-4, .cells-2 { grid-template-columns: minmax(0, 1fr); }
  .facts { grid-template-columns: minmax(0, 1fr); }
}

/* ================================ ВСТАВКА-ПЕРЕДЫШКА — ПОРТ ИЗ WAF, УЖЕ
   Их .interstitial несёт текст на подложке поверх снимка и cover-кроп;
   условие владельца 2026-09-02 — ничем не перекрывать, показывать
   целиком. Оставлены их вертикальный ритм (margin-block) и роль; снят
   оверлей и кроп. Тема выбирает файл по образцу героя; скрытая тема
   с loading=lazy не грузится. */
.interstitial {
  width: var(--index-page);
  margin-inline: auto;
  margin-block: clamp(72px, 10vh, 140px) 0;
}
.interstitial img {
  display: block;
  width: 100%;
  height: auto;
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius-xl);
}
.interstitial-light { display: none; }
[data-theme="proof-light"] .interstitial-dark { display: none; }
[data-theme="proof-light"] .interstitial-light { display: block; }
@media print {
  .interstitial-dark { display: none; }
  .interstitial-light { display: block; }
}

/* 404 (W-IMG-4): заголовок страницы «не найдено» — типографика H2 секций,
   но это единственный H1 страницы. */
.nf-title {
  margin-bottom: var(--space-5);
  color: var(--text-primary);
  font-size: clamp(28px, 3vw, 38px);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.015em;
}
#not-found { border-bottom: 0; padding-top: calc(var(--space-16) + var(--space-8)); }
