/* ═══════════════════════════════════════════════════════════════════════════
   TradeVerify — design system
   Single stylesheet. No build step. Structure follows urbanise.css.

   Direction: dark chassis, orange rationed to action, and a SECOND colour
   system that is the product's own output — verified / expiring / lapsed /
   checking. Nothing on this site is coloured decoratively. If a colour is not
   an action and not a compliance status, it is ink.

   Contrast: every ratio in the comments below was MEASURED against the exact
   pairing named, not carried over from another project. Both themes clear
   WCAG 2.1 AA (4.5:1) for normal text on every surface they are used on.
   Dark worst case  4.66:1  (--faint on --raised)
   Light worst case 4.91:1  (--faint on --bg)
   Re-measure if any surface or ink token changes.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Fonts ────────────────────────────────────────────────────────────────
   Self-hosted, latin subset, variable. 77 KB total for three families.
   Deliberately not Inter / DM Sans / Poppins / Wix Madefor (what the old site
   used), and deliberately not Archivo (the sibling sites' display face) so
   TradeVerify is not mistaken for LegalStep or Urbanise.
   All three are SIL Open Font License 1.1.
   font-display:swap — the LCP element is the <h1>, so a swap is preferable to
   an invisible-text period.
   ───────────────────────────────────────────────────────────────────────── */
@font-face {
  font-family: 'Familjen Grotesk';
  src: url('/assets/fonts/familjen-grotesk-latin.woff2') format('woff2');
  font-weight: 400 700;          /* variable weight axis */
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Hanken Grotesk';
  src: url('/assets/fonts/hanken-grotesk-latin.woff2') format('woff2');
  font-weight: 300 800;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Martian Mono';
  src: url('/assets/fonts/martian-mono-latin.woff2') format('woff2');
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FEFF, U+FFFD;
}

/* ── Tokens: dark (default) ───────────────────────────────────────────────
   Chassis is layered, not flat black — --bg matches the base the old site
   already served, so returning visitors do not experience a colour jump.
   ───────────────────────────────────────────────────────────────────────── */
:root {
  --bg:        #0B1215;
  --surface:   #111A1F;
  --raised:    #162127;
  --line:      rgba(237, 241, 243, 0.10);
  --line-2:    rgba(237, 241, 243, 0.18);

  --ink:       #EDF1F3;   /* 16.62:1 on --bg · 15.51 surface · 14.42 raised */
  --muted:     #93A1A8;   /*  7.11:1 on --bg ·  6.63 surface ·  6.17 raised */
  --faint:     #7C8B93;   /*  5.37:1 on --bg ·  5.01 surface ·  4.66 raised
                             Lightened from the value carried over from Centcom
                             (#6B7A82), which measured 4.26:1 on --bg and only
                             3.69:1 on --raised — a fail at the 11px mono size
                             this token exists for. Do not darken it again. */

  /* Action. Orange is the brand value, shared with Centcom so the marketing
     site and the app a customer logs into read as one family. It is used for
     buttons, links and focus rings, and for nothing else. */
  --signal:       #FF683A; /* 6.56:1 on --bg · 6.12 surface · 5.69 raised */
  --signal-hover: #FF8E6B; /* 8.40:1 on --bg */
  --signal-press: #FF4207; /* 5.43:1 on --bg */
  --on-signal:    #140B07; /* 6.75:1 on --signal.
                              White on this orange is 2.88:1 — never use it. */
  --signal-weak:  rgba(255, 104, 58, 0.12);

  /* Status taxonomy — the product's own output, and the only other colour
     system on the site. Each has a ~12% chip background. */
  --verified:    #3FB950; /* 7.44:1 on --bg · 6.45 raised */
  --expiring:    #D29922; /* 7.48:1 on --bg · 6.50 raised */
  --lapsed:      #F85149; /* 5.64:1 on --bg · 4.89 raised */
  --checking:    #4493F8; /* 6.09:1 on --bg · 5.29 raised */
  --verified-bg: rgba(63, 185, 80, 0.12);
  --expiring-bg: rgba(210, 153, 34, 0.12);
  --lapsed-bg:   rgba(248, 81, 73, 0.12);
  --checking-bg: rgba(68, 147, 248, 0.12);

  /* Paper — the single inverted section (the audit-trail evidence block).
     Dark product world, light document artefact. --signal fails on paper
     (2.59:1), so the paper scope substitutes --signal-paper below. */
  --paper:        #F1F3F6;
  --paper-ink:    #14181F; /* 16.01:1 on --paper */
  --paper-muted:  #48525E; /*  7.14:1 on --paper */
  --paper-line:   rgba(20, 24, 31, 0.14);
  --signal-paper: #BC3809; /*  5.09:1 on --paper */

  /* Type */
  --display: 'Familjen Grotesk', ui-sans-serif, system-ui, -apple-system, sans-serif;
  --sans:    'Hanken Grotesk', ui-sans-serif, system-ui, -apple-system, sans-serif;
  --mono:    'Martian Mono', ui-monospace, 'SF Mono', Menlo, monospace;

  --r-sm: 4px;
  --r: 8px;
  --r-lg: 14px;
  --gutter: clamp(20px, 5vw, 48px);
  /* Column ladder. THREE widths, deliberately far apart — 1180 / 820 / 660.
     Anything closer than ~160px apart reads as a misalignment rather than a
     step, which is what the earlier ad-hoc 760/820/860/900 mix looked like.
     Every centred block on the site is one of these three and nothing else. */
  --measure: 1180px;         /* wide   — grids, tables, evidence, CTA, footer */
  --measure-prose: 820px;    /* prose  — reading columns and section headers  */
  --measure-narrow: 660px;   /* narrow — standalone centred pages (404, thanks) */
  --section: clamp(64px, 7.4vw, 106px);

  --shadow: 0 1px 2px rgba(0,0,0,.28), 0 8px 28px rgba(0,0,0,.22);

  /* Hero stage. Two surface washes and a measuring grid, all of them ink —
     the hero gets depth without a decorative colour entering the system.
     The grid is a nod to the product: a register is ruled paper. */
  --hero-wash-1: rgba(22, 33, 39, 0.92);
  --hero-wash-2: rgba(17, 26, 31, 0.80);
  --hero-grid:   rgba(237, 241, 243, 0.055);
  --hero-lift:   0 2px 4px rgba(0,0,0,.30), 0 26px 70px rgba(0,0,0,.44);

  color-scheme: dark;
}

/* ── Tokens: light ────────────────────────────────────────────────────────
   Not a tint of the dark theme — a separately measured palette. The status
   hues are darkened rather than reused, because the dark-theme greens and
   ambers fall under 3:1 on a white surface.
   Worst pairing in this theme: --faint on --bg at 4.91:1.
   ───────────────────────────────────────────────────────────────────────── */
:root[data-theme="light"] {
  --bg:        #EDF0F4;
  --surface:   #FFFFFF;
  --raised:    #F7F9FB;
  --line:      rgba(20, 24, 31, 0.12);
  --line-2:    rgba(20, 24, 31, 0.22);

  --ink:       #14181F;   /* 15.57:1 on --bg · 17.79 surface · 16.86 raised */
  --muted:     #48525E;   /*  6.95:1 on --bg ·  7.94 surface ·  7.53 raised */
  --faint:     #5D6975;   /*  4.91:1 on --bg ·  5.61 surface ·  5.32 raised */

  --signal:       #BC3809; /* 4.95:1 on --bg · 5.66 surface · 5.37 raised */
  --signal-hover: #9E2F06;
  --signal-press: #8A2905;
  --on-signal:    #FFFFFF; /* 5.66:1 on --signal — inverted vs the dark theme,
                              where dark ink is the accessible pairing. */
  --signal-weak:  rgba(188, 56, 9, 0.10);

  --hero-wash-1: rgba(255, 255, 255, 0.95);
  --hero-wash-2: rgba(247, 249, 251, 0.85);
  --hero-grid:   rgba(20, 24, 31, 0.065);
  --hero-lift:   0 2px 4px rgba(20,24,31,.07), 0 26px 64px rgba(20,24,31,.14);

  --verified:    #16702F; /* 5.41:1 on --bg */
  --expiring:    #7D5800; /* 5.62:1 on --bg */
  --lapsed:      #B31B26; /* 5.92:1 on --bg */
  --checking:    #0A56AC; /* 6.25:1 on --bg */
  --verified-bg: rgba(22, 112, 47, 0.10);
  --expiring-bg: rgba(125, 88, 0, 0.10);
  --lapsed-bg:   rgba(179, 27, 38, 0.10);
  --checking-bg: rgba(10, 86, 172, 0.10);

  /* The evidence block is the site's one weight shift. In the dark theme it
     works by inversion — a light document against a dark product world. In the
     light theme #F1F3F6 on #EDF0F4 is a 1.7% luminance difference and the
     inversion disappears entirely, so here the artefact reads as a SHEET
     instead: pure white, a real border, and a lifted shadow. Same idea
     (a document you can hand to someone), different mechanism.
     --signal-paper stays the dark orange — the bright brand orange is 2.6:1
     on white and fails outright. */
  --paper:       #FFFFFF;
  --paper-ink:   #14181F; /* 17.79:1 on --paper */
  --paper-muted: #48525E; /*  7.94:1 on --paper */
  --paper-line:  rgba(20, 24, 31, 0.16);

  --shadow: 0 1px 2px rgba(20,24,31,.06), 0 8px 28px rgba(20,24,31,.08);

  color-scheme: light;
}

/* ── Reset ───────────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 84px;   /* clears the sticky nav on anchor jumps */
}

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.62;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-feature-settings: "tnum";   /* tabular figures — dates and licence
                                      numbers must align down a column */
  overflow-x: hidden;
}

