/* ==========================================================================
   Service page — the v2 shape
   Loaded by managed-it-services.html, wiring-cabling.html and
   backup-disaster-recovery.html.

   The filename is historical: this was the v2 candidate's stylesheet, and on
   2026-09-24 PK chose that shape for every service page. It is now simply the
   service-page stylesheet. Renaming it would touch four files for no gain.

   The shape, chosen because four of the five pages were running one spine and
   one colour rhythm and the service pages needed a silhouette of their own:

     compact hero + breadcrumb → scope on a panel overlapping the hero →
     one dark signature section → friction → proof + FAQ on one ground → form

   Backup & Disaster Recovery varies it deliberately (scope flat rather than
   overlapping, its own devices), which is the point: the shape is a starting
   position, not a template to fill.
   ========================================================================== */

/* ------------------------------------------------------- compact hero -- */
/* Hero size signals depth. The cinematic hero belongs to top-level pages —
   home, Why PAconnect, AI, the four category pages. Service pages sit a level
   down, so theirs is compact.

   The modifier resets the hero's own custom properties. A value set on the
   element wins over the :root breakpoint overrides in styles.css, so the
   compact hero has to restate its values at each breakpoint itself. */

.hero--compact {
  --hero-pad: 52px 24px 136px;   /* bottom room for the scope panel to overlap */
  --hero-size: 50px;
  --hero-lead: 17px;
  --hero-measure: 62%;
}
.hero--compact .hero__art { opacity: .4; }
.hero--compact .hero__copy { gap: 18px; }

/* Breadcrumb in place of the eyebrow: Home › Services › this page.

   The category step (Run / Protect / Connect / Advance) came out on
   2026-09-24, when PK decided against category pages for launch — four pages
   that would each route to two services out of four is thin, and the menu
   already teaches the framework, in colour, on every page. The categories stay
   exactly as they are in the menu and the footer; they simply are not a step
   in the path any more.

   "Services" is still text rather than a link. It becomes a link if the single
   Services overview page is ever built. Only pages that exist are links: a
   crumb that goes nowhere is the same broken promise as a dead menu head. */
.crumbs {
  margin: 0; padding: 0; list-style: none;
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  font-size: 13px; font-weight: 600; letter-spacing: .02em;
  color: rgba(255, 255, 255, .64);
}
.crumbs li { display: flex; align-items: center; gap: 6px; }
.crumbs li + li::before { content: "\203A"; color: var(--gold-500); }
.crumbs a { color: rgba(255, 255, 255, .86); text-decoration: none; }
.crumbs a:hover { color: var(--green-400); text-decoration: underline; }
.crumbs [aria-current="page"] { color: var(--green-400); }

/* -------------------------------------------------- compact + motion -- */
/* A compact hero carrying a loop but no photo cards (Managed IT v2,
   2026-09-12). The plain compact hero runs its art at 40%, which was tuned
   for abstract texture sitting directly behind the headline. Artwork built to
   the current direction is composed and *brightness-measured* against 65%
   instead — its subject sits right of centre, clear of the copy — so showing
   it at 40% would undo the lighting work. Both layers match, so the crossfade
   cannot shift.

   This is the halfway state: when a page gains its photo cards it becomes
   .hero--layered, which already implies 65%, and this modifier comes off.

   The `:not(.has-video)` is load-bearing, not tidiness. hero-video.css fades
   the still out with `.hero.has-video .hero__art`, which carries the same
   specificity as this rule — and service-v2.css loads later, so without the
   guard this rule wins, the still stays at 65% UNDER the video at 65%, and
   the hero shows two offset copies of the artwork: doubled grid planes and a
   blur that looks like a bad encode. (Found on Managed IT v2, 2026-09-12.)
   The layered hero avoids this by accident — its selector is one class
   lighter, so it loses to has-video cleanly. */
.hero--compact.hero--motion:not(.has-video) .hero__art { opacity: .8; }
.hero--compact.hero--motion.has-video .hero__video { opacity: .8; }

/* The scrim, re-cut for artwork that has a subject in it (PK, 2026-09-12:
   "muddy and diluted… we lose almost all of the gold hue on the right").
   Three faults, all measured on rack-strata:

   1. THE SCRIM IS GREEN. `rgba(7, 27, 11)` over a gold wash pulls the red
      channel down and leaves green standing: the artwork's (61, 50, 18) —
      red leading, so it reads gold — arrived on the page as (29, 33, 12),
      green leading. That is not dimming, it is a hue flip, and gold is the
      one colour in the palette a green scrim destroys. So this one is
      neutral ink.
   2. TOO LITTLE ART SURVIVED. 65% art under a 28% scrim left 47% of the
      artwork at the right edge. At 80% under 12% it is 70%, and the gold
      samples back at (46, 38, 15) — red leading again.
   3. THE FALL-OFF STARTED TOO EARLY. The old ramp was already halfway open
      by 46%, which washed the middle without protecting anything. This one
      holds near-black across the left 38%, where the headline actually sits,
      then opens late and fast — darker where it matters, clearer where it
      does not.

   The left edge ends up *darker* than before, not lighter: .8 × .03 = 2.4%
   of the artwork against the old .65 × .05 = 3.25%.

   Scoped to .hero--motion deliberately. The homepage, Why and AI heroes were
   all measured against the shared scrim, and their artwork is texture rather
   than subject — it has nothing to lose to a heavy scrim. Roll this out per
   hero, re-measuring each.

   Lightened again to "half" on 2026-09-12, PK's pick from the scrim test in
   prototypes/managed-it-hero-scrim/ — which also proved that removing the
   scrim entirely is not the answer: at zero scrim the gold STILL renders
   green-led, so the hue deficit is in the plate, not the treatment, while the
   worst text drops to 4.46 (and 3.66 at full art). This keeps far more of the
   artwork than the values above did and still measures 6.49 at its worst. */
.hero--motion .hero__scrim {
  background: linear-gradient(90deg,
    rgba(10, 12, 10, .72) 0%,
    rgba(10, 12, 10, .62) 38%,
    rgba(10, 12, 10, .34) 72%,
    rgba(10, 12, 10, .06) 100%);
}
/* A stray line of prose in the comment above swallowed that rule from
   2026-09-12 until 2026-09-17, so desktop heroes with a loop were running the
   default GREEN scrim the whole time — the one this block exists to replace.
   Once it applied, Managed IT's lead measured 4.65 at its worst frame: a long
   lead runs out over the brighter strata. The lead's white goes from .82 to
   .9 on these heroes to restore the margin. */
.hero--motion .hero__lead { color: rgba(255, 255, 255, .9); }

/* Below 1025px the compact hero releases its measure and the copy runs the
   full width — straight out over the bright right side of the art, where a
   horizontal scrim is at its thinnest. Measured at 375 with the desktop
   values: green accent 3.51, lead 4.17, both under the 4.5 small text needs,
   while desktop sat at 7.27. The gradient's whole premise — text on the left,
   artwork on the right — stops being true at this width, so the scrim stops
   being a gradient and becomes close to a flat dim. */
@media (max-width: 1024px) {
  .hero--motion .hero__scrim {
    background: linear-gradient(90deg,
      rgba(10, 12, 10, .9) 0%,
      rgba(10, 12, 10, .87) 50%,
      rgba(10, 12, 10, .8) 100%);
  }
}

/* ------------------------------------------------------- layered hero -- */
/* The compact hero with real photo cards set ABOVE the scrim, instead of
   photos baked into the background image. Promoted 2026-09-11 from
   prototypes/wiring-hero-demo/, whose README records why: baked-in photos came
   through at ~17% brightness under v2's 40% art and scrim, were cropped
   differently at every width, and vanished on a phone.

   The background is abstract only, so it can serve a whole category (every
   Connect page, say) while each service page swaps in its own photos. The
   cards are fixed-name slots, per the art direction.

   Needs hero-video.css loaded BEFORE this file, so the .65 video weight below
   wins over its .62. */

/* 65%, measured before adoption (demo README): with the people out of the
   image the cables are pure mood and can carry more light than v2's 40%,
   which was tuned for texture directly behind the headline. Still and video
   match, so the crossfade cannot shift. hero-video.css drops the still to 0
   once the video plays; that rule outranks this one, as it should. */
.hero--layered .hero__art { opacity: .65; }
.hero--layered.has-video .hero__video { opacity: .65; }

/* The copy keeps the left column; the cards take the right. The grid column
   sets the copy's width here, so the compact hero's 62% measure is released. */
.hero--layered .hero__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: start;
  column-gap: 48px;
}
.hero--layered .hero__copy { max-width: none; }

/* Cards sit above the scrim because .hero__inner is positioned and comes after
   it in the markup. That is the whole point — the scrim darkens the
   background art, never the people.

   Default: a row of two, which is what tablets and phones get. The
   overlapped desktop layout is in the min-width block below. */
.hero__cards {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  width: min(100%, 560px);
}
.hero__card {
  margin: 0;
  aspect-ratio: 4 / 3;
  border-radius: 14px;
  overflow: hidden;
  box-shadow: 0 24px 50px rgba(0, 0, 0, .45);
  /* A faint light edge separates a dark photo from a dark hero. */
  outline: 1px solid rgba(255, 255, 255, .16);
  outline-offset: -1px;
  /* The cards fade in after the background has had a beat to itself (PK,
     2026-09-12, from prototypes/hero-cards-demo/). Opacity only, no rise:
     the artwork behind them is already moving, and the delay buys a reading
     order — headline, lead, then the proof photos arriving.

     Two constraints, if these numbers are ever revisited. It deliberately
     does NOT wait for the video: that attaches after load, in idle time, and
     never arrives at all on phones, reduced motion or data-saver, so gating
     on it would leave those visitors staring at an empty frame. And 450ms is
     near the ceiling — these images are the largest thing in the hero, and
     LCP stops when they become visible, so a later fade makes a fast page
     measure slow. */
  animation: paFade 620ms var(--ease) both;
  animation-delay: 450ms;
}
.hero__card--b { animation-delay: 570ms; }
/* Card C only exists on .hero--strip. It keeps the same rhythm as A and B. */
.hero__card--c { animation-delay: 690ms; }
.hero__card img {
  display: block;
  width: 100%; height: 100%;
  object-fit: cover;
}
/* Focal points: card A's faces sit in the top third; card B's vans and the
   office sign sit right of centre in the wide original. */
.hero__card--a img { object-position: 50% 30%; }
.hero__card--b img { object-position: 80% 50%; }
/* Access control (PK, 2026-11-02): the technician stands at the left of a
   wide frame, so card A's square crop keeps him; the fob and reader sit
   left of center in card B. */
.hero--access .hero__card--a img { object-position: 22% 40%; }
.hero--access .hero__card--b img { object-position: 40% 50%; }

/* ------------------------------------------------- the strip variant --
   Three wide, shallow cards along the bottom of the hero instead of the
   overlapped pair, so the artwork's subject stays visible above them.

   When to reach for it (PK, 2026-09-12): look at the hero still and ask
   whether it has a strong subject sitting right of centre. Overlapped is the
   default and stays the default; the strip is for the stills that would lose
   their subject underneath it. `data-field`, on Backup & Disaster Recovery,
   is the first — its cube, ring and padlock run from 52% across to the right
   edge and the document icons fan across the upper right, exactly where the
   overlapped pair sits. Promoted from prototypes/hero-cards-demo/.

   Three things this costs, all measured at 1440 on 2026-09-12.

   It takes THREE photos rather than two, and at 212 × 133 each one is about a
   third smaller than the overlapped card A — so it wants photos whose subject
   survives a wide shallow crop, not tight portraits.

   And it makes the hero TALLER: 725px against the overlapped hero's 579px.
   The cards and the copy share one grid row, so this row's 660px width leaves
   the copy a 444px measure instead of 643px, the headline and lead wrap onto
   more lines, and the hero grows 146px. That is a quarter taller, and more
   than halfway to the full-width "below the copy" layout this was meant to be
   the cheap alternative to. Re-measure on a page whose hero copy is much
   shorter or longer than the low voltage page's. */
.hero--strip .hero__cards { grid-template-columns: repeat(3, 1fr); }
.hero--strip .hero__card { aspect-ratio: 16 / 10; }

/* Overlapped, desktop only (PK, 2026-09-11). The people get the larger
   square; the fleet sits smaller and landscape over its lower corner. The
   people are the proof, so they get the size. */
@media (min-width: 1025px) {
  .hero__cards {
    grid-template-columns: minmax(0, 1fr);
    width: clamp(360px, 32vw, 470px);
    margin-top: 6px;
  }
  .hero__card { grid-area: 1 / 1; }
  .hero__card--a { width: 72%; aspect-ratio: 1 / 1; justify-self: start; align-self: start; }
  .hero__card--b {
    width: 58%;
    justify-self: end; align-self: end;
    margin-top: 40%;
    box-shadow: 0 28px 60px rgba(0, 0, 0, .55);
  }
}

/* The strip, desktop only — it has to undo the overlap above, so it comes
   after it and repeats the media query rather than nesting inside it.
   `align-self: end` drops the row to the bottom of the hero, which works
   because the copy is the taller of the two grid items. The cards land
   roughly 53–77% down the frame; the scope panel covers what is below. */
@media (min-width: 1025px) {
  .hero--strip .hero__cards {
    width: clamp(420px, 46vw, 660px);
    margin-top: 0;
    align-self: end;
  }
  .hero--strip .hero__card {
    grid-area: auto;
    width: auto;
    margin-top: 0;
    justify-self: stretch;
    align-self: end;
  }
  /* Three shallow cards carry less shadow than one large square, or the row
     reads as a bar rather than as three photographs. */
  .hero--strip .hero__card--b { box-shadow: 0 24px 50px rgba(0, 0, 0, .45); }
}

/* -------------------------------------------------------------- scope -- */
/* Scope comes first: someone arriving from a search wants "do they do this?"
   answered before anything else. The panel is pulled up over the hero, so
   content crosses the seam between the two bands — which breaks the stacked,
   horizontal blockiness better than any border could. */

.scope {
  position: relative;
  background: var(--ink-50);
  padding: 0 24px 88px;
}
.scope__panel {
  position: relative;
  max-width: var(--shell);
  margin: -92px auto 0;
  background: var(--white);
  border-top: 3px solid var(--gold-500);
  border-radius: 14px;
  box-shadow: 0 24px 60px rgba(10, 12, 10, .16);
  padding: 44px 48px 40px;
  display: grid; gap: 28px;
}
.scope__list {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: 32px;
}
.scope__item {
  display: flex; align-items: flex-start; gap: 14px;
  padding: 22px 0;
  border-top: 1px solid var(--ink-200);
}
.scope__icon {
  flex: none;
  width: 42px; height: 42px;
  border-radius: 10px;
  background: var(--green-50);
  color: var(--green-700);
  display: flex; align-items: center; justify-content: center;
}
/* The service noun, above the outcome. The h3 carries what the client gets;
   this keeps the searchable term ("Vendor management", "Fiber") on the page
   and scannable, so leading with outcomes costs nothing in SEO or in
   answering "do they do this?". */
.scope__kind {
  display: block;
  margin-bottom: 5px;
  font-size: 12px; font-weight: 700; line-height: 1.2;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-600);
}
.scope__item h3 {
  margin: 0 0 6px;
  font-family: 'Oswald', sans-serif;
  font-weight: 600; font-size: 18px; line-height: 1.2;
  text-transform: uppercase; letter-spacing: .01em;
  color: var(--ink-900);
}
.scope__item p { margin: 0; font-size: 15px; line-height: 1.6; color: var(--ink-600); }

/* ------------------------------------------------------------- stakes -- */
/* The "why change" band (added 2026-09-11). Prose on the left, three points
   on the right — the service-page rule is that no section is paragraphs
   alone, and the points also stop this reading as an essay on a page people
   scan. Light ground, because the page keeps exactly one dark band. */

.stakes { background: var(--white); }
.stakes__inner {
  max-width: var(--shell); margin: 0 auto;
  padding: var(--section-pad);
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 56px; align-items: start;
}
.stakes__copy p { margin: 18px 0 0; font-size: 17px; line-height: 1.75; color: var(--ink-700); }
.stakes__points { list-style: none; margin: 0; padding: 0; display: grid; gap: 14px; }
.stakes__points li {
  padding: 20px 22px;
  background: var(--ink-50);
  border-left: 3px solid var(--green-600);
  border-radius: 0 10px 10px 0;
}
.stakes__points h3 {
  margin: 0 0 6px;
  font-family: 'Oswald', sans-serif;
  font-weight: 600; font-size: 18px; line-height: 1.2;
  text-transform: uppercase; letter-spacing: .01em;
  color: var(--ink-900);
}
.stakes__points p { margin: 0; font-size: 15px; line-height: 1.65; color: var(--ink-600); }

/* ----------------------------------------------------------- timeline -- */
/* The signature section — the one thing this service shows that no other page
   does, and the page's one dark band below the hero (v1 has two). Doubles as the
   answer the hero's "first 90 days" button points at. */

.timeline {
  position: relative;
  background: var(--ink-950);
  color: var(--white);
  /* Lands the section's top edge — its green seam — exactly under the sticky
     header when the hero's "first 90 days" button is followed. Measured, not
     guessed: the header is 105px from 820px up and 109px below, switching at
     the 819px nav collapse. KEEP IN STEP with that breakpoint (styles.css). */
  scroll-margin-top: 105px;
}
/* The seam: the footer's own green edge, reused top and bottom so the dark
   band meets its light neighbours on an on-brand line rather than a bare cut.
   The bottom edge is mirrored so the gradient runs the other way. Shared by
   every v2 signature band — the timeline here, the before/after and the test
   restore below. */
.timeline::before,
.timeline::after,
.ba::before,
.ba::after,
.restore::before,
.restore::after {
  content: "";
  position: absolute; left: 0; right: 0;
  height: 4px;
  background: linear-gradient(90deg, var(--ink-950) 0%, var(--green-700) 60%, var(--green-500) 100%);
}
.timeline::before, .ba::before, .restore::before { top: 0; }
.timeline::after, .ba::after, .restore::after { bottom: 0; transform: scaleX(-1); }

.timeline__inner {
  max-width: var(--shell); margin: 0 auto;
  padding: var(--section-pad);
  display: grid; gap: 48px;
}
.timeline__steps {
  list-style: none; margin: 0; padding: 0;
  position: relative;
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 36px;
}
/* The line tying the stages together, running behind the markers. */
.timeline__steps::before {
  content: "";
  position: absolute; top: 22px; left: 23px; right: 23px;
  height: 2px;
  background: linear-gradient(90deg, var(--gold-500), rgba(240, 168, 48, .18));
}
.timeline__step { position: relative; display: flex; flex-direction: column; gap: 12px; }
.timeline__marker {
  position: relative;
  width: 46px; height: 46px;
  border-radius: 50%;
  background: var(--ink-950);
  border: 2px solid var(--gold-500);
  color: var(--gold-400);
  font-family: 'Oswald', sans-serif; font-size: 18px; font-weight: 600;
  display: flex; align-items: center; justify-content: center;
}
.timeline__label {
  font-size: 12px; font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--green-400);
}
.timeline__step h3 {
  margin: 0;
  font-family: 'Oswald', sans-serif;
  font-weight: 600; font-size: 26px; line-height: 1.1;
  text-transform: uppercase; letter-spacing: .01em;
  color: var(--white);
}
.timeline__step p { margin: 0; font-size: 15px; line-height: 1.7; color: var(--ink-300); max-width: 36ch; }

/* ── the takeover timeline, animated ───────────────────────────────────────
   managed-it-services.html. PK picked this on 2026-10-17 from
   prototypes/timeline-animated/.

   THE STRONGEST CASE OF THE THREE ANIMATED DEVICES, because the subject of
   this one IS order — "It sounds disruptive, and the order is what stops it
   being." The motion is the argument rather than decoration. The drive is
   about time running out; the map is about arrangement; this is about
   sequence. A device without that kind of claim should stay static.

   ⚠ THE HOLDS ARE THE WHOLE POINT. The line is a single pseudo-element, so
   the staging lives in the keyframe: draw to the midpoint, HOLD while marker
   two lands, then continue to three. Flatten tDraw into a straight 0->100%
   and it becomes one continuous slide that says nothing about stages.

   ⚠ REVEAL.JS WAS CHANGED FOR THIS. Its entry used to stagger
   .timeline__step at 70ms apart, which fought a ~1000ms-per-stage beat. It
   now puts .is-in on the CONTAINER (items: null) and the hidden state moved
   from the step to .timeline__steps. The three must stay in step: reveal.js
   GROUPS, the hidden-state list above, and the rules below.

   Line is 3px, up from 2 — a hairline is hard to see DRAWING; it needs weight
   to read as a path rather than a divider. The fade starts at 82% instead of
   immediately, so the line is solid through stage three and only trails off
   past it: the process continuing, rather than the line running out.

   THE RESTING STATE IS THE FINISHED SECTION, so a reduced-motion visitor —
   who never gets .js-reveal, so never gets .is-in — sees it as built. */

.timeline__steps { --t-base: 220ms; }

.timeline__steps::before {
  height: 3px;
  background: linear-gradient(90deg,
    var(--gold-500) 0%, var(--gold-500) 82%, rgba(240, 168, 48, .18) 100%);
}

.timeline__steps.is-in::before {
  animation: tDraw 2000ms linear calc(var(--t-base) + 500ms) backwards;
  transform-origin: left center;
}
.timeline__steps.is-in .timeline__step:nth-child(1) .timeline__marker { animation: tPop 420ms cubic-bezier(.2,1.5,.4,1) calc(var(--t-base) + 0ms)    backwards; }
.timeline__steps.is-in .timeline__step:nth-child(2) .timeline__marker { animation: tPop 420ms cubic-bezier(.2,1.5,.4,1) calc(var(--t-base) + 1000ms) backwards; }
.timeline__steps.is-in .timeline__step:nth-child(3) .timeline__marker { animation: tPop 420ms cubic-bezier(.2,1.5,.4,1) calc(var(--t-base) + 2000ms) backwards; }

/* One beat per STAGE, not per element — label, heading and paragraph move
   together. Separately they would be nine events and it turns fussy. */
.timeline__steps.is-in .timeline__step:nth-child(1) .timeline__label,
.timeline__steps.is-in .timeline__step:nth-child(1) h3,
.timeline__steps.is-in .timeline__step:nth-child(1) p { animation: tRise 420ms ease-out calc(var(--t-base) + 250ms)  backwards; }
.timeline__steps.is-in .timeline__step:nth-child(2) .timeline__label,
.timeline__steps.is-in .timeline__step:nth-child(2) h3,
.timeline__steps.is-in .timeline__step:nth-child(2) p { animation: tRise 420ms ease-out calc(var(--t-base) + 1250ms) backwards; }
.timeline__steps.is-in .timeline__step:nth-child(3) .timeline__label,
.timeline__steps.is-in .timeline__step:nth-child(3) h3,
.timeline__steps.is-in .timeline__step:nth-child(3) p { animation: tRise 420ms ease-out calc(var(--t-base) + 2250ms) backwards; }

