/* =========================================================================
   MADD OX CONSULTING — site stylesheet
   Descended from mockup 1, "MODERN SCIENTIFIC": light-grey/white base, brand-purple
   accents, generous whitespace, subtle pattern art.
   Structure is deliberately Squarespace-portable: every section is a plain
   full-width band containing one centered max-width container. No sticky-scroll
   choreography, no canvas, no exotic layout primitives.

   PATHS: this file lives at assets/css/site.css, so every url() below is
   relative to THAT directory — i.e. "../fonts/…", "../patterns/…". Image paths
   written in index.html are relative to the page and stay "assets/…".
   ========================================================================= */

/* -------------------------------------------------------------------------
   BRAND FONTS — Lexend (headings) + Figtree (body), self-hosted.
   Google Fonts is deliberately NOT hotlinked (security requirement): the WOFF2
   files ship in assets/fonts/ and are served from our own origin, which is all
   the CSP's `font-src 'self'` allows.
   Figtree's variable axis starts at 300 — nothing on this page asks for a
   weight lighter than 500, so no synthetic thinning can occur.
   ------------------------------------------------------------------------- */
@font-face {
  font-family: "Lexend";
  src: url("../fonts/Lexend-Variable.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Figtree";
  src: url("../fonts/Figtree-Variable.woff2") format("woff2-variations");
  font-weight: 300 900;
  font-style: normal;
  font-display: swap;
}

:root{
  /* Palette — sampled from the brand logo SVGs. Do not introduce new hues. */
  --ink:#110040;
  --ink-2:#1b044c;
  --plum:#37126d;
  --grape:#5c2279;
  --violet:#6427a2;
  --purple:#8f3bd4;
  /* R11: the CREAM (#fcf4e6) is retired. The client asked to try the brand
     kit's light grey in its place, so the token is renamed as well as
     revalued — a variable called --cream holding a neutral grey is exactly the
     kind of thing that misleads the next person to read this file. Every
     `.band--paper` class in the five HTML pages is `.band--paper` now, and no
     literal cream rgba() survives anywhere (the header wash and the two footer
     text colours were hard-coded 252,244,230 and are re-stated below).
     If cream ever comes back it is a one-line change here. */
  --paper:#f2f2f2;
  --tan:#cba36d;
  --tan-dk:#90602f;
  --sage:#afb59f;
  --white:#ffffff;
  /* R5: --flag (the RED that marked draft copy awaiting Jayme's wording) is
     DELETED, not merely unused. The client signed off on the wording, so there
     is no longer any such thing as draft copy on this site — every paragraph is
     ordinary body text in --ink or --muted. The .placeholder rule, the footer
     legend that explained the red, and its lighter-red swatch are gone with it.
     If a future round needs to mark copy for review again, mark it in the
     CHANGELOG, not on the live page. */

  /* ---- Gradient accent lines ----
     ACCENT LINES ONLY. Never a section background wash, and never behind text —
     these are for hairlines, rules and edges, where there is no contrast risk.
     --grad-cool-text is the one exception: it is the all-dark variant, safe to
     clip into text on white (every stop clears 4.5:1). --grad-cool must NOT be
     used for text — its light-blue end would fail.
     R3 NOTE: --grad-cool-text is currently UNREFERENCED. Its three former users
     (.stat-v, .stat-grid li::before, .approach-item::before) all moved to the
     per-section ramps this round. It is kept, not deleted, because it is the
     one token that records "this is the gradient that is safe on text" — but
     do not go looking for its effect on the page, there isn't one. */
  --grad-warm:linear-gradient(90deg,#e8763a 0%,#e0559b 100%);
  --grad-cool:linear-gradient(90deg,#37126d 0%,#8f3bd4 55%,#5cb4d6 100%);
  --grad-cool-text:linear-gradient(90deg,#37126d 0%,#5c2279 45%,#6427a2 100%);

  --font-head:"Lexend","Helvetica Neue",Arial,system-ui,sans-serif;
  --font-body:"Figtree",system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;

  --wrap:1120px;
  --gutter:24px;
  --radius:14px;

  --rule:rgba(17,0,64,.12);
  --rule-strong:rgba(17,0,64,.22);
  --muted:rgba(17,0,64,.68);
  --shadow:0 1px 2px rgba(17,0,64,.05), 0 12px 34px rgba(17,0,64,.07);
}

*,*::before,*::after{box-sizing:border-box;}
/* ### R11b — THE [hidden] GUARD. THIS IS A SHIPPED-BUG FIX, NOT BOILERPLATE. ###
   The "Thank you, your message has been sent" panel was rendering permanently,
   stacked under the contact form, on every page load. Its markup carries the
   `hidden` attribute and site.js only clears it after a successful POST, so the
   markup and the script were both correct. The stylesheet was not: `[hidden]`
   is a UA rule of specificity (0,1,0), and `.form-done{display:flex}` is an
   AUTHOR rule of the same specificity — and author beats UA at equal
   specificity, always. So the panel's own layout rule quietly cancelled its own
   hidden attribute.

   Any `display` declaration on any element that is ever toggled with `hidden`
   reintroduces this, which is why the fix is global and carries !important
   rather than being a one-line patch on .form-done. `hidden` means hidden.
   If something ever genuinely needs to be visible while carrying the attribute,
   that element is using the wrong mechanism. */
[hidden]{display:none !important;}
html{-webkit-text-size-adjust:100%;}
body{
  margin:0;
  background:var(--white);
  color:var(--ink);
  font-family:var(--font-body);
  font-size:17px;
  line-height:1.65;
  overflow-x:hidden;            /* belt-and-braces: never a horizontal scrollbar */
  -webkit-font-smoothing:antialiased;
}
img,svg{max-width:100%;}
h1,h2,h3,h4{font-family:var(--font-head);line-height:1.15;margin:0 0 .5em;letter-spacing:-.015em;font-weight:600;}
h1{font-size:clamp(2.05rem,5.2vw,3.5rem);font-weight:700;letter-spacing:-.03em;}
h2{font-size:clamp(1.65rem,3.4vw,2.4rem);letter-spacing:-.02em;}
h3{font-size:1.16rem;}
p{margin:0 0 1.1em;}
a{color:var(--violet);}
a:hover{color:var(--purple);}

/* ---------- Focus: always visible, never removed ---------- */
:focus-visible{
  outline:3px solid var(--purple);
  outline-offset:3px;
  border-radius:4px;
}

.skip-link{
  position:absolute;left:-9999px;top:0;z-index:100;
  background:var(--ink);color:var(--paper);
  padding:12px 20px;border-radius:0 0 10px 0;
  font-weight:600;text-decoration:none;
}
.skip-link:focus{left:0;}

/* Screen-reader-only heading. Lives here rather than in a style="" attribute
   because the CSP no longer allows 'unsafe-inline' for styles. */
.visually-hidden{
  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;
}

/* ---------- Layout primitives (Squarespace-mappable) ---------- */
.band{width:100%;padding:clamp(56px,8vw,104px) 0;position:relative;}
.band--paper{background:var(--paper);}
.band--white{background:var(--white);}
.wrap{width:100%;max-width:var(--wrap);margin:0 auto;padding:0 var(--gutter);}

/* ### R14b — .divider AND .divider--hairline ARE DELETED. ###
   TOMBSTONE, because this class has been whittled down over five rounds and the
   next person should not go looking for it:

     R2   a thin SEQUENCING "breaker" strip, rejected by Austin
     R6   all .divider__sweep rules deleted (a gradient bar that swept the rule),
          leaving a childless <div class="divider divider--hairline">
     R4   the About -> How I work instance deleted from the markup
     R12  the Expertise -> Contact instance deleted (the surge feathers instead)
     R14b the last instance, Services -> About, deleted — the client asked for
          the gradient bar to sit above "Meet Jaymeth", and stacking a 1px grey
          line and a 3px gradient bar at one boundary is not a thing anyone
          wants. .band--sep on #about does that job now.

   So both selectors could only ever match nothing, which is the same reason R6
   dropped .divider__sweep and R12 dropped .xp-list. Every boundary on the site
   is now carried by .band--sep, .band-pair::after or nothing at all.

   IF A PLAIN HAIRLINE IS EVER WANTED AGAIN, do not resurrect this: the client
   has objected to a flat grey line twice (R3, "a hard grey line"), which is the
   whole reason .band--sep exists. */

.eyebrow{
  font-family:var(--font-head);
  font-size:.74rem;font-weight:600;letter-spacing:.16em;text-transform:uppercase;
  color:var(--grape);margin:0 0 14px;
}

/* =========================================================================
   R14 — THE SECTION RULE IS NOW AS WIDE AS THE LABEL ABOVE IT.

   Austin, on the 08-03 call: "see how under the About there's that little line
   — it's not extended for, like, the Madd Ox Approach, but I can extend it to
   make them fit the full word." So the rule is no longer a fixed 72px stub: it
   measures the eyebrow it belongs to, short under "About", long under
   "Expertise & Track Record".

   ### WHY THIS IS A PSEUDO-ELEMENT AND THE <div class="sec-rule"> IS GONE. ###
   CSS has no way to size one element to a SIBLING's width. .sec-rule was a
   sibling of .eyebrow, so as long as it stayed a separate box the only widths
   available to it were a hard-coded number or the full container. Drawing it as
   the eyebrow's own ::after makes the measurement automatic: .eyebrow becomes
   inline-block (shrink-to-fit around its text), and a block-level ::after inside
   it resolves width:100% against exactly that. No JavaScript, no magic numbers,
   and it cannot fall out of step when a label is reworded.

   The nine <div class="sec-rule"> elements are deleted from all five pages
   rather than left hidden — an empty decorative div with no stylesheet entry is
   the exact thing R6 cleaned up once already.

   SCOPED TO .sec-head ON PURPOSE. The hero's eyebrow is also .eyebrow and has
   never carried a rule; an unscoped ::after would have added one.

   The margin here replaces BOTH the old .eyebrow bottom margin (14px, cancelled
   just below) and the old .sec-rule margin (0 0 20px), so the vertical rhythm
   between label, rule and heading is unchanged.
   ========================================================================= */
.sec-head .eyebrow{display:inline-block;margin-bottom:0;}
.sec-head .eyebrow::after{
  content:"";
  display:block;
  height:2px;width:100%;
  /* 14px/20px reproduces the old spacing EXACTLY: the eyebrow used to carry
     margin-bottom:14px and .sec-rule margin:0 0 20px, and adjacent-sibling
     collapsing made that a 14px gap above the rule and 20px below it. Because
     .eyebrow is now inline-block it establishes a block formatting context, so
     neither of these margins collapses out and both are real space. */
  margin:14px 0 20px;
  background-color:var(--rule-strong);   /* fallback if the gradient ever fails */
  background-image:var(--grad-cool);
  border-radius:2px;
}

/* ---------- Buttons ---------- */
.btn{
  display:inline-flex;align-items:center;gap:.6em;
  font-family:var(--font-head);font-size:1rem;font-weight:600;
  padding:15px 30px;border-radius:999px;
  text-decoration:none;border:2px solid transparent;cursor:pointer;
  transition:background-color .18s ease,color .18s ease,border-color .18s ease,transform .18s ease;
}
/* Primary sits on --grape (#5c2279), the mid-dark purple, not the bright
   --purple. White on grape is 10.7:1. Hover still lifts to --violet. */
.btn--primary{background:var(--grape);color:var(--white);}
.btn--primary:hover{background:var(--violet);color:var(--white);transform:translateY(-1px);}
.btn--ghost{background:transparent;color:var(--plum);border-color:var(--rule-strong);}
.btn--ghost:hover{border-color:var(--purple);color:var(--purple);}

/* =========================================================================
   HEADER
   ========================================================================= */
.site-header{
  position:sticky;top:0;z-index:40;
  background:rgba(242,242,242,.94);
  backdrop-filter:saturate(140%) blur(8px);
  border-bottom:1px solid var(--rule);
}
/* R11 — "make the logo in the top banner a little bit bigger to fill the space
   more. Maybe even make the banner a little bigger too. It's just a little
   jarringly small, particularly compared to the font size of the tag line."
   Logo 44 -> 58px and the bar 72 -> 90px, i.e. both up by roughly a third, so
   the logo gains presence without the bar getting tighter around it: 16px of
   clear space above and below, against 14px before. The lockup is 600x200, so
   58px tall renders 174px wide.
   The compact-on-scroll pair moves with it (58 -> 72px bar, 37 -> 48px logo) and
   keeps the same ~80% ratio to the resting size.
   scroll-padding-top at the bottom of this file is part of this change — it has
   to stay ahead of the bar's height or an anchored section lands underneath it.
   The <=600px block reins both numbers back in: at 375px a 174px-wide lockup
   plus the Menu button is close to the gutter, and a 90px sticky bar is a lot of
   a phone viewport to give up. */
.header-inner{
  display:flex;align-items:center;justify-content:space-between;gap:16px;
  min-height:90px;
  transition:min-height .24s ease;
}
/* Compact-on-scroll: the sticky header must not eat the phone viewport. */
.site-header.is-scrolled .header-inner{min-height:72px;}
.brand{display:inline-flex;align-items:center;text-decoration:none;flex:0 0 auto;}
.brand img{height:58px;width:auto;display:block;transition:height .24s ease;}
.site-header.is-scrolled .brand img{height:48px;}

.nav-desktop{display:flex;align-items:center;gap:6px;}
.nav-desktop a{
  font-family:var(--font-head);font-size:.95rem;font-weight:500;
  color:var(--ink);text-decoration:none;
  padding:10px 14px;border-radius:8px;
  transition:color .16s ease,background-color .16s ease;
}
.nav-desktop a:hover{color:var(--purple);background:rgba(143,59,212,.07);}
/* R11 — "the Get in touch button in the banner seems a little oversized
   relative to the other nearby text, and can we make the text in the button
   white instead of the dark color."

   THE DARK TEXT WAS A SPECIFICITY BUG, not a styling choice. `.nav-desktop a`
   is (0,1,1) — one class plus one type selector — and it sets color:var(--ink).
   `.btn--primary` is only (0,1,0), so it LOST, and the button rendered ink on
   --grape (2.4:1, and it fails AA outright). The reason it looked correct in
   review is that `.nav-desktop .btn:hover` below is (0,2,0) and does win, so the
   text snapped to white the moment a mouse touched it. The rule below is
   (0,2,0) as well, so the resting state now matches the hover state: white on
   --grape, 10.7:1.

   SIZE: .93rem/11px-22px was actually larger than the .95rem nav links once the
   pill padding is counted. Down to .88rem and 9px/19px, which lands the button
   at ~36px tall against the nav links' ~40px box, so it reads as a control
   sitting among the links rather than the biggest thing in the bar. The shadow
   is cut to match — .btn--primary's default 0 6px 18px is tuned for the hero's
   full-size CTA and was a second source of visual weight up here. */
.nav-desktop .btn{
  margin-left:10px;padding:9px 19px;font-size:.88rem;
  color:var(--white);
  box-shadow:0 3px 10px rgba(92,34,121,.18);
}
.nav-desktop .btn:hover{background:var(--violet);color:var(--white);box-shadow:0 5px 14px rgba(92,34,121,.26);}

.nav-toggle{
  display:none;
  align-items:center;gap:9px;
  font-family:var(--font-head);font-size:.9rem;font-weight:600;
  background:transparent;color:var(--ink);
  border:1.5px solid var(--rule-strong);border-radius:999px;
  padding:11px 18px;min-height:44px;cursor:pointer;
}
.nav-toggle .bars{position:relative;width:16px;height:11px;flex:0 0 auto;}
.nav-toggle .bars i{
  position:absolute;left:0;right:0;height:1.8px;background:currentColor;border-radius:2px;
  transition:transform .2s ease,opacity .2s ease;
}
.nav-toggle .bars i:nth-child(1){top:0;}
.nav-toggle .bars i:nth-child(2){top:4.6px;}
.nav-toggle .bars i:nth-child(3){top:9.2px;}
.nav-toggle[aria-expanded="true"] .bars i:nth-child(1){transform:translateY(4.6px) rotate(45deg);}
.nav-toggle[aria-expanded="true"] .bars i:nth-child(2){opacity:0;}
.nav-toggle[aria-expanded="true"] .bars i:nth-child(3){transform:translateY(-4.6px) rotate(-45deg);}

/* Animated disclosure: collapsed to zero height + visibility:hidden (so links
   are out of the tab order when closed), eased open via max-height/opacity.
   Reduced-motion strips the transition, leaving an instant, still-functional
   show/hide. */
.nav-mobile{
  overflow:hidden;
  max-height:0;opacity:0;visibility:hidden;
  border-top:1px solid transparent;
  /* visibility flips instantly (keeps links out of the tab order when closed);
     only height + opacity ease, so the menu is never stuck mid-transition. */
  transition:max-height .32s cubic-bezier(.22,.61,.36,1),opacity .22s ease;
}
.nav-mobile.is-open{
  max-height:78vh;opacity:1;visibility:visible;
  border-top-color:var(--rule);
  padding-bottom:16px;
}
.nav-mobile .wrap{padding-top:8px;}
.nav-mobile a{
  display:flex;align-items:center;min-height:54px;
  font-family:var(--font-head);font-size:1.08rem;font-weight:500;
  color:var(--ink);text-decoration:none;
  padding:8px 8px;border-bottom:1px solid var(--rule);border-radius:10px;
}
.nav-mobile a:hover{color:var(--purple);background:rgba(143,59,212,.05);}
/* Final item ("Get in touch") reads as a full-width primary button on mobile —
   same --grape treatment as .btn--primary. */
.nav-mobile a:last-child{
  justify-content:center;margin-top:12px;min-height:54px;
  background:var(--grape);color:var(--white);font-weight:600;
  border:none;border-radius:999px;
  box-shadow:0 6px 18px rgba(92,34,121,.22);
}
.nav-mobile a:last-child:hover{background:var(--violet);color:var(--white);}

/* =========================================================================
   HERO
   ========================================================================= */
/* R12 — the BOTTOM padding comes down, 96px -> 72px at desktop. This is half of
   the client's "same with the 4 buttons down to the services section, shorten
   that up a tiny bit as well"; the other half is the #services padding-top
   override further down. Splitting the reduction across both sides of the join
   rather than taking it all from one keeps the four cards and the Services
   eyebrow equidistant from the boundary, which is what makes the gap read as
   one deliberate space instead of a section that starts too close to its own
   edge. Together: 96 + 104 = 200px of gap becomes 72 + 80 = 152px.
   The TOP padding is untouched — nothing about the headline's position was in
   the brief. */
.hero{
  position:relative;overflow:hidden;
  background:var(--paper);
  padding:clamp(64px,9vw,124px) 0 clamp(44px,5.5vw,72px);
}
/* ---- Hero right-side pattern background ----
   The old separate "foil card" is gone, and so is the Turing ("brain squiggle")
   pattern the client found too busy IN THE HERO. (R4-R10 had it back, much
   fainter, on the Services band; R11 deleted that too, so it is now on no band
   at all.) What remains here is the
   SEQUENCING pattern —
   the DNA-ladder art he liked — held quietly to the right/bottom-right so the
   headline column reads on plain --paper. An iridescent "surge foil" sheen rides
   on top and its position is driven by scroll (JS sets --holo-*), giving the
   shiny colour movement the card had — now tied to the page.
   Every layer is decorative and aria-hidden. Degrades to a static pattern wash
   with JS off and under reduced-motion.

   The container carries a VERTICAL feather (see below); each child carries its
   own horizontal one. Compositing the two on the container is what stops the
   sheen from being sliced off at full strength where .hero{overflow:hidden}
   meets the next band. */
.hero__bg{
  position:absolute;inset:0;z-index:0;pointer-events:none;overflow:hidden;
  /* Vertical feather applied to the WHOLE stack. It multiplies with each
     child's own mask, so the pattern AND both foil layers dissolve before the
     hero's bottom edge — no hard horizontal seam at the band boundary. */
  -webkit-mask-image:linear-gradient(180deg,#000 0,#000 70%,transparent 100%);
  mask-image:linear-gradient(180deg,#000 0,#000 70%,transparent 100%);
}
.hero__bg-pattern{
  position:absolute;inset:0;
  background-image:url("../patterns/Madd_Ox_Pattern_Sequencing_Purple.webp");
  background-size:auto 118%;
  background-position:right center;
  background-repeat:no-repeat;
  opacity:.12;                     /* was .5 on the old Turing art — much quieter */
  /* ONE radial mask, deliberately: an ellipse anchored off the right edge gives
     horizontal AND vertical falloff in a single mask layer. Two stacked linear
     masks would need mask-composite:intersect, which defaults to `add` (union)
     where unsupported — i.e. it would fail OPEN and wash the whole hero.
     Result: the ladders sit in roughly the right 45–50%, densest at the right
     edge, gone by mid-page and soft at top and bottom. */
  -webkit-mask-image:radial-gradient(58% 86% at 106% 58%, #000 0%, #000 28%, rgba(0,0,0,.55) 56%, rgba(0,0,0,.18) 78%, transparent 96%);
  mask-image:radial-gradient(58% 86% at 106% 58%, #000 0%, #000 28%, rgba(0,0,0,.55) 56%, rgba(0,0,0,.18) 78%, transparent 96%);
  /* Slow ambient vertical parallax; JS nudges it a little more on scroll. */
  transform:translate3d(0,var(--holo-scroll,0px),0);
  will-change:transform;
}
/* Iridescent sheen — the "surge foil" colour movement the client explicitly
   asked to keep. On the light hero it reads as a soft brand-hued shimmer that
   shifts as the page scrolls. `soft-light` and a low opacity keep it subtle on
   the light grey rather than a loud metallic sheen. The idle animation only breathes
   opacity so it never fights the scroll-driven position. */
.hero__bg-foil{
  position:absolute;inset:0;
  background:repeating-linear-gradient(122deg,
    rgba(143,59,212,0)   0%,
    rgba(143,59,212,.28) 6%,
    rgba(201,79,201,.24) 12%,
    rgba(92,180,214,.2)  18%,
    rgba(203,163,109,.24) 24%,
    rgba(143,59,212,.28) 30%,
    rgba(143,59,212,0)   36%);
  background-size:210% 210%;
  background-position:var(--holo-fx,50%) var(--holo-fy,20%);
  mix-blend-mode:soft-light;opacity:.55;
  -webkit-mask-image:linear-gradient(90deg,transparent 0%,transparent 32%,#000 80%,#000 100%);
  mask-image:linear-gradient(90deg,transparent 0%,transparent 32%,#000 80%,#000 100%);
  animation:hero-foil-breathe 10s ease-in-out infinite alternate;
}
/* Secondary, finer sheen crossing the other way — kept very light so it only
   adds a hint of depth to the shift, not a bright specular streak. */
.hero__bg-foil2{
  position:absolute;inset:0;
  background:repeating-linear-gradient(-56deg,
    rgba(255,255,255,0)   0%,
    rgba(214,150,224,.18) 7%,
    rgba(140,210,224,.15) 14%,
    rgba(255,255,255,0)   22%);
  background-size:210% 210%;
  background-position:var(--holo-gx,50%) var(--holo-gy,60%);
  mix-blend-mode:soft-light;opacity:.35;
  -webkit-mask-image:linear-gradient(90deg,transparent 0%,transparent 32%,#000 80%,#000 100%);
  mask-image:linear-gradient(90deg,transparent 0%,transparent 32%,#000 80%,#000 100%);
}
@keyframes hero-foil-breathe{
  0%  {opacity:.45;}
  100%{opacity:.7;}
}
/* Colour bleed at the hero's bottom edge. Half of the "hard gradient seam" the
   client reported was not the foil at all: it was the flat --paper -> --white
   step where the hero meets the white Services band. This dissolves the hero
   band colour into white. (R11: --paper is #f2f2f2 now, not the old cream, so
   the step this dissolves is smaller than it was — the ramp is unchanged.) Sits above .hero__bg (same z-index, later in paint order) and
   below all content (z-index:1).

   R3: strengthened, because the 1px hairline divider that used to sit here is
   deleted — this bleed and the Services stack's top feather are now the ONLY
   things carrying the hero into the next band, and the client asked for the
   two to "combine and flow better" rather than meet at a hard grey line.
   120px -> clamp(150px,20vw,220px), and the ramp is eased through explicit
   mid-stops instead of the default two-stop interpolation, so the step
   dissolves gradually over most of that distance rather than doing most of its
   work in the last third. Hand-authored rgba (no color-mix anywhere here).
   Length only — no percentage of the hero box, so the ambient pattern above it
   is unaffected.

   ### R12 — SHORTENED, 220px -> 160px, AND THE REASON IS THE OPPOSITE OF R3's ###
   R3 lengthened this bleed to make the hero and Services "combine and flow
   better". It worked too well: the client now reports the reverse problem —
   "currently they flow into one another and there isn't a super great visual
   cue that you're on a new page." A 220px dissolve means the --paper -> --white
   step is spread so thin that there is no perceptible boundary anywhere.
   Shortening it to 160px brings the colour change back within a distance the eye
   registers as an edge, WITHOUT reintroducing the R3 complaint: the ramp still
   ends at a full opaque #ffffff, so there is no step left for a hard line to
   form on, and .band--sep now paints a coloured bar directly over the boundary,
   which is what actually announces the section. Length only, as before.

   ### R12b — THE RULE IS DELETED. THERE IS NO STEP LEFT FOR IT TO DISSOLVE. ###
   Everything above is history. This bleed existed for exactly one reason, stated
   in its own first line: "the flat --paper -> --white step where the hero meets
   the WHITE Services band". R12b turned the Services band --paper — the client
   asked for "normal grey like before" — so the hero and Services are now the
   same #f2f2f2 and there is no step.

   Leaving it in place would have been actively wrong, not merely redundant: the
   ramp ends at a fully opaque #ffffff, so against a grey Services band it would
   have painted a 160px WHITE stripe across the bottom of the hero — a band that
   matches neither of its neighbours, which is a worse seam than the one this was
   ever asked to fix.

   IF SERVICES EVER GOES BACK TO WHITE, this rule has to come back with it. That
   is the whole dependency: .hero::after exists if and only if the hero and the
   band below it are different colours. It is also removed from the print block's
   hide list, since the selector would match nothing. */

@keyframes panel-in{
  0%  {opacity:0;transform:translate3d(0,-6px,0);}
  100%{opacity:1;transform:none;}
}
/* R6 deletes @keyframes divider-sweep and the two lines that attached it
   (.divider__sweep at 11s, .divider--hairline .divider__sweep at 14s). Its only
   other user, .sec-rule::after, is deleted too, so nothing references it. See
   the .divider notes above for why. */

.hero__inner{position:relative;z-index:1;}
/* Plain block wrapper around the headline column. (It used to be a two-column
   grid holding the holographic foil card; that card is gone.) */
.hero__lead{display:block;}
/* Copy is held to the left ~62% so the right-side pattern reads behind open
   space, not behind the headline. */
.hero__col{max-width:min(640px,62%);}
@media (max-width:880px){.hero__col{max-width:100%;}}
.hero h1{margin-bottom:.42em;color:var(--ink);text-wrap:balance;}
.hero__sub{
  font-size:clamp(1.02rem,1.7vw,1.24rem);
  color:var(--muted);max-width:560px;margin-bottom:1.9em;line-height:1.6;
}
.hero__actions{display:flex;flex-wrap:wrap;gap:14px;align-items:center;}

/* Four-category strip under the hero. It has NO top border of any kind as of
   R11 — first the original hairline, then the gradient ::before strip that
   replaced it, both now deleted at the client's request (see the R11 note below
   the .cat-strip rule). The separation is space alone. */
/* R4 pushed this strip BELOW THE FOLD on desktop first paint — the client had
   asked that the "specializes in four core areas" line NOT be visible when the
   landing page opens — with margin-top:clamp(140px,24vw,352px), measured
   against a 1470x956 viewport so the lead paragraph's top landed at 977px.
   R6 REVERSED that: at the time the client wanted the whole lead paragraph
   readable without scrolling. R11b REVERSES IT BACK — Austin, looking at it on
   his Mac: "move the 'Madd Ox Consulting specializes in four core areas' down a
   hair so it doesn't show up in my landing page look."

   ### THE UNIT CHANGED, AND THAT IS THE POINT OF THIS ROUND ###
   Every previous attempt at this — R4's 24vw, R6's 5vw, R11's 3.75vw — measured
   the gap in VIEWPORT WIDTH. The thing being controlled is whether an element
   clears the FOLD, which is a viewport HEIGHT question, so all three were
   steering with the wrong instrument: they were tuned against one assumed window
   (R4 against 1470x956, R6 against a ~780px content height) and silently
   mis-predicted on any other. That is why this keeps coming back.

   The gap is now `40vh`, so it scales with the fold instead of guessing at it.
   Everything above .cat-strip is fixed-height at a given width — call it 654px
   at desktop (the stack below, minus the gap) — so the lead sits below the fold
   whenever:

       654 + 0.40H > H   ->   654 > 0.60H   ->   H < 1090px

   i.e. for every viewport shorter than 1090px, which is every laptop: a 14"
   MacBook Pro gives ~870-900px of viewport after browser chrome, a 16" ~1030px.
   The 420px cap takes over above H=1050 and still clears a 1074px fold. On a
   desktop 27" at ~1300px of viewport the lead WILL be visible — that is the one
   case this cannot cover without leaving an absurd hole in the hero, and it is
   the correct trade.

   Arithmetic at a 1440x900 viewport, stacking from the top of the document to
   the TOP of .cat-strip__lead:
     sticky header            91px   (90px min-height + 1px border)
     .hero padding-top       124px   (9vw, capped at 124)
     .eyebrow                 34px   (~20px line + 14px margin)
     h1, two lines           157px   (56px font x 1.15 x 2 + .5em margin)
     .hero__sub, ~4 lines    146px
     .hero__actions           54px
                           -------
                             606px of fixed stack
     .cat-strip margin-top   360px   <-- 40vh at 900px
     .cat-strip padding-top   48px
                           -------
                            1014px to the top of the lead, against a 900px fold

   vh IS SAFE HERE, with one caveat worth stating: on mobile browsers vh is
   measured against the largest viewport (URL bar retracted) and is unstable
   while scrolling. It never matters, because the <=600px block below resets this
   to a flat 34px — on a phone the hero is already taller than the screen and
   there is nothing to push down. Between 601px and 880px wide the vh applies and
   behaves normally. */
/* ### R12 — THE GAP IS CUT ROUGHLY IN HALF, AND THAT REVERSES R11b. ###
   Client: "on the home page, shorten the gap between the buttons and the text
   below, it's too big of a gap."

   THAT TEXT IS .cat-strip__lead, AND THIS MARGIN IS THE GAP. Read the R11b block
   above before touching it again, because the size was not arbitrary: 40vh was
   chosen to push the lead BELOW THE FOLD on a laptop, which is the opposite ask
   from the same person. The later instruction wins, but the trade is real and
   worth stating plainly rather than silently losing: at 20vh the lead paragraph
   WILL now be partly visible on first paint on a typical laptop. Re-running
   R11b's arithmetic at 1440x900, with the hero's shorter bottom padding this
   round does not touch:

     606px of fixed stack above .cat-strip   (unchanged from R11b)
     .cat-strip margin-top   180px   <-- 20vh at 900px, was 360px
     .cat-strip padding-top   32px   <-- was 48px
                           -------
                            818px to the top of the lead, against a 900px fold

   So the lead now clears the fold only above ~1360px of viewport height instead
   of below ~1090px. If the below-the-fold behaviour turns out to matter more
   than the gap, the lever is this one number and nothing else — put it back to
   40vh and the R11b geometry returns exactly.

   The UNIT stays vh, which is R11b's real contribution and is still correct: the
   thing being spaced is measured against the fold, so it has to scale with the
   viewport's height rather than its width. The <=600px block below still resets
   this to a flat value — on a phone the hero is already taller than the screen
   and there is nothing to push down. */
.cat-strip{
  margin-top:clamp(32px,20vh,240px);
  padding-top:clamp(24px,2.6vw,32px);
  position:relative;z-index:1;
}
/* R11: .cat-strip::before is DELETED. It was a 2px --grad-cool rule spanning the
   full 1120px container above the "specializes in four core areas" line. The
   client: "the line in the middle of the home page feels like it extends a
   little too far into the background. Honestly just remove it fully?" — the lead
   paragraph under it is capped at 640px, so the rule ran roughly 480px past the
   only thing it was introducing, out into empty hero background. The strip's
   padding-top stays: it is what keeps the four cards from crowding the CTAs, and
   the gap alone now does the separating. This was the last
   .cat-strip::before reference on the page; the print block below drops it too.
   The other two gradient accent strips (.footer-top::after, .subhead::before)
   are untouched — both sit directly on top of full-width content. */
.cat-strip__lead{
  font-size:1rem;color:var(--muted);max-width:640px;margin-bottom:30px;
}
.cat-grid{
  display:grid;gap:18px;
  grid-template-columns:repeat(4,1fr);
  list-style:none;margin:0;padding:0;
}
/* ### R12 — THREE STACKED ROWS BECOME TWO. ###
   Client: "for the services buttons on the home page, make the numbers a little
   bigger and push them up to the same line as the icons. Too much white space in
   the boxes."

   The card WAS display:block with three block-level children, so the icon, the
   number and the title each took a full row of their own: 32px of icon + 14px,
   then ~11px of number + 6px, then the title. The number row was the emptiest
   line on the page — a 11px glyph alone across a 250px card.

   GRID, NOT FLEX, and the reason is the markup. The three children are direct
   children of the <a> and their source order is icon / number / title, but the
   layout needs the first two side by side on one row and the third spanning
   both. Named areas express that in three declarations without adding a wrapper
   <div> to the HTML, and — the part flex could not do without reordering the
   DOM — they let the title span the full width underneath while the icon column
   stays sized to its own content.

   The number is .7rem -> 1.02rem, i.e. ~46% larger, which is the size that sits
   optically level with a 32px icon rather than floating near its baseline;
   align-items:center on the row is what actually puts them "on the same line".
   Tracking comes down .14em -> .07em to compensate: letter-spacing tuned for a
   11px label reads as loose and gappy once the glyphs are 16px.

   WHITE SPACE, the third part of the ask: the removed row was ~17px, the padding
   comes in ~2-4px on each edge, and the row-gap between the icon line and the
   title is 12px against the old 6px + 14px pair. Net, a card is ~30px shorter
   and the remaining space is distributed rather than pooled in one dead band.

   The .cat-card rule further down (position/overflow, in MICRO-INTERACTIONS)
   sets no `display`, so this grid survives it. */
.cat-card{
  background:var(--white);
  border:1px solid var(--rule);
  border-radius:var(--radius);
  padding:18px 18px 20px;
  text-decoration:none;color:var(--ink);
  display:grid;
  grid-template-columns:auto 1fr;
  grid-template-areas:
    "icon num"
    "title title";
  align-items:center;
  column-gap:12px;
  row-gap:12px;
  transition:border-color .18s ease,transform .18s ease,box-shadow .18s ease;
}
.cat-card:hover{border-color:rgba(143,59,212,.5);transform:translateY(-2px);box-shadow:var(--shadow);color:var(--ink);}
.cat-card img{grid-area:icon;height:32px;width:32px;display:block;margin-bottom:0;}
/* Deliberately still the lighter --purple: the page would go monotone if every
   accent moved to --grape. --purple on white is 5.6:1, and at 16px this is
   comfortably normal-size text, so AA applies and is met.
   justify-self:start keeps the numeral tucked against the icon instead of being
   flung to the far edge by the 1fr track. */
.cat-card__n{
  grid-area:num;justify-self:start;
  font-family:var(--font-head);font-size:1.02rem;font-weight:700;letter-spacing:.07em;
  color:var(--purple);display:block;margin-bottom:0;line-height:1;
}
.cat-card__t{grid-area:title;font-family:var(--font-head);font-size:1rem;font-weight:600;line-height:1.3;display:block;}

/* =========================================================================
   SECTION HEADS
   ========================================================================= */
.sec-head{max-width:720px;margin-bottom:clamp(36px,4.5vw,54px);}
.sec-head p{color:var(--muted);font-size:1.05rem;margin-bottom:0;}

/* =========================================================================
   SERVICES — ACCORDION (native <details>/<summary>)
   Chosen over button+aria-controls so that (a) keyboard + screen-reader support
   is native and bulletproof, and (b) browsers' find-in-page can still locate
   text inside a CLOSED panel and auto-open it. All 39 line items are real,
   crawlable text in the DOM at load — nothing is JS-injected.
   ========================================================================= */
.acc-tools{
  display:flex;gap:10px;flex-wrap:wrap;align-items:center;
  margin-bottom:22px;
}
.acc-tools__btn{
  font-family:var(--font-head);font-size:.82rem;font-weight:600;
  color:var(--plum);background:transparent;
  border:1.5px solid var(--rule-strong);border-radius:999px;
  padding:8px 16px;cursor:pointer;
  transition:border-color .16s ease,color .16s ease;
}
.acc-tools__btn:hover{border-color:var(--purple);color:var(--purple);}

.acc{
  position:relative;
  border:1px solid var(--rule);
  border-radius:var(--radius);
  background:var(--white);
  overflow:hidden;
  margin-bottom:14px;
  transition:border-color .18s ease,box-shadow .18s ease;
}
.acc[open]{border-color:rgba(143,59,212,.42);box-shadow:var(--shadow);}

/* ---- ONE MASTER RAMP, SLICED BY ORDINAL POSITION, HEADING BAR ONLY ----
   R3: the client wants the four header bars to read as a SINGLE gradient laid
   over the whole section and glimpsed only through the bars — card 1's
   bottom-right handing off exactly to card 2's top-left, and so on.

   The ramp is declared ONCE, as five anchors at the quarter points:

       0/4        1/4        2/4        3/4        4/4
     --plum ---- --grape --- --violet - --purple - --tan-dk
     #37126d     #5c2279     #6427a2     #8f3bd4    #90602f

   Card N then takes the slice [N-1, N]. The handoff is not a convention that
   has to be remembered — card N's end and card N+1's start are LITERALLY THE
   SAME TOKEN, so no future edit can knock them out of register. Editing one
   anchor re-flows both cards that touch it, which is the correct behaviour.

   HEIGHT-STABILITY (see the .band-surge notes for the bug this avoids): the
   slice a card gets is chosen by its ORDINAL CLASS, never by measurement. No
   background-attachment:fixed, no percentage background-size, nothing that
   samples the viewport or the container. The gradient box is the <summary>
   itself, whose height is set by its own padding and content and does NOT
   change when a panel opens — only the .acc and the band grow. So expanding a
   category cannot recolour any header, including its own.

   The gradient is IDENTICAL collapsed and expanded, by design: an open card
   already announces itself through the tone ring (see .acc[open]::after), so
   re-tinting the header on open would be a second, redundant signal.

   The expanded panel is still deliberately untouched — white surface, --purple
   bullets — so the reading experience is identical in all four categories. */
/* R4b — "pull the orange up and to the left, make it more orange total."
   SUPERSEDED BY R11, WHICH REMOVED THE ORANGE ENTIRELY — read the R11 block
   below for the colours actually in force. Point 1 is history; point 2 still
   describes the anchor positions, which R11 kept unchanged.
   Two separate changes, both here:

   1. MORE ORANGE, and an actual orange. The warm end was --tan-dk #90602f,
      which is a muted brown-tan; it now runs #9a4419 -> #b85423, a real burnt
      orange in the same family as --grad-warm. It cannot go brighter than that:
      the header bars carry WHITE text including the 12px .acc__num, so every
      colour on this ramp has to clear 4.5:1 against white. #b85423 is 4.85:1
      and is the ceiling — the brand kit's #FBAE5F is 1.9:1 and #e8763a is
      2.95:1, both unusable behind white text.
   2. UP AND TO THE LEFT. The ramp anchors are no longer at even quarter points.
      Warm now starts at 68% of the gradient axis instead of 100%, so on the
      one-big-box gradient the orange reaches up into card 3 and spreads across
      all of card 4 rather than sitting in card 4's bottom-right corner. The
      cool anchors compress into the first two thirds to make room. */
/* R11 — "I am liking the gradient on the buttons in the services page. But
   instead of the orange color, can we do a purple to light purple to light
   blue?"

   The two warm anchors are gone; the ramp now ends in the brand's blue family
   and the whole thing reads plum -> grape -> violet -> light purple ->
   periwinkle -> light blue. R4b's anchor POSITIONS are deliberately unchanged
   (0/15/32/50/68/100%), so the blue occupies exactly the real estate the orange
   used to — up into card 3 and across all of card 4 — which is the balance the
   client signed off on last round.

   THE CEILING IS THE SAME CONSTRAINT IT ALWAYS WAS: every colour on this ramp
   carries WHITE text (the title, the 12px .acc__num, the chevron), so every one
   of them has to clear 4.5:1 against white. That is why "light blue" here is a
   medium blue and not a sky blue — the brand kit's #29C0E4 is 1.9:1 against
   white and is unusable behind white text, exactly as #FBAE5F was on the warm
   side. #1772b7 is a 65/35 darkening of the kit's own two secondary blues
   (#0D489E and #29C0E4), which keeps it inside the brand family while landing
   at 5.09:1. Measured, all six anchors against white:
     #37126d 14.3   #5c2279 10.7   #6427a2  8.9
     #8f3bd4  5.6   #6a4fd0  5.8   #1772b7  5.1
   Nothing here may go lighter without the white text failing. */
:root{
  --acc-ramp-0:#37126d;   /* --plum      */
  --acc-ramp-1:#5c2279;   /* --grape     */
  --acc-ramp-2:#6427a2;   /* --violet    */
  --acc-ramp-3:#8f3bd4;   /* --purple, the "light purple" */
  --acc-ramp-4:#6a4fd0;   /* periwinkle — the purple->blue hinge, white 5.8:1 */
  --acc-ramp-5:#1772b7;   /* light blue, white 5.09:1 — the AA ceiling */
}
/* R4 — "each header should have connected gradients, as if there was no white
   space between the four and you applied the gradient to the entire thing as
   one big box."

   R3 already made the four END COLOURS match at the joins, but each header
   still painted its own 135deg gradient inside its own box, so the gradient
   RESTARTED four times: the ramp ran left-to-right across every bar and the
   diagonal reset at each card. What the client is describing is one gradient
   whose box is all four headers stacked with the gaps removed.

   That is what this is. The master ramp — all five anchors, so the
   interpolation still travels plum -> grape -> violet -> purple -> tan rather
   than cutting a straight two-stop line from #37126d to #90602f, which would
   pass through mud — is painted at 400% height, i.e. the height of four
   headers, and each card is offset to its own quarter of it.

   The four background-position percentages are not eyeballed. With an image 4x
   the box height, a percentage p resolves to (boxH - 4*boxH) * p = -3*boxH*p,
   so p = n/3 puts card n's slice exactly n header-heights down the image:
   0% -> 0, 33.333% -> -1 header, 66.667% -> -2, 100% -> -3. Card N's last
   painted pixel row IS card N+1's first, with nothing in between.

   HEIGHT-STABLE, so the R3 rule survives: 400% is a percentage of the
   <summary>, not of the band, and a summary's height is set by its own padding
   and content — it does not change when a panel opens. Only the .acc and the
   band grow. Expanding a category still cannot recolour any header.

   The only thing that can break the register is the four headers having
   DIFFERENT heights, which happens on narrow viewports where one title wraps to
   more lines than another. The slices then run at slightly different scales but
   still in the right order and still dark -> light, which is a graceful
   degradation rather than a visible seam. */
.acc--c1{--acc-tone:var(--plum);}
.acc--c2{--acc-tone:var(--grape);}
.acc--c3{--acc-tone:var(--violet);}
.acc--c4{--acc-tone:#1772b7;}   /* follows the blue end of the ramp above */
.acc--c1 > summary{background-position:0% 0%;}
.acc--c2 > summary{background-position:0% 33.333%;}
.acc--c3 > summary{background-position:0% 66.667%;}
.acc--c4 > summary{background-position:0% 100%;}

.acc > summary{
  position:relative;                /* the old z-index:1 lifted this above the
                                       deleted left rail; nothing needs it now */
  display:flex;align-items:center;gap:18px;
  padding:24px clamp(20px,3vw,30px);
  cursor:pointer;list-style:none;
  /* background-COLOR, not the shorthand: the shorthand would reset the gradient
     below every time this rule cascades. The colour is also the fallback if a
     browser somehow drops the image. */
  background-color:var(--acc-tone,var(--grape));
  background-image:linear-gradient(158deg,
    var(--acc-ramp-0) 0%,
    var(--acc-ramp-1) 15%,
    var(--acc-ramp-2) 32%,
    var(--acc-ramp-3) 50%,
    var(--acc-ramp-4) 68%,
    var(--acc-ramp-5) 100%);
  /* 400% = the height of all four headers stacked with the white space taken
     out. See the .acc--cN block above for why this is height-stable and for the
     arithmetic behind each card's background-position. */
  background-size:100% 400%;
  background-repeat:no-repeat;
}
.acc > summary::-webkit-details-marker{display:none;}
/* Hover as a full-bleed inset wash rather than a background change — a
   background/background-image change here would have to re-declare the whole
   gradient in four more places. An inset shadow paints over the background and
   under the content, and has zero layout cost. */
.acc > summary:hover{box-shadow:inset 0 0 0 999px rgba(255,255,255,.09);}
/* Cream, not --purple: a purple ring on a purple gradient is invisible. Cream
   on the lightest header colour (#8f3bd4) is 5.1:1, well past the 3:1 a focus
   indicator needs. */
.acc > summary:focus-visible{outline:3px solid var(--paper);outline-offset:-4px;}

/* Header furniture is white now that the bar is saturated. Lightest point in
   the whole set is #8f3bd4 (white = 5.6:1); #90602f is 5.4:1. Both clear AA
   for normal text. */
.acc__num{
  flex:0 0 auto;
  font-family:var(--font-head);font-size:.76rem;font-weight:700;letter-spacing:.1em;
  color:var(--white);
  width:34px;
}
/* The category icons are single-colour --ink artwork and would disappear on a
   dark header. brightness(0) crushes them to pure black, invert(1) flips that
   to pure white — no per-file editing, and it works on any future icon. */
.acc__icon{flex:0 0 auto;width:30px;height:30px;display:block;filter:brightness(0) invert(1);}
.acc__title{
  flex:1 1 auto;
  font-family:var(--font-head);font-size:clamp(1.02rem,2.1vw,1.28rem);font-weight:600;
  line-height:1.25;color:var(--white);margin:0;
}
.acc__chev{
  flex:0 0 auto;width:13px;height:13px;
  border-right:2.2px solid var(--white);border-bottom:2.2px solid var(--white);
  transform:rotate(45deg);margin-top:-5px;
  transition:transform .2s ease;
}
.acc[open] .acc__chev{transform:rotate(-135deg);margin-top:3px;}

/* ---- Heavier edge when expanded, with ZERO reflow ----
   Deliberately NOT border-width: growing the border would shift every card
   below it and reintroduce the page-jump the client reported.

   The obvious version of this is `box-shadow:inset 0 0 0 2px <tone>` on the
   card. It does NOT work here, and the reason is worth recording: an inset
   shadow is painted with the element's own background, i.e. BEFORE any
   descendant. Now that <summary> carries an opaque full-width gradient, the
   summary would paint straight over the ring for the whole height of the
   header, leaving the tone edge visible only around the panel. So the ring is
   drawn as a positioned overlay instead — a 2px border on an absolutely
   positioned ::after, which paints above the summary, is clipped to the card's
   radius, and takes part in no layout whatsoever. Same zero-reflow guarantee.
   pointer-events:none so it can never swallow a click on the summary. */
.acc[open]::after{
  content:"";position:absolute;inset:0;z-index:2;
  border:2px solid var(--acc-tone,var(--purple));
  /* -1px: the overlay sits inside the card's own 1px border, so its corner
     radius has to be one pixel tighter to nest cleanly instead of squaring off. */
  border-radius:calc(var(--radius) - 1px);
  pointer-events:none;
}
.acc--c1[open]{border-color:var(--plum);}
.acc--c2[open]{border-color:var(--grape);}
.acc--c3[open]{border-color:var(--violet);}
.acc--c4[open]{border-color:#1772b7;}

.acc__panel{
  padding:0 clamp(20px,3vw,30px) 30px;
  border-top:1px solid var(--rule);
  padding-top:26px;
}
.acc__list{
  list-style:none;margin:0;padding:0;
  columns:2;column-gap:44px;
}
/* R14 — these three are DIRECT-CHILD selectors (`> li`) now that one item
   contains a nested .acc__sublist. As plain descendant selectors they also
   matched the sub-items, which then got two markers and two sets of padding, and
   only rendered correctly because .acc__sublist happens to be declared later in
   this file and won the cascade on equal specificity. Relying on source order to
   suppress a duplicate bullet is not a thing to leave in place. */
.acc__list > li{
  break-inside:avoid;
  position:relative;
  padding:9px 0 9px 26px;
  font-size:.97rem;line-height:1.55;
  color:var(--ink);
}
/* Panel bullets stay --purple in every category — the tone change is the
   heading bar's job only. */
.acc__list > li::before{
  content:"";position:absolute;left:2px;top:1.02em;
  width:7px;height:7px;border-radius:50%;
  background:var(--purple);opacity:.85;
}
/* The long regulatory-standards item reads better full width. */
.acc__list > li.is-wide{column-span:all;padding-top:16px;}

/* R14 — the nested list inside that one wide item. Client: "it's kind of a super
   bullet (it has a bulleted list of its own). Maybe we can play with this
   bullet/sub-bulleting to make this look a little more even?"

   The evenness comes from making the second level visibly a second level rather
   than four more first-level bullets: a hollow ring instead of a filled disc,
   one step smaller, indented under the parent's text rather than its bullet. It
   deliberately does NOT go into columns — .is-wide already spans both, and
   splitting four short items across a 44px gutter would strand one alone. */
.acc__sublist{
  list-style:none;margin:8px 0 0;padding:0;
}
.acc__sublist > li{
  position:relative;
  padding:5px 0 5px 20px;
  font-size:.93rem;line-height:1.5;
  color:var(--ink);
}
.acc__sublist > li::before{
  content:"";position:absolute;left:2px;top:.78em;
  width:6px;height:6px;border-radius:50%;
  border:1.5px solid var(--purple);
  background:transparent;opacity:.75;
}

/* R12b: .acc__prose is DELETED. It existed for one round, to hold the Expertise
   entries while they were built out of this component. The client asked for
   those to be "a simpler, different but similar type of dropdown... lighter/
   cleaner, not thick and heavy like the services one", so they are now .dd — a
   separate component further down this file — and nothing outside Services uses
   .acc any more. .acc is once again what its section header says it is: the
   Services accordion, and only that. */

/* =========================================================================
   ABOUT
   ========================================================================= */
.about-grid{
  display:grid;grid-template-columns:300px 1fr;gap:clamp(32px,5vw,64px);
  align-items:start;
}
.headshot{
  background:var(--paper);
  border:1px solid var(--rule);
  border-radius:var(--radius);
  padding:0;overflow:hidden;
}
.headshot figcaption{
  font-size:.8rem;color:var(--muted);
  padding:14px 16px;border-top:1px solid var(--rule);
  text-align:center;
}
.about-body p:last-of-type{margin-bottom:0;}
.about-facts{
  list-style:none;margin:30px 0 0;padding:26px 0 0;
  border-top:1px solid var(--rule);
  display:grid;grid-template-columns:repeat(3,1fr);gap:22px;
}
.about-facts dt,.about-facts .f-k{
  font-family:var(--font-head);font-size:.7rem;font-weight:700;letter-spacing:.13em;
  text-transform:uppercase;color:var(--grape);margin-bottom:6px;
}
/* margin:0 kills the UA's default dd indent. It used to be a style="" attribute
   on each <dd>; the CSP no longer allows inline styles, so it lives here. */
.about-facts .f-v{font-size:.95rem;color:var(--ink);line-height:1.45;margin:0;}

/* =========================================================================
   CONTACT
   ========================================================================= */
/* R2: SINGLE COLUMN. This deliberately reverses the earlier two-up tweak — the
   client asked for the "Send a message" box to expand across the whole thing
   and for the section to feel balanced. The contact methods are now a 3-up
   strip stacked ABOVE a full-width form. */
/* ### R12b — "BRING THE CONTACT FORM UP A BIT, JUST LESS DEADSPACE." ###
   Three gaps sit between the "Contact" heading and the top of the form card, and
   the fix is to take a slice off each rather than gut one of them:

     #contact .sec-head margin-bottom   54 -> 32px   (heading block -> methods)
     .contact-list      padding-bottom  30 -> 22px   (methods -> its hairline)
     .contact-grid      gap             44 -> 28px   (hairline -> form card)
                                      ------------
                                        128 -> 82px at desktop

   ~46px of the form's travel, and it comes up as one movement because no single
   gap collapses far enough to make two elements look joined. Taking it all from
   .contact-grid instead would have put the form hard against the rule under the
   contact methods, which reads as a mistake rather than as tighter spacing.

   THE HEADING DOES NOT MOVE. The band's own padding-top is untouched, so this is
   the form rising toward a fixed heading, not the whole section shifting up — the
   client asked for the form, and Contact's top spacing matches every other band
   on the page and should keep matching it.

   The phone block below already resets .contact-list to its own gap values; it is
   unaffected, and at <=600px these clamps sit at their lower bounds anyway. */
#contact .sec-head{margin-bottom:clamp(22px,2.8vw,32px);}
.contact-grid{
  display:grid;grid-template-columns:1fr;
  gap:clamp(18px,2.4vw,28px);align-items:start;
}
/* Three methods side by side, label stacked over value, closed off with a
   single hairline that doubles as the rule between the strip and the form.
   minmax(0,1fr) rather than 1fr so a long address can never push a track wider
   than its share and force the page into horizontal scroll. */
.contact-list{
  list-style:none;margin:0;
  padding:0 0 clamp(16px,2.2vw,22px);   /* R12b: was 22-30px. See .contact-grid. */
  display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  gap:24px clamp(20px,3vw,40px);
  border-bottom:1px solid var(--rule);
}
.contact-list li{
  padding:0;border-bottom:none;
  display:flex;flex-direction:column;align-items:flex-start;gap:6px;
  min-width:0;
}
.contact-list .c-k{
  font-family:var(--font-head);font-size:.7rem;font-weight:700;letter-spacing:.13em;
  text-transform:uppercase;color:var(--grape);
  /* NOT flex:0 0 96px any more. These li are column flex containers now, so a
     flex-basis would set the label's HEIGHT, not a label-column width. */
  flex:none;
}
.contact-list .c-v{font-size:1.02rem;word-break:break-word;max-width:100%;}
.contact-list a{color:var(--violet);text-underline-offset:3px;}


/* =========================================================================
   LEGAL PAGES — privacy.html and terms.html
   R5. These two ship as TEMPLATES: the structure, the chrome and every
   section heading are final, and the wording of the sections that require an
   actual legal decision is not written yet. Everything about the styling below
   is built around making that state impossible to miss or to publish by
   accident — an unfinished privacy policy that LOOKS finished is worse than no
   page at all, because a visitor would reasonably rely on it.

   Three mechanisms, and all three come off together when the copy lands:
     1. .legal-draft — a banner at the top of the page saying, in plain words,
        that the page is a template and not yet in force;
     2. .legal-slot  — every unwritten section renders as a visibly outlined
        block that says what belongs there. It is not styled to blend in;
     3. <meta name="robots" content="noindex"> in each page's head, so neither
        template can be indexed while it is still a template.

   COLOUR: --grape on a purple-tinted panel, NOT red. The red convention this
   site used for draft copy was retired in R5 and is not coming back for this.
   Contrast on the .legal-slot backing (#F3EDF9): --grape 8.8:1, --ink 15.9:1.
   ========================================================================= */
.legal-draft{
  border:1px solid rgba(143,59,212,.38);
  border-left:5px solid var(--purple);
  border-radius:var(--radius);
  background:rgba(143,59,212,.06);
  padding:20px clamp(18px,3vw,26px);
  margin:0 0 clamp(30px,4vw,42px);
}
.legal-draft h2{
  font-size:1rem;letter-spacing:.02em;margin:0 0 .45em;color:var(--grape);
}
.legal-draft p{margin:0;font-size:.95rem;color:var(--ink);}
.legal-draft p + p{margin-top:.6em;}

.legal-meta{
  font-size:.9rem;color:var(--muted);
  margin:0 0 clamp(30px,4vw,42px);
  padding-bottom:18px;border-bottom:1px solid var(--rule);
}

/* The body is a single readable measure — narrower than .wrap, because legal
   prose at 1120px is unreadable. */
.legal-body{max-width:760px;}
.legal-body > section{margin:0 0 clamp(30px,4vw,44px);}
.legal-body h2{
  font-size:1.22rem;margin:0 0 .6em;
  /* The number is content, not decoration: it is in the markup so that a
     screen reader and a printed copy both announce "Section 4", which is how
     anyone referring back to a policy cites it. */
}
.legal-body h3{font-size:1.02rem;margin:1.4em 0 .5em;}
.legal-body ul{margin:0 0 1.1em;padding-left:1.25em;}
.legal-body li{margin-bottom:.4em;}
.legal-body p:last-child{margin-bottom:0;}

/* Table of contents — plain anchor list, no JS. */
.legal-toc{
  margin:0 0 clamp(30px,4vw,42px);
  padding:0;list-style:none;
  display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px 28px;
}
.legal-toc a{font-size:.94rem;text-underline-offset:3px;}
@media (max-width:600px){.legal-toc{grid-template-columns:1fr;}}

/* An unwritten section. Deliberately conspicuous. */
.legal-slot{
  border:1px dashed rgba(143,59,212,.55);
  border-radius:10px;
  background:rgba(143,59,212,.05);
  padding:16px 18px;
  margin:0;
  font-size:.94rem;color:var(--ink);
}
.legal-slot strong{color:var(--grape);}

/* =========================================================================
   FOOTER
   ========================================================================= */
/* R7: position:relative and overflow:hidden were added in R5 purely to host
   .bubbles--footer. That field is gone with every other bubble, so both are
   dropped and this is back to the plain rule it was. */
.site-footer{background:var(--ink);color:rgba(242,242,242,.82);padding:clamp(48px,6vw,72px) 0 40px;}
.site-footer a{color:#c9a3f2;}
.site-footer a:hover{color:var(--paper);}
/* Bottom border is a gradient accent line drawn as an ::after strip. */
.footer-top{
  position:relative;
  display:grid;grid-template-columns:1fr auto;gap:32px;align-items:start;
  padding-bottom:32px;
}
.footer-top::after{
  content:"";position:absolute;left:0;right:0;bottom:0;height:2px;
  background-image:var(--grad-cool);border-radius:2px;
}
.footer-top img{height:56px;width:auto;display:block;}
.footer-nav{display:flex;flex-wrap:wrap;gap:8px 26px;}
.footer-nav a{font-family:var(--font-head);font-size:.93rem;text-decoration:none;}
/* R5: the line that used to sit here explained the red draft copy. There is no
   red draft copy any more (see the --flag note in :root), so the slot carries
   the legal links instead — the conventional home for them, and the reason the
   colophon below no longer has to grow a third column. */
.footer-legal{
  margin:26px 0 0;
  display:flex;flex-wrap:wrap;gap:6px 22px;
  font-size:.85rem;
}
.footer-legal a{font-family:var(--font-head);text-decoration:none;}
.footer-legal a:hover{text-decoration:underline;text-underline-offset:3px;}
.colophon{
  margin-top:22px;display:flex;flex-wrap:wrap;gap:8px 24px;justify-content:space-between;
  font-size:.85rem;color:rgba(242,242,242,.6);
}

/* =========================================================================
   SCROLL REVEAL
   CONTENT-SAFETY CONTRACT: the hiding rules are scoped to html.js-reveal, and
   that class is ONLY added by JS after it has confirmed IntersectionObserver
   exists. With JS off, JS broken, or IO unsupported, .reveal is an inert class
   and every element renders at full opacity in its natural position. Nothing on
   this page depends on script or animation to become readable.
   ========================================================================= */
.js-reveal .reveal{
  opacity:0;
  transform:translate3d(0,22px,0);
  transition:opacity .62s cubic-bezier(.22,.61,.36,1),transform .62s cubic-bezier(.22,.61,.36,1);
}
.js-reveal .reveal.is-in{opacity:1;transform:none;}

/* Subtle child stagger. Delays are declarative CSS — JS never writes styles. */
.js-reveal .reveal-group > *{
  opacity:0;
  transform:translate3d(0,18px,0);
  transition:opacity .55s cubic-bezier(.22,.61,.36,1),transform .55s cubic-bezier(.22,.61,.36,1);
}
.js-reveal .reveal-group.is-in > *{opacity:1;transform:none;}
.js-reveal .reveal-group.is-in > *:nth-child(1){transition-delay:.04s;}
.js-reveal .reveal-group.is-in > *:nth-child(2){transition-delay:.12s;}
.js-reveal .reveal-group.is-in > *:nth-child(3){transition-delay:.20s;}
.js-reveal .reveal-group.is-in > *:nth-child(4){transition-delay:.28s;}
.js-reveal .reveal-group.is-in > *:nth-child(5){transition-delay:.36s;}
.js-reveal .reveal-group.is-in > *:nth-child(6){transition-delay:.44s;}

/* =========================================================================
   MICRO-INTERACTIONS
   ========================================================================= */
.btn{position:relative;}
/* Shadow rgbas follow the button colour: --grape (92,34,121), not --purple. */
.btn--primary{box-shadow:0 6px 18px rgba(92,34,121,.22);}
.btn--primary:hover{transform:translateY(-2px);box-shadow:0 10px 26px rgba(92,34,121,.32);}
.btn--primary:active{transform:translateY(0);box-shadow:0 4px 12px rgba(92,34,121,.28);}
.btn--ghost:hover{transform:translateY(-2px);background:rgba(143,59,212,.05);}
.btn--ghost:active{transform:translateY(0);}
.btn{transition:background-color .18s ease,color .18s ease,border-color .18s ease,
                transform .22s cubic-bezier(.22,.61,.36,1),box-shadow .22s ease;}

/* Nav links get an accent underline that wipes in from the left. */
.nav-desktop a:not(.btn){position:relative;}
.nav-desktop a:not(.btn)::after{
  content:"";position:absolute;left:14px;right:14px;bottom:5px;height:2px;
  background:var(--purple);border-radius:2px;
  transform:scaleX(0);transform-origin:left center;
  transition:transform .26s cubic-bezier(.22,.61,.36,1);
}
.nav-desktop a:not(.btn):hover::after{transform:scaleX(1);}

.cat-card{position:relative;overflow:hidden;}
.cat-card img{transition:transform .3s cubic-bezier(.22,.61,.36,1);}
.cat-card:hover img{transform:translateY(-3px) scale(1.06);}
.cat-card:hover{transform:translateY(-4px);}
.acc-tools__btn{transition:border-color .16s ease,color .16s ease,transform .18s ease,background-color .16s ease;}
.acc-tools__btn:hover{transform:translateY(-1px);background:rgba(143,59,212,.05);}
.acc-tools__btn:active{transform:translateY(0);}

/* Accordion: eased, not instant. <details> cannot transition its own height
   without a JS height dance, so the PANEL CONTENT eases in on open instead —
   opacity + a short lift. Cheap, smooth, and it degrades to a plain open panel. */
.acc[open] .acc__panel{animation:panel-in .34s cubic-bezier(.22,.61,.36,1) both;}
/* box-shadow, not background-color: the hover wash is now an inset shadow. */
.acc > summary{transition:box-shadow .2s ease;}
.acc__icon{transition:transform .3s cubic-bezier(.22,.61,.36,1);}
.acc > summary:hover .acc__icon{transform:scale(1.1) rotate(-4deg);}
.acc[open] .acc__icon{transform:scale(1.06);}
.acc__chev{transition:transform .28s cubic-bezier(.22,.61,.36,1),margin-top .28s ease;}

/* =========================================================================
   BAND TEXTURES — NONE LEFT. R7 removed every one.

   This section used to hold the Bubbles-pattern washes and the CSS-drawn bubble
   fields. The client's instruction was "get rid of all the bubbles", so all of
   them went: .bubbles / --hero / --footer, .seam-bubbles, .band-pair__tex and
   .form-card::before, plus @keyframes float-up which had no other user. R6 had
   already deleted the .band__tex family from About and Contact.

   Madd_Ox_Pattern_Bubbles_Purple.png/.webp are now referenced by nothing. The
   files are kept in assets/patterns/ because they are brand artwork, not
   because anything renders them — do not "fix" a missing reference by wiring
   one back in.

   R11 UPDATE: the Turing "brain squiggle" wash that this block used to exempt
   is gone as well — the client asked for it removed from Services, which was
   its only user. So Madd_Ox_Pattern_Turing_Purple_02.webp joins the two Bubbles
   files as brand artwork the page does not render. The ONE pattern still drawn
   anywhere is Sequencing, in the hero (.hero__bg-pattern).
========================================================================= */
.band{overflow:hidden;}

/* ---- ONE BUBBLES FIELD ACROSS #approach + #expertise ----
   R3 replaces the R2 "bridge". R2 used two separately-positioned halves either
   side of the section boundary, engineered to stay in register; the client
   said make it actually seamless, "if you cant do it seemlessly, find a way, or
   physically combine the two sections". The two halves are deleted —
   .band__tex--bridge / -bridge-a / -bridge-b and their markup are gone.

   Why one element was impossible before, and is possible now: #approach and
   #expertise are sibling <section>s, every .band is overflow:hidden, and the
   two carried different background colours. So index.html now wraps the pair in
   a single positioned .band-pair. That wrapper owns the background colour, the
   overflow:hidden and ONE texture layer spanning both sections; the sections
   themselves go transparent and overflow:visible so the single field shows
   through continuously. There is no seam to hide because there is no seam.

   COLOUR CHOICE — the pair is CREAM. Band rhythm down the page is now
   hero paper / services white / about paper / PAIR paper / contact white /
   footer ink. Merging two bands into one necessarily breaks the strict
   alternation somewhere, and --paper is the better place to spend it: it keeps
   the page's two visually loud white bands (the Services surge and the Contact
   form) separated by one continuous warm stretch, rather than running
   approach + expertise + contact as three consecutive white bands with only a
   hairline between them. It also puts this texture on the same base colour as
   the neighbouring About band. (R6/R7: neither band carries any texture now,
   so the "one continuous motif" half of that argument is gone — the
   band-rhythm half still holds, and the shared --paper is still what makes the
   pair read as one surface.) #approach's class changed white -> paper
   to match. (R11 made every .sec-rule cool regardless of band, so the second
   half of that parenthetical no longer distinguishes anything.)

   The mask is a single radial anchored to the RIGHT edge and centred
   VERTICALLY on the pair — i.e. densest exactly at the old boundary, softest
   everywhere else. One layer only, for the mask-composite reason documented
   above. Percentage sizing is safe here in a way it is NOT in the Services band
   (see the height-stability rule there): nothing inside this pair expands or
   collapses, so its height is static.

   Deliberately NOT animated — the client asked for less motion this round, not
   more, so there is nothing here for the reduced-motion block to switch off. */
.band-pair{
  position:relative;
  overflow:hidden;                 /* the WRAPPER clips, so the sections need not */
  background:var(--paper);
}
/* ---- R4: About and How I Work are now ONE continuous --paper stretch ----
   The client asked to remove the line between "Meet Jayme" and "How I work" and
   close the gap. The hairline is gone from index.html; this is the second half
   of that change. Both bands carry the full .band padding
   (clamp(56px,8vw,104px)) top AND bottom, so even with no rule between them the
   two were separated by ~208px of empty --paper at desktop — which still read as
   "two sections", just without a divider.
   Trimming BOTH sides of the join (rather than one side by twice as much) keeps
   the meeting point centred: About's content and the "The Madd Ox Approach"
   eyebrow now sit ~68px apart at desktop instead of ~208px, and the two blocks
   read as one field. Nothing else moves: About keeps its full top padding under
   the Services divider, and #expertise keeps its own spacing below. */
/* ### SPACING AT THE THREE JOINS. TWO DIFFERENT NUMBERS, FOR ONE REASON. ###
   A boundary with NO rule wants to be tight, so the blocks merge — that is R4's
   34px, and it is what the client asked for at About -> Approach.

   A boundary WITH a .band--sep bar cannot use 34px: the bar then reads as an
   underline on whichever block is nearer rather than as a divider between two,
   and at small viewports (18px) it almost touches the text above it. Those joins
   get ~52px a side, symmetric — an off-centre rule reads as belonging to the
   closer block. 52px is still a long way from the 208px these started at, which
   is the half of R4 the client wanted kept ("pull this up a little bit so that
   it's a little bit tighter").

   R14b MOVED the rule off the About -> Approach join (up to the top of #about),
   so that join goes back to R4's 34px — the widening only ever existed to give
   the bar room, and the bar is not there any more. Approach -> Expertise keeps
   its rule and so keeps 52px.

   #about's padding-TOP is deliberately untouched: it is the full .band padding
   (clamp(56px,8vw,104px)), which is already ample room under the rule that now
   sits at its top edge. */
#about{padding-bottom:clamp(18px,2.4vw,34px);}
.band-pair > #approach{
  padding-top:clamp(18px,2.4vw,34px);
  padding-bottom:clamp(30px,3.6vw,52px);
}
.band-pair > #expertise{padding-top:clamp(30px,3.6vw,52px);}
/* ---- R12: THE PAIR DISSOLVES INTO THE CONTACT BAND ----
   The hairline that used to sit between #expertise and #contact is deleted from
   index.html this round, because the purple surge moved onto Contact and now
   feathers in over that band's first ~330px — a 1px rule laid across the top of
   that feather is precisely the seam the feather exists to prevent. Deleting the
   rule on its own would have left the bare --paper -> --white colour step it was
   hiding, so this is the second half of the change: the same technique
   .hero::after uses at the top of the page, pointed the other way.

   Length only (no percentage of the pair's box), so nothing here re-derives from
   how tall Approach and Expertise happen to be. It ends on a fully opaque
   #ffffff, which is what guarantees there is no residual step for a hard line to
   form on.

   z-index:0 against .wrap's z-index:1 — the eight stat cards and the section
   content stay above the wash, and the wash only ever paints over the wrapper's
   own --paper background. */
.band-pair::after{
  content:"";
  position:absolute;left:0;right:0;bottom:0;z-index:0;
  height:clamp(90px,12vw,150px);
  background:linear-gradient(180deg,
    rgba(255,255,255,0)   0%,
    rgba(255,255,255,.28) 38%,
    rgba(255,255,255,.62) 64%,
    rgba(255,255,255,.88) 84%,
    #ffffff               100%);
  pointer-events:none;
}
/* Both inner sections become transparent windows onto the wrapper. The
   two-class selector outranks .band--paper / .band--white (0,1,0). */
.band-pair > .band{background:transparent;overflow:visible;}
.wrap{position:relative;z-index:1;}

/* =========================================================================
   R12 — SERVICES: THE SECOND HALF OF THE "SHORTEN IT UP" ASK.
   The client asked for the gap between the four hero cards and the Services
   section to come down "a tiny bit". .hero's bottom padding carries the other
   half of it — see the note there for why the reduction is split across both
   sides of the join rather than taken from one. 104px -> 80px at desktop.
   ========================================================================= */
#services{padding-top:clamp(44px,6vw,80px);}

/* =========================================================================
   R12 — .band--sep: THE "YOU ARE ON A NEW SECTION" CUE.

   Client: "including a bit more of a separator right as you get to the services
   page. Currently they flow into one another and there isn't a super great
   visual cue that you're on a new page."

   ### WHY THIS IS NOT THE GREY LINE FROM R3 ###
   R3 deleted a 1px hairline here because the client reported "a hard grey line".
   Putting a divider back is therefore a decision that has to answer that report
   directly, not hope it is forgotten. Three properties do:

     1. IT IS COLOURED, not grey. --grad-cool is the same brand ramp already
        carrying .sec-rule, .footer-top::after and .subhead::before, so it reads
        as the site's own accent language rather than a UA border. The thing the
        client objected to was a neutral rule appearing where he expected a
        gradient to continue.
     2. IT HAS NO ENDPOINTS. The horizontal mask takes both ends to fully
        transparent, so there is no hard start or stop anywhere on the line — the
        specific quality that made the old hairline read as a seam stretched
        across the page. It also means the bar cannot collide with the gutters at
        any viewport width.
     3. IT SITS ON THE BOUNDARY, not near it. The old hairline was a separate 1px
        element between two bands, i.e. a third surface; this is painted at
        top:0 of the Services band itself, so the colour step and the bar are the
        same edge rather than two edges a few pixels apart.

   The inset shade is the other half of the cue and does the heavier lifting: a
   soft shadow immediately under the bar reads as the Services band sitting on a
   slightly different plane from the hero. A boundary the eye reads as depth is
   much harder to miss than one it reads as a line, which is the actual complaint
   being answered.

   THE THREE NUMBERS ARE A SET, AND CHANGING ONE ALONE BREAKS IT. For an inset
   shadow the inner rectangle is the padding box offset by dy and inset by the
   spread, so with dy:16px and spread:-16px the inner edge lands at EXACTLY y=0 —
   flush with the top of the band — and the left, right and bottom inner edges
   fall 16px outside the box, which is what keeps this a top-edge shade rather
   than a vignette around the whole section. The blur then decides how deep it
   goes: an edge sitting on the boundary renders at half alpha and fades to
   nothing over blur/2.

   ### R12b RAISED THE ALPHA, .16 -> .20, AND THE REASON MATTERS ###
   When this was written, the boundary had TWO cues: this shade and a --paper ->
   --white colour step. R12b made the Services band --paper to match the hero
   ("just back to normal grey like before"), which deleted the colour step
   entirely — the bar and this shade are now the only things marking a boundary
   the client specifically asked to be able to see. So it carries the work alone
   and is tuned up to suit.

   Alpha .20 renders at .10 peak (the edge sits on the boundary, so half), fading
   to nothing by y=15px. Over #f2f2f2 that composites to #DCDAE0 at the top
   row — a shade you read as the section sitting on a nearer plane, not a bar.
   This is close to the ceiling: past roughly .26 the top row starts reading as a
   grey rule in its own right, which is precisely the thing R3 deleted and the one
   failure mode this element must not reintroduce.

   z-index:2 puts the gradient above the .band-dna layer (z-index:0) and above
   .wrap (z-index:1). Nothing overlaps it in practice — the band's padding keeps
   content well clear of y=0 — but the layer order should not depend on that.
   .band{overflow:hidden} clips it to the band, which is what keeps the 3px bar
   from bleeding over the hero.
   ========================================================================= */
.band--sep{box-shadow:inset 0 16px 30px -16px rgba(17,0,64,.20);}
.band--sep::before{
  content:"";
  position:absolute;left:0;right:0;top:0;height:3px;z-index:2;
  background-image:var(--grad-cool);
  -webkit-mask-image:linear-gradient(90deg,transparent 0%,#000 16%,#000 84%,transparent 100%);
          mask-image:linear-gradient(90deg,transparent 0%,#000 16%,#000 84%,transparent 100%);
  pointer-events:none;
}

/* =========================================================================
   R12 — .band-dna: THE HERO'S SEQUENCING ART, CARRIED INTO SERVICES.

   Client: "remove the background shimmer (e.g. just light gray?) or just extend
   the background dna elements from the home page."

   Taking the second option. A flat grey would have solved the shimmer complaint
   by deleting the band's only visual interest, and it would have put --paper
   directly against the hero's --paper with nothing between them — which is the
   opposite of the separator he asked for in the same sentence. Continuing the
   hero's own artwork keeps the two bands related without making them identical,
   and Sequencing is the one background he has consistently said he likes.

   ### THE SCALE IS MATCHED TO THE HERO ON PURPOSE ###
   The artwork is 2309x1803 (aspect 1.281). The hero draws it at `auto 118%` of
   its own box, which at a typical ~820px hero resolves to roughly 970px tall.
   This layer is pinned at a FIXED 1150px tall, i.e. within ~20% of the hero's
   rendered size, so a ladder here is recognisably the same ladder as the one
   above the boundary. Sizing it to this band instead would have drawn the same
   file at whatever height the accordion happened to be, and the pattern would
   have read as a different, coarser texture rather than a continuation.

   ### HEIGHT-STABILITY — the rule this band has enforced since R3 ###
   The band grows ~750px when all four accordion panels open. Nothing here may be
   derived from that height, or the background re-scales on every toggle (the bug
   the client reported in R3). Every value below is either a fixed px or a
   percentage of a fixed px box:
     - the LAYER BOX is a hard-coded 1400px tall pinned to the band's top, so its
       own percentages resolve to constants;
     - background-size is `auto 1150px`, fixed px, so the artwork cannot rescale;
     - background-position is a keyword pair measured from the top-right, so the
       band can only ever grow downward past pixels that are already painted;
     - the radial mask's percentages resolve against that fixed 1400px box on the
       vertical axis, and against the band's WIDTH on the horizontal — and the
       width does not change when a panel opens.
   Opening a category cannot move, rescale or recolour one pixel of this layer.

   ### NO BOTTOM FEATHER IS NEEDED, AND THAT IS NOT AN OVERSIGHT ###
   The surge layers this replaces needed a `calc(100% - 215px)` bottom exception
   because they painted a gradient across their whole box. This one does not: the
   artwork is `no-repeat` at 1150px tall, so BELOW y=1150 there is simply nothing
   drawn. The radial is dimensioned so its vertical falloff completes just past
   that — centred at 30% of 1400 (420px) with a 56% (784px) radius, fully
   transparent at 96% of it, i.e. by y=1173 — which puts the mask at ~0.024 where
   the artwork's own bottom edge is. At .12 layer opacity that is roughly 0.3% of
   an already-light pixel, an order of magnitude under the 5/255 threshold this
   file uses. There is no edge to feather because the artwork has already gone.

   ONE MASK LAYER, as everywhere else in this file: a single ellipse anchored off
   the right edge gives horizontal AND vertical falloff at once. Two stacked
   linear masks would need mask-composite:intersect, which defaults to `add`
   (union) where unsupported — i.e. it fails OPEN and washes the whole band.

   CONTRAST. The mask holds the left ~52% of the band at zero, which is where the
   section heading and every accordion card sit; the cards are opaque white and
   paint above this entire layer regardless. The .sec-head paragraph is pinned to
   --ink below purely so it renders identically to what the client has already
   signed off on twice — with only this layer left the band is near enough white
   under that text for --muted to pass comfortably, so the pin is preserved
   state, not a contrast requirement.
   ========================================================================= */
/* ### R12b — .band-dna IS DELETED. THE SERVICES BAND HAS NO TEXTURE AT ALL. ###
   Client: "get rid of the dna in the services background, just back to normal
   grey like before."

   Everything above this point is the record of how that layer was measured and
   masked; no rule below renders it, and the <div class="band-dna"> is gone from
   index.html too. Madd_Ox_Pattern_Sequencing_Purple.webp is still very much in
   use — .hero__bg-pattern draws it, and the hero is the one background the
   client has kept through every round. Do not "clean up" that asset.

   THE FULL LIST OF UNRENDERED BRAND ARTWORK is now: Bubbles (since R7), Turing
   (since R11). Sequencing is NOT on it. All three files stay in
   assets/patterns/ because they are brand artwork; a missing reference here is
   not a bug to repair by wiring one back into a band.

   WHAT REPLACED IT IS NOTHING — the band is a flat --paper field, which is why
   `band--white` became `band--paper` in index.html at the same time ("normal
   grey" is the hero's #f2f2f2, not the white this band shipped with). The two
   knock-on edits that HAD to travel with that colour change:
     1. .hero::after is deleted — see the tombstone in the HERO block above. It
        dissolved the hero's grey into a WHITE Services band, and against a grey
        one it would have painted a white stripe across the hero's bottom edge.
     2. .band--sep's shade is strengthened. It was one cue alongside a white/grey
        colour step; with both bands the same colour it is now the only marker of
        that boundary, and the client asked specifically to be able to see it. */

/* Preserves the reviewed rendering of the Services lead paragraph. This pin has
   moved twice without its VALUE ever changing: .band--surge carried it until the
   surge went to Contact, .band--dna until R12b deleted that class. It is scoped
   to the ID now so there is no third class for it to fall off.
   It is preservation, not a contrast requirement — --muted is 6.8:1 on --paper
   and would pass. Left at --ink because that is what the client has reviewed,
   and the band losing a texture is no reason to change the weight of the copy on
   top of it. Delete it freely if this lead should ever match the --muted leads in
   About, Approach and Expertise, which are now the same colour band. */
#services .sec-head p{color:var(--ink);}

/* =========================================================================
   THE PURPLE SURGE BAND BACKGROUND.

   ### R12 — THIS IS THE CONTACT BAND NOW, NOT SERVICES. ###
   Client: "transfer the background purple shimmer gradient thing to flow into
   the contact page seamlessly." The `band--surge` class moved from #services to
   #contact in index.html and the two <div> layers moved with it; NOT ONE VALUE
   IN THE RULES BELOW CHANGED. Everything from here to the end of the surge block
   was written for the Services band and every measurement in it — the 1400x1340
   reference geometry, the 2200px boxes, the "the band grows ~750px when panels
   open" argument — describes THAT band. Read it as the record of how these
   numbers were derived, not as a description of where they now sit.

   What that means for maintenance:
     - the height-stability rule below is no longer load-bearing. Contact has no
       accordion and does not change height. The rules are kept exactly as
       written because they are correct and re-deriving them against a new
       reference would be churn with no visible payoff;
     - the container's top feather (0 -> full over the band's first 330px) is now
       the thing doing the "flow into contact seamlessly" work, together with
       .band-pair::after dissolving the --paper pair above it into white and the
       deletion of the hairline that used to sit between them;
     - CONTRAST WAS RE-CHECKED FOR THE NEW BAND, since the text on it is not the
       text this was cleared against. Worst case is both surge peaks coinciding,
       which puts the backing at ~#D0C2DF. On that: --ink 11.9:1, --muted 5.8:1,
       and the --violet contact links 5.3:1. All pass AA. The form card is opaque
       --paper and sits above the wash entirely.

   Bottom to top: band --white -> __l1 -> __l2 -> .wrap (z-index:1, holds the
   section head, the contact methods and the opaque form card).
   R11 REMOVED THE TURING TEXTURE LAYER that used to sit at the bottom of this
   stack; read the R11 block further down before treating any of the texture
   arithmetic in this header as live. The height-stability rule it states DOES
   still govern both surviving layers.

   R2: the DNA-ladder (Sequencing) wash that used to live here is gone — the
   client asked for "a more purple forward surge gradient". The Sequencing
   PNG/WebP files are STILL IN USE by .hero__bg-pattern; do not delete them.
   R2b: the client then asked for the TURING pattern back underneath it. R11
   removed it again, permanently — that layer no longer exists.

   Technique is the hero's .hero__bg-foil / -foil2: a repeating-linear-gradient
   at an oversized background-size, moved by background-position. Two changes
   were required to make it work HERE rather than on the lighter hero band:

   1. BLEND. The hero uses mix-blend-mode:soft-light. soft-light over a WHITE
      backdrop is a mathematical no-op (backdrop 1.0 stays 1.0), so on this
      white band the hero's numbers would render literally nothing. These
      layers use `multiply`, which over white returns the source colour
      unchanged and can only ever darken — it cannot wash anything out.
   2. STOPS. The hero's gold (--tan) and cyan stops are dropped; #cba36d is
      kept only as a ~.06 trace on the second layer so the surge does not read
      as flatly monochrome. Everything else is --purple / --violet / --grape.

   ### R3 — THE HEIGHT-STABILITY RULE FOR THIS WHOLE BAND ###
   Client report: "the background gradients and colors are shifting and jumping
   around as you open and close the service boxes." Root cause: __l1 was sized
   210% 210% and __l2 230% 230%. Those are percentages OF THE BAND BOX, and the
   band grows by ~750px when all four panels open — so both gradients rescaled
   and the entire surge re-coloured every time a category was toggled. The
   container's vertical feather (14%/84%) had the same disease: at 1340px it
   feathered over 188px, at 2090px over 293px, so the top of the band visibly
   changed too.

   THE RULE, now applied to every layer here: NOTHING in this band may be
   derived from the band's live HEIGHT. Concretely —
     - background-size: fixed px on both axes (never %, never cover);
     - background-position: fixed px measured FROM THE TOP-LEFT, so the band
       can only ever grow downward past already-painted pixels;
     - masks: px stops measured from the top, or percentages along the 90deg
       (width) axis, which is stable because the band's WIDTH never changes
       when a panel opens;
     - the one deliberate exception is the container's BOTTOM feather, pinned
       with calc(100% - 215px) so it stays a constant distance above the
       band's bottom edge. That edge is supposed to move — that is what "the
       section extends downward" means — and the feather has to follow it.
   Reference geometry for the fixed px values below: a 1400px-wide viewport
   with the band in its default state (card 1 open) at ~1340px tall, which is
   what 210%/230% used to resolve to. Nothing re-derives them at runtime.

   Contrast (recomputed for R3, since __tex went .06 -> .13): the lowest-
   contrast text this band can carry is the .sec-head paragraph. Computed
   through the real blend chain against the actual pixels of the Turing file —
   its darkest 0.5th-percentile pixel, texture mask FULLY OPEN, and BOTH surge
   peaks coinciding — the backing bottomed out at #CBC2D9, where --muted read
   5.47:1 and --ink 11.15:1.
   R5 SUPERSEDES THE NUMBERS ABOVE, not the method. The texture now runs at .20
   with no horizontal mask relieving the left edge, so the same triple worst
   case lands at #A492BD: --muted 4.09:1 (FAILS) and --ink 6.78:1 (passes).
   That is why .band--surge .sec-head p is pinned to --ink — see the R5 block on
   .band-surge__tex for the full working. Do not put --muted text back into this
   band without redoing that arithmetic.
   The accordion cards are solid white and sit above all of it.
   ========================================================================= */
.band--surge{position:relative;}
.band-surge{
  position:absolute;inset:0;z-index:0;pointer-events:none;overflow:hidden;
  /* Vertical feather on the CONTAINER, so it applies once and ALL THREE layers
     dissolve together before the band's top and bottom edges. A hard seam at a
     band boundary is a bug the client has already reported once.

     R3 does two things to it. (1) The stops are px, not %, so opening a panel
     cannot move them (see the height-stability rule above). (2) The top feather
     is much longer and eased through a mid stop, because the divider that used
     to separate the hero from this band is GONE — the surge stack now has to
     fade UP out of the hero's --paper->white bleed instead of starting at full
     strength against a hairline. 0 -> .34 over the first 140px, then to full at
     330px, which is roughly where the section heading begins. */
  -webkit-mask-image:linear-gradient(180deg,rgba(0,0,0,0) 0px,rgba(0,0,0,.34) 140px,#000 330px,#000 calc(100% - 215px),rgba(0,0,0,0) 100%);
  mask-image:linear-gradient(180deg,rgba(0,0,0,0) 0px,rgba(0,0,0,.34) 140px,#000 330px,#000 calc(100% - 215px),rgba(0,0,0,0) 100%);
}
/* ---- TURING ("brain squiggle") base texture ----
   R2b: the client asked for this pattern back on Services — but faint, under
   the surge. MEASURE THIS FILE BEFORE CHANGING THE OPACITY. Unlike Bubbles and
   Sequencing, which are dark line-work on an opaque LIGHT background, this
   asset is the inverse: a near-fully-opaque DARK field (mean rgb 45,14,97,
   median 43,14,96 — i.e. around --plum, with alpha mostly 255). Consequences:

     - it darkens the band far faster per unit of opacity than the other two
       patterns do, so the opacity that looks right for a Bubbles layer is much
       too strong here (R7 note: every Bubbles layer this compared against is
       now deleted. The register it means is roughly .085-.09, which is where
       they all sat);
     - the light/dark spread inside the image is small, so most of what it
       contributes is a flat tint and only a little is visible texture;
     - an unmasked box shows a hard DARK rectangle on the white band, which is
       the same seam failure every band-texture comment in this file warns
       about, only louder.

   R3 — "i think it might be there but i cant tell." It was there, at .06, and
   the client was right. The fix is NOT simply more opacity: because the field
   is dark and nearly flat, raising a FLAT opacity greys the whole band instead
   of revealing texture. Measured off the actual file: at .06 the dark field
   composites to #F1F0F3 and the light squiggles to #F7F3FB — a 1.04:1
   separation, i.e. at or below the just-noticeable threshold, which is exactly
   why it read as "maybe there". Three changes, together:

     1. OPACITY .06 -> .13. Field #E0DEE5, squiggles #EEE6F6 — 1.10:1
        separation, roughly 2.5x the visible modulation. .13 is also the
        ceiling: see the contrast arithmetic in the band header comment.
        (R5 note: that ceiling was computed against the red draft copy, which no
        longer exists. It is superseded — the layer runs at .20 now. Kept as the
        record of how the number was arrived at.)
     2. MASK confined to the RIGHT. A flat .13 across the band would be the
        "even grey tint" failure. This mask holds the left 50% of the band at
        ZERO — clean white under the section heading and the accordion — and
        ramps to full only past 90%, so the pattern reads as deliberate
        right-hand texture rather than a wash.
     3. SCALE 760px -> 1180px tall. The squiggles are ~55% larger, so an
        individual squiggle is legible at a glance instead of dissolving into
        noise at the size the eye actually samples it.

   Right-weighting also keeps it away from the two things it must never compete
   with: the section heading (left, top) and the accordion (opaque white cards
   painted above this entire stack).

   HEIGHT-STABLE, both properties. background-size is fixed px, so the texture
   cannot rescale when a panel opens. The mask is a 90deg linear — its
   percentages resolve along the WIDTH only, and the band's width does not
   change when a panel opens. The old radial mask was sized 92% vertically and
   centred at 50% height, so it stretched and re-centred on every toggle; that
   made it part of the shifting-background bug this round fixes.

   Still exactly ONE mask layer, deliberately — stacking two would need
   mask-composite:intersect, which falls back to `add` (union) where it is
   unsupported and would fail OPEN, washing the whole band. The container's
   px-anchored vertical feather multiplies with this for the top/bottom
   falloff, so the texture still dissolves before both band edges.

   R4 — "make the gradient and brain pattern MUCH more prominent in the ENTIRE
   background of the services section." Two changes, and note which one is
   which: the R3 mask held the LEFT HALF OF THE BAND AT ZERO, so half the
   section had no texture at all. That is the part that had to go — the mask now
   starts at .42 on the left edge instead of 0, so the pattern is present across
   the whole width while still being densest on the right (a flat mask would be
   the "even grey tint" failure R3 warns about above). Opacity then goes
   .13 -> .20 and the scale 1180 -> 1320px so the squiggles read as squiggles.

   THE .13 CEILING IN THE HEADER COMMENT IS NOT VIOLATED — it was computed for
   the far right of the band, where the mask is fully open, and .20 * 1.0 there
   would indeed exceed it. It does not, because the mask's right end is unchanged
   at #000 only past 90% and the container's vertical feather multiplies in; the
   binding case is the text at the LEFT, where the effective alpha is
   .20 * .42 = .084 — still below R3's flat .13 — and where the container feather
   cuts it further (the section head sits in the 140-330px ramp, mask .34-.9).
   Measured after the edit at 1470px: the backing under the .sec-head paragraph
   composites to #F3F0F6, where the red draft copy read 7.2:1 and --muted 8.0:1.
   The right edge, which carries no text, is where the pattern may be loud.

   ### R5 — "EXPAND THE TURING PATTERN, AS ONE, ACROSS THE WHOLE SERVICES PAGE" ###

   Up to R4b this layer TILED: `background-repeat:repeat` at `auto 1320px`, so a
   1400px-wide band drew roughly 1.06 x 1.0 copies of the artwork and cut a tile
   boundary down the right-hand third and across at ~1320px. That is not one
   pattern, and the boundary is not theoretical — the file was measured:

     mean |dRGB| across the LEFT/RIGHT wrap : 54.9   (interior control: 4.7)
     mean |dRGB| across the TOP/BOTTOM wrap : 20.6   (interior control: 6.0)

   i.e. the vertical seam is ~12x an ordinary neighbouring-column step. At .20
   opacity that lands ~11/255 on screen, which is a visible line, and it was
   sitting in the emptiest part of the band. Madd_Ox_Pattern_Turing_Purple_02 is
   simply not a seamless tile, the same way Madd_Ox_Pattern_Bubbles_Purple is
   not. The deleted .band-pair__tex carried the same fix for the same reason —
   a non-seamless asset shown as ONE non-repeating copy rather than tiled.

   So the layer now paints exactly ONE copy, scaled to span the section:

     - background-repeat:no-repeat — there is no second copy, so there is no
       boundary to see. This is the whole point of the round.
     - background-size:cover — one copy scaled until it covers the box in BOTH
       axes, at the artwork's own 1089:1090 aspect. No stretch, no squash: the
       overflow is cropped, and on a square source the crop is symmetric.
     - the BOX is a fixed 2200px-tall strip pinned to the band's TOP, not
       inset:0.

   THAT LAST POINT IS THE HEIGHT-STABILITY RULE, NOT A VIOLATION OF IT. `cover`
   is forbidden against the BAND because the band grows ~750px when the panels
   open (see the rule in the header comment). It is safe against a box whose
   height is a hard-coded 2200px and whose width is the band's width, because
   neither number moves when a panel opens — the same technique the deleted
   .band-pair__tex used. Opening a category cannot rescale, re-crop or re-colour
   one pixel of this layer; the band just grows downward past paint that is
   already there.

   WHY 2200px. It is the tallest the reference desktop band ever gets: ~1340px
   with card 1 open, ~2090px with all four open (the geometry the whole band is
   dimensioned against). So at desktop the single copy covers the section in
   every accordion state. Coverage past that is deliberately not chased — when
   a phone opens all four panels the band runs past 3000px, but the added height
   is ~100% opaque white accordion card, and the only remedy would be scaling
   the artwork to ~3x its native size, which turns squiggles into blobs. Instead
   the layer FADES OUT rather than stopping: the mask below dissolves it over
   the last quarter of its own box, so where coverage ends there is a soft
   falloff and never a hard edge — the same contract as every other texture here.

   SCALE. 2200 / 1090 = 2.02x native, against R4's 1320 / 1090 = 1.21x. The
   squiggles are two-thirds larger again, which is the "expand" half of the ask;
   it also means the file is being upscaled 2x, so it reads softer. At .20
   opacity behind opaque cards that is a wash, not an image, and soft is right.

   MASK — one layer, still, for the mask-composite:intersect reason documented
   on every other texture in this file. R4b's horizontal mask is GONE and a
   vertical one takes its place, because with a single non-repeating copy the
   important edge is the bottom of the artwork, not the left of the band. Its
   stops are percentages of a FIXED 2200px box, so they resolve to fixed pixels
   (solid to 1628px, gone by 2200px) and are height-stable in the same way the
   90deg mask was. The container's own feather still multiplies in on top, so
   the band's top and bottom edges are unchanged.

   CONTRAST, recomputed for losing the horizontal mask (effective alpha at the
   left edge .20 x .88 -> .20 x 1.0). Worst case is the triple: the file's
   darkest 0.5th-percentile pixel (#0D003F), both surge peaks coinciding, and
   the container feather fully open. Backing bottoms out at #A492BD, where --ink
   reads 6.78:1. That is why .band--surge .sec-head p is pinned to --ink below:
   at --muted the same worst case is 4.09:1 and would fail. Everything else in
   this band is either --ink, a white-on-gradient header bar, or text on an
   opaque white card. */
/* ### R11 — THE TURING TEXTURE IS DELETED. ###
   Client: "I don't think I like the brain squiggly background on the services
   page... Honestly just remove it fully."

   .band-surge__tex and its <div> in index.html are both gone. Everything above
   this point — the R2b/R3/R4/R5 blocks documenting how that layer was measured,
   masked, scaled and contrast-checked — is now HISTORY ONLY. It is kept because
   it is the record of how this band's numbers were arrived at (the container
   feather and both surge layers still depend on the same height-stability rule
   and the same 2200px reference box), but no rule below renders that artwork.

   Madd_Ox_Pattern_Turing_Purple_02.webp is now referenced by NOTHING — same
   status the Bubbles files have had since R7. The file stays in
   assets/patterns/ because it is brand artwork; do not "repair" the unused
   asset by wiring it back into a band.

   WHAT SURVIVES: the two purple surge layers (__l1 / __l2). They are a colour
   wash, not the pattern the client objected to, and they are what keeps the
   Services band from being a flat white slab between two grey ones. The
   Sequencing "DNA ladders" in the hero survive too — the client named them as
   the one background he would keep.

   CONTRAST, recomputed with the texture gone. The worst case was the triple of
   the texture's darkest pixels plus both surge peaks, bottoming out at #A492BD.
   With only the two surge layers left, both peaks coinciding puts the backing at
   roughly #D0C2DF, where --muted reads 5.8:1 and --ink 11.9:1 — so the pin below
   is no longer load-bearing. It is kept anyway: --ink is what this band has
   shipped with since R5, the client has reviewed it twice, and nothing about
   removing a texture is a reason to change the weight of the copy on top of it.
   The rule may be deleted freely if the lead paragraph should ever match the
   other sections' --muted. */
.band--surge .sec-head p{color:var(--ink);}
/* Primary surge. Broad bands raking up-right. Multiplies over the Turing
   texture below it, so the two compound rather than simply stacking.

   R3 — THIS IS THE BUG THE CLIENT REPORTED. Was `background-size:210% 210%`
   with `background-position:0% 28%`. Both were height-derived:

     - 210% of the band HEIGHT rescaled the whole gradient every time a panel
       opened (1340px band -> 2814px tall gradient; 2090px band -> 4389px), so
       the stripes stretched and the band re-coloured on every toggle;
     - a PERCENTAGE background-position with an oversized image resolves to
       (boxHeight - imageHeight) * p, so the vertical offset moved as well —
       the same paint slid up and down the band as it grew.

   Both are now fixed px, resolved once at the reference geometry documented in
   the band header (1400 x 1340): 210% x 210% -> 2940 x 2810px, and 0% 28% ->
   (1400-2940)*0 = 0px, (1340-2810)*0.28 = -412px. Identical appearance at
   desktop, and now byte-identical whatever the panels do. Positions are
   measured from the TOP-LEFT, so growth can only ever reveal new gradient
   below what is already painted; nothing above the fold moves.

   The px width is the one visual trade documented for this round: the surge no
   longer rescales with the viewport, so it reads as broader, calmer stripes on
   a phone and tiles once past ~2940px of viewport width. background-repeat is
   the default `repeat` and both ends of the stripe period are alpha 0, so that
   tile boundary carries a ~2% alpha step — below a 5/255 RGB difference, and
   the alternative (keeping % on the width axis) would have been a partial fix
   that still looked like a percentage to the next person reading this.

   R4: stop alphas raised ~1.55x and the layer opacity .5 -> .75, so the surge
   reads as actual colour rather than a hint of one. The GEOMETRY is untouched —
   same angle, same fixed px size and position, same keyframes — so the
   height-stability guarantee above still holds exactly as written; only the
   alpha channel changed. */
/* ### R7 — THE GREY BAR DOWN THE RIGHT-HAND SIDE. THIS IS THE FIX. ###

   Client: "the grey bar/line on the right hand side is still showing up. get rid
   of that, make it look nice and flush."

   ROOT CAUSE, measured in the browser rather than reasoned about. Both layers
   were `background-repeat:repeat` (the CSS default) with an oversized fixed
   background-size, and __l2 additionally sat at background-position-x:-1820px.
   Its tile is 3220px wide, so:

       tile 1 spans x = -1820 -> 1400        tile 2 starts at x = 1400

   A repeating-linear-gradient does NOT continue across a tile boundary — the
   phase restarts. So at x=1400 the stripe pattern jumps, and everything right of
   1400 renders at an unrelated tone. On a 1440px window that is a 40px vertical
   strip hard against the right edge: the client's grey bar. It moves with the
   window width, which is why it appeared to come and go. Confirmed by planting a
   1px marker at x=1400 in the live page — it landed exactly on the strip's edge.

   __l1 had the same class of defect waiting: 2940px wide from x=0, so its own
   seam sits at x=2940 (invisible below a 2940px viewport, visible above it) and
   its vertical seam at y=2398 was reachable by a tall phone band.

   THE FIX — one copy, always covering, never repeating. Identical treatment to
   .band-surge__tex above, for the identical reason:

     - background-repeat:no-repeat. No tile boundary can exist, so no phase jump
       can exist, at any viewport width. This is the actual bug fix.
     - background-size width max(100%,2600px): at least the box width, so the one
       tile always spans the band edge to edge however wide the window is; the
       2600px floor keeps the stripe scale sane on narrow screens. Deriving from
       WIDTH is safe — the band's width does not change when a panel opens.
     - a FIXED 2200px-tall box pinned to the top, not inset:0, with a matching
       2200px background-size height. This is what preserves the R3
       height-stability rule while using a percentage anywhere: the box height is
       a hard-coded constant, so opening a panel cannot rescale or re-phase the
       gradient. Verified live — expanding all four panels grew the band from
       1320px to 2081px while this box stayed at exactly 2200px.
     - background-position:0 0. The old -1820px/-412px offsets were R3's
       conversion of percentage positions; with a single non-repeating tile they
       have no purpose left, and the -1820px one was the bug.
     - a bottom fade mask, so where the 2200px box ends the layer dissolves
       instead of cutting. Only reachable on a phone with all four panels open;
       everywhere else the container's own feather gets there first.

   The stripe scale changes slightly as a consequence — the gradient line is
   computed from the background-size box, which is now 2600x2200 rather than
   2940x2810, so the stripes read a little narrower. That is the one deliberate
   visual trade for an edge that is flush at every window width. */
.band-surge__l1{
  position:absolute;left:0;right:0;top:0;height:2200px;
  background-image:repeating-linear-gradient(118deg,
    rgba(143,59,212,0)   0%,
    rgba(143,59,212,.25) 7%,
    rgba(100,39,162,.31) 13%,
    rgba(92,34,121,.23)  19%,
    rgba(143,59,212,.28) 26%,
    rgba(143,59,212,0)   34%);
  background-size:max(100%,2600px) 2200px;
  background-position:0 0;
  background-repeat:no-repeat;
  mix-blend-mode:multiply;
  opacity:.75;
  -webkit-mask-image:linear-gradient(180deg,#000 0%,#000 74%,rgba(0,0,0,0) 100%);
  mask-image:linear-gradient(180deg,#000 0%,#000 74%,rgba(0,0,0,0) 100%);
  /* R6: animation:surge-drift-a 78s and will-change:background-position both
     deleted — the client asked for the Services background to be static, and
     with no animation the will-change hint only forces a needless layer. */
}
/* Secondary, finer surge crossing the other way. Carries the one trace of tan.
   Same R3 conversion: 230% x 230% -> 3220 x 3080px, and 100% 72% ->
   (1400-3220)*1 = -1820px, (1340-3080)*0.72 = -1253px.
   R4: same alpha-only raise as __l1 (.13 -> .21, tan trace .06 -> .10, layer
   .42 -> .60). The tan stays a TRACE deliberately — it is what stops the band
   reading as flatly monochrome, and raising it any further would start to look
   like the orange the client is removing from the Approach section this round. */
/* R7: same treatment as __l1, and this is the layer that actually carried the
   bug — its -1820px x-offset is what put a tile boundary at x=1400, i.e. inside
   every window narrower than 1400px of usable width plus the tile. Read the R7
   block on __l1 above before changing anything here. The width floor is 2800px
   rather than 2600px only to keep this layer's stripes coarser than __l1's, as
   they were before; the two must not land on the same period or they stop
   reading as two crossing rakes. */
.band-surge__l2{
  position:absolute;left:0;right:0;top:0;height:2200px;
  background-image:repeating-linear-gradient(-54deg,
    rgba(143,59,212,0)    0%,
    rgba(100,39,162,.21)  8%,
    rgba(203,163,109,.10) 15%,
    rgba(143,59,212,0)    23%);
  background-size:max(100%,2800px) 2200px;
  background-position:0 0;
  background-repeat:no-repeat;
  mix-blend-mode:multiply;
  opacity:.60;
  -webkit-mask-image:linear-gradient(180deg,#000 0%,#000 74%,rgba(0,0,0,0) 100%);
  mask-image:linear-gradient(180deg,#000 0%,#000 74%,rgba(0,0,0,0) 100%);
  /* R6: animation:surge-drift-b 104s and will-change:background-position both
     deleted — the Services background is static now. */
}
/* R6 deletes @keyframes surge-drift-a and surge-drift-b along with the CSS-only
   drift they drove across the Services band. The client's note was "there is a
   weird line that goes in and out on the background of the services page... just
   make it static", and the whole section is now static: nothing here, and
   nothing bordering it, animates. Nothing else referenced either keyframe.
   What that drift used to be, for the record, in case it is ever wanted back:
   background-position ONLY (no transform, no size change), long durations, small
   travel, `alternate` so it could never jump at a tile boundary — a ran
   0px -412px -> -400px -676px, b ran -1820px -1253px -> -1420px -940px, both
   converted in R3 from percentage endpoints to px at the reference geometry so
   the travel could not change length when an accordion panel opened mid-run.
   The `from` values are the background-position values still declared above. */

/* =========================================================================
   SECTION HEAD RULE — gradient accent under each section eyebrow.
   No text sits on it, so no contrast constraint applies here.

   R11 — THIS IS NO LONGER BAND-SCOPED, and the change is a consequence of the
   client's note on the Services headers rather than a separate decision. The
   rule used to be warm (#e8763a -> #e0559b) on the two WHITE bands and cool on
   the rest. With the orange gone from the accordion ramp, those two 72px bars —
   one directly above the now blue-and-purple Services headers, one in Contact —
   were the last orange on the site, and they would have read as a leftover.

   --grad-cool is also, as it happens, the exact progression he asked for:
   #37126d -> #8f3bd4 at 55% -> #5cb4d6, i.e. purple to light purple to light
   blue. Its light-blue end is fine HERE because this element carries no text;
   it is the same #5cb4d6 that the header comment at the top of this file warns
   must never be clipped into type.

   --grad-warm is now referenced by nothing. It is kept in :root as a brand
   token, not as something the page renders.
   ========================================================================= */
.sec-head{position:relative;}
/* R14 — .sec-rule and the .band--white/.band--paper pair that re-declared its
   gradient are DELETED. The rule is drawn by .sec-head .eyebrow::after now (see
   the R14 block near the top of this file); the second declaration was already
   redundant after R11 made every rule use --grad-cool regardless of band, so
   there was nothing to carry forward. */
/* HISTORY, so the sweep is not reinvented: R6 deleted a .sec-rule::after that
   swept a 40%-wide white bar along this rule every 6.5s. The client described "a
   weird line that goes in and out on the background" of Services and asked for
   it static. R14 has since taken over the ::after slot on the eyebrow to draw
   the rule itself — there is no longer a free pseudo-element here to animate,
   which is a happy accident but not the reason it is gone. */

/* =========================================================================
   ABOUT — headshot frame + credibility points with icons
   ========================================================================= */
.headshot{position:relative;}
/* No texture overlay over the real photo — it would muddy the face. */
.headshot img{display:block;width:100%;height:auto;position:relative;z-index:0;}
.headshot figcaption{position:relative;z-index:2;background:var(--white);}
.about-facts .f-k{display:flex;align-items:center;gap:8px;}
.about-facts .f-k img{width:17px;height:17px;flex:0 0 auto;opacity:.85;}

/* =========================================================================
   APPROACH / EXPERTISE / NUMBERS — real content pulled from the live site
   ========================================================================= */
/* ---- HOW I WORK ----
   R2: there are FIVE values, and a repeat(2,1fr) grid left the fifth stranded
   alone on its own row. A 6-column track fixes that without any extra markup:
   items 1-3 take 2 columns each (3 across), items 4-5 take 3 each (2 across).
   Both rows fill the width exactly, so the block reads as one even object.
   6 is the smallest track count divisible by both 3 and 2. */
.approach-grid{
  display:grid;grid-template-columns:repeat(6,1fr);gap:22px;
  list-style:none;margin:0;padding:0;
}
.approach-item:nth-child(-n+3){grid-column:span 2;}
.approach-item:nth-child(n+4){grid-column:span 3;}
/* ---- R4: ONE TONE PER VALUE, NOW PURPLE -> INDIGO -> BRAND BLUE ----
   "Make the colour scheme more blue-purple in the How I Work section instead of
   orange-purple." The orange card is gone entirely — it was the only warm thing
   in the section and --grad-warm on its bar was the loudest part of it — and
   the five-card progression now runs plum -> grape -> violet -> indigo ->
   #0D489E, landing on the brand's own secondary BLUE.

   #0D489E and #29C0E4 are not invented hues: both are on the brand kit's
   secondary/accent row (p.07) alongside #8F3BD4 / #5C2279 / #110040. The
   "do not introduce new hues" note at the top of this file still holds.

   The R3 structure is untouched — four variables per card, declared in one
   place, so a tone change stays a single-line edit — and so is the R3 rule that
   the BAR and the HEADING do not share stops: a bar carries no text and may end
   on the bright #8f3bd4 / #29C0E4, whereas every stop that lands on a glyph has
   to clear 4.5:1 against that card's own tint. Measured on white / on each
   card's own tint:
     3 #6a35c8   7.1:1 / 6.6:1
     4 #6a35c8 -> #3a5cc0   7.1:1 / 6.2:1  and  6.1:1 / 5.6:1
     5 #3a5cc0 -> #0D489E   6.1:1 / 5.2:1  and  8.2:1 / 7.0:1
   Cards 4 and 5 are still the tight pair and still set the tint ceiling, which
   is why those two washes sit at ~8% of the hue. #29C0E4 is 1.9:1 on white and
   appears ONLY on card 5's bar, never on text.

   The BAR chain is deliberately continuous end-to-end across the five cards —
   #37126d -> #5c2279 -> #6427a2 -> #8f3bd4 -> #0D489E -> #29C0E4 — so the top
   edges read as one ramp down the section, the same idea as the Services
   headers and the stat cards. */
/* ---- R2/R3 note, kept for history: "some orange, light purple, dark purple" ----
   R3 gives each card FOUR values instead of two, all declared in one place per
   card so a tone change is a single-line edit:

     --ap-bar    the 4px top edge (decorative, no text, may stay bright)
     --ap-a/--ap-b  the two stops of the heading's own gradient
     --ap-solid  the solid colour a browser without background-clip:text gets,
                 and what print forces the heading back to
     --ap-tint   the card's background wash

   The five tones keep their R2 order: plum -> grape -> violet -> light purple
   -> orange. The BAR and the HEADING deliberately do not use the same stops:
   a bar carries no text and can end on the bright #c94fc9 / #e8763a, whereas
   every stop that lands on a glyph has to clear 4.5:1 against that card's own
   tint. So the heading gradients are the contrast-clamped portion of the same
   progression. Measured against each card's own tint, both stops:
     1 plum   #37126d -> #5c2279   12.0:1 / 9.0:1
     2 grape  #5c2279 -> #6427a2    9.1:1 / 7.6:1
     3 violet #6427a2 -> #7f34c4    7.7:1 / 5.7:1
     4 light  #7f34c4 -> #8f3bd4    5.8:1 / 4.9:1
     5 orange #9a3f18 -> #b4501f    6.3:1 / 4.7:1
   R4 replaced cards 3-5 of that progression; see the block above for the
   values now in force and why. The reasoning it recorded is what R4 reused:
   --grad-warm's #e8763a is 2.95:1 on white and fails outright, which is why an
   orange that had to carry text was always the awkward one in this set. */
.approach-item{
  position:relative;overflow:hidden;   /* clips the top bar to the card radius */
  border:1px solid var(--rule);
  border-radius:var(--radius);padding:26px 24px;box-shadow:var(--shadow);
  /* Defaults = card 1, and the fallback for any 6th card added later. */
  --ap-bar:linear-gradient(90deg,#37126d 0%,#5c2279 100%);
  --ap-a:#37126d;--ap-b:#5c2279;--ap-solid:var(--plum);--ap-tint:#edeaf2;
  /* R3: the card surface is no longer flat white. A soft wash in the card's own
     hue, strongest at the top-left where the heading sits and resolving to
     white by 72% so the body copy sits on a near-white surface. Peak tint is
     only ~9% of the hue over white (8% for orange), which keeps --ink body text
     at 16.1:1 worst case — far past the 7:1 target. background-color stays
     white underneath as the fallback if the image is ever dropped. */
  background-color:var(--white);
  background-image:linear-gradient(155deg,var(--ap-tint) 0%,#ffffff 72%);
}
.approach-item:nth-child(2){
  --ap-bar:linear-gradient(90deg,#5c2279 0%,#6427a2 100%);
  --ap-a:#5c2279;--ap-b:#6427a2;--ap-solid:var(--grape);--ap-tint:#f0ebf3;
}
.approach-item:nth-child(3){
  --ap-bar:linear-gradient(90deg,#6427a2 0%,#8f3bd4 100%);
  --ap-a:#6427a2;--ap-b:#6a35c8;--ap-solid:var(--violet);--ap-tint:#f1ecf7;
}
.approach-item:nth-child(4){
  --ap-bar:linear-gradient(90deg,#8f3bd4 0%,#0d489e 100%);
  --ap-a:#6a35c8;--ap-b:#3a5cc0;--ap-solid:#3a5cc0;--ap-tint:#edeff9;
}
.approach-item:nth-child(5){
  --ap-bar:linear-gradient(90deg,#0d489e 0%,#29c0e4 100%);
  --ap-a:#3a5cc0;--ap-b:#0d489e;--ap-solid:#0d489e;--ap-tint:#e9f1fa;
}
/* Same accent-line language as .footer-top::after and .subhead::before — a
   flat gradient strip across the card's TOP edge. */
.approach-item::before{
  content:"";position:absolute;left:0;right:0;top:0;height:4px;
  background-image:var(--ap-bar);
}

/* Noticeably bigger than R2's flat 1.16rem, and responsive: 21.1px at the
   760px single-column collapse, up to 27.5px on a wide desktop. */
.approach-item h3{
  font-size:clamp(1.32rem,2.1vw,1.72rem);
  line-height:1.18;
  margin-bottom:.45em;
  color:var(--ap-solid);
}
/* The heading itself carries the gradient. Same @supports + solid-colour
   fallback pattern as .stat-v, and the print block forces it back to
   --ap-solid. Note both `color` declarations are on the SAME selector, so the
   later one simply wins — that is deliberate. Putting the fallback on a
   higher-specificity per-card selector would have beaten `color:transparent`
   here and painted the text solid ON TOP of its own gradient, hiding it.
   width:fit-content matters: background-clip:text paints only inside the
   glyphs, so a full-width block would sample just the left slice of the ramp
   and short words like "Adept" would look flat. */
@supports (background-clip:text) or (-webkit-background-clip:text){
  .approach-item h3{
    width:-webkit-fit-content;width:fit-content;max-width:100%;
    background-image:linear-gradient(90deg,var(--ap-a) 0%,var(--ap-b) 100%);
    -webkit-background-clip:text;
            background-clip:text;
    color:transparent;
  }
}
.approach-item p{margin:0;color:var(--ink);font-size:.97rem;line-height:1.62;}

@media (max-width:760px){
  .approach-grid{grid-template-columns:1fr;}
  /* The spans MUST be reset: `span 2` against a single-column template would
     generate implicit columns and blow the layout out past the viewport. */
  .approach-item:nth-child(-n+3),
  .approach-item:nth-child(n+4){grid-column:auto;}
}

/* =========================================================================
   .dd — EXPERTISE & TRACK RECORD DROPDOWNS.

   HISTORY, because this element has now been three things in three rounds:
     up to R12   a <dl class="xp-list"> — four hairline rows, each an uppercase
                 --grape label over a paragraph. Not interactive.
     R12         rebuilt as .acc, the Services accordion, verbatim. Client:
                 "convert the lists to some type of dropdown like the services
                 page."
     R12b        rebuilt again as this. Client: "make it a simpler, different but
                 similar type of dropdown. Lighter/cleaner, not thick and heavy
                 like the services one."

   ### WHY THIS IS A SEPARATE COMPONENT AND NOT AN .acc MODIFIER ###
   The obvious move was `.acc--light`. It is the wrong one, and the reason is
   worth recording so nobody re-merges them. Every bit of .acc's weight is
   structural, not decorative trim: an opaque full-bleed gradient <summary> at
   400% background-size, white text sized against it, a 1px card border, a 14px
   radius, a drop shadow on open, and a 2px positioned tone ring. "Lighter" means
   cancelling all seven. A modifier that overrides almost every declaration of
   its parent is not a modifier — it is a second component wearing the first
   one's class, and the next person to touch .acc has to reason about a variant
   that shares none of its behaviour. Worse, the two would be coupled: retuning
   the Services ramp, which the client has already asked for twice, would drag
   this section along for no reason.

   So .acc is Services and only Services, and .dd is this. They share nothing but
   the native <details>/<summary> element underneath.

   ### WHAT KEEPS THEM RECOGNISABLY RELATED ("similar") ###
     - the same native disclosure element and the same interaction;
     - a chevron on the right that flips through the same angle;
     - a four-step brand tone progression down the four items, in the same order
       the rest of the page uses;
     - the first one open on load, matching Services.

   ### WHAT MAKES IT LIGHTER ("simpler, cleaner") ###
     - NO filled bar. The title is --ink directly on the band.
     - NO card: no border box, no radius, no shadow. Rows are separated by a
       single hairline, which is the same device the <dl> it replaced used, so
       the section keeps the visual rhythm it had before it became interactive.
     - the tone appears in three small places — an 8px dot, and the title and
       chevron of the OPEN row — instead of flooding a header bar. That inverts
       .acc's relationship to colour: there, colour is the surface and the text
       sits on it; here, colour marks state and position.

   ### CONTRAST ###
   The tones carry TEXT on --paper (#f2f2f2) here, whereas .acc's ramp carries
   white text on saturated fills — the opposite constraint, so the ramps are not
   interchangeable. That is exactly why t4 is #0d489e and not .acc's #1772b7:
   #1772b7 was picked as the LIGHTEST blue that white text can sit on, and as
   dark text on --paper it lands at 4.54:1 — passing, but with no margin at all.
   #0d489e is the brand kit's own secondary blue (p.07), already used by the
   Approach and stat ramps, and measures 7.7:1. Measured, all four on --paper:
     --plum #37126d 12.7   --grape #5c2279 9.6
     --violet #6427a2 8.0  #0d489e 7.7
   Everything clears AA with room. The default --dd-tone is --violet so a fifth
   item added later inherits a passing colour rather than none.
   ========================================================================= */
.dd{
  --dd-tone:var(--violet);
  border-bottom:1px solid var(--rule);
}
/* first-of-type, not a rule on the container: under .reveal-group the four rows
   fade in one after another, and a border on the wrapper would paint a bare
   hairline across empty space before any of them arrived. On the row, the line
   arrives with the content it belongs to. */
.dd:first-of-type{border-top:1px solid var(--rule);}
.dd--t1{--dd-tone:var(--plum);}
.dd--t2{--dd-tone:var(--grape);}
.dd--t3{--dd-tone:var(--violet);}
.dd--t4{--dd-tone:#0d489e;}   /* brand secondary blue — see the contrast note */

.dd > summary{
  display:flex;align-items:center;gap:14px;
  padding:17px 2px;
  cursor:pointer;list-style:none;
}
.dd > summary::-webkit-details-marker{display:none;}
/* offset:-3px keeps the ring inside the row rather than straddling the hairline
   into the neighbouring one. */
.dd > summary:focus-visible{outline:3px solid var(--purple);outline-offset:-3px;border-radius:8px;}

/* ### R14 — .dd__dot IS REPLACED BY .dd__icon. ###
   Client: "instead of doing the bullet here, do like a little icon thing just to
   like separate them", then named one brand icon per category. The 8px tone dot
   is deleted; these are 22px brand SVGs.

   THE TONE STILL HAS TO READ. The dot was the main place --dd-tone appeared on a
   closed row, and these SVGs are a flat #110040 that cannot be recoloured
   without inlining them. So the tone moves to a soft tinted disc BEHIND the
   icon, mixed from --dd-tone itself. That keeps the four-step progression down
   the list and keeps the open row distinct, while the mark stays exactly the
   artwork from the brand kit.

   Sized 22px against .acc__icon's 26px — same rule as the chevron, the lighter
   component stays visibly lighter. */
/* ### R14d — NO DISC. THE ICON IS THE ICON. ###
   R14 put each of these brand SVGs inside a tinted circle (`border-radius:50%`
   plus a color-mix background). The client, twice: "the icons are just a bit too
   cropped in... I think it's the borders around the circle you have."

   R14c tried to fix that by making the circle bigger relative to the artwork, on
   the theory that `border-radius:50%` clips an <img>'s content and the corners of
   a square icon were being shaved. That reasoning is sound and the geometry did
   improve, but it did not fix what he was seeing. THE DISC IS GONE INSTEAD, which
   removes every mechanism by which the artwork can be cut: no radius, no clip, no
   padding box, nothing painted behind the mark.

   DO NOT PUT IT BACK. A round mask around square line-art was always going to
   fight the artwork — several of these icons (Dropper especially, drawn
   corner-to-corner on the diagonal) reach into their own corners by design. If a
   future round wants a shape behind the icon, it needs a WRAPPER element to carry
   it so the clip never touches the <img>, not a background on the image itself.

   WHERE THE TONE WENT. The disc was one of three places --dd-tone appeared. The
   other two are untouched and still carry the four-step progression down the
   list: the title colour and the chevron colour both switch to the row's tone on
   hover and when open. Nothing was lost that a reader can see.

   26px matches the closed-row weight the dot used to have without the icon
   looking undersized now that it stands alone. */
.dd__icon{
  flex:0 0 auto;width:26px;height:26px;
  transition:transform .22s cubic-bezier(.22,.61,.36,1);
}
.dd[open] .dd__icon{transform:scale(1.08);}

.dd__title{
  flex:1 1 auto;margin:0;
  font-family:var(--font-head);font-size:1.06rem;font-weight:600;
  line-height:1.35;color:var(--ink);
  transition:color .18s ease;
}
.dd > summary:hover .dd__title,
.dd[open] .dd__title{color:var(--dd-tone);}

/* Thinner strokes and a smaller box than .acc__chev (1.8px/9px against
   2.2px/13px) — the chevron is the one piece of furniture both components share,
   so it is where the weight difference has to read most clearly. */
.dd__chev{
  flex:0 0 auto;width:9px;height:9px;
  border-right:1.8px solid var(--muted);
  border-bottom:1.8px solid var(--muted);
  transform:rotate(45deg);margin-top:-4px;
  transition:transform .24s cubic-bezier(.22,.61,.36,1),margin-top .24s ease,border-color .18s ease;
}
.dd[open] .dd__chev{transform:rotate(-135deg);margin-top:3px;}
.dd > summary:hover .dd__chev,
.dd[open] .dd__chev{border-color:var(--dd-tone);}

/* The left padding aligns the panel's first character under the first character
   of its own title, so it is the marker's total width plus the summary's 14px
   gap. R14d: the icon is a bare 26px with no padding box, so 26 + 14 = 40px.
   Derived, not eyeballed — if the icon size above changes, this changes with it. */
.dd__panel{padding:0 0 20px 40px;}
/* Reuses @keyframes panel-in, the same easing .acc[open] .acc__panel uses. This
   is the one place the two components deliberately match: <details> cannot
   transition its own height, so both fade their content in instead, and the two
   sections should not open at visibly different speeds. */
.dd[open] .dd__panel{animation:panel-in .3s cubic-bezier(.22,.61,.36,1) both;}
/* ### R14 — COLUMNS NOW, AND THE OLD REASONING NO LONGER APPLIES. ###
   The note that stood here said "NOT columns... these are single running
   sentences, and a column break would throw the reader back to the top of the
   band mid-thought." That was correct about a <p>. The client asked for these to
   stop being paragraphs — "these are also like lists... maybe we can make this
   bulleted... I think you could probably do like a two column bulleted list" —
   so each panel is a .dd__list of self-contained items, and the objection to
   breaking a sentence across a column boundary is gone with the sentence.

   break-inside:avoid is what keeps the longest item (Dosage Forms' solid-oral
   line) from being sliced across the gutter.

   .dd__panel p is KEPT, not deleted: no panel uses it today, but it is three
   lines and it is the fallback if any single entry ever goes back to prose. */
.dd__list{
  list-style:none;margin:0;padding:0;
  columns:2;column-gap:44px;
}
.dd__list > li{
  break-inside:avoid;
  position:relative;
  padding:6px 0 6px 20px;
  font-size:.96rem;line-height:1.55;color:var(--ink);
}
/* A small square rather than .acc__list's round dot — the two components are
   meant to stay tellable apart, and the row marker above is already carrying the
   tone, so this one stays a neutral --purple at low opacity. */
.dd__list > li::before{
  content:"";position:absolute;left:2px;top:.72em;
  width:5px;height:5px;border-radius:1px;
  background:var(--purple);opacity:.7;
}
.dd__panel p{
  margin:0;
  font-size:.96rem;line-height:1.62;color:var(--ink);
  max-width:88ch;
}

/* R12: the .xp-list rules are DELETED, not merely unused. That <dl> is the
   markup .dd above replaced, so every one of its selectors could only ever match
   nothing now. Nothing else in the project referenced .xp-list. */

.stat-grid{
  list-style:none;margin:0;padding:0;
  display:grid;grid-template-columns:repeat(4,1fr);gap:20px;
}
.stat-grid li{
  position:relative;overflow:hidden;   /* clips the top edge to the card radius */
  border:1px solid var(--rule);
  border-radius:var(--radius);padding:22px 20px;
  /* R4: white stays as the background-COLOR fallback; the wash on top of it is
     what makes each column's hue visible across the card and not only on its
     5px edge. Same 155deg/72% geometry as .approach-item, so the tint is
     strongest at the top-left where the numeral sits and has resolved to white
     by the time it reaches the label. */
  background-color:var(--white);
  background-image:linear-gradient(155deg,var(--s-tint) 0%,#ffffff 72%);
}
/* ---- ONE MASTER RAMP, SAMPLED BY COLUMN POSITION ----
   R3: the client asked for this section unified under a single simple gradient.
   R2's eight-card chain (plum -> purples -> orchid -> pink -> orange -> tan)
   was ordered but it was seven hues, and it ran in DOM order, so the colours
   had no relationship to the divider above them. Replaced by one three-stop
   ramp, dark purple -> light purple, shared by the divider AND the cards:

       0%  --plum #37126d    50%  --violet #6427a2    100%  --purple #8f3bd4

   .subhead::before IS that ramp at full width (and R2's --grad-warm accent cap,
   .subhead::after, is deleted — the client asked for simple). Each card then
   samples the SAME ramp across ITS COLUMN's share of the width, so a card's top
   edge is the colour the divider above it has at that horizontal position, and
   the eight edges line up vertically with it. At 4 columns that is the quarter
   points, interpolated in sRGB between the three anchors:

       0% #37126d   25% #4e1d88   50% #6427a2   75% #7a31bb   100% #8f3bd4

   Cards 1+5 share column 1, 2+6 column 2, 3+7 column 3, 4+8 column 4. The
   stops live in custom properties on the <li> so the numerals inside can reuse
   the exact same gradient, and so the two responsive breakpoints only have to
   restate two values instead of sixteen. */
/* ---- R4: A MUCH WIDER RAMP, AND IT NOW COLOURS THE WHOLE CARD ----
   "Make the gradient and amount of colour change more prominent in the By the
   numbers section." R3's ramp ran #37126d -> #8f3bd4, dark purple to light
   purple; sampled across four columns that is a ~28-value step between
   neighbouring cards on a 3px line, which is why the section read as eight
   identical white boxes.

   Three changes, together:
     1. THE RAMP IS LONGER. It now runs plum -> violet -> purple -> brand blue
        -> brand cyan, i.e. it crosses hue as well as lightness, so adjacent
        cards are obviously different colours rather than shades. #0D489E and
        #29C0E4 are the brand kit's own secondary colours (p.07) and are the
        same pair the Approach cards now end on, so the two sections read as
        one continuous idea down the page.
     2. THE EDGE IS THICKER. 3px -> 5px, matching .subhead::before above it.
     3. THE COLOUR IS ON THE CARD, NOT JUST ITS EDGE. Each card gets the same
        corner wash the Approach cards use, in its own column's hue.

   BAR STOPS vs NUMERAL STOPS — the same split as .approach-item, for the same
   reason. --sb-a/--sb-b paint the 5px edge, which carries no text and may end
   on #29C0E4; --s-a/--s-b paint the gradient-clipped numerals and are clamped
   to end at #0D489E, because #29C0E4 is 1.9:1 on white and would make the last
   number unreadable. Every numeral stop clears 4.5:1 against its own tint:
   #37126d 14.3, #592295 11.4, #7f34c2 7.6, #4143b4 7.8, #0d489e 8.2 on white,
   and the washes are ~7% of the hue so none of those drops below 6.8:1.
   Column stops are the master ramp interpolated at the quarter points; they
   are not eyeballed, and the two breakpoints below re-sample the same ramp. */
.stat-grid li{--s-a:#37126d;--s-b:#0d489e;--sb-a:#37126d;--sb-b:#29c0e4;--s-tint:#eeecf4;}
.stat-grid li:nth-child(4n+1){--s-a:#37126d;--s-b:#592295;--sb-a:#37126d;--sb-b:#592295;--s-tint:#eeecf3;}
.stat-grid li:nth-child(4n+2){--s-a:#592295;--s-b:#7f34c2;--sb-a:#592295;--sb-b:#7f34c2;--s-tint:#f0ecf7;}
.stat-grid li:nth-child(4n+3){--s-a:#7f34c2;--s-b:#4143b4;--sb-a:#7f34c2;--sb-b:#4143b4;--s-tint:#eeedf8;}
.stat-grid li:nth-child(4n+4){--s-a:#4143b4;--s-b:#0d489e;--sb-a:#4143b4;--sb-b:#29c0e4;--s-tint:#e9f2f8;}
.stat-grid li::before{
  content:"";position:absolute;left:0;right:0;top:0;height:5px;
  background-image:linear-gradient(90deg,var(--sb-a) 0%,var(--sb-b) 100%);
}
/* Solid --grape FIRST: that is what any engine without background-clip:text
   renders, and it is 10.7:1 on the white stat card. */
.stat-v{
  display:block;font-family:var(--font-head);font-size:2rem;font-weight:700;
  color:var(--grape);line-height:1;margin-bottom:8px;letter-spacing:-.02em;
}
/* Gradient numerals — now THE SAME GRADIENT AS THE BAR ABOVE THIS CARD, same
   direction, rather than the generic --grad-cool-text, so the number visibly
   matches its own card edge and the divider above the section. --s-a/--s-b
   inherit from the <li>.
   No clamping was needed: every stop on this ramp clears 4.5:1 on white —
   #37126d 14.3:1, #4e1d88 11.3:1, #6427a2 8.9:1, #7a31bb 7.0:1, #8f3bd4 5.6:1.
   The ramp deliberately stops at --purple; anything lighter would have forced
   the numerals to use a darker sub-range than the bars.
   width:fit-content because background-clip:text paints only inside the glyphs
   — on a full-width block a two-character number would sample a sliver of the
   ramp and read as flat. */
@supports (background-clip:text) or (-webkit-background-clip:text){
  .stat-v{
    width:-webkit-fit-content;width:fit-content;max-width:100%;
    background-image:linear-gradient(90deg,var(--s-a) 0%,var(--s-b) 100%);
    -webkit-background-clip:text;
            background-clip:text;
    color:transparent;
  }
}
.stat-k{display:block;font-size:.82rem;color:var(--muted);line-height:1.42;}
/* The ramp has to follow the COLUMNS, not the DOM order, so both breakpoints
   re-sample it. `:nth-child(n)` on the 1-column rule is not decoration — it
   lifts that selector to the same specificity as the 4n+ rules above so the
   later source position decides, instead of losing to them. */
@media (max-width:900px){
  .stat-grid{grid-template-columns:repeat(2,1fr);}
  /* 2 columns -> sample the R4 ramp in halves, bars and numerals separately. */
  .stat-grid li:nth-child(2n+1){--s-a:#37126d;--s-b:#7f34c2;--sb-a:#37126d;--sb-b:#7f34c2;--s-tint:#eeecf3;}
  .stat-grid li:nth-child(2n+2){--s-a:#7f34c2;--s-b:#0d489e;--sb-a:#7f34c2;--sb-b:#29c0e4;--s-tint:#ecf0f8;}
}
@media (max-width:480px){
  .stat-grid{grid-template-columns:1fr;}
  /* 1 column -> every card carries the whole ramp. */
  .stat-grid li:nth-child(n){--s-a:#37126d;--s-b:#0d489e;--sb-a:#37126d;--sb-b:#29c0e4;--s-tint:#eeeef5;}
}

/* Sub-head that separates the two halves of the combined Expertise + Track
   Record band. Its top border is a gradient accent strip. */
.subhead{
  position:relative;
  font-family:var(--font-head);font-weight:600;
  font-size:clamp(1.2rem,2.4vw,1.5rem);color:var(--ink);letter-spacing:-.01em;
  margin:clamp(42px,5.5vw,64px) 0 24px;
  padding-top:clamp(30px,4vw,40px);
}
/* R3: the master ramp for this whole section, and the ONLY generated content
   .subhead has. Three stops, dark purple -> light purple, left to right; the
   eight stat cards below sample this exact ramp at their column positions (see
   .stat-grid li). R2's --grad-warm accent cap (.subhead::after) is DELETED —
   the client asked for simple, and a warm cap over a cool bar was a second
   idea competing with the first. --grad-cool is deliberately not reused here:
   it ends on a light blue #5cb4d6 that is outside this section's purple story
   and could not be shared with the numerals. */
/* R4: this is the master ramp the eight cards sample, so it is the one place
   the widened purple -> brand blue -> brand cyan progression is declared at
   full width. 4px -> 5px to match the card edges below it. */
.subhead::before{
  content:"";position:absolute;left:0;right:0;top:0;height:5px;
  background-image:linear-gradient(90deg,#37126d 0%,#6427a2 33%,#8f3bd4 60%,#0d489e 85%,#29c0e4 100%);
  border-radius:999px;
}

/* =========================================================================
   CONTACT — methods column + Netlify-backed form
   ========================================================================= */
.contact-list .c-k{display:flex;align-items:center;gap:9px;}
.contact-list .c-k img{width:18px;height:18px;flex:0 0 auto;opacity:.8;}
.contact-list li{transition:transform .22s cubic-bezier(.22,.61,.36,1);}
.contact-list li:hover{transform:translateX(4px);}

/* R2: FULL CONTAINER WIDTH. justify-self:end, max-width:520px and the
   desktop-only upward lift are all gone, along with the #contact .sec-head
   bottom margin that existed only to clear that lift. Padding is a little more
   generous than before so a 1072px-wide card still reads as a deliberate panel
   rather than a stretched one; the pieces INSIDE it (intro line, field row) are
   what actually stop it looking empty — see below. */
.form-card{
  position:relative;
  width:100%;
  background:var(--paper);
  border:1px solid var(--rule);
  border-radius:var(--radius);
  padding:clamp(24px,3.2vw,40px) clamp(24px,3.6vw,44px);
  overflow:hidden;
}
.form-card > *{position:relative;z-index:1;}
.form-card h3{margin-bottom:.4em;}
/* Capped at a comfortable measure — at full container width this single line
   would otherwise run the whole 1000px and look like stray text. */
.form-card__intro{font-size:.95rem;color:var(--muted);margin-bottom:26px;max-width:62ch;}
/* Netlify honeypot. The [hidden] attribute already does this; the rule is here
   so the field can never be revealed by a stray layout change. */
.hp{display:none !important;}

.field{margin-bottom:16px;}
.field label{
  display:block;
  font-family:var(--font-head);font-size:.72rem;font-weight:700;letter-spacing:.12em;
  text-transform:uppercase;color:var(--grape);margin-bottom:7px;
}
/* R11b: "(optional)" on the one field that is not required. Marking the
   exception is the right way round — labelling the other three "required" would
   put three pieces of noise on the card to communicate the same one fact. Lower
   weight and --muted so it reads as an aside on the label, not a second label.
   --muted on --paper is 6.8:1. */
.field__opt{
  font-weight:500;letter-spacing:.06em;text-transform:none;
  color:var(--muted);font-size:.92em;
}
.field input,.field textarea{
  width:100%;
  /* >=16px keeps iOS from auto-zooming the viewport on focus. */
  font-family:var(--font-body);font-size:max(16px,1rem);color:var(--ink);
  background:var(--white);
  border:1.5px solid var(--rule-strong);
  border-radius:10px;
  padding:12px 14px;
  transition:border-color .18s ease,box-shadow .18s ease,transform .18s ease;
}
.field textarea{min-height:130px;resize:vertical;line-height:1.55;}
.field input::placeholder,.field textarea::placeholder{color:rgba(17,0,64,.42);}
.field input:hover,.field textarea:hover{border-color:rgba(143,59,212,.45);}
.field input:focus,.field textarea:focus{
  outline:none;
  border-color:var(--purple);
  box-shadow:0 0 0 3px rgba(143,59,212,.18);
}
/* Focus-visible still paints the standard ring for keyboard users. */
.field input:focus-visible,.field textarea:focus-visible{
  outline:3px solid var(--purple);outline-offset:2px;
}
/* Name / Email / Company three-up. At full card width a two-up row plus a
   lone full-width Company field left an obvious dead half-row; folding Company
   onto the row is what makes the wider card read as balanced. */
.field-row{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px;}
.form-actions{display:flex;flex-wrap:wrap;gap:14px;align-items:center;margin-top:6px;}
.form-status{
  font-size:.88rem;color:var(--grape);font-weight:600;
  min-height:1.4em;
}
/* An error here has to be distinguishable from "Sending…" without relying on
   colour alone — the text itself says what went wrong and offers the email
   address, so this is reinforcement, not the only signal. --plum rather than a
   red: this site has no red in its palette (see the --flag note in :root) and
   introducing one for a transient form state is not worth reopening. */
.form-status--error{color:var(--plum);}

/* ---- INLINE CONFIRMATION ----
   R9. Replaces the form in place after a successful submission, so the visitor
   is never navigated to a separate page. Same card treatment as .form-card so
   the section does not visibly change shape when the swap happens — it reads as
   the card's contents changing, not as a new component appearing.
   The min-height is the one number worth explaining: without it the page would
   jump upward by the difference between the form's height and this panel's when
   the swap happens, which is the same class of layout-jump complaint the
   accordion already had. 380px is a little under the form's rendered height at
   desktop, so the panel never grows the card and the collapse is gentle. */
.form-done{
  background:var(--white);
  border:1px solid rgba(143,59,212,.42);
  border-radius:var(--radius);
  box-shadow:var(--shadow);
  padding:clamp(30px,4vw,48px);
  min-height:380px;
  display:flex;flex-direction:column;justify-content:center;
  position:relative;z-index:1;
}
.form-done:focus{outline:none;}          /* focus is moved here programmatically;
                                            the panel is not an interactive control,
                                            so a focus ring on it would be noise */
.form-done:focus-visible{outline:3px solid var(--purple);outline-offset:3px;}
.form-done__mark{
  font-family:var(--font-head);
  font-size:2.4rem;line-height:1;margin:0 0 .35em;
  color:var(--purple);
}
.form-done h3{font-size:1.5rem;margin-bottom:.5em;}
.form-done p{margin-bottom:.9em;max-width:56ch;}
.form-done p:last-child{margin-bottom:0;}
.form-done__alt{font-size:.94rem;color:var(--muted);}
@media (max-width:600px){
  .form-done{min-height:0;}             /* the phone form is much taller than the
                                            panel; pinning a desktop height here
                                            would just add dead space */
}

/* =========================================================================
   RESPONSIVE — verified at 1280 / 768 / 375, no horizontal scroll
   ========================================================================= */
@media (max-width:1040px){
  .cat-grid{grid-template-columns:repeat(2,1fr);}
}

/* The desktop-only @media(min-width:881px) block is GONE. It held exactly two
   rules — the -64px .form-card lift and the compensating #contact .sec-head
   bottom margin that existed only to clear it — and both were made obsolete by
   the R2 move to a single-column, full-width contact section. .sec-head now
   uses the same margin here as in every other section. */

/* ---- Tablet / large phone: the hero is a single copy column and the about
        grid stacks. .contact-grid is already single-column at every width. ---- */
@media (max-width:880px){
  .nav-desktop{display:none;}
  .nav-toggle{display:inline-flex;}
  .hero__col{max-width:none;}
  .about-grid{grid-template-columns:1fr;}
  .headshot{max-width:300px;}
  .acc__list{columns:1;}
  /* R14 — .dd__list collapses at the same width and for the same reason: two
     columns of ~45 characters is below a readable measure once the container
     stops growing. */
  .dd__list{columns:1;}
  /* Two-up name/email; Company takes the whole second line rather than sitting
     orphaned in a half-width track. */
  .field-row{grid-template-columns:repeat(2,minmax(0,1fr));}
  .field-row .field:nth-child(3){grid-column:1 / -1;}
}

/* ---- Phones: retuned type scale, spacing rhythm, full-width CTAs, and touch
        targets. min-width:0 guards on the grids keep any content from forcing
        the layout wider than the viewport (kills the 375/390px overflow). ---- */
@media (max-width:600px){
  body{font-size:16px;}
  :root{--gutter:18px;}
  /* R11 reins the enlarged banner back in for phones. At 375px the gutters leave
     339px of usable width, and a 58px-tall lockup is 174px of it; with the Menu
     button at ~104px and a 16px flex gap that is 294px, which is fine at 375 but
     runs into the gutter at 320. 48px tall = 144px wide gives ~50px of slack at
     320px. The bar comes down with it — 90px of sticky header is a lot of a
     small viewport to spend, and the phone logo no longer needs that height. */
  .header-inner{min-height:74px;}
  .site-header.is-scrolled .header-inner{min-height:62px;}
  .brand img{height:48px;}
  .site-header.is-scrolled .brand img{height:40px;}
  .hero{padding:clamp(34px,10vw,58px) 0 40px;}
  .hero h1{font-size:clamp(1.55rem,6.9vw,2.05rem);}
  .hero__sub{font-size:1.02rem;margin-bottom:1.5em;}
  .hero__actions{gap:12px;}
  .hero__actions .btn{flex:1 1 100%;justify-content:center;}
  .cat-strip{margin-top:34px;padding-top:28px;}
  .cat-grid{grid-template-columns:1fr;}
  .about-facts{grid-template-columns:1fr;gap:18px;}
  .footer-top{grid-template-columns:1fr;}
  .acc > summary{gap:12px;padding:18px 16px;min-height:48px;}
  .acc__num{width:26px;font-size:.7rem;}
  .acc__icon{width:24px;height:24px;}
  .acc__title{font-size:1.06rem;}
  .acc__panel{padding-left:16px;padding-right:16px;}
  .acc-tools__btn{min-height:44px;padding:11px 18px;}
  /* .dd needs far less phone tuning than .acc — there is no bar to reflow and no
     icon or numeral to shrink. min-height is the one thing worth stating rather
     than inferring: at 15px padding the row already measures ~53px, but the touch
     target should not depend on the title happening to be one line. */
  .dd > summary{padding:15px 2px;min-height:48px;}
  .dd__title{font-size:1rem;}
  /* R14d — no disc here either, so this is just a size step: 22px bare, and the
     panel indent tracks it at 22 + 14 = 36. */
  .dd__icon{width:22px;height:22px;}
  .dd__panel{padding-bottom:18px;padding-left:36px;}
  /* The 3-up methods strip stacks. Each li is already a column flex box. */
  .contact-list{grid-template-columns:1fr;gap:18px;}
  .contact-list li{gap:4px;}
  .field-row{grid-template-columns:1fr;gap:0;}
  .field-row .field:nth-child(3){grid-column:auto;}
  .form-actions .btn{flex:1 1 100%;justify-content:center;}
}

/* =========================================================================
   REDUCED MOTION — the hard stop.
   Everything above that moves is switched off here by the universal
   animation/transition kill: the ambient pattern drift, the hero foil breathe,
   the accordion panel animation, the reveal transitions, and the JS parallax
   (the script reads the same media query and never binds a scroll listener when
   it matches).
   R3 added no new animation, and R7 removed the last of the bubble layers
   along with @keyframes float-up, so there is nothing extra to switch off; the
   gradient work in those rounds (accordion ramp, stat ramp, approach headings
   and washes, the longer hero bleed) is all static paint.
   R6 rewrote the list above and deleted the line
   `.divider__sweep,.sec-rule::after{display:none;}` from the block below. Both
   of those sweeps are gone from the stylesheet entirely now — .sec-rule::after
   was deleted outright, .divider__sweep lost all of its rules — so there is
   nothing left for a display:none to hide. R6 also deleted the Services surge
   drift (.band-surge__l1 / __l2), which this comment used to name as one of the
   things the kill covered; those layers no longer animate for anyone, reduced
   motion or not. Nothing still referenced below has been deleted.

   Just as important: every .reveal / .reveal-group element is forced back to
   full opacity and zero offset, so nothing is left invisible when its
   transition is removed. Reduced motion must never mean reduced content.
   ========================================================================= */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation:none !important;
    transition:none !important;
    scroll-behavior:auto !important;
  }
  html{scroll-behavior:auto !important;}
  .js-reveal .reveal,
  .js-reveal .reveal-group > *,
  .js-reveal .reveal.is-in,
  .js-reveal .reveal-group.is-in > *{
    opacity:1 !important;
    transform:none !important;
  }
}
@media (prefers-reduced-motion:no-preference){
  html{scroll-behavior:smooth;}
}
/* R11: 96px -> 116px, tracking the taller banner. The sticky bar is 90px plus a
   1px border at rest, so an anchored section needs at least 91px of scroll
   padding to clear it and a little more to not look wedged underneath. */
html{scroll-padding-top:116px;}

/* ---------- Print: the full services list must survive Ctrl-P ---------- */
@media print{
  /* Every decorative layer that is still emitted. R7 removed .band-pair__tex,
     .seam-bubbles, .bubbles and .form-card::before from this list because all
     four rules were deleted with the bubbles — the selectors could only ever
     match nothing. R6 removed .band__tex for the same reason, and .subhead::after
     went with R2's warm accent cap. */
  /* R11 drops .cat-strip::before from this list — the rule is deleted from the
     stylesheet, so the selector could only ever match nothing. .band-surge stays:
     the Turing texture went with R11 but the two surge layers are still painted
     inside that container (it hangs off Contact rather than Services as of R12,
     which changes nothing here — the selector is on the container, not the band).
     R12 ADDED TWO and R12b REMOVED THREE, so read the list rather than the
     history. Still here: .band--sep::before, the separator bar at the top of
     Services — pure decoration, and in monochrome it would render as a solid
     black rule across the page. Its inset shade is killed by the
     `.band{box-shadow:none !important}` rule further down.
     GONE from this list: .band-dna (R12's Sequencing layer, deleted with the
     client's "get rid of the dna in the services background") and .hero::after
     (deleted once Services went grey and there was no colour step to dissolve).
     Both selectors could now only ever match nothing. */
  .site-header,.hero__bg,.band-surge,.band--sep::before,
  /* R14b: .divider dropped from this list with the class itself. */
  .acc-tools,.nav-mobile,.skip-link,.sec-head .eyebrow::after,
  .subhead::before,.footer-top::after,
  .approach-item::before,.stat-grid li::before,
  .acc[open]::after{display:none !important;}
  /* The wrapper around #approach + #expertise owns their background colour now,
     so it needs the same white-out as .band (below) or the pair would print a
     full-bleed --paper rectangle. */
  .band-pair{background:#fff !important;}
  /* Reveal elements must print even if they never entered the viewport. */
  .js-reveal .reveal,.js-reveal .reveal-group > *{
    opacity:1 !important;transform:none !important;
  }
  *,*::before,*::after{animation:none !important;transition:none !important;}
  /* Clipped gradient text prints as nothing on most drivers — force the numbers
     back to a solid colour. R3: the approach headings are gradient-clipped too
     now, and get the same treatment, each falling back to its own --ap-solid
     rather than one shared colour. */
  .stat-v{
    background:none !important;color:var(--grape) !important;
    -webkit-text-fill-color:currentColor !important;
  }
  .approach-item h3{
    background:none !important;color:var(--ap-solid) !important;
    -webkit-text-fill-color:currentColor !important;
  }
  /* R3: the five approach cards carry a colour wash on screen. Print them on
     plain white — the top edge is already hidden above. */
  .approach-item{background:#fff !important;border:1px solid #999;box-shadow:none !important;}
  /* R4 gave the stat cards the same on-screen wash; print them plain too. */
  .stat-grid li{background:#fff !important;border:1px solid #999;}
  .form-card{border:1px solid #999;background:#fff;}
  body{background:#fff;color:#000;font-size:11pt;}
  /* R12 adds box-shadow to this rule. `background` is a shorthand and does not
     reset box-shadow, so .band--sep's inset separator shade would otherwise
     print as a grey smear across the top of the Services section — the ::before
     bar above is hidden, but the shade under it is a different property. */
  .band{padding:18px 0;background:#fff !important;box-shadow:none !important;}
  /* #services is an ID (1,0,0) and would beat the .band rule above (0,1,0), so
     the screen padding-top has to be undone explicitly or Services alone prints
     with 80px of dead space above its heading. Same specificity, later source. */
  #services{padding-top:18px;}
  /* [open] is needed on the box-shadow too: .acc[open] is more specific than
     .acc, so a bare .acc rule would lose to it and print the drop shadow. */
  /* !important on the border too: .acc--cN[open] sets border-color and is more
     specific, so an open card would otherwise print a coloured edge. */
  .acc{border:1px solid #999 !important;page-break-inside:avoid;}
  .acc,.acc[open]{box-shadow:none !important;}
  .acc > summary{box-shadow:none !important;background:#fff !important;}
  /* R2 CRITICAL: the accordion headings are white on a saturated gradient on
     screen. The gradient is stripped above, so without this the four category
     titles would print white on white — i.e. the section would lose its
     headings entirely. */
  .acc__title,.acc__num{color:var(--ink) !important;}
  .acc__icon{filter:none !important;}          /* back to the ink artwork */
  /* Both chevrons are decorative; .acc's is additionally white-on-white once the
     gradient above is stripped. */
  .acc__chev,.dd__chev{display:none !important;}
  /* details[open] is forced in JS-free print too. BOTH accordions need this —
     a collapsed panel prints as a heading with its content missing, and the whole
     point of this block is that the full lists survive Ctrl-P. */
  .acc__panel,.dd__panel{display:block !important;}
  /* The .dd hairlines are rgba and print nearly invisible; #ccc keeps the four
     rows readable as separate entries on paper. */
  .dd,.dd:first-of-type{border-color:#ccc !important;}
  .acc__list{columns:2;}
  /* R14 — .dd__list is a real list now, so it gets the same two-column restore
     .acc__list has: the phone breakpoint collapsed both to one column, and a
     printed page is the desktop width, not a phone. Without this, the four
     Expertise panels print as one very long ragged column. */
  .dd__list{columns:2;}
  /* R14d: the print override that killed the dropdown icons' tinted disc is gone
     with the disc itself. The icons are bare dark line-art and print as-is. */
  .site-footer{background:#fff;color:#000;border-top:1px solid #999;}
  .site-footer a,.footer-legal a{color:#000;}
  a[href^="http"]::after{content:" (" attr(href) ")";font-size:9pt;}
}