img, svg, video { display: block; max-width: 100%; }
img { height: auto; }
button, input, select, textarea { font: inherit; color: inherit; }
a { color: var(--signal); text-decoration-thickness: 1px; text-underline-offset: 3px; }
a:hover { color: var(--signal-hover); }

/* Focus. A visible ring everywhere, never on mouse click.
   Absent or near-invisible on all three sibling sites — fixed here. */
:focus-visible {
  outline: 2px solid var(--signal);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}

::selection { background: var(--signal-weak); color: var(--ink); }

/* Skip link — missing from every sibling site. First tab stop on every page. */
.skip {
  position: absolute;
  left: 12px; top: -60px;
  z-index: 200;
  padding: 10px 16px;
  background: var(--signal);
  color: var(--on-signal);
  font-weight: 600;
  border-radius: var(--r);
  transition: top .16s ease;
}
.skip:focus { top: 12px; color: var(--on-signal); }

.vh {   /* visually hidden, still read by assistive tech */
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ── Layout ──────────────────────────────────────────────────────────────── */
.wrap {
  width: 100%;
  max-width: var(--measure);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.wrap-prose  { max-width: var(--measure-prose); }
.wrap-narrow { max-width: var(--measure-narrow); }
/* A narrower column is only worth having when the inset it creates reads as a
   deliberate step. Just above each cap the step is a few pixels — the prose
   column sits 6px inside the wide one at 830px wide — which looks like a
   mistake rather than a tier. Below these points the column simply gives up
   and fills the gutters, so the page has one edge instead of two near-identical
   ones. 920/760 are the widths at which each step falls under ~40px a side. */
@media (max-width: 920px) { .wrap-prose  { max-width: none; } }
@media (max-width: 760px) { .wrap-narrow { max-width: none; } }
/* container-type gives the pull-out below a scrollbar-free page width to work
   from. 100vw counts the scrollbar, which put every breakout figure ~7px wide
   on either side at viewports under 1180px. No .section contains a fixed or
   absolutely positioned element, so the containment this establishes is inert. */
.section { padding-block: var(--section); container-type: inline-size; }
.section + .section { padding-top: 0; }
.section-bordered { border-top: 1px solid var(--line); }
/* ...but not when the second one is BORDERED. Collapsing the top padding is
   what makes two plain sections read as one continuous run; a bordered section
   is the opposite, an explicit break, and with padding-top:0 its rule was drawn
   hard against the content — 15px below the line against ~100px of air above
   it. A rule needs room on both sides, so a bordered section always pays for
   its own top padding. */
.section + .section-bordered { padding-top: var(--section); }

/* ── Type scale ───────────────────────────────────────────────────────────
   Display face is set tight (-0.028em at h1) because Familjen Grotesk is
   drawn loose by default and reads soft at large sizes otherwise.
   ───────────────────────────────────────────────────────────────────────── */
h1, h2, h3, h4 { font-family: var(--display); font-weight: 700; line-height: 1.08; }

/* Max is 3.3rem, not larger: the hero h1 sits in a ~560px column at 1280px,
   and anything above this wraps to five short lines and swamps the ledger.
   A full-width h1 (interior pages) may use .d1-wide. */
h1, .d1 {
  font-size: clamp(2.05rem, 1.3rem + 2.7vw, 3.3rem);
  letter-spacing: -0.028em;
  text-wrap: balance;
}
.d1-wide { font-size: clamp(2.15rem, 1.3rem + 3.4vw, 4rem); }
h2, .d2 {
  font-size: clamp(1.7rem, 1.1rem + 2.5vw, 2.85rem);
  letter-spacing: -0.022em;
  text-wrap: balance;
}
h3, .d3 {
  font-size: clamp(1.2rem, 1rem + 0.85vw, 1.5rem);
  letter-spacing: -0.014em;
  font-weight: 600;
  line-height: 1.22;
}
h4 { font-size: 1.05rem; letter-spacing: -0.008em; font-weight: 600; }

/* Section eyebrow. Display face, not mono — mono is reserved for data. */
.eyebrow {
  font-family: var(--display);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--signal);
  margin-bottom: 14px;
}

.lede {
  font-size: clamp(1.06rem, 1rem + 0.35vw, 1.24rem);
  color: var(--muted);
  max-width: 62ch;
  line-height: 1.6;
}
p { max-width: 68ch; }
p + p { margin-top: 1em; }
.prose > * + * { margin-top: 1.1em; }
.prose h2 { margin-top: 1.8em; }
.prose h3 { margin-top: 1.5em; }
.prose ul, .prose ol { padding-left: 1.3em; }
.prose li + li { margin-top: .45em; }

strong { font-weight: 600; color: var(--ink); }

/* Data type. The deliberate risk in this system: the mono face carries data
   at DISPLAY size, larger than the prose around it. The product's whole value
   is a status, against a date, from a named authority, at a known time — so
   the typography privileges the date over the sentence. Martian Mono is wide,
   so tracking is pulled in to stop data rows sprawling. */
.data, .mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.data { letter-spacing: -0.045em; }
.data-lg {
  font-family: var(--mono);
  font-size: clamp(1.1rem, .95rem + .7vw, 1.45rem);
  font-weight: 500;
  letter-spacing: -0.05em;
  font-variant-numeric: tabular-nums;
}
.label {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--faint);
}

