/* ==========================================================================
   NIVIO — BRAND STANDARDS v3.0 LAYER
   September 2026. Supersedes the v2-derived build.

   Scope: VISUAL ONLY. This file contains no text content and changes nothing
   about how Nivio writes. Voice rules stand exactly as they are.

   Load order, last in <head>:
       1. the page's own style element
       2. shared/nivio-v3-compat.css      (D4 markup -> v3 token remap)
       3. shared/nivio-v3.css     (this file — v3 deltas over that)

   What this file changes relative to the v2 build:
       - self-hosted Geist, replacing the Google Fonts CDN load     (p27)
       - display/heading weight 500 -> 600                          (p28)
       - the full v3 type scale, tracking and casing rules          (p28-29)
       - radius 0 everywhere -> 3px controls / 4px cards / 0 imagery(p34)
       - container 1240 -> 1280, section padding 112/78 -> 96/64    (p32-33)
       - --signal-ink -> the three-value Signal family              (p23)
       - the eight-step neutral ramp and semantic states            (p23)
       - button, input, card, nav, footer and table specs           (p36-40)
       - the four motion tokens                                     (p46)
   ========================================================================== */

/* ---------------------------------------------------------------
   0. TYPEFACES — self-hosted (p27)
   "Self-host WOFF2. Do not load from a third-party CDN."
   Weights shipped: 400/500/600 sans, 400/500 mono. Nothing else is used.
   --------------------------------------------------------------- */
@font-face{font-family:"Geist";src:url("/shared/fonts/Geist-Regular.woff2") format("woff2");
  font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:"Geist";src:url("/shared/fonts/Geist-Medium.woff2") format("woff2");
  font-weight:500;font-style:normal;font-display:swap}
@font-face{font-family:"Geist";src:url("/shared/fonts/Geist-SemiBold.woff2") format("woff2");
  font-weight:600;font-style:normal;font-display:swap}
@font-face{font-family:"Geist Mono";src:url("/shared/fonts/GeistMono-Regular.woff2") format("woff2");
  font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:"Geist Mono";src:url("/shared/fonts/GeistMono-Medium.woff2") format("woff2");
  font-weight:500;font-style:normal;font-display:swap}

/* ---------------------------------------------------------------
   1. TOKENS — transcribed from p62, the single source of truth
   --------------------------------------------------------------- */
:root{
  /* color: core */
  --nv-carbon:#171918; --nv-graphite:#222523; --nv-slate:#3A3D3B; --nv-ash:#5F6461;
  --nv-steel:#9EA29F;  --nv-fog:#C8C6BE;      --nv-mist:#E4E1D8;  --nv-bone:#F2F0E9;

  /* color: signal — three values, three jobs (p23) */
  --nv-signal:#D7E84A;       /* anything on a dark surface; the brand accent   */
  --nv-signal-hov:#C3D53C;
  --nv-signal-act:#B0C133;
  --nv-signal-mid:#8EA31D;   /* non-text graphics on light: slash, rules      */
  --nv-signal-moss:#41630F;  /* text + accent marks on light — 6.11:1 on Bone */
  /* --nv-signal-deep:#5A6416 RETIRED at v3.2 — replaced by Signal Moss above    */

  /* semantic states — product UI only, each clears 4.5:1 on its surface */
  --nv-positive-d:#8FCB6E; --nv-caution-d:#F0C05A; --nv-critical-d:#FF7A5C; --nv-info-d:#7FB8D9;
  --nv-positive-l:#3F7A2E; --nv-caution-l:#8A6209; --nv-critical-l:#B3341A; --nv-info-l:#1F5F82;

  /* roles */
  --nv-surface:var(--nv-carbon);
  --nv-surface-raised:var(--nv-graphite);
  --nv-surface-alt:var(--nv-bone);
  --nv-text:var(--nv-bone);
  --nv-text-muted:var(--nv-steel);
  --nv-text-on-alt:var(--nv-carbon);
  --nv-text-muted-alt:var(--nv-ash);
  --nv-border:rgba(242,240,233,.10);
  --nv-border-strong:rgba(242,240,233,.45);  /* choice controls — a 10% edge disappears */
  --nv-border-alt:var(--nv-fog);
  --nv-focus:var(--nv-signal);

  /* type */
  --nv-font:"Geist","Helvetica Neue",Helvetica,Arial,sans-serif;
  --nv-font-mono:"Geist Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;

  /* tracking */
  --nv-track-display:-.03em;  --nv-track-h1:-.026em;  --nv-track-h2:-.02em;
  --nv-track-h3:-.012em;      --nv-track-metric:-.025em;
  --nv-track-label:.12em;     --nv-track-data:.02em;  --nv-track-button:.13em;

  /* space — 8-point scale (p32). No arbitrary values. */
  --nv-space-1:.25rem; --nv-space-2:.5rem;  --nv-space-3:.75rem; --nv-space-4:1rem;
  --nv-space-5:1.5rem; --nv-space-6:2rem;   --nv-space-7:3rem;   --nv-space-8:4rem;
  --nv-space-9:6rem;   --nv-space-10:8rem;

  /* layout (p33) */
  --nv-container:1280px; --nv-gutter:2rem; --nv-margin:5rem;
  --nv-section:6rem;      /* 96px desktop */
  --nv-section-m:4rem;    /* 64px mobile  */

  /* radius + border (p34) */
  --nv-radius-0:0;        /* imagery — never rounded          */
  --nv-radius-sm:3px;     /* buttons, inputs                  */
  --nv-radius:4px;        /* cards, panels                    */
  --nv-radius-full:999px; /* status dots only                 */
  --nv-border-w:1px;

  /* motion (p46) */
  --nv-ease:cubic-bezier(.2,0,0,1);
  --nv-fast:120ms; --nv-base:220ms; --nv-slow:400ms; --nv-data-draw:600ms;
}
@media (prefers-reduced-motion:reduce){
  :root{--nv-fast:0ms;--nv-base:0ms;--nv-slow:0ms;--nv-data-draw:0ms}
  *,*::before,*::after{animation-duration:.001ms !important;animation-iteration-count:1 !important;
    transition-duration:.001ms !important;scroll-behavior:auto !important}
}

