/* base styles */

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

/* Tokens are generated from @evilmartians/harmony and prepended at build
   time. Only var(--zerp-*) color references are allowed in this file. */

/* The five font stacks, named once. A deck that configures its own families
   (zerp.fonts in its package.json) gets these five redefined after this
   sheet, which is the whole mechanism — every rule below reads a token, so a
   family lives in exactly one place.

   "Zerp Symbols" is a single-glyph face declared with unicode-range: U+2192,
   so the browser consults it for → and for nothing else. It sits behind the
   real family in every general stack: an author who types → anywhere gets a
   known arrow instead of a per-machine fallback, and no other character is
   affected. --zerp-font-marker names it first, for the two arrow markers:
   a pipeline reading a rendered deck back out takes the first family of the
   computed stack as the run's font. --zerp-font-nav leaves it out entirely —
   see .nav button. */
:root {
  --zerp-font-body: "Montserrat", "Zerp Symbols", sans-serif;
  --zerp-font-display: "Montserrat", "Zerp Symbols", sans-serif;
  --zerp-font-marker: "Zerp Symbols", "Montserrat", sans-serif;
  --zerp-font-mono: "Roboto Mono", "Zerp Symbols", monospace;
  --zerp-font-nav: "Roboto Mono", monospace;

  /* The deck's design canvas. A deck that declares zerp.size gets these two
     overridden by a size-tokens style block; everything geometric below
     reads them, so the canvas lives in exactly one place. */
  --zerp-stage-w: 1920px;
  --zerp-stage-h: 1080px;
}

html,
body {
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: var(--zerp-bg);
  color: var(--zerp-text);
  font-family: var(--zerp-font-body);
}

b,
strong {
  font-weight: 700;
}

em,
i {
  font-style: italic;
}

img,
video {
  max-width: 100%;
}

img {
  border-radius: 10px;
  object-fit: contain;
  max-height: calc(var(--zerp-stage-h) * 0.6);
}

a {
  color: var(--zerp-accent);
  text-decoration: none;
  font-weight: 700;
}

a:hover {
  text-decoration: underline;
}

hr {
  border: none;
  border-top: 1px solid var(--zerp-border);
  margin: 20px 0;
}

/* The stage is the deck's fixed design canvas. The runtime scale-to-fits it
   into the real viewport with a transform; layout inside never changes, so
   every measurement (checks, exports, print) sees the same geometry at any
   window size. container-type: size makes cqw/cqh resolve against the
   canvas — the sanctioned author-facing stage-relative units. Chrome stays
   OUTSIDE the stage: a transformed ancestor would become the containing
   block of its position: fixed elements. */
[data-zerp-stage] {
  position: relative;
  width: var(--zerp-stage-w);
  height: var(--zerp-stage-h);
  transform-origin: 0 0;
  container-type: size;
}

/* The frame owns slide visibility. The inner .slide remains an authored
   layout surface, so a deck can safely choose display: grid/flex/block on a
   slide root without changing whether inactive slides occupy the page. */
[data-zerp-slide] {
  display: none;
  width: var(--zerp-stage-w);
  height: var(--zerp-stage-h);
}

[data-zerp-slide][data-zerp-slide-active] {
  display: flex;
}

.slide {
  display: flex;
  width: 100%;
  height: 100%;
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  padding: 50px 70px;
  flex-direction: column;
  /* "safe" so an overflowing slide spills below the frame only: verify's
     body.scrollHeight then measures the full overflow magnitude and print
     clips bottom-only. Plain "center" would push the top half of the overflow
     above the frame edge where it is clipped and invisible. Engines without
     "safe" support degrade to plain center (the status quo). */
  justify-content: safe center;
  position: relative;
}

.slide.top {
  justify-content: flex-start;
}

/* Default vertical rhythm between slide blocks. Kept before component
   rules so a component's own margin wins the specificity tie. */
.slide > * + * {
  margin-top: 14px;
}

/* The hidden attribute must win over display-setting classes like .row. */
[hidden] {
  display: none !important;
}