/* ── Buttons ─────────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; gap: 9px;
  padding: 13px 22px;
  border-radius: var(--r);
  font-family: var(--sans);
  font-weight: 600;
  font-size: .97rem;
  line-height: 1;
  text-decoration: none;
  border: 1px solid transparent;
  cursor: pointer;
  transition: background .16s ease, border-color .16s ease, color .16s ease, transform .08s ease;
}
.btn svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 2.1; }
.btn:active { transform: translateY(1px); }

.btn-solid { background: var(--signal); color: var(--on-signal); }
.btn-solid:hover { background: var(--signal-hover); color: var(--on-signal); }
.btn-solid:active { background: var(--signal-press); }

.btn-line { border-color: var(--line-2); color: var(--ink); background: transparent; }
.btn-line:hover { border-color: var(--signal); color: var(--signal); background: var(--signal-weak); }

.btn-ghost { color: var(--muted); padding-inline: 6px; }
.btn-ghost:hover { color: var(--ink); }

.actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 30px; }

/* ── Nav ─────────────────────────────────────────────────────────────────── */
.nav {
  position: sticky; top: 0; z-index: 100;
  display: flex; align-items: center; gap: 20px;
  padding: 14px var(--gutter);
  background: color-mix(in srgb, var(--bg) 86%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--line);
}
/* ── Brand lockup ────────────────────────────────────────────────────────
   Two placements, deliberately different:

   NAV — wordmark only, inline, taking currentColor. The supplied lockup puts a
   1080-unit square QR mark beside a 368-unit-tall wordmark, so constraining the
   lockup to nav height drives the mark under 30px, where its modules collapse
   into a grey smudge. Re-proportioning someone's logo to fit is worse than
   using an official reduction, so the nav uses the wordmark alone.

   FOOTER — the full supplied lockup, unmodified, at 54px tall, which is where
   the mark starts to resolve.

   COLOUR — the supplied wordmark is #FF683A, which is --signal exactly. On the
   light theme that measures 2.9:1 on white; logos are exempt from WCAG 1.4.3,
   but it is genuinely hard to read, so the light theme uses --signal (#BC3809
   there) and a matching logo-light.svg. Flagged for the owner's approval.
   ───────────────────────────────────────────────────────────────────────── */
.lockup {
  display: inline-flex; align-items: center; gap: 10px;
  text-decoration: none; color: var(--signal); margin-right: auto;
}
.lockup-mark {
  width: 32px; height: 32px;   /* 32px is the floor at which the QR modules
                                  still separate; below this it reads as a
                                  grey square. Do not shrink it. */
  flex: none;
  display: block;
  color: var(--ink);
}
.lockup .wordmark {
  height: 19px;          /* cap height, not box height — the viewBox is cropped
                            to the wordmark's measured bounding box */
  width: auto;
  flex: none;
  display: block;
  color: var(--signal);
}
.lockup:hover .wordmark { color: var(--signal-hover); }

.lockup-full { margin-right: 0; }
.lockup-full img { height: 54px; width: auto; }

/* Theme-swapped logo variants. Both files ship; CSS picks one. */
.on-light { display: none; }
:root[data-theme="light"] .on-dark { display: none; }
:root[data-theme="light"] .on-light { display: block; }

@media (max-width: 640px) {
  .lockup-mark { width: 28px; height: 28px; }
  .lockup .wordmark { height: 17px; }
  .lockup-full img { height: 44px; }
}

.nav-set { display: flex; align-items: center; gap: 4px; }
/* Dropdown triggers are <button> for keyboard semantics, so the UA's default
   button chrome has to be stripped or they render as grey boxes beside the
   anchor links. */
.nav-link {
  padding: 8px 12px;
  border-radius: var(--r-sm);
  color: var(--muted);
  font-size: .94rem; font-weight: 500;
  text-decoration: none;
  background: transparent;
  border: 0;
  font-family: inherit;
  cursor: pointer;
}
button.nav-link { display: inline-flex; align-items: center; gap: 6px; }
button.nav-link::after {
  content: ""; flex: none;
  width: 6px; height: 6px;
  border-right: 1.6px solid currentColor; border-bottom: 1.6px solid currentColor;
  transform: rotate(45deg) translate(-1px, -1px);
}
.nav-link:hover { color: var(--ink); background: var(--raised); }
.nav-link[aria-current="page"] { color: var(--ink); font-weight: 600; }

/* Dropdown. Hover on pointer devices, click-toggle for keyboard and touch —
   .is-open is set by the page script. */
.nav-group { position: relative; }
/* The menu is anchored at top:100% with a TRANSPARENT top padding acting as a
   bridge, rather than offset with a gap. With a gap, moving the pointer
   diagonally from the trigger toward an item leaves the hover area for a frame
   and the menu vanishes mid-reach — which is what made this hard to use. The
   bridge keeps the hover region continuous from trigger to first item. */
.nav-menu {
  position: absolute; top: 100%; left: 0;
  min-width: 232px;
  padding: 14px 6px 6px;     /* the 14px top is the bridge */
  background: transparent;
  border: 0;
  display: none;
}
.nav-menu > div {
  padding: 6px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: var(--shadow);
}
/* Keep the trigger looking active while its menu is open. */
.nav-group:hover > .nav-link,
.nav-group.is-open > .nav-link { color: var(--ink); background: var(--raised); }
.nav-group:hover .nav-menu, .nav-group.is-open .nav-menu { display: block; }
.nav-menu a {
  display: block; padding: 9px 12px;
  border-radius: var(--r-sm);
  color: var(--muted); text-decoration: none; font-size: .93rem;
}
.nav-menu a:hover { background: var(--raised); color: var(--ink); }

.theme-btn {
  display: inline-grid; place-items: center;
  width: 36px; height: 36px;
  background: transparent; border: 1px solid var(--line);
  border-radius: var(--r-sm); color: var(--muted); cursor: pointer;
}
.theme-btn:hover { color: var(--ink); border-color: var(--line-2); }
.theme-btn svg { width: 16px; height: 16px; fill: none; stroke: currentColor; }
.theme-btn .icon-moon { display: none; }
:root[data-theme="light"] .theme-btn .icon-sun { display: none; }
:root[data-theme="light"] .theme-btn .icon-moon { display: block; }

.nav-cta { padding: 10px 18px; }

.nav-burger {
  display: none;
  width: 38px; height: 38px;
  background: transparent; border: 1px solid var(--line);
  border-radius: var(--r-sm); color: var(--ink); cursor: pointer;
}
.nav-burger svg { width: 20px; height: 20px; margin: auto; fill: none; stroke: currentColor; stroke-width: 2; }

/* ── Hero ──────────────────────────────────────────────────────────────────
   The hero is a STAGE: its own surface, distinct from the sections below it,
   rather than the same flat colour running the length of the page. Depth comes
   from two surface washes and a ruled grid, and every one of those values is
   ink or a surface token. No colour enters here that is not an action (the
   CTA) or a compliance status (the ledger's own output) — the rule at the top
   of this file holds in the most tempting place to break it.
   ───────────────────────────────────────────────────────────────────────── */
.hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding-block: clamp(52px, 6.5vw, 96px) clamp(40px, 4.4vw, 62px);
  border-bottom: 1px solid var(--line);
}
/* Only the homepage carries the authority strip, and that strip brings its own
   rule and its own bottom padding. Everywhere else the hero pays for its own
   bottom space — without this it was the page's last line of copy sitting 1px
   off the closing hairline. */
