/* ==========================================================================
   data source — DARK FLOW
   A scroll-driven walkthrough of the seven product areas, modelled on
   workflows.io/services/abm: one pinned stage, a diagram per step, an OUTCOME
   badge, and a stepper along the bottom whose connector fills as you scroll.

   Standalone: this file and dark-flow.js are loaded only by platform.html, so
   nothing here can reach the homepage. It does load glass-nav.css, because the
   nav is already theme-aware — marking the sections [data-dark] is enough to
   flip it, no second navigation component.

   ── THE SCROLL MECHANIC ────────────────────────────────────────────────────
   The section is `steps * 100vh` tall and its inner stage is `sticky; top: 0`.
   Scrolling therefore moves the page while the stage stays put, and the JS maps
   scroll depth to an active index. That is the whole trick — no scroll
   hijacking, no wheel listeners, no fighting the browser. Native scrolling
   keeps the scrollbar honest, keyboard paging works, and a user who drags the
   scrollbar to the middle lands mid-walkthrough exactly as they'd expect.

   ── THE ACCENT ─────────────────────────────────────────────────────────────
   The reference is lime. This is deliberately NOT — data source is blue, and
   copying an accent is how you end up looking like someone else's site. The
   layout language is borrowed; the palette stays ours.
   ========================================================================== */

/* `.df` is the standalone page (body); `.df-scope` is the same thing as a
   SECTION dropped into a light page. Both carry the tokens.

   🔴 `color` must be re-declared here, not just --ink. On the homepage this
   section sits inside a light page whose `color` is already resolved, and
   inheritance passes the COMPUTED colour down, not the variable — so redefining
   --ink alone would leave every piece of plain text dark-on-dark while anything
   with an explicit `color: var(--ink-2)` flipped correctly. Same trap the nav
   hit; same fix. */
.df,
.df-scope {
  --bg: #05070B;
  --bg-2: #080B12;
  --ink: #F2F6FC;
  --ink-2: #A8B4C8;
  --ink-3: #7C8AA2;
  --ink-4: #566274;

  --accent: #4F8DFF;
  --accent-2: #2563EB;
  --accent-soft: rgba(79, 141, 255, .16);

  --line: rgba(255, 255, 255, .09);
  --line-2: rgba(255, 255, 255, .055);
  --chip: rgba(255, 255, 255, .045);

  --mat: cubic-bezier(.22, 1, .36, 1);
  --geo: cubic-bezier(.65, 0, .35, 1);

  --sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, monospace;
  --brand-face: "Orbitron", "Michroma", sans-serif;

  color-scheme: dark;
}

* { box-sizing: border-box; }

body.df {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

.df img { max-width: 100%; display: block; }
.df a { color: inherit; text-decoration: none; }
.df h1, .df h2, .df h3 { margin: 0; font-weight: 500; letter-spacing: -.022em; line-height: 1.12; }
.df p { margin: 0; }

.df-wrap { width: 100%; max-width: 1240px; margin: 0 auto; padding: 0 28px; }

.df-mono {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-4);
}

/* the page's ambient ground — two soft pools, so pure black never reads flat */
.df-ground {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(60% 42% at 22% 12%, rgba(37, 99, 235, .16), transparent 70%),
    radial-gradient(52% 40% at 84% 78%, rgba(79, 141, 255, .10), transparent 72%),
    linear-gradient(180deg, var(--bg) 0%, var(--bg-2) 55%, var(--bg) 100%);
}
.df-ground::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: radial-gradient(rgba(255, 255, 255, .055) 1px, transparent 1px);
  background-size: 30px 30px;
  opacity: .5;
}

.df-page { position: relative; z-index: 1; }

/* The section-scoped ground. On platform.html the ground is a fixed, page-wide
   layer; embedded in the light homepage it has to be the section's own
   background instead, or it would paint over the whole document. */
.df-scope {
  position: relative;
  color: var(--ink);
  font-family: var(--sans);
  background:
    radial-gradient(58% 40% at 20% 8%, rgba(37, 99, 235, .18), transparent 70%),
    radial-gradient(50% 38% at 86% 82%, rgba(79, 141, 255, .12), transparent 72%),
    linear-gradient(180deg, var(--bg) 0%, var(--bg-2) 50%, var(--bg) 100%);
}
.df-scope::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: radial-gradient(rgba(255, 255, 255, .055) 1px, transparent 1px);
  background-size: 30px 30px;
  opacity: .5;
}
.df-scope > * { position: relative; }

/* resets, scoped so they cannot reach the light page around the section */
.df-scope h2, .df-scope h3 { margin: 0; font-weight: 500; letter-spacing: -.022em; line-height: 1.12; }
.df-scope p { margin: 0; }
.df-scope a { color: inherit; text-decoration: none; }
.df-scope img { max-width: 100%; display: block; }

/* ==========================================================================
   HERO
   ========================================================================== */

.df-hero { padding: 190px 0 90px; text-align: center; }
.df-hero h1 { font-size: clamp(34px, 5vw, 62px); max-width: 900px; margin: 14px auto 0; }
.df-hero p {
  max-width: 620px;
  margin: 22px auto 0;
  color: var(--ink-2);
  font-size: 17px;
}
.df-hero .df-accent { color: var(--accent); }

/* ==========================================================================
   THE PINNED STAGE
   ========================================================================== */

.df-flow { position: relative; }

/* Full-bleed on purpose. This element owns the art — grid, streak, horizon —
   so it must span the viewport; when it also carried `df-wrap` the background
   stopped at 1240px and the art sat in a centred column with bare page either
   side. Layout moved to .df-stage-in. */
.df-stage {
  position: sticky;
  top: 0;
  height: 100vh;
  min-height: 680px;
  padding: 72px 0 26px;
}

.df-stage-in {
  position: relative;
  z-index: 1;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 22px;
}

/* the big rounded card everything happens inside */
.df-card {
  position: relative;
  flex: 1;
  min-height: 0;
  max-height: 720px;
  border-radius: 26px;
  border: 1px solid var(--line);
  background:
    radial-gradient(120% 80% at 50% 0%, rgba(255, 255, 255, .045), rgba(255, 255, 255, .012) 60%, transparent),
    rgba(255, 255, 255, .015);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .07),
    inset -14px -14px 50px rgba(255, 255, 255, .022),
    inset 14px 14px 50px rgba(0, 0, 0, .38),
    0 50px 120px -50px rgba(0, 0, 0, .9);
  overflow: hidden;
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}

