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

html {
  background: var(--zl-paper);
  color: var(--zl-ink);
  font-family: var(--zl-font-body);
  scroll-behavior: smooth;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}

body {
  margin: 0;
  min-width: 20rem;
  overflow-x: hidden;
  background: var(--zl-paper);
  color: var(--zl-body);
  font-size: var(--zl-text-md);
  line-height: var(--zl-leading-copy);
}

button,
input,
textarea,
select {
  font: inherit;
}

a {
  color: inherit;
}

img,
svg,
canvas {
  display: block;
  max-width: 100%;
}

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

h1,
h2,
h3 {
  color: var(--zl-ink);
  font-family: var(--zl-font-display);
  font-weight: var(--zl-weight-regular);
  letter-spacing: var(--zl-tracking-display);
}

h1 {
  max-width: 13ch;
  font-size: var(--zl-text-display);
  line-height: var(--zl-leading-tight);
}

h2 {
  max-width: 18ch;
  font-size: var(--zl-text-h2);
  line-height: var(--zl-leading-heading);
}

h3 {
  font-size: var(--zl-text-h3);
  line-height: 1.12;
}

:focus-visible {
  outline: 2px solid var(--zl-cobalt);
  outline-offset: 4px;
}

::selection {
  background: var(--zl-vermilion);
  color: var(--zl-on-vermilion);
}

.zl-page {
  padding-inline: var(--zl-page-gutter);
}

.zl-frame {
  width: min(100%, var(--zl-frame-max));
  margin-inline: auto;
  border-inline: 1px solid var(--zl-line);
}

.zl-section {
  padding: var(--zl-section-space) clamp(1.25rem, 3.4vw, 4rem);
  border-bottom: 1px solid var(--zl-line);
}

.zl-grid-2 {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.zl-stack {
  display: flex;
  flex-direction: column;
  gap: var(--zl-space-5);
}

.zl-lead {
  max-width: 38rem;
  color: var(--zl-body);
  font-size: var(--zl-type-lead-size);
  line-height: var(--zl-leading-lead);
}

.zl-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

@media (max-width: 52rem) {
  .zl-grid-2 {
    grid-template-columns: 1fr;
  }

  .zl-frame {
    border-inline: 0;
  }

  .zl-section {
    padding-inline: 0;
  }
}

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

  *,
  *::before,
  *::after {
    scroll-behavior: auto !important;
    animation-duration: var(--zl-duration-reduced) !important;
    animation-iteration-count: 1 !important;
    transition-duration: var(--zl-duration-reduced) !important;
  }
}

/* ---------------------------------------------------------------------------
   Scroll reveal
   Driven by scripts/reveal.js. The hidden start state is gated on
   html[data-reveal-ready], which only that script sets -- so if it never runs,
   nothing is hidden and the page renders complete. Distances are deliberately
   small: 20px of rise reads considered, 60px reads like a slideshow.
   --------------------------------------------------------------------------- */
[data-reveal-ready] [data-reveal]:not([data-reveal="none"]) {
  opacity: 0;
  transform: translate3d(0, var(--reveal-rise, 28px), 0);
  transition:
    opacity var(--zl-duration-reveal) var(--zl-ease-standard),
    transform var(--zl-duration-reveal) var(--zl-ease-standard);
  transition-delay: calc(var(--reveal-index, 0) * var(--reveal-step, 90ms));
  will-change: opacity, transform;
}

[data-reveal-ready] [data-reveal="fade"] { --reveal-rise: 0px; }
[data-reveal-ready] [data-reveal="rise-sm"] { --reveal-rise: 12px; }
[data-reveal-ready] [data-reveal="rise-lg"] { --reveal-rise: 32px; }

[data-reveal-ready] [data-reveal][data-reveal-state="in"] {
  opacity: 1;
  transform: none;
  will-change: auto;
}

@media (prefers-reduced-motion: reduce) {
  [data-reveal-ready] [data-reveal] {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
}

/* Word-by-word blur reveal — see scripts/blur-text.js.
   Mirrors the supplied BlurText keyframes: blurred and offset, through a
   half-opacity mid-step, to sharp. Only applies once the script has split the
   text (data-blur-ready), so the headline is plain and legible without JS. */
[data-blur-ready] { --blur-stagger: 70ms; }

[data-blur-ready] .blur-word {
  display: inline-block;
  opacity: 0;
  filter: blur(10px);
  transform: translateY(-0.42em);
  will-change: transform, filter, opacity;
}

[data-blur-ready][data-blur-state="in"] .blur-word {
  animation: blur-word-in 0.7s var(--zl-ease-standard) forwards;
  /* --blur-base lets a caller cascade whole blocks (see the proof row, which
     walks left to right in step with its sweep) on top of the word stagger. */
  animation-delay: calc(var(--blur-base, 0ms) + var(--word-index, 0) * var(--blur-stagger));
}

@keyframes blur-word-in {
  0%   { opacity: 0;  filter: blur(10px); transform: translateY(-0.42em); }
  50%  { opacity: .5; filter: blur(5px);  transform: translateY(0.05em); }
  100% { opacity: 1;  filter: blur(0);    transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  [data-blur-ready] .blur-word,
  [data-blur-ready][data-blur-state="in"] .blur-word {
    opacity: 1;
    filter: none;
    transform: none;
    animation: none;
    will-change: auto;
  }
}
