/* Визуальный слой сайтов сети Bridge — общий для всех спутников.

   Перенесён из `bridge-dns-static/index.html` — эталона сети, по которому
   выглядят bridge-dns.com и bridge-cdn.com. Значения токенов побайтно
   совпадают с `bridge-cdn-design-system/tokens/tokens.css` (сверено), так
   что расхождения систем нет: это тот же токен-набор плюс паттерн-слой
   `index-*`, которого в дизайн-системе нет.

   Убраны блоки, специфичные для DNS: демо-виджет зоны, таймлайн
   распространения, showcase origin-контролей, схемы. Всё остальное —
   шапка, герой, секции, плитки возможностей, панель-выноска, шаги,
   сравнение, FAQ-сетка, финальная полоса, подвал — сохранено как есть.

   Правило дизайн-системы действует без исключений: ни одного цвета и
   отступа мимо токенов.

   ЭТОТ ФАЙЛ — ЕДИНСТВЕННЫЙ ИСТОЧНИК. Сайты получают копию; править надо
   здесь и переносить, иначе четыре копии разойдутся и каждый дефект
   придётся чинить четырежды (ради чего комплект и заведён).

   АКЦЕНТ, 2026-08-24. Был Tailwind-индиго (#6366f1 / #4f46e5) — ровно тот  unslop-ignore
   дефолт, который читается как «цвет никто не выбирал», и он никогда не
   совпадал со знаком: во всех логотипах сети один хроматический цвет,
   #47704c. Теперь акцент — этот знак. В светлой теме цвет знака взят
   дословно; в тёмной поднят до той же светлоты, что была у индиго
   (#538259), поэтому сменился только тон, а контрасты тёмной темы не
   изменились до второго знака. Полный разбор — README комплекта, «Акцент».

   ШРИФТ, 2026-08-24. Был Inter — вторая половина той же проблемы. Теперь
   IBM Plex Sans для текста и IBM Plex Mono для машинного: сайты сети
   состоят из сертификатов, кодов ошибок и команд, а моноширинный до сих
   пор был системным, то есть на каждой машине своим. */


:root {
  /* Линия схемы. Отдельный токен, а не `--border-subtle`: разделитель
     вёрстки обязан быть почти невидим, схема — читаема, и одним цветом
     обе задачи не тянутся. Объявлен в корне, потому что схемы есть и на
     внутренних страницах, а `.index-homepage` там нет. */
  --viz-line: color-mix(in srgb, var(--text-muted) 78%, transparent);

  --font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  /* Моноширинного НЕТ вовсе — решение владельца 2026-08-28, «моноширинный
     необходимо убрать». Убран не только из интерфейса, но и из кода: у WAF
     он оставался в одном месте, у нас не остался нигде.
     Цена названа и уплачена: единственный пример, державшийся на колонках
     (форма файла llms.txt), переписан таблицей — она читается любым
     шрифтом. Токен снят намеренно, чтобы его нечем было вернуть молча. */
  --font-xs: 12px;
  --font-sm: 13px;
  --font-base: 14px;
  --font-md: 16px;
  --font-lg: 18px;
  --font-xl: 22px;
  --font-2xl: 28px;
  --font-3xl: 34px;

  --leading-tight: 1.2;
  --leading-snug: 1.35;
  --leading-normal: 1.55;

  --weight-regular: 400;
  --weight-medium: 500;
  --weight-semibold: 600;

  --z-sticky: 40;

  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-10: 40px;
  --space-12: 48px;
  --space-16: 64px;
  --space-20: 80px;

  --content-default: 880px;

  --radius-sm: 6px;
  --radius-md: 8px;
  --radius-lg: 12px;
  --radius-xl: 16px;
  --radius-pill: 999px;

  --transition: 150ms cubic-bezier(0.4, 0, 0.2, 1);

  /* ─── Словарь дизайн-системы сети ────────────────────────────────────
     Имена и значения перенесены из `bridge-waf-static/tokens.css`. Там,
     где у нас уже есть свой токен того же смысла, стоит псевдоним, а не
     второе значение: источник правды должен быть один.

     Зачем вообще: компоненты сети (шапка, карточки, схемы) написаны на
     ЭТОМ словаре. Без него каждый перенос превращается в переписывание. */
  --font-sans: var(--font-family);
  --fs-display: clamp(38px, 6.4vw, 64px);
  --fs-h2: clamp(26px, 3.2vw, 32px);
  --fs-card: 20px;
  --fs-lead: clamp(17px, 1.6vw, 20px);
  --fs-body: 16px;
  --fs-sm: 14px;
  --fs-label: 12px;
  --lh-display: 1;
  --lh-h2: 1.2;
  --lh-card: 1.4;
  --lh-lead: 1.5;
  --lh-body: 1.6;
  --ls-display: -0.04em;
  --ls-card: -0.04em;
  --ls-lead: -0.01em;
  --ls-body: -0.01em;
  --space-24: 96px;
  --container: 1248px;
  --gutter: clamp(20px, 6.7vw, 96px);
  --radius: 4px;
  --hairline: 1px;
  --ease: cubic-bezier(0.44, 0, 0.56, 1);
  --t-fast: 0.3s var(--ease);
  --t-slow: 0.4s var(--ease);
  --ease-io: cubic-bezier(0.65, 0, 0.35, 1);
}



