/* ================================================================
   FONTS
   Self-hosted rather than loaded from Google. Two reasons: served from
   our own origin they arrive before first paint, so text never renders in
   a fallback and then reflows mid-animation, which is what made the first
   page load stutter. And it removes a third party that would otherwise
   receive every visitor's IP address, which the privacy policy had to
   disclose.

   Chakra Petch ships as static weights (600 for headings, 700 for the hero);
   IBM Plex Sans and JetBrains Mono are variable, so one file each covers the
   whole range they are used at. All three are SIL Open Font Licence, which
   permits self-hosting.
   ================================================================ */
@font-face{
  font-family:'Chakra Petch';font-style:normal;font-weight:600;font-display:swap;
  src:url('/fonts/chakra-petch-600.woff2') format('woff2');
}
@font-face{
  font-family:'Chakra Petch';font-style:normal;font-weight:700;font-display:swap;
  src:url('/fonts/chakra-petch-700.woff2') format('woff2');
}
@font-face{
  font-family:'IBM Plex Sans';font-style:normal;font-weight:400 600;font-display:swap;
  src:url('/fonts/ibm-plex-sans-var.woff2') format('woff2-variations');
}
@font-face{
  font-family:'JetBrains Mono';font-style:normal;font-weight:400 500;font-display:swap;
  src:url('/fonts/jetbrains-mono-var.woff2') format('woff2-variations');
}

:root{
  color-scheme:light;
  --ink:#17181C;
  --ink-2:#22242A;
  --ink-3:#2E3037;
  --paper:#EDEEF0;
  --card:#FFFFFF;
  --slate:#5A5E6B;
  --slate-d:#A5A8B4;
  --line:#D9DADF;
  /* Form field borders on the dark contact section. Deliberately not --ink-3,
     which is also a decorative divider: WCAG 1.4.11 wants 3:1 on anything that
     identifies a control, and this clears it against both the section
     background (3.49:1) and the field fill (3.06:1). A divider needs neither. */
  --field-line:#6B6E7A;

  /* Semantic layer. --ink used to do three incompatible jobs at once: page
     text, dark-band background, and inverted button fill. Those diverge the
     moment a dark theme exists, so they are separate tokens now and the dark
     theme redefines only these. */
  --fg:#17181C;            /* text on the page ground            */
  --band:#17181C;          /* .slab, .contact, .cta background   */
  --foot-bg:#22242A;       /* footer surface                     */
  --field-bg:#22242A;      /* form field fill                    */
  --invert-bg:#17181C;     /* primary button                     */
  --invert-fg:#FFFFFF;
  --invert-bg-h:#000000;   /* primary button, hover              */
  --accent-fg:#A63D17;     /* accent as text and as a focus ring */
  --head-bg:rgba(255,255,255,.94);
  --head-frost:rgba(255,255,255,.72);
  --panel-bg:rgba(255,255,255,.94);
  --panel-frost:rgba(255,255,255,.82);
  --shadow:rgba(23,24,28,.09);
  --shadow-lift:rgba(23,24,28,.16);
  --shadow-panel:rgba(23,24,28,.17);
  --icon-sun:none;   /* which glyph the toggle shows */
  --icon-moon:block;

  /* Dark palette, defined once. The two blocks below only remap; a value
     change happens here and nowhere else. */
  --dk-paper:#08090C;  --dk-card:#15171D;   --dk-band:#272B34;
  --dk-foot:#111318;   --dk-field:#08090C;
  --dk-fg:#ECEDF1;     --dk-slate:#A2A7B4;  --dk-slate-d:#A5A8B4;
  --dk-line:#262A33;   --dk-ink-3:#3B404C;  --dk-field-line:#7E828F;
  --dk-invert-bg:#ECEDF1; --dk-invert-fg:#14161A; --dk-invert-bg-h:#FFFFFF;
  --dk-accent-fg:#F0842B;
  --dk-head-bg:rgba(18,20,25,.94);   --dk-head-frost:rgba(18,20,25,.72);
  --dk-panel-bg:rgba(18,20,25,.96);  --dk-panel-frost:rgba(18,20,25,.88);
  --dk-shadow:rgba(0,0,0,.5); --dk-shadow-lift:rgba(0,0,0,.62); --dk-shadow-panel:rgba(0,0,0,.6);
  --dk-icon-sun:block; --dk-icon-moon:none;
  /* The hero photograph has a light and a dark version, and the scrim that
     turns its left half into a text ground is paired with it: paper over the
     light photo, near-black over the dark one. Two tokens, desktop and phone,
     because the phone scrim runs top to bottom instead of left to right. */
  --hero-scrim:linear-gradient(90deg,rgba(237,238,240,.64) 0%,rgba(237,238,240,.46) 52%,rgba(237,238,240,.04) 100%);
  --hero-scrim-m:linear-gradient(180deg,rgba(237,238,240,.45) 0%,rgba(237,238,240,.72) 38%,rgba(237,238,240,.88) 100%);
  --dk-hero-scrim:linear-gradient(90deg,rgba(8,9,12,.64) 0%,rgba(8,9,12,.46) 52%,rgba(8,9,12,.04) 100%);
  --dk-hero-scrim-m:linear-gradient(180deg,rgba(8,9,12,.5) 0%,rgba(8,9,12,.72) 38%,rgba(8,9,12,.88) 100%);
  /* Ambient texture for the dark bands. 135deg, not 45deg: that is the same
     diagonal as skewX(-45deg), which resolves to matrix(1,0,-1,1,0,0) and so
     leans "/". At 45deg the field would cross every skewed rule on
     the page at a right angle instead of extending them.
     0.03 is a ceiling as much as a choice, see the note by .site-foot. */
  --dark-tex:none; /* the 3% hairline texture was retired 2026-09-10; the token stays so the rule below is inert */
  /* One hue, two tones. The old pair ran #6C4CF1 to #22D3EE: 64 degrees of
     hue, which is the signature of a gradient nobody chose, and cool on cool
     against neutrals that are already cool. This is an 8 degree ramp inside
     one warm hue, which reads as a decision, and it is the only warm thing on
     a page of cold greys. Both ends still carry their accessibility duties:
     the dark end is 5.48:1 as text on paper, the light end 5.94:1 as a focus
     ring on --ink-2 and 7.62:1 on the dark ground. */
  --beam-a:#A63D17;
  --beam-b:#F0842B;
  /* Corner-cut scale. Everything derives from the logo's 45 degree cuts, so
     the sizes should read as one system: three for boxes, one for the
     full-width section angles. */
  --cut-sm:10px;   /* chips, form fields, small controls */
  --cut:14px;      /* buttons */
  --cut-lg:22px;   /* cards, the header bar, the mobile panel */
  --cut-band:52px; /* full-width section angles */
  --gut:24px;
}
*{margin:0;padding:0;box-sizing:border-box}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}
body{
  font-family:'IBM Plex Sans',system-ui,-apple-system,sans-serif;
  background:var(--paper);color:var(--fg);line-height:1.62;
  -webkit-font-smoothing:antialiased;overflow-x:hidden;
}
/* Reduced motion means no movement, not no feedback. Vestibular triggers are
   translation and scale, not opacity, so cross-fades stay: a control that
   changes with no acknowledgement at all reads as broken. Movement is what
   gets removed, at each site below. */
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{
    animation-duration:.01ms!important;
    transition-property:opacity,color,background-color,border-color,box-shadow!important;
    transition-duration:.16s!important;
  }
}
::selection{background:var(--fg);color:var(--paper)}
a{color:inherit}
:focus-visible{outline:2.5px solid var(--accent-fg);outline-offset:3px;border-radius:2px}
/* Clipped controls cannot show an outline at their cut corners, so they take
   an inset ring that follows the clip. --ring is the accent on paper and the
   light end of the beam on the dark bands, where the accent fails 3:1. */
:root{--ring:var(--accent-fg)}
.slab,.contact,.cta,.site-foot{--ring:var(--beam-b)}
.btn:focus-visible,.to-top:focus-visible,.theme-toggle:focus-visible,.mnav-toggle:focus-visible,
.faq-all:focus-visible,.mnav-caret:focus-visible,.svc-caret:focus-visible{
  outline:none;box-shadow:inset 0 0 0 2.5px var(--ring);
}

/* Skip link. Off-screen until focused rather than display:none, which would
   take it out of the tab order and defeat the point. It lands above the fixed
   header, so it needs a z-index higher than the bar's 60. */
.skip{
  position:absolute;left:24px;top:-100px;z-index:70;
  background:var(--invert-bg);color:var(--invert-fg);
  font-family:'JetBrains Mono',monospace;font-size:0.8125rem;
  text-transform:uppercase;letter-spacing:.08em;text-decoration:none;
  padding:13px 20px;
  clip-path:polygon(var(--cut) 0,100% 0,100% calc(100% - var(--cut)),calc(100% - var(--cut)) 100%,0 100%,0 var(--cut));
  transition:top .16s ease;
}
.skip:focus{top:14px}
/* The skip target needs tabindex="-1" or focus stays on the link and the next
   Tab restarts from the top of the document, which defeats the whole feature.
   main is a landing region rather than a control, so it takes no focus ring;
   the heading that follows tells the user where they landed. */
main[tabindex="-1"]:focus{outline:none}
img,svg{display:block}

.wrap{max-width:1120px;margin:0 auto;padding:0 var(--gut)}
/* Tracking is optical, so it scales with size: large type needs tightening,
   small type needs close to none. One value across every heading size was
   doing neither job well. */
h1,h2,h3,h4{font-family:'Chakra Petch',sans-serif;font-weight:600;line-height:1.12;letter-spacing:-.005em}
h1{letter-spacing:-.022em}
h2{letter-spacing:-.014em}
h3{letter-spacing:-.006em}
h4{letter-spacing:0}
/* A real scale. h1 and h2 shared one size, so a page title and every section
   heading below it landed within 11px of each other and nothing was dominant.
   Four steps now: 56 page title / 30 section / 18.5 subsection / 16 item,
   and every heading at a level is that one size on every page: a rail h2,
   a band-head h2, a card h2 and a project h2 all render at 30, so a reader
   can tell rank from size. The 34/27 split that used to sit inside h2 was
   the audit's single most repeated finding. */
h1{font-size:clamp(2rem,5vw,3.25rem);margin:18px 0 16px;text-wrap:balance}
h2{font-size:clamp(1.5rem,3vw,1.875rem);margin:18px 0 16px;text-wrap:balance}
.lede{color:var(--slate);font-size:1.0625rem;max-width:56ch}

/* ---------------- buttons ---------------- */
/* Buttons are uppercase, matching the header. Uppercase marks controls and
   labels across this site; sentence case carries content. Set with
   text-transform so the markup stays sentence case. A size smaller with
   tracking, for the usual reason: caps carry more weight and need the
   letter-spacing to stay readable. */