.hero:has(.authority) { padding-bottom: 0; }
/* The hero closes itself with a hairline, so a bordered section immediately
   after it drew a second one 0px below the first. */
.hero + .section-bordered { border-top: 0; }
/* Wash. Offset from the two corners the content occupies, so the light falls
   behind the ledger and under the headline rather than evenly. */
.hero::before {
  content: ''; position: absolute; inset: 0; z-index: -2;
  background:
    radial-gradient(118% 92% at 76% 4%,  var(--hero-wash-1), transparent 60%),
    radial-gradient(96% 84% at 2% 98%,   var(--hero-wash-2), transparent 56%);
}
/* The ruled grid. Masked to a soft oval so it never meets an edge as a hard
   tile boundary, and sized at 64px so it reads as texture, not as a chart. */
.hero::after {
  content: ''; position: absolute; inset: 0; z-index: -1;
  background-image:
    linear-gradient(to right,  var(--hero-grid) 1px, transparent 1px),
    linear-gradient(to bottom, var(--hero-grid) 1px, transparent 1px);
  background-size: 64px 64px;
  -webkit-mask-image: radial-gradient(78% 64% at 52% 16%, #000 30%, transparent 78%);
          mask-image: radial-gradient(78% 64% at 52% 16%, #000 30%, transparent 78%);
}
/* The headline spans BOTH columns and the supporting copy sits under it,
   beside the ledger. Kept inside the left column it had ~470px to work in,
   which is four words a line and six lines deep at any size worth calling a
   display face. Across the full measure the same sentence is two lines, so the
   type can carry real scale without the ledger losing the width it needs. */
.hero-grid {
  display: grid;
  /* The ledger gets the larger share. Martian Mono is a wide face, and at
     parity the firm-name column truncates. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.18fr);
  grid-template-areas:
    "head  head"
    "copy  ledger";
  column-gap: clamp(32px, 4.5vw, 60px);
  row-gap: clamp(28px, 3.4vw, 46px);
  align-items: start;
}
.hero-head   { grid-area: head; }
.hero-copy   { grid-area: copy; }
.ledger-wrap { grid-area: ledger; }
.hero .lede { margin-top: 0; }
.hero-points {
  list-style: none; padding: 0;
  margin-top: 28px;
  display: grid; gap: 9px;
}
.hero-points li {
  display: flex; align-items: center; gap: 10px;
  color: var(--muted); font-size: .95rem;
}
.hero-points svg {
  width: 15px; height: 15px; flex: none;
  fill: none; stroke: var(--verified); stroke-width: 2.6;
}

/* The hero headline runs larger than an interior h1. It sits in its own column
   beside the ledger, so the ceiling that applies elsewhere (see .d1) is set
   by the column, not by the page. */
.hero h1 {
  font-size: clamp(2.15rem, 1.1rem + 3.5vw, 3.9rem);
  letter-spacing: -0.032em;
  line-height: 1.04;
  max-width: 21ch;      /* three balanced lines at the top of the range */
}

/* ── The floating check ──────────────────────────────────────────────────
   One live status, lifted off the ledger and overlapping its edge, so the
   panel reads as something currently running rather than a static table.
   It carries the `checking` status that the palette already defines and the
   rest of the site rarely gets to show.
   ───────────────────────────────────────────────────────────────────────── */
.ledger-wrap { position: relative; padding-top: 46px; }
.ledger-wrap .ledger { box-shadow: var(--hero-lift); }
/* Above the panel, not across it. Overlapping the card covered either a
   contractor row or the head's own timestamp, and a decorative pill is not
   allowed to obscure the data the panel exists to show. */
.ledger-float {
  position: absolute; z-index: 3;
  right: 0; top: 0;
  display: flex; align-items: center; gap: 9px;
  padding: 9px 14px;
  background: var(--surface);
  border: 1px solid var(--line-2);
  border-radius: 999px;
  box-shadow: var(--hero-lift);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: -0.02em;
  color: var(--muted);
  white-space: nowrap;
  animation: float-bob 5s ease-in-out infinite;
}
.ledger-float .dot {
  width: 7px; height: 7px; flex: none;
  border-radius: 50%;
  background: var(--checking);
  animation: pulse 1.1s ease-in-out infinite;
}
@keyframes float-bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-7px); }
}

/* ── Person card ─────────────────────────────────────────────────────────
   A portrait sits beside the name rather than above it, so a card that has one
   and a card that does not still read as a pair. The second card in this grid
   is a capability statement that deliberately names nobody, and a large avatar
   would have left it looking like a card with its photo missing.

   The supplied headshots are square on a white ground, so the circular crop
   carries its own background. The ring is a hairline rather than a shadow —
   on the light theme a white disc on a white panel otherwise has no edge.
   ───────────────────────────────────────────────────────────────────────── */
.person-head {
  display: flex;
  /* TOP-aligned, not centred. Centring the name block against a 72px portrait
     pushed it 10px below the heading of the card beside it, which on a site
     that spent a whole pass getting its columns onto one edge is exactly the
     kind of near-miss that reads as a mistake. The portrait simply hangs the
     extra height below the role line. */
  align-items: flex-start;
  gap: 16px;
}
.person-head img {
  width: 72px; height: 72px;
  flex: none;
  border-radius: 50%;
  object-fit: cover;
  background: var(--raised);
  box-shadow: 0 0 0 1px var(--line-2);
}
.person-head h3 { margin: 0; }
.person-head .label { margin-top: 8px; }   /* matches the sibling card's role line */

/* The capability card sits under the two people rather than beside one of them,
   so the grid reads as "who" then "how" instead of three equal things. */
.panel-wide { grid-column: 1 / -1; }

@media (max-width: 420px) {
  .person-head img { width: 60px; height: 60px; }
}

/* ── Authority strip ─────────────────────────────────────────────────────
   The honest form of a customer logo wall. TradeVerify cannot show customer
   logos it has no permission for, and it will not invent a transacted-volume
   figure, so the proof offered here is the thing that is actually true and
   actually differentiating: the registers it reads from.

   F2 APPLIES HERE. Text only. No registry logo, ever, without written
   permission from that body — and the non-affiliation line below is part of
   the component, not decoration. Do not drop it, and do not restyle it into
   something a reader can skip. Names must match the sources table on
   /how-it-works; that table is the source of truth and carries the currency
   date. Anything added here must be Live there first.
   ───────────────────────────────────────────────────────────────────────── */
.authority {
  margin-top: clamp(46px, 5.5vw, 78px);
  padding-block: 24px 40px;
  border-top: 1px solid var(--line);
}
.authority-label {
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--faint);
  margin-bottom: 14px;
}
.authority-list {
  list-style: none; padding: 0; margin: 0;
  display: flex; flex-wrap: wrap;
  gap: 11px 28px;
  align-items: center;
}
.authority-list li {
  font-family: var(--display);
  font-weight: 600;
  font-size: .96rem;
  letter-spacing: -0.012em;
  color: var(--muted);
}
.authority-note {
  margin-top: 16px;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--faint);
  max-width: 78ch;
}
.authority-note a { color: var(--faint); }
.authority-note a:hover { color: var(--signal); }

@media (max-width: 860px) {
  .authority-list { gap: 9px 20px; }
  .authority-list li { font-size: .9rem; }
}

