/* ============================================================
   RMS Automation — homepage
   ============================================================ */

/* ===================== HERO / SCROLL-SCRUB ===================== */
.scrollytell{
  position:relative;
  height:460vh;             /* the scroll track that drives the footage */
}
.pin{
  position:sticky; top:0;
  height:100vh;
  overflow:hidden;
  background:var(--void);
}

#reel{
  position:absolute; inset:0;
  width:100%; height:100%;
}

/* vignette + gradient scrim so type always clears the footage */
.pin::after{
  content:"";
  position:absolute; inset:0;
  pointer-events:none;
  background:
    linear-gradient(to right, rgba(10,11,12,.95) 0%, rgba(10,11,12,.88) 26%, rgba(10,11,12,.5) 54%, rgba(10,11,12,0) 80%),
    linear-gradient(to top, rgba(10,11,12,.95) 0%, rgba(10,11,12,0) 38%),
    radial-gradient(120% 90% at 50% 45%, rgba(10,11,12,0) 40%, rgba(10,11,12,.7) 100%);
}

/* --- loading state --- */
.reel-load{
  position:absolute; inset:0; z-index:6;
  display:grid; place-content:center; gap:16px;
  background:var(--void);
  transition:opacity .5s ease;
}
.reel-load.done{ opacity:0; pointer-events:none; }
.reel-load .bar{
  width:min(240px,50vw); height:2px; background:var(--edge); overflow:hidden;
}
.reel-load .bar i{
  display:block; height:100%; width:0%;
  background:var(--arc);
  transition:width .2s linear;
}
.reel-load .pct{
  font-family:var(--mono); font-size:11px; letter-spacing:.22em;
  color:var(--ash-dim); text-transform:uppercase; text-align:center;
}

/* --- stage copy, cross-faded against the footage --- */
.stages{
  position:absolute; z-index:5;
  left:0; right:0; top:0; bottom:0;
  display:grid;
  align-content:center;
  padding-left:calc(var(--gut) + 56px);
  padding-right:var(--gut);
  max-width:var(--max);
  margin-inline:auto;
}
.stage{
  grid-area:1/1;
  opacity:0;
  transform:translateY(18px);
  transition:opacity .45s ease, transform .45s ease;
  pointer-events:none;
  max-width:min(560px, 46vw);
}
.stage.on{ opacity:1; transform:none; pointer-events:auto; }

.stage .act{
  font-family:var(--mono); font-size:11px; letter-spacing:.24em;
  text-transform:uppercase; color:var(--arc); margin:0 0 18px;
  display:flex; align-items:center; gap:10px;
  max-width:none;          /* .stage p caps at 34ch, which is tiny at 11px */
  white-space:nowrap;
}
.stage .act b{
  font-weight:400; color:var(--ash);
}
.stage h1, .stage h2{
  font-family:var(--display);
  font-variation-settings:"wdth" 118,"wght" 800;
  font-size:clamp(38px, 7.4vw, 92px);
  line-height:.93;
  letter-spacing:-.03em;
  text-transform:uppercase;
  margin:0 0 20px;
}
.stage h1 .thin, .stage h2 .thin{
  font-variation-settings:"wdth" 118,"wght" 300;
  color:var(--ash);
  display:block;
}
.stage p{
  color:var(--ash); font-size:clamp(14.5px,1.4vw,17px);
  max-width:34ch; margin:0;
}
/* .stage .cta lived here. Stage 03 used to end on a Start-your-subscription
   / See-the-platform pair, but the reel is still pinned at that point and the
   buttons sat on top of the footage. Both calls to action are made again in
   the nav, the pricing table and the closing panel, so nothing is lost. */

/* --- act rail: the three beats of the footage --- */
.rail{
  position:absolute; z-index:5;
  left:var(--gut); top:50%; transform:translateY(-50%);
  display:grid; gap:0;
}
.rail li{ list-style:none; display:flex; align-items:center; gap:12px; height:64px; position:relative; }
.rail li::before{
  content:""; position:absolute; left:3px; top:0; bottom:0;
  width:1px; background:var(--edge);
}
.rail li:first-child::before{ top:50%; }
.rail li:last-child::before{ bottom:50%; }
.rail .dot{
  width:7px; height:7px; flex:none; border-radius:50%;
  background:var(--edge); position:relative; z-index:1;
  transition:background .3s, box-shadow .3s, transform .3s;
}
.rail .lbl{
  font-family:var(--mono); font-size:10px; letter-spacing:.18em;
  text-transform:uppercase; color:var(--ash-dim);
  transition:color .3s;
  white-space:nowrap;
}
.rail li.on .dot{
  background:var(--arc);
  box-shadow:0 0 0 4px rgba(255,138,31,.16);
  transform:scale(1.15);
}
.rail li.on .lbl{ color:var(--bone); }

/* --- scroll hint --- */
.hint{
  position:absolute; z-index:5; bottom:26px; left:50%;
  transform:translateX(-50%);
  display:flex; align-items:center; gap:10px;
  font-family:var(--mono); font-size:10px; letter-spacing:.2em;
  text-transform:uppercase; color:var(--ash-dim);
  transition:opacity .4s;
}
.hint .track{ width:1px; height:26px; background:var(--edge); position:relative; overflow:hidden; }
.hint .track::after{
  content:""; position:absolute; inset:0; background:var(--arc);
  animation:drip 2s cubic-bezier(.7,0,.3,1) infinite;
}
@keyframes drip{ 0%{ transform:translateY(-100%);} 55%,100%{ transform:translateY(100%);} }

/* fallback when JS/motion is off */
.reel-fallback{ display:none; position:absolute; inset:0; }
.reel-fallback img{ width:100%; height:100%; object-fit:cover; }

/* ===================== GENERIC SECTION ===================== */
.section{ padding:clamp(84px,11vw,150px) 0; position:relative; }
.section-alt{ background:var(--graphite); border-block:1px solid var(--edge); }

.section-head{ max-width:60ch; margin:0 0 clamp(44px,5vw,72px); }
.section-head h2{
  font-family:var(--display);
  font-variation-settings:"wdth" 112,"wght" 760;
  font-size:clamp(30px,4.6vw,54px);
  line-height:1.02; letter-spacing:-.025em;
  text-transform:uppercase; margin:0 0 18px;
}
/* That 18px is the gap to the sub-copy underneath. Where a head runs
   title-only, it has nothing to separate and would just pad the block's
   own bottom margin by an arbitrary amount. */
.section-head h2:last-child{ margin-bottom:0; }
.section-head h2 .thin{
  font-variation-settings:"wdth" 112,"wght" 300; color:var(--ash);
}
.section-head p{ color:var(--ash); font-size:clamp(15px,1.5vw,17.5px); margin:0; max-width:52ch; }

/* The code-window block (.api-grid, .api-copy, .api-facts and the whole
   .code snippet chrome) lived here. It belonged to a developer-API
   section that is no longer on any page — RMS is sold to contractors
   who photograph sheets, not to people integrating an endpoint, and
   #interop already answers what goes in and what comes out in the
   language they actually use. Roughly 70 lines. */

/* inline cross-references — a fact lives in exactly one section, and
   everywhere else that needs it points here instead of restating it */
.section-head a{
  color:var(--arc);
  text-decoration:underline;
  text-decoration-color:rgba(255,138,31,.35);
  text-underline-offset:3px;
  transition:text-decoration-color .2s;
}
.section-head a:hover{ text-decoration-color:var(--arc); }

/* Inside a plan chip the link takes the chip's colour rather than the
   accent. Two of the nine chips glowing orange at rest would be a second
   accent competing with the cascade, and the cascade is supposed to be
   the only accent on this section. The underline carries the affordance. */
.plan li a{
  color:inherit;
  text-decoration:underline;
  text-decoration-color:var(--ash-dim);
  text-underline-offset:3px;
  transition:text-decoration-color .2s;
}
.plan li a:hover{ text-decoration-color:var(--arc); }

/* The "how it works" four-step strip (.steps / .step) lived here. It went
   with the #howit section — its four cards restated either the
   #f-analyzer pitch or the #interop in/out list. */

/* ===================== PRICING ===================== */
/* The three plans used to argue for the middle one: Premium carried a
   lighter surface, a gold top rule, a "Most popular" badge and the only
   accent button, while Basic carried five struck-through rows naming
   what it doesn't do. A reader comparing them was being told the answer
   before they had read the question. All of that is gone. The three
   cards are now identical surfaces and the only accent that appears is
   one the reader summons by reaching for a tier. */

/* ── the drawn line ──────────────────────────────────────────────────
   A hairline that draws from the left and fades out before it arrives,
   so it never reads as a border and never needs an end cap. Paired with
   .lit, a set of boxes that warm together when the region is hovered
   rather than one at a time under the cursor.

   Both are armed by a container hover, never their own — hovering the
   region lights the whole set at once, which is the part that reads as
   considered rather than twitchy.

   Two numbers control the whole effect. --draw-dur is how long one line
   takes; --draw-step is the gap between neighbours in the sweep. The
   step has to stay a large fraction of the duration or the stagger is
   swamped and the lines read as appearing together — at .09s against
   .34s each line is roughly a quarter drawn before the next starts,
   which is still comfortably legible as a sequence.

   The step is set so the whole sweep lands in about .8s. The furthest
   line is (--ci 2 + --gi 3) steps back, so the total is 5 steps plus one
   duration. Add a fourth group to a card and that becomes 6 steps; drop
   the step to keep the total where it is, or the far corner of the last
   card finishes a full second after the pointer arrives and the effect
   stops feeling like a response to the reader. */
.plans{
  --draw-dur:.34s;
  --draw-step:.09s;
  display:grid; grid-template-columns:repeat(3,1fr); gap:1px;
  background:var(--edge); border:1px solid var(--edge);
}
/* The line takes its card's hue rather than a shared amber, so the cascade
   arrives in the same colour the card's button is already orbiting in.
   70% over the near-black card lands within a shade of --arc-dim, which is
   what this was before, so the middle card is effectively unchanged. */
.draw{
  display:block; height:1px; border:0; margin:0;
  background:linear-gradient(90deg,
    color-mix(in srgb, var(--glow, var(--arc)) 70%, transparent), transparent);
  transform:scaleX(0); transform-origin:left;
  transition:transform var(--draw-dur) cubic-bezier(.4,0,.2,1);
}
.plan li{
  font-size:12.5px; color:var(--ash); line-height:1.35;
  border:1px solid var(--edge); border-radius:2px; padding:5px 11px;
  transition:border-color .24s, color .24s;
}

/* The cascade. Reaching for a tier draws the line through it and through
   every tier it contains, because that is exactly what the tier gets you
   — the additive structure stops being a sentence the copy repeats and
   becomes something the reader watches happen. Even-handed by
   construction: the mechanic is identical from any of the three, and the
   card that produces the longest run is the one the reader pointed at. */
.plans:has(.plan[data-i="0"]:hover) .plan[data-i="0"] .draw,
.plans:has(.plan[data-i="1"]:hover) .plan[data-i="0"] .draw,
.plans:has(.plan[data-i="1"]:hover) .plan[data-i="1"] .draw,
.plans:has(.plan[data-i="2"]:hover) .plan .draw{ transform:scaleX(1); }

.plans:has(.plan[data-i="0"]:hover) .plan[data-i="0"] li,
.plans:has(.plan[data-i="1"]:hover) .plan[data-i="0"] li,
.plans:has(.plan[data-i="1"]:hover) .plan[data-i="1"] li,
.plans:has(.plan[data-i="2"]:hover) .plan li{
  border-color:color-mix(in srgb, var(--glow, var(--arc)) 62%, transparent);
  color:var(--bone);
}