/* Bridge: the D4-era aliases the existing markup still reads.
   --signal-ink was one value in the v2 build and could not be right on both
   grounds. v3 splits it, so the alias now points at the correct member. */
:root{
  --signal:var(--nv-signal); --signal-h:var(--nv-signal-hov);
  --signal-ink:var(--nv-signal-mid);        /* graphic use */
  --signal-ink-bone:var(--nv-signal-moss);  /* text on light — 6.11:1 */
  --carbon:var(--nv-carbon); --bone:var(--nv-bone);
  --steel:var(--nv-steel);   --slate:var(--nv-slate);
}

/* ---------------------------------------------------------------
   2. TYPE — the v3 scale (p28)
   Authority comes from scale and space. v2 set display at 500; v3 sets 600,
   and 700+ is off-system entirely.
   --------------------------------------------------------------- */
html{font-size:16px}   /* every rem in the scale assumes this. Do not change it to scale a layout. */
body{font-family:var(--nv-font);font-weight:400;font-size:1rem;line-height:1.6;
  -webkit-font-smoothing:antialiased}

h1,h2,h3,h4,.hero-h,.sec-h,.cta-h,.display,.metric,.met-v,.pf-v{
  font-weight:600 !important;font-family:var(--nv-font)}

.hero-h,.display{font-size:clamp(2.5rem,6vw,6rem);line-height:1.02;
  letter-spacing:var(--nv-track-display)}
h1{font-size:3.5rem;line-height:1.08;letter-spacing:var(--nv-track-h1)}
h2,.sec-h,.cta-h{font-size:2.375rem;line-height:1.15;letter-spacing:var(--nv-track-h2)}
h3{font-size:1.5rem;line-height:1.25;letter-spacing:var(--nv-track-h3)}
.lede{font-weight:400;font-size:1.1875rem;line-height:1.55;letter-spacing:0;max-width:52em}
p,.body{font-size:1rem;line-height:1.6;letter-spacing:0}
small,.small{font-size:.875rem;line-height:1.55}

/* Body copy is never below 16px on web (p28), and 16px minimum on inputs
   stops iOS zooming the page on focus (p37). */
p,li,td,input,select,textarea{font-size:max(1rem,1em)}

/* Metric + its mono label are a unit; the label sits 8px below, never beside. */
.metric,.met-v,.pf-v{font-size:clamp(2rem,4vw,4rem);line-height:1;
  letter-spacing:var(--nv-track-metric);font-variant-numeric:tabular-nums lining-nums}
.met-l,.pf-l,.metric-label{display:block;margin-top:var(--nv-space-2);
  font-family:var(--nv-font-mono);font-weight:500;font-size:.6875rem;line-height:1.4;
  letter-spacing:var(--nv-track-label);text-transform:uppercase}

/* Mono roles */
.eyebrow,.kicker,.bx-k,.cap-k,.lab,.tape,.tape-run,.label{
  font-family:var(--nv-font-mono) !important;font-weight:500;font-size:.6875rem;
  line-height:1.4;letter-spacing:var(--nv-track-label);text-transform:uppercase}
.data,.tbl,.tbl td,code,kbd{font-family:var(--nv-font-mono);font-weight:400;
  font-size:.8125rem;line-height:1.5;letter-spacing:var(--nv-track-data);
  font-variant-numeric:tabular-nums lining-nums}

/* Numbers stack and align, so tabular figures everywhere they can (p29). */
table,.metrics,.proof,.met,.pf{font-variant-numeric:tabular-nums lining-nums}

/* NEVER (p29): the family ships italics; the brand does not use them.
   Underlines only on inline text links. Nothing above weight 600. */
em,i,cite,address{font-style:normal}
h1,h2,h3,h4,h5,h6,.btn,.nav a,button{text-decoration:none}
b,strong{font-weight:600}

/* Letterspacing is never added to sentence-case type. Mono labels, buttons and
   nav are the only tracked elements, and they are all uppercase. */
p,li,.lede,blockquote{letter-spacing:0 !important}

@media(max-width:768px){
  .hero-h,.display{font-size:clamp(2.5rem,10vw,3rem);line-height:1.06}
  h1{font-size:2.125rem}
  h2,.sec-h,.cta-h{font-size:1.6875rem}
  h3{font-size:1.3125rem}
  .lede{font-size:1.0625rem}
  .metric,.met-v,.pf-v{font-size:clamp(2rem,9vw,2.5rem)}
}