/* ── Signature: the Verification Ledger ──────────────────────────────────
   The hero is a real register, not a photograph.

   HOUSE RULE (from UrbaniseAI/index.html): the ledger is server-rendered in
   its RESOLVED state in the HTML. Where motion is permitted, the script
   rewinds the rows to `checking` and resolves them on a stagger. It never
   builds the rows. Without this the most persuasive content on the page would
   live only inside JS strings, and crawlers and LLM retrieval fetchers would
   see an empty panel.

   All ledger data is CLEARLY FICTIONAL — invented firm names, invented licence
   numbers — and the panel is labelled as an example, so it can never be read
   as a real contractor's record.
   ───────────────────────────────────────────────────────────────────────── */
.ledger {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow);
  overflow: hidden;
}
.ledger-head {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 13px 16px;
  background: var(--raised);
  border-bottom: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: -0.02em;
  color: var(--faint);
}
.ledger-head .who { color: var(--muted); font-weight: 500; }
.ledger-head .stamp { margin-left: auto; }
.ledger-rows { display: grid; }
/* The status + expiry column is max-content and must never be squeezed — it is
   the whole point of the row. The firm name is the flexible column and takes
   the ellipsis; the licence column sizes to its own content. */
.ledger-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto max-content;
  gap: 6px 14px;
  align-items: center;
  padding: 13px 16px;
  border-bottom: 1px solid var(--line);
}
.ledger-row:last-child { border-bottom: 0; }
.ledger-firm {
  font-weight: 500; font-size: .92rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ledger-auth {
  font-family: var(--mono);
  font-size: 9.5px; letter-spacing: -0.01em;
  color: var(--faint);
  text-transform: uppercase;
}
.ledger-lic {
  display: flex; align-items: baseline; gap: 7px;
  font-family: var(--mono);
  font-size: 11px; letter-spacing: -0.045em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.ledger-meta { display: flex; align-items: center; gap: 10px; justify-content: flex-end; }
/* The expiry date is set LARGER than the body copy around it. This is the
   typography arguing the product's thesis; it is the one intentional
   hierarchy inversion on the site. Pull back one scale step if it fails. */
.ledger-exp {
  font-family: var(--mono);
  font-size: 12.5px; font-weight: 500; letter-spacing: -0.05em;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  white-space: nowrap;
}
/* ── .ledger-list — the register as the product's Contractor List ─────────
   Added 18/09/2026, round-4 review: the strata hero panel was an invented
   "CONTRACTOR REGISTER" with an authority/licence/expiry shape that does not
   correspond to the screen a client actually opens. This modifier reshapes the
   same .ledger markup into the Contractor List — compliance state with its open
   item count, entity name, trade — with the coloured left rail the app draws.

   It is a modifier rather than a change to .ledger because index.html uses the
   unmodified panel and was not part of this review. The two will diverge until
   that page is looked at; see REVIEW-NOTES-ROUND-4.md, B4.
   ───────────────────────────────────────────────────────────────────────── */
.ledger-list .ledger-row {
  grid-template-columns: max-content minmax(0, 1fr) auto;
  gap: 6px 12px;
  padding-left: 13px;
  border-left: 3px solid transparent;
}
.ledger-list .ledger-trade {
  font-family: var(--mono);
  font-size: 10.5px; letter-spacing: -0.01em;
  text-transform: uppercase;
  color: var(--faint);
  white-space: nowrap;
}
/* The rail restates the row's status at the edge of the panel, so the shape of
   the list reads before any of the text does — which is how the real screen is
   scanned. */
.ledger-row.is-critical { border-left-color: var(--lapsed); }
.ledger-row.is-lapsed   { border-left-color: var(--lapsed); }
.ledger-row.is-expiring { border-left-color: var(--expiring); }
.ledger-row.is-verified { border-left-color: var(--verified); }

@media (max-width: 460px) {
  .ledger-list .ledger-row { grid-template-columns: max-content minmax(0, 1fr); }
  .ledger-list .ledger-trade { grid-column: 2; justify-self: start; }
}

.ledger-note {
  max-width: none;   /* overrides the 68ch prose measure on <p> */
  padding: 11px 16px;
  background: var(--raised);
  border-top: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 10.5px; letter-spacing: -0.01em;
  color: var(--faint);
}

/* ── Status chips — the second colour system ─────────────────────────────── */
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 9px 4px 7px;
  border-radius: 999px;
  font-family: var(--mono);
  font-size: 10px; font-weight: 500;
  letter-spacing: 0.015em; text-transform: uppercase;
  white-space: nowrap;
  border: 1px solid transparent;
}
.chip::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: currentColor; flex: none;
}
.chip-verified { color: var(--verified); background: var(--verified-bg); border-color: var(--verified-bg); }
.chip-expiring { color: var(--expiring); background: var(--expiring-bg); border-color: var(--expiring-bg); }
.chip-lapsed   { color: var(--lapsed);   background: var(--lapsed-bg);   border-color: var(--lapsed-bg); }
.chip-checking { color: var(--checking); background: var(--checking-bg); border-color: var(--checking-bg); }
/* The app's CRITICAL state is a solid pill, not a tint — it is the one status
   that outranks everything else on the row, and the tinted chips read as peers. */
.chip-critical { color: var(--bg); background: var(--lapsed); border-color: var(--lapsed); }

/* The checking dot pulses only while a row is resolving. */
.chip-checking::before { animation: pulse 1.1s ease-in-out infinite; }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: .32; } }

/* A LIVE chip's dot beats continuously — the status it reports is a present
   tense one ("this integration is running now"), not a stored result, and a
   static dot reports it as though it were.

   `chip-live` is a MODIFIER, never a status of its own: it carries no colour
   and is always written alongside chip-verified. That is deliberate. Scoping
   the beat to `.tv .chip-verified` instead would have caught every verified
   chip in every table on the site, including the ones that report a stored
   result and must stay still.

   0.5Hz, and a dip to .28 rather than to nothing. Well under the 3Hz
   photosensitivity threshold, and closer to a breath than a flash. */
.chip-live::before { animation: live-beat 2s ease-in-out infinite; }
@keyframes live-beat {
  0%, 45%   { opacity: 1; }
  65%       { opacity: .28; }
  85%, 100% { opacity: 1; }
}

/* Table rows cued by a cascade. Opacity only, no transform: a transform on a
   <tr> is supported but fights the collapsed border model and can leave a
   row's rule hairline behind while the row itself moves. Not worth 4px of
   travel. As everywhere else, opacity keeps the row in the accessibility
   tree — nothing here is ever hidden from a screen reader. */
.tv.is-cued tbody tr { opacity: 0; transition: opacity .5s ease; }
.tv.is-cued tbody tr.is-in { opacity: 1; }

/* A spec list cued by the same cascade. These rows are divs rather than table
   rows, so unlike the table above they can carry the small lift the hero
   cascades use without fighting the collapsed border model. */
.spec.is-cued > div {
  opacity: 0;
  transform: translateY(5px);
  transition: opacity .5s ease, transform .5s ease;
}
.spec.is-cued > div.is-in { opacity: 1; transform: none; }

/* ── Cards and panels ────────────────────────────────────────────────────── */
.grid { display: grid; gap: 20px; }
.grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }

.card {
  padding: 26px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
}
.card h3 { margin-bottom: 10px; }
.card p { color: var(--muted); font-size: .96rem; }
.card-link { text-decoration: none; color: inherit; display: block; transition: border-color .16s ease; }
.card-link:hover { border-color: var(--line-2); color: inherit; }

.panel {
  padding: clamp(24px, 3.2vw, 40px);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
}

/* ── The one inverted section ────────────────────────────────────────────
   /audit-trail's evidence block and its homepage counterpart. An actual
   register extract, presented as a document. This is the site's single
   weight shift — dark product world, light document artefact — and it earns
   its place by reinforcing "we produce evidence you can hand to an insurer or
   a regulator". No other section inverts. Do not add a second.
   ───────────────────────────────────────────────────────────────────────── */