:root,
:root[data-theme="gruvbox-dark"] {
  --bg: #17171c;
  --bg-hero: #1c1c22;
  /* Была #282828 — земля gruvbox, доставшаяся вместе с именем темы. С
     переходом на индиго она выпала из семейства: у WAF все поверхности
     нейтральные (#17171c / #1c1c21 / #191c1a / #0e100f), а #282828 на
     полтона теплее и светлее всех. Взята приподнятая поверхность того же
     семейства. Побочно это чинит худшую точку всех вечерних замеров:
     подложка героя была тем фоном, на котором проваливался и акцент, и
     приглушённый текст. */
  --index-hero-bg: #1c1c21;
  --bg-elevated: #18181b;
  --bg-panel-soft: #1c1c21;
  /* Словарь поверхностей WAF. `--line` намеренно ПОЛУПРОЗРАЧНАЯ, а не
     наш непрозрачный `--border-subtle`: у них хайрлайны просвечивают
     подложку, и это часть узнаваемости. Оба токена живут рядом — старые
     правила читают свой, новые компоненты сети читают этот. */
  --text: var(--text-primary);
  --muted: var(--text-secondary);
  --dim: var(--text-muted);
  --panel: var(--bg-panel);
  --panel-2: var(--bg-elevated);
  --bg-raise: #0e100f;
  --line: rgba(255, 255, 255, 0.09);
  --line-soft: rgba(255, 255, 255, 0.035);
  --warn-soft: var(--warning-soft);
  --text-primary: #fafafa;
  --text-secondary: #a1a1aa;
  /* Разведён по темам 2026-08-27 (Q-NET-03, группа А, решение владельца).
     Одно значение на обе темы давало 3.05 на подложке героя в тёмной и
     4.07 в светлой при норме 4.5 — половина всех провалов контраста
     сайта. Сдвинута только светлота, тон прежний. */
  --text-muted: #8e8e95;
  --border-subtle: #1d1d20;
  --border-strong: #3f3f46;
  /* Группа Б Q-NET-03, решение владельца 2026-08-27. Акцент затемнён
     с #538259: подпись кнопки на нём давала 4.34 при норме 4.5, и
     светлее её сделать нельзя — даже чистый белый даёт 4.46. Проход
     достижим только затемнением заливки. */
  /* ─── Акцент: индиго сети ────────────────────────────────────────────
     Решение владельца 2026-08-27: сайт переходит на палитру WAF и SSL,
     то есть на акцент живого bridge-cdn.com. Зелёный «знак сети» был
     спутниковым выбором; после перехода мы расходимся по цвету с
     bridge-indexnow.com — цена принята владельцем, Q-NET-04 закрывается.

     Значения перенесены из `bridge-waf-static/tokens.css` вместе с
     доводом: заливка кнопки НЕ равна знаку. Пара #f6f6fe на #6366f1
     даёт 4.15 при норме 4.5, поэтому фон кнопки берётся отдельным
     токеном того же тона и насыщенности, светлота опущена до проходной —
     #5b5ef0, 4.54.

     `--accent-text` — наша добавка, которой у WAF нет, и она нужна:
     у нас есть подложка героя `--index-hero-bg` #282828, на которой знак
     #6366f1 текстом даёт 3.30. Роль текста берёт #818cf8 — 4.94. */
  --accent: #6366f1;
  --accent-hover: #818cf8;
  --accent-fill: #5b5ef0;
  --accent-fg: #f6f6fe;
  --accent-color: var(--accent-fg);
  --accent-text: #818cf8;
  --accent-secondary: #818cf8;
  --accent-soft: rgb(99 102 241 / 0.16);
  --on-accent: #04052f;
  --depth: rgba(56, 189, 248, 0.06);
  --fade: rgba(10, 11, 10, 0);
  --success: #34d399;
  --success-soft: rgb(52 211 153 / 0.16);
  --warning: #fbbf24;
  --warning-soft: rgb(251 191 36 / 0.16);
  --danger: #f87171;
  --control-bg: #131315;
  --control-hover: #27272a;
  --control-active: #3f3f46;
  --shadow-sm: 0 1px 2px rgb(0 0 0 / 0.4);
}



:root[data-theme="proof-light"] {
  --bg: #efeff0;
  --bg-hero: #fafafa;
  /* Была #f3eae1 — светлая земля gruvbox. Тёплый кремовый спорит с индиго;
     у WAF светлые поверхности нейтральны (#efeff0 / #fbfbfa / #ffffff /
     #f4f4f4). Взята приподнятая того же семейства. */
  --index-hero-bg: #fbfbfa;
  --bg-elevated: #ffffff;
  --bg-panel-soft: #f7f7f8;
  --text: var(--text-primary);
  --muted: var(--text-secondary);
  --dim: var(--text-muted);
  --panel: var(--bg-panel);
  --panel-2: var(--bg-elevated);
  --bg-raise: #fbfbfa;
  --line: rgba(0, 0, 0, 0.11);
  --line-soft: rgba(0, 0, 0, 0.045);
  --warn-soft: var(--warning-soft);
  --text-primary: #18181b;
  --text-secondary: #52525b;
  --text-muted: #6a6a73;
  --border-subtle: #e4e4e7;
  --border-strong: #d4d4d8;
  /* Светлая тема: знак проходит сам (5.29 на худшей поверхности), поэтому
     заливка кнопки равна ему, как и у WAF.

     Наведение, наоборот, взято ТЕМНЕЕ знака. У WAF в светлой теме стоит
     #818cf8, но он даёт 2.51 на подложке героя, а у нас тот же токен
     статически красит `.kb-kicker` — светлое наведение здесь не годится.
     Причина та же, что была на зелёном: наведение не должно ухудшать
     читаемость. */
  --accent: #4f46e5;
  --accent-hover: #3f37c9;
  --accent-fill: #4f46e5;
  --accent-fg: #f6f6fe;
  --accent-color: var(--accent-fg);
  --accent-text: #4f46e5;
  --accent-secondary: #4f46e5;
  --accent-soft: rgb(79 70 229 / 0.1);
  --on-accent: #04052f;
  --depth: rgba(56, 189, 248, 0.05);
  --fade: rgba(255, 255, 255, 0);
  --success: #059669;
  --success-soft: rgb(5 150 105 / 0.12);
  --warning: #b45309;
  --warning-soft: rgb(180 83 9 / 0.12);
  --danger: #dc2626;
  --control-bg: #f4f4f5;
  --control-hover: #efeff0;
  --control-active: #d4d4d9;
  --shadow-sm: 0 1px 2px rgb(24 24 27 / 0.06);
}



*,
*::before,
*::after { box-sizing: border-box; }



html {
  font-size: 16px;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  scroll-behavior: smooth;
}



body {
  margin: 0;
  background: var(--bg);
  color: var(--text-primary);
  font-family: var(--font-family);
  font-size: var(--font-base);
  font-weight: var(--weight-regular);
  line-height: var(--leading-normal);
}



h1, h2, h3, h4, p { margin-top: 0; }



h1 {
  margin-bottom: var(--space-2);
  color: var(--text-primary);
  font-size: var(--font-2xl);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.01em;
  line-height: var(--leading-tight);
}



h2 {
  margin-bottom: var(--space-3);
  color: var(--text-primary);
  font-size: var(--font-md);
  font-weight: var(--weight-semibold);
  line-height: 1.3;
}



h3 {
  margin-bottom: var(--space-3);
  font-size: var(--font-base);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-snug);
}



p { color: var(--text-secondary); }



::selection {
  background: var(--accent-soft);
  color: var(--text-primary);
}



a {
  color: var(--accent-text);
  text-decoration: none;
  transition: color var(--transition);
}



a:hover { color: var(--accent-hover); }



button { font: inherit; color: inherit; }



:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}



.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
  min-height: 36px;
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--control-bg);
  color: var(--text-primary);
  cursor: pointer;
  font-size: var(--font-sm);
  font-weight: var(--weight-medium);
  line-height: 1.2;
  text-decoration: none;
  transition: background-color var(--transition), border-color var(--transition), color var(--transition);
  user-select: none;
  white-space: nowrap;
}



.btn:hover {
  background: var(--control-hover);
  border-color: var(--border-strong);
  color: var(--text-primary);
}



.btn:active { background: var(--control-active); }



.btn-primary {
  border-color: transparent;
  /* `--accent-fill`, а не `--accent`: знак сети фоном кнопки даёт с
     подписью 4.15 при норме 4.5 — замерено, число совпало с тем, что
     записано в токенах WAF. Заливка того же тона, светлота опущена до
     проходной. */
  background: var(--accent-fill);
  color: var(--accent-color);
  font-weight: var(--weight-semibold);
}



.btn-primary:hover {
  border-color: transparent;
  background: var(--accent-hover);
  color: var(--on-accent);
}



.btn-ghost {
  border-color: transparent;
  background: var(--bg-hero);
  color: var(--text-secondary);
}



.btn-ghost:hover {
  background: var(--control-hover);
  border-color: transparent;
  color: var(--text-primary);
}