/* a pool of light under the active panel's outcome */
.df-card::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -30%;
  width: 70%;
  height: 60%;
  transform: translateX(-50%);
  pointer-events: none;
  background: radial-gradient(50% 50% at 50% 50%, rgba(79, 141, 255, .16), transparent 70%);
}

/* ── panels ─────────────────────────────────────────────────────────────── */

.df-panel {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 34px 30px 30px;
  opacity: 0;
  visibility: hidden;
  transform: translateY(14px) scale(.985);
  transition:
    opacity .45s var(--mat),
    transform .55s var(--mat),
    visibility 0s linear .55s;
}
.df-panel.is-active {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition:
    opacity .45s var(--mat),
    transform .55s var(--mat),
    visibility 0s;
}

.df-panel-head {
  display: flex;
  align-items: center;
  gap: 13px;
  margin-bottom: 4px;
}
.df-panel-head h2 { font-size: clamp(19px, 2.2vw, 27px); font-weight: 450; }

.df-panel-ico {
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  flex: none;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, .04);
  color: var(--ink-2);
  box-shadow: inset -3px -3px 10px rgba(255, 255, 255, .05), inset 3px 3px 10px rgba(0, 0, 0, .5);
}
.df-panel-ico svg { width: 18px; height: 18px; }

/* the diagram area — flexes so every panel's outcome block lines up */
.df-art {
  flex: 1;
  min-height: 0;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 18px 0 10px;
}

/* ── outcome ────────────────────────────────────────────────────────────── */

.df-outcome {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  height: 38px;
  padding: 0 18px 0 8px;
  border-radius: 999px;
  background: linear-gradient(180deg, #6BA0FF, var(--accent-2));
  color: #fff;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .16em;
  text-transform: uppercase;
  box-shadow: 0 10px 34px -10px rgba(79, 141, 255, .9), inset 0 1px 0 rgba(255, 255, 255, .35);
}
.df-outcome span {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: rgba(0, 0, 0, .35);
}
.df-outcome svg { width: 12px; height: 12px; }

/* Sits under the category name now, so it is a standfirst rather than a
   footnote: tighter to the heading above it, and a touch narrower so the line
   length stays readable directly beneath a short title. */
.df-outcome-text {
  margin-top: 10px;
  max-width: 620px;
  text-align: center;
  color: var(--ink-2);
  font-size: 15px;
  line-height: 1.5;
}

/* ==========================================================================
   THE STEPPER
   ========================================================================== */

.df-steps {
  display: grid;
  /* The count was hardcoded at 7 despite the comment here claiming otherwise, so
     when the walkthrough dropped to four steps they filled the first four of seven
     columns and stranded left at ~57% of the width.
     The final column is `auto` and its trailing connector is hidden, so the 1fr
     columns absorb all remaining width and the LAST DOT lands on the bubble edge —
     the rail then measures the same span as the card above it. */
  grid-template-columns: repeat(3, 1fr) auto;
  gap: 0;
  align-items: start;
  flex: none;
}

.df-step {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 14px;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--ink-4);
  cursor: pointer;
  text-align: left;
  transition: color .4s var(--mat);
}
.df-step.is-active { color: var(--ink); }
.df-step.is-done { color: var(--ink-3); }
.df-step:hover { color: var(--ink-2); }

.df-step-rail { display: flex; align-items: center; gap: 0; width: 100%; }

.df-dot {
  position: relative;
  width: 11px;
  height: 11px;
  flex: none;
  border-radius: 50%;
  border: 1.5px solid rgba(255, 255, 255, .28);
  background: var(--bg);
  transition: border-color .4s var(--mat), box-shadow .4s var(--mat);
}
.df-step.is-active .df-dot,
.df-step.is-done .df-dot {
  border-color: var(--accent);
  box-shadow: 0 0 0 4px rgba(79, 141, 255, .14), 0 0 18px rgba(79, 141, 255, .55);
}
.df-step.is-active .df-dot::after {
  content: "";
  position: absolute;
  inset: 2px;
  border-radius: 50%;
  background: var(--accent);
}

/* the connector. A dashed track with a solid fill laid over it — the fill's
   width is set per-step from JS as sub-progress, which is what makes the line
   creep while you scroll INSIDE a step rather than snapping between them. */
.df-link {
  position: relative;
  flex: 1;
  height: 2px;
  margin: 0 8px;
  border-radius: 2px;
  background-image: linear-gradient(90deg, rgba(255, 255, 255, .26) 0 6px, transparent 6px 12px);
  background-size: 12px 2px;
  background-repeat: repeat-x;
}
.df-link i {
  position: absolute;
  inset: 0 auto 0 0;
  display: block;
  width: 0;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--accent-2), var(--accent));
  box-shadow: 0 0 12px rgba(79, 141, 255, .8);
}
.df-step:last-child .df-link { display: none; }

.df-step-label { font-size: 14px; font-weight: 450; padding-right: 14px; }
.df-step.is-active .df-step-label { font-weight: 550; }

/* ==========================================================================
   DIAGRAM VOCABULARY
   Every panel is composed from these, so seven diagrams read as one system
   instead of seven drawings.
   ========================================================================== */

.d-col { display: flex; flex-direction: column; align-items: center; gap: 14px; }
.d-row { display: flex; align-items: center; justify-content: center; gap: 12px; flex-wrap: wrap; }

