/* Scroll story section. Every class is prefixed `story-` because the source
   template used .container, .hero and .col — all three already belong to the
   hero grid above. */
:root {
  --st-light: #efeae3;
  --st-dark: #0d0d0f;
}

.story {
  position: relative;
  width: 100%;
  background-color: var(--st-light);
  will-change: background-color;
  font-family: var(--ov-font-display);
}

.story h2,
.story h3 {
  font-weight: 500;
  margin: 0;
}

.story h2 {
  font-size: clamp(1.75rem, 4vw, 4rem);
  letter-spacing: -0.03em;
  line-height: 1.125;
}

.story h3 {
  font-size: clamp(1.25rem, 2.4vw, 2.25rem);
  letter-spacing: -0.02em;
  line-height: 1.2;
}

.story img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ── Opening statement ───────────────────────────────────────────────── */
.story-intro {
  /* The highlighter plate and the ink that sits on it. Same pairing the
     horizontal section uses for its own marker, so the two read as one
     device: the gold is a fill here, never text, and the near-black on top of
     it measures 8.6:1. */
  --si-accent: #d4a857;
  --si-on-accent: #14100c;
  position: relative;
  width: 100%;
  height: 100svh;
  padding: clamp(1.5rem, 4vw, 3rem);
  /* Explicit column. The old rule set align-content on a block element, where
     it does nothing — the centring was coming from elsewhere and there was no
     way to anchor anything to the foot of the section. */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  overflow: hidden;
}

/* ── Contour field ───────────────────────────────────────────────────── */
/* A <canvas> is a replaced element: with inset:0 and no width/height it does
   not stretch, it sits at its default 300x150 in the corner. The explicit
   sizing is what makes this fill its parent — which is now the whole light
   panel, so the field carries on behind the marquee instead of ending at the
   fold. */
.story-contours {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  z-index: 0;
  pointer-events: none;
}

/* ── Statement ───────────────────────────────────────────────────────── */
/* Selector weighted past `.story h2`, which sets a size, a leading and a
   tracking of its own for the section's other headings. Matching its
   specificity and relying on source order would work today and break the
   first time these rules are reordered. */
/* Selector weighted past `.story h2`, which sets a size, a leading and a
   tracking of its own for the section's other headings. Matching its
   specificity and relying on source order would work today and break the
   first time these rules are reordered. */
.story .story-statement {
  position: relative;
  z-index: 1;
  width: min(92%, 1100px);
  margin: 0 auto;
  /* Zodiak, the project's own serif, rather than the condensed display face
     tried first. That one was heavy enough to shout over a section whose job
     is to set a tone, and at 6.4vw it filled the screen. This is lighter in
     colour, smaller, and it is the face the highlighter reference is set in.
     Already loaded for the rest of the page, so it costs nothing. */
  font-family: var(--vc-serif, "Zodiak", Georgia, serif);
  font-weight: 700;
  font-size: clamp(1.75rem, 4.4vw, 4rem);
  line-height: 1.14;
  letter-spacing: -0.015em;
  text-wrap: balance;
}

/* The highlighter.
   
   Built as a background gradient on the span itself, with
   box-decoration-break, and not as an absolutely positioned pseudo-element.
   That distinction is load-bearing wherever this pattern appears in this
   project: an absolute ::before resolves against a single inline fragment, so
   the moment a highlighted phrase wraps, every line but one is left with no
   plate behind it — and since the ink is near-black to sit on the gold, those
   lines would vanish into the page. This way each line fragment carries its
   own plate.
   
   The plate sits on the baseline like a real marker — under the ascenders,
   not over them.
   
   It is background-size that is animated, not a custom property. A transition
   on a custom property does nothing unless the property has been registered
   through @property, which is a newer feature than everything else this file
   relies on; background-size has been transitionable forever and needs no
   registration.
   
   Painted is the default state on purpose. If the script never runs the copy
   still reads as intended rather than as bare ink; story.js closes the plate
   during setup and reopens it as the words resolve. */