/* ---- landing chrome ---- */

.landing-shell {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  background-color: var(--index-hero-bg, var(--bg-hero));
}



.landing-shell .landing-footer { background-color: var(--bg); }



/* ─── Шапка: структура сети ──────────────────────────────────────────────

   Перенята у `bridge-waf-static` (base.css:255) вместе с разметкой. Что
   изменилось против прежней нашей:

   1. Ряд внутри `.wrap`, а не сетка на всю ширину. Прежде шапка была
      `grid` в три колонки с собственным отступом
      `max(40px, (100% - 1200px)/2)` — своя механика ширины, не совпадающая
      с остальной страницей.
   2. Имя сайта словом рядом со знаком. Логотип без подписи не говорит,
      где посетитель находится, а сеть из семи доменов выглядит одинаково.
   3. Разделитель `/` между брендом и разделами — он же отделяет «где я»
      от «куда можно».
   4. Фон непрозрачный, с волосяной линией снизу. Прежде стоял
      полупрозрачный с `backdrop-filter: blur(12px)`; на длинной странице
      под шапкой просвечивал текст, а размытие стоит кадра на каждый
      скролл.
   5. Появилось мобильное меню. Его отсутствие было настоящим дефектом:
      на 390px строка разделов уезжала за край экрана. */

.landing-header {
  position: sticky;
  top: 0;
  z-index: 40;
  background: transparent;
  border-bottom: var(--hairline) solid transparent;
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}

/* Шапка уплотняется на прокрутке, а не стоит сплошной с самого верха.
   Наверху страницы она прозрачна, и герой читается во всю высоту; за
   первые 40 пикселей прокрутки набирает подложку и волосяную линию.

   На прокруточной шкале (`scroll()`), а не на слушателе: слушатель на
   каждый кадр ради одного свойства — плохой размен, а бюджет JS этого
   сайта занят темой и годом. Браузер без шкал получает сплошную шапку
   сразу: она остаётся читаемой, просто без перехода. Перенято у WAF. */
@supports (animation-timeline: scroll()) {
  .landing-header {
    animation: nav-solid linear both;
    animation-timeline: scroll();
    animation-range: 0 40px;
  }
}

@keyframes nav-solid {
  to {
    background: color-mix(in srgb, var(--bg) 86%, transparent);
    border-bottom-color: var(--line);
  }
}

@supports not (animation-timeline: scroll()) {
  .landing-header {
    background: var(--bg);
    border-bottom-color: var(--line);
  }
}

.hdr-in {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  height: 56px;
}

.hdr-brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font: 500 15px/1 var(--font-sans);
  letter-spacing: -0.01em;
  color: var(--text);
  white-space: nowrap;
}

.brand-mark { width: 22px; height: 22px; }
:root[data-theme="proof-light"] .brand-mark-dark { display: none; }
:root:not([data-theme="proof-light"]) .brand-mark-light { display: none; }

.hdr-slash { color: var(--line); font-family: var(--font-sans); }

.hdr-nav {
  position: relative;
  display: flex;
  gap: var(--space-6);
  min-width: 0;
}

.hdr-nav a {
  font: 400 var(--fs-label)/1 var(--font-sans);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.hdr-nav a:hover { color: var(--text); }

.hdr-right {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* Меню на чекбоксе, а не на `<details>`: приёмка ищет вопросы FAQ сперва
   по `<summary>`, и вторая такая пара в шапке утащила бы «Menu» в список
   вопросов. Скрипта чекбокс тоже не требует — бюджет JS этого сайта уже
   занят темой и годом. Довод перенят вместе с решением. */
.hdr-menu { display: none; position: relative; }
.hdr-menu-input { position: absolute; width: 1px; height: 1px; opacity: 0; }

.hdr-menu-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: 34px;
  padding: 0 var(--space-3);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  font: 400 var(--fs-label)/1 var(--font-sans);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  cursor: pointer;
  transition: color var(--t-fast), border-color var(--t-fast);
}

.hdr-menu-btn:hover { color: var(--text); border-color: var(--dim); }
.hdr-menu-input:checked ~ .hdr-menu-btn { color: var(--text); border-color: var(--dim); }
.hdr-menu-input:focus-visible ~ .hdr-menu-btn {
  outline: 2px solid var(--accent-secondary);
  outline-offset: 2px;
  color: var(--text);
}

.landing-theme-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  min-width: 36px;
  min-height: 36px;
  padding: 0;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--bg-panel-soft);
  color: var(--text-secondary);
}



.landing-theme-btn:hover { color: var(--text-primary); }


.landing-theme-btn svg { width: 18px; height: 18px; }



.landing-footer { padding: 0 var(--space-10) var(--space-10); }



.landing-footer-inner {
  display: grid;
  gap: var(--space-8);
  width: min(100%, 1200px);
  margin: 0 auto;
  padding-top: var(--space-10);
  border-top: 1px solid var(--border-subtle);
}



.landing-footer-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-10);
  align-items: start;
}



.landing-footer-col { display: grid; gap: var(--space-3); align-content: start; }



.landing-footer-tag {
  max-width: 260px;
  color: var(--text-secondary);
  font-size: var(--font-sm);
}



.landing-footer-heading {
  margin: 0;
  color: var(--text-primary);
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}



.landing-footer-list {
  display: grid;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--font-sm);
}



.landing-footer-list a { color: var(--text-secondary); font-weight: 500; }


.landing-footer-name { color: var(--text-secondary); font-weight: 400; }


.landing-footer-list a:hover { color: var(--text-primary); }



.landing-footer-desc {
  display: block;
  color: var(--text-secondary);
  font-size: var(--font-xs);
}



.landing-footer-brand {
  /* Как в шапке: знак и имя словом. Прежде стоял логотип 56×56 без
     подписи — семь доменов сети выглядят одинаково, и подвал не говорил,
     на каком из них человек находится. Размер знака приведён к сетевому. */
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font: 500 15px/1 var(--font-sans);
  letter-spacing: -0.01em;
  color: var(--text);
}

.landing-footer-brand .brand-mark { width: 28px; height: 28px; }



.landing-footer-brand .brand-logo { height: 56px; }



/* Historical note, kept accurate 2026-08-27: --text-muted used to fail AA at
   13px against both theme backgrounds, which is why these rules reach for
   --text-secondary instead. The token was split by theme that evening
   (Q-NET-03 group A) and now clears 4.5:1 on every surface, so the original
   reason no longer holds. The colours below are left as they are: dimming
   this fine print is a look decision, not a contrast fix. Contrast itself is
   non-negotiable here (AGENTS.md). */
/* Низ подвала — ОДНОЙ строкой: ссылка слева, копирайт справа, над ними
   волосяная линия. Приём `bridge-waf-static` (`.ftr-bottom`). Прежде это
   были два отдельных абзаца друг под другом, без разделителя, и ссылка
   шла подчёркнутой — три отличия сразу, заметные при сравнении подвалов
   рядом. Копирайт отодвигается `margin-left: auto`, как у эталона: на
   узком экране строка переносится, и он встаёт под ссылкой сам. */
