/* ============================================================
   CRAFT · SLIDES

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

   Craft-токены задают палитру. Geologica работает в заголовках
   и цифрах, Onest в тексте, mono только внутри кода.
   Прописных и декоративного движения нет.
   ============================================================ */

:root {
  /* Слайды используют те же смысловые роли, что и интерфейсы. Текстовый
     акцент читают, меткой рисуют линии и точки, заливка держит белый текст. */
  --bg: var(--craft-bg);
  --surface: var(--craft-surface);
  --surface-muted: var(--craft-surface-muted);
  --surface-sunken: var(--craft-sunken);
  --line: var(--craft-line);
  --line-strong: var(--craft-line-strong);
  --text: var(--craft-text);
  --muted: var(--craft-muted);
  --accent: var(--craft-accent);
  --accent-mark: var(--craft-accent-mark);
  --accent-fill: var(--craft-accent-fill);
  --on-accent: var(--craft-on-accent);
  --on-accent-muted: var(--craft-on-accent-muted);
  --on-accent-line: var(--craft-on-accent-line);
  --code-text: var(--craft-code-text);
  --selection: var(--craft-selection);

  /* Контур блока: яркий у изображения, спокойный у схемы и разлиновки. */
  --edge-strong: var(--text);
  --edge: var(--muted);

  --stage-pad: 0px;
  --font-display: var(--craft-font-display);
  --font-text: var(--craft-font-text);
  --font-mono: var(--craft-font-mono);
  --pad-x: 3.6cqw;
  --pad-y: 5cqh;
}

.slide {
  font-family: var(--font-text); color: var(--text);
  /* Запасные значения для разлиновки: см. .pad, где те же токены
     заданы в долях слайда. */
  --rule: 1px;
  --rule-strong: 3px;
  --chart-bar: 3.2cqh;
  --chart-gap: 3.4cqh;
}
.pad {
  padding: calc(var(--pad-y) + 6cqh) var(--pad-x);
  justify-content: center; gap: 3cqh;

  /* Разлиновка задана в долях слайда, а не в пикселях: в обзоре плитка
     мельче кадра в пять-шесть раз, и линия обязана худеть вместе с ней,
     иначе во столько же раз выглядит жирнее нужного. Тоньше пикселя
     линия не бывает, поэтому пол в 1px: на плитке вся разлиновка
     сходится к нему и потому выглядит ровно, без скачков толщины.
     На показе значения возвращаются к привычным 1px и 3px.
     cqw здесь считается от .slide — контейнера слайда. */
  --rule: max(1px, .0625cqw);
  --rule-strong: max(1px, .1875cqw);
}

/* ============================================================
   ПРИМИТИВЫ

   Они задают визуальную грамматику, но не диктуют композицию.
   Локальные значения можно менять через custom properties прямо
   на конкретном слайде: --gap, --columns, --column-ratio.
   ============================================================ */

/* поверхности */
.surface-accent { background: var(--accent-fill); color: var(--on-accent); }
/* На яркой заливке нейтрали строятся только прозрачностью белого. */
.surface-accent,
.comparison-side.is-after,
.flow-node--accent,
.data-table tr[data-total] {
  --text: var(--on-accent);
  --muted: var(--on-accent-muted);
  --line: var(--on-accent-line);
  --accent: var(--on-accent);
  --accent-mark: var(--on-accent);
  /* Красным по красному тонировать нечего — на заливке выделение белое. */
  --selection: var(--craft-selection-on-accent);
}
/* Панель допустима только внутри композиции, не как третий фон страницы. */
.surface-panel  { background: var(--surface); color: var(--text); }
.surface-code   { background: var(--surface-sunken); color: var(--text); }

/* композиция */
.stack {
  display: flex;
  flex-direction: column;
  gap: var(--gap, 2.4cqh);
}
/* Стандартный поясняющий блок: локальный заголовок и короткая заметка.
   Комбинируется с columns, frame, графиками и другими примитивами. */
.content-block {
  display: grid;
  align-content: center;
  gap: var(--gap, 1.4cqh);
  max-width: var(--content-block-width, 32ch);
}
.cluster {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--gap, 1.4cqw);
}
.columns {
  display: grid;
  grid-template-columns: var(--column-ratio, repeat(var(--columns, 2), minmax(0, 1fr)));
  gap: var(--gap, 3cqw);
}
/* Основной материал и дополнительная информация справа используют
   одну пропорцию независимо от типа основного содержимого. */
.content-aside {
  --content-block-width: none;
  grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr);
  gap: 3cqw;
}
/* Для основного блока с подписями у правого края нужен больший разрыв,
   чтобы подписи не сливались с пояснением в соседней колонке. */
.content-aside--separated { gap: 6cqw; }
.place-center { justify-content: center; }
.place-fill { flex: 1; min-height: 0; }
.bleed-x {
  margin-inline: calc(-1 * var(--pad-x));
  padding-inline: var(--pad-x);
}

/* изображение без собственной композиции */
.media-cover { width: 100%; height: 100%; object-fit: cover; display: block; }
.media-contain { width: 100%; height: 100%; object-fit: contain; display: block; }

/* Строка списка: тонкая линия сверху, плотность задаёт --row-pad.
   На этой основе строятся оба списка колоды; различает их только маркер. */
.plain-list, .bullets { list-style: none; margin: 0; padding: 0; display: grid; }
.plain-list > li, .bullets > li {
  padding: var(--row-pad, 2.6cqh) 0;
  border-top: var(--rule) solid var(--line);
}
.plain-list > li:first-child, .bullets > li:first-child { border-top: 0; }
/* Плотность следует фактическому числу пунктов; кегль не меняется. */
.plain-list:has(> li:nth-child(4)), .bullets:has(> li:nth-child(4)) { --row-pad: 1.7cqh; }
.plain-list:has(> li:nth-child(6)), .bullets:has(> li:nth-child(6)) { --row-pad: 1cqh; }

/* Ненумерованный список: залитый акцентный круг стоит непосредственно
   перед текстом. Отдельной колонки и вертикального разделителя нет. */
.plain-list {
  --marker-size: .72cqw;
  --marker-gap: 1.2cqw;
}
.plain-list > li {
  display: grid;
  grid-template-columns: var(--marker-size) 1fr;
  column-gap: var(--marker-gap);
  align-items: center;
}
.plain-list > li::before {
  content: "";
  width: var(--marker-size); height: var(--marker-size);
  border-radius: 50%;
  background: var(--accent-mark);
}


/* Векторная диагональ дорожек: линии выходят за границы viewBox,
   поэтому узор без разрывов закрывает слайд от края до края. */
.lanes {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  overflow: hidden;
}
.lanes path {
  fill: none;
  stroke: currentColor;
  stroke-width: 48;
  opacity: .09;
}

/* --- служебка: заголовок слайда сверху слева, номер снизу справа.
   Оба всегда в одном месте; заголовок необязателен. --- */
.slide-title, .slide-number {
  position: absolute;
  margin: 0; pointer-events: none;
}
.slide-title, .comparison-title {
  left: var(--pad-x); top: var(--pad-y);
  font: 600 2.4cqw/1.12 var(--font-display);
  letter-spacing: -.02em;
  max-width: 70cqw;
}
/* номер как на майке: крупно, но на всех слайдах одинаково */
.slide-number {
  right: var(--pad-x); bottom: var(--pad-y);
  font: 700 2.9cqw/1 var(--font-display); letter-spacing: -.03em;
  opacity: .92;
}
/* Общее число страниц автор не показывает: на слайде только текущий номер.
   Счётчик «/ всего» остаётся в панели подсказок. */
.slide-number span { display: none; }

/* --- типографика --- */
.type-hero {
  font: 700 7.2cqw/1.04 var(--font-display);
  letter-spacing: -.03em; margin: 0;
}
.type-title {
  font: 700 3.9cqw/1.08 var(--font-display);
  letter-spacing: -.025em; margin: 0;
}
.type-heading { font: 600 2.1cqw/1.25 var(--font-display); letter-spacing: -.02em; margin: 0; }
.type-lede { font: 400 2.35cqw/1.46 var(--font-text); margin: 0; max-width: 36ch; }
.type-note { font: 400 1.6cqw/1.5 var(--font-text); margin: 0; color: var(--muted); }
.type-metric, .comparison-value {
  font: 700 var(--metric-size, 6.6cqw)/1 var(--font-display);
  letter-spacing: -.04em;
}
/* На тёмном вторичный текст серый; на ярком это белый с прозрачностью. */
.surface-accent .type-note, .surface-panel .type-note { line-height: 1.56; }
.surface-accent .type-note { color: var(--muted); opacity: 1; }
.surface-panel .type-note { color: var(--muted); }
.surface-accent .slide-title, .surface-accent .slide-number { color: var(--on-accent); }
/* Короткая подпись внутри блока: отметка времени, сторона сравнения,
   категория графика, пояснение к показателю. Одна роль — один кегль. */
.timeline-key, .comparison-label, .scoreboard .k,
.chart-row > span, .column > span, .line-scale > span, .line-key {
  color: var(--muted);
  font: 500 1.5cqw/1.4 var(--font-text);
  margin: 0;
}

/* --- 01 обложка --- */
.s-cover .type-hero, .s-title-card .type-hero {
  font-size: 8.2cqw; line-height: 1.02; text-wrap: balance;
}
.s-cover .type-hero { max-width: 18ch; }
.s-title-card .type-hero { max-width: 13ch; }
/* автор стоит в паре к номеру: тот же край, та же линия, тот же кегль */
.byline {
  position: absolute; left: var(--pad-x); bottom: var(--pad-y);
  font: 700 2.9cqw/1 var(--font-display);
  letter-spacing: -.03em; margin: 0;
}
/* на обложке номер не нужен: считать не от чего */
.s-cover .slide-number { display: none; }

/* --- 02 раздел --- */
.s-section .lanes path {
  stroke: var(--surface-muted);
  opacity: 1;
}
.s-section .pad { justify-content: center; gap: 1.5cqh; }
.s-section .jersey {
  position: absolute; right: 3cqw; top: 50%; transform: translateY(-50%);
  font: 700 30cqw/.78 var(--font-display); letter-spacing: -.04em; color: var(--text);
}
.s-section .type-hero { position: relative; font-size: 6.4cqw; }

/* --- 04 план: список разделов, больше ничего --- */

/* --- 04 тезис --- */
.s-statement .pad { justify-content: center; gap: 7cqh; }
.s-statement .type-hero { font-size: 6.6cqw; max-width: 15ch; }
.s-statement .wide-statement { max-width: 22ch; }
.s-statement u { text-decoration: none; box-shadow: inset 0 -.08em 0 0 var(--text); }

