/* ============================================================================
   The decision loop.

   Two surfaces, one diagram. Loaded by / (the gate section under the hero)
   and by /karar-dongusu/ (the stage that page is built on).

   WHY THERE IS NO JAVASCRIPT IN HERE. check.py fails the build on any scroll
   listener, and that rule is the reason this is built on CSS scroll-driven
   animations (`animation-timeline: view()`), not on a rAF loop. The browser
   drives every frame off the scroller itself, so nothing here can jank the
   main thread and nothing has to be torn down on navigation.

   THE BASE STYLES ARE THE FINISHED STATE. Everything scroll-driven lives
   inside `@supports (animation-timeline: view())`, and the styles outside it
   describe the page as it looks when the choreography has already run: the
   gate full-bleed, the loop page's words at their corners, the diagram
   visible. So a browser without scroll timelines, and a visitor who asked for
   reduced motion, both get a finished composition rather than an empty one.
   This is the same contract steps.css states for its scenes, and it is why
   the reduced-motion branch here is an explicit opt-out: the global
   `animation-duration: .01ms` reset in pursula.css cannot neutralise a
   scroll-driven animation, whose progress is scroll position and not time.

   Colour: every value resolves through the surface tokens (--fg, --muted,
   --rule, --signal, --link, --trail), so both components re-skin for light and
   dark and cannot violate DESIGN.md 3.3 by being moved between an ink and a
   bone section. No raw hex, per check.py.
   ========================================================================= */

/* The gate's geometry, registered so it can interpolate. An unregistered
   custom property animates as a discrete swap at 50%, which is the difference
   between a box that grows and a box that jumps. */
@property --gate-max {
  syntax: '<length>';
  inherits: true;
  initial-value: 100vw;
}
@property --gate-pad-x {
  syntax: '<length>';
  inherits: true;
  initial-value: 0px;
}
@property --gate-pad-y {
  syntax: '<length>';
  inherits: true;
  initial-value: 0px;
}
@property --gate-radius {
  syntax: '<length>';
  inherits: true;
  initial-value: 0px;
}

/* ============================================================ 1. the gate ==

   The section under the hero. It starts as a panel inset at the site gutter
   and opens to full bleed as the reader arrives, which is the one moment on
   this site where a section announces itself.

   THE SECTION'S OWN HEIGHT NEVER CHANGES WHILE THE ANIMATION RUNS, and that
   is load-bearing rather than tidy. `animation-timeline: view()` measures the
   element against the scrollport; if the same animation also changed the
   element's height, every frame would move the range it is being measured
   against and the panel would judder. So the padding is INSIDE a border-box
   min-height, and the vertical half of the animation only runs where a locked
   viewport height is safe (see --gate-pad-y-start below).
   ========================================================================= */

.loopgate {
  position: relative;
  display: grid;
  padding: var(--gate-pad-y) var(--gate-pad-x);

  /* Named for the circuit inside it. The line and the tiles have to draw
     against the PANEL's arrival, not against their own boxes: the same
     animation is resizing them, and a timeline measuring a box you are
     resizing moves the range it is measuring against on every frame. */
  view-timeline: --gate block;

  /* Where the opening starts FROM. The vertical one is 0 by default, so on a
     short or narrow viewport the panel opens sideways only and the section's
     height is fixed by its content, which cannot feed back into the timeline.
     The desktop query below is the one place it becomes non-zero, and it
     turns on the locked height in the same rule. */
  --gate-pad-x-start: var(--pu-safe-l);
  --gate-pad-y-start: 0px;
}

/* WHY 1100 AND 700, MEASURED. The circular ring needs the core copy plus the
   top and bottom stages to fit the panel's height, and the core gets TALLER as
   the panel narrows because it wraps: 334px at 1100px wide, 382px at 920px.
   At 920x700 the top and bottom stages overlapped the copy by 59 and 102px,
   and at 1366x768 they met it exactly. Below this threshold the ring is the
   same two-up list mobile gets, which is legible at any size - and the height
   lock switches on with it, so a locked section can never hold a ring that
   does not fit. */