/* ---------------------------------------------------------------
   3. GEOMETRY — radius returns, in three steps (p34)
   The v2 build set radius to 0 on everything. v3 keeps imagery square but
   gives controls 3px and cards 4px. "Nothing between 5px and full, and
   nothing pill-shaped except status dots."
   --------------------------------------------------------------- */
.btn,button,input,select,textarea,.tabbtn,.sd-go,.field,.inp{
  border-radius:var(--nv-radius-sm) !important}
.card,.bx,.panel,.met,.pf,.q-card,.cta-card,.step,.module,.tile,.sol-card,
.article-cta,.pc-row,.grid6>*{border-radius:var(--nv-radius) !important}
img,video,picture,figure,svg.media,.media,.hero-media,.shot{
  border-radius:var(--nv-radius-0) !important}
.pill-live,.dot,.status-dot,.bdg-tag,.hero-badge,.cta-badge,.pc-pill,.cap-chip,
.pc-lights span,.pc-lights i,.sd-donut,.sd-node{border-radius:var(--nv-radius-full) !important}

/* Depth is a change of surface value, never a shadow — on cards, on modals,
   on the logo, anywhere (p34). */
*{box-shadow:none !important}
.focus-ring,:focus-visible{box-shadow:none !important}

/* Borders and dividers (p34) */
.on-bone,.bone,#modules{--nv-rule:var(--nv-border-alt)}
:root{--nv-rule:var(--nv-border)}

/* ---------------------------------------------------------------
   4. LAYOUT (p32-33)
   --------------------------------------------------------------- */
.wrap,.container,.inner,.sec-in,.nav-in,.foot-in,.hero-in{
  max-width:var(--nv-container) !important;
  padding-left:var(--nv-gutter);padding-right:var(--nv-gutter);
  margin-left:auto;margin-right:auto}
section,.sec{padding-top:var(--nv-section);padding-bottom:var(--nv-section)}
@media(max-width:768px){
  .wrap,.container,.inner,.sec-in,.nav-in,.foot-in,.hero-in{
    padding-left:1.25rem;padding-right:1.25rem}
  section,.sec{padding-top:var(--nv-section-m);padding-bottom:var(--nv-section-m)}
}

/* Card grids: 3-up -> 2-up at md -> 1-up at sm. Never a horizontal scroller.
   Gap 20px desktop / 16px mobile (p38). */
.grid6,.cards,.card-grid{gap:20px}
@media(max-width:768px){.grid6,.cards,.card-grid{gap:16px}}

/* Cards in a row are equal height; content is trimmed, not the card stretched. */
.grid6,.cards,.card-grid{align-items:stretch}

/* No layout may scroll the page sideways at any breakpoint (p64). */
html,body{max-width:100%;overflow-x:hidden}
img,svg,video,canvas,table{max-width:100%}

/* ---------------------------------------------------------------
   5. BUTTONS (p36)
   One primary per view — it is the Signal element.
   --------------------------------------------------------------- */
.btn,button.btn,a.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:9px;
  min-height:44px;padding:0 22px;
  font-family:var(--nv-font-mono) !important;font-weight:500 !important;
  font-size:.75rem;line-height:1;letter-spacing:var(--nv-track-button);
  text-transform:uppercase;text-decoration:none;
  border:1px solid transparent;
  transition:background-color var(--nv-fast) ease-out,
             border-color var(--nv-fast) ease-out,
             color var(--nv-fast) ease-out}
.btn.sm{min-height:34px;padding:0 16px;font-size:.6875rem;gap:8px}
.btn.lg{min-height:54px;padding:0 30px;font-size:.8125rem;gap:10px}

/* Primary — Signal fill, Carbon label. 13.07:1 on Carbon. */
.btn.p,.btn-signal,.btn.primary{
  background:var(--nv-signal) !important;color:var(--nv-carbon) !important;
  border-color:var(--nv-signal) !important;font-weight:500 !important}
.btn.p:hover,.btn-signal:hover,.btn.primary:hover{
  background:var(--nv-signal-hov) !important;border-color:var(--nv-signal-hov) !important}
.btn.p:active,.btn-signal:active,.btn.primary:active{
  background:var(--nv-signal-act) !important;border-color:var(--nv-signal-act) !important;
  transform:none !important}

/* Secondary — outlined. Never an outlined Signal button: the border disappears. */
.btn.s,.btn-line,.btn.secondary{
  background:transparent !important;color:var(--nv-text) !important;
  border-color:var(--nv-text) !important}
.btn.s:hover,.btn-line:hover,.btn.secondary:hover{
  background:rgba(242,240,233,.06) !important}

/* Tertiary — label plus a right-arrow glyph, never a chevron or an icon font. */
.btn.t,.btn-text{background:transparent !important;border-color:transparent !important;
  color:var(--nv-signal) !important;padding:0 !important}

.btn[disabled],.btn.disabled,button[disabled]{
  background:var(--nv-slate) !important;color:var(--nv-steel,#9EA29F) !important;
  border-color:var(--nv-slate) !important;cursor:default;pointer-events:none}

/* No full-width buttons on desktop, no lift on hover, no shadows. */
@media(min-width:769px){.btn{width:auto !important}}
.btn:hover{transform:none !important}

/* On a Bone ground the Signal button keeps its Carbon label; the Carbon button
   is the secondary and the outlined button is the tertiary (p36). */
#modules .btn.s,.on-bone .btn.s,.bone .btn.s{
  color:var(--nv-carbon) !important;border-color:var(--nv-carbon) !important}