/* --- 05 список: номер в своей колонке, отделённой вертикальной линией --- */
.bullets > li {
  position: relative;
  display: grid; grid-template-columns: 7cqw 1fr; gap: 0; align-items: stretch;
}
.bullets li > div { padding-left: 2cqw; }
.bullets li::after {
  content: "";
  position: absolute; left: 7cqw; inset-block: 0;
  border-left: var(--rule) solid var(--line);
}
.bullets li::before {
  content: attr(data-n);
  text-align: center;
  font: 700 3.1cqw/1 var(--font-display); letter-spacing: -.03em; color: var(--accent);
}
.bullets li h3 { font: 600 2.25cqw/1.25 var(--font-display); letter-spacing: -.02em; margin: 0 0 .7cqh; }
.bullets li small { display: block; font: 400 1.6cqw/1.5 var(--font-text); opacity: .85; }
/* --- 06 сравнение: справа «стало» появляется следующей страницей --- */
.comparison-split {
  position: absolute; inset: 0;
  display: grid; grid-template-columns: 1fr 1fr;
}
.comparison-side {
  position: relative;
  padding: calc(var(--pad-y) + 6cqh) var(--pad-x) var(--pad-y);
  display: flex; flex-direction: column; justify-content: center;
}
.comparison-side.is-before { background: var(--surface); }
.comparison-side.is-after { background: var(--accent-fill); color: var(--on-accent); }
.comparison-title { position: absolute; margin: 0; }
.comparison-body { display: grid; gap: 1.8cqh; align-content: center; }
/* Число в половине слайда — тот же .type-metric, только компактнее. */
.comparison-value { --metric-size: 5.8cqw; margin: 0; }
/* В половине слайда строки компактнее. */
.comparison-points {
  --row-pad: 1.1cqh;
  --marker-size: .48cqw;
  --marker-gap: .9cqw;
  margin: 1cqh 0 0;
  font: 400 1.8cqw/1.4 var(--font-text);
}
.is-after .comparison-label { opacity: 1; }

/* --- 07 цитата --- */
.s-quote .pad { justify-content: center; }
.s-quote figure { margin: 0; display: grid; gap: 3.4cqh; }
.s-quote blockquote {
  margin: 0; font: 600 4.6cqw/1.18 var(--font-display); letter-spacing: -.025em; max-width: 22ch;
}
.s-quote figcaption { font: 400 1.7cqw/1.46 var(--font-text); opacity: .85; }
.s-quote figcaption b { display: block; font-weight: 600; opacity: 1; }

/* --- 08 табло: те же колонки, что и у шагов, разделённые тонкой линией --- */
.scoreboard {
  display: grid; grid-template-columns: repeat(3, 1fr); flex: 1;
  align-content: center;
}
.scoreboard > div { padding-right: 2.4cqw; display: grid; align-content: start; }
.scoreboard > div:not(:first-child) {
  padding-left: 2.4cqw;
  border-left: var(--rule) solid var(--line);
}
.scoreboard > div > .frag { display: grid; gap: 1.4cqh; }
.scoreboard .v { color: var(--accent); }

/* --- 09 код --- */
/* Синтаксис красит локальный Highlight.js; моноширинный шрифт используется только здесь. */
.code {
  background: transparent; color: var(--code-text);
  margin: 0; padding: 3.2cqh 0; overflow: hidden;
  font: 400 1.7cqw/1.65 var(--font-mono);
  tab-size: 4;
}
.code code { display: block; font: inherit; }
.code-line {
  position: relative;
  display: block;
  min-height: 1.65em;
  padding: 0;
  white-space: pre;
}
.code:has(.code-line.is-highlighted) .code-line:not(.is-highlighted) {
  opacity: .18;
}

/* Палитра синтаксиса согласована с Craft и имеет запас контраста для проектора. */
.code .hljs-comment,
.code .hljs-quote { color: var(--muted); }
.code .hljs-keyword,
.code .hljs-selector-tag,
.code .hljs-literal,
.code .hljs-type { color: var(--accent); font-weight: 650; }
.code .hljs-string,
.code .hljs-regexp,
.code .hljs-template-tag,
.code .hljs-template-variable { color: var(--craft-code-gold); }
.code .hljs-number,
.code .hljs-symbol,
.code .hljs-bullet { color: var(--craft-code-blue); }
.code .hljs-title,
.code .hljs-title.function_,
.code .hljs-title.class_ { color: var(--text); font-weight: 650; }
.code .hljs-built_in,
.code .hljs-attr,
.code .hljs-attribute { color: var(--craft-code-gold); }
.code .hljs-variable,
.code .hljs-params,
.code .hljs-punctuation { color: var(--code-text); }

/* --- 10 иллюстрация --- */
.figure { align-items: center; }
/* В медиа-шаблоне изображение начинается сразу под стандартным
   заголовком и занимает всю оставшуюся полезную высоту. */
.s-figure .figure { align-items: stretch; }
.s-figure .frame { aspect-ratio: auto; }
/* Снимок табло занимает половину слайда, не больше: правее живут
   QR и адрес, и им нужна своя половина. */
.s-figure .sigame-resource-layout {
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
}
.s-figure .frame.resource-shot--wide {
  align-self: center;
  justify-self: start;
  max-width: 58cqw;
  aspect-ratio: 16 / 9;
}
/* Ряд сетки ограничен высотой фигуры, правая колонка центрируется по нему.
   Пост разрезан на две полосы: шапка с аватаром и именем канала стоит на
   месте, а текст расписания под ней идёт до нижнего края слайда и на
   активном слайде сам едет вниз до последней строки, как длинный скриншот
   дизайн-системы. */
.s-figure .figure:has(> .resource-shot--channel) { grid-template-rows: minmax(0, 1fr); }
.s-figure .pad:has(.resource-shot--channel) { padding-bottom: 0; }
.s-figure .frame.resource-shot--channel {
  align-self: stretch;
  justify-self: start;
  width: 100%;
  height: auto;
  aspect-ratio: auto;
  grid-template-rows: auto minmax(0, 1fr);
  /* Снимок поста: пузырь сообщения на прозрачном фоне, без рамки. */
  border: 0;
  background: transparent;
}
.s-figure .frame.resource-shot--channel img { display: block; width: 100%; height: auto; }
/* Окно текста служит контейнером размера: 100cqh в кадрах это его высота. */
.channel-post-body { container-type: size; overflow: hidden; }
@keyframes channel-scroll { to { translate: 0 calc(-100% + 100cqh); } }
.slide[data-active] .channel-post-body img { animation: channel-scroll 25s linear 1s both; }
@media print { .channel-post-body img { animation: none; } }

.quiz-photo-deck,
.prize-photo-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1.5cqw;
  min-height: 0;
}
.quiz-photo-card,
.prize-photo-card {
  width: 100%;
  height: 100%;
  aspect-ratio: auto;
  margin: 0;
  overflow: hidden;
  background: var(--bg);
}
.frame, .photo {
  border: var(--rule) solid var(--edge-strong);
  position: relative;
  display: grid;
}
.frame { aspect-ratio: 16/10; }
/* Пока изображения нет — видно, какое место оно займёт.
   Один и тот же блок стоит внутри .frame, .photo и .shot. */
.placeholder {
  position: absolute;
  inset: var(--placeholder-inset, 1.4cqh);
  border: var(--rule) dashed var(--line);
  display: grid;
  place-items: center;
  font: 400 1.5cqw/1 var(--font-text);
  color: var(--muted);
}
.figure .content-block { --gap: 1.4cqh; min-height: 0; }
.quiz-almaty-summary { align-content: center; gap: 4cqh; }
.quiz-almaty-stats { display: grid; gap: 2.5cqw; }
.quiz-almaty-stats p { margin: 0; }
.quiz-almaty-stats strong {
  display: block;
  color: var(--accent);
  font: 650 5.4cqw/.95 var(--font-display);
  letter-spacing: -.05em;
}
.quiz-almaty-stats span {
  display: block;
  margin-top: 1cqh;
  font: 500 1.55cqw/1.25 var(--font-text);
  color: var(--muted);
}


/* --- 11 протокол результатов --- */
.data-table { border-collapse: collapse; width: 100%; font: 500 2cqw/1.28 var(--font-text); }
.data-table th, .data-table td { text-align: left; padding: 1.7cqh 1.2cqw; }
.data-table th {
  font: inherit;
  color: var(--muted);
  border-bottom: var(--rule-strong) solid var(--text);
}
.data-table tbody tr { border-bottom: var(--rule) solid var(--line); }
.data-table .num {
  font-family: var(--font-text);
  text-align: right;
}
.data-table tr[data-total] { background: var(--accent-fill); color: var(--on-accent); }

/* --- 12 дистанция --- */
.steps { display: grid; grid-template-columns: repeat(4, 1fr); flex: 1; align-content: center; }
.steps.quiz-formats { grid-template-columns: repeat(3, 1fr); }
.steps > div { padding: 0 1.8cqw; display: grid; gap: 1.1cqh; align-content: start; border-top: var(--rule-strong) solid var(--line); padding-top: 2cqh; }
/* Последний раскрытый элемент и есть текущий шаг. */
.steps > .frag[data-shown]:not(:has(~ .frag[data-shown])) {
  border-top-color: var(--accent-mark);
}
.steps .n {
  font: 700 2.5cqw/1 var(--font-display); letter-spacing: -.03em; color: var(--accent);
}
/* Заголовок и пояснение колонки одинаковы у шагов и у событий таймлайна. */
.steps h3, .timeline h3 { font: 600 1.8cqw/1.25 var(--font-display); letter-spacing: -.02em; margin: 0; }
.steps p, .timeline p { font: 400 1.55cqw/1.5 var(--font-text); color: var(--muted); margin: 0; }

/* --- график: подпись и значение стоят над дорожкой, вывод — по центру графика --- */
.chart-layout {
  min-height: 0;
  display: grid;
  align-content: center;
  align-items: center;
}
.bar-chart { display: grid; gap: var(--chart-gap); }
.chart-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  row-gap: 1.2cqh;
  column-gap: 1.2cqw;
}
.chart-row > strong {
  color: var(--text);
  font: 650 1.9cqw/1 var(--font-display);
  font-variant-numeric: tabular-nums;
  text-align: right;
}
/* Толщина столбца одна на оба графика: лежащий и стоящий — один и тот
   же приём, повёрнутый на четверть. Задана в долях высоты слайда,
   поэтому ширина стоящего столбца тоже считается в cqh: иначе на том
   же слайде они оказались бы разной толщины. */
.chart-track { grid-column: 1 / -1; height: var(--chart-bar); background: var(--surface); overflow: hidden; }
.chart-track i { display: block; width: var(--value); height: 100%; background: var(--text); }
.chart-row.is-focus > span, .chart-row.is-focus > strong { color: var(--accent); }
.chart-row.is-focus .chart-track i { background: var(--accent-mark); }

/* --- вертикальный график: та же величина, но по периодам --- */
/* Периоды идут слева направо, поэтому столбцы стоят, а не лежат:
   порядок времени читается сам. Подписи снизу оттого короткие — под
   столбец такой толщины длинное название не встаёт; если названия
   длинные, берут горизонтальный .bar-chart.

   Колонка равна толщине столбца, а промежуток между колонками — тот же
   --chart-gap, что между дорожками лежащего графика: два графика на
   соседних слайдах должны держать один ритм, иначе стоящий выглядит
   разреженным. Из-за этого график занимает не всю ширину поля, а
   ровно свою, и стоит по центру. Подпись и значение шире колонки и
   центрируются относительно неё, выходя за края.

   Все три ряда — значение, дорожка, период — общие для всех столбцов
   через subgrid, поэтому опора у графика одна на всех. */