@keyframes tDraw {
    0% { transform: scaleX(0);  }
   25% { transform: scaleX(.5); }
   50% { transform: scaleX(.5); }   /* the hold: marker two lands here */
   75% { transform: scaleX(1);  }
  100% { transform: scaleX(1);  }
}
@keyframes tPop  { from { transform: scale(0); opacity: 0; } to { transform: scale(1); opacity: 1; } }
@keyframes tRise { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } }

@media (prefers-reduced-motion: reduce) {
  .timeline__steps.is-in::before,
  .timeline__steps.is-in .timeline__marker,
  .timeline__steps.is-in .timeline__label,
  .timeline__steps.is-in .timeline__step h3,
  .timeline__steps.is-in .timeline__step p { animation: none !important; }
}

/* ------------------------------------------------------ before / after -- */
/* Structured Cabling's signature section, and its one dark band below the
   hero. Two stacked photos; the "after" is clipped to the right of a divider
   the visitor drags.

   The control is a native range input stretched over the frame at opacity 0:
   drag, click-to-jump and keyboard all come from the browser, and js/app.js
   only mirrors its value into --p (0–1). No library.

   The divider is NOT at --p × width. A range input puts its thumb centre at
   half-a-thumb + value × (width − thumb), so the visible line uses the same
   formula, or a click near either edge would land up to 24px off the line.
   --thumb must match the thumb width set on the pseudo-elements below. */

.ba {
  position: relative;
  background: var(--ink-950);
  color: var(--white);
  /* Lands the seam under the sticky header when the hero's secondary button
     is followed — same measured values as .timeline. KEEP IN STEP with the
     819px nav collapse. */
  scroll-margin-top: 105px;
}
.ba__inner {
  max-width: var(--shell); margin: 0 auto;
  padding: var(--section-pad);
  display: grid; gap: 48px;
}
/* Column widths in proportion to the two aspect ratios (16:9 and 3:4, i.e.
   16 : 6.75), so the landscape and portrait frames come out the same height
   whatever the width. fr shares out the space after the gap, so the match is
   exact. */
.ba__grid {
  display: grid;
  grid-template-columns: minmax(0, 16fr) minmax(0, 6.75fr);
  gap: 32px;
  align-items: start;
}
.ba__item { margin: 0; display: flex; flex-direction: column; gap: 14px; }

.ba__frame {
  --p: .5;
  --thumb: 48px;
  --edge: calc(var(--thumb) / 2 + (100% - var(--thumb)) * var(--p));
  position: relative;
  overflow: hidden;
  border-radius: 14px;
  background: var(--ink-900);
  box-shadow: 0 24px 60px rgba(0, 0, 0, .5);
  outline: 1px solid rgba(255, 255, 255, .12);
  outline-offset: -1px;
}
/* Fixed ratios, so a replacement photo crops rather than reshaping the band. */
.ba__frame--wide { aspect-ratio: 16 / 9; }
.ba__frame--tall { aspect-ratio: 3 / 4; }

.ba__img {
  position: absolute; inset: 0;
  display: block;
  width: 100%; height: 100%;
  object-fit: cover;
}
.ba__img--after { clip-path: inset(0 0 0 var(--edge)); }

.ba__tag {
  position: absolute; top: 14px;
  padding: 6px 10px;
  border-radius: 6px;
  background: rgba(10, 12, 10, .8);
  font-size: 11px; font-weight: 700; line-height: 1;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-300);
  pointer-events: none;
}
.ba__tag--before { left: 14px; }
.ba__tag--after { right: 14px; color: var(--green-400); }

/* The visible divider: a gold line and a knob, both decorative. */
.ba__handle {
  position: absolute; top: 0; bottom: 0;
  left: var(--edge);
  width: 0;
  pointer-events: none;
}
.ba__handle::before {
  content: "";
  position: absolute; top: 0; bottom: 0; left: -1px;
  width: 2px;
  background: var(--gold-500);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .3);
}
.ba__knob {
  position: absolute; top: 50%; left: 0;
  transform: translate(-50%, -50%);
  width: 44px; height: 44px;
  border-radius: 50%;
  background: var(--gold-500);
  color: var(--ink-900);
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 6px 18px rgba(0, 0, 0, .45);
  transition: transform 160ms var(--ease);
}
.ba__frame:hover .ba__knob { transform: translate(-50%, -50%) scale(1.06); }

/* The real control. touch-action: pan-y hands vertical swipes back to the
   page, so a phone visitor can still scroll past the photo. */
.ba__range {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  margin: 0; padding: 0;
  opacity: 0;
  cursor: ew-resize;
  -webkit-appearance: none; appearance: none;
  background: transparent;
  touch-action: pan-y;
}
/* A full-height thumb, so on iOS Safari — which will not jump to a tap on the
   track — the whole gold line is grabbable, not just the knob. */
.ba__range::-webkit-slider-runnable-track { height: 100%; background: transparent; border: 0; }
.ba__range::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 48px; height: 2000px;
  background: transparent; border: 0;
}
.ba__range::-moz-range-track { height: 100%; background: transparent; border: 0; }
.ba__range::-moz-range-thumb { width: 48px; height: 2000px; background: transparent; border: 0; border-radius: 0; }

/* The input is invisible, so its focus ring has to be drawn on the knob. */
.ba__range:focus-visible ~ .ba__handle .ba__knob {
  outline: 3px solid var(--white);
  outline-offset: 3px;
}

.ba__caption { margin: 0; font-size: 14px; line-height: 1.6; color: var(--ink-300); }
.ba__caption strong { color: var(--white); font-weight: 700; }

/* ----------------------------------------------------------- handover -- */
/* Structured Cabling's proof column. Until a cabling client quote exists, the
   evidence is the deliverable itself — what the client is handed at the end. */

.handover { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
.handover li {
  display: flex; align-items: flex-start; gap: 14px;
  padding: 16px 18px;
  background: var(--white);
  border: 1px solid var(--ink-200);
  border-radius: 10px;
}
.handover__icon {
  flex: none;
  width: 26px; height: 26px; margin-top: 1px;
  border-radius: 50%;
  background: var(--green-600);
  color: var(--white);
  display: flex; align-items: center; justify-content: center;
}
.handover h3 { margin: 0 0 3px; font-size: 16px; font-weight: 700; line-height: 1.4; color: var(--ink-900); }
.handover p { margin: 0; font-size: 15px; line-height: 1.6; color: var(--ink-600); }

/* ------------------------------------------------- low voltage extras -- */
/* wiring-cabling.html, revision 2 (2026-09-11). The first build read as a
   feature list; these carry the argument it lacked. Brief:
   copy/structured-cabling-plan.md. */

/* A small contractor link under the hero buttons: present, not loud. It opens
   the form with the contractor option already chosen (js/app.js). */
.hero__aside { margin: 0; font-size: 14px; font-weight: 600; }
.hero__aside a {
  color: rgba(255, 255, 255, .86);
  text-decoration: underline;
  text-decoration-color: rgba(255, 255, 255, .35);
  text-underline-offset: 4px;
}
.hero__aside a:hover { color: var(--green-400); text-decoration-color: currentColor; }

/* Scope items lead with the outcome; the material sits above as a small label,
   so the searchable noun survives without leading. --green-700 on white. */
.scope__kind {
  display: block;
  margin-bottom: 4px;
  font-size: 11.5px; font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--green-700);
}

/* Why it matters — the owner argument, with a device beside it (no section is
   paragraphs alone): the systems a business runs, drawn as layers resting on
   the infrastructure. The gold line over the base layer is "the line"
   everything above inherits. */
.stakes { background: var(--white); }
.stakes__inner {
  max-width: var(--shell); margin: 0 auto;
  padding: var(--section-pad);
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr);
  gap: 64px; align-items: center;
}
.stakes__copy { display: flex; flex-direction: column; gap: 24px; }
.stakes__pull {
  margin: 0;
  padding: 18px 22px;
  background: var(--ink-50);
  border-left: 3px solid var(--gold-500);
  border-radius: 0 10px 10px 0;
  font-size: 17px; font-weight: 600; line-height: 1.6;
  color: var(--ink-900);
}

.stack { margin: 0; display: flex; flex-direction: column; gap: 14px; }
.stack__layers { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.stack__layer {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 16px;
  background: var(--ink-50);
  border: 1px solid var(--ink-200);
  border-radius: 10px;
  font-size: 15px; font-weight: 600;
  color: var(--ink-800);
}
.stack__layer svg { flex: none; color: var(--green-700); }
.stack__layer--base {
  position: relative;
  margin-top: 14px;
  padding: 18px;
  flex-wrap: wrap;
  background: var(--green-700);
  border-color: var(--green-700);
  color: var(--white);
}
.stack__layer--base::before {
  content: "";
  position: absolute; left: 0; right: 0; top: -9px;
  height: 2px;
  background: var(--gold-500);
}
.stack__layer--base svg { color: var(--gold-400); }
.stack__layer--base strong { font-size: 16px; }
.stack__layer--base span { margin-left: auto; font-size: 13px; color: rgba(255, 255, 255, .82); }
.stack figcaption { font-size: 14px; line-height: 1.6; color: var(--ink-600); }

/* For contractors — modest by design until PK has talked to the team: one
   light band, a job-site photo, four points and a button into the same form. */
.trade { background: var(--white); }
.trade__inner {
  max-width: var(--shell); margin: 0 auto;
  padding: var(--section-pad);
  display: grid;
  grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr);
  gap: 56px; align-items: center;
}
.trade__photo { margin: 0; }
.trade__photo img {
  display: block; width: 100%; height: auto;
  aspect-ratio: 4 / 3; object-fit: cover;
  border-radius: 14px;
  box-shadow: 0 22px 50px rgba(0, 0, 0, .16);
}
.trade__copy { display: flex; flex-direction: column; align-items: flex-start; gap: 26px; }
.trade__points {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 22px 28px;
}
.trade__points li { padding-top: 14px; border-top: 2px solid var(--gold-500); }
.trade__points h3 {
  margin: 0 0 6px;
  font-family: 'Oswald', sans-serif;
  font-weight: 600; font-size: 17px; line-height: 1.2;
  text-transform: uppercase; letter-spacing: .01em;
  color: var(--ink-900);
}
.trade__points p { margin: 0; font-size: 15px; line-height: 1.6; color: var(--ink-600); }


/* ------------------------------------------------- backup & DR extras -- */
/* backup-disaster-recovery.html (2026-09-17). Copy:
   copy/backup-dr-copy.md. The spine follows that page's argument, so it has
   three devices of its own and no friction picker, stats band or card grid.
   Bands: dark hero → white (the gap) → green tint (the two questions) → pale
   (scope) → dark (the test restore) → pale (FAQ) → green (form). */

/* Nothing overlaps this hero, so it gives back the bottom padding the compact
   hero keeps for the scope panel. The phone value is restated in the 720px
   block below, because this selector outranks the compact hero's own phone
   override. */
.hero--compact.hero--no-panel { --hero-pad: 52px 24px 80px; }

/* A heading's second line in an accent, the h2 counterpart of .hero__accent:
   --green-700 on light grounds, --green-400 on ink, --gold-400 on the green
   form band. */
.h2__accent { display: block; color: var(--green-700); }
.section-head--dark .h2__accent { color: var(--green-400); }
.consultation .h2__accent { color: var(--gold-400); }

/* The gap. Two equal columns rather than the cabling page's 1.15 / .85: the
   device here is a two-column table, and it needs the room. */
.stakes--gap .stakes__inner {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: start;
}
.stakes--gap .section-head p { font-size: 16px; }

/* ── "What you'll be asked" ────────────────────────────────────────────────
   backup-disaster-recovery.html. WAS A REAL TABLE until 2026-10-17; the
   markup carries the full reasoning. In short, a table told people to scan
   for their row when the whole value is in reading it down, and the column
   doing the arguing was set smaller and lighter than the column asking.

   ⚠ THE FRAME LIVES IN THE ANSWER, NOT THE HEADER, AND THAT IS THE POINT.
   A header is read once; this needs to be understood four times. Every
   verdict therefore contains the word "backups" so each row stands alone.
   The header above is a convenience, not the mechanism — deleting it should
   cost nothing. If a future edit shortens the verdicts back to "Nothing",
   the frame has to move somewhere else or the chart stops making sense by
   its third row. */
/* ⚠ THE SHADOW AND THE 14px RADIUS ARE RESTORED, NOT NEW. Both were on the
   original table and I lost them rewriting it into a div on 2026-10-17 —
   carried the border and background over, dropped these. PK noticed the
   panel had stopped standing apart from the page and nearly asked for a
   darker border to compensate, which would have been treating the symptom.
   The line defines the edge; the shadow lifts the panel off the page. They
   do different jobs and this chart is the only panel in a white section, so
   the lift earns its place. */
.asks {
  background: #fff;
  /* A LITERAL VALUE RATHER THAN A TOKEN, ON PURPOSE (PK, 2026-10-17: "just a
     hair" darker). --ink-200 #d7dcd8 was too faint against white and
     --ink-300 #b6bdb8 is a jump, not a hair — this sits between them. It is
     scoped to this one panel deliberately: --ink-200 is the border on 21
     other rules including nav links, form fields and FAQ items, so moving
     the token to fix one chart would move all of them. */
  border: 1px solid #c8cfca; border-radius: 14px;
  /* .08 restored from the deployed version, then lifted to .12 so the panel
     separates without the shadow becoming the thing you notice. */
  box-shadow: 0 18px 40px rgba(10, 12, 10, .12);
  padding: 4px 26px 8px;
}
.asks__head {
  display: grid; grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr);
  gap: 36px; padding: 14px 0 8px; border-bottom: 2px solid var(--ink-300);
}
.asks__head span {
  font-size: 17px; font-weight: 700; color: var(--green-700);
}
.asks__row {
  display: grid; grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr);
  gap: 36px; padding: 13px 0; border-bottom: 1px solid var(--ink-200);
  align-items: baseline;
}
.asks__row:last-child { border-bottom: 0; }
.asks__q {
  margin: 0; font-size: 16px; font-weight: 600; color: var(--ink-600);
}
/* SPACING TIGHTENED 2026-10-17 from 22px rows / 8px under the verdict. PK
   compared four steps side by side at the real 544px column width and took
   the tightest: "tightening it up makes it a little bit easier of a read".
   It takes 90px out of a 609px block. THE TYPE DID NOT CHANGE — shrinking it
   was tried first and bought only 23px, so the bulk was never the words. The
   gain is that a verdict and its qualifier now group as ONE unit instead of
   two stacked items. */

/* 23px, not 30. A sentence cannot carry the point size a single word can
   without swamping the row — the repetition of the PHRASE does the work the
   size used to. */
.asks__verdict {
  margin: 0; font-family: 'Oswald', sans-serif;
  font-size: 23px; font-weight: 600; line-height: 1.15;
  letter-spacing: .01em; color: var(--ink-900);
}
.asks__verdict--hot { color: var(--gold-700); }
.asks__why {
  margin: 5px 0 0; font-size: 14.5px; line-height: 1.55;
  color: var(--ink-600); max-width: 46ch;
}

@media (max-width: 820px) {
  .asks { padding: 4px 18px 8px; }
  .asks__head { display: none; }      /* the answers carry the frame already */
  .asks__row { grid-template-columns: 1fr; gap: 8px; }
}

/* The prose column runs about twice the chart's height, so on desktop the
   chart holds under the sticky header while the reader goes down the prose.
   129px is the 105px header plus a little air. */
@media (min-width: 1025px) {
  .stakes--gap .asks { position: sticky; top: 129px; }
}

/* The two questions, on the same green tint as the v2 friction band. */
.decide {
  background: #e2f1e4;
  /* The hero's secondary button lands here — same measured values as
     .timeline. KEEP IN STEP with the 819px nav collapse. */
  scroll-margin-top: 105px;
}
.decide__inner {
  max-width: var(--shell); margin: 0 auto;
  padding: var(--section-pad);
  display: grid; gap: 40px;
}

/* ON INK (PK, 2026-10-17). The section carries the page's one device and PK
   wanted it to pop, so this is the page's single dark band. Only the backup
   page takes the modifier; .decide is also used on access control, network
   design and video surveillance, which stay on the green tint. */
.decide--dark { background: var(--ink-950); color: var(--ink-100); }
/* ⚠ THE EYEBROW FLIPS TO GOLD ON INK. The sitewide rule puts it in
   --green-700, which is correct on light grounds and measures 2.25:1 here —
   against the 4.5:1 small text needs. --gold-500 measures 9.67:1. This is the
   same split already recorded for the dark header: green owns light grounds,
   gold keeps rules, buttons and dark-ground labels, where it passes. */