.btn{
  display:inline-block;font-size:0.8438rem;font-weight:600;text-decoration:none;
  line-height:1.62;
  text-transform:uppercase;letter-spacing:.08em;
  padding:14px 26px;border:none;cursor:pointer;font-family:inherit;
  clip-path:polygon(var(--cut) 0,100% 0,100% calc(100% - var(--cut)),calc(100% - var(--cut)) 100%,0 100%,0 var(--cut));
  transition:background .16s ease,color .16s ease;
}
.btn-dark{background:var(--invert-bg);color:var(--invert-fg)}
.btn-dark:hover{background:var(--invert-bg-h)}
.btn-ghost{background:transparent;color:var(--fg);box-shadow:inset 0 0 0 1.5px var(--fg)}
.btn-ghost:hover{background:var(--fg);color:var(--paper)}
.btn-lite{background:#fff;color:#17181C}
.btn-lite:hover{background:#DFE2E7} /* not --paper: in the dark theme that is #08090C under ink text */

/* ---------------- brand ---------------- */
.brand{display:inline-flex;align-items:center;text-decoration:none;color:var(--fg)}
.brand svg{height:19px;width:auto;flex:none;display:block}
.site-foot .brand{color:#fff}
@media(max-width:420px){.brand svg{height:16px}}

/* ---------------- header ---------------- */
/* ---------------- floating header ---------------- */
body{padding-top:82px}
.site-head{
  position:fixed;top:0;left:0;right:0;z-index:60;
  padding:14px var(--gut) 0;background:none;border:none;
  pointer-events:none;
}
/* The shadow sits on the row, not on .site-head. A filter on an ancestor
   creates a backdrop root, which stops backdrop-filter on a descendant from
   sampling anything behind it: the bar would frost over nothing. Keeping both
   on the same element avoids that. drop-shadow rather than box-shadow because
   the row is clipped, and box-shadow would be clipped away with it. */
.site-head .row{
  pointer-events:auto;
  max-width:1120px;margin:0 auto;padding:0 24px;
  height:64px;gap:20px;
  /* Grid, not space-between. With three flex children the nav only lands in
     the centre when the brand and the button happen to be the same width;
     change either and it drifts. Equal 1fr rails centre it structurally. */
  display:grid;grid-template-columns:1fr auto 1fr;align-items:center;
  background:var(--head-bg);
  clip-path:polygon(var(--cut-lg) 0,100% 0,100% calc(100% - var(--cut-lg)),calc(100% - var(--cut-lg)) 100%,0 100%,0 var(--cut-lg));
  filter:drop-shadow(0 5px 18px var(--shadow));
  transition:background .22s ease,filter .22s ease;
}
.site-head.lifted .row{
  filter:drop-shadow(0 8px 26px var(--shadow-lift));
}
/* Only thin the bar where the browser can actually frost what is behind it.
   Written as a positive @supports on purpose: if backdrop-filter is missing,
   nothing here applies and the bar stays opaque, rather than degrading into
   the plain translucency this replaced. */
@supports ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .site-head.lifted .row{
    background:var(--head-frost);
    -webkit-backdrop-filter:blur(16px) saturate(150%);
    backdrop-filter:blur(16px) saturate(150%);
  }
}
/* 24px of inner padding matches the page's own 24px content inset, so the
   wordmark and the button sit on the same vertical lines as the headline and
   body copy below them. */
/* Homepage only: the bar is transparent over the hero photo until the page
   has scrolled 16px, then .lifted frosts it exactly as on every other page.
   Only the surface changes: background and shadow off. The type keeps its
   theme tokens, because the photo under it is light in the light theme and
   dark in the dark one, so --fg is already the right colour on it. The bar's
   geometry, grid and clip stay, so nothing moves when it frosts.
   :not(.lifted) rather than a second class, so site.js needs no change. */
body.home .site-head:not(.lifted) .row{background:transparent;filter:none}
.site-head .row > .brand{justify-self:start}
.site-head .row > nav{justify-self:center}
.site-head .row > .head-actions{justify-self:end}
section[id]{scroll-margin-top:104px}
@media(max-width:600px){
  body{padding-top:74px}
  .site-head{padding:10px 14px 0}
  .site-head .row{height:58px;padding:0 10px}
}
.site-nav{display:flex;gap:30px;list-style:none}
/* Header bar type is uppercase. Done with text-transform rather than in the
   markup, so the underlying text stays sentence case for screen readers and
   for the capitalisation rule the rest of the site follows. Uppercase needs
   letter-spacing to stay legible and runs a size smaller to carry the same
   visual weight as the sentence-case original. */
.site-nav a{
  font-size:0.8125rem;font-weight:600;text-decoration:none;color:var(--slate);
  text-transform:uppercase;letter-spacing:.1em;
}
/* Only the size differs now; caps and tracking come from .btn. */
.site-head .head-actions .btn{font-size:0.8125rem}
.site-nav a:hover{color:var(--fg)}
/* Over the frost the flat grey thins out against busy content, so the links
   step up to the page colour at 85% once the bar has lifted. */
.site-head.lifted .site-nav a:not([aria-current]){color:var(--fg);opacity:.85}
@media(max-width:820px){.site-nav{display:none}}

/* ---------------- services mega-panel ---------------- */
.has-menu{display:flex;align-items:center;gap:3px}
/* The caret is hidden in the markup and revealed by site.js, so with no
   JavaScript "Services" is just a link and there is no dead control. */
/* 44px hit area on a 12x8 glyph: the padding grows the target and the
   negative margin puts the glyph back where it was, so the nav does not move. */
.svc-caret{
  display:none;padding:18px 16px;margin:-13px -13px;border:0;background:none;color:var(--slate);
  cursor:pointer;line-height:0;
}
.svc-caret:not([hidden]){display:inline-flex}
.svc-caret svg{width:11px;height:8px;transition:transform .2s ease}
.svc-caret:hover{color:var(--fg)}
.site-head.svc-open .svc-caret{color:var(--fg)}
.site-head.svc-open .svc-caret svg{transform:rotate(180deg)}

/* A .site-head child, like .mnav: the row's clip-path and drop-shadow would
   otherwise trap and clip it. Same width and centring as the row. */
.svc-panel{
  display:none;pointer-events:auto;
  max-width:1120px;margin:10px auto 0;padding:0;
  background:var(--panel-bg);
  clip-path:polygon(var(--cut-lg) 0,100% 0,100% calc(100% - var(--cut-lg)),calc(100% - var(--cut-lg)) 100%,0 100%,0 var(--cut-lg));
  filter:drop-shadow(0 10px 28px var(--shadow-panel));
  opacity:0;transform:translateY(-10px);visibility:hidden;
  transition:opacity .24s ease,transform .3s cubic-bezier(.2,.7,.3,1),visibility 0s linear .3s;
}
@supports ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .svc-panel{
    background:var(--panel-frost);
    -webkit-backdrop-filter:blur(16px) saturate(150%);
    backdrop-filter:blur(16px) saturate(150%);
  }
}
@media(min-width:821px){.svc-panel{display:block}}
.site-head.svc-open .svc-panel{
  opacity:1;transform:none;visibility:visible;
  transition:opacity .24s ease,transform .3s cubic-bezier(.2,.7,.3,1),visibility 0s;
}
.svc-panel ul{
  list-style:none;display:grid;grid-template-columns:repeat(3,1fr);
  gap:1px;background:var(--line);
}
.svc-panel a{
  display:block;height:100%;padding:16px 18px 17px;text-decoration:none;
  background:var(--card);transition:background .16s ease;
}
.sp-name{
  display:block;font-family:'Chakra Petch',sans-serif;font-weight:600;
  font-size:0.9062rem;line-height:1.25;letter-spacing:-.004em;color:var(--fg);
}
.sp-desc{display:block;margin-top:5px;font-size:0.7812rem;line-height:1.45;color:var(--slate)}
@media (hover:hover) and (pointer:fine){
  .svc-panel a:hover{background:var(--paper)}
  .svc-panel a:hover .sp-name{color:var(--accent-fg)}
}
.svc-panel a:focus-visible{background:var(--paper)}

/* ---------------- mobile navigation ---------------- */
.sr-only{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;
}
.head-actions{display:flex;align-items:center;gap:10px}
.head-actions .btn{white-space:nowrap}

.mnav-toggle{
  display:none;width:44px;height:44px;padding:0;border:none;background:transparent;
  cursor:pointer;align-items:center;justify-content:center;
  clip-path:polygon(var(--cut-sm) 0,100% 0,100% calc(100% - var(--cut-sm)),calc(100% - var(--cut-sm)) 100%,0 100%,0 var(--cut-sm));
}
.mnav-bars{position:relative;display:block;width:20px;height:14px}
.mnav-bars::before,.mnav-bars::after{
  content:'';position:absolute;left:0;width:20px;height:2px;background:var(--fg);
  transform:skewX(-45deg);
  transition:transform .3s cubic-bezier(.2,.7,.3,1),top .3s cubic-bezier(.2,.7,.3,1);
}
.mnav-bars::before{top:2px}
.mnav-bars::after{top:10px}
.site-head.menu-open .mnav-bars::before{top:6px;transform:rotate(45deg)}
.site-head.menu-open .mnav-bars::after{top:6px;transform:rotate(-45deg)}

.mnav{
  display:none;pointer-events:auto;
  max-width:1120px;margin:10px auto 0;padding:10px 24px 20px;
  background:var(--panel-bg);
  clip-path:polygon(var(--cut-lg) 0,100% 0,100% calc(100% - var(--cut-lg)),calc(100% - var(--cut-lg)) 100%,0 100%,0 var(--cut-lg));
  filter:drop-shadow(0 10px 28px var(--shadow-panel));
  opacity:0;transform:translateY(-12px);visibility:hidden;
  transition:opacity .26s ease,transform .32s cubic-bezier(.2,.7,.3,1),visibility 0s linear .32s;
}
@supports ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .mnav{
    background:var(--panel-frost);
    -webkit-backdrop-filter:blur(16px) saturate(150%);
    backdrop-filter:blur(16px) saturate(150%);
  }
}
.site-head.menu-open .mnav{
  opacity:1;transform:none;visibility:visible;
  transition:opacity .26s ease,transform .32s cubic-bezier(.2,.7,.3,1),visibility 0s;
}

.mnav ul{list-style:none}
.mnav li{
  opacity:0;transform:translateY(10px);
  transition:opacity .24s ease,transform .24s cubic-bezier(.2,.7,.3,1);
}
/* A menu answers a tap, so the whole thing lands inside ~360ms: 25ms steps
   on a 240ms item transition. It was 40ms steps on 340ms, which put the
   last link past half a second. */
.site-head.menu-open .mnav li{opacity:1;transform:none}
.site-head.menu-open .mnav > nav > ul > li:nth-child(1){transition-delay:.03s}
.site-head.menu-open .mnav > nav > ul > li:nth-child(2){transition-delay:.055s}
.site-head.menu-open .mnav > nav > ul > li:nth-child(3){transition-delay:.08s}
.site-head.menu-open .mnav > nav > ul > li:nth-child(4){transition-delay:.105s}
.site-head.menu-open .mnav > nav > ul > li:nth-child(5){transition-delay:.13s}
.site-head.menu-open .mnav > nav > ul > li:nth-child(6){transition-delay:.155s}
.site-head.menu-open .mnav > nav > ul > li:nth-child(7){transition-delay:.18s}
/* The nine services, folded under the Services item and closed until the
   caret beside it is pressed: nine rows always open were a wall on a phone.
   Two columns of plain sentence-case links. The wrapper animates a grid
   track, the same device the FAQ uses, so it needs no measuring. */
.mnav-has-sub{display:flex;align-items:center;border-bottom:1px solid var(--line)}
.mnav-has-sub a{flex:1;border-bottom:0}
.mnav-caret{
  display:none;width:44px;height:44px;padding:0;margin:0 -8px 0 0;border:0;background:none;
  color:var(--slate);cursor:pointer;align-items:center;justify-content:center;
}
.mnav-caret:not([hidden]){display:inline-flex}
.mnav-caret svg{width:12px;height:8px;transition:transform .2s ease}
.mnav-caret[aria-expanded="true"]{color:var(--fg)}
.mnav-caret[aria-expanded="true"] svg{transform:rotate(180deg)}
.mnav-sub-wrap{display:grid;grid-template-rows:0fr;transition:grid-template-rows .24s cubic-bezier(.2,.7,.3,1)}
.mnav-sub-wrap.open{grid-template-rows:1fr}
.mnav-sub-wrap > .mnav-sub{min-height:0;overflow:hidden}
.mnav-sub{list-style:none;margin:0;padding:0 0 0 12px;display:grid;grid-template-columns:1fr 1fr;gap:0 18px}
.mnav-sub-wrap.open > .mnav-sub{padding:4px 0 10px 12px;border-bottom:1px solid var(--line)}
@media (prefers-reduced-motion:reduce){.mnav-sub-wrap{transition:none}}
.mnav-sub li{transition-delay:0s!important}
.mnav a.mnav-sub-link{
  font-family:'IBM Plex Sans',system-ui,sans-serif;font-size:0.875rem;font-weight:500;
  text-transform:none;letter-spacing:0;padding:7px 0;border-bottom:0;gap:0;
}
.mnav a.mnav-sub-link::before{display:none}
/* Uppercase to match the bar. A size smaller with tracking added, for the
   same reason: uppercase carries more weight and needs the letter-spacing to
   stay readable. The padding sets the tap target, so dropping the type size
   does not shrink it. */
.mnav a{
  display:flex;align-items:center;gap:12px;padding:14px 0;text-decoration:none;
  color:var(--fg);font-family:'Chakra Petch',sans-serif;font-weight:600;
  font-size:1.0625rem;line-height:1.2;border-bottom:1px solid var(--line);
  text-transform:uppercase;letter-spacing:.06em;
}
.mnav a::before{
  content:'';width:0;height:2px;flex:none;transform:skewX(-45deg);
  background:linear-gradient(90deg,var(--beam-a),var(--beam-b));
}
.mnav a[aria-current]::before{width:18px}
.mnav a[aria-current]{color:var(--accent-fg)}
@media(max-width:820px){
  /* :not([hidden]) so the toggle stays gone until site.js reveals it: with
     JavaScript off there is no dead control, the footer carries the nav. */
  .mnav-toggle:not([hidden]){display:inline-flex}
  .mnav{display:block}
}
/* The wordmark, CTA and toggle need about 318px of row to sit on one line.
   They fit on every common phone; below this they do not, and the CTA gives
   way because Contact is inside the panel anyway. */