.column-chart {
  min-height: 0;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  grid-auto-flow: column;
  grid-auto-columns: var(--chart-bar);
  justify-content: center;
  column-gap: var(--chart-gap);
}
.column {
  min-width: 0;
  grid-row: span 3;
  display: grid;
  grid-template-rows: subgrid;
  row-gap: 1.2cqh;
}
.column > strong {
  grid-row: 1;
  justify-self: center;
  width: max-content;
  color: var(--text);
  font: 650 1.9cqw/1 var(--font-display);
}
.column > span {
  grid-row: 3;
  justify-self: center;
  width: max-content;
}
/* Столбец растёт снизу, поэтому флекс с прижимом к концу: доля высоты
   считается от дорожки, а её высоту задаёт ряд сетки. Ширина дорожки
   ограничена: во всю ячейку столбец превращается в плакатную плашку и
   спорит с текстом, а сравниваются столбцы всё равно высотой. */
.column-track {
  grid-row: 2;
  background: var(--surface);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}
.column-track i { display: block; height: var(--value); background: var(--text); }
.column.is-focus > span, .column.is-focus > strong { color: var(--accent); }
.column.is-focus .column-track i { background: var(--accent-mark); }

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

   Второй ряд задаёт фон сравнения и потому приглушён — акцент носит
   тот ряд, о котором доклад. Названия рядов стоят у концов линий, а не
   в отдельной легенде: легенда заставляет глаз ходить от линии к
   образцу цвета и обратно, подпись у конца читается на месте. */
.line-chart {
  /* Колонка подписей: ширина по самому длинному названию ряда. */
  --line-keys: 15cqw;
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--line-keys);
  grid-template-rows: minmax(0, 1fr) auto;
  column-gap: 1.4cqw;
  row-gap: 1.6cqh;
}
.line-plot { grid-area: 1 / 1; min-height: 0; }
.line-keys { grid-area: 1 / 2; position: relative; }
/* Подпись стоит на высоте конца своей линии: доля берётся от поля
   графика, поэтому колонка подписей делит с ним ряд сетки. */
.line-key {
  position: absolute;
  left: 0;
  top: var(--at);
  translate: 0 -50%;
}
.line-key.is-focus { color: var(--accent); }
.line-plot svg { display: block; width: 100%; height: 100%; overflow: visible; }
/* Линия тянется по обеим осям вместе с полем графика, а толщина линий
   остаётся экранной: иначе растяжение сплющивало бы штрих. */
.line-base {
  stroke: var(--line);
  stroke-width: var(--rule);
  vector-effect: non-scaling-stroke;
}
/* Стойка отмечает момент, а не значение: пунктир отличает её от линий
   самого графика — сплошная здесь читалась бы как ещё один ряд. */
.line-mark {
  stroke: var(--accent-mark);
  stroke-width: var(--rule);
  stroke-dasharray: 4px 5px;
  vector-effect: non-scaling-stroke;
}
.line-series {
  fill: none;
  stroke: var(--muted);
  stroke-width: var(--rule-strong);
  vector-effect: non-scaling-stroke;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.line-scale { grid-area: 2 / 1; display: flex; justify-content: space-between; gap: 1cqw; }
.line-series.is-focus { stroke: var(--accent-mark); }
.line-scale > .is-focus { color: var(--accent); }

/* --- таймлайн: непрерывная ось времени, событие отмечено точкой на ней --- */
.timeline {
  --timeline-gap: 2cqw;
  min-height: 0;
  flex: 1;
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(var(--columns, 5), minmax(0, 1fr));
  align-content: center;
}
.timeline li { min-width: 0; padding-right: var(--timeline-gap); }
.timeline li > i { position: relative; display: block; height: 1.25cqw; margin: 2.4cqh 0 2.6cqh; }
/* Отрезки соседних событий смыкаются в одну линию. */
.timeline li > i::before {
  content: "";
  position: absolute;
  left: 0;
  right: calc(-1 * var(--timeline-gap));
  top: 50%;
  border-top: var(--rule) solid var(--line);
}
/* Ось доходит только до последнего раскрытого события. */
.timeline li:last-child > i::before,
.timeline > .frag[data-shown]:not(:has(~ .frag[data-shown])) > i::before { display: none; }
/* Акцент держит событие, о котором идёт речь: последнее раскрытое
   или явно отмеченное на нераскрывающемся таймлайне. */
.timeline > .frag[data-shown]:not(:has(~ .frag[data-shown])) > i::after,
.timeline .is-current > i::after { background: var(--accent-mark); }
.timeline > .frag[data-shown]:not(:has(~ .frag[data-shown])) .timeline-key,
.timeline > .frag[data-shown]:not(:has(~ .frag[data-shown])) h3,
.timeline .is-current .timeline-key,
.timeline .is-current h3 { color: var(--accent); }
.timeline li > i::after {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  width: 1.25cqw;
  height: 1.25cqw;
  border-radius: 50%;
  background: var(--muted);
  outline: .3cqw solid var(--bg);
}
.timeline-key { display: block; }
.timeline p { margin-top: .8cqh; max-width: 18ch; }

/* --- схема процесса: равные прямоугольники, стрелка на весь промежуток ---
   Связи рисуются полосками фона, а не рамками: одна деталь — один отрезок,
   поэтому горизонтальную, вертикальную и обратную связь описывает один приём. --- */
.flow {
  --flow-gap: 5cqw;
  --flow-drop: 9cqh;
  --flow-return: 8cqh;
  min-height: 0;
  flex: 1;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-template-rows: auto auto auto;
  column-gap: var(--flow-gap);
  align-content: center;
}
.flow > .flow-node, .flow > .flow-step { grid-row: 2; }
.flow-step { position: relative; min-width: 0; display: grid; }
.flow-node {
  min-width: 0;
  display: grid;
  place-content: center;
  padding: 4cqh 2cqw;
  text-align: center;
  border: var(--rule) solid var(--edge);
}
.flow-node > strong { font: 600 2.1cqw/1.2 var(--font-display); letter-spacing: -.02em; }
.flow-node--accent {
  background: var(--accent-fill);
  border-color: var(--accent-fill);
  color: var(--text);
}

/* Связь занимает весь промежуток, наконечник упирается в следующий узел. */
.flow-arrow {
  position: absolute;
  top: 50%;
  right: 100%;
  width: var(--flow-gap);
  height: var(--rule);
  transform: translateY(-50%);
  background: var(--muted);
}
.flow-arrow::after {
  content: "";
  position: absolute;
  top: 50%;
  right: 0;
  width: .9cqw;
  height: .9cqw;
  transform: translateY(-50%);
  background: var(--muted);
  clip-path: polygon(0 0, 100% 50%, 0 100%);
}
/* Ветка вниз: та же связь, повёрнутая на четверть. */
.flow > .flow-step--down { grid-row: 3; grid-column: 2; padding-top: var(--flow-drop); }
.flow-arrow--down {
  top: 0;
  right: auto;
  left: 50%;
  width: var(--rule);
  height: var(--flow-drop);
  transform: translateX(-50%);
}
.flow-arrow--down::after {
  top: auto;
  right: auto;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  clip-path: polygon(0 0, 100% 0, 50% 100%);
}
/* Обратная связь: прямоугольный обход поверху от последнего узла к первому. */
.flow-return {
  grid-row: 1;
  grid-column: 1 / -1;
  position: relative;
  height: var(--flow-return);
  margin-inline: calc((100% - 2 * var(--flow-gap)) / 6);
}
.flow-return::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: var(--rule);
  background: var(--muted);
}
.flow-return::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  width: var(--rule);
  background: var(--muted);
}
.flow-return > i {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: var(--rule);
  background: var(--muted);
}
.flow-return > i::after {
  content: "";
  position: absolute;
  bottom: 0;
  left: 50%;
  width: .9cqw;
  height: .9cqw;
  transform: translateX(-50%);
  background: var(--muted);
  clip-path: polygon(0 0, 100% 0, 50% 100%);
}

/* --- визуальное сравнение: то же сравнение, только в теле стоит изображение --- */
/* Изображение занимает полезную высоту целиком, поэтому нужен запас
   под заголовком и над номером страницы: центрированному тексту хватает
   6cqh, а прижатому к краям блока — нет. Зазор один на все раскладки,
   где медиа растянуто: иллюстрация, ресурс и половины сравнения. */
.s-figure .pad,
.s-visual-compare .comparison-side {
  padding-block: calc(var(--pad-y) + 10cqh) calc(var(--pad-y) + 8cqh);
}
.s-visual-compare .comparison-body { flex: 1; min-height: 0; align-content: stretch; }
.s-visual-compare .frame { width: 100%; height: 100%; aspect-ratio: auto; }

/* --- заголовочная перебивка: обложка без мотива и служебки --- */
.s-title-card .pad { justify-content: center; }
/* --- обо мне: слева кто, справа фото --- */
.about {
  display: grid; grid-template-columns: 1fr auto; gap: 5cqw; align-items: center;
}
.about .name { font: 600 4.4cqw/1.1 var(--font-display); letter-spacing: -.03em; margin: 0 0 2cqh; }
.about p { font: 400 2.1cqw/1.45 var(--font-text); margin: 0 0 1cqh; max-width: 34ch; }
.about .speaker-role { max-width: none; white-space: nowrap; }
.speaker-role__separator { opacity: .55; }
.about .handle { font: 500 2.1cqw/1.45 var(--font-text); color: var(--accent); margin-top: 2cqh; }
.channel-link { color: inherit; text-decoration: none; }
.photo {
  margin: 0; width: 26cqw; aspect-ratio: 3/4;
  overflow: hidden;
}
.speaker-stage-photo { object-position: 43% center; }

/* --- итоги: выводы одной строкой каждый --- */
/* Разметка и плотность приходят из .plain-list; рецепт задаёт только кегль. */
.takeaways { font: 500 2.9cqw/1.25 var(--font-display); letter-spacing: -.025em; }

/* --- QR: один блок для ресурса внутри доклада и для финала ---
   Квадрат с акцентным контуром и адрес рядом — один элемент. --- */
.qr {
  width: 12cqw;
  aspect-ratio: 1;
  border: .24cqw solid var(--accent-mark);
  display: grid;
  place-items: center;
  overflow: hidden;
  font: 500 1.4cqw/1.4 var(--font-text);
  color: var(--muted);
}
.qr img, .qr svg { width: 100%; height: 100%; object-fit: contain; }
.qr svg { color: var(--accent-mark); }
.qr-link { display: grid; gap: 1cqh; justify-items: start; }
.qr-link a { color: inherit; text-decoration: none; }
.where {
  font: 500 1.8cqw/1.25 var(--font-text);
  color: var(--accent);
  margin: 0;
  white-space: nowrap;
}