@media (min-width: 1100px) and (min-height: 700px) {
  .loopgate {
    /* height, NOT min-height. Measured: with min-height the ring's own
       intrinsic height (619px) plus the frame's padding pushed the section to
       907px at the start of the opening and 860px at the end, so the section
       was resizing the very range the view timeline measures it against. A
       hard lock takes the section's height out of the animation entirely. */
    height: 100svh;
    --gate-pad-y-start: var(--pu-7);
  }
  /* Consequence of the lock: the panel and the ring fill the row they are
     given instead of asking for a height. Nothing inside the ring has an
     intrinsic height worth speaking of - only the core copy is in flow, the
     wire, the arrows and the four stages are all positioned. */
  .loopgate__frame { min-height: 0; }
  .loopgate .circuit { min-height: 0; height: 100%; }
}

.loopgate__frame {
  position: relative;
  width: min(var(--gate-max), 100%);
  margin-inline: auto;
  display: grid;
  border-radius: var(--gate-radius);
  /* THE PANEL IS THE DEEPER SURFACE AND THE SURROUND IS THE HERO'S OWN INK
     (founder's decision, 31 August 2026 - the two were the other way round).
     It reads better as well as being what was asked for: the panel is now a
     recess cut into the hero's material rather than a slab sitting on the
     reading surface, and opening it to full bleed deepens the whole screen
     instead of merely widening a box. */
  background: var(--pu-ink-deep);
  padding: var(--pu-8) var(--pu-6);
  /* Nothing may escape while the panel is small: the diagram's nodes are
     absolutely positioned against this box. `clip` rather than `hidden`
     because there is nothing here to scroll. */
  overflow: clip;
}

@media (min-width: 900px) {
  .loopgate__frame { padding: var(--pu-9) var(--pu-8); }
}

/* Ink-deep on ink is 1.19:1 in BOTH themes now, because .surf-brand is ink
   whichever theme is running. DESIGN.md 5: separation on ink is a hairline,
   never a shadow. Outline rather than border, so it moves no layout, and it
   fades out as the panel reaches full bleed - by then there is no surround
   left for it to separate the panel from. */
.loopgate__frame {
  outline: 1px solid var(--pu-ink-raised);
  outline-offset: -1px;
}

/* ONE LINK, WHOLE PANEL. The call to action is the only interactive element
   in the box and its ::after covers the panel, so the entire area is the same
   single link: one tab stop, one accessible name, and no nested anchors. */
/* NO `position` ON THE LINK ITSELF, and that is the whole trick. An absolutely
   positioned ::after resolves against its element's nearest POSITIONED
   ancestor, so a `position: relative` here made the link its own containing
   block and the hit area covered the button plus a little air instead of the
   panel - measured, five of eight probe points on the panel missed the link.
   Left unpositioned, the ::after resolves against .ring, which fills the
   panel's content box, so the offsets that carry it out to the panel's own
   edge are exactly the frame's padding. z-index goes on the ::after, which IS
   positioned, and lifts it over the absolutely positioned stages. */
.loopgate__cta::after {
  content: '';
  position: absolute;
  z-index: 1;
  inset: calc(var(--pu-8) * -1) calc(var(--pu-6) * -1);
}
@media (min-width: 900px) {
  .loopgate__cta::after { inset: calc(var(--pu-9) * -1) calc(var(--pu-8) * -1); }
}

/* SHORT DESKTOP. A 1366x768 laptop is the common case the circular ring only
   just misses, and it misses on height alone. Halving the panel's vertical
   padding and stepping the core copy down recovers 160px of ring, which turns
   a 1px overlap into a 67px gap. Both are reversed above 880px, where there is
   room for the copy at full size. */
