/* ==========================================================================
   components.v1.css
   Everything page agents need. Class inventory (grouped):

   0  BASE        body defaults, :focus-visible, ::selection, .skip-link,
                  .wrap (content container), .idw-b/.idw-o (logo fills),
                  .t-amber (the one approved colour utility),
                  body.has-actionbar (+ --actionbar-h) fixed-bar clearance,
                  the coarse-pointer 16px control floor
   1  NAV         .nav .nav-logo .nav-links .nav-item .nav-has-drop
                  .nav-link .nav-drop-btn .nav-drop .nd-item .nd-l .nd-d
                  .nav-right .nav-cta .nav-burger
                  .nav-drawer .drw-top .drw-sub .drw-cta  (html.nav-locked)
                  Active page: body[data-active="..."] + a[data-nav="..."]
   2  FOOTER      .footer .footer-grid .footer-brand .footer-col .footer-h
                  .footer-meta .footer-note .footer-tm .footer-bottom
                  .footer-legal
   3  TYPE        .display .h1-sheet .h2-sheet .kicker .kicker-n .annot
                  .lede .measure
   4  SLAB        .slab[data-actor="src|idw|usr|est|crd|acs|vis|aud"]
   5  TITLE BLOCK .title-block .tb-cell .tb-label .tb-fig .tb-unit .tb-cert
                  (count-up target: [data-count] [data-prefix] [data-suffix])
   6  DIMENSION   .dim .dim-line .dim-fig .dim-labels .dim-pair (--len)
   7  STAMP       .stamp .stamp-l .stamp-sub (steps(2,end) slam on .in)
   8  CHIPS+RAILS .chip .chip-dot .chip-grid .spec-rail .sr-row .sr-label
                  .sr-value .rail-layout .sheet-thumb .thumb-no .thumb-title
                  .thumb-d .thumb-grid
   9  CTA         .cta-pill .cta-ghost
   10 ELEVATION   svg.elevation .draw .lbl  (pathLength="1" on every path)
                  stroke utils .st-src .st-idw .st-usr .st-est .st-crd
                  .st-acs .st-vis .st-aud ; fill utils .fl-src .fl-idw
                  .fl-usr .fl-est .fl-crd .fl-acs .fl-vis .fl-aud
   11 PULSE       .pulse-dot (+ .run, fired by reveal.v1.js on [data-pulse]
                  containers; offset-path set inline per use; colour via
                  CSS color, e.g. style="color:var(--c-idw)")
   12 REVEAL      .r (hidden ONLY under html.js) .in fired state,
                  word-rise .line > .w, stagger via --i
   13 LEGAL       body.doc-light .doc-sheet .doc-meta .dm-label .dm-value
                  .clause .clause-n
   14 MOTION      reduced-motion kill-switch (html.no-motion + media query)

   Glass appears ONLY on .nav here; the scenario transport bar is the second
   and final glass surface and is built by the scenarios page agent.
   ========================================================================== */

/* ==========================================================================
   0. BASE
   ========================================================================== */
*,*::before,*::after{box-sizing:border-box}
body{
  margin:0;
  min-height:100svh;
  background-color:var(--bg);
  /* ambient room lighting: opposing warm/cool radials, all under 10% alpha */
  background-image:
    radial-gradient(ellipse 60% 40% at 50% -10%, rgba(37,99,235,.08), transparent 55%),
    radial-gradient(ellipse 40% 30% at 85% 14%, rgba(245,156,0,.03), transparent 45%),
    radial-gradient(ellipse 100% 30% at 50% 0%, rgba(255,255,255,.02), transparent 60%);
  background-repeat:no-repeat;
  color:var(--ink-2);
  font-family:var(--font);
  font-size:15px;
  font-weight:400;
  line-height:1.55;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
}
/* A PAGE-OWNED FIXED BOTTOM BAR, and the space it must not steal.
   A page whose primary action sits in a fixed bottom bar (the Photon demo's
   phone action bar is the only one today) adds .has-actionbar to <body> and
   publishes the bar's MEASURED height as --actionbar-h. This reserves exactly
   that much at the END of the document, so the last of the footer is never
   trapped under the bar - measured at 390, the bar is 95px tall against 88px of
   footer padding, so the headroom was negative. The reserved strip sits directly
   beneath the fixed bar, so it never reads as a blank band under the footer, and
   the fallback covers the frames before the first measurement.
   A CLASS, not body:has(.dm-actionbar): :has() is unsupported in Firefox before
   121 while this project's support contract starts at 111, where the clearance
   silently vanished - and the bar is portalled to <body>, so the rule belongs to
   this shared layer rather than to a page-local <style> block reaching into
   global markup. LiveDemo's PhoneActionBar owns both the class and the value. */
body.has-actionbar{padding-bottom:var(--actionbar-h, 96px)}
h1,h2,h3,h4,h5,h6{margin:0;color:var(--ink-1)}
p{margin:0}
a{color:inherit;text-decoration:none}
img,svg{display:block;max-width:100%}
button{font-family:inherit;font-size:inherit}
ul,ol{margin:0;padding:0}
::selection{background:rgba(37,99,235,.45);color:var(--ink-1)}
:focus-visible{outline:var(--focus-ring);outline-offset:var(--focus-offset)}

/* iOS FOCUS AUTO-ZOOM FLOOR. Mobile Safari zooms the whole document when a
   focusable form control whose text is smaller than 16px takes focus, and the
   zoom PERSISTS after blur - measured at 390 on 5 August 2026: focusing the
   enquiry email field (14px) or the language switcher (11px) left the page
   zoomed with the nav clipped off both edges. The floor is therefore a platform
   behaviour to satisfy, not a type choice, so it is authored ONCE here for
   coarse pointers at phone/tablet widths; every desktop register is untouched.
   Checkboxes and radios carry no text and never trigger it; a range slider has
   no glyphs either. option/optgroup are left alone - the native picker sizes
   its own rows.
   !important is deliberate and is the only way this holds: the sub-16px values
   live in page-local <style> blocks (which load AFTER this file) and in
   two-class selectors like .lang-switcher .lang-switcher-select, so any
   element-level selector loses on cascade or specificity - and there is no
   legitimate case for sub-16px touch typing to protect. */
@media (pointer:coarse) and (max-width:900px){
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
  select,
  textarea{font-size:16px !important}
}

.skip-link{
  position:fixed;top:8px;left:8px;z-index:140;
  padding:10px 16px;background:var(--blue);color:#fff;
  font-size:13px;font-weight:700;border-radius:var(--r-1);
  /* Parked off BOTH axes, not just above the viewport. It used to sit only
     32px above the top edge (translateY(-200%) alone), and iOS Safari's
     toolbar collapse/expand transitions displace fixed layers by tens of px
     mid-animation - the owner photographed the pill straddling the status bar
     on an iPhone (4 August 2026). With the x axis also off-screen, no vertical
     displacement can ever reveal it; :focus still brings it fully in. */
  transform:translate(calc(-100% - 24px),-200%);
}
.skip-link:focus{transform:none}
/* The bypass TARGET. reveal.v1.js gives the fragment target tabindex="-1" and
   focuses it, because activating the link previously scrolled without moving
   keyboard focus (Codex live QA, 4 August 2026: focus stayed on <body>, so the
   next Tab restarted in the global chrome). A focusable <main> then drew the
   global :focus-visible ring around the WHOLE page - measured solid 2px
   rgb(37,99,235) enclosing every section. Suppress it on the container only:
   the landing point is announced by the focus move itself, and the visitor's
   next Tab still shows a real ring on a real control. Every other focus
   indicator on the site is untouched. */
main[tabindex="-1"]:focus,
main[tabindex="-1"]:focus-visible{outline:none}