.decide--dark .eyebrow { color: var(--gold-500); }
.decide--dark .section-head p { color: rgba(255, 255, 255, .82); }
.decide--dark .decide__q h3 { color: #fff; }
.decide--dark .decide__q p  { color: rgba(255, 255, 255, .78); }
/* Full width, and the text set in two columns so the measure stays readable
   at 1152 — the same device .he--wide uses, and it echoes the two question
   columns directly above it rather than introducing a fifth width. */
.decide--dark .decide__tradeoff {
  background: #171c18;
  border-left-color: var(--gold-500);
  color: rgba(255, 255, 255, .82);
  max-width: none;
  column-count: 2; column-gap: 48px;
}
@media (max-width: 860px) {
  .decide--dark .decide__tradeoff { column-count: 1; }
}

/* The outage line: last good copy → work to redo → failure → time until
   you're working → back to work. Point labels hang below the line and span
   labels sit above it, both absolutely positioned so the columns stay the
   width of the markers. Every label is real text in reading order, so the
   figure needs no ARIA to make sense aloud. */
/* REBUILT 2026-10-17 as an SVG device on ink. What it replaced was a flat
   two-hairline timeline on white, and it had four faults, all of them PK's:

   1. "Time until you're working" was drawn in SOLID BRAND GREEN. That span is
      the downtime — the cost — rendered in the colour this site uses for good
      news, beside a gold hatched bar that correctly read as a warning.
   2. The two spans were identical, 331x8px, so the whole thing read as a
      neutral process diagram rather than as two costs.
   3. The failure marker was 44px against 8px spans. THE LOUDEST ELEMENT WAS
      THE ONE NOBODY CAN INFLUENCE; the two things a reader can actually
      change were hairlines.
   4. The labels naming the two costs were 12px — smaller than the 14px
      eyebrow above them once eyebrows went up sitewide.

   THE SHAPE IS THE ARGUMENT, which is why this one was chosen over two richer
   alternatives. Both wedges are pinched to nothing at the instant of failure
   and widen as they move away from it, because that is the actual arithmetic:
   the further back the last good copy, the more work is redone; the longer the
   stop runs, the more it costs. At the moment itself nothing is lost yet — it
   is the distance either side that bills you. Rejected alternatives and the
   reasoning are in prototypes/outage-track-dark/. */
/* FULL CONTENT WIDTH (PK, 2026-10-17). It was capped at 880, which made it a
   fourth right edge in a section that already had too many: the lede ran to
   640, the device to 880, its caption to 614, the two questions to 1152 and
   the trade-off to 750. Five right edges down one column, all left-aligned —
   PK: "something doesn't seem right". The section now has TWO widths: a narrow
   lede, because a standfirst wants a short measure, and everything else at the
   1152 the question grid sets. */
.outage { margin: 0; }
.outage__svg { display: block; width: 100%; height: auto; }
/* The divider is its own element so it can span the whole drawing while the
   caption text keeps a readable measure beneath it. As a border on the
   caption it stopped at 614 and read as a stub under a full-width graphic. */
.outage__rule { height: 1px; margin-top: 16px; background: #2f3733; }
.outage figcaption {
  margin-top: 13px; max-width: 74ch;
  font-size: 14.5px; line-height: 1.6;
  color: rgba(255, 255, 255, .62);
}

/* Device type. Cost labels are set in Oswald so they read as instrument
   legends rather than as body copy, and at 13 viewBox units they land near
   26px at the 2x render — no longer the smallest text in the section. */
.outage__lab {
  font-family: 'Oswald', sans-serif; font-size: 13px; font-weight: 600;
  letter-spacing: .085em; text-transform: uppercase;
}
.outage__lab--redo { fill: #f5be5c; }
.outage__lab--wait { fill: #e8755c; }
.outage__end {
  font-family: 'Open Sans', sans-serif; font-size: 10.5px; font-weight: 700;
  fill: #a7b0b6;
}
.outage__end--fail { fill: #f5be5c; }
.outage__lever {
  font-family: 'Open Sans', sans-serif; font-size: 9.5px; font-weight: 600;
  font-style: italic; fill: #7d8882;
}

/* ⚠ SVG + CSS TRANSFORM, TWO TRAPS, BOTH BIT WHILE BUILDING THIS.
   1. A CSS transform REPLACES an element's SVG transform attribute, it does
      not compose with it. A keyframe ending at scale(1) threw away
      translate(220 21) and dropped the bolt to viewBox 0,0, off the left edge
      of the drawing. Anything positioned by attribute and animated by CSS
      needs an outer <g> holding the position and an inner <g> taking the
      animation — which is why the bolt is nested twice in the markup.
   2. transform-box defaults to view-box, so transform-origin: 50% 50% is the
      centre of the WHOLE DRAWING, not of the element. Scaling the two end
      dots that way flung them in from the middle. Every scaled element here
      therefore declares fill-box explicitly. */
.outage__flash, .outage__glow, .outage__dot, .outage__bolt-in {
  transform-box: fill-box; transform-origin: 50% 50%;
}
.outage__wedge { transform-box: fill-box; }
.outage__wedge--redo { transform-origin: 100% 50%; }
.outage__wedge--wait { transform-origin: 0% 50%; }

/* Hidden start states, gated on .js-reveal so a visitor without JS — or one
   who prefers reduced motion, who never gets the class — sees the device
   fully drawn. KEEP IN STEP with the .outage entry in reveal.js GROUPS. */
.js-reveal .outage__base,
.js-reveal .outage__ticks,
.js-reveal .outage__flash,
.js-reveal .outage__glow,
.js-reveal .outage__bolt-in,
.js-reveal .outage__dot,
.js-reveal .outage__lab,
.js-reveal .outage__end,
.js-reveal .outage__lever { opacity: 0; }
.js-reveal .outage__wedge { transform: scaleX(0); }

.outage.is-in .outage__base    { animation: outFade   300ms ease-out forwards; }
.outage.is-in .outage__ticks   { animation: outFade   400ms ease-out 120ms forwards; }
.outage.is-in .outage__flash   { animation: outStrike 520ms cubic-bezier(.2,.9,.3,1) 300ms forwards; }
.outage.is-in .outage__glow    { animation: outBloom 1300ms ease-out 330ms forwards; }
.outage.is-in .outage__bolt-in { animation: outBolt   460ms cubic-bezier(.2,.9,.3,1) 300ms forwards; }
.outage.is-in .outage__wedge   { animation: outOpen  1500ms cubic-bezier(.22,.75,.22,1) 520ms forwards; }
.outage.is-in .outage__lab     { animation: outRise   420ms ease-out 1500ms forwards; }
.outage.is-in .outage__dot     { animation: outSnap   300ms ease-out 1820ms forwards; }
.outage.is-in .outage__end     { animation: outRise   380ms ease-out 1900ms forwards; }
.outage.is-in .outage__lever   { animation: outRise   460ms ease-out 2120ms forwards; }

@keyframes outFade   { to { opacity: 1; } }
@keyframes outOpen   { to { transform: scaleX(1); } }
@keyframes outRise   { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } }
@keyframes outSnap   { from { opacity: 0; transform: scale(.3); }      to { opacity: 1; transform: scale(1); } }
@keyframes outStrike { 0% { opacity: 0; transform: scaleY(.2); } 34% { opacity: 1; transform: scaleY(1); } 100% { opacity: .5; transform: scaleY(1); } }
@keyframes outBolt   { 0% { opacity: 0; transform: scale(.5); } 45% { opacity: 1; transform: scale(1.18); } 100% { opacity: 1; transform: scale(1); } }
@keyframes outBloom  { 0% { opacity: 0; transform: scale(.3); } 26% { opacity: .95; transform: scale(1.05); } 100% { opacity: .42; transform: scale(1); } }

/* ⚠ SVG TEXT DOES NOT HAVE A MINIMUM SIZE. A 440-unit viewBox in a 295px
   column renders at 0.67, which put the cost labels at 8.7px, the end labels
   at 7px and the lever lines at 6.4px — the same fault the drive device had
   before its viewBox was halved. The type is therefore scaled back up in
   viewBox units at phone width.

   THE LEVER LINES GO RATHER THAN SHRINK. They say the same thing as the
   caption directly below ("how often copies run sets the first, and what
   comes back first shortens the second"), so on a narrow screen the caption
   carries it and the drawing keeps the room. */
@media (max-width: 700px) {
  .outage__lab   { font-size: 18px; letter-spacing: .05em; }
  .outage__end   { font-size: 15px; }
  .outage__lever { display: none; }
}

/* The swatches on the two question headings below match the device. The wait
   swatch was --green-600 and is now the same red the wedge carries: both of
   these are costs, and neither belongs in the brand's good-news colour. */
.decide__q--redo h3::before {
  background: repeating-linear-gradient(135deg,
    var(--gold-500) 0 6px, var(--gold-400) 6px 11px);
}
.decide__q--wait h3::before {
  background: linear-gradient(90deg, #e8755c, #c9543c);
}

.decide__questions {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 28px 48px;
}
.decide__q h3 {
  margin: 0 0 10px;
  display: flex; align-items: center; gap: 12px;
  font-family: 'Oswald', sans-serif;
  font-weight: 600; font-size: 21px; line-height: 1.2;
  text-transform: uppercase; letter-spacing: .01em;
  color: var(--ink-900);
}
.decide__q h3::before {
  content: "";
  flex: none;
  width: 26px; height: 8px;
  border-radius: 4px;
}
.decide__q p { margin: 0; font-size: 16px; line-height: 1.7; color: var(--ink-700); }
.decide__tradeoff {
  margin: 0;
  max-width: 82ch;
  padding: 18px 22px;
  background: var(--white);
  border-left: 3px solid var(--gold-500);
  border-radius: 0 10px 10px 0;
  font-size: 16px; line-height: 1.7;
  color: var(--ink-800);
}

/* The proof section with no photo yet (Backup & DR, 2026-09-23): one column,
   and the account styled like the quote block it will sit beside when a real
   photo exists. */
.proof--text .proof__inner { grid-template-columns: minmax(0, 1fr); }
.proof--text .proof__body { max-width: 74ch; }
.proof__quote p { margin: 0; font-size: 18px; line-height: 1.7; color: var(--ink-800); }
.proof__quote .proof__source {
  margin-top: 12px;
  font-size: 13px; line-height: 1.5;
  color: var(--ink-500);
}

/* Scope without the overlap: an ordinary band holding the same white panel.
   Doubled class so it outranks .scope's own phone padding further down. */
.scope.scope--flat { padding: 88px 24px; }
.scope--flat .scope__panel { margin-top: 0; }
/* Four items read better two by two than as three plus an orphan. The
   phone breakpoint below still takes it to one column. */
.scope__list--four { grid-template-columns: repeat(2, minmax(0, 1fr)); }

/* The test restore: the page's one dark band. Four numbered cards rather
   than the Managed IT timeline's markers on a line, so the two signature
   sections don't read as one component. */
.restore {
  position: relative;
  background: var(--ink-950);
  color: var(--white);
  /* Same measured landing as .timeline. KEEP IN STEP with the 819px nav
     collapse. */
  scroll-margin-top: 105px;
}
.restore__inner {
  max-width: var(--shell); margin: 0 auto;
  padding: var(--section-pad);
  display: grid; gap: 44px;
}
.restore__steps {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 20px;
}
.restore__step {
  display: flex; flex-direction: column; gap: 10px;
  padding: 22px 22px 26px;
  background: rgba(255, 255, 255, .04);
  border: 1px solid rgba(255, 255, 255, .1);
  border-top: 2px solid var(--gold-500);
  border-radius: 0 0 12px 12px;
}
.restore__num {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 14px; font-weight: 600; letter-spacing: .06em;
  color: var(--gold-400);
}
.restore__step h3 {
  margin: 0;
  font-family: 'Oswald', sans-serif;
  font-weight: 600; font-size: 21px; line-height: 1.15;
  text-transform: uppercase; letter-spacing: .01em;
  color: var(--white);
}
.restore__step p { margin: 0; font-size: 15px; line-height: 1.65; color: var(--ink-300); }
.restore__close {
  margin: 0;
  padding-left: 18px;
  border-left: 3px solid var(--green-400);
  font-family: 'Oswald', sans-serif;
  font-size: 24px; font-weight: 500; line-height: 1.3;
  letter-spacing: .01em;
  color: var(--white);
}

/* ----------------------------------------------------------- friction -- */
/* In the shared component the friction buttons are white cards on a white
   section, held apart only by a hairline; colour arrives once one is tapped.
   On v2 that section also sat in a run of three pale bands, which made it the
   most washed-out stretch of the page (PK, 2026-09-10). A green tint behind
   the buttons fixes both, and adds variety without bringing a dark band back.

   #e2f1e4 is --green-500 at 14% over white, written as a literal rather than
   through color-mix(): no build step, and Pinegrow opens these files directly.
   Measured on it: intro text 8.0:1, grey labels 5.4:1. The gold eyebrow is
   3.1:1 here, but it is already 3.6:1 on plain white — a sitewide issue logged
   in the README, not one this tint introduces.

   The selected state is redrawn. The shared one is a pale green fill, which
   would sink into a green ground and lose the "I picked this" feedback; a
   white card with a green edge and bar keeps it unmistakable. */

.friction { background: #e2f1e4; }
.symptom {
  border-color: transparent;
  box-shadow: 0 2px 8px rgba(12, 44, 18, .08);
}
.symptom:hover { box-shadow: 0 10px 24px rgba(12, 44, 18, .14); }
.symptom[aria-pressed="true"] {
  background: var(--white);
  border-color: var(--green-600);
  box-shadow: inset 4px 0 0 var(--green-600), 0 2px 8px rgba(12, 44, 18, .08);
}
/* These rules restyle the card's box-shadow, which would otherwise beat the
   focus ring's ink band (see styles.css, "Two-tone focus ring"). Repeat the
   band — and keep the selected state's green bar, which is the only thing
   telling the visitor what they picked. */
.symptom:focus-visible { box-shadow: 0 0 0 7px var(--ink-950); }
.symptom[aria-pressed="true"]:focus-visible {
  box-shadow: inset 4px 0 0 var(--green-600), 0 0 0 7px var(--ink-950);
}

/* -------------------------------------------------------------- proof -- */
/* Proof and the FAQ share one continuous ground instead of alternating bands:
   fewer colour changes is what makes the Why page feel least blocky. There was
   a hairline on this edge while the friction section above was white, since a
   line only helps where two near-identical colours meet. With friction now
   green-tinted the colour change is itself the edge, so the line came out. */

.proof { background: var(--ink-50); }
.proof__inner {
  max-width: var(--shell); margin: 0 auto;
  padding: var(--section-pad);
  padding-bottom: 40px;
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 56px; align-items: center;
}
.proof__photo { margin: 0; }
.proof__photo img {
  display: block; width: 100%; height: auto;
  aspect-ratio: 4 / 3; object-fit: cover;
  border-radius: 14px;
  box-shadow: 0 22px 50px rgba(0, 0, 0, .16);
}
.proof__body { display: flex; flex-direction: column; gap: 24px; }
/* One real quote instead of the trio, so it reads as evidence for this claim
   rather than a testimonial block dropped in from the homepage. */
.proof__quote {
  margin: 0;
  padding: 22px 24px;
  background: var(--white);
  border-left: 3px solid var(--gold-500);
  border-radius: 0 10px 10px 0;
}
.proof__quote blockquote { margin: 0; font-size: 17px; line-height: 1.7; color: var(--ink-800); }
.proof__quote figcaption {
  margin-top: 12px;
  font-size: 12px; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-500);
}
/* Join the FAQ onto the same ground rather than starting a new band. */
.proof + .faq .faq__inner { padding-top: 24px; }

/* ------------------------------------------------------------- reveal -- */
/* Hidden start states for v2's own sections. The matching GROUPS entries live
   in reveal.js — KEEP THE TWO IN STEP. A hidden state with no GROUPS entry is
   never revealed and stays invisible; that is exactly how the footer silently
   stopped animating on 2026-09-09. Kept here rather than in reveal.css so that
   deleting v2 deletes them too; stray GROUPS entries then match nothing. */

.js-reveal :where(
  .scope .section-head,
  .scope__list > .scope__item,
  .stakes__copy,
  .stakes__points > li,
  .timeline .section-head,
  .timeline__steps,
  .ba .section-head,
  .ba__grid > .ba__item,
  .proof__photo,
  .proof .section-head,
  .proof__quote,
  .handover > li,
  .stakes__copy,
  .stack__layers > .stack__layer,
  .trade__photo,
  .trade__copy > *,
  .asks,
  .decide .section-head,
  .outage,
  .decide__questions > .decide__q,
  .decide__tradeoff,
  .restore .section-head,
  .restore__steps > .restore__step,
  .restore__close,
  /* network-design.html, added 2026-10-14. Matching GROUPS entries are in
     reveal.js — the two MUST stay in step. */
  .daily .section-head,
  .daily__grid > .daily__item,
  .he .section-head,
  .he__copy,
  .he__fig,
  .nw .section-head,
  .nw__fig,
  .opt > .opt__item,
  .bought .section-head
) {
  opacity: 0;
  transform: translateY(var(--reveal-shift));
  transition:
    opacity var(--reveal-dur) var(--ease),
    transform var(--reveal-dur) var(--ease);
  transition-delay: var(--reveal-delay, 0ms);
}

@media (prefers-reduced-motion: reduce) {
  .js-reveal :where(
    .scope .section-head, .scope__list > .scope__item,
    .stakes__copy, .stakes__points > li,
    .timeline .section-head, .timeline__steps,
    .ba .section-head, .ba__grid > .ba__item,
    .proof__photo, .proof .section-head, .proof__quote, .handover > li,
    .stakes__copy, .stack__layers > .stack__layer, .trade__photo, .trade__copy > *,
    .asks, .decide .section-head, .outage, .decide__questions > .decide__q,
    .decide__tradeoff, .restore .section-head, .restore__steps > .restore__step,
    .restore__close,
    .daily .section-head, .daily__grid > .daily__item,
    .he .section-head, .he__copy, .he__fig,
    .nw .section-head, .nw__fig, .opt > .opt__item, .bought .section-head
  ) {
    opacity: 1; transform: none; transition: none;
  }
  .hero__card { animation: none; }
  .ba__knob { transition: none; }
}

/* -------------------------------------------------------- breakpoints -- */

@media (max-width: 819px) {
  .timeline, .ba, .decide, .restore { scroll-margin-top: 109px; }
}

@media (max-width: 1024px) {
  .hero--compact { --hero-measure: 100%; }
  .scope__list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* The copy needs the full width, so the cards drop beneath it as a row.
     They stay in the hero rather than disappearing: on a phone this is the
     only place a visitor sees PAconnect people before scrolling. */
  .hero--layered .hero__inner { grid-template-columns: minmax(0, 1fr); row-gap: 32px; }
  .stakes__inner { grid-template-columns: minmax(0, 1fr); gap: 40px; }
  .stakes--gap .stakes__inner { grid-template-columns: minmax(0, 1fr); }
  .restore__steps { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 900px) {
  .stakes__inner { grid-template-columns: 1fr; gap: 32px; }
  .proof__inner { grid-template-columns: 1fr; gap: 32px; }
  /* Stacked. The portrait frame is capped, or at tablet width it would run
     ~960px tall — more scrolling than the comparison is worth. */
  .ba__grid { grid-template-columns: minmax(0, 1fr); gap: 40px; }
  .ba__item--tall { width: min(100%, 440px); }
  .trade__inner { grid-template-columns: minmax(0, 1fr); gap: 32px; }
  .decide__questions { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 720px) {
  .hero--compact {
    --hero-pad: 36px 20px 108px;
    --hero-size: 36px;
    --hero-lead: 16px;
  }
  .scope { padding: 0 16px 64px; }
  .scope__panel { margin-top: -72px; padding: 30px 22px 24px; }
  .scope__list { grid-template-columns: 1fr; }

  .hero__cards { gap: 10px; }
  .hero__card { border-radius: 10px; }

  /* The strip drops its third card on phones. Three across a 375px screen is
     about 105px per card, and the whole point of these photos is that a face
     reads — below roughly 150px it stops. Two cards at phone width are then
     the same two-up layout as the overlapped pages, at the same 163px width,
     though still shallower: 163 × 102 here against 163 × 122, since the strip
     keeps its 16/10 crop at every width. Consequence for the page: order the
     photos so the two that matter most are A and B, because C is the one
     nobody on a phone sees. */
  .hero--strip .hero__cards { grid-template-columns: 1fr 1fr; }
  .hero--strip .hero__card--c { display: none; }
  .ba__tag { top: 10px; }
  .ba__tag--before { left: 10px; }
  .ba__tag--after { right: 10px; }
  .trade__points { grid-template-columns: minmax(0, 1fr); }

  /* Backup & DR on phones. */
  .hero--compact.hero--no-panel { --hero-pad: 36px 20px 60px; }
  .scope.scope--flat { padding: 64px 16px; }
  .restore__steps { grid-template-columns: minmax(0, 1fr); }
  .restore__close { font-size: 20px; }

  /* The outage line turns vertical: markers in one 44px column, labels to
     their right. "Before / after the failure" in the caption holds either way. */
  .outage { padding: 26px 20px 22px; }
  .outage__track {
    grid-template-columns: 44px;
    grid-template-rows: 44px 76px 44px 76px 44px;
    justify-items: center;
    margin: 0;
  }
  .outage__point { width: 44px; }
  .outage__label,
  .outage__point:first-child .outage__label,
  .outage__point:last-child .outage__label {
    top: 50%; left: 60px; right: auto;
    transform: translateY(-50%);
  }
  .outage__span { width: 8px; height: auto; align-self: stretch; margin: 6px 0; }
  /* The span is 8px wide and centred in the 44px column, so it starts 18px
     in; 42px more lines its label up with the point labels at 60px. */
  .outage__span-label { bottom: auto; top: 50%; left: 42px; transform: translateY(-50%); }
  .outage figcaption { text-align: left; }

  /* Stages stack; the connecting line turns vertical and runs down through
     the markers, with the text beside them. */
  .timeline__steps { grid-template-columns: 1fr; gap: 32px; }
  .timeline__steps::before {
    top: 23px; bottom: 23px; left: 22px; right: auto;
    width: 2px; height: auto;
    background: linear-gradient(180deg, var(--gold-500), rgba(240, 168, 48, .18));
  }
  .timeline__step { padding-left: 66px; min-height: 46px; }
  .timeline__marker { position: absolute; left: 0; top: 0; }
}

/* ------------------------------------------------------------- 404 -- */
/* site/404.html. No hero and no artwork: an error page should say what
   happened, offer the pages that exist, and get out of the way. It borrows
   this stylesheet only for the shell measurements. */
.notfound { background: var(--white); }
.notfound__inner {
  max-width: var(--shell); margin: 0 auto;
  padding: var(--section-pad);
  display: flex; flex-direction: column; gap: 18px;
}
.notfound h1 {
  margin: 0;
  font-family: 'Open Sans', sans-serif; font-weight: 800;
  font-size: var(--h2-size); line-height: 1.12;
  color: var(--ink-900);
}
.notfound__lead { margin: 0; font-size: 18px; line-height: 1.7; color: var(--ink-600); max-width: 60ch; }
.notfound__links {
  margin-top: 22px;
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 48px;
}
.notfound__links h2 {
  margin: 0 0 14px;
  font-family: 'Oswald', sans-serif;
  font-weight: 600; font-size: 20px; line-height: 1.2;
  text-transform: uppercase; letter-spacing: .01em;
  color: var(--ink-900);
}
.notfound__links ul { margin: 0; padding: 0; list-style: none; display: grid; gap: 10px; }
.notfound__links li a { font-size: 16px; font-weight: 600; }
.notfound__links p { margin: 0 0 16px; font-size: 16px; line-height: 1.7; color: var(--ink-600); max-width: 46ch; }
.notfound__phone a {
  font-family: 'Oswald', sans-serif; font-size: 26px; letter-spacing: .02em;
  color: var(--green-700); text-decoration: none;
}
.notfound__phone a:hover { text-decoration: underline; }

@media (max-width: 720px) {
  .notfound__links { grid-template-columns: minmax(0, 1fr); gap: 32px; }
}

/* ----------------------------------------------------------- legal -- */
/* site/privacy.html. One narrow prose column: nobody reads a privacy page in
   three columns, and the measure is what makes it readable at all. */
.legal { background: var(--white); }
.legal__inner {
  max-width: 74ch; margin: 0 auto;
  padding: var(--section-pad);
}
.legal h1 {
  margin: 14px 0 0;
  font-family: 'Open Sans', sans-serif; font-weight: 800;
  font-size: var(--h2-size); line-height: 1.12;
  color: var(--ink-900);
}
.legal .rule { margin: 18px 0 22px; }
.legal__lead { margin: 0; font-size: 18px; line-height: 1.75; color: var(--ink-700); }
.legal__updated {
  margin: 10px 0 0;
  font-size: 13px; color: var(--ink-500);
}
.legal h2 {
  margin: 38px 0 10px;
  font-family: 'Oswald', sans-serif;
  font-weight: 600; font-size: 21px; line-height: 1.2;
  text-transform: uppercase; letter-spacing: .01em;
  color: var(--ink-900);
}
.legal p { margin: 0 0 14px; font-size: 16.5px; line-height: 1.75; color: var(--ink-700); }
.legal p strong { color: var(--ink-900); }

/* ---------------------------------------------------------- voice extras -- */
/* business-phone-systems.html (2026-10-01). Copy: copy/voice-copy.md.
   Brief: copy/brief-voice.md.

   The spine follows that page's argument, so it has two devices of its own and
   no friction picker, stats band, compare tabs or services card grid. The
   features grid is the only grid on the page.

   Bands: dark hero -> white (why change) -> DARK (the monthly figure, the one
   dark band) -> green tint (the switch) -> pale (when the internet goes) ->
   white (before you sign) -> pale (day to day, the empty proof slot and the
   FAQ on one continuous ground) -> green (form).

   NO REVEAL ENTRIES. The two new devices deliberately have no hidden start
   state here, because adding one without a matching GROUPS entry in
   js/reveal.js leaves the element invisible for good — and this page's build
   was scoped not to touch reveal.js. The shared pieces it reuses (.hero__copy,
   .stakes__copy, .consultation__copy, .form-card) already animate. If the
   devices are ever given a reveal, ADD BOTH HALVES IN THE SAME CHANGE. */

/* ------------------------------------------------------ hero card slots -- */
/* Neither photo exists yet: images/photo-voice-desk.jpg (1:1 on desktop, 4:3
   below, ~1100x1000) and images/photo-voice-install.jpg (4:3, 1100x825). The
   frames carry a tinted ground so a missing file reads as a composed dark
   panel rather than a white gap, and `color: transparent` keeps a broken
   image's alt text from sprawling across it — the alt is still read aloud, and
   it doubles as the shot brief. Until the files land each frame shows a small
   broken-image glyph, which is the browser's and is the honest signal that a
   photo is outstanding; it goes when the files do. Dropping the real files in
   needs no markup or CSS change, which is the whole point of fixed-name
   slots. */
.hero--voice .hero__card {
  background: linear-gradient(140deg, var(--green-900) 0%, var(--ink-800) 72%);
}
.hero--voice .hero__card img { color: transparent; }

/* ----------------------------------------------------------- why change -- */
/* One prose column rather than the two-column .stakes used elsewhere: the
   argument here is a single run of three paragraphs and a split would break
   it. The device is typographic — one sentence of the body is lifted out of
   its paragraph as a display line. It is MOVED, not duplicated, so no copy was
   invented to give the section something to look at. */
.stakes--voice .stakes__inner {
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
}
.stakes--voice .stakes__copy { max-width: 76ch; gap: 26px; }
.stakes--voice .stakes__copy .section-head { max-width: none; }
/* Beats `.stakes__copy p` (0,1,1) on class count, which would otherwise add a
   second top margin to a paragraph the flex gap has already spaced. */
.stakes--voice .stakes__after {
  margin: 0;
  font-size: 17px; line-height: 1.75;
  color: var(--ink-700);
}

/* == SIGNATURE — the itemised figure. THE PAGE'S ONE DARK BAND. ============
   ONE COLUMN, read top to bottom like a statement: what is in the monthly
   figure, a rule, then what isn't. NOT a card grid and NOT a comparison
   table — the argument is arithmetic, and a grid would turn a statement into
   a feature matrix.

   THE TWO HALVES ARE VISUALLY EQUAL. Same row geometry, same label type, same
   body type and the same body colour. Naming the exclusions is what makes the
   inclusions credible, so "Not in the figure" is never shrunk, greyed or set
   smaller; the only difference is the colour of its group label, and both
   labels clear 8:1 on this ground (--green-400 8.14:1, --gold-500 9.3:1).
   --green-700, THE PAconnect green, is unusable here at 2.3:1 — the same trap
   the dark header and the footer hit.

   Body text is --ink-300 on --ink-950: 11.2:1, the same pairing the Backup
   page's dark band uses. */
.tally {
  position: relative;
  background: var(--ink-950);
  color: var(--white);
  /* Same measured landing as .timeline and .restore. The hero's secondary
     button lands here. KEEP IN STEP with the 819px nav collapse. */
  scroll-margin-top: 105px;
}
.tally__inner {
  max-width: var(--shell); margin: 0 auto;
  padding: var(--section-pad);
  display: grid; gap: 34px; justify-items: start;
}
/* The product mark, at the head of this section rather than in the hero: it is
   a product mark, not a hero element, so it gets a sensible cap.
   ON-DARK ARTWORK ONLY. images/paconnect-voice-on-light.png has a BLACK
   wordmark and disappears completely on this ground. */
.tally__logo { width: min(100%, 250px); height: auto; display: block; }

.tally__statement { width: 100%; max-width: 980px; display: grid; }
.tally__group { display: grid; }
.tally__head {
  margin: 0 0 6px;
  font-size: 12px; font-weight: 700; line-height: 1.3;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--green-400);
}
.tally__group--out .tally__head { color: var(--gold-500); }
.tally__rows { margin: 0; display: grid; }
.tally__row {
  display: grid;
  grid-template-columns: minmax(0, 270px) minmax(0, 1fr);
  column-gap: 36px;
  padding: 18px 0;
  border-top: 1px solid rgba(255, 255, 255, .14);
}
.tally__row dt {
  margin: 0;
  font-family: 'Oswald', sans-serif;
  font-weight: 600; font-size: 17px; line-height: 1.25;
  text-transform: uppercase; letter-spacing: .01em;
  color: var(--white);
}
.tally__row dd { margin: 0; font-size: 15.5px; line-height: 1.7; color: var(--ink-300); }
/* The rule between the halves — the hinge the whole device turns on, so it is
   the full measure and gold rather than a hairline. */
.tally__rule { width: 100%; height: 2px; margin: 30px 0 26px; background: var(--gold-500); }
.tally__close {
  margin: 0;
  padding-left: 18px;
  border-left: 3px solid var(--green-400);
  font-family: 'Oswald', sans-serif;
  font-size: 24px; font-weight: 500; line-height: 1.3;
  letter-spacing: .01em;
  color: var(--white);
}

/* == DEVICE — the caller line ==============================================
   Five stages across the top, ONE CONTINUOUS LINE underneath labelled "what
   your callers hear", crossing from the old system to the new phones at stage
   3 with no break. The unbroken line is the whole point: it shows the
   mechanism in one picture.

   IT MUST NOT READ AS A THIRD NUMBERED-STEP COMPONENT. Backup & DR has four
   numbered cards (.restore__step) and Managed IT a three-stage marker
   timeline (.timeline). So the stages carry no numerals, no badges, no border
   and no fill — they are typeset as quiet labels for the line. The track is
   the only strongly coloured thing in the figure, which is what makes the eye
   follow it rather than count the stages.

   The track is ONE element with ONE gradient, so it cannot acquire a gap: the
   colour changes across the stage 2/3 boundary and the bar is continuous
   through it. The blend is wide on purpose — a hard edge would read as a
   join, which is exactly the wrong idea.

   The line and the stages share one five-column grid, so the two line labels
   sit under their own spans of the track and the crossover lands on the right
   boundary. */
.switchover {
  background: #e2f1e4;
  /* KEEP IN STEP with the 819px nav collapse. */
  scroll-margin-top: 105px;
}
.switchover__inner {
  max-width: var(--shell); margin: 0 auto;
  padding: var(--section-pad);
  display: grid; gap: 40px;
}

.relay {
  margin: 0;
  padding: 34px 40px 30px;
  background: var(--white);
  border-radius: 14px;
  box-shadow: 0 2px 8px rgba(12, 44, 18, .08);
  display: grid; gap: 30px;
}
.relay__stages {
  list-style: none; margin: 0; padding: 0;
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  column-gap: 24px;
}
.relay__stage { display: flex; flex-direction: column; gap: 6px; }
/* The time phrase IS the stage label. The copy doc numbers them "1 · Before
   install day"; those digits are its table's own numbering, and printing them
   would have built the numbered-step component this device must not be. The
   <ol> carries the order for a screen reader. */
.relay__when {
  font-size: 11.5px; font-weight: 700; line-height: 1.3;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-500);
}
.relay__stage h3 {
  margin: 0;
  font-family: 'Oswald', sans-serif;
  font-weight: 600; font-size: 17px; line-height: 1.2;
  text-transform: uppercase; letter-spacing: .01em;
  color: var(--ink-900);
}
.relay__stage p { margin: 0; font-size: 14.5px; line-height: 1.6; color: var(--ink-600); }

.relay__line {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  column-gap: 24px;
  row-gap: 14px;
  align-items: center;
}
.relay__label {
  grid-column: 1 / -1;
  font-size: 11.5px; font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-900);
}
.relay__track {
  grid-column: 1 / -1;
  position: relative;
  height: 14px;
  border-radius: 999px;
  background: linear-gradient(90deg,
    var(--ink-500) 0 35%,
    var(--green-500) 45% 100%);
}
/* The crossover, marked once. One marker on a line reinforces the line as the
   subject; five would have turned the stages back into steps. */
.relay__track::after {
  content: "";
  position: absolute; top: 50%; left: 40%;
  width: 20px; height: 20px;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  background: var(--white);
  border: 3px solid var(--gold-500);
}
.relay__seg {
  margin: 0;
  display: flex; align-items: center; gap: 9px;
  font-size: 13px; font-weight: 700; line-height: 1.4;
  color: var(--ink-800);
}
.relay__seg::before { content: ""; flex: none; width: 12px; height: 12px; border-radius: 50%; }
.relay__seg--old { grid-column: 1 / 3; }
.relay__seg--new { grid-column: 3 / -1; }
.relay__seg--old::before { background: var(--ink-500); }
.relay__seg--new::before { background: var(--green-500); }
.relay figcaption { font-size: 15px; line-height: 1.6; color: var(--ink-600); }

/* ------------------------------------------- when the internet goes -- */
/* Nothing in this section sells, prices or describes a failover product, and
   it must not acquire one (PK, 2026-10-01). The two blocks are what is true
   with no hardware; the limit is the honest half, which is why it carries the
   gold rule rather than the green one; the pointer hands the subject to the
   Network page and is deliberately not a link until that page exists; and the
   callout is evidence of judgement, not an offer. */
.offline {
  background: var(--ink-50);
  scroll-margin-top: 105px;
}
.offline__inner {
  max-width: var(--shell); margin: 0 auto;
  padding: var(--section-pad);
  display: grid; gap: 34px;
}
.offline__grid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 24px;
}
/* Hairline and lift added 2026-10-18 (PK). These were the only cards in the
   section without them — .offline__callout, the same white-card-with-an-
   accent-edge a few rows down, already carried the shadow, so one section was
   speaking two languages. Shadow matches that neighbour exactly; the hairline
   is the #c8cfca PK settled on for .asks the day before. The 3px accent
   overrides the hairline on the left edge, so the two never double up. */
.offline__block,
.offline__limit {
  padding: 24px 26px;
  background: var(--white);
  border: 1px solid #c8cfca;
  border-left: 3px solid var(--green-600);
  border-radius: 0 10px 10px 0;
  box-shadow: 0 18px 40px rgba(10, 12, 10, .08);
}
.offline__limit { border-left-color: var(--gold-500); max-width: 88ch; }
.offline__block h3,
.offline__limit h3 {
  margin: 0 0 8px;
  font-family: 'Oswald', sans-serif;
  font-weight: 600; font-size: 19px; line-height: 1.2;
  text-transform: uppercase; letter-spacing: .01em;
  color: var(--ink-900);
}
.offline__block p,
.offline__limit p { margin: 0; font-size: 16px; line-height: 1.7; color: var(--ink-700); }
.offline__pointer { margin: 0; max-width: 82ch; font-size: 15.5px; line-height: 1.7; color: var(--ink-600); }
/* The page name, not a link: Network Design & Management does not exist yet,
   and a link that goes nowhere is the same broken promise as a menu head that
   looks clickable and isn't. --green-700 is 8.71:1 on this ground. */
.offline__pointer em { font-style: normal; font-weight: 700; color: var(--green-700); }
.offline__callout {
  margin: 0;
  max-width: 74ch;
  padding: 26px 28px;
  background: var(--white);
  border: 1px solid #c8cfca;
  border-top: 3px solid var(--gold-500);
  border-radius: 0 0 12px 12px;
  box-shadow: 0 18px 40px rgba(10, 12, 10, .08);
}
.offline__callout p { margin: 0; font-size: 17px; line-height: 1.75; color: var(--ink-800); }
.offline__callout strong { color: var(--green-700); }

/* --------------------------------------------------- before you sign -- */
/* Four blocks, two by two, in the doc's order — they escalate by what each one
   costs us, ending on the self-denying one. Gold top rules rather than the
   green left rule the other pages' point lists use, so this does not read as
   another .stakes__points. */
.sign {
  background: var(--white);
  scroll-margin-top: 105px;
}
.sign__inner {
  max-width: var(--shell); margin: 0 auto;
  padding: var(--section-pad);
  display: grid; gap: 34px;
}
.sign__grid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 24px 32px;
}
.sign__block {
  padding: 22px 24px 24px;
  background: var(--ink-50);
  border-top: 2px solid var(--gold-500);
  border-radius: 0 0 12px 12px;
}
.sign__block h3 {
  margin: 0 0 8px;
  font-family: 'Oswald', sans-serif;
  font-weight: 600; font-size: 19px; line-height: 1.2;
  text-transform: uppercase; letter-spacing: .01em;
  color: var(--ink-900);
}
.sign__block p { margin: 0; font-size: 15.5px; line-height: 1.7; color: var(--ink-700); }

/* ---------------------------------------------------------- day to day -- */
/* THE ONLY GRID ON THE PAGE. Compact: the searchable noun as a small label,
   one plain line each. Four across, so seven items fill a row of four and a
   row of three rather than leaving a single orphan under a row of three.
   The video-and-chat line sits BENEATH the grid in the quieter treatment —
   inside it, the grid's own heading would be asserting use of the one feature
   nobody has ever requested. */
.daily {
  background: var(--ink-50);
  scroll-margin-top: 105px;
}
.daily__inner {
  max-width: var(--shell); margin: 0 auto;
  padding: var(--section-pad);
  display: grid; gap: 32px;
}
.daily__grid {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  column-gap: 28px; row-gap: 22px;
}
/* GOLD EDGE ON EACH ITEM (PK, 12 Oct, from a marked-up screenshot). Before
   this the entire device was a one-pixel grey hairline - no colour, nothing
   from the brand - while comparable lists elsewhere (.stack__layer on the
   cabling page) get icons and a gold row. The list read as correct and flat.

   Gold rather than green on purpose: the labels are already --green-700, and
   green label over gold rule is the device the whole site uses at section
   heads. This is the same pairing one level down. */
.daily__item {
  padding: 18px 0 20px 14px;
  border-top: 1px solid var(--ink-200);
  border-left: 3px solid var(--gold-500);
}
/* BOLDER, 16 Oct (PK): at 12px Open Sans these "faded right into the copy".
   They are item HEADINGS, not eyebrows, and the site already has a treatment
   for exactly that job — .opt__name on the network page, Oswald 18px green.
   Matched to it rather than inventing a third style, keeping the uppercase
   this device has always had. Wrapping to two lines is fine (PK). */
.daily__label {
  display: block; margin-bottom: 9px;
  font-family: 'Oswald', sans-serif;
  font-size: 18px; font-weight: 600; line-height: 1.2;
  letter-spacing: .03em; text-transform: uppercase;
  color: var(--green-700);
}
.daily__item p { margin: 0; font-size: 15px; line-height: 1.6; color: var(--ink-600); }
/* Two-paragraph items (cyber-security.html, "Why now") need air between them. */
.daily__item p + p { margin-top: 10px; }
/* --ink-500 on --ink-50 is 5.76:1. Quieter than the grid, still AA. */
.daily__aside {
  margin: 0; max-width: 86ch;
  padding-top: 20px;
  border-top: 1px solid var(--ink-200);
  font-size: 15px; line-height: 1.7; color: var(--ink-500);
}

/* ------------------------------------------------- the empty proof slot -- */
/* The section exists so #proof resolves and so the ready markup has somewhere
   to live; it has no inner element, so it renders at zero height.
   The shared `.proof + .faq .faq__inner` rule joins the FAQ onto the proof
   section's ground by cutting its top padding — correct when there is a proof
   section to join onto, wrong when it is empty. Restored here. */
.proof--pending { padding: 0; }
.proof--pending + .faq .faq__inner { padding: var(--section-pad-tight); }

/* ------------------------------------------- voice extras breakpoints -- */

@media (max-width: 819px) {
  .tally, .switchover, .offline, .sign, .daily { scroll-margin-top: 109px; }
}

@media (max-width: 1024px) {
  .daily__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* The layered hero keeps the shared horizontal scrim, whose premise is text
     on the left and artwork on the right. Below 1025px the copy runs the full
     width and runs straight out over the thinnest end of it — the same fault
     .hero--motion fixes for the loop heroes. Measured at 375 over the real
     wave-horizon still: the lead came out at 4.55:1, 0.05 above what 16px
     text needs, which is not a margin. Flattened to close to an even dim the
     lead measures 10.51:1, the green accent 7.11 and the breadcrumb 7.81 —
     worst case across the whole hero.
     Scoped to this page: the other layered hero (wiring-cabling) was measured
     on its own artwork and is not touched here. */
  .hero--voice .hero__scrim {
    background: linear-gradient(90deg,
      rgba(7, 27, 11, .95) 0%,
      rgba(7, 27, 11, .91) 50%,
      rgba(7, 27, 11, .86) 100%);
  }
}

/* 1150, not 1024, and it is measured rather than inherited. The shell stops
   growing at 1200, so a stage column is 195px from 1248px up and shrinks
   below it: 185px at 1151 and 160px at 1025, which is about twenty characters
   a line for the longest stage. The device does not survive as a cramped row,
   so it turns vertical while the columns are still readable. 1150 is also the
   mega panel's own density boundary — see "Where the header collapses". */
@media (max-width: 1150px) {
  /* THE CALLER LINE GOES VERTICAL.

     The track is replaced by the stages' own left borders: the rows have no
     gap, so the borders are contiguous and the line is still ONE UNBROKEN
     LINE running through all five stages, changing colour at stage 3. Same
     picture, turned ninety degrees. The two line labels become a key above
     the list, which is what `order: -1` is for. */
  .relay { padding: 28px 24px 26px; gap: 24px; }
  .relay__line {
    order: -1;
    display: flex; flex-wrap: wrap; align-items: center;
    gap: 8px 22px;
  }
  .relay__label { width: 100%; }
  .relay__track { display: none; }
  .relay__stages { grid-template-columns: minmax(0, 1fr); column-gap: 0; }
  .relay__stage {
    position: relative;
    gap: 4px;
    padding: 0 0 26px 26px;
    border-left: 5px solid var(--ink-500);
  }
  .relay__stage:nth-child(n + 3) { border-left-color: var(--green-500); }
  .relay__stage:last-child { padding-bottom: 0; }
  /* The crossover marker, on the line at the head of stage 3. */
  .relay__stage--cross::before {
    content: "";
    position: absolute; left: -12px; top: 1px;
    width: 19px; height: 19px;
    border-radius: 50%;
    background: var(--white);
    border: 3px solid var(--gold-500);
  }
}

@media (max-width: 900px) {
  .offline__grid { grid-template-columns: minmax(0, 1fr); }
  .sign__grid { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 720px) {
  .tally__logo { width: min(100%, 200px); }
  /* The label and its line stack, which is why the label is a heading rather
     than a table cell: it reads as a statement either way. */
  .tally__row { grid-template-columns: minmax(0, 1fr); row-gap: 6px; padding: 16px 0; }
  .tally__row dt { font-size: 16px; }
  .tally__rule { margin: 24px 0 20px; }
  .tally__close { font-size: 20px; }

  .relay { padding: 24px 20px 22px; }
  .relay__stage { padding: 0 0 22px 20px; border-left-width: 4px; }
  .relay__stage--cross::before { left: -10px; width: 17px; height: 17px; }

  .offline__block, .offline__limit { padding: 20px; }
  .offline__callout { padding: 22px 20px; }
  .offline__callout p { font-size: 16px; }

  .daily__grid { grid-template-columns: minmax(0, 1fr); }
}

/* ---------------------------------------------------------- cloud extras -- */
/* cloud-services.html (2026-10-02). Copy: copy/cloud-copy.md.
   Brief: copy/brief-cloud.md.

   The spine follows that page's argument — the objections a server replacement
   produces, in the order they arrive — so it has ONE device of its own (the
   ledger) and no friction picker, stats band, compare tabs, services card grid
   or testimonial row. Nothing here restyles a shared component; every rule is
   either a new class or scoped to one of this page's own sections.

   Bands: dark hero -> white (why change) -> DARK (the ledger, the one dark
   band) -> pale (how it runs) -> white (how it's bought) -> green tint (who
   reads the license bill) -> pale (proof and the FAQ on one continuous ground)
   -> green (form).

   NO REVEAL ENTRIES, for the same reason as the voice extras above: a hidden
   start state with no matching GROUPS entry in js/reveal.js is never revealed
   and stays invisible for good, and this build was scoped not to touch
   reveal.js. The shared pieces the page reuses (.hero__copy, .stakes__copy,
   .consultation__copy, .form-card) already animate. If the ledger is ever
   given a reveal, ADD BOTH HALVES IN THE SAME CHANGE. */

/* ------------------------------------------------- a page not built yet -- */
/* A page name written as emphasis rather than a link, because the destination
   does not exist: Network Design & Management, and the Network/Servers page the
   30 September cross-link points at (whose URL is also unsettled — the live
   network-servers.html is being redirected, decision 7). A link that goes
   nowhere is the same broken promise as a menu head that looks clickable and
   isn't, so these become <a> the day those pages ship.
   Its own class rather than a bare `em`, so nothing here reaches into the
   shared .faq__answer or .consultation__sub rules.
   --green-700 is 8.71:1 on --ink-50 and 9.1:1 on white; --gold-400 is the
   band's own eyebrow colour, measured at 5.15:1 over the hex-floor texture. */
.pending-page { font-style: normal; font-weight: 700; color: var(--green-700); }
.consultation .pending-page { color: var(--gold-400); }

/* ----------------------------------------------------------- why change -- */
/* One prose column, not the two-column .stakes: the argument is a single run of
   three paragraphs about the quote on the reader's desk, and a split would
   break it.
   The device is the aside — the page's one explicit competitor observation,
   set apart on its own ground under a gold rule instead of left as a fourth
   paragraph. It keeps the body type scale exactly: it is SET APART, never
   demoted, and it is deliberately the only such block on the page, because the
   whole discipline here is that the observation is made once. */
.stakes--cloud .stakes__inner {
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
}
.stakes--cloud .stakes__copy { max-width: 78ch; gap: 30px; }
.stakes--cloud .stakes__copy .section-head { max-width: none; }
.stakes__aside {
  margin: 0;
  padding: 26px 28px;
  background: var(--ink-50);
  border-top: 2px solid var(--gold-500);
  border-radius: 0 0 12px 12px;
}
/* Same 17px/1.75 as the body paragraphs above it — equal weight, different
   ground. --ink-700 on --ink-50 is 10.9:1. */
.stakes__aside p { margin: 0; font-size: 17px; line-height: 1.75; color: var(--ink-700); }

/* == SIGNATURE — THE LEDGER. THE PAGE'S ONE DARK BAND. =====================
   Two facing columns — what usually moves, what we'd tell you to leave on site
   — sharing one gold spine, with the arithmetic underneath spanning both.

   THE SECOND COLUMN IS THE POINT; the first exists to make it credible. So the
   halves are EQUAL in every respect that carries weight: same label type, same
   body type, same body colour, same row geometry, same hairlines, same column
   width. The ONLY difference is the colour of the column head, and both clear
   8:1 on this ground (--green-400 8.14:1, --gold-500 9.3:1). Body text is
   --ink-300 on --ink-950: 11.2:1, the pairing the other dark bands use.
   --green-700, THE PAconnect green, is unusable here at 2.3:1 — the same trap
   the dark header and the footer hit. Never let a later edit shrink, grey or
   otherwise demote the right-hand column.

   IT MUST READ AS TWO HALVES OF ONE LIST. No fills, no borders, no rounded
   corners on the columns; one gold spine between them, full height, which is
   the hinge the device turns on — the same role the horizontal gold rule plays
   in the Voice page's itemized statement, turned ninety degrees. A card grid
   would make it a shopping list and a comparison table would make it a
   scorecard; it is neither.

   IT MUST NOT RESEMBLE THE EXISTING DEVICES. Backup & DR has four numbered
   cards (.restore__step), Voice a one-column statement (.tally) and a gradient
   caller line (.relay), Managed IT a three-stage marker timeline (.timeline).
   This carries no numerals, no markers, no cards and no gradient.

   The columns are `align-content: start`, so the shorter one does not stretch
   its rows; the spine is full height either way, which is what stops the right
   column reading as truncated while it has two published rows against four. */
.ledger {
  position: relative;
  background: var(--ink-950);
  color: var(--white);
  /* Same measured landing as .timeline, .ba and .restore: the header is 105px
     from 820px up. The hero's secondary button lands here.
     KEEP IN STEP with the 819px nav collapse. */
  scroll-margin-top: 105px;
}
/* The seam the other v2 signature bands carry, so this dark band meets its
   light neighbours on an on-brand line rather than a bare cut. Repeated here
   rather than added to the shared selector list above, which belongs to the
   pages that already use it. */
.ledger::before,
.ledger::after {
  content: "";
  position: absolute; left: 0; right: 0;
  height: 4px;
  background: linear-gradient(90deg, var(--ink-950) 0%, var(--green-700) 60%, var(--green-500) 100%);
}
.ledger::before { top: 0; }
.ledger::after { bottom: 0; transform: scaleX(-1); }

.ledger__inner {
  max-width: var(--shell); margin: 0 auto;
  padding: var(--section-pad);
  display: grid; gap: 38px; justify-items: start;
}

/* Two equal columns with the spine between them. `1fr 2px 1fr` rather than a
   border on one column, so the spine is a thing in its own right and neither
   half owns it. */
.ledger__cols {
  width: 100%;
  display: grid;
  grid-template-columns: minmax(0, 1fr) 2px minmax(0, 1fr);
  column-gap: 44px;
  align-items: stretch;
}
.ledger__spine { background: var(--gold-500); }
.ledger__col { display: grid; align-content: start; }
.ledger__head {
  margin: 0 0 6px;
  font-size: 12px; font-weight: 700; line-height: 1.3;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--green-400);
}
/* The only difference between the halves. Same size, same weight, same
   tracking — a different colour, nothing more. */
.ledger__col--stay .ledger__head { color: var(--gold-500); }

.ledger__rows { margin: 0; display: grid; align-content: start; }
.ledger__row {
  display: grid; gap: 7px;
  padding: 18px 0;
  border-top: 1px solid rgba(255, 255, 255, .14);
}
.ledger__row dt {
  margin: 0;
  font-family: 'Oswald', sans-serif;
  font-weight: 600; font-size: 18px; line-height: 1.2;
  text-transform: uppercase; letter-spacing: .01em;
  color: var(--white);
}
.ledger__row dd { margin: 0; font-size: 15.5px; line-height: 1.7; color: var(--ink-300); }
/* The Voice link, the only link in the band. --green-400 on ink is 8.14:1. */
.ledger__row a { color: var(--green-400); text-decoration: underline; text-underline-offset: 2px; }
.ledger__row a:hover { color: var(--green-300); }

/* The arithmetic, spanning both columns: the line between them is a
   calculation, not a philosophy. It sits under the whole device, so it reads as
   the ledger's own footing rather than as a third column. */
.ledger__math {
  width: 100%; max-width: 86ch;
  display: grid; gap: 16px;
  padding-top: 4px;
}
.ledger__math p { margin: 0; font-size: 16.5px; line-height: 1.75; color: var(--ink-300); }

/* --------------------------------------------------------- how it runs -- */
/* Three stages STACKED, each a label in a left rail with the prose beside it —
   not three across. Managed IT already has a three-stage timeline with numbered
   markers on a line, and a row of three here would have read as the same
   component. No numerals anywhere: the copy doc numbers the stages, which is
   its own table numbering, and this page carries no numeral at all.

   The device is the lighting rather than the count. The first two stages are
   quiet — flat, on the section's own ground — and the third is lit: white, with
   a gold rule down its rail. That is the H2 ("only the last one your staff
   notice") said in one picture, and it costs no copy to say. */
.runs {
  background: var(--ink-50);
  scroll-margin-top: 105px;
}
.runs__inner {
  max-width: var(--shell); margin: 0 auto;
  padding: var(--section-pad);
  display: grid; gap: 34px;
}
.runs__stages {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: 2px;
  max-width: 96ch;
}
.runs__stage {
  display: grid;
  grid-template-columns: minmax(0, 210px) minmax(0, 1fr);
  column-gap: 36px;
  padding: 24px 26px;
  border-left: 3px solid var(--ink-200);
  background: transparent;
}
.runs__label {
  margin: 0;
  font-family: 'Oswald', sans-serif;
  font-weight: 600; font-size: 20px; line-height: 1.15;
  text-transform: uppercase; letter-spacing: .01em;
  color: var(--ink-900);
}
.runs__stage p { margin: 0; font-size: 16px; line-height: 1.7; color: var(--ink-700); }
/* The one stage anybody in the building experiences, so it is the one that is
   lit. --ink-900 and --ink-700 both pass comfortably on white as well as on
   --ink-50, so nothing in the type changes — only the ground and the rail. */
.runs__stage--felt {
  background: var(--white);
  border-left-color: var(--gold-500);
  border-radius: 0 12px 12px 0;
  box-shadow: 0 12px 30px rgba(10, 12, 10, .06);
}
/* The honest paragraph, carrying a hairline so it reads as the section's
   conclusion rather than a fourth stage. */
.runs__honest {
  margin: 0; max-width: 84ch;
  padding-top: 22px;
  border-top: 1px solid var(--ink-200);
  font-size: 17px; line-height: 1.75; color: var(--ink-700);
}

/* ------------------------------------------------------ how it's bought -- */
/* Three short facts, and the copy doc is explicit: NOT A GRID. One column,
   each fact opening with its own sentence in bold. A grid here would turn
   three plain facts into a feature matrix, which is the register the whole page
   exists to avoid. */
.bought {
  background: var(--white);
  scroll-margin-top: 105px;
}
.bought__inner {
  max-width: var(--shell); margin: 0 auto;
  padding: var(--section-pad);
  display: grid; gap: 30px;
}
.bought__facts { display: grid; gap: 26px; max-width: 82ch; }
.bought__facts p { margin: 0; font-size: 17px; line-height: 1.75; color: var(--ink-700); }
.bought__facts strong { color: var(--ink-900); }
.bought__facts a { color: var(--green-700); font-weight: 600; }

/* ------------------------------------------- who reads the license bill -- */
/* The page's second argument gets its own ground, so it is not read as a
   footnote to the section before it. The green tint is the token, not the
   literal the switchover band uses.
   THE CLOSING LINE IS LOAD-BEARING: it is where a weaker page would promise a
   saving. It is set as the site's display-close device (the one .tally__close
   and .restore__close use on the dark bands) because the sentence is the
   section's conclusion, not a caption. See the markup comment before changing
   anything in here — the prohibition on savings claims is absolute. */
.review {
  background: var(--green-50);
  scroll-margin-top: 105px;
}
.review__inner {
  max-width: var(--shell); margin: 0 auto;
  padding: var(--section-pad);
  display: grid; gap: 30px;
}
.review__body { display: grid; gap: 22px; max-width: 82ch; }
.review__body p { margin: 0; font-size: 17px; line-height: 1.75; color: var(--ink-700); }
.review__close {
  margin: 0; max-width: 72ch;
  padding-left: 18px;
  border-left: 3px solid var(--green-700);
  font-family: 'Oswald', sans-serif;
  font-size: 24px; font-weight: 500; line-height: 1.3;
  letter-spacing: .01em;
  color: var(--ink-900);
}

/* ------------------------------------------- cloud extras breakpoints -- */

@media (max-width: 819px) {
  .ledger, .runs, .bought, .review { scroll-margin-top: 109px; }
}

@media (max-width: 1024px) {
  .ledger__cols { column-gap: 32px; }
  .runs__stage { grid-template-columns: minmax(0, 170px) minmax(0, 1fr); column-gap: 26px; }
}

/* 900, not 720: below this the two ledger columns are about 300px each, which
   is roughly forty characters a line for a label that can run to five words.
   The device does not survive as two cramped columns, so it stacks while they
   are still readable. */
@media (max-width: 900px) {
  /* THE LEDGER STACKS, AND THE SPINE TURNS HORIZONTAL.

     This is the half of the device most likely to go wrong, because the point
     of the whole section is the SECOND column — and a stacked second column is
     exactly the thing that ends up reading as an afterthought at the bottom.
     Three things stop that here:
       1. the spine survives, as a full-width gold rule between the halves, so
          the second half arrives after the device's own hinge rather than
          trailing off the end of the first;
       2. both heads keep their size, tracking and weight, and both halves keep
          the same row geometry and body type — nothing is reduced;
       3. the arithmetic paragraph still sits under BOTH, which is what holds
          them together as one list.
     Do not "tidy" this by hiding the spine or shrinking the second head. */
  .ledger__cols {
    grid-template-columns: minmax(0, 1fr);
    row-gap: 30px;
  }
  .ledger__spine { height: 2px; width: 100%; }
}

@media (max-width: 720px) {
  .ledger__inner { gap: 30px; }
  .ledger__row { padding: 16px 0; }
  .ledger__row dt { font-size: 17px; }
  .ledger__math p { font-size: 16px; }

  /* The rail label stacks above its prose. It is a heading either way, so the
     reading order and the screen-reader experience do not change. */
  .runs__stage {
    grid-template-columns: minmax(0, 1fr);
    row-gap: 8px;
    padding: 20px 20px 22px;
  }
  .runs__label { font-size: 18px; }
  .runs__honest { font-size: 16px; }

  .stakes__aside { padding: 22px 20px; }
  .stakes__aside p { font-size: 16px; }
  .bought__facts p { font-size: 16px; }
  .review__body p { font-size: 16px; }
  .review__close { font-size: 20px; }
}

/* ══════════════════════════════════════════════════════════════════════════
   THE HEX-CELL DIAGRAM SYSTEM — first piece, 12 October 2026.

   PK's ask: a consistent iconography that can carry an argument where a
   photograph cannot. Built from the hexagon the brand already owns in
   hex-horizon and the van wrap, so it extends the existing visual language
   rather than sitting beside a second one.

   THE RULE THAT KEEPS IT HONEST, and it is the whole point: a diagram goes in
   only where it SAYS something the copy cannot show. The outage diagram earns
   its place because "what happens when the internet goes down" is a mechanism,
   and mechanisms draw well and photograph badly. A list does not get one.
   If a second section gets a diagram and it feels unnecessary, it is — take
   it out rather than keep it for consistency.

   Three states, and they are the system:
     --live   something working      green
     --dead   something not working  ink, desaturated
     break    the point of failure   gold
   ══════════════════════════════════════════════════════════════════════════ */

/* Margins are tighter than they were (34/40) because the viewBox crop gave
   back the ~50px above and ~42px below that used to be empty space inside
   the SVG. PK flagged the gap on both sides, 18 Oct. */
.dg { margin: 16px 0 18px; }
.dg svg { display: block; width: 100%; height: auto; }

/* ── cells ─────────────────────────────────────────────────────────────── */
.dg-cell { fill: none; stroke-width: 2.4; }
.dg-cell--live { stroke: var(--green-600, #1c7a2a); }
.dg-cell--fill { fill: rgba(46, 158, 60, .10); }
.dg-cell--dead { stroke: var(--ink-400, #8b948c); stroke-dasharray: 5 4; }

/* ── links between cells ───────────────────────────────────────────────── */
.dg-link { fill: none; stroke-width: 3; stroke-linecap: round; }
.dg-link--live { stroke: var(--green-600, #1c7a2a); }
.dg-link--dead { stroke: var(--ink-300, #b6bdb7); stroke-dasharray: 6 6; }
.dg-arrowhead { fill: var(--green-600, #1c7a2a); }
.dg-runner { fill: var(--green-600, #1c7a2a); offset-rotate: 0deg; }

/* ── the break: gold, because gold is the palette's attention colour and
      this is a warning rather than an error ───────────────────────────── */
.dg-break line { stroke: var(--gold-700, #b87a0c); stroke-width: 3.5; stroke-linecap: round; }

/* ── glyphs inside the cells ───────────────────────────────────────────── */
.dg-glyph { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.dg-glyph--live { fill: var(--green-600, #1c7a2a); stroke: none; }
.dg-glyph--on   { stroke: var(--green-600, #1c7a2a); fill: none; }
.dg-glyph--dead { stroke: var(--ink-400, #8b948c); fill: none; }

/* ── type. Oswald for the one heading, Open Sans for everything read ───── */
.dg-head {
  font-family: 'Oswald', sans-serif; font-size: 13px; font-weight: 600;
  letter-spacing: .1em; fill: var(--green-700, #14571e);
}
.dg-label { font-family: 'Open Sans', sans-serif; font-size: 14px; fill: var(--ink-800, #222823); }
.dg-label--dead { fill: var(--ink-500, #6f786f); }
.dg-sub { font-family: 'Open Sans', sans-serif; font-size: 12.5px; fill: var(--ink-600, #5a635a); }
.dg-break-label {
  font-family: 'Open Sans', sans-serif; font-size: 12px; font-weight: 700;
  letter-spacing: .04em; fill: var(--gold-700, #b87a0c); text-transform: uppercase;
}
.dg-mini { font-family: 'Open Sans', sans-serif; font-size: 11.5px; fill: var(--ink-600, #5a635a); text-anchor: middle; }
.dg-mini--on { fill: var(--green-700, #14571e); font-weight: 700; }

.dg figcaption {
  margin-top: 16px; font-size: 14.5px; line-height: 1.6;
  color: var(--ink-600); max-width: 76ch;
}

/* ── MOTION ───────────────────────────────────────────────────────────────
   The diagram plays the outage rather than illustrating its aftermath. Each
   element carries its own cue as an inline `--d`, and the timeline is written
   out in a comment on the figure in business-phone-systems.html.

   ⚠ DELAYS ARE A CUSTOM PROPERTY, NOT DELAY CLASSES. The first build used
   .a1–.a9 carrying `animation-delay: Nms !important`, which beat the inline
   delay on the one element that needed its own, and the break beat silently
   did not play. A var() has no specificity to fight.

   ⚠ NEVER PUT TWO OF THESE CLASSES ON ONE ELEMENT. Each sets the `animation`
   SHORTHAND, so the later rule REPLACES the earlier rather than adding to it
   — `dg-draw` plus `dg-dim` on one path cost the draw entirely and the fault
   was invisible in the end state. Wrap instead: the fade goes on a parent
   <g>, the draw on the child.

   KEEP IN STEP with the .dg entry in the GROUPS table in js/reveal.js. The
   hidden states are gated on .js-reveal so a visitor without it — including
   anyone who prefers reduced motion — gets the finished drawing. */
/* ⚠ WITHOUT JS THE PAGE MUST SHOW THE END STATE, NOT THE OPENING ONE.
   Three things exist only so they can be destroyed on screen — the live
   upper path, the lit desk phones, and the call travelling to them. They are
   therefore hidden by DEFAULT and switched on only for .js-reveal, so a
   visitor with no JS (or who prefers reduced motion, who never gets the
   class) sees the finished picture: broken path, dark desk phones. Without
   this, both the live and the dead copy render stacked on top of each other.
   The travelling call and the ring pulses stay hidden there too — a parked
   dot in a static drawing means nothing. */
.dg-dim, .dg-runner, .dg-ring { opacity: 0; }
.js-reveal .dg-dim { opacity: 1; }
.js-reveal .dg-stage,
.js-reveal .dg-pop { opacity: 0; }
.js-reveal .dg-draw { stroke-dasharray: 600; stroke-dashoffset: 600; }

.dg-pop, .dg-break { transform-box: fill-box; transform-origin: 50% 50%; }
.dg-ring { transform-box: fill-box; transform-origin: 50% 50%; }

.dg.is-in .dg-stage  { animation: dgFade  420ms ease-out var(--d, 0ms) forwards; }
.dg.is-in .dg-draw   { animation: dgDraw  560ms ease-out var(--d, 0ms) forwards; }
.dg.is-in .dg-pop    { animation: dgPop   460ms cubic-bezier(.2,.9,.3,1) var(--d, 0ms) forwards; }
.dg.is-in .dg-dim    { animation: dgDim   420ms ease-out var(--d, 0ms) forwards; }
.dg.is-in .dg-ring   { animation: dgRing 1400ms ease-out var(--d, 0ms) infinite; }
.dg.is-in .dg-runner { animation: dgRun   900ms cubic-bezier(.4,0,.5,1) var(--d, 0ms) forwards; }

@keyframes dgFade { to { opacity: 1; } }
@keyframes dgDraw { to { stroke-dashoffset: 0; } }
@keyframes dgDim  { to { opacity: 0; } }
@keyframes dgPop  { from { opacity: 0; transform: scale(.55); } to { opacity: 1; transform: scale(1); } }
@keyframes dgRing { from { opacity: .7; transform: scale(.35); } to { opacity: 0; transform: scale(1.9); } }
@keyframes dgRun  { from { opacity: 1; offset-distance: 0%; } to { opacity: 1; offset-distance: 100%; } }

@media (prefers-reduced-motion: reduce) {
  .dg.is-in .dg-stage, .dg.is-in .dg-draw, .dg.is-in .dg-pop,
  .dg.is-in .dg-dim, .dg.is-in .dg-ring, .dg.is-in .dg-runner {
    animation-duration: 1ms; animation-delay: 0ms; animation-iteration-count: 1;
  }
}

/* On a phone the horizontal flow is unreadable at 340px wide, so it scrolls
   rather than shrinking into illegibility. The caption carries the argument
   for anyone who does not scroll it. */
@media (max-width: 720px) {
  .dg { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .dg svg { min-width: 640px; }
}

/* ══════════════════════════════════════════════════════════════════════════
   "WATCH IT IN A MINUTE" — added 30 Oct 2026 (Explainers sub-session 1).
   The same outage story as the diagram above, narrated by PK, in about a
   minute. OPT-IN: the diagram stays the default, and nothing for the player
   (js/phone-explainer.js, media/phone-explainer.mp3, the Anton face) loads
   until the button is pressed. The player then takes the diagram's place;
   "Close and show the diagram" puts it back.

   Only the frame, the buttons and the focus rings are the site's. INSIDE the
   frame the player keeps its own editorial-collage look on purpose (PK likes
   an occasional change of look), so the colors below the stage are its own.
   Source and decisions: prototypes/voice-explainer-vox/index.html.
   ══════════════════════════════════════════════════════════════════════════ */
/* Pulled up toward the caption it belongs to; the grid gap alone left it
   floating halfway to the next block. */
.dg-watch {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px 16px;
  margin-top: -18px;
}
.dg-watch[hidden] { display: none; }
.dg-watch .btn { display: inline-flex; align-items: center; gap: 10px; padding: 13px 24px; }
.dg-watch .btn svg { width: 13px; height: 13px; flex: none; }
.dg-watch__note { font-size: 14px; color: var(--ink-600); }

/* Capped at 960 so the whole player, controls included, fits a laptop
   screen under the sticky header. */
.explainer {
  width: 100%; max-width: 960px; justify-self: center;
  display: grid; gap: 12px;
  scroll-margin-top: 110px;
}
.explainer[hidden] { display: none; }
.explainer__top {
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between;
  gap: 6px 16px;
}
.explainer__label { margin: 0; font-size: 14px; font-weight: 700; color: var(--ink-600); }

.explainer__frame {
  border-radius: 12px; overflow: hidden;
  background: #151714; box-shadow: 0 14px 44px rgba(20, 18, 12, .28);
}
.explainer__stage { position: relative; aspect-ratio: 16 / 9; background: #151714; overflow: hidden; }
.explainer__stage > svg { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
/* Paper grain over everything; the tile is generated by the script. */
.explainer__grain {
  position: absolute; inset: 0; pointer-events: none;
  mix-blend-mode: multiply; opacity: .55; background-size: 220px;
}
.explainer__vignette {
  position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(ellipse at center, rgba(0, 0, 0, 0) 58%, rgba(40, 30, 10, .22) 100%);
}
/* Shown only if the browser refuses to start the sound without another
   click (some do, the first time). */
.explainer__poster {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; gap: 12px; align-items: center; justify-content: center;
  background: rgba(10, 12, 10, .35); border: 0; font: inherit; cursor: pointer;
}
.explainer__poster[hidden] { display: none; }
.explainer__poster-btn {
  width: 84px; height: 84px; border-radius: 50%; background: var(--gold-500);
  display: grid; place-items: center; box-shadow: 0 8px 30px rgba(0, 0, 0, .4);
}
.explainer__poster-btn svg { width: 30px; height: 30px; margin-left: 5px; }
.explainer__poster-lbl {
  background: #151714; color: var(--white); font-weight: 700; font-size: 14px;
  padding: 6px 14px; border-radius: 20px;
}

/* Captions in a strip under the picture: overlaid, they would sit on the
   buried line exactly where the cut happens. Always on. */
.explainer__cap {
  min-height: 78px; display: flex; align-items: center; justify-content: center;
  text-align: center; background: #151714; color: var(--white);
  padding: 12px 28px; font-size: clamp(14px, 1.8vw, 19px); line-height: 1.45; font-weight: 600;
}
.explainer__cap span { opacity: 0; transition: opacity .25s; max-width: 820px; }
.explainer__cap.is-on span { opacity: 1; }

.explainer__bar {
  display: flex; align-items: center; gap: 14px;
  padding: 12px 16px; background: var(--ink-950); color: var(--white); font-size: 13px;
}
.explainer__bar .btn { flex: none; padding: 8px 16px; font-size: 13px; }
.explainer__track {
  flex: 1; min-width: 60px; height: 6px; position: relative; cursor: pointer;
  background: rgba(255, 255, 255, .18); border-radius: 3px;
}
/* A taller hit area than the 6px bar shows, for fingers. */
.explainer__track::before { content: ''; position: absolute; inset: -12px 0; }
.explainer__fill {
  position: absolute; left: 0; top: 0; bottom: 0; width: 0;
  background: var(--gold-500); border-radius: 3px;
}
.explainer__time { font-variant-numeric: tabular-nums; color: rgba(255, 255, 255, .78); white-space: nowrap; }

@media (max-width: 600px) {
  .dg-watch { margin-top: -10px; }
  .explainer__cap { min-height: 64px; padding: 10px 14px; font-size: 14px; }
  .explainer__bar { gap: 8px; padding: 10px; }
  .explainer__bar .btn { padding: 7px 12px; }
  .explainer__time { font-size: 12px; }
  .explainer__poster-btn { width: 60px; height: 60px; }
}


/* ══════════════════════════════════════════════════════════════════════════
   NETWORK DESIGN PAGE — added 2026-10-14 with network-design.html.
   Four blocks: a plain section wrapper, the head-end dark band (the page's
   one device), the multi-site option list, and a three-up variant of .daily.
   ══════════════════════════════════════════════════════════════════════════ */

/* Three findings rather than .daily's four. The grid is otherwise identical,
   so the gold edge and green label come along unchanged. */
.daily__grid--three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
/* Two items in the base four-column grid render as a pair of tall narrow
   columns (PK spotted it on video-surveillance.html, 16 Oct). Same family as
   --three. */
.daily__grid--two { grid-template-columns: repeat(2, minmax(0, 1fr)); }

/* ---------------------------------------------------- plain section ------ */
/* The wireless section needs a wrapper and nothing else; every existing
   section class on this page is already carrying a background of its own. */
.nw { background: var(--white); scroll-margin-top: 105px; }
/* TWO COLUMNS, copy left and the image right (PK, 2026-10-14). It was
   stacked, which left a column of dead white beside the copy and pushed the
   image into its own band. His note, and it is a straight graphic-design
   call: put it "on the right-hand side basically almost balancing out the
   exact section to the left where the copy is". */
.nw__inner {
  max-width: var(--shell); margin: 0 auto;
  padding: var(--section-pad);
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 0.9fr);
  gap: 48px; align-items: center;
}
.nw__inner .section-head { margin: 0; }
/* The slot fills its column here rather than sitting at its own max-width. */
.nw__inner .slot { max-width: none; }
.nw__fig { margin: 0; }
.nw__fig img {
  display: block; width: 100%; max-width: 760px; height: auto;
  border: 1px solid var(--ink-200); border-radius: 4px;
}
.nw__fig figcaption {
  margin-top: 12px; max-width: 72ch;
  font-size: 14px; line-height: 1.6; color: var(--ink-600);
}

/* ---------------------------------------------- the head end (device) ---- */
/* THE PAGE'S ONE DARK BAND. Same ground as every other dark band on the site.
   Accents follow the standing rule for ink: --green-700 is unusable here at
   2.3:1, so the eyebrow is sand/gold and any green is --green-400. */
.he { background: var(--ink-950); color: var(--ink-100); scroll-margin-top: 105px; }
.he__inner {
  max-width: var(--shell); margin: 0 auto;
  padding: var(--section-pad);
  display: grid; gap: 34px;
}
.he__body {
  display: grid; grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  gap: 48px; align-items: start;
}
.he__copy p {
  margin: 0 0 16px; max-width: 54ch;
  font-size: 16px; line-height: 1.7; color: rgba(255, 255, 255, .82);
}
.he__copy p:last-child { margin-bottom: 0; }
.he__note {
  padding-top: 14px; border-top: 1px solid rgba(255, 255, 255, .14);
  font-size: 14.5px !important; color: rgba(255, 255, 255, .62) !important;
}
.he__fig { margin: 0; min-width: 0; }
/* The SVG carries its own colours — they are real cable colours, not design
   tokens, and must not be "corrected" to the palette. */
.he__svg {
  display: block; width: 100%; height: auto;
  background: #11150f; border: 1px solid #2b322d; border-radius: 4px;
}
.he__fig figcaption {
  margin-top: 13px; max-width: 60ch;
  font-size: 14px; line-height: 1.6; color: rgba(255, 255, 255, .62);
}

/* ══ THE HEAD-END PANEL: PAIRING A LABEL TO ITS PORTS ═══════════════════
   Added 2026-10-19, PK's idea. Each legend row owns one to three ports;
   selecting a row lights the row, its ports and their numbers together, so
   the color code reads as a code rather than as decoration. Driven by
   js/head-end.js, which reads nothing but the data-run attributes.

   ⚠ NOTHING IS EVER RECOLORED. The port colors are real cable colors (see
   the note on .he__svg above) and must not be shifted toward the palette.
   A run is highlighted by DIMMING EVERYTHING ELSE and adding a white
   keyline — the only treatment that works on both the near-black camera run
   and the white phone run, where brightening does nothing at all.

   ⚠ REST STATE IS THE FINISHED PANEL. Everything here is additive: with no
   JavaScript, or with reduced motion (which never gets .js-reveal, so the
   script returns early), the visitor sees the panel exactly as it was before
   any of this existed. The grey opening state is applied BY SCRIPT and
   removed; building it as the default would hide real content.

   Two tunables, both here:
     --hx-step    dwell per run; seven runs, so x7 is the whole walk
     --hx-dim     how far everything else drops while a run is selected */
.hx { --hx-step: 820ms; --hx-fade: 240ms; --hx-dim: .22; --hx-reveal: 520ms; }
.hx-item { transition: opacity var(--hx-fade) ease; }
.hx-ring { opacity: 0; transition: opacity var(--hx-fade) ease; }
.hx-num  { transition: fill var(--hx-fade) ease; }

.hx.is-lit .hx-item       { opacity: var(--hx-dim); }
.hx.is-lit .hx-item.is-on { opacity: 1; }
.hx.is-lit .hx-ring.is-on { opacity: 1; }
.hx.is-lit .hx-num.is-on  { fill: #ffffff; font-weight: 700; }

/* the legend rows are the control surface, and they are keyboard reachable
   so the pairing is not mouse-only */
.hx-hit { fill: transparent; cursor: pointer; }
.hx--explore .hx-hit:focus-visible { fill: rgba(255, 255, 255, .07); outline: none; }
.hx-hint { font-family: 'Open Sans', sans-serif; font-size: 9.5px; fill: #6f7a74; font-style: italic; }

/* THE OPENING STATE: twelve identical cables, no numbers, no legend. It is
   there so the reader can VALUE the finished panel — the copy beside it
   promises "a fault found in two minutes and a fault found in forty", and
   the pairing alone only ever shows the tidy end state. */
.hx-port, .hx-swatch { transition: fill var(--hx-reveal) ease; }
.hx-legend, .hx-num  { transition: opacity var(--hx-reveal) ease; }
.hx.is-raw .hx-port,
.hx.is-raw .hx-swatch { fill: #474f49; }
.hx.is-raw .hx-num,
.hx.is-raw .hx-legend { opacity: 0; }
.hx-raw-note {
  font-family: 'Open Sans', sans-serif; font-size: 10.5px; fill: #8d968f;
  opacity: 0; transition: opacity 300ms ease;
}
.hx.is-raw .hx-raw-note { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .hx-item, .hx-ring, .hx-num, .hx-port, .hx-swatch, .hx-legend, .hx-raw-note { transition: none; }
}

/* ------------------------------------------------- multi-site options ---- */
/* THE TRADE-OFF LINE IS THE POINT OF THIS COMPONENT. Sebek volunteered the
   downsides and printing them is the most persuasive thing on the page, so
   the trade-off gets its own treatment rather than trailing the paragraph as
   ordinary text. Gold label on a hairline, which is the same green-label /
   gold-rule pairing the site uses one level up. */
.opt {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 24px;
  counter-reset: opt;
}
/* The base is three because the network page has three options. Two items in
   a three-column grid leave an orphan gap, and four wrap one item onto a row
   of its own. Both turned up on the security pages (PK, 16 Oct), so the count
   gets a modifier the same way .daily__grid does. */
.opt--two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.opt--four { grid-template-columns: repeat(2, minmax(0, 1fr)); }
/* THE LIFT, same as the .asks panel on the backup page (PK, 22 Oct: the
   Cyber Security cards "don't have the drop shadow treatment"). The line
   defines the edge; the shadow lifts the card off the green ground. Applies
   to every .opt card, so the access control, surveillance and network pages
   get it too, and they all match. */
.opt__item {
  background: var(--white);
  border: 1px solid #c8cfca; border-radius: 4px;
  box-shadow: 0 18px 40px rgba(10, 12, 10, .12);
  padding: 22px 22px 20px;
  display: flex; flex-direction: column; gap: 10px;
}
/* 20px, not 18 (PK, 16 Oct). Same face, weight and colour as .daily__label,
   but these stay SENTENCE CASE because they are questions and uppercase
   questions scan badly. Sentence case reads smaller at a matched size, so the
   extra 2px is what makes the two devices feel equal. */
.opt__name {
  margin: 0;
  font-family: 'Oswald', sans-serif; font-weight: 600;
  font-size: 20px; line-height: 1.25; letter-spacing: .01em;
  color: var(--green-700);
}
.opt__item p { margin: 0; font-size: 15px; line-height: 1.6; color: var(--ink-600); }
.opt__trade {
  margin-top: auto !important; padding-top: 14px;
  border-top: 1px solid var(--ink-200);
  color: var(--ink-700) !important;
}
.opt__trade span {
  display: block; margin-bottom: 4px;
  font-size: 11px; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--gold-700);
}

/* ------------------------------------------------------- claim-check ----- */
/* A second home for the marker team.css already defines, because this page
   does not load team.css. Identical declarations on purpose. If a third page
   needs it, move the rule to styles.css and delete both copies.
   Purpose: make a provisional claim VISIBLE, because a reviewer cannot
   question a sentence that reads as finished. */
.claim-check {
  --cc: #8c2b1e;
  position: relative; border-left: 3px solid var(--cc); padding-left: 14px;
}
.claim-check::before {
  content: 'CHECK — ' attr(data-claim);
  display: block; margin-bottom: 8px;
  font: 700 11px/1.45 'Open Sans', sans-serif; letter-spacing: .04em;
  text-transform: uppercase; color: var(--cc);
}
/* On the dark bands the ink-ground red is unreadable (same trap as
   --green-700 on ink). Lift it; #e8755c measures 6.1:1 on --ink-950. */
.consultation .claim-check,
.he .claim-check { --cc: #e8755c; }

@media (max-width: 900px) {
  .nw__inner { grid-template-columns: 1fr; gap: 28px; }
  .he__body { grid-template-columns: 1fr; gap: 30px; }
  .opt { grid-template-columns: 1fr; }
  .daily__grid--three, .daily__grid--two { grid-template-columns: 1fr; }
}


/* ── network page: images and empty photo slots ────────────────────────────
   AN EMPTY SLOT IS VISIBLE, NOT COMMENTED OUT (PK, 2026-10-14). His reason,
   and it is right: "I will never know where that's supposed to be placed if I
   don't see it on the page. Commenting it out does not help me at all."
   Same family as .is-scamp in team.css — loud on purpose, delete the markup
   rather than the rule. */
.slot {
  display: grid; place-items: center;
  aspect-ratio: 16 / 9; width: 100%; max-width: 560px;
  background: var(--ink-100);
  border: 3px dashed var(--gold-700); border-radius: 4px;
  text-align: center; padding: 20px;
}
.he .slot { background: #171c18; border-color: var(--gold-500); }
.slot__label {
  font: 700 12px/1.3 'Open Sans', sans-serif;
  letter-spacing: .12em; text-transform: uppercase; color: var(--gold-700);
}
.he .slot__label { color: var(--gold-500); }
.slot__what { margin: 8px 0 0; font-size: 15px; line-height: 1.5; color: var(--ink-700); max-width: 42ch; }
.he .slot__what { color: rgba(255,255,255,.75); }
.slot__spec {
  margin: 10px 0 0; font: 400 12.5px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--ink-600);
}
.he .slot__spec { color: rgba(255,255,255,.5); }

/* SMALL AND TREATED, NOT FULL BLEED (PK, 2026-10-14). The first pass put
   full-width square photographs on the page and he was right that they read as
   dumped there: "that is not what I am going for... that should be three
   hundred pixels wide". These borrow the hero cards geometry exactly — 4:3,
   14px radius, soft drop — so an inset reads as the same family as the hero
   rather than as a different kind of object. */
.shots { display: flex; flex-wrap: wrap; gap: 18px; }
.shot { margin: 0; width: 300px; max-width: 100%; }
.shot img {
  display: block; width: 100%; height: 100%;
  aspect-ratio: 4 / 3; object-fit: cover;
  border-radius: 14px;
  box-shadow: 0 16px 34px rgba(10, 12, 10, .18);
}
.he .shot img {
  box-shadow: 0 24px 50px rgba(0, 0, 0, .45);
  outline: 1px solid rgba(255, 255, 255, .16); outline-offset: -1px;
}
.shot figcaption {
  margin-top: 10px; font-size: 13px; line-height: 1.5; color: var(--ink-600);
}
.he .shot figcaption { color: rgba(255, 255, 255, .6); }

/* The empty slot matches the cards it sits beside. */
.slot--mini { width: 300px; max-width: 100%; aspect-ratio: 4 / 3; border-radius: 14px; padding: 16px; }
.slot--mini .slot__what { font-size: 13.5px; }
.slot--mini .slot__spec { font-size: 11px; }



@media (max-width: 900px) {
  .he__proof { grid-template-columns: 1fr; gap: 24px; }
}

/* A real photograph standing in for the one we actually want. Same family as
   .is-scamp and .slot — loud on purpose; delete the markup, not the rule. */
.is-temp { position: relative; }
.is-temp img { outline: 3px dashed var(--gold-700); outline-offset: -3px; }
.is-temp::after {
  content: 'TEMPORARY — shows the kind of shot, not the shot';
  position: absolute; top: 10px; left: 10px;
  background: var(--gold-700); color: #fff;
  font: 700 11px/1 'Open Sans', sans-serif; letter-spacing: .08em;
  text-transform: uppercase; padding: 6px 9px; border-radius: 3px;
}


/* ── network page: a section head with one figure beside it ────────────────
   PK, 2026-10-15: images stacked above a section leave a column of dead
   space next to the heading. Head left, one figure right, both centred on the
   same line. Width is per-instance via --figw so a section can take a small
   shot or a medium one without new classes.
   "Small is fine with me. We can always make things larger." */
.withfig {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, var(--figw, 300px));
  gap: 44px; align-items: center;
}
.withfig .section-head { margin: 0; }
.withfig .shot { width: 100%; }
/* The row sits directly above the section's content, so it keeps its own
   bottom spacing tight rather than inheriting the grid's full gap. */
.daily__inner .withfig,
.decide__inner .withfig { margin-bottom: -6px; }

@media (max-width: 860px) {
  .withfig { grid-template-columns: 1fr; gap: 26px; }
  .withfig .shot { max-width: 360px; }
}


/* ── pending section: an ANSWER we are waiting on ──────────────────────────
   Same family as .slot, .is-temp and .is-scamp — loud on purpose, and the
   markup gets deleted rather than the rule.

   .slot marks a missing PHOTOGRAPH. This marks a missing ANSWER: a section
   whose argument we know we want and cannot yet write, because the person who
   does the work has not been asked or has not replied.

   WHY IT EXISTS (PK, 2026-10-16). The team gives better input looking at a
   built page than at a questionnaire — "once it's in front of me in HTML
   format, where it's something solid to look at as opposed to questions and
   answers in a document, I'll get more input from the guys. It's a visual
   thing with us." A hole that is VISIBLE on a rendered page gets answered
   faster than a question in an inbox, because reacting costs the reviewer
   less than composing. So a thin page is not a reason to delay showing it —
   it is the reason to show it.

   ⚠ NEVER SHIPS. The pre-launch check must fail on any .pending left in
   site/, the same way it fails on a SCAMP- image reference. */
.pending {
  border: 3px dashed var(--gold-700); border-radius: 4px;
  background: var(--ink-100);
  padding: 20px 22px; margin: 18px 0;
}
.pending__label {
  display: block; margin-bottom: 8px;
  font: 800 11px/1 'Open Sans', sans-serif; letter-spacing: .1em;
  text-transform: uppercase; color: var(--gold-700);
}
.pending__q {
  margin: 0; font-size: 17px; line-height: 1.45; font-weight: 700;
  color: var(--ink-900); max-width: 52ch;
}
.pending__why {
  margin: 10px 0 0; font-size: 14px; line-height: 1.55;
  color: var(--ink-700); max-width: 56ch;
}
.he .pending { background: #171c18; border-color: var(--gold-500); }
.he .pending__label { color: var(--gold-500); }
.he .pending__q { color: #fff; }
.he .pending__why { color: rgba(255,255,255,.75); }


/* ── .xlink: a trailing cross-link paragraph ───────────────────────────────
   The 14 Oct meeting asked for more crossover between service pages, and
   until now each page improvised its own markup for the sentence that hands
   the reader to a neighbouring page. This is that sentence: it sits last in
   a section, set apart from the body above it.
   Used by video-surveillance.html and access-control.html, which are a pair
   and link to each other in both directions. */
.xlink {
  margin: 4px 0 0; padding-top: 18px;
  border-top: 1px solid var(--rule, #d8ddd9);
  font-size: 16px; line-height: 1.6; color: var(--ink-700);
  max-width: 68ch;
}
.xlink a { font-weight: 700; white-space: nowrap; }


/* ── .he--wide: the device runs wide with the copy beneath it ──────────────
   video-surveillance.html, PK 16 Oct. The default .he__body puts the figure
   in a narrow column beside the prose, which suits an upright device like the
   network page's head end. The recording drive is a LANDSCAPE composition —
   object left, readout right — so a narrow column rendered a wide drawing
   small and the machining rings and specular sweep stopped registering.

   CAPPED AT 880, NOT FULL BLEED, AND THE CAP IS THE WHOLE POINT. The viewBox
   is 440 wide, so the band's full 1100px would scale it ~2.5x and render the
   counter at about 105px against a 30px heading — the diagram would stop
   supporting the argument and become it. At 880 it scales 2x, the counter
   lands near 84px, and every detail reads.

   The copy sets in two columns underneath so the band does not finish on one
   long measure of text under a wide graphic, which is where this layout sags.
   Measured: 630px tall, so it clears a 1440x900 laptop in a single screen. */
.he--wide .he__body { display: block; }
.he--wide .he__fig { max-width: 880px; }
.he--wide .section-head .rule { margin-bottom: 8px; }

/* The summary line is HTML, not SVG text. It used to be drawn inside the
   viewBox along with a divider rule, which cost 46 of 212 viewBox units —
   about 90px of height at 2x scale, spent on one sentence. As markup it is
   selectable, translatable, and it scales with the type system rather than
   with the drawing. Do not move it back into the SVG. */
.he__kicker {
  max-width: 880px; margin: 16px 0 0; padding-top: 14px;
  border-top: 1px solid #2f3733;
  font-family: 'Open Sans', sans-serif; font-size: 16px; font-weight: 800;
  color: var(--gold-400);
}
.he--wide .he__copy {
  margin-top: 9px; max-width: 980px;
  column-count: 2; column-gap: 44px;
}
.he--wide .he__copy p { break-inside: avoid; }

@media (max-width: 900px) {
  .he--wide .he__copy { column-count: 1; }
}

/* This band runs tighter than the site rhythm ON PURPOSE (PK, 16 Oct). The
   goal is the whole device plus its copy in one screen, and at the standard
   96px section padding the section measures 890px — past a 1440x900 laptop
   once the sticky header is allowed for. 64/60 brings it to roughly 820.
   It is the only section on the site that does this, and the reason is that
   it is the only one whose argument is carried by a picture you have to see
   whole. */
.he--wide .he__inner { padding-top: 64px; padding-bottom: 60px; }


/* ── the drive device, animated ────────────────────────────────────────────
   video-surveillance.html. PK picked this from
   prototypes/surveillance-device-compare/animate.html on 2026-10-17.

   IT RUNS AS ONE SEQUENCE, NOT THREE EFFECTS. Counter, bar, platter and arm
   all move together for 2.8s, then everything stops at once, the pivot and
   the end marker snap red, and "It stopped here" arrives last. THE STOP IS
   THE POINT — the motion exists only to make the stop mean something. Do not
   stagger these into separate little animations.

   ⚠ THE EASING IS NOT DECORATIVE AND WAS CHOSEN BY MEASUREMENT. The first
   attempt used cubic-bezier(.22,.7,.3,1), which put 91% of the motion in the
   first half of the time: too fast to read, then a crawl. The curve below is
   at 76% by halfway and still settles. Measured side by side at 1.5s, 2.8s
   and 4.5s; PK picked 2.8s with this curve.

   ⚠ THE RESTING STATE IS THE APPROVED STATIC DESIGN. Every rule here moves
   things AWAY from the finished picture and back to it, so anything that
   never fires — an old browser, a reduced-motion visitor, JS off — shows the
   design exactly as signed off. Nothing degrades.

   It keys off .is-in, which reveal.js already puts on .he__fig. No second
   observer, and reduced-motion visitors never get .js-reveal in the first
   place, so the fallback is free. */

.he__fig {
  --dev-dur: 2800ms;
  --dev-ease: cubic-bezier(.4, .08, .25, 1);
}

/* Resting = finished. */
.dev__fill   { transform: scaleX(1); transform-origin: left center; transform-box: fill-box; }
.dev__dot    { transform: translateX(0); }
.dev__arm    { transform: rotate(0deg); transform-origin: 152px 34px; }
.dev__rings  { transform: rotate(0deg); transform-origin: 86px 76px; }
.dev__pivotRed, .dev__endRed { transform-box: fill-box; transform-origin: center; opacity: 1; }
.dev__stopLabel { transform-box: fill-box; opacity: 1; }

/* Running. */
.he__fig.is-in .dev__fill  { animation: devFill  var(--dev-dur) var(--dev-ease) forwards; }
.he__fig.is-in .dev__dot   { animation: devRide  var(--dev-dur) var(--dev-ease) forwards; }
.he__fig.is-in .dev__arm   { animation: devSweep var(--dev-dur) var(--dev-ease) forwards; }
.he__fig.is-in .dev__rings {
  animation: devSpin calc(var(--dev-dur) / 2.6) linear 0s 2,
             devSettle 1ms linear var(--dev-dur) forwards;
}
.he__fig.is-in .dev__pivotRed,
.he__fig.is-in .dev__endRed    { animation: devSnap 260ms ease-out calc(var(--dev-dur) + 60ms) backwards; }
.he__fig.is-in .dev__stopLabel { animation: devRise 320ms ease-out calc(var(--dev-dur) + 240ms) backwards; }

@keyframes devFill   { from { transform: scaleX(0); }            to { transform: scaleX(1); } }
@keyframes devRide   { from { transform: translateX(-234px); }   to { transform: translateX(0); } }
@keyframes devSweep  { from { transform: rotate(-26deg); }       to { transform: rotate(0deg); } }
@keyframes devSpin   { from { transform: rotate(0deg); }         to { transform: rotate(360deg); } }
@keyframes devSettle { to   { transform: rotate(0deg); } }
@keyframes devSnap   { from { opacity: 0; transform: scale(.4); } to { opacity: 1; transform: scale(1); } }
@keyframes devRise   { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }

/* Belt and braces. reveal.js already skips reduced-motion visitors, so this
   should never be needed — which is exactly why it is cheap to keep. */
@media (prefers-reduced-motion: reduce) {
  .he__fig.is-in .dev__fill, .he__fig.is-in .dev__dot,
  .he__fig.is-in .dev__arm,  .he__fig.is-in .dev__rings,
  .he__fig.is-in .dev__pivotRed, .he__fig.is-in .dev__endRed,
  .he__fig.is-in .dev__stopLabel { animation: none !important; }
}


/* ── the takeover timeline, PINNED ─────────────────────────────────────────
   managed-it-services.html, PK 2026-10-17, from
   prototypes/timeline-scroll-modes/ where four drivers were compared.

   WHAT THIS IS, AND WHAT IT IS NOT. The section parks while the sequence
   plays, and you leave once you have been through it. NOTHING INTERCEPTS THE
   WHEEL — the track below is simply ~3 screens tall and the content is
   sticky inside it, so scrollbar, keyboard, trackpad and screen readers all
   behave normally. The version that earns the "scroll-jacking" name swallows
   wheel events and moves the page in fixed jumps; this does not.

   IT RELEASES AFTER ONE PASS. Once the sequence completes, js/timeline-pin.js
   collapses the track and un-sticks the content, so scrolling back up is an
   ordinary section rather than three screens of scrubbing again. Two traps
   were hit building that and both are easy to reintroduce:

     1. COLLAPSING THE TRACK IS ONLY HALF THE JOB. Leave .tl-pin__stick
        sticky at 100vh and a 735px section sits in a 690px box with its top
        sliced off. The release must un-stick as well, which is what
        .is-released does below.
     2. RELEASE AT COMPLETION, NOT WHEN THE SECTION LEAVES THE SCREEN.
        Waiting for it to scroll past means reversing before that point
        scrubs backwards through the whole track — the exact trapping this
        removes. The script compensates scroll against the SECTION so it does
        not move on screen when the track collapses.

   min-height, never a fixed height with overflow hidden — a section taller
   than the window should overflow, not be cut.

   PINNING IS OPT-IN PER VIEWPORT and the script decides: too short or too
   narrow and none of this applies, the timer animation above plays instead.
   That is also the reduced-motion path, and the no-JavaScript path. */

.tl-pin__stick { position: static; }

.tl-pin { --tl-pin-top: 105px; }   /* clears the sticky site header */
.tl-pin.is-pinning { height: 300vh; }
.tl-pin.is-pinning .tl-pin__stick {
  position: sticky; top: var(--tl-pin-top);
}
/* ⚠ NO min-height:100vh AND NO CENTRING HERE, both were a mistake. Forcing a
   viewport-tall box centred the 761px section and clipped BOTH ends on any
   window shorter than that. Sticking it to the top instead means the part
   that actually animates — heading, markers, labels, the top 508px — is
   always on screen, and only the tail of the paragraphs can fall below the
   fold on a short window. */

.tl-pin.is-pinning.is-released { height: auto; }
.tl-pin.is-pinning.is-released .tl-pin__stick {
  position: static; min-height: 0; display: block;
}

/* While the script is driving the sequence from scroll position, the
   entry-triggered timer version must not also be running. */
.tl-pin.is-pinning .timeline__steps.is-in::before,
.tl-pin.is-pinning .timeline__steps.is-in .timeline__marker,
.tl-pin.is-pinning .timeline__steps.is-in .timeline__label,
.tl-pin.is-pinning .timeline__steps.is-in .timeline__step h3,
.tl-pin.is-pinning .timeline__steps.is-in .timeline__step p { animation: none !important; }

@media (prefers-reduced-motion: reduce) {
  .tl-pin.is-pinning { height: auto; }
  .tl-pin.is-pinning .tl-pin__stick { position: static; min-height: 0; display: block; }
}

/* ══════════════════════════════════════════════════════════════════════════
   CAREERS PAGE — added 2026-10-19 with careers.html.

   Only ONE new component here. The application form at the foot of that page
   reuses .consultation wholesale, because .consultation already IS a
   full-width green band with copy left and a .form-card right — building a
   parallel .apply for it would have been inventing a name for a pattern that
   exists, which is the fault this file warns about at length elsewhere.

   What is new is the opening block: copy on the left, four photographs
   cascading down the right.
   ══════════════════════════════════════════════════════════════════════════ */

.careers { background: var(--white); padding: var(--section-pad); }
.careers__inner {
  max-width: var(--shell); margin: 0 auto;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 64px;
  align-items: start;
}
/* The copy is sticky, the same device the Why page's essay rail uses. The
   stack of four photographs runs 1300px against roughly 780px of copy, so
   without this the left column ends in 500px of white and the section reads
   as unfinished. Pinned, the reader keeps the argument in view while the
   four disciplines scroll past it — which is the point of the four shots.
   align-self: start is required: a stretched grid item has nothing to stick
   inside. 108px clears the sticky header, matching why.css. */
.careers__copy {
  display: flex; flex-direction: column; gap: 18px;
  position: sticky; top: 108px; align-self: start;
}
/* This section opens the page, so the h1 carries hero weight — but in Open
   Sans rather than the hero's uppercase Oswald, because there is no hero band
   behind it to justify that much shout. */
.careers__copy h1 {
  margin: 4px 0 0;
  font-family: 'Open Sans', sans-serif;
  font-size: clamp(34px, 4vw, 52px); font-weight: 800;
  line-height: 1.08; letter-spacing: -.015em;
  color: var(--ink-900);
}
.careers__copy .rule { margin: 2px 0 6px; }
.careers__copy p { margin: 0; font-size: 16px; line-height: 1.75; color: var(--ink-700); max-width: 54ch; }
.careers__lead { font-size: 18px !important; color: var(--ink-800) !important; }
/* The honest line about there not always being a posting. Gold edge rather
   than green: it is a caveat, not a selling point. */
.careers__note {
  margin-top: 8px !important;
  padding: 16px 20px;
  background: var(--ink-50);
  border-left: 3px solid var(--gold-500);
  border-radius: 0 10px 10px 0;
  font-size: 15.5px !important;
  color: var(--ink-800) !important;
}

/* ── the cascade ─────────────────────────────────────────────────────────
   Four shots stepping down and across rather than sitting in a 2x2. A tidy
   grid of four photographs reads as a stock-photo wall; an overlap reads as
   one company's actual week, which is the only thing a candidate is really
   asking about.

   Mechanism is deliberately plain: normal flow, each shot 74% wide with a
   different left offset, pulled up over its predecessor by a negative
   bottom margin. No absolute positioning, so the container height is still
   computed from the content and nothing needs a magic number.

   Desktop-weighted on purpose — 83% of this site's visitors are on one. It
   folds to a single clean column under 900px, where overlap would only
   crowd. */
.careers__stack { position: relative; }
/* 56%, down from 74% when there were four. Six shots at the old size made a
   2,000px column against 800px of copy, and PK's note was that they did not
   need to be that big in the first place — the size was filling space rather
   than earning it. Smaller also buys more horizontal slack for the offsets,
   so the cascade zig-zags instead of barely stepping. */
.careers__shot {
  position: relative;
  width: 56%;
  margin: 0 0 -30px;
  border-radius: 10px;
  overflow: hidden;
  box-shadow: 0 22px 44px rgba(10, 12, 10, .22);
  /* a hairline keeps two overlapping photographs from bleeding into each
     other where both happen to be dark at the seam */
  outline: 1px solid rgba(255, 255, 255, .55);
  outline-offset: -1px;
}
.careers__shot:last-child { margin-bottom: 0; }
.careers__shot img { display: block; width: 100%; height: auto; }

/* Offsets never exceed 44% (100 − the 56% width) or the shot leaves the
   column. The pattern drifts right while stepping back twice, so it reads as
   a hand-laid stack rather than a staircase. */
.careers__shot--1 { margin-left: 0;   z-index: 1; }
.careers__shot--2 { margin-left: 26%; z-index: 2; }
.careers__shot--3 { margin-left: 8%;  z-index: 3; }
.careers__shot--4 { margin-left: 34%; z-index: 4; }
.careers__shot--5 { margin-left: 14%; z-index: 5; }
.careers__shot--6 { margin-left: 40%; z-index: 6; }

/* The discipline label. It sits ON the photograph so the pairing cannot be
   broken by a reflow, over a scrim heavy enough to carry white text whatever
   the image does underneath.
   It sits at the TOP, not the bottom, and that is load-bearing: every shot
   overlaps the BOTTOM of the one before it, so a bottom-set caption has its
   right-hand half covered by the next photograph. Tops are never covered,
   because the shot above always carries the lower z-index. */
.careers__shot figcaption {
  position: absolute; left: 0; right: 0; top: 0;
  padding: 11px 16px 26px;
  background: linear-gradient(to bottom, rgba(10, 12, 10, .88), rgba(10, 12, 10, 0));
  font-family: 'Open Sans', sans-serif;
  font-size: 12px; font-weight: 800;
  letter-spacing: .1em; text-transform: uppercase;
  color: #fff;
}

@media (max-width: 1100px) {
  .careers__inner { gap: 44px; }
  .careers__shot { width: 68%; margin-bottom: -22px; }
  .careers__shot--2 { margin-left: 20%; }
  .careers__shot--3 { margin-left: 6%;  }
  .careers__shot--4 { margin-left: 26%; }
  .careers__shot--5 { margin-left: 10%; }
  .careers__shot--6 { margin-left: 32%; }
}
@media (max-width: 900px) {
  .careers__inner { grid-template-columns: minmax(0, 1fr); gap: 40px; }
  /* One column: nothing to stick beside, and a pinned block would simply sit
     on top of the photographs. */
  .careers__copy { position: static; }
  .careers__shot {
    width: 100%; margin: 0 0 16px; margin-left: 0 !important;
    box-shadow: 0 12px 26px rgba(10, 12, 10, .16);
  }
  .careers__shot:last-child { margin-bottom: 0; }
}


/* ── the two-in-the-morning night: cyber-security.html, #two-am ─────────────
   PK picked version D from prototypes/two-am-timeline/ on 2026-10-22: A's
   night (a heart-monitor line, a running clock, dawn) with C's office moved
   into the top-right corner, on the hex horizon art.
   THE RESTING STATE IS THE FINISHED PICTURE — dawn, office awake, every pin
   and the email showing. js/two-am.js adds .armed to rewind it to 2am, then
   .s1 … .s7 to replay the night. No JS or reduced motion gets the morning,
   which tells the whole story anyway.
   Red is #e8755c, the dark-band red (6.1:1 on ink), never --green-700. */
.tn {
  --tn-threat: #e8755c; --tn-safe: var(--green-400);
  position: relative; height: 400px; margin-top: 28px;
  border: 1px solid #1f2621; border-radius: 6px; overflow: hidden;
  background: #0a0c0a;
}
.tn__hex { position: absolute; inset: 0; pointer-events: none;
  background: url('/images/bg-hex-horizon.jpg') center 58% / cover no-repeat;
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 52%, transparent 100%);
          mask-image: linear-gradient(180deg, #000 0%, #000 52%, transparent 100%);
  transition: opacity 1.8s ease, filter 1.8s ease; }
.tn__stars { position: absolute; inset: 0; opacity: .55; pointer-events: none;
  background-image:
    radial-gradient(1px 1px at 12% 18%, #fff 50%, transparent 51%),
    radial-gradient(1px 1px at 27% 9%,  #fff 50%, transparent 51%),
    radial-gradient(1px 1px at 41% 22%, #cfd8d1 50%, transparent 51%),
    radial-gradient(1.5px 1.5px at 58% 12%, #fff 50%, transparent 51%),
    radial-gradient(1px 1px at 69% 27%, #cfd8d1 50%, transparent 51%),
    radial-gradient(1px 1px at 83% 8%,  #fff 50%, transparent 51%),
    radial-gradient(1px 1px at 92% 20%, #fff 50%, transparent 51%),
    radial-gradient(1px 1px at 6% 31%,  #cfd8d1 50%, transparent 51%);
  transition: opacity 1.6s ease; }
.tn__alarm { position: absolute; inset: 0; opacity: 0; pointer-events: none;
  background: radial-gradient(40% 80% at 19% 55%, rgba(232,117,92,.30), transparent 70%); }

.tn__clock { position: absolute; left: 28px; top: 22px; z-index: 3; display: flex; align-items: baseline; gap: 8px;
  font-family: 'Oswald', sans-serif; line-height: 1; color: #fff; }
.tn__time { font-size: 64px; font-weight: 600; letter-spacing: .01em; font-variant-numeric: tabular-nums; }
.tn__ampm { font-size: 22px; font-weight: 500; color: rgba(255,255,255,.6); }
.tn__lapse { margin-left: 10px; padding: 5px 8px; border: 1px solid rgba(245,190,92,.5); border-radius: 99px;
  font: 700 11px/1 'Open Sans', sans-serif; letter-spacing: .14em; text-transform: uppercase;
  color: var(--gold-400); opacity: 0; transition: opacity .4s; }

.tn__svg { position: absolute; left: 0; right: 0; top: 150px; width: 100%; height: 170px; z-index: 1; }
.tn__calm { fill: none; stroke: rgba(92,191,99,.75); stroke-width: 2; vector-effect: non-scaling-stroke;
  stroke-dasharray: 1000; stroke-dashoffset: 0; }
.tn__spike { fill: none; stroke: var(--tn-safe); stroke-width: 3; vector-effect: non-scaling-stroke; stroke-linejoin: round;
  filter: drop-shadow(0 0 6px rgba(92,191,99,.6)); transition: stroke .5s, filter .5s; }

.tn__pin { position: absolute; left: var(--x); top: var(--y, 104px); z-index: 2; width: 150px;
  display: flex; flex-direction: column; align-items: center; text-align: center;
  transform: translateX(-50%); transition: opacity .45s ease, transform .45s cubic-bezier(.2,.8,.2,1); }
.tn__pin b { font: 600 18px/1 'Oswald', sans-serif; color: #fff; font-variant-numeric: tabular-nums; }
.tn__pin span { margin-top: 4px; font: 600 13px/1.3 'Open Sans', sans-serif; color: rgba(255,255,255,.8); }
.tn__pin i { display: block; width: 1px; height: var(--stem, 34px); margin-top: 8px;
  background: linear-gradient(180deg, rgba(255,255,255,.5), transparent); }
.tn__pin--alert b { color: var(--tn-threat); }
.tn__pin--ok b { color: var(--tn-safe); }
.tn__pin--ghost { opacity: .5; }
.tn__pin--ghost span { font-weight: 400; font-style: italic; }
.tn__pin--ghost i { background: none; border-left: 1px dashed rgba(255,255,255,.4); }

.tn__mail { position: absolute; right: 26px; bottom: 22px; z-index: 3; width: 320px; padding: 14px 16px 15px 19px;
  background: #fff; border-radius: 10px; box-shadow: 0 18px 40px rgba(0,0,0,.5); font-family: 'Open Sans', sans-serif;
  transition: opacity .45s ease, transform .45s cubic-bezier(.2,.8,.2,1); }
.tn__mail::before { content: ''; position: absolute; left: 0; top: 14px; bottom: 14px; width: 3px; border-radius: 3px; background: var(--green-500); }
.tn__mail small { display: block; font-size: 11.5px; color: var(--ink-500); }
.tn__mail strong { display: block; margin: 3px 0 4px; font-size: 15px; color: var(--ink-900); }
.tn__mail p { margin: 0; font-size: 13.5px; line-height: 1.45; color: var(--ink-600); }

/* the office, top right */
.tn__office { position: absolute; right: 30px; top: 26px; width: min(430px, 40%); z-index: 2; }
.tn__tag { display: block; margin-bottom: 10px; text-align: right;
  font: 700 11px/1 'Open Sans', sans-serif; letter-spacing: .14em; text-transform: uppercase; color: rgba(255,255,255,.45); }
.tn__tag b { font-weight: 800; color: rgba(255,255,255,.8); }
.tn__row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; align-items: end; }
.tn__mon { display: flex; flex-direction: column; align-items: center; }
.tn__scr { position: relative; width: 100%; aspect-ratio: 16 / 10; border-radius: 4px; overflow: hidden;
  border: 2px solid #3a443c; background: linear-gradient(160deg, #24402f 0%, #172a1f 70%);
  box-shadow: 0 0 18px rgba(92,191,99,.18);
  transition: background .8s ease, border-color .5s ease, box-shadow .6s ease; }
.tn__scr::before { content: ''; position: absolute; inset: 0; opacity: 1; transition: opacity .8s ease;
  background:
    linear-gradient(rgba(255,255,255,.22), rgba(255,255,255,.22)) 0 100% / 100% 12% no-repeat,
    linear-gradient(rgba(255,255,255,.14), rgba(255,255,255,.14)) 12% 22% / 46% 30% no-repeat,
    linear-gradient(rgba(245,190,92,.30), rgba(245,190,92,.30)) 78% 46% / 30% 34% no-repeat; }
.tn__scr::after { content: ''; position: absolute; inset: 0; display: grid; place-items: center; font: 800 22px/1 'Open Sans', sans-serif; }
.tn__led { position: absolute; right: 5px; bottom: 4px; width: 4px; height: 4px; border-radius: 50%; background: var(--gold-500); opacity: 0; }
.tn__sweep { position: absolute; top: 0; bottom: 0; left: -40%; width: 40%; opacity: 0;
  background: linear-gradient(90deg, transparent, rgba(92,191,99,.85), transparent); }
.tn__neck { width: 10px; height: 9px; background: #2f3831; }
.tn__foot { width: 34px; height: 3px; border-radius: 2px; background: #2f3831; }
.tn__lbl { margin-top: 7px; font: 600 9.5px/1 ui-monospace, Menlo, monospace; letter-spacing: .04em; color: rgba(255,255,255,.4); }
.tn__mon--hit .tn__lbl { color: rgba(255,255,255,.7); }

/* armed: rewind to 2am */
.tn.armed .tn__hex { opacity: .45; filter: brightness(.55) saturate(.6); }
.tn.armed .tn__stars { opacity: 1; }
.tn.armed .tn__pin { opacity: 0; transform: translateX(-50%) translateY(10px); }
.tn.armed .tn__mail { opacity: 0; transform: translateY(24px); }
.tn.armed .tn__spike { opacity: 0; stroke: var(--tn-threat); filter: drop-shadow(0 0 8px rgba(232,117,92,.8)); }
.tn.armed .tn__scr { background: #0c0f0d; border-color: #262e28; box-shadow: none; }
.tn.armed .tn__scr::before { opacity: 0; }
.tn.armed .tn__led { opacity: 1; animation: tnStandby 2.4s ease-in-out infinite; }
.tn.armed .tn__mon:nth-child(2) .tn__led { animation-delay: .6s; }
.tn.armed .tn__mon:nth-child(3) .tn__led { animation-delay: 1.2s; }
.tn.armed .tn__mon:nth-child(4) .tn__led { animation-delay: 1.8s; }

/* s1, 2:07: the spike, and the reception screen with it */
.tn.armed.s1 .tn__spike { opacity: 1; }
.tn.armed.s1 .tn__alarm { animation: tnAlarm 1.1s ease-out 2; }
.tn.armed.s1 .tn__p1, .tn.armed.s2 .tn__p2, .tn.armed.s3 .tn__p3 { opacity: 1; transform: translateX(-50%); }
.tn.armed.s1 .tn__mon--hit .tn__scr {
  border-color: var(--tn-threat); box-shadow: 0 0 28px rgba(232,117,92,.65), 0 14px 26px rgba(232,117,92,.25);
  background: repeating-linear-gradient(0deg, #3a1410 0 2px, #2a0d0a 2px 4px); animation: tnGlitch .22s steps(2) 9; }
.tn.armed.s1 .tn__mon--hit .tn__scr::after { content: '!'; color: #ffb3a3; text-shadow: 0 0 10px var(--tn-threat); }
.tn.armed.s1 .tn__mon--hit .tn__led { opacity: 0; }
/* s3, 2:11: contained */
.tn.armed.s3 .tn__spike { stroke: var(--tn-safe); filter: drop-shadow(0 0 6px rgba(92,191,99,.6)); }
.tn.armed.s3 .tn__mon--hit .tn__scr { border-color: var(--tn-safe); box-shadow: 0 0 24px rgba(92,191,99,.5); background: #0f2216; animation: none; }
.tn.armed.s3 .tn__mon--hit .tn__scr::after { content: '\2713'; color: var(--tn-safe); text-shadow: 0 0 10px rgba(92,191,99,.7); }
.tn.armed.s3 .tn__mon--hit .tn__sweep { animation: tnSweep .9s ease-out 1; }
/* s4: the step that was not needed */
.tn.armed.s4 .tn__p4 { opacity: .5; transform: translateX(-50%); }
/* s5: fast-forward; reception goes back to sleep */
.tn.armed.s5 .tn__lapse { opacity: 1; }
.tn.armed.s5 .tn__mon--hit .tn__scr { background: #0c0f0d; border-color: #262e28; box-shadow: none; transition-duration: 1.2s; }
.tn.armed.s5 .tn__mon--hit .tn__scr::after { content: ''; }
.tn.armed.s5 .tn__mon--hit .tn__led { opacity: 1; }
/* s6: dawn, and the office wakes one desk at a time */
.tn.armed.s6 .tn__hex { opacity: 1; filter: brightness(1) saturate(1); }
.tn.armed.s6 .tn__stars { opacity: .25; }
.tn.armed.s6 .tn__lapse { opacity: 0; }
.tn.armed.s6 .tn__scr { background: linear-gradient(160deg, #24402f 0%, #172a1f 70%); border-color: #3a443c; box-shadow: 0 0 18px rgba(92,191,99,.18); }
.tn.armed.s6 .tn__scr::before { opacity: 1; }
.tn.armed.s6 .tn__led { opacity: 0; animation: none; }
.tn.armed.s6 .tn__mon:nth-child(2) .tn__scr, .tn.armed.s6 .tn__mon:nth-child(2) .tn__scr::before { transition-delay: .35s; }
.tn.armed.s6 .tn__mon:nth-child(3) .tn__scr, .tn.armed.s6 .tn__mon:nth-child(3) .tn__scr::before { transition-delay: .7s; }
.tn.armed.s6 .tn__mon:nth-child(4) .tn__scr, .tn.armed.s6 .tn__mon:nth-child(4) .tn__scr::before { transition-delay: 1.05s; }
/* s7: the email */
.tn.armed.s7 .tn__mail { opacity: 1; transform: none; }

@keyframes tnStandby { 0%, 100% { opacity: .15; } 50% { opacity: 1; } }
@keyframes tnAlarm { 0% { opacity: 0; } 25% { opacity: 1; } 100% { opacity: 0; } }
@keyframes tnGlitch { 0% { transform: translateX(0); } 50% { transform: translateX(-2px); filter: hue-rotate(-12deg); } 100% { transform: translateX(1px); } }
@keyframes tnSweep { 0% { left: -40%; opacity: 1; } 100% { left: 110%; opacity: 0; } }

.tn-note { margin: 12px 0 0; font-size: 12.5px; color: rgba(255,255,255,.45); }

/* The device runs the band's full width, past the 880px cap .he--wide puts
   on figures (PK, 22 Oct: "I want that to be full width"). */
.he--wide .he__fig--full { max-width: none; }

/* PHONES: in the corner the office shrinks to postage stamps, and four
   labels along a 335px line run into each other. So on phones the night
   becomes a column: clock, office, the line, then the four steps as a
   list, then the email. Same story, read downwards. */
@media (max-width: 900px) {
  .tn { height: auto; padding: 0 16px 18px; }
  .tn__time { font-size: 44px; }
  .tn__office { position: relative; right: auto; top: auto; width: auto; margin: 92px 0 0; }
  .tn__tag { text-align: left; }
  .tn__svg { position: relative; top: auto; display: block; height: 130px; margin: 18px -16px 6px; width: calc(100% + 32px); }
  .tn__pin { position: relative; left: auto; top: auto; width: auto;
    padding: 0 0 14px; align-items: flex-start; text-align: left; transform: none !important; }
  .tn__pin i { display: none; }
  .tn__pin span { font-size: 12.5px; }
  .tn__mail { position: relative; right: auto; bottom: auto; width: auto; margin-top: 6px; }
}


/* ── how a move runs: cloud-services.html, #how-it-runs ─────────────────────
   PK picked version D from prototypes/cloud-runs/ on 2026-10-22. Two lanes,
   one job: our work above (a long, busy bar), your staff's day below (a flat
   line that keeps pace with it), and a gold beam joining the two at cutover.
   Three dark cards underneath light in turn; Cutover stays lit.
   ⚠ ON THE LIGHT GROUND ON PURPOSE (PK: a change of pace from the ink bands).
   ⚠ CUTOVER IS AFTER HOURS (PK, 22 Oct, "99% sure"), so nothing here may say
   staff notice it. The desk line never dips.
   ⚠ RESTING STATE = FINISHED PICTURE. js/cloud-runs.js adds .armed to rewind,
   then .s1 … .s4. No JS or reduced motion gets the end state. */
.cr { --cr-felt: var(--gold-500); --cr-felt-deep: var(--gold-700); position: relative; margin-top: 22px; }
.cr__lanes { position: relative; display: grid; grid-template-columns: 230px 1fr; row-gap: 22px; column-gap: 28px; }
.cr__rail { align-self: center; }
.cr__rail b { display: block; font: 800 17px/1.2 'Open Sans', sans-serif; letter-spacing: .14em; text-transform: uppercase; color: var(--ink-900); }
.cr__rail small { display: block; margin-top: 6px; font-size: 16px; line-height: 1.4; color: var(--ink-600); }

.cr__track { position: relative; height: 104px; display: flex; border-radius: 10px; overflow: hidden;
  background: #fff; border: 1px solid #c8cfca; box-shadow: 0 22px 46px rgba(10,12,10,.14); }
.cr__seg { position: relative; height: 100%; display: flex; flex-direction: column; justify-content: center; padding: 0 22px;
  color: #fff; overflow: hidden; white-space: nowrap; transform-origin: left;
  transition: transform .9s cubic-bezier(.5,0,.3,1); }
.cr__seg b { font: 600 30px/1 'Oswald', sans-serif; letter-spacing: .01em; }
.cr__seg span { margin-top: 7px; font: 600 13px/1.2 'Open Sans', sans-serif; opacity: .85; }
.cr__seg--disc  { flex: 0 0 24%; background: var(--green-700); }
.cr__seg--build { flex: 0 0 68%; background-color: var(--green-600);
  background-image: repeating-linear-gradient(-45deg, rgba(255,255,255,.13) 0 12px, transparent 12px 24px);
  background-size: 34px 34px; }
.cr__seg--cut   { flex: 1 1 auto; padding: 0; align-items: center; background: var(--cr-felt); color: var(--ink-900); }
.cr__seg--cut b { font-size: 22px; }
.cr__pk { position: absolute; top: 50%; width: 8px; height: 8px; margin-top: -4px; border-radius: 50%; background: rgba(255,255,255,.85); opacity: 0; }

.cr__desk { position: relative; height: 104px; border-radius: 10px; background: #fff; border: 1px solid #c8cfca; }
.cr__desk svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.cr__line { fill: none; stroke: var(--ink-600); stroke-width: 12; stroke-linecap: butt; vector-effect: non-scaling-stroke;
  stroke-dasharray: 1000; stroke-dashoffset: 0; transition: stroke-dashoffset 1s cubic-bezier(.5,0,.3,1); }
.cr__gold { fill: none; stroke: var(--cr-felt-deep); stroke-width: 12; stroke-linecap: butt; vector-effect: non-scaling-stroke;
  transition: opacity .4s ease; }
.cr__tag { position: absolute; top: 18px; font: 600 13px/1 'Open Sans', sans-serif; color: var(--ink-500); }
.cr__tag--start { left: 22px; }
.cr__tag--end { right: 22px; font-weight: 800; color: var(--cr-felt-deep); transition: opacity .4s ease; }
.cr__glow { position: absolute; left: 96%; top: 50%; width: 140px; height: 140px; margin: -70px 0 0 -70px; border-radius: 50%;
  background: radial-gradient(closest-side, rgba(240,168,48,.45), transparent); opacity: 0; pointer-events: none; }
.cr__beam { position: absolute; left: calc(230px + 28px + (100% - 258px) * .96); top: 104px; width: 4px; height: 22px; margin-left: -2px;
  background: var(--cr-felt); transform-origin: top; box-shadow: 0 0 12px rgba(240,168,48,.7);
  transition: transform .9s cubic-bezier(.5,0,.3,1); }

.cr__cards { list-style: none; margin: 34px 0 0; padding: 0; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 18px; }
.cr__card { padding: 22px 22px 20px; border-radius: 6px; background: var(--ink-950); border: 1px solid #2b322d;
  box-shadow: 0 18px 40px rgba(10,12,10,.18);
  transition: transform .7s cubic-bezier(.2,.8,.2,1), box-shadow .7s ease, border-color .7s ease; }
.cr__card h3 { margin: 0 0 8px; font: 600 22px/1.2 'Oswald', sans-serif; color: var(--green-400); }
.cr__card p { margin: 0; font-size: 15px; line-height: 1.65; color: rgba(255,255,255,.78); }
.cr__card--felt { border-color: var(--cr-felt); border-top: 6px solid var(--cr-felt); box-shadow: 0 22px 46px rgba(184,122,12,.32); transform: translateY(-6px); }
.cr__card--felt h3 { color: #fff; }
.cr__card--felt h3::after { content: 'Done after hours'; display: block; margin-top: 6px;
  font: 700 11px/1.2 'Open Sans', sans-serif; letter-spacing: .12em; text-transform: uppercase; color: var(--gold-400);
  transition: opacity .5s ease; }

/* armed: rewind */
.cr.armed .cr__seg { transform: scaleX(0); }
.cr.armed .cr__line { stroke-dashoffset: 1000; }
.cr.armed .cr__gold, .cr.armed .cr__tag--end { opacity: 0; }
.cr.armed .cr__beam { transform: scaleY(0); }
.cr.armed .cr__card { transform: none; box-shadow: 0 18px 40px rgba(10,12,10,.18); border-color: #2b322d; border-top-width: 1px; }
.cr.armed .cr__card--felt h3::after { opacity: 0; }
/* s1 discovery: the desk line keeps pace with the bar above */
.cr.armed.s1 .cr__seg--disc { transform: scaleX(1); transition-duration: 1s; }
.cr.armed.s1 .cr__line { stroke-dashoffset: 764; transition-duration: 1s; }
.cr.armed.s1 .cr__card:nth-child(1) { border-color: var(--green-400); box-shadow: 0 16px 34px rgba(20,87,30,.35); transform: translateY(-4px); }
/* s2 the build */
.cr.armed.s2 .cr__seg--build { transform: scaleX(1); transition-duration: 2.4s; animation: crMarch 1.1s linear infinite; }
.cr.armed.s2 .cr__line { stroke-dashoffset: 20; transition-duration: 2.4s; }
.cr.armed.s2 .cr__pk { animation: crPacket 1.6s linear infinite; }
.cr.armed.s2 .cr__card:nth-child(1) { border-color: #2b322d; box-shadow: 0 18px 40px rgba(10,12,10,.18); transform: none; }
.cr.armed.s2 .cr__card:nth-child(2) { border-color: var(--green-400); box-shadow: 0 16px 34px rgba(20,87,30,.35); transform: translateY(-4px); }
/* s3 cutover */
.cr.armed.s3 .cr__seg--cut { transform: scaleX(1); transition-duration: .3s; }
.cr.armed.s3 .cr__line { stroke-dashoffset: 0; transition-duration: .3s; }
.cr.armed.s3 .cr__beam { transform: scaleY(1); transition-delay: .25s; }
.cr.armed.s3 .cr__gold { opacity: 1; transition-delay: .45s; }
.cr.armed.s3 .cr__glow { animation: crGlow 1.4s ease-out .45s 2; }
.cr.armed.s3 .cr__card:nth-child(2) { border-color: #2b322d; box-shadow: 0 18px 40px rgba(10,12,10,.18); transform: none; }
.cr.armed.s3 .cr__card--felt { border-color: var(--cr-felt); border-top: 6px solid var(--cr-felt); box-shadow: 0 22px 46px rgba(184,122,12,.32); transform: translateY(-6px); }
.cr.armed.s3 .cr__card--felt h3::after { opacity: 1; transition-delay: .5s; }
.cr.armed.s4 .cr__tag--end { opacity: 1; }
.cr.armed.s4 .cr__pk { animation: none; opacity: 0; }
@keyframes crMarch { to { background-position: 34px 0; } }
@keyframes crPacket { 0% { left: 2%; opacity: 0; } 10% { opacity: 1; } 90% { opacity: 1; } 100% { left: 96%; opacity: 0; } }
@keyframes crGlow { 0% { opacity: 0; } 30% { opacity: 1; } 100% { opacity: 0; } }

/* ONE SCREEN (PK, 22 Oct): the whole section should fit a laptop window
   below the sticky header, so it runs tighter than the site rhythm, the
   same call the surveillance band made, and the heading sits on two lines
   rather than three. */
#how-it-runs .runs__inner { padding-top: 60px; padding-bottom: 60px; }
#how-it-runs .section-head { max-width: none; }
#how-it-runs .section-head .h2__accent { display: inline; }

/* phones: lanes stack, the beam goes, the cards become a list */
@media (max-width: 900px) {
  .cr__lanes { grid-template-columns: 1fr; row-gap: 10px; }
  .cr__rail b { font-size: 14px; }
  .cr__rail small { font-size: 14px; }
  .cr__track, .cr__desk { height: 86px; }
  .cr__seg { padding: 0 12px; }
  .cr__seg b { font-size: 16px; }
  .cr__seg--disc { flex-basis: 33%; }
  .cr__seg--build { flex-basis: 59%; }
  .cr__seg span { display: none; }
  .cr__beam { display: none; }
  .cr__tag { top: 12px; font-size: 11.5px; }
  .cr__cards { grid-template-columns: 1fr; margin-top: 22px; }
}


/* ══ THE SAMPLE REPORT: compliance.html's device ═══════════════════════
   Added 2026-10-29. PK's pick from prototypes/compliance-report/ (version
   E): the sample assessment, unredacted, as two real pages. Page one in
   front; page two, "Every area we checked", tucked behind it so its Risk
   column shows down the right-hand edge. The report has depth, not one
   sheet (PK: one page alone reads as "not much there").

   ⚠ NOT REDACTED, ON PURPOSE. The first three prototypes blacked out the
   findings; PK stopped and wondered why everything was covered, which is
   his self-explanatory test failing. A made-up practice's findings give a
   competitor nothing and show an owner what they would get.

   ⚠ SIZED IN CONTAINER UNITS (cqw). Every measurement is a share of the
   page's own width, so at 480px or 300px on a phone it is one picture,
   just smaller. Do not "fix" a size to px.

   ⚠ THE REST STATE IS THE FINISHED PICTURE. js/compliance-report.js adds
   .rpt--armed to hide things and steps them back in; with reduced motion
   or no JS it never runs and the reader gets both pages, stamped.

   ⚠ NO "NEXT ASSESSMENT" DATE in the header. The example report has one;
   it would bring back the yearly promise cut from the page (PK, 29 Oct). */
/* Capped by screen height as well as width (PK, 29 Oct, 1600x900 laptop):
   the device is 1.5x as tall as it is wide, so on a short screen it shrinks
   to fit under the sticky header instead of running off the bottom. */
.rpt { position: relative; max-width: min(560px, calc((100vh - 150px) / 1.5)); margin: 0 0 0 auto; padding-top: 4%; }
.rpt__p1 { container-type: inline-size; position: relative; z-index: 2; width: 86%; }
.rpt__p2 {
  /* The visible strip is page two's right edge, which is exactly where its
     Risk column sits. A steeper tilt swung the lower rows back behind page
     one, so the tilt stays small and pivots at the top. */
  container-type: inline-size;
  position: absolute; z-index: 1; top: 0; left: 17%; width: 83%; bottom: 7%;
  transform: rotate(1.4deg); transform-origin: 50% 0;
  transition: transform .9s cubic-bezier(.2,.7,.2,1), opacity .6s ease;
}
.rpt__page {
  position: relative; overflow: hidden;
  background: #fbfbf7; color: #1b1f1c; border-radius: .6cqw;
  /* the deep bottom padding is the stamp's own margin: it covers nothing */
  padding: 5.6cqw 6.4cqw 19cqw;
  box-shadow: 0 6cqw 12cqw rgba(0,0,0,.55), 0 0 0 1px rgba(255,255,255,.06);
  font-family: 'Open Sans', sans-serif;
  display: flex; flex-direction: column; gap: 3cqw;
  transition: opacity .6s ease, transform .6s cubic-bezier(.2,.7,.2,1);
}
.rpt__page2 {
  height: 100%; overflow: hidden;
  background: #f4f4ef; color: #1b1f1c; border-radius: .6cqw;
  padding: 5.6cqw 5.6cqw 4cqw;
  box-shadow: 0 4cqw 9cqw rgba(0,0,0,.5), 0 0 0 1px rgba(255,255,255,.05);
  font-family: 'Open Sans', sans-serif;
}
.rpt__top { display: flex; justify-content: space-between; align-items: flex-start; gap: 3cqw; }
.rpt__logo { width: 26cqw; height: auto; display: block; }
.rpt__sample {
  font-size: 2.1cqw; font-weight: 800; letter-spacing: .16em; text-transform: uppercase;
  color: #6b746d; border: .3cqw solid #b9c0ba; border-radius: .6cqw; padding: .6cqw 1.4cqw;
}
.rpt__eyebrow { font-size: 2.2cqw; font-weight: 800; letter-spacing: .14em; text-transform: uppercase; color: var(--green-700); margin: 0; }
.rpt__title { font-size: 5.2cqw; font-weight: 800; line-height: 1.1; margin: .8cqw 0 0; }
.rpt__meta { font-size: 2.4cqw; color: #59625b; margin: 1cqw 0 0; }
.rpt__rule { width: 11cqw; height: .45cqw; background: var(--gold-500); margin-top: 2.2cqw; }

.rpt__count { display: grid; grid-template-columns: repeat(3, 1fr); gap: 2cqw; }
.rpt__ct { border-radius: .8cqw; padding: 1.8cqw 2cqw; color: #fff; }
.rpt__ct b { display: block; font-size: 7.4cqw; font-weight: 800; line-height: 1; font-variant-numeric: tabular-nums; }
.rpt__ct span { display: block; margin-top: .6cqw; font-size: 2.1cqw; font-weight: 800; letter-spacing: .12em; text-transform: uppercase; opacity: .92; }
/* Rating colors are the report's own, not palette tokens: a High has to
   read as a warning on paper. White on each measures 4.5:1 or better. */
.rpt__ct--h, .rpt__chip--h { background: #b3261e; }
.rpt__ct--m, .rpt__chip--m { background: #a1640a; }
.rpt__ct--l, .rpt__chip--l { background: #4d5b50; }
.rpt__sub { font-size: 2.2cqw; color: #59625b; margin: -1.6cqw 0 0; }

.rpt__h { font-size: 2.6cqw; font-weight: 800; letter-spacing: .02em; margin: 0 0 1.4cqw; }
.rpt__fixes { list-style: none; margin: 0; padding: 0; display: grid; gap: 1.7cqw; }
.rpt__fixes li { display: grid; grid-template-columns: 3cqw 11.5cqw 1fr 13cqw; gap: 1.6cqw; align-items: start; padding-left: 1cqw; }
.rpt__n { font-size: 2.6cqw; font-weight: 800; color: #59625b; line-height: 1.5; }
.rpt__chip {
  justify-self: start; display: inline-block;
  font-size: 1.85cqw; font-weight: 800; letter-spacing: .08em; text-transform: uppercase;
  color: #fff; border-radius: .5cqw; padding: .35cqw 1cqw; line-height: 1.6;
}
.rpt__body { display: grid; gap: .5cqw; min-width: 0; }
.rpt__t { font-size: 2.35cqw; font-weight: 700; line-height: 1.35; }
.rpt__d { font-size: 2.05cqw; line-height: 1.35; color: #59625b; }
.rpt__when { font-size: 2cqw; font-weight: 700; color: #3d4740; text-align: right; line-height: 1.5; }

.rpt__ins { border: .3cqw solid #d6dbd7; border-radius: .8cqw; padding: 2.2cqw 2.4cqw; background: #f3f5f2; }
.rpt__ins ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 1.1cqw; }
.rpt__ins li { display: flex; justify-content: space-between; gap: 2cqw; font-size: 2.2cqw; }
.rpt__ins b { font-weight: 800; white-space: nowrap; }
.rpt__yes { color: var(--green-700); }
.rpt__after { color: #a1640a; }
.rpt__no { color: #b3261e; }

.rpt__foot { margin-top: auto; display: flex; justify-content: space-between; font-size: 1.9cqw; color: #8a928c; }

.rpt__stamp {
  position: absolute; right: 16cqw; bottom: 5.2cqw; z-index: 3; pointer-events: none;
  font-family: 'Oswald', sans-serif; font-weight: 700; font-size: 6.6cqw; letter-spacing: .1em;
  color: rgba(179, 38, 30, .88); border: .9cqw solid rgba(179, 38, 30, .88); border-radius: 1.2cqw;
  padding: .2cqw 2.6cqw; transform: rotate(-11deg); mix-blend-mode: multiply;
  transition: transform .28s cubic-bezier(.3,1.6,.5,1), opacity .2s;
}

.rpt__h2 { font-size: 3.6cqw; font-weight: 800; margin: .8cqw 0 .4cqw; }
.rpt__sub2 { font-size: 2.2cqw; color: #59625b; margin: 0 0 2.4cqw; }
.rpt__areas { width: 100%; border-collapse: collapse; font-size: 2cqw; line-height: 1.35; }
.rpt__areas th { text-align: left; font-size: 1.8cqw; font-weight: 800; letter-spacing: .08em; text-transform: uppercase;
                 color: #59625b; padding: 0 1.2cqw 1.2cqw 0; border-bottom: .3cqw solid #cfd5d0; }
.rpt__areas td { padding: 1.25cqw 1.2cqw 1.25cqw 0; border-bottom: .2cqw solid #e0e4e0; vertical-align: top; }
.rpt__areas td:first-child { font-weight: 700; width: 19%; }
.rpt__areas td:last-child { width: 12%; padding-right: 0; }
.rpt__areas .rpt__chip { font-size: 1.7cqw; }

/* ── the animation: armed is the before state; steps bring it back ──
   Fix rows ~0.4s apart with a 0.7s fade, a beat, then the insurer's
   answers (PK, 29 Oct: slow enough to invite reading). --d is set per row
   by the script. Then page two slides out, then the stamp lands. */
.rpt__fixes li, .rpt__ins li { transition: opacity .7s ease, transform .7s cubic-bezier(.2,.7,.2,1); }
.rpt--armed .rpt__page { opacity: 0; transform: translateY(4cqw); }
.rpt--armed .rpt__fixes li, .rpt--armed .rpt__ins li { opacity: 0; transform: translateY(1.6cqw); }
.rpt--armed .rpt__p2 { opacity: 0; transform: translate(-20.5%, 4.5%) rotate(0deg); }
.rpt--armed .rpt__stamp { opacity: 0; transform: rotate(-11deg) scale(1.9); }
.rpt--armed.rpt--go .rpt__page { opacity: 1; transform: none; }
.rpt--armed.rpt--go .rpt__p2 { opacity: 1; }
.rpt--armed.rpt--rows .rpt__fixes li,
.rpt--armed.rpt--rows .rpt__ins li { opacity: 1; transform: none; transition-delay: var(--d, 0ms); }
.rpt--armed.rpt--slid .rpt__p2 { transform: rotate(1.4deg); }
.rpt--armed.rpt--stamped .rpt__stamp { opacity: 1; transform: rotate(-11deg) scale(1); }

@media (max-width: 900px) { .rpt { margin: 0 auto; } }

/* The band behind the report: the hex horizon under a dark wash (PK's
   pick, 29 Oct, over a soft glow and flat ink: "it just adds that extra
   dimension, just pulls you into it"). Same family as the server-room
   band on other pages. Only on compliance.html#report. */
.he--hex {
  background:
    linear-gradient(rgba(10, 12, 10, .58), rgba(10, 12, 10, .58)),
    url("/images/bg-hex-horizon-band.jpg") center / cover no-repeat,
    var(--ink-950);
}

/* ══ THE DOOR LOG (access-control.html#leaving) ══════════════════════════
   Added 2026-11-02, the page's one device (README, One device per page).
   An example of what the CLIENT sees in the app: one person's last day, the
   change that switched them off, and the Saturday attempt that failed.
   ⚠ It is the client's record, read by the client. Nothing in it may imply
   that we were watching (no paid monitoring; Sebek, 16 Oct and 2 Nov).
   Colors on ink follow the standing rule: --green-400, never --green-700;
   the denied red is the same #e8755c the claim-check uses on dark bands.
   Driven by js/door-log.js; the hidden state exists only under .dlog--armed,
   which the script adds and reduced motion never gets. */
.dlog {
  background: #11150f; border: 1px solid #2b322d; border-radius: 6px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, .45);
  padding: 18px 20px 8px;
  font-size: 14.5px; line-height: 1.45; color: rgba(255, 255, 255, .86);
}
.dlog__top {
  display: flex; justify-content: space-between; align-items: center;
  padding-bottom: 12px; border-bottom: 1px solid #2b322d;
}
.dlog__title {
  font: 600 13px/1 'Oswald', sans-serif; letter-spacing: .14em;
  text-transform: uppercase; color: var(--gold-500);
}
.dlog__sample {
  font-size: 11px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: rgba(255, 255, 255, .55); border: 1px solid #3a423c; border-radius: 3px;
  padding: 3px 7px;
}
.dlog__rows { list-style: none; margin: 0; padding: 0; }
.dlog__row {
  display: grid; grid-template-columns: 7.2em minmax(0, 1fr) minmax(0, 1.25fr) 5.6em;
  gap: 14px; align-items: baseline;
  padding: 13px 0; border-bottom: 1px solid #222823;
}
.dlog__row:last-child { border-bottom: 0; }
.dlog__time { font-variant-numeric: tabular-nums; color: rgba(255, 255, 255, .6); white-space: nowrap; }
.dlog__door { font-weight: 600; }
.dlog__res {
  justify-self: end;
  font-size: 11.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  padding: 3px 8px; border-radius: 3px; border: 1px solid currentColor;
}
.dlog__res--ok  { color: var(--green-400); }
.dlog__res--chg { color: var(--gold-500); }
.dlog__res--no  { color: #e8755c; }
.dlog__row--change { background: rgba(240, 168, 48, .07); margin: 0 -20px; padding-left: 20px; padding-right: 20px; }
.dlog__row--change .dlog__who { color: #fff; }
/* "From home" is Sebek's: "All changes can be made from the comfort of
   their own home." A second, quieter line so the row stays two lines tall. */
.dlog__by { display: block; font-size: 12.5px; color: rgba(255, 255, 255, .6); }
.dlog__row--denied .dlog__door,
.dlog__row--denied .dlog__who { color: #fff; }

/* Phones: time and result share a line, door and person below. */
@media (max-width: 560px) {
  .dlog { padding: 14px 14px 6px; font-size: 14px; }
  .dlog__row { grid-template-columns: minmax(0, 1fr) auto; gap: 4px 12px; }
  .dlog__time { grid-column: 1; grid-row: 1; }
  .dlog__res  { grid-column: 2; grid-row: 1; }
  .dlog__door { grid-column: 1 / -1; }
  .dlog__who  { grid-column: 1 / -1; }
  .dlog__row--change { margin: 0 -14px; padding-left: 14px; padding-right: 14px; }
}

/* The animation. Rows arrive one by one; the script sets --d per row so the
   change row and the denied row each get a beat of their own. */
.dlog__row { transition: opacity .6s ease, transform .6s cubic-bezier(.2, .7, .2, 1); }
.dlog--armed .dlog__row { opacity: 0; transform: translateY(8px); }
.dlog--armed.dlog--go .dlog__row { opacity: 1; transform: none; transition-delay: var(--d, 0ms); }
.dlog--armed.dlog--go .dlog__row--denied .dlog__res--no { animation: dlog-flag .9s ease var(--d, 0ms) 1 both; }
@keyframes dlog-flag {
  0%   { background: transparent; }
  45%  { background: rgba(232, 117, 92, .28); }
  100% { background: transparent; }
}
@media (prefers-reduced-motion: reduce) {
  .dlog__row { transition: none; }
  .dlog__res--no { animation: none !important; }
}

/* Nov 4: the cutover glow (.cr__glow) sits at 96% of the desk with a 140px
   halo, so on a phone its right half ran past the viewport and the page
   scrolled sideways by 36px. Clip sideways only, so the halo still shows
   above and below the desk. */
@media (max-width: 760px) { .cr { overflow-x: clip; } }