@media (min-width: 1100px) and (max-height: 880px) {
  .loopgate__frame { padding-block: var(--pu-7); }
  .circuit__core .h1 { font-size: clamp(1.5rem, 1rem + 1.4vw, 2.125rem); }
  .circuit__core .lead { font-size: 1rem; }
  .circuit__core > * + * { margin-top: var(--pu-4); }
}
.loopgate__frame:hover .loopgate__cta { background: var(--pu-ember-press); }

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .loopgate {
      animation: loopgate-open linear both;
      animation-timeline: view();
      /* Finishes exactly when the section is fully in view, which is the
         moment the panel is asked to be the whole screen. */
      animation-range: entry 12% entry 100%;
    }
  }
}

@keyframes loopgate-open {
  from {
    --gate-max: var(--pu-w-std);
    --gate-pad-x: var(--gate-pad-x-start);
    --gate-pad-y: var(--gate-pad-y-start);
    --gate-radius: var(--pu-r-lg);
  }
  to {
    --gate-max: 100vw;
    --gate-pad-x: 0px;
    --gate-pad-y: 0px;
    --gate-radius: 0px;
  }
}

/* ======================================================== 2. the circuit ==

   Problem -> Karar -> Sonuc -> Deneyim, clockwise, with the sentence the
   diagram is about sitting inside it.

   WHAT THIS REPLACED AND WHY. The first version was a hairline ellipse with
   the four labels floating outside it and four direction marks built from the
   two borders of a rotated square. It failed on three counts the design
   references name outright: a ring drawn round a void is a progress ring
   standing in for content; direction marks made of CSS borders are not drawn
   icons and read as crop marks; and stripped of its copy the composition said
   nothing at all. What it also did was opt out of this system's own strongest
   move - DESIGN.md 6 documents the signature motion as an SVG path drawn with
   stroke-dashoffset - and reach for an ornament instead.

   So the loop IS that path now. One continuous clockwise circuit, drawn rather
   than decorated, with the signal the engine consumes riding it as tiles: the
   media, behaviour, message and transaction the copy already names. The tiles
   drift, which is the one perpetual motion on this site and a deliberate
   exception to DESIGN.md 6's "never looped" (founder's decision, 31 August
   2026): a static constellation reads as a diagram, a drifting one reads as
   something running.

   PATH GEOMETRY IS SHARED WITH CSS, EXACTLY. The svg is preserveAspectRatio
   ="none" over a 0 0 100 100 box, so a point at (x, y) on the path lands at
   left: x% / top: y% of the element whatever the panel's aspect ratio is.
   Every tile percentage below is a real point ON the path, which is why they
   sit on the line at 1100px and still sit on it at full bleed. The stroke is
   vector-effect: non-scaling-stroke, so stretching never thickens the
   hairline.

   MOBILE IS NOT A SHRUNK CIRCUIT. Below 1100px the four stages are a two-up
   list under the copy and the circuit is gone: a loop narrow enough to fit a
   phone puts every label on top of its neighbour, and a diagram nobody can
   read is worse than a list everybody can.
   ========================================================================= */

.circuit {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  justify-items: center;
  align-content: center;
  gap: var(--pu-7);
  width: 100%;
}

.circuit__core {
  text-align: center;
  max-width: 46ch;
}
.circuit__core .lead { color: var(--muted); }
.circuit__core > * + * { margin-top: var(--pu-5); }

.circuit__stages {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--pu-6) var(--pu-5);
  width: 100%;
}

.circuit__stage {
  display: grid;
  gap: var(--pu-2);
  align-content: start;
}
.circuit__num {
  font-family: var(--pu-font-mono);
  font-size: .75rem;
  letter-spacing: .12em;
  color: var(--signal);
  font-variant-numeric: tabular-nums;
}
.circuit__name {
  font-size: 1.0625rem;
  font-weight: 600;
  letter-spacing: -0.01em;
}
.circuit__note {
  font-size: .9375rem;
  line-height: 1.45;
  color: var(--muted);
}

/* The line, the tiles and the return mark only exist in the circuit layout. */
.circuit__wire,
.circuit__tile,
.circuit__return { display: none; }