.landing-footer-bottom {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
  align-items: baseline;
  margin-top: var(--space-8);
  padding-top: var(--space-8);
  border-top: var(--hairline, 1px) solid var(--line, var(--border-subtle));
  font-size: var(--font-xs);
  color: var(--text-muted);
}

.landing-footer-bottom a { color: var(--text-muted); text-decoration: none; }
.landing-footer-bottom a:hover { color: var(--text-primary); }
.landing-footer-bottom .landing-footer-copy { margin-left: auto; }

.landing-footer-copy {
  color: var(--text-secondary);
  font-size: var(--font-sm);
}



@keyframes indexFadeUp {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}



.index-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
}



.index-actions .btn {
  min-height: 46px;
  padding: 0 var(--space-6);
  font-size: var(--font-base);
}



.index-flagship .index-icon-tile { margin-bottom: var(--space-4); }



.index-homepage .index-flagship h2 {
  margin-bottom: var(--space-4);
  color: var(--accent);
  font-size: clamp(24px, 2.4vw, 32px);
}



.index-homepage .index-flagship .index-copy {
  margin-bottom: var(--space-8);
  max-width: 780px;
}



/* ---- origin-controls demo panel ---- */

/* Load-bearing: the demo's fields carry display:grid, which outranks the UA's
   [hidden] { display: none }



/* ---- switch — ported from the dashboard's uiSwitch (imports/client/main.css
   .switch/.switch-track). Saving/saved states are dropped: nothing persists here.
   The track transition carries the whole animation, so no JS class flags. ---- */

.switch {
  position: relative;
  display: inline-flex;
  flex: 0 0 auto;
  width: 44px;
  height: 24px;
}



/* ---- drawbridge toggle — ported verbatim from the dashboard's uiBridge
   (imports/client/ui/bridge.css). Open ±48° / --text-muted, closed flat /
   --accent, 1280ms. .is-closed drives the visual, never input:checked. ---- */

.bridge {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 52px;
  height: 40px;
  cursor: pointer;
}



.index-homepage .index-migration h2 { margin-bottom: var(--space-4); }


.index-homepage .index-migration .index-copy { margin-bottom: var(--space-6); }



.index-homepage .index-migration .index-copy { max-width: none; }



/* ---- responsive tail — must stay last ---- */

@media (max-width: 1080px) {



  .index-migration-list:not(.index-migration-list--stack) { grid-template-columns: repeat(2, minmax(0, 1fr)); }


}

@media (max-width: 900px) {
  .index-flow-grid { grid-template-columns: 1fr; }



  .landing-header {
    gap: var(--space-4);
    padding-inline: var(--space-6);
  }






  .landing-footer {
    padding: 0 max(var(--space-4), env(safe-area-inset-left, 0px))
      max(var(--space-8), env(safe-area-inset-bottom, 0px))
      max(var(--space-4), env(safe-area-inset-right, 0px));
  }


}

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


  .index-migration-list:not(.index-migration-list--stack) { grid-template-columns: 1fr; }



  .landing-footer-grid { grid-template-columns: 1fr; gap: var(--space-8); }


}

/* Below 660px, nav links can't fit alongside brand + actions on one row —
   drop the nav and keep the header a single row. */
@media (max-width: 660px) {
  /* Прежде здесь шапка перестраивалась в сетку «бренд + кнопки», а строка
     разделов просто пряталась: на телефоне попасть в разделы было нельзя
     вовсе. Теперь их открывает меню. */
  .hdr-in { gap: var(--space-4); }
  .hdr-brand { font-size: 14px; }


}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }



  *,
  *::before,
  *::after {
    animation: none !important;
    transition-duration: 0.01ms !important;
  }



  .landing-header {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    background: var(--bg);
    border-bottom: 1px solid var(--border-subtle);
  }
}


/* Токены дизайн-системы, которых нет в наборе эталона, но которые
   требуются `components.css` и `patterns/kb-article.css`. Значения
   скопированы из `bridge-cdn-design-system/tokens/tokens.css` без
   изменений. Без них ломается сетка статьи: `--content-article`
   не разрешается, `grid-template-columns` становится невалидным,
   и оглавление уезжает под текст. */

:root {
  --content-article: 680px;
  --content-wide: 1120px;
  --font-4xl: 44px;
  --leading-relaxed: 1.75;
  --z-dropdown: 50;
}

:root,
:root[data-theme="gruvbox-dark"] {
  --bg-panel: #151519;
  --danger-soft: rgb(248 113 113 / 0.16);
  --info: #38bdf8;
  --info-soft: rgb(56 189 248 / 0.16);
  --shadow-soft: 0 12px 32px rgb(0 0 0 / 0.45);
}

:root[data-theme="proof-light"] {
  --bg-panel: #fcfcfd;
  --danger-soft: rgb(220 38 38 / 0.1);
  --info: #0369a1;
  --info-soft: rgb(3 105 161 / 0.12);
  --shadow-soft: 0 12px 32px rgb(24 24 27 / 0.08);
}

/* --- Внутренние страницы: разбавляем полотно текста -------------------
   Приёмы взяты из паттернов эталона (панель, плитки, финальная полоса) и
   перенесены в контекст статьи. Ни одного значения мимо токенов. */

/* Ответный абзац статьи — не рядовой текст, а ответ на вопрос страницы.
   Поднят по кеглю и отделён акцентной линейкой, как панель на главной. */
/* Лид БЕЗ коробки — как у `bridge-waf-static`. Прежде он шёл плашкой с
   цветной полосой слева, и на снимке рядом с WAF это первое, что видно:
   у них лид просто крупный текст, у нас выноска. Ответный абзац — часть
   статьи, а не примечание к ней, и рамка вокруг него делает из начала
   чтения отступление. */
.kb-lead {
  margin: var(--space-6) 0 var(--space-8);
  /* 62ch → 70ch вместе с расширением прозы (владелец, 2026-08-31) */
  max-width: 70ch;
  color: var(--text-secondary);
  font-size: var(--font-lg);
  line-height: 1.6;
}

/* Ключевые факты страницы: короткие пары «что / значение» под лидом. */
.kb-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-3);
  margin-bottom: var(--space-10);
  padding: 0;
  list-style: none;
}

.kb-fact {
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  background: var(--bg-panel-soft);
}