/* SCROLL-INTO-VIEW MUST CLEAR THE FIXED CHROME.
   The chrome bar (40px) and the nav below it occupy the top 106px of every page.
   Anything the browser scrolls into view - a keyboard user tabbing forward, a
   fragment link, an element calling scrollIntoView - lands at viewport y 0 by
   default, which puts it UNDER that chrome and out of reach. Measured while
   fixing the checkout pending-lock test: the seat input scrolled to top -0.08
   and a click at its own centre hit span.return-banner__label instead.
   :where() keeps this at zero specificity, so any page can still opt out, and
   scroll-margin affects only where a scroll comes to rest - never layout. The
   pricing page's own 150px on the #trial cell still wins for that deep link. */
:where(a[href], button, input, select, textarea, summary, [tabindex]:not([tabindex="-1"])){
  scroll-margin-top:120px;
}

.wrap{
  max-width:1280px;margin:0 auto;
  padding-inline:calc(var(--frame) + 16px);
}

/* ID-ware logo fills (inverse variant: white wordmark + amber D-mark) */
/* Logo fills follow the CI per-background VARIANTS, not the body ink. --ink-1
   made the mark near-black on the light register, which is CI's print-only
   black/white variant; --logo-mark resolves to brand blue there and to the
   inverted variant on any dark ground. See tokens.v1.css --logo-mark. */
