/* home.css — только то, что есть на главной и больше нигде
 *
 * Слой сети Bridge WAF. Разбор системы — `docs/design-system.md`.
 * Файл собственный, не копия комплекта: `site.css` и `_ops/site-kit/**`
 * общие на четыре спутника, и правка там задела бы соседей.
 *
 * Порядок подключения обязателен:
 *   fonts.css → tokens.css → base.css → home.css | kb.css
 */

/* ------------------------------------------------------------------- герой */

.hero { border-bottom: var(--hairline) solid var(--line); }
.hero-in {
  display: grid;
  grid-template-columns: minmax(0, 618px) minmax(0, 1fr);
  gap: var(--space-16);
  align-items: start;
  padding-block: clamp(56px, 9vw, 140px) clamp(48px, 7vw, 104px);
}
h1 {
  margin-top: var(--space-8);
  font-size: var(--fs-display);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
  font-weight: 600;
  text-wrap: balance;
}
h1 .second { display: block; color: var(--muted); font-weight: 400; }

.hero-lead { margin-top: var(--space-8); }
.hero-cta { margin-top: var(--space-16); display: flex; flex-wrap: wrap; gap: var(--space-3); }

/* ------------------------------------------- панель режимов: главный акцент

   Спека §5.2 просит интерактивный переключатель с автоциклом, который
   останавливается навсегда при первом взаимодействии. Сделан без скрипта:
   четвёртая радио-кнопка `auto` включена по умолчанию и гоняет цикл, а любой
   клик по вкладке переключает радио-группу — auto снимается и цикл больше
   не запускается. Ровно требуемое поведение, ноль строк JS.

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

.modes {
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--panel) 82%, transparent);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  overflow: hidden;
}
.modes input { position: absolute; width: 1px; height: 1px; opacity: 0; }

.modes-tabs { position: relative; display: flex; border-bottom: var(--hairline) solid var(--line); }
.modes-tabs label {
  flex: 1;
  padding: var(--space-3) var(--space-4);
  font: 400 var(--fs-label)/1.4 var(--font-sans);
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--dim); text-align: center; cursor: pointer;
  border-right: var(--hairline) solid var(--line);
  transition: color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.modes-tabs label:last-of-type { border-right: 0; }
.modes-tabs label:hover { color: var(--muted); }

/* индикатор скользит между позициями, а не перерисовывается рамкой */
.modes-ind {
  position: absolute; left: 0; bottom: -1px;
  width: calc(100% / 3); height: 2px;
  background: var(--accent);
  transition: transform var(--dur) var(--ease-out), background var(--dur) var(--ease-out);
}

.modes-stack { display: grid; }
.modes-panel {
  grid-area: 1 / 1;
  padding: var(--space-6);
  opacity: 0; visibility: hidden;
  transition: opacity var(--dur) var(--ease-out), visibility var(--dur);
}
.modes-panel .log { margin-top: var(--space-4); }

/* --- ручной выбор: auto снят, цикл не идёт --- */
#mode-off:checked     ~ .modes-stack .modes-panel[data-mode="off"],
#mode-monitor:checked ~ .modes-stack .modes-panel[data-mode="monitor"],
#mode-block:checked   ~ .modes-stack .modes-panel[data-mode="block"] {
  opacity: 1; visibility: visible; animation: none;
}
#mode-off:checked     ~ .modes-tabs label[for="mode-off"],
#mode-monitor:checked ~ .modes-tabs label[for="mode-monitor"],
#mode-block:checked   ~ .modes-tabs label[for="mode-block"] {
  color: var(--text); background: var(--panel-2);
}
#mode-off:checked     ~ .modes-tabs .modes-ind { transform: translateX(0);    background: var(--mode-off); }
#mode-monitor:checked ~ .modes-tabs .modes-ind { transform: translateX(100%); background: var(--mode-monitor); }
#mode-block:checked   ~ .modes-tabs .modes-ind { transform: translateX(200%); background: var(--mode-block); }