#modules .btn.s:hover,.on-bone .btn.s:hover{background:rgba(23,25,24,.06) !important}
#modules .btn.t,.on-bone .btn.t{color:var(--nv-signal-moss) !important}

/* ---------------------------------------------------------------
   6. FORMS + INPUTS (p37)
   Labels always visible above the field. Never placeholder-as-label.
   --------------------------------------------------------------- */
label,.field-label{display:block;margin-bottom:var(--nv-space-2);
  font-family:var(--nv-font-mono);font-weight:500;font-size:.6875rem;
  letter-spacing:var(--nv-track-label);text-transform:uppercase;
  color:var(--nv-text-muted)}
/* The text-field rule. Radio and checkbox are deliberately excluded — they are
   not fields you type into, and every property here is wrong for them:
   width:100% stretches them, min-height BEATS any height they set, and the
   !important border overrides the shape. Before this exclusion the first radio
   on a Nivio surface rendered as a 44px tall slot. See CHOICE CONTROLS below. */
input:not([type="radio"]):not([type="checkbox"]),select,textarea{
  width:100%;min-height:44px;padding:0 14px;
  font-family:var(--nv-font);font-size:.875rem;color:var(--nv-text);
  background:transparent;border:1px solid var(--nv-border) !important}
textarea{padding:12px 14px;min-height:96px;line-height:1.6}

/* ---------------------------------------------------------------
   CHOICE CONTROLS — radio + checkbox
   Radius governance (§03): controls are 3px, status dots are full. A radio
   reads as a dot and is round; a checkbox is a control and takes the 3px.
   Both are drawn rather than native so the accent is Signal on every platform.
   --------------------------------------------------------------- */
input[type="radio"],input[type="checkbox"]{
  appearance:none;-webkit-appearance:none;
  flex:0 0 auto;display:inline-block;vertical-align:middle;
  width:15px;height:15px;min-width:0;min-height:0;margin:0;padding:0;
  background:transparent;cursor:pointer;
  border:1px solid var(--nv-border-strong) !important}
/* Shape is not set here — see RADIUS GOVERNANCE at the end of this file, where
   every radius decision on the site is made. A checkbox takes the 3px control
   radius from the rule there; a radio is listed with the round things. */
/* Checked reads as fill, not only colour: the radio thickens to a ring and the
   checkbox takes a solid ground with a Carbon tick. */
input[type="radio"]:checked{
  border:5px solid var(--nv-signal) !important}
input[type="checkbox"]:checked{
  background:var(--nv-signal);
  border-color:var(--nv-signal) !important;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%23171918' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round' d='M3.5 8.5l3 3 6-6'/%3E%3C/svg%3E");
  background-size:13px 13px;background-position:center;background-repeat:no-repeat}
input[type="radio"]:focus-visible,input[type="checkbox"]:focus-visible{
  outline:2px solid var(--nv-signal);outline-offset:2px}
input[type="radio"]:disabled,input[type="checkbox"]:disabled{
  opacity:.45;cursor:not-allowed}

/* A choice control sits beside its label, so the stacked-label rule above does
   not apply to it. */
label.nv-choice{display:flex;align-items:flex-start;gap:9px;
  margin-bottom:0;text-transform:none;letter-spacing:normal;
  font-family:var(--nv-font);font-size:.875rem;font-weight:400;
  color:var(--nv-text);cursor:pointer}
.field,.form-row{margin-bottom:20px}
input::placeholder,textarea::placeholder{color:var(--nv-ash)}
input:focus,select:focus,textarea:focus{
  border-color:var(--nv-signal) !important;
  outline:2px solid var(--nv-signal);outline-offset:2px}
input[aria-invalid="true"],.field.error input,.error input{
  border-color:var(--nv-critical-d) !important}
.field-error,.error-msg{margin-top:var(--nv-space-2);font-size:.875rem;
  color:var(--nv-critical-d)}
@media(max-width:768px){
  input,select,textarea{font-size:1rem;min-height:52px}  /* 16px stops iOS zoom */
}
#modules input,.on-bone input,#modules textarea,.on-bone textarea{
  color:var(--nv-carbon);border-color:var(--nv-border-alt) !important}
#modules label,.on-bone label{color:var(--nv-text-muted-alt)}
#modules .field-error,.on-bone .field-error{color:var(--nv-critical-l)}

/* ---------------------------------------------------------------
   7. FOCUS + TARGETS — required (p25, p64)
   2px Signal at 2px offset, 3:1 against the adjacent surface.
   Focus outlines are never removed.
   --------------------------------------------------------------- */
:focus-visible{outline:2px solid var(--nv-signal) !important;outline-offset:2px !important}
#modules :focus-visible,.on-bone :focus-visible{outline-color:var(--nv-signal-moss) !important}
a,button,.btn,input,select,textarea,[tabindex]{outline-offset:2px}
a,button,.btn,summary,[role="button"]{min-height:44px}
.nav a,.foot a,p a,li a{min-height:0}      /* inline text links keep their line box */
@media(pointer:coarse){
  .nav a,.foot a,[role="button"]{min-height:44px;display:inline-flex;align-items:center}
}