@media(max-width:349px){
  .head-actions .btn{display:none}
}
@media(max-width:600px){.site-head .btn{padding:12px 16px;font-size:0.8438rem}}

/* ---------------- hero ---------------- */
/* The homepage hero is a full-bleed photograph with the copy set on it. The
   photo comes in a light and a dark version of the same composition, chosen
   by <picture> on the system scheme and corrected by site.js when the
   visitor has toggled the other way, so the hero follows the theme like
   every other surface: page tokens for the type, --hero-scrim for the
   ground. The header rides over it transparent until the first scroll (see
   the body.home rules under the header).

   Stacking inside the hero: photo 0, scrim 1, beam 2, copy 3. The beam sat
   under the scrim for a day and the one warm accent on the page rendered
   washed to peach; it is the signature and it paints over the wash.

   The section is pulled up under the fixed header by the body's padding-top
   and pads itself back down, so the photo starts at the top of the viewport.
   Those two numbers have to move together with body{padding-top}.

   Its bottom edge is the 52px drop, the same cut every other full-width band
   uses, and the beam sits exactly on it: the beam's lower edge and the
   clip-path edge are the same line, so the photo ends where the paper
   begins and there is no third angle. This is what the beam comment below
   warned about: the hero has a background now, so its edge is a real edge
   and the section under it inherits the cut. */
.hero{
  position:relative;overflow:hidden;
  margin-top:-82px;padding:calc(82px + 118px) 0 150px;
  min-height:min(820px,92vh);
  display:flex;flex-direction:column;justify-content:center;
  color:var(--fg);background:var(--paper);
  clip-path:polygon(0 0,100% 0,100% calc(100% - var(--cut-band)),0 100%);
}
/* Two motions live on the photo, and both are transforms on their own
   compositor layer, so neither repaints the hero or anything under the
   frosted header. Never put a filter here: see the note on the dark bands.

   1. The settle. The image opens at 1.07 and eases to exactly 1 over 16s, so
      the state it rests in is the one pixel-true to the file: a Ken Burns
      that ends scaled up would leave a permanent upscale. It runs once, as a
      CSS animation, from the first painted frame with no script involved.
      The origin matches object-position so the settle pulls toward the same
      point the crop is anchored on.
   2. The parallax, on .hero-media, driven by the scroll position through a
      CSS scroll timeline. It was a scroll listener in site.js first, and on
      a phone that felt sticky: iOS hands scroll events to the page late
      during a flick, so the photo held still and then caught up. A scroll
      timeline runs on the compositor with no script in the loop and moves
      with the finger. Browsers without it get a still photo, which is the
      right fallback; there is no JavaScript one. The range is the first
      viewport height and the travel is a quarter of it, so the ratio is a
      quarter of the scroll. The image is 116% tall and starts 16% high so
      it has room to travel without exposing an edge.

   Both are movement, so reduced motion removes both. */
.hero-media{position:absolute;inset:0;z-index:0;overflow:hidden}
@supports (animation-timeline:scroll()){
  .hero-media{
    animation:hero-drift linear both;
    animation-timeline:scroll(root block);
    animation-range:0 100vh;
  }
}
@keyframes hero-drift{
  from{transform:none}
  to{transform:translate3d(0,25vh,0)}
}
.hero-img{
  position:absolute;left:0;right:0;top:-16%;height:116%;width:100%;
  object-fit:cover;object-position:72% 50%;
  transform-origin:72% 50%;
  animation:hero-settle 16s cubic-bezier(.2,.7,.3,1) both;
}
@keyframes hero-settle{
  from{transform:scale(1.07) translateX(.8%)}
  to{transform:none}
}
@media (prefers-reduced-motion:reduce){
  .hero-img,.hero-media{animation:none;transform:none}
}
/* The scrim is what makes the left half of the photo a text ground. It is
   paired with the photo through --hero-scrim, defined beside the palette. */
.hero-scrim{position:absolute;inset:0;z-index:1;pointer-events:none;background:var(--hero-scrim)}
.hero-media picture{display:contents}
.hero .wrap{position:relative;z-index:3;width:100%}
.hero h1{
  font-size:clamp(2.375rem,5.6vw,4.125rem);font-weight:700;color:var(--fg);
  max-width:14ch;margin:0 0 22px;text-wrap:balance;
}
.hero .lede{font-size:1.1875rem;max-width:50ch;color:var(--fg)}
.hero-cta{display:flex;gap:22px;flex-wrap:wrap;align-items:center;margin-top:34px}
/* One primary button and one text link, not the catalog's button pair. The
   link keeps the button's type and a 44px row; the arrow is its affordance. */
.hero-alt{
  display:inline-flex;align-items:center;gap:9px;padding:14px 0;
  font-family:inherit;font-size:0.8437rem;font-weight:600;
  letter-spacing:.08em;text-transform:uppercase;color:var(--fg);text-decoration:none;
}
.hero-alt svg{width:14px;height:10px;flex:none;transition:transform .24s cubic-bezier(.2,.7,.3,1)}
@media(max-width:600px){
  .hero{margin-top:-74px;padding:calc(74px + 84px) 0 124px;min-height:min(700px,88vh)}
  .hero-img{object-position:62% 50%}
  .hero-scrim{background:var(--hero-scrim-m)}

}
/* The beam marks the seam between the hero and the section under it. It rides
   the same 52px drop as --cut-band, so it stays parallel to every other
   section edge on the site as you scroll.

   It replaced a free-floating wedge that sat at its own angle, roughly 6.6
   degrees at the top edge and 6.0 at the bottom, so its two sides were not
   even parallel to each other. Nothing else on the site used those angles.
   The site runs two angle languages and both are consistent: corner cuts and
   skewed rules are true 45 degrees, full-width section edges are the 52px
   drop. The wedge belonged to neither, and reserved 200px of hero to do it.

   The hero carries a photograph now, so this is a real edge: .hero is
   clipped on the same 52px drop and the beam's lower edge sits on that
   clip line. Change one and the other has to follow. */
.beam{
  position:absolute;left:0;right:0;bottom:0;height:70px;z-index:2;
  background:linear-gradient(90deg,var(--beam-a),var(--beam-b));
  clip-path:polygon(0 calc(100% - 12px),100% calc(100% - 64px),100% calc(100% - 52px),0 100%);
  pointer-events:none;
}

/* ---------------- generic section ---------------- */
/* Section rhythm. 96px was on every band on the site, so nothing was ever
   dense and nothing was ever open: the page breathed at one rate from top to
   bottom, which is a large part of why it read as generated. Three weights
   now, chosen by how much content the section is actually carrying. */
.band{padding:96px 0}
.band.dense{padding:52px 0 58px}
.band.open{padding:132px 0 140px}
.band-head{max-width:660px;margin-bottom:52px}
.band.dense .band-head{margin-bottom:30px}
.band-head .lede{margin-top:14px}
.band-head .lede a{color:var(--fg);text-decoration:underline;text-underline-offset:3px}
/* Section heads open on the h2 now. There used to be a mono label above it
   (removed site-wide, see CLAUDE.md), and the h2's 18px top margin was the
   gap between the two; without the label it is dead space. */
.band-head > h2:first-child,.cta-copy > h2:first-child,.contact-grid h2:first-child,
.scope > h2:first-child,.wrap > h1:first-child{margin-top:0}

