/* ══════════════════════════════════════════════════════════════════════════
   DIGITAL UMBRELLA — brand stylesheet

   Direction A, "restrained menace", with neon blue. Decided 18 Aug 2026 from
   the three built out in explorations/directions.html.

   The premise: the cyberpunk lives in the CRAFT, not the neon. Mono labels,
   hairline rules, a hex field you only notice on purpose, and exactly one
   thing that glows. It reads as an infrastructure company with taste and
   survives a procurement review without needing to be explained.

   Single source of truth for both pages. Change a token here, not in a page.
   ══════════════════════════════════════════════════════════════════════════ */

@import url('./fonts.css');

:root {
  /* ── ground ─────────────────────────────────────────────────────────── */
  --bg:        #08090D;   /* page, darker than any panel so panels lift */
  --panel:     #0A0C10;   /* the main surface */
  --panel-2:   #0C0F15;   /* insets: stat cells, code, table stripes */
  --line:      #1C212C;   /* hairline: separates, not a control boundary */
  --line-2:    #39414F;   /* structural divider — a border you should notice */

  /* Interactive boundaries. WCAG 1.4.11 requires 3:1 for the edge of a
     control, which is a different and stricter bar than text contrast. The
     first version of this used --line-2 at 1.48:1: a button with no
     perceivable border, and invisible to a text-only contrast audit. */
  --edge:      #5A6478;   /* 3.3:1 on --panel */
  --edge-hi:   #7C889E;   /* hover */

  /* ── ink ────────────────────────────────────────────────────────────── */
  --ink:       #F7F8FB;   /* headings           18.4:1 on --panel */
  --ink-2:     #C8CEDA;   /* strong body        13.0:1 */
  --ink-3:     #99A1B2;   /* body                7.5:1 */
  --ink-4:     #828A9E;   /* small labels        5.6:1 — the floor. Nothing
                             dimmer than this carries text, ever. */

  /* ── accent ─────────────────────────────────────────────────────────── */
  --a:         #4D9EFF;   /* neon blue           7.1:1 on --panel, AAA */
  --a-dim:     #123A6E;   /* borders only, never text */
  --a-ink:     #06080C;   /* label on an accent fill  7.3:1 */

  /* Bloom. The whole reason this reads neon rather than merely blue. */
  --glow-sm: 0 0 20px color-mix(in srgb, var(--a) 38%, transparent);
  --glow-lg: 0 0 34px color-mix(in srgb, var(--a) 30%, transparent);

  /* ── type ───────────────────────────────────────────────────────────── */
  --f-sans: 'IBM Plex Sans', ui-sans-serif, system-ui, -apple-system, sans-serif;
  --f-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;

  /* ── metrics ────────────────────────────────────────────────────────── */
  --s-1: .35rem;  --s-2: .6rem;   --s-3: .9rem;
  --s-4: 1.15rem; --s-5: 1.5rem;  --s-6: 2.25rem;  --s-7: 3rem;

  --maxw: 1120px;
  --pad: clamp(1.15rem, 4vw, 2.5rem);
  --r: 6px;
}

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

html {
  scroll-behavior: smooth;
  /* The topbar is sticky at 68px; without this every in-page anchor parks its
     heading underneath it. */
  scroll-padding-top: 84px;
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink-3);
  font: 400 16px/1.65 var(--f-sans);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Skip link — the first thing a keyboard user meets. */
.skip {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  display: inline-flex; align-items: center; min-height: 44px;
  padding: .75rem 1.25rem; background: var(--a); color: var(--a-ink);
  font: 500 14px/1 var(--f-sans); text-decoration: none;
}
.skip:focus { left: 0; }

:where(a, button, input, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--a);
  outline-offset: 3px;
  border-radius: 2px;
}

/* ── layout ──────────────────────────────────────────────────────────── */
.wrap { max-width: var(--maxw); margin: 0 auto; padding-inline: var(--pad); }
.section { padding-block: clamp(3.5rem, 9vw, 6.5rem); }
.section + .section { border-top: 1px solid var(--line); }