/* ---------------------------------------------------------------
   8. CARDS + METRIC TILES (p38)
   --------------------------------------------------------------- */
.card,.bx,.panel,.q-card,.sol-card,.tile{
  padding:24px;border:1px solid var(--nv-border)}
@media(max-width:768px){.card,.bx,.panel,.q-card,.sol-card,.tile{padding:20px}}
/* Hover on an interactive card: border to Signal at 25%, arrow to Signal.
   No lift, no scale. */
a.card:hover,a.bx:hover,a.sol-card:hover,.card.is-link:hover{
  border-color:rgba(215,232,74,.25) !important}
a.card:hover .arw,a.sol-card:hover .arw{color:var(--nv-signal) !important}
/* Metric tile: number, mono label 8px below, Signal rule at the base. */
.met::after,.metric-tile::after{content:"";display:block;width:64px;height:2px;
  margin-top:var(--nv-space-5);background:var(--nv-signal)}

/* ---------------------------------------------------------------
   9. NAVIGATION + FOOTER (p39)
   --------------------------------------------------------------- */
.nav,header.nav,.site-header{height:72px;background:var(--nv-carbon);
  border-bottom:1px solid transparent;transition:border-color var(--nv-fast) ease-out}
.nav.scrolled,.site-header.scrolled{border-bottom-color:rgba(242,240,233,.12)}
.nav a,.nav-l a,.nav-r a{
  font-family:var(--nv-font-mono);font-weight:500;font-size:.6875rem;
  letter-spacing:var(--nv-track-button);text-transform:uppercase;
  color:var(--nv-steel);text-decoration:none;
  transition:color var(--nv-fast) ease-out}
.nav a:hover{color:var(--nv-bone);
  box-shadow:0 2px 0 -1px var(--nv-signal) !important}   /* 1px Signal underline, 2px below */
.nav a.active,.nav a[aria-current]{color:var(--nv-bone)}
.nav-l,.nav-c,.nav-r{gap:32px}
.brand-logo,.nav .wm,.nav svg.wm{width:118px;height:auto}
@media(max-width:768px){
  .nav,header.nav,.site-header{height:60px}
  .brand-logo,.nav .wm,.nav svg.wm{width:96px}
}
.foot,footer{background:var(--nv-carbon);color:var(--nv-steel)}
.foot a,footer a{font-family:var(--nv-font-mono);font-size:.6875rem;
  letter-spacing:var(--nv-track-data);color:var(--nv-steel)}
.foot a:hover,footer a:hover{color:var(--nv-bone)}
.foot h4,.foot .col-h,footer .col-h{font-family:var(--nv-font-mono);font-weight:500;
  font-size:.6875rem;letter-spacing:var(--nv-track-label);text-transform:uppercase;
  color:var(--nv-signal)}

/* ---------------------------------------------------------------
   10. TABLES + DATA DENSITY (p40)
   Density is a feature, but it has to be organised.
   --------------------------------------------------------------- */
.tbl,table{width:100%;border-collapse:collapse;font-family:var(--nv-font-mono)}
.tbl th,table th{font-size:.6875rem;letter-spacing:.12em;text-transform:uppercase;
  color:var(--nv-steel);font-weight:500;text-align:left;
  position:sticky;top:0;background:inherit}
.tbl td,table td{font-size:.75rem;height:40px;
  border-bottom:1px solid rgba(242,240,233,.10)}
.tbl tr:hover td,table tbody tr:hover td{background:rgba(242,240,233,.04)}
.tbl td.num,table td.num,.tbl td[align="right"]{text-align:right;
  font-variant-numeric:tabular-nums lining-nums}
/* No zebra rows, no vertical rules, no coloured row backgrounds,
   no Signal in a table body. */
.tbl tr:nth-child(even) td,table tr:nth-child(even) td{background:none}
/* Tables scroll inside their own container, never stack into cards. */
.table-wrap,.tbl-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch}

/* ---------------------------------------------------------------
   11. MOTION (p46)
   Opacity and 8-16px translations only. Nothing bounces or overshoots.
   --------------------------------------------------------------- */
a,button,.btn,input,select,textarea,.card,.bx,.nav a{
  transition-timing-function:ease-out;transition-duration:var(--nv-fast)}
.reveal,.accordion,.menu,.mega{transition:all var(--nv-base) var(--nv-ease)}
.page-enter,.route{transition:all var(--nv-slow) var(--nv-ease)}
.chart,.chart *{transition:all var(--nv-data-draw) ease-out}
/* Stagger caps at six items. */
.stagger>*:nth-child(n+7){transition-delay:240ms !important}
/* No parallax, no scroll-jacking, no autoplay video behind text,
   no looping ambient animation. */
[data-parallax]{transform:none !important}

/* ---------------------------------------------------------------
   12. COLOUR GOVERNANCE — the pairings that fail (p25)
   These three are the mistakes the matrix says to watch for. They are
   neutralised here so a stray class cannot reintroduce them.
   --------------------------------------------------------------- */