/* --- скриншот во весь слайд: поля не нужны, картинка и есть содержание --- */
.s-shot .pad { padding: 0; }
.shot {
  position: absolute; inset: 0; margin: 0;
  display: grid; place-items: center;
  background: var(--surface);
}
.shot .placeholder { --placeholder-inset: 3cqh; }
/* Подложка уходит за край только по горизонтали. Она вынесена в
   псевдоэлемент, поэтому padding не сдвигает базовую линию текста. */
.s-shot .slide-title, .s-shot .slide-number, .shot figcaption {
  z-index: 2;
  isolation: isolate;
}
.s-shot .slide-title::before,
.s-shot .slide-number::before,
.shot figcaption::before {
  content: "";
  position: absolute;
  inset-block: -.8cqh;
  z-index: -1;
  background: var(--bg);
}
.s-shot .slide-title::before,
.shot figcaption::before {
  left: calc(-1 * var(--pad-x));
  right: -1.4cqw;
}
.s-shot .slide-number::before {
  left: -1.4cqw;
  right: calc(-1 * var(--pad-x));
}
.shot figcaption {
  position: absolute; left: var(--pad-x); bottom: var(--pad-y);
  font: 400 1.6cqw/1.4 var(--font-text); color: var(--muted); max-width: 55cqw;
}

/* --- 14 финал --- */
.s-end .pad { justify-content: center; gap: 4cqh; }
.s-end .handle {
  font: 600 3.2cqw/1.1 var(--font-display); letter-spacing: -.03em;
  color: var(--accent); margin: 0;
}
.s-end .qr-link {
  position: absolute;
  right: var(--pad-x);
  top: 50%;
  transform: translateY(-50%);
  grid-auto-flow: column;
  align-items: center;
  gap: 2cqw;
}
.s-end .qr { order: 2; }
.s-end .where { font-size: 1.5cqw; text-align: right; }

/* ============================================================
   Движение раскрытия

   Базовая тема использует движение для роста столбца графика, а deck.js —
   для явно помеченного показателя data-count. Оба доходят от нуля до
   значения и помогают прочитать данные. В конкретной колоде допустим
   другой короткий однократный акцент на графическом элементе.
   Прочерченная ось, выезжающая связь, появление обычного текста,
   переходы между слайдами и циклы соперничают с говорящим, поэтому их нет.

   Право на движение выдаёт deck.js атрибутом data-reveal: его получает
   то, что раскрылось на переходе вперёд. Поэтому на следующих шагах,
   при возврате назад и на первом кадре страницы столбец сразу стоит на
   значении, а печать гасит анимации целиком (base.css).
   ============================================================ */

/* Замедление к концу: значение читается на остановке, а не в разгоне.
   Задержка по строкам задаёт порядок чтения сверху вниз. */
@keyframes chart-track-grow { from { width: 0; } }

[data-reveal] .chart-track i {
  animation: chart-track-grow 900ms cubic-bezier(.22, 1, .36, 1) var(--chart-delay, 0ms) both;
}
.chart-row:nth-child(2) { --chart-delay: 90ms; }
.chart-row:nth-child(3) { --chart-delay: 180ms; }
.chart-row:nth-child(4) { --chart-delay: 270ms; }
.chart-row:nth-child(n+5) { --chart-delay: 360ms; }

/* Столбец растёт от опоры вверх — то же движение, повёрнутое вместе с
   графиком. Задержки между столбцами нет: периоды сравниваются целиком,
   и очередь по одному растянула бы это на лишние полсекунды. */
@keyframes column-track-grow { from { height: 0; } }

[data-reveal] .column-track i {
  animation: column-track-grow 900ms cubic-bezier(.22, 1, .36, 1) both;
}

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

   Оба конца обрезки заданы явно: базовое значение свойства — none,
   а inset() с none не интерполируется и дал бы скачок на середине. */
@keyframes line-plot-draw {
  from { clip-path: inset(0 100% 0 0); }
  to { clip-path: inset(0); }
}
/* Названия рядов ждут свои линии: подпись у конца пути, которого ещё
   нет, — обещание вместо факта. */
@keyframes line-key-in { from { opacity: 0; } to { opacity: 1; } }

[data-reveal] .line-plot svg {
  animation: line-plot-draw 900ms cubic-bezier(.35, .35, .65, 1) both;
}
[data-reveal] .line-key { animation: line-key-in 200ms linear 900ms both; }

/* Цифры выравниваются по табличной ширине везде, где их сравнивают. */
.slide-number, .type-metric, .comparison-value, .bullets > li::before,
.steps .n, .timeline-key, .chart-row > strong, .data-table .num,
.s-section .jersey {
  font-variant-numeric: tabular-nums;
}

/* ============================================================
   Локальная композиция доклада о production-системе квизов
   ============================================================ */

.deck-kicker {
  margin: 0 0 1cqh;
  font: 600 1.45cqw/1.2 var(--font-text);
  color: var(--muted);
}
.surface-accent .deck-kicker { color: var(--on-accent-muted); }
.title-lede { max-width: 24ch; margin: .7cqh 0 0; color: var(--muted); }

.origin-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  min-height: 53cqh;
  border: var(--rule) solid var(--line);
}
.origin-grid > div { padding: 4cqw; display: grid; align-content: center; gap: 2cqh; }
.origin-grid > div + div { border-left: var(--rule) solid var(--line); }
.origin-label { margin: 0; font: 600 1.5cqw/1 var(--font-text); color: var(--muted); }
.origin-grid h2, .origin-grid p { margin-block: 0; }
.origin-block .type-lede { color: var(--muted); }

.mini-timeline {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  min-height: 45cqh;
  align-items: stretch;
  border-top: var(--rule-strong) solid var(--text);
}
.mini-event { position: relative; padding: 3cqh 2cqw 0 0; }
.mini-event + .mini-event { padding-left: 2cqw; border-left: var(--rule) solid var(--line); }
.mini-event::before { content: ""; position: absolute; top: calc(-.45cqw - var(--rule-strong) / 2); left: 0; width: .9cqw; aspect-ratio: 1; border-radius: 50%; background: var(--accent-mark); }
.mini-event + .mini-event::before { left: -.45cqw; }
.mini-event span { font: 600 1.45cqw/1 var(--font-text); color: var(--muted); }
.mini-event h3 { margin: 4cqh 0 1cqh; font: 650 2.25cqw/1.1 var(--font-display); }
.mini-event p { margin: 0; font-size: 1.55cqw; line-height: 1.35; color: var(--muted); }
.mini-event.is-turn { background: var(--accent-fill); color: var(--on-accent); padding-right: 2cqw; }
.mini-event.is-turn span, .mini-event.is-turn p { color: var(--on-accent-muted); }

.wave { height: 12cqh; display: flex; align-items: center; gap: .45cqw; margin: 3cqh 0; }
.wave i { display: block; width: .45cqw; background: currentColor; }
.wave i:nth-child(1), .wave i:nth-child(12) { height: 25%; }
.wave i:nth-child(2), .wave i:nth-child(11) { height: 46%; }
.wave i:nth-child(3), .wave i:nth-child(9) { height: 72%; }
.wave i:nth-child(4), .wave i:nth-child(8) { height: 42%; }
.wave i:nth-child(5), .wave i:nth-child(10) { height: 90%; }
.wave i:nth-child(6) { height: 58%; }
.wave i:nth-child(7) { height: 100%; }
.wave-manual { color: var(--muted); }
.wave-auto { color: var(--on-accent); }
.pressure-list { max-width: 74cqw; margin-inline: auto; }

.system-flow {
  display: flex;
  align-items: stretch;
  justify-content: center;
  width: 100%;
  margin-top: 3cqh;
}
.system-node {
  width: 13.5cqw;
  min-height: 17cqh;
  padding: 2cqh .9cqw;
  border: var(--rule) solid var(--line-strong);
  display: grid;
  place-content: center;
  text-align: center;
  font: 650 1.45cqw/1.15 var(--font-display);
}
.system-node small { display: block; margin-top: 1.2cqh; font: 500 1.05cqw/1.25 var(--font-text); color: var(--muted); }
.system-arrow { display: grid; place-items: center; width: 2.3cqw; font: 600 1.8cqw/1 var(--font-display); color: var(--accent); }
.repo-builder { background: var(--accent-fill); color: var(--on-accent); border-color: var(--accent-fill); }
.repo-builder small { color: var(--on-accent-muted); }
.contract-node { border-style: dashed; }
.output-node { border-width: var(--rule-strong); }
.architecture-caption { margin: 4cqh auto 0; font: 650 2.35cqw/1.18 var(--font-display); text-align: center; }

.repo-thirds { display: grid; grid-template-columns: repeat(3, 1fr); min-height: 100%; padding-top: 13cqh; }
.repo-thirds > div { padding: 4cqw 3cqw; display: grid; align-content: center; gap: 2cqh; border-right: var(--rule) solid var(--line); }
.repo-thirds > div:last-child { border-right: 0; }
.repo-thirds .repo-focus { background: var(--accent-fill); color: var(--on-accent); }
.repo-thirds h2, .repo-thirds p, .repo-thirds small { margin: 0; }
.repo-thirds h2 { font: 650 2.5cqw/1.1 var(--font-display); }
.repo-thirds p { font-size: 1.55cqw; line-height: 1.35; }
.repo-thirds small { font-size: 1.15cqw; color: var(--muted); }
.repo-thirds .repo-focus small { color: var(--on-accent-muted); }
.repo-index { font: 650 1.4cqw/1 var(--font-display); color: var(--accent); }
.repo-focus .repo-index { color: var(--on-accent); }

.quote-slide .pad { display: grid; grid-template-columns: .85fr 1.15fr; align-items: center; gap: 6cqw; }
.quote-slide blockquote { margin: 0; display: flex; gap: 1cqw; }
.quote-slide blockquote > span { font: 700 6cqw/.8 var(--font-display); }
.quote-slide blockquote p { margin: 0; font: 650 3.9cqw/1.2 var(--font-display); }
.quote-context { display: grid; gap: 2cqh; }
.quote-context h2 { margin: 0; font: 650 2.8cqw/1.15 var(--font-display); }
.quote-context > p:not(.type-note) { margin: 0; font-size: 1.55cqw; line-height: 1.45; color: var(--on-accent-muted); }
.quote-context code { color: inherit; }
.two-metrics { grid-template-columns: repeat(2, 1fr); }
.metric-warning { display: flex; justify-content: center; gap: 4cqw; }
.metric-warning p { margin: 0; font: 650 1.8cqw/1.2 var(--font-display); color: var(--accent); }

.control-track { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0; border-top: var(--rule-strong) solid var(--text); }
.control-track > div { min-height: 42cqh; padding: 3cqh 2cqw; border-right: var(--rule) solid var(--line); }
.control-track > div:last-child { border-right: 0; background: var(--accent-fill); color: var(--on-accent); }
.control-track span { font: 650 1.5cqw/1 var(--font-display); color: var(--accent); }
.control-track > div:last-child span { color: var(--on-accent); }
.control-track h3 { margin: 8cqh 0 1.4cqh; font: 650 2cqw/1.12 var(--font-display); }
.control-track p { margin: 0; font-size: 1.35cqw; line-height: 1.4; color: var(--muted); }
.control-track > div:last-child p { color: var(--on-accent-muted); }

