/* ==========================================================================
   Innosano  |  built to Innosano Brand Identity Guidelines v2.0 (2 Sep 2026)
   Self contained: no external requests at runtime. The brand typeface is
   self hosted under the SIL Open Font License (fonts/42dot-Sans-OFL.txt).
   ========================================================================== */

/* Light 300 is deliberately NOT loaded. Heath ruled it out on 7 September
   2026: nothing on this site may be lighter than Regular 400. With no 300 face
   declared, a stray font-weight:300 falls back to the 400 face rather than
   quietly rendering Light. */
@font-face{font-family:"42dot Sans";src:url(fonts/42dot-sans-latin-400-normal.woff2) format("woff2");font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:"42dot Sans";src:url(fonts/42dot-sans-latin-500-normal.woff2) format("woff2");font-weight:500;font-style:normal;font-display:swap}
@font-face{font-family:"42dot Sans";src:url(fonts/42dot-sans-latin-700-normal.woff2) format("woff2");font-weight:700;font-style:normal;font-display:swap}

:root{
  /* ---- primary, from the brand guide ---------------------------------- */
  --blue:#0022FF;          /* Innosano Blue.  Key brand moments, links, actions, focus */
  --navy:#071C6B;          /* Innosano Navy.  Backgrounds and overlays */
  --olive:#568B07;         /* Accent Olive.   Highlights and key brand details */
  --near-black:#17171C;    /* Near Black.     Headings, icons, high emphasis */

  /* ---- functional ------------------------------------------------------ */
  --solar:#2F4FAF;
  --muted-blue:#8999FF;    /* on dark only */
  --amber:#FF9A00;
  --signal-red:#FD0000;

  /* ---- neutral scale --------------------------------------------------- */
  --charcoal:#2D2D2D;
  --stone:#474747;
  --pebble:#A5A5A5;
  --paper:#F5F5F5;
  --white:#FFFFFF;

  /* ---- applied roles, contrast checked against WCAG 2.1 ----------------
     On white:  Blue 7.86  Navy 15.14  Near Black 17.86  Stone 9.29
                Olive 4.12 -> large text and graphics only, never small text
     On Navy:   white 15.14  Pebble 6.14  Muted Blue 5.81                  */
  --ink:var(--near-black);
  --body:var(--stone);
  --dim:#5E5E64;
  --blue-wash:#E7EAFF;
  --blue-paper:#EDF1FF;
  --blue-line:#D8E0FF;
  --line:#E2E2E6;
  --line-dk:rgba(255,255,255,.16);
  --wash:var(--paper);
  --wash-2:#EBEBEF;
  --focus:var(--blue);
  /* The rule under the current nav item. Accent Olive by default, so the
     indicator is a brand colour rather than a new one; change it here and it
     changes in both the desktop nav and the mobile menu. */
  --nav-active-color:var(--olive);
  /* Olive, darkened, for a stroke icon on a pale olive tint: brand olive is
     4.12:1 on white and gets faint once the ground is tinted. Heath, 8 Sep
     2026: it does not have to be the exact brand colour as long as every one
     of them is the same. This is that one value. */
  --olive-ink:#4A7A06;

  --r:6px;
  --r-lg:14px;
  --maxw:1180px;
  --shadow:0 1px 2px rgba(23,23,28,.04),0 12px 40px rgba(23,23,28,.08);
  --shadow-sm:0 1px 2px rgba(23,23,28,.05);
  --ease:cubic-bezier(.2,.7,.3,1);
  --font:"42dot Sans",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Arial,sans-serif;
}

*,*::before,*::after{box-sizing:border-box}
/* THE ROOT SIZE IS THE WHOLE TYPE SCALE. Every rem on this site resolves
   against it, so this one number moves headings, labels, cards, the mocks and
   the legal pages together.

   It is a percentage, not a px value, and that is deliberate: a reader who has
   raised their browser's base font size keeps that increase, which a hard px
   value would silently discard. Terms clause 6.1 commits this site to WCAG AA,
   and overriding a reader's own accessibility setting cuts against that.

   IT WENT 16 -> 18 -> 16, AND THE ROUND TRIP IS WORTH KNOWING ABOUT. It was
   raised to 112.5% (18px) on 7 September 2026, on the reasoning that the scale
   came from Brand Identity Guidelines v2.0 and those are print sizes, so the
   whole thing was about an eighth too small for a backlit screen.

   That judgment was made on a browser sitting at 75% zoom, which nobody
   realised until 8 September. At 75%, a 16px root renders as an apparent 12px
   — genuinely too small, which is what was being seen and corrected for. At
   100% it never was. So the raise was compensating for the zoom, and once the
   figure titles had been enlarged against the same 75% view the compensation
   was doubled up. Reverted, with the two figure titles trimmed alongside.

   16px body is the browser default and the standard for long-form reading,
   which the Privacy Policy's 135 clauses are. Do not go below it: 14-15px
   belongs in dense application UI, not on a page someone reads compliance
   text on at a mine site. And if this number ever moves again, `npm run
   contrast` is not optional — the hero measures are in ch, so they move with
   it. */
html{font-size:100%;-webkit-text-size-adjust:100%;scroll-behavior:smooth}

body{
  margin:0;font-family:var(--font);font-size:1rem;font-weight:400;line-height:1.65;
  color:var(--body);background:var(--white);-webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
}

img,svg,video{max-width:100%;height:auto;display:block}
a{color:var(--blue);text-decoration:none;transition:color .15s var(--ease)}
a:hover{text-decoration:underline}
:focus-visible{outline:2.5px solid var(--focus);outline-offset:3px;border-radius:2px}

/* Brand type hierarchy: H1 Bold, H2 Medium, H3 Regular, body Regular.
   Light (300) is reserved for large lead paragraphs where the stroke stays
   legible; it is not used for dense or legal text. */
h1,h2,h3,h4{color:var(--ink);line-height:1.14;letter-spacing:-.02em;margin:0 0 .55em}
/* The ceiling was 3.7rem (59.2px) and came down to 3.25rem (52px) on sq's
   direction of 8 September 2026 — a 12% trim, still four lines on the home
   hero, so the hero block only loses 33px.

   52px still out-ranks the sub-page heroes, which is why this was the size
   chosen: .hero-sub h1 tops out at 2.95rem, 47.2px, so the home headline stays
   10% larger than every other page's.

   Changed here on the base rule rather than by adding `.hero h1`, and that is
   the point worth remembering: `.hero h1` and `.hero-sub h1` are both (0,1,1),
   so a new .hero rule placed after the sub-page one would win on source order
   and shrink every sub-page heading too. The base rule is (0,0,1) and loses to
   .hero-sub h1, so in practice it reaches only the home hero.

   The vw term is untouched: it governs below about 1127px and already scales
   down on its own. */
h1{font-size:clamp(2.3rem,4.9vw,3.25rem);font-weight:700;letter-spacing:-.032em}
h2{font-size:clamp(1.65rem,3.1vw,2.4rem);font-weight:500;letter-spacing:-.026em}
h3{font-size:1.2rem;font-weight:500;letter-spacing:-.012em}
h4{font-size:1rem;font-weight:500}
p{margin:0 0 1.15em}
strong{font-weight:500;color:var(--ink)}

.container{width:100%;max-width:var(--maxw);margin-inline:auto;padding-inline:28px}
.narrow{max-width:840px}

.skip{position:absolute;left:-9999px}
.skip:focus{left:16px;top:16px;z-index:300;background:var(--white);padding:12px 18px;border-radius:var(--r);box-shadow:var(--shadow)}

/* ---------------------------------------------------------------- header --*/
.site-header{
  position:sticky;top:0;z-index:200;background:rgba(255,255,255,.9);
  backdrop-filter:saturate(180%) blur(14px);-webkit-backdrop-filter:saturate(180%) blur(14px);
  border-bottom:1px solid var(--line);
}
.header-inner{display:flex;align-items:center;gap:30px;min-height:78px}

/* WORDMARK PLACEHOLDER. The approved logo artwork had not been issued when
   this was built, so the brand name is set in the brand typeface, lowercase,
   in Innosano Blue, as the approved wordmark is. The device mark is
   deliberately NOT recreated. Replace this whole anchor with the supplied
   SVG lockup when it arrives. See CLAUDE.md, "The logo". */
/* flex:0 0 auto on the anchor as well as the img. The anchor is itself a flex
   item of its parent, and when it shrank the global img{max-width:100%}
   clamped the logo to the shrunken box while the height stayed fixed — 177x28
   at 320px, a ratio of 6.32 against the artwork's 6.42. Both the anchor and
   the image have to refuse to shrink for the mark to stay undistorted.
   flex-wrap:wrap is the consequence of that: refusing to shrink meant the
   footer lockup, which still carries the INNOMENTE GROUP label, ran 12px past
   a 320px viewport. It now drops the label to a second line instead. */
.logo{display:flex;align-items:center;gap:11px;row-gap:4px;flex-wrap:wrap;color:var(--blue);flex:0 0 auto}
/* Height comes from --logo-h so the header can carry a larger lockup than the
   footer without a second rule per surface. The img still ships width and
   height attributes at the same ratio, so the space is reserved before the SVG
   arrives. */
/* flex:0 0 auto matters. The img is a flex item, so without it the header
   squeezes the logo at very narrow widths instead of overflowing — and since
   the height is fixed and the width automatic, squeezing distorts the artwork.
   At 320px it was rendering 177x34, a ratio of 5.2 against the lockup's 6.42.
   A brand mark must not be stretched. */
.logo img{height:var(--logo-h,24px);width:auto;max-width:none;flex:0 0 auto;display:block}
.site-header .logo{--logo-h:38px}
/* The inlined animated lockup. Sized by height like the <img> version, and
   with no width to reserve because an inline SVG has nothing to fetch — there
   is no layout shift to guard against here. */
.logo-anim{display:block;line-height:0;flex:0 0 auto}
/* ---- the animated lockup's own keyframes -------------------------------- */
/* MOVED OUT OF THE SVG ON 8 SEPTEMBER 2026, and it had to be. LogoAnimated
   inlines Heath's animated SVG so the file's prefers-reduced-motion rule can
   reach it, and the file carried its keyframes in an inline <style> element.
   The production CSP in DEPLOY.md is `style-src 'self'` with no
   'unsafe-inline', which blocks an inline stylesheet as surely as a style
   attribute — so under the real header the logo did not animate at all, on any
   of the fifteen pages, and said nothing about it. The resting state is the
   plain artwork, so it looked like a static logo rather than a broken one.

   Copied verbatim from logo-animated-blue.svg; the white file's block is
   byte-identical, checked. THE CONSEQUENCE: if Heath re-exports the animation,
   the new keyframes have to be copied here as well. `npm run check` fails on
   any inline <style> in the built output, which is what stops a fresh export
   being dropped in and quietly not animating again. */
@keyframes slide_mark {
  0% { transform: translateX(-56.60px); opacity: 0.05; }
  0.000% { transform: translateX(-56.60px); opacity: 0.05; }
  7.995% { transform: translateX(0px); opacity: 1; animation-timing-function: cubic-bezier(0.65,0,0.35,1); }
  100.000% { transform: translateX(0px); opacity: 1; }
}
.slide_mark {
  animation: slide_mark 15.010s infinite;
  animation-timing-function: cubic-bezier(0.65,0,0.35,1);
}
@keyframes slide_i {
  0% { transform: translateX(-79.70px); opacity: 0.05; }
  2.132% { transform: translateX(-79.70px); opacity: 0.05; }
  10.127% { transform: translateX(0px); opacity: 1; animation-timing-function: cubic-bezier(0.65,0,0.35,1); }
  100.000% { transform: translateX(0px); opacity: 1; }
}
.slide_i {
  animation: slide_i 15.010s infinite;
  animation-timing-function: cubic-bezier(0.65,0,0.35,1);
}
@keyframes slide_n1 {
  0% { transform: translateX(-108.40px); opacity: 0.05; }
  4.264% { transform: translateX(-108.40px); opacity: 0.05; }
  12.258% { transform: translateX(0px); opacity: 1; animation-timing-function: cubic-bezier(0.65,0,0.35,1); }
  100.000% { transform: translateX(0px); opacity: 1; }
}
.slide_n1 {
  animation: slide_n1 15.010s infinite;
  animation-timing-function: cubic-bezier(0.65,0,0.35,1);
}
@keyframes slide_n2 {
  0% { transform: translateX(-136.20px); opacity: 0.05; }
  6.396% { transform: translateX(-136.20px); opacity: 0.05; }
  14.390% { transform: translateX(0px); opacity: 1; animation-timing-function: cubic-bezier(0.65,0,0.35,1); }
  100.000% { transform: translateX(0px); opacity: 1; }
}
.slide_n2 {
  animation: slide_n2 15.010s infinite;
  animation-timing-function: cubic-bezier(0.65,0,0.35,1);
}
@keyframes slide_o1 {
  0% { transform: translateX(-166.10px); opacity: 0.05; }
  8.528% { transform: translateX(-166.10px); opacity: 0.05; }
  16.522% { transform: translateX(0px); opacity: 1; animation-timing-function: cubic-bezier(0.65,0,0.35,1); }
  100.000% { transform: translateX(0px); opacity: 1; }
}
.slide_o1 {
  animation: slide_o1 15.010s infinite;
  animation-timing-function: cubic-bezier(0.65,0,0.35,1);
}
@keyframes slide_s {
  0% { transform: translateX(-188.10px); opacity: 0.05; }
  10.660% { transform: translateX(-188.10px); opacity: 0.05; }
  18.654% { transform: translateX(0px); opacity: 1; animation-timing-function: cubic-bezier(0.65,0,0.35,1); }
  100.000% { transform: translateX(0px); opacity: 1; }
}
.slide_s {
  animation: slide_s 15.010s infinite;
  animation-timing-function: cubic-bezier(0.65,0,0.35,1);
}
@keyframes slide_a {
  0% { transform: translateX(-215.90px); opacity: 0.05; }
  12.791% { transform: translateX(-215.90px); opacity: 0.05; }
  20.786% { transform: translateX(0px); opacity: 1; animation-timing-function: cubic-bezier(0.65,0,0.35,1); }
  100.000% { transform: translateX(0px); opacity: 1; }
}
.slide_a {
  animation: slide_a 15.010s infinite;
  animation-timing-function: cubic-bezier(0.65,0,0.35,1);
}
@keyframes slide_n3 {
  0% { transform: translateX(-244.00px); opacity: 0.05; }
  14.923% { transform: translateX(-244.00px); opacity: 0.05; }
  22.918% { transform: translateX(0px); opacity: 1; animation-timing-function: cubic-bezier(0.65,0,0.35,1); }
  100.000% { transform: translateX(0px); opacity: 1; }
}
.slide_n3 {
  animation: slide_n3 15.010s infinite;
  animation-timing-function: cubic-bezier(0.65,0,0.35,1);
}
@keyframes slide_o2 {
  0% { transform: translateX(-273.90px); opacity: 0.05; }
  17.055% { transform: translateX(-273.90px); opacity: 0.05; }
  25.050% { transform: translateX(0px); opacity: 1; animation-timing-function: cubic-bezier(0.65,0,0.35,1); }
  100.000% { transform: translateX(0px); opacity: 1; }
}
.slide_o2 {
  animation: slide_o2 15.010s infinite;
  animation-timing-function: cubic-bezier(0.65,0,0.35,1);
}
@keyframes drop_accent {
  0% { transform: translateY(-24.00px); opacity: 0.05; }
  27.715% { transform: translateY(-24.00px); opacity: 0.05; }
  33.378% { transform: translateY(0px); opacity: 1; animation-timing-function: cubic-bezier(0.34,1.56,0.64,1); }
  100% { transform: translateY(0px); opacity: 1; }
}
.drop_accent {
  animation: drop_accent 15.010s infinite;
  animation-timing-function: cubic-bezier(0.34,1.56,0.64,1);
}
@media (prefers-reduced-motion: reduce) {
  [class*="slide_"], .drop_accent { animation: none !important; transform: none !important; opacity: 1 !important; }
}