.d-node {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 10px 15px;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: var(--chip);
  font-size: 13px;
  color: var(--ink-2);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .05);
  white-space: nowrap;
}
.d-node b { color: var(--ink); font-weight: 500; }
.d-node .d-ico {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  flex: none;
  border-radius: 6px;
  background: rgba(255, 255, 255, .07);
  color: var(--ink-3);
}
.d-node .d-ico svg { width: 12px; height: 12px; }
.d-node.is-on {
  border-color: rgba(79, 141, 255, .45);
  background: var(--accent-soft);
  color: var(--ink);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .12), 0 8px 26px -10px rgba(79, 141, 255, .7);
}
.d-node.is-on .d-ico { background: rgba(79, 141, 255, .30); color: #DCE9FF; }

/* a vertical dashed drop between rows */
.d-drop {
  width: 2px;
  height: 26px;
  background-image: linear-gradient(180deg, rgba(255, 255, 255, .3) 0 5px, transparent 5px 10px);
  background-size: 2px 10px;
}

/* a bracket that gathers several nodes into one */
.d-brace {
  width: min(420px, 70%);
  height: 20px;
  border: 2px solid rgba(255, 255, 255, .16);
  border-bottom: 0;
  border-radius: 10px 10px 0 0;
}

.d-pill {
  padding: 5px 11px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, .04);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* a table mock */
.d-table {
  width: min(560px, 100%);
  border-radius: 14px;
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, .022);
  overflow: hidden;
}
.d-table-h,
.d-table-r {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr .7fr;
  gap: 14px;
  padding: 10px 15px;
  align-items: center;
}
.d-table-h {
  border-bottom: 1px solid var(--line-2);
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-4);
}
.d-table-r + .df-table-r,
.d-table-r { border-top: 1px solid var(--line-2); font-size: 12.5px; color: var(--ink-2); }
.d-bar { height: 7px; border-radius: 4px; background: rgba(255, 255, 255, .09); }
.d-bar.on { background: linear-gradient(90deg, rgba(79, 141, 255, .8), rgba(79, 141, 255, .35)); }