.kb-fact-label {
  display: block;
  margin-bottom: var(--space-1);
  /* не --text-muted: так решено по B-37, когда токен не проходил AA.
     С 2026-08-27 он проходит (Q-NET-03, группа А) — возврат возможен,
     но это отдельное решение о виде, а не починка контраста. */
  color: var(--text-secondary);
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.kb-fact-value {
  color: var(--text-primary);
  font-size: var(--font-sm);
  line-height: 1.45;
}

/* Раздел «дальше по теме» — плитками, а не списком ссылок. */
.kb-related {
  margin-top: var(--space-12);
  padding-top: var(--space-8);
  border-top: 1px solid var(--border-subtle);
}

.kb-related-label {
  margin-bottom: var(--space-4);
  /* не --text-muted: так решено по B-37, когда токен не проходил AA.
     С 2026-08-27 он проходит (Q-NET-03, группа А) — возврат возможен,
     но это отдельное решение о виде, а не починка контраста. */
  color: var(--text-secondary);
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* Финальная полоса на внутренней странице: уже колонки статьи, спокойнее
   главной, но конверсия есть на каждой странице. */
.kb-cta {
  margin-top: var(--space-10);
  padding: var(--space-8);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-xl);
  background: var(--bg-panel-soft);
  text-align: center;
}

.kb-cta h2 {
  margin-bottom: var(--space-3);
  font-size: var(--font-xl);
}

.kb-cta p {
  max-width: 52ch;
  margin: 0 auto var(--space-5);
  color: var(--text-secondary);
  font-size: var(--font-sm);
  line-height: 1.6;
}

.kb-cta .index-actions { justify-content: center; }

/* Токены страницы на уровне документа.
   У эталона они висят на `.index-homepage` — обёртке лендинга, у которой
   ещё и `overflow: hidden`. Для внутренних страниц эта обёртка не годится:
   `overflow: hidden` у предка отключает `position: sticky`, то есть убил бы
   липкое оглавление статьи. Поэтому сами значения подняты на `:root`, а
   класс `.index-homepage` остаётся только на лендинге, как в эталоне. */
:root {
  --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(--accent-secondary) 18%, transparent);
  --index-tone-success-fg: var(--success);
  --index-tone-success-soft: var(--success-soft);
}

/* Ширина внутренних страниц равна ширине лендинга.
   `.wrap` дизайн-системы даёт 1120 px, а весь сайт сети живёт в 1200
   (`--index-page`). Разница читается как «страницы разной ширины», что и
   было названо владельцем первым дефектом. */
/* Контейнер внутренней страницы — как `.wrap` у WAF: `--container` плюс
   тот же `--gutter`. Прежде он брал `--index-page` без внутренних полей,
   и полоса выходила 1200 против 1248 у эталона, а левый край текста стоял
   на 120 вместо 96. Токены у нас и у WAF совпадают дословно, расходилось
   только их применение. */
.kb-page {
  width: 100%;
  max-width: var(--container, 1248px);
  margin-inline: auto;
  padding-inline: var(--gutter, clamp(20px, 6.7vw, 96px));
}

.kb-breadcrumbs { padding-top: var(--space-6); }

/* Перечень тезисов внутри статьи — плитками, как на лендинге.
   Серия абзацев «**Тезис.** пояснение» это список, а не проза; подряд
   идущие такие абзацы читаются полотном и теряют смысл перечисления. */
/* Ячейки встык, как у W4 на главной (порт WAF): панельки с почти
   невидимой рамкой и auto-fit-колонками читались парящим текстом с
   рваными пустотами — замечание владельца 2026-08-31. Две колонки;
   нечётный последний пункт занимает всю строку — дыр в сетке нет. */
.kb-points {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--hairline, 1px);
  margin-block: var(--space-6);
  padding: 0;
  list-style: none;
  background: var(--line);
  border: var(--hairline, 1px) solid var(--line);
}

.kb-point {
  padding: var(--space-6);
  background: var(--bg);
  min-width: 0;
}

.kb-point:last-child:nth-child(odd) { grid-column: 1 / -1; }

/* Внутри прозы список наследует её отступы (padding-left у ul, зазор
   li + li) — сквозь них светил фон-разделитель сетки. Ячейкам они не
   принадлежат. */
.kb-prose ul.kb-points { padding-left: 0; }
.kb-prose .kb-point + .kb-point { margin-top: 0; }

.kb-point-title {
  margin-bottom: var(--space-2);
  color: var(--text-primary);
  font-size: var(--font-sm);
  font-weight: var(--weight-semibold);
  line-height: 1.35;
}

.kb-point p {
  color: var(--text-secondary);
  font-size: var(--font-sm);
  line-height: 1.6;
}

/* Заголовок навигационной плитки — абзац, а не заголовок раздела:
   плитка не является частью структуры документа, а `<h3>` сразу после
   `<h1>` ломает порядок уровней (поймано приёмкой на `/vs/`). */
.nav-card-title {
  margin-bottom: var(--space-1);
  color: var(--text-primary);
  font-size: var(--font-sm);
  font-weight: var(--weight-semibold);
  line-height: 1.35;
}

/* Мобильные ширины для внутренних страниц.
   У эталона `--index-page` переопределяется на 1080 и 760 ТОЛЬКО внутри
   `.index-homepage` — обёртки лендинга. Страницы KB её не носят (иначе
   `overflow: hidden` убил бы липкое оглавление), поэтому те же
   переопределения продублированы на `:root`. Без них на телефоне
   оставалось 80 px полей с каждой стороны. */
@media (max-width: 1080px) {
  :root { --index-page: min(100% - 64px, 1180px); }
}

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

  /* Плитки и факты — в одну колонку: на узком экране две колонки дают
     строку в три слова. */
  .kb-points,
  .kb-facts { grid-template-columns: 1fr; }
  .kb-point:last-child:nth-child(odd) { grid-column: auto; }

  .kb-cta { padding: var(--space-6); }

  .kb-lead {
    padding: var(--space-4) var(--space-5);
    font-size: var(--font-md);
  }
}

/* Навигация по разделам на телефоне.
   Эталон прячет ссылки шапки ниже 660 px — ему можно: он одностраничник,
   и ссылки там якорные. У нас это переходы между коконами, и прятать их
   значит оставить читателя без навигации до самого подвала. Поэтому вместо
   `display: none` — строка с горизонтальной прокруткой под брендом. */
@media (max-width: 660px) {
  .landing-header {
    grid-template-columns: auto 1fr;
    grid-template-areas: 'brand actions' 'links links';
    row-gap: var(--space-2);
  }

}

/* --- Раскладка статьи: свои правила, а не чужие ----------------------
   Сетка статьи трижды за вечер ломалась из-за того, что правило жило в
   `patterns/kb-article.css` и читало переменные, объявленные в другом
   файле. Здесь она объявлена в том же файле, где объявлены её значения, и
   у каждой переменной есть запасное значение: даже если объявление
   потеряется, правило останется валидным и сетка не схлопнется. */

/* Две колонки, ОГЛАВЛЕНИЕ СЛЕВА — форма `bridge-waf-static`. Числа
   перенесены с эталона дословно: 216px под оглавление, `clamp` на
   промежуток. Прежде было наоборот: статья 680px слева, оглавление 240px
   справа с `justify-self: end`, и владелец назвал это расхождением с
   айдентикой 2026-08-28.

   Замер до и после, ширина окна 1440: у WAF оглавление на x=192, проза на
   x=480 шириной 686. У нас было оглавление на x=560, проза на x=848
   шириной 472 — то есть текст вдвое уже и прижат к правому краю. */
.kb-layout {
  display: grid;
  grid-template-columns: 216px minmax(0, 1fr);
  gap: clamp(32px, 5vw, 72px);
  align-items: start;
  padding: 0 0 clamp(56px, 8vw, 112px);
}

.kb-layout > .kb-toc { grid-column: 1; grid-row: 1; }
.kb-layout > .kb-body { grid-column: 2; grid-row: 1; min-width: 0; }

.kb-toc {
  position: sticky;
  top: 84px;
  align-self: start;
  max-height: calc(100vh - var(--space-20, 80px) - var(--space-6, 24px));
  overflow-y: auto;
}