.ownership-flow { display: grid; grid-template-columns: 1fr auto 1fr auto 1fr; align-items: stretch; }
.ownership-column { padding: 3cqw 2.2cqw; min-height: 37cqh; border: var(--rule) solid var(--line); display: grid; align-content: start; gap: 2cqh; }
.ownership-column span { font: 650 1.45cqw/1 var(--font-text); color: var(--accent); }
.ownership-column h2 { margin: 3cqh 0 0; font: 650 2.15cqw/1.15 var(--font-display); }
.ownership-column p { margin: 0; font-size: 1.35cqw; line-height: 1.4; color: var(--muted); }
.ownership-link { display: grid; place-items: center; width: 3cqw; color: var(--accent); font-size: 1.8cqw; }
.agent-owner { background: var(--accent-fill); color: var(--on-accent); border-color: var(--accent-fill); }
.agent-owner span, .agent-owner p { color: var(--on-accent-muted); }
.ownership-bottom { margin: 1cqh 0 0; text-align: center; font: 650 2.2cqw/1.2 var(--font-display); }
.test-counter { margin: 1cqh 0 0; color: var(--on-accent-muted); }

.preset-stack { height: 20cqh; display: flex; align-items: end; gap: 1cqw; margin: 2cqh 0; }
.preset-stack i { display: block; width: 6cqw; height: 15cqh; border: var(--rule-strong) solid currentColor; }
.preset-stack i:nth-child(2) { height: 18cqh; }
.preset-stack i:nth-child(3) { height: 13cqh; }
.preset-stack i:nth-child(4) { height: 16.5cqh; }
.deleted-mark { font: 700 14cqw/.8 var(--font-display); margin: 1cqh 0; }
.cost-list { max-width: 76cqw; margin-inline: auto; }

@media print {
  .mini-event.is-turn, .repo-focus, .repo-builder, .agent-owner { print-color-adjust: exact; -webkit-print-color-adjust: exact; }
}