.logo-anim svg{height:var(--logo-h,24px);width:auto;max-width:none;display:block}

/* No page-side logo animation any more. Heath's animated SVGs carry their own
   keyframes, their own 15.01s cycle with the hold built into the percentages,
   and their own prefers-reduced-motion rule — so the timing lives in the
   artwork where it can be edited in one line, and this stylesheet has nothing
   to say about it. The LogoMark component and its keyframes are deleted rather
   than left as a second, competing implementation. */
.logo:hover{text-decoration:none}
.logo b{font-weight:700;font-size:1.42rem;letter-spacing:-.035em;line-height:1}
.logo.on-dark{color:#fff}
.logo.on-dark .sub{color:var(--muted-blue)}
.logo .sub{font-weight:400;font-size:.68rem;color:var(--dim);letter-spacing:.16em;text-transform:uppercase;white-space:nowrap}

/* The nav links carry their padding and radius at rest, with a transparent
   background, so hovering paints a pill rather than growing the item and
   nudging its neighbours. The gap is small because the padding now supplies
   most of the spacing between labels. */
.nav{margin-left:auto;display:flex;align-items:center;gap:4px}
.nav a{
  color:var(--ink);font-size:.94rem;font-weight:400;
  padding:9px 14px;border-radius:var(--r);background:transparent;
  transition:background-color .16s var(--ease),color .16s var(--ease);
}
.nav a:hover{background:var(--blue-wash);color:var(--blue);text-decoration:none}
.nav a.cta{background:var(--blue);color:#fff;padding:10px 20px;border-radius:var(--r);font-weight:500;margin-left:10px}
.nav a.cta:hover{background:#001BCC;color:#fff}
@media (prefers-reduced-motion:reduce){.nav a{transition:none}}

/* THE CURRENT PAGE INDICATOR: a short olive rule under the label, in both the
   desktop nav and the mobile menu.
   It hangs off .nav-label rather than off the link, because the link is a
   padded pill and the rule has to span the words, not the pill. In the mobile
   menu that matters more still: those links are full-width blocks, so a rule
   drawn on the link would run the width of the panel.
   Olive measures 4.12:1 on the white header and 3.46:1 on the blue-wash hover
   pill. Both clear the 3:1 that WCAG 1.4.11 asks of a non-text indicator,
   which is the criterion that applies to a rule — not the 4.5:1 that keeps
   olive off type everywhere else on this site.
   The resting state is drawn and then scaled to nothing, so switching pages
   grows the new rule in from its centre instead of snapping it on. Every nav
   link is in the DOM at all times and only the attribute changes, so the
   transition runs on a route change and does not run on first paint. */
.nav-label{position:relative;display:inline-block}
.nav-label::after{
  content:"";position:absolute;left:0;right:0;bottom:-1px;height:3px;
  border-radius:2px;background:var(--nav-active-color);
  transform:scaleX(0);opacity:0;
  transition:transform .18s var(--ease),opacity .18s var(--ease);
}
.nav [aria-current] > .nav-label::after,
.menu .panel [aria-current] > .nav-label::after{transform:scaleX(1);opacity:1}
@media (prefers-reduced-motion:reduce){.nav-label::after{transition:none}}

/* ------------------------------------------------------ products dropdown --*/
/* A <details> disclosure, for the reason the mobile menu is one: it opens and
   closes with no JavaScript, so the six product pages stay reachable from every
   page with scripting off. Script only adds outside-click, Escape and
   close-on-select. It is a click control, not a hover one, so it works on a
   touch screen, and <summary> brings the keyboard with it.

   The trigger is styled as a nav item rather than as a button, because it sits
   in the run of them and should not read as something else. */
.nav .drop{position:relative}
.nav .drop>summary{
  list-style:none;cursor:pointer;display:flex;align-items:center;gap:7px;
  color:var(--ink);font-size:.94rem;font-weight:400;
  padding:9px 14px;border-radius:var(--r);background:transparent;
  transition:background-color .16s var(--ease),color .16s var(--ease);
}
.nav .drop>summary::-webkit-details-marker{display:none}
.nav .drop>summary:hover{background:var(--blue-wash);color:var(--blue)}
/* The caret is a square with two borders, rotated: no glyph, no file, and it
   turns to point up when the menu is open. */
.nav .drop>summary::after,
.menu .panel .drop>summary::after{
  content:"";width:6px;height:6px;margin-top:-3px;flex:0 0 auto;
  border-right:1.6px solid currentColor;border-bottom:1.6px solid currentColor;
  transform:rotate(45deg);transition:transform .16s var(--ease);
}
.nav .drop[open]>summary::after,
.menu .panel .drop[open]>summary::after{transform:rotate(-135deg);margin-top:2px}
.nav .drop-panel{
  position:absolute;top:calc(100% + 8px);left:0;min-width:252px;
  background:var(--white);border:1px solid var(--line);border-radius:var(--r-lg);
  box-shadow:var(--shadow);padding:8px;
}
.nav .drop-panel a{
  display:block;padding:10px 14px;border-radius:var(--r);
  color:var(--ink);white-space:nowrap;
}
.nav .drop-panel a:hover{background:var(--blue-wash);color:var(--blue);text-decoration:none}

/* In the mobile panel the same markup opens inline, so the list pushes the
   items below it down rather than covering them. */
.menu .panel .drop>summary{
  list-style:none;cursor:pointer;display:flex;align-items:center;gap:7px;
  padding:12px 15px;border-radius:var(--r);color:var(--ink);
  /* border:0 and the font reset are not tidying. `.menu summary` styles the
     Menu BUTTON — 1px border, .9rem, weight 500 — and that selector matches
     every summary inside .menu, including this one. This rule is more specific
     but never reset those three, so the mobile Products row wore the button's
     border and its type while every link beside it was 1rem weight 400.
     Visible in sq's screenshot of 10 September 2026 as a box drawn round the
     word Products. */
  border:0;font-size:1rem;font-weight:400;
}
.menu .panel .drop>summary::-webkit-details-marker{display:none}
.menu .panel .drop>summary:hover{background:var(--blue-wash);color:var(--blue)}
.menu .panel .drop-panel{padding:0 0 4px 14px}
.menu .panel .drop-panel a{padding:10px 15px;font-size:.94rem}
@media (prefers-reduced-motion:reduce){
  .nav .drop>summary,.nav .drop>summary::after,.menu .panel .drop>summary::after{transition:none}
}

.menu{margin-left:auto;display:none}
.menu summary{list-style:none;cursor:pointer;padding:10px 15px;border:1px solid var(--line);border-radius:var(--r);font-size:.9rem;font-weight:500;color:var(--ink);transition:background-color .16s var(--ease),border-color .16s var(--ease)}
.menu summary:hover{background:var(--blue-wash);border-color:var(--blue-wash);color:var(--blue)}
.menu summary::-webkit-details-marker{display:none}
/* THE PANEL IS CAPPED AND SCROLLS, because it could not all be reached.
   It is position:absolute inside a sticky header, so it is anchored to the top
   of the viewport and anything past the fold cannot be scrolled to. Measured
   with the Products list open: 652px tall, which fits a 390x844 phone and runs
   55px past a 375x667 one, cutting off Contact entirely.

   dvh, not vh: on iOS Safari 100vh is the viewport as if the address bar were
   not there, so a vh cap is too tall on exactly the device this protects. The
   vh line is the fallback for anything without dvh and is deliberately first.
   overscroll-behavior:contain stops a flick inside the menu scrolling the page
   behind it. */
.menu .panel{
  position:absolute;left:18px;right:18px;margin-top:13px;
  background:var(--white);border:1px solid var(--line);border-radius:var(--r-lg);
  box-shadow:var(--shadow);padding:10px;
  max-height:calc(100vh - 108px);
  max-height:calc(100dvh - 108px);
  overflow-y:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;
}
.menu .panel a{display:block;padding:12px 15px;border-radius:var(--r);color:var(--ink)}
.menu .panel a:hover{background:var(--blue-wash);color:var(--blue);text-decoration:none}

/* --------------------------------------------------------------- buttons --*/
.btn{display:inline-block;padding:13px 26px;border-radius:var(--r);font-weight:500;font-size:1rem;border:1px solid transparent;transition:background .18s var(--ease),border-color .18s var(--ease),color .18s var(--ease)}
.btn:hover{text-decoration:none}
.btn-primary{background:var(--blue);color:#fff}
.btn-primary:hover{background:#001BCC;color:#fff}
.btn-light{background:#fff;color:var(--navy)}
.btn-light:hover{background:var(--wash-2);color:var(--navy)}
/* Over a video the .16 white border of the old rule dropped below the 3:1
   non-text minimum. Firmer edge plus a slight scrim behind the label. */
.btn-outline-light{background:rgba(7,28,107,.35);color:#fff;border-color:rgba(255,255,255,.55)}
.btn-outline-light:hover{background:rgba(255,255,255,.1);color:#fff}
.btn-row{display:flex;flex-wrap:wrap;gap:14px;margin-top:34px}

/* -------------------------------------------------------------- sections --*/
section{padding:100px 0}
section.tight{padding:62px 0}
.wash{background:var(--wash)}
.rule{border-top:1px solid var(--line)}

/* Eyebrow in Innosano Blue with the olive rule used throughout the guide. */
/* 0.84rem is 13.44px, up from 0.7rem / 11.2px on Heath's direction of
   7 September 2026 — the label was among the smallest real text on the site.
   Letter-spacing eases from .18em to .15em because tracking that reads as
   deliberate at 11px reads as gappy at 13px, and the olive rule grows with it
   so the pairing keeps its proportions. */
.eyebrow{
  display:flex;align-items:center;gap:12px;
  font-size:.84rem;font-weight:500;letter-spacing:.15em;text-transform:uppercase;
  color:var(--blue);margin:0 0 14px;
}

/* THE OLIVE ACCENT IS A VERTICAL BAR BEFORE THE LABEL. sq, 9 September 2026:

     |  PRODUCTS
     Six capabilities. One connected infrastructure.

   This reverses the arrangement of 8 September, which put a 56px horizontal
   rule on its own line between the label and the heading with equal 17px ink
   gaps either side. That version is in the git history if it is ever wanted
   back; the reason it went is that sq wanted the accent leading the label
   rather than dividing it from the heading.

   THE BAR IS TALLER THAN THE CAPS ON PURPOSE. The label is 13.44px with a cap
   height near 9.7px, so a bar matched to the caps reads as a stray tick. It
   went in at 1.25em, 16.8px, and sq asked for 35% more on 9 September 2026:
   1.69em, 22.7px. That is two and a third times the cap height and half again
   the em box, so the bar reads as a deliberate accent standing beside the type
   rather than as punctuation inside it.

   flex:0 0 3px rather than a width, because the bar is a flex item: a width
   alone would let it shrink when a label wraps. And the margin-bottom is 14px
   now, not 8px: the 8px was tuned to sit under a horizontal rule that supplied
   its own separation, and without it the label sat almost on the heading. */
.eyebrow::before{
  content:"";flex:0 0 3px;height:1.69em;background:var(--olive);border-radius:1px;
}
/* Narrow screens tighten the label rather than shrink it. At 13.44px the long
   eyebrows — "One connected record, many roles", "Sample Collection and
   Management" — wrapped to two lines at 390px and one reached three at 320px,
   where at 11.2px they had wrapped only at 360 and below. Easing the tracking
   and letting the olive rule give up width buys back the room, so nothing goes
   past two lines at any width and the label keeps its new size. */
@media (max-width:560px){
  .eyebrow{letter-spacing:.1em;margin-bottom:12px;gap:10px}
}
/* THE MEASURE IS A SHARE OF THE CONTENT WIDTH, NOT A ch VALUE. Heath,
   8 September 2026: the description under a section heading should reach at
   least 90% of the width of whatever sits below it, so the left column reads
   as one block down the page instead of a paragraph floating short of the
   grid under it.

   90% rather than 100% is his compromise on the readability point, and the
   cost is real and worth writing down: at 1440 this runs about 94 characters
   per line against the 45 to 75 that reads most easily. It is a percentage so
   it tracks the container at every width rather than drifting as type sizes
   change, and it goes to 100% on a phone, where a 10% gap beside a raggedly
   wrapped paragraph reads as a mistake rather than an inset.

   .hero .lead keeps its own 46ch and MUST: that number came from measuring
   contrast over the hero video, not from taste. See CLAUDE.md.

   It was set when the root was 18px, where it rendered 630px and measured
   5.24:1 against the 4.5 minimum. At the 16px root it renders 560px — about
   56 characters a line, inside the 45-75 band — and measures 5.99:1. So it is
   now more conservative than it has to be, and deliberately left that way:
   widening it back toward 52ch spends real contrast headroom on a measure
   that already reads well. Re-measure before touching it. */
.lead{font-size:1.2rem;font-weight:400;line-height:1.6;color:var(--dim);max-width:90%}
/* Spacing that used to be an inline style attribute on the page. Same reason:
   the production CSP blocks those. */
.lead.lead-spaced{margin-bottom:38px}
.note.note-spaced{margin-top:2.6em}
.prose-spaced{margin-top:44px}

/* ------------------------------------------------------------------ hero --*/
.hero{position:relative;background:var(--navy);color:#fff;overflow:hidden;isolation:isolate}
.hero .slot{position:absolute;inset:0;border:0;border-radius:0;z-index:-2}
/* The scrim. Strongly directional on purpose: it has to hold the left side down
   far enough for white type to clear 4.5:1 over a moving image, while opening
   up on the right so the video is actually visible rather than a rumour. The
   stops were set by measuring, not by eye. See "The home hero video" in
   CLAUDE.md before touching them. */
.hero::after{
  content:"";position:absolute;inset:0;z-index:-1;
  background:
    linear-gradient(100deg,
      rgba(7,28,107,.985) 0%,
      rgba(7,28,107,.965) 30%,
      rgba(7,28,107,.82) 48%,
      rgba(7,28,107,.34) 72%,
      rgba(7,28,107,.10) 100%),
    radial-gradient(720px 400px at 88% 6%,rgba(0,34,255,.28),transparent 70%);
}
/* THE HOME HERO'S VERTICAL RHYTHM, AND WHY IT IS TWO RULES NOT ONE.
   The generic section rule puts 100px above and below every section, and this
   padding sat on top of it, so the real gap was 228px above the eyebrow and
   212px below the buttons — about a fifth of a 1440x900 screen spent on empty
   navy at each end. Both were cut by about 55% on sq's direction of 8 September
   2026, which meant zeroing the section padding here rather than fighting it:
   the numbers below are now the whole gap, which is the point.

   Only the home hero. .hero-sub and .hero-split have their own padding and were
   not part of the complaint. Changing the hero's height moves the ::after
   scrim's percentage stops relative to the type, so `npm run contrast` is not
   optional after touching these. */
.hero:not(.hero-sub):not(.hero-split){padding-block:0}
.hero-inner{padding:103px 0 95px;max-width:900px}
/* Shadows are belt and braces, not the safety margin. The measured contrast has
   to pass on the scrim alone, because the WCAG formula takes no account of a
   text shadow; these only make moving detail behind the type less distracting. */
.hero h1,.hero .lead,.hero .eyebrow{text-shadow:0 1px 2px rgba(7,28,107,.5),0 6px 28px rgba(7,28,107,.45)}
/* 27ch, up from 23, on sq's direction of 8 September 2026: at 23ch the
   headline set four lines and he wanted three. 27ch renders 860px at the 52px
   size and breaks exactly where he asked —

     The critical operational infrastructure
     connecting people to the world
     around them.

   It is a hair under the .hero-inner 900px cap, so it never drives the layout.
   Kept in ch rather than px for the same reason .hero .lead is: the measure
   then follows the clamp down as the viewport narrows instead of having to be
   re-tuned at every step.

   WIDENING THE HEADLINE MOVES IT RIGHT, INTO THE PART OF THE SCRIM THAT OPENS
   UP FOR THE VIDEO. That is exactly the failure `npm run contrast` exists to
   catch, so it was re-measured across nine frames and five widths after this
   change, not assumed. */
.hero h1{color:#fff;max-width:27ch}
.hero .eyebrow{color:var(--muted-blue)}
/* Narrower than the 66ch of the global .lead so the paragraph stays inside the
   part of the hero the scrim holds down, which is what lets the scrim open up
   on the right and the video actually show. */
/* 46ch, not 52. The measure is in ch, so it grew with the root size, and the
   paragraph reached further across the hero and out of the zone the scrim
   holds down — the home page lead measured 4.14:1 against the 4.5 minimum
   after the scale went up. Narrowing the measure fixes it without touching the
   type size or dimming the video. */
.hero .lead{color:#CDD5F5;font-size:1.26rem;max-width:46ch}
.hero-sub .hero-inner{padding:92px 0 80px}
.hero-sub h1{font-size:clamp(2rem,3.9vw,2.95rem)}
.hero-sub .lead{font-size:1.16rem}

/* ----------------------------------------------------------- hero media --*/
/* The home hero background. Sits at the same z-index as the .slot placeholder
   it replaces, so the existing .hero::after scrim still paints over it and the
   navy .hero background still paints instantly underneath, before any media
   arrives.

   The opacity on the layer is the single knob that controls how present the
   video feels. It was set by measuring the contrast of the white headline
   against the rendered pixels behind it, not by eye: the hero commits to WCAG
   AA under clause 6.1 of the Terms, and a background that drifts brighter
   would break it. Re-measure if you change it. */
/* The grade is done here rather than baked into the encode. Baking it in was
   tried and rejected: contrast and unsharp amplify the compression noise of an
   already-lossy source, and every re-encode came out LARGER than the file it
   replaced. In CSS it costs no bytes at all, and it stays tunable.

   No sharpen filter for the same reason: the source is 1280x720 h264, so there
   is no real detail left to recover — sharpening it would only crisp up its
   artefacts. Contrast and saturation are what actually read as "clearer" on a
   soft nebula. */
.hero-media{
  position:absolute;inset:0;z-index:-2;opacity:.85;
  filter:contrast(1.2) saturate(1.25);
}
.hero-media picture,.hero-poster,.hero-video{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:65% 50%;
}
.hero-video{opacity:0;transition:opacity 900ms var(--ease)}
.hero-video.is-ready{opacity:1}

/* The scrim stops are percentages, but the type is a fixed pixel width, so on a
   narrower viewport the same paragraph reaches further across the hero and out
   of the dark zone. Measured at 900px the lead fell to 3.34:1. Below 1100px the
   scrim therefore holds much longer; there is less room for the video at that
   width anyway, so little is lost. */
@media (max-width:1100px){
  .hero::after{
    background:
      linear-gradient(100deg,
        rgba(7,28,107,.985) 0%,
        rgba(7,28,107,.975) 46%,
        rgba(7,28,107,.90) 68%,
        rgba(7,28,107,.45) 100%),
      radial-gradient(720px 400px at 88% 6%,rgba(0,34,255,.28),transparent 70%);
  }
}

/* Desktop and tablet get the video; a phone gets plain navy. Below 768px the
   whole media layer goes, not just the video, so the hero is the flat brand
   colour rather than a still at high opacity. HeroMedia.jsx mirrors the same
   breakpoint and never attaches the sources there, so no video is downloaded
   either.

   Reduced motion keeps the poster: the objection is to movement, not imagery. */
@media (max-width:767px){
  .hero-media{display:none}
}
@media (prefers-reduced-motion:reduce){
  .hero-video{display:none}
}

/* ----------------------------------------------------------- hero split --*/
/* Half navy, half photograph, the picture bleeding to the top, bottom and
   right edges of the section. Not a rounded card inset in the band.
   Behind the type the scrim would have to dim the picture far enough for white
   to hold 4.5:1, which is the wash-out that was rejected. Beside it the type is
   on flat navy — white measures 15.14:1 there and needs no scrim — and the
   picture is untouched at full opacity.
   The picture is absolutely positioned against .hero, so .container must NOT
   be given position:relative or the bleed collapses to the container's box. */
.hero-split{overflow:hidden}
/* No grid. A grid track is a percentage of the CONTAINER, while the picture is
   a percentage of the VIEWPORT — 56% of 1124 is 629 and 56% of 1440 is 806, so
   the track reserved 177px less than the picture actually took and the two
   drifted apart as the window changed. At 1500 the words ran 2px into the
   picture.
   The type is kept clear with a right padding computed from the same variable
   the picture's width comes from, so they cannot disagree. (100vw - 100%) / 2
   is the distance from the viewport edge to the container's content edge, so
   the expression is: how far the picture reaches INTO the container, plus a
   44px gap. */
.hero-split .container{display:block}
/* THE PER-PAGE HERO GEOMETRY, AS CLASSES. These were inline custom properties
   until 8 September 2026, when the production CSP in DEPLOY.md was tested
   against the built site: `style-src 'self'` with no 'unsafe-inline' blocks a
   style attribute, so every --split-ar fell back to the 1.82 default and the
   heroes silently took the wrong shape. CSP failures do not show up in the
   page; only a console entry nobody reads in production.

   Each ratio is the aspect its own photograph was cropped to, which is what
   keeps the letterbox bands at zero. If you add a ratio in a page, add the
   rule here as well — see the note in blocks.jsx.

   EVERY ONE IS QUALIFIED WITH .hero-split, and that is load bearing. The
   .hero-split rule that defaults --split-ar to 1.82 and --split-w to 56vw is
   declared further down this file at the same specificity, so an unqualified
   .hs-ar-160 loses the cascade to it and the hero silently takes the default
   shape. Found by measuring: every page read 1.82 with the right class on the
   element. */
.hero-split.hs-ar-106{--split-ar:1.06}
.hero-split.hs-ar-107{--split-ar:1.07}
.hero-split.hs-ar-110{--split-ar:1.10}
.hero-split.hs-ar-128{--split-ar:1.28}
.hero-split.hs-ar-135{--split-ar:1.35}
.hero-split.hs-ar-143{--split-ar:1.43}
.hero-split.hs-ar-160{--split-ar:1.60}
.hero-split.hs-ar-182{--split-ar:1.82}
.hero-split.hs-pos-right{--split-pos:right center}
.hero-split.hs-pos-center{--split-pos:center}
.hero-split.hs-wide{--split-w:min(62vw,1000px)}
.hero-split .hero-inner{
  /* 56px above, doubled from 28 on sq's direction of 9 September 2026: the
     eyebrow sat too close to the top of the navy. The home hero keeps its own
     103px and the type-only pages their 92px — those were never tight, and
     doubling 192px on Account Deletion would have been absurd. Safe to grow
     because every split hero sits exactly at its min-height, which the
     photograph's aspect ratio sets, so the extra padding is taken out of slack
     rather than making the hero taller and letterboxing the picture. Verified
     0px of letterbox on all ten after the change. */
  max-width:none;padding:56px 0 26px;
  padding-right:max(44px,calc(var(--split-w) - (100vw - 100%) / 2 + 44px));
}
.hero-split .hero-aside{
  /* min(), not a flat 54%. The hero's height is capped, so past about 1500px
     the photograph stops growing while a percentage panel keeps going — at
     1920 that left a 301px navy gap inside the panel, between the type and a
     picture floating off to the right. 744px is the width the photograph
     occupies at the capped height, so beyond that point the panel stops
     growing with it. */
  position:absolute;top:0;right:0;bottom:0;width:var(--split-w);
  margin:0;border-radius:0;box-shadow:none;background:none;
}
.hero-split .hero-aside::after{display:none}
/* CONTAIN, so nothing is ever cropped, on any page, at any width.
   Cover was tried and is better looking when it fits, but it cannot be trusted
   here: the row's height is whichever is taller, the picture or the words, and
   pages differ enormously in how many lines their headline takes. The About
   page's headline runs to a 747px block at 1440, which tipped its panel to
   1.07 against a 1.6 picture and put a 49.6% trim on the SIDES — half the
   photograph gone. No single set of numbers fixes that for every page, because
   the words are not the same length on every page.
   So the picture is scaled to fit and anchored by --split-pos: where the panel
   is taller than the picture's ratio it spans the full panel width with navy
   above and below; where it is wider it spans the full height and stays flush
   against the section's right edge. Either way it touches that edge, and
   nobody is ever cut. */
.hero-split .hero-aside img{width:100%;height:100%;object-fit:contain;object-position:var(--split-pos,right center)}
/* THE HERO'S HEIGHT COMES FROM THE PANEL'S WIDTH, which is what makes all of
   the above work. The panel is min(54vw, 744px) and the photograph is 1.15:1,
   so a height of min(47vw, 647px) gives the panel the photograph's own aspect
   ratio and cover has nothing to trim.
   These two numbers are a pair and must be recalculated together whenever the
   photograph's crop changes, or when either one is nudged for looks — they
   have been recalculated four times already. 56/30.5 is 1.836, a shade WIDER
   than the photograph's 1.820, which forces cover to take its slack off the
   bottom. Err narrow and cover trims the SIDES instead — at one point that
   was 3% at 1300 and 7% at 1241, off the outermost two people. Erring wide is
   the safe direction: the bottom is steps, the sides are faces.

   AND THE SPLIT STOPS AT 1400, not 1240. Below about 1400 the type is taller
   than min-height, so the type sets the hero height, the panel goes taller
   than the photograph's ratio, and cover trims the sides — measured at 3.2%
   at 1300 and 6.9% at 1241, which comes off the outermost two people. No
   padding tweak fixes that; the two constraints simply pull opposite ways as
   the viewport narrows. Stacked, the photograph is whole and full width, so
   the honest answer is to stack sooner.
   Get this wrong and the failure is specific and bad: with the height set from
   the type instead, the panel grew taller than the picture as the viewport
   narrowed and cover ate the top and bottom — 13% off each end at 1100, 19% at
   901, which takes the back row off. Deriving it from the width instead means
   the two always agree. */
/* padding-block:0 is not cosmetic, it is what lets min-height work at all.
   The generic section rule puts 100px above and below every section, so the
   hero measured 100 + 437 + 100 = 637px and the 560px min-height never
   applied — which quietly tilted the panel and sent cover's trim onto the
   sides. The type's own padding on .hero-inner supplies the breathing room
   here instead. */
.hero-split{--split-w:min(56vw,800px);--split-ar:1.82;min-height:calc(var(--split-w) / var(--split-ar));padding-block:0}

@media (max-width:1300px){
  /* Stacked: words first, then the picture at its own aspect ratio, still full
     bleed to the left and right edges of the section. */
  .hero-split{min-height:0}
  .hero-split .hero-inner{padding-right:0;padding-bottom:34px}
  /* -28px, and -22px below 760, to cancel .container's padding-inline so the
     picture still reaches both edges of the section when stacked. */
  .hero-split .hero-aside{position:static;width:auto;margin:0 -28px}
  .hero-split .hero-aside img{height:auto;object-fit:contain}
}

/* -------------------------------------------------------- feature image --*/
/* A supplied photograph in a capability page's feature slot. */
.fig-shot{position:relative;margin:0;border-radius:var(--r-lg);overflow:hidden;line-height:0}
.fig-shot img{width:100%;height:auto;display:block}
/* ---------------------------------------------------------- dark band --*/
/* A full width section carrying a photograph behind navy. The picture is
   texture, not subject: the scrim over it is deliberately heavy, because the
   stat cards inside carry real body copy and clause 6.1 of the Terms commits
   the site to WCAG AA.

   The picture is the dark server aisle, restored on 7 September 2026 after the
   padlock photograph was tried here and read as too bright. The padlock idea
   survives as .dark-band-lock below: a drawn shape rather than a photograph,
   so its visibility is a number rather than a property of the image.

   Re-measure with scripts/contrast.mjs after any change here; the figures are
   in the commit rather than inline, because they have been wrong in this
   comment twice. */
.dark-band{position:relative;background:var(--navy);color:#fff;overflow:hidden;isolation:isolate}
.dark-band>.container{position:relative}
.dark-band-bg{position:absolute;inset:0;z-index:-2;opacity:.9}
.dark-band-bg img{width:100%;height:100%;object-fit:cover;object-position:72% 50%;display:block}
/* The digital padlock on the left, faint — Heath's direction: recognisable and
   no more. A drawn outline was tried first at 0.075, 0.15 and 0.16 and never
   worked: over navy it was either invisible or, once large enough to see, a
   rounded rectangle rather than a padlock.
   This is a photograph, and it is composited with mix-blend-mode:screen rather
   than plain opacity. Screen makes the crop's dark circuit board drop away to
   nothing against the navy and lets only the bright cyan lines through, so
   there is no rectangular edge to hide and no dark patch sitting on the band.
   The band sets isolation:isolate, so the blend cannot reach past it.
   UNDER THE SCRIM, at z-index -2 with the background photograph, not above it.
   Above the scrim it was a glowing lock sitting directly behind body copy, and
   that is not a matter of taste: it took the stat text from 5.81:1 to 2.05:1
   and the lead to 2.36:1, against a 4.5 minimum the Terms commit to. Beneath
   the scrim the same shape is washed by the same gradient that washes the
   photograph, which is what keeps the copy readable and the lock faint — the
   two requirements are the same requirement. */
.dark-band-lock{
  position:absolute;left:-34px;top:50%;transform:translateY(-50%);
  width:min(430px,34vw);z-index:-2;pointer-events:none;
  mix-blend-mode:screen;opacity:.85;
}
/* A radial mask, because screen alone does not hide the crop's edges. The
   photograph's darkest pixels are dark BLUE, not black, so screen still lifts
   them a little and the result was a visible square of slightly paler navy
   with a lock in it. The mask fades the layer to nothing well before its
   edges, so there is no edge to notice. closest-side keeps the fade circular
   whatever the element's aspect ratio. */
.dark-band-lock img{width:100%;height:auto;display:block}
.dark-band-lock{
  -webkit-mask-image:radial-gradient(closest-side,#000 34%,rgba(0,0,0,.35) 62%,transparent 84%);
  mask-image:radial-gradient(closest-side,#000 34%,rgba(0,0,0,.35) 62%,transparent 84%);
}
@media (max-width:900px){.dark-band-lock{display:none}}
/* On a phone the cover crop is a narrow vertical slice, and at 72% that slice
   is the brightest part of the picture — the lit cabling — right where the
   eyebrow sits. Panning to the dark end of the frame for narrow viewports took
   the eyebrow from 4.78:1 to comfortably clear, and reads better besides. */
@media (max-width:767px){
  .dark-band-bg img{object-position:14% 50%}
}
.dark-band::after{
  content:"";position:absolute;inset:0;z-index:-1;
  background:
    linear-gradient(180deg,rgba(7,28,107,.89) 0%,rgba(7,28,107,.79) 55%,rgba(7,28,107,.90) 100%),
    radial-gradient(900px 420px at 88% 0,rgba(0,34,255,.24),transparent 70%);
}
.dark-band h2{color:#fff}
.dark-band .lead{color:#CDD5F5}
.dark-band .eyebrow{color:var(--muted-blue)}
.dark-band .stat{border-top-color:var(--olive)}
.dark-band .stat .k{color:#fff}
.dark-band .stat p{color:var(--pebble)}

/* ------------------------------------------------------ section figure --*/
/* A photograph beside prose in a .split. Fixed 4:3 so the two columns of the
   section stay balanced whatever the length of the paragraph next to it. */
.side-photo{margin:0;border-radius:var(--r-lg);overflow:hidden;line-height:0;box-shadow:var(--shadow-sm)}
.side-photo img{width:100%;aspect-ratio:4/3;object-fit:cover;display:block}

/* --------------------------------------------------- responsible ai flow --*/
/* Numbered steps sharing the badge language of the other diagrams. The counter
   badge is a real number rather than a ::before counter so it is announced in
   order by a screen reader reading the list. */

/* --------------------------------------------------- designer image slot --*/
.slot{
  background:
    repeating-linear-gradient(135deg,rgba(7,28,107,.05) 0 2px,transparent 2px 11px),
    linear-gradient(150deg,var(--wash-2) 0%,var(--wash) 100%);
  border:1px solid var(--line);border-radius:var(--r-lg);
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:7px;
  text-align:center;padding:34px;min-height:280px;color:var(--dim);
}
.slot .tag{font-size:.64rem;font-weight:500;letter-spacing:.2em;text-transform:uppercase;color:var(--blue)}
.slot .spec{font-size:.88rem;font-weight:400;max-width:46ch;line-height:1.55;margin:0}
.slot .dim{font-size:.76rem;color:var(--pebble);font-variant-numeric:tabular-nums}
.hero .slot{background:linear-gradient(150deg,#0B2A8F 0%,var(--navy) 100%);min-height:0;padding:0}
.hero .slot .tag,.hero .slot .spec,.hero .slot .dim{display:none}
.slot-wide{min-height:340px}
.slot-tall{min-height:440px}

/* ----------------------------------------------------------------- grids --*/
.grid{display:grid;gap:24px}
.g2{grid-template-columns:repeat(2,1fr)}
/* Six capabilities, three up. The earlier 6-track/span-2 rule was written for
   five cards and left the sixth orphaned on a half width row of its own once
   Sample Collection and Management was added at v8.0. */
.g5{grid-template-columns:repeat(3,1fr)}
.split{display:grid;grid-template-columns:1fr 1fr;gap:60px;align-items:center}

/* A blue tint rather than white. The products section behind them is white, so
   the tint is what gives the cards their edge; the border is tinted to match
   rather than left neutral grey against it.

   Measured on #EDF1FF: card body copy on Stone 5.71:1, headings 15.84:1, the
   "Learn more" link 7.16:1 — all clear of 4.5. The tint is 1.13:1 against the
   white section, which is a surface distinction and not a component boundary,
   and the border carries that job. Do not deepen it without re-checking the
   body copy, which is the value with the least room. */
.card{
  background:var(--blue-paper);border:1px solid var(--blue-line);border-radius:var(--r-lg);
  padding:32px;box-shadow:var(--shadow-sm);display:flex;flex-direction:column;
  transition:border-color .2s var(--ease),background-color .2s var(--ease),box-shadow .2s var(--ease),transform .2s var(--ease);
}
a.card:hover{border-color:var(--blue);background:var(--blue-wash);box-shadow:var(--shadow);transform:translateY(-3px);text-decoration:none}
/* The picture at the top of the card, with the icon on it as a white badge.
   Heath's reference, 8 September 2026: it replaced the icon-beside-the-heading
   arrangement, so .card-head is gone. aspect-ratio rather than a fixed height,
   so the banner keeps its shape as the grid goes three up, two up and one up. */
.card-shot{
  display:block;position:relative;line-height:0;overflow:hidden;
  border-radius:var(--r);margin-bottom:18px;aspect-ratio:2.2;background:var(--wash-2);
}
.card-shot img{width:100%;height:100%;object-fit:cover;display:block}
/* 90% white, so a little of the photograph reads through the disc — and it is
   an rgba background, NOT opacity on the element. opacity:.9 here would have
   composited the icon along with the disc and faded it to 90% too; the icon has
   to stay solid, which is the whole point of putting it on a light ground. */
.card-mark{
  position:absolute;left:14px;top:14px;width:54px;height:54px;border-radius:50%;
  background:rgba(255,255,255,.9);display:flex;align-items:center;justify-content:center;
  box-shadow:var(--shadow-sm);
}
.card-mark .card-icon{width:27px;height:27px}

/* A line drawing in a softened palette tone, no badge. Decorative: the kicker
   and heading beside it name the capability, so nothing here carries meaning
   and no contrast minimum applies. See CardIcon.jsx before adding colour
   logic. */
.card-icon{
  width:34px;height:34px;flex:0 0 auto;
  fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;
  transition:transform .2s var(--ease);
}
.card-icon.tone-blue{color:#4C64FF}
.card-icon.tone-navy{color:#5E6B9F}
.card-icon.tone-solar{color:#637BC3}
.card-icon.tone-olive{color:#7FA23F}
a.card:hover .card-icon{transform:scale(1.08)}
@media (prefers-reduced-motion:reduce){.card-icon{transition:none}a.card:hover .card-icon{transform:none}}
.card .kicker{font-size:.72rem;font-weight:500;letter-spacing:.1em;text-transform:uppercase;color:var(--blue);margin-bottom:12px}
/* The heading carries its own spacing. It used to sit in a .card-head flex row
   beside the icon and take its margin from that, and this rule set margin:0 to
   keep the two aligned — an earlier margin-bottom:10px here made the heading's
   box 36.1px against the icon's 34px and put the letters 5px above the icon's
   centre. The icon is on the picture now, so there is nothing to align to and
   the margin comes back. */
.card h3{margin:0 0 12px;font-size:1.16rem;line-height:1.25}
.card p{color:var(--dim);font-size:.95rem;line-height:1.62;margin:0}
/* margin-top:auto pins the call to action to the bottom of the card, so the
   six cards line up across a row whatever the length of the blurb. */
.card .more{margin-top:auto;padding-top:20px;font-weight:500;color:var(--blue);font-size:.92rem}

.stat{border-top:3px solid var(--blue);padding:22px 0 0}
.stat .k{font-size:1.04rem;font-weight:500;color:var(--ink);margin-bottom:9px;letter-spacing:-.012em}
.stat p{margin:0;color:var(--dim);font-size:.94rem;line-height:1.62}

/* ----------------------------------------------------------------- lists --*/
.ticks{list-style:none;padding:0;margin:0 0 1.5em;display:grid;gap:12px}
.ticks li{position:relative;padding-left:31px;color:var(--body)}
.ticks li::before{
  content:"";position:absolute;left:1px;top:.56em;width:15px;height:8px;
  border-left:2.4px solid var(--olive);border-bottom:2.4px solid var(--olive);
  transform:rotate(-45deg);
}
.ticks.cols2{display:block;columns:2;column-gap:48px}
.ticks.cols2 li{margin-bottom:12px;break-inside:avoid;-webkit-column-break-inside:avoid}
.cols2{columns:2;column-gap:44px}
.plain{padding-left:1.2em;margin:0 0 1.3em}
.plain li{margin-bottom:.55em}
.plain li::marker{color:var(--blue)}

/* ---------------------------------------------------------------- prose --*/
.prose>h2{margin-top:2.4em;font-size:clamp(1.3rem,2.1vw,1.56rem)}
.prose>h2:first-child{margin-top:0}
.prose>h3{margin-top:1.9em;font-size:1.04rem;color:var(--navy)}
.prose table{border-collapse:collapse;width:100%;margin:0 0 1.8em;font-size:.92rem;line-height:1.55}
.prose th,.prose td{border:1px solid var(--line);padding:12px 15px;text-align:left;vertical-align:top}
.prose th{background:var(--wash);font-weight:500;color:var(--ink)}
.table-scroll{overflow-x:auto;-webkit-overflow-scrolling:touch;margin:0 0 1.8em}
.table-scroll table{margin:0;min-width:720px}
.updated{color:var(--dim);font-size:.89rem;margin-bottom:2.4em;padding-bottom:1.6em;border-bottom:1px solid var(--line)}

.note{border-left:3px solid var(--olive);background:#F3F7EC;padding:20px 24px;border-radius:0 var(--r) var(--r) 0;margin:0 0 1.8em}
.note p:last-child{margin-bottom:0}
.note strong{color:var(--ink)}
/* A label-and-value list. max-content on the first track makes it exactly as
   wide as the longest label and no wider, so every value starts at the same x
   without a fixed indent that would go stale the moment a label changes. It
   stacks below 520px, where holding two columns would squeeze the longest
   value — the opening hours — into a ribbon. */
.kv{display:grid;grid-template-columns:max-content 1fr;gap:10px 20px;margin:0 0 1.8em}
.kv dt{color:var(--dim)}
.kv dd{margin:0;color:var(--ink)}
@media (max-width:520px){
  .kv{grid-template-columns:1fr;gap:2px 0}
  .kv dd{margin-bottom:14px}
  .kv dd:last-child{margin-bottom:0}
}
/* ------------------------------------------------ vision and mission page --*/
/* The principle is a quotation from clause 33 of the Privacy Policy and the
   deck attributes it, so it is a blockquote with a cite rather than a
   paragraph. It borrows .note for the olive rule the brand uses on a pulled-out
   statement; margin-inline:0 is not optional, because a blockquote carries 40px
   of user-agent inset that would push it out of alignment with the prose. */
.note.principle{margin-inline:0}
.note.principle cite{display:block;margin-top:12px;font-style:normal;font-size:.88rem;color:var(--dim)}
/* Vision and Mission's two platform cards. The label is the deck's own word,
   "Vision", standing over each statement; it is not a heading, so it must not
   enter the outline. */
/* THE TWO COLOURS ARE SWAPPED ROUND ON THESE CARDS ONLY, sq's direction of
   9 September 2026: the kicker goes grey and the Vision and Mission labels take
   the brand blue, which is the reverse of the home grid. Scoped to
   .vis-platforms so the home cards keep the blue kicker.

   Both selectors carry three classes on purpose. `.card .kicker` sets the blue
   at (0,2,0) and `.card p` sets --dim on any paragraph at (0,2,0), so a
   two-class rule here would win only on source order — and that is precisely
   how the --split-ar classes were quietly lost on 8 September. (0,3,0) cannot
   be reordered into losing.

   Measured on the card's --blue-paper #EDF1FF, both normal-size text needing
   4.5:1: Stone 8.24:1 and Blue 6.97:1. Stone rather than Charcoal because at
   12.22:1 Charcoal reads as body copy rather than as a grey label, and rather
   than --dim because 11.52px uppercase wants more than 5.71:1 behind it. */
.vis-platforms .card .kicker{color:var(--stone)}
.vis-platforms .card .vis-label{color:var(--blue)}
.vis-platforms .kicker{margin-bottom:12px}
.vis-label{
  margin:14px 0 0;font-size:.72rem;font-weight:500;letter-spacing:.1em;
  text-transform:uppercase;color:var(--solar);
}
.vis-stmt{margin:6px 0 0;font-size:1.08rem;line-height:1.5;color:var(--ink)}
/* Each card carries a Vision and a Mission now, so the second pair needs to
   read as a second pair rather than a fourth paragraph. The adjacent-sibling
   chain targets only a label that follows a completed pair, which is why it is
   written this way rather than as a margin on every .vis-label. */
.vis-stmt + .vis-label{margin-top:22px}
/* ---- the Vision and Mission hero panel -------------------------------- */
/* Innosano's own vision and mission sit in the hero, in the panel a photograph
   would otherwise occupy — sq's direction of 9 September 2026, which also
   dropped the hero photograph from the plan. It reuses .hero-aside's
   positioning (absolute, full height, --split-w wide) and only changes what
   happens inside: a box centred in the panel rather than a picture filling it.

   The fill is white at 5% over navy, which composites to rgb(19,39,114), and
   the figure is deliberate: measured there, white type is 13.39:1 and the
   Muted Blue labels 5.14:1. At 8% the labels drop to 4.73 and the margin over
   4.5 stops being comfortable. Re-measure if the navy or the fill moves.

   Below 1300px the split hero stacks, so the panel returns to the flow and
   loses the absolute positioning that .hero-aside gives it. */
/* THE BOX SITS EXACTLY OVER THE RIGHT-HAND CARD BELOW IT, and both numbers
   that achieve it are derived from the container rather than typed in.
   .hero-aside is absolutely positioned to the viewport's right edge, so left
   to itself the box floated out past the content column — it read as detached
   from the page it belongs to.

   Its right edge is the container's content right edge: half the gutter
   outside the container, plus the container's own 28px padding. Its width is
   one card: the container's content width less the grid's 24px gap, halved.
   At 1440 that resolves to 732 → 1282, which is the right card to the pixel.
   Nothing here is a magic number, so it holds as --maxw or the padding move. */
/* THE HERO IS TWO EQUAL COLUMNS THAT MIRROR THE TWO CARDS BELOW IT, and both
   are derived from the container so nothing here is a typed-in number.

     --vm-col   one card: the container's content width less the grid's 24px
                gap, halved.
     --vm-gap   the distance from the viewport's right edge to the container's
                content right edge: half the outer gutter plus the container's
                own 28px padding.

   The type column is --vm-col wide, so it lines up with the left card. The
   panel is --vm-col + --vm-gap wide and carries --vm-gap as right padding, so
   the box inside it is --vm-col wide and its right edge is the container's.
   At 1440 that resolves to 158 → 708 and 732 → 1282: the two cards, exactly.

   THE PANEL'S WIDTH IS THE FIX FOR A REAL BUG, not tidiness. It used to
   inherit .hero-aside's --split-w, which is capped at min(56vw, 800px) while
   --vm-gap keeps growing with the viewport. At 2560 the padding was 718px of
   an 800px panel, so the box was crushed to 82px wide and 1018px tall and
   spilled straight through the hero, which is overflow:hidden. Measured, not
   guessed — the failure only appears above about 1900px, which is why two
   rounds of checking at 1440 and below never saw it. */
.hero-vm-split{
  --vm-col:calc((min(100vw, var(--maxw)) - 56px - 24px) / 2);
  /* min-height:0 so the row is driven by its content rather than by
     --split-w / --split-ar. That ratio exists to give a photograph its shape,
     and there is no photograph here — leaving it in place forced the row 86px
     taller than either column needed and put the slack where nothing could
     use it. */
  min-height:0;
}
/* A REAL TWO-COLUMN GRID, NOT AN ABSOLUTELY POSITIONED PANEL, and the grid is
   the same one the cards use: one --vm-col track, the 24px grid gap, then
   whatever is left — which inside this container IS a second --vm-col. So the
   two hero columns land on the two cards below without either being told where
   the container's edges are. That also retires --vm-gap and the width
   arithmetic it fed, which is what crushed the box above 1900px.

   align-items:stretch is what makes the two columns the same height, and
   space-between is what "distributes" the left one: the row takes its height
   from the taller column, and the shorter one spreads its slack into the gap
   between the heading and the paragraph. So the tops and bottoms align by
   construction rather than by a tuned number — before this the box sat 42px
   low at the top and 77px low at the bottom. NO COPY CHANGED to achieve it. */
.hero-vm-split .container{
  display:grid;grid-template-columns:var(--vm-col) minmax(0, 1fr);
  column-gap:24px;align-items:stretch;
}
.hero-vm-split .hero-inner{
  max-width:none;padding:56px 0 134px;
  display:flex;flex-direction:column;justify-content:space-between;
}
/* space-between positions MARGIN boxes, so the lead's own bottom margin left
   its last line 22px above the panel's lower edge and the two columns finished
   at different heights after all. Zeroed here only: the paragraph is the last
   thing in this column, so it has nothing to be separated from.

   The top padding is 56px, matching .hero-split rather than this page's own
   doubled 34. Doubling gave 68 and put the eyebrow 12px lower than every other
   page's, which sq spotted immediately — the gap above the eyebrow is a
   cross-page alignment, not a per-page value.

   `.hero-vm-split .lead`, not `.hero-vm-split .hero .lead` — the mod class is
   ON the .hero section, so the three-part selector asked for a .hero inside a
   .hero and matched nothing at all. It reported the same 22px twice before I
   read my own selector properly. */
.hero-vm-split .lead{margin-bottom:0}
/* The bottom padding is 84px on BOTH columns, not just the left one, and that
   is what keeps them aligned while the navy grows. The row takes its height
   from the taller item's outer box, so adding the same padding to each grows
   the row without moving either content box relative to the other. Put it on
   one column only and the box slides 50px out of step with the type. */
.hero-vm-split .hero-vm{
  position:static;width:auto;padding:56px 0 134px;
  display:flex;align-items:stretch;justify-content:stretch;
}
.vm-box{
  width:100%;
  background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.2);
  border-radius:var(--r-lg);padding:32px 34px 36px;
}
/* The three labels are LARGER than the statements under them, which inverts
   the small-caps-label convention used elsewhere on the site and is deliberate:
   sq's direction of 9 September 2026. The tracking comes down from .14em to
   .06em to pay for it — uppercase at 20.8px with the old tracking sprawls
   across half the box. The statements match .hero-sub .lead at 1.16rem, so the
   panel reads at the same size as the intro paragraph beside it. */
/* There was an INNOSANO label above the first Vision, removed on sq's
   direction of 9 September 2026. The eyebrow already says VISION AND MISSION
   and the headline names the company, so it was the third time of telling. */
.vm-label{
  margin:0;font-size:1.3rem;font-weight:500;letter-spacing:.06em;
  text-transform:uppercase;color:var(--muted-blue);
}
.vm-stmt{margin:10px 0 0;color:#fff;font-size:1.16rem;line-height:1.55}
.vm-label + .vm-stmt + .vm-label{margin-top:26px}
@media (max-width:1300px){
  /* Stacked: the panel returns to the flow inside .container. margin-inline:0
     cancels the negative margin .hero-split gives an aside, which is right for
     a full-bleed photograph and wrong for a bordered box — it would hang the
     border off both screen edges. */
  /* .hero.hero-split .hero-vm, not .hero-vm, and the extra class is load
     bearing. Two rules give an aside `margin:0 -28px` and `margin:0 -22px` to
     take a photograph full bleed when the hero stacks, both at (0,2,0), and one
     of them is declared further down this file than this block — so a (0,1,0)
     override loses on specificity in one query and on source order in the
     other. Full bleed is right for a picture and wrong for a bordered box: it
     hung the border off both screen edges. Measured 0 → 390 at a 390px
     viewport before this. */
  /* .hero.hero-split .hero-vm, not .hero-vm, and the extra class is load
     bearing. Two rules give an aside `margin:0 -28px` and `margin:0 -22px` to
     take a photograph full bleed when the hero stacks, both at (0,2,0), and one
     of them is declared further down this file than this block — so a (0,1,0)
     override loses on specificity in one query and on source order in the
     other. Full bleed is right for a picture and wrong for a bordered box: it
     hung the border off both screen edges. Measured 0 → 390 at a 390px
     viewport before this. */
  .hero.hero-split .hero-vm{width:auto;padding:0;margin-top:30px;margin-inline:0}
  .hero-vm-split .container{display:block}
  .hero-vm-split .hero-inner{max-width:none;display:block;padding:68px 0 0}
}
@media (max-width:520px){
  .vm-box{padding:24px 22px 26px}
  .vm-label{font-size:1.16rem}
  .vm-stmt{font-size:1.06rem}
}

/* The approved "Powered by innosano" lockup, blue on the paper section. Sized
   by width with height:auto so the 158x13 artwork cannot be distorted; 190px
   puts its cap height at roughly body scale. Measured on --paper #F5F5F5: the
   grey 5.86:1, the blue 7.21:1 and the olive device 3.78:1, so the two text
   parts clear 4.5 and the graphic mark clears 3. There is a navy version at
   powered-by-navy.svg if this ever lands on a white surface where blue is too
   loud, and a white one still zipped in the brand folder for navy surfaces. */
.vis-powered{margin:30px 0 0}
.vis-powered img{display:block;width:190px;height:auto}

/* ------------------------------------------------- legal clause numbering -*/
.legal h2{
  display:grid;grid-template-columns:2.6rem 1fr;align-items:baseline;
  font-size:1.16rem;font-weight:500;color:var(--blue);margin:2.3em 0 .8em;
}
.legal h2:first-of-type{margin-top:0}
.legal .clause{display:grid;grid-template-columns:3.1rem 1fr;margin:0 0 .75em;margin-left:2.6rem}
.legal .clause .num{font-weight:500;color:var(--solar);font-variant-numeric:tabular-nums}
.legal .clause p{margin:0 0 .5em}
.legal .clause ul{margin:0 0 .6em;padding-left:1.1em}
.legal .clause li{margin-bottom:.28em}
.legal .clause li::marker{color:var(--olive)}
.legal .clause ul.rolelist{list-style:none;padding-left:0}
.legal .clause ul.rolelist li{padding-left:18px;position:relative}
.legal .clause ul.rolelist li::before{content:"";position:absolute;left:0;top:.62em;width:7px;height:7px;background:var(--olive)}
.legal .clause ul.rolelist strong{color:var(--ink)}

/* -------------------------------------------------------------- contact --*/
.contact-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:24px}
.contact-block{border:1px solid var(--line);border-radius:var(--r-lg);padding:30px;background:var(--white)}
.contact-block h2{margin-bottom:10px;font-size:1.06rem;font-weight:500;letter-spacing:-.012em}
.contact-block p{margin-bottom:.45em;color:var(--dim);font-size:.95rem}

/* ----------------------------------------------------------------- band --*/
.band{
  position:relative;overflow:hidden;isolation:isolate;
  background:linear-gradient(122deg,var(--navy) 0%,#0B2A8F 100%);
  /* Bottom padding is a quarter less than the top: the block ends on buttons,
     which carry their own visual weight and their own 38px of space above
     them, so matching the top left the band looking bottom-heavy. */
  color:#CDD5F5;border-radius:var(--r-lg);padding:62px 56px 47px;
  display:grid;grid-template-columns:minmax(0,1fr) auto;gap:56px;align-items:center;
}
.band-copy{min-width:0}

/* The decorative mark. Purely ornamental — see BandArt.jsx — so it is the
   first thing to go when space is short rather than something to shrink into
   illegibility. It is removed below 900px, where the band's copy needs the
   whole width. */
.band-art{width:236px;flex:0 0 auto}
.band-art svg{width:100%;height:auto;display:block;overflow:visible}
.band-art .ba-rings path{fill:none;stroke:var(--muted-blue);stroke-width:1.9;stroke-linecap:round}
.band-art .ba-spokes line{stroke:var(--muted-blue);stroke-width:1.6;stroke-linecap:round;opacity:.55}
.band-art .ba-dots-fill circle{fill:var(--muted-blue)}
.band-art .ba-dots-open circle{fill:none;stroke:var(--muted-blue);stroke-width:1.5}
/* The centred glyph is held just under full white. At 100% it was the loudest
   thing in the band, competing with the heading it is meant to sit beside
   rather than support — it is ornament, not a second headline. */
.band-art .ba-mark{opacity:.85}
.band-art .ba-mark path{fill:none;stroke:#fff;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
@media (max-width:1100px){
  .band{gap:40px}
  .band-art{width:190px}
}
@media (max-width:900px){
  .band{grid-template-columns:1fr}
  .band-art{display:none}
}
.band::after{content:"";position:absolute;inset:0;z-index:-1;background:radial-gradient(620px 320px at 92% 0%,rgba(0,34,255,.5),transparent 72%)}
.band h2{color:#fff;margin-bottom:.4em}
.band p{color:#BFC9F0;max-width:56ch;margin-bottom:0;font-size:1.04rem;font-weight:400}
/* A little more than the site's standard 34px, because the band's own padding
   is 62px and a gap that reads as generous inside a tight section reads as
   mean inside a roomy one. */
.band .btn-row{margin-top:38px}

/* --------------------------------------------------------------- footer --*/
/* .8875rem is 14.2px at the 16px root. It governs the footer's body — the
   column links and the brand tagline — so they step down together rather than
   the tagline ending up larger than the links it sits beside. */
.site-footer{background:var(--navy);color:#A9B4E0;padding:74px 0 34px;font-size:.8875rem}
/* Links are white at 80%, going to full white on hover, on sq's direction of
   8 September 2026. It is `color:rgba(...)`, not `opacity:.8`: opacity would
   create a stacking context and dim everything inside the link, the focus ring
   included, where a colour with alpha touches only the glyphs. The composite
   over navy is rgb(205,210,225) and measures 10.00:1, so it clears the 4.5
   minimum with room — the hover to 15.14:1 is a signal, not a rescue. The
   global `a` rule already carries a .15s colour transition. */
.site-footer a{color:rgba(255,255,255,.8)}
.site-footer a:hover{color:#fff}
.foot-grid{display:grid;grid-template-columns:1.7fr 1fr 1fr 1fr;gap:44px;margin-bottom:52px}
/* THE COLUMN HEADS OUT-RANK THEIR LISTS ON SIZE AND WEIGHT, NOT ON COLOUR.
   They were .72rem/500 — 11.5px against links at 14.7px — so a white uppercase
   label was being out-shouted by six larger lines beneath it, and read as part
   of the list. Brightness could not fix that: the head was already pure white.
   Now .925rem, 14.8px against 14.2px links, so the head is the larger type as
   well as the brighter — which is what it was missing. The 0.6px difference is
   small on its own and is not carrying this alone: uppercase, the .12em
   tracking, full white against the links' 80%, and the 2:1 gap ratio below it
   are all doing work.

   WEIGHT IS 500, AND 600 IS NOT AN OPTION HERE. It shipped at 700 first and sq
   asked for 600, but only 400, 500 and 700 are self-hosted. CSS font matching
   resolves a requested 600 by checking weights at or above the target first, so
   it picks the 700 face: the declaration would read 600 and render identically
   to 700. Measured, not assumed — the ink width of "PRODUCTS" is 94.33px at
   both, and 93.02px at 500. So the real choices were 500, or adding a 600
   woff2 and a fourth font request. sq took 500.

   No olive rule here, deliberately, though every section eyebrow on the site
   has one and it was the first thing I proposed: sq ruled it out on 8 September
   2026 in favour of managing it with size and weight alone. Colour is
   untouched, so white on navy stays 15.14:1. */
.foot-grid .foot-h{color:#fff;font-size:.925rem;letter-spacing:.12em;text-transform:uppercase;margin:0 0 20px;font-weight:500}
/* 6px, down from 11. THE BOX GAP IS NOT THE GAP THE EYE SEES. Each item's
   23.4px line box carries about 4.6px of half-leading top and bottom, so an
   11px box gap read as ~20px of ink-to-ink space against ~26px from the column
   head to the first item — near enough to equal that the head stopped looking
   like a head. At 6px the ink gap is ~15px against the head's ~26px, so the
   list reads as one block under a title rather than a run of equal lines.
   Measure ink, not boxes, if you change this. */
.foot-grid ul{list-style:none;padding:0;margin:0;display:grid;gap:6px}
.foot-brand .logo{color:#fff}
.foot-brand .logo .sub{color:#A9B4E0}
.foot-brand p{margin:18px 0 0;max-width:35ch;line-height:1.62}
/* The footer's bottom bar is gone: the copyright moved under the legal links
   and the postal address, which shared that bar, is carried in full on the
   Contact, About and Account Deletion pages. */
.foot-brand .logo{--logo-h:38px}
.foot-group{margin:22px 0 0;font-size:.72rem;font-weight:500;letter-spacing:.1em;text-transform:uppercase;color:#A9B4E0}
.foot-copy{margin:20px 0 0;font-size:.82rem;line-height:1.55;color:#8E9AD0}

/* ----------------------------------------------------------- responsive --*/
/* The lockup is 6.42 times as wide as it is tall, so a 38px logo is 244px
   across. That fits beside the nav on a desktop and not on a phone, hence the
   steps down. There is no longer a step below 380px: at 27px the logo is 173px
   wide, which clears the burger and the container padding even at 320. */
@media (max-width:1100px){
  .site-header .logo{--logo-h:32px}
  .header-inner{min-height:74px}
}
@media (max-width:520px){
  .site-header .logo{--logo-h:27px}
  .header-inner{min-height:68px}
}

/* Three columns only where three columns fit. The product cards carry kickers
   up to "Digital identity and connection", which measures 295px, and a third
   of the container is narrower than that below about 1160px — the label wrapped
   to two lines and no tracking down to 0.02em fixed it. Two wider columns are
   the honest answer at that width. */
@media (max-width:1160px){
  .g3,.g5{grid-template-columns:repeat(2,1fr)}
  .g5>*,.g5>:nth-child(4),.g5>:nth-child(5){grid-column:span 1}
}

@media (max-width:980px){
  .g3,.g5{grid-template-columns:repeat(2,1fr)}
  .g5>*,.g5>:nth-child(4),.g5>:nth-child(5){grid-column:span 1}
  .split{grid-template-columns:1fr;gap:40px}
  .split-rev>:first-child{order:0}
  .foot-grid{grid-template-columns:1fr 1fr;gap:38px}
  .band{padding:46px 34px}
  section{padding:82px 0}
}
/* THE NAV BAR COLLAPSES AT 900px, NOT AT THE 760px THE REST OF THIS BLOCK
   USES, AND THIS NUMBER HAS NOW MOVED TWICE FOR THE SAME REASON.

   Adding Home and the Products caret took the bar from 454px to 545px, which
   beside a 226px lockup no longer fitted a 768px viewport: every page
   overflowed by 60px between 761 and 839. That set it to 860.

   Adding Vision on 9 September 2026 took the bar to 582px, needing 808px plus
   the gap, and 860 was no longer enough headroom: 861 to 865 overflowed by up
   to 5px. A five-pixel window is exactly the kind of thing a width sweep with
   84px gaps steps straight over, and `npm run shots` did — 840 and 900 both
   pass. Found by walking every width one pixel at a time.

   900 clears it with about 90px to spare. IF A NAV LABEL IS EVER ADDED OR
   LENGTHENED, RE-MEASURE PER PIXEL rather than trusting the sweep. */
@media (max-width:900px){
  .nav{display:none}
  .menu{display:block}
}

@media (max-width:760px){
  .lead,.sec-copy{max-width:100%}
  body{font-size:16.5px}
  .container{padding-inline:22px}
  .hero-split .hero-aside{margin:0 -22px}
  section{padding:62px 0}
  .hero-inner{padding:68px 0 61px}
  .hero-sub .hero-inner{padding:70px 0 60px}
  .g2,.g3,.g5,.contact-grid{grid-template-columns:1fr}
  .cols2,.ticks.cols2{columns:1}
  .foot-grid{grid-template-columns:1fr;gap:32px}
  .band{padding:38px 26px}
  .prose table{font-size:.86rem}
  .prose th,.prose td{padding:10px 11px}
  .legal h2{grid-template-columns:2.2rem 1fr;font-size:1.06rem}
  .legal .clause{grid-template-columns:2.9rem 1fr;margin-left:0}
}

/* Below about 520px the horizontal lockup plus the INNOMENTE GROUP label plus
   the Menu button no longer fit on one line, and the label runs under the
   button. Drop the label in the header only; the footer has room for it. */
@media (max-width:520px){
  .site-header .logo .sub{display:none}
}

@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important;scroll-behavior:auto!important}}

@media print{
  .site-header,.site-footer,.band,.btn-row,.slot{display:none!important}
  body{font-size:11pt;color:#000;background:#fff}
  .hero{background:#fff;color:#000;padding:0}
  .hero::after{display:none}
  .hero h1,.hero .lead{color:#000}
  .hero-inner{padding:0 0 18pt}
  section{padding:0 0 12pt}
  a{color:#000;text-decoration:none}
  .prose tr,.legal .clause{page-break-inside:avoid}
}
.screen-badge{
  margin-left:auto;font-size:.62rem;font-weight:500;letter-spacing:.14em;text-transform:uppercase;
  color:var(--blue);border:1px solid var(--blue);border-radius:100px;padding:4px 11px;white-space:nowrap;
}
.tally .n{font-size:1.5rem;font-weight:500;color:var(--ink);letter-spacing:-.03em;line-height:1}
.tally .l{font-size:.84rem;color:var(--dim)}

/* Specificity has to beat the generic `.table-scroll table{min-width:720px}`
   further up this file, which otherwise forces a 720px table into a 308px
   wrapper and leaves only the first column visible. */
.screen .table-scroll table.screen-table{border-collapse:collapse;width:100%;font-size:.9rem;min-width:560px}
/* Dates and counts line up when they are tabular. */
.screen-table .num{font-variant-numeric:tabular-nums;color:var(--dim)}
.dash-nav.is-on{color:var(--ink);font-weight:500;border-left-color:var(--blue);background:var(--white)}
.panel{border:1px solid var(--line);border-radius:var(--r);padding:15px 16px;background:var(--white);margin-bottom:12px}
.dash-grid .panel{margin-bottom:0}
/* ---- Digital Passport: one passport, three relationships --------------- */
/* Phone left, a hub in the middle, the three organisations right, then four
   notes across the foot. The phone shell reuses the .pp-* rules it has always
   used; everything added for the restyle is .ppx-*.

   The spokes are a spine and stubs rather than the reference's bezier curves:
   the same connector language as the home page, and it survives the grid
   restacking twice. The spine runs the full height of the card stack rather
   than stopping at the first and last dots, because ending it there needs the
   card heights as constants and they change the moment a note wraps. */
.ppx{margin:0}
/* Matched to .flow-h, the figure title on the sample and quantum pages: same
   size, weight and tracking, so the three infographic titles read as one thing
   rather than three near misses. */
.ppx-h{margin:0 0 26px;font-size:1.15rem;font-weight:500;color:var(--ink);letter-spacing:-.018em}
.ppx-grid{display:grid;grid-template-columns:298px 196px 1fr;gap:36px;align-items:center}

/* the eight rows on the phone */
.ppx-rows{list-style:none;padding:0;margin:0;display:grid;gap:6px}
.ppx-rows li{
  display:flex;align-items:center;gap:10px;
  background:var(--wash);border-radius:var(--r);padding:8px 10px;
}
.ppx-row-ic{
  width:29px;height:29px;border-radius:8px;flex:0 0 auto;
  display:flex;align-items:center;justify-content:center;
}
.ppx-row-ic svg{width:16px;height:16px;fill:none;stroke-width:1.8;
  stroke-linecap:round;stroke-linejoin:round}
.ppx-row-ic.tone-a{background:var(--blue-wash)}
.ppx-row-ic.tone-a svg{stroke:var(--blue)}
.ppx-row-ic.tone-b{background:#E6ECFA}
.ppx-row-ic.tone-b svg{stroke:var(--solar)}
.ppx-row-ic.tone-c{background:#EDF3E2}
.ppx-row-ic.tone-c svg{stroke:var(--olive-ink)}
.ppx-row-ic.tone-d{background:#E4E7F2}
.ppx-row-ic.tone-d svg{stroke:var(--navy)}
.ppx-row-t{display:grid;gap:1px;min-width:0}
.ppx-row-t b{font-size:.77rem;font-weight:500;color:var(--ink);letter-spacing:-.01em}
.ppx-row-t em{font-style:normal;font-size:.67rem;color:var(--dim);line-height:1.3}

/* the hub */
.ppx-hub{position:relative;text-align:center}
.ppx-hub-mark{
  width:92px;height:92px;border-radius:50%;background:var(--blue-wash);
  display:flex;align-items:center;justify-content:center;margin:0 auto 14px;
  box-shadow:0 0 0 10px rgba(231,234,255,.5);
}
.ppx-hub-mark svg{width:36px;height:36px;fill:none;stroke:var(--blue);stroke-width:1.8;
  stroke-linecap:round;stroke-linejoin:round}
.ppx-hub-t{margin:0;font-size:1rem;font-weight:500;color:var(--ink);letter-spacing:-.015em;line-height:1.25}
.ppx-hub-s{margin:14px 0 0;font-size:.79rem;color:var(--dim);line-height:1.5}
.ppx-hub-s b{display:block;color:var(--ink);font-weight:500;margin-bottom:2px}
/* the two spokes off the hub, to the phone and to the spine */
.ppx-hub::before,.ppx-hub::after{
  content:"";position:absolute;top:46px;width:36px;height:1px;background:var(--line);
}
.ppx-hub::before{left:-36px}
.ppx-hub::after{right:-36px}

/* the three organisations */
.ppx-orgs{position:relative;margin:0;display:grid;gap:14px}
.ppx-orgs::before{content:"";position:absolute;left:-18px;top:0;bottom:0;width:1px;background:var(--line)}
.ppx-org{
  position:relative;display:grid;grid-template-columns:148px 1fr 186px;
  background:var(--white);border:1px solid var(--line);border-radius:var(--r-lg);
  overflow:hidden;align-items:stretch;
}
.ppx-org::before{content:"";position:absolute;left:-18px;top:50%;width:18px;height:1px;background:var(--line)}
.ppx-org::after{
  content:"";position:absolute;left:-21.5px;top:50%;transform:translateY(-50%);
  width:8px;height:8px;border-radius:50%;
}
.ppx-org.tone-a::after{background:var(--blue)}
.ppx-org.tone-b::after{background:var(--solar)}
.ppx-org.tone-d::after{background:var(--navy)}
/* The photographs are 1672x941 in a panel a tenth that area, so cover, with
   object-position set per card: a centred crop took the clinician's face off. */
.ppx-org-photo{position:relative;display:block;line-height:0;background:var(--wash-2)}
.ppx-org-photo img{width:100%;height:100%;object-fit:cover;display:block}
/* Focal points, per card, keyed on the tone class. Also moved out of a style
   attribute for the CSP: centred, the clinician's face was cropped off. */
.ppx-org.tone-a .ppx-org-photo img{object-position:52% 55%}
.ppx-org.tone-b .ppx-org-photo img{object-position:62% 40%}
.ppx-org.tone-d .ppx-org-photo img{object-position:40% 50%}
/* 90% white, matching the product cards: a little of the photograph reads
   through the disc while the icon stays solid. rgba, not opacity — opacity on
   the element would fade the icon with the disc. The hub mark is not touched:
   it sits on the section's own background, not on a picture, so there is
   nothing behind it to show through. */
.ppx-org-mark{
  position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  width:52px;height:52px;border-radius:50%;background:rgba(255,255,255,.9);
  display:flex;align-items:center;justify-content:center;box-shadow:var(--shadow-sm);
}
.ppx-org-mark svg{width:24px;height:24px;fill:none;stroke-width:1.8;
  stroke-linecap:round;stroke-linejoin:round}
.ppx-org-mark.tone-a svg{stroke:var(--blue)}
.ppx-org-mark.tone-b svg{stroke:var(--solar)}
.ppx-org-mark.tone-d svg{stroke:var(--navy)}
.ppx-org-main{padding:16px 18px}
.ppx-org dt{font-size:.94rem;font-weight:500;color:var(--ink);letter-spacing:-.012em;line-height:1.3}
.ppx-org-sub{margin:2px 0 10px;font-size:.75rem;color:var(--dim)}
.ppx-org-sees{margin:0}
.ppx-org-sees ul{list-style:none;padding:0;margin:0;display:grid;gap:5px}
.ppx-org-sees li{display:flex;align-items:flex-start;gap:8px;font-size:.77rem;
  color:var(--body);line-height:1.4}
.ppx-tick{flex:0 0 auto;width:14px;height:14px;margin-top:2px}
.ppx-tick svg{width:14px;height:14px;fill:none;stroke:var(--olive-ink);stroke-width:2;
  stroke-linecap:round;stroke-linejoin:round}
.ppx-org-note{
  margin:0;padding:16px 18px;border-left:1px solid var(--line);align-self:center;
  font-size:.75rem;color:var(--dim);line-height:1.5;
}

/* the four notes across the foot */
.ppx-feats{
  list-style:none;padding:26px 0 0;margin:28px 0 0;border-top:1px solid var(--line);
  display:grid;grid-template-columns:repeat(4,1fr);gap:22px;
}
.ppx-feats li{display:flex;align-items:flex-start;gap:12px}
.ppx-feat-ic{
  width:40px;height:40px;border-radius:50%;background:var(--blue-wash);flex:0 0 auto;
  display:flex;align-items:center;justify-content:center;
}
.ppx-feat-ic svg{width:19px;height:19px;fill:none;stroke:var(--blue);stroke-width:1.8;
  stroke-linecap:round;stroke-linejoin:round}
.ppx-feats b{display:block;font-size:.84rem;font-weight:500;color:var(--ink);
  letter-spacing:-.01em;margin-bottom:2px}
/* display:block is the fix, not the line-height. As an inline, this em's own
   1.45 did nothing to the line boxes: an inline box does not set the height of
   the line it sits on — the containing block's strut does, and the parent span
   inherits body's 1.65 at 18px, so every line box was 29.7px tall for 13.5px
   text and the two lines sat 13.7px apart. Blockifying it makes its own
   line-height govern. The sibling b was already display:block, which is why
   only the description looked wrong. */
.ppx-feats em{display:block;font-style:normal;font-size:.75rem;color:var(--dim);line-height:1.45}

@media (max-width:1160px){
  /* Stacked. The spokes go with the columns they connected. */
  .ppx-grid{grid-template-columns:1fr;gap:30px;justify-items:center}
  .ppx-hub::before,.ppx-hub::after{display:none}
  .ppx-orgs{width:100%}
  .ppx-orgs::before,.ppx-org::before,.ppx-org::after{display:none}
  .ppx-phone{width:298px}
}
@media (max-width:820px){
  .ppx-org{grid-template-columns:120px 1fr}
  .ppx-org-note{grid-column:1 / -1;border-left:0;border-top:1px solid var(--line);padding-top:12px}
  .ppx-feats{grid-template-columns:repeat(2,1fr)}
}
@media (max-width:560px){
  .ppx-org{grid-template-columns:1fr}
  .ppx-org-photo{height:132px}
  .ppx-feats{grid-template-columns:1fr;gap:16px}
  .ppx-phone{width:100%;max-width:298px}
}

/* ---- sample lifecycle infographic, Sample Collection -------------------- */
/* A staged flow, not a list of lists: numbered marks sitting on a connector
   rail, each stage carrying its own timestamp, a sentence of description and
   the fields captured there. The rail is drawn behind the marks with a single
   border on the <ol>, so it stays continuous however tall the columns get. */
.flowfig{
  margin:0;border:1px solid var(--line);border-radius:var(--r-lg);
  background:var(--white);padding:26px 28px 24px;
}
.flow-top{display:flex;align-items:center;gap:16px;margin-bottom:26px}
/* 1.02 -> 1.3rem on 8 September 2026, then trimmed to 1.15rem the same day:
     the enlargement was judged on a browser at 75% zoom, where it read as an
     apparent 17.5px, and at 100% it was 23.4px and too heavy for the card.
     Applies to
   both figures that use it — this one and the sample lifecycle — because they
   are the same kind of title on the same kind of card and there is no reason
   for them to differ. */
.flow-h{
  margin:0;font-size:1.15rem;font-weight:500;color:var(--ink);letter-spacing:-.018em;
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;
}
.flow-top .screen-badge{margin-left:auto}

.flow{list-style:none;margin:0 0 22px;padding:0;display:grid;grid-template-columns:repeat(4,1fr);gap:0}
.fstage{position:relative;padding:0 20px 0 0}
.fstage:not(:last-child){border-right:1px solid var(--line);margin-right:20px}
/* The connector: a rule across the row of marks, sitting behind them. */
.fstage::before{
  content:"";position:absolute;left:29px;right:-20px;top:28px;height:2px;background:var(--line);
}
.fstage:last-child::before{display:none}
/* The arrowhead, so the rail reads as a direction rather than a rule. Two
   borders on a rotated square: no glyph, no file, and it inherits --line. */
.fstage:not(:last-child)::after{
  content:"";position:absolute;right:-20px;top:29px;width:7px;height:7px;
  border-top:2px solid var(--line);border-right:2px solid var(--line);
  transform:translateY(-50%) rotate(45deg);
}
/* A filled circle carrying a white stroke icon, per the reference style, with
   the stage number as a small counter badge so the sequence stays explicit —
   icons alone do not carry order. Each stage takes its own palette colour and
   the progression runs blue, solar, olive, navy. White on olive is 4.12:1,
   which clears the 3:1 minimum for a non-text graphic. */
.fstage-mark{
  position:relative;z-index:1;display:flex;align-items:center;justify-content:center;
  width:58px;height:58px;border-radius:50%;margin-bottom:16px;
}
.fstage-mark svg{
  width:27px;height:27px;fill:none;stroke:var(--white);stroke-width:1.7;
  stroke-linecap:round;stroke-linejoin:round;
}
.fstage-mark b{
  position:absolute;top:-2px;right:-2px;width:21px;height:21px;border-radius:50%;
  background:var(--white);color:var(--ink);font-size:.68rem;font-weight:500;
  display:flex;align-items:center;justify-content:center;
  box-shadow:0 0 0 1px var(--line);
}
.mark-a{background:var(--blue)}
.mark-b{background:var(--solar)}
.mark-c{background:var(--olive)}
.mark-d{background:var(--navy)}
.fstage-t{margin:0;font-size:.98rem;font-weight:500;color:var(--ink);letter-spacing:-.012em}
.fstage-when{margin:3px 0 0;font-size:.74rem;color:var(--blue);font-variant-numeric:tabular-nums}
.fstage-d{margin:10px 0 14px;font-size:.85rem;color:var(--dim);line-height:1.55}
/* Some flows carry no field list — the post quantum diagram is four stages and
   four sentences. Give the description room to breathe instead. */
.flow-plain .fstage-d{margin-bottom:0}
/* SOFT BADGES, this figure only. Heath's reference for the quantum page uses a
   pale tinted disc with a coloured stroke icon rather than the filled disc with
   a white icon the other flow uses. Scoped to .flow-plain so the sample
   lifecycle keeps the solid marks it was signed off with a few commits ago.
   The counter takes the stage colour too, which it could not when the disc was
   saturated. Decorative either way: every mark here is aria-hidden and the
   label beside it carries the meaning. */
.flow-plain .fstage-mark.mark-a{background:var(--blue-wash)}
.flow-plain .fstage-mark.mark-a svg,
.flow-plain .fstage-mark.mark-a b{color:var(--blue);stroke:var(--blue)}
.flow-plain .fstage-mark.mark-b{background:#E6ECFA}
.flow-plain .fstage-mark.mark-b svg,
.flow-plain .fstage-mark.mark-b b{color:var(--solar);stroke:var(--solar)}
.flow-plain .fstage-mark.mark-c{background:#EDF3E2}
.flow-plain .fstage-mark.mark-c svg,
.flow-plain .fstage-mark.mark-c b{color:var(--olive-ink);stroke:var(--olive-ink)}
.flow-plain .fstage-mark.mark-d{background:#E4E7F2}
.flow-plain .fstage-mark.mark-d svg,
.flow-plain .fstage-mark.mark-d b{color:var(--navy);stroke:var(--navy)}
.flow-plain .fstage-mark b{stroke:none}
.fstage-f{list-style:none;padding:0;margin:0;display:grid;gap:9px}
.fstage-f li{
  display:flex;align-items:flex-start;gap:10px;
  font-size:.8rem;color:var(--body);line-height:1.45;
}
/* Decorative: the label beside each says what it is, so they are aria-hidden.
   Tinted in the stage's colour so a column reads as one column. */
.fstage-ic{
  width:26px;height:26px;border-radius:50%;flex:0 0 auto;margin-top:-3px;
  display:flex;align-items:center;justify-content:center;
}
.fstage-ic svg{width:14px;height:14px;fill:none;stroke-width:1.8;
  stroke-linecap:round;stroke-linejoin:round}
.fstage.tone-a .fstage-ic{background:var(--blue-wash)}
.fstage.tone-a .fstage-ic svg{stroke:var(--blue)}
.fstage.tone-b .fstage-ic{background:#E6ECFA}
.fstage.tone-b .fstage-ic svg{stroke:var(--solar)}
.fstage.tone-c .fstage-ic{background:#EDF3E2}
.fstage.tone-c .fstage-ic svg{stroke:var(--olive-ink)}
.fstage.tone-d .fstage-ic{background:#E4E7F2}
.fstage.tone-d .fstage-ic svg{stroke:var(--navy)}

/* One flowing sentence, not a flex row: as a flex child the bold label was
   shrinking to three lines on a narrow screen while the rest of the sentence
   sat beside it. The olive rule is a border, so it still spans the block. */
.flow-audit{
  margin:0;background:var(--wash);border-left:3px solid var(--olive);
  border-radius:0 var(--r) var(--r) 0;padding:13px 16px;
  font-size:.85rem;color:var(--stone);line-height:1.55;
}
.flow-audit b{color:var(--ink);font-weight:500}
.flow-audit-bar{display:flex;align-items:center;gap:14px;padding:12px 16px}
.flow-audit-ic{
  width:34px;height:34px;border-radius:50%;flex:0 0 auto;background:#EDF3E2;
  display:flex;align-items:center;justify-content:center;
}
.flow-audit-ic svg{width:18px;height:18px;fill:none;stroke:var(--olive-ink);
  stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
.flow-cap{margin:14px 0 0;font-size:.8rem;color:var(--dim)}

@media (max-width:980px){
  .flow{grid-template-columns:repeat(2,1fr);gap:26px 0}
  .fstage:nth-child(2n){border-right:0;margin-right:0}
  .fstage:nth-child(2n)::before,
  .fstage:nth-child(2n)::after{display:none}
}
@media (max-width:620px){
  .flowfig{padding:20px 18px 18px}
  .flow{grid-template-columns:1fr;gap:22px}
  /* Side by side on a phone, which is the reference layout: circle left, text
     right, rail running down through the circles. */
  /* :not(:last-child) out-specifies a bare .fstage, so the column divider
     survived into the stacked layout as stray vertical stubs. Match the
     specificity to clear it. */
  .fstage,.fstage:not(:last-child){border-right:0;margin-right:0}
  .fstage{padding-right:0;padding-left:74px;min-height:58px}
  .fstage::before,.fstage:last-child::before{
    display:block;left:28px;right:auto;top:62px;bottom:-22px;width:2px;height:auto;
  }
  .fstage:last-child::before{display:none}
  .fstage::after{display:none}
  .fstage-mark{position:absolute;left:0;top:0;margin:0}
  .flow-top{flex-wrap:wrap;gap:10px}
  .flow-top .screen-badge{margin-left:0}
}
.sp-eyebrow.is-on{color:var(--blue)}
.sp-eyebrow.is-on::after{content:"";height:3px;flex:0 0 34px;background:var(--olive)}
.sp-tag.is-on{color:var(--blue);border-color:var(--blue)}
/* min(), not a flat 300px. The device mock is 300px wide by design, but a
   320px viewport leaves only 276px inside the container padding, so a fixed
   width made the grid column 300px and pushed the whole figure 2px past the
   viewport — the only horizontal overflow anywhere on the site across 15
   pages and 16 widths from 320 to 1920. */
.pp-phone{
  width:min(300px,100%);border:1px solid var(--line);border-radius:30px;background:var(--white);
  box-shadow:var(--shadow);overflow:hidden;position:relative;padding-top:10px;
}
.pp-notch{
  position:absolute;top:9px;left:50%;transform:translateX(-50%);
  width:78px;height:5px;border-radius:3px;background:var(--wash-2);
}
.pp-bar{display:flex;align-items:center;gap:10px;padding:16px 18px 12px}
.pp-bar-t{font-size:.86rem;font-weight:500;color:var(--ink);letter-spacing:-.012em}
.pp-badge{
  margin-left:auto;font-size:.54rem;font-weight:500;letter-spacing:.12em;text-transform:uppercase;
  color:var(--blue);border:1px solid var(--blue);border-radius:100px;padding:3px 8px;white-space:nowrap;
}
.pp-body{padding:0 18px 20px}

.pp-id{display:grid;grid-template-columns:auto 1fr;gap:12px;align-items:center;margin-bottom:16px}
.pp-id img{width:46px;height:46px;border-radius:50%;object-fit:cover;background:#E5E9F7}
.pp-id-t{display:grid;gap:2px;min-width:0}
.pp-id-t b{font-size:.95rem;font-weight:500;color:var(--ink);letter-spacing:-.012em}
.pp-id-t em{font-style:normal;font-size:.74rem;color:var(--dim);font-variant-numeric:tabular-nums}
.pp-verified{
  grid-column:1 / -1;display:flex;align-items:center;gap:7px;
  font-size:.72rem;color:var(--body);
}
.pp-verified svg{width:14px;height:14px;fill:none;stroke:var(--olive);stroke-width:1.8;
  stroke-linecap:round;stroke-linejoin:round;flex:0 0 auto}

@media (max-width:860px){
  .pp{grid-template-columns:1fr;gap:26px;justify-items:center}
  .pp-side{width:100%;padding-top:0}
}
.coc-steps li.is-final::before{border-color:var(--blue);background:var(--blue)}
.seg .is-on{background:var(--wash);color:var(--ink);font-weight:500}
.case-table .num{text-align:right;font-variant-numeric:tabular-nums;padding-right:0}
.case-table th.num{text-align:right}
.inj-top.is-alt{background:#EDF1FC;border-color:#D9E1F7}
.inj-top .l{font-size:.71rem;color:var(--dim)}
.inj-top .n{font-size:1.7rem;font-weight:500;color:var(--ink);letter-spacing:-.03em;line-height:1.1}
.inj .l{font-size:.71rem;color:var(--dim);padding-right:34px}
.inj .n{font-size:1.28rem;font-weight:500;color:var(--ink);letter-spacing:-.02em;line-height:1.2}

@media (max-width:760px){
  /* Fit rather than scroll. A table that clips mid word reads as broken even
     though it scrolls, and the three remaining columns fit once the avatar and
     some padding come out. */
  .screen .table-scroll table.screen-table{min-width:0;font-size:.8rem}
}

/* --------------------------------------------------------------- diagrams --*/
/* Built from HTML rather than SVG so the type reflows: an SVG has one internal
   coordinate system, so text sized to read in the 500px desktop column comes
   out around 9.5px in the 346px mobile one. See DiagramRoleAccess.jsx.

   Every value below is a token from the top of this file. Olive appears only as
   rules and markers, never behind type: it is 4.12:1 on white and fails the
   4.5:1 minimum. */

/* ---- entrance reveal ---------------------------------------------------- */
/* Plays once on scroll, never loops. A loop or a carousel would engage WCAG
   2.2.2, which needs a visible pause control for anything auto updating past
   five seconds, and clause 6.1 of the Terms makes that binding.

   The resting state below IS the finished state, so with JavaScript off the
   diagram is simply present. Only script adds .is-armed, and only on an element
   still below the fold. */
.sec-anim.is-armed .rec-items li,
.sec-anim.is-armed .sec-card,
.sec-anim.is-armed .dg-audit{opacity:0}

.sec-anim.is-ready .rec-items li{animation:dg-rise .45s var(--ease) both}
.sec-anim.is-ready .rec-items li:nth-child(1){animation-delay:.02s}
.sec-anim.is-ready .rec-items li:nth-child(2){animation-delay:.10s}
.sec-anim.is-ready .rec-items li:nth-child(3){animation-delay:.18s}
.sec-anim.is-ready .rec-items li:nth-child(4){animation-delay:.26s}
.sec-anim.is-ready .rec-items li:nth-child(5){animation-delay:.34s}
.sec-anim.is-ready .sec-card{animation:dg-rise .45s var(--ease) both}
.sec-anim.is-ready .sec-card:nth-child(1){animation-delay:.46s}
.sec-anim.is-ready .sec-card:nth-child(2){animation-delay:.58s}
.sec-anim.is-ready .sec-card:nth-child(3){animation-delay:.70s}
.sec-anim.is-ready .dg-audit{animation:dg-rise .45s var(--ease) .84s both}

@keyframes dg-rise{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
@keyframes dg-draw{from{transform:scaleY(0)}to{transform:scaleY(1)}}
@keyframes dg-slide{from{opacity:0;transform:translateX(-14px)}to{opacity:1;transform:none}}
@keyframes dg-pop{from{transform:scale(0)}to{transform:scale(1)}}

/* The site already honours this everywhere; the script also declines to arm,
   so this is belt and braces. */
@media (prefers-reduced-motion:reduce){
  .sec-anim.is-armed .rec-items li,
  .sec-anim.is-armed .sec-card,
  .sec-anim.is-armed .dg-audit{opacity:1}
  .sec-anim.is-ready *{animation:none!important}
}

.dg-audit{
  display:flex;align-items:center;gap:14px;
  font-size:.86rem;color:var(--stone);margin-top:2px;
}
.dg-audit::before{content:"";height:3px;flex:0 0 32px;background:var(--olive)}

/* ---- full width section: intro, then three cards ------------------------ */
/* SHARED BY TWO SECTIONS on the home page, "Connected in real time" and
   "AI that helps people make safer, better informed decisions". Same shape both
   times: a centred intro block, a row of three cards joined by a connector,
   then an olive captioned footnote. Named sec-* rather than after either one,
   because a second section adopting the pattern is what generalised it — and
   duplicating a hundred lines of this to rename it would have been worse than
   sharing it. .rec-items, the record's icon row, belongs to the first only. */
/* Heath's reference of 8 September 2026: the record contents as an icon row
   under the heading, the three views as cards beneath, joined by a connector.
   It was a narrow figure beside the copy; not one word changed with the move.

   NO PANEL AROUND IT. --shadow is used only on floating UI on this site — the
   nav dropdown, the mobile menu, the skip link, a card on hover — and no
   static content section is boxed, so the reference's big shadowed card would
   have been a new pattern. The three cards take the same treatment as the
   product cards further up the page rather than inventing a third card style. */
/* THE INTRO BLOCK IS LEFT JUSTIFIED, THE CARDS ARE CENTRED. Heath,
   8 September 2026: centring the intro did not look good, and the difference
   is length. Six lines of centred copy read as a deliberate opening; eleven,
   which is what the Responsible AI section runs to, read as a wall — the eye
   has to find the start of every line. The cards are three or four words and a
   sentence each, so centring them still works, and it keeps them tied to the
   badge above.

   The copy column starts at the same left edge as the heading rather than
   sitting centred with left-aligned text inside it, which would have indented
   it against the heading for no reason. Its 74ch is what limits the measure. */
.sec-top{text-align:left}
.sec-top h2{margin-top:6px}
.sec-copy{max-width:90%;margin:16px 0 0}
/* The same size as .lead, which is the other section description on this page.
   At body size these paragraphs ran about 124 characters per line once the
   measure went to 90% — the same pixel width fits far more of a smaller face —
   against roughly 80 for .lead. Matching the type makes the two sections
   consistent to read as well as to look at, which is the point of the 90%. */
.sec-copy p{margin:0 0 .7em;color:var(--body);font-size:1.2rem;line-height:1.6}
.sec-copy p:last-child{margin-bottom:0}

.rec-items{
  list-style:none;padding:26px 0 0;margin:30px 0 0;
  display:grid;grid-template-columns:repeat(5,1fr);gap:16px 14px;
  border-top:1px solid var(--line);
}
.rec-items li{
  display:flex;flex-direction:column;align-items:center;gap:10px;
  font-size:.9rem;line-height:1.35;color:var(--ink);font-weight:500;
}
.rec-ic{
  width:44px;height:44px;border-radius:50%;flex:0 0 auto;
  display:flex;align-items:center;justify-content:center;
}
.rec-ic svg{width:22px;height:22px;fill:none;stroke-width:1.7;
  stroke-linecap:round;stroke-linejoin:round}
/* Tinted grounds with a stroke icon, rather than the filled badge the other
   diagrams use: there are five of them in a row and five saturated discs would
   shout over the heading above. The cards below keep the filled badge. */
.ic-blue{background:var(--blue-wash)}
.ic-blue svg{stroke:var(--blue)}
.ic-solar{background:#E6ECFA}
.ic-solar svg{stroke:var(--solar)}
.ic-olive{background:#EDF3E2}
.ic-olive svg{stroke:var(--olive-ink)}
.ic-navy{background:#E4E7F2}
.ic-navy svg{stroke:var(--navy)}

/* The connector: a stem down the middle, a bar across the card centres, then a
   stem and a dot into each card. Drawn with borders and pseudo-elements, so it
   inherits --line, costs no file, and disappears cleanly when the cards stack. */
.sec-cards{
  position:relative;margin:52px 0 0;padding-top:40px;
  display:grid;grid-template-columns:repeat(3,1fr);gap:20px;
}
.sec-cards::before{
  content:"";position:absolute;top:0;left:16.6%;right:16.6%;height:1px;background:var(--line);
}
.sec-cards::after{
  content:"";position:absolute;top:-30px;left:50%;width:1px;height:30px;background:var(--line);
}
.sec-card{
  position:relative;background:var(--blue-paper);border:1px solid var(--blue-line);
  border-radius:var(--r-lg);box-shadow:var(--shadow-sm);
  padding:26px 24px 24px;text-align:center;
}
.sec-card::before{content:"";position:absolute;top:-40px;left:50%;width:1px;height:40px;background:var(--line)}
.sec-card::after{
  content:"";position:absolute;top:-44px;left:50%;transform:translateX(-50%);
  width:9px;height:9px;border-radius:50%;
}
.sec-card.tone-a::after{background:var(--blue)}
.sec-card.tone-b::after{background:var(--solar)}
.sec-card.tone-d::after{background:var(--navy)}
.sec-mark{
  width:54px;height:54px;border-radius:50%;margin:0 auto 14px;
  display:flex;align-items:center;justify-content:center;
}
.sec-mark svg{width:26px;height:26px;fill:none;stroke:var(--white);stroke-width:1.7;
  stroke-linecap:round;stroke-linejoin:round}
/* dt/dd in the record section, b/em in the Responsible AI one. The first is a
   description list of role/what-they-see pairs; the second is an ordered list,
   because those three steps are a sequence and the order is the claim. Same
   styling either way. */
.sec-card dt,
.sec-card .sec-t{display:block;font-weight:500;color:var(--ink);font-size:1.06rem;letter-spacing:-.012em}
.sec-card dd,
.sec-card .sec-d{display:block;margin:6px 0 0;color:var(--dim);font-size:.93rem;line-height:1.55;font-style:normal;font-weight:400}
/* An ordered list needs its markers off; the numbered badge carries the order.
   padding-inline-start, NOT padding: .sec-cards uses padding-top to make room
   for the connector, and a blanket padding:0 here is more specific, so it
   collapsed that space and hid the horizontal bar behind the cards. */
ol.sec-cards{list-style:none;padding-inline-start:0}
/* The step number, on the mark rather than beside it, so it survives the card
   going from centred to a row on a narrow screen. */
.sec-mark{position:relative}
.sec-n{
  position:absolute;top:-3px;right:-5px;
  width:21px;height:21px;border-radius:50%;background:var(--white);
  border:1px solid var(--line);color:var(--ink);
  font-size:.7rem;font-weight:500;line-height:19px;text-align:center;
}

.sec-foot{justify-content:center;margin:30px 0 0}
.sec-link{text-align:center;margin:14px 0 0}

@media (max-width:1000px){
  .rec-items{grid-template-columns:repeat(3,1fr)}
  .sec-cards{grid-template-columns:1fr;gap:14px;margin-top:34px;padding-top:0}
  .sec-cards::before,.sec-cards::after,
  .sec-card::before,.sec-card::after{display:none}
  /* Stacked, the cards read better as rows: badge beside the label rather than
     a centred badge over a one-line title in a very wide box. */
  .sec-card{display:grid;grid-template-columns:auto 1fr;gap:0 16px;text-align:left;padding:20px}
  .sec-mark{margin:0;width:46px;height:46px;grid-row:span 2}
  .sec-card dt{align-self:end}
}
@media (max-width:640px){
  /* Left justified on a phone, by Heath's direction. Centring a paragraph that
     runs seven lines leaves it ragged on both edges, and the eyebrow's olive
     rule was stranded beside a two-line label: it drops to its own line under
     the text now, which is also why the label fits on one line again. */
  .sec-copy{margin-top:12px}
  .rec-items{grid-template-columns:repeat(2,1fr);gap:18px 10px}
  .sec-foot{justify-content:flex-start;flex-wrap:wrap}
  .sec-link{text-align:left}
}

@media (max-width:520px){
}

/* ------------------------------------------------- contained photo frame --*/
/* A photograph whose own aspect ratio does not match the frame, shown whole
   rather than cropped. The picture is contained and centred; the same file,
   scaled up, blurred and dimmed, fills the remainder so the band reads as one
   image instead of a picture floating on a slab.

   Use this when cropping would cost you something that matters, such as the
   back row of a group photograph. When the supplied image already matches the
   frame, use a plain <img> and let it fill: this costs a second decode of the
   same file and is not free. */
/* The frame takes the photograph's own aspect ratio, so there is no empty
   frame to fill and no blurred copy behind it — Heath's direction of
   7 September 2026. The light blue ground shows only if an image is ever
   letterboxed inside it, which with a natural aspect ratio it is not.
   It runs the full width of its container: the original frame is 4:3 landscape
   rather than the near-square crop that preceded it, so at full width it is
   about 700px tall rather than 1400, and no cap is needed. */
.photo-frame{
  position:relative;overflow:hidden;border-radius:var(--r-lg);
  background:var(--blue-paper);
}
.photo-frame img{width:100%;height:auto;display:block}
/* A hairline round the frame, so the picture has an edge against the page. */
.photo-frame::after{
  content:"";position:absolute;inset:0;pointer-events:none;
  border-radius:var(--r-lg);box-shadow:inset 0 0 0 1px rgba(23,23,28,.08);
}

/* ------------------------------------------------------------------ team --*/
/* FLEX, NOT GRID, AND THE REASON IS THAT THIRTEEN IS PRIME.
   Elliot Hyland made the list thirteen on 9 September 2026, and a four-track
   grid then read 4+4+4+1 — a single portrait stranded on its own row. No column
   count divides thirteen except one and thirteen, so no choice of tracks fixes
   it: three gives 3+3+3+3+1 and two gives a trailing one as well. Wrapping flex
   with justify-content:center is the fix that holds at every width, because a
   short final row centres itself instead of hanging off the left edge. A full
   row fills the line exactly, so those are unchanged.

   Five across at desktop is sq's arrangement: 5+5+3. The basis is exact rather
   than a fraction unit, less a hundredth of a pixel so sub-pixel rounding can
   never drop a row to four. --cols is the only thing the media queries touch.

   The circle shrinks as a consequence, from about 209px to 164px, because it is
   76% of a column that is now a fifth of the row rather than a quarter. sq
   approved that, and it has a second benefit: the interim portraits are 270px
   square, so at 164px they are finally above 1x and stop looking soft. */
.team-grid{
  --cols:5;--colgap:26px;
  margin-top:44px;display:flex;flex-wrap:wrap;justify-content:center;
  gap:30px var(--colgap);
}
/* Name and role centred under the portrait. */
.person{
  text-align:center;
  flex:0 1 calc((100% - (var(--cols) - 1) * var(--colgap)) / var(--cols) - 0.01px);
}
.person .shot{
  /* 76% of the column: 84% first, then a further 10% on Heath's direction of
     7 September 2026. Set here AND on .person img.shot below, not as one rule
     above both, because the img rule is later in the file at equal specificity
     and would win. */
  width:76%;margin-left:auto;margin-right:auto;
  aspect-ratio:1/1;border-radius:var(--r-lg);margin-bottom:16px;
  background:
    repeating-linear-gradient(135deg,rgba(7,28,107,.05) 0 2px,transparent 2px 11px),
    linear-gradient(150deg,var(--wash-2) 0%,var(--wash) 100%);
  border:1px solid var(--line);
  display:flex;align-items:center;justify-content:center;text-align:center;padding:14px;
}
.person .shot span{font-size:.6rem;font-weight:500;letter-spacing:.18em;text-transform:uppercase;color:var(--pebble)}
/* The portrait itself, once supplied. Selector is .person img.shot so it
   out-specifies the placeholder rule above, which would otherwise keep the
   hatched fill, the border and the 14px padding.

   The interim images are circular cut-outs composited on white, so the frame
   carries no border and no rounded box of its own: a square outline drawn round
   a circle on white reads as a mistake. If square portraits are supplied later,
   restore border and border-radius here. */
.person img.shot{
  width:76%;margin-left:auto;margin-right:auto;height:auto;aspect-ratio:1/1;object-fit:cover;
  padding:0;background:none;margin-bottom:16px;
  /* The supplied portraits are a circular cut-out inscribed in a white square,
     touching all four edges, so a 50% radius lands the ring exactly on the
     picture's own edge and clips the white corners away. That is why a border
     works here now when the earlier comment said it would not: a square
     outline drawn round a circle read as a mistake, a circular one does not.
     border-box means the 3px sits inside the 76%. */
  border:3px solid var(--blue-line);border-radius:50%;
}
.person .nm{font-weight:500;color:var(--ink);font-size:1.02rem;letter-spacing:-.012em}
.person .rl{color:var(--dim);font-size:.9rem;line-height:1.45;margin-top:3px}
@media (max-width:980px){.team-grid{--cols:3}}
@media (max-width:760px){.team-grid{--cols:2;--colgap:18px;row-gap:24px}}