@media (min-width: 1100px) and (min-height: 700px) {
  .circuit {
    min-height: min(660px, 72svh);
    gap: 0;
  }

  /* THE CORE STAYS IN FLOW and is centred by the grid, rather than being
     positioned like everything else here. That is what keeps .circuit the
     containing block for .loopgate__cta::after: a positioned core would
     capture it and the panel-wide hit area would collapse to the copy. */
  .circuit__core {
    width: min(44ch, 40%);
    max-width: none;
  }

  .circuit__stages {
    display: block;
    position: absolute;
    inset: 0;
  }

  .circuit__stage { position: absolute; width: min(22ch, 20%); }
  /* The four points where the path crosses the mid-line of each side, pulled
     just inside it so the label sits within the circuit rather than
     straddling the hairline. */
  .circuit__stage--1 { top: 7%; left: 50%; translate: -50% 0; text-align: center; }
  .circuit__stage--2 { top: 50%; right: 6%; translate: 0 -50%; text-align: left; }
  .circuit__stage--3 { bottom: 7%; left: 50%; translate: -50% 0; text-align: center; }
  .circuit__stage--4 { top: 50%; left: 6%; translate: 0 -50%; text-align: right; }

  .circuit__wire {
    display: block;
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    overflow: visible;
    pointer-events: none;
  }
  .circuit__line {
    fill: none;
    stroke: var(--trail);
    stroke-width: 1;
    stroke-opacity: var(--trail-op);
    vector-effect: non-scaling-stroke;
  }

  /* THE ONE DIRECTION MARK ON THE PAGE, at the point where the circuit closes
     and hands back to Problem. One rather than four: the numbers already carry
     the order and the line draws in the direction of travel, so a second,
     third and fourth arrowhead only add noise to the thing they were meant to
     clarify. It sits on the top edge, which stays horizontal under any
     stretch, so left/top percentages put it exactly on the line. */
  .circuit__return {
    display: block;
    position: absolute;
    left: 32%;
    top: 4%;
    translate: -50% -50%;
    width: 20px;
    height: 20px;
    color: var(--signal);
    pointer-events: none;
  }
  .circuit__return path {
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
  }

  /* THE SIGNAL RIDING THE LOOP. Small, quiet, and never captioned: a tile is
     evidence that something is moving through the circuit, and a label on it
     would turn ten pieces of evidence into ten more things to read. */
  .circuit__tile {
    display: block;
    position: absolute;
    width: clamp(38px, 3.4vw, 62px);
    aspect-ratio: 1;
    object-fit: cover;
    border-radius: 2px;
    pointer-events: none;
    opacity: .9;
    animation: circuit-drift var(--drift, 19s) ease-in-out infinite alternate;
    animation-delay: var(--drift-delay, 0s);
  }

  /* Ten points, each really on the path. Top edge y=4, right edge x=96,
     bottom edge y=96, left edge x=4; the corner points are the 45 degree
     positions of the r=12 arcs. */
  .circuit__tile--1  { left: 66%;   top: 4%;    --drift: 17s; --drift-delay: -2s;  --dx: 9px;   --dy: 13px; }
  .circuit__tile--2  { left: 92.5%; top: 7.5%;  --drift: 23s; --drift-delay: -8s;  --dx: -8px;  --dy: 10px; }
  .circuit__tile--3  { left: 96%;   top: 34%;   --drift: 19s; --drift-delay: -5s;  --dx: -13px; --dy: 6px; }
  .circuit__tile--4  { left: 96%;   top: 66%;   --drift: 26s; --drift-delay: -13s; --dx: -11px; --dy: -8px; }
  .circuit__tile--5  { left: 92.5%; top: 92.5%; --drift: 21s; --drift-delay: -3s;  --dx: -7px;  --dy: -9px; }
  .circuit__tile--6  { left: 62%;   top: 96%;   --drift: 18s; --drift-delay: -11s; --dx: 10px;  --dy: -12px; }
  .circuit__tile--7  { left: 34%;   top: 96%;   --drift: 24s; --drift-delay: -6s;  --dx: -9px;  --dy: -11px; }
  .circuit__tile--8  { left: 7.5%;  top: 92.5%; --drift: 20s; --drift-delay: -16s; --dx: 9px;   --dy: -10px; }
  .circuit__tile--9  { left: 4%;    top: 34%;   --drift: 22s; --drift-delay: -9s;  --dx: 13px;  --dy: 7px; }
  .circuit__tile--10 { left: 7.5%;  top: 7.5%;  --drift: 16s; --drift-delay: -4s;  --dx: 7px;   --dy: 11px; }
}