/* Signal text on Bone is 1.19:1 — invisible. Use Carbon, or Signal Deep. */
#modules [style*="color:#D7E84A"],#modules .signal-text,
.on-bone .signal-text,.bone .signal-text{color:var(--nv-signal-moss) !important}
/* Steel is a dark-surface text colour and a light-surface rule only (2.27:1). */
#modules .steel-text,.on-bone .steel-text{color:var(--nv-text-muted-alt) !important}
/* Slate is a surface, never type (1.61:1 on Carbon). */
.slate-text{color:var(--nv-steel) !important}

/* Signal is never a page or section background, never a decorative wash over
   photography, never a gradient or a reduced-opacity tint. */
section[style*="background:#D7E84A"],section[style*="background:var(--signal)"]{
  background:var(--nv-carbon) !important}

/* ==========================================================================
   13. THE MARK ON THE PAGE (p16, p19, p39)

   The v3 wordmark is a different drawing from the v2 one: 6.05:1 where the old
   mark was 3.74:1, and the lockups were rebuilt on the same 730-unit canvas.
   That shared canvas is deliberate — one height rule now sizes the bare
   wordmark AND every lockup so the NIVIO portion lands at the same width.

       height 20.5px  ->  NIVIO portion 118px   (p39 desktop)
       height 16.6px  ->  NIVIO portion  96px   (p39 mobile)
       height 15.3px  ->  NIVIO portion  88px   (the floor, p16)

   Any height rule written against the old 3.74:1 mark now renders ~60% too
   wide, which is why the inline sizing was stripped from the markup.
   ========================================================================== */
.brand-logo,.brand-mark,img[src*="/logos/v3/"]{
  height:20.5px !important;width:auto !important;
  display:block;flex-shrink:0;vertical-align:middle}
@media(max-width:768px){
  .brand-logo,.brand-mark,img[src*="/logos/v3/"]{height:16.6px !important}
}

/* Clear space = 1x the cap height of the N, on all four sides (p16).
   At 20.5px rendered the cap height is ~19.5px, so 20px of guaranteed room. */
.brand,.nav-l a:first-child,a:has(>.brand-logo){padding:20px;margin:-20px;
  display:inline-flex;align-items:center}

/* Below the lockup minimum, drop the product name and show the bare wordmark
   rather than shrinking the lockup under its floor (p16).
   Scoped with :has() so a header that carries only the bare wordmark — every
   blog post and home-full — is not hidden along with it. */
.brand-mark{display:none !important}
@media(max-width:768px){
  .brand:has(.brand-mark) .brand-logo:not(.brand-mark),
  a:has(.brand-mark) .brand-logo:not(.brand-mark){display:none !important}
  .brand-mark{display:block !important}
}

/* The mark never sits on a busy or mid-tone field, never carries a shadow,
   a glow, a box or an outline, and is never tinted to sit behind content. */
.brand-logo,.brand-mark{filter:none !important;opacity:1 !important;
  mix-blend-mode:normal !important;background:none !important}

/* ==========================================================================
   14. MOBILE NAVIGATION (p39, p56)

   "Full-screen Carbon overlay, items at 24px, CTA pinned to base."

   The site had no mobile nav at all, so the desktop link row simply overflowed
   the viewport — 30 of 38 pages scrolled sideways, which the p64 checklist
   makes a launch blocker. shared/nav-mobile.js builds this overlay out of the
   links already in the header; it adds no copy and no new destinations.
   ========================================================================== */
.nv-menu-btn{display:none}

@media(max-width:768px){
  .nv-menu-btn{
    display:inline-flex;align-items:center;justify-content:center;
    width:44px;height:44px;margin-right:-10px;   /* 44x44 tap target (p37) */
    background:none;border:0;cursor:pointer;color:var(--nv-bone);
    -webkit-tap-highlight-color:transparent}
  .nv-menu-btn svg{width:20px;height:20px;display:block}

  /* The desktop link row is removed on mobile, not shrunk (p56: remove, then
     re-stack). This is what stops the horizontal overflow. */
  .nav-r,.nav-c,.nav-links,.nav-menu{display:none !important}

  .nv-overlay{
    position:fixed;inset:0;z-index:9999;
    display:flex;flex-direction:column;
    background:var(--nv-carbon);
    padding:calc(60px + 20px) 20px 20px;   /* clears the 60px mobile header */
    opacity:0;visibility:hidden;
    transform:translateY(-8px);            /* enters from where it came from */
    transition:opacity var(--nv-base) var(--nv-ease),
               transform var(--nv-base) var(--nv-ease),
               visibility 0s linear var(--nv-base)}
  .nv-overlay[data-open]{opacity:1;visibility:visible;transform:none;
    transition-delay:0s}
  html.nv-locked,html.nv-locked body{overflow:hidden}

  .nv-overlay a{
    display:flex;align-items:center;min-height:44px;
    padding:var(--nv-space-3) 0;
    font-family:var(--nv-font);font-size:1.5rem;line-height:1.25;
    font-weight:500;letter-spacing:var(--nv-track-h3);
    text-transform:none;color:var(--nv-bone);text-decoration:none;
    border-bottom:1px solid var(--nv-border)}
  .nv-overlay a:last-of-type{border-bottom:0}
  .nv-overlay a:active{color:var(--nv-signal)}

  /* The CTA is pinned to the base of the overlay, full width, 52px tall. */
  .nv-overlay .nv-cta{
    margin-top:auto;justify-content:center;
    min-height:52px;border-bottom:0;
    background:var(--nv-signal);color:var(--nv-carbon);
    font-family:var(--nv-font-mono);font-weight:500;font-size:.75rem;
    letter-spacing:var(--nv-track-button);text-transform:uppercase;
    border-radius:var(--nv-radius-sm)}

  /* Mobile buttons: full width, 52px tall, stacked with a 12px gap (p56). */
  .hero .btn,.cta-card .btn,.hero-cta .btn{
    width:100% !important;min-height:52px}
  .hero-cta,.btn-row,.cta-row{display:flex;flex-direction:column;gap:12px}

  /* No sticky bottom CTA bar, no interstitials, no horizontal card scrollers. */
  .sticky-cta,.mobile-cta-bar,.interstitial{display:none !important}
  .cards,.grid6,.card-grid{overflow-x:visible !important;
    grid-auto-flow:row !important}
}