/* ── type scale ──────────────────────────────────────────────────────── */
h1, h2, h3 { color: var(--ink); margin: 0; text-wrap: balance; }
h1 { font: 600 clamp(2.1rem, 5.4vw, 3.4rem)/1.07 var(--f-sans); letter-spacing: -.028em; }
h2 { font: 600 clamp(1.5rem, 3.4vw, 2.1rem)/1.15 var(--f-sans); letter-spacing: -.02em; }
h3 { font: 600 1.0625rem/1.4 var(--f-sans); letter-spacing: -.01em; }
p { margin: 0; }
.lede { color: var(--ink-3); font-size: clamp(1rem, 1.8vw, 1.15rem); max-width: 58ch; }
.measure { max-width: 66ch; }

/* The mono label is the brand's signature. Used for eyebrows, stat captions,
   table headers, metadata — anywhere the page is naming a thing rather than
   speaking. */
.label {
  margin: 0;
  font: 500 12px/1.3 var(--f-mono);
  letter-spacing: .16em; text-transform: uppercase; color: var(--ink-4);
}
.eyebrow {
  display: inline-flex; align-items: center; gap: .5rem;
  padding: .35rem .7rem .35rem .55rem;
  border: 1px solid var(--a-dim); border-radius: 999px;
  background: color-mix(in srgb, var(--a) 8%, transparent);
  box-shadow: inset 0 0 14px color-mix(in srgb, var(--a) 10%, transparent);
  color: var(--a); font: 500 11px/1 var(--f-mono);
  letter-spacing: .14em; text-transform: uppercase;
}

/* ── the hex field ───────────────────────────────────────────────────── */
/* Two crossed gradients plus a mask. Barely visible on purpose: you should
   register that the surface has structure, not see a grid. */
/* overflow-x: clip, because .hero-mark is positioned to bleed past the right
   edge on purpose — `right` resolves to -1vw at every realistic width, and -4rem
   under 620px. Without clipping, that bleed drags the document wider than the
   viewport and the whole page scrolls sideways by ~1vw. Measured live: 1227 vs
   1215.

   `clip` rather than `hidden`: hidden on one axis forces the other to `auto`,
   which creates a scroll container and would break any sticky positioning
   inside. clip does not. */
