/* Evolve locked type system — Space Grotesk display + Satoshi body */
/* ONE face, not three.
   This is a variable font — fvar wght 300-700, default 300 — and all three blocks carried
   the identical payload. Declaring a single font-weight on a variable font tells the browser
   'this file IS that weight', so it never touched the axis: 500, 600 and 700 all rendered at
   the default 300 and the heading hierarchy did not exist. Measured, same string at 56px:
   300 -> 3806 ink pixels, 500 -> 5064, 700 -> 5765, once the range is declared.
   A range also means the 29KB is fetched once instead of three times. */
@font-face{font-family:'Space Grotesk';font-style:normal;font-weight:300 700;font-display:block;src:url(space-grotesk-300.a0d054c4.woff2) format('woff2');}

/* ============================================================================
   FutureSafe — Evolve. Website wireframes, Phase 1.

   The design law is BRIEF-website-03-evolve.md §1 and nothing here departs from it.
   Three things this stylesheet is built to make hard to get wrong:

   1. Every ground sets BOTH its background and its text color. There is no rule anywhere that
      sets a background alone, because a light card on a dark page inheriting the dark-ground
      secondary landed at 1.93:1 earlier in this run.
   2. Secondary text is a pair, not a value. Slate on light grounds, Mist on dark. No single
      value clears 4.5:1 on both navy and ice — the window between the two requirements is
      empty, so this is arithmetic and not taste.
   3. Signal sets type and rules on dark grounds only. On ice it is 1.52:1. Signal deep is the
      answer on light, at 4.78:1.
   ========================================================================= */

:root{
  --navy:#07141F; --raised:#0E2233; --ice:#EAF2F8; --paper:#FDFEFF;
  --blue:#008FE8; --bridge:#00BBD4; --signal:#00E0A4; --deep:#00795A;
  --slate:#5A6E83; --mist:#9DB2C4;
  --ramp:linear-gradient(118deg,#008FE8 0%,#00BBD4 52%,#00E0A4 100%);

  /* All spacing is a multiple of 8. Use the scale rather than arbitrary values. */
  --s1:8px; --s2:16px; --s3:24px; --s4:32px; --s5:48px; --s6:72px; --s7:112px; --s8:168px;
  /* The scroll felt stacked and close. The top of the scale is what governs how a page BREATHES —
     the distance between one idea ending and the next beginning — so that is where the room went,
     rather than inflating the gaps inside a section, which would only have loosened the content. */

  /* GA-02 — ONE value for the room around a picture, anywhere on the site.
   *
   * Every image block used to carry its own margin: 48px on the hero shots, 32px on the About Us
   * one, a flat 72px inside post and legal prose. Three numbers, none of which moved when the
   * screen narrowed, so a phone got the desktop gap — which is precisely what "images have too
   * much room between" describes. Worse, two of them DOUBLED: the hero shot's 48px margin sat
   * inside a grid whose gap was already 48, so the picture stood 96px clear of the words above it
   * while the sections either side of it were closer than that.
   *
   * One token, so an image block sits in the page at the same density as a text block and one edit
   * moves the whole site. It has a ceiling as well as a floor: --s5 is the padding `.doc-body`
   * carries at every width, and a picture with more room around it than the section holding it is
   * exactly the thing being fixed. It steps down once, on a phone. */
  --img-gap:var(--s5);

  --display:'Space Grotesk',system-ui,sans-serif;
  --body:'Satoshi',system-ui,sans-serif;
  --measure:68ch;
  color-scheme:dark;    /* pairs with the meta: Auto Dark must never repaint these pages */
}

*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}

/* HP-01 — in-page links glide, and land where you can read them.
 *
 * "See How It Works" was a raw hash jump: the page teleported, and because the header is sticky
 * and 65–77px tall, the heading it jumped to arrived underneath it. Two separate faults with one
 * cause — an anchor that was never told anything about the page it lives in.
 *
 * `scroll-padding-top` rather than a `scroll-margin-top` on each target: it is set once, on the
 * scrolling element, and it covers every anchor on the site — the section links, the seventeen
 * entries in a legal document's contents list, and anything the client adds through the editor
 * later. `--head-h` is the header's real height and is already re-declared at the width where the
 * bar gets shorter, so the offset follows it rather than repeating a number that will drift.
 *
 * The reduced-motion block below turns off animations and transitions, neither of which is what
 * scroll-behavior is, so it is switched off by name. The destination does not change; only whether
 * getting there is animated. */
html{scroll-behavior:smooth; scroll-padding-top:calc(var(--head-h) + var(--s3))}
@media (prefers-reduced-motion:reduce){ html{scroll-behavior:auto} }
body{
  margin:0; background:var(--navy); color:var(--paper);
  font-family:var(--body); font-weight:400; font-size:17px; line-height:1.65;
  -webkit-font-smoothing:antialiased; font-synthesis-weight:none;
}
img,svg{max-width:100%}
img{height:auto}

/* ── focus and skip ─────────────────────────────────────────────────────── */
:where(a,button,summary,[tabindex]):focus-visible{
  outline:3px solid var(--signal); outline-offset:3px; border-radius:2px;
}
.ice :where(a,button,summary):focus-visible,
.paper :where(a,button,summary):focus-visible{outline-color:var(--deep)}
.skip{
  position:absolute; left:var(--s2); top:-64px; z-index:99;
  background:var(--signal); color:var(--navy); padding:12px 20px; border-radius:6px;
  font-family:var(--body); font-weight:700; text-decoration:none; transition:top .12s;
}
.skip:focus{top:var(--s2)}

/* ── grounds ────────────────────────────────────────────────────────────────
   Four grounds, each a background AND a color. Nothing sets one without the other. */
.navy  {background:var(--navy);  color:var(--paper)}
.raised{background:var(--raised);color:var(--paper)}
.ice   {background:var(--ice);   color:var(--navy)}
.paper {background:var(--paper); color:var(--navy)}

/* Secondary ink is set on PROSE ONLY — never on `li` generically.
   `.ice li{color:slate}` beats `.wtile{color:navy}` on specificity, so a generic list rule
   quietly repainted the step headings and every partner tile in the secondary ink. Each list
   below states its own color instead, and headings are pinned per ground so nothing can
   demote them by inheritance. */
.navy p,.raised p{color:var(--mist)}
.ice p,.paper p{color:var(--slate)}
.navy .lead,.raised .lead{color:var(--paper)}
.ice .lead,.paper .lead{color:var(--navy)}

.navy :is(h1,h2,h3,h4),.raised :is(h1,h2,h3,h4){color:var(--paper)}
.ice :is(h1,h2,h3,h4),.paper :is(h1,h2,h3,h4){color:var(--navy)}

/* ── layout ─────────────────────────────────────────────────────────────── */
.wrap{width:min(1180px,calc(100% - var(--s5))); margin-inline:auto}
.band{position:relative; padding-block:var(--s8); overflow:hidden}
.band + .band{border-top:1px solid transparent}
/* A near-white block on a near-white ground with no edge is invisible. Ice meeting paper needs
   a line, and so does every light surface sitting on a light ground. */