/* Declarative step reveals: the runtime toggles .revealed on [data-step]
   elements and .step-done on [data-until-step] elements as ↓/↑ advance the
   slide's step counter. Space is reserved (visibility, not display) so
   stepping never reflows a centered slide. Restyle [data-step].revealed in
   deck CSS for highlight-type steps instead of appear-type steps.

   Scoped to @media screen so they simply do not exist in print: there,
   [data-step] content prints in its final revealed state without specificity
   fights, and the print block at the end of this file owns [data-until-step]'s
   final (absent) state. css-model.ts skips every rule nested in an at-rule and
   these attribute selectors were already unsupported there, so the scoping is
   inert for zerp check. */
@media screen {
  [data-step]:not(.revealed) {
    visibility: hidden;
  }

  [data-until-step].step-done {
    visibility: hidden;
  }

  /* Swap-type steps exchange same-sized content in place (a label, a glyph):
     collapse instead of reserving, so the counterpart can take the slot. */
  [data-step].swap:not(.revealed) {
    display: none;
  }

  [data-until-step].swap.step-done {
    display: none;
  }
}

/* Headings are display type, and a deck may set a face for them alone. Zero
   specificity via :where() so `h1 { font-family: var(--zerp-font-body) }` in a
   deck's own stylesheet is enough to opt out — with `.slide h1` an author would
   have to match the framework's own selector to decline the feature. Opting
   other elements in works either way: a rule naming h2 directly beats the
   family h2 inherits from .slide. */
:where(.slide h1) {
  font-family: var(--zerp-font-display);
}

.slide h1 {
  font-size: 3.2em;
  font-weight: 900;
  line-height: 1.15;
  margin-bottom: 20px;
}

.slide h2 {
  font-size: 2.2em;
  font-weight: 700;
  line-height: 1.2;
  margin-bottom: 14px;
}

.slide h3 {
  font-size: 1.4em;
  font-weight: 700;
  margin-bottom: 10px;
}

/* Soft defaults live in :where() so a utility class on the element always
   wins (e.g. h3.accent, p.xl, .value.red). Structural properties stay in
   the specific rules above/below and are not meant to be overridden. */
.slide :where(h3) {
  color: var(--zerp-muted);
}

/* Inside a boxed component an h3 is a card title, not a muted subtitle. */
.card :where(h3),
.key-thought :where(h3),
.steps > * :where(h3) {
  color: var(--zerp-text);
}

.slide :where(p, li) {
  font-size: 1.25em;
  line-height: 1.5;
}

.slide ul {
  list-style: none;
  padding: 0;
}

.slide li {
  padding: 5px 0;
}

/* --zerp-font-marker, not --zerp-font-body: the symbol face has to be named
   first here, because a pipeline that reads a rendered deck back out takes the
   first family of the computed stack as the run's one font, and this marker is
   the arrow. It carries nothing but U+2192, so the body family still draws the
   space after it. */
.slide ul li::before {
  content: "→ ";
  font-family: var(--zerp-font-marker);
  color: var(--zerp-accent);
}

.slide ol {
  list-style: none;
  padding: 0;
  counter-reset: zerp-ol;
}

/* Block flow, not flex: the counter is an in-flow inline box on the first
   line, so wrapped lines return under the marker exactly like ul's arrow.
   A flex li would also make the marker a flex item, which export pipelines
   (PPTX) cannot position — an inline marker is plain text to them. */
.slide ol > li {
  counter-increment: zerp-ol;
  border-bottom: 1px solid var(--zerp-border);
  padding: 10px 0;
}

.slide ol > li:last-child {
  border-bottom: none;
}

.slide ol > li::before {
  content: counter(zerp-ol, decimal-leading-zero);
  margin-right: 16px;
  font-family: var(--zerp-font-mono);
  font-size: 1.2em;
  font-weight: 700;
  color: var(--zerp-accent);
}

code,
kbd {
  font-family: var(--zerp-font-mono);
  font-size: 0.95em;
  background: var(--zerp-surface);
  border: 1px solid var(--zerp-border);
  border-radius: 6px;
  padding: 2px 8px;
}

pre {
  background: var(--zerp-surface);
  border: 1px solid var(--zerp-border);
  border-radius: 10px;
  padding: 18px 24px;
  margin: 16px 0;
  overflow: auto;
  text-align: left;
}

pre code {
  background: none;
  border: none;
  padding: 0;
  font-size: 1.05em;
  line-height: 1.6;
}