/* The drift is small on purpose: about a quarter of a tile's own width over
   twenty seconds, which reads as alive at the edge of vision and never as a
   thing demanding to be watched. The -50% pair is the tile's own centring, so
   it has to be carried through both ends of the keyframe. */
@keyframes circuit-drift {
  from { translate: -50% -50%; }
  to   { translate: calc(-50% + var(--dx, 9px)) calc(-50% + var(--dy, -7px)); }
}

/* Wider still: the panel is full bleed by now, so the stages take a narrower
   measure rather than stretching into lines nobody tracks back to a number. */
@media (min-width: 1440px) {
  .circuit__stage { width: min(22ch, 15%); }
  .circuit__core { width: min(44ch, 34%); }
}

/* --------------------------------------------------- drawing the circuit -- */

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) and (min-width: 1100px) and (min-height: 700px) {
    /* pathLength="1" on the path is what makes this geometry-independent: the
       dash array is in path units, so one unit is the whole circuit however
       the box is stretched, and no JavaScript has to measure it. */
    .loopgate .circuit__line {
      stroke-dasharray: 1;
      animation: circuit-draw linear both;
      animation-timeline: --gate;
      animation-range: entry 18% entry 94%;
    }

    /* The tiles keep their own drift AND take an arrival from the gate's
       timeline, so they light up behind the line as it reaches them rather
       than all at once. Two animations, two timelines, one element. */
    .loopgate .circuit__tile {
      animation-name: circuit-drift, circuit-arrive;
      animation-duration: var(--drift, 19s), auto;
      animation-timing-function: ease-in-out, linear;
      animation-iteration-count: infinite, 1;
      animation-direction: alternate, normal;
      animation-fill-mode: none, both;
      animation-delay: var(--drift-delay, 0s), 0s;
      animation-timeline: auto, --gate;
      animation-range: normal, entry calc(20% + var(--seq, 0%)) entry calc(42% + var(--seq, 0%));
    }

    /* The return mark has no drift: it marks a fixed point on the line, and a
       moving arrowhead would read as an eleventh tile. It arrives last,
       because it is the statement that the loop closed. */
    .loopgate .circuit__return {
      animation: circuit-arrive linear both;
      animation-timeline: --gate;
      animation-range: entry 86% entry 97%;
    }

    .loopgate .circuit__tile--1  { --seq: 0%; }
    .loopgate .circuit__tile--2  { --seq: 5%; }
    .loopgate .circuit__tile--3  { --seq: 10%; }
    .loopgate .circuit__tile--4  { --seq: 15%; }
    .loopgate .circuit__tile--5  { --seq: 20%; }
    .loopgate .circuit__tile--6  { --seq: 25%; }
    .loopgate .circuit__tile--7  { --seq: 30%; }
    .loopgate .circuit__tile--8  { --seq: 35%; }
    .loopgate .circuit__tile--9  { --seq: 40%; }
    .loopgate .circuit__tile--10 { --seq: 45%; }
  }
}

@keyframes circuit-draw {
  from { stroke-dashoffset: 1; }
  to   { stroke-dashoffset: 0; }
}
/* Arrival along a path with deceleration, per DESIGN.md 6. Nothing starts at
   scale 0: a tile that is already a shape reads as arriving, not as popping
   into being. The scale is composed into the drift's translate by the
   individual `scale` property, so the two animations do not fight. */
@keyframes circuit-arrive {
  from { opacity: 0; scale: .82; }
  to   { opacity: .9; scale: 1; }
}