.paper {
  background: var(--paper);
  color: var(--paper-ink);
  border: 1px solid var(--paper-line);
  border-radius: var(--r-lg);
  padding: clamp(24px, 3.4vw, 44px);
  /* Lifted further than a .card — this is the one element on the site that is
     meant to look like a physical document sitting on the page. */
  box-shadow: 0 2px 6px rgba(0,0,0,.10), 0 18px 48px rgba(0,0,0,.16);
}
.paper h2, .paper h3 { color: var(--paper-ink); }
.paper p, .paper td { color: var(--paper-muted); }
.paper a { color: var(--signal-paper); }
.paper .label, .paper .ledger-auth { color: var(--paper-muted); }
.paper .doc-rule { border-top: 1px solid var(--paper-line); margin-block: 18px; }
/* min-width forces the scroller rather than crushing four columns of dates and
   authority names into an unreadable 320px. */
.paper table { width: 100%; border-collapse: collapse; min-width: 440px; }
.paper .table-scroll { margin-inline: -4px; padding-inline: 4px; }
/* caption-side is set per-table, not globally — without this the note renders
   above the extract and reads as a heading rather than a footnote. */
.paper caption {
  caption-side: bottom;
  margin-top: 12px;
  text-align: left;
  color: var(--paper-muted);
}
.paper th, .paper td {
  text-align: left; padding: 9px 12px;
  border-bottom: 1px solid var(--paper-line);
  font-size: .9rem;
}
.paper th {
  font-family: var(--mono); font-size: 10px;
  text-transform: uppercase; letter-spacing: .02em;
  color: var(--paper-muted); font-weight: 500;
}
.paper .data, .paper .mono { color: var(--paper-ink); }

/* ── Tables (coverage / sources) ─────────────────────────────────────────── */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
table.tv {
  width: 100%; border-collapse: collapse;
  min-width: 620px;   /* forces the scroller rather than crushing columns */
}
table.tv th, table.tv td {
  text-align: left; padding: 12px 14px;
  border-bottom: 1px solid var(--line);
  font-size: .93rem;
  vertical-align: top;
}
/* The State abbreviation beside a jurisdiction's full name. Both forms are
   what people write, so both are on the page; the abbreviation is set back so
   the column still reads as one name rather than two. */
.mono-note {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .02em;
  color: var(--faint);
  white-space: nowrap;
}
table.tv th {
  font-family: var(--mono); font-size: 10.5px; font-weight: 500;
  text-transform: uppercase; letter-spacing: .02em;
  color: var(--faint);
  border-bottom-color: var(--line-2);
}
table.tv td.data { color: var(--muted); font-size: 12px; }
table.tv caption {
  caption-side: bottom; margin-top: 12px;
  font-size: .84rem; color: var(--faint); text-align: left;
}

/* ── Ordered steps ───────────────────────────────────────────────────────
   USED ON /how-it-works AND NOWHERE ELSE. Numbered 01/02/03 sequences are
   excluded from the rest of the site by design — they are the single most
   common filler pattern in B2B SaaS marketing. They are permitted here only
   because the content genuinely is an ordered process, and because these
   numbers are the same ordinal positions emitted in the HowTo JSON-LD.
   If you find yourself wanting this component on another page, the content
   there is almost certainly a list, not a process. Use .grid.
   ───────────────────────────────────────────────────────────────────────── */
.steps { counter-reset: step; display: grid; gap: 0; margin-top: 40px; }
.step {
  counter-increment: step;
  display: grid;
  grid-template-columns: 74px minmax(0, 1fr);
  gap: 0 24px;
  padding: 30px 0;
  border-top: 1px solid var(--line);
}
.step:last-child { border-bottom: 1px solid var(--line); }
.step::before {
  content: "0" counter(step);
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: -0.04em;
  color: var(--signal);
  padding-top: 4px;
}
.step h3 { margin-bottom: 9px; }
.step p { color: var(--muted); }
.step-note {
  margin-top: 12px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: -0.01em;
  color: var(--faint);
  max-width: none;
}

@media (max-width: 640px) {
  .step { grid-template-columns: 1fr; gap: 8px; padding: 24px 0; }
  .step::before { padding-top: 0; }
}

/* ── FAQ ─────────────────────────────────────────────────────────────────
   <details> only. Every answer must be present verbatim in the HTML — the
   JSON-LD FAQPage mirrors this markup exactly, and an answer that is not in
   the DOM is a structured-data violation.
   ───────────────────────────────────────────────────────────────────────── */
.faq { display: grid; gap: 0; }
.faq details { border-bottom: 1px solid var(--line); }
.faq summary {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 20px 0;
  cursor: pointer;
  font-family: var(--display);
  font-weight: 600; font-size: 1.06rem; letter-spacing: -0.012em;
  list-style: none;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: ""; flex: none;
  width: 11px; height: 11px;
  border-right: 2px solid var(--faint); border-bottom: 2px solid var(--faint);
  transform: rotate(45deg) translate(-3px, -3px);
  transition: transform .18s ease;
}
.faq details[open] summary::after { transform: rotate(225deg) translate(-3px, -3px); }
.faq summary:hover { color: var(--signal); }
.faq .answer { padding-bottom: 22px; color: var(--muted); }
.faq .answer p { max-width: 74ch; }

/* ── Legal documents ─────────────────────────────────────────────────────
   /terms and /privacy carry documents drafted elsewhere and reproduced here
   verbatim — clause numbering, capitalisation and cross-references included.
   The site's display h2 tops out at 2.85rem, which is right for a marketing
   section header and absurd for fifteen all-caps clause headings, so the type
   scale is pulled in within .legal only. Nothing here changes any other page.

   .cl is a numbered clause: a two-column grid so the marker hangs beside the
   text rather than running into it, indented one step per nesting level. The
   markers are literal, not counters — the document cross-references "clause
   1.6" and "clause 9(c)", so a CSS counter that renumbered would break it.
   ───────────────────────────────────────────────────────────────────────── */
.legal h2 {
  font-size: clamp(1.18rem, 1.02rem + 0.62vw, 1.42rem);
  letter-spacing: 0.005em;
  margin-top: 2.4em;
}
.legal h3 {
  font-size: clamp(1rem, .95rem + 0.24vw, 1.1rem);
  letter-spacing: 0.005em;
  color: var(--ink);
  margin-top: 1.8em;
}
.legal > p, .legal .cl { font-size: .95rem; line-height: 1.68; }
.legal .cl {
  display: grid;
  grid-template-columns: 3.4em 1fr;
  max-width: 68ch;
}
.legal .cl-2 { margin-left: 3.4em; }
.legal .cl-3 { margin-left: 6.8em; }
.legal .cl-m { color: var(--faint); }
/* Definitions. The document calls this a table; a description list is what it
   actually is, and it survives a 320px screen, which a table would not. */
.legal .defs { margin-top: 1.4em; }
.legal .defs dt {
  font-family: var(--display); font-weight: 600;
  color: var(--ink);
  margin-top: 1.5em;
}
.legal .defs dd {
  margin: .3em 0 0;
  max-width: 68ch;
  font-size: .95rem; line-height: 1.68;
}
@media (max-width: 560px) {
  .legal .cl { grid-template-columns: 2.7em 1fr; }
  .legal .cl-2 { margin-left: 1.4em; }
  .legal .cl-3 { margin-left: 2.8em; }
}