.ice + .paper, .paper + .ice{border-top:1px solid #D3DFEA}

/* ── type ───────────────────────────────────────────────────────────────────
   Space Grotesk is display. Satoshi is everything read at length, and every figure. The two
   never swap lanes: if a rule below sets --display on something that is read at length, it is
   a bug, not a choice. */
/* ── the scale ──────────────────────────────────────────────────────────────
   Rebuilt against the measured reference (see 06-briefs/REFERENCE-AUDIT-bouayaben.md).

   The old scale topped out at 62px against 17px body — a 3.6 : 1 spread, which reads as a
   well-set document rather than a statement. The reference runs 144 : 17, or 8.5 : 1. The RATIO
   is the effect, not the absolute size: a page needs somewhere to go between its loudest and its
   quietest voice, and ours had less than half the distance available.

   Matching 8.5 : 1 outright was too far — a 124px headline ate whole viewports on a page that has
   to carry argument, not just a name. This sits at 92 : 17, or 5.4 : 1: half again on the original
   spread, which is where the contrast starts working without the page shouting.

   Two rules that come with the size and are not optional:
     · leading collapses below 1 as type grows — 0.95 on display. Sub-one leading is what stops a
       huge headline reading as a paragraph.
     · tracking tightens as type grows and LOOSENS as it shrinks. A flat -.024em everywhere was
       too tight at 13px and too loose at 120px. */
h1,h2,h3,h4{font-family:var(--display); font-weight:700; margin:0}
h1{font-size:clamp(42px,6.6vw,92px); line-height:.97; letter-spacing:-.026em}
h2{font-size:clamp(30px,4.4vw,56px); font-weight:600; line-height:1.02; letter-spacing:-.022em}
h3{font-size:clamp(21px,2.2vw,27px); font-weight:600; line-height:1.16; letter-spacing:-.015em}
h4{font-size:20px; font-weight:600; letter-spacing:-.012em}
p{margin:0 0 var(--s3); max-width:var(--measure)}
p:last-child{margin-bottom:0}

.lead{font-family:var(--body); font-weight:500; font-size:clamp(19px,1.85vw,24px); line-height:1.4;
  letter-spacing:-.008em; max-width:40ch}
h2 + .lead,h1 + .lead{margin-top:var(--s3)}
/* A heading and the sentence under it are one idea, and the gap has to say so.
 *
 * The system had a rule for a heading followed by a `.lead` and another for a `.standfirst`, and
 * none at all for a heading followed by an ordinary paragraph — which is most of them. Where that
 * happened the gap was literally zero: "Why Safeguard Matters" sat on its own first sentence at
 * 56px with nothing between them, and three headings on Book a call did the same at 27px.
 *
 * The values are the ones the system already uses rather than new ones: --s3 is what a lead gets
 * under the same headings, --s2 is what .tl-body and .card.svc give their h3s.
 *
 * Zero specificity, so every component that has already chosen its own gap keeps it. `.person h3`
 * pairs a name with a role at 4px and should; `.card h3` and `.step h3` sit tight at 12px by
 * design. This rule speaks only where nothing else has. */
:where(h1,h2):has(+ p){margin-bottom:var(--s3)}
:where(h3,h4):has(+ p){margin-bottom:var(--s2)}
/* A heading followed straight by a two-column block had no gap at all — the grids each carry
   their own top margin and `.split` did not. */
h2 + .split,h2 + .split-40,h2 + .figure-row{margin-top:var(--s5)}
/* 58ch of Satoshi at 22px is 884px — a lead line long enough to lose, and wide enough to run
   into the corner signature. 40ch holds it to a readable measure and clears the device. */
.standfirst{font-family:var(--body); font-weight:400; font-size:clamp(18px,1.5vw,22px);
  line-height:1.55; max-width:46ch; margin-top:var(--s4)}
.navy .standfirst,.raised .standfirst{color:var(--mist)}
.ice .standfirst,.paper .standfirst{color:var(--slate)}

.eyebrow{
  display:flex; align-items:center; gap:var(--s2);
  font-family:var(--body); font-weight:700; font-size:12px; letter-spacing:.16em;
  text-transform:uppercase; margin:0 0 var(--s4); max-width:none;
}
.navy .eyebrow,.raised .eyebrow{color:var(--signal)}
.ice .eyebrow,.paper .eyebrow{color:var(--deep)}
.data{font-variant-numeric:tabular-nums; font-weight:500}
.navy .data,.raised .data{color:var(--blue)}
.ice .data,.paper .data{color:var(--deep)}

/* ── the cascade ────────────────────────────────────────────────────────────
   Section opener, call to action, wayfinding. Never as pattern, fill or decoration. There is no
   rule in this file that tiles, repeats or backgrounds it. */
.chev{display:inline-flex; align-items:center; gap:var(--g,4px); flex:none}
.chev b{
  width:calc(var(--s) * .58); height:var(--s); background:var(--c);
  clip-path:polygon(0 0,42% 0,100% 50%,42% 100%,0 100%,58% 50%);
}

/* ── the signature ──────────────────────────────────────────────────────────
   The mark as a fine line, in the corner of a surface at 42% of its height, with a margin of
   0.15 × the mark's own height on both edges. Not used on paper — the panel goes on ice instead. */
/* 42% of a book page is a corner mark. 42% of a 700px web band is a billboard, so it is capped:
   the proportion is honored where the band is short, and the cap stops it becoming the surface. */
.sig{position:absolute; right:6.3%; bottom:6.3%; height:min(42%, 260px); width:auto;
  pointer-events:none}

/* GA-03 — the mark in the corner, or the mark under the ladder. Never the mark behind a button.
 *
 * The corner signature only works while the band has a corner to spare. Swept 20px at a time, the
 * mark's box crosses a call-to-action button at EVERY width from 360px to 1340px on the two pages
 * that carry it. Nobody had seen it below 560px because the old rule hid it there — which is why
 * the report came in as a tablet-and-landscape bug rather than a phone one, and why hiding it
 * harder would have been fixing the symptom.
 *
 * Below 1360px the mark stops being a corner signature and becomes a centered one, sitting in the
 * band's own bottom padding. Padding is outside the content box, so there is no width at which it
 * can collide with anything. Faded, because a mark that large at full strength directly under a
 * button reads as a third button.
 *
 * ── the part this got wrong twice, and why it is written this way now ───────
 *
 * The first version kept the corner treatment above 1360px, because the sweep said the mark
 * cleared the buttons from 1360 up. It cleared them by EIGHT PIXELS, on one Mac. CI failed at
 * exactly 1360 on three pages: a Linux runner has no Satoshi — the license keeps the font off
 * shared machines — so the labels set in a wider fallback and ate the eight pixels. A visitor with
 * the real font gets a third answer again.
 *
 * The lesson is not "move the breakpoint". Measuring where a mark clears a BUTTON is measuring
 * where it clears a piece of text, and the width of a piece of text is not a fact about the
 * layout. So the mark is now sized from the CONTENT COLUMN, which is a declared number:
 *
 *     gutter = (100% − column) ÷ 2 − 6.3% inset − 16px of air
 *
 * The buttons live inside that column, so a mark that cannot enter the column cannot reach a
 * button, whatever font the machine has. It grows from 121px at 1320 to its full 304px at about
 * 1760, with no breakpoint in between. Width drives it and `aspect-ratio` supplies the height,
 * because the old `height:min(42%,260px)` sized the mark from the band's height — the one
 * dimension with nothing to do with the collision.
 *
 * The second mistake was assuming one column. The stricter check above caught it immediately:
 * Safeguard's closing band is plain `.close`, on the 1180px measure, where the gutter is 23px at
 * 1440 and there is simply nowhere to sign. So the corner treatment is scoped to `.wrap-narrow`,
 * which is the only band that has a margin to put a mark in, and everything else takes the
 * centered one at every width. Two treatments, and which one a band gets follows from its measure
 * rather than from a list of page names.
 *
 * This is the "Is that something we want to change?" from the client review, answered: yes, and
 * this is Option A from the spec — the mark keeps its place in the page rather than being deleted
 * from every screen smaller than a desktop. */
.close .sig{
  right:auto; left:50%; translate:-50% 0;
  bottom:var(--s2); opacity:.5;
  /* Sized from its height here, with `aspect-ratio` off, because this mark is not fitting a gutter
     — it is sitting in a known 72–168px of band padding. */
  width:auto; height:40px; aspect-ratio:auto;
}
/* 1320px is where the narrow measure's gutter first holds a legible mark: (1320 − 880) ÷ 2 − 83 −
   16 = 121px. Below it there is a gutter, but not one worth signing in. */
@media (min-width:1320px){
  .close.wrap-narrow .sig{
    left:auto; right:6.3%; translate:none; bottom:6.3%; opacity:1;
    width:min(304px, calc((100% - var(--col)) / 2 - 6.3% - var(--s2)));
    height:auto; aspect-ratio:1104 / 944;
  }
}

/* ── buttons: the CTA ladder ────────────────────────────────────────────────
   Rung 1 commit, rung 2 consider, rung 3 explore. One rung 1 per page, always paired with a
   rung 2 at the page end. Signal fills the primary on dark grounds; on light it cannot, because
   Signal against ice is 1.52:1 and the button would not be distinguishable from the page — navy
   fills it there instead. */
.btn{
  display:inline-flex; align-items:center; gap:12px;
  font-family:var(--body); font-weight:700; font-size:15px; letter-spacing:.005em;
  padding:15px 26px; border-radius:8px; text-decoration:none; border:1.5px solid transparent;
  transition:transform .12s ease, background-color .12s ease, border-color .12s ease;
}
.btn:hover{transform:translateY(-1px)}
.primary{background:var(--signal); color:var(--navy)}
.primary:hover{background:#3AE9BA}
.primary.on-light{background:var(--navy); color:var(--paper)}
.primary.on-light:hover{background:#122636}
.secondary{background:transparent; color:var(--paper); border-color:var(--mist)}
.secondary:hover{border-color:var(--paper); background:rgba(157,178,196,.10)}
.secondary.on-light{color:var(--navy); border-color:var(--slate)}
.secondary.on-light:hover{border-color:var(--navy); background:rgba(90,110,131,.08)}

.ladder{display:flex; flex-wrap:wrap; gap:var(--s2); margin-top:var(--s4)}

.lateral{
  display:inline-flex; align-items:center; gap:8px; font-weight:700; font-size:15px;
  text-decoration:none; border-bottom:1.5px solid currentColor; padding-bottom:2px;
}
.navy .lateral,.raised .lateral{color:var(--signal)}
.ice .lateral,.paper .lateral{color:var(--deep)}
.lateral::after{content:"→"; font-weight:400}
.laterals{display:flex; flex-wrap:wrap; gap:var(--s4); margin-top:var(--s4)}

/* Body links inside running text — not the ladder, but they still have to clear AA.
   The selector was `.ice main a`, which asks for a <main> INSIDE a ground. The grounds are
   sections inside <main>, so it matched nothing and never had the chance to be wrong: no body
   prose on this site contained an inline link until the client editor let someone write one.
   Underlined as well as colored, because color alone is not a link. */
main :is(.navy,.raised) a:not(.btn):not(.lateral):not(.card){color:var(--signal)}
main :is(.ice,.paper) a:not(.btn):not(.lateral):not(.card){color:var(--deep)}
main .doc a:not(.btn):not(.lateral):not(.card){text-underline-offset:.18em}

/* ── header ─────────────────────────────────────────────────────────────── */
.site-head{position:sticky; top:0; z-index:40; background:rgba(7,20,31,.94);
  backdrop-filter:blur(10px); border-bottom:1px solid #16293A; color:var(--paper)}

/* The header is in flow and sticky, so a hero asking for 100svh produces header PLUS a screen —
   the hero runs past the fold and everything in it sits lower than it should. Named here so the
   hero can subtract it, and re-declared at the breakpoint where the bar gets shorter. */
:root{--head-h:77px}
.head-in{display:flex; align-items:center; gap:var(--s5); min-height:76px}
.brand{display:flex; align-items:center; flex:none}
/* The nav lockup renders at a 13.3px cap height. Below roughly 40px the guidelines require the
   flat or single-color master, so this is the flat one — checked, not assumed. */
.brand img{height:30px; width:auto; display:block}
/* The auto margin is what holds the right-hand end of the header together: it eats the free space
   between the logo and the nav, and the call to action rides along after it.
 *
 * Which makes hiding the nav catastrophic for one frame. `hidden` is display:none, the auto margin
 * goes with it, and the button falls back against the logo — a compact button sitting beside the
 * wordmark with no page names, which is exactly the glitch Raph kept seeing between pages. The nav
 * is hidden for a frame whenever the script syncs against a transient narrow viewport during a
 * cross-document navigation, and no amount of getting that timing right makes the collapse safe.
 *
 * So the free space has a second owner. When the nav is hidden the call to action takes the auto
 * margin itself and stays where it belongs. Nothing changes while the nav is visible, because the
 * rule does not match then. */
.site-head nav{margin-inline-start:auto}
.site-head nav[hidden] ~ .head-cta{margin-inline-start:auto}
.site-head nav ul{display:flex; gap:var(--s4); list-style:none; margin:0; padding:0}
/* A nav label is a phrase, not a paragraph: "About Us" broken across two lines is not a nav.
   With nowrap, a row that runs out of space overflows instead of quietly re-wrapping — which is the
   failure mode we want, because the resize gate can see an overflow and cannot see a wrap. */
.site-head nav a{white-space:nowrap}
.site-head nav a{color:var(--mist); text-decoration:none; font-weight:500; font-size:15px;
  padding-block:6px; border-bottom:2px solid transparent}
.site-head nav a:hover{color:var(--paper)}
.site-head nav a[aria-current="page"]{color:var(--paper); border-bottom-color:var(--signal)}
.head-cta{padding:11px 20px; font-size:14px; flex:none}

/* ── footer ─────────────────────────────────────────────────────────────── */
.site-foot{background:var(--raised); color:var(--paper); padding-top:var(--s7);
  border-top:1px solid #16293A}
/* `minmax(0,…)` on every track, because `1fr` is `minmax(auto,1fr)` and auto is the column's
   MIN-CONTENT width — which here is `salesteam@futuresafe.com`, an unbreakable 160px string. At
   320px the two-column footer had 120px to give it, so the track refused to shrink and the whole
   page scrolled sideways by 6px. Found at 320px, which nothing in this repo had ever measured;
   invisible at 375 and above. The wrap guard below is the other half: a track that CAN shrink is
   no use if the word inside it cannot. */
.foot-in{display:grid; grid-template-columns:1.5fr 1fr 1.3fr 1.2fr; gap:var(--s5);
  padding-bottom:var(--s6)}
.foot-in > *{min-width:0}
.foot-nav a,.foot-nav li{overflow-wrap:anywhere}
/* The one place on the site where the mark renders in full expression: 112px tall is a 41.5px
   cap height, which clears the 40px threshold. Below 768px it drops to the flat master. */
/* The footer lockup, kept in proportion when the column is narrower than it is.
 *
 * These asked for an exact height and `width:auto`, which is right until the reset's
 * `img{max-width:100%}` clamps the width in a narrow column. The height is explicit and the width is
 * not, so the width gives and the height does not: the mark rendered at 2.92:1 against its true
 * 4.84:1 — a squashed logo, on every page in the site, at every width below 620px. The large one did
 * the same between 840 and 960.
 *
 * `max-height` instead of `height` lets the image satisfy both constraints the way a replaced
 * element is supposed to: it scales down whole rather than distorting. Nothing changes at any width
 * where it already fitted. */
/* max-height rather than height, so the reset's `img{max-width:100%}` scales the mark down whole
   instead of squeezing it. A declared height with an undeclared width is what distorted it: the
   width gave and the height did not. */
.foot-lockup-sm{display:block; max-height:60px; height:auto; width:auto; max-width:100%}
.foot-brand p{font-size:15px; margin-top:var(--s3); color:var(--mist); max-width:34ch}
.social{display:flex; gap:var(--s3); list-style:none; margin:var(--s3) 0 0; padding:0}
.social a{color:var(--mist); font-size:14px; font-weight:700}
.foot-nav h2{font-family:var(--body); font-size:11px; font-weight:700; letter-spacing:.14em;
  text-transform:uppercase; color:var(--signal); margin:0 0 var(--s3)}
.foot-nav ul{list-style:none; margin:0; padding:0; display:grid; gap:12px}
.foot-nav li{color:var(--mist); font-size:15px}
.foot-nav a{color:var(--mist); text-decoration:none}
.foot-nav a:hover{color:var(--paper); text-decoration:underline}
/* The footer's ground is Raised, which is dark — so its secondary ink is Mist, not Slate.
   Slate here measured 3.08:1. This is the pair rule doing its job: the value is chosen by the
   ground, not by the role. */
.fl{display:block; font-size:11px; font-weight:700; letter-spacing:.1em; text-transform:uppercase;
  color:var(--mist); margin-bottom:2px}
.foot-base{border-top:1px solid #1B3145; padding-block:var(--s3) var(--s4)}
.foot-base p{font-size:13px; color:var(--mist); margin:0}

/* §5.2 — a link a thumb can actually hit.
 *
 * The footer carries twenty-odd links set at 15px, inline, 12px apart: an 18px tall target on a
 * 44px-wide finger, with the next one 30px below it. That is under WCAG's 24px floor before it is
 * anywhere near Apple's 44, and the footer is where the legal links and the support address live —
 * the two things somebody is most likely to be reaching for in a hurry. The same is true of a
 * legal document's contents list, which is seventeen 15px links stacked 8px apart.
 *
 * Scoped to a coarse pointer, so the desktop footer the client signed off is untouched to the
 * pixel. This is a phone problem and it gets a phone answer. Padding rather than a min-height,
 * because the padding is what carries the target out past the glyphs, and the gap shrinks to
 * match so the column does not simply become twice as tall. */
@media (hover:none) and (pointer:coarse){
  .foot-nav a,.social a,.foot-base a,.toc a{display:inline-block; padding-block:10px}
  .foot-nav ul{gap:4px}
  .social{gap:var(--s4)}
  .toc ol{gap:0}
}

/* ── hero ───────────────────────────────────────────────────────────────── */
.hero{padding-block:var(--s8) var(--s8)}
.hero h1{max-width:19ch}
.hero .ladder{margin-top:var(--s5)}

/* ── the gradient moment ────────────────────────────────────────────────────
   Exactly one per page, full-bleed, never behind running text. Contrast shifts across the ramp:
   Slate manages 1.46:1 against the blue end. Navy is the only ink that clears 4.5:1 at every
   stop — 5.40:1 at the worst of them — so anything set on the ramp is navy. */
.ramp{background:var(--ramp); color:var(--navy); padding-block:var(--s7)}
.ramp h2,.ramp h3,.ramp p,.ramp li,.ramp .eyebrow{color:var(--navy)}
.ramp .eyebrow{color:var(--navy)}
.ramp .btn.primary{background:var(--navy); color:var(--paper)}
.ramp .btn.secondary{color:var(--navy); border-color:rgba(7,20,31,.55)}
.ramp .rows{display:grid; gap:var(--s3); margin-top:var(--s4); max-width:none}
.ramp .rows p{font-family:var(--display); font-weight:600; font-size:clamp(24px,3.6vw,48px);
  letter-spacing:-.022em; line-height:1.08; margin:0; max-width:none; color:var(--navy)}

/* ── cards and grids ────────────────────────────────────────────────────── */
.grid2{display:grid; grid-template-columns:repeat(2,1fr); gap:var(--s4)}
.grid3{display:grid; grid-template-columns:repeat(3,1fr); gap:var(--s4)}
.grid4{display:grid; grid-template-columns:repeat(4,1fr); gap:var(--s3)}
.split{display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr); gap:var(--s6); align-items:start}
.split-40{display:grid; grid-template-columns:minmax(0,.85fr) minmax(0,1.15fr); gap:var(--s6); align-items:start}

.card{padding:var(--s4); border-radius:12px; display:block}
/* Cards set their own ground, so they set their own ink. A card on navy is raised; a card on
   ice is paper AND carries an edge, because a near-white block on a near-white ground with no
   edge is simply not there. */
.navy .card{background:var(--raised); color:var(--paper); border:1px solid #1B3145}
.raised .card{background:var(--navy); color:var(--paper); border:1px solid #1B3145}
.ice .card{background:var(--paper); color:var(--navy); border:1px solid #D3DFEA}
.paper .card{background:var(--ice); color:var(--navy); border:1px solid #D3DFEA}
.card h3{margin-bottom:12px}
.card p{font-size:16px; margin-bottom:0}
.navy .card p,.raised .card p{color:var(--mist)}
.ice .card p,.paper .card p{color:var(--slate)}

/* ── numbered sequences: wayfinding lives here ──────────────────────────── */
.steps{display:grid; grid-template-columns:repeat(4,1fr); gap:var(--s5); margin-top:var(--s5);
  list-style:none; padding:0; align-items:stretch}
.step{position:relative}
.step .num{font-family:var(--body); font-weight:700; font-size:13px; letter-spacing:.12em;
  font-variant-numeric:tabular-nums; display:block; margin-bottom:var(--s2)}
.navy .step .num,.raised .step .num{color:var(--blue)}
.ice .step .num,.paper .step .num{color:var(--deep)}
.step h3{margin-bottom:12px}
.step p{font-size:16px; max-width:32ch; margin:0}
/* The two chevrons between steps — the only place on a page the device may repeat. They sit in
   the middle of the gutter, belonging to neither step, which is what "between" has to look like:
   sat tight against the next number they read as part of it. */
.step .link{position:absolute; right:calc(var(--s5) / -2); top:15px; transform:translateX(50%) translateY(-50%)}
.step:last-child .link{display:none}

/* ── plain lists that used to be headings ───────────────────────────────── */
.ticks{list-style:none; margin:var(--s4) 0 0; padding:0; display:grid; gap:14px}
.ticks li{display:flex; gap:14px; align-items:flex-start; font-size:17px; line-height:1.5}
.navy .ticks li,.raised .ticks li{color:var(--mist)}
.ice .ticks li,.paper .ticks li{color:var(--slate)}
.ticks li::before{content:""; flex:none; width:8px; height:8px; border-radius:50%; margin-top:9px}
.navy .ticks li::before,.raised .ticks li::before{background:var(--signal)}
.ice .ticks li::before,.paper .ticks li::before{background:var(--deep)}
.ticks.two{grid-template-columns:repeat(2,1fr); gap:14px var(--s5)}

.rule-list{list-style:none; margin:var(--s4) 0 0; padding:0; display:grid;
  grid-template-columns:repeat(3,1fr); gap:var(--s3)}
.rule-list li{font-family:var(--display); font-weight:600; font-size:clamp(20px,2.4vw,30px);
  letter-spacing:-.02em; line-height:1.05; padding-top:var(--s3); border-top:2px solid}
.navy .rule-list li,.raised .rule-list li{border-color:var(--blue); color:var(--paper)}
.ice .rule-list li,.paper .rule-list li{border-color:var(--deep); color:var(--navy)}

/* ── pull quote ─────────────────────────────────────────────────────────── */
.pull{font-family:var(--display); font-weight:600; font-size:clamp(29px,4.8vw,64px);
  letter-spacing:-.024em; line-height:1.0; margin:0; max-width:19ch}
.pull em{font-style:normal; display:block}
.ramp .pull{color:var(--navy)}

/* ── placeholders ───────────────────────────────────────────────────────── */
.ph{margin:0; display:flex; flex-direction:column; justify-content:flex-end; gap:6px;
  padding:var(--s3); border-radius:12px; position:relative; overflow:hidden}
.ph.raised{background:var(--raised); color:var(--paper); border:1px dashed #24405A}
.ph.ice{background:var(--ice); color:var(--navy); border:1px dashed #B9CBDB}
.ph.paper{background:var(--paper); color:var(--navy); border:1px dashed #C9D8E5}
.ph-what{font-family:var(--body); font-weight:700; font-size:11px; letter-spacing:.14em;
  text-transform:uppercase}
.ph.raised .ph-what{color:var(--mist)} .ph.ice .ph-what,.ph.paper .ph-what{color:var(--slate)}
.ph-alt{font-size:13px; line-height:1.4; display:block}
.ph.raised .ph-alt{color:var(--mist)} .ph.ice .ph-alt,.ph.paper .ph-alt{color:var(--slate)}
.ph-alt i{font-style:normal; font-weight:700; font-size:10px; letter-spacing:.12em;
  text-transform:uppercase; margin-right:8px; padding:2px 6px; border-radius:3px}
.ph.raised .ph-alt i{background:#1B3145; color:var(--signal)}
/* Signal deep on #DCE7F0 is 4.31:1 — under AA for 10px text. The chip takes the next surface
   along instead: paper behind an ice placeholder (5.36:1), ice behind a paper one (4.78:1). */
.ph.ice .ph-alt i{background:var(--paper); color:var(--deep); border:1px solid #C9D8E5}
.ph.paper .ph-alt i{background:var(--ice); color:var(--deep)}

/* ── real photography ───────────────────────────────────────────────────────
   The same box a placeholder occupies, so swapping one for the other cannot move the layout.
   `object-fit:cover` and an explicit intrinsic width/height on the tag mean the space is reserved
   before the image decodes — without that, every page with a photo reflows as it loads and the
   reveal animations fire against a layout that then shifts under them. */
.shot{margin:0; position:relative; overflow:hidden; border-radius:12px; background:var(--raised)}
.shot.ice{background:#DCE7F0} .shot.paper{background:var(--ice)}
.shot img{width:100%; height:100%; object-fit:cover; display:block}
/* A photograph on a dark ground wants its edge softened into the band, not a hard rectangle
   floating on it. On the light grounds it takes a hairline instead, for the same reason a
   near-white tile needs one. */
.shot.raised{box-shadow:0 12px 34px rgba(0,0,0,.30)}
/* The hero photograph takes the same box the home hero's diagram takes, and hides at the same
   breakpoint the grid collapses at — a 6:5 photograph stacked under a 42px headline on a phone
   pushes the first call to action below the fold for no gain. */
.hero-shot{max-width:540px; margin-inline:auto; width:100%}
.believe-shot{margin-top:var(--s4)}
/* The hero shot carried `margin-top:var(--s5)` here at mobile widths, on top of a hero grid whose
   gap was already --s5. One picture, two owners of the space above it, 96px where every other
   break in the page was 48. GA-02's doubled spacing, in the one place it was most visible. The
   grid gap keeps the job; the margin is gone. */

/* The blog index hero is two lines and no ladder, so it takes a full-width strip rather than the
   square-ish art the other heroes carry — a 4:3 picture beside two lines reads as a caption. The
   strip stays on small screens; unlike the square art it costs almost no vertical room.
   It is not in a grid, so here the margin IS the single owner of the space above it. */
.hero-strip{margin-top:var(--img-gap); max-width:none}
@media (max-width:700px){ .hero-strip{aspect-ratio:16 / 7 !important} }

/* BL-01 — the strip stops being a fixed letterbox and takes the room the screen actually has.
 *
 * The source is 1200 × 900. A 21:6 box is 3.5:1, so `cover` was throwing away 62% of the picture's
 * height — and throwing away the SAME 62% at 1440px, at 1680px and at 1920px, because the ratio
 * was fixed and the crop fraction went with it. The bigger the display, the more obviously a
 * photograph was being served as a slice, which is what "cropped at the top and bottom on larger
 * desktop views" is describing.
 *
 * A fixed ratio cannot answer that, so the strip gives it up above the breakpoint and takes
 * whatever height the hero has left after the words. The hero is still one screen; the strip is
 * now the part of it that grows. Measured against the same three widths, share of the photograph
 * visible: 1440 × 900 → 44% (was 38), 1920 × 1080 → 58% (was 38). The crop stays centered, so it
 * opens up symmetrically top and bottom rather than sliding.
 *
 * The floor matters as much as the growth. On a short laptop — 1440 × 700 with chrome and a
 * bookmarks bar — the leftover is only 194px, which would be a worse letterbox than the one being
 * fixed. 340px is roughly what the fixed ratio gave, so the short-screen case is held level rather
 * than regressed, at the cost of a hero a little over one screen there. The cap stops the other
 * end: on a very tall display the strip would otherwise become a full-bleed photograph and the
 * page would open on a picture rather than a sentence.
 *
 * What this does not do is uncrop it. A 4:3 photograph in a strip is cropped at every width there
 * is; only a wide-format source shows the whole frame. This makes the crop generous and responsive
 * instead of fixed and mean. */
@media (min-width:1081px){
  .hero:has(.hero-strip){align-items:stretch}
  .hero:has(.hero-strip) > .wrap{display:flex; flex-direction:column; min-height:0}
  .hero-strip{
    aspect-ratio:auto !important;
    /* `flex:1 1 0`, not `1 1 auto`. With an auto basis the figure contributes its own intrinsic
       height — 990px, from the image's width and height attributes — to the hero's content, so the
       hero grew to fit the picture and then had no leftover to divide. A zero basis makes the
       strip contribute nothing and take only what is left, which is the whole idea. */
    flex:1 1 0;
    min-height:340px;
    max-height:60vh;
  }
}
.shot.ice,.shot.paper{box-shadow:0 8px 24px rgba(7,20,31,.09); outline:1px solid rgba(7,20,31,.07);
  outline-offset:-1px}

/* ── the partner wall ───────────────────────────────────────────────────────
   Ten full-color third-party marks on a dark ground, competing with a device and a three-stop
   ramp, is the hardest version of this problem across the three routes. It is not solved by
   fighting it: the wall goes on ice, where a vendor's own colors can be themselves.

   The marks are the client's own published artwork, which is line art on a white ground.
   `mix-blend-mode:multiply` drops that white into the light band and leaves every pixel of the
   mark itself alone. The alternative — knocking each one out to a flat white or navy silhouette —
   is the one thing a partner wall must not do to somebody else's trademark. */
/* Four columns rather than five, so the lead partner's 2×2 tile plus eight others fills three rows
   exactly. A five-column grid left a hole beside the lead that no arrangement of eight marks closes. */
.wall{list-style:none; margin:var(--s5) 0 0; padding:0;
  display:grid; grid-template-columns:repeat(4,1fr); gap:var(--s2)}
/* The lead partner — iboss. Twice the linear size of a standard tile, which is the top of the
   50–100% Jason asked for, and it earns that by area rather than by decoration: no accent, no
   badge, no border treatment. A wall that dresses one vendor's mark has started editing it. */
.pmark-lead{grid-column:span 2; grid-row:span 2}
.pmark-lead img{max-height:112px}
/* A partner whose artwork has not arrived yet. Named, in the brand's own display face, so the tile
   reads as reserved rather than broken — and so nobody mistakes it for their real mark. */
.pmark-pending span{font-family:var(--display); font-weight:600; font-size:clamp(20px,2.4vw,30px);
  letter-spacing:-.015em; color:var(--slate)}
.pmark-lead.pmark-pending span{font-size:clamp(30px,4vw,46px)}

.pmark{display:flex; align-items:center; justify-content:center; min-height:104px;
  padding:var(--s2) var(--s3);
  background:var(--paper); border:1px solid #D3DFEA; border-radius:10px}
/* On the paper ground a paper tile is the ground: it would read as an outline and nothing else.
   The tile takes ice there, which is the same relationship one step along the ramp of lights. */
.paper .pmark{background:var(--ice)}
.pmark img{max-width:100%; max-height:56px; width:auto; height:auto; object-fit:contain;
  mix-blend-mode:multiply}
.pmark{transition:transform .3s cubic-bezier(.22,.8,.28,1), border-color .3s ease, box-shadow .3s ease}
.pmark:hover{border-color:#9FBBD2; box-shadow:0 8px 22px rgba(7,20,31,.09)}

/* The mark itself must not be dimmed by a hover state — multiply already ties it to the tile. */

/* ── people ─────────────────────────────────────────────────────────────── */
.people{list-style:none; margin:var(--s5) 0 0; padding:0;
  display:grid; grid-template-columns:repeat(4,1fr); gap:var(--s3)}
.person .ph{aspect-ratio:1/1; margin-bottom:var(--s3)}
.person h3{font-size:20px; margin-bottom:4px}
.person .role{font-size:12px; font-weight:700; letter-spacing:.1em; text-transform:uppercase;
  margin:0 0 12px}
.navy .person .role,.raised .person .role{color:var(--blue)}
.ice .person .role,.paper .person .role{color:var(--deep)}
.person p{font-size:15px; max-width:none}

/* ── blog ───────────────────────────────────────────────────────────────── */
.posts{list-style:none; margin:var(--s6) 0 0; padding:0;
  display:grid; grid-template-columns:repeat(3,1fr); gap:var(--s5) var(--s4)}
.post .ph{aspect-ratio:768/403; margin-bottom:var(--s3)}
.post h3{margin-bottom:12px}
.post h3 a{text-decoration:none; color:inherit}
.post h3 a:hover{text-decoration:underline}
.post p{font-size:15px; max-width:none; margin-bottom:12px}
.post time{font-size:12px; font-weight:700; letter-spacing:.1em; text-transform:uppercase}
.navy .post time,.raised .post time{color:var(--mist)}
.ice .post time,.paper .post time{color:var(--slate)}

/* ── long-form reading: posts and legal ─────────────────────────────────────
   Satoshi at length, on ice, at a measure that does not need re-finding on every page. */
.doc{max-width:72ch; margin-inline:auto}
.doc p,.doc li{font-size:17px; line-height:1.72; max-width:none}
.ice .doc li,.paper .doc li{color:var(--slate)}
.navy .doc li,.raised .doc li{color:var(--mist)}
.doc h2{font-size:clamp(24px,2.4vw,30px); margin:var(--s6) 0 var(--s3)}
.doc h3{margin:var(--s5) 0 var(--s2)}
.doc ul,.doc ol{padding-left:1.3em; display:grid; gap:10px; margin:0 0 var(--s3)}
.docmeta{display:flex; flex-wrap:wrap; gap:var(--s3); font-size:13px; font-weight:700;
  letter-spacing:.1em; text-transform:uppercase; margin-top:var(--s3)}
.ice .docmeta,.paper .docmeta{color:var(--slate)}
/* The date, at the size it was drawn.
 *
 * `.docmeta` asks for 13px, but `.doc p` asks for 17px one tenth of a point more specifically, so
 * the byline rendered at full body-copy size — as loud as the sentence under it, on every post.
 * Matched at 0,2,0 so the metadata owns its own size. */
.doc p.docmeta{font-size:13px}
.toc{margin:var(--s5) 0 0; padding:var(--s4); border-radius:12px;
  background:var(--paper); color:var(--navy); border:1px solid #D3DFEA}
.toc h2{font-family:var(--body); font-size:11px; font-weight:700; letter-spacing:.14em;
  text-transform:uppercase; color:var(--deep); margin:0 0 var(--s2)}
.toc ol{margin:0; padding-left:1.2em; display:grid; gap:8px}
.toc a{color:var(--navy); font-size:15px}
.crumb{display:flex; align-items:center; gap:10px; font-size:12px; font-weight:700;
  letter-spacing:.1em; text-transform:uppercase; margin:0 0 var(--s4); flex-wrap:wrap}
.ice .crumb,.paper .crumb{color:var(--slate)}
.crumb a{text-decoration:none}

/* ── contact ────────────────────────────────────────────────────────────── */
.contact-card .big{font-family:var(--display); font-weight:600; font-size:clamp(19px,1.9vw,24px);
  letter-spacing:-.014em; text-decoration:none; display:block; margin-top:var(--s3); word-break:break-word}
.ice .contact-card .big,.paper .contact-card .big{color:var(--deep)}
.navy .contact-card .big,.raised .contact-card .big{color:var(--signal)}
.contact-card .tel{display:block; margin-top:12px; font-size:17px; font-weight:700;
  font-variant-numeric:tabular-nums}

/* ── review note ────────────────────────────────────────────────────────── */
.rnote{position:fixed; right:var(--s3); bottom:var(--s3); z-index:60; max-width:min(430px,calc(100vw - 32px))}
.rnote details{background:var(--paper); color:var(--navy); border:1px solid #C9D8E5;
  border-radius:12px; box-shadow:0 18px 44px rgba(7,20,31,.28)}
.rnote summary{cursor:pointer; padding:14px 18px; font-family:var(--body); font-weight:700;
  font-size:13px; line-height:1.4; color:var(--navy); border-radius:12px}
.rnote[open] summary,.rnote details[open] summary{border-bottom:1px solid #E2EAF2}
.rnote .rlead{font-size:13px; line-height:1.5; color:var(--navy); padding:14px 18px 0; margin:0;
  max-width:none}
.rnote ul{margin:12px 0 0; padding:0 18px 18px 34px; display:grid; gap:9px}
.rnote li{font-size:13px; line-height:1.5; color:var(--slate)}
.rnote li b{color:var(--navy)}

/* ── responsive ─────────────────────────────────────────────────────────────
   The current site is desktop-shaped. Checked at 375 / 768 / 1280 / 1920. */
@media (max-width:1080px){
  .wrap{width:min(1180px,calc(100% - var(--s4)))}
  .grid4{grid-template-columns:repeat(3,1fr)}
  /* Three columns would leave the 2-wide lead with a single orphan beside it on its rows. Two
     columns keeps the tessellation: lead 2×2, then four rows of two. */
  .wall{grid-template-columns:repeat(2,1fr)}
  .people{grid-template-columns:repeat(2,1fr)}
  .posts{grid-template-columns:repeat(2,1fr)}
  .foot-in{grid-template-columns:repeat(2,1fr); gap:var(--s5)}
  .steps{grid-template-columns:repeat(2,1fr); gap:var(--s5)}
  /* The wayfinding chevrons only mean anything while the four steps are one row. Wrapped into a
     2 × 2 grid they point from step 1 at step 2's number and from step 3 at step 4's — direction
     with nothing to direct. The device is removed rather than re-aimed. */
  .step .link{display:none}
  .site-head nav ul{gap:var(--s3)}
  /* The links tighten here, and the button must not tighten with them.
   *
   * Both dropped to the same 24px, so the row read link, link, link, link, link, link, button, all
   * evenly spaced — and the button stopped looking like the one thing on the row that is not a link.
   * Above this width the links sit 32px apart and the button stands 48px clear, and that difference
   * is the whole of the hierarchy. Kept proportional to the tighter row rather than reset to 48px,
   * which would crowd the logo at 900px. */
  .head-in{gap:var(--s4)}
}
/* The nav collapses EARLIER than the rest of the layout, because it runs out of room earlier.
 *
 * It used to share the 820px breakpoint with everything else, and between 821px and about 880px the
 * header was still in desktop mode while needing more width than it had: logo, six links and the
 * button wanted 842px inside 808px. The row could not shrink, so the PAGE grew — every page on the
 * site scrolled sideways by 18px at 840px, silently, on the one range nobody spot-checks.
 *
 * Found by sweeping widths 20px at a time rather than testing three sizes. It is invisible at 800px
 * and invisible at 900px.
 *
 * 900px was the first answer and it was still too low. It stopped the page scrolling sideways, but
 * between 905px and 940px the row survived by SHRINKING its links until the labels wrapped —
 * "About Us" and "Contact Us" breaking across two lines. Nothing overflowed, so nothing complained,
 * and the header just quietly stopped being a nav. Raph caught it in a window about 933px wide.
 *
 * A wrapped header is also why the button appeared to jump: a second row exists for a moment while
 * the webfont settles, so the call to action paints at the far left of it before the row reflows.
 *
 * Measured rather than guessed: the labels hold their line down to 960px and wrap at 940px. 980px
 * is that boundary with room for the call to action's own text to change, which it can — the client
 * can edit it. Everything else still changes at 820px, because everything else still fits there. */
@media (max-width:980px){
  /* Below this width the nav becomes a disclosure panel. The button is `hidden` in the markup and
     revealed by the script, so a browser with no JavaScript keeps a visible, working nav instead
     of a dead hamburger — the failure mode of every menu that hides itself in CSS. */
  .head-in{justify-content:space-between; min-height:64px; flex-wrap:wrap; gap:var(--s2)}
  :root{--head-h:65px}
  html:not(.nvd) .head-cta{display:none}
  html:not(.nvd) .nav-toggle{order:3}
  html:not(.nvd) .site-head nav{order:4; width:100%; margin-inline-start:0}
  html:not(.nvd) .site-head nav ul{flex-direction:column; gap:0; padding-bottom:var(--s2)}
  html:not(.nvd) .site-head nav li{border-top:1px solid #16293A}
  html:not(.nvd) .site-head nav a{display:block; padding:14px 2px; font-size:17px; border-bottom:0}
  html:not(.nvd) .site-head nav a[aria-current="page"]{border-bottom:0; color:var(--signal)}
}

@media (max-width:820px){
  body{font-size:16px}
  .band{padding-block:var(--s7)}
  .hero{padding-block:var(--s6) var(--s7)}
  .split,.split-40,.grid2,.grid3,.rule-list,.ticks.two{grid-template-columns:1fr; gap:var(--s4)}
  .rnote{right:var(--s2); bottom:var(--s2)}
}
@media (max-width:560px){
  :root{--img-gap:var(--s4)}
  /* BL-02 — `.posts` used to be in this list, which was a no-op that read as a decision: the
     listing is already two across from 1080px down, so repeating it here changed nothing and
     quietly guaranteed the phone kept two half-width cards. It steps to one column at 700px
     instead, below. */
  .grid4,.people{grid-template-columns:repeat(2,1fr)}
  /* One column on a phone, and the lead stops spanning — there is nothing left to span. */
  .wall{grid-template-columns:1fr}
  .pmark-lead{grid-column:span 1; grid-row:span 1}
  .steps{grid-template-columns:1fr; gap:var(--s4)}
  .step{padding-right:0}
  .step .link{display:none}
  .ladder .btn{width:100%; justify-content:center}
  .band{padding-block:var(--s6)}
  .head-cta{padding:10px 14px; font-size:13px}
  .head-cta .chev{display:none}
}
/* BL-02 — one post per row on a phone.
 *
 * The listing stepped 3 → 2 at 1080px and then stopped, so a 390px phone showed two cards 163px
 * wide: a thumbnail the size of a stamp, a title broken over four lines, and two tap targets side
 * by side across a screen a thumb has to hit one of. It is the whole point of the page and it was
 * the least usable thing on the site.
 *
 * 700px rather than the 768 tablet breakpoint, because 768 is where a tablet portrait genuinely
 * has room for two 352px cards and a phone in landscape does not. Below it, one card, full
 * measure. The card internals need nothing: `.post .ph` is a ratio and the title and excerpt were
 * already set from the content width, so they size up on their own.
 *
 * Placed after the 560px block on purpose — that block used to list `.posts` and would otherwise
 * win on source order. */
@media (max-width:700px){
  .posts{grid-template-columns:1fr; gap:var(--s5)}
}

@media (min-width:1600px){ .wrap{width:min(1320px,calc(100% - var(--s6)))} }

@media (prefers-reduced-motion:reduce){
  *{animation:none !important; transition:none !important}
  .btn:hover{transform:none}
}

/* ============================================================================
   Components introduced by the five remaining commercial pages and the two
   long-form templates. Same two disciplines as above: anything that sets a
   background sets an ink, and no generic element selector paints a component.
   ========================================================================= */

/* ── hero variants ──────────────────────────────────────────────────────── */
.hero.short{padding-block:var(--s7) var(--s7)}
.hero.short h1{max-width:16ch}
.lead-hero{margin-top:var(--s3)}
.kicker{font-family:var(--body); font-weight:700; font-size:13px; letter-spacing:.12em;
  text-transform:uppercase; margin:0 0 var(--s3)}
.navy .kicker,.raised .kicker{color:var(--signal)}
.ice .kicker,.paper .kicker{color:var(--deep)}
.hero .story{margin-top:var(--s5); gap:var(--s5)}
.hero .story p{max-width:46ch}
.hero .laterals{margin-top:var(--s4)}

/* ── the single rule line ───────────────────────────────────────────────────
   Safeguard's "No client contact. No loss of control. No added operational burden." is ONE line
   in the source, unlike the homepage's three. It is not split into three here, because splitting
   a sentence is a rewrite. */
.rule-single{font-family:var(--display); font-weight:600; font-size:clamp(22px,2.8vw,36px);
  letter-spacing:-.02em; line-height:1.12; padding-top:var(--s3); border-top:2px solid;
  max-width:22ch}
.navy .rule-single,.raised .rule-single{border-color:var(--blue); color:var(--paper)}
.ice .rule-single,.paper .rule-single{border-color:var(--deep); color:var(--navy)}

/* ── the figure ─────────────────────────────────────────────────────────────
   $500,000 is the only hard number anywhere on the site, and on the live page it is one h2 among
   sixty. Space Grotesk, tabular figures, given a line of its own. */
.figure-row{display:grid; grid-template-columns:auto minmax(0,1fr); gap:var(--s5);
  align-items:center; margin-top:var(--s6); padding-block:var(--s4); border-block:1px solid}
.ice .figure-row,.paper .figure-row{border-color:#D3DFEA}
.navy .figure-row,.raised .figure-row{border-color:#1B3145}
.figure{font-family:var(--display); font-weight:700; font-size:clamp(46px,7.4vw,100px);
  letter-spacing:-.03em; line-height:1; margin:0; font-variant-numeric:tabular-nums; max-width:none}
.ice .figure,.paper .figure{color:var(--deep)}
.navy .figure,.raised .figure{color:var(--signal)}
.figure-note{margin:0; max-width:46ch}
.tail{margin-top:var(--s4)}

/* ── lifecycle phases ───────────────────────────────────────────────────── */
.phases{list-style:none; margin:var(--s5) 0 0; padding:0; display:grid;
  grid-template-columns:repeat(3,1fr); gap:var(--s4)}
.phases li{padding-top:var(--s3); border-top:2px solid}
.navy .phases li,.raised .phases li{border-color:var(--signal)}
.ice .phases li,.paper .phases li{border-color:var(--deep)}
.phases h3{font-size:clamp(19px,1.9vw,23px); margin-bottom:10px}
.phases p{font-size:16px; margin:0; max-width:30ch}
.coverage-tail{margin-top:var(--s6); align-items:center}
.sub{margin-top:var(--s5); margin-bottom:var(--s2)}

/* ── service cards ──────────────────────────────────────────────────────── */
.cards{display:grid; grid-template-columns:repeat(3,1fr); gap:var(--s3); margin-top:var(--s5)}
.card.svc{display:flex; flex-direction:column}
.card.svc .num{font-family:var(--body); font-weight:700; font-size:12px; letter-spacing:.14em;
  font-variant-numeric:tabular-nums; margin-bottom:var(--s3)}
.navy .card.svc .num,.raised .card.svc .num{color:var(--blue)}
.ice .card.svc .num,.paper .card.svc .num{color:var(--deep)}
.card.svc h3{font-size:clamp(19px,1.8vw,22px); margin-bottom:var(--s2)}
.card.svc p + p{margin-top:var(--s2)}
.card.svc p{margin-bottom:0}
/* The last two of five sit on a three-column grid. Rather than leave a hole, they take the
   remaining width — the count is content, not layout. */
.cards .card.svc:nth-child(4){grid-column:span 2}

/* ── the word swap ──────────────────────────────────────────────────────────
   "Our job is / not to add noise. / to create stability." arrives from the live page as three
   fragments with empty elements between them. Rendered as the swap it plainly is: no words added,
   no words dropped, and the strike carries the meaning the empty elements used to. */
.swap{margin:var(--s4) 0 0; max-width:none; display:grid; gap:4px;
  font-family:var(--display); font-weight:600; letter-spacing:-.026em; line-height:1.0;
  font-size:clamp(28px,4.6vw,60px); color:var(--navy)}
/* Navy at 50% composites to 2.41:1 against the blue end of the ramp. At 72% it is 3.64:1, which
   clears the 3:1 large-text requirement at every stop — the strike and the weight carry the
   hierarchy, the opacity only softens it. */
.swap .stem{opacity:.72}
.swap s{text-decoration-thickness:2px; opacity:.72}
.swap b{font-weight:700}

/* ── about ──────────────────────────────────────────────────────────────── */
.role-lead{max-width:56ch}
.team-h{margin-top:var(--s7)}

/* ── contact ────────────────────────────────────────────────────────────── */
.contact-grid{margin-top:var(--s5)}
.contact-card{padding:var(--s5)}
.contact-card p{margin-bottom:0}
.contact-card .big{border-bottom:1.5px solid currentColor; padding-bottom:3px}
.ice .contact-card .tel,.paper .contact-card .tel{color:var(--navy); text-decoration:none}
.navy .contact-card .tel,.raised .contact-card .tel{color:var(--paper); text-decoration:none}

/* ── blog index ─────────────────────────────────────────────────────────── */
.post h2{font-size:clamp(20px,1.9vw,24px); font-weight:600; margin-bottom:12px}
.post h2 a{text-decoration:none; color:inherit}
.post h2 a:hover{text-decoration:underline}
.post time{display:block; margin-bottom:10px}
.posts.three{grid-template-columns:repeat(3,1fr)}
.posts.three h3{font-size:19px}

/* ── long-form: posts and legal ─────────────────────────────────────────────
   Satoshi at length on the light ground, at a 72-character measure set once and not improvised
   per page. This is the longest reading on the site — 5,502 words on the Privacy Policy alone. */
.doc-head{padding-block:var(--s7) var(--s4)}
.doc-head .doc h1{font-size:clamp(32px,4.2vw,52px)}
.doc-body{padding-top:var(--s5)}
/* The lead image on a post, and the gap under it.
 *
 * This said `.ph` — the placeholder — so every page drawn during review had 72px under its image
 * and every page with a REAL image had none: the body copy started against the photograph. The
 * design was reviewed and signed off at 72px; only the selector never learned about the real thing.
 * A post carries `.shot`, so it is named here too. */
/* GA-02 — one token for the room around a picture in running prose. A flat 72px did not move when
   the screen narrowed, so a phone got the desktop gap. */
.doc :is(.ph,.shot){margin-bottom:var(--img-gap)}
/* D-10, from the 8 September list, and it is an exception to the line above rather than an
   oversight in it.
 *
 * A post's LEAD image is not a picture sitting in a page — it is the thing between the headline and
 * the first sentence of the article, and it shipped with 0px of separation. Zak asked for daylight
 * there specifically and got 72px on 8 September. Putting it on --img-gap along with everything
 * else quietly took a third of that back, which is the sort of undoing nobody notices for a month.
 *
 * So it keeps its 72px, and only it: `> .shot:first-child` is the lead image and nothing else,
 * because the build writes it as the first child of `.doc`. Images further down the article are
 * prose images and stay on the token. */
.doc-body .doc > .shot:first-child{margin-bottom:var(--s6)}
.doc > h2:first-of-type{margin-top:var(--s5)}
/* The 100px scroll-margin that used to sit here is now `scroll-padding-top` on the scroller, set
   from --head-h and applied to every anchor on the site rather than to these two. Leaving it would
   have added to the new offset and landed the heading 189px down the screen. */
.crumb a{color:inherit}
.crumb a:hover{text-decoration:underline}
.toc{margin-bottom:var(--s6)}
.toc ol{counter-reset:none}
.toc a{text-decoration:none; border-bottom:1px solid transparent}
.toc a:hover{border-bottom-color:currentColor}

/* ── responsive, for the components above ───────────────────────────────── */
@media (max-width:1080px){
  .cards{grid-template-columns:repeat(2,1fr)}
  .cards .card.svc:nth-child(4){grid-column:auto}
  .phases{grid-template-columns:1fr; gap:var(--s3)}
  .posts.three{grid-template-columns:repeat(2,1fr)}
}
@media (max-width:820px){
  .figure-row{grid-template-columns:1fr; gap:var(--s3)}
  .cards,.contact-grid,.posts.three{grid-template-columns:1fr}
  .hero .story{gap:var(--s3)}
  .doc-head{padding-block:var(--s6) var(--s3)}
}

/* ── tables inside long-form documents ──────────────────────────────────────
   The Privacy Policy carries three of them and the live page renders them as a run of loose
   lines. A table wider than its measure scrolls inside its own box; the page body never does. */
.tw{overflow-x:auto; margin:0 0 var(--s5); border:1px solid #D3DFEA; border-radius:10px;
  background:var(--paper)}
.doc table{border-collapse:collapse; width:100%; min-width:520px; color:var(--navy)}
.doc th,.doc td{text-align:left; vertical-align:top; padding:14px 18px; font-size:15px;
  line-height:1.55; border-bottom:1px solid #E2EAF2}
.doc th{font-family:var(--body); font-weight:700; font-size:11px; letter-spacing:.12em;
  text-transform:uppercase; color:var(--deep); background:var(--ice); border-bottom:1px solid #D3DFEA}
.doc td{color:var(--slate)}
.doc td:first-child{color:var(--navy); font-weight:700; width:34%}
.doc tbody tr:last-child :is(td,th){border-bottom:0}

/* ── the mobile disclosure ──────────────────────────────────────────────────
   Ships hidden and is revealed by the script that can also reopen the panel, so no-JS keeps a
   working nav. Above 820px the button is gone and the panel is the ordinary inline nav. */
.vh{position:absolute; width:1px; height:1px; margin:-1px; padding:0; overflow:hidden;
  clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap; border:0}
.nav-toggle{display:none; align-items:center; justify-content:center; width:44px; height:44px;
  background:transparent; border:1px solid #1B3145; border-radius:8px; cursor:pointer; padding:0}
.nav-toggle[hidden]{display:none}
.nav-bars{display:grid; gap:4px; width:18px}
.nav-bars i{display:block; height:2px; background:var(--paper); border-radius:2px}
.nav-toggle[aria-expanded="true"]{border-color:var(--signal)}
/* The panel scrolls if the viewport is short — a nav taller than the screen with no scroll is a
   nav whose last item cannot be reached. */
/* One owner for the panel CTA, defined last and scoped to the header, so a later `display` rule
   in another block cannot silently win and drop it out of flex layout. */
.site-head .nav-panel-cta{display:none}
/* The same ownership has to cover COLOR, and for the same reason. This button is an <a> sitting
   inside .site-head nav, so `.site-head nav a{color:var(--mist)}` — 0,2,1 — outranked
   `.primary{color:var(--navy)}` at 0,1,0 and painted the label mist on the green gradient. The
   chevron kept its navy because it is drawn, not typed, which is the tell: an arrow you can see
   beside words you cannot. Matched at 0,3,1 so the button owns its own foreground. */
.site-head nav a.nav-panel-cta,
.site-head nav a.nav-panel-cta span{color:var(--navy); font-weight:700}
@media (max-width:980px){
  /* Revealed by a class stamped before first paint, not by the script removing `hidden` later.
     Waiting for the script left one frame where the header had neither the button nor the
     hamburger — just a logo and empty space — which is a smaller version of the same flicker.
     The `hidden` attribute stays in the markup and still does its job: with no JavaScript there is
     no `.js` class, so the toggle stays hidden and the full nav shows instead of a dead button. */
  html.js:not(.nvd) .nav-toggle{display:inline-flex}
  html:not(.nvd) .site-head .nav-panel-cta{display:flex; width:100%; justify-content:center;
    margin:var(--s2) 0 var(--s3)}
  html:not(.nvd) #nav-panel{max-height:calc(100vh - 64px); overflow-y:auto; overflow-x:hidden}
  html.nav-open:not(.nvd), html.nav-open:not(.nvd) body{overflow:hidden}
  /* Locking the background scroll is right; doing it with overflow:hidden takes away the scroll
     container that position:sticky resolves against, so the header dropped to its static place at
     the top of the DOCUMENT while the viewport stayed where it was. Open the menu 3,000px down the
     page and it opened 3,000px above your screen — the menu was working perfectly and was simply
     nowhere near you. Fixed positioning is anchored to the viewport and does not care about the
     lock. */
  html.nav-open:not(.nvd) .site-head{position:fixed; top:0; left:0; right:0}
  /* Fixed takes the bar out of flow, so everything under it jumps up by exactly its height the
     moment the menu opens — measured at 65px, and very visible with the scroll locked. Put the
     height back as padding for as long as the menu is open. */
  html.nav-open:not(.nvd) body{padding-top:var(--head-h)}
}

/* ============================================================================
   ICONS — drawn on the mark's angle system, stroked with the brand ramp.
   The set is bespoke; nothing from the live site is reused.
   ========================================================================= */
.isprite{position:absolute; width:0; height:0; overflow:hidden}
.ico{
  width:30px; height:30px; flex:none; display:block;
  fill:none; stroke:url(#ig); stroke-width:1.7;
  stroke-linecap:round; stroke-linejoin:round;
  /* The ramp is painted per-icon in the symbol's own 0–24 space, so an icon at any size
     traverses the whole ramp rather than sampling a slice of a page-wide gradient. */
  overflow:visible;
}
/* Signal is 1.52:1 on ice. The accent's ground picks its direction — it always has. */
.ico.on-light{stroke:url(#igL)}
/* The accent is the part that carries the icon's meaning; it is what moves. */
.ico .a{stroke-width:1.9}

/* ── the four behaviors ────────────────────────────────────────────────────
   Assigned per icon in icons.mjs, so the thing that animates is the thing that means
   something — not the whole glyph wobbling. Triggered from the enclosing item, so the
   hover target is the row, not a 28px square. */
.ico *{transform-box:fill-box; transform-origin:center; transition:transform .32s cubic-bezier(.22,.8,.28,1)}
.ico .a{transition:transform .32s cubic-bezier(.22,.8,.28,1), stroke-dashoffset .5s ease}

:is(.ico-row,.card,.step,.svc,.phases li):hover .m-slide .a{transform:translateX(2.5px)}
:is(.ico-row,.card,.step,.svc,.phases li):hover .m-pop   .a{transform:scale(1.22)}
:is(.ico-row,.card,.step,.svc,.phases li):hover .m-sweep .a{transform:translateY(4px)}
.m-draw .a{stroke-dasharray:1; stroke-dashoffset:0; pathLength:1}
:is(.ico-row,.card,.step,.svc,.phases li):hover .m-draw .a{
  animation:ico-redraw .62s cubic-bezier(.22,.8,.28,1)}
@keyframes ico-redraw{ from{stroke-dashoffset:1} to{stroke-dashoffset:0} }

/* the whole glyph acknowledges the hover, quietly */
:is(.ico-row,.card,.step,.svc,.phases li):hover .ico{transform:translateY(-1px)}
.ico{transition:transform .32s cubic-bezier(.22,.8,.28,1)}

/* ── icon rows: the list form ─────────────────────────────────────────────── */
.ico-list{list-style:none; margin:var(--s5) 0 0; padding:0; display:grid; gap:var(--s2)}
.ico-list.two{grid-template-columns:repeat(2,1fr); gap:var(--s2) var(--s5)}
.ico-row{
  display:flex; gap:var(--s3); align-items:flex-start; padding:14px 16px 14px 0;
  border-top:1px solid; font-size:17px; line-height:1.45;
}
.navy .ico-row,.raised .ico-row{border-color:#1B3145; color:var(--mist)}
.ice .ico-row,.paper .ico-row{border-color:#D3DFEA; color:var(--slate)}
.ico-row:hover{border-color:var(--signal)}
.ice .ico-row:hover,.paper .ico-row:hover{border-color:var(--deep)}
.ico-row span{padding-top:3px}

@media (max-width:820px){ .ico-list.two{grid-template-columns:1fr} }

/* ── icons inside the existing components ─────────────────────────────────── */
.step .ico{margin-bottom:var(--s3)}
.phases li{display:grid; gap:10px}
.phases li .ico{margin-bottom:4px}
.svc-top{display:flex; align-items:center; justify-content:space-between; margin-bottom:var(--s3)}
.svc-top .num{margin-bottom:0}
.card.svc .num{margin-bottom:0}

/* ============================================================================
   MOTION

   Everything here is scoped to `html.anim`, set by an inline script before paint and ONLY when
   the reader has not asked for reduced motion. No class → no hidden state, no transform, no
   observer. A browser that never runs the script shows the finished page rather than a blank one,
   which is the failure mode of every reveal library that hides in CSS and shows in JS.
   ========================================================================= */

.anim [data-reveal] > .wrap > *,
.anim [data-reveal] > .wrap { will-change:opacity, transform }

/* The band itself rises; its children follow, staggered, so a section arrives as a phrase rather
   than as one slab. */
.anim [data-reveal] .wrap > *{
  opacity:0; transform:translateY(18px);
  transition:opacity .62s cubic-bezier(.22,.8,.28,1), transform .62s cubic-bezier(.22,.8,.28,1);
}
.anim [data-reveal].in .wrap > *{opacity:1; transform:none}
.anim [data-reveal].in .wrap > :nth-child(2){transition-delay:.07s}
.anim [data-reveal].in .wrap > :nth-child(3){transition-delay:.14s}
.anim [data-reveal].in .wrap > :nth-child(4){transition-delay:.20s}
.anim [data-reveal].in .wrap > :nth-child(n+5){transition-delay:.25s}

/* Lists and grids stagger their own items — the interest is in the sequence, not the block. */
.anim [data-reveal] :is(.ico-list,.steps,.cards,.people,.posts,.wall,.phases,.rule-list) > *{
  opacity:0; transform:translateY(14px);
  transition:opacity .5s cubic-bezier(.22,.8,.28,1), transform .5s cubic-bezier(.22,.8,.28,1);
}
.anim [data-reveal].in :is(.ico-list,.steps,.cards,.people,.posts,.wall,.phases,.rule-list) > *{
  opacity:1; transform:none;
}
.anim [data-reveal].in :is(.ico-list,.steps,.cards,.people,.posts,.wall,.phases,.rule-list) > :nth-child(1){transition-delay:.10s}
.anim [data-reveal].in :is(.ico-list,.steps,.cards,.people,.posts,.wall,.phases,.rule-list) > :nth-child(2){transition-delay:.16s}
.anim [data-reveal].in :is(.ico-list,.steps,.cards,.people,.posts,.wall,.phases,.rule-list) > :nth-child(3){transition-delay:.22s}
.anim [data-reveal].in :is(.ico-list,.steps,.cards,.people,.posts,.wall,.phases,.rule-list) > :nth-child(4){transition-delay:.28s}
.anim [data-reveal].in :is(.ico-list,.steps,.cards,.people,.posts,.wall,.phases,.rule-list) > :nth-child(5){transition-delay:.34s}
.anim [data-reveal].in :is(.ico-list,.steps,.cards,.people,.posts,.wall,.phases,.rule-list) > :nth-child(n+6){transition-delay:.40s}

/* The hero does not wait to be scrolled to — it is already there. */
.anim .hero .wrap > *{opacity:0; transform:translateY(20px); animation:rise .8s cubic-bezier(.22,.8,.28,1) forwards}
.anim .hero .wrap > :nth-child(1){animation-delay:.05s}
.anim .hero .wrap > :nth-child(2){animation-delay:.13s}
.anim .hero .wrap > :nth-child(3){animation-delay:.21s}
.anim .hero .wrap > :nth-child(4){animation-delay:.29s}
.anim .hero .wrap > :nth-child(n+5){animation-delay:.36s}
@keyframes rise{ to{opacity:1; transform:none} }
.anim .hero .sig{opacity:0; animation:sig-in 1.5s cubic-bezier(.22,.8,.28,1) .25s forwards; transition:none}
@keyframes sig-in{ from{opacity:0; transform:translateY(24px)} to{opacity:1} }

/* ── hover: the glow ────────────────────────────────────────────────────────
   Cards pick up a ramp-lit edge and a lift. The glow is the brand ramp at low opacity behind the
   card, not a generic drop shadow — so the thing that lights up is the thing the brand is made of. */
.card,.pmark,.post .ph,.post .shot,.contact-card{position:relative; transition:transform .34s cubic-bezier(.22,.8,.28,1), border-color .34s ease}
.card::after,.contact-card::after{
  content:""; position:absolute; inset:-1px; border-radius:inherit; z-index:-1;
  background:var(--ramp); opacity:0; transition:opacity .34s ease; filter:blur(14px);
}
.anim .card:hover,.anim .contact-card:hover{transform:translateY(-3px)}
.card:hover::after,.contact-card:hover::after{opacity:.28}
.navy .card:hover,.raised .card:hover{border-color:#2C4A66}
.ice .card:hover,.paper .card:hover{border-color:#B9CBDB}

.anim .pmark:hover{transform:translateY(-2px)}

/* Post cards: the image plate lifts, the title picks up the accent. */
.post{transition:transform .34s cubic-bezier(.22,.8,.28,1)}
.anim .post:hover{transform:translateY(-3px)}
.ice .post:hover :is(h2,h3) a,.paper .post:hover :is(h2,h3) a{color:var(--deep)}
.navy .post:hover :is(h2,h3) a,.raised .post:hover :is(h2,h3) a{color:var(--signal)}

/* Lateral links: the arrow travels, the rule fills. */
.lateral{position:relative; border-bottom-color:transparent;
  background-image:linear-gradient(currentColor,currentColor);
  background-repeat:no-repeat; background-position:0 100%; background-size:0% 1.5px;
  transition:background-size .34s cubic-bezier(.22,.8,.28,1)}
.lateral::after{transition:transform .34s cubic-bezier(.22,.8,.28,1); display:inline-block}
.lateral:hover{background-size:100% 1.5px}
.anim .lateral:hover::after{transform:translateX(4px)}

/* Buttons: the cascade advances. */
.btn .chev b{transition:transform .3s cubic-bezier(.22,.8,.28,1)}
.anim .btn:hover .chev b:nth-child(1){transform:translateX(3px)}
.anim .btn:hover .chev b:nth-child(2){transform:translateX(4.5px)}
.anim .btn:hover .chev b:nth-child(3){transform:translateX(6px)}

/* Steps: the wayfinding chevrons lean forward as the row is read. */
.anim .steps:hover .step .link .chev b{transform:translateX(2px)}
.step .link .chev b{transition:transform .3s cubic-bezier(.22,.8,.28,1)}

/* Nav: the current-page rule grows rather than appearing. */
.site-head nav a{position:relative; transition:color .24s ease}
.site-head nav a::after{content:""; position:absolute; left:0; right:0; bottom:-2px; height:2px;
  background:var(--signal); transform:scaleX(0); transform-origin:left;
  transition:transform .3s cubic-bezier(.22,.8,.28,1)}
.anim .site-head nav a:hover::after{transform:scaleX(1)}

@media (prefers-reduced-motion:reduce){
  /* Belt and braces: the class is never set, but if anything above is reached it does nothing. */
  [data-reveal] .wrap > *,[data-reveal] :is(.ico-list,.steps,.cards,.people,.posts,.wall,.phases,.rule-list) > *{
    opacity:1 !important; transform:none !important; transition:none !important; animation:none !important;
  }
  .hero .sig{opacity:1 !important; animation:none !important}
}

/* ============================================================================
   RHYTHM — breaking the sameness.

   Every band was eyebrow → heading → two columns, at one width, with one vertical rhythm. Read
   end to end that is a template, not a page: the eye learns the shape by the third section and
   stops looking. Four levers, applied deliberately rather than everywhere:

     .band-tight / .band-tall   vertical rhythm varies with the weight of what is in the band
     .wrap-narrow / .wrap-wide  the measure itself changes, so the page has a waist
     .band-center               one band per page addresses the reader head-on
     .offset                    a column steps down, so a two-up stops reading as a table

   The grounds already alternate. This makes the SHAPE alternate too.
   ========================================================================= */

.band-tight{padding-block:var(--s7)}
.band-tall{padding-block:calc(var(--s8) + var(--s7))}
/* `--col` so the corner signature can size itself from this measure rather than repeating the
   number. The two have to agree — the mark's whole job is to stay outside this column — and a
   number written twice is a number that will disagree. */
.wrap-narrow{--col:880px}
.wrap-narrow > .wrap{width:min(var(--col),calc(100% - var(--s5)))}
.wrap-wide > .wrap{width:min(1400px,calc(100% - var(--s5)))}

.band-center{text-align:center}
.band-center .wrap > *{margin-inline:auto}
.band-center :is(p,.lead,.standfirst,h1,h2){max-width:34ch; margin-inline:auto}
.band-center .ladder,.band-center .laterals{justify-content:center}
.band-center .eyebrow{justify-content:center}

/* A stepped column: the second column starts lower, so the pair reads as a sequence rather than
   as two cells of a table. Only above the breakpoint — stacked, a step is just a gap. */
@media (min-width:1081px){
  /* `.offset` stepped the second column down to break the table feel. On The Problem it just read
     as misalignment — the heading and the list are a pair and should start on the same line. */
  .offset > div:last-child{margin-top:0}
  /* An asymmetric split, for sections where one side genuinely carries more. */
  .split-wide{grid-template-columns:minmax(0,1.35fr) minmax(0,.85fr)}
}

/* The hero gets a light behind the mark — Andrew's "glowy". It is the brand ramp, heavily blurred
   and clipped to the band, not a generic radial: the light in the corner is the same light the
   logo is made of. */
/* Full viewport. The band below was cresting into the fold and undercutting the headline — a hero
   that is nearly a screen tall reads as a mistake, where exactly one screen reads as a decision. */
/* One screen, and the content has to FIT one screen — pinning the height without sizing the type
   to it just moves the overflow below the fold, which is the problem it was meant to solve. The
   headline now shares its band with the graphic, so it takes the narrower column's measure. */
.hero{isolation:isolate; min-height:100svh; display:flex; align-items:center;
  padding-block:var(--s7)}
.hero-grid h1{font-size:clamp(36px,4.4vw,64px); line-height:1.0}
.hero-grid .standfirst{font-size:clamp(17px,1.25vw,19px); max-width:42ch; margin-top:var(--s3)}
.hero-grid .ladder{margin-top:var(--s5)}
.hero > .wrap{width:min(1180px,calc(100% - var(--s5)))}
.hero-grid{display:grid; grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);
  gap:var(--s7); align-items:center}
@media (max-width:1080px){
  /* One screen INCLUDING the header, not one screen after it. 100svh on its own made the hero a
     full screen that then began 77px down, so it overhung the fold by exactly the height of the
     bar and pushed its own content that much lower.
     Padding drops with it. At --s7 the hero carried 112px top and bottom on top of being a full
     screen tall, which on a 390px phone left 215px of nothing above the headline before a word
     was read. The content is flex-centered, so the padding only ever needed to be a floor for a
     headline too tall to center. */
  /* `safe` is the whole point of this line.
   *
   * A hero that centers its contents is right until the contents outgrow the screen, and then plain
   * `center` pushes the top of the copy ABOVE the top of the hero, where it cannot be scrolled back
   * to. `safe center` centers while there is room and falls back to the top when there is not, which
   * is what "nudge it tighter up under the header" asks for on About Us — a hero carrying a
   * two-column story block that will never be one screen tall on a phone, and should not pretend. */
  .hero{min-height:calc(100svh - var(--head-h)); padding-block:var(--s5); align-items:safe center}
  .hero-grid{grid-template-columns:1fr; gap:var(--img-gap)}
  /* The graphic stays, below the words. Hiding it left the full-height hero with nothing in the
     space it had just been given, and it is the one picture on the page that explains the product
     rather than decorating it. */
  .hero-art{max-width:340px}

  /* GA-01 — a hero that carries a picture sizes to what is in it, and the picture sits in the
   * middle of the room left over.
   *
   * The rule this replaces gave the copy column its own full screen and let the artwork follow
   * underneath. The reasoning was sound and the result was not, because the copy is shorter than a
   * screen on every page except Home: the column centered its content and banked the remainder as
   * dead space between the last button and the picture, while the picture itself finished flush
   * against the section below. Measured at 390px before this change — gap above the image, gap
   * below it:
   *
   *     Safeguard   277px / 0px        Services   204px / 0px        Home   118px / 0px
   *
   * That asymmetry IS the client's report: "the hero image defaults to the bottom of the screen
   * causing a large gap to appear". Nothing was positioning the image at the bottom. The gap above
   * it was the leftover of a screen the copy did not need, and there was no gap below it because
   * the hero ended where the image ended.
   *
   * So the full-screen minimum comes off any hero that has a picture in it, and the room either
   * side of that picture comes from one place: the grid gap above, the hero's own bottom padding
   * below, both `--img-gap`. Equal by construction rather than by arithmetic, at every width, with
   * no fixed margin to break at the next screen size. A hero with no picture — About Us — keeps
   * the full screen, because there is nothing to center and the copy fills it honestly.
   *
   * The hero is no longer exactly one screen tall on a phone. That was the earlier decision and
   * the client has now asked for the opposite in as many words: "This should remain central
   * between the buttons and the next section." */
  .hero:has(.hero-shot,.hero-strip,.hero-art){
    min-height:0;
    padding-block:var(--s5) var(--img-gap);
    display:block;
  }
}
@supports not (height: 100svh){
  /* svh is the small viewport height — the one that does not change as mobile browser chrome
     hides on scroll. Without it, vh is measured against the LARGE viewport and the hero is taller
     than the screen it is meant to fill. */
  @media (max-width:1080px){ .hero{min-height:calc(100vh - var(--head-h))} }
}
.hero::before{
  content:""; position:absolute; z-index:-1; pointer-events:none;
  right:-8%; top:-30%; width:min(760px,72vw); aspect-ratio:1;
  background:var(--ramp); opacity:.14; filter:blur(120px); border-radius:50%;
}
.anim .hero::before{animation:aura 14s ease-in-out infinite alternate}
@keyframes aura{
  from{transform:translate3d(0,0,0) scale(1); opacity:.11}
  to  {transform:translate3d(-4%,4%,0) scale(1.12); opacity:.18}
}

/* A full-bleed hairline of the ramp, for a band edge that needs marking without a whole moment. */
.edge-ramp{position:relative}
.edge-ramp::after{content:""; position:absolute; left:0; right:0; bottom:0; height:2px;
  background:var(--ramp); opacity:.85}

@media (prefers-reduced-motion:reduce){ .hero::before{animation:none} }
@media (max-width:1080px){
  .band-tall{padding-block:var(--s8)}
  .wrap-wide > .wrap{width:min(1180px,calc(100% - var(--s4)))}
}

/* Prose in two columns — for a centered band where a single 34ch measure would run too long. */
.two-col-prose{columns:2; column-gap:var(--s6); max-width:none; margin-top:var(--s4)}
.two-col-prose p{max-width:none; break-inside:avoid}
.band-center .two-col-prose{text-align:left}
.band-center .rule-list{text-align:left; margin-top:var(--s6)}
.band-center .pull{max-width:20ch}
@media (max-width:820px){ .two-col-prose{columns:1} }

/* ── the heading assembles ─────────────────────────────────────────────────
   Reference audit §3, at word level rather than character level — see the note in lib.mjs for why.
   One inline-block per word, each rising on a stagger, and each answering the cursor. */
/* `white-space:normal` and a max-width, so a word longer than the column can still break where
   breaking is legal. With `nowrap` it could not: on a 320px phone "Behind-the-scenes" is 318px of
   inline-block inside a 272px column, and it hung 22px past the edge of its own hero — the one word
   on the site with hyphens in it, at the one width nobody opens. Normal wrapping does not break
   words mid-syllable; it breaks at spaces and at existing hyphens, which is where this one should
   break anyway. Every shorter word is unaffected, because there is nothing in them to break. */
.anim .wsplit .word{display:inline-block; white-space:normal; max-width:100%; vertical-align:baseline;
  opacity:0; transform:translateY(.34em);
  animation:word-in .82s cubic-bezier(.19,1,.22,1) forwards;
  transition:transform .5s cubic-bezier(.19,1,.22,1), color .4s ease}
@keyframes word-in{ to{opacity:1; transform:none} }
.anim .wsplit .word:hover{color:var(--signal)}
.ice .anim .wsplit .word:hover,.paper .anim .wsplit .word:hover{color:var(--deep)}

/* ── the index row ──────────────────────────────────────────────────────────
   From the reference audit §2. A list of statements set as display type, one per full-width row,
   hairline-separated, ordinal far left, icon far right. Roughly 4× scale contrast inside one row.
   Only for items with no body copy — a row with a paragraph under it is a card. */
.index-list{list-style:none; margin:var(--s6) 0 0; padding:0; counter-reset:none}
.index-row{
  display:grid; grid-template-columns:auto 1fr auto; align-items:center; gap:var(--s5);
  padding:var(--s4) 0; border-top:1px solid;
  transition:border-color .35s ease;
}
.index-list li:last-child{border-bottom:1px solid}
.navy .index-row,.raised .index-row{border-color:#1B3145}
.ice .index-row,.paper .index-row{border-color:#D3DFEA}
.navy .index-list li:last-child,.raised .index-list li:last-child{border-bottom-color:#1B3145}
.ice .index-list li:last-child,.paper .index-list li:last-child{border-bottom-color:#D3DFEA}

.ix-n{font-family:var(--body); font-weight:700; font-size:12px; letter-spacing:.16em;
  font-variant-numeric:tabular-nums; padding-top:.4em}
.navy .ix-n,.raised .ix-n{color:var(--mist)}
.ice .ix-n,.paper .ix-n{color:var(--slate)}
.ix-t{font-family:var(--display); font-weight:600; font-size:clamp(21px,2.6vw,34px);
  line-height:1.08; letter-spacing:-.018em}
.navy .ix-t,.raised .ix-t{color:var(--paper)}
.ice .ix-t,.paper .ix-t{color:var(--navy)}
.index-row .ico{width:34px; height:34px}

/* HP-02 — the row still acknowledges the cursor, and it no longer moves the page to do it.
 *
 * It used to step in by animating `padding-left`, which is a layout property, on an element whose
 * second column is a text column. Three things followed from that, and the third is the bug the
 * client reported.
 *
 * 1. The step ran over 0.7s, so the whole row's text was re-laid-out on every frame of it.
 * 2. The ramp-fill block further down sets `padding-inline:var(--s3)` — 24px — on the row. Below
 *    820px the hover step set `padding-left:var(--s2)`, 16px. So on a phone the hover did not step
 *    the row IN. It pulled it 8px OUT, and handed the text column 8px it did not have before.
 * 3. Eight pixels is enough to un-wrap a line. Measured at 412px: hovering item 3 takes the row
 *    from 95px to 74px and lifts everything below it by 21px. "Causing the padding to increase or
 *    decrease depending on which ones you have highlighted" is a precise description of exactly
 *    this, written by someone who could see it happening and could not see the CSS.
 *
 * Two changes, and either alone would have been a patch rather than a fix.
 *
 * The step becomes `translate` on the row's three children. Translation is not layout: the text
 * column keeps its width to the pixel, at every width, for any length of copy the client later
 * writes into it, so no state of this component can move the page again. The ramp fill stays where
 * it is, which is correct — the fill IS the row, and only its contents step.
 *
 * And every hover affordance here is gated on a device that actually has a pointer. iOS Safari
 * synthesizes `:hover` on tap and LEAVES IT THERE until you tap something else, so on a phone all
 * of this fired on touch and stayed fired — which is why the report says items 6 and 7 are worst.
 * They are last: there is nothing below them to tap next, so their stuck state is the one you sit
 * looking at. On a phone the list is now a list. That is the honest version of it — the rows are
 * not links and there is nothing to open. */
@media (hover:hover) and (pointer:fine){
  .anim .index-row:hover > *{translate:var(--s2) 0}
  .navy .index-row:hover,.raised .index-row:hover{border-color:var(--signal)}
  .ice .index-row:hover,.paper .index-row:hover{border-color:var(--deep)}
}
.index-row > *{transition:translate .7s cubic-bezier(.19,1,.22,1)}

@media (max-width:820px){
  .index-row{grid-template-columns:auto 1fr; gap:var(--s3); padding:var(--s3) 0}
  .index-row .ico{display:none}
}

/* ── 1 · the heading unmask ─────────────────────────────────────────────────
   A fade says "here is a heading". A mask sliding off it says "you have arrived somewhere". The
   clip is on a wrapper so the type itself is never transformed — text scaled or translated during
   a transition is text rendered at a fraction of its real quality. */
.anim [data-reveal] :is(h2,.pull,.ix-t,.rule-single){
  clip-path:inset(0 0 108% 0);
  transition:clip-path .95s cubic-bezier(.19,1,.22,1);
}
.anim [data-reveal].in :is(h2,.pull,.ix-t,.rule-single){clip-path:inset(0 0 -12% 0)}
/* The h1 is handled per character instead — see the split. */
.anim [data-reveal] h1{clip-path:none}

/* ── 2 · hairlines draw ─────────────────────────────────────────────────────
   Borders cannot be animated from nothing, so the rule is a pseudo-element that scales from its
   left edge. It reads as the section drawing its own structure as you arrive. */
.index-row{position:relative; border-top-color:transparent !important}
.index-row::before{
  content:""; position:absolute; left:0; right:0; top:0; height:1px; background:currentColor;
  opacity:.22; transform:scaleX(0); transform-origin:left;
  transition:transform 1.1s cubic-bezier(.19,1,.22,1);
}
/* The closing hairline is the last row's own border-bottom, left static. It was drawn by a
   pseudo-element twice over: first on li:last-child::after, where it fought the hover fill for
   the same pseudo; then on the list's ::after, where it underlined the text mid-glyph, because
   the last row's box extends below the list's. A border needs no pseudo, cannot collide with
   the fill, and sits exactly on the row's real bottom edge. */
[data-reveal].in .index-row::before{transform:scaleX(1)}
.anim [data-reveal]:not(.in) .index-row::before{transform:scaleX(0)}
.index-row:nth-child(2)::before{transition-delay:.06s}
.index-row:nth-child(3)::before{transition-delay:.12s}
.index-row:nth-child(4)::before{transition-delay:.18s}
.index-row:nth-child(5)::before{transition-delay:.24s}
.index-row:nth-child(n+6)::before{transition-delay:.30s}

.rule-list li{position:relative; border-top-color:transparent !important}
.rule-list li::before{
  content:""; position:absolute; left:0; right:0; top:0; height:2px; background:currentColor;
  transform:scaleX(0); transform-origin:left;
  transition:transform 1s cubic-bezier(.19,1,.22,1);
}
.navy .rule-list li::before,.raised .rule-list li::before{background:var(--blue)}
.ice .rule-list li::before,.paper .rule-list li::before{background:var(--deep)}
[data-reveal].in .rule-list li::before{transform:scaleX(1)}
.rule-list li:nth-child(2)::before{transition-delay:.1s}
.rule-list li:nth-child(3)::before{transition-delay:.2s}

/* ── 3 · further, and slower ────────────────────────────────────────────────
   The old reveal moved 14–18px in .5–.62s, which is a twitch. The reference travels further and
   takes 0.7s+; that is what reads as deliberate rather than nervous. */
.anim [data-reveal] .wrap > *{transform:translateY(38px);
  transition:opacity .85s cubic-bezier(.19,1,.22,1), transform .85s cubic-bezier(.19,1,.22,1)}
.anim [data-reveal] :is(.ico-list,.steps,.cards,.people,.posts,.wall,.phases,.index-list) > *{
  transform:translateY(30px);
  transition:opacity .72s cubic-bezier(.19,1,.22,1), transform .72s cubic-bezier(.19,1,.22,1);
}
.anim [data-reveal].in .wrap > :nth-child(2){transition-delay:.10s}
.anim [data-reveal].in .wrap > :nth-child(3){transition-delay:.19s}
.anim [data-reveal].in .wrap > :nth-child(4){transition-delay:.27s}

/* ── 4 · letters answer the cursor ──────────────────────────────────────────
   The h1 is already split per character for its entrance. The same spans make each letter a hover
   target: it lifts, takes the accent, and settles back. Neighbours are nudged by the script, so
   the whole word responds rather than one letter twitching in isolation. */
/* the hover lives on .word — see "the heading assembles" above */

/* ── icons: draw themselves in, and answer harder ───────────────────────── */
.anim [data-reveal] .ico path{stroke-dasharray:1; stroke-dashoffset:1; pathLength:1}
.anim [data-reveal].in .ico path{animation:ico-draw 1.1s cubic-bezier(.19,1,.22,1) forwards}
.anim [data-reveal].in .ico path:nth-child(2){animation-delay:.09s}
.anim [data-reveal].in .ico path:nth-child(3){animation-delay:.17s}
.anim [data-reveal].in .ico path:nth-child(4){animation-delay:.24s}
@keyframes ico-draw{ to{stroke-dashoffset:0} }

/* hover, roughly doubled */
:is(.ico-row,.card,.step,.svc,.phases li,.index-row):hover .m-slide .a{transform:translateX(5px)}
:is(.ico-row,.card,.step,.svc,.phases li,.index-row):hover .m-pop   .a{transform:scale(1.42)}
:is(.ico-row,.card,.step,.svc,.phases li,.index-row):hover .m-sweep .a{transform:translateY(7px)}
:is(.ico-row,.card,.step,.svc,.phases li,.index-row):hover .m-draw  .a{animation:ico-redraw .72s cubic-bezier(.19,1,.22,1)}
:is(.ico-row,.card,.step,.svc,.phases li,.index-row):hover .ico{transform:translateY(-3px) scale(1.06)}
.ico,.ico *{transition:transform .42s cubic-bezier(.19,1,.22,1)}

@media (prefers-reduced-motion:reduce){
  [data-reveal] :is(h1,h2,.pull,.ix-t,.rule-single){clip-path:none !important}
  .index-row::before,.rule-list li::before{transform:none !important}
  .ico path{stroke-dashoffset:0 !important; animation:none !important}
}

/* ============================================================================
   THE CURSOR, AND THE GLOW
   ========================================================================= */


/* ── buttons glow ───────────────────────────────────────────────────────────
   The glow is the brand ramp behind the button, not a gray drop shadow — the light a button gives
   off should be made of the same thing the brand is. */
.btn{position:relative; isolation:isolate}
.btn::before{
  content:""; position:absolute; inset:-3px; z-index:-1; border-radius:inherit;
  background:var(--ramp); opacity:0; filter:blur(16px);
  transition:opacity .42s ease, inset .42s cubic-bezier(.19,1,.22,1);
}
.btn:hover::before{opacity:.75; inset:-9px}
.btn.secondary::before{opacity:0}
.btn.secondary:hover::before{opacity:.34; inset:-7px}
/* the fill sweeps rather than switches */
.btn.primary{background-image:linear-gradient(118deg,var(--signal),var(--bridge) 62%,var(--signal));
  background-size:220% 100%; background-position:0 0;
  transition:background-position .7s cubic-bezier(.19,1,.22,1), transform .34s cubic-bezier(.19,1,.22,1)}
.anim .btn.primary:hover{background-position:100% 0}
.btn.primary.on-light{background-image:none}

/* ============================================================================
   THE VERTICAL TIMELINE — "How This Works" / "How Safeguard Works"

   Four stages of one process, stacked, with a spine down the left that fills as you scroll past.
   Sideways was a misread: a horizontal track is a gallery, and this is a sequence you read top to
   bottom like everything else on the page.

   The fill is scroll-linked. Without JavaScript the spine is simply drawn in full and the steps
   are an ordinary stacked list — nothing here depends on the motion.
   ========================================================================= */
.tl{list-style:none; margin:var(--s6) 0 0; padding:0; position:relative; max-width:820px}
.tl-spine{position:absolute; left:23px; top:12px; bottom:12px; width:2px; background:currentColor;
  opacity:.16}
.tl-spine i{position:absolute; left:0; right:0; top:0; height:var(--tl-p,0%); display:block;
  background:var(--signal)}
.ice .tl-spine i,.paper .tl-spine i{background:var(--deep)}

.tl-step{position:relative; display:grid; grid-template-columns:48px 1fr; gap:var(--s5);
  padding-block:var(--s5)}
.tl-step:first-child{padding-top:0}
.tl-step:last-child{padding-bottom:0}
.tl-dot{position:relative; z-index:1; width:48px; height:48px; border-radius:50%;
  display:grid; place-items:center; border:1px solid;
  transition:border-color .5s ease, background-color .5s ease, transform .5s cubic-bezier(.19,1,.22,1)}
.navy .tl-dot,.raised .tl-dot{background:var(--navy); border-color:#1B3145}
.ice .tl-dot,.paper .tl-dot{background:var(--ice); border-color:#D3DFEA}
.paper .tl-dot{background:var(--paper)}
.tl-dot .ico{width:24px; height:24px}
/* the step the spine has reached lights up */
.tl-step.lit .tl-dot{border-color:var(--signal); transform:scale(1.08)}
.ice .tl-step.lit .tl-dot,.paper .tl-step.lit .tl-dot{border-color:var(--deep)}

.tl-body{padding-top:2px}
.tl-body .num{display:block; margin-bottom:var(--s2)}
.tl-body h3{margin-bottom:var(--s2)}
.tl-body p{max-width:48ch; margin:0}

@media (max-width:820px){
  .tl-step{grid-template-columns:40px 1fr; gap:var(--s3); padding-block:var(--s5)}
  .tl-dot{width:40px; height:40px}
  .tl-spine{left:19px}
}
@media (prefers-reduced-motion:reduce){
  .tl-spine i{height:100% !important}
}

/* ============================================================================
   THE CURSOR, AND THE GLOW
   ========================================================================= */

/* ── the cursor ─────────────────────────────────────────────────────────────
   A ring that follows the pointer with lag, swells over anything interactive, and takes a label
   where one is useful. Pointer-fine only — a touch device has no cursor to replace, and a stylus
   or a switch user should never lose the real one. The native cursor is NOT hidden: hiding it is
   how these break, because the moment the script stalls the page has no pointer at all. */
.cursor{
  position:fixed; top:0; left:0; z-index:80; pointer-events:none;
  width:34px; height:34px; margin:-17px 0 0 -17px; border-radius:50%;
  border:1.5px solid var(--signal); opacity:0;
  transition:width .34s cubic-bezier(.19,1,.22,1), height .34s cubic-bezier(.19,1,.22,1),
             margin .34s cubic-bezier(.19,1,.22,1), opacity .3s ease,
             background-color .34s ease, border-color .34s ease;
  display:grid; place-items:center;
}
.cursor.on{opacity:.9}
.cursor b{font-family:var(--body); font-weight:700; font-size:10px; letter-spacing:.14em;
  text-transform:uppercase; color:var(--navy); opacity:0; transition:opacity .24s ease;
  white-space:nowrap}
/* over something interactive it swells and fills */
.cursor.hot{width:76px; height:76px; margin:-38px 0 0 -38px; background:var(--signal);
  border-color:var(--signal)}
.cursor.hot b{opacity:1}
/* on light grounds the ring has to be the ink that works there */
.cursor.light{border-color:var(--deep)}
.cursor.light.hot{background:var(--deep); border-color:var(--deep)}
.cursor.light.hot b{color:var(--paper)}
@media (hover:none),(pointer:coarse){ .cursor{display:none} }
@media (prefers-reduced-motion:reduce){ .cursor{display:none} }

/* ── buttons glow ───────────────────────────────────────────────────────────
   The glow is the brand ramp behind the button, not a gray drop shadow — the light a button gives
   off should be made of the same thing the brand is. */
.btn{position:relative; isolation:isolate}
.btn::before{
  content:""; position:absolute; inset:-3px; z-index:-1; border-radius:inherit;
  background:var(--ramp); opacity:0; filter:blur(16px);
  transition:opacity .42s ease, inset .42s cubic-bezier(.19,1,.22,1);
}
.btn:hover::before{opacity:.75; inset:-9px}
.btn.secondary::before{opacity:0}
.btn.secondary:hover::before{opacity:.34; inset:-7px}
/* the fill sweeps rather than switches */
.btn.primary{background-image:linear-gradient(118deg,var(--signal),var(--bridge) 62%,var(--signal));
  background-size:220% 100%; background-position:0 0;
  transition:background-position .7s cubic-bezier(.19,1,.22,1), transform .34s cubic-bezier(.19,1,.22,1)}
.anim .btn.primary:hover{background-position:100% 0}
.btn.primary.on-light{background-image:none}

/* ============================================================================
   THE HORIZONTAL TIMELINE — "How This Works" / "How Safeguard Works"

   The section pins to the viewport and the four steps travel sideways as you scroll down. It is
   the right shape for the content: these are four stages of one process, and a horizontal track
   says "sequence" in a way a 2×2 grid never can.

   It is built so the fallback IS the design, not a degraded version of it. With no JavaScript, or
   under reduced motion, or on a narrow screen, `.track` is an ordinary flex row that scrolls with
   the finger and the section is a normal band. Nothing is hidden and nothing is pinned; the
   sideways reading is simply done by hand instead of by the scroll.
   ========================================================================= */
.rail{position:relative}
.track{
  /* it is an <ol> because it is an ordered process — the markers are carried by the 01–04 labels,
     so the browser's own numbering is turned off rather than shown alongside them */
  list-style:none; padding-left:0;
  display:flex; gap:var(--s5); margin-top:var(--s6); padding-bottom:var(--s3);
  overflow-x:auto; scroll-snap-type:x proximity; -webkit-overflow-scrolling:touch;
}
.track > *{flex:0 0 min(420px,78vw); scroll-snap-align:start}
.track::-webkit-scrollbar{height:4px}
.track::-webkit-scrollbar-thumb{background:#1B3145; border-radius:4px}
.ice .track::-webkit-scrollbar-thumb,.paper .track::-webkit-scrollbar-thumb{background:#D3DFEA}

/* the connecting line the steps travel along */
.track .step{position:relative; padding-top:var(--s5)}
.track .step::before{
  content:""; position:absolute; left:0; right:calc(var(--s5) * -1); top:14px; height:2px;
  background:currentColor; opacity:.16;
}
.track .step:last-child::before{right:0}
.track .step::after{
  content:""; position:absolute; left:0; top:9px; width:12px; height:12px; border-radius:50%;
  background:var(--signal);
}
.ice .track .step::after,.paper .track .step::after{background:var(--deep)}

/* ── pinned mode — added by script only when it can be done properly ──────── */
.rail.pinned{height:var(--rail-h)}
.rail.pinned .rail-in{
  position:sticky; top:0; height:100vh; display:flex; flex-direction:column;
  justify-content:center; overflow:hidden;
}
.rail.pinned .track{
  overflow:visible; margin-top:var(--s5); padding-bottom:0;
  transform:translate3d(calc(var(--rail-x) * -1),0,0);
  transition:none; scroll-snap-type:none;
}
.rail.pinned .track > *{flex:0 0 clamp(320px,30vw,460px)}
/* a progress rule across the foot of the pinned section */
.rail.pinned .rail-bar{position:relative; height:2px; margin-top:var(--s6); background:currentColor;
  opacity:.14}
.rail.pinned .rail-bar i{position:absolute; inset:0 auto 0 0; width:var(--rail-p); background:var(--signal)}
.ice .rail.pinned .rail-bar i,.paper .rail.pinned .rail-bar i{background:var(--deep)}
.rail:not(.pinned) .rail-bar{display:none}

@media (prefers-reduced-motion:reduce){
  .rail.pinned{height:auto !important}
  .rail.pinned .rail-in{position:static; height:auto}
  .rail.pinned .track{transform:none; overflow-x:auto}
}

/* `position: sticky` does not work inside an ancestor with `overflow: hidden` — the band clips its
   own corner signature, which quietly disabled the pin and left a 1,420px empty section. The band
   that carries a pinned rail opts out of the clip. */
.band:has(.rail.pinned){overflow:visible}
.band:has(.rail.pinned) > .wrap{overflow:visible}

/* ── the one-pager page ─────────────────────────────────────────────────────
   The number the website does not currently carry, given a band of its own. */
.bignum{font-family:var(--display); font-weight:700; font-size:clamp(96px,17vw,240px);
  line-height:.82; letter-spacing:-.04em; margin:0; font-variant-numeric:tabular-nums;
  display:flex; align-items:baseline; justify-content:center; gap:.12em}
.navy .bignum,.raised .bignum{color:var(--signal)}
.ice .bignum,.paper .bignum{color:var(--deep)}
.bignum small{font-size:.18em; font-weight:600; letter-spacing:-.01em}
.bignum-note{font-family:var(--display); font-weight:600; font-size:clamp(22px,3vw,40px);
  letter-spacing:-.022em; line-height:1.05; margin-top:var(--s3); max-width:none}
.navy .bignum-note,.raised .bignum-note{color:var(--paper)}
.ramp-note{margin-top:var(--s4); color:var(--navy); max-width:52ch; font-size:clamp(17px,1.5vw,20px)}
.band-center .ramp-note{margin-inline:auto}

/* ── the strip ──────────────────────────────────────────────────────────────
   Not every section needs a room. "Safeguard is built into what we deliver" is a single statement
   with one supporting paragraph — it earns a band the height of its content and no more. */
.band.strip{padding-block:var(--s7)}
.band.strip .split{gap:var(--s7); align-items:center}
.band.strip h2{font-size:clamp(26px,3.2vw,40px); line-height:1.06}
.band.strip p{font-size:17px; line-height:1.6}
.band.strip .laterals{margin-top:var(--s3)}

/* ── 5 · the index row fills with the ramp on hover ──────────────────────────
   The whole strip lights, and the ink goes to paper so it stays legible on the gradient at every
   stop — navy would be right for type ON the ramp, but this is type OVER a ramp that has just
   arrived under it, and paper is what holds against the blue end. */
.index-row{overflow:hidden}
.index-row::after{
  content:""; position:absolute; inset:0; z-index:-1; background:var(--ramp);
  transform:scaleX(0); transform-origin:left;
  /* Faster than the ink, and fast-start: the fill must be UNDER the text before the text darkens.
     Independently-timed, the ink used to arrive first and sit navy on the navy band for a beat. */
  transition:transform .5s cubic-bezier(.19,1,.22,1);
}
.index-row{z-index:0; padding-inline:var(--s3); margin-inline:calc(var(--s3) * -1)}
/* Pointer devices only — see HP-02 above. Left ungated, a tap on iOS lights a row with the full
   brand ramp and leaves it lit, and tapping through 1 to 7 leaves a trail of them. */
@media (hover:hover) and (pointer:fine){
  .index-row:hover::after{transform:scaleX(1)}
  .index-row:hover .ix-t,.index-row:hover .ix-n{color:var(--navy)}
  .index-row:hover .ico{stroke:var(--navy)}
}
/* `translate` is named in both transition lists below because a transition SHORTHAND resets every
   other transition on the element — the same trap the reveal system fell into, noted underneath. */
.index-row .ix-t,.index-row .ix-n,.index-row .ico{
  transition:color .3s ease .08s, stroke .3s ease .08s,
             translate .7s cubic-bezier(.19,1,.22,1)}
/* The reveal system declares `transition: clip-path …` on .ix-t at (0,3,0), and a transition
   SHORTHAND resets every other transition on the element — so the ink flip above never animated
   inside a reveal band: it snapped to navy in one frame. This rule outranks it at (0,4,0) and
   carries both jobs, so the title can still unclip on arrival AND ease its ink on hover. */
.anim [data-reveal] .index-row .ix-t,
.anim [data-reveal] .index-row .ix-n,
.anim [data-reveal] .index-row .ico{
  transition:clip-path .95s cubic-bezier(.19,1,.22,1), color .3s ease .08s, stroke .3s ease .08s,
             translate .7s cubic-bezier(.19,1,.22,1)}
/* the list itself sits back a size — the section heading carries the volume, not every row */
.ix-t{font-size:clamp(19px,1.9vw,26px) !important; line-height:1.2 !important}
.index-row{padding-block:var(--s3)}

/* ── the hero graphic ───────────────────────────────────────────────────────
   An escalation path on the mark's own 60° lattice: an alert arrives at the edge, is detected,
   triaged, escalated, answered. The route is lit and the standing mesh is not, so the eye is
   told where to look, and the nodes lean toward the pointer. */
.hero-art{position:relative; aspect-ratio:1; width:100%; max-width:540px; margin-inline:auto}
.hero-art svg{width:100%; height:100%; overflow:visible}

/* The standing mesh — present, and subordinate. At #1B3145 on .35 it was invisible against navy,
   so the diagram read as seven loose dots rather than as a system with a route through it. */
.ha-edges line{stroke:#2C4E68; stroke-width:.45}

/* The route stays legible between pulses. Without a resting rail the path only existed while the
   dash was on it, which is half the reason the graphic looked empty. */
.ha-rail{fill:none; stroke:url(#haG); stroke-width:.6; opacity:.3;
  stroke-linecap:round; stroke-linejoin:round}

/* Two dashes share the route: a dim trail and a bright head. Both patterns sum to exactly 100,
   which is the polyline's pathLength — so each wraps seamlessly and there is ALWAYS one dash in
   flight. The first version ran a 22-unit dash against a 210-unit gap on a 108-unit path: for
   roughly half of every loop nothing at all was moving. */
.ha-trail,.ha-head{fill:none; stroke:url(#haG); stroke-linecap:round; stroke-linejoin:round}
.ha-trail{stroke-width:.6; opacity:.5; stroke-dasharray:26 74; stroke-dashoffset:100}
.ha-head{stroke-width:.95; stroke-dasharray:4 96; stroke-dashoffset:100;
  filter:drop-shadow(0 0 1.6px rgba(0,224,164,.85))}
/* Two signals, running opposite ways and at different speeds.
   One pulse alone reads as a loading bar — it arrives, it stops, it starts again. Traffic in both
   directions is what the diagram is actually describing: an alert travelling up to be answered,
   and the answer coming back down. The periods are deliberately not multiples of each other
   (4.6s against 6.1s), so the two never settle into lockstep and the graphic never looks like it
   is repeating. */
.anim .ha-trail,.anim .ha-head{animation:ha-run 4.6s linear infinite}
.anim .ha-back{animation:ha-run-back 6.1s linear infinite}
@keyframes ha-run{ from{stroke-dashoffset:100} to{stroke-dashoffset:0} }
@keyframes ha-run-back{ from{stroke-dashoffset:0} to{stroke-dashoffset:100} }

/* The return signal: dimmer and thinner than the outbound one, so the route still reads in one
   direction and this sits underneath it as an answer rather than competing for the same line. */
.ha-back{fill:none; stroke:url(#haG); stroke-linecap:round; stroke-linejoin:round;
  stroke-width:.5; opacity:.34; stroke-dasharray:3 97; stroke-dashoffset:0}

/* Nodes: an entry, three transits, a terminus, and the standing work. Uniform dots were the
   other half of the problem — nothing marked where the story began or ended. */
.ha-dot{fill:var(--signal)}
.ha-n.entry .ha-dot{fill:none; stroke:var(--blue); stroke-width:.62}
.ha-n.mid   .ha-dot{fill:var(--bridge)}
.ha-n.term  .ha-dot{fill:var(--signal); filter:drop-shadow(0 0 2.4px rgba(0,224,164,.7))}
.ha-n.ctx   .ha-dot{fill:#4A7391}

/* Only the two ends breathe. Seven pulsing halos was noise. */
.ha-halo{fill:none; stroke-width:.3; opacity:.32; stroke:var(--signal)}
.ha-n.entry .ha-halo{stroke:var(--blue)}
.anim .ha-n.entry .ha-halo{animation:ha-pulse-e 3.6s ease-in-out infinite}
.anim .ha-n.term  .ha-halo{animation:ha-pulse-t 3.6s ease-in-out infinite; animation-delay:.9s}
@keyframes ha-pulse-e{ 0%,100%{r:5.1; opacity:.34} 50%{r:7.4; opacity:.05} }
@keyframes ha-pulse-t{ 0%,100%{r:5.5; opacity:.40} 50%{r:8.6; opacity:.05} }

/* Micro-labels. Every one is a word the live site already uses — this introduces no new copy.
   Mist on navy is 8.5:1; mist is the DARK-ground secondary and the hero is the only ground it
   appears on here. */
.ha-lb{font-family:var(--body); font-weight:700; font-size:2.5px; letter-spacing:.15em;
  text-transform:uppercase; fill:var(--mist)}
.ha-n.term .ha-lb{fill:var(--signal)}
.ha-n.ctx  .ha-lb{fill:#6E8CA4}

.ha-n{transition:transform .5s cubic-bezier(.19,1,.22,1)}
@media (prefers-reduced-motion:reduce){
  .ha-trail,.ha-head{stroke-dasharray:none; stroke-dashoffset:0; animation:none}
  .ha-back{display:none}
  .ha-halo{animation:none}
}

/* ── 6 · the timeline down the middle, steps alternating ────────────────────
   A spine down the center with the steps taking turns either side. It reads as a sequence with
   two hands rather than a list with a rule beside it — and each side gets the full measure.
   Below the breakpoint it collapses to the single left-hand spine, because alternating sides in
   a 375px column is just a zigzag. */
@media (min-width:1081px){
  .tl{max-width:none}
  .tl-spine{left:50%; margin-left:-1px}
  .tl-step{grid-template-columns:1fr 96px 1fr; gap:0; align-items:center; padding-block:var(--s5)}
  .tl-step .tl-dot{grid-column:2; justify-self:center}
  .tl-step.left  .tl-body{grid-column:1; grid-row:1; text-align:right; padding-right:var(--s5)}
  .tl-step.right .tl-body{grid-column:3; grid-row:1; text-align:left;  padding-left:var(--s5)}
  .tl-step.left  .tl-body p{margin-left:auto}
  .tl-body p{max-width:38ch}
  /* the spine glows as the fill passes */
  .tl-spine i{box-shadow:0 0 14px 2px rgba(0,224,164,.55)}
  .ice .tl-spine i,.paper .tl-spine i{box-shadow:0 0 14px 2px rgba(0,121,90,.4)}
  /* the icon draws itself in as its step lights */
  .tl-step .ico path{stroke-dasharray:1; stroke-dashoffset:1}
  .tl-step.lit .ico path{animation:ico-draw .8s cubic-bezier(.19,1,.22,1) forwards}
  .tl-step.lit .ico path:nth-child(2){animation-delay:.08s}
  .tl-step.lit .ico path:nth-child(3){animation-delay:.15s}
  .tl-step.lit .tl-dot{box-shadow:0 0 22px 3px rgba(0,224,164,.30)}
  .ice .tl-step.lit .tl-dot,.paper .tl-step.lit .tl-dot{box-shadow:0 0 22px 3px rgba(0,121,90,.22)}
}
@media (prefers-reduced-motion:reduce){
  .tl-step .ico path{stroke-dashoffset:0 !important; animation:none !important}
}

/* ── 7 · the guarantee band breathes at the foot ────────────────────────────
   The copy sat centered in the band and read as floating. More room below than above lifts it and
   gives the gradient somewhere to run out. */
.ramp{padding-block:var(--s7) var(--s8)}

/* ── 8 · the closing statement arrives a line at a time ─────────────────────
   The last thing on the page is the only thing on its band, so it can afford to be read to you.
   Clipped per line and unmasked in sequence — the same device as the section headings, given the
   room to be noticed. */
/* [data-reveal].close, not [data-reveal] .close. The band carries BOTH on the same element —
   <section class="band navy close ..." data-reveal> — so the descendant combinator that was here
   asked for a .close INSIDE a [data-reveal] and never matched anything. The whole block was dead:
   the per-line reveal never ran, and worse, .ln never became a block, so the line spans the script
   builds ran together inline and ate the space at every line break. "your environment" rendered as
   "yourenvironment" on every page with a closing statement. */
/* A close band with no closing statement. The blog page has none of its own and deliberately
   does not borrow one, but it kept the padding of a band built to hold a headline — so two
   buttons sat alone in most of a screen and read as content that had failed to load. */
/* The scheduler frame on /book-a-call/. Only rendered when a share link is configured, so this
   sits unused until one is. Sized generously because every scheduler is a different height and a
   short frame turns into a nested scrollbar, which is the worst version of a booking form. */
.sched{border:1px solid var(--line); border-radius:12px; overflow:hidden; background:#fff}
.sched iframe{display:block; width:100%; height:min(78vh,860px); border:0}

.close.bare{padding-block:var(--s6)}
.close.bare .sig{display:none}

.anim [data-reveal].close .pull{clip-path:none}
.anim [data-reveal].close .pull .ln{display:block; clip-path:inset(0 0 108% 0);
  transition:clip-path 1s cubic-bezier(.19,1,.22,1)}
.anim [data-reveal].in.close .pull .ln{clip-path:inset(0 0 -14% 0)}
.anim [data-reveal].in.close .pull .ln:nth-child(2){transition-delay:.13s}
.anim [data-reveal].in.close .pull .ln:nth-child(3){transition-delay:.26s}
.anim [data-reveal].in.close .pull .ln:nth-child(n+4){transition-delay:.39s}