/* a code block */
.d-code {
  width: min(520px, 100%);
  border-radius: 14px;
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, .45);
  overflow: hidden;
  font-family: var(--mono);
  font-size: 12px;
}
.d-code-h {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 9px 13px;
  border-bottom: 1px solid var(--line-2);
  color: var(--ink-4);
  font-size: 10px;
  letter-spacing: .1em;
  text-transform: uppercase;
}
.d-code-h em { display: block; width: 8px; height: 8px; border-radius: 50%; background: rgba(255, 255, 255, .16); }
.d-code pre { margin: 0; padding: 13px; color: var(--ink-2); line-height: 1.7; white-space: pre-wrap; }
.d-code .k { color: #7FB0FF; }
.d-code .s { color: #9AE6B4; }

/* ==========================================================================
   FOOTER
   ========================================================================== */

.df-foot {
  border-top: 1px solid var(--line-2);
  padding: 40px 0 44px;
  margin-top: 30px;
  color: var(--ink-3);
  font-size: 13px;
}
.df-foot-in { display: flex; align-items: center; justify-content: space-between; gap: 20px; flex-wrap: wrap; }
.df-foot-brand { display: flex; align-items: center; gap: 9px; }
.df-foot-brand img { width: 20px; max-width: none; }
.df-foot-brand b { font-family: var(--brand-face); font-weight: 400; font-size: 13px; color: var(--ink); }
.df-foot a:hover { color: var(--ink); }
.df-foot nav { display: flex; gap: 20px; flex-wrap: wrap; }

/* ==========================================================================
   FALLBACKS
   ========================================================================== */

/* No JS means nothing sets .is-active, so the stage would pin an empty card
   forever. Unpin it and let every panel stand in normal flow instead — the
   content is all still there, it just reads as a list. */
.no-js .df-flow { height: auto !important; }
.no-js .df-stage { position: static; height: auto; min-height: 0; }
.no-js .df-stage-in { display: block; height: auto; }
.no-js .df-card { max-height: none; overflow: visible; margin-bottom: 20px; }
.no-js .df-panel {
  position: static;
  opacity: 1;
  visibility: visible;
  transform: none;
  border-bottom: 1px solid var(--line-2);
}
.no-js .df-steps { display: none; }

@media (prefers-reduced-motion: reduce) {
  .df-panel { transition-duration: .01ms; }
  .df-panel, .df-panel.is-active { transform: none; }
}

@media (max-width: 900px) {
  .df-hero { padding: 150px 0 60px; }
  .df-stage { height: auto; position: static; padding: 40px 0; }
  .df-stage-in { display: block; height: auto; }
  .df-flow { height: auto !important; }
  .df-card { max-height: none; overflow: visible; }
  /* stacked on small screens: pinning a 100vh stage on a phone fights the
     browser chrome resizing and leaves the diagram squashed */
  .df-panel {
    position: static;
    opacity: 1;
    visibility: visible;
    transform: none;
    border-bottom: 1px solid var(--line-2);
  }
  .df-panel:last-child { border-bottom: 0; }
  .df-steps { display: none; }
}

/* ==========================================================================
   ANIMATION
   Every panel replays its entrance each time it becomes active (dark-flow.js
   removes and re-adds `.is-in`, forcing a reflow between). The elements inside
   carry `--d` as their stagger index, so one rule choreographs all seven
   diagrams and adding a node to any of them needs no new CSS.

   🔴 Every INFINITE animation here carries a NEGATIVE delay. A background tab
   pauses animations, and a loop starting at `opacity: 0` or off-screen would
   park invisible — a negative delay starts it mid-cycle so the resting state
   is always something you can see.
   ========================================================================== */

/* ── entrance: the diagram assembles itself ─────────────────────────────── */

.df-panel .d-node,
.df-panel .d-pill,
.df-panel .d-table,
.df-panel .d-code,
.df-panel .d-drop,
.df-panel .d-brace,
.df-panel .df-outcome,
.df-panel .df-outcome-text {
  opacity: 0;
}

.df-panel.is-in .d-node,
.df-panel.is-in .d-pill,
.df-panel.is-in .d-table,
.df-panel.is-in .d-code,
.df-panel.is-in .df-outcome,
.df-panel.is-in .df-outcome-text {
  animation: dfRise .62s var(--mat) forwards;
  animation-delay: calc(90ms + var(--d, 0) * 70ms);
}
@keyframes dfRise {
  from { opacity: 0; transform: translateY(14px) scale(.97); }
  to   { opacity: 1; transform: none; }
}

/* connectors draw themselves downward instead of fading */
.df-panel.is-in .d-drop {
  animation: dfDraw .5s var(--mat) forwards;
  animation-delay: calc(140ms + var(--d, 0) * 70ms);
  transform-origin: top center;
}
@keyframes dfDraw {
  from { opacity: 0; transform: scaleY(0); }
  to   { opacity: 1; transform: scaleY(1); }
}

.df-panel.is-in .d-brace {
  animation: dfBrace .55s var(--mat) forwards;
  animation-delay: calc(140ms + var(--d, 0) * 70ms);
  transform-origin: center top;
}
@keyframes dfBrace {
  from { opacity: 0; transform: scaleX(.4); }
  to   { opacity: 1; transform: none; }
}

/* the panel title slides in ahead of its diagram */
.df-panel .df-panel-head { opacity: 0; }
.df-panel.is-in .df-panel-head { animation: dfRise .55s var(--mat) forwards; }

/* ── the dashes travel, so a static diagram still reads as flowing ─────── */

.df-panel.is-active .d-drop {
  background-image: linear-gradient(180deg, rgba(79, 141, 255, .85) 0 5px, transparent 5px 10px);
  animation: dfDraw .5s var(--mat) forwards,
             dfFlow 1.1s linear -0.4s infinite;
}
@keyframes dfFlow { to { background-position-y: 10px; } }

/* ── the highlighted node breathes ──────────────────────────────────────── */

.df-panel.is-active .d-node.is-on {
  animation: dfRise .62s var(--mat) forwards,
             dfPulse 3.4s ease-in-out -1.2s infinite;
  animation-delay: calc(90ms + var(--d, 0) * 70ms), 0s;
}
@keyframes dfPulse {
  0%, 100% { box-shadow: inset 0 1px 0 rgba(255,255,255,.12), 0 8px 26px -10px rgba(79,141,255,.7); }
  50%      { box-shadow: inset 0 1px 0 rgba(255,255,255,.16), 0 12px 40px -10px rgba(79,141,255,1); }
}

/* ── bars fill rather than appear ───────────────────────────────────────── */

.df-panel.is-in .d-bar.on {
  animation: dfFill .9s var(--mat) forwards;
  animation-delay: calc(320ms + var(--d, 0) * 90ms);
  transform-origin: left center;
}
@keyframes dfFill { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* ── the outcome badge gets a sheen crossing it ─────────────────────────── */

.df-outcome { position: relative; overflow: hidden; }
.df-outcome::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 34%, rgba(255, 255, 255, .55) 50%, transparent 66%);
  transform: translateX(-100%);
}
.df-panel.is-active .df-outcome::after {
  animation: dfSheen 2.8s var(--mat) -0.8s infinite;
}
@keyframes dfSheen {
  0%   { transform: translateX(-100%); }
  55%  { transform: translateX(100%); }
  100% { transform: translateX(100%); }
}

/* ── the card's light pool drifts, so the dark never sits perfectly still ── */

.df-card::after { animation: dfDrift 14s ease-in-out -5s infinite; }
@keyframes dfDrift {
  0%, 100% { transform: translateX(-50%) translateY(0) scale(1); opacity: .9; }
  50%      { transform: translateX(-46%) translateY(-6%) scale(1.08); opacity: 1; }
}

/* ── the active step's dot pulses ───────────────────────────────────────── */

.df-step.is-active .df-dot { animation: dfDot 2.6s ease-in-out -0.9s infinite; }
@keyframes dfDot {
  0%, 100% { box-shadow: 0 0 0 4px rgba(79,141,255,.14), 0 0 18px rgba(79,141,255,.55); }
  50%      { box-shadow: 0 0 0 7px rgba(79,141,255,.08), 0 0 26px rgba(79,141,255,.85); }
}

/* ── the leading edge of the connector fill glows ───────────────────────── */

.df-link i::after {
  content: "";
  position: absolute;
  right: -3px;
  top: 50%;
  width: 6px;
  height: 6px;
  transform: translateY(-50%);
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 12px 2px rgba(79, 141, 255, .9);
}
.df-step:not(.is-active) .df-link i::after { display: none; }

/* Reduced motion: keep the information, drop the movement. Entrances resolve
   instantly and every loop is switched off. */
@media (prefers-reduced-motion: reduce) {
  .df-panel .d-node,
  .df-panel .d-pill,
  .df-panel .d-table,
  .df-panel .d-code,
  .df-panel .d-drop,
  .df-panel .d-brace,
  .df-panel .df-outcome,
  .df-panel .df-outcome-text,
  .df-panel .df-panel-head { opacity: 1; animation: none !important; transform: none !important; }
  .df-card::after,
  .df-outcome::after,
  .df-step.is-active .df-dot { animation: none !important; }
  .df-panel .d-bar.on { transform: none !important; }
}

/* ==========================================================================
   THE SECTION HEADER + THE FUTURISTIC GROUND
   ========================================================================== */


/* 🔴 The art hangs off .df-stage, NOT the section.
   The section is seven viewports tall, so a background on it would place the
   image somewhere in a 7000px box and it would be off-screen for most of the
   scroll. The stage is sticky and exactly one viewport, so art attached here is
   always exactly in frame — and it holds still while the panels change, which
   is the effect the reference gets. It also avoids background-attachment:fixed,
   which is expensive to composite and ignored outright on iOS. */
.df-stage {
  background:
    radial-gradient(46% 34% at 16% 14%, rgba(37, 99, 235, .20), transparent 72%),
    radial-gradient(42% 32% at 88% 84%, rgba(79, 141, 255, .13), transparent 74%),
    /* 🔴 The wash is NOT optional. Raw, this plate measures 1.2:1 against the
       panel text — its bright core lands exactly where the diagram sits, which
       is the documented failure mode for bg-dark-streak. The card above it is
       almost transparent (fill .015) so it cannot rescue the contrast on its
       own. This holds the streak back to a glow. Measure contrast WITH this
       layer composited; the raw plate number is meaningless here. */
    linear-gradient(180deg, rgba(5, 7, 11, .58) 0%, rgba(5, 7, 11, .86) 46%, rgba(5, 7, 11, .70) 100%),
    url("../img/bg-dark-streak.webp") center 74% / 150% auto no-repeat;
}

/* the technical grid: fine lines on a wide pitch, cleared from the middle by a
   radial mask so the card never sits on top of ruled paper */
.df-stage::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    repeating-linear-gradient(90deg, rgba(140, 180, 255, .09) 0 1px, transparent 1px 96px),
    repeating-linear-gradient(0deg,  rgba(140, 180, 255, .07) 0 1px, transparent 1px 96px);
  -webkit-mask-image: radial-gradient(70% 58% at 50% 48%, transparent 30%, #000 100%);
  mask-image: radial-gradient(70% 58% at 50% 48%, transparent 30%, #000 100%);
}
/* .df-stage-in already carries position/z-index, so the art layers below it */

/* a horizon line sliding across, which is what stops the ground reading as a
   still photograph. Negative delay so it parks mid-travel in a paused tab. */
.df-stage::after {
  content: "";
  position: absolute;
  left: -20%;
  right: -20%;
  top: 74%;
  height: 1px;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(90deg, transparent, rgba(120, 175, 255, .55) 45%, rgba(190, 220, 255, .8) 50%, rgba(120, 175, 255, .55) 55%, transparent);
  animation: dfHorizon 9s ease-in-out -3s infinite;
}
@keyframes dfHorizon {
  0%, 100% { transform: translateX(-14%); opacity: .55; }
  50%      { transform: translateX(14%);  opacity: 1; }
}


@media (prefers-reduced-motion: reduce) {
  .df-stage::after { animation: none !important; }
}

/* stacked layout: the stage is no longer one viewport, so the art would smear
   down a very tall box — drop it and keep the section's own ground */
@media (max-width: 900px) {
  .df-stage { background: none; }
  .df-stage::before, .df-stage::after { display: none; }
}

/* ==========================================================================
   CONTENT SCALE
   Everything inside the card is sized off ONE factor. The card had a lot of
   dead air around a small diagram; this fills it without touching the card,
   the stage or the stepper — those are layout, and scaling them would change
   how much of the viewport the section occupies.

   Written as calc() against --df-s rather than as baked numbers so the whole
   set moves together: change the factor, not thirty values. The originals
   above are untouched, so this is one block to delete if it goes.
   ========================================================================== */

.df-card { --df-s: 1.36; }

/* Vertical padding is vh-bound, NOT scaled. At 1366x768 — a very common laptop
   — the 36% bump pushed the tallest panel 5px past the card, and the card
   clips (overflow:hidden), so the bottom of the caption simply vanished. The
   type scale is what was asked for, so the padding yields instead: it keeps
   its full value on tall screens and gives ~30px back on short ones. */
.df-panel {
  padding: clamp(18px, 3.4vh, calc(34px * var(--df-s)))
           30px
           clamp(14px, 2.6vh, calc(24px * var(--df-s)));
}

.df-panel-head { gap: calc(13px * var(--df-s)); }
.df-panel-head h2 { font-size: clamp(calc(19px * var(--df-s)), calc(2.2vw * var(--df-s)), calc(27px * var(--df-s))); }
.df-panel-ico { width: calc(42px * var(--df-s)); height: calc(42px * var(--df-s)); }
.df-panel-ico svg { width: calc(18px * var(--df-s)); height: calc(18px * var(--df-s)); }

.df-card .d-col { gap: calc(14px * var(--df-s)); }
.df-card .d-row { gap: calc(12px * var(--df-s)); }

.df-card .d-node {
  gap: calc(9px * var(--df-s));
  padding: calc(10px * var(--df-s)) calc(15px * var(--df-s));
  border-radius: calc(12px * var(--df-s));
  font-size: calc(13px * var(--df-s));
}
.df-card .d-node .d-ico {
  width: calc(22px * var(--df-s));
  height: calc(22px * var(--df-s));
  border-radius: calc(6px * var(--df-s));
}
.df-card .d-node .d-ico svg { width: calc(12px * var(--df-s)); height: calc(12px * var(--df-s)); }

.df-card .d-drop { width: calc(2px * var(--df-s)); height: calc(26px * var(--df-s)); }
.df-card .d-brace { height: calc(20px * var(--df-s)); }

.df-card .d-table { width: min(calc(560px * var(--df-s)), 100%); border-radius: calc(14px * var(--df-s)); }
.df-card .d-table-h,
.df-card .d-table-r { gap: calc(14px * var(--df-s)); padding: calc(10px * var(--df-s)) calc(15px * var(--df-s)); }
.df-card .d-table-h { font-size: calc(9.5px * var(--df-s)); }
.df-card .d-table-r { font-size: calc(12.5px * var(--df-s)); }
.df-card .d-bar { height: calc(7px * var(--df-s)); border-radius: calc(4px * var(--df-s)); }

.df-card .d-pill {
  padding: calc(5px * var(--df-s)) calc(11px * var(--df-s));
  font-size: calc(10px * var(--df-s));
}

.df-card .d-code { width: min(calc(520px * var(--df-s)), 100%); border-radius: calc(14px * var(--df-s)); font-size: calc(12px * var(--df-s)); }
.df-card .d-code-h { padding: calc(9px * var(--df-s)) calc(13px * var(--df-s)); font-size: calc(10px * var(--df-s)); }
.df-card .d-code pre { padding: calc(13px * var(--df-s)); }

/* The pill above it is gone, so the caption now needs its own separation from
   the diagram rather than inheriting the pill's. */
.df-card .df-outcome-text {
  margin-top: calc(9px * var(--df-s));
  max-width: calc(600px * var(--df-s));
  font-size: calc(14px * var(--df-s));
}

/* 🔴 A short viewport cannot hold the full 36%. The card is bounded by the
   100vh stage and clips, so on a 720px-tall window the tallest panel ran 31px
   past it even with the padding already at its floor. Something has to give,
   and the least-bad thing is the factor: the type stays proportionally right,
   just smaller where there is less room to put it. Measured: 1280x720 was the
   first size that failed, so the tier starts just above it. */
@media (max-height: 800px) {
  .df-card { --df-s: 1.18; }
  .df-art { padding: 10px 0 6px; }
}

/* Below the breakpoint the panels stack in normal flow and the card has no
   fixed height, so a 36% bump on a phone just makes everything oversized. */
@media (max-width: 900px) {
  .df-card { --df-s: 1; }
}


/* ── Lifted panel headings (df-heads) ────────────────────────────────────────
   The heading + caption sit ABOVE the glass card rather than inside it, so the
   card holds only the diagram. dark-flow.js mirrors each panel is-active onto the
   matching .df-head, so they cross-fade together. */
.df-heads {
  --df-s: 1.36;                 /* the card declares this too; out here it is gone */
  position: relative;
  width: min(760px, 100%);
  /* Inside the card the heading had the card padding between it and the sticky
     nav. Lifted out it had nothing and collided, hence the vh-based top margin. */
  margin: clamp(8px, 4.2vh, 46px) auto clamp(14px, 2.2vh, 22px);
  min-height: 7.4em;            /* reserve height so the card never shifts on swap */
  text-align: center;
}
.df-head {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  opacity: 0;
  visibility: hidden;
  transform: translateY(10px);
  transition: opacity .45s var(--mat), transform .55s var(--mat), visibility 0s linear .55s;
}
.df-head.is-active {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition: opacity .45s var(--mat), transform .55s var(--mat), visibility 0s;
}
/* Out here it is a section title, not a card label. At the in-card size it read
   as undersized floating in open space. */
.df-head .df-panel-head h2 { font-size: clamp(24px, 2.9vw, 38px); letter-spacing: -.015em; }
.df-head .df-panel-ico { width: 36px; height: 36px; }
.df-head .df-panel-ico svg { width: 16px; height: 16px; }
/* Identical to the in-card caption size — the .df-card rule stopped matching once
   the caption moved out, so it silently shrank until this restated it. */
.df-head .df-outcome-text {
  margin-top: calc(9px * var(--df-s));
  max-width: calc(600px * var(--df-s));
  font-size: calc(14px * var(--df-s));
}

/* The card holds only the diagram now, so centre it and stop it towering. */
.df-card .df-panel { justify-content: center; }
.df-card { max-height: min(560px, 52vh); }

/* A merged panel carries two diagrams worth of content, so it takes a tighter
   scale. --df-s is what every d-* size is calc()d from and CSS vars inherit, so
   setting it on the panel rescales that panel alone. */
.df-panel-merged { --df-s: 1.15; }
.df-panel-merged .d-col { gap: 11px; }

/* The art is sized off --df-s while the card is vh-bound, so on a short screen the
   card shrank and its contents did not — and the card is overflow:hidden, so it
   clipped silently. Measured, not estimated: at 768 the worst panel went from 92px
   over to comfortably inside. */
@media (max-height: 900px) {
  .df-card { --df-s: 1.20; }
  .df-panel-merged { --df-s: 0.95; }
}
@media (max-height: 820px) {
  .df-card { --df-s: 1.10; }
  .df-panel-merged { --df-s: 0.85; }
}

/* Last step is pinned to the right edge (see grid-template-columns above). The dot
   sits at the start of its rail, which in an auto column inset it by the label
   width — so that rail justifies to the end. */
.df-step:last-child .df-link { display: none; }
.df-step:last-child { align-items: flex-end; text-align: right; }
.df-step:last-child .df-step-rail { justify-content: flex-end; }

/* Labels the #api section so the MCP / API step and the section it hands off to
   stop reading as two separate pitches for the same thing. */
.df-eyebrow {
  letter-spacing: .14em;
  text-transform: uppercase;
  font-size: 11px;
  opacity: .55;
  margin-bottom: 10px;
}


/* ==========================================================================
   DIAGRAM VOCABULARY v2
   The first pass was loose nodes in rows, which read as "things that exist"
   rather than "a process that runs". These primitives encode the mechanics of
   each flow, taken from how the real tools work:

   · WATERFALL — providers are tried in a FIXED ORDER and the chain STOPS at
     the first confident answer; later providers are never called. That
     short-circuit is the whole idea, so the skipped ones are drawn as skipped
     rather than omitted. (Clay's waterfall model.)
   · SEQUENCE — a multichannel cadence is not a list of sends; it is steps
     separated by WAITS with CONDITIONAL branches, and a reply exits the
     sequence. Drawing it without the conditions is what made it look like
     floating nodes. (lemlist / Reply.io / Amplemarket conditional sequences.)
   · MATCHED AUDIENCE — the ads lane is COMPANY-level while outreach is
     PERSON-level, they run in parallel off one audience, and the list is
     matched against the network rather than uploaded once. (LinkedIn Matched
     Audiences.)
   · CRM — a pipeline rail plus ONE unified record: activity timeline and an
     explicit next action, which is what a modern CRM view actually shows.
   ========================================================================== */

/* ── the query bar ──────────────────────────────────────────────────────── */

.d-query {
  display: flex;
  align-items: center;
  gap: calc(11px * var(--df-s, 1));
  width: min(calc(560px * var(--df-s, 1)), 100%);
  padding: calc(11px * var(--df-s, 1)) calc(15px * var(--df-s, 1));
  border-radius: calc(12px * var(--df-s, 1));
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, .05);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .06);
  font-size: calc(13px * var(--df-s, 1));
  color: var(--ink);
}
.d-query svg { width: calc(15px * var(--df-s, 1)); height: calc(15px * var(--df-s, 1)); flex: none; color: var(--ink-3); }
.d-query .d-q-count {
  margin-left: auto;
  font-family: var(--mono);
  font-size: calc(10px * var(--df-s, 1));
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-4);
  white-space: nowrap;
}