/* ---------------- slab (dark signature band) ---------------- */
.slab{
  background:var(--band);color:#E7E8EC;position:relative;
  clip-path:polygon(0 var(--cut-band),100% 0,100% calc(100% - var(--cut-band)),0 100%);
  padding:120px 0 126px;
}
.slab h2{color:#fff}
.slab .lede{color:var(--slate-d);max-width:60ch}
/* The Why band is an exchange, not a grid of value props: what a client
   asks for on the left, what Atlerix says on the right. No stock block has
   this shape, it mirrors the free first conversation, and it carries the
   four principles as answers instead of headings with a dash above each. */
.exchange{margin:0;display:grid;gap:0}
.exchange > div{display:grid;grid-template-columns:minmax(0,5fr) minmax(0,7fr);gap:10px 48px;padding:26px 0;border-top:1px solid rgba(255,255,255,.14)}
.exchange > div:last-child{border-bottom:1px solid rgba(255,255,255,.14)}
.exchange dt{font-family:'Chakra Petch',sans-serif;font-weight:600;font-size:1.15625rem;line-height:1.3;letter-spacing:-.006em;color:#fff}
.exchange dd{margin:0;font-size:1.0625rem;line-height:1.62;color:var(--slate-d);max-width:56ch}
@media(max-width:820px){.exchange > div{grid-template-columns:1fr;gap:8px}}
.slab-more{margin-top:44px;font-size:1.0625rem;color:var(--slate-d);max-width:60ch}
.slab-more a{color:#fff;text-decoration:underline;text-underline-offset:3px}
.slab-grid{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(266px,1fr));
  gap:42px 54px;margin-top:64px;
}
.slab-item h3{font-size:1.1562rem;color:#fff;margin:15px 0 9px}
.slab-item p{font-size:0.9375rem;color:var(--slate-d);max-width:40ch}
.slab-grid.two{grid-template-columns:repeat(auto-fit,minmax(min(420px,100%),1fr))}
.slab-grid.two .slab-item p{max-width:48ch}

/* ---------------- services ---------------- */

/* Homepage "what we do": a plain statement, not a list. The nav mega-panel
   and /services carry the specifics. .hp-lead is the one line given the
   weight a statistic would have, without being a number the site cannot
   honestly claim. */
.hp-say{max-width:62ch}
.hp-lead{
  font-size:clamp(1.25rem,2.4vw,1.5625rem);line-height:1.4;letter-spacing:-.008em;
  font-weight:500;color:var(--fg);margin-bottom:22px;
}
.hp-say p:not(.hp-lead){font-size:1.0625rem;line-height:1.65;color:var(--slate);max-width:58ch}
/* The ownership promise on its own line in the page colour, and the one
   sentence on the homepage that names every service, as descriptions with
   links, so a visitor who never opens the menu still learns what is sold. */
.hp-say p.hp-promise{font-size:1.1875rem;color:var(--fg);max-width:54ch;margin-top:22px}
/* The nine services as a chunked three-column list, one per line, 44px
   rows on a phone. It uses the section's width instead of leaving the right
   half empty, and it is not a card grid. */
.hp-services{list-style:none;margin:30px 0 0;padding:0;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:4px 28px}
.hp-services a{display:inline-flex;align-items:center;min-height:36px;font-size:0.9375rem;color:var(--fg);text-decoration:underline;text-underline-offset:3px}
.hp-work{margin-top:18px;font-size:0.9375rem;color:var(--slate);max-width:62ch}
.hp-work a{color:var(--fg);text-decoration:underline;text-underline-offset:3px}
@media(max-width:820px){.hp-services{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:600px){.hp-services{grid-template-columns:1fr;gap:0}.hp-services a{min-height:44px}}
.hp-say p:not(.hp-lead) + p{margin-top:16px}
/* The two routes out of the homepage statement: what we offer, and what it
   looks like in practice. A flex row so the gap matches .hero-cta. */
.hp-actions{display:flex;gap:13px;flex-wrap:wrap;margin-top:36px}
/* the one button that follows a section, same gap everywhere */
.band-more{margin-top:40px}
.band.flush-top{padding-top:0}
.hint.after-direct{margin-top:30px}
/* The services overview is nine ruled rows, not nine cards (2026-09-10).
   Each row is one anchor: the name at the h2 step on the left, the one-line
   descriptor on the right in the same face and size, grey, the way the nav
   panel already describes each service. No paragraph, no bullets (they live
   on the service page), no equalized heights, and no clip-path, so the
   global focus outline is not cut off. The name's accent rule is visible at
   rest, so a row reads as a link before hover, and grows on hover inside its
   own box like the direct links. */
.svc-ex{list-style:none;margin:0;padding:0}
.svc-ex li{border-top:1px solid var(--line)}
.svc-ex li:last-child{border-bottom:1px solid var(--line)}
.svc-ex a{
  display:grid;grid-template-columns:minmax(0,4fr) minmax(0,8fr);gap:8px 48px;
  padding:34px 0 36px;text-decoration:none;color:inherit;align-items:start;
}
.svc-ex h2{margin:0;font-size:1.875rem}
.svc-ex h2::after{
  content:'';display:block;width:28px;height:2px;margin-top:10px;
  background:linear-gradient(90deg,var(--beam-a),var(--beam-b));
  transform:skewX(-45deg);transform-origin:left;
  transition:transform .24s cubic-bezier(.2,.7,.3,1);
}
.svc-ex p{
  margin:0;font-family:'Chakra Petch',sans-serif;font-weight:600;
  font-size:1.875rem;line-height:1.3;letter-spacing:-.014em;color:var(--slate);max-width:24ch;
}
.svc-ex a:focus-visible{outline-offset:8px}
@media(max-width:820px){
  .svc-ex a{grid-template-columns:1fr;gap:4px;padding:24px 0}
  .svc-ex h2,.svc-ex p{font-size:1.5rem}
  .svc-ex p{max-width:none}
}
.svc-next{margin-top:34px;font-size:1.0625rem;color:var(--slate);max-width:60ch}
.svc-next a{color:var(--fg);text-decoration:underline;text-underline-offset:3px}
.svc-next a:hover{color:var(--accent-fg)}

/* ---------------- how ---------------- */
.how{background:var(--card);border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
.how-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:2px;background:var(--line)}
.how-item{background:var(--card);padding:32px 26px 34px}
.how-item .n{
  font-family:'JetBrains Mono',monospace;font-size:0.75rem;letter-spacing:.12em;color:var(--slate);
}
.how-item h3{font-size:1.1562rem;margin:12px 0 9px}
.how-item h4{font-size:1rem;margin:12px 0 9px}
.how-item p{font-size:0.9062rem;color:var(--slate)}

/* ---------------- faq ---------------- */
.faq-list{max-width:840px;border-top:1px solid var(--line)}
.detail .faq-list{max-width:none}
details{border-bottom:1px solid var(--line)}
summary{
  list-style:none;cursor:pointer;padding:24px 44px 24px 0;position:relative;
  font-family:'Chakra Petch',sans-serif;font-weight:600;font-size:1.125rem;line-height:1.3;
}
summary::-webkit-details-marker{display:none}
summary::after{
  content:'';position:absolute;right:6px;top:31px;width:12px;height:12px;
  border-right:2px solid var(--fg);border-bottom:2px solid var(--fg);
  transform:rotate(45deg);transition:transform .2s ease;
}
details[open] summary::after{transform:rotate(-135deg)}
details p{padding:0 0 26px;color:var(--slate);font-size:0.9688rem;max-width:70ch}
/* FAQ answers expand by transitioning a grid track from 0fr to 1fr rather
   than by animating a measured height. Measuring forced a synchronous
   reflow at the moment the animation started, which showed up as a stutter
   on the first frame.

   The default here is the open state, so with JavaScript off details[open]
   simply shows its answer. site.js sets .js on <html> and only then does the
   collapsed state apply. */
.faq-body{display:grid;grid-template-rows:1fr}
/* The answer's own bottom padding has to live on an inner element. On the
   grid item it keeps a 26px box even at 0fr, so the row never collapses. */
.faq-inner{min-height:0;overflow:hidden}
html.js .faq-body{
  grid-template-rows:0fr;opacity:0;
  transition:grid-template-rows .3s cubic-bezier(.2,.7,.3,1),opacity .2s ease;
}
html.js details.faq-open .faq-body{grid-template-rows:1fr;opacity:1}
/* Under reduced motion site.js lets the browser toggle natively and never adds
   .faq-open, so native `open` has to be enough on its own. Without this the
   row stays at 0fr and the answer never appears, which made the whole page
   unusable for anyone with that setting on. */
@media (prefers-reduced-motion:reduce){
  html.js details[open] .faq-body{grid-template-rows:1fr;opacity:1}
}
/* the chevron turns as soon as a close begins, not when it finishes */
html.js details[open]:not(.faq-open) summary::after{transform:rotate(45deg)}

/* ---------------- contact ---------------- */
.contact{background:var(--band);color:#E7E8EC;padding:110px 0 116px}
.contact h2{color:#fff;max-width:16ch}
.contact .lede{color:var(--slate-d)}
.contact-grid{display:grid;grid-template-columns:1fr 1fr;gap:64px}
@media(max-width:900px){.contact-grid{grid-template-columns:1fr;gap:46px}}
.direct{margin-top:36px;display:grid;gap:2px}
.direct.direct-first{margin:22px 0 26px}
.direct a{
  font-family:'JetBrains Mono',monospace;font-size:1.0625rem;color:#fff;text-decoration:none;
  display:inline-flex;align-items:center;min-height:44px;gap:11px;width:fit-content;
}
.direct a::before{content:'';width:16px;height:2px;background:var(--beam-b);transform:skewX(-45deg);transform-origin:left;flex:none}
.direct a:hover{color:var(--beam-b)}

.field{margin-bottom:17px}
.field label{
  display:block;font-family:'JetBrains Mono',monospace;font-size:0.7188rem;
  letter-spacing:.1em;text-transform:uppercase;color:var(--slate-d);margin-bottom:8px;
}
.field label .opt{text-transform:none;letter-spacing:0}
/* 16px, not 15: Safari on iOS zooms the whole page into any focused field
   under 16px, and the visitor then has to pinch back out to find Send. The
   padding gives back the pixel so the field height does not change. */
.field input,.field select,.field textarea{
  width:100%;font-family:inherit;font-size:1rem;color:#fff;
  background:var(--field-bg);border:1px solid var(--field-line);padding:12px 14px;
  clip-path:polygon(var(--cut-sm) 0,100% 0,100% calc(100% - var(--cut-sm)),calc(100% - var(--cut-sm)) 100%,0 100%,0 var(--cut-sm));
}
.field textarea{resize:vertical;min-height:112px}
/* The outline must not be removed here. This selector is more specific than
   the global :focus-visible rule, so an `outline:none` silently won that
   fight and left keyboard users with no focus indicator on the contact form.
   Cyan rather than the global purple because these sit on dark: 8.58:1
   against the field fill, where purple manages 3.33:1. */
.field input:focus,.field select:focus,.field textarea:focus{border-color:var(--beam-b)}
.field input:focus-visible,.field select:focus-visible,.field textarea:focus-visible{
  outline:2.5px solid var(--beam-b);outline-offset:2px;
}
/* appearance:none takes the native chevron with it, and without one the
   control read as a text box saying "Not sure yet". The chevron is the same
   12x8 path the services caret uses, drawn in --slate-d since the field sits
   on the dark band in both themes. A data: image is allowed by img-src. */
.field select{
  appearance:none;cursor:pointer;padding-right:40px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%23A5A8B4' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right 15px center;background-size:12px 8px;
}
/* Inline validation. :user-invalid is the browser's own "this field has been
   touched and is wrong" state, so the message shows on blur with no script.
   site.js adds .is-err on a failed submit for browsers without it, and moves
   focus to the first bad field. Messages live in the markup, one per
   required field, and are tied to the control with aria-describedby. */
.field-err{display:none;font-size:0.7812rem;color:#FFB4B4;margin-top:7px}
.field :user-invalid{border-color:#FFB4B4}
.field :user-invalid ~ .field-err,.field.is-err .field-err{display:block}
.field.is-err input,.field.is-err textarea,.field.is-err select{border-color:#FFB4B4}
.hint{font-size:0.7812rem;color:var(--slate-d);margin-top:16px}
.hint.hint-err{color:#FFB4B4}
/* The live region for a failed submit. Empty and out of flow at rest, so a
   screen reader has nothing to read on load; it collapses back when cleared. */
#form-status{margin-top:8px}
#form-status:empty{display:none}

/* ---------------- footer ---------------- */
.site-foot{background:var(--foot-bg);color:var(--slate-d);padding:64px 0 40px}
.foot-top{display:flex;justify-content:space-between;gap:44px;flex-wrap:wrap;margin-bottom:48px}
.site-foot .brand span{color:#fff}
.foot-blurb{font-size:0.875rem;max-width:32ch;margin-top:15px}
.foot-brand{max-width:34ch}
.foot-cta{margin-top:24px}
/* The Services heading is itself a link, so it also matches `.foot-cols a`,
   which sets the 14px used by the link lists below. Without inheriting back,
   that heading renders 3px larger than Company and Legal. */
.foot-cols h3 a{
  color:inherit;text-decoration:none;transition:color .16s ease;
  font-size:inherit;letter-spacing:inherit;font-weight:inherit;
}
.foot-cols{display:flex;gap:56px;flex-wrap:wrap}
/* Nine services in three groups of three, which is chunking, not grouping:
   the columns carry no labels, because the site says nothing about how the
   services relate. */
.foot-all{margin:0 0 10px;font-size:0.875rem}
.foot-all a{color:#fff;text-decoration:underline;text-underline-offset:3px}
.foot-svc{display:grid;grid-template-columns:repeat(3,auto);gap:0 28px}
@media(max-width:720px){.foot-svc{grid-template-columns:1fr}}
.foot-cols h3{
  font-family:'JetBrains Mono',monospace;font-size:0.6875rem;letter-spacing:.14em;
  /* 4.93:1 on --ink-2. That passes AA but it is the thinnest margin on the
     site, so anything that lightens the ground under it, a texture especially,
     puts it under. It was briefly lifted to #969AA6 for exactly that reason and
     put back when the footer texture was dropped. */
  text-transform:uppercase;color:#8E919C;margin-bottom:14px;font-weight:400;
}
.foot-cols ul{list-style:none;display:grid;gap:9px}
.foot-cols a{font-size:0.875rem;text-decoration:none;color:#C7C9D2}
.foot-cols a:hover{color:#fff}
.foot-base{
  border-top:1px solid var(--ink-3);padding-top:24px;
  display:flex;justify-content:space-between;gap:14px;flex-wrap:wrap;font-size:0.7812rem;
}

/* ---------------- page entry ---------------- */
/* Every page fades its content in on load. This is a plain CSS animation on
   purpose: it starts at the first painted frame with no JavaScript involved,
   so there is no window where content is invisible waiting for a script.

   It is deliberately NOT gated behind a feature query. It previously sat
   inside @supports not (view-transition-name: none), paired with a
   cross-document @view-transition. That test is wrong: Safari supported the
   view-transition-name property for same-document transitions long before it
   supported cross-document navigation, so the query switched this animation
   off on browsers that then had no transition at all, and pages arrived with
   no animation whatsoever.

   Scoped to main rather than body: an opacity or transform animation on an
   ancestor of the header would make it the containing block for the fixed
   bar, and the header would ride down the page with the content. */
main{animation:page-in .34s cubic-bezier(.2,.7,.3,1) both}
@keyframes page-in{
  from{opacity:0;transform:translateY(8px)}
  to{opacity:1;transform:none}
}
@media (prefers-reduced-motion:reduce){
  /* the page still fades in, it just does not travel */
  main{animation:page-in-fade .2s ease both}
}
@keyframes page-in-fade{from{opacity:0}to{opacity:1}}

/* ---------------- reveal ---------------- */
/* The hidden state is applied by site.js, and only to elements below the
   first viewport. Anything already on screen must never start hidden: with
   cross-document view transitions the browser captures the new page's first
   painted frame, and if that frame is missing content the page arrives
   incomplete and then flashes in once deferred JavaScript catches up. That
   was the stutter on every page open, and putting opacity:0 in CSS here is
   what caused it. Do not move it back. */
/* 500ms on the built-in `ease` is deliberate and was chosen by eye. It was
   briefly shortened to 300ms on the house curve because an external animation
   standard caps interface motion at 300ms and flags built-in easings. That
   rule is about controls answering a click, where lag reads as lag. A scroll
   reveal is not that: the slow start of `ease` and the longer settle are what
   make it read as smooth, and shortening it made the whole site feel abrupt.
   Do not "fix" this again. */
.rv{transition:opacity .5s ease,transform .5s ease}
.rv-hide{opacity:0;transform:translateY(16px);transition:none}
/* reveals keep the fade and lose the 16px rise */
@media (prefers-reduced-motion:reduce){.rv-hide{transform:none}}

/* ================================================================
   SERVICE PAGES
   ================================================================ */

/* ---------------- breadcrumb ---------------- */
.crumb ol{list-style:none;display:flex;flex-wrap:wrap;align-items:center}
.crumb li{
  font-family:'JetBrains Mono',monospace;font-size:0.7188rem;letter-spacing:.12em;
  text-transform:uppercase;color:var(--slate);
}
.crumb li+li::before{content:'/';margin:0 9px;color:var(--line)}
.crumb a{text-decoration:none;color:var(--slate)}
.crumb a:hover{color:var(--fg)}
.crumb [aria-current]{color:var(--fg)}

/* ---------------- page hero ---------------- */
.page-hero{position:relative;overflow:hidden;padding:52px 0 76px}
.page-hero h1{
  font-size:clamp(2.125rem,5.2vw,3.5rem);font-weight:700;
  max-width:18ch;margin:20px 0 20px;text-wrap:balance;
}
.page-hero .lede{font-size:1.125rem;max-width:58ch}
.stage-date{
  font-family:'JetBrains Mono',monospace;font-size:0.75rem;letter-spacing:.1em;
  text-transform:uppercase;color:var(--slate);margin:-6px 0 18px;
  display:inline-flex;align-items:center;gap:11px;
}
/* Section list under a policy hero, so a clause can be reached and linked. */
.jump{display:flex;flex-wrap:wrap;gap:6px 18px;margin-top:22px;font-size:0.875rem;list-style:none;padding:0}
.jump a{color:var(--slate);text-decoration:underline;text-underline-offset:3px}
.jump a:hover{color:var(--fg)}
/* the closing cross-link on a policy: same size as the policy's own sections */
.legal-next{padding:74px 0 78px}
.legal-next .band-head{margin-bottom:0}
.legal-next h2{max-width:24ch}
.legal-next .btn-row{margin-top:28px}
/* thanks and 404: one column, one message */
.solo{padding:120px 0 140px}
.solo .wrap{max-width:640px}
.solo .lede+.lede,.solo .routes{margin-top:22px}
.solo .actions{margin-top:36px}
.routes{list-style:none;padding:0;display:grid;gap:10px}
.routes a,.solo .lede a{color:var(--fg);font-weight:600;text-decoration:underline;text-underline-offset:3px}
.stage-date::before{
  content:'';width:20px;height:2px;flex:none;
  background:linear-gradient(90deg,var(--beam-a),var(--beam-b));transform:skewX(-45deg);
}
.site-nav a[aria-current]{color:var(--fg);font-weight:600}

/* ---------------- service stat ---------------- */
/* One researched figure per service, set apart at the top of "What we do".
   The inline link is the citation. */
.svc-stat{
  display:grid;grid-template-columns:290px 1fr;gap:0 64px;align-items:start;
  padding:6px 0 34px;margin-bottom:50px;border-bottom:1px solid var(--line);
}
.svc-stat-figure{
  font-family:'Chakra Petch',sans-serif;font-weight:700;
  font-size:clamp(2rem,5vw,3.5rem);line-height:.92;letter-spacing:-.035em;
  color:var(--fg);
}
.svc-stat-body{max-width:54ch}
.svc-stat-line{font-size:0.9688rem;line-height:1.6;color:var(--slate)}
/* The citation is the one line the page asks to be trusted on, so it is not
   the smallest text on the page and not shouted: 12px mono, sentence case. */
.svc-stat-src{
  display:block;margin-top:9px;font-family:'JetBrains Mono',monospace;
  font-size:0.75rem;letter-spacing:.02em;color:var(--slate);
}
.svc-stat-src a{color:var(--slate);text-decoration:underline;text-underline-offset:2px}
.svc-stat-src a:hover{color:var(--accent-fg)}
@media(max-width:900px){
  .svc-stat{grid-template-columns:1fr;gap:14px}
}

/* ---------------- detail layout ---------------- */
.detail{display:grid;grid-template-columns:290px 1fr;gap:0 64px}
.detail+.detail{margin-top:84px}
.rail{position:sticky;top:104px;align-self:start}
.rail .n{font-family:'JetBrains Mono',monospace;font-size:0.7812rem;letter-spacing:.1em;color:var(--slate)}
.rail h2{margin:12px 0 14px}
.rail p{font-size:0.9375rem;color:var(--slate);max-width:34ch}
@media(max-width:900px){
  .detail{grid-template-columns:1fr;gap:20px}
  .rail{position:static}
  .rail p{max-width:60ch}
}

/* detail blocks rendered inside the dark slab */
.slab .rail h2{color:#fff}
.slab .rail p,.slab .detail .lede{color:var(--slate-d)}
.slab .detail+.detail{margin-top:72px}

/* prose inside a detail column */
.detail .lede{max-width:64ch}
.detail .lede + .lede{margin-top:18px}
.cta-copy{max-width:660px}
.lede-strong{color:var(--fg)}
.contact-cut{clip-path:polygon(0 var(--cut-band),100% 0,100% 100%,0 100%)}
.band.tight{padding-top:24px}

/* ---------------- blog ---------------- */
.post-meta{
  font-family:'JetBrains Mono',monospace;font-size:0.7812rem;letter-spacing:.1em;
  text-transform:uppercase;color:var(--slate);margin:-8px 0 18px;
}
/* A link that leaves the site says so. Only in post bodies, where research
   links and service links used to look identical. */
.post-body a[href^="http"]::after{content:"\2197";font-size:.8em;margin-left:.15em;display:inline-block}
.post-next{margin-top:34px;font-size:0.9688rem;color:var(--slate)}
.post-next a{color:var(--fg);text-decoration:underline;text-underline-offset:3px}
.post-list{max-width:760px;border-top:1px solid var(--line)}
.post-row{padding:30px 0;border-bottom:1px solid var(--line)}
.post-date{
  font-family:'JetBrains Mono',monospace;font-size:0.75rem;letter-spacing:.1em;
  text-transform:uppercase;color:var(--slate);margin:0 0 10px;
}
.post-row h2{letter-spacing:-.012em;margin-bottom:8px}
.post-row h2 a{
  color:var(--fg);text-decoration:none;display:inline-flex;align-items:baseline;gap:11px;
}
.post-row h2 a::after{
  content:'';align-self:center;width:18px;transform-origin:left;height:2px;flex:none;transform:skewX(-45deg) scaleX(0);
  background:linear-gradient(90deg,var(--beam-a),var(--beam-b));
  transition:transform .24s cubic-bezier(.2,.7,.3,1);
}
.post-row > p{font-size:0.9688rem;color:var(--slate);max-width:60ch}
@media (hover:hover) and (pointer:fine){
  .post-row h2 a:hover{color:var(--accent-fg)}
  .post-row h2 a:hover::after{transform:skewX(-45deg) scaleX(1)}
}

.post-body{max-width:68ch}
.post-body p{font-size:1.0312rem;line-height:1.72;color:var(--slate)}
.post-body p + p{margin-top:17px}
.post-body ul{margin:17px 0;padding-left:1.25em;font-size:1.0312rem;line-height:1.72;color:var(--slate)}
.post-body li + li{margin-top:10px}
.post-body li::marker{color:var(--accent-fg)}
.post-body b{color:var(--fg);font-weight:600}
.post-body h2{
  font-size:clamp(1.25rem,2.4vw,1.5rem);letter-spacing:-.01em;color:var(--fg);
  margin:38px 0 14px;
}
.post-body a,.post-sources a{color:var(--accent-fg);text-decoration:underline;text-underline-offset:2px}

/* Cited sources, set apart and smaller. */
.post-sources{max-width:68ch;margin-top:44px;padding-top:24px;border-top:1px solid var(--line)}
.post-sources h2{
  font-family:'JetBrains Mono',monospace;font-size:0.75rem;letter-spacing:.1em;
  text-transform:uppercase;color:var(--slate);margin-bottom:14px;
}
.post-sources ul{list-style:none;display:grid;gap:11px}
.post-sources li{font-size:0.8438rem;line-height:1.55;color:var(--slate)}
.post-sources li a{color:var(--slate)}
.post-sources li a:hover{color:var(--accent-fg)}

/* ---------------- capability list ---------------- */
.cap-list{border-top:1px solid var(--line)}
.cap{padding:23px 0;border-bottom:1px solid var(--line)}
.cap h3{font-size:1.1562rem;margin-bottom:7px}
.cap p{font-size:0.9375rem;color:var(--slate);max-width:66ch}
/* A note under the paragraph, in the page colour, says what that item is as
   a thing where the service is a document: how long, what each line carries. */
.cap-note{font-size:0.9375rem;color:var(--fg);margin-top:8px;max-width:66ch}
.page-hero .hero-alt{margin-top:8px}

/* ---------------- story-shaped service page ---------------- */
/* The second service-page shape (see CLAUDE.md, "Two service-page shapes").
   Section order borrowed from exadel.com's service pages; everything in it
   is this site's own component language: cut corners, hairlines, the type
   scale, the figure, real photographs with no people in them. */
.st-hero{display:grid;grid-template-columns:minmax(0,7fr) minmax(0,5fr);gap:48px 72px;align-items:end;padding:36px 0 40px}
.st-hero .deck{font-family:'Chakra Petch',sans-serif;font-weight:600;font-size:1.875rem;line-height:1.3;letter-spacing:-.014em;color:var(--slate);max-width:22ch;margin:0 0 26px}
.st-hero .hero-alt{margin-top:0}
.st-fig{border-top:1px solid var(--line);padding-top:22px;max-width:40ch}
.st-fig .svc-stat-figure{margin-bottom:12px}
.st-intro{display:grid;grid-template-columns:minmax(0,6fr) minmax(0,6fr);gap:48px 72px;align-items:center;padding:0 0 96px;border-bottom:1px solid var(--line)}
.st-intro h2{max-width:16ch}
.st-intro .lede+.lede{margin-top:18px}
.st-pic{aspect-ratio:5/4;overflow:hidden;clip-path:polygon(0 0,calc(100% - var(--cut-lg)) 0,100% var(--cut-lg),100% 100%,0 100%)}
.st-pic img,.st-cta-pic img{width:100%;height:100%;object-fit:cover;display:block}
.st-head{padding:96px 0 40px}
.st-head h2{max-width:22ch}
.st-head .lede{max-width:58ch;margin-top:8px}
.st-rows{border-top:1px solid var(--line)}
.st-row{display:grid;grid-template-columns:290px minmax(0,1fr);gap:8px 64px;padding:30px 0 32px;border-bottom:1px solid var(--line)}
.st-row h3{margin:0}
.st-row p{font-size:0.9375rem;color:var(--slate);max-width:60ch;margin:0}
.st-row .cap-note{color:var(--fg);margin-top:8px}
.st-quote{text-align:center;padding:26px 0 30px}
.st-quote-l{font-family:'Chakra Petch',sans-serif;font-weight:600;font-size:clamp(1.75rem,3.6vw,2.625rem);line-height:1.3;letter-spacing:-.016em;color:#fff;max-width:22ch;margin:0 auto}
.st-quote-s{color:var(--slate-d);font-size:1.0625rem;max-width:48ch;margin:22px auto 0}
.st-why{display:grid;grid-template-columns:290px minmax(0,1fr);gap:40px 64px;padding:96px 0;align-items:start}
.st-why .btn{margin-top:22px}
.st-big{font-family:'Chakra Petch',sans-serif;font-weight:600;font-size:1.875rem;line-height:1.3;letter-spacing:-.014em;color:var(--slate);max-width:26ch;margin:0 0 44px}
.st-big b{color:var(--fg);font-weight:600}
.st-four{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:30px 40px;max-width:60ch}
.st-four p{font-size:0.9375rem;color:var(--slate);margin:0}
.st-four b{display:block;color:var(--fg);font-weight:600;margin-bottom:4px}
.st-faq{padding:0 0 96px}
.st-cta{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:16px;padding:0 0 96px}
.st-cta-pic{min-height:440px;aspect-ratio:1;overflow:hidden;clip-path:polygon(0 0,calc(100% - var(--cut-lg)) 0,100% var(--cut-lg),100% 100%,0 100%)}
.st-cta-card{background:var(--card);padding:60px 56px;clip-path:polygon(0 0,calc(100% - var(--cut-lg)) 0,100% var(--cut-lg),100% 100%,0 100%);display:flex;flex-direction:column;justify-content:center}
.st-cta-card h2{max-width:14ch}
.st-cta-card .lede{max-width:44ch;margin:8px 0 26px}
@media(max-width:820px){
  .st-hero,.st-intro,.st-why,.st-cta{grid-template-columns:1fr}
  .st-hero{padding:20px 0 56px;align-items:start}
  .st-row{grid-template-columns:1fr;gap:6px}
  .st-four{grid-template-columns:1fr}
  .st-cta-pic{min-height:260px;aspect-ratio:auto}
  .st-cta-card{padding:40px 24px}
}

/* ---------------- stack shape ----------------
   Third service-page shape (2026-09-10), a one-to-one layout of the solution
   pages on exadel.com, on this site's faces and tokens: the hook alone as the
   hero; a statement with four pain points in the right half; a photo band
   with the figure; three numbered cards that pin and stack on scroll; the
   other services as photo rows; the questions; a photo beside a paper card.
   Their sizes are kept (48 display, 40 statement, 28 rows, 18 bold, 96
   numeral, 16px inset on the bleeds) so the two pages measure the same;
   corners are cut with the tokens where they use radii. See CLAUDE.md. */
.x-hero{padding:126px 0 118px}
.x-hero h1{font-size:3rem;line-height:1.1;max-width:26ch;margin:22px 0 0}
.x-why{padding:0 0 128px}
.x-state{
  font-family:'Chakra Petch',sans-serif;font-weight:600;font-size:2.5rem;line-height:1.25;
  letter-spacing:-.014em;max-width:30ch;margin:0 0 70px;text-wrap:balance;
}
.x-pains{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:32px}
.x-pain-grid{grid-column:2;display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:56px 32px}
.x-pain svg{width:48px;height:48px;display:block;color:var(--fg);margin-bottom:40px}
.x-b{font-weight:600;font-size:1.125rem;line-height:1.25;margin:0 0 12px;color:var(--fg)}
.x-pain p,.x-card-body p,.x-fw-head p,.x-lede{font-size:1.125rem;line-height:1.3;color:var(--slate);margin:0}
.x-pain .x-b,.x-card-body .x-b{color:var(--fg)}

/* The three bleeds are wider than the text column but not edge to edge:
   a 5.6vw inset, which is the reference's proportion (its band margin is
   about 40% of its text margin). Full width at 16px read too wide on a
   desktop and the text column read too narrow (owner, 2026-09-11). */
.x-stat,.x-stack,.x-cta{--bleed:max(var(--gut),5.6vw)}
.x-stat{padding:0 var(--bleed)}
.x-stat-in{
  position:relative;height:571px;overflow:hidden;background:var(--ink);
  clip-path:polygon(0 0,calc(100% - var(--cut-lg)) 0,100% var(--cut-lg),100% 100%,0 100%);
}
.x-stat-in picture{position:absolute;inset:0;display:block}
.x-stat-in img{position:absolute;left:0;right:0;top:-9%;width:100%;height:118%;object-fit:cover;display:block}
.x-stat-in::after{content:'';position:absolute;inset:0;background:linear-gradient(180deg,rgba(23,24,28,.04) 28%,rgba(23,24,28,.82) 100%)}
.x-stat-body{position:absolute;left:24px;right:24px;bottom:24px;z-index:1;display:flex;gap:20px;align-items:flex-end;color:#fff}
.x-stat-arrow{
  width:160px;height:160px;flex:none;display:grid;place-items:center;color:#fff;
  background:rgba(255,255,255,.14);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);
  clip-path:polygon(var(--cut-sm) 0,100% 0,100% calc(100% - var(--cut-sm)),calc(100% - var(--cut-sm)) 100%,0 100%,0 var(--cut-sm));
}
.x-stat-arrow svg{width:64px;height:64px}
.x-stat-n{
  font-family:'Chakra Petch',sans-serif;font-weight:700;font-size:3.5rem;line-height:1;
  letter-spacing:-.035em;color:#fff;margin:0 0 6px;
}
.x-stat-line{font-size:1rem;line-height:1.25;color:#fff;max-width:26ch;margin:0}
.x-stat .svc-stat-src,.x-stat .svc-stat-src a{color:rgba(255,255,255,.72)}
.x-stat .svc-stat-src a:hover{color:#fff}

.x-fw{padding:120px 0 0}
.x-fw-head{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:32px 48px;align-items:center;margin-bottom:96px}
.x-fw-head h2,.x-list h2,.x-cta-card h2{font-size:3rem;line-height:1.1;margin:0}
.x-fw-head h2{max-width:12ch}
.x-fw-head p{max-width:28ch}
.x-stack{display:grid;grid-template-columns:minmax(0,1fr);gap:48px;padding:0 var(--bleed);timeline-scope:--xs1,--xs2}
/* Each card shares its grid cell with an empty sentinel that is not sticky,
   so the sentinel's view timeline is pure layout while the card pins. */
.x-sent{grid-column:1;pointer-events:none;visibility:hidden}
.x-stack > :nth-child(1),.x-stack > :nth-child(2){grid-row:1}
.x-stack > :nth-child(3),.x-stack > :nth-child(4){grid-row:2}
.x-stack > :nth-child(5),.x-stack > :nth-child(6){grid-row:3}
.x-card{
  position:sticky;top:98px;grid-column:1;z-index:1;display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:12px;
  background:var(--card);padding:40px 16px 16px;transform-origin:50% 0;
  clip-path:polygon(0 0,calc(100% - var(--cut-lg)) 0,100% var(--cut-lg),100% 100%,0 100%);
}
.x-card-n{font-size:6rem;font-weight:400;line-height:1;letter-spacing:-.02em;margin:0;padding-left:16px;font-variant-numeric:tabular-nums;color:var(--fg)}
.x-card h3{font-size:3rem;line-height:1.1;margin:0;letter-spacing:-.014em}
.x-card-art{max-width:564px;margin-top:48px;aspect-ratio:564/318;overflow:hidden;
  clip-path:polygon(var(--cut-sm) 0,100% 0,100% calc(100% - var(--cut-sm)),calc(100% - var(--cut-sm)) 100%,0 100%,0 var(--cut-sm))}
.x-card-art img{width:100%;height:100%;object-fit:cover;display:block}
.x-card-body{max-width:380px;margin-top:48px}
.x-card-body .x-b{margin-bottom:18px}
/* Cards pin under the header and each new card slides over the one before
   it, which shrinks to .85 and fades as it is covered, on the next card's
   view timeline. Runs on the compositor; browsers without view timelines
   stack the cards and skip the fade. The range is measured: it opens as the
   next card's top passes the pinned card's bottom and closes as it lands. */
@supports (animation-timeline:view()) and (timeline-scope:--a) and (animation-range:exit calc(100% + 48px)){
  /* The sentinel's scrollport is inset to the pin line, so "exit 0%" is the
     moment the card pins. The fade runs from one gap past that (the next
     card's top meets this card's bottom) to one card height plus the gap
     later (the next card lands), whatever the viewport height. */
  .x-sent{view-timeline-inset:98px 0}
  .x-stack > :nth-child(1){view-timeline-name:--xs1}
  .x-stack > :nth-child(3){view-timeline-name:--xs2}
  .x-stack > :nth-child(2){animation:x-sink linear both;animation-timeline:--xs1;animation-range:exit 48px exit calc(100% + 48px)}
  .x-stack > :nth-child(4){animation:x-sink linear both;animation-timeline:--xs2;animation-range:exit 48px exit calc(100% + 48px)}
  .x-stat-in img{animation:x-par linear both;animation-timeline:view();animation-range:entry 0% exit 100%}
}
@keyframes x-sink{to{transform:scale(.85);opacity:0}}
@keyframes x-par{from{transform:translateY(-7%)}to{transform:translateY(7%)}}

.x-list{padding:120px 0 0}
.x-list h2{max-width:14ch}
.x-lede{max-width:44ch;margin:24px 0 64px}
.x-rows{list-style:none;margin:0;padding:0;border-top:1px solid var(--line)}
.x-rows li{border-bottom:1px solid var(--line)}
.x-rows a{
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:16px 48px;
  padding:16px 0;text-decoration:none;color:inherit;align-items:start;
}
.x-rows picture{display:block;width:216px;aspect-ratio:216/265;overflow:hidden;
  clip-path:polygon(var(--cut-sm) 0,100% 0,100% calc(100% - var(--cut-sm)),calc(100% - var(--cut-sm)) 100%,0 100%,0 var(--cut-sm))}
.x-rows img{width:100%;height:100%;object-fit:cover;display:block}
.x-row-text{padding-top:8px;max-width:444px}
.x-rows h3{font-size:1.75rem;line-height:1.1;margin:0}
.x-rows p{
  font-family:'Chakra Petch',sans-serif;font-weight:600;font-size:1.75rem;line-height:1.1;
  letter-spacing:-.006em;color:var(--slate);margin:0 0 22px;
}
.x-more{display:inline-flex;align-items:center;gap:8px;font-size:0.9375rem;font-weight:600;padding:8px 8px 8px 0;color:var(--fg)}
.x-more svg{width:14px;height:10px;flex:none;transition:transform .24s cubic-bezier(.2,.7,.3,1)}
.x-rows a:hover .x-more svg{transform:translateX(4px)}
.x-rows a:focus-visible{outline-offset:6px}

.x-faq{padding:120px 0 0}
.x-cta{padding:120px var(--bleed) 16px}
.x-cta-in{display:grid;grid-template-columns:675fr 717fr;gap:16px;min-height:760px}
.x-cta-pic{position:relative;overflow:hidden;
  clip-path:polygon(0 0,calc(100% - var(--cut-lg)) 0,100% var(--cut-lg),100% 100%,0 100%)}
.x-cta-pic picture,.x-cta-pic img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}
.x-cta-card{
  background:var(--card);padding:80px;display:flex;flex-direction:column;justify-content:center;gap:32px;
  clip-path:polygon(0 0,calc(100% - var(--cut-lg)) 0,100% var(--cut-lg),100% 100%,0 100%);
}
.x-cta-card h2{max-width:14ch}

/* Headings marked [data-lines] are split by site.js into their rendered
   lines; each line sits in an overflow-hidden mask and rises into place.
   The hidden class is set by the script, never here, for the reason .rv
   gives. The mask keeps .14em under the line so descenders survive it. */
.lm{display:block;overflow:hidden;padding-bottom:.14em;margin-bottom:-.14em}
.ln{display:block;transition:transform .9s cubic-bezier(.2,.7,.2,1);transition-delay:calc(var(--i,0)*.09s)}
.lines-hide .ln{transform:translateY(112%);transition:none}
/* a reveal that fades without the 16px rise, for body copy next to a heading that rises */
.rv.fade.rv-hide{transform:none}

@media (prefers-reduced-motion:reduce){
  .x-card,.x-stat-in img{animation:none!important}
}
@media(max-width:820px){
  .x-hero{padding:40px 0 64px}
  .x-hero h1,.x-fw-head h2,.x-list h2,.x-cta-card h2,.x-card h3{font-size:2.125rem}
  .x-state{font-size:1.625rem;margin-bottom:44px}
  .x-why{padding-bottom:72px}
  .x-pains{grid-template-columns:1fr}
  .x-pain-grid{grid-column:1;grid-template-columns:1fr;gap:40px}
  .x-pain svg{margin-bottom:22px}
  .x-stat-in{height:auto;min-height:540px}
  .x-stat-body{flex-direction:column;align-items:flex-start;left:16px;right:16px;bottom:16px}
  .x-stat-arrow{width:96px;height:96px}
  .x-stat-arrow svg{width:40px;height:40px}
  .x-fw,.x-list,.x-faq,.x-cta{padding-top:72px}
  .x-fw-head{grid-template-columns:1fr;margin-bottom:48px}
  .x-card{grid-template-columns:1fr;top:90px;padding:24px 16px 16px;gap:8px}
  .x-sent{view-timeline-inset:90px 0}
  .x-card-n{font-size:4rem;padding-left:0}
  .x-card-art,.x-card-body{margin-top:24px}
  .x-lede{margin-bottom:40px}
  .x-rows a{grid-template-columns:104px minmax(0,1fr);gap:20px}
  .x-rows picture{width:104px}
  .x-row-text{padding-top:0}
  .x-rows h3,.x-rows p{font-size:1.25rem}
  .x-rows p{margin-bottom:12px}
  .x-cta-in{grid-template-columns:1fr;min-height:0}
  .x-cta-pic{min-height:280px}
  .x-cta-card{padding:40px 24px}
}

/* ---------------- outcomes ---------------- */
.gains{list-style:none;display:grid;gap:14px}
.gains li{font-size:0.9688rem;color:var(--slate);padding-left:22px;position:relative;max-width:66ch}
.gains li::before{
  content:'';position:absolute;left:0;top:9px;width:10px;height:10px;
  background:var(--fg);clip-path:polygon(0 0,100% 0,100% 100%);
}
.gains b{color:var(--fg);font-weight:600}
.slab .gains li{color:var(--slate-d)}
.slab .gains li::before{background:#fff}
.slab .gains b{color:#fff}

/* ---------------- tech chips ---------------- */
.chips{list-style:none;display:flex;flex-wrap:wrap;gap:9px}
/* ---------------- projects on /work ----------------
   A project is a stack of small scannable units rather than three dense
   paragraphs, which is what this was first and which nobody read. Everything
   reuses an existing component where one exists: .svc-stat for the figure,
   .gains for the lists, .how-grid for the numbered steps, .chips for the
   stack. Only the wrapper, the before/after pair and the difficulty card are
   new. Someone who reads only the subheads should still get the story. */
/* The first project needs real air under the section head. This margin
   collapses against .band-head's 52px, so it has to exceed it to have any
   effect. Later projects get a rule between them instead. */
.proj{margin-top:76px}
.proj + .proj{margin-top:88px;padding-top:80px;border-top:1px solid var(--line)}
.proj-head h2{margin:0 0 10px}
.proj-tag{font-size:0.9062rem;color:var(--slate);margin-bottom:34px}
.proj-tag a{color:var(--fg);text-decoration:underline;text-underline-offset:2px}
.proj-sec{margin-top:46px}
.proj-sec h3{font-size:1.1562rem;margin:0 0 20px}

.proj-ba{display:grid;grid-template-columns:repeat(auto-fit,minmax(258px,1fr));gap:32px 54px}
.proj-ba h4{font-size:1rem;margin-bottom:12px}
.proj-ba .gains li{font-size:0.9375rem}

/* The difficulty note is the most credible thing on the page, so it is set
   apart as a card rather than left as a closing paragraph nobody reaches.
   Cut corners, never a radius, like every other box on the site. */
.proj-hard{
  margin-top:46px;background:var(--card);padding:28px 30px 30px;
  clip-path:polygon(var(--cut-lg) 0,100% 0,100% calc(100% - var(--cut-lg)),calc(100% - var(--cut-lg)) 100%,0 100%,0 var(--cut-lg));
}
.proj-hard h3{font-size:1.1562rem;margin:0 0 14px}
.proj-hard p{font-size:0.9688rem;line-height:1.65;color:var(--slate);max-width:70ch}
.proj-note{margin-top:26px;font-size:0.8438rem;color:var(--slate)}
.chips li{
  font-family:'JetBrains Mono',monospace;font-size:0.75rem;letter-spacing:.05em;
  color:var(--slate);background:var(--card);border:1px solid var(--line);padding:8px 14px;
  clip-path:polygon(var(--cut-sm) 0,100% 0,100% calc(100% - var(--cut-sm)),calc(100% - var(--cut-sm)) 100%,0 100%,0 var(--cut-sm));
}

/* ---------------- sub-heads inside a detail column ---------------- */
.sub{
  font-family:'JetBrains Mono',monospace;font-size:0.7188rem;letter-spacing:.14em;
  text-transform:uppercase;color:var(--slate);margin-bottom:18px;
  display:flex;align-items:center;gap:11px;
}
.sub::before{content:'';width:20px;height:2px;background:currentColor;transform:skewX(-45deg);flex:none}
.block+.block{margin-top:56px}

/* ---------------- scope ---------------- */
.scope{
  background:var(--card);border:1px solid var(--line);padding:40px 36px 38px;
  clip-path:polygon(0 0,calc(100% - var(--cut-lg)) 0,100% var(--cut-lg),100% 100%,0 100%);
}
.scope h2{margin-top:14px}
.scope ul{list-style:none;display:flex;flex-wrap:wrap;gap:10px;margin:24px 0}
.scope li{
  font-family:'JetBrains Mono',monospace;font-size:0.7812rem;color:var(--slate);
  border:1px dashed var(--line);padding:8px 14px;
}
.scope p{font-size:0.9688rem;color:var(--slate);max-width:64ch}

/* ---------------- related ---------------- */

/* A slab running straight into the closing CTA left a thin light zigzag
   between two dark sections, which reads as a seam rather than a join. Drop
   the cuts between them so they render as one dark region. */
.slab.flush{clip-path:polygon(0 var(--cut-band),100% 0,100% 100%,0 100%);padding-bottom:112px}
.slab.flush + .cta{clip-path:none;padding-top:0}

/* ---------------- cta band ---------------- */
.cta{
  background:var(--band);color:#E7E8EC;padding:100px 0 104px;
  clip-path:polygon(0 var(--cut-band),100% 0,100% 100%,0 100%);
}
.cta h2{color:#fff;max-width:18ch}
.cta .lede{color:var(--slate-d)}
.cta-row{display:flex;gap:13px;flex-wrap:wrap;margin-top:34px}

/* The quiet close, for editorial pages. Deliberately not a band: one rule,
   one line, no buttons. The weight difference between this and .cta is the
   point - a reader should feel which pages are selling and which are not. */
.cta-quiet{padding:52px 0 68px;border-top:1px solid var(--line)}
.cta-quiet p{font-size:1.0625rem;color:var(--slate);max-width:60ch}
.cta-quiet a{color:var(--fg);text-decoration:underline;text-underline-offset:3px}
.cta-quiet a:hover{color:var(--accent-fg)}
.btn-outline{background:transparent;color:#fff;box-shadow:inset 0 0 0 1.5px rgba(255,255,255,.9)}
.btn-outline:hover{background:rgba(255,255,255,.07);box-shadow:inset 0 0 0 1.5px #fff}


/* ================================================================
   HOVER MOTION
   Anything that links somewhere gets the same gesture: a skewed accent
   rule drawing in, and a small nudge in the direction of travel.

   The rule is a block-level ::after in normal flow rather than an
   absolutely positioned one. Positioning it would make its heading a
   containing block, which would collapse the stretched ::before overlay
   that makes a whole card clickable down to just the heading.

   Resting styles and the current-page indicator live outside the pointer
   query. Only the :hover rules are gated, so a tap on a touch screen does
   not leave an element stuck in its hover state.
   ================================================================ */

/* ---------------- resting styles ---------------- */
/* The accent rules under titles and beside links draw in on hover by scaling
   a fixed-width box from the left, never by animating width: width is a
   layout property and every frame relaid out the line it sat in. */
.site-nav a{position:relative;display:inline-block;padding:12px 0}
.site-nav a::after{
  content:'';position:absolute;left:0;right:100%;bottom:5px;height:2px;
  background:linear-gradient(90deg,var(--beam-a),var(--beam-b));
  transform:skewX(-45deg);
  transition:right .26s cubic-bezier(.2,.7,.3,1);
}
.site-nav a[aria-current]::after{right:0}
.foot-cols a{display:inline-block;transition:color .16s ease,transform .18s ease}
.crumb a{transition:color .16s ease}
.direct a{transition:color .18s ease,transform .18s ease}
.direct a::before{transition:transform .24s cubic-bezier(.2,.7,.3,1),background .18s ease}
summary{transition:color .18s ease}
summary::after{transition:transform .2s ease,border-color .18s ease}
.btn{transition:background .16s ease,color .16s ease,transform .16s ease,box-shadow .16s ease}
.slab a:not(.btn){transition:color .18s ease,border-color .18s ease}

/* Type sizes are rem, spacing is px. A rem size follows the browser's own
   text-size setting (a 20px default makes 1rem twenty pixels), which px
   never did; the values are the same pixels at the default 16px root.
   Spacing stays in px on purpose: the section rhythm and the cut geometry
   are tuned to the pixel and should not swell with the text. */

/* ---------------- theme switch ---------------- */
/* Only while the toggle is flipping. site.js puts .theming on <html> for
   260ms around the attribute change, so every surface and every colour eases
   between themes instead of jumping a full brightness step in one frame. It
   must come off again, or every hover on the site inherits this duration. */
html.theming,html.theming *,html.theming *::before,html.theming *::after{
  transition:background-color .22s ease,color .22s ease,border-color .22s ease,box-shadow .22s ease;
}

/* ---------------- press, every pointer ---------------- */
/* Feedback on pointer-down, not on release, and not gated by pointer type:
   the hover block below is switched off for touch, which left a phone visitor
   with nothing at all between the tap and the page changing. Boxes shrink a
   hair; text links dim. 80ms in, so it reads as the press itself. The tap
   highlight iOS paints by default is a grey rectangle that ignores the cut
   corners, so it goes now that there is real feedback in its place. */
body{-webkit-tap-highlight-color:transparent}
.btn:active,.theme-toggle:active,.to-top:active,.faq-all:active,
.mnav-toggle:active,.svc-caret:active,.svc-panel a:active{
  transform:scale(.975);transition-duration:80ms;
}
.btn:active{transform:translateY(0) scale(.975)}
summary:active,.mnav a:active,.site-nav a:active,.hero-alt:active,.foot-cols a:active,.direct a:active,
.crumb a:active,.post-row h2 a:active,.post-body a:active,.cta-quiet a:active,.svc-stat-src a:active,
.svc-ex a:active,.svc-next a:active{
  opacity:.6;transition-duration:80ms;
}
/* movement goes under reduced motion, the acknowledgement stays */
@media (prefers-reduced-motion:reduce){
  .btn:active,.theme-toggle:active,.to-top:active,.faq-all:active,
  .mnav-toggle:active,.svc-caret:active,.svc-panel a:active{transform:none;opacity:.6}
}

/* ---------------- hover, pointer devices only ---------------- */
@media (hover:hover) and (pointer:fine){
  /* The row's rule grows inside its own box, the same gesture as .direct;
     nothing lifts, nothing moves under the cursor. */
  .svc-ex a:hover h2::after{transform:skewX(-45deg) scaleX(1.6875)}
  .svc-ex a:hover h2{color:var(--accent-fg)}


  .site-nav a:hover::after{right:0}
  .foot-cols a:hover{transform:translateX(4px)}
  .direct a:hover{transform:translateX(3px)}
  .direct a:hover::before{transform:skewX(-45deg) scaleX(1.6875);background:linear-gradient(90deg,var(--beam-a),var(--beam-b))}

  summary:hover{color:var(--accent-fg)}
  summary:hover::after{border-color:var(--accent-fg)}

  .btn:hover{transform:translateY(-2px)}
  .hero-alt:hover svg{transform:translateX(5px)}
  .btn-dark:hover{box-shadow:0 8px 18px rgba(23,24,28,.22)}
  .btn-lite:hover{box-shadow:0 8px 18px rgba(0,0,0,.28)}

  .foot-cols h3 a:hover{color:#fff}
}

/* ---------------- ambient texture on the dark bands ---------------- */
/* Applied last on purpose. Each of these sets `background` as a shorthand,
   which resets background-image to none, so an earlier rule would be wiped.

   Deliberately not on the beam and not on any light surface. The wedge that
   used to carry hatching wore it at .09, which on these grounds would put
   footer headings under AA.

   .slab.flush + .cta render as one dark region, and the pattern phase restarts
   at that join because each element origins its own background. At this
   contrast the step is not visible; if the opacity is ever raised, check that
   seam on /how-we-work first.

   The footer is deliberately excluded and stays a plain surface. It is already
   a different ground from these bands, --ink-2 rather than --ink, and it is the
   one dark area dense with small text. */
.slab,.contact,.cta{background-image:var(--dark-tex)}

/* ================= dark theme ================= */
/* Two ways in. The media query follows the operating system, and is skipped
   when the visitor has explicitly chosen light. The attribute selector is the
   header toggle, which wins in both directions. site.js writes data-theme;
   an inline script in every <head> applies it before first paint so the page
   never flashes the wrong theme.

   With no stored choice and no JavaScript, the media query still works on its
   own, so the theme degrades to following the system rather than to nothing.

   The site alternates light and dark bands down every page, and the angled
   --cut-band edges are visible only because of that tonal contrast. In light
   mode that edge is 15.28:1; two dark tones cannot reproduce it. So the
   relationship is inverted rather than copied: the page ground becomes the
   darkest surface and the bands lift above it, which is how raised surfaces
   read in a dark interface, and the separation is carried by tone: 1.40:1,
   widened from 1.22:1 when the hairline below was taken out.

   A hairline drawn with filter: drop-shadow() was tried here and removed. It
   traced the clipped shape correctly, but a filter is re-rasterized whenever
   anything inside the filtered element changes, and these bands hold 22 .rv
   reveal elements across the site while main runs page-in above them. Every
   frame of a scroll reveal or a page fade repainted two drop-shadows across a
   full-width band, which made dark mode visibly less smooth than light.

   **Do not put a filter on .slab, .contact or .cta.** If the cut ever needs
   more definition, buy it with tone or a pseudo-element, never a filter on a
   section that wraps animated content. */
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]){
    color-scheme:dark;
    --paper:var(--dk-paper);        --card:var(--dk-card);
    --band:var(--dk-band);          --foot-bg:var(--dk-foot);
    --field-bg:var(--dk-field);     --fg:var(--dk-fg);
    --slate:var(--dk-slate);        --slate-d:var(--dk-slate-d);
    --line:var(--dk-line);          --ink-3:var(--dk-ink-3);
    --field-line:var(--dk-field-line);
    --invert-bg:var(--dk-invert-bg);   --invert-fg:var(--dk-invert-fg);
    --invert-bg-h:var(--dk-invert-bg-h);
    --accent-fg:var(--dk-accent-fg);
    --head-bg:var(--dk-head-bg);       --head-frost:var(--dk-head-frost);
    --panel-bg:var(--dk-panel-bg);     --panel-frost:var(--dk-panel-frost);
    --shadow:var(--dk-shadow);         --shadow-lift:var(--dk-shadow-lift);
    --shadow-panel:var(--dk-shadow-panel);
    --icon-sun:var(--dk-icon-sun); --icon-moon:var(--dk-icon-moon);
    --hero-scrim:var(--dk-hero-scrim); --hero-scrim-m:var(--dk-hero-scrim-m);
  }
}
:root[data-theme="dark"]{
    color-scheme:dark;
    --paper:var(--dk-paper);        --card:var(--dk-card);
    --band:var(--dk-band);          --foot-bg:var(--dk-foot);
    --field-bg:var(--dk-field);     --fg:var(--dk-fg);
    --slate:var(--dk-slate);        --slate-d:var(--dk-slate-d);
    --line:var(--dk-line);          --ink-3:var(--dk-ink-3);
    --field-line:var(--dk-field-line);
    --invert-bg:var(--dk-invert-bg);   --invert-fg:var(--dk-invert-fg);
    --invert-bg-h:var(--dk-invert-bg-h);
    --accent-fg:var(--dk-accent-fg);
    --head-bg:var(--dk-head-bg);       --head-frost:var(--dk-head-frost);
    --panel-bg:var(--dk-panel-bg);     --panel-frost:var(--dk-panel-frost);
    --shadow:var(--dk-shadow);         --shadow-lift:var(--dk-shadow-lift);
    --shadow-panel:var(--dk-shadow-panel);
    --icon-sun:var(--dk-icon-sun); --icon-moon:var(--dk-icon-moon);
    --hero-scrim:var(--dk-hero-scrim); --hero-scrim-m:var(--dk-hero-scrim-m);
}

/* ---------------- theme toggle ---------------- */
/* Which glyph shows is driven by --icon-sun / --icon-moon, which the dark
   remap flips. That keeps the rule single-sourced: it works for the system
   media query and the manual override without being written twice. The button
   offers the theme you would move to, so light shows a moon. */
.theme-toggle{
  display:inline-flex;align-items:center;justify-content:center;
  width:44px;height:44px;flex:none;padding:0;
  background:transparent;border:none;cursor:pointer;color:var(--slate);
  box-shadow:inset 0 0 0 1.5px var(--line);
  clip-path:polygon(var(--cut-sm) 0,100% 0,100% calc(100% - var(--cut-sm)),calc(100% - var(--cut-sm)) 100%,0 100%,0 var(--cut-sm));
  transition:color .16s ease,box-shadow .16s ease;
}
.theme-toggle svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:1.9}
.theme-toggle .ico-moon{fill:currentColor;stroke:none}
.theme-toggle .ico-sun{display:var(--icon-sun)}
.theme-toggle .ico-moon{display:var(--icon-moon)}

/* Below the nav breakpoint the header has no room, so the control moves into
   the panel and takes the shape of the rows around it. */
/* Settles in after the staggered nav links, the same delay .mnav-direct used
   to carry before the direct contact links were dropped as redundant with the
   Contact link above them: Contact already covers this, and .mnav-theme is
   now the panel's last item. */
.mnav-theme{
  display:none;
  opacity:0;transform:translateY(10px);
  transition:opacity .3s ease,transform .34s cubic-bezier(.2,.7,.3,1);
}
.site-head.menu-open .mnav-theme{opacity:1;transform:none;transition-delay:.2s}
@media(max-width:820px){
  .head-actions .theme-toggle{display:none}
  .mnav-theme{display:block}
}
/* On the homepage only, the hero repeats "Get in touch" a scroll below the
   header, so the header copy is redundant on the one screen small enough to
   show both without scrolling. The header button is dropped on mobile and the
   theme toggle takes its place, rather than moving to the panel: there is
   nothing left in .head-actions to share the row with the hamburger.
   .home is a marker on <body>, set by hand in index.html since it is not
   generated. Every other page keeps the header CTA, because its hero carries
   no button of its own and the header is the only route to contact without
   scrolling. */
@media(max-width:820px){
}
.theme-toggle-panel{
  width:100%;height:auto;justify-content:flex-start;gap:12px;
  /* The nav rows above carry a zero-width ::before marker plus a 12px gap, so
     their text starts 12px in. Match it or this row sits proud of the list. */
  padding:14px 0 14px 12px;box-shadow:none;clip-path:none;
  border-bottom:1px solid var(--line);color:var(--fg);
  font-family:'Chakra Petch',sans-serif;font-weight:600;font-size:1.0625rem;
  line-height:1.2;text-transform:uppercase;letter-spacing:.06em;
}
@media (hover:hover) and (pointer:fine){
  .theme-toggle:hover{color:var(--fg);box-shadow:inset 0 0 0 1.5px var(--fg)}
  .theme-toggle-panel:hover{box-shadow:none;color:var(--accent-fg)}
}

/* ============ user preference: transparency and contrast ============ */
/* Both come straight from the apple-design skill's accessibility section, and
   both are the same shape of promise as prefers-reduced-motion: the setting
   exists because the effect causes that person a problem, so the effect has to
   actually go, not soften. */

/* Someone who has asked their system to reduce transparency should not get a
   blurred bar over moving content. The frost is dropped and the surfaces go
   fully opaque, which is also what a browser without backdrop-filter already
   falls back to, so this path is well trodden. */
@media (prefers-reduced-transparency:reduce){
  .site-head .row,
  .site-head.lifted .row,
  .mnav,
  .svc-panel{
    -webkit-backdrop-filter:none;
    backdrop-filter:none;
  }
  .site-head .row,.site-head.lifted .row,
  body.home .site-head:not(.lifted) .row{background:var(--card)}
  .mnav,.svc-panel{background:var(--card)}
}

/* prefers-contrast: more asks for defined edges rather than implied ones, so
   every surface that relied on a shadow or a faint fill to separate it gets a
   real border, and the two dimmest text colours step up. --fg and --band-fg
   are already past 12:1 and do not move. */
@media (prefers-contrast:more){
  :root{
    --slate:#3E424E;        /* body on light: 5.57 -> 8.64 */
    --line:#7B808C;         /* 3.41:1 on paper and 3.96:1 on card, so a
                               divider is a real edge rather than an implied
                               one. The default #D9DADF is 1.20:1, which is
                               fine for decoration and not for this mode. */
    --field-line:#A8ACB8;
  }
  .site-head .row,.mnav,.svc-panel{box-shadow:inset 0 0 0 1.5px var(--fg)}
  .how-item{border-color:var(--fg)}
  .svc-ex li{border-color:var(--fg)}
  .btn-ghost{box-shadow:inset 0 0 0 2px var(--fg)}
  .btn-outline{box-shadow:inset 0 0 0 2px #fff}
  .theme-toggle{box-shadow:inset 0 0 0 2px var(--fg)}
  .foot-cols h3{color:#C7C9D2}
  .slab .lede,.contact .lede,.cta .lede{color:#D2D5DC}
}

/* ---------------- faq: expand all ---------------- */
/* Sits above the groups, in the mono label register, so it reads
   as a control rather than another question. Hidden in the markup and
   unhidden by site.js: with JavaScript off every answer is already open, so
   the control would have nothing to do. */
.faq-controls{display:flex;justify-content:flex-end;margin:0 0 26px}
.faq-all{
  font-family:'JetBrains Mono',monospace;font-size:0.7188rem;font-weight:500;
  letter-spacing:.12em;text-transform:uppercase;
  color:var(--slate);background:transparent;border:none;cursor:pointer;
  padding:9px 14px;
  box-shadow:inset 0 0 0 1.5px var(--line);
  clip-path:polygon(var(--cut-sm) 0,100% 0,100% calc(100% - var(--cut-sm)),calc(100% - var(--cut-sm)) 100%,0 100%,0 var(--cut-sm));
  transition:color .16s ease,box-shadow .16s ease;
}
.faq-all[hidden]{display:none}
@media (hover:hover) and (pointer:fine){
  .faq-all:hover{color:var(--fg);box-shadow:inset 0 0 0 1.5px var(--fg)}
}

/* ---------------- back to top ---------------- */
/* Hidden until there is something to go back to. It takes the same shape and
   border as the theme toggle so the two read as one family of small controls,
   and it sits clear of the mobile panel, which is anchored to the top. */
.to-top{
  position:fixed;right:var(--gut);bottom:var(--gut);z-index:55;
  width:44px;height:44px;padding:0;flex:none;cursor:pointer;
  display:inline-flex;align-items:center;justify-content:center;
  background:var(--card);color:var(--fg);border:none;
  box-shadow:inset 0 0 0 1.5px var(--line);
  clip-path:polygon(var(--cut-sm) 0,100% 0,100% calc(100% - var(--cut-sm)),calc(100% - var(--cut-sm)) 100%,0 100%,0 var(--cut-sm));
  /* visibility, not just opacity, so the button leaves the tab order while it
     is invisible. Same pattern the mobile panel uses: the visibility flip is
     delayed on the way out so the fade is still seen. */
  opacity:0;visibility:hidden;transform:translateY(8px);
  transition:opacity .22s ease,transform .22s cubic-bezier(.2,.7,.3,1),
             color .16s ease,box-shadow .16s ease,visibility 0s linear .22s;
}
.to-top[hidden]{display:none}
.to-top.show{opacity:1;visibility:visible;transform:none;transition-delay:0s}
.to-top svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:2;
  stroke-linecap:round;stroke-linejoin:round}
@media (hover:hover) and (pointer:fine){
  .to-top:hover{color:var(--accent-fg);box-shadow:inset 0 0 0 1.5px var(--accent-fg)}
}
/* movement is what goes, the fade stays */
@media (prefers-reduced-motion:reduce){
  .to-top{transform:none}
  .to-top.show{transform:none}
}

/* ==================== print ==================== */
/* A service page or a policy is the kind of thing someone prints to read away
   from the screen or to put in front of somebody else. Two things matter most:
   the dark bands must not dump ink, and collapsed answers must not vanish. */
@media print{
  /* every surface goes to paper white with black text, whatever the theme */
  :root{
    --paper:#fff; --card:#fff; --band:#fff; --foot-bg:#fff; --field-bg:#fff;
    --fg:#000; --slate:#222; --slate-d:#222; --line:#bbb; --ink-3:#bbb;
    --field-line:#666; --invert-bg:#fff; --invert-fg:#000;
    --dark-tex:none;
  }
  html,body{background:#fff!important;color:#000!important}
  .slab,.contact,.cta,.site-foot,.how,.how-item{
    background:#fff!important;color:#000!important;
    background-image:none!important;box-shadow:none!important;
  }
  .slab h2,.slab h3,.contact h2,.cta h2,.site-foot *,
  .slab .lede,.contact .lede,.cta .lede{color:#000!important}

  /* screen furniture that means nothing on paper */
  .site-head,.mnav,.skip,.beam,.theme-toggle,.to-top,.hero-cta,.cta-row,
  .foot-cta,.crumb,.foot-cols{display:none!important}

  /* the angled cuts cost ink and say nothing here */
  .slab,.contact,.cta,.btn,.how-item,.site-head .row,.hero{
    clip-path:none!important;
  }

  /* the hero photograph is screen furniture too; its copy prints as plain
     black on white with the rest of the page */
  .hero-media,.hero-scrim{display:none!important}
  .hero{background:#fff!important;color:#000!important;margin-top:0!important;padding-top:0!important;min-height:0!important}
  .hero h1,.hero .lede{color:#000!important}

  /* nothing is hidden waiting for a scroll that will never happen */
  .rv,.rv-hide{opacity:1!important;transform:none!important}
  .x-card{position:static!important;opacity:1!important;transform:none!important;animation:none!important}
  .lines-hide .ln{transform:none!important}
  .x-stat-in,.x-cta-pic,.x-cta-card,.x-card{clip-path:none!important}
  main{animation:none!important}

  /* a collapsed answer would otherwise print as a heading with nothing under
     it, which is the one genuinely broken outcome on paper */
  details,html.js details{display:block!important}
  .faq-body,html.js .faq-body{
    display:block!important;grid-template-rows:none!important;opacity:1!important;
  }
  .faq-inner{overflow:visible!important}
  summary{list-style:none}
  summary::after{display:none!important}

  /* the reader cannot click, so give them the address */
  .direct a[href^="mailto:"]::after,.direct a[href^="tel:"]::after{content:""}
  main a[href^="http"]::after{content:" (" attr(href) ")";font-size:0.6875rem;color:#444}

  /* keep blocks whole where a break would be confusing */
  section{break-inside:auto;page-break-inside:auto}
  .svc-ex li,.how-item,details,.detail{break-inside:avoid;page-break-inside:avoid}
  h1,h2,h3{break-after:avoid;page-break-after:avoid}

  .wrap{max-width:none;padding:0}
  section{padding:18pt 0!important}
  body{font-size:11pt;line-height:1.45;padding-top:0!important}
  h1{font-size:22pt} h2{font-size:15pt} h3{font-size:12pt}
  @page{margin:16mm}
}