.field { position: relative; isolation: isolate; overflow-x: clip; }
.field::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background-image:
    linear-gradient(color-mix(in srgb, #fff 4%, transparent) 1px, transparent 1px),
    linear-gradient(90deg, color-mix(in srgb, #fff 4%, transparent) 1px, transparent 1px);
  background-size: 56px 56px;
  mask-image: radial-gradient(85% 70% at 72% 0%, #000 0%, transparent 76%);
}
.field::after {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background: radial-gradient(110% 80% at 86% -12%,
              color-mix(in srgb, var(--a) 16%, transparent), transparent 62%);
}

/* ── brand ───────────────────────────────────────────────────────────── */
.brand {
  display: inline-flex; align-items: center; gap: .6rem;
  color: var(--ink); text-decoration: none;
  font: 600 15px/1 var(--f-sans); letter-spacing: .15em; text-transform: uppercase;
}
.brand svg { flex: none; color: var(--a); }
.brand:hover { color: #fff; }

/* ── nav ─────────────────────────────────────────────────────────────── */
.topbar { border-bottom: 1px solid var(--line); background: color-mix(in srgb, var(--bg) 88%, transparent); backdrop-filter: blur(10px); position: sticky; top: 0; z-index: 40; }
.topbar .wrap { display: flex; align-items: center; gap: 1.5rem; min-height: 68px; }
.topnav { display: flex; align-items: center; gap: 1.5rem; margin-left: auto; }
.topnav a {
  display: inline-flex; align-items: center; min-height: 44px;   /* hit area, not just text */
  color: var(--ink-4); text-decoration: none; font-size: 14px;
}
.topnav a:hover, .topnav a[aria-current="page"] { color: var(--ink); }
@media (max-width: 560px) { .topnav { gap: 1rem; } .topnav .hide-sm { display: none; } }

/* ── buttons ─────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  min-height: 44px; padding: .75rem 1.35rem;      /* 44px: a real touch target */
  border: 1px solid transparent; border-radius: var(--r);
  font: 500 15px/1 var(--f-sans); text-decoration: none; cursor: pointer;
  transition: background .18s ease, border-color .18s ease, color .18s ease, box-shadow .18s ease;
}
.btn-primary { background: var(--a); color: var(--a-ink); font-weight: 600; box-shadow: var(--glow-sm); }
.btn-primary:hover { background: #fff; box-shadow: var(--glow-lg); }
.btn-ghost { border-color: var(--edge); color: var(--ink-2); }
.btn-ghost:hover { border-color: var(--edge-hi); color: var(--ink); background: color-mix(in srgb, #fff 4%, transparent); }

/* ── panels, grids, stats ────────────────────────────────────────────── */
.panel { background: var(--panel); border: 1px solid var(--line); border-radius: var(--r); }
.grid { display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line); border-radius: var(--r); overflow: hidden; }
.grid > * { background: var(--panel); padding: clamp(1.25rem, 3vw, 1.85rem); }
.cols-2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); }
.cols-3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr)); }

.stat b { display: block; color: var(--ink); font: 600 clamp(1.6rem, 3.4vw, 2.15rem)/1.05 var(--f-sans); font-variant-numeric: tabular-nums; }
.stat span { display: block; margin-top: .45rem; font: 500 12px/1.45 var(--f-mono); letter-spacing: .1em; text-transform: uppercase; color: var(--ink-4); }

/* ── steps ───────────────────────────────────────────────────────────── */
/* Numbered because the content genuinely IS a sequence — a scoping procedure
   runs in order. Numbering anything else would be decoration. */
.step { display: grid; grid-template-columns: auto 1fr; gap: 1.1rem; align-items: start; }
.step-n {
  display: grid; place-items: center; width: 30px; height: 30px;
  border: 1px solid var(--a-dim); border-radius: 4px;
  color: var(--a); font: 500 12px/1 var(--f-mono);
  background: color-mix(in srgb, var(--a) 7%, transparent);
}
.step h3 { margin-bottom: .3rem; }

/* ── list ────────────────────────────────────────────────────────────── */
.ticks { list-style: none; margin: 0; padding: 0; display: grid; gap: .7rem; }
.ticks li { display: grid; grid-template-columns: auto 1fr; gap: .7rem; align-items: start; }
.ticks svg { margin-top: .35em; color: var(--a); flex: none; }

.prose-sm { font-size: 15px; }

/* Two-column split, replacing four inline auto-fit grids that used four
   different minima for no reason. */
.split { display: grid; gap: var(--s-6); grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr)); }
.split > * { min-width: 0; }
.stack { display: grid; gap: var(--s-4); }

/* ── footer ──────────────────────────────────────────────────────────── */
/* The contact address is the page's main conversion, so it gets a real target
   rather than the height of its own text. */
.mail {
  display: inline-flex; align-items: center; min-height: 44px;
  color: var(--a); text-decoration: none;
  font: 500 17px/1.4 var(--f-mono); word-break: break-all;
}
.mail:hover { text-decoration: underline; }

footer { border-top: 1px solid var(--line); padding-block: 3rem 4rem; }
footer .cols { display: flex; flex-wrap: wrap; gap: 2rem 3rem; align-items: start; }
footer a {
  display: inline-flex; align-items: center; min-height: 32px;
  color: var(--ink-4); text-decoration: none;
}
footer a:hover { color: var(--ink-2); }
/* 32px plus the 0.55rem gap between them clears WCAG 2.5.8's 24px target size
   with spacing. Footer links are not primary navigation, so the full 44px
   would cost more vertical rhythm than it buys. */
/* Uses the --ink-4 floor, not a dimmer one-off. The first version here was
   #626B7D with a comment claiming 4.6:1; it measures 3.71:1 and fails. The
   comment was written from estimate, which is the entire reason the audit
   reads values out of this file rather than trusting what it says. */
footer .fine { color: var(--ink-4); font-size: 13px; }
footer nav { display: grid; gap: .35rem; }
footer .brand { min-height: 44px; }