/* Proof figures become a 2x2 grid on mobile, not a row and not a carousel.
   All four stay visible — they travel as a set (p4, p56). */
@media(max-width:768px){
  .proof,.metrics,.proof-row{
    display:grid !important;grid-template-columns:1fr 1fr !important;
    gap:var(--nv-space-5) var(--nv-space-4)}
  .proof>*,.metrics>*,.proof-row>*{min-width:0}
}

/* ==========================================================================
   15. SECTION ALTERNATION (p54)
   Carbon, Bone, Carbon, Bone. Never three consecutive sections on the same
   ground — the alternation is the page's rhythm, and the Bone band is what
   delivers the 30% in the colour budget.
   ========================================================================== */
.on-bone,#modules,section.bone{
  background:var(--nv-surface-alt);color:var(--nv-text-on-alt)}
.on-bone h1,.on-bone h2,.on-bone h3,#modules h1,#modules h2,#modules h3{
  color:var(--nv-text-on-alt)}
.on-bone p,#modules p{color:var(--nv-text-muted-alt)}
.on-bone .eyebrow,#modules .eyebrow,.on-bone .kicker,#modules .kicker{
  color:var(--nv-signal-moss)}
.on-bone hr,#modules hr,.on-bone .rule,#modules .rule{
  border-color:var(--nv-border-alt)}

/* ==========================================================================
   16. PRINT — the mark goes to its solid variant, colour drops out
   ========================================================================== */