.kb-prose { max-width: 76ch; }   /* было 68ch как у WAF; расширено решением владельца 2026-08-31 — «текст сжат» */

.kb-prose h2 { scroll-margin-top: var(--space-16, 64px); }
.kb-prose h3 { scroll-margin-top: var(--space-16, 64px); }

@media (max-width: 900px) {
  .kb-layout {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-8, 32px);
  }

  .kb-toc { display: none; }
}

/* Блик по главной кнопке — приём `bridge-waf-static`, перенесён дословно.
   Кнопка призыва единственная на экране и должна быть заметна без
   крика: полоса света раз в шесть секунд ловит взгляд там, где
   статичная заливка его уже потеряла.

   `overflow: hidden` на кнопке обязателен: без него полоса выезжает за
   её край и бежит по секции. Прозрачность 0.18 подобрана так, чтобы
   подпись оставалась читаемой в момент прохода — контраст подписи на
   заливке 4.54, и блик его не съедает. */
.btn-primary { position: relative; overflow: hidden; }

.btn-primary::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: -60%;
  width: 40%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.18), transparent);
  transform: skewX(-20deg);
  animation: shimmer 6s var(--ease-io, ease-in-out) infinite;
}

@keyframes shimmer {
  0%, 72%   { transform: translateX(0) skewX(-20deg); }
  88%, 100% { transform: translateX(420%) skewX(-20deg); }
}

/* --- Выпадающее меню разделов в шапке ---------------------------------
   Пункт кокона, у которого есть страницы, раскрывается по наведению и по
   фокусу с клавиатуры. Открывается на CSS, без единой строки JS: меню и
   так строится на сборке, и добавлять ради него скрипт незачем. */

.nav-item {
  position: relative;
  display: flex;
  align-items: center;
}

.nav-item > a {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1, 4px);
}

.nav-caret {
  font-size: 9px;
  line-height: 1;
  opacity: 0.6;
  transition: transform var(--transition, 0.18s ease);
}

.nav-menu {
  position: absolute;
  top: 100%;
  left: 50%;
  z-index: 50;
  display: grid;
  gap: var(--space-1, 4px);
  min-width: 260px;
  max-width: 340px;
  margin: 0;
  padding: var(--space-2, 8px);
  list-style: none;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg, 12px);
  background: var(--bg-panel);
  box-shadow: var(--shadow-lg, 0 18px 48px rgb(0 0 0 / 0.5));
  opacity: 0;
  visibility: hidden;
  transform: translate(-50%, 4px);
  transition: opacity var(--transition, 0.18s ease),
              transform var(--transition, 0.18s ease),
              visibility var(--transition, 0.18s ease);
}

.nav-item:hover .nav-menu,
.nav-item:focus-within .nav-menu {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, 0);
}

.nav-item:hover .nav-caret,
.nav-item:focus-within .nav-caret { transform: rotate(180deg); }

.nav-menu a {
  display: block;
  padding: var(--space-2, 8px) var(--space-3, 12px);
  border-radius: var(--radius-md, 8px);
  color: var(--text-secondary);
  font-size: var(--font-sm);
  line-height: 1.35;
  text-decoration: none;
}

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

   Текущий пункт ВЫДЕЛЕН, а не притенён. Напрашивается притенить его
   (opacity, --text-muted), но выше по файлу прямо записано: --text-muted
   не держит AA на 13px ни в одной из тем, а opacity поверх --text-secondary
   уводит вниз и его. «Вы здесь» одинаково читается и через усиление —
   при этом контраст растёт, а не падает. */
/* Два правила, которых у нас не было вовсе, а у WAF есть: список сети
   шире обычного (в нём семь доменов, а не пять пунктов кокона) и его
   пункты без маркеров. Без них выпадающий список сети рисовался узким и
   с точками — то самое расхождение «шапка не как у WAF», которое
   владелец увидел 2026-08-28. Перенесено дословно. */
.nav-menu-network { min-width: 320px; }
.nav-menu-network li { list-style: none; }

.nav-menu-network li[aria-current] {
  padding: var(--space-2, 8px) var(--space-3, 12px);
  border-radius: var(--radius-md, 8px);
  color: var(--text-primary);
  font-size: var(--font-sm);
  line-height: 1.35;
  font-weight: 500;
}
.nav-menu a:hover,
.nav-menu a:focus-visible {
  background: var(--bg-panel-soft);
  color: var(--text-primary);
}

/* Мост под меню: курсор не проваливается в зазор между пунктом и списком. */
.nav-item::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 0;
  width: 100%;
  height: var(--space-3, 12px);
}

@media (max-width: 660px) {
  /* На телефоне раскрытие не нужно: пункты и так прокручиваются строкой,
     а вложенное меню поверх узкого экрана только мешает. */
  .nav-menu { display: none; }
  .nav-caret { display: none; }
}

/* Ссылка на машиночитаемые документы: требование внешнего стандарта —
   `llms.txt` должен быть достижим из подвала, а не только по прямому
   адресу. Тон подписи, не акцента. */