.plans:has(.plan[data-i="0"]:hover) .plan[data-i="0"] h3,
.plans:has(.plan[data-i="1"]:hover) .plan[data-i="0"] h3,
.plans:has(.plan[data-i="1"]:hover) .plan[data-i="1"] h3,
.plans:has(.plan[data-i="2"]:hover) .plan h3{ color:var(--bone); }

/* Two staggers at once — across the cards and down each card — which
   resolves into a single diagonal wavefront: every line whose card index
   plus group index is equal fires together. Hence the sum in the calc
   rather than a delay per card and a second one per group. */
.plan{ --ci:0; }
.plan[data-i="1"]{ --ci:1; }
.plan[data-i="2"]{ --ci:2; }
.plan .draw{ --gi:0; transition-delay:calc((var(--ci) + var(--gi)) * var(--draw-step)); }
.plan-g .draw{ --gi:1; }
.plan-g + .plan-g .draw{ --gi:2; }
.plan-g + .plan-g + .plan-g .draw{ --gi:3; }

/* Reduced motion keeps the line, because it is carrying the inclusion
   relationship and is not only decoration — it simply stops travelling
   and is already drawn wherever the cascade puts it. */
@media (prefers-reduced-motion:reduce){
  .draw{ transition:none; }
}

.plan{
  background:var(--void); padding:30px 26px 28px;
  display:flex; flex-direction:column; transition:background .3s;
}
.plan:hover{ background:var(--graphite); }
.plan-top{ display:flex; align-items:baseline; justify-content:space-between; gap:12px; }
.plan h3{
  font-family:var(--display);
  font-variation-settings:"wdth" 114,"wght" 800;
  font-size:22px; text-transform:uppercase; margin:0; letter-spacing:-.02em;
  color:var(--ash); transition:color .28s;
}
.plan .price{
  font-family:var(--mono); font-size:12.5px; color:var(--arc); margin:0;
  font-variant-numeric:tabular-nums;
}
.plan .plan-hr{ margin:10px 0 20px; }
/* The min-height is the one thing here the mock did not have. Its three
   blurbs happened to wrap to the same depth; these do not, and without it
   the group headings sit at three different heights, which is exactly the
   thing the diagonal sweep needs to be regular to read as a diagonal. */
.plan .desc{ color:var(--ash); font-size:13px; margin:0 0 22px; line-height:1.6; min-height:62px; }
.plan-g{ margin-bottom:20px; }
.plan-h{
  font-family:var(--mono); font-size:9.5px; letter-spacing:.18em; text-transform:uppercase;
  color:var(--ash-dim); margin:0 0 10px;
}
/* Chips that wrap, not a stacked list. The capability names have to stay
   short enough to read as tokens — the moment one becomes a sentence the
   row breaks into ragged blocks and the whole thing reads as a bulleted
   list wearing borders. */
.plan ul{ list-style:none; margin:0 0 10px; padding:0; display:flex; flex-wrap:wrap; gap:7px; }
/* The auto margin sits on .seats rather than .go so the seat count and the
   button travel to the floor together. The three lists are different
   lengths, so some card has to absorb the slack somewhere — better it
   opens above the commercial block than inside it. */
.plan .seats{
  font-family:var(--mono); font-size:10px; letter-spacing:.14em; text-transform:uppercase;
  color:var(--ash-dim); margin:auto 0 18px;
}
.plan .go .btn{ width:100%; justify-content:center; }

/* Each plan's button orbits in its own colour: the two UI accents plus
   --ion, which exists because two accents cannot make three peers.

   Hue is the ONLY thing that varies. Same duration, same blur, same
   spread, same alpha — so the row differentiates without ranking, which
   is the whole reason the badge and the gold fill came out. Read the
   order as left-to-right, not low-to-high; nothing here nominates a plan.
   Swapping the three values changes nothing but which card is which.

   The delays are negative so each button starts mid-orbit instead of all
   three waiting at 0deg and then moving off together. Offsets are a third
   and two thirds of --glow-dur, which spaces the spots evenly around the
   set without ever letting two line up. */
/* .glow ships as inline-flex so it hugs a normal button. These span the
   card, so the wrapper has to stretch with them. */
.plan .go.glow{ display:flex; }

/* No --glow-delay: the three orbit in phase. They were offset by a third
   of a cycle each at first, which was livelier but put one spot on a top
   edge while another was at the bottom -- and a reader glances for about a
   second, not for the 3.4s it takes that to average out. In phase, every
   instant of the loop shows three buttons alike in everything but hue.

   It also keeps this out of the cascade's way. The drawn lines stagger to
   mean something, tier inclusion; a second unrelated stagger running under
   them permanently just blurs the one that carries information. */
/* Set on the card, not the button, so the drawn lines and the chip borders
   read the same hue the button orbits in. One plan, one colour. */
.plan[data-i="0"]{ --glow:var(--plasma); }
.plan[data-i="1"]{ --glow:var(--arc);    }
.plan[data-i="2"]{ --glow:var(--ion);    }

/* ===================== CLOSING CTA ===================== */
.close{ text-align:center; padding:clamp(90px,12vw,160px) 0; position:relative; overflow:hidden; }
.close::before{
  content:""; position:absolute; left:50%; top:0; transform:translateX(-50%);
  width:min(680px,90vw); height:1px;
  background:linear-gradient(90deg,transparent,var(--arc),transparent);
  z-index:3;
}
/* the amber hairline above is this section's divider — the .bleed base
   border would just stack a second line right under the enterprise rule */
.close.bleed{ border-top:0; }
.close h2{
  font-family:var(--display);
  font-variation-settings:"wdth" 120,"wght" 800;
  font-size:clamp(34px,6.4vw,80px); line-height:.95; letter-spacing:-.03em;
  text-transform:uppercase; margin:0 auto 22px; max-width:16ch;
}
.close p{ color:var(--ash); font-size:16.5px; max-width:46ch; margin:0 auto 34px; }
/* --ash was picked against flat --void. Over a photograph the darkest the
   scrim gets it is ~33/255, which drops that grey to about 4.3:1 — under
   the AA floor. These two sections sit on photos, so lift the body copy. */