/* ==================================================== 3. the loop page ==

   /karar-dongusu/. The title is the page until the reader scrolls, and then
   it gets out of the way of the thing it names.

   THE TURKISH TITLE IS TWO WORDS AND THE ENGLISH IS THREE, so the
   choreography is defined by ROLE and not by count: one word leaves to the
   right, one goes to the top left, one goes to the bottom right. Turkish
   simply has no leading word to send away (there is no article), and the
   markup says so with data-lang-only rather than by hiding a translation.
   ========================================================================= */

.dl-stage {
  position: relative;
  view-timeline: --dl block;
  /* HOW FAR THE WORDS TRAVEL, and why the horizontal one is not a plain vw.
     The words start centred as one line, so the room a word has before it
     leaves the pin is (half the pin) minus (where the word already is) - and
     at narrow desktop widths the title very nearly fills the viewport, so the
     outer words start almost at the edge and have nowhere left to go. A flat
     18vw measured 2px of clearance for the English "Loop" at 900px and would
     have overflowed between 1100 and 1300px. The clamp grows far more slowly
     than the title does, so the margin holds at every width; the vertical is
     free to be generous because the pin is tall and the words are short, and
     it is the vertical that does most of the separating. */
  --dl-travel-x: clamp(64px, 9vw, 260px);
  --dl-travel-y: 32svh;

  /* A word in flight is a transform, and transformed overflow is scrollable
     overflow: at 1280px the English "Loop" finished 7px past the right edge
     and the page grew a horizontal scrollbar, which is a WCAG 1.4.10 reflow
     failure. `clip` rather than `hidden` because this must NOT become a scroll
     container - .dl-pin sticks against the document scroller, and a scroll
     container here would pin it to this box instead. */
  overflow: clip;
}

.dl-pin {
  display: grid;
  justify-items: center;
  align-content: center;
  gap: var(--pu-7);
  padding-block: var(--pu-9);
  min-height: 60svh;
  text-align: center;
}

.dl-title {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: baseline;
  gap: 0 .3em;
  margin: 0;
  font-size: clamp(2.75rem, .4rem + 9.2vw, 9rem);
  line-height: .96;
  font-weight: 700;
  letter-spacing: -0.04em;
  text-wrap: nowrap;
}
.dl-word { display: inline-block; will-change: transform; }
/* The lead word is English-only and is authored [hidden]. A class selector
   outranks the UA's `[hidden] { display: none }`, so without this the word
   "The" sat in front of the Turkish title with its hidden attribute set and
   no effect - and .dl-title being a flex container blockified it on top. */
.dl-word[hidden] { display: none; }

.dl-scroll { display: grid; justify-items: center; gap: var(--pu-3); }
.dl-scroll__hint {
  font-family: var(--pu-font-mono);
  font-size: .75rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--muted);
}
.dl-scroll .btn svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.dl-figure { width: 100%; }

.dl-crumb { justify-self: center; }

/* THE SCROLL LINK'S TARGET IS AN ORDINARY IN-FLOW BOX, deliberately. It began
   as `position: absolute; top: 78%` with a 1px size, which reads as tidier and
   is a construction fragment navigation should not have to reason about. In
   flow with zero height it adds nothing to the layout and is a target every
   browser resolves the same way.
   Where it lands is tuned in two places, because the two layouts are different
   pages: here it sits directly under the diagram, and the scroll-margin keeps
   the diagram on screen instead of scrolling it off the top. */