@media print{
  .nav,.foot,.nv-menu-btn,.nv-overlay{display:none !important}
  body{background:#fff;color:#000}
}

/* ==========================================================================
   17. CANCEL THE v2 MARK-SWAP RULE

   nivio-v3-compat.css hides `.brand-logo:not(.brand-mark)` below 640px so a product
   lockup drops to the bare wordmark. That rule is unscoped, so on every header
   that carries ONLY the bare wordmark — all 21 blog pages, home-full and
   checkout — it hid the logo outright and the mobile header rendered empty.

   The swap is re-expressed here scoped to headers that actually have something
   to swap to. :has() is the whole point; where it is unsupported the mark
   stays visible, which is the safe direction to fail.
   ========================================================================== */
@media(max-width:768px){
  .brand-logo:not(.brand-mark){display:block !important}
  /* any parent that holds both marks swaps one for the other */
  *:has(> .brand-mark) > .brand-logo:not(.brand-mark){display:none !important}
}

/* ==========================================================================
   18. THE MARK IS NEVER SQUEEZED

   `img{max-width:100%}` is right for content images and wrong for the mark:
   inside a flex header the wrapping link shrinks and the clamp drags the
   wordmark under its 88px minimum. On the blog headers it was landing at 46px.
   The mark opts out of the clamp, and its link stops shrinking.
   ========================================================================== */
.brand-logo,.brand-mark,img[src*="/logos/v3/"]{
  max-width:none !important;min-width:0;flex-shrink:0 !important}
.brand,a:has(>.brand-logo),a:has(>.brand-mark),.article-header a:has(img){
  flex-shrink:0 !important;min-width:0}

/* ==========================================================================
   19. THE ACTUAL CAUSES OF MOBILE OVERFLOW

   `overflow-x:hidden` on the body keeps a phone from scrolling sideways, but
   it hides the problem rather than solving it — content still runs off the
   edge, it just cannot be reached. These are the four real causes, fixed at
   the source. The body clip stays as a production safety net only.
   ========================================================================== */

/* 19.1 The grid blowout. Grid items default to `min-width:auto`, so one
   unbreakable string sets the track's minimum. On a 320px container the hero
   track resolved to 505px and took the headline and paragraph out with it. */
.hero-inner,.hero-grid,.grid,.split,[class*="-grid"]{min-width:0}
.hero-inner>*,.hero-grid>*,.grid>*,.split>*,[class*="-grid"]>*{min-width:0}
@media(max-width:768px){
  .hero-inner,.hero-grid,.split{grid-template-columns:1fr !important}
}

/* 19.2 Nowrap decoration. The live pill is decoration, not navigation, and
   its nowrap was part of the same minimum. */
@media(max-width:768px){
  .pill-live,.hero-badge,.cta-badge{white-space:normal !important;max-width:100%}
}

/* 19.3 Tables scroll inside their own container and never stack into cards
   (p33). Blog articles set tables directly in the prose. */
.article-body table,.prose table,.stat-table{display:block;width:100%;
  overflow-x:auto;-webkit-overflow-scrolling:touch}
.article-body table thead,.prose table thead,.stat-table thead{white-space:nowrap}

/* 19.4 Long button labels wrap rather than clip. A label that needs this is a
   copy problem — p36 asks for two to three words — but it should degrade
   readably in the meantime. */
@media(max-width:768px){
  .btn{white-space:normal;text-align:center;line-height:1.25;
    padding-top:10px;padding-bottom:10px;height:auto}
}

/* ==========================================================================
   20. THE ANNOTATION TAPE IS NOT A TICKER (p46, and §7 of the spec)

   "No parallax, no scroll-jacking, no autoplay video with motion behind text,
   no looping ambient animation."

   The tape was running an 80-second infinite marquee. It is technical
   notation — it should sit still and be read. The content is unchanged; only
   the loop is removed, and what does not fit is clipped at the container edge
   exactly as before.
   ========================================================================== */
/* Exception, at the client's direction: a page may opt one tape back into the
   loop with .tape-marquee. Index does, because its tape carries outcome
   figures the client wants read as a running feed rather than a fixed
   caption. Every other .tape-run on the site stays still, as below. */
.tape-run:not(.tape-marquee){animation:none !important;transform:none !important}
.tape{overflow:hidden}
@media(max-width:768px){
  .tape-run{display:flex;gap:var(--nv-space-5)}
}

/* ==========================================================================
   21. THE PRODUCT CONSOLE ON A PHONE

   The console mocks are the densest thing on the site — a real board with a
   topbar, meta row and a table. `.product-card` clipped them at the card edge,
   so on a phone the right-hand columns simply vanished with no way to reach
   them. p40 sanctions exactly one remedy for this: "horizontal scroll inside
   the table container only." The board keeps its density and becomes
   reachable, and the page itself still does not move sideways.
   ========================================================================== */
@media(max-width:768px){
  .product-card{overflow-x:auto !important;-webkit-overflow-scrolling:touch;
    overscroll-behavior-x:contain}
  .product-card>*{min-width:min-content}
  /* the scroll affordance: a hairline fade at the live edge, not a scrollbar */
  .product-card{scrollbar-width:thin}
}

/* ==========================================================================
   22. TAP TARGETS ON THE ARTICLE PAGES (p37, p64)

   "Tap targets 44x44px minimum, 8px apart." The blog's back-link and its
   in-article CTA were rendering at 35px tall, because the article stylesheet
   sets its own padding after the component rules. They are navigation, not
   inline prose links, so they take the full target.
   ========================================================================== */
.nav-back,.article-cta .btn,.article-body .btn,a.btn.p{
  min-height:44px !important;display:inline-flex;align-items:center}
@media(max-width:768px){
  .nav-back{padding:var(--nv-space-2) 0}
  .article-cta .btn,.article-body .btn{width:100%;justify-content:center;
    min-height:52px !important}
}

/* ==========================================================================
   23. THE RADIUS SCALE, ENFORCED (p34)

   "Radius stays small. Nivio's geometry is engineered, not friendly. Nothing
   between 5px and full, and nothing pill-shaped except status dots."

   Section 3 above set the radius on the components we know by name. That is
   not enough: the console mocks and the hero chips carry their own radii in
   page-level CSS under class names that vary per page, and they were still
   rendering at 16-24px and as full pills. The scale is therefore asserted the
   same way the v2 layer asserted square — from the universal selector down —
   and then the three legitimate exceptions are given back.
   ========================================================================== */
*,*::before,*::after{border-radius:var(--nv-radius) !important}   /* 4px ceiling */

/* Controls sit one step tighter. */
.btn,button,input,select,textarea,.tabbtn,.sd-go,.field,.inp,
[role="button"],[type="button"],[type="submit"]{
  border-radius:var(--nv-radius-sm) !important}

/* Imagery is square, always — no rounded corners on full-bleed media (p43). */
img,video,picture,canvas,figure,.media,.hero-media,.shot,.thumb{
  border-radius:var(--nv-radius-0) !important}

/* Status dots, live pills and the radio are the only round things on the page.
   A radio reads as a dot by convention on every platform, so it is a legitimate
   exception rather than a break in the governance. A CHECKBOX is not — it keeps
   the 3px control radius from the rule above. */
.dot,.status-dot,.pc-lights span,.pc-lights i,.sd-node,.sd-donut,
.avatar,.q-avatar,[class*="-dot"],
input[type="radio"]{border-radius:var(--nv-radius-full) !important}

/* The live indicator is a dot plus a word — the dot is round, the chip is not. */
.pill-live,.live{border-radius:var(--nv-radius-sm) !important}
.pill-live::before,.live::before{border-radius:var(--nv-radius-full) !important}

/* The v2 layer keeps an explicit pill allowlist — .bdg-tag, .hero-badge,
   .cta-badge, .pc-pill, .cap-chip — at 999px. A class selector outranks the
   universal one even when both are !important, so those five have to be named
   to be corrected. None of them is a status dot; they are chips and badges,
   and v3 gives chips the control radius. */
.bdg-tag,.hero-badge,.cta-badge,.pc-pill,.cap-chip,.chip,.tag,.badge{
  border-radius:var(--nv-radius-sm) !important}