/* --- автоцикл: три слота по 6.5с, сдвиг отрицательной задержкой --- */
@keyframes panel-cycle {
  0%      { opacity: 0; visibility: hidden; }
  2%      { opacity: 1; visibility: visible; }
  31%     { opacity: 1; visibility: visible; }
  33%,
  100%    { opacity: 0; visibility: hidden; }
}
@keyframes ind-cycle {
  0%,  30%   { transform: translateX(0);    background: var(--mode-off); }
  33%, 63%   { transform: translateX(100%); background: var(--mode-monitor); }
  66%, 96%   { transform: translateX(200%); background: var(--mode-block); }
  100%       { transform: translateX(0);    background: var(--mode-off); }
}
@keyframes tab-cycle {
  0%   { color: var(--dim); background: transparent; }
  2%, 31% { color: var(--text); background: var(--panel-2); }
  33%, 100% { color: var(--dim); background: transparent; }
}
#mode-auto:checked ~ .modes-stack .modes-panel,
#mode-auto:checked ~ .modes-tabs label { animation: panel-cycle 19.5s var(--ease-out) infinite; }
#mode-auto:checked ~ .modes-tabs label { animation-name: tab-cycle; }
#mode-auto:checked ~ .modes-stack .modes-panel[data-mode="monitor"],
#mode-auto:checked ~ .modes-tabs label[for="mode-monitor"] { animation-delay: -13s; }
#mode-auto:checked ~ .modes-stack .modes-panel[data-mode="block"],
#mode-auto:checked ~ .modes-tabs label[for="mode-block"] { animation-delay: -6.5s; }
#mode-auto:checked ~ .modes-tabs .modes-ind { animation: ind-cycle 19.5s var(--ease-out) infinite; }

/* фокус с клавиатуры виден на подписи: сама радио-кнопка скрыта */
#mode-off:focus-visible     ~ .modes-tabs label[for="mode-off"],
#mode-monitor:focus-visible ~ .modes-tabs label[for="mode-monitor"],
#mode-block:focus-visible   ~ .modes-tabs label[for="mode-block"] {
  outline: 2px solid var(--accent-secondary); outline-offset: -2px; color: var(--text);
}

.modes-state {
  font-size: var(--fs-sm);
  line-height: 1.6;
  color: var(--muted);
}
.modes-state b { color: var(--text); font-weight: 500; }

.log {
  font: 400 12px/1.9 var(--font-sans);
  color: var(--muted);
  background: var(--bg);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
  padding: var(--space-4);
  overflow-x: auto;
}
.log div { white-space: pre; }
.log .t { color: var(--dim); }
.log .w { color: var(--accent-secondary); }
.log .r { color: var(--text); }
.log .muted { color: var(--dim); }

/* ------------------------------------------------------------- адаптивность */