/* Реальные product shots */
.product-shot { background: #0b0c0d; }
.product-shot__image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.product-shot__caption {
  position: absolute;
  left: 2.2cqw;
  bottom: 3.4cqh;
  max-width: 44cqw;
  padding: 2.1cqh 1.6cqw;
  background: rgb(10 10 10 / .92);
  border-left: .34cqw solid var(--accent-mark);
  color: #fff;
}
.product-shot__caption--right { left: auto; right: 2.2cqw; }
.product-shot__caption--top { bottom: auto; top: 3.4cqh; }
.product-shot__caption h2 { margin: 0; font: 650 2.35cqw/1.08 var(--font-display); }
.product-shot__caption p { margin: .8cqh 0 0; font-size: 1.22cqw; line-height: 1.3; color: rgb(255 255 255 / .76); }
.product-shot .slide-number {
  padding: .6cqh .55cqw;
  background: rgb(10 10 10 / .88);
  color: #fff;
}
.product-shot--app .product-shot__caption { max-width: 48cqw; }

/* Записанное демо занимает всю сцену без служебных подписей поверх интерфейса. */
.n-video-full { background: #0b0c0d; }
.n-video-full__media {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
}
.slide[data-active] .n-video-full__media {
  animation: q-question-fade-in 500ms ease-in-out both;
}
.n-video-full__title {
  left: 50%;
  right: auto;
  translate: -50% 0;
  max-width: none;
  z-index: 3;
  isolation: isolate;
  white-space: nowrap;
}
.n-video-full__title::before {
  content: "";
  position: absolute;
  top: calc(-1 * var(--pad-y));
  bottom: -.8cqh;
  inset-inline: -1.4cqw;
  z-index: -1;
  background: var(--bg);
}
/* Заголовок нужен только чтобы назвать демо. Через три секунды он
   уезжает вверх и не мешает смотреть интерфейс. */
.slide[data-active] .n-video-full__title {
  animation: n-video-full-title-hide 600ms ease-in 3s both;
}
@keyframes n-video-full-title-hide {
  to { translate: -50% -250%; }
}
.n-video-full .slide-number {
  z-index: 3;
  isolation: isolate;
}
.n-video-full .slide-number::before {
  content: "";
  position: absolute;
  inset-block: -.8cqh;
  left: -1.4cqw;
  right: calc(-1 * var(--pad-x));
  z-index: -1;
  background: var(--bg);
}

/* Заглушка под ещё не записанное полноэкранное видео. */
.n-video-full .placeholder {
  --placeholder-inset: 0;
  border-color: #3a3c3f;
  color: #9a9ea3;
  padding: 0 12cqw;
  text-align: center;
  line-height: 1.4;
}

@media print {
  .n-video-full:not(:has(.placeholder)) {
    background: #0b0c0d url("media/package-generation-demo-2026-09-13-poster.webp") center / cover no-repeat;
  }
  .n-video-full__media { visibility: hidden; }
  .slide[data-active] .n-video-full__title { animation: none; }
}

/* Скриншоты не уходят под заголовок, подпись и номер страницы. */
.s-shot .shot {
  inset: auto;
  top: 14cqh;
  left: var(--pad-x);
  width: 70cqw;
  aspect-ratio: 16 / 9;
  border: var(--rule) solid var(--edge-strong);
}
.s-shot .shot .media-cover { object-fit: contain; }
.s-shot .slide-title::before,
.s-shot .slide-number::before,
.s-shot .shot figcaption::before { display: none; }
.s-shot .shot figcaption {
  left: 0;
  bottom: -6.5cqh;
  max-width: 76cqw;
}

/* Хронологическая карта использует весь кадр: у PNG прозрачные промежутки,
   поэтому отдельная подложка и рамка только мешают ритму сетки. */
.s-shot--timeline .shot {
  top: 50%;
  left: 0;
  width: 100cqw;
  aspect-ratio: 4085 / 1512;
  transform: translateY(-50%);
  border: 0;
  background: transparent;
}

.video-triptych { background: #0b0c0d; color: #fff; }
.video-triptych__frames { display: grid; grid-template-columns: repeat(3, 1fr); height: 70cqh; }
.video-triptych figure { position: relative; margin: 0; overflow: hidden; border-right: var(--rule) solid rgb(255 255 255 / .2); }
.video-triptych figure:last-child { border-right: 0; }
.video-triptych img { width: 100%; height: 100%; object-fit: contain; background: #050505; display: block; }
.video-triptych figcaption {
  position: absolute;
  left: 1.5cqw;
  top: 2.6cqh;
  padding: .55cqh .65cqw;
  background: rgb(10 10 10 / .86);
  font: 600 1.15cqw/1 var(--font-text);
}
.video-triptych__caption { height: 30cqh; padding: 3cqh 3.6cqw; display: grid; align-content: start; gap: .8cqh; }
.video-triptych__caption h2 { margin: 0; font: 650 2.75cqw/1.08 var(--font-display); }
.video-triptych__caption p { margin: 0; font-size: 1.25cqw; color: rgb(255 255 255 / .68); }

/* ============================================================
   СЛОВАРЬ ПАБ-КВИЗА

   Композиции этой колоды. Общая тема их не знает.
   ============================================================ */

/* --- слово в центре внимания --- */
.q-word .pad { justify-content: center; gap: 6cqh; }
.q-word-stage { display: grid; align-content: center; justify-items: start; gap: 2cqh; }
.q-word-main {
  font: 600 11cqw/1 var(--font-display);
  letter-spacing: -.045em; margin: 0; color: var(--text);
}
.q-word-note { font: 400 2.6cqw/1.35 var(--font-text); color: var(--muted); margin: 0; }
.q-word .scoreboard { flex: 0 0 auto; }

/* ============================================================
   РАЗБОР ПАКЕТА

   Пакет — это рамка: всё, что внутри неё, и есть пакет. Заливкой
   его отмечать не за что, поэтому границу держит линия, а не цвет.
   Внутри рамка делится на пять раундов, каждый раунд — на свои
   вопросы. Красным помечены только названия тем: это единственное,
   на что слайд просит посмотреть.

   Пока уровень не раскрыт, его строка имеет нулевую высоту, поэтому
   рамка растёт вместе с содержимым, а схема центрируется сама.
   ============================================================ */
.q-tree { display: grid; align-content: center; }

.q-pack {
  border: var(--rule) solid var(--line-strong);
  background: transparent;
}
.q-pack-head {
  margin: 0; display: grid; gap: .3cqh;
  padding: 1.1cqh 1.1cqw;
}
.q-pack-head > b { font: 400 1.4cqw/1.15 var(--font-text); color: var(--muted); }
.q-pack-head > strong { font: 600 2cqw/1.15 var(--font-display); letter-spacing: -.03em; }

.q-branch-wrap { display: grid; grid-template-rows: 1fr; min-height: 0; }
.q-branch-wrap > * { overflow: hidden; min-height: 0; }
.q-branch-wrap:not([data-shown]) { grid-template-rows: 0fr; }

/* Доли одного целого не разъезжаются карточками: их делят линии.
   Ряды отделяет линия той же силы, что и колонки, — это разрезы
   одной рамки, а не отдельные блоки. */
.q-grid {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(var(--q-cols, 5), 1fr);
  border-top: var(--rule) solid var(--line-strong);
}
.q-grid > li { display: grid; align-content: start; min-width: 0; }
.q-grid > li + li { border-left: var(--rule) solid var(--line-strong); }
.q-cell { display: grid; align-content: start; gap: .3cqh; padding: 1.1cqh 1.1cqw; }
.q-cell > b { font: 400 1.4cqw/1.15 var(--font-text); color: var(--muted); }
.q-cell > strong { font: 600 1.55cqw/1.25 var(--font-display); letter-spacing: -.02em; }

/* Тема надписывает свою группу вопросов. Там, где раунд состоит из
   одной темы, группа одна и названия у неё нет; там, где тем
   несколько, столбец разделён на подписанные группы. */
.q-theme {
  margin: 0; padding: .9cqh 1.1cqw .7cqh;
  border-bottom: var(--rule) solid var(--line);
  font: 650 1.4cqw/1.25 var(--font-display); letter-spacing: -.01em;
  color: var(--text);
}
.q-theme:not(:first-child) { border-top: var(--rule) solid var(--line); }
.q-grid--questions ol:first-child { padding-top: .8cqh; }

.q-grid--questions ol {
  list-style: none; margin: 0; padding: 0 0 .5cqh; display: grid;
}
.q-grid--questions ol li {
  padding: .35cqh 1.1cqw;
  font: 400 1.4cqw/1.35 var(--font-text);
  color: var(--muted); font-variant-numeric: tabular-nums;
}
.q-grid--questions ol .q-question-row {
  display: grid;
  grid-template-columns: 1.5cqw minmax(0, 1fr);
  gap: .35cqw;
  align-items: baseline;
  font-size: 1.05cqw;
}
.q-question-row b { color: var(--muted); font-weight: 400; }
.q-question-row span { color: var(--text); }

/* Раунд Своей игры — настоящая таблица: строка это тема, столбцы это
   её пять вопросов с ценами. Цены и так стоят в столбцах, поэтому в
   шапке раунда лестницу не повторяем. Ширину колонки тем определяет
   само название, ограничение только сверху: темы подобраны короткие и
   встают в строку. Своя типографика, а не .data-table: три таблицы
   рядом набираются мельче одиночной. */
.q-round-table {
  width: 100%; border-collapse: collapse;
}
.q-round-table th, .q-round-table td {
  padding: 1.1cqh .2cqw;
  border-top: var(--rule) solid color-mix(in srgb, var(--line) 55%, transparent);
  border-left: var(--rule) solid var(--line);
}
.q-round-table tr:first-child th, .q-round-table tr:first-child td { border-top: 0; }
/* Название темы стоит на той же вертикали, что и название раунда. */
.q-round-table th {
  max-width: 45%; border-left: 0;
  padding-inline: 1.1cqw; text-align: left;
  font: 600 1.4cqw/1.25 var(--font-display); letter-spacing: -.02em;
  color: var(--text);
}
/* Четырёхзначным ценам третьего раунда нужна вся ячейка, поэтому
   боковых полей у чисел нет: их центрирует сама колонка. */
.q-round-table td {
  padding-inline: 0; text-align: center;
  font: 400 1.4cqw/1.25 var(--font-text);
  color: var(--muted); font-variant-numeric: tabular-nums;
}

/* Движение достаётся ветке на том шаге, где она появляется.
   Один проход вперёд; назад, по прямой ссылке и в печати тихо. */
@keyframes q-branch-open { from { grid-template-rows: 0fr; } }
@keyframes q-card-in { from { opacity: 0; } }

.q-branch-wrap[data-reveal] { animation: q-branch-open 560ms cubic-bezier(.22, 1, .36, 1) both; }
.q-branch-wrap[data-reveal] > .q-grid > li {
  animation: q-card-in 300ms linear var(--q-delay, 320ms) both;
}
.q-grid > li:nth-child(2) { --q-delay: 360ms; }
.q-grid > li:nth-child(3) { --q-delay: 400ms; }
.q-grid > li:nth-child(4) { --q-delay: 440ms; }
.q-grid > li:nth-child(5) { --q-delay: 480ms; }

/* ============================================================
   КАДР ВИДЕО

   Не скриншот: вёрстка canvas-экспортёра Mindsweep Quiz, собранная
   заново. Размеры переведены из его базовой сетки 1280×720 в cqw
   кадра, поэтому пропорции совпадают с MP4. Кадр занимает слайд
   целиком, без заголовка, подписи и номера страницы. Шрифт — тот же
   системный гротеск, которым рисует экспортёр; Geologica и Onest
   сюда не заходят, иначе кадр перестанет быть кадром продукта.
   ============================================================ */
.q-frame-slide .pad { display: none; }
/* Единственная подпись поверх кадра: она называет, что за экран показан.
   Набор, отступ и подложка у неё те же, что у заголовка и номера на
   обычных слайдах, — меняется только выравнивание: по центру страницы,
   потому что слева и справа наверху у кадра свои бейджи. Подложка
   симметрично выходит за текст, потому что элемент стоит не у края. */
.q-frame-slide .slide-title {
  left: 50%; right: auto; translate: -50% 0;
  max-width: none;
  z-index: 3; isolation: isolate;
}
.q-frame-slide .slide-title::before {
  content: "";
  position: absolute;
  /* Подложка доходит до края экрана — как в каталоге у полноэкранного
     скриншота, только край здесь верхний, а не левый: элемент стоит по
     центру и ближайший к нему край — верх. */
  top: calc(-1 * var(--pad-y));
  bottom: -.8cqh;
  inset-inline: -1.4cqw;
  z-index: -1;
  background: var(--bg);
}

/* Номер страницы поверх кадра решён так же, как в каталоге у слайда со
   скриншотом во весь экран: подложка цвета колоды уходит за правый край.
   Она вынесена в псевдоэлемент, поэтому padding не двигает базовую линию
   цифр. */
.q-frame-slide .slide-number { z-index: 3; isolation: isolate; }
.q-frame-slide .slide-number::before {
  content: "";
  position: absolute;
  inset-block: -.8cqh;
  left: -1.4cqw;
  right: calc(-1 * var(--pad-x));
  z-index: -1;
  background: var(--bg);
}

.q-video {
  position: absolute; inset: 0;
  container-type: inline-size;
  background: #0a0a0a;
  overflow: hidden;
  --q-paper: #fafaf5;
  --q-ink: #0a0a0a;
  --q-accent: #ff2e4d;
  --q-accent2: #ffe600;
  --q-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --q-margin: 2.5cqw;
}
.q-video .media-cover { position: absolute; inset: 0; }
@keyframes q-question-fade-in { from { opacity: 0; } }
.slide[data-active] .q-video--question {
  animation: q-question-fade-in 500ms ease-in-out both;
}
/* Затемнение под HUD повторяет drawScrims экспортёра: без него белые
   бейджи теряются на светлой фотографии. Это часть кадра, а не декор. */
.q-video::before, .q-video::after { content: ""; position: absolute; left: 0; right: 0; }
.q-video::before {
  top: 0; height: 28%;
  background: linear-gradient(rgba(14, 10, 10, .58), rgba(14, 10, 10, .3) 52%, rgba(14, 10, 10, 0));
}
.q-video::after {
  top: 60%; bottom: 0;
  background: linear-gradient(rgba(14, 10, 10, 0), rgba(14, 10, 10, .34) 42%, rgba(14, 10, 10, .72));
}
.q-hud { position: absolute; inset: 0; z-index: 1; font-family: var(--q-font); }
.q-hud p { margin: 0; }

.q-hud-left { position: absolute; left: var(--q-margin); top: var(--q-margin); display: grid; justify-items: start; gap: .47cqw; }
.q-badge-round {
  background: var(--q-accent2); color: var(--q-ink);
  font: 700 2.34cqw/3.75cqw var(--q-font);
  padding: 0 1.25cqw; text-transform: uppercase;
}
.q-badge-theme {
  background: var(--q-ink); color: var(--q-paper);
  border: max(1px, .078cqw) solid var(--q-paper);
  font: 700 2.03cqw/3.28cqw var(--q-font);
  padding: 0 1.25cqw; text-transform: uppercase;
}

.q-hud-right {
  position: absolute; right: var(--q-margin); top: var(--q-margin);
  display: grid; justify-items: end;
}
.q-num {
  font: 700 10.31cqw/.76 var(--q-font);
  color: var(--q-paper); font-variant-numeric: tabular-nums;
  border-bottom: .3125cqw solid var(--q-accent2);
  padding-bottom: .62cqw;
}
.q-num-total {
  font: 700 2.03cqw/1 var(--q-font); color: var(--q-paper);
  text-transform: uppercase; padding-top: .78cqw;
}

/* Отсчёт идёт настоящий: лента из 26 значений едет вверх по одному
   шагу в секунду, последние шесть уже покрашены в красный, как их
   красит danger-ветка экспортёра. Лента, а не счётчик в CSS-переменной:
   так кадр одинаково работает в любом движке и в печати стоит на 25. */
.q-timer {
  position: absolute; left: 0; right: 0; top: 25.87cqw;
  transform: translateY(-50%);
  height: 11.72cqw; overflow: hidden;
  text-shadow: 0 .31cqw 2.81cqw rgba(0, 0, 0, .7);
}
.q-timer-reel {
  display: grid; justify-items: center; grid-auto-rows: 11.72cqw;
  font: 700 11.72cqw/1 var(--q-font);
  color: var(--q-paper); font-variant-numeric: tabular-nums;
}
.q-timer-reel > b {
  display: grid; place-items: center;
  width: 100%; height: 11.72cqw;
  font-weight: 700; line-height: 1;
}
.q-timer-reel > .is-danger { color: var(--q-accent); }

@keyframes q-tick { to { transform: translateY(-293cqw); } }
/* Отсчёт привязан к активному слайду, а не к переходу вперёд: он должен
   идти и когда на кадр попали по прямой ссылке или вернулись назад. */
.slide[data-active] .q-video--question .q-timer-reel {
  animation: q-tick 25s steps(25, end) both;
}

.q-plate {
  position: absolute; left: var(--q-margin); bottom: var(--q-margin);
  min-height: 7.34cqw;
  display: flex; align-items: center;
  border-left: .3125cqw solid var(--q-accent);
  padding: 1.72cqw;
  font: 700 3.44cqw/1.18 var(--q-font);
  text-transform: uppercase;
}
.q-plate--question {
  right: var(--q-margin);
  border-left-color: var(--q-accent2);
  color: var(--q-paper);
  text-shadow: 0 .23cqw 1.4cqw rgba(0, 0, 0, .7);
}
.q-plate--answer {
  max-width: calc(100% - var(--q-margin) * 2);
  background: var(--q-paper); color: var(--q-ink);
  box-shadow: 0 .47cqw 1.87cqw rgba(0, 0, 0, .55);
}

/* Ответ раскрывается сам, без нажатия, — как в видео. Тайминги взяты
   из createAnswerAnimationPlan: фотография проявляется с 1-й по 2-ю
   секунду, плашка выезжает с 3-й по 4-ю. Ширину плашки ведёт easeOutQuart
   экспортёра, прозрачность — easeInOutQuad, поэтому это две анимации
   с разными кривыми, а не одна. */
@keyframes q-photo-in { from { opacity: 0; } }
@keyframes q-plate-wipe { from { clip-path: inset(0 100% 0 0); } }
@keyframes q-plate-fade { from { opacity: 0; } }
.slide[data-reveal] .q-video--answer .q-photo {
  animation: q-photo-in 1000ms ease-in-out 1000ms both;
}
.slide[data-reveal] .q-plate--answer {
  animation:
    q-plate-wipe 1000ms cubic-bezier(.165, .84, .44, 1) 3000ms both,
    q-plate-fade 1000ms ease-in-out 3000ms both;
}


/* --- пакет SIGame как архив: одна коробка, внутри content.xml с вложенной
   структурой и три папки с медиа. Вложенные рамки показывают иерархию
   раунды → темы → вопросы без дерева из текста. --- */
.s-archive .pad { padding-top: calc(var(--pad-y) + 9cqh); }
.archive {
  display: grid;
  grid-template-rows: auto 1fr;
  border: var(--rule) solid var(--edge-strong);
}
.archive-name, .archive-file {
  margin: 0;
  display: flex; align-items: baseline; gap: 1.2cqw;
  font: 400 1.6cqw/1.4 var(--font-text); color: var(--muted);
}
.archive-name > strong, .archive-file > strong {
  font: 600 2.1cqw/1.2 var(--font-display); letter-spacing: -.02em; color: var(--text);
}
.archive-name {
  padding: 1.8cqh 2cqw;
  border-bottom: var(--rule) solid var(--edge-strong);
}
.archive-body {
  min-height: 0;
  display: grid;
  grid-template-columns: 1.2fr 1fr;
}
.archive-xml {
  min-height: 0;
  display: grid;
  grid-template-rows: auto auto;
  align-content: center;
  gap: 2cqh;
  padding: 3cqh 2cqw;
  border-right: var(--rule) solid var(--line);
}
.archive-level {
  min-height: 0;
  display: grid;
  grid-template-rows: auto auto;
  gap: 1.4cqh;
  padding: 1.6cqh 1.6cqw;
  border: var(--rule) solid var(--edge);
}
.archive-level > b { font: 600 1.9cqw/1.2 var(--font-display); letter-spacing: -.02em; }
.archive-level--last { background: var(--accent-fill); border-color: var(--accent-fill); }
.archive-level--last > b { color: var(--on-accent); }
.archive-media {
  --row-pad: 2.4cqh;
  margin: 0;
  padding: 3cqh 2cqw;
  align-content: center;
  font: 400 1.6cqw/1.4 var(--font-text);
  color: var(--muted);
}
.archive-media strong {
  display: block;
  font: 600 2.1cqw/1.2 var(--font-display); letter-spacing: -.02em; color: var(--text);
}

/* --- история в коммитах: перенос таймлайна дашборда. Сверху столбцы
   коммитов по месяцам стопкой из редактора и базы, под осью вехи в несколько
   рядов. Геометрия в единицах viewBox, цвета из токенов данных. Столбцы
   растут от оси тем же движением, что и .column-chart. --- */
.s-history .pad { gap: 1.6cqh; justify-content: space-between; }
.h-keys { display: flex; gap: 2.4cqw; margin: 0; justify-content: flex-start; font: 500 1.5cqw/1.4 var(--font-text); color: var(--muted); }
.h-keys span { display: inline-flex; align-items: center; gap: .7cqw; }
.h-keys i { width: 1.1cqw; height: 1.1cqw; background: var(--key); }
.h-chart { flex: 1; min-height: 0; width: 100%; display: block; align-self: center; }
.h-base { stroke: var(--text); stroke-width: 2; }
.h-month { fill: var(--muted); font: 500 24px var(--font-text); }
.h-value { fill: var(--text); font: 600 24px var(--font-display); font-variant-numeric: tabular-nums; }
.h-dot { fill: var(--accent-mark); stroke: var(--bg); stroke-width: 3; }
.h-label { fill: var(--text); font: 650 24px var(--font-display); letter-spacing: -.01em; }
.h-col { transform-box: view-box; transform-origin: var(--ox) 330px; }
@keyframes h-col-grow { from { transform: scaleY(0); } }
[data-reveal] .h-col { animation: h-col-grow 900ms cubic-bezier(.22, 1, .36, 1) both; }

/* --- проект в цифрах: табло с выравниванием по правому краю колонки --- */
.s-figures .scoreboard > div > .frag { justify-items: end; text-align: end; }
.s-figures .scoreboard .v { justify-self: end !important; font-variant-numeric: tabular-nums; white-space: nowrap; overflow: visible; }

/* --- редактор было / стало: первая страница показывает только «было»,
   раскрытие открывает «стало» и сдвигает центр к нему. Сдвиг задан
   keyframes, а не transition: атрибуты шага и метка движения ставятся
   не в один кадр, и transition успевает пропустить изменение. Движение
   только при переходе вперёд, при возврате и в печати сразу конечное состояние. --- */
.s-visual-compare--editor .comparison-split { grid-template-columns: minmax(0, 1fr) minmax(0, 0fr); }
.s-visual-compare--editor .comparison-split:has(> .is-after[data-shown]),
.s-visual-compare--editor .comparison-split:has(> .is-after:not(.frag)) { grid-template-columns: minmax(0, 1fr) minmax(0, 2.4fr); }
@keyframes editor-split-shift { from { grid-template-columns: minmax(0, 1fr) minmax(0, 0fr); } }
/* «Сегодня» въезжает: при раскрытии внутри слайда или при входе вперёд на отдельный слайд. */
.s-visual-compare--editor .comparison-split:has(> .is-after[data-reveal]),
.s-visual-compare--editor[data-reveal] .comparison-split:has(> .is-after:not(.frag)) {
  animation: editor-split-shift 800ms cubic-bezier(.22, 1, .36, 1) both;
}
.s-visual-compare--editor .comparison-side { min-width: 0; overflow: hidden; }
.s-visual-compare--editor .comparison-side.is-after { padding-inline: var(--pad-x); }
.s-visual-compare--editor .comparison-body { align-content: center; justify-items: start; }
.s-visual-compare--editor .frame { width: 100%; max-width: 66cqw; height: auto; aspect-ratio: 16 / 9; }


/* --- итерации: шаблон «UI в первый день / Сегодня» три раза подряд ---
   Каждый шаг повторяет то же движение, что и у пары: новая красная
   колонка въезжает справа, прежние ужимаются вместе со своими кадрами,
   прежняя красная темнеет. На третьем шаге прошлые кадры встают
   столбиком слева, чтобы «Сегодня» осталось крупным: «Конец первого
   дня» уезжает под «Первую итерацию» той же анимацией. Колонки заданы
   абсолютными рамками, поэтому любое изменение геометрии анимируется
   одним правилом. Движение только при переходе вперёд. --- */
.s-iterations .comparison-split { display: block; --left: 29.41%; }
.s-iterations .comparison-side {
  position: absolute;
  top: 0; left: 100%; width: 0; height: 100%;
  min-width: 0;
  overflow: hidden;
  padding-inline: var(--pad-x);
  background: var(--bg);
  color: var(--craft-text);
  --pad-top: calc(var(--pad-y) + 10cqh);
  --pad-bottom: calc(var(--pad-y) + 8cqh);
  padding-block: var(--pad-top) var(--pad-bottom);
}
/* Шаг 1: первая на весь экран. */
.s-iterations .comparison-side[data-for="1"] { left: 0; width: 100%; }
/* Шаг 2: первая слева, вторая справа. */
.s-iterations .comparison-split:has(> [data-for="2"][data-shown]) > [data-for="1"] { width: var(--left); }
.s-iterations .comparison-split:has(> [data-for="2"][data-shown]) > [data-for="2"] { left: var(--left); width: calc(100% - var(--left)); }
/* Шаг 3: первая и вторая столбиком слева, третья справа. */
.s-iterations .comparison-split:has(> [data-for="3"][data-shown]) > [data-for="1"],
.s-iterations .comparison-split:has(> [data-for="3"][data-shown]) > [data-for="2"] {
  left: 0; width: var(--left); height: 50%;
  --pad-top: calc(var(--pad-y) + 6cqh);
  --pad-bottom: var(--pad-y);
}
.s-iterations .comparison-split:has(> [data-for="3"][data-shown]) > [data-for="2"] { top: 50%; }
.s-iterations .comparison-split:has(> [data-for="3"][data-shown]) > [data-for="3"] { left: var(--left); width: calc(100% - var(--left)); }
/* Красная та колонка, что въехала последней. Первая не въезжает, она тёмная. */
.s-iterations .comparison-side[data-shown]:not(:has(~ .comparison-side[data-shown])) {
  background: var(--accent-fill);
  color: var(--on-accent);
}
/* Откуда ехать при переходе вперёд: из рамок предыдущего шага. */
@keyframes iteration-full { from { left: 0; width: 100%; height: 100%; } }
@keyframes iteration-left { from { left: 0; top: 0; width: var(--left); height: 100%; padding-block: calc(var(--pad-y) + 10cqh) calc(var(--pad-y) + 8cqh); } }
@keyframes iteration-right-red {
  from { left: var(--left); top: 0; width: calc(100% - var(--left)); height: 100%;
         padding-block: calc(var(--pad-y) + 10cqh) calc(var(--pad-y) + 8cqh);
         background: var(--accent-fill); color: var(--on-accent); }
}
@keyframes iteration-enter { from { left: 100%; width: 0; } }
.s-iterations .comparison-side { animation-duration: 800ms; animation-timing-function: cubic-bezier(.22, 1, .36, 1); animation-fill-mode: both; }
.s-iterations .comparison-split:has(> [data-for="2"][data-reveal]) > [data-for="1"] { animation-name: iteration-full; }
.s-iterations .comparison-split:has(> [data-for="2"][data-reveal]) > [data-for="2"] { animation-name: iteration-enter; }
.s-iterations .comparison-split:has(> [data-for="3"][data-reveal]) > [data-for="1"] { animation-name: iteration-left; }
.s-iterations .comparison-split:has(> [data-for="3"][data-reveal]) > [data-for="2"] { animation-name: iteration-right-red; }
.s-iterations .comparison-split:has(> [data-for="3"][data-reveal]) > [data-for="3"] { animation-name: iteration-enter; }
.s-iterations .comparison-title { max-width: none; right: var(--pad-x); }
.s-iterations .comparison-body { align-content: center; justify-items: start; }
.s-iterations .frame { width: 100%; max-width: 66cqw; height: auto; aspect-ratio: 16 / 9; overflow: hidden; }
.s-iterations .frame img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: top; }
/* Номер на подложке, как у скриншотов: плашка уходит за правый край. */
.s-iterations > .slide-number { color: var(--craft-text); z-index: 3; isolation: isolate; }
.s-iterations > .slide-number::before {
  content: "";
  position: absolute;
  inset-block: -.8cqh;
  left: -1.4cqw;
  right: calc(-1 * var(--pad-x));
  z-index: -1;
  background: var(--bg);
}

/* --- дизайн из вариантов: три ряда, варианты переезжают между ними ---
   Слева три шага напротив своих рядов. Справа холст: четыре варианта в
   верхнем ряду, на втором шаге два отмеченных съезжают во второй ряд,
   на третьем съезжаются в собранный интерфейс третьего ряда и прячутся
   под ним. Позиции в переменных, откуда ехать тоже, движение только при
   переходе вперёд, без затуханий. Рамки SVG. --- */
.s-design-flow { --pick-a: var(--accent); --pick-b: var(--craft-warn); --row: 21cqh; }
.s-design-flow .design-flow {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
  grid-template-rows: var(--row) var(--row) 16cqh;
  column-gap: 3cqw;
  align-content: center;
}
.s-design-flow .design-label { display: grid; gap: .8cqh; align-content: start; padding-top: 1cqh; }
.s-design-flow .design-label[data-step="1"] { grid-area: 1 / 1; }
.s-design-flow .design-label[data-step="2"] { grid-area: 2 / 1; }
.s-design-flow .design-label[data-step="3"] { grid-area: 3 / 1; }
.s-design-flow .design-label .n { font: 700 2.5cqw/1 var(--font-display); letter-spacing: -.03em; color: var(--accent); }
.s-design-flow .design-label h3 { font: 600 1.8cqw/1.25 var(--font-display); letter-spacing: -.02em; margin: 0; }
.s-design-flow .design-canvas { grid-area: 1 / 2 / 4 / 3; position: relative; min-height: 0; }
.s-design-flow .wire-card {
  position: absolute;
  top: var(--top, 0);
  left: var(--left, calc(var(--i) * 26%));
  width: var(--w, 22%);
  z-index: 1;
  visibility: hidden;
}
.s-design-flow .wire { width: 100%; height: auto; display: block; fill: none; stroke: var(--edge); stroke-width: 1.5; }
.s-design-flow .wire rect:first-child { fill: var(--surface); }
/* Шаг 1: варианты видны. */
.s-design-flow .design-flow:has([data-step="1"][data-shown]) .wire-card:not(.wire-ghost) { visibility: visible; }
/* Копии на прежних местах: не двигаются, не анимируются, только притушены. */
.s-design-flow .wire-ghost { z-index: 0; animation: none !important; }
.s-design-flow .wire-ghost .wire { opacity: .3; }
.s-design-flow .wire-ghost .wire rect { stroke: var(--edge) !important; stroke-width: 1.5 !important; }
.s-design-flow .wire-ghost[data-ghost="2"] { --top: var(--row); }
.s-design-flow .wire-ghost[data-ghost="2"][data-gp="a"] { --left: 26%; }
.s-design-flow .wire-ghost[data-ghost="2"][data-gp="b"] { --left: 52%; }
.s-design-flow .design-flow:has([data-step="2"][data-shown]) .wire-ghost[data-ghost="1"],
.s-design-flow .design-flow:has([data-step="3"][data-shown]) .wire-ghost[data-ghost="2"] { visibility: visible; }
/* Шаг 2: отмеченные во втором ряду и в цвете, остальные притушены. */
.s-design-flow .design-flow:has([data-step="2"][data-shown]) .wire-card:not([data-pick]):not(.wire-ghost) .wire { opacity: .3; }
.s-design-flow .design-flow:has([data-step="2"][data-shown]) .wire-card[data-pick] {
  --top: var(--row); --w: 22%;
  --from-top: 0; --from-left: calc(var(--i) * 26%); --from-w: 22%;
}
.s-design-flow .design-flow:has([data-step="2"][data-shown]) .wire-card[data-pick="a"] { --left: 26%; }
.s-design-flow .design-flow:has([data-step="2"][data-shown]) .wire-card[data-pick="b"] { --left: 52%; }
.s-design-flow .design-flow:has([data-step="2"][data-shown]) .wire .is-a { stroke: var(--pick-a); }
.s-design-flow .design-flow:has([data-step="2"][data-shown]) .wire .is-b { stroke: var(--pick-b); }
.s-design-flow .design-flow:has([data-step="2"][data-shown]) .wire-card[data-pick="a"] .wire > rect:first-child { stroke: var(--pick-a); stroke-width: 2.5; }
.s-design-flow .design-flow:has([data-step="2"][data-shown]) .wire-card[data-pick="b"] .wire > rect:first-child { stroke: var(--pick-b); stroke-width: 2.5; }
/* Шаг 3: отмеченные съезжаются в третий ряд и сливаются в один интерфейс.
   Красный отдаёт панель и карточки, жёлтый шапку: его рамка становится
   прозрачной, строки схлопываются, шапка сдвигается за панель, карточки
   красного опускаются под шапку. Геометрия SVG анимируется как CSS. --- */
.s-design-flow .design-flow:has([data-step="3"][data-shown]) .wire-card[data-pick] {
  --top: calc(var(--row) * 2); --left: 39%; --w: 22%;
  --from-top: var(--row); --from-w: 22%;
}
.s-design-flow .design-flow:has([data-step="3"][data-shown]) .wire-card[data-pick="a"] { --from-left: 26%; z-index: 1; }
.s-design-flow .design-flow:has([data-step="3"][data-shown]) .wire-card[data-pick="b"] { --from-left: 52%; z-index: 2; }
.s-design-flow .design-flow:has([data-step="3"][data-shown]) .wire-card[data-pick="a"] .wire > rect.frame { stroke: var(--edge); stroke-width: 1.5; }
.s-design-flow .design-flow:has([data-step="3"][data-shown]) .wire-card[data-pick="b"] .wire > rect.frame { fill: none; stroke: none; }
.s-design-flow .design-flow:has([data-step="3"][data-shown]) .wire-card[data-pick="a"] .cards.r0 { y: 24px; height: 28px; }
.s-design-flow .design-flow:has([data-step="3"][data-shown]) .wire-card[data-pick="a"] .cards.r1 { y: 60px; height: 28px; }
.s-design-flow .design-flow:has([data-step="3"][data-shown]) .wire-card[data-pick="b"] .rows { height: 0px; stroke-width: 0; }
.s-design-flow .design-flow:has([data-step="3"][data-shown]) .wire-card[data-pick="b"] .top { x: 40.5px; width: 119px; }
/* Притушение плавное: копии и отвергнутые варианты гаснут до 30%, пока карточки едут. */
@keyframes design-dim { from { opacity: 1; } }
.s-design-flow .design-flow:has([data-step="2"][data-reveal]) .wire-card:not([data-pick]):not(.wire-ghost) .wire,
.s-design-flow .design-flow:has([data-step="2"][data-reveal]) .wire-ghost[data-ghost="1"] .wire,
.s-design-flow .design-flow:has([data-step="3"][data-reveal]) .wire-ghost[data-ghost="2"] .wire {
  animation: design-dim 800ms cubic-bezier(.22, 1, .36, 1) both;
}
/* Движение при переходе вперёд: из позиций предыдущего шага. */
@keyframes design-move { from { top: var(--from-top); left: var(--from-left); width: var(--from-w); } }
@keyframes merge-cards-r0 { from { y: 10px; height: 36px; } }
@keyframes merge-cards-r1 { from { y: 54px; height: 36px; } }
@keyframes merge-rows { from { height: 10px; stroke-width: 1.5; } }
@keyframes merge-top { from { x: .5px; width: 159px; } }
.s-design-flow .design-flow:has([data-step="2"][data-reveal]) .wire-card[data-pick],
.s-design-flow .design-flow:has([data-step="3"][data-reveal]) .wire-card[data-pick] {
  animation: design-move 800ms cubic-bezier(.22, 1, .36, 1) both;
}
.s-design-flow .design-flow:has([data-step="3"][data-reveal]) .wire-card[data-pick] rect { animation-duration: 800ms; animation-timing-function: cubic-bezier(.22, 1, .36, 1); animation-fill-mode: both; }
.s-design-flow .design-flow:has([data-step="3"][data-reveal]) .wire-card[data-pick="a"] .cards.r0 { animation-name: merge-cards-r0; }
.s-design-flow .design-flow:has([data-step="3"][data-reveal]) .wire-card[data-pick="a"] .cards.r1 { animation-name: merge-cards-r1; }
/* Строки схлопываются быстро, пока карточка ещё в пути: иначе они проезжают поверх карточек красного. */
.s-design-flow .design-flow:has([data-step="3"][data-reveal]) .wire-card[data-pick="b"] .rows { animation-name: merge-rows; animation-duration: 150ms; animation-timing-function: linear; }
.s-design-flow .design-flow:has([data-step="3"][data-reveal]) .wire-card[data-pick="b"] .top { animation-name: merge-top; }

/* --- длинный скриншот: рамка во всю полезную высоту, картинка едет вверх ---
   Прокрутка привязана к активному слайду, как отсчёт таймера: идёт и по
   прямой ссылке, и при возврате. Конец на последнем экране страницы. --- */
.s-scroll-shot .shot {
  --shot-h: 86cqh;
  top: 14cqh;
  width: 62cqw;
  height: var(--shot-h);
  aspect-ratio: auto;
  overflow: hidden;
  border-bottom: 0;
}
.s-scroll-shot .shot img { width: 100%; height: auto; display: block; }
@keyframes scroll-shot { to { translate: 0 calc(-100% + var(--shot-h)); } }
.slide[data-active].s-scroll-shot .shot img { animation: scroll-shot 35s linear 1s both; }
@media print { .s-scroll-shot .shot img { animation: none; } }

/* --- пара картинок: две рамки 16:9 рядом, подписи под ними --- */
.s-pair .pad { padding-block: calc(var(--pad-y) + 10cqh) calc(var(--pad-y) + 6cqh); }
.s-pair .pad:not(:has(> .slide-title)) { padding-block: calc(var(--pad-y) + 4cqh); }
.s-pair .pair { flex: 1; min-height: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 3cqw; align-content: center; }
.s-pair .pair-item { margin: 0; display: grid; gap: 1.6cqh; }
/* Обе рамки одной высоты, по пропорции более высокой картинки. Более
   широкая прижата к верху, остаток внизу закрашен цветом её фона
   через --pair-fill, чтобы выглядело как продолжение экрана. */
.s-pair .pair { align-items: start; }
.s-pair .pair-item .frame { width: 100%; height: auto; aspect-ratio: 1408 / 719; overflow: hidden; background: var(--pair-fill, var(--surface)); }
.s-pair .pair-item img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; object-position: top; }
.s-pair figcaption { font: 600 1.8cqw/1.25 var(--font-display); letter-spacing: -.02em; }

/* --- вехи под теми же месяцами, что и столбцы графика --- */
.h-events { flex: 1; min-height: 0; width: 100%; display: block; align-self: center; overflow: visible; }
/* Невидимая легенда держит ось месяцев на той же высоте, что и на графике. */
.h-keys--spacer { visibility: hidden; }
.h-tick { stroke: var(--line); stroke-width: 2; }
/* Главные разделы доклада красным, остальные вехи приглушённо. */
.h-mark .h-dot { fill: var(--text); }
.h-mark .h-label { fill: var(--text); }
.h-mark--main .h-dot { fill: var(--accent-mark); }
.h-mark--main .h-label { fill: var(--accent); }
.h-events .h-label { font-size: 22px; }
/* Подложка закрывает линии, но сливается с фоном слайда. */
.h-events .h-label-bg { fill: var(--bg); }
/* Вход вперёд: ось поднимается с места оси графика, столбцы едут с ней
   и сжимаются вдвое, затем вехи проявляются слева направо. Без входа
   вперёд и в печати всё стоит в конечном положении. */
.h-axis, .h-ghost { transform-box: view-box; }
.h-ghost { transform-origin: 0 330px; transform: translateY(-180px) scaleY(.5); }
.h-ghost .h-value { opacity: 0; }
@keyframes h-axis-rise { from { transform: translateY(180px); } }
@keyframes h-ghost-squash { from { transform: translateY(0) scaleY(1); } to { transform: translateY(-180px) scaleY(.5); } }
@keyframes h-ghost-out { from { opacity: 1; } to { opacity: 0; } }
@keyframes h-marks-in { from { opacity: 0; } }
.slide[data-reveal] .h-axis { animation: h-axis-rise 900ms cubic-bezier(.22, 1, .36, 1) 150ms both; }
.slide[data-reveal] .h-ghost { animation: h-ghost-squash 900ms cubic-bezier(.22, 1, .36, 1) 150ms both; }
.slide[data-reveal] .h-ghost .h-col { animation: none; }
.slide[data-reveal] .h-ghost .h-value { animation: h-ghost-out 250ms linear both; }
.slide[data-reveal] .h-mark { animation: h-marks-in 450ms ease-out calc(650ms + var(--i) * 90ms) both; }
@media print {
  .slide[data-reveal] .h-axis, .slide[data-reveal] .h-ghost, .slide[data-reveal] .h-ghost .h-value, .slide[data-reveal] .h-mark { animation: none !important; }
}