blockquote {
  border: 1px solid var(--zerp-border);
  border-left: 4px solid var(--zerp-accent);
  padding: 14px 20px;
  margin: 16px 0;
  background: var(--zerp-surface);
  border-radius: 0 10px 10px 0;
  font-style: italic;
  font-size: 1.2em;
}

blockquote p {
  font-size: 1em;
}

table {
  border-collapse: collapse;
  margin: 16px auto;
  font-size: 1.15em;
}

th,
td {
  padding: 10px 18px;
  text-align: left;
}

th {
  font-weight: 700;
  border-bottom: 2px solid var(--zerp-border);
}

td {
  border-bottom: 1px solid var(--zerp-border);
}

tr:last-child td {
  border-bottom: none;
}

table.mono td {
  font-family: var(--zerp-font-mono);
}

table.compact th,
table.compact td {
  padding: 4px 14px;
  text-align: center;
}

figure {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  margin: 16px 0;
}

figure img {
  max-height: calc(var(--zerp-stage-h) * 0.55);
}

figcaption,
.caption {
  font-size: 1.25em;
  color: var(--zerp-muted);
  text-align: center;
}

.card {
  background: var(--zerp-surface);
  border: 1px solid var(--zerp-border);
  border-radius: 14px;
  padding: 24px 28px;
}

.cols-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 32px;
  align-items: center;
}

.cols-3 {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 32px;
  align-items: center;
}

.cols-4 {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 24px;
  align-items: center;
}

/* Cards in a shared row read as a set: equalize their heights. */
.cols-2 > .card,
.cols-3 > .card,
.cols-4 > .card,
.row > .card,
.flow > .card,
.compare > .card {
  align-self: stretch;
}

.stat {
  text-align: center;
}

.stat .value {
  font-family: var(--zerp-font-mono);
  font-size: 3em;
  font-weight: 900;
  line-height: 1.1;
}

.stat :where(.value) {
  color: var(--zerp-accent);
}

.stat .label {
  margin-top: 6px;
}

.stat :where(.label) {
  font-size: 1.25em;
  color: var(--zerp-muted);
}

.stat-row {
  display: flex;
  gap: 48px;
  align-items: flex-end;
  justify-content: center;
  flex-wrap: wrap;
  margin: 24px 0;
}

.compare {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  gap: 30px;
  align-items: center;
  margin: 24px 0;
}

.compare > :first-child {
  grid-column: 1;
  grid-row: 1;
}

.compare > :last-child {
  grid-column: 3;
  grid-row: 1;
}

.compare::after {
  content: "vs";
  grid-column: 2;
  grid-row: 1;
  font-size: 1.6em;
  font-weight: 900;
  color: var(--zerp-faint);
}

.compare[data-vs]::after {
  content: attr(data-vs);
}

.flow {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 36px;
  flex-wrap: wrap;
  margin: 20px 0;
  font-size: 1.25em;
  font-weight: 700;
}

.flow > * + * {
  position: relative;
}

.flow > * + *::before {
  content: "→";
  font-family: var(--zerp-font-marker);
  position: absolute;
  top: 50%;
  left: -18px;
  transform: translate(-50%, -50%);
  color: var(--zerp-faint);
  font-weight: 400;
  font-size: 1.25rem;
}

.flow > :where(.card) {
  font-size: 1rem;
  font-weight: 400;
}

.steps {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 28px;
  margin: 24px 0;
  counter-reset: zerp-step;
}

.steps > * {
  background: var(--zerp-surface);
  border: 1px solid var(--zerp-border);
  border-radius: 14px;
  padding: 24px;
  text-align: center;
  counter-increment: zerp-step;
}

.steps > *::before {
  content: counter(zerp-step, decimal-leading-zero);
  display: block;
  font-family: var(--zerp-font-mono);
  font-size: 2em;
  font-weight: 900;
  color: var(--zerp-accent);
  margin-bottom: 8px;
}

.timeline {
  display: flex;
  gap: 20px;
  flex-wrap: wrap;
  justify-content: center;
  margin: 16px 0;
}

.timeline .item {
  background: var(--zerp-surface);
  border: 1px solid var(--zerp-border);
  border-radius: 10px;
  padding: 14px 18px;
  text-align: center;
  min-width: 130px;
}

.timeline .item .year {
  font-family: var(--zerp-font-mono);
  font-weight: 700;
}