@media (max-width: 1080px) {
  .hero-in { grid-template-columns: minmax(0, 1fr); gap: var(--space-12); }
  .cells-4 { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 760px) {
  .docs-grid, .facts { grid-template-columns: 1fr; }
  .hero-cta { margin-top: var(--space-10); }
  .hero-cta .btn { flex: 1 1 auto; }
}

/* На печати прокруточной шкалы нет: без этого блока всё, до чего читатель не
   долистал, ушло бы в PDF прозрачным. Та же причина — у скриншотов страницы
   целиком. */
@media print {
  .sec-head > *, .cells > * > *, .docs-grid a > *, .faq > .index-faq-item > *,
  .facts > div > *, .final .wrap > * { animation: none !important; opacity: 1 !important; transform: none !important; }
  .hero-art, .viz { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  * { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
}

/* --- ведро токенов: лимит на домен ---------------------------------------- */

@keyframes drip  { 0% { transform: translateY(0); opacity: 0; }
                  10% { opacity: 1; }
                  90% { opacity: 1; }
                 100% { transform: translateY(52px); opacity: 0; } }
@keyframes level { 0% { transform: scaleY(1); } 62% { transform: scaleY(0.06); }
                  70% { transform: scaleY(0.06); } 100% { transform: scaleY(1); } }
@keyframes flash429 { 0%, 58% { opacity: 0; } 64%, 76% { opacity: 1; } 84%, 100% { opacity: 0; } }

.drop { fill: var(--accent-secondary); animation: drip 1.9s linear infinite; }
.drop:nth-of-type(2) { animation-delay: -0.65s; }
.drop:nth-of-type(3) { animation-delay: -1.3s; }
.bucket-level { fill: var(--accent); opacity: 0.65; transform-origin: 50% 100%;
  animation: level 7s var(--ease) infinite; }
.chip-429 { animation: flash429 7s linear infinite; }
.chip-429 rect { fill: var(--stop-soft); stroke: var(--stop); }
.chip-429 text { font: 400 10px var(--font-sans); fill: var(--stop); }

/* --- партия записей: копятся и уходят пачкой ------------------------------ */

@keyframes gather { 0% { opacity: 0; transform: translateX(-10px); }
                   12% { opacity: 1; transform: none; }
                   64% { opacity: 1; transform: none; }
                   72% { opacity: 0; transform: translateX(252px); }
                  100% { opacity: 0; transform: translateX(252px); } }
@keyframes sweep  { 0%, 62% { opacity: 0; } 68% { opacity: 1; } 88%, 100% { opacity: 0; } }

.ev { fill: var(--warn); animation: gather 4.4s var(--ease) infinite; }
.ev:nth-of-type(2) { animation-delay: -0.5s; }
.ev:nth-of-type(3) { animation-delay: -1s; }
.ev:nth-of-type(4) { animation-delay: -1.5s; }
.ev:nth-of-type(5) { animation-delay: -2s; }
.batch-sweep { stroke: var(--accent); stroke-width: 1.5; fill: none; animation: sweep 4.4s linear infinite; }
/* Записи, доехавшие до приёмника: появляются ровно тогда, когда партия уходит,
   иначе приёмник всё время пустой и стрелка ведёт в никуда. */
@keyframes landed { 0%, 68% { opacity: 0; } 76%, 100% { opacity: 0.55; } }
.landed { fill: var(--warn); opacity: 0; animation: landed 4.4s linear infinite; }

/* --- слои: где этот фильтр стоит, а где нет ------------------------------- */

@keyframes hover-lift { to { transform: translate(0, -6px); } }

.slab-off { fill: var(--panel-2); stroke: var(--viz-line); }
.slab-on  { fill: var(--accent-soft); stroke: var(--accent); }
.slab-grp { transition: transform 0.4s var(--ease); }
.cells > *:hover .slab-grp[data-on] { transform: translate(0, -6px); }

/* --- микрографика в полосе фактов ----------------------------------------- */

.facts > div { position: relative; overflow: hidden; }

/* --- уголки-скобки на панели, приём koyeb --------------------------------- */

.bracketed { position: relative; }
.bracketed::before, .bracketed::after {
  content: ""; position: absolute; width: 10px; height: 10px;
  border: 1px solid var(--dim); pointer-events: none;
}
.bracketed::before { top: -5px; left: -5px; border-right: 0; border-bottom: 0; }
.bracketed::after { bottom: -5px; right: -5px; border-left: 0; border-top: 0; }

/* --- кнопки и вкладки: ускоренная реакция, как у koyeb -------------------- */

.btn { transition: background var(--t-ui), color var(--t-ui), border-color var(--t-ui), transform var(--t-ui); }
.btn:active { transform: translateY(1px); }
.btn-primary:hover { box-shadow: 0 0 0 4px var(--glow); }

/* --- уважение к настройке «меньше движения» ------------------------------- */

/* На печати прокруточной шкалы нет: без этого блока всё, до чего читатель не
   долистал, ушло бы в PDF прозрачным. Та же причина — у скриншотов страницы
   целиком. */
@media print {
  .sec-head > *, .cells > * > *, .docs-grid a > *, .faq > .index-faq-item > *,
  .facts > div > *, .final .wrap > * { animation: none !important; opacity: 1 !important; transform: none !important; }
  .hero-art, .viz { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .caret, .pkt, .gate, .tally, .drop, .bucket-level,
  .chip-429, .ev, .batch-sweep, .landed { animation: none !important; }
  .bucket-level { transform: scaleY(0.55); }
  .ev, .tally, .caret { opacity: 1; }
  .landed { opacity: 0.55; }
  .chip-429 { opacity: 0; }
  @supports (animation-timeline: view()) {
    .sec-head > *, .cells > * > *, .docs-grid a > *, .faq > .index-faq-item > *,
    .facts > div > *, .final .wrap > * {
      animation: none; opacity: 1; transform: none;
    }
  }
}

/* задержки потока вынесены в классы: nth-of-type считает <rect> вперемешку с
   разметкой сцены и ломается от любой правки рисунка */
.d1 { animation-delay: -1.1s; }
.d2 { animation-delay: -2.3s; }
.d3 { animation-delay: -3.4s; }
.d4 { animation-delay: -4.2s; }
.viz svg .bucket-level, .viz svg .ev, .viz svg .pkt, .viz svg .drop,
.viz svg .chip-429, .viz svg .slab-grp, .viz svg .landed,
.path svg .ride, .path svg .ride-copy { transform-box: fill-box; }
.bucket-level { transform-origin: 50% 100%; }

/* ==================================================== ИЗОБРАЖЕНИЯ И ПАРАЛЛАКС
   Параллакс у спеки §7 — на rAF-цикле. Здесь он на `animation-timeline: view()`:
   ту же работу делает сама прокруточная шкала CSS, и это не стоит ни строки
   скрипта. Где браузер её не знает, картинка просто стоит на месте.
   ------------------------------------------------------------------------- */

@keyframes par-slow { from { transform: translate3d(0, -4%, 0) scale(1.06); }
                      to   { transform: translate3d(0,  4%, 0) scale(1.06); } }

.bg-art { position: absolute; inset: 0; z-index: -1; overflow: hidden; }
.bg-art img {
  width: 100%; height: 118%;
  object-fit: cover;
  will-change: transform;
  transform: translate3d(0, -2%, 0) scale(1.06);
}
@supports (animation-timeline: view()) {
  .bg-art img { animation: par-slow linear both; animation-timeline: view(); animation-range: cover; }
}

/* Мост — знак сети. Взят с живого bridge-cdn.com, где стоит в герое главной,
   и это единственная графика, по которой посетитель узнаёт, что перешёл
   внутри одной экосистемы, а не на чужой сайт.

   Исходник — PNG на сплошной подложке #282828 (тёмная) и #f3eae1 (светлая):
   главный растворяет её через mix-blend-mode: lighten / darken, но это
   требует, чтобы подложка кадра совпадала с подложкой файла, то есть
   перекрасило бы весь наш герой в тёплый. Вместо этого подложка вырезана в
   альфу, а дальше оставлена ТОЛЬКО альфа: цвет даёт CSS. Один файл 60 КиБ
   обслуживает обе темы вместо двух по 180 и 107.

   Тон взят с оригинала: тёплое золото #b0a088 на тёмной, тёмно-серое
   #403838 на светлой. */
.bg-bridge {
  -webkit-mask-image: url("/images/bg/bridge-mask-512.webp");
  mask-image: url("/images/bg/bridge-mask-512.webp");
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: 108% 96%;
  mask-position: 108% 96%;
  -webkit-mask-size: min(1080px, 78%) auto;
  mask-size: min(1080px, 78%) auto;
  background-color: #b0a088;
  opacity: 0.62;
}
:root[data-theme="proof-light"] .bg-bridge {
  background-color: #403838;
  opacity: 0.34;
}
@media (max-width: 900px) {
  .bg-bridge {
    -webkit-mask-image: url("/images/bg/bridge-mask-512.webp");
    mask-image: url("/images/bg/bridge-mask-512.webp");
    -webkit-mask-size: 150% auto;
    mask-size: 150% auto;
    opacity: 0.3;
  }
}
.hero .bg-art::after {
  content: "";
  position: absolute; inset: 0;
  background:
    linear-gradient(100deg, var(--bg) 26%, color-mix(in srgb, var(--bg) 92%, transparent) 50%, transparent 78%),
    linear-gradient(to bottom, transparent 55%, var(--bg) 96%);
}
:root[data-theme="proof-light"] .hero .bg-art img { opacity: 0.3; }
/* Под заголовком и лидом снимок поднимал подложку до 213 — дисплей давал
   4.3:1 при норме 7. Спека §5.1: поднимать остановку градиента, не гасить
   картинку. Левая треть уходит почти в чистый фон, справа снимок остаётся. */
:root[data-theme="proof-light"] .hero .bg-art::after {
  background:
    linear-gradient(100deg, var(--bg) 30%, color-mix(in srgb, var(--bg) 96%, transparent) 52%, transparent 80%),
    linear-gradient(to bottom, transparent 55%, var(--bg) 96%);
}

/* §4.3 интерстишл: единственная полноширинная картинка с текстом на странице */
/* Полоса перестала быть украшением: на ней стоит утверждение о цене слепого
   включения и переход к продукту. Высота теперь задаётся содержимым, а не
   числом, иначе текст упрётся в края на узких экранах. */
.interstitial {
  position: relative;
  min-height: clamp(380px, 52vh, 620px);
  display: grid; align-items: center;
  overflow: hidden;
  margin-block: clamp(72px, 10vh, 140px);
  padding-block: clamp(48px, 7vh, 88px);
}
/* Текст сидит на подложке с размытием, а не прямо на снимке: замер показал
   4.4:1 у заголовка при норме 7 и 1.6:1 у лида при норме 4.5 — зелёный поток
   проходит ровно под строками. Гасить снимок сильнее нельзя, он тогда
   исчезает; подложка держит контраст и оставляет картинку видимой вокруг. */
.interstitial-in {
  position: relative; z-index: 1;
  width: calc(100% - var(--gutter) * 2);
  max-width: 820px;
  margin-inline: auto;
  padding: clamp(28px, 4vw, 48px) clamp(24px, 4vw, 56px);
  text-align: center;
  background: color-mix(in srgb, var(--bg) 84%, transparent);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius);
}
.interstitial-in .lead { color: var(--text); opacity: 0.86; }
.interstitial-in .eyebrow { display: block; margin-bottom: var(--space-4); }
.interstitial-in .h2 { margin: 0; }
.interstitial-in .lead { margin: var(--space-6) auto 0; max-width: 56ch; }
.interstitial-in .row { margin-top: var(--space-8); display: flex;
  justify-content: center; flex-wrap: wrap; gap: var(--space-3); }
.interstitial img {
  position: absolute; inset: 0;
  width: 100%; height: 130%;
  object-fit: cover; object-position: center 45%;
  opacity: 0.78;
  will-change: transform;
  transform: translate3d(0, -3%, 0) scale(1.04);
}
/* Рендеры сделаны для тёмной темы: на белом фоне снимок даёт серое пятно, а
   подложка текста читается дырой в нём. В светлой теме картинка уходит почти
   в след — остаётся зелёная взвесь по краям, ради которой она и стоит. */
:root[data-theme="proof-light"] .interstitial img { opacity: 0.16; }
:root[data-theme="proof-light"] .interstitial-in {
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
}
@supports (animation-timeline: view()) {
  .interstitial img { animation: par-slow linear both; animation-timeline: view(); animation-range: cover; }
}
/* швов быть не должно: картинка растворяется в фон со всех сторон */
.interstitial::after {
  content: "";
  position: absolute; inset: 0; pointer-events: none;
  background:
    radial-gradient(70% 60% at 50% 50%, color-mix(in srgb, var(--bg) 82%, transparent) 0%, transparent 78%),
    linear-gradient(to bottom, var(--bg) 0%, transparent 26%, transparent 74%, var(--bg) 100%),
    linear-gradient(to right, var(--bg) 0%, transparent 14%, transparent 86%, var(--bg) 100%);
}

/* финальная полоса: та же мембрана отражением, поток идёт к кнопке */
.final { position: relative; isolation: isolate; overflow: hidden; }
.final .bg-art img { transform: scaleX(-1) scale(1.06); opacity: 0.22; }
:root[data-theme="proof-light"] .final .bg-art img { opacity: 0.14; }
/* Середина градиента была полностью прозрачной, и текст блока «кто это
   ведёт» садился прямо на светлый участок снимка: замер после смены
   гарнитуры дал 3.57:1 при норме 4.5. Гарнитура тут ни при чём — она лишь
   сдвинула рамку строки на другой пиксель; дыра в подложке была всегда.
   Поднят стоп, а не погашен снимок: по краям картинка видна как прежде,
   под строками появляется пол. */
.final .bg-art::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(to bottom,
    var(--bg) 0%,
    color-mix(in srgb, var(--bg) 72%, transparent) 30%,
    color-mix(in srgb, var(--bg) 72%, transparent) 70%,
    var(--bg) 100%);
}
@supports (animation-timeline: view()) {
  .final .bg-art img { animation: none; }
}

/* ================================================================ ДЕТАЛИ §12 */

/* шапка: фон и граница проявляются после 40px прокрутки. Спека вешает это на
   слушатель scroll; здесь — на `animation-timeline: scroll()`, ту же работу
   делает прокруточная шкала CSS. */
.landing-header {
  background: transparent;
  border-bottom-color: transparent;
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}
@keyframes nav-solid {
  to { background: color-mix(in srgb, var(--bg) 86%, transparent); border-bottom-color: var(--line); }
}
@supports (animation-timeline: scroll()) {
  .landing-header {
    animation: nav-solid linear both;
    animation-timeline: scroll();
    animation-range: 0 40px;
  }
}
@supports not (animation-timeline: scroll()) {
  .landing-header { background: var(--bg); border-bottom-color: var(--line); }
}

/* кнопка действия: блик слева направо раз в 6с */
.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) infinite;
}
@keyframes shimmer { 0%, 72% { transform: translateX(0) skewX(-20deg); }
                     88%, 100% { transform: translateX(420%) skewX(-20deg); } }