.close.bleed p{ color:#BFC5C9; }
.close .cta{ display:flex; gap:12px; justify-content:center; flex-wrap:wrap; }

/* ===================== FULL-BLEED PHOTO SECTIONS =====================
   Shared base for every section that runs a photograph edge to edge with
   copy sitting on top of it. A scrim element does the darkening rather
   than a filter on the image, so the photo keeps its own contrast and
   only the area under the text gets pushed down. Two scrim variants:
   the default reads left-to-right for left-aligned copy, `-c` closes in
   from all four edges for centred copy. */
.bleed{
  position:relative;
  min-height:clamp(560px,80vh,820px);
  display:flex; align-items:center;
  overflow:hidden;
  border-block:1px solid var(--edge);
  background:var(--void);
}
.bleed-bg{
  position:absolute; inset:0;
  width:100%; height:100%;
  object-fit:cover;
  /* settle the photo into the palette so it reads as part of the site */
  filter:saturate(.9) contrast(1.02);
}
/* left-to-right + bottom scrim so the copy always clears the footage */
.bleed-scrim{
  position:absolute; inset:0; pointer-events:none;
  background:
    linear-gradient(to right, rgba(10,11,12,.94) 0%, rgba(10,11,12,.82) 30%, rgba(10,11,12,.35) 62%, rgba(10,11,12,0) 88%),
    linear-gradient(to top, rgba(10,11,12,.7) 0%, rgba(10,11,12,0) 40%);
}
.bleed-scrim-c{
  position:absolute; inset:0; pointer-events:none;
  background:
    radial-gradient(120% 90% at 50% 50%, rgba(10,11,12,.9) 0%, rgba(10,11,12,.74) 42%, rgba(10,11,12,.3) 78%, rgba(10,11,12,.12) 100%),
    linear-gradient(to bottom, rgba(10,11,12,.8) 0%, rgba(10,11,12,0) 26%, rgba(10,11,12,0) 74%, rgba(10,11,12,.8) 100%);
}
.bleed .shell{ position:relative; z-index:2; }

/* ---------- left-lit bleed band (left-aligned copy) ----------
   Was .onsite-copy, named after the "Generate reports on site" photo band
   it was written for. That band's photo and copy were replaced by the
   pricing reel, and a class called .onsite sitting on a section about
   workflow would have sent the next reader looking for a field-work
   section that no longer exists. The rules themselves never had anything
   to do with being on site — this is the type for copy in the lit corner
   of a .bleed with .bleed-scrim, and nothing more. */
.bleed-copy{ max-width:min(560px,52vw); }
.bleed-copy .eyebrow{ color:var(--arc); }
.bleed-copy h2{
  font-family:var(--display);
  font-variation-settings:"wdth" 118,"wght" 800;
  font-size:clamp(38px,6.4vw,84px);
  line-height:.93; letter-spacing:-.03em;
  text-transform:uppercase; margin:14px 0 20px;
}
.bleed-copy h2 .thin{
  font-variation-settings:"wdth" 118,"wght" 300;
  color:var(--ash); display:block;
}
/* The 30px bottom margin here was the gap to the button row. The buttons
   are gone and this paragraph is now the last thing in the band, so the
   margin would have become trailing space inside a box .bleed centres
   vertically — 30px at the bottom and none at the top offsets the whole
   block upward by half that. */
.bleed-copy > p{
  color:var(--bone); font-size:clamp(15px,1.5vw,17.5px);
  max-width:44ch; margin:0;
}
/* .bleed-copy .cta was here, with the flex row for the two buttons. */

/* ---------- pricing reel band ----------
   The one .bleed on the site whose background is video and whose copy is
   left-aligned. .ent-reel is the other video band, but its copy is centred
   over .bleed-scrim-c, so none of its rules reach here — what carries over
   is the two findings it recorded, both of which apply again.

   Both of these are corrections for footage rather than photography. Video
   is brighter than the graded stills the .bleed rules were written against,
   and it moves, and the eye tracks a moving background under copy far more
   readily than a still one. */

/* .ent-reel's brightness pull, at the same value and for the same reason:
   stacked on .bleed-bg's own saturate(.9) contrast(1.02), it takes the edge
   off the brightest clip so the scrim is not doing all the work alone. */
.reel-band .bleed-bg{ filter:saturate(.86) contrast(1.04) brightness(.92); }

/* --ash is the .thin colour everywhere else and it is wrong over footage —
   it is a mid-grey, and the midtones of a lit building are mid-grey too, so
   the second line of the headline sinks into them. .ent-reel already found
   this and moved to #C3C9CD; this is the same value.

   Measured, not eyeballed. Against the brightest frame of the reel the
   .thin line at --ash came out 3.08:1 at 1440 and 1.25:1 at 560. 84px
   display type only needs 3:1, so the desktop figure was passing on a
   technicality and the mobile one was a plain failure. At #C3C9CD it is
   5.02:1 at 1440 and 7.46:1 at 560 — clearing 4.5:1, which it does not
   even need to. See the mobile scrim note in the 820px block for the other
   half of the fix.

   Worth knowing if this band is ever re-shot: the photograph this reel
   replaced measured 1.52:1 on the same line. The band was already failing
   before the video went in, and the video is what made it measurable. */
.reel-band .bleed-copy h2 .thin{ color:#C3C9CD; }

/* ===================== FEATURE RUN =====================
   Five photographed sections, one per feature, in place of the centred
   masthead that used to list the same five as text pills.

   Shorter than a standalone .bleed. One of those is an event; five in a
   row at 80vh each is four screens of scrolling before the platform
   section, which is the opposite of the point. 62vh still reads as
   full-bleed while keeping the whole run to roughly three screens. */
.spot{ min-height:clamp(440px,62vh,660px); }

/* Consecutive .bleed siblings would stack two 1px --edge borders into a
   visible 2px seam between every pair. The run reads as one continuous
   sequence, so the internal borders come off and only the outer two
   survive: the top of the first and the bottom of the last. */
.spot + .spot{ border-top:0; }

/* Narrower than .bleed-copy. That section is a single showpiece and
   can afford a long line; these are five short beats and want to be
   read at a glance, not settled into. */
.spot-copy{ max-width:min(440px,46vw); }
.spot-copy .eyebrow{ color:var(--arc); }
.spot-copy h2{
  font-family:var(--display);
  font-variation-settings:"wdth" 118,"wght" 800;
  font-size:clamp(32px,4.6vw,58px);
  line-height:.95; letter-spacing:-.03em;
  text-transform:uppercase; margin:14px 0 18px;
}
.spot-copy h2 .thin{
  font-variation-settings:"wdth" 118,"wght" 300;
  color:var(--ash); display:block;
}
.spot-copy > p{
  color:var(--bone); font-size:clamp(14.5px,1.4vw,16.5px);
  line-height:1.6; max-width:38ch; margin:0;
}
/* .spot-copy .cta lived here, along with a .spot-r override that packed it
   against the right edge and a narrow-screen override that unwound it. Only
   #f-forms ever carried one, which made the first frame of the run behave
   differently from the four beneath it. All five are now copy only. */

/* ---------- the mirrored half of the run ----------
   Copy to the right, photograph reading left. Alternating stops five
   identical layouts from turning into wallpaper, and it gives the eye
   a reason to keep going down the page.

   Both halves have to flip together. The scrim is a one-way ramp from
   opaque to clear, so flipping the copy without flipping the scrim
   would park the text on the bright side and grade the empty side. */
.spot-r .spot-copy{ margin-left:auto; text-align:right; }
.spot-r .spot-copy > p{ margin-left:auto; }
/* row-reverse, not justify-content: the eyebrow carries a 22px rule as a
   ::before, which on a right-aligned block should trail the text rather
   than lead it. Reversing the axis moves the rule to the far side and
   flex-start then packs the pair against the right edge, since with the
   axis reversed that IS the start. */
.spot-r .spot-copy .eyebrow{ flex-direction:row-reverse; justify-content:flex-start; }
.spot-r .bleed-scrim{
  background:
    linear-gradient(to left, rgba(10,11,12,.94) 0%, rgba(10,11,12,.82) 30%, rgba(10,11,12,.35) 62%, rgba(10,11,12,0) 88%),
    linear-gradient(to top, rgba(10,11,12,.7) 0%, rgba(10,11,12,0) 40%);
}

/* Narrow screens have no room for a copy column beside the photograph,
   so the scrim goes flat-ish and full-width and the text sits over the
   middle of the frame. The right-hand variant unwinds here too: at this
   width the mirroring reads as inconsistency rather than rhythm. */
@media (max-width:760px){
  .spot-copy{ max-width:none; }
  .spot-r .spot-copy{ margin-left:0; text-align:left; }
  .spot-r .spot-copy > p{ margin-left:0; }
  .spot-r .spot-copy .eyebrow{ flex-direction:row; justify-content:flex-start; }
  .spot .bleed-scrim,
  .spot-r .bleed-scrim{
    background:
      linear-gradient(to top, rgba(10,11,12,.92) 0%, rgba(10,11,12,.72) 45%, rgba(10,11,12,.5) 100%);
  }
}

/* ===================== PUNCHLINE BAND ===================== */
.band{ background:var(--arc); padding:clamp(40px,6vw,72px) 0; text-align:center; }
.band-head{
  font-family:var(--display); font-variation-settings:"wdth" 120,"wght" 800;
  font-size:clamp(26px,4.4vw,48px); line-height:1; letter-spacing:-.03em;
  text-transform:uppercase; color:var(--void); margin:0 0 14px;
}
.band-sub{ color:rgba(10,11,12,.82); font-size:clamp(14px,1.5vw,17px); max-width:60ch; margin:0 auto; font-weight:500; }

/* ===================== CENTERED SECTION HEAD ===================== */
.section-head-c{ text-align:center; max-width:74ch; margin-inline:auto; }
.section-head-c .eyebrow{ justify-content:center; }
.section-head-c p{ max-width:64ch; margin-inline:auto; }
.section-head-c p + p{ margin-top:14px; }
.section-head-c b{ color:var(--bone); font-weight:600; }

/* .thin-note and .thin-note em lived here — the small centred footnote
   under a section head. Both disclaimers that used it (the #interop one
   and the #math one) have been pulled out pending the disclaimer group
   that will sit below the products, so the class is currently on nothing.
   Kept out rather than kept warm: an unused rule is a rule nobody checks
   against the markup. This commit is the recovery point if the grouped
   disclaimers want the same treatment. */

/* ===================== INTEROPERABILITY =====================
   IN and OUT set at display size in outline, with the items packed
   against them. The two halves used to be a pair of matching cards
   side by side, which left the direction of travel to a small mono
   label and an arrow; at this size the word itself is the label and
   there is nothing to misread.

   Outlined rather than filled so a 138px word does not out-shout the
   copy sitting next to it.

   Worth knowing before editing the lists: the two sides are NOT rows
   of pairs. A coordination study feeds both the TCC chart and the
   PowerDB CSV, so nothing here should imply that item three on the
   left becomes item three on the right. */

/* ---------- the two halves have to share one grid ----------
   An `auto` first track sizes to its own content, and IN and OUT are
   nowhere near the same width — 171px against 363px at full size — so
   the two lists would start 192px apart and the whole thing would look
   like a mistake. The track is pinned instead, and pinned as a multiple
   of the type size rather than as its own clamp, so the two stay locked
   at every viewport instead of drifting apart between breakpoints.

   2.72 is the measured width of OUT in ems of font-size (2.627) with a
   little headroom, so the longer of the two words sets the track and the
   shorter one just sits in it. */
.io-flow{
  --io-fs:clamp(58px,11vw,138px);
  --io-track:calc(var(--io-fs) * 2.72);
}
.io-half{ display:grid; grid-template-columns:var(--io-track) 1fr; gap:clamp(20px,4vw,54px); align-items:start; }
.io-half + .io-half{ margin-top:clamp(34px,5vw,64px); padding-top:clamp(34px,5vw,64px); border-top:1px solid var(--edge); }
.io-word{
  font-family:var(--display); font-variation-settings:"wdth" 122,"wght" 800;
  font-size:var(--io-fs); line-height:.82; letter-spacing:-.045em;
  text-transform:uppercase; margin:0; color:transparent;
  white-space:nowrap;
  -webkit-text-stroke:2px var(--arc);

  /* Optical, not metric: `align-items:start` lines up the line BOX, which
     leaves the cap sitting well under the first hairline of the list.
     Pulled up so the flat top of the letters and the rule beside them read
     as one line. In ems so it tracks the clamp.

     Measured against the flat-topped letters — the I and N of IN, the U and
     T of OUT. The O lands about 2px proud of them, which is the round-letter
     overshoot every typeface draws on purpose: a circle stopped dead on the
     cap line reads as short. Correcting it would be the actual error. */
  margin-top:-.059em;

  /* ---------- and the same problem sideways ----------
     The two words are centred on each other, which cannot be done by
     centring their boxes: a glyph's box is its ADVANCE, and the ink
     inside it is not centred. IN is very nearly symmetric, but OUT is
     not — the O carries a fat left bearing while the T's right arm runs
     almost to the edge of its advance — so box-centring leaves OUT
     sitting about 3px right of IN.

     So each word is placed by its measured ink centre instead: shifted
     until the middle of the painted letters lands on the middle of the
     track. Values are in ems, so they ride the clamp and hold at every
     size rather than only at the design width.

     Measuring them was awkward and is worth writing down. Canvas
     TextMetrics gives exact ink bearings but only for the DEFAULT font
     instance — this is Archivo at wdth 122, which renders 28% wider —
     and getBBox() on an SVG <text> returns the advance box, not the ink.
     What the SVG did give was an advance width matching the live DOM to
     0.01px, and two advances at two letter counts is enough to solve for
     the width-axis scale (1.2798) and separate it from the letter
     spacing. Scaling the canvas bearings by that lands the ink centres
     below. If the type size, the wdth axis or the tracking changes, all
     three of these numbers have to be measured again. */
  text-indent:calc(var(--io-track) / 2 - var(--io-ic));
}
.io-half-in{  --io-ic:.6193em; }    /* ink centre of IN,  from its origin */
.io-half-out{ --io-ic:1.3353em; }   /* ink centre of OUT, from its origin */
.io-half-out .io-word{ -webkit-text-stroke-color:var(--plasma); }

.io-word em{
  display:block; font-style:normal; font-family:var(--mono);
  font-size:10.5px; letter-spacing:.2em; margin-top:14px;
  -webkit-text-stroke:0; color:var(--ash-dim);
  /* text-indent inherits into block children and the kicker is one, so it gets
     reset and centred in the track rather than dragged along by an offset that
     was calculated for a 138px outline. The word's ink centre is the middle of
     the track too, so the two end up on the same axis. */
  text-indent:0; text-align:center;
}

.io-list{ display:grid; grid-template-columns:repeat(2,1fr); gap:20px 34px; margin:0; padding:0; list-style:none; }
.io-list li{ padding-top:14px; border-top:1px solid var(--edge); }
.io-list b{ display:block; font-size:15px; font-weight:600; color:var(--bone); }
.io-list span{ display:block; font-size:12.5px; color:var(--ash); margin-top:3px; line-height:1.5; }

/* The .io-flow + .thin-note spacing override sat here. It only ever
   existed to hold the interop disclaimer clear of the two flow halves,
   and that disclaimer has moved out with the rest of them. */

/* Firefox has shipped -webkit-text-stroke for years, but if it ever goes
   away the words would come out invisible, since the fill is transparent. */
@supports not ((-webkit-text-stroke:1px black)){
  .io-word{ color:var(--arc); }
  .io-half-out .io-word{ color:var(--plasma); }
}

/* Stacked, the word no longer has a track beside it to sit in — so the
   track becomes the column. Same arithmetic, same shared axis, just
   measured against the full width instead of a fixed one. Leaving the
   fixed track here would park IN about 50px in from the rule below it
   for no visible reason. */
@media (max-width:760px){
  .io-flow{ --io-track:100%; }
  .io-half{ grid-template-columns:1fr; gap:22px; }
  .io-list{ grid-template-columns:1fr; }
}

/* ===================== THE HONEST MATH =====================
   This used to be a trio of stat tiles, then two four-step compare
   cards, then a three-row table, then a note — and between them they
   carried six facts. The cards narrated the two rows the table then
   counted; the stat tiles quoted a number the table foot quoted again;
   and steps 1 and 2 were printed once per card despite being identical
   in both, so eight lines went on four facts.

   All of it is one chart now. Two totals drawn to a single scale say
   the percentage, the hours and the comparison in one look, which was
   what the stat tiles and the table were separately trying to say. */

/* Minutes, not pixels. Both rails are drawn as a share of one worst case —
   a 200-device job, the top of the dial's range — so the two bars stay
   comparable to each other at every setting, and the drawing cannot
   quietly drift out of step with the totals printed beside it. The scale
   is fixed rather than re-fitted to the current job on purpose: a bar that
   rescales as you drag it shows no change at all. */
.math-bars{ max-width:760px; margin-inline:auto; }
.math-count{
  font-family:var(--mono); font-size:11px; letter-spacing:.16em;
  text-transform:uppercase; color:var(--ash-dim); margin:0 0 26px; text-align:center;
}
.math-count b{
  font-family:var(--display); font-variation-settings:"wdth" 112,"wght" 800;
  font-size:40px; letter-spacing:-.03em; color:var(--bone); margin-right:10px;
  /* The count changes under the reader's own thumb, so the digits have to
     hold their column or the label beside them shuffles as it counts. */
  font-variant-numeric:tabular-nums; vertical-align:-6px;
}
.math-row{ display:grid; grid-template-columns:96px 1fr auto; gap:18px; align-items:center; }
.math-lab{
  font-family:var(--mono); font-size:11px; letter-spacing:.14em; text-transform:uppercase;
  color:var(--ash); margin:0; text-align:right;
}
.math-rail{ height:34px; background:#101315; border:1px solid var(--edge); }
/* No width here on purpose. The two rails are different lengths, and a
   default in the stylesheet would be one of them — which is how the RMS
   bar came out the same length as Today with the script switched off. The
   40-device widths are written inline in the markup instead, next to the
   totals they agree with, and the script overwrites them from there. */
.math-fill{ height:100%; background:var(--steel); }
.math-row-win .math-fill{ background:rgba(255,138,31,.35); border-right:2px solid var(--arc); }
.math-tot{
  font-family:var(--display); font-variation-settings:"wdth" 112,"wght" 780;
  font-size:22px; margin:0; color:var(--ash); min-width:104px;
  font-variant-numeric:tabular-nums;
}
.math-row-win .math-tot{ color:var(--arc); }
/* The breakdown sits beside the bar rather than inside it. Indented to
   clear the label column and start where the rail does. */
.math-aside{
  font-family:var(--mono); font-size:11px; color:var(--ash-dim); margin:8px 0 16px 114px;
}
.math-aside span{ color:var(--ash); }

/* ── the dial ──
   A native range input would have been fewer lines, but it cannot be made
   to match the rails above it without rebuilding the track and the thumb
   anyway, at which point the only thing left of the input is the keyboard
   handling — which is twenty lines in js/math.js and is written there. */
.math-dial{
  position:relative; height:34px; margin:22px 0 0; cursor:ew-resize;
  touch-action:none; outline-offset:4px;
}
.math-dial::before{
  content:""; position:absolute; left:0; right:0; top:16px; height:2px; background:var(--edge);
}
.math-dial-fill{
  position:absolute; left:0; top:16px; height:2px; background:var(--arc-dim); width:17.5258%;
}
.math-knob{
  position:absolute; top:8px; left:17.5258%; width:18px; height:18px; margin-left:-9px;
  background:var(--arc); border-radius:50%; box-shadow:0 0 0 5px rgba(255,138,31,.15);
}
.math-scale{
  display:flex; justify-content:space-between; margin:10px 0 0;
  font-family:var(--mono); font-size:10.5px; letter-spacing:.12em;
  text-transform:uppercase; color:var(--ash-dim);
}
/* The one thing the entrance has to leave behind is the knowledge that the
   thing can be grabbed. It pulses twice once the entrance settles, and goes
   for good the first time it is touched. */
.math-hint{
  margin:14px 0 0; text-align:center;
  font-family:var(--mono); font-size:11px; letter-spacing:.12em; text-transform:uppercase;
  color:var(--ash-dim);
}
.math-hint::before{ content:"↔ "; color:var(--arc); }
.math-bars.armed .math-hint{ opacity:0; }
.math-bars.ready .math-hint{ animation:math-pulse 2.6s ease 2; }
.math-bars.used .math-hint{ opacity:0; transition:opacity .3s ease; animation:none; }
@keyframes math-pulse{ 0%,100%{ opacity:.45 } 50%{ opacity:1 } }

.math-out{
  display:grid; grid-template-columns:1fr 1fr;
  margin:34px 0 0; border-top:1px solid var(--edge);
}
.math-out div{ padding:22px 24px; }
.math-out div + div{ border-left:1px solid var(--edge); }
.math-out b{
  display:block; font-family:var(--display); font-variation-settings:"wdth" 112,"wght" 800;
  font-size:30px; color:var(--bone); letter-spacing:-.02em; font-variant-numeric:tabular-nums;
}
.math-out div + div b{ color:var(--arc); }
.math-out span{ display:block; margin-top:7px; font-size:13.5px; line-height:1.5; color:var(--ash); }
/* .math-fine (the mono footnote that closed the chart) and the .math-note
   set (the 36/36 accuracy panel, in plasma so it read as a separate claim
   from the time argument around it) were both here. The markup for both
   came out of index.html; see the comments at the tail of .math-bars there
   for what each was carrying and where it is meant to go.

   .math-out is now the last thing in the chart, which is why its
   border-top above is the only seam left in the block. */

/* Stacked, the label and the total move off the sides and onto their own
   line so the bar keeps the full column. Sharing the row with them left it
   213px of 335 — and a bar that has given away a third of its width is no
   longer drawn to a scale worth reading. */
@media (max-width:760px){
  .math-row{ grid-template-columns:1fr auto; row-gap:8px; }
  .math-lab{ text-align:left; }
  .math-rail{ grid-column:1 / -1; height:26px; }
  .math-tot{ min-width:0; font-size:19px; }
  .math-aside{ margin-left:0; }
  .math-out{ grid-template-columns:1fr; }
  .math-out div{ padding:20px 0; }
  .math-out div + div{ border-left:0; border-top:1px solid var(--edge); }
}

/* ===================== SYSTEM OF RECORD =====================
   This was six equal cells in a bordered grid — the same shape as every
   other grid on the page, and the most inert thing here given it is the
   section that has to argue records are structured rather than piled up.
   It now pins and scrubs: the hierarchy walks down one level per step,
   the fact beside it changes with the level, and the photograph behind
   both descends with them. The argument is made by the movement.

   Section chrome: the section IS the frame. It runs full bleed, so #record
   drops .section-alt — its graphite banding was only there to keep the page
   alternating, and a photographic band is a stronger break than a shade
   change — and it drops its padding, because the head moved inside the pin
   and there is no longer anything to pad.

   No border either. It used to carry a border-top, because a plain #math
   sat above it and without a seam the two ran together as one field of
   void. #interop sits above it now and .section-alt already draws its own
   bottom border, so keeping one here would only stack a second line on top
   of it. Below, the frame ends on a full-bleed photograph against #math's
   void, which is a harder edge than a border would be. */
.rec-sec{ padding:0; }

/* --- the head, inside the frame ---------------------------------------
   Pinned to the top of the pin rather than scrolling above it, so it labels
   every one of the seven steps instead of leaving before the first. It is
   out of flow, which is what lets the tree and copy row stay centred in the
   pin independently of how tall the title runs.

   Left aligned, not .section-head-c. This section abandoned centring. */
.rec-title{ position:absolute; left:0; right:0; top:clamp(64px,9vh,104px); z-index:2; }
.rec-head{ margin:0; }

/* The display size gets a viewport-height cap on top of the site's usual
   width clamp. On a pinned section the title and the tree/copy row share one
   fixed screen, and at 54px the head runs three lines — around 195px with
   its eyebrow. Measured against the row centred at 50% ± 105px, the two
   collide below roughly 730px of viewport height, which is an ordinary
   13-inch laptop. 5.6vh drops the head to about 40px there, where the title
   breaks to two lines instead of three and the collision disappears. Above
   ~960px tall the cap is inert and the site's normal 54px applies. */
.rec-head h2{ font-size:clamp(30px, min(4.6vw, 5.6vh), 54px); }

/* The thin half of the lockup is --ash everywhere else on the site. Over
   these frames it measures 2.2:1 even inside the corner patch below, which
   fails at any threshold. This is large text, so the bar is 3:1 rather than
   4.5:1, and #BCB7AF clears it at 4.1:1 with room. It reads as clearly
   dimmer than --bone while most of the lockup's contrast comes from the
   weight drop, 760 to 300, which is unmissable at this size. */
.rec-head h2 .thin{ color:#BCB7AF; }

/* The eyebrow inherits --ash from .section-head p, which is a panel colour
   and was correct while this head sat on flat --void above the frame. Inside
   the frame it is the worst-placed text in the section: it rides the top of
   the head, and the head's own corner patch is centred at a fixed 190px, so
   as the viewport shortens and top:clamp(64px,9vh,104px) walks the head
   upward the eyebrow climbs toward the patch's soft edge. At 1440x680 it
   measures 2.31:1 on the bays frame.

   The scrim cannot answer this one. Ten percent of the eyebrow's box on that
   frame is over half brightness and some of it is fully clipped white — bay
   lighting shot straight on — and pulling a dim grey up to 4.5:1 against
   blown white needs the backdrop crushed to near black, which is the exact
   trade the rest of this section is built to refuse.

   So the type moves instead. #C8C3BA is the dimmest value that clears 4.5:1
   across all seven frames at 1440x900, 1440x680, 1280x720, 1920x1080 and
   1024x700, landing at 4.88 on that same worst case. It ends up brighter
   than the title's .thin only because .thin is large text and answers to
   3:1 while this, at 17.5px, answers to 4.5:1. Every colour in this frame
   sits at the dimmest value its own worst case allows; they differ because
   their worst cases and their thresholds do. */
.rec-head .eyebrow{ color:#C8C3BA; }

.rec-head h2, .rec-head .eyebrow{ text-shadow:0 1px 16px rgba(10,11,12,.85), 0 1px 3px rgba(10,11,12,.7); }

/* Seven steps at roughly 43vh of scroll each. The obvious 50vh per step
   would have run to 450vh, which is a lot of page to hand one section;
   400vh still reads as a deliberate beat without outstaying itself. */
.rec-wrap{ height:400vh; position:relative; background:var(--void); }
.rec-pin{ position:sticky; top:0; height:100vh; display:flex; align-items:center; overflow:hidden; }

/* --- the photograph layer ---------------------------------------------
   Seven frames stacked, one visible at a time, cross-faded on the same
   step the tree and the copy move on. The first four are a real descent —
   campus, plant, substation, breaker — so the picture is closing in on
   exactly the thing the tree is pointing at. The last three stop
   descending and widen back out, because the guarantees they carry are
   about custody rather than location.

   Each frame arrives at scale 1.06 and settles to 1 over 1.4s, slower
   than the 0.9s fade. The push therefore outlives the cross-fade, so two
   frames never look like a slideshow swap; the movement carries through
   the cut. Transform and opacity only, both compositor-driven, so the
   scrub stays off the main thread. */
/* Clips the frames' cross-fade. .rec-img rests at scale(1.06) and settles to
   scale(1), so an incoming frame is 6 percent wider than the pin and hangs 3
   percent off each side. Vertically that is invisible — the pin is bounded by
   the sections above and below — but horizontally it pushed the document 24px
   wider than the viewport at 800px and the page scrolled sideways. The scale
   is a transition effect and has no business changing layout width. */
.rec-bg{ position:absolute; inset:0; z-index:0; background:var(--void); overflow:hidden; }
/* Black lift. These frames are night photography, and measured straight
   off the file, 69 to 81 percent of every one of them sits at a luminance
   under 0.005 — most have a median of exactly zero. There is almost no
   picture in them to reveal, so easing the scrim alone brings nothing
   back: multiplying zero stays zero.

   Screening a dark grey over them raises the floor off pure black to
   about 0.006, which is what film blacks actually do — they sit above the
   paper, they are never the absence of signal. The photographs read as
   photographs instead of as seven rectangles of void.

   .rec-bg is positioned with a z-index, so it opens its own stacking
   context and the blend is trapped inside it. It touches the images and
   nothing else — not the scrim above, not the type. */
.rec-bg::after{
  content:""; position:absolute; inset:0; z-index:1;
  background:rgb(32,32,32); mix-blend-mode:screen; pointer-events:none;
}
.rec-img{
  position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
  opacity:0; transform:scale(1.06); will-change:opacity, transform;
  transition:opacity .9s ease, transform 1.4s cubic-bezier(.2,.7,.3,1);
}
.rec-img.on{ opacity:1; transform:scale(1); }

/* Text runs in two columns across the full width here, unlike the feature
   run where copy takes one side and .bleed-scrim grades the other. So
   there is no vertical stripe of frame left for the picture to live in —
   the columns span 144px to 1256px of a 1400px pin. What IS free is
   horizontal: the type only occupies the middle ~24% of the height, so
   this scrim grades top to bottom instead, going nearly opaque through the
   band the text sits in and opening up above and below it.

   The first attempt swept left-to-right like .bleed-scrim and thinned to
   .58 around the 62% mark, which is exactly where the right-hand copy
   column sits. On the substation frame that put a sodium pole light
   directly behind body copy and dropped local contrast to 2.5:1.
   Deepening that stripe did not rescue it — even at .78 it only reached
   3.8:1, and by then the photograph was gone anyway. Banding it
   horizontally clears 4.5:1 on all seven frames AND keeps more of the
   picture, because the highlights worth having are in the sky and the
   foreground, not behind the paragraph.

   The band stops are in pixels off the centre line, not percentages. The
   type is a fixed height — the copy block is 210px and the tree 197px,
   both vertically centred — so as a share of the pin it grows on short
   viewports and shrinks on tall ones. Percentage stops that bracketed it
   at 900px tall let it slide out into the feather at 680px. Pixel stops
   track it at every height, and as a bonus the open windows get bigger on
   tall screens rather than staying a fixed proportion. 130px clears the
   tallest block with room; 250px is where the feather begins.

   The alphas are much lower than they look like they should be. An earlier
   pass ran .90 through the band against .30 across, which measured
   beautifully for contrast and left between 0.3 and 5.8 percent of the
   frame visible — the photographs were effectively not there. These values
   put 25 to 41 percent above the visibility floor and still clear 4.5:1,
   because the body colour came up to match. See .rec-step p:last-child.

   The 90px vignette at each end is a seam, not a legibility device. The
   lift above raises pure black to around rgb(40,41,42), and this frame
   runs full bleed between neighbours painted in --void, so without it the
   frame would meet them as a visible grey band across the page. It closes
   only the last 90px, well clear of the open windows.

   The middle layer is the head's own patch. The head could not use the band
   above — it sits at the top of the frame, which is deliberately the most
   open part of the scrim, and measured there the title lands between 1.1:1
   and 1.5:1 on bays, export and office. Nor could the band simply grow to
   swallow it: title plus row is a ~440px block, and a plateau that tall
   closes both picture windows outright at anything under a 1000px viewport.

   So the head gets a local ellipse instead of a full-width stripe. The head
   is a fixed 555px box in the top-left, roughly 9 percent of the frame, and
   darkening that corner costs far less than darkening a band across the
   whole width. Measured across all seven frames at 1440x900, 1440x680,
   1280x720, 1920x1080 and 1024x700, it takes the worst-case title contrast
   from 1.18 to between 6.58 and 7.24 against --bone, while frame visibility
   only falls from 29-42 percent to 18-31.

   Pixel radii, and the centre is calc(50% - 340px) rather than a percentage,
   because the head is a fixed-width box: the shell gutter is (100% - 1240)/2,
   so gutter + 278 is exactly 50% - 340 at every width the shell is not
   clamped at. Percentages would drift off the head as the viewport widened. */
.rec-scrim{
  position:absolute; inset:0; z-index:1; pointer-events:none;
  background:
    linear-gradient(to bottom,
      rgba(10,11,12,.74) 0,
      rgba(10,11,12,.10) 90px,
      rgba(10,11,12,.08) calc(50% - 250px),
      rgba(10,11,12,.62) calc(50% - 130px),
      rgba(10,11,12,.62) calc(50% + 130px),
      rgba(10,11,12,.08) calc(50% + 250px),
      rgba(10,11,12,.10) calc(100% - 90px),
      rgba(10,11,12,.80) 100%),
    radial-gradient(900px 320px at calc(50% - 340px) 190px,
      rgba(10,11,12,.66) 0%,
      rgba(10,11,12,.66) 55%,
      rgba(10,11,12,0) 100%),
    linear-gradient(to right,
      rgba(10,11,12,.24) 0%,
      rgba(10,11,12,.07) 40%,
      rgba(10,11,12,.07) 70%,
      rgba(10,11,12,.24) 100%);
}

.rec-in{ position:relative; z-index:2; display:grid; grid-template-columns:1fr 1fr; gap:clamp(28px,5vw,70px); width:100%; align-items:center; }
.rec-tree{ list-style:none; margin:0; padding:0; }
.rec-tree li{
  font-family:var(--mono); font-size:clamp(13px,1.5vw,17px);
  padding:11px 0; color:var(--ash-dim);
  opacity:.28; transform:translateX(-6px);
  transition:opacity .45s ease, transform .45s cubic-bezier(.2,.7,.3,1), color .45s ease;
  padding-left:calc(var(--d) * 26px + 16px);
  position:relative;
}
.rec-tree li[data-d="0"]{ --d:0; } .rec-tree li[data-d="1"]{ --d:1; }
.rec-tree li[data-d="2"]{ --d:2; } .rec-tree li[data-d="3"]{ --d:3; }
.rec-tree li.on{ opacity:1; transform:none; color:var(--bone); }
.rec-tree li.on::before{
  content:""; position:absolute; left:calc(var(--d) * 26px); top:50%; width:9px; height:1px;
  background:var(--arc);
}
/* This caption wants to be a dim grey and cannot be one. --ash-dim is a panel
   colour and disappears over a photograph outright; --ash, the obvious step
   up, still only reaches 1.9:1 on the brightest frame. At 11px it is small
   text and owes 4.5:1, and measured over all seven frames at 1440x900,
   1440x680, 1280x720, 1920x1080 and 1024x700 nothing below this value clears
   that bar — the floor is the office frame at 1920x1080, where --ash lands at
   1.9, the title's #BCB7AF at 3.5 and this at 4.64. That figure is taken over
   the whole tree box rather than the caption runs alone, so the real margin
   is wider than it looks.

   It is the same trade the body copy below makes, for the same reason: the
   scrim is deliberately open here so the photograph survives, which is only
   affordable if the type on it is bright. So the caption steps down from
   --bone by 16 points rather than the 90-odd a grey would want, and leans on
   size and tracking to carry the hierarchy instead of colour. */
.rec-tree li small{ color:#DCD7CE; font-size:11px; letter-spacing:.12em; text-transform:uppercase; margin-left:10px; }

/* Fixed height so the tree beside it does not shift as steps of different
   lengths swap through. 210px clears the longest of the seven. */
.rec-copy{ min-height:210px; }
.rec-step{ display:none; }
.rec-step.on{ display:block; animation:recin .5s cubic-bezier(.2,.7,.3,1) both; }
@keyframes recin{ from{ opacity:0; transform:translateY(12px); } to{ opacity:1; transform:none; } }
.rec-step p.k{ font-family:var(--mono); font-size:10.5px; letter-spacing:.2em; text-transform:uppercase; color:var(--arc); margin:0 0 12px; }
.rec-step h4{
  font-family:var(--display); font-variation-settings:"wdth" 108,"wght" 760;
  font-size:clamp(22px,2.8vw,34px); margin:0 0 12px; letter-spacing:-.02em; text-transform:uppercase;
}
/* Body sits at --bone here rather than the --ash this site uses everywhere
   else, because everywhere else it is on a flat panel. Measured against the
   lit parts of these frames --ash lands at 1.7:1, which is unreadable;
   --bone is 5.2:1 on the same pixels. Letting the scrim stay light enough
   for the photograph to survive is only affordable if the type is bright
   enough to sit on it, so this trade is the whole reason the images read at
   all. The shadow is a second line of defence over the few blown
   highlights, and does nothing on the dark majority of the frame. */
.rec-step p:last-child{
  color:var(--bone); font-size:15px; line-height:1.6; margin:0; max-width:42ch;
  text-shadow:0 1px 14px rgba(10,11,12,.9), 0 1px 3px rgba(10,11,12,.75);
}
.rec-step h4, .rec-tree li.on{ text-shadow:0 1px 16px rgba(10,11,12,.85), 0 1px 3px rgba(10,11,12,.7); }
.rec-prog{ display:flex; gap:5px; margin-top:28px; }
.rec-prog i{ height:2px; width:38px; background:var(--edge); transition:background .35s ease; }
.rec-prog i.on{ background:var(--arc); }

/* Narrow. The section still pins and still scrubs — only the two columns
   inside the pin go away.

   This block used to unpin the whole thing and print all seven steps as a
   list, which turned the one section on the page built around a descent into
   a wall of text with a still photograph behind it. The reason was never the
   interaction; it was that 1fr 1fr has nowhere to go on a phone. So the
   columns stack and the pin stays. */
@media (max-width:880px){
  /* Shorter track. The scrub advances a step per (height - 100vh) / 7, so
     400vh means roughly 43vh of thumb per step — a lot of flicking on a
     device where a swipe is most of a screen. 300vh puts a step at about
     28vh, which lands two to a swipe and keeps all seven inside a gesture
     or three. */
  .rec-wrap{ height:300vh; }

  /* dvh, not vh, and this is the one thing that actually breaks a pin on a
     phone. Mobile browsers report 100vh as the viewport with the address bar
     HIDDEN, so a pin sized in vh is taller than what you can see for as long
     as the bar is up, and the bottom of the frame — the progress bar — sits
     under it. dvh tracks the visible height, so the frame is always exactly
     the screen, and it agrees with the window.innerHeight the scrub does its
     arithmetic against. vh stays first as the fallback. */
  .rec-pin{ height:100vh; height:100dvh; }

  /* The head comes out of absolute positioning and joins the column, so the
     group centres as one and the copy cannot slide under the title on a
     short screen. Nothing here depends on a viewport height that fits. */
  .rec-pin{ flex-direction:column; justify-content:center; gap:clamp(18px,3.5vh,38px); }
  .rec-title{ position:relative; top:auto; }
  .rec-head{ margin:0; }
  .rec-head h2{ font-size:clamp(25px,6.6vw,40px); }

  .rec-in{ grid-template-columns:1fr; gap:clamp(16px,3vh,30px); }
  /* Tighter rows and a shallower indent per level: the desktop 26px step
     puts the fourth level 78px in, which is a fifth of a 390px screen spent
     on indentation. */
  .rec-tree li{ font-size:clamp(12px,3.3vw,15px); padding:7px 0;
                padding-left:calc(var(--d) * 17px + 14px); }
  /* The 210px floor is there to stop the desktop column jumping as steps of
     different lengths swap through it. In a stacked layout that floor is
     just dead space above the progress bar, and the steps are close enough
     in height that the movement it prevents is a few pixels. */
  .rec-copy{ min-height:0; }
  .rec-step h4{ font-size:clamp(19px,5vw,27px); }
  .rec-prog{ margin-top:20px; }
  /* Seven fixed 38px segments plus gaps is 296px, which overflows a 360px
     screen once the shell gutter is taken off. They divide the width now. */
  .rec-prog i{ width:auto; flex:1; }

  /* The desktop scrim aims a dark band at the middle of the frame, where a
     single centred row of type sits, and a radial at the head's position in
     the left column. Stacked, the type runs nearly the full height of the
     pin and the head is centred, so both of those are pointed at nothing.
     This is the same band widened to the column: .62 in the middle is the
     value the desktop band already uses under this type, kept rather than
     re-guessed, and the ends go darker because that is where the frame meets
     the sections above and below. */
  .rec-scrim{
    background:
      linear-gradient(to bottom,
        rgba(10,11,12,.84) 0,
        rgba(10,11,12,.62) 10%,
        rgba(10,11,12,.62) 90%,
        rgba(10,11,12,.88) 100%),
      linear-gradient(to right,
        rgba(10,11,12,.30) 0%,
        rgba(10,11,12,.12) 45%,
        rgba(10,11,12,.30) 100%);
  }
}

/* The Lead Scanner block (.leads, .lead, .leads-how and the pill badge)
   lived here. It went with the #leads section — four cards splitting one
   sentence #f-leadscan already carries over its photograph. It was also
   the only section on the page running its own #070809 background, which
   is why the page now alternates cleanly from #record straight through
   to #pricing. */

/* ===================== PRICING EXTRAS ===================== */
/* .price-bar and the .usage panels were both here, and both were the same
   mistake in different shapes: a summary of the cards, printed next to the
   cards. The bar named Pre-Job Analyzer, Database and Lead Scanner, all of
   which are chips a few pixels below it, and .price-bar b.arc set only
   Premium's entry in accent — the same thumb on the scale as the badge and
   the gold button. The one axis neither the cards nor the intro stated per
   tier was how much AI work each carries; that is the third .plan-g now. */

.price-fine{ text-align:center; color:var(--ash-dim); font-family:var(--mono); font-size:11px; letter-spacing:.06em; margin:22px 0 0; }

/* The TOP-UP block (.topup, .topup-card, .tc-price, .topup-math, .tm-row)
   was here. It was the last thing on the page still arguing a tier: the
   .topup-math panel walked the reader from "$24 + top-ups" to a
   .tm-row b.arc highlighting Premium as the better buy — the badge and
   the gold button in table form. The mechanic it explained survives in
   the section intro and in legal/terms.html. */

/* ===================== ENTERPRISE (corporations) ===================== */
.ent{ background:#050608; }
.ent-rule{ height:2px; background:linear-gradient(90deg, var(--arc-dim), var(--arc), var(--arc-dim)); }
/* .ent-head also carries .bleed, so it gets the photo + scrim treatment.
   The min-height there is for standalone bleed sections; here the copy
   sets the height, so relax it back down. */
.ent-head{ background:#050608; padding:clamp(64px,9vw,110px) 0; text-align:center; }
.ent-head.bleed{ min-height:0; }
/* .ent-badge — the "Enterprise · Custom engagement" pill — was here. It
   came out of enterprise/index.html for saying a fourth time what the nav
   link, the URL and the first line of the paragraph had already said. */
.ent-head h2{
  font-family:var(--display); font-variation-settings:"wdth" 118,"wght" 800;
  font-size:clamp(36px,6vw,72px); line-height:.95; letter-spacing:-.03em;
  text-transform:uppercase; margin:0 0 22px;
}
.ent-head .thin{ font-variation-settings:"wdth" 118,"wght" 300; color:var(--ash); display:block; }
.ent-head > .shell > p{ color:var(--ash); font-size:clamp(15px,1.6vw,18px); max-width:60ch; margin:0 auto; line-height:1.6; }
.ent-aud{ margin-top:16px !important; color:var(--ash-dim) !important; font-size:13px !important; }
/* same photo-contrast lift as .close.bleed p — see note there */
.ent-head.bleed > .shell > p{ color:#BFC5C9; }
.ent-head.bleed > .shell > p.ent-aud{ color:#8E979D !important; }

/* ---- Closing block --------------------------------------------------
   .ent-head grew a CTA when it moved to the bottom of the page and
   absorbed .ent-cta. Everything below is the difference between "opening
   claim" and "closing claim with a button on it", which is why it is a
   modifier rather than an edit to .ent-head — the pricing page still
   renders .ent-cta on its own, and .ent-head's base rules still describe
   a block of centred copy over a photograph.

   The base rule sets `margin:0 auto` on every child paragraph, which is
   correct when there are two of them and wrong once there are four with a
   button in the middle. Spacing is therefore set per-role here rather than
   by loosening the base rule and re-tightening it upstream. */
.ent-close{ padding-block:clamp(72px,10vw,124px); }

/* --ash-dim is #4C5459. On the flat #050608 panel .ent-cta sits on that is
   a legible grey; over the substation photograph it falls into the haze in
   the upper third of the frame and the line stops being readable. Same
   reason .ent-head.bleed lifts its paragraphs to #BFC5C9. */
.ent-close .ent-cta-kick{ color:#8E979D; margin-bottom:18px; }

.ent-close .btn{ margin-top:30px; }

/* ---- Field reel -----------------------------------------------------
   Six generated clips cut into one 45s silent montage, ordered as a
   descent in scale: Earth from orbit at night, a city intersection, a
   building going up, a substation, a panel interior, and finally one man
   writing on a clipboard. Each shot is closer than the last, so the edit
   travels from the whole planet down to a single sheet of paper.

   That ladder replaces the old rule, which was that no two neighbours may
   share both camera distance and colour temperature — clips matching on
   both start to read as one long take, the one thing a montage must not
   do. Scale descent is the stronger organiser and it happens to satisfy
   the old rule everywhere except the first cut, where orbit and city are
   both wide and both cool. That one is safe on its own: the jump from
   400km to street level is far too large to read as a continuous take.

   0.6s dissolves, not hard cuts. The clips are eight seconds each and
   generated, so they have no natural in or out point to cut on; a hard
   cut exposes that, a dissolve absorbs it.
   It opens the page. It used to sit between the claim and the pillars, and
   the argument for that spot was that a reader wants one beat of the actual
   world before a wall of nouns. Putting it first makes the same argument
   harder: the page now shows the work before it describes any of it.

   NOT the home page's scrollytell. That is a canvas blitted frame by frame
   off scroll position, because it has to run backwards as cleanly as
   forwards. This just plays. There is no scroll coupling to get wrong, so
   there is no reason to carry 150 decoded stills and a scrub loop to show
   it — <video> is the cheap and correct element here.

   Reuses .bleed exactly as the photographs do, including .bleed-scrim-c.
   The scrim arrived with the headline and does two jobs at once: it keeps
   the copy legible, and it is also the answer to the footage reading too
   bright. Those are the same problem. A photograph on this site is one
   exposure and can be graded once; this is five clips shot under five
   different lights, and no single filter() settles all of them — the
   substation dusk goes muddy long before the daytime timelapse stops
   glaring. A scrim is exposure-independent: it puts the same darkness over
   every frame regardless of what that frame was lit by. */
/* No min-height override — this runs at the full .bleed height, 560–820px,
   the same as every photographic band on the site. Two earlier passes held
   it shorter on the theory that a wordless block would compete with the
   hero above it at full size. There is no longer a hero above it to compete
   with; this is the hero. */

/* The `.ent-head + .ent-reel{ border-top:0 }` seam fix was here. Two .bleed
   siblings stacked their 1px --edge borders into a visible 2px line, the
   same case .spot + .spot answers on the home page. They are no longer
   siblings — the reel opens the page and .ent-head closes it, with the
   pillars and outcomes in between — so the seam cannot occur. */

.ent-reel{ text-align:center; }

.ent-reel .bleed-bg{
  /* .bleed-bg's own grade is saturate(.9) contrast(1.02); this adds a
     brightness pull on top. Deliberately gentler than it was before the
     scrim went in — the two stack, and at the old .8 with a scrim over it
     the dusk clips lost their shadow detail entirely and the reel went
     flat. The filter now only takes the edge off the daylight shots; the
     scrim does the actual darkening. */
  filter:saturate(.86) contrast(1.04) brightness(.92);
}

/* A shade heavier than the stock .bleed-scrim-c. That one is tuned for a
   still photograph, and this is moving: the eye tracks motion under copy
   far more readily than it tracks a static background, so the headline
   needs more separation here than the same words would need over a photo.
   Same shape as the original, just deeper through the middle. */
.ent-reel .bleed-scrim-c{
  background:
    radial-gradient(120% 90% at 50% 50%, rgba(5,6,8,.86) 0%, rgba(5,6,8,.68) 42%, rgba(5,6,8,.3) 78%, rgba(5,6,8,.14) 100%),
    linear-gradient(to bottom, rgba(5,6,8,.74) 0%, rgba(5,6,8,0) 28%, rgba(5,6,8,0) 72%, rgba(5,6,8,.74) 100%);
}

/* Smaller than .ent-head h2 on purpose. That is the page's title and this
   is a caption on a picture; at the same size they would read as two
   competing claims stacked a screen apart, which is the one thing the
   band was not supposed to add. */
.ent-reel h2{
  font-family:var(--display); font-variation-settings:"wdth" 118,"wght" 800;
  font-size:clamp(26px,4.2vw,50px); line-height:1.02; letter-spacing:-.03em;
  text-transform:uppercase; margin:0; color:var(--bone);
}
/* Lighter than the --ash used for .thin elsewhere. Over footage rather
   than over a flat panel, --ash sits too close to the midtones behind it
   and the second line starts to disappear against the brighter clips. */
.ent-reel h2 .thin{
  font-variation-settings:"wdth" 118,"wght" 300;
  color:#C3C9CD; display:block;
}

/* Motion off → js/reel.js never calls play(), so the element sits on its
   poster frame. Nothing to do in CSS: hiding the video would take the
   poster with it, and the poster is the whole fallback. */

.ent-pillars{ background:#080A0F; padding:clamp(56px,7vw,80px) 0; }
.ent-kick{ font-family:var(--mono); font-size:11px; letter-spacing:.22em; text-transform:uppercase; color:var(--ash-dim); text-align:center; margin:0 0 clamp(32px,4vw,48px); }

/* ===================== ENTERPRISE PILLARS =====================
   Six services as three phases, not six items in a grid.

   The unit is the ROW: two pillars that belong to the same phase, with
   the phase named in the gutter between them and a rail running the full
   height of the row behind that name. The pair is held to its phase by a
   line, which is the whole argument of the layout — these are stages of
   one engagement, not a menu.

   Colour is attached to the PHASE, not the pillar. Three hues, and the
   mix strengths climb across them (see .ph-0/1/2 below) so the section
   warms as it moves down the page: cool at deploy, warm at operate.

   TWO THINGS TO KNOW BEFORE EDITING.

   1. An undefined custom property inside color-mix() — or inside any
      function — invalidates the whole declaration, silently. A frame that
      renders colourless is the hardest defect in this file to see. So
      every block re-establishes --g, and every var() read carries a
      fallback. Keep it that way.

   2. The activation state is written as SWITCHES, not as a repeated
      selector list. A pillar can be lit by four different things now
      (pointing at it, focusing it, pointing at its phase rail, and the
      entrance cascade). Repeating that list on the eleven rules
      downstream would be unreadable and would need another pass the next
      time a trigger is added.

        --sx --sy   edge scales      --ec  edge colour
        --pc        copy colour
        --d1..--d3  the draw stagger
   ============================================================== */
.pil{ --p1:var(--plasma); --p2:var(--ion); --p3:var(--arc);
  /* the colour behind the section, so a phase label can knock a hole in
     its own rail. It is #080A0F because .ent-pillars is. */
  --bg:#080A0F; }
.ph-0{ --glow:var(--p1); --tint:15%; --tintl:32%; --tinth:22%; }
.ph-1{ --glow:var(--p2); --tint:22%; --tintl:46%; --tinth:32%; }
.ph-2{ --glow:var(--p3); --tint:30%; --tintl:62%; --tinth:44%; }

/* ---------- the frame ----------
   Four 1px children, each scaled down at rest and out to full on
   activation. They are children rather than a border because a border
   cannot be drawn one edge at a time. */
.fr{ position:relative; }
.fr > i{ position:absolute; pointer-events:none;
  background:color-mix(in srgb, var(--g) var(--tinth, 32%), var(--edge));
  transition:transform .3s cubic-bezier(.4,0,.2,1), background-color .3s;
  transition-delay:0s; }
.fr > i.t{ top:0;    left:0;  width:100%; height:1px; }
.fr > i.r{ top:0;    right:0; width:1px;  height:100%; }
.fr > i.b{ bottom:0; right:0; width:100%; height:1px; }
.fr > i.l{ bottom:0; left:0;  width:1px;  height:100%; }
.fr:focus-visible{ outline:0; }
/* The numeral that used to sit beside the heading is gone; the wrapper is
   kept because the spacing below the heading belongs to it, not to h3. */
.fr-h{ margin:0 0 8px; }
.fr-h h3{ font-family:var(--display); font-variation-settings:"wdth" 104,"wght" 700;
  font-size:16px; letter-spacing:-.01em; color:var(--bone); margin:0; line-height:1.2; }
.fr p{ font-size:12.5px; line-height:1.6; color:var(--ash-dim); margin:0;
  transition:color .28s; }
.lab b{ display:block; font-family:var(--mono); font-size:10.5px; letter-spacing:.2em;
  text-transform:uppercase; font-weight:400;
  color:color-mix(in srgb, var(--g) var(--tintl, 46%), var(--ash)); }

/* ---------- the row ---------- */
.af-r{ --g:var(--glow, var(--arc));
  display:grid; grid-template-columns:minmax(0,1fr) 132px minmax(0,1fr); align-items:stretch; }
.af-r + .af-r{ margin-top:12px; }

/* The rail runs the FULL row height and the label sits on it, knocking a
   hole in the line — one continuous stem interrupted by its own name. An
   earlier version stacked label over rail in a two-row grid, which left
   the rail about 20px and read as three stubs down the page instead of
   three stems, which is the entire idea. */
.af-mid{ position:relative; display:flex; align-items:center; justify-content:center;
  /* the pointer target is the whole gutter, not the line — a 1px target
     would be unhittable */
  cursor:default; }
/* z-index because the rail is a positioned sibling that comes LATER in
   the markup: without it the line paints straight through the label's
   background and across the word. */
.af-mid .lab{ position:relative; z-index:1; text-align:center; padding:9px 7px;
  background:var(--bg, #080A0F); }
.af-rail{ position:absolute; left:50%; top:0; bottom:0; width:1px; margin-left:-.5px;
  background:color-mix(in srgb, var(--g) var(--tint, 22%), var(--edge)); }
.af-rail i{ position:absolute; inset:0; background:var(--g); transform:scaleY(0);
  transform-origin:top; transition:transform .4s cubic-bezier(.4,0,.2,1); }
/* the stem lights for the whole phase, whichever trigger reached it */
.af-r:hover .af-rail i, .af-r:focus-within .af-rail i,
.af-r.lit .af-rail i{ transform:scaleY(1); }

.af-i{ padding:20px 24px 22px; transition:background-color .28s; }

/* ---------- the switches ---------- */
.af-i:hover, .af-i:focus-visible,
.af-r.lit .af-i{
  --sx:1; --sy:1; --ec:var(--g); --pc:var(--ash);
  --d1:.08s; --d2:.16s; --d3:.24s;
  background:var(--graphite); }
/* The rail trigger, and it is a SEPARATE RULE on purpose. :has() is what
   makes it work at all — .af-a comes BEFORE .af-mid in the markup, so no
   sibling combinator can reach back to it — but an unknown selector
   invalidates the entire list it sits in. Folded into the rule above, one
   browser without :has() would take plain hover down with it. Alone, it
   fails to nothing and the pillars still answer the pointer. */
.af-r:has(.af-mid:hover) .af-i{
  --sx:1; --sy:1; --ec:var(--g); --pc:var(--ash);
  --d1:.08s; --d2:.16s; --d3:.24s;
  background:var(--graphite); }

.af-i > i{ background:var(--ec, color-mix(in srgb, var(--g) var(--tinth, 32%), var(--edge))); }
.af-i p{ color:var(--pc, var(--ash-dim)); }

/* The stagger lives on the SWITCH, not on the edge, so it applies on the
   way in and is 0s on the way out: the four edges retract together
   rather than running the circuit backwards. */
.af-a > i.l, .af-b > i.r{ transition-delay:var(--d1, 0s); }
.af-i > i.b            { transition-delay:var(--d2, 0s); }
.af-a > i.r, .af-b > i.l{ transition-delay:var(--d3, 0s); }

/* The LEFT pillar opens leftward: its resting stub sits at the top-right,
   against the rail, and the circuit runs anticlockwise back to it. */
.af-a > i.t{ transform:scaleX(var(--sx, .22)); transform-origin:right; }
.af-a > i.b{ transform:scaleX(var(--sx, .22)); transform-origin:left; }
.af-a > i.l{ transform:scaleY(var(--sy, 0));   transform-origin:top; }
.af-a > i.r{ transform:scaleY(var(--sy, 0));   transform-origin:bottom; }
/* The RIGHT pillar mirrors it: stub at the top-left, clockwise. The two
   stubs meet across the gutter and the two circuits open outward from
   the rail. */
.af-b > i.t{ transform:scaleX(var(--sx, .22)); transform-origin:left; }
.af-b > i.b{ transform:scaleX(var(--sx, .22)); transform-origin:right; }
.af-b > i.r{ transform:scaleY(var(--sy, 0));   transform-origin:top; }
.af-b > i.l{ transform:scaleY(var(--sy, 0));   transform-origin:bottom; }

/* Reduced motion keeps the mechanic and drops the movement: the frame
   still answers the pointer, it just arrives rather than draws. */
@media (prefers-reduced-motion:reduce){
  .fr > i, .af-rail i, .af-i{ transition:none !important; }
  .af-a > i, .af-b > i{ transition-delay:0s !important; }
}

/* Below the gutter breakpoint the three columns cannot hold: 132px of
   label between two cards leaves each about 250px. So the row unstacks
   into one column, the phase label becomes a left-aligned header for the
   pair it used to sit between, and the rail — which only ever existed to
   tie a horizontal pair together — is dropped rather than rotated. The
   frames stay, and so does every trigger except the rail's. */
@media (max-width:900px){
  .af-r{ grid-template-columns:minmax(0,1fr); gap:10px; }
  .af-r + .af-r{ margin-top:26px; }
  /* order, not markup: the label sits BETWEEN the pair in source because
     that is where it belongs on the ring layout. Stacked, "between" means
     "after the first card", which reads as a caption on 01 rather than a
     header for the pair — so it is pulled to the top of the row here. It
     is a presentational reorder of a heading over content it already
     labels, so the visual and reading orders still agree. */
  .af-mid{ order:-1; display:block; }
  .af-mid .lab{ padding:0; text-align:left; background:none; }
  .af-rail{ display:none; }
  .af-i{ padding:18px 20px 20px; }
}

/* ================ ENTERPRISE OUTCOMES — OW, THE PRODUCT SHOT ================
   The deliverables, led by a picture of the thing being delivered.

   HISTORY, because it explains the shape. This section has been redrawn
   several times and every attempt before this one tried to solve it with
   colour: a legend plus a list (OM), then six tinted panes (OT). OT fixed
   OM's measured faults — it put hue on an area at rest, it anchored every
   item on --bone — and it still did not land. The note it drew was that
   the section felt EMPTY, and that six coloured rectangles were not going
   to fill it however they were tinted.

   That was the right read, and the cause was not the treatment. The site
   sells a piece of software and, before this commit, did not contain one
   picture of it. Every image on it was a photograph of switchgear. So the
   fix here is not a fifth palette, it is the product itself: a screenshot
   of the running app, at native scale, and the deliverables demoted to a
   list beneath it.

   WHY IT DOES NOT LOOK LIKE THE PILLARS. The brief on this section has
   been, from the start, that it must not repeat the section above it. The
   pillars are six equal tiles on a 1px --edge grid that light up and draw
   a rule on hover. So this has no tiles, no grid gaps, and nothing that
   moves: one image, then three columns divided by vertical hairlines. The
   pillars' cascade is untouched and still the only hover mechanic in the
   run.

   ON THE IMAGE SCALE. app-devices.webp is the app captured at a 1100px
   viewport on a 2x display — 2200 device px standing for 1100 CSS px. The
   shell is 1240 wide less a 64px gutter each side, so the frame lands at
   1112 CSS px and the screenshot renders at essentially 1:1. That is the
   whole reason for the odd capture width: shoot it at the width it will
   be shown at, and the app's 13px type stays 13px type instead of turning
   into grey texture. If the shell ever changes width, re-shoot; do not
   scale.

   The second frame is a detail — one breaker's insulation-resistance
   record — and it is deliberately small and overlapping. It is not there
   to be read. It is there so the first frame reads as one screen of many
   rather than as the whole product.

   COLOUR. Kept from OT so the classification survives the redesign:
   plasma for what is deployed, ion for what is written, arc for what
   continues. It is now carried by a 6px marker and a tinted rule rather
   than by a wash, because the image is the loud thing on this section and
   two loud things is one too many. --bone still anchors every item, which
   was the one part of OT that measured right.

   On --ion: site.css calls it "glow only — nothing else should reach for
   it". That comment predates the code. .plan[data-i="2"] gives --ion to a
   pricing card's chip borders and drawn rule, and .ph-1 gives it to a
   whole pillar phase, so both shipped designs already use it as a flat
   accent. Following the shipped precedent; if the rule is meant to hold,
   the fix belongs in those two places, not here. */

.ent-outcomes{ background:#050608; border-top:1px solid rgba(255,255,255,.05); padding:clamp(56px,7vw,80px) 0; }

/* --- the product frame ------------------------------------------------
   The overhang on the detail frame is negative bottom, so .ow-fig carries
   the clearance for it in its own margin rather than letting it collide
   with the columns below. */
.ow-fig{ position:relative; margin:0 0 clamp(56px,6vw,78px); }

/* The bloom. Two soft radials sitting behind the frame and escaping it on
   every side, so the screenshot is lit rather than pasted onto the black.
   These are rgba literals, not color-mix into transparent: an alpha ramp
   is exactly what a gradient stop wants, and hard-coding the two token
   values here means a stop can never silently invalidate the whole
   declaration the way an undefined custom property inside color-mix()
   does. --plasma is #4ADBEE, --ion is #9C7CF4.

   THE GEOMETRY IS NOT FREE-HAND. Each ellipse has to finish inside this
   box or the box's own edge becomes the edge of the glow, and a glow with
   a straight edge reads as a grey rectangle — which is exactly what the
   first pass shipped. The rule is: centre ± (size × last-stop) must stay
   within 0-100% on both axes. At 38% x 35% with the last stop at 68% the
   reach is 25.8% across and 23.8% down, so the centres sit at 26%/24% and
   74%/76% and both ellipses close with about 0.2% to spare. Move a centre
   and the size has to move with it. */
.ow-fig::before{
  content:""; position:absolute; z-index:0; pointer-events:none;
  left:-14%; right:-14%; top:-18%; bottom:-22%;
  background:
    radial-gradient(38% 35% at 26% 24%, rgba(74,219,238,.20), rgba(74,219,238,0) 68%),
    radial-gradient(38% 35% at 74% 76%, rgba(156,124,244,.17), rgba(156,124,244,0) 68%);
}

.ow-fr{ position:relative; z-index:1; border:1px solid var(--edge);
  background:var(--graphite); overflow:hidden;
  /* the inset hairline is the light catching the top edge of a panel; the
     drop is what lifts it off #050608, which is flatter than --void */
  box-shadow:inset 0 1px 0 rgba(255,255,255,.055), 0 34px 70px -34px rgba(0,0,0,.95); }
.ow-fr img{ width:100%; height:auto; }

/* The detail frame. Held inside the shell's gutter — at the widest
   viewport the gutter is 64px and this reaches 14px into it, so it never
   crosses the page edge. Below 900px it is dropped outright rather than
   shrunk: at that width it would be a thumbnail of a data table, which is
   worse than no second frame. */
.ow-fr-b{ position:absolute; right:-14px; bottom:-30px; width:38%; z-index:2;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.06), 0 26px 54px -18px rgba(0,0,0,1); }

/* Left-aligned, and capped at 54%, because the detail frame occupies the
   right 38% plus its overhang. Centred it would run under it.

   --ash-dim was the first choice, to match .ent-kick above it. Measured
   against #050608 it lands at 2.6:1 and the line effectively disappeared;
   the kicker gets away with it because it is a label the reader skips,
   and this is a caption that says what they are looking at. --ash clears
   4.5:1. */
.ow-cap{ margin:22px 0 0; max-width:54%;
  font-family:var(--mono); font-size:10.5px; line-height:1.7; letter-spacing:.12em;
  text-transform:uppercase; color:var(--ash); }

/* --- the deliverables -------------------------------------------------
   Three columns, divided by vertical hairlines rather than boxed. The
   divider is a border on the second and third group, so it never doubles
   and never leaves a rule hanging past the last column. */
.ow-k{ display:grid; grid-template-columns:repeat(3, minmax(0,1fr));
  border-top:1px solid var(--edge); }

/* Scoped to .ow-k deliberately. A bare [data-p] selector would set --g on
   any element anywhere in the document carrying that attribute. */
.ow-k [data-p="0"]{ --g:var(--plasma, #4ADBEE); }  /* deployed   — live software  */
.ow-k [data-p="1"]{ --g:var(--ion, #9C7CF4); }     /* documented — written work   */
.ow-k [data-p="2"]{ --g:var(--arc, #FF8A1F); }     /* retained   — continues after */

.ow-g{ padding:26px 0 0; }
.ow-g + .ow-g{ border-left:1px solid var(--edge); padding-left:clamp(20px,2.6vw,34px); }
.ow-g:not(:last-child){ padding-right:clamp(20px,2.6vw,34px); }

/* Mixed into --ash, which is opaque, not into transparent — see the note
   on the pillars. At 62% this computes openly coloured at rest. */
.ow-gk{ display:flex; align-items:center; gap:10px; margin:0 0 16px;
  font-family:var(--mono); font-size:9.5px; font-weight:400;
  letter-spacing:.18em; text-transform:uppercase;
  color:color-mix(in srgb, var(--g, var(--arc)) 62%, var(--ash)); }
.ow-gk::after{ content:""; flex:1; height:1px;
  background:linear-gradient(to right,
    color-mix(in srgb, var(--g, var(--arc)) 45%, var(--edge)), var(--edge)); }

.ow-l{ list-style:none; margin:0; padding:0; display:grid; gap:12px; }
.ow-l li{ position:relative; padding-left:17px;
  font-size:14.5px; line-height:1.45; color:var(--bone); }
.ow-l li::before{ content:""; position:absolute; left:0; top:.6em;
  width:6px; height:6px; background:var(--g, var(--arc)); }

.ent-cta{ background:#050608; border-top:1px solid rgba(255,138,31,.12); padding:clamp(64px,9vw,110px) 0; text-align:center; }
.ent-cta-kick{ font-family:var(--mono); font-size:10.5px; letter-spacing:.2em; text-transform:uppercase; color:var(--ash-dim); margin:0 0 22px; }
.ent-cta h3{ font-family:var(--display); font-variation-settings:"wdth" 110,"wght" 780; font-size:clamp(22px,3.2vw,34px); line-height:1.05; margin:0 auto 16px; max-width:20ch; }
.ent-cta > .shell > p{ color:var(--ash); font-size:15px; max-width:52ch; margin:0 auto 30px; line-height:1.6; }

/* ===================== FOOTER LEGAL ===================== */
.foot-legal{ margin-top:44px; padding-top:28px; border-top:1px solid var(--edge); display:grid; gap:12px; }
.foot-legal p{ margin:0; font-size:11.5px; line-height:1.7; color:var(--ash-dim); max-width:none; }
.foot-legal b{ color:var(--ash); font-weight:600; }

/* ===================== RESPONSIVE ===================== */
@media (max-width:1040px){
  /* Below the shell's full width the screenshot is being scaled down, so
     the detail frame — already the smaller of the two — is the first
     thing to go illegible. Pull it in and shorten the caption's cap to
     match the space it leaves. */
  .ow-fr-b{ width:42%; right:-8px; bottom:-24px; }
  .ow-cap{ max-width:50%; }
}
@media (max-width:820px){
  .scrollytell{ height:400vh; }
  .rail{ display:none; }
  .stages{ padding-left:var(--gut); align-content:end; padding-bottom:96px; }
  .stage{ max-width:none; }
  .pin::after{
    background:
      linear-gradient(to top, rgba(10,11,12,.96) 8%, rgba(10,11,12,.3) 52%, rgba(10,11,12,.6) 100%);
  }
  .plans{ grid-template-columns:1fr; }
  .plan .desc{ min-height:0; }
  .bleed-copy{ max-width:none; }
  /* narrow viewports have no room for a side-lit scrim — go bottom-up */
  .bleed-scrim{
    background:linear-gradient(to top, rgba(10,11,12,.95) 12%, rgba(10,11,12,.55) 55%, rgba(10,11,12,.35) 100%);
  }
  .bleed-scrim-c{
    background:linear-gradient(to top, rgba(10,11,12,.92) 0%, rgba(10,11,12,.78) 50%, rgba(10,11,12,.86) 100%);
  }

  /* The bottom-up scrim above assumes the footage behind it is a photograph
     with no hard highlights near the middle of the band. It is weakest at
     55% — and at this width the copy block sits at roughly 35–65%, straight
     through the weak zone. On the .spot photos that is survivable. On the
     pricing reel it is not: the first clip runs hard specular light strips
     across exactly that band, and the .thin half of the headline (--ash, a
     mid-grey by design) measured 1.25:1 against them. That is not a near
     miss, it is unreadable.

     So this band gets its own. Held near .84 across the whole middle
     instead of dipping, which is the same strength .bleed-scrim-c already
     uses on the enterprise page, and brings the .thin line back over the
     3:1 that 84px display type needs. The video still reads as light and
     movement behind it — a background reel at this width is texture, and
     the headline outranks it.

     Scoped to .reel-band and not folded into .bleed-scrim on purpose: that
     rule is shared with the five .spot sections on the home page, and this
     is a fault in one piece of footage, not in the scrim.

     Held flatter than the stock gradient rather than deeper — a plateau at
     .82 across 22–74%, which is the span the copy block occupies at this
     width. Not dipping through the middle is the whole change; the peak
     value barely moved.

     The plateau is set by the eyebrow, not by the headline. Once .thin went
     to #C3C9CD it stopped being the weak element — the binding constraint
     became .eyebrow, which is --arc at 11px, so it needs 4.5:1 where 84px
     display type needs 3:1. It sits at the top of the copy block, which
     under a bottom-up gradient is the darkest part of the copy but the
     weakest part of the scrim, and clip 1 puts a specular light strip
     across exactly there. At the earlier .78/.72 ramp it measured 3.86:1.
     Flat .82 brings it to 5.29:1 and takes .thin to 7.46:1.

     Worst-case numbers throughout: sampled per glyph pixel against the
     brightest frame of each clip, not against an average of the band. */
  .reel-band .bleed-scrim{
    background:linear-gradient(to top,
      rgba(10,11,12,.9) 0%, rgba(10,11,12,.82) 22%,
      rgba(10,11,12,.82) 74%, rgba(10,11,12,.52) 100%);
  }
}
@media (max-width:900px){
  /* The detail frame is dropped, not shrunk. Everything the desktop rules
     set has to be undone explicitly: the figure's margin was sized for an
     overhang that no longer exists, the caption's cap was sized for a
     frame that is no longer beside it, and the column divider was a LEFT
     border with paddings on both sides acting as a gutter. */
  .ow-fr-b{ display:none; }
  .ow-fig{ margin-bottom:clamp(34px,5vw,46px); }
  .ow-cap{ max-width:none; }

  .ow-k{ grid-template-columns:1fr; }
  .ow-g{ padding:22px 0 2px; }
  .ow-g + .ow-g{ border-left:0; border-top:1px solid var(--edge); padding-left:0; }
  .ow-g:not(:last-child){ padding-right:0; padding-bottom:22px; }
}
@media (max-width:560px){
  .ow-cap{ font-size:10px; letter-spacing:.1em; }
}

/* motion off → static hero, no pin, no scrub */
@media (prefers-reduced-motion:reduce){
  .scrollytell{ height:auto; }
  .pin{ position:relative; height:auto; min-height:92vh; }
  #reel{ display:none; }
  .reel-load{ display:none; }
  .reel-fallback{ display:block; }
  .stages{ position:relative; inset:auto; min-height:92vh; }
  .stage{ opacity:1; transform:none; position:relative; }
  .stage:not(:first-child){ display:none; }
  .hint{ display:none; }
  .rail li.on .dot{ transform:none; }

  /* The dial itself stays live — it is a control, not an animation, and a
     reader who has asked for less motion has not asked to be locked out of
     it. Only the entrance and the pulse that advertises it are dropped. */
  .math-hint{ animation:none; }

  /* #record degrades to a finished, static state. Unpinning stacks the run
     exactly as the narrow breakpoint does, so the scrim has the same
     problem and takes the same flat wash. The frames stop cross-fading
     with the scrub that drove them — the first one stays up as a still
     backdrop behind the whole run. */
  .rec-wrap{ height:auto; padding:clamp(56px,10vw,84px) 0 clamp(84px,11vw,150px); }
  .rec-pin{ position:static; height:auto; display:block; }
  .rec-title{ position:static; }
  .rec-head{ margin-bottom:clamp(30px,4vw,52px); }
  .rec-head h2{ font-size:clamp(30px,4.6vw,54px); }
  .rec-step{ display:block; margin-bottom:24px; animation:none; }
  .rec-tree{ margin-bottom:30px; }
  .rec-tree li{ opacity:1; transform:none; color:var(--bone); }
  .rec-scrim{ background:rgba(10,11,12,.80); }
  .rec-prog{ display:none; }
  .rec-img{ transition:none; transform:none; }

  /* Outcomes has no entry here on purpose. The previous design needed one
     because it moved — a rule drew across each pane on hover. This one is
     a still image and three static columns; there is nothing to turn off,
     so turning nothing off is the correct degraded state. */
}