/* ══════════════════════════════════════════════════════════════════════════
   MOTION

   One idea: the umbrella deploys from a hub, and coverage spreads outward.
   Everything here is that idea. See assets/motion.js.

   Every effect below is disabled by the prefers-reduced-motion block near the
   top of this file, which sets the final state immediately rather than
   playing a faster version of the animation.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── DEPLOY ─────────────────────────────────────────────────────────────
   The mark opens: ribs extend from the hub, then the canopy panels fill in
   behind them. 700ms, once, on load. This is the one piece of motion on the
   site that could not belong to any other company. */
[data-deploy] .rib {
  stroke-dasharray: 11;
  stroke-dashoffset: 11;
  transform-origin: 12px 12px;
}
[data-deploy] .canopy { opacity: 0; transform-origin: 12px 12px; transform: scale(.82); }
[data-deploy] .panel  { opacity: 0; }
[data-deploy] .hub    { transform-origin: 12px 12px; transform: scale(0); }

[data-deploy].is-deployed .hub {
  animation: du-hub .28s cubic-bezier(.34,1.2,.64,1) forwards;
}
[data-deploy].is-deployed .rib {
  animation: du-rib .42s cubic-bezier(.22,.68,.3,1) forwards;
  animation-delay: .16s;
}
[data-deploy].is-deployed .canopy {
  animation: du-canopy .46s cubic-bezier(.22,.68,.3,1) forwards;
  animation-delay: .3s;
}
[data-deploy].is-deployed .panel {
  animation: du-panel .5s ease forwards;
  animation-delay: .42s;
}

@keyframes du-hub    { to { transform: scale(1); } }
@keyframes du-rib    { to { stroke-dashoffset: 0; } }
@keyframes du-canopy { to { opacity: 1; transform: scale(1); } }
@keyframes du-panel  { to { opacity: .16; } }

/* The logo is the instance that animates — an umbrella opening as the page
   loads is the brand's own gesture. It is inline SVG for that reason: CSS
   cannot reach into the shadow tree a <use> clones, so every other instance
   on the page is a static <use> and carries no data-deploy. */
.mark { flex: none; }

/* ── COVER ──────────────────────────────────────────────────────────────
   The hex field canvas sits behind the hero, beneath the text. It replaces the
   static CSS grid in .field when present. */
.hero-canvas {
  position: absolute; inset: 0; width: 100%; height: 100%;
  z-index: -1; pointer-events: none;
}
.field:has(.hero-canvas)::before { display: none; }   /* canvas supersedes the static grid */

/* ── CONVERGE ───────────────────────────────────────────────────────────
   Rises and fades. No bounce, no scale: the page is stating facts, and easing
   that overshoots reads as a product pleased with itself. */
[data-reveal] {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity .5s cubic-bezier(.22,.68,.3,1), transform .5s cubic-bezier(.22,.68,.3,1);
  will-change: opacity, transform;
}
[data-reveal].is-in { opacity: 1; transform: none; }

/* Dropped once the element has arrived: leaving will-change on every revealed
   node keeps a compositor layer alive for the life of the page. */
[data-reveal].is-in { will-change: auto; }

/* The primary button's glow breathes on hover — the canopy catching light.
   Hover only, so it is never ambient motion competing with the reading. */
.btn-primary { transition: background .18s ease, box-shadow .35s ease, transform .18s ease; }
.btn-primary:hover { transform: translateY(-1px); }

@media (prefers-reduced-motion: reduce) {
  [data-reveal] { opacity: 1; transform: none; transition: none; }
  [data-deploy] .rib { stroke-dashoffset: 0; }
  [data-deploy] .canopy { opacity: 1; transform: none; }
  [data-deploy] .panel { opacity: .16; }
  [data-deploy] .hub { transform: none; }
  [data-deploy].is-deployed * { animation: none !important; }
  .btn-primary:hover { transform: none; }
}

/* ── The hero canopy ────────────────────────────────────────────────────
   The deploy at 26px is too small to read as motion — you register the logo
   as already-drawn. This is the same mark at a size where opening it is the
   page's opening gesture, and it sits exactly where the hex pulse originates,
   so the canopy opens and the coverage spreads from the same point. */