.story .story-statement em {
  font-style: normal;
  color: var(--si-on-accent);
  padding: 0 0.14em;
  background-image: linear-gradient(var(--si-accent), var(--si-accent));
  background-repeat: no-repeat;
  background-position: left bottom 0.04em;
  background-size: 100% 0.86em;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
  transition: background-size 0.75s cubic-bezier(0.22, 1, 0.36, 1);
}

/* ── Scroll cue ──────────────────────────────────────────────────────── */
.story-cue {
  position: absolute;
  left: 50%;
  bottom: clamp(1.5rem, 4vh, 3rem);
  transform: translateX(-50%);
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.7rem;
  text-decoration: none;
  color: rgba(13, 13, 15, 0.55);
  font-family: var(--rs-mono, "JetBrains Mono", monospace);
  /* 11px / 0.2em: los mismos valores que .sec-eyebrow en style.css. Este
     aviso y los índices de sección son la misma clase de objeto —una etiqueta
     mono en versalita— y estaban a dos escalas distintas sin motivo. */
  font-size: 11px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  opacity: 0;
  transition: color 0.3s ease;
}
.story-cue:hover { color: rgba(13, 13, 15, 0.9); }

/* Filete que baja del texto hacia la flecha.

   Antes el rótulo y la flecha flotaban sueltos, separados por aire. El filete
   les da un eje y, sobre todo, incorpora el aviso al vocabulario de la página:
   es la versión vertical del filete que separa numeral y nombre en el índice
   de sección, y el mismo gesto trazado que .story-intro-line hace sobre el
   titular de esta misma sección.

   Va con `order` porque en el DOM la flecha sigue al texto y el
   pseudo-elemento caería después de ambos; el orden visual que se busca es
   texto → filete → flecha. */
.story-cue__text { order: 1; }

.story-cue::after {
  content: "";
  order: 2;
  width: 1px;
  height: clamp(16px, 2.6vh, 24px);
  background: currentColor;
  opacity: 0.28;
  transition: height 0.45s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.45s ease;
}

.story-cue__arrow { order: 3; }

/* El filete se extiende al pasar por encima: un gesto de trazo, coherente con
   cómo el resto de la página dibuja sus líneas, en lugar de un simple cambio
   de color. */
.story-cue:hover::after {
  height: clamp(24px, 3.6vh, 34px);
  opacity: 0.5;
}