/* документация: стрелка уезжает вправо */
.docs-grid h3::after { content: " \2192"; color: var(--dim); display: inline-block;
  transition: transform var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out); }
.docs-grid a:hover h3::after { transform: translateX(4px); color: var(--accent-hover); }

/* ========================================= ДИПТИХ: АСИММЕТРИЯ ОТКАЗОВ §10

   Спека предлагала перевес по прокрутке: правая панель опускается, левая
   поднимается. Сделал — и это читалось как сломанная вёрстка: ячейки
   разъезжались, а между ними проступал фон-разделитель сетки жирной полосой,
   потому что сетка рисует линии зазором в 1px, а не рамками.

   Асимметрия осталась, но выражена тем, что сдвинуть нельзя: правая колонка
   шире, у неё край цвета отказа и приподнятая подложка, левая приглушена.
   Вес на стороне той ошибки, которая дороже, — и ничто не едет.
   ------------------------------------------------------------------------- */

.asymmetry { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); align-items: stretch; }
.asymmetry > *:first-child { color: var(--muted); }
.asymmetry > *:first-child .cell-title { color: var(--muted); }
.asymmetry > *:last-child {
  background: var(--bg-raise);
  box-shadow: inset 3px 0 0 var(--mode-block);
  --card-accent: var(--mode-block);
}
.asymmetry > *:last-child .cell-body { color: var(--text); }