.hero-mark {
  position: absolute;
  top: clamp(-1rem, 2vw, 2rem);
  right: clamp(-3rem, -1vw, 1rem);
  width: clamp(190px, 27vw, 330px);
  height: auto;
  color: var(--a);
  opacity: .5;
  z-index: -1;
  pointer-events: none;
}
.hero-mark .rib { stroke-dasharray: 11; stroke-dashoffset: 11; }
.hero-mark .canopy { opacity: 0; transform: scale(.86); transform-origin: 12px 12px; }
.hero-mark .panel { opacity: 0; }
.hero-mark .hub { transform: scale(0); transform-origin: 12px 12px; }

/* Slower and more deliberate than the logo's: this one is meant to be watched. */
.hero-mark.is-deployed .hub    { animation: du-hub .34s cubic-bezier(.34,1.2,.64,1) forwards; }
.hero-mark.is-deployed .rib    { animation: du-rib .72s cubic-bezier(.22,.68,.3,1) forwards; animation-delay: .2s; }
.hero-mark.is-deployed .canopy { animation: du-canopy .8s cubic-bezier(.22,.68,.3,1) forwards; animation-delay: .46s; }
.hero-mark.is-deployed .panel  { animation: du-panel .9s ease forwards; animation-delay: .66s; }

/* Ribs deploy one after another, outward — an umbrella does not open all at
   once. Six 60ms steps, so the whole sweep costs 300ms. */
/* nth-OF-TYPE, not nth-child: the hub circle is the first child, so nth-child
   was off by one and the sixth rib fell through to the base delay — five ribs
   swept and one appeared early. Among <path> siblings the six ribs come first
   and the panel last, so nth-of-type(1..6) is exactly the ribs. */
.hero-mark.is-deployed .rib:nth-of-type(1) { animation-delay: .20s; }
.hero-mark.is-deployed .rib:nth-of-type(2) { animation-delay: .26s; }
.hero-mark.is-deployed .rib:nth-of-type(3) { animation-delay: .32s; }
.hero-mark.is-deployed .rib:nth-of-type(4) { animation-delay: .38s; }
.hero-mark.is-deployed .rib:nth-of-type(5) { animation-delay: .44s; }
.hero-mark.is-deployed .rib:nth-of-type(6) { animation-delay: .50s; }

@media (max-width: 620px) {
  /* Behind the headline on a narrow screen it competes with the text it is
     supposed to sit behind. */
  .hero-mark { opacity: .28; width: 56vw; top: -1rem; right: -4rem; }
}

@media (prefers-reduced-motion: reduce) {
  .hero-mark .rib { stroke-dashoffset: 0; }
  .hero-mark .canopy { opacity: 1; transform: none; }
  .hero-mark .panel { opacity: .16; }
  .hero-mark .hub { transform: none; }
  .hero-mark.is-deployed * { animation: none !important; }
}

/* ── The scope panel ────────────────────────────────────────────────────
   The product, shown rather than described. A page that only asserts what a
   tool produces is asking to be taken on faith by a vendor with no customers;
   this is the thing itself, and it is also what stops the page reading as bare.

   The motion is the third of the three ideas — CONVERGE. Rows arrive, then the
   total resolves. That is literally the product's promise: many judgements,
   one number everyone can defend. */
.scope {
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--panel); overflow: hidden;
  font-family: var(--f-mono);
}
.scope-head {
  display: flex; align-items: center; gap: .75rem;
  padding: .7rem 1rem; border-bottom: 1px solid var(--line);
  background: var(--panel-2);
  font: 500 11px/1 var(--f-mono); letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-4);
}
.scope-head .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--a); box-shadow: 0 0 8px var(--a); flex: none; }
.scope-head .count { margin-left: auto; color: var(--ink-2); letter-spacing: .08em; }