.landing-footer-links {
  margin: 0 0 var(--space-2, 8px);
  font-size: var(--text-sm, 0.875rem);
  color: var(--text-secondary, #b8b0a6);
}
.landing-footer-links a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.landing-footer-links a:hover { color: var(--text-primary, #f2e9dd); }


/* ═══ Словарь схем ════════════════════════════════════════════════════

   Заводится ОДИН раз, до первой схемы. В `site.css` этого сайта не было
   ни одного класса `viz-`, и если каждую схему красить по-своему, четыре
   рисунка разъедутся по именам ещё до того, как их станет четыре.

   Цветовых ролей две и третьей не будет: «пробел» — чего нет, «заполнено»
   — что появилось. У Bridge WAF три цвета заняты режимами `off` /
   `monitor` / `block`; у нас заняты эти две. Всё остальное — «это важно»,
   «это в области» — выражается формой, весом и подписью, иначе
   единственная цветовая семантика сайта размывается до украшения.

   `--viz-line` заведён отдельным токеном, а не взят от границ:
   разделитель вёрстки обязан быть почти невидим, схема — читаема, и
   одним цветом обе задачи не тянутся.

   Цвет задаётся ТОЛЬКО классом. Презентационные `fill=`/`stroke=` в
   разметке перебиваются правилом CSS, и линия молча остаётся чужого
   цвета; на bridge-waf.com это ловилось трижды, третий раз — уже на
   отгруженной странице. Проверяется `scripts/check-css-local.py`.       */

.viz {
  margin: var(--space-6) 0 0;
  max-width: var(--content-default);
  margin-inline: auto;
  width: 100%;
}

.viz svg { width: 100%; height: auto; display: block; }

.viz-line   { fill: none; stroke: var(--viz-line); stroke-width: 1.5; }
.viz-solid  { fill: var(--bg); stroke: var(--viz-line); stroke-width: 1.5; }
.viz-label  { fill: var(--text-muted); font-size: 10px; }
.viz-glyph  { fill: var(--text-secondary); font-size: 15px; font-weight: var(--weight-semibold); }

/* Две цветовые роли словаря, и обе общие: схемы есть и на главной, и на
   внутренних страницах. Держать их в `home.css` значило бы, что рисунок
   статьи молча теряет цвет — правило оттуда на внутренней не применяется.
   Поймано `check-css-local.py` на первой же схеме статьи. */
.viz-filled   { fill: var(--accent-soft); stroke: var(--accent); stroke-width: 1.5; }
.viz-frame    { fill: var(--bg); stroke: var(--viz-line); stroke-width: 1.5; }
.viz-gap      { fill: none; stroke: var(--viz-line); stroke-width: 1.5; stroke-dasharray: 4 3; }
.viz-line-gap { fill: none; stroke: var(--viz-line); stroke-width: 1.5; stroke-dasharray: 4 3; }
.viz-label-on { fill: var(--text-primary); font-size: 10px; font-weight: var(--weight-medium); }

/* Схема на узком экране: СВОЯ прокрутка вместо общего сжатия.

   Правило было написано для главной и жило в `home.css`, то есть
   действовало на ОДНОЙ странице с одной схемой. Семнадцать внутренних
   страниц с семью схемами его не получали: там `.viz svg { width: 100% }`
   ужимал картинку целиком, и подписи, заданные в пользовательских
   единицах SVG, ужимались вместе с ней — 10px превращались в 6.9px при
   ширине окна 390 (замер: фигура 358 при `viewBox` 520, масштаб 0.688).

   Один компонент вёл себя по-разному на двух типах страниц, причём хуже —
   там, где схем больше. Правило перенесено в общий слой; `B-71`.

   Почему не поднять кегль в медиазапросе: у `tag-gaps` заголовок
   `description` при 10px занимает ~52 единицы над ячейкой шириной 66, а
   при 14px — ~73, и заголовки столбцов наедут друг на друга. Правка,
   чинящая одну схему, ломает другую. Прокрутка сохраняет пропорции всех.

   Страница по горизонтали при этом не едет никогда: прокручивается
   фигура, а не документ. Проверено замером `documentElement.scrollWidth`
   против `clientWidth` в обеих темах на 390 и 1440. */
@media (max-width: 560px) {
  .viz { overflow-x: auto; }
  .viz svg { min-width: 460px; }
}

/* «Заполнено» — единственная акцентная роль на схеме. */



/* Порог 1140, а не 660: замер по пятнадцати ширинам показал, что строка
   разделов помещается в ряд вплоть до 660, НО выпадающая панель последнего
   пункта («Network») вылезает за экран уже на 720 — документ получает
   горизонтальную прокрутку. Считать по ряду недостаточно, потому что панель
   позиционирована абсолютно и в ширину ряда не входит.

   1140 — тот же порог, что у WAF, и по той же причине: у них седьмой пункт
   выталкивал шапку на 1024. У нас пунктов шесть, так что порог с запасом. */
@media (max-width: 1140px) {
  /* Мобильное меню сети: открывает САМУ навигацию, а не её копию. Копия
     означала бы одни и те же пункты в двух местах разметки на каждой из
     восемнадцати страниц — и постоянный повод им разойтись. `:has()`
     позволяет открыть `.hdr-nav` по чекбоксу из соседней ветки дерева.

     Вложенные выпадающие меню внутри скрыты: на узком экране нужен список
     разделов, а не всё дерево сразу. Перенято у WAF вместе с доводом. */
  .hdr-nav { display: none; }
  .hdr-menu { display: block; }
  .hdr-slash { display: none; }

  .hdr-in { position: relative; }

  .hdr-in:has(.hdr-menu-input:checked) .hdr-nav {
    display: block;
    /* `right` отсчитывается от padding-box обёртки, поэтому 0 прижал бы
       панель к самому краю экрана; вровень с отступом её ставит --space-6,
       то есть тот же отступ, что у `.wrap`. */
    position: absolute;
    right: var(--space-6);
    top: calc(100% + var(--space-2));
    z-index: 45;
    min-width: 220px;
    padding: var(--space-2);
    background: var(--panel);
    border: var(--hairline) solid var(--line);
    border-radius: var(--radius);
  }

  .hdr-in:has(.hdr-menu-input:checked) .nav-menu { display: none; }
  /* Значок раскрытия в меню не нужен: раскрывать нечего, панель уже
     открыта, а стрелка обещает второй уровень, которого здесь нет. */
  .hdr-in:has(.hdr-menu-input:checked) .nav-caret { display: none; }

  .hdr-in:has(.hdr-menu-input:checked) .nav-item > a {
    display: block;
    padding: var(--space-3);
    font: 400 var(--fs-label)/1 var(--font-sans);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--muted);
  }

  .hdr-in:has(.hdr-menu-input:checked) .nav-item > a:hover {
    color: var(--text);
    background: var(--panel-2);
  }
}

/* Ниже 420px не помещается уже сам ряд: знак с подписью, кнопка меню,
   переключатель темы и призыв дают 347 при коробке в 312. Подпись бренда
   уходит, знак остаётся — он и ведёт на главную, а `aria-label` ссылки
   называет сайт полностью, так что для диктора ничего не теряется. */
@media (max-width: 420px) {
  .hdr-brand { font-size: 0; gap: 0; }
  .hdr-brand .brand-mark { font-size: initial; }

  /* На 320px даже без подписи ряду не хватало 20px: знак 22, зазор 24 и
     правая группа 230 (меню 62 + тема 36 + призыв 116 + свои зазоры).
     Ужимаются зазоры, а не органы управления: спрятать призыв или тему
     значит убрать со страницы действие, а зазор ничего не несёт.
     Замерено на 300, 320, 360 и 390. */
  .hdr-in { gap: var(--space-2); }
  .hdr-right { gap: var(--space-1); }

  /* Призыв — самый широкий элемент группы (116px из 230). Подрезаются его
     боковые отступы, надпись остаётся целиком: «Bridge CDN» и стрелка
     говорят, куда ведёт кнопка, а отступ не говорит ничего. */
  .hdr-right .btn { padding-inline: var(--space-3); }
}


/* ═══ Фоновая система ═══════════════════════════════════════════════════

   Перенята у `bridge-waf-static` (base.css:628). Их формулировка причины
   точна: плоский фон сверху донизу — главная причина ощущения
   недоделанности. Три слоя, и каждый делает своё:

   1. Техническая сетка 48×48 с радиальной маской — читается у верхнего
      края и растворяется книзу, так что не спорит с текстом.
   2. Три радиальных пятна с размытием 48px и медленным дрейфом. Пятна
      берут цвета системы: акцент, глубину и предупреждение. Дрейф 34
      секунды — на границе заметности, страница «дышит», а не мигает.
   3. Зерно. Без него градиенты выглядят пластиковыми — их довод, и он
      верен: проверено снятием слоя.

   Отличие от WAF: у них `body` перестал красить фон, чтобы не накрыть
   слои. У нас фон на `body` оставлен. Слои лежат на `z-index: -2` и `-1`,
   то есть выше холста и ниже содержимого, так что не накрываются; зато
   при оверскролле не показывается белая полоса. */

.site-bg,
.site-noise {
  position: fixed;
  inset: 0;
  pointer-events: none;
}

.site-bg { z-index: -2; background: var(--bg); }

.site-bg::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(to right, var(--line-soft) 1px, transparent 1px),
    linear-gradient(to bottom, var(--line-soft) 1px, transparent 1px);
  background-size: 48px 48px;
  -webkit-mask-image: radial-gradient(ellipse 90% 55% at 50% 0%, #000 35%, transparent 100%);
  mask-image: radial-gradient(ellipse 90% 55% at 50% 0%, #000 35%, transparent 100%);
}

.site-bg::after {
  content: "";
  position: absolute;
  inset: -25%;
  opacity: 0.42;
  background:
    radial-gradient(620px circle at 18% 12%, var(--accent-soft), transparent 62%),
    radial-gradient(760px circle at 86% 44%, var(--depth), transparent 62%),
    radial-gradient(520px circle at 42% 88%, var(--warn-soft), transparent 60%);
  filter: blur(48px);
  animation: bg-drift 34s var(--ease-io) infinite alternate;
}

:root[data-theme="proof-light"] .site-bg::after { opacity: 0.4; }

@keyframes bg-drift {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(-4%, 3%, 0) scale(1.09); }
}

.site-noise {
  z-index: -1;
  opacity: 0.028;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

:root[data-theme="proof-light"] .site-noise { opacity: 0.05; mix-blend-mode: multiply; }


/* ═══ Движение схем ═════════════════════════════════════════════════════

   У WAF на внутренней странице всегда что-то происходит: связки
   прочерчиваются, узлы загораются по очереди. Их анимации привязаны к их
   же схемам атак и один в один не переносятся, но приём переносится —
   словарь движения для примитивов, а не для конкретной картинки.

   Наши схемы собраны из трёх частей: `.viz-line` — связка, `.viz-hop` —
   узел, `.viz-filled` — прибытие. Этого хватает.

   ГЛАВНОЕ ПРАВИЛО, и оно стоило отдельного дефекта в этой же сессии:
   исходное состояние берётся ТОЛЬКО из заливки анимации. В базовом
   правиле связка сплошная, а узел непрозрачный. Тогда глобальное
   `animation: none !important` при `prefers-reduced-motion` и на печати
   оставляет схему целой. Поставь `stroke-dashoffset` в базовое правило —
   и у всех, кто просит убрать движение, схема исчезнет насовсем. */

@keyframes viz-draw {
  from { stroke-dasharray: 160; stroke-dashoffset: 160; }
  to   { stroke-dasharray: 160; stroke-dashoffset: 0; }
}

@keyframes viz-lit {
  from { opacity: 0.32; }
  to   { opacity: 1; }
}

@supports (animation-timeline: view()) {
  .viz-kb .viz-line {
    animation: viz-draw 1s var(--ease-io) both;
    animation-timeline: view();
    animation-range: entry 25% entry 85%;
  }

  .viz-kb .viz-hop,
  .viz-kb .viz-arrive {
    animation: viz-lit 0.6s var(--ease-io) both;
    animation-timeline: view();
    animation-range: entry 30% entry 90%;
  }

  /* Узлы загораются по очереди, а не разом: цепочка читается как путь, а
     не как вспышка. Сдвиг задаётся диапазоном, а не задержкой — на
     прокруточной шкале задержка не работает. */
  .viz-kb .viz-hop:nth-of-type(2) { animation-range: entry 38% entry 96%; }
  .viz-kb .viz-hop:nth-of-type(3) { animation-range: entry 46% cover 4%; }
  .viz-kb .viz-hop:nth-of-type(4) { animation-range: entry 54% cover 12%; }
}


/* ---- Печать ---------------------------------------------------------

   Бумага белая, а фоны браузер по умолчанию не печатает: значение
   `print-color-adjust` — `economy`. Тёмная тема при этом теряет фон, но
   СОХРАНЯЕТ светлый текст, и получается почти пустой лист. Замер
   2026-08-27, `/meta-tags/` в тёмной теме, media=print, на белой бумаге:

       h1        #fafafa   1.04   — заголовка на листе не видно
       текст     #a1a1aa   2.56
       крошки    #8e8e95   3.25
       ссылки    #719876   3.25

   Светлая тема печаталась нормально (17.72 / 7.73), то есть дефект
   доставался ровно тем, у кого система просит тёмную — а она у сети по
   умолчанию.

   Поэтому на печати палитра принудительно бумажная, независимо от
   выбранной темы: перечислены все три селектора, которыми тема задаётся,
   иначе `[data-theme]` перебьёт правило обратно.                       */

@media print {
  :root,
  :root[data-theme="gruvbox-dark"],
  :root[data-theme="proof-light"] {
    --bg: #ffffff;
    --bg-hero: #ffffff;
    --bg-elevated: #ffffff;
    --bg-panel: #ffffff;
    --bg-panel-soft: #ffffff;
    --index-hero-bg: #ffffff;
    --text-primary: #18181b;
    --text-secondary: #3f3f46;
    --text-muted: #52525b;
    --border-subtle: #d4d4d8;
    --border-strong: #a1a1aa;
    --accent: #3730a3;
    --accent-text: #3730a3;
    --accent-hover: #3730a3;
    --accent-secondary: #3730a3;
    --accent-fill: #3730a3;
    --accent-fg: #ffffff;
    --accent-color: #ffffff;
    --on-accent: #ffffff;
    --accent-soft: transparent;
    --shadow-sm: none;
  }

  body { background: #ffffff; }

  /* Фоновая система на бумаге не нужна и вредна: `position: fixed`
     печатается один раз на первом листе, а зерно с пятнами съедают тонер
     ради атмосферы, которой на бумаге не бывает. */
  .site-bg,
  .site-noise { display: none; }

  /* Схемы гасятся, иначе уходят на бумагу в предсостоянии: узлы при
     opacity 0.32, связки непрочерченными. На экране это начало движения,
     на листе — просто бледная схема, потому что двигаться там нечему.

     Третий раз за сессию тот же класс: добавил слой движения — добавь его
     и в блок печати. Первые два раза это были раскрытия главной и
     картинка героя. */
  .viz-kb .viz-line,
  .viz-kb .viz-hop,
  .viz-kb .viz-arrive {
    animation: none !important;
    opacity: 1 !important;
    stroke-dasharray: none !important;
    stroke-dashoffset: 0 !important;
  }

  /* Интерактивный хром на бумаге бесполезен: выпадающие меню не
     раскрываются, переключатель темы не переключает, кнопка призыва
     никуда не ведёт. Решение владельца 2026-08-27 (B-62) — убрать его
     целиком. Остаётся то, ради чего страницу и печатают: текст, схемы,
     таблицы, крошки и подвал с адресом.

     `.kb-cta` снимается вместе с заголовком и абзацем: это конверсионная
     полоса, а не часть материала (см. `kb_cta()` в сборщике — «это ХРОМ
     сайта, а не редакционный блок»). */
  .landing-header,
  .kb-toc,
  .kb-cta,
  .index-actions,
  .index-actions-note,
  [data-toggle-theme] {
    display: none !important;
  }

  /* Ссылки на бумаге не нажимаются, и подчёркивание всей прозы делает
     её пятнистой. Цвет остаётся: он отличает ссылку от текста. */
  a { text-decoration: none; }
}