@media (max-width: 760px) {
  .asymmetry { grid-template-columns: 1fr; }
}

/* ===================================================== ПУТЬ ЗАПРОСА §11
   Схема CLIENT → EDGE → WAF → ORIGIN. Точка едет по пути, на рубеже её
   поведение зависит от режима: проходит, помечается, отказывается.
   Три дорожки нарисованы разом одна под другой — так видно, что различие
   не в скорости и не в маршруте, а в исходе.
   ------------------------------------------------------------------------- */

.path { margin-top: var(--space-12); }
.path svg { width: 100%; height: auto; overflow: visible; }
/* На узком экране схема ужимается до нечитаемых подписей: 760 единиц вьюбокса
   в 350 пикселях дают кегль около 4px. Даём ей прокрутку в своём контейнере —
   страница по горизонтали при этом не едет. */
@media (max-width: 720px) {
  .path { overflow-x: auto; padding-bottom: var(--space-3); }
  .path svg { min-width: 620px; }
}
.path-lane { stroke: var(--viz-line); stroke-width: 1; fill: none; }
.path-node { fill: var(--bg-raise); stroke: var(--viz-line); }
.path-label { font: 400 10px var(--font-sans); fill: var(--dim); letter-spacing: 0.08em; }
.path-label-on { fill: var(--text); }
.path-waf { fill: none; stroke: var(--viz-line); stroke-dasharray: 4 5; stroke-width: 1; }