.dl-anchor {
  display: block;
  height: 0;
  scroll-margin-top: 62svh;
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) and (min-width: 640px) and (min-height: 620px) {
    /* The scroll distance the choreography is spread over. `contain` is the
       range during which a taller-than-viewport element covers the viewport
       completely, which is exactly the span the pin below is stuck for, so
       the animation starts when the pin locks and ends when it releases. */
    .dl-stage { min-height: 300svh; }

    .dl-pin {
      position: sticky;
      top: var(--chrome-h);
      height: calc(100svh - var(--chrome-h));
      min-height: 0;
      padding-block: var(--pu-6);
      align-content: center;
    }
    /* One cell, three layers. The title flies over the diagram rather than
       pushing it, and the scroll cue sits at the foot of the same cell. */
    .dl-pin > * { grid-area: 1 / 1; width: 100%; }
    .dl-crumb { align-self: start; }
    .dl-scroll { align-self: end; }
    /* THE WORDS FLY OVER THE DIAGRAM, NOT UNDER IT. Grid items in one cell
       paint in DOM order, and the figure is last, so without this it covered
       the layers above it - measured: the element on top of the scroll button
       was a diagram label, which made the button unclickable. */
    .dl-figure { z-index: 0; }
    .dl-crumb, .dl-title, .dl-scroll { z-index: 1; }

    /* In this layout the pin is the stage's only flow content, so the target
       would otherwise sit at the very top of a 300svh box and "Scroll" would
       move the reader to the START of the choreography. The offset puts it at
       roughly 84% of the pinned span, which is past the point where the words
       have reached their corners (46%) and the diagram has arrived (58%). */
    .dl-anchor { margin-top: 88svh; scroll-margin-top: 0; }
    /* Measured: without this the title stretched to the full 787px cell and
       its baseline-aligned words sat at the TOP of it, so both corner moves
       started 250px higher than the middle of the screen and the top-left one
       finished 161px off the pin entirely. */
    .dl-title { align-self: center; }

    .dl-word,
    .dl-scroll,
    .dl-figure {
      animation-timeline: --dl;
      animation-timing-function: linear;
      animation-fill-mode: both;
    }

    .dl-word--lead {
      animation-name: dl-lead-out;
      animation-range: contain 0% contain 16%;
    }
    .dl-word--a {
      animation-name: dl-corner-tl;
      animation-range: contain 2% contain 46%;
    }
    .dl-word--b {
      animation-name: dl-corner-br;
      animation-range: contain 2% contain 46%;
    }
    .dl-scroll {
      animation-name: dl-cue-out;
      animation-range: contain 0% contain 12%;
    }
    .dl-figure {
      animation-name: dl-figure-in;
      animation-range: contain 22% contain 58%;
    }
  }
}

/* The lead word leaves along the reading direction and does not come back.
   It keeps its box while it goes, so the two words that stay do not reflow
   underneath it mid-flight. */
@keyframes dl-lead-out {
  from { translate: 0 0; opacity: 1; }
  to { translate: 120vw 0; opacity: 0; }
}
@keyframes dl-corner-tl {
  from { translate: 0 0; scale: 1; }
  to {
    translate: calc(var(--dl-travel-x) * -1) calc(var(--dl-travel-y) * -1);
    scale: .34;
  }
}
@keyframes dl-corner-br {
  from { translate: 0 0; scale: 1; }
  to { translate: var(--dl-travel-x) var(--dl-travel-y); scale: .34; }
}
@keyframes dl-cue-out {
  from { opacity: 1; translate: 0 0; }
  to { opacity: 0; translate: 0 var(--pu-5); }
}
/* Arrival along a path with deceleration, per DESIGN.md 6. The scale starts
   near its resting size rather than at zero: nothing on this site pops. */
@keyframes dl-figure-in {
  from { opacity: 0; scale: .94; }
  to { opacity: 1; scale: 1; }
}

/* ------------------------------------------------------- the four stages -- */

.stages { list-style: none; counter-reset: stage; display: grid; gap: var(--pu-6); }
@media (min-width: 768px) {
  .stages { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--pu-7) var(--pu-6); }
}

.stage {
  counter-increment: stage;
  display: grid;
  gap: var(--pu-3);
  align-content: start;
  padding-top: var(--pu-5);
  border-top: 1px solid var(--rule);
}
.stage__num {
  font-family: var(--pu-font-mono);
  font-size: .75rem;
  letter-spacing: .12em;
  color: var(--signal);
  font-variant-numeric: tabular-nums;
}
.stage h3 { font-size: 1.25rem; }
.stage p { color: var(--muted); }
.stage__out {
  font-size: .9375rem;
  color: var(--fg);
  padding-top: var(--pu-3);
  border-top: 1px solid var(--rule);
}
.stage__out b { font-weight: 600; color: var(--link); }