.timeline :where(.item .year) {
  font-size: 1.3em;
  color: var(--zerp-accent);
}

.timeline .item .label {
  margin-top: 4px;
}

.timeline :where(.item .label) {
  font-size: 1em;
}

.key-thought {
  background: var(--zerp-surface);
  border: 2px solid var(--zerp-accent);
  border-radius: 14px;
  padding: 28px 36px;
  margin: 16px 0;
  text-align: center;
}

.key-thought :where(p) {
  font-size: 1.4em;
  font-weight: 700;
}

.key-thought > * + * {
  margin-top: 8px;
}

/* Consistent affordance line for stepped slides: "↓ deal the next card". */
.step-hint {
  font-size: 1em;
  font-weight: 700;
  color: var(--zerp-muted);
  text-align: center;
}

/* Definition-slide header: big icon, shouty label; follow with a plain or
   .muted paragraph for the tagline. */
.concept {
  text-align: center;
}

.concept .icon {
  font-size: 5em;
  line-height: 1.2;
}

.concept .label {
  margin-top: 10px;
}

.concept :where(.label) {
  font-size: 2.4em;
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 5px;
  color: var(--zerp-accent);
}

.pill {
  display: inline-block;
  width: fit-content;
  border-radius: 999px;
  padding: 5px 16px;
  font-size: 1em;
  font-weight: 700;
  background: var(--zerp-surface);
  border: 1px solid var(--zerp-border);
}

.interactive-badge {
  display: inline-block;
  width: fit-content;
  border-radius: 999px;
  padding: 5px 16px;
  font-size: 1em;
  font-weight: 700;
  background: var(--zerp-green-tint);
  color: var(--zerp-green-on-tint);
  border: 1px solid var(--zerp-green-solid);
  margin-bottom: 14px;
}

/* Title-slide recipe: <div class="slide title"> with h1 + h3 + .meta. */
.slide.title {
  text-align: center;
}

.slide.title h1 {
  font-size: 4em;
}

.title :where(h3) {
  font-size: 1.6em;
  color: var(--zerp-accent);
}

.slide.title .meta {
  margin-top: 36px;
  color: var(--zerp-muted);
  line-height: 1.7;
}

.block-label {
  position: absolute;
  top: 24px;
  left: 70px;
  font-size: 1em;
  font-weight: 700;
  color: var(--zerp-muted);
  text-transform: uppercase;
  letter-spacing: 3px;
}

.corner {
  position: absolute;
  top: 40px;
  right: 70px;
  margin: 0;
}

.corner img {
  width: 140px;
}

.img-row {
  display: flex;
  gap: 24px;
  align-items: center;
  justify-content: center;
  margin: 24px 0;
  flex-wrap: wrap;
}

.img-row img {
  max-height: 300px;
  border: 1px solid var(--zerp-border);
  border-radius: 10px;
  object-fit: contain;
  background: var(--zerp-surface);
  padding: 6px;
}

/* Captioned entries in an image row: wrap each img in a figure. */
.img-row :where(figure) {
  margin: 0;
}

.grid-demo {
  display: grid;
  grid-template-columns: repeat(7, 48px);
  gap: 3px;
  justify-content: center;
  margin: 16px 0;
}

.grid-demo .cell {
  width: 48px;
  height: 48px;
  border: 2px solid var(--zerp-border);
  border-radius: 5px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1em;
  font-weight: 700;
  color: var(--zerp-muted);
}

.grid-demo .cell.filled {
  background: var(--zerp-red-solid);
  border-color: var(--zerp-red-solid);
  color: var(--zerp-on-solid);
}

.center {
  text-align: center;
}

.row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 24px;
  flex-wrap: wrap;
  margin: 16px 0;
}

/* A table inside a row is laid out by the row's gap — its own auto-centering
   margins would swallow the row's free space and fling the tables apart. */
.row > :where(table) {
  margin: 0;
}