@keyframes ride-pass { 0% { transform: translateX(0); opacity: 0; }
                       6% { opacity: 1; }
                      94% { opacity: 1; }
                     100% { transform: translateX(540px); opacity: 0; } }
/* block доезжает только до кромки рубежа и гаснет — до ORIGIN не доходит */
@keyframes ride-stop { 0% { transform: translateX(0); opacity: 0; }
                      12% { opacity: 1; }
                      44% { transform: translateX(240px); opacity: 1; }
                      54% { transform: translateX(240px) scale(2.4); opacity: 0; }
                     100% { transform: translateX(240px) scale(2.4); opacity: 0; } }
/* копия уходит вниз в журнал — в обоих режимах, где что-то записывается */
@keyframes copy-monitor { 0%, 42% { opacity: 0; transform: translate(310px, 0); }
                         50% { opacity: 1; }
                         84% { opacity: 1; transform: translate(310px, 128px); }
                        100% { opacity: 0; transform: translate(310px, 128px); } }
@keyframes copy-block   { 0%, 42% { opacity: 0; transform: translate(240px, 0); }
                         50% { opacity: 1; }
                         84% { opacity: 1; transform: translate(310px, 48px); }
                        100% { opacity: 0; transform: translate(310px, 48px); } }
@keyframes step-lit { 0%, 100% { opacity: 0.25; } 12%, 26% { opacity: 1; } }