/* ── Spec rows ───────────────────────────────────────────────────────────
   Label/value pairs for /security and /developers. Deliberately austere: this
   is the part of the site a risk-averse buyer reads closely, and it should
   read like a datasheet rather than like marketing. The value column is mono
   because most values are versions, regions, cadences and dates.
   ───────────────────────────────────────────────────────────────────────── */
.spec { display: grid; margin-top: 10px; }
.spec > div {
  display: grid;
  grid-template-columns: minmax(0, 230px) minmax(0, 1fr);
  gap: 6px 26px;
  padding: 16px 0;
  border-bottom: 1px solid var(--line);
}
.spec > div:first-child { border-top: 1px solid var(--line); }
.spec dt {
  font-family: var(--mono); font-size: 10.5px; font-weight: 500;
  letter-spacing: .02em; text-transform: uppercase;
  color: var(--faint);
  padding-top: 3px;
}
.spec dd { margin: 0; color: var(--muted); font-size: .95rem; }
.spec dd .data { color: var(--ink); font-size: 12.5px; }
.spec dd p + p { margin-top: .55em; }
/* A badge sitting inside a spec row, under the line it belongs to. Sized well
   below the footer lockup on purpose: this is one milestone among five, not a
   trust panel, and it should not outweigh the rows above it. */
.spec-badge { display: block; margin-top: 12px; width: 104px; height: auto; }

@media (max-width: 640px) {
  .spec > div { grid-template-columns: 1fr; gap: 6px; padding: 14px 0; }
}

/* A pending fact. Used ONLY where a value is a {{PLACEHOLDER}} awaiting a real
   answer from the Register — it makes an unresolved value visually obvious in
   a page full of resolved ones, so a gap cannot quietly ship looking finished.
   tools/preflight.sh blocks the deploy while any placeholder remains, so this
   style should never be seen by a visitor. */
.pending {
  display: inline-block;
  font-family: var(--mono); font-size: 11px;
  padding: 2px 7px;
  border: 1px dashed var(--expiring);
  border-radius: var(--r-sm);
  background: var(--expiring-bg);
  color: var(--expiring);
}

/* ── Glossary ────────────────────────────────────────────────────────────
   One page, anchored sections — not 25 separate files. Per-term pages rank
   marginally better in isolation, but 25 hand-maintained files with no build
   step is 25 more copies of the nav and footer for a paragraph of definition.
   Each .gloss-term id is the fragment in its DefinedTerm @id, so the schema
   and the anchors cannot drift apart.
   ───────────────────────────────────────────────────────────────────────── */
.gloss-index {
  display: flex; flex-wrap: wrap; gap: 7px;
  padding: 20px 0 4px;
}
.gloss-index a {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .01em;
  padding: 6px 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--muted); text-decoration: none;
}
.gloss-index a:hover { color: var(--signal); border-color: var(--signal); background: var(--signal-weak); }

.gloss-set { margin-top: 14px; }
.gloss-term {
  padding: 26px 0;
  border-top: 1px solid var(--line);
  scroll-margin-top: 96px;   /* clears the sticky nav on an anchor jump */
}
.gloss-term:last-child { border-bottom: 1px solid var(--line); }
.gloss-term h3 { margin-bottom: 8px; }
.gloss-term p { color: var(--muted); }
.gloss-term p + p { margin-top: .7em; }
.gloss-also {
  margin-top: 10px;
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .01em;
  color: var(--faint);
}
.gloss-also a { color: var(--faint); text-decoration: underline; text-underline-offset: 2px; }
.gloss-also a:hover { color: var(--signal); }
/* Jurisdiction tag — the vocabulary genuinely differs by State and Territory,
   and using the wrong word marks you as an outsider to this audience. */
.juris {
  display: inline-block;
  font-family: var(--mono); font-size: 9.5px; font-weight: 500;
  letter-spacing: .03em; text-transform: uppercase;
  padding: 3px 7px; margin-left: 8px;
  border-radius: var(--r-sm);
  background: var(--raised); color: var(--faint);
  vertical-align: middle;
}

/* ── Forms ───────────────────────────────────────────────────────────────
   Labels WRAP inputs — never placeholder-as-label.
   ───────────────────────────────────────────────────────────────────────── */
.form { display: grid; gap: 16px; }
.form-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
.form label { display: block; font-size: .88rem; font-weight: 500; color: var(--muted); }
.form label .req { color: var(--signal); }
.form input, .form select, .form textarea {
  width: 100%;
  margin-top: 7px;
  padding: 12px 14px;
  background: var(--surface);
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  color: var(--ink);
  font-size: .97rem;
}
.form input:hover, .form select:hover, .form textarea:hover { border-color: var(--faint); }
.form input:focus, .form select:focus, .form textarea:focus {
  outline: 2px solid var(--signal);
  outline-offset: 1px;
  border-color: transparent;
}
.form textarea { min-height: 118px; resize: vertical; }
.form-note { font-family: var(--mono); font-size: 11px; color: var(--faint); }

/* ── Footer ──────────────────────────────────────────────────────────────
   Carries the legal name, ABN, registered address, email and phone sitewide.
   For an Australian B2B vendor asking a risk-averse professional to route
   liability-bearing decisions through the platform, this is trust
   infrastructure, not boilerplate.
   ───────────────────────────────────────────────────────────────────────── */
.footer {
  border-top: 1px solid var(--line);
  padding-block: clamp(44px, 5vw, 68px) 34px;
  margin-top: var(--section);
}
.footer-grid {
  display: grid;
  grid-template-columns: 1.5fr repeat(3, minmax(0, 1fr));
  gap: 34px;
}
/* Was h4. These label a column of links rather than open a section, and as
   headings they put an H2->H4 jump in the outline of every page with no H3s. */
.footer .footer-col-label {
  font-family: var(--mono); font-size: 10.5px; font-weight: 500;
  text-transform: uppercase; letter-spacing: .04em;
  color: var(--faint);
  margin-bottom: 14px;
}
.footer ul { list-style: none; padding: 0; display: grid; gap: 9px; }
.footer a { color: var(--muted); text-decoration: none; font-size: .93rem; }
.footer a:hover { color: var(--signal); }
/* SCA (VIC) corporate membership badge. Sits under the lockup and tagline.
   Supplied artwork is navy + orange, cut out of its light plate — it holds up
   on both chassis without a white tile behind it, so it gets none. */
.footer-badge {
  display: block;
  margin-top: 22px;
  width: 132px; height: auto;
}
.footer-legal {
  margin-top: 34px; padding-top: 22px;
  border-top: 1px solid var(--line);
  display: flex; flex-wrap: wrap; gap: 8px 22px; align-items: baseline;
  font-size: .85rem; color: var(--faint);
}
.footer-legal .data { font-size: 11.5px; }

/* ── Hero cascade ────────────────────────────────────────────────────────
   The hidden half of the hero's looping choreography (the JS half is at the
   foot of index.html). The `is-cued` class is added by that script and ONLY
   by that script, so with JS off, or under prefers-reduced-motion where the
   script bails on its first line, every item renders at full opacity and
   nothing here applies.

   Deliberately opacity + transform, never `display` or `visibility`: an item
   at zero opacity is still in the accessibility tree and still read by a
   screen reader, so the loop costs a non-visual reader nothing. Hiding these
   properly would repeatedly yank the value proposition out from under them.
   ──────────────────────────────────────────────────────────────────────── */
.hero-points.is-cued li,
.authority-list.is-cued li {
  opacity: 0;
  transform: translateY(5px);
  transition: opacity .5s ease, transform .5s ease;
}
.hero-points.is-cued li.is-in,
.authority-list.is-cued li.is-in {
  opacity: 1;
  transform: none;
}