/* ── the waterfall ──────────────────────────────────────────────────────── */

.d-wfs { width: min(calc(560px * var(--df-s, 1)), 100%); display: flex; flex-direction: column; gap: calc(7px * var(--df-s, 1)); }

.d-wf-label {
  font-family: var(--mono);
  font-size: calc(9.5px * var(--df-s, 1));
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-4);
  margin: 0 0 calc(2px * var(--df-s, 1));
}

.d-wf {
  display: grid;
  grid-template-columns: calc(132px * var(--df-s, 1)) 1fr;
  align-items: center;
  gap: calc(12px * var(--df-s, 1));
}
.d-wf-name { font-size: calc(12.5px * var(--df-s, 1)); color: var(--ink-2); white-space: nowrap; }
.d-wf-chain { display: flex; align-items: center; gap: calc(5px * var(--df-s, 1)); flex-wrap: nowrap; }

.d-prov {
  display: inline-flex;
  align-items: center;
  gap: calc(5px * var(--df-s, 1));
  padding: calc(5px * var(--df-s, 1)) calc(9px * var(--df-s, 1));
  border-radius: calc(7px * var(--df-s, 1));
  border: 1px solid var(--line-2);
  background: rgba(255, 255, 255, .035);
  font-size: calc(11px * var(--df-s, 1));
  color: var(--ink-3);
  white-space: nowrap;
}
/* tried and returned nothing — still drawn, because "we called it and it
   missed" is different information from "we never called it" */