.ride { r: 4; animation: ride-pass 3.4s linear infinite; }
.ride-off     { fill: var(--mode-off); }
.ride-monitor { fill: var(--mode-monitor); }
.ride-block   { fill: var(--mode-block); animation-name: ride-stop; }
.ride-copy    { fill: var(--mode-monitor); }
.copy-monitor { animation: copy-monitor 3.4s linear infinite; }
.copy-block   { fill: var(--mode-block); animation: copy-block 3.4s linear infinite; }
.path-tick    { stroke-width: 3; }
/* Цвет метки — классом. Атрибут stroke= перебивается любым правилом CSS,
   которое задаёт то же свойство, и делает это молча. */
.path-tick-off     { stroke: var(--mode-off); }
.path-tick-monitor { stroke: var(--mode-monitor); }
.path-tick-block   { stroke: var(--mode-block); }
.path-tick-accent  { stroke: var(--accent); }
.path-node-off { opacity: 0.35; }
.path-x       { stroke: var(--mode-block); stroke-width: 1.5; fill: none; opacity: 0.7; }
.path-step    { animation: step-lit 3.4s linear infinite; }
.path-step-2  { animation-delay: 0.28s; }
.path-step-3  { animation-delay: 0.56s; }

/* ============================================== ПРОИЗВОДИТЕЛЬНОСТЬ §14

   `content-visibility: auto` здесь пробовался и снят. Выигрыш на десятке
   секций небольшой, а `contain-intrinsic-size` приходится угадывать — при
   промахе полоса прокрутки прыгает на скролле, и страница целиком (печать,
   снимок, поиск по странице) отдаётся пустой ниже первого экрана.

   Пауза лупов за пределами вьюпорта (спека §9.3) без скрипта недостижима:
   `animation-play-state` прокруточной шкалой не управляется. Лупы здесь
   дешёвые — трансформации и прозрачность на паре десятков узлов, без
   layout и paint, — поэтому оставлены идти. Если понадобится строго,
   это IntersectionObserver и +12 строк JS.
   ------------------------------------------------------------------------- */

/* ==================================== ДОСТУПНОСТЬ И ПЕЧАТЬ (сводно) §15
   Блок стоит последним намеренно: он гасит и то, что добавлено выше по
   файлу. Страница обязана остаться полностью читаемой и понятной без
   единого движения — это критерий приёмки спеки §17.
   ------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .site-bg::after { animation: none; }
  .bg-art img, .interstitial img { transform: none !important; animation: none !important; }
  .btn-primary::after { display: none; }
  /* автоцикл не крутится: показываем monitor — режим, с которого продукт
     просит начинать */
  #mode-auto:checked ~ .modes-stack .modes-panel,
  #mode-auto:checked ~ .modes-tabs label,
  #mode-auto:checked ~ .modes-tabs .modes-ind { animation: none !important; }
  #mode-auto:checked ~ .modes-stack .modes-panel[data-mode="monitor"] {
    opacity: 1 !important; visibility: visible !important;
  }
  #mode-auto:checked ~ .modes-tabs label[for="mode-monitor"] { color: var(--text); background: var(--panel-2); }
  #mode-auto:checked ~ .modes-tabs .modes-ind { transform: translateX(100%); background: var(--mode-monitor); }
  .asymmetry > * { transform: none !important; opacity: 1 !important; }
}