.story-cue__arrow svg {
  display: block;
  width: 12px;
  height: 34px;
  stroke: currentColor;
  stroke-width: 1.3;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Travels the length of its own stroke and restarts — the pause at the top of
   each cycle is what keeps it from reading as a jitter. */
@keyframes storyCueNudge {
  0%, 62% { transform: translateY(0); opacity: 1; }
  82% { transform: translateY(7px); opacity: 0.25; }
  83% { transform: translateY(-7px); opacity: 0; }
  100% { transform: translateY(0); opacity: 1; }
}
.story-cue__arrow { animation: storyCueNudge 2.8s ease-in-out infinite; }

@media (prefers-reduced-motion: reduce) {
  .story-flow__layer--a,
  .story-flow__layer--b,
  .story-cue__arrow { animation: none; }
  .story-cue::after { transition: none; }
  }

/* Draws in as the page is scrolled, completing just before the words begin
   to reveal — a lead-in the eye follows down into the phrase, rather than a
   static rule. Scoped to above the heading only: no matching line runs after
   it, so it reads as an approach, not a bracket around the text. */
.story-intro-line {
  position: relative;
  display: block;
  width: 9px;
  height: clamp(64px, 10vh, 120px);
  margin: 0 auto clamp(1.5rem, 4vh, 2.5rem);
}

/* The unfilled measure, drawn faintly from the start. Showing the full extent
   up front is what keeps the draw from reading as open-ended: the stroke is
   visibly filling a length it cannot pass. */
.story-line-track {
  position: absolute;
  left: 50%;
  top: 0;
  width: 1px;
  height: 100%;
  transform: translateX(-50%);
  background: linear-gradient(
    to bottom,
    rgba(13, 13, 15, 0.16) 0%,
    rgba(13, 13, 15, 0.05) 100%
  );
}

/* The stroke itself, weighted so it darkens toward the leading edge — it
   reads as being extruded downward rather than simply becoming taller. */
.story-line-draw {
  position: absolute;
  left: 50%;
  top: 0;
  width: 1px;
  height: 100%;
  transform: translateX(-50%) scaleY(0);
  transform-origin: top center;
  background: linear-gradient(
    to bottom,
    rgba(13, 13, 15, 0.12) 0%,
    rgba(13, 13, 15, 0.5) 45%,
    rgba(13, 13, 15, 0.9) 100%
  );
  will-change: transform;
}

/* Origin mark: the stroke is anchored to something, not floating. */
.story-line-node {
  position: absolute;
  left: 50%;
  top: 0;
  width: 3px;
  height: 3px;
  margin: -1px 0 0 -1.5px;
  border-radius: 50%;
  background: rgba(13, 13, 15, 0.55);
}

/* Rides the leading edge as the stroke extends, then settles as the terminal
   mark once the measure is full. */
.story-line-tip {
  position: absolute;
  left: 50%;
  top: 0;
  width: 5px;
  height: 5px;
  margin: -2.5px 0 0 -2.5px;
  border-radius: 50%;
  background: #0d0d0f;
  box-shadow:
    0 0 0 3px rgba(13, 13, 15, 0.08),
    0 0 12px 2px rgba(13, 13, 15, 0.18);
  opacity: 0;
  will-change: transform, opacity;
}

@media (prefers-reduced-motion: reduce) {
  .story-line-draw { transform: translateX(-50%) scaleY(1) !important; }
  .story-line-tip { opacity: 1 !important; }
}

/* Word-by-word blur-in for the opening statement — a port of the TextBlurIn
   component. Each word starts fully blurred and transparent and resolves in
   place; there is no vertical travel, which is what makes it read as coming
   into focus rather than sliding in. story.js wraps each word and drives the
   tween (duration 0.8, stagger 0.04, ease-out — the component's own values). */
.reveal-word {
  display: inline-block;
  opacity: 0;
  filter: blur(10px);
}

@media (prefers-reduced-motion: reduce) {
  .reveal-word {
    opacity: 1;
    filter: none;
  }
}

/* ── Tilted image marquee ───────────────────────────────────────────── */
.story-marquee {
  position: relative;
  width: 100%;
  height: 50svh;
  overflow: hidden;
}

.story-marquee-wrapper {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) rotate(-5deg);
  width: 150%;
  height: 100%;
}

.story-marquee-images {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-75%, -50%);
  width: 200%;
  height: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  will-change: transform;
}

.story-marquee-img {
  flex: 1;
  width: 100%;
  aspect-ratio: 5/3;
}

@media (max-width: 1000px) {
  .story-intro h2 { width: 100%; }

  .story-marquee-wrapper { width: 300%; }

}

/* ── Marquee drift, on the compositor ─────────────────────────────────── */
/* The tilted strip slides sideways as it enters. Same motion as before; it
   is who computes it that changed.
   
   A scrubbed tween is evaluated on the main thread from a scroll position the
   compositor has already moved past, so the strip applies last frame's offset
   to this frame's scroll. This strip shows that error more than most of the
   page because it sits inside a rotate(-5deg): a rotated composited layer has
   no axis-aligned edges, so a sub-pixel error lands on the diagonal boundary
   of every photograph at once and reads as the whole band shimmering rather
   than as one thing being slightly late.
   
   Driven from a view() timeline the offset is resolved by the compositor
   alongside the scroll, so there is no error left to land anywhere.
   
   The range is the same window the scrub used — the strip's top edge reaching
   the bottom of the viewport, through to it reaching the top. .story-marquee
   is 50svh, so that window is the first two thirds of `cover` (one viewport
   out of the viewport-plus-half-viewport that `cover` spans). The section's
   height is a fixed fraction of the viewport, so that ratio holds at every
   size, phones included. */
@keyframes story-marquee-drift {
  from { transform: translate(-75%, -50%); }
  to { transform: translate(-50%, -50%); }
}

@supports (animation-timeline: view()) {
  .story-marquee {
    view-timeline: --story-marquee-in block;
  }
  .story-marquee-images {
    animation: story-marquee-drift 1ms linear both;
    animation-timeline: --story-marquee-in;
    animation-range: cover 0% cover 66.6667%;
  }
}