.d-prov.is-miss { opacity: .45; text-decoration: line-through; text-decoration-color: rgba(255,255,255,.3); }
/* never reached: the chain had already stopped */
.d-prov.is-skip { opacity: .28; }
.d-prov.is-hit {
  border-color: rgba(79, 141, 255, .5);
  background: rgba(79, 141, 255, .16);
  color: var(--ink);
  box-shadow: 0 6px 18px -8px rgba(79, 141, 255, .8);
}
.d-prov b { font-weight: 500; color: inherit; }
.d-tick { display: inline-grid; place-items: center; width: calc(13px * var(--df-s, 1)); height: calc(13px * var(--df-s, 1)); border-radius: 50%; background: var(--accent); color: #fff; flex: none; }
.d-tick svg { width: calc(8px * var(--df-s, 1)); height: calc(8px * var(--df-s, 1)); }

.d-seq-arrow { color: var(--ink-4); font-size: calc(11px * var(--df-s, 1)); line-height: 1; flex: none; }

/* ── two parallel lanes ─────────────────────────────────────────────────── */

.d-lanes {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: calc(16px * var(--df-s, 1));
  width: min(calc(620px * var(--df-s, 1)), 100%);
  align-items: start;
}

.d-lane {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: calc(5px * var(--df-s, 1));
  padding: calc(11px * var(--df-s, 1));
  border-radius: calc(13px * var(--df-s, 1));
  border: 1px solid var(--line-2);
  background: rgba(255, 255, 255, .022);
}
.d-lane-h {
  display: flex;
  align-items: baseline;
  gap: calc(7px * var(--df-s, 1));
  margin-bottom: calc(4px * var(--df-s, 1));
}
.d-lane-h b { font-size: calc(12px * var(--df-s, 1)); font-weight: 500; color: var(--ink); }
.d-lane-h span {
  font-family: var(--mono);
  font-size: calc(8.5px * var(--df-s, 1));
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-4);
}