@media print {
  .site-bg, .site-noise, .viz, .path,
  .bg-art, .interstitial, .btn-primary::after { display: none !important; }
  .modes-panel { opacity: 1 !important; visibility: visible !important; position: static; }
  .faq-body { grid-template-rows: 1fr !important; }
  .landing-header { position: static; }
}

/* ============================================ СЛОЙ ОЖИВЛЕНИЯ ПАНЕЛИ (JS)

   Классы ставит `js/home.js`. Всё ниже — надстройка: без скрипта панель
   работает, просто без печати, вспышки и счётчика.
   ------------------------------------------------------------------------- */

/* печать строки: не посимвольно перебором узлов, а отсечкой по ширине
   ступенями — тот же эффект машинописи одним свойством */
@keyframes line-type { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0 0 0 0); } }
@keyframes line-drop { from { opacity: 0; } to { opacity: 1; } }

.js-on .modes-panel.is-live .log > div {
  animation: line-drop 0.01s linear both, line-type 0.5s steps(30) both;
  animation-delay: calc(var(--i, 0) * 160ms);
}

/* вердикт вспыхивает и гаснет — §5.2 */
@keyframes verdict-flash {
  0%   { box-shadow: 0 0 0 0 transparent; }
  30%  { box-shadow: 0 0 12px 1px currentColor; }
  100% { box-shadow: 0 0 0 0 transparent; }
}
.vd { border-radius: 2px; padding: 0 2px; }
.vd-stop { color: var(--mode-block); }
.js-on .modes-panel.is-live .vd {
  animation: verdict-flash 0.42s var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * 160ms + 0.3s);
}

/* в block путь зачёркивается слева направо */
.pth { position: relative; }
.pth::after {
  content: ""; position: absolute; left: 0; right: 0; top: 50%; height: 1px;
  background: var(--mode-block);
  transform: scaleX(0); transform-origin: left;
}
.js-on .modes-panel.is-live .pth::after {
  animation: strike 0.22s var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * 160ms + 0.45s);
}
@keyframes strike { to { transform: scaleX(1); } }

.modes-count {
  margin: var(--space-4) 0 0;
  font: 400 var(--fs-label)/1 var(--font-sans);
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--dim);
}
.modes-count [data-count] { color: var(--text); font-size: var(--fs-sm); }
.modes-panel[data-mode="monitor"] .modes-count [data-count] { color: var(--mode-monitor); }
.modes-panel[data-mode="block"]   .modes-count [data-count] { color: var(--mode-block); }

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

   Каретка живёт ТОЛЬКО пока идёт печать. Прежде она стояла на самом
   `[data-type]` и потому мигала вечно: три готовых значения выглядели как
   три недозаполненных поля ввода. Каретка — метафора набора; когда набор
   кончился, она означает «здесь чего-то не хватает». */
.js-on [data-type][data-typing]::after {
  content: "\258F"; color: var(--accent-secondary);
  animation: blink 1.1s steps(1) infinite;
}

@media (prefers-reduced-motion: reduce) {
  .js-on .modes-panel.is-live .log > div,
  .js-on .modes-panel.is-live .vd,
  .js-on .modes-panel.is-live .pth::after { animation: none !important; }
  .pth::after { transform: scaleX(1); }
  .js-on [data-type][data-typing]::after { content: none; }
}

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

.hero-note, .final-note {
  margin-top: var(--space-6);
  font-size: var(--fs-sm);
  line-height: 1.6;
  color: var(--dim);
}
/* Финальная строка лежит поверх снимка и акцентного пятна: на --dim она
   давала 3.9:1. Это снятие возражения перед кнопкой, ему и по смыслу
   положен вес. */
.final-note { color: var(--muted); }
.hero-note code, .final-note code { font-family: var(--font-sans); color: var(--muted); }
.final-note { text-align: center; }