.stack {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.spread {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}

.grow {
  flex: 1;
}

.lg {
  font-size: 1.25em;
}

.xl {
  font-size: 1.6em;
}

.sm {
  font-size: 0.9em;
}

/* Detail line under a heading or bold statement (safe muted-at-small). */
.sub {
  font-size: 1em;
  font-weight: 700;
  color: var(--zerp-muted);
}

/* Width-capped centered block; margin-inline keeps the vertical rhythm. */
.narrow {
  max-width: 720px;
  margin-inline: auto;
}

.mono {
  font-family: var(--zerp-font-mono);
}

.raised {
  box-shadow: var(--zerp-shadow);
}

.muted {
  color: var(--zerp-muted);
}

.accent {
  color: var(--zerp-accent);
  font-weight: 700;
}

.ok {
  color: var(--zerp-ok);
  font-weight: 700;
}

.warn {
  color: var(--zerp-warn);
  font-weight: 700;
}

.danger {
  color: var(--zerp-danger);
  font-weight: 700;
}

.blue {
  color: var(--zerp-blue);
  font-weight: 700;
}

.green {
  color: var(--zerp-green);
  font-weight: 700;
}

.orange {
  color: var(--zerp-orange);
  font-weight: 700;
}

.purple {
  color: var(--zerp-purple);
  font-weight: 700;
}

.red {
  color: var(--zerp-red);
  font-weight: 700;
}

.amber {
  color: var(--zerp-amber);
  font-weight: 700;
}

.teal {
  color: var(--zerp-teal);
  font-weight: 700;
}

.tint-blue {
  background: var(--zerp-blue-tint);
}

.tint-green {
  background: var(--zerp-green-tint);
}

.tint-orange {
  background: var(--zerp-orange-tint);
}

.tint-purple {
  background: var(--zerp-purple-tint);
}

.tint-red {
  background: var(--zerp-red-tint);
}

.tint-amber {
  background: var(--zerp-amber-tint);
}

.tint-teal {
  background: var(--zerp-teal-tint);
}

/* Border emphasis for surface components (.card, .key-thought, .timeline
   .item, .steps cells): recolors the existing border by meaning. */
.edge-blue {
  border-color: var(--zerp-blue);
}

.edge-green {
  border-color: var(--zerp-green);
}

.edge-orange {
  border-color: var(--zerp-orange);
}

.edge-purple {
  border-color: var(--zerp-purple);
}

.edge-red {
  border-color: var(--zerp-red);
}

.edge-amber {
  border-color: var(--zerp-amber);
}

.edge-teal {
  border-color: var(--zerp-teal);
}

.edge-accent {
  border-color: var(--zerp-accent);
}

.edge-ok {
  border-color: var(--zerp-ok);
}

.edge-warn {
  border-color: var(--zerp-warn);
}

.edge-danger {
  border-color: var(--zerp-danger);
}

/* Colored emphasis inside a matching tint auto-pairs with the on-tint role. */
.tint-blue .blue,
.tint-blue .accent {
  color: var(--zerp-blue-on-tint);
}

.tint-green .green,
.tint-green .ok {
  color: var(--zerp-green-on-tint);
}

.tint-orange .orange {
  color: var(--zerp-orange-on-tint);
}

.tint-purple .purple {
  color: var(--zerp-purple-on-tint);
}

.tint-red .red,
.tint-red .danger {
  color: var(--zerp-red-on-tint);
}

.tint-amber .amber,
.tint-amber .warn {
  color: var(--zerp-amber-on-tint);
}

.tint-teal .teal {
  color: var(--zerp-teal-on-tint);
}

.pill.tint-blue,
.pill.accent {
  background: var(--zerp-blue-tint);
  color: var(--zerp-blue-on-tint);
  border-color: var(--zerp-blue-solid);
}

.pill.tint-green,
.pill.ok {
  background: var(--zerp-green-tint);
  color: var(--zerp-green-on-tint);
  border-color: var(--zerp-green-solid);
}

.pill.tint-orange {
  background: var(--zerp-orange-tint);
  color: var(--zerp-orange-on-tint);
  border-color: var(--zerp-orange-solid);
}

.pill.tint-purple {
  background: var(--zerp-purple-tint);
  color: var(--zerp-purple-on-tint);
  border-color: var(--zerp-purple-solid);
}

.pill.tint-red,
.pill.danger {
  background: var(--zerp-red-tint);
  color: var(--zerp-red-on-tint);
  border-color: var(--zerp-red-solid);
}

.pill.tint-amber,
.pill.warn {
  background: var(--zerp-amber-tint);
  color: var(--zerp-amber-on-tint);
  border-color: var(--zerp-amber-solid);
}

.pill.tint-teal {
  background: var(--zerp-teal-tint);
  color: var(--zerp-teal-on-tint);
  border-color: var(--zerp-teal-solid);
}

.nav {
  position: fixed;
  bottom: 24px;
  right: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  gap: 10px;
  z-index: 100;
}

/* The one stack deliberately left without "Zerp Symbols": these two buttons
   are ← and →, and no bundled subset covers either, so today both come from
   the same system fallback and match each other. Adding the U+2192-only face
   would resolve → and leave ← behind, pairing a long thin arrow with a short
   heavy one. Nav chrome is never exported (it is display:none in print), so
   it gains nothing from a deterministic glyph and would only lose the pair. */
.nav button {
  background: none;
  border: none;
  color: var(--zerp-muted);
  padding: 0 8px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  cursor: pointer;
  font-size: 1em;
  font-weight: 700;
  font-family: var(--zerp-font-nav);
}

.nav button:hover {
  color: var(--zerp-text);
}

.nav button:disabled {
  color: var(--zerp-faint);
  cursor: default;
}

.counter {
  position: fixed;
  bottom: 24px;
  left: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  font-size: 1em;
  font-weight: 700;
  color: var(--zerp-muted);
  font-family: var(--zerp-font-mono);
  z-index: 100;
}

.source-badge {
  position: fixed;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%);
  height: 36px;
  display: flex;
  align-items: center;
  padding: 0 18px;
  border-radius: 999px;
  border: 1px solid var(--zerp-border);
  background: var(--zerp-surface);
  color: var(--zerp-muted);
  font-size: 1em;
  font-weight: 700;
  font-family: var(--zerp-font-mono);
  z-index: 100;
}