/* one step in a cadence */
.d-seq {
  display: flex;
  align-items: center;
  gap: calc(8px * var(--df-s, 1));
  padding: calc(7px * var(--df-s, 1)) calc(9px * var(--df-s, 1));
  border-radius: calc(9px * var(--df-s, 1));
  background: rgba(255, 255, 255, .045);
  box-shadow: inset 0 0 0 1px var(--line-2);
  font-size: calc(11.5px * var(--df-s, 1));
  color: var(--ink-2);
}
.d-seq b { color: var(--ink); font-weight: 500; }
.d-day {
  font-family: var(--mono);
  font-size: calc(9px * var(--df-s, 1));
  letter-spacing: .06em;
  color: var(--ink-4);
  min-width: calc(34px * var(--df-s, 1));
  flex: none;
}
.d-seq .d-ico { width: calc(19px * var(--df-s, 1)); height: calc(19px * var(--df-s, 1)); border-radius: calc(5px * var(--df-s, 1)); }
.d-seq .d-ico svg { width: calc(11px * var(--df-s, 1)); height: calc(11px * var(--df-s, 1)); }
.d-seq.is-on { background: rgba(79, 141, 255, .15); box-shadow: inset 0 0 0 1px rgba(79, 141, 255, .35); color: var(--ink); }
.d-seq.is-on .d-ico { background: rgba(79, 141, 255, .3); color: #DCE9FF; }

/* the wait / condition between two steps — the thing that makes it a
   sequence rather than a list */
.d-cond {
  display: flex;
  align-items: center;
  gap: calc(6px * var(--df-s, 1));
  padding-left: calc(11px * var(--df-s, 1));
  font-family: var(--mono);
  font-size: calc(8.5px * var(--df-s, 1));
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink-4);
  line-height: 1.1;
}
.d-cond::before {
  content: "";
  width: 1px;
  height: calc(13px * var(--df-s, 1));
  flex: none;
  background-image: linear-gradient(180deg, rgba(255,255,255,.3) 0 3px, transparent 3px 6px);
  background-size: 1px 6px;
}
/* the exit branch — a reply leaves the sequence */
.d-exit {
  display: flex;
  align-items: center;
  gap: calc(6px * var(--df-s, 1));
  margin-left: calc(11px * var(--df-s, 1));
  padding: calc(5px * var(--df-s, 1)) calc(9px * var(--df-s, 1));
  border-radius: calc(7px * var(--df-s, 1));
  border: 1px dashed rgba(79, 141, 255, .45);
  font-size: calc(10.5px * var(--df-s, 1));
  color: #9DC0FF;
  align-self: flex-start;
}

/* the hand-off arrow between the two lanes */
.d-cross {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(8px * var(--df-s, 1));
  margin-top: calc(4px * var(--df-s, 1));
  font-family: var(--mono);
  font-size: calc(8.5px * var(--df-s, 1));
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-4);
}
.d-cross i {
  flex: 1;
  height: 1px;
  background-image: linear-gradient(90deg, rgba(79,141,255,.5) 0 6px, transparent 6px 12px);
  background-size: 12px 1px;
}

/* ── CRM ────────────────────────────────────────────────────────────────── */

.d-stages { display: flex; align-items: stretch; gap: calc(6px * var(--df-s, 1)); width: min(calc(600px * var(--df-s, 1)), 100%); }
.d-stage {
  flex: 1;
  padding: calc(8px * var(--df-s, 1)) calc(6px * var(--df-s, 1));
  border-radius: calc(9px * var(--df-s, 1));
  border: 1px solid var(--line-2);
  background: rgba(255, 255, 255, .03);
  text-align: center;
}
.d-stage span { display: block; font-family: var(--mono); font-size: calc(8.5px * var(--df-s, 1)); letter-spacing: .08em; text-transform: uppercase; color: var(--ink-4); }
.d-stage b { display: block; margin-top: calc(3px * var(--df-s, 1)); font-size: calc(15px * var(--df-s, 1)); font-weight: 500; color: var(--ink-2); }
.d-stage.is-on { border-color: rgba(79, 141, 255, .45); background: rgba(79, 141, 255, .14); }
.d-stage.is-on b { color: var(--ink); }