/* ── Reveal ──────────────────────────────────────────────────────────────
   Sibling IntersectionObserver fade. The JS bails on prefers-reduced-motion
   via matchMedia; this rule is the CSS half of the same bail.
   ───────────────────────────────────────────────────────────────────────── */
.reveal { opacity: 0; transform: translateY(14px); transition: opacity .5s ease, transform .5s ease; }
.reveal.in { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .reveal { opacity: 1; transform: none; transition: none; }
  .chip-checking::before { animation: none; }
  .hero-points.is-cued li,
  .authority-list.is-cued li { opacity: 1; transform: none; transition: none; }
  .tv.is-cued tbody tr { opacity: 1; transition: none; }
  .spec.is-cued > div { opacity: 1; transform: none; transition: none; }
  .chip-live::before { animation: none; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}

/* ── Responsive ──────────────────────────────────────────────────────────
   Down to 320px. The ledger is the constraint: below 640px it drops the
   licence column to a second line rather than scrolling horizontally,
   because a horizontally scrolling hero reads as broken.
   ───────────────────────────────────────────────────────────────────────── */
@media (max-width: 1020px) {
  .hero-grid { grid-template-columns: 1fr; }
  .hero-grid { grid-template-areas: "head" "copy" "ledger"; }
  .grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .footer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 860px) {
  .nav-burger { display: grid; }
  .nav-set {
    display: none;
    position: absolute; top: 100%; left: 0; right: 0;
    flex-direction: column; align-items: stretch; gap: 2px;
    padding: 12px var(--gutter) 20px;
    background: var(--bg);
    border-bottom: 1px solid var(--line);
    box-shadow: var(--shadow);
  }
  .nav-set.is-open { display: flex; }
  .nav-link { padding: 11px 8px; font-size: 1rem; }
  .nav-menu { position: static; display: block; padding: 0 0 0 12px; }
  .nav-menu > div { background: transparent; border: 0; box-shadow: none; padding: 0; }
  .nav-cta { justify-content: center; margin-top: 8px; }
  .theme-btn { align-self: flex-start; }
  .grid-3 { grid-template-columns: 1fr; }
}

@media (max-width: 640px) {
  body { font-size: 16px; }
  .grid-2, .grid-4 { grid-template-columns: 1fr; }
  .form-row { grid-template-columns: 1fr; }
  .footer-grid { grid-template-columns: 1fr; gap: 26px; }

  /* Two lines per row: the firm name, then licence + status/expiry.
     The status column is max-content and never shrinks; the licence column
     takes min-width:0 + overflow so a long authority name truncates instead of
     overflowing its grid track and colliding with the chip. `nowrap` alone is
     not enough — an item with nowrap overflows rather than shrinking, which is
     exactly the collision this replaces. */
  /* One column, three lines per row: firm name, then the licence, then the
     status and expiry. A two-column mobile row either collides (if the mono
     licence is nowrap) or truncates the licence to nothing (if it is not) —
     and the licence number is the data the whole row exists to carry, so it
     is never the thing that gets dropped. Taller rows are the right trade. */
  .ledger-row {
    grid-template-columns: 1fr;
    row-gap: 7px;
    padding: 15px 14px;
  }
  .ledger-firm { font-size: .95rem; white-space: normal; }
  .ledger-lic { min-width: 0; flex-wrap: wrap; }
  .ledger-meta { justify-content: flex-start; gap: 10px; }
  .ledger-head { flex-direction: column; align-items: flex-start; gap: 3px; }
  .ledger-head .sep { display: none; }   /* the stacked head needs no separator */
  .ledger-head .stamp { margin-left: 0; }
}

@media (max-width: 360px) {
  :root { --gutter: 16px; }
  .ledger-head { font-size: 10px; }
}

/* ── Product screenshots ─────────────────────────────────────────────────
   Real captures of the application, framed as figures.

   TWO THINGS ABOUT THESE, BOTH LOAD-BEARING:

   1. THE SOURCE IMAGES COME THROUGH tools/prepare-screenshots.py, never
      hand-copied. The raw 2880x1800 captures live in _screenshots-raw/light/,
      OUTSIDE assets/ because tools/deploy.sh copies assets/ wholesale —
      anything in here ships. The 2026 set was staged against demo data, so
      there is no redaction step any more, but the pipeline stays: it selects
      which captures ship and downscales them to 1440x900.

   2. The app's own UI is light, always. It does not follow this site's theme,
      so a screenshot dropped straight onto the dark chassis reads as a hole
      punched in the page. The frame below gives it a deliberate mount — inset
      ring, own border, slight lift — so it reads as an inserted artefact
      rather than a styling failure. In light theme the mount nearly vanishes,
      which is correct.

   Every .shot carries a <figcaption>. These are illustrative captures with
   sample data, and the caption is where that is said.
   ───────────────────────────────────────────────────────────────────────── */
.shot {
  margin: 0;
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--raised);
  border: 1px solid var(--line-2);
  box-shadow: var(--shadow);
}
/* <picture> is inline by default, which would leave a text-baseline gap under
   the capture inside the figure's border. The <img> rule below still does all
   the sizing; this only stops the wrapper introducing a line box. */
.shot picture {
  display: block;
}
.shot img {
  display: block;
  width: 100%;
  height: auto;
  /* The capture has its own light chrome; a hairline stops it bleeding into
     the frame on the two sides where its background is near-white. */
  border-bottom: 1px solid var(--line);
}
.shot figcaption {
  padding: 11px 15px;
  font-family: var(--sans);
  font-size: 12.5px;
  letter-spacing: -.01em;
  line-height: 1.45;
  color: var(--faint);
  background: var(--raised);
}
/* Wider than the prose column it sits in, where there is room for it.
   The pull-out is COMPUTED so the figure lands exactly on the wide column's
   content edge, whatever column it happens to sit in. A fixed negative margin
   (what this used to be) put every figure on a different edge — one per parent
   width — and let a figure inside a full-width .wrap spill past the site
   measure entirely. The max() floors it at 0, so a figure already at the wide
   edge, or a narrow viewport with no room to give, simply doesn't move. */
.shot-wide {
  --pull: max(0px, (min(100cqi, var(--measure)) - var(--gutter) * 2 - 100%) / 2);
  margin-inline: calc(-1 * var(--pull));
}
/* Two related captures side by side — a modal and its result, the portal and
   its thank-you — so neither claims a full-width section it hasn't earned. */
.shot-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
  align-items: start;
}
@media (max-width: 860px) {
  .shot-pair { grid-template-columns: 1fr; }
}
@media (max-width: 700px) {
  /* .shot-wide needs no override here any more — below 920px the prose column
     already fills the gutters, so the computed pull resolves to 0 on its own. */
  .shot figcaption { font-size: 12px; padding: 10px 12px; }
}

/* ── Print ───────────────────────────────────────────────────────────────
   The audit-trail evidence block is meant to be handed to an insurer or a
   regulator, so it must print as a document rather than a dark screenshot.
   ───────────────────────────────────────────────────────────────────────── */
@media print {
  .nav, .footer, .skip, .actions { display: none; }
  body { background: #fff; color: #000; font-size: 11pt; }
  .paper, .card, .panel, .ledger { box-shadow: none; border: 1px solid #999; }
  .ledger-float { display: none; }   /* an in-progress check means nothing on paper */
  .hero::before, .hero::after { display: none; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 9pt; }
}