.source-badge[hidden] {
  display: none;
}

.progress {
  position: fixed;
  top: 0;
  left: 0;
  height: 3px;
  background: var(--zerp-accent);
  z-index: 100;
  transition: width 0.3s;
}

.theme-toggle {
  position: fixed;
  bottom: 24px;
  right: 110px;
  width: 36px;
  height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  border-radius: 999px;
  color: var(--zerp-muted);
  cursor: pointer;
  padding: 0;
  z-index: 100;
}

.theme-toggle:hover {
  color: var(--zerp-text);
}

/* Both icons ship; the runtime names the one that matches the next press. */
.theme-toggle svg {
  width: 20px;
  height: 20px;
  display: none;
}

.theme-toggle[data-theme-target="light"] .theme-icon-sun,
.theme-toggle[data-theme-target="dark"] .theme-icon-moon {
  display: block;
}

/* ---- print: one page per slide (Chromium print backend) ---- */
@media print {
  html,
  body {
    width: auto;
    height: auto;
    overflow: visible;
  }

  /* The screen rules above are stage-fixed (--zerp-stage-w/h), so print
     re-asserts viewport units here. In Chromium's print context vh
     resolves against the page area, so 100vh = one page at any print
     size. overflow:hidden clips slide overflow instead of letting it
     perturb pagination. */
  [data-zerp-slide] {
    display: flex;
    width: 100vw;
    height: 100vh;
    break-after: page;
    break-inside: avoid;
    overflow: hidden;
  }

  /* No trailing blank page after the last slide. */
  [data-zerp-slide]:not(:has(~ [data-zerp-slide])) {
    break-after: auto;
  }

  /* The screen transform must not shrink the printed page. container-type:
     size stays active here (inherited from the screen rule), so an `auto`
     box would resolve to a 0 container and collapse any author cqh/cqw
     units. 100vw/100vh keep the container tracking the page box instead,
     consistent with each frame being one page in print. */
  [data-zerp-stage] {
    transform: none !important;
    width: 100vw;
    height: 100vh;
  }

  /* Presentation chrome has no meaning on paper. */
  .nav,
  .counter,
  .theme-toggle,
  .progress,
  .source-badge {
    display: none !important;
  }

  /* Steps print in their final state: [data-step] content is visible
     (its screen-only hiding rules do not apply in print); "until"
     content is gone — swap variants collapse, others reserve space. */
  [data-until-step] {
    visibility: hidden;
  }

  [data-until-step].swap {
    display: none;
  }

  :root {
    print-color-adjust: exact;
    -webkit-print-color-adjust: exact;
  }
}