.d-rec {
  width: min(calc(600px * var(--df-s, 1)), 100%);
  border-radius: calc(13px * var(--df-s, 1));
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, .028);
  overflow: hidden;
}
.d-rec-h {
  display: flex;
  align-items: center;
  gap: calc(9px * var(--df-s, 1));
  padding: calc(9px * var(--df-s, 1)) calc(13px * var(--df-s, 1));
  border-bottom: 1px solid var(--line-2);
  font-size: calc(12px * var(--df-s, 1));
  color: var(--ink-2);
}
.d-rec-h b { color: var(--ink); font-weight: 500; font-size: calc(12.5px * var(--df-s, 1)); }
.d-rec-h .d-rec-meta { margin-left: auto; font-family: var(--mono); font-size: calc(9px * var(--df-s, 1)); letter-spacing: .08em; text-transform: uppercase; color: var(--ink-4); }

.d-tl { padding: calc(9px * var(--df-s, 1)) calc(13px * var(--df-s, 1)); display: flex; flex-direction: column; gap: calc(6px * var(--df-s, 1)); }
.d-tl-row { display: flex; align-items: center; gap: calc(9px * var(--df-s, 1)); font-size: calc(11.5px * var(--df-s, 1)); color: var(--ink-2); }
.d-tl-row .d-day { min-width: calc(40px * var(--df-s, 1)); }
.d-tl-dot { width: calc(6px * var(--df-s, 1)); height: calc(6px * var(--df-s, 1)); border-radius: 50%; background: rgba(255,255,255,.28); flex: none; }
.d-tl-row.is-on .d-tl-dot { background: var(--accent); box-shadow: 0 0 10px rgba(79,141,255,.9); }
.d-tl-row.is-on { color: var(--ink); }

.d-next {
  display: flex;
  align-items: center;
  gap: calc(9px * var(--df-s, 1));
  padding: calc(9px * var(--df-s, 1)) calc(13px * var(--df-s, 1));
  border-top: 1px solid var(--line-2);
  background: rgba(79, 141, 255, .09);
  font-size: calc(11.5px * var(--df-s, 1));
  color: var(--ink);
}
.d-next .d-rec-meta { margin-left: auto; font-family: var(--mono); font-size: calc(9px * var(--df-s, 1)); letter-spacing: .08em; text-transform: uppercase; color: #9DC0FF; }

/* the new primitives inherit the same staggered entrance as the originals */
.df-panel .d-query,
.df-panel .d-wfs,
.df-panel .d-lanes,
.df-panel .d-stages,
.df-panel .d-rec { opacity: 0; }
.df-panel.is-in .d-query,
.df-panel.is-in .d-wfs,
.df-panel.is-in .d-lanes,
.df-panel.is-in .d-stages,
.df-panel.is-in .d-rec {
  animation: dfRise .62s var(--mat) forwards;
  animation-delay: calc(90ms + var(--d, 0) * 70ms);
}

@media (prefers-reduced-motion: reduce) {
  .df-panel .d-query, .df-panel .d-wfs, .df-panel .d-lanes,
  .df-panel .d-stages, .df-panel .d-rec { opacity: 1; animation: none !important; }
}

/* stacked layout: two lanes side by side is unreadable on a phone */
@media (max-width: 900px) {
  .d-lanes { grid-template-columns: 1fr; }
  .d-wf { grid-template-columns: 1fr; gap: calc(4px * var(--df-s, 1)); }
  .d-wf-chain { flex-wrap: wrap; }
  .d-stages { flex-wrap: wrap; }
}

/* 🔴 Short-viewport spacing relief for the rebuilt diagrams.
   Measured after the v2 bodies landed: at 1280x720 the Search & Enrichment
   panel (query bar + 3 waterfall rows + a 4-row table) ran 21px past the card,
   which clips SILENTLY — overflow:hidden, no scrollbar, the last table row
   just disappears.

   Fixed by taking it out of the GAPS rather than dropping --df-s another tier.
   The type is already at 1.10 there; shrinking it again to buy 21px would cost
   legibility on the screen that can least afford it, whereas the column gap and
   the connector drops are pure whitespace. Together they give back ~47px. */
@media (max-height: 780px) {
  .df-card .d-col { gap: calc(7px * var(--df-s)); }
  .df-card .d-drop { height: calc(14px * var(--df-s)); }
  .df-card .d-wfs { gap: calc(5px * var(--df-s)); }
  .df-card .d-lane { gap: calc(4px * var(--df-s)); padding: calc(9px * var(--df-s)); }
}

/* ==========================================================================
   HORIZONTAL OVERFLOW + A PROGRESS BAR THAT ACTUALLY COMPLETES
   ========================================================================== */

/* 🔴 The page scrolled sideways by 327px. Measured offenders, all decorative:
   `.lq-caustics` (2238px wide, starting at -200), `.orb-night`, `.footer-glow`,
   and this file's own `.df-stage::after` horizon line, which is deliberately
   `left:-20%; right:-20%` so the light bleeds past the edges.

   Fixed by CLIPPING each decorative layer at its own container rather than
   putting `overflow-x: hidden` on `body` or `html`. That shortcut would have
   broken the walkthrough outright: an ancestor with a clipping overflow makes
   `position: sticky` stop working, and the entire pinned stage depends on it. */
.df-stage { overflow: hidden; }

/* the last connector has to reach the last dot, or the bar never completes.
   The last column is `auto` (as wide as its label) and its content was pinned
   to flex-end, which parked the final dot ~84px past the end of the connector
   feeding it — so the rail always stopped short of the step it was pointing at.
   Aligning the column to the start closes the gap; the LABEL still runs to the
   card's edge, so the "flush right" intent survives, it is just the label that
   is flush rather than the dot. */
.df-step:last-child { align-items: flex-start; text-align: left; }
.df-step:last-child .df-step-rail { justify-content: flex-start; }