.scope-row {
  display: grid; grid-template-columns: 1fr auto auto;
  align-items: center; gap: .9rem;
  padding: .62rem 1rem;
  border-bottom: 1px solid color-mix(in srgb, var(--line) 55%, transparent);
  font-size: 12.5px; color: var(--ink-2);
}
.scope-row:last-of-type { border-bottom: 0; }
.scope-row .band {
  font-size: 10px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-4);
  padding: .16rem .4rem; border: 1px solid var(--line);
  border-radius: 3px;
}
.scope-row .days { color: var(--ink); font-variant-numeric: tabular-nums; min-width: 3.6em; text-align: right; }

.scope-total {
  display: grid; grid-template-columns: 1fr auto;
  align-items: baseline; gap: .9rem;
  padding: .85rem 1rem;
  border-top: 1px solid var(--line-2);
  background: linear-gradient(180deg, color-mix(in srgb, var(--a) 6%, transparent), transparent);
}
.scope-total .k { font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--ink-4); }
.scope-total .v { font: 600 1.5rem/1 var(--f-sans); color: var(--ink); font-variant-numeric: tabular-nums; }
.scope-total .sub { grid-column: 1 / -1; font-size: 11.5px; color: var(--ink-4); }

/* CONVERGE — rows land in sequence, then the total. */
.scope [data-row] { opacity: 0; transform: translateX(-8px); }
.scope.is-in [data-row] {
  animation: du-row .42s cubic-bezier(.22,.68,.3,1) forwards;
}
/* nth-CHILD with the header counted, not nth-of-type: .scope-head is also a
   div, so nth-of-type(1) was the header and every row was off by one — the
   same mistake as the rib stagger, in a different shape. Children are
   head(1), rows(2-4), total(5). */
.scope.is-in > [data-row]:nth-child(2) { animation-delay: .06s; }
.scope.is-in > [data-row]:nth-child(3) { animation-delay: .14s; }
.scope.is-in > [data-row]:nth-child(4) { animation-delay: .22s; }
.scope.is-in > [data-row]:nth-child(5) { animation-delay: .32s; }
@keyframes du-row { to { opacity: 1; transform: none; } }

/* ── Cards respond to the pointer ───────────────────────────────────────
   The grid was inert: nothing on the page acknowledged being pointed at,
   which is most of why it read as flat. */
.grid > * { transition: background .2s ease, box-shadow .2s ease; }
.grid > *:hover { background: var(--panel-2); }

.step { transition: transform .2s ease; }
.step:hover .step-n { border-color: var(--a); box-shadow: var(--glow-sm); }
.step-n { transition: border-color .2s ease, box-shadow .2s ease; }

/* Heading LEVEL is semantics; heading SIZE is design. A section heading that
   must sit below another in the outline should not shrink because of it, so
   the display size travels on a class instead of the tag. */
.h-display { font: 600 clamp(1.5rem, 3.4vw, 2.1rem)/1.15 var(--f-sans); letter-spacing: -.02em; color: var(--ink); }

/* The topbar nav scrolls rather than hiding a link. Hiding "Company" on mobile
   removed the only route to company information from a site that a buyer is
   already asking "who are you?" about. */
.topnav { overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
.topnav::-webkit-scrollbar { display: none; }
.topnav a { white-space: nowrap; }
@media (max-width: 560px) { .topnav .hide-sm { display: inline-flex; } }

/* ── The procedure rail ─────────────────────────────────────────────────
   A line running through the four steps, drawn as they arrive. The steps are
   numbered because scoping genuinely IS a sequence — the rail makes that
   structural rather than a claim in the copy. */
.steps { position: relative; }
.steps::before {
  content: "";
  position: absolute; left: 15px; top: 8px; bottom: 8px; width: 1px;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--a) 55%, transparent),
    color-mix(in srgb, var(--a) 12%, transparent));
  transform: scaleY(0); transform-origin: top;
  transition: transform 1.1s cubic-bezier(.22,.68,.3,1);
}
.steps.is-in::before { transform: scaleY(1); }

/* On a multi-column layout the rail would cut across the columns rather than
   thread through the sequence, so it only exists when the steps are stacked. */
@media (min-width: 760px) { .steps::before { display: none; } }

@media (prefers-reduced-motion: reduce) {
  .steps::before { transform: scaleY(1); transition: none; }
}