.idw-b{fill:var(--logo-mark)}
.idw-o{fill:#f59c00} /* CI orange, identical in every variant - never swaps */

.t-amber{color:var(--amber-ink)}

/* ==========================================================================
   1. NAV - the ONE glass surface in this file (bible section 2 recipe)
   Markup contract (copy verbatim from docs/IDW26-TEMPLATE.html):
   <header class="nav">
     <a class="nav-logo" href="index.html">[inline logo svg]</a>
     <ul class="nav-links">
       <li class="nav-item nav-has-drop">
         <a class="nav-link" data-nav="solutions" href="solutions.html">Solutions</a>
         <button class="nav-drop-btn" type="button" aria-expanded="false"
                 aria-controls="drop-solutions" aria-label="Open Solutions menu">[caret]</button>
         <div class="nav-drop" id="drop-solutions">[.nd-item links]</div>
       </li>
       ...
     </ul>
     <div class="nav-right">
       <a class="nav-cta" href="scenarios.html">Interactive demo</a>
       <button class="nav-burger" type="button" aria-expanded="false"
               aria-controls="nav-drawer" aria-label="Open menu">[svg]</button>
     </div>
   </header>
   <nav class="nav-drawer" id="nav-drawer" aria-label="Site menu">...</nav>
   Dropdowns work without JS via :focus-within; with JS via aria-expanded;
   on pointer:fine they also open on hover.
   ========================================================================== */
.nav{
  /* Flush to the fixed 40px chrome bar. The former 42px offset exposed a 2px
     full-width strip from the obsolete drafting-frame ::before on every dark
     desktop route. */
  position:sticky;top:40px;z-index:90;
  display:flex;align-items:center;gap:28px;
  height:64px;
  margin-inline:calc(var(--frame) + 1px);
  padding:0 16px;
  background:var(--glass-bg);
  -webkit-backdrop-filter:var(--glass-blur);
          backdrop-filter:var(--glass-blur);
  /* border on ALL edges, not just the bottom: over vivid hero fields the
     55%-alpha glass panel's unarticulated square edges read as a raw exposed
     rectangle (right third of .page-hero routes, where the contrast scrim
     fades to transparent). A full hairline + bottom radius makes the panel
     boundary deliberate. */
  border:1px solid var(--glass-border);
  border-radius:0 0 var(--r-2) var(--r-2);
  box-shadow:var(--glass-inset);
}
/* min-height buys the tap target without moving the mark: the anchor was sized
   purely by its 24px SVG child (measured 123.8x24 on every page, 16px under the
   40px minimum) while the nav around it is 64px tall, so there was 40px of
   unused height to claim. align-items:center keeps the logo optically where it
   was - this grows the hit area only. */
.nav-logo{flex:none;display:flex;align-items:center;min-height:40px}
.nav-logo svg{height:24px;width:auto}

.nav-links{
  display:flex;align-items:center;gap:2px;
  list-style:none;
}
.nav-item{position:relative;display:flex;align-items:center}
.nav.scrolled{background:rgba(24,24,27,.72)}
/* (the body.doc-light nav-opacity overrides retired 2026-08-03 with the
   legal pages' move to the dark register; the light THEME nav themes itself
   through --glass-bg) */
.nav-link{
  position:relative;
  display:inline-flex;align-items:center;
  padding:8px 10px;
  font-size:13px;font-weight:600;letter-spacing:.01em;
  color:var(--ink-2);
  border-radius:var(--r-1);
  transition:color .2s var(--ease-monument);
}
.nav-link:hover{color:var(--ink-1)}

/* active page state */
body[data-active="solutions"] .nav a[data-nav="solutions"],
body[data-active="sectors"]   .nav a[data-nav="sectors"],
body[data-active="about"]     .nav a[data-nav="about"],
body[data-active="interactive"] .nav a[data-nav="interactive"]{
  color:var(--amber-ink); /* amber as TEXT - 2.10:1 on the light nav glass unmapped */
}
/* 2px amber underline tick on the active link */
body[data-active="solutions"] .nav a[data-nav="solutions"]::after,
body[data-active="sectors"]   .nav a[data-nav="sectors"]::after,
body[data-active="about"]     .nav a[data-nav="about"]::after,
body[data-active="interactive"] .nav a[data-nav="interactive"]::after{
  content:'';position:absolute;
  left:10px;right:10px;bottom:2px;height:2px;
  background:var(--amber);
}

.nav-drop-btn{
  display:inline-flex;align-items:center;justify-content:center;
  /* 32px box, no negative margin: clears the 24px target-size minimum
     including spacing from the adjacent nav link (Lighthouse target-size) */
  width:32px;height:32px;
  background:none;border:0;cursor:pointer;
  color:var(--ink-3);
  border-radius:var(--r-1);
}
.nav-drop-btn:hover{color:var(--ink-1)}
.nav-drop-btn svg{
  width:10px;height:10px;
  transition:transform .2s var(--ease-monument);
}
.nav-drop-btn[aria-expanded="true"] svg{transform:rotate(180deg)}

.nav-drop{
  position:absolute;top:calc(100% + 10px);left:0;
  z-index:95;
  min-width:300px;
  display:flex;flex-direction:column;gap:2px;
  padding:8px;
  background:
    linear-gradient(180deg, rgba(255,255,255,.03), transparent 40%),
    var(--bg-elev);
  border:1px solid var(--hairline-strong);
  border-radius:var(--r-2);
  box-shadow:var(--shadow-slab);
  opacity:0;visibility:hidden;transform:translateY(8px);
  transition:
    opacity .18s var(--ease-monument),
    transform .18s var(--ease-monument),
    visibility 0s linear .18s;
}
/* no-JS fallback ONLY: with JS present the chevron button is the keyboard
   affordance (APG disclosure pattern: Enter toggles aria-expanded), and an
   unscoped :focus-within held the panel open after an explicit close click,
   which made the chevron look dead. */
html:not(.js) .nav-item:focus-within .nav-drop,
/* JS-toggled (button precedes panel in markup) */
.nav-drop-btn[aria-expanded="true"] ~ .nav-drop{
  opacity:1;visibility:visible;transform:none;
  transition-delay:0s,0s,0s;
}
/* hover for precise pointers only */
@media (hover:hover) and (pointer:fine){
  .nav-has-drop:hover .nav-drop{
    opacity:1;visibility:visible;transform:none;
    transition-delay:0s,0s,0s;
  }
  /* an explicit chevron-close wins over hover until the pointer leaves the
     item (reveal.v1.js adds .drop-suppress on close, removes it on
     mouseleave/reopen) - otherwise clicking the chevron shut could never
     visibly close a menu the pointer was still holding open. aria-expanded
     stays excluded so a re-open click works while still hovering. */
  .nav-has-drop.drop-suppress:hover .nav-drop-btn[aria-expanded="false"] ~ .nav-drop{
    opacity:0;visibility:hidden;transform:translateY(8px);
    transition-delay:0s,0s,.18s;
  }
  /* hover bridge so the cursor can cross the 10px gap */
  .nav-has-drop::after{
    content:'';position:absolute;left:0;right:0;top:100%;height:12px;
  }
  .nav-has-drop:not(:hover):not(:focus-within)::after{pointer-events:none}
}

.nd-item{
  display:flex;flex-direction:column;gap:2px;
  padding:10px 12px;border-radius:var(--r-1);
  transition:background .15s var(--ease-monument);
}
.nd-item:hover,
.nd-item:focus-visible{background:rgba(250,250,250,.05)}
.nd-l{font-size:13px;font-weight:600;color:var(--ink-1)}
.nd-d{font-size:var(--t-small);color:var(--ink-3);letter-spacing:.02em}

.nav-right{display:flex;align-items:center;justify-content:flex-end;gap:10px;margin-left:auto}
/* phones in the long locales: at 375px the funnel nav (logo + trial pill +
   burger) overran the viewport by 10-15px on nl/de - tighten the row and
   step the logo down one size */
/* PHONES: the nav becomes a SOLID, full-bleed band flush under the chrome bar
   (owner, 5 August 2026: "on mobile make the nav bar solid" and it "floats
   down too much"). Same reasoning as .chrome-bar's 700px rule in frame.v1.css:
   translucent glass ghosts content through it and a blurred fixed layer lags
   momentum scroll. Full-bleed + flush (top 40, was 42 with side margins)
   removes the floating-pill read and gives 10px of the vertical stack back
   (64 -> 56 height). --floor is the one fully opaque surface token in both
   themes; --chrome-bg-strong is still 2% translucent. */
@media (max-width:700px){
  .nav{
    top:40px;
    height:56px;
    margin-inline:0;
    border-left:0;border-right:0;border-top:0;
    border-radius:0;
    background:var(--floor);
    -webkit-backdrop-filter:none;backdrop-filter:none;
  }
  .nav.scrolled{background:var(--floor)}
}
@media (max-width:430px){
  .nav{gap:14px;padding:0 12px}
  .nav-right{gap:8px}
  .nav-logo svg{height:20px}
}
/* theme toggle - same chrome register as the chevron buttons; 40px box for
   the tap-target floor */
.theme-toggle{
  display:inline-flex;align-items:center;justify-content:center;
  width:40px;height:40px;flex:none;
  background:none;border:0;cursor:pointer;
  color:var(--ink-3);
  border-radius:var(--r-1);
}
.theme-toggle:hover{color:var(--ink-1)}
.theme-toggle svg{width:16px;height:16px}
/* phones: the bar has no room for the toggle next to the trial pill (the
   nav-right row measured 414px against a 390 viewport) - the drawer carries
   a labelled instance instead */
@media (max-width:600px){
  .nav .theme-toggle{display:none}
}
.theme-toggle-drawer{
  display:none;
  align-items:center;gap:10px;
  width:100%;
  padding:10px 0; /* 40px+ tap target, matching the drawer rows */
  background:none;border:0;cursor:pointer;text-align:left;
  color:var(--ink-2);
}
.theme-toggle-drawer svg{width:16px;height:16px;flex:none}
.theme-toggle-drawer:hover{color:var(--ink-1)}
@media (max-width:600px){
  .nav-drawer .theme-toggle-drawer{display:flex}
}
/* the swap itself must be instant: without this every surface with its own
   colour transition animates in a staggered wave when data-theme flips
   (ThemeToggle adds the class for the flip frame only) */
html.theme-switching *,
html.theme-switching *::before,
html.theme-switching *::after{transition:none !important}
.nav-cta{
  display:inline-flex;align-items:center;justify-content:center;gap:7px;
  /* 40px, not 38: this is the nav's primary control on every funnel page and
     38px measured 2px under the minimum tap target. The nav band is 64px tall,
     so nothing else moves. */
  min-height:40px;box-sizing:border-box;
  padding:8px 18px;
  border-radius:var(--r-pill);
  background:var(--blue);color:#fff;
  font-size:13px;font-weight:700;
  transition:transform .2s var(--ease-monument),background .2s var(--ease-monument);
}
.nav-cta:hover{transform:translateY(-1px);background:var(--blue-deep)}
.nav-cta svg{width:13px;height:13px;stroke:currentColor;fill:none;stroke-width:2}
/* N2 - persistent primary trial CTA (amber) + quieter demo secondary (ghost) */
.nav-cta-trial{background:var(--amber);color:#0c0a09}
.nav-cta-trial:hover{background:#ffb02e;box-shadow:0 0 18px -4px var(--glow-amber)}
.nav-cta-ghost{background:transparent;color:var(--ink-2);border:1px solid var(--hairline-strong)}
.nav-cta-ghost:hover{background:none;color:var(--ink-1);border-color:var(--hairline-bright)}
.nav-cta-lead{display:inline-flex;align-items:center;gap:7px}
.nav-cta-short{display:none}
.nav-cta-sub{display:none}
@media (min-width:1180px){
  .nav-cta-trial{flex-direction:column;align-items:center;justify-content:center;gap:1px;padding-top:5px;padding-bottom:5px}
  .nav-cta-trial .nav-cta-sub{display:block;font-size:9px;font-weight:600;letter-spacing:.04em;text-transform:none;color:#0c0a09;line-height:1.1;text-align:center}
  /* The trial CTA carries a second caption line, so it is taller than the
     single-line ghost. Centre both pills on a shared vertical axis (row
     align-items:center) and centre the stacked lead + caption block inside the
     trial pill, so the two CTAs sit level and read as one balanced cluster. Both
     pills inherit min-height from .nav-cta, so their heights match as well. */
  .nav-right{align-items:center}
  .nav-cta-ghost{align-items:center;justify-content:center}
}

.nav-burger{
  display:none;
  /* flex:none, or the declared width is only a flex BASIS: as a flex item of
     .nav-right with the default flex-shrink:1 the burger was compressed to
     33.5px (37.6px on the funnel pages that also carry the trial CTA) whenever
     the row got tight. The sibling .nav-logo has always carried flex:none,
     which is why the logo kept its width and this did not. */
  flex:none;
  width:40px;height:40px;
  align-items:center;justify-content:center;
  background:none;border:0;cursor:pointer;
  color:var(--ink-1);
  border-radius:var(--r-1);
}
.nav-burger svg{width:20px;height:20px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round}

/* mobile: burger + full-screen drawer below 900px (NOT glass - budget) */
.nav-drawer{
  position:fixed;inset:0;z-index:85;
  display:flex;flex-direction:column;gap:0;
  /* top pad clears the sticky header band (top ~42px + 64px height ~= 106px),
     which is lifted above the open drawer (html.nav-locked .nav below) so the
     burger stays tappable to close - the first link must not sit under it. */
  padding:112px calc(var(--frame) + 16px) 48px;
  overflow-y:auto;
  background:var(--floor);  /* fully opaque: a translucent drawer lets page content ghost through */
  opacity:0;visibility:hidden;
  transition:opacity .25s var(--ease-monument),visibility 0s linear .25s;
}
.nav-drawer.open{
  opacity:1;visibility:visible;
  /* the fixed return banner sits at z-index 125 (frame.v1.css .return-banner);
     the closed drawer rests below it at 85, but an OPEN full-screen drawer must
     cover the banner so its links are not occluded - lift it just above. */
  z-index:126;
  transition-delay:0s,0s;
}
html.nav-locked{overflow:hidden}
/* While the drawer is open it sits at z-index 126 (above the return banner).
   The burger that closes it lives inside .nav (z-index 90, its own stacking
   context), so without this the open drawer covers its own close control and a
   pointer user is trapped (keyboard Escape still works). Lift the whole nav bar
   above the open drawer so the burger stays tappable; .nav has a glass
   background + backdrop blur, so drawer content does not bleed through it. */
html.nav-locked .nav{z-index:127}
.drw-top{
  padding:14px 0 8px;
  font-size:17px;font-weight:700;color:var(--ink-1);
  border-bottom:1px solid var(--hairline);
}
.drw-sub{
  padding:9px 0 9px 18px;
  font-size:14px;color:var(--ink-3);
}
.drw-sub:hover,.drw-top:hover{color:var(--amber-ink)}
.drw-cta{color:var(--amber-ink);border-bottom:0}

@media (max-width:900px){
  .nav-links{display:none}
  .nav-burger{display:flex}
}
/* The Photon funnel enters its compact mobile nav at the same 700px boundary
   as the solid header. Waiting until 480px left the German trial + photo CTAs
   wider than the 481-699px header. Keep PIAM's secondary CTA unchanged here;
   the adjacent-sibling selector is specific to the Photon trial pair. */
@media (max-width:700px){
  .nav-cta-trial + .nav-cta-ghost{display:none}
  .nav-cta-trial{padding:7px 12px;font-size:12px}
  /* The full trial label overruns the row in the long locales (de/nl); swap to
     the already-localised short label throughout the compact header range. */
  .nav-cta-trial .nav-cta-lead{display:none}
  .nav-cta-trial .nav-cta-short{display:inline-flex;align-items:center;gap:7px;white-space:nowrap}
}
@media (max-width:480px){
  .nav-cta-ghost{display:none}        /* all funnels keep one primary CTA on narrow phones */
}

/* ==========================================================================
   2. FOOTER - static HTML on every page, never JS-injected
   ========================================================================== */
.footer{
  position:relative;
  /* NO stand-alone margin here: a margin between two z1 surfaces paints
     nothing, so the fixed field composited at full strength through it and
     the pre-footer gap read as a vivid exposed band on every non-legal route.
     The 64px of air above the footer is painted by main::after instead
     (surveyed.v1.css, next to the join model). */
  margin-top:0;
  padding:64px calc(var(--frame) + 16px) 88px;
  border-top:1px solid var(--hairline);
  background:var(--bg-footer);  /* one step darker than ground */
}
/* corner crosshair ticks on the top hairline */
.footer::before,
.footer::after{
  content:'';position:absolute;top:-5px;
  width:9px;height:9px;pointer-events:none;
  background:
    linear-gradient(var(--hairline-strong),var(--hairline-strong)) center/9px 1px no-repeat,
    linear-gradient(var(--hairline-strong),var(--hairline-strong)) center/1px 9px no-repeat;
}
.footer::before{left:calc(var(--frame) + 7px)}
.footer::after{right:calc(var(--frame) + 7px)}
.footer-grid{
  max-width:1280px;margin:0 auto;
  display:grid;
  /* Only the BRAND track is declared; the nav columns are implicit and equal.
     A fixed `1.4fr 1fr 1fr 1fr` reserved four tracks while launch mode renders
     three children - the sectors column is withheld whenever every sector page
     is hidden - leaving a measured 303px of empty track at 1440 (owner report).
     Implicit columns are created per actual child, so a withheld column
     collapses and the remaining ones share the width.
     NOT `1.4fr repeat(auto-fit,minmax(180px,1fr))`: an fr track outside an
     auto-repeat is INVALID, and the engine discards the whole declaration -
     verified in Chromium, CSS.supports() false and the grid collapsed to a
     single full-width column. */
  grid-template-columns:1.4fr;
  grid-auto-flow:column;
  grid-auto-columns:1fr;
  gap:40px;
}
/* 26px, not 14: the gap below the mark IS its protection zone, and the CI
   Manual (p.11) requires clear space equal to the height of the letter "i" -
   0.975 of the rendered height, so 25.4px against the 26px artwork below. 14px
   was 0.55 of an "i", i.e. running on the manual's tight-space exception in a
   footer that has room. The other three sides already clear 2.5 "i" or more. */
.footer-brand{display:flex;flex-direction:column;gap:26px}
/* align-self is load-bearing, not tidying. This is a COLUMN flex container, so
   the default stretch resolves the svg's cross size (its width) to the whole
   369px column while the artwork is only 134px wide - and the default
   preserveAspectRatio="xMidYMid" then centres the artwork inside that box,
   putting the mark 117px right of the tagline beneath it. width:auto does not
   prevent this: it is not a definite cross size, so stretch still applies.
   flex-start makes the svg take its intrinsic width and sit flush left. */
.footer-brand svg{height:26px;width:auto;align-self:flex-start}
.footer-brand p{font-size:13px;color:var(--ink-3);max-width:300px}
.footer-h{
  margin-bottom:8px;
  font-size:var(--t-label);font-weight:800;line-height:1.2;
  letter-spacing:1.3px;text-transform:uppercase;
  color:var(--ink-3);
}
.footer-col a{
  display:block;padding:4px 0;
  font-size:12.5px;letter-spacing:.01em;color:var(--ink-3);
  transition:color .2s var(--ease-monument);
}
.footer-col a:hover{color:var(--ink-1)}
.footer-meta{
  max-width:1280px;margin:48px auto 0;
  display:flex;flex-direction:column;gap:6px;
}
.footer-note{
  font-size:12px;color:var(--ink-3);
  font-variant-numeric:tabular-nums;
}
.footer-tm{font-size:var(--t-small);color:var(--ink-3);max-width:72ch}
.footer-bottom{
  max-width:1280px;margin:20px auto 0;
  padding-top:20px;border-top:1px solid var(--hairline);
  display:flex;flex-wrap:wrap;justify-content:space-between;gap:14px;
  font-size:12px;color:var(--ink-3);
}
/* baseline alignment: the Cookie settings entry is a padded BUTTON (44px tap
   target) among ~20px links - without a baseline cross-axis its label sat a
   level below the row ("dropdown level") */
.footer-legal{display:flex;flex-wrap:wrap;gap:18px;align-items:baseline}
.footer-legal a{color:var(--ink-3);transition:color .2s var(--ease-monument)}
.footer-legal a:hover{color:var(--ink-1)}
@media (max-width:900px){
  /* grid-auto-flow MUST return to row here. The desktop rule flows columns so a
     withheld nav column can collapse; leaving that on would keep creating
     implicit COLUMNS past this explicit two-track template instead of wrapping
     to a second row, so the footer would never stack. */
  .footer-grid{grid-template-columns:1fr 1fr;grid-auto-flow:row;gap:32px}
  /* TOUCH TARGETS. Measured at 390 before this: the column links were 334x27.4
     and the legal row's links 29.3x18.6. The latter is under WCAG 2.5.8's 24px
     minimum, and the inline-links-in-a-sentence exception does not cover them -
     they are a navigation row. Desktop density is deliberately unchanged; the
     Cookie settings button in the same row is already padded to a 44px target,
     so matching the links to it also keeps that row on one alignment. */
  .footer-col a{display:flex;align-items:center;min-height:44px;padding-block:6px}
  .footer-legal{align-items:center;row-gap:2px}
  /* min-width as well as min-height: WCAG 2.5.8 is 24px on BOTH axes, and the
     short labels fail on width alone - "DPA" measured 23.5px wide at 390 even
     with the 44px height. justify-content centres the label in the padded box. */
  .footer-legal a{
    display:inline-flex;align-items:center;justify-content:center;
    min-height:44px;min-width:44px;
  }
}
@media (max-width:520px){
  .footer-grid{grid-template-columns:1fr;grid-auto-flow:row}
}

/* ==========================================================================
   3. TYPE
   The headline scale (.display / .h1-sheet / .h2-sheet), the bracketed .kicker
   and .lede are defined ONCE, in surveyed.v1.css - it loads after this sheet at
   equal specificity, so a second base definition here would be dead code that
   makes a future edit report success while changing nothing (docs/decisions.md
   2026-07-25). Do not reintroduce them. Only .annot and .measure, which
   surveyed.v1.css does not redefine, lives here. `.measure` moved with .lede -
   `.lede.measure` is a deliberate 100-plus-use pairing that widens the lede to
   the full 64ch, and it only wins on source order, so it has to stay after
   .lede's own max-width.
   ========================================================================== */
.annot{
  font-size:var(--t-label);font-weight:600;
  letter-spacing:1.3px;text-transform:uppercase;
  color:var(--annot);
  font-variant-numeric:tabular-nums;
}

/* ==========================================================================
   4. SLAB - feature card (sharp corners, hairline border, actor left rule)
   ========================================================================== */
.slab{
  position:relative;
  padding:26px 26px 28px;
  background:
    linear-gradient(180deg, rgba(255,255,255,.025), transparent 40%),
    var(--surface);
  border:1px solid var(--hairline);
  border-left:3px solid var(--actor, var(--hairline-strong));
  border-radius:var(--r-0);
  box-shadow:var(--shadow-slab);
  transition:
    transform .3s var(--ease-monument),
    border-color .25s var(--ease-monument);
}
.slab::after{
  content:'';position:absolute;inset:-1px;
  pointer-events:none;
  box-shadow:0 0 0 1px rgba(37,99,235,.25), 0 16px 48px -12px var(--glow-blue);
  opacity:0;
  transition:opacity .25s var(--ease-monument);
}
.slab:hover{
  transform:translateY(-2px);
  border-top-color:color-mix(in srgb, var(--actor, var(--hairline-bright)) 35%, var(--hairline));
  border-right-color:color-mix(in srgb, var(--actor, var(--hairline-bright)) 35%, var(--hairline));
  border-bottom-color:color-mix(in srgb, var(--actor, var(--hairline-bright)) 35%, var(--hairline));
}
.slab:hover::after{opacity:1}
.slab[data-actor="src"]{--actor:var(--c-src)}
.slab[data-actor="idw"]{--actor:var(--c-idw)}
.slab[data-actor="usr"]{--actor:var(--c-usr)}
.slab[data-actor="est"]{--actor:var(--c-est)}
.slab[data-actor="crd"]{--actor:var(--c-crd)}
.slab[data-actor="acs"]{--actor:var(--c-acs)}
.slab[data-actor="vis"]{--actor:var(--c-vis)}
.slab[data-actor="aud"]{--actor:var(--c-aud)}
.slab h3{font-size:17px;font-weight:700;margin-bottom:8px}
.slab p{font-size:14px;color:var(--ink-3);line-height:1.55}

/* ==========================================================================
   5. TITLE BLOCK - engineering stat table (bible 5b)
   Figures: <span class="tb-fig" data-count="50" data-suffix="+">50+</span>
   Final value ALWAYS shipped in HTML; reveal.v1.js animates then restores it.
   One cell is always certifications (.tb-cert stamped text).
   ========================================================================== */
.title-block{
  display:grid;
  grid-template-columns:repeat(auto-fit, minmax(170px, 1fr));
  gap:1px;
  background:var(--hairline-strong);
  border:1px solid var(--hairline-strong);
  border-radius:var(--r-0);
  box-shadow:var(--edge);
}
.tb-cell{
  display:flex;flex-direction:column;gap:10px;justify-content:space-between;
  min-height:108px;
  padding:20px;
  background:var(--bg);
  transition:background .2s var(--ease-monument);
}
.tb-cell:hover{background:var(--bg-1)}
.tb-fig::after{
  content:'';display:block;
  width:20px;height:1px;margin-top:8px;
  background:var(--amber);
}
.tb-label{
  font-size:var(--t-label);font-weight:800;
  letter-spacing:1.3px;text-transform:uppercase;
  color:var(--ink-3);
}
.tb-fig{
  font-size:clamp(1.75rem, 1.3rem + 1.4vw, 2.5rem);
  font-weight:800;line-height:1;letter-spacing:-.02em;
  color:var(--ink-1);
  font-variant-numeric:tabular-nums;
  overflow-wrap:anywhere;
}
/* text-valued figures ("Government scale", "Enterprise scale") use a much
   smaller size than numeric figures so the words fit the cell without clipping.
   line-height matches the numeric .tb-fig (1) so both share a top edge when a
   row mixes numeric and text figures; padding on .tb-cell gives wrapped text
   its breathing room instead of leading. */
.tb-fig-text{
  font-size:clamp(1.05rem, .85rem + .7vw, 1.45rem);
  line-height:1;letter-spacing:-.01em;
  font-variant-numeric:normal;
}
.tb-unit{font-size:.5em;font-weight:700;color:var(--ink-3)}
.tb-cert{
  font-size:12px;font-weight:800;
  letter-spacing:1.3px;text-transform:uppercase;
  color:var(--stamp);
  line-height:1.8;
}

/* ==========================================================================
   6. DIMENSION CALLOUT (bible 5d)
   <div class="dim r">
     <span class="dim-line" aria-hidden="true"></span>
     <span class="dim-fig">&lt; 4 MIN</span>
     <span class="dim-line" aria-hidden="true"></span>
   </div>
   Optional endpoint labels: .dim-labels row directly after the .dim.
   .dim-pair stacks two dims of visibly different length via --len
   (e.g. style="--len:95%" vs style="--len:20%").
   ========================================================================== */
.dim{
  position:relative;
  display:flex;align-items:center;
  width:var(--len, 100%);
  max-width:600px;
  height:32px;
}
.dim::before,
.dim::after{
  content:'';flex:none;
  width:1px;height:18px;
  background:var(--datum);
}
.dim-line{
  flex:1;height:1px;
  background:var(--datum);
  transform-origin:left center;
}
.dim-line:last-of-type{transform-origin:right center}
html.js .dim .dim-line{
  transform:scaleX(0);
  transition:transform .9s var(--ease-draw) calc(var(--i, 0) * .18s);
}
html.js .in .dim .dim-line,
html.js .dim.in .dim-line{transform:scaleX(1)}
.dim-fig{
  flex:none;
  padding:0 14px;
  font-size:13px;font-weight:800;
  letter-spacing:1.3px;text-transform:uppercase;
  color:var(--ink-1);
  font-variant-numeric:tabular-nums;
  white-space:nowrap;
}
.dim-labels{
  display:flex;justify-content:space-between;gap:16px;
  width:var(--len, 100%);max-width:600px;
  margin-top:4px;
  font-size:var(--t-label);font-weight:700;
  letter-spacing:1.3px;text-transform:uppercase;
  color:var(--ink-3);
}
.dim-pair{display:grid;gap:20px}

/* ==========================================================================
   7. APPROVAL STAMP (bible 5g) - amber double-ring ellipse, rotated -8deg,
   two-frame steps() slam on .in. Only against true certifications.
   Mark the stamp aria-hidden="true" and put the real text alongside.
   ========================================================================== */
.stamp{
  position:relative;
  display:inline-flex;flex-direction:column;
  align-items:center;justify-content:center;gap:2px;
  width:160px;height:100px;
  padding:12px;
  border:2px solid var(--stamp);
  border-radius:50%;
  color:var(--stamp);
  text-transform:uppercase;text-align:center;
  font-weight:800;letter-spacing:1.3px;font-size:var(--t-label);
  transform:rotate(-8deg);
}
.stamp::before{
  content:'';position:absolute;inset:4px;
  border:1px solid var(--stamp);border-radius:50%;
}
.stamp-l{font-size:13px}
.stamp-sub{font-size:var(--t-label);font-weight:700}
html.js .stamp{opacity:0}
html.js .in .stamp,
html.js .stamp.in{
  animation:stamp-slam .2s steps(2,end) forwards;
}
@keyframes stamp-slam{
  from{opacity:0;transform:rotate(-8deg) scale(1.6)}
  to{opacity:1;transform:rotate(-8deg) scale(1)}
}
html.js.no-motion .stamp{opacity:1;animation:none}

/* ==========================================================================
   8. CHIPS, SPEC RAIL, SHEET THUMBNAILS
   ========================================================================== */
.chip{
  display:inline-flex;align-items:center;gap:7px;
  padding:6px 10px;
  border:1px solid var(--hairline);
  border-radius:var(--r-1);
  background:var(--surface);
  font-size:var(--t-label);font-weight:600;
  letter-spacing:.04em;
  color:var(--ink-2);
  white-space:nowrap;
  transition:border-color .15s var(--ease-monument);
}
.chip:hover{border-color:var(--hairline-bright)}
.chip-dot{
  width:5px;height:5px;flex:none;
  border-radius:50%;
  background:var(--actor, var(--amber));
}
.chip-grid{display:flex;flex-wrap:wrap;gap:8px}

/* left sticky annotation rail (product pages) inside .rail-layout */
.rail-layout{
  display:grid;
  grid-template-columns:260px minmax(0,1fr);
  gap:clamp(32px, 4vw, 72px);
  align-items:start;
}
.spec-rail{
  position:sticky;top:96px;
  align-self:start;
  display:flex;flex-direction:column;
  border:1px solid var(--hairline);
  background:var(--surface);
}
.sr-row{
  display:flex;flex-direction:column;gap:3px;
  padding:14px 16px;
  border-bottom:1px solid var(--hairline);
}
.sr-row:last-child{border-bottom:0}
.sr-label{
  max-width:100%;
  font-size:var(--t-label);font-weight:800;line-height:1.3;
  letter-spacing:1.3px;text-transform:uppercase;
  color:var(--ink-3);
  text-wrap:balance;
}
.sr-value{
  font-size:13px;font-weight:600;color:var(--ink-1);
  font-variant-numeric:tabular-nums;
}
@media (max-width:900px){
  /* minmax(0,1fr) not 1fr: a 1fr track keeps min-width:auto, so a child that
     can't shrink (e.g. a width:100% SVG figure) blows the single column past
     the viewport and produces a horizontal scrollbar on mobile. */
  .rail-layout{grid-template-columns:minmax(0,1fr)}
  .spec-rail{position:static}
}

/* related-page sheet thumbnails */
.thumb-grid{
  display:grid;
  grid-template-columns:repeat(auto-fit, minmax(220px, 1fr));
  gap:16px;
}
.sheet-thumb{
  display:flex;flex-direction:column;gap:6px;
  padding:18px;
  border:1px solid var(--hairline);
  background:var(--surface);
  border-radius:var(--r-0);
  transition:transform .3s var(--ease-monument),border-color .3s var(--ease-monument);
}
.sheet-thumb:hover{transform:translateY(-2px);border-color:var(--hairline-strong)}
.thumb-no{
  font-size:var(--t-label);font-weight:800;
  letter-spacing:1.3px;text-transform:uppercase;
  color:var(--amber-ink);
  font-variant-numeric:tabular-nums;
}
.thumb-title{font-size:15px;font-weight:700;color:var(--ink-1)}
.thumb-d{font-size:12px;color:var(--ink-3)}

/* ==========================================================================
   9. CTA PILLS - the ONLY pill radius in the system (plus .nav-cta)
   ========================================================================== */
.cta-pill{
  position:relative;
  display:inline-flex;align-items:center;gap:8px;
  padding:14px 28px;
  border-radius:var(--r-pill);
  background:var(--blue);color:#fff;
  font-size:14px;font-weight:600;letter-spacing:.01em;
  border:0;cursor:pointer;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.18);  /* machined top edge */
  transition:
    transform .2s var(--ease-monument),
    background .2s var(--ease-monument),
    filter .2s var(--ease-monument);
}
.cta-pill::after{
  content:'';position:absolute;inset:0;
  border-radius:inherit;pointer-events:none;
  box-shadow:0 0 24px -6px rgba(37,99,235,.5);
  opacity:0;
  transition:opacity .2s var(--ease-monument);
}
.cta-pill:hover{transform:translateY(-1px);filter:brightness(1.12)}
/* light theme: the pill's machined sheen composites the --blue fill up to
   ~rgb(77,128,238), taking its white label to 3.71:1 at 390 - deepen the
   fill one brand step so the label holds AA under the sheen */
/* Light register: --blue is remapped to the CI brand blue in tokens.v1.css, so
   this pill and the nav CTA are the SAME blue. Do not reintroduce a per-selector
   override here - that is what left one button navy and the other indigo. */
.cta-pill:hover::after{opacity:1}
.cta-pill:active{transform:translateY(1px)}
/* inline arrow glyphs: size them HERE, once. An unsized <svg viewBox> falls back
   to its 300x150 intrinsic size (capped only by img,svg{max-width:100%}), which
   measured 137x137 inside a cta-band pill and pushed the label onto three lines
   on five pages. Page-local copies of this rule are duplication debt - a later
   duplicate is what silently overrode the home hero type, so keep it shared. */
.cta-pill svg,.cta-ghost svg{
  width:16px;height:16px;flex:none;
  fill:none;stroke:currentColor;
  stroke-width:2;stroke-linecap:round;stroke-linejoin:round;
}
.cta-ghost{
  display:inline-flex;align-items:center;gap:8px;
  padding:13px 27px;
  border-radius:var(--r-pill);
  border:1px solid var(--hairline-strong);
  background:transparent;color:var(--ink-1);
  font-size:14px;font-weight:600;letter-spacing:.01em;
  cursor:pointer;
  transition:
    transform .2s var(--ease-monument),
    border-color .2s var(--ease-monument),
    background .2s var(--ease-monument);
}
.cta-ghost:hover{
  transform:translateY(-1px);
  border-color:var(--ink-3);
  background:rgba(250,250,250,.04);
}
.cta-ghost:active{transform:translateY(1px)}

/* ==========================================================================
   10. ELEVATION CONTRACT - self-drawing diagrams (bible 5c)
   Diagrams come ONLY from docs/IDW26-DIAGRAMS.html. Every drawable path:
   class="draw", pathLength="1". Labels: class="lbl". Stagger with --i on
   the path (180ms steps). Reveal: .in on the svg or any ancestor.
   ========================================================================== */
svg.elevation{
  width:100%;height:auto;max-width:880px;
  margin-inline:auto;
  /* faint dot-grid backdrop panel, faded at the edges */
  background-image:
    radial-gradient(ellipse 75% 75% at 50% 50%, rgba(0,0,0,0) 55%, var(--bg) 100%),
    radial-gradient(rgba(250,250,250,.05) 1px, transparent 1.4px);
  background-size:100% 100%, 20px 20px;
  background-position:center, center;
}
svg.elevation .draw{
  fill:none;
  stroke-width:var(--stroke-w);
  stroke-dasharray:1;
  stroke-dashoffset:1;
  vector-effect:non-scaling-stroke;
  transition:stroke-dashoffset 1.2s var(--ease-draw) calc(var(--i, 0) * .18s);
}
.in svg.elevation .draw,
svg.elevation.in .draw{stroke-dashoffset:0}
svg.elevation .lbl{
  fill:var(--annot);
  font-family:var(--font);
  font-size:11px;
  font-weight:700;
  letter-spacing:1.3px;
  opacity:0;
  transition:opacity .6s var(--ease-monument) .9s;
}
svg.elevation .lbl.dim-fig{fill:var(--amber-ink);font-weight:800}
/* VIEWBOX-COMPENSATED LABELS (documented trap 4). font-size inside an SVG with
   a viewBox and no width/height is expressed in USER UNITS, so the browser
   multiplies it by the viewBox->viewport scale. Measured at 390px that scale
   runs 0.318 (/cards) to 0.394 (/healthcare ward), which rendered the 11px
   declaration above at 3.5-4.3px effective - the labels vanished.

   The compensation therefore has to track the viewport, not the sheet: rendered
   width is roughly 0.85 x viewport for the standard figure column, so the units
   needed for a constant 11px effective are proportional to 1/viewport. CSS
   cannot invert a viewport unit, but over the 390-720px band a straight line
   fits inside +/-1px of the target. Fitted against measured scale on
   /access-management (the modal 800-unit sheet): 14.1 units at 720px, 29.3 at
   390px, giving 47.26 - 4.606vw, clamped so it cannot run away outside the fit.
   Effective result across the band is 11.0-12.6px - never below the floor.

   Whitespace around the '-' is mandatory; without it the whole declaration is
   discarded and the labels silently fall back to the 11px above.

   Per-figure escape hatch: a sheet whose viewBox or column width differs
   materially from the 800-unit norm states its own value, which outranks this
   on specificity - .perim-fig svg.elevation.perim-plan--tall text.lbl (460
   units, 20) and .survey-fig svg.ps-plan--tall .lbl (420 units, 18) already do,
   and both measure 12.9px effective at 390px. /cards renders its 800-unit sheet
   into a narrower 254px column (scale 0.318) and needs ~35 units, so it still
   wants a page-side value.

   The fill also brightens, mirroring the same page precedent: --annot on a
   4px-tall glyph run is unreadable even once the size is right. */
@media (max-width:720px){
  svg.elevation .lbl{
    font-size:clamp(14px, 47.26px - 4.606vw, 30px);
    letter-spacing:.06em;
    fill:var(--ink-2);
  }
}
.in svg.elevation .lbl,
svg.elevation.in .lbl{opacity:1}
/* dashed strokes (fences, routes, datum feeds): pathLength-normalised dasharray
   cannot both dash and draw, so .dash keeps its dashes and fades in on its beat */
svg.elevation .draw.dash{
  stroke-dasharray:.018 .014;
  stroke-dashoffset:0;
  opacity:0;
  transition:opacity .7s var(--ease-draw) calc(var(--i, 0) * .18s);
}
.in svg.elevation .draw.dash,
svg.elevation.in .draw.dash{opacity:1}
/* no-JS and reduced motion render the finished drawing */
html.no-motion svg.elevation .draw,
html:not(.js) svg.elevation .draw{stroke-dashoffset:0;transition:none}
html.no-motion svg.elevation .lbl,
html:not(.js) svg.elevation .lbl{opacity:1;transition:none}
html.no-motion svg.elevation .draw.dash,
html:not(.js) svg.elevation .draw.dash{opacity:1;transition:none}

/* actor colour utilities - stroke */
.st-src{stroke:var(--c-src)}
.st-idw{stroke:var(--c-idw)}
.st-usr{stroke:var(--c-usr)}
.st-est{stroke:var(--c-est)}
.st-crd{stroke:var(--c-crd)}
.st-acs{stroke:var(--c-acs)}
.st-vis{stroke:var(--c-vis)}
.st-aud{stroke:var(--c-aud)}
/* actor colour utilities - fill */
.fl-src{fill:var(--c-src)}
.fl-idw{fill:var(--c-idw)}
.fl-usr{fill:var(--c-usr)}
.fl-est{fill:var(--c-est)}
.fl-crd{fill:var(--c-crd)}
.fl-acs{fill:var(--c-acs)}
.fl-vis{fill:var(--c-vis)}
.fl-aud{fill:var(--c-aud)}

/* ==========================================================================
   11. TRAVELLING PULSE (bible 5e) - the only organic motion.
   Place inside a container with [data-pulse] (optional value = delay in ms,
   default 1500 to let the diagram draw first). Set the path inline:
     <span class="pulse-dot" style="offset-path:path('M0 0 ...');
           color:var(--c-idw)"></span>
   reveal.v1.js adds .run after the container fires .in plus the delay.
   Loop with --pulse-iter, retime with --pulse-dur / --pulse-delay.
   ========================================================================== */
.pulse-dot{
  position:absolute;top:0;left:0;
  width:18px;height:18px;
  border-radius:50%;
  pointer-events:none;
  opacity:0;
  color:var(--sky);
  offset-rotate:0deg;
  offset-distance:0%;
  background:
    radial-gradient(circle, currentColor 0 2.6px, rgba(0,0,0,0) 3.4px),
    radial-gradient(circle, var(--glow-pulse) 0 2px, rgba(0,0,0,0) 8.6px);
}
.pulse-dot.run{
  animation:pulse-travel var(--pulse-dur, 2.4s) var(--ease-draw)
    var(--pulse-delay, 0s) var(--pulse-iter, 1) both;
}
@keyframes pulse-travel{
  0%{opacity:0;offset-distance:0%}
  12%{opacity:1}
  88%{opacity:1}
  100%{opacity:0;offset-distance:100%}
}
html.no-motion .pulse-dot{display:none}

/* ==========================================================================
   12. REVEAL STATES - hidden ONLY under html.js (crawlers/no-JS see all)
   .r          base reveal target (IO adds .in once, threshold .25)
   --i         sibling stagger index (120ms slabs, 60ms words)
   .line > .w  masked word-rise for the monument headline:
     <span class="line"><span class="w" style="--i:0">One</span> ...</span>
   ========================================================================== */
html.js .r{
  opacity:0;
  transform:translateY(18px);
  filter:blur(6px);
  transition:
    opacity .8s var(--ease-monument) calc(var(--i, 0) * 120ms),
    transform .8s var(--ease-monument) calc(var(--i, 0) * 120ms),
    filter .8s var(--ease-monument) calc(var(--i, 0) * 120ms);
}
html.js .r.in{opacity:1;transform:none;filter:none}
html.js.no-motion .r{opacity:1;transform:none;filter:none;transition:none}

/* PHONES: a shorter, lighter reveal on the INTERSECTION-OBSERVER FLOOR.
   Scope matters here. Where the browser supports scroll-driven timelines this
   transition is overridden outright by `transition: none` in the @supports block
   in surveyed.v1.css, and the phone symptom the owner reported ("text blurs and
   doesn't appear until near the top") lives THERE, in animation-range - it is
   fixed at that rule, not this one. What this block covers is the floor: no-JS
   is not affected (the hidden state needs html.js), but older mobile Safari
   without view() takes it, and so does every session after a client-side
   navigation, when reveal.v1.js sets html.no-view-anim and hands the whole site
   back to the observer. On a phone you flick rather than dwell, so the floor gets
   half the duration and a third of the blur too. Desktop is untouched and
   html.no-motion above still wins outright. */
@media (max-width:700px){
  html.js .r{
    filter:blur(2px);
    transform:translateY(10px);
    transition:
      opacity .4s var(--ease-monument) calc(var(--i, 0) * 60ms),
      transform .4s var(--ease-monument) calc(var(--i, 0) * 60ms),
      filter .4s var(--ease-monument) calc(var(--i, 0) * 60ms);
  }
  html.js .line>.w{transition:transform .4s var(--ease-monument) calc(var(--i, 0) * 30ms)}
}

.line{display:block;overflow:hidden}
.line>.w{display:inline-block}
html.js .line>.w{
  transform:translateY(115%);
  transition:transform .8s var(--ease-monument) calc(var(--i, 0) * 60ms);
}
html.js .in .line>.w,
html.js .r.in .line>.w{transform:none}
html.js.no-motion .line>.w{transform:none;transition:none}

/* --------------------------------------------------------------------------
   LCP / above-the-fold headings - readable in the FIRST painted frame.
   The masked word-rise (and the .r block reveal) start fully hidden until the
   IntersectionObserver adds .in, which only happens AFTER first paint - so an
   early capture, and the LCP element itself, show an empty heading (a
   conversion + LCP defect). Elements tagged .line-lcp / .r-lcp opt in to a
   base state that is legible at t=0 (full opacity, no blur, correct colour)
   and settle with a short rise on .in. Signature choreography is unchanged
   for everything below the fold. Both variants are html.no-motion /
   prefers-reduced-motion safe (pinned static, no transition).
   line-lcp puts the entrance on the LINE as a whole and holds its words at
   rest, so they stay visible inside the still-masked line (overflow unchanged,
   so the amber-rule / line-amber underlines are unaffected) and nothing clips. */
html.js .line-lcp{
  transform:translateY(0.22em);
  transition:transform .7s var(--ease-monument);
}
html.js .line-lcp>.w{transform:none}
html.js .in .line-lcp,
html.js .r.in .line-lcp{transform:none}
html.js.no-motion .line-lcp,
html.js.no-motion .line-lcp>.w{transform:none;transition:none}

/* block copy (kicker / eyebrow / lede): visible at first paint, subtle rise. */
html.js .r.r-lcp{
  opacity:1;
  transform:translateY(10px);
  filter:none;
  transition:transform .8s var(--ease-monument) calc(var(--i, 0) * 120ms);
}
html.js .r.r-lcp.in{transform:none}
html.js.no-motion .r.r-lcp{transform:none;transition:none}

/* line-soft: a non-masking line wrapper (StaggerHeading uses it for the blur
   section-heading variant). The masked .line clips a single-line band via
   overflow:hidden, which would clip a section heading that wraps to two visual
   rows. line-soft keeps the words visible so the blur-to-sharp settles cleanly,
   and pairs with the small unclipped rise on .w-blur below. */
.line-soft{display:block;overflow:visible}

/* Blur-to-sharp word variant (StaggerHeading blur prop; adapted from the React
   Bits BlurText effect, folded in here as a class - no new dependency). Each word
   animates filter:blur(8px) -> 0 ALONGSIDE a small unclipped rise, on the same
   .in trigger and --i stagger. Only transform + filter animate (no layout). */
html.js .line>.w.w-blur,
html.js .line-soft>.w.w-blur{
  transform:translateY(14px);
  filter:blur(8px);
  transition:
    transform .8s var(--ease-monument) calc(var(--i, 0) * 60ms),
    filter .8s var(--ease-monument) calc(var(--i, 0) * 60ms);
}
html.js .in .line>.w.w-blur,
html.js .r.in .line>.w.w-blur,
html.js .in .line-soft>.w.w-blur,
html.js .r.in .line-soft>.w.w-blur{transform:none;filter:none}
/* Reduced motion: render static and unblurred, exactly like the plain word. */
html.js.no-motion .line>.w.w-blur,
html.js.no-motion .line-soft>.w.w-blur{transform:none;filter:none;transition:none}
@media (prefers-reduced-motion: reduce){
  html.js .line>.w.w-blur,
  html.js .line-soft>.w.w-blur{transform:none !important;filter:none !important}
}

/* REVEAL FAILSAFE - html.reveal-safe is set by the layout boot watchdog when
   reveal.v1.js has not executed within 3s (network blip, blocked asset, edge
   403: a 12 August 2026 capture served the document while every subresource
   403'd, leaving 83% of the home page's text at opacity 0). The hide states
   above are applied by an inline script that cannot fail, so the un-hide must
   not depend on the one fetched file. This list pins EVERY hidden state in
   section 12 to its finished frame - a new hidden state added above must add
   its selector here, exactly as it must add a .no-motion rescue. Placed after
   the whole section so equal-specificity contests resolve here by order; the
   scroll-timeline path excludes .reveal-safe in surveyed.v1.css the same way
   it excludes .no-view-anim. */
html.js.reveal-safe .r,
html.js.reveal-safe .r.r-lcp,
html.js.reveal-safe .line>.w,
html.js.reveal-safe .line-lcp,
html.js.reveal-safe .line-lcp>.w,
html.js.reveal-safe .line>.w.w-blur,
html.js.reveal-safe .line-soft>.w.w-blur{opacity:1;transform:none;filter:none;transition:none}

/* ==========================================================================
   13. LEGAL DOCUMENT REGISTER - RETIRED 2026-08-03. The paper-era .doc-*
   rules that lived here moved to legal.v1.css (single owner, loaded last)
   when the legal pages adopted the dark LegalPage register; body.doc-light
   is no longer set anywhere.
   ========================================================================== */

/* ==========================================================================
   14. REDUCED-MOTION KILL-SWITCH (first-class mode, mirrored by JS gates)
   ========================================================================== */
@media (prefers-reduced-motion: reduce){
  *,
  *::before,
  *::after{
    animation-duration:.01ms !important;
    animation-delay:0ms !important;
    animation-iteration-count:1 !important;
    transition-duration:.01ms !important;
    transition-delay:0ms !important;
    scroll-behavior:auto !important;
  }
  /* Pin the reveal to its finished state without waiting for reveal.v1.js to add
     html.no-motion: under reduced motion the .r initial opacity:0 / translate /
     blur must never paint, even in the sliver before the script boots (otherwise
     content flashes hidden). This mirrors html.js.no-motion .r but is keyed on
     the media query so it does not depend on JS timing. */
  html.js .r{opacity:1 !important;transform:none !important;filter:none !important}
  html.js .line>.w{transform:none !important}
  html.js .line-lcp{transform:none !important}
}
html.no-motion *,
html.no-motion *::before,
html.no-motion *::after{
  animation-duration:.01ms !important;
  animation-delay:0ms !important;
  animation-iteration-count:1 !important;
  transition-duration:.01ms !important;
  transition-delay:0ms !important;
}

/* ==========================================================================
   15  SOCIAL PROOF + TESTIMONIALS (slots). Styled but rendered only when the
   data is populated with approved, consented content - see SocialProof.tsx,
   Testimonials.tsx and src/data/social-proof.ts (shipped empty on purpose).
   ========================================================================== */
/* "Trusted by" wordmark band (WordmarkBand.tsx) - text names, no logos */
/* hosted inside a veiled .sheet-section by WordmarkBand.tsx (a bare top-level
   band's own margins would expose the fixed field - see the seam class of
   bug); the section supplies the air, so no outer margin here */
.wm-band{margin:0;padding-block:28px;border-top:1px solid var(--hairline);border-bottom:1px solid var(--hairline);text-align:center}
.wm-label{font-family:var(--font-mono);font-size:var(--t-label);font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-3);margin-bottom:18px}
.wm-names{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:14px 32px;list-style:none;padding:0;margin:0}
.wm-name{font-family:var(--font-display);font-size:clamp(15px,1rem + .4vw,19px);font-weight:700;letter-spacing:-.01em;color:var(--ink-2)}

.sp-strip{margin:48px 0;text-align:center}
.sp-label{
  font-family:var(--font-mono);font-size:var(--t-label);font-weight:600;
  letter-spacing:.14em;text-transform:uppercase;color:var(--ink-3);margin-bottom:20px;
}
.sp-logos{
  display:flex;flex-wrap:wrap;align-items:center;justify-content:center;
  gap:28px 48px;list-style:none;padding:0;margin:0;
}
.sp-logos img{
  height:28px;width:auto;opacity:.62;filter:grayscale(1);
  transition:opacity .2s var(--ease-monument),filter .2s var(--ease-monument);
}
.sp-logos li:hover img{opacity:1;filter:grayscale(0)}

.tm-heading{
  font-family:var(--font-mono);font-size:var(--t-label);font-weight:600;
  letter-spacing:.14em;text-transform:uppercase;color:var(--ink-3);
}
.tm-grid{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));
  gap:16px;margin-top:24px;list-style:none;padding:0;
}
.tm-card{
  padding:24px;border:1px solid var(--hairline);border-radius:12px;
  background:linear-gradient(180deg,rgba(255,255,255,.03),transparent 32%),var(--surface);
}
.tm-quote{margin:0;font-size:15px;line-height:1.6;color:var(--ink-1)}
.tm-attr{margin:16px 0 0;font-size:13px;color:var(--ink-3)}
.tm-name{font-weight:700;color:var(--ink-2)}
