/* tokens.css — the single source of truth for every DigitalTrustPass surface.
 *
 * PREFLIGHT_SCOPE.md 8. The visual system is ported wholesale from
 * provadeconsentimento.com.br: the same palette, the same depth effects, the
 * same Syne / Geist / JetBrains Mono ramp. What is NOT ported is Tailwind —
 * it contributed layout utilities, not the look, and adding a Node build step
 * to a Python backend to obtain `flex` and `p-6` would cost the property that
 * a template edit deploys instantly. The utilities below cover what the
 * templates actually use.
 *
 * FRONTEND_SPEC.md 2 ("the upgrade is subtraction") is WITHDRAWN. It produced
 * a page that read as unfinished rather than austere.
 *
 * Fonts are self-hosted latin-subset variable woff2 (~104 KB). No Google
 * Fonts call: FRONTEND_SPEC.md 10 forbids third-party requests, and a product
 * that reports what other sites load does not get to load anything itself.
 */

/* ─────────────────────────── Faces ─────────────────────────── */

@font-face {
  font-family: "Syne";
  src: url("/static/fonts/syne-latin.woff2") format("woff2");
  font-weight: 400 800;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Geist";
  src: url("/static/fonts/geist-latin.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url("/static/fonts/jetbrains-mono-latin.woff2") format("woff2");
  font-weight: 100 800;
  font-style: normal;
  font-display: swap;
}

/* ─────────────────────────── Tokens ─────────────────────────── */

:root {
  /* Surfaces — the inherited dark ladder, unchanged */
  --surface-base:    #080808;
  --surface-sunken:  #0f0f0f;
  --surface-raised:  #141414;
  --surface-overlay: #1a1a1a;

  /* Text. Ratios against --surface-base, computed not guessed.
   * --text-muted stays at #808080 (~5.0:1); the inherited #555555 computed
   * near 2.8:1 and failed AA at any size. Nothing below 4.5:1 carries text. */
  --text-primary:   #f2f2f2;   /* ~18.1:1  AAA */
  --text-secondary: #a0a0a0;   /* ~4.6:1   AA  */
  --text-muted:     #808080;   /* ~5.0:1   AA  */

  --border-subtle:  #1e1e1e;
  --border-default: #2a2a2a;
  --border-strong:  #3d3d3d;

  /* Accents. The cyan is BACK — it earns its place in the depth effects
   * (glow lines, verified glow, shimmer) where a single hue reads flat. */
  --accent:       #5b6af5;   /* ~4.7:1  AA */
  --accent-hover: #6b7aff;
  --accent-deep:  #4f63e8;
  --cyan:         #0ea5e9;

  /* Severity. PREFLIGHT_SCOPE.md 4: permitted on a private report about the
   * reader's OWN site, because ordering is what they are paying for. Still
   * forbidden on anything published or about a named third party (DEC-018) —
   * those surfaces use --attention and the neutral `class` vocabulary only. */
  --sev-critical: #ef4444;
  --sev-high:     #f97316;
  --sev-medium:   #f59e0b;
  --sev-low:      #64748b;
  --ok:           #22c55e;
  --attention:    #f59e0b;

  /* 4px scale */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px; --s5: 24px;
  --s6: 32px; --s7: 48px; --s8: 64px; --s9: 96px;

  --r-tag: 3px; --r-control: 8px; --r-card: 12px; --r-modal: 16px;

  --font-display: "Syne", ui-sans-serif, system-ui, sans-serif;
  --font-sans: "Geist", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --measure: 46rem;
  --measure-wide: 68rem;
}

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--surface-base);
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 1.65;
  min-height: 100dvh;
}

::selection { background: var(--accent); color: #fff; }

/* ─────────────────────────── Type ─────────────────────────── */

h1, h2, h3 { margin: 0 0 var(--s3); font-family: var(--font-display); font-weight: 600; }
h1 { font-size: clamp(36px, 6vw, 60px); line-height: 1.02; letter-spacing: -0.035em; font-weight: 500; }
h2 { font-size: clamp(26px, 3.4vw, 34px); line-height: 1.15; letter-spacing: -0.025em; margin-top: var(--s7); }
h3 { font-size: 20px; line-height: 1.3; letter-spacing: -0.015em; margin-top: var(--s5); }
p  { margin: 0 0 var(--s3); }

a { color: var(--accent-hover); text-underline-offset: 3px; }
a:hover { color: var(--text-primary); }

.mono, code, pre { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

.label {
  font-family: var(--font-mono);
  font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--text-muted);
}
.lead    { font-size: clamp(17px, 2vw, 19px); line-height: 1.6; color: var(--text-secondary); }
.muted   { color: var(--text-secondary); }
.small   { font-size: 13px; }
.measure { max-width: var(--measure); }
.center  { text-align: center; }

.stat-number {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.04em;
}

/* ─────────────────────────── Layout ─────────────────────────── */

.wrap      { max-width: var(--measure);      margin: 0 auto; padding: var(--s5) var(--s4) var(--s9); }
.wrap-wide { max-width: var(--measure-wide); margin: 0 auto; padding: var(--s5) var(--s4) var(--s9); }

.stack > * + * { margin-top: var(--s3); }
.row  { display: flex; gap: var(--s3); flex-wrap: wrap; align-items: center; }
.row-between { display: flex; gap: var(--s4); flex-wrap: wrap; align-items: center; justify-content: space-between; }

.grid   { display: grid; gap: var(--s4); }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); }
/* Fixed 4-up: severity counts must stay on one row, because reading them
 * as a ladder is the point. auto-fit would wrap the fourth alone. */
.grid-4 { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 40rem) { .grid-4 { grid-template-columns: repeat(2, 1fr); } }

.section { margin-top: var(--s8); }

/* ─────────────────────────── Depth ─────────────────────────── */

/* Gradient mesh — the ambient field the whole design sits on */
.mesh-bg {
  background-color: var(--surface-base);
  background-image:
    radial-gradient(ellipse 80% 50% at 20% 20%, rgba(79,99,232,0.06) 0%, transparent 60%),
    radial-gradient(ellipse 60% 40% at 80% 80%, rgba(14,165,233,0.05) 0%, transparent 60%),
    radial-gradient(ellipse 40% 30% at 60% 40%, rgba(79,99,232,0.03) 0%, transparent 50%);
}


.doc-texture {
  background-image: repeating-linear-gradient(
    45deg,
    rgba(255,255,255,0.015) 0,
    rgba(255,255,255,0.012) 1px,
    transparent 0,
    transparent 50%);
  background-size: 8px 8px;
}

.glow-line {
  height: 1px; border: 0; margin: var(--s7) 0;
  background: linear-gradient(90deg,
    transparent 0%,
    rgba(79,99,232,0.6) 30%,
    rgba(14,165,233,0.8) 50%,
    rgba(79,99,232,0.6) 70%,
    transparent 100%);
}

.accent-glow {
  box-shadow: 0 0 0 1px rgba(79,99,232,0.2),
              0 0 20px rgba(79,99,232,0.08),
              0 0 40px rgba(79,99,232,0.04);
}
.verified-glow {
  box-shadow: 0 0 0 1px rgba(14,165,233,0.2),
              0 0 20px rgba(14,165,233,0.08),
              0 0 40px rgba(14,165,233,0.04);
}

.glass {
  background: rgba(20, 20, 20, 0.6);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid var(--border-subtle);
}

.gradient-text {
  background: linear-gradient(135deg, var(--accent) 0%, var(--accent-hover) 100%);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent;
}


/* Hero word only — never a paragraph */
.shimmer-text {
  background: linear-gradient(90deg,
    #f2f2f2 0%, #ffffff 40%, var(--accent-deep) 50%, var(--cyan) 60%, #f2f2f2 100%);
  background-size: 200% auto;
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent;
  animation: shimmer 8s linear infinite;
}

/* Gradient edge — static. The pulse reported nothing. */
.live-border { position: relative; }
.live-border::before {
  content: '';
  position: absolute; inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: linear-gradient(135deg,
    rgba(79,99,232,0.6), rgba(14,165,233,0.3), rgba(79,99,232,0.1), transparent);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
  pointer-events: none;
}

/* Travelling light — one per page, on the hero only */
.scan-line {
  position: absolute; left: 0; right: 0; height: 1px;
  background: linear-gradient(90deg,
    transparent, rgba(79,99,232,0.4), rgba(14,165,233,0.6), rgba(79,99,232,0.4), transparent);
  animation: scan 12s linear infinite;
  pointer-events: none;
}

.hero {
  position: relative;
  overflow: hidden;
  padding: var(--s8) 0 var(--s7);
}
.hero::before {
  content: '';
  position: absolute; inset: 0;
  background: radial-gradient(ellipse at center, rgba(79,99,232,0.10) 0%, var(--surface-base) 70%);
  pointer-events: none;
}
.hero > * { position: relative; z-index: 1; }

@keyframes shimmer      { 0% { background-position: -200% center; } 100% { background-position: 200% center; } }
@keyframes scan         { 0% { top: 0; } 100% { top: 100%; } }
@keyframes border-pulse { 0%, 100% { opacity: 0.2; } 50% { opacity: 0.6; } }
@keyframes ping-slow    { 0%, 100% { transform: scale(1); opacity: 0.2; } 50% { transform: scale(1.3); opacity: 0; } }

/* ─────────────────────────── Components ─────────────────────────── */

.badge {
  display: inline-flex; align-items: center; gap: var(--s2);
  padding: 5px 12px;
  border: 1px solid var(--border-default);
  border-radius: 999px;
  background: var(--surface-raised);
  font-family: var(--font-mono);
  font-size: 11px; letter-spacing: 0.06em;
  color: var(--text-secondary);
}
.badge-accent { border-color: rgba(91,106,245,0.35); color: var(--accent-hover); }
.badge::before {
  content: ''; width: 5px; height: 5px; border-radius: 50%;
  background: currentColor; flex: none;
}
.badge-plain::before { display: none; }

.panel {
  background: var(--surface-raised);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-card);
  padding: var(--s5);
  margin: 0 0 var(--s4);
}
.panel-accent    { border-left: 2px solid var(--accent); }
.panel-attention { border-left: 2px solid var(--attention); }

.card {
  background: var(--surface-raised);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-card);
  padding: var(--s5);
  transition: transform 300ms ease, border-color 200ms ease;
}
.card:hover { transform: translateY(-2px); border-color: var(--border-default); }
.card h3 { margin-top: 0; }
.card p:last-child { margin-bottom: 0; }

.card-glass { background: rgba(20,20,20,0.6); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); }

/* Severity pills — private, remediation-directed reports only */
.sev {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 9px; border-radius: var(--r-tag);
  font-family: var(--font-mono); font-size: 11px;
  letter-spacing: 0.08em; text-transform: uppercase;
  border: 1px solid currentColor;
}
.sev-critical { color: var(--sev-critical); background: rgba(239,68,68,0.08); }
.sev-high     { color: var(--sev-high);     background: rgba(249,115,22,0.08); }
.sev-medium   { color: var(--sev-medium);   background: rgba(245,158,11,0.08); }
.sev-low      { color: var(--sev-low);      background: rgba(100,116,139,0.10); }
.sev-pass     { color: var(--ok);           background: rgba(34,197,94,0.08); }

.pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px; border-radius: 999px;
  border: 1px solid var(--border-default);
  background: var(--surface-sunken);
  font-family: var(--font-mono); font-size: 11px;
  color: var(--text-secondary);
}
.pill-pass::before { content: ''; width: 5px; height: 5px; border-radius: 50%; background: var(--ok); }
.pill-fail::before { content: ''; width: 5px; height: 5px; border-radius: 50%; background: var(--sev-high); }

.btn {
  display: inline-block;
  padding: 12px 22px;
  border: 1px solid transparent;
  border-radius: var(--r-control);
  background: var(--accent);
  color: #fff;
  font: inherit; font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 140ms ease, border-color 140ms ease, color 140ms ease;
}
.btn:hover { background: var(--accent-hover); color: #fff; }
.btn-quiet {
  background: transparent; color: var(--text-secondary);
  border-color: var(--border-default);
}
.btn-quiet:hover { background: var(--surface-raised); color: var(--text-primary); border-color: var(--border-strong); }

/* Pulsing halo behind the primary CTA */
.btn-pulse { position: relative; display: inline-flex; }
.btn-pulse::before {
  content: ''; position: absolute; inset: 0;
  border-radius: var(--r-control);
  background: var(--accent-hover);
  animation: ping-slow 3s ease-in-out infinite;
  pointer-events: none;
}
.btn-pulse > .btn { position: relative; }

input[type="text"], input[type="email"], input[type="url"] {
  width: 100%;
  padding: 12px 14px;
  background: var(--surface-sunken);
  border: 1px solid var(--border-default);
  border-radius: var(--r-control);
  color: var(--text-primary);
  font: inherit;
  transition: border-color 140ms ease;
}
input::placeholder { color: var(--text-muted); }
input:focus { border-color: var(--accent); }

:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 60%, transparent);
  border-radius: var(--r-control);
}

hr { border: 0; border-top: 1px solid var(--border-subtle); margin: var(--s6) 0; }

table { width: 100%; border-collapse: collapse; font-size: 13px; }
td, th { padding: 10px 10px 10px 0; text-align: left; vertical-align: top; border-bottom: 1px solid var(--border-subtle); }
th { color: var(--text-muted); font-weight: 600; }

pre {
  background: var(--surface-sunken);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-card);
  padding: var(--s4);
  overflow-x: auto;
  font-size: 12px;
  color: var(--text-secondary);
  white-space: pre-wrap;
  word-break: break-all;
}

code { font-size: 0.9em; color: var(--text-primary); }

/* ─────────────────── Scrollbar ─────────────────── */

::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: var(--surface-sunken); }
::-webkit-scrollbar-thumb { background: var(--border-default); border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: var(--accent); }
* { scrollbar-width: thin; scrollbar-color: var(--border-default) var(--surface-sunken); }

/* ─────────────────── Motion policy ─────────────────── */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ─────────────────── Chrome ─────────────────── */

footer.site {
  border-top: 1px solid var(--border-subtle);
  margin-top: var(--s8);
  padding-top: var(--s5);
  font-size: 13px;
  color: var(--text-secondary);
}
footer.site a { color: var(--text-secondary); }
footer.site a:hover { color: var(--text-primary); }

/* ═══════════════════════════════════════════════════════════════════════════
   PRODUCT COMPONENTS — the scan's own UI, used on the marketing page
   ═══════════════════════════════════════════════════════════════════════════
   The landing page exhibits the product rather than describing it. These three
   are the report's actual vocabulary: a check row, a finding, and the evidence
   under it. They are styled here — not in a template — so the page that sells a
   report and the report itself cannot drift apart (FRONTEND_SPEC 3).

   Rule carried from the scan discipline: every machine-readable string —
   path, header, status code, hash, timestamp — renders in mono. Every
   human-facing string renders in the sans face. The split is what makes a
   technical product read as technical. */

/* ── Check row ───────────────────────────────────────────────────────────────
   A real <table>: check name, outcome and detail are three columns of the same
   record repeated down the page, which is what a table is for. Screen readers
   announce the row header with each cell; a stack of <div>s announces nothing.
   Laid out with grid rather than table-layout so the columns behave the same
   as they did, and `display:grid` on <tr> is why the roles are restored
   explicitly below. */
.check-table { width: 100%; border-collapse: collapse; }
.check-table caption { text-align: left; padding: 0 var(--s3) var(--s2); }
.check-table th, .check-table td { border: 0; padding: 0; font-weight: 400; }

.check-row {
  display: grid;
  grid-template-columns: 1fr auto minmax(0, 14rem);
  gap: var(--s3);
  align-items: baseline;
  padding: 10px var(--s3);
  border-bottom: 1px solid var(--border-subtle);
  transition: background-color 120ms ease;
}
.check-row:hover { background: var(--surface-raised); }
.check-row:last-child { border-bottom: 0; }
.check-row .name { font-size: 14px; color: var(--text-primary); text-align: left; }
.check-row .detail {
  font-family: var(--font-mono); font-size: 12px;
  color: var(--text-muted); text-align: right;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.check-mark { font-family: var(--font-mono); font-size: 13px; }
.check-mark.pass { color: var(--ok); }
.check-mark.warn { color: var(--sev-medium); }
.check-mark.fail { color: var(--sev-critical); }

/* At phone widths the fixed 14rem detail column starves the name — measured at
 * 390px it left 63px for "Content-Security-Policy". The detail is the
 * supporting half, so it yields first. */
@media (max-width: 34rem) {
  .check-row { grid-template-columns: 1fr auto minmax(0, 8rem); gap: var(--s2); }
  .check-row .detail { font-size: 11px; }
}

/* ── Finding card — the signature component ──────────────────────────────── */
.finding-card {
  background: var(--surface-raised);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-card);
  overflow: hidden;
}
.finding-card > * { padding: var(--s4) var(--s5); }
.finding-card > * + * { border-top: 1px solid var(--border-subtle); }
.finding-card .fc-head { display: flex; gap: var(--s3); align-items: center; justify-content: space-between; flex-wrap: wrap; }
.finding-card h3 { margin: 0; font-size: 18px; }
.finding-card .fc-body p { margin: 0; }
.finding-card .fc-body p + p { margin-top: var(--s3); }
.finding-card .fc-fix { background: var(--surface-sunken); }
.finding-card .fc-fix p { margin: 0; }
/* Severity reads on the left edge as well as in the badge, so the card is
 * scannable in a column without reading a word of it. */
.finding-card.sev-edge-critical { border-left: 2px solid var(--sev-critical); }
.finding-card.sev-edge-high     { border-left: 2px solid var(--sev-high); }
.finding-card.sev-edge-medium   { border-left: 2px solid var(--sev-medium); }
.finding-card.sev-edge-low      { border-left: 2px solid var(--sev-low); }

/* ── Evidence panel — the repeated visual motif ──────────────────────────────
   Marked up as <dl>. Observed / field / detected-stack are literally
   term-definition pairs, and the <div> wrapper around each <dt>/<dd> is valid
   HTML5 — it is what makes them griddable without a wrapper element that means
   nothing. */
.evidence {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: var(--s4);
  margin: 0;
}
.evidence dd { margin: 0; }
.evidence .k {
  font-family: var(--font-mono); font-size: 10px;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--text-muted); display: block; margin-bottom: 3px;
}
.evidence .v {
  font-family: var(--font-mono); font-size: 13px;
  color: var(--text-primary); word-break: break-all;
}

/* ── Report preview ──────────────────────────────────────────────────────────
   A compact rendering of a real report. Deliberately carries NO score.
   The scope document specified an "87 / 100" here; every real report prints
   "There is no score and no grade — the counts below are counts, not a mark out
   of anything", so a score on this page would have to be invented, in a
   component whose whole job is to show what the buyer actually receives. */
.report-preview {
  background: var(--surface-raised);
  border: 1px solid var(--border-default);
  border-radius: var(--r-modal);
  overflow: hidden;
}
.report-preview > * { padding: var(--s4) var(--s5); }
.report-preview > * + * { border-top: 1px solid var(--border-subtle); }
.report-preview .rp-head {
  background: var(--surface-sunken);
  display: flex; justify-content: space-between; align-items: baseline;
  gap: var(--s3); flex-wrap: wrap;
}
.report-preview .rp-counts {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--s3); text-align: center;
}
.rp-line {
  display: grid;
  grid-template-columns: 2.2rem 5.5rem 1fr;
  gap: var(--s3);
  align-items: baseline;
  padding: 9px 0;
  border-bottom: 1px solid var(--border-subtle);
}
.rp-line:last-child { border-bottom: 0; }
.rp-line .idx { font-family: var(--font-mono); font-size: 12px; color: var(--text-muted); }
/* A badge is inline-flex, but a grid cell stretches it to the full column.
 * Without this it renders as a full-width bar on phones. */
.rp-line .sev { justify-self: start; }
.rp-line .ttl { font-size: 14px; color: var(--text-primary); }
@media (max-width: 34rem) {
  .rp-line { grid-template-columns: 2rem 1fr; }
  .rp-line .ttl { grid-column: 1 / -1; padding-left: 2rem; }
}

/* Announced by screen readers, invisible on screen. The check glyphs are
 * decorative (aria-hidden) so a reader hears "passed" rather than "check mark". */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ═══════════════════════════════════════════════════════════════════════════
   HERO — split. Copy left, instrument right.
   ═══════════════════════════════════════════════════════════════════════════
   The instrument is on screen before anyone types, because the product running
   in about a second is the strongest thing this page has and hiding it until
   click meant a first-time visitor saw a claim about an instrument rather than
   one. Asymmetric on purpose: a centred hero reads as a landing page, a split
   one reads as software. */
/* align-items: START, not center. Centring two columns of unequal height
 * pushes the shorter one down — so the headline began roughly 130px below the
 * top of the panel beside it and the hero had no shared edge at all. Worse, the
 * panel GROWS when a scan lands (the result foot appears), which re-centred the
 * column and shifted the headline down mid-read.
 *
 * Starting them means the label and the panel's title bar share one line, the
 * left column reads as the first column of a document rather than as something
 * floating next to a box, and nothing moves when the scan finishes. */
.hero-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--s8);
  align-items: start;
  padding: var(--s7) 0 var(--s8);
  position: relative;
}
/* Then the last 12px. The panel's own label sits 12px inside it — 11px of head
 * padding plus the 1px border — so aligning the two BOXES still left the two
 * mono labels, same face and same size, on baselines 12px apart. Aligning the
 * type instead of the containers is the version that reads as deliberate. */
.hero-split > *:first-child { padding-top: 12px; }

@media (max-width: 60rem) {
  .hero-split { grid-template-columns: 1fr; gap: var(--s6); padding-top: var(--s5); }
  .hero-split > *:first-child { padding-top: 0; }
}

/* ── The instrument ───────────────────────────────────────────────────────── */
.inst {
  background: var(--surface-raised);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-card);
  overflow: hidden;
}
.inst-head {
  display: flex; justify-content: space-between; align-items: center; gap: var(--s3);
  padding: 11px var(--s4);
  background: var(--surface-sunken);
  border-bottom: 1px solid var(--border-subtle);
}
.irow {
  display: grid;
  grid-template-columns: 1fr auto minmax(0, 8.5rem);
  gap: var(--s3);
  align-items: baseline;
  padding: 9px var(--s4);
  border-bottom: 1px solid var(--border-subtle);
}
.irow .n { font-size: 13.5px; color: var(--text-primary); }
.irow .d {
  font-family: var(--font-mono); font-size: 11px; color: var(--text-muted);
  text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.irow .m { font-family: var(--font-mono); font-size: 12px; color: var(--text-muted); }
.irow .m.pass { color: var(--ok); }
.irow .m.warn { color: var(--sev-medium); }
/* Rows the FREE scan does not run. Dimmed and labelled rather than hidden: the
 * whole pitch is "here is the list", so the list has to be complete — and
 * marking one of these as passed during a free scan is the one lie this page
 * cannot afford. */
.irow.locked .n { color: var(--text-secondary); }
.irow.locked .d { color: var(--accent-hover); }

.inst-stats {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: var(--s3); padding: var(--s4);
  border-bottom: 1px solid var(--border-subtle);
}
.inst-stats .v { font-family: var(--font-mono); font-size: 22px;
                 font-variant-numeric: tabular-nums; letter-spacing: -0.04em; }
.inst-foot { padding: var(--s4); background: var(--surface-sunken); }
@media (max-width: 34rem) {
  .irow { grid-template-columns: 1fr auto minmax(0, 6.5rem); }
  .inst-stats { grid-template-columns: repeat(2, 1fr); row-gap: var(--s4); }
}


/* ═══════════════════════════════════════════════════════════════════════════
   SURFACE CRAFT — static, not animated
   ═══════════════════════════════════════════════════════════════════════════
   The scan-line was removed from the hero: a decorative 1px sweep on a 12s loop
   competes with the panel beside it that does real scanning, and loses.

   Everything below is static. The rule that survived that decision:
   MOTION MUST REPORT STATE. A loop that reports nothing is decoration wearing
   an instrument's clothes, and it is exactly what a technical reader discounts.
   Sophistication in this register comes from light, edges and alignment. */

/* ── Light source ─────────────────────────────────────────────────────────
   One hairline of light on the top edge of every raised surface, as though lit
   from above. It is the cheapest thing on this page and the one that most makes
   a flat dark rectangle read as a physical object. 4.5% white — any more and it
   becomes a visible stripe rather than an edge. */
.panel, .card, .inst, .finding-card, .report-preview {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.045);
}
/* The glow variants keep their glow AND gain the edge. */
.accent-glow {
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.045),
              0 0 0 1px rgba(79,99,232,0.2),
              0 0 20px rgba(79,99,232,0.08),
              0 0 40px rgba(79,99,232,0.04);
}
.verified-glow {
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.045),
              0 0 0 1px rgba(14,165,233,0.2),
              0 0 20px rgba(14,165,233,0.08),
              0 0 40px rgba(14,165,233,0.04);
}

/* ── Calibration ticks ────────────────────────────────────────────────────
   Two corner marks on the instrument, in accent, sitting over its own border.
   Borrowed from crop marks and measuring equipment: they say "this thing is
   aligned to something" without saying anything. Diagonal rather than four —
   four corners reads as a frame, two reads as registration. */
.inst { position: relative; }
.inst::before,
.inst::after {
  content: "";
  position: absolute;
  width: 11px; height: 11px;
  pointer-events: none;
  border-style: solid;
  border-color: color-mix(in srgb, var(--accent) 60%, transparent);
}
.inst::before { top: -1px;    left: -1px;  border-width: 1px 0 0 1px; border-radius: var(--r-card) 0 0 0; }
.inst::after  { bottom: -1px; right: -1px; border-width: 0 1px 1px 0; border-radius: 0 0 var(--r-card) 0; }

/* ── Rows respond to the pointer ──────────────────────────────────────────
   The one motion a list should have: it reports where you are. */
.irow { transition: background-color 120ms ease; }
.irow:hover { background: var(--surface-overlay); }

/* ── Focus, sharpened ─────────────────────────────────────────────────────
   A crisp 1px accent edge plus a thin ring, rather than a soft 2px halo. Softer
   focus reads as a glow effect; a hard edge reads as a control. */
:focus-visible {
  outline: none;
  border-radius: var(--r-control);
  box-shadow: 0 0 0 1px var(--accent),
              0 0 0 4px color-mix(in srgb, var(--accent) 22%, transparent);
}
input:focus { border-color: var(--accent); }

/* ── Section rules, asymmetric ────────────────────────────────────────────
   The symmetric centre-weighted glow reads as decoration. Weighting the accent
   to the left edge and letting it fade reads as a margin rule on a technical
   document — the same information, less costume. */
.glow-line {
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--accent) 85%, transparent) 0%,
    color-mix(in srgb, var(--cyan) 45%, transparent) 12%,
    var(--border-subtle) 45%,
    transparent 100%);
}

/* ── Arrival ──────────────────────────────────────────────────────────────
   Sections fade up once as they enter. This DOES report something — that you
   have reached a new part of the document — which is why it is the only motion
   being added. Applied by script so that with JS off nothing is ever hidden. */
.reveal { opacity: 0; transform: translateY(12px); }
.reveal-in {
  opacity: 1; transform: none;
  transition: opacity 460ms cubic-bezier(.4,0,.2,1), transform 460ms cubic-bezier(.4,0,.2,1);
}
@media (prefers-reduced-motion: reduce) {
  .reveal, .reveal-in { opacity: 1; transform: none; transition: none; }
}

/* ── Disclosure ───────────────────────────────────────────────────────────
   For apparatus a reader may want and most will not: proof, raw tokens,
   anything correct but unreadable. Present, one click away, and not the last
   impression a $29 product leaves. */
.disclosure {
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-card);
  background: var(--surface-raised);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.045);
  padding: var(--s3) var(--s4);
  margin: var(--s4) 0;
}
.disclosure > summary {
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-secondary);
  list-style: none;
}
.disclosure > summary::-webkit-details-marker { display: none; }
.disclosure > summary::before { content: "+ "; color: var(--accent-hover); }
.disclosure[open] > summary::before { content: "− "; }
.disclosure > summary:hover { color: var(--text-primary); }
.disclosure pre { margin-bottom: 0; }

/* ═══════════════════════════════════════════════════════════════════════════
   RHYTHM — the fixes for "blocky"
   ═══════════════════════════════════════════════════════════════════════════
   Measured on the live page: 28 bordered boxes, every section exactly 1056px
   wide, every h2 exactly 34px, every section gap exactly 64px. Nothing was
   wrong with any one of those. Together they mean no section can look more
   important than another, and a reader's eye has nothing to catch on.

   The cure is variation, not decoration. */

/* ── 1. Not every section deserves the same air ──────────────────────────── */
.section-lg    { margin-top: var(--s9); }   /* a new idea starts here */
.section-tight { margin-top: var(--s5); }   /* this continues the one above */

/* ── 2. Two heading scales, not one ──────────────────────────────────────
   The two or three moments that carry the argument get a larger size. If
   every heading is the same, the page has no spine. */
h2.lead-h {
  font-size: clamp(32px, 4.4vw, 46px);
  line-height: 1.05;
  letter-spacing: -0.03em;
}

/* ── 3. Editorial blocks instead of bordered cards ───────────────────────
   A hairline rule across the top and nothing else. Removes four boxes from the
   page and reads like a printed reference rather than a dashboard. The accent
   segment is short on purpose — a full-width accent rule is a divider, a stub
   is an index mark. */
.rule-block { position: relative; padding-top: var(--s4); }
.rule-block::before {
  content: "";
  position: absolute; top: 0; left: 0; right: 0; height: 1px;
  background: var(--border-default);
}
.rule-block::after {
  content: "";
  position: absolute; top: 0; left: 0; width: 34px; height: 1px;
  background: var(--accent);
}
.rule-block h3 { margin-top: var(--s2); }

/* ── 4. A numbered spine ─────────────────────────────────────────────────
   Small mono ordinals down the page. Costs nothing, and it is most of why a
   long document feels edited rather than stacked. */
.idx {
  font-family: var(--font-mono);
  font-size: 11px; letter-spacing: 0.16em;
  color: var(--accent-hover);
  margin-right: var(--s3);
}

/* ── 5. One element allowed past the measure ─────────────────────────────
   Everything sitting on the same two edges is what makes a page feel like a
   column of blocks. Letting a single wide element break out gives the eye a
   reason to move. Only above the point where there is room for it. */
@media (min-width: 78rem) {
  .bleed { margin-left: calc(var(--s7) * -1); margin-right: calc(var(--s7) * -1); }
}

/* ── Browser chrome ───────────────────────────────────────────────────────
   A bordered <div> reads as a section of a page. The same thing inside a
   window frame reads as a screenshot of software that exists — which is most
   of what "this product is real" looks like on a landing page, and costs
   nothing but a title bar.

   Drawn in CSS rather than shipped as an image so it inherits the palette and
   stays sharp at any density. */
.chrome {
  border: 1px solid var(--border-default);
  border-radius: var(--r-modal);
  overflow: hidden;
  background: var(--surface-raised);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.045),
              0 24px 48px -24px rgba(0,0,0,0.9);
}
.chrome-bar {
  display: flex; align-items: center; gap: var(--s3);
  padding: 10px var(--s4);
  background: var(--surface-sunken);
  border-bottom: 1px solid var(--border-subtle);
}
.chrome-dots { display: flex; gap: 6px; flex: none; }
.chrome-dots i {
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--border-strong); display: block;
}
.chrome-url {
  flex: 1;
  font-family: var(--font-mono); font-size: 11px;
  color: var(--text-muted);
  background: var(--surface-base);
  border: 1px solid var(--border-subtle);
  border-radius: 999px;
  padding: 4px var(--s3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chrome-body { padding: 0; }
.chrome-body img { display: block; width: 100%; height: auto; }

/* An email, rather than a browser. Same idea, different frame. */
.mailframe { border: 1px solid var(--border-default); border-radius: var(--r-card);
             overflow: hidden; background: var(--surface-raised);
             box-shadow: inset 0 1px 0 rgba(255,255,255,0.045); }
.mailframe .mailhead {
  padding: var(--s3) var(--s4); background: var(--surface-sunken);
  border-bottom: 1px solid var(--border-subtle);
}
.mailframe img { display: block; width: 100%; height: auto; }

/* Product shots sit on a slightly recessed ground so the frame reads as
   floating above the page rather than pasted onto it. */
.shot-ground {
  background: var(--surface-sunken);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-modal);
  padding: var(--s6);
}
@media (max-width: 40rem) { .shot-ground { padding: var(--s4); } }

/* ═══════════════════════════════════════════════════════════════════════════
   RESPONSE — the page reacting to a pointer
   ═══════════════════════════════════════════════════════════════════════════
   Distinct from the motion policy above, and not in tension with it. That rule
   is about LOOPS: an animation that runs on its own while reporting nothing.
   Everything here is a transition triggered by the reader, and what it reports
   is where their pointer is and whether a thing is pressable. That is the
   oldest job motion has on an interface and the least decorative.

   All of it is 120–180ms. Anything slower reads as an effect rather than a
   response; anything with a bounce reads as a toy. */

/* ── Pressable things must depress ───────────────────────────────────────── */
.btn {
  transition: background-color 140ms ease, border-color 140ms ease,
              color 140ms ease, transform 120ms ease, box-shadow 140ms ease;
}
.btn:hover {
  transform: translateY(-1px);
  box-shadow: 0 6px 18px -8px rgba(79, 99, 232, 0.55);
}
.btn:active {
  transform: translateY(1px);
  box-shadow: none;
  transition-duration: 40ms;   /* the press must feel instant; the release can ease */
}
.btn-quiet:hover { box-shadow: 0 6px 18px -10px rgba(0, 0, 0, 0.8); }

/* ── The index stub extends under the pointer ────────────────────────────
   The four editorial blocks have no border to light up, so the accent stub
   does the reporting instead: 34px at rest, 72px under the pointer. It is the
   one place on the page where the hover state is also the page's own motif. */
.rule-block::after { transition: width 180ms cubic-bezier(.4, 0, .2, 1); }
.rule-block:hover::after { width: 72px; }
.rule-block { transition: color 140ms ease; }
.rule-block:hover h3 { color: var(--text-primary); }

/* ── Report lines ───────────────────────────────────────────────────────── */
.rp-line { transition: background-color 120ms ease; }
.rp-line:hover { background: var(--surface-overlay); }
.rp-line:hover .idx { color: var(--accent-hover); }
.rp-line .idx { transition: color 120ms ease; }

/* ── Tags ───────────────────────────────────────────────────────────────── */
.pill { transition: border-color 140ms ease, color 140ms ease, background-color 140ms ease; }
.pill:hover {
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  color: var(--text-primary);
}

/* ── Screenshots lift ────────────────────────────────────────────────────
   A window frame that reacts is a window; one that does not is a picture of
   one. Small — 2px and a deepened drop — because these are large elements and
   a large element moving far is a slide, not a response. */
.chrome, .mailframe {
  transition: transform 180ms cubic-bezier(.4, 0, .2, 1), box-shadow 180ms ease,
              border-color 180ms ease;
}
.chrome:hover, .mailframe:hover {
  transform: translateY(-2px);
  border-color: var(--border-strong);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.045),
              0 32px 60px -28px rgba(0, 0, 0, 0.95);
}

/* ── Navigation ──────────────────────────────────────────────────────────
   A rule that grows from the left rather than an underline that appears. Same
   information, and it moves in the direction the text is read. */
nav a.label, footer.site a {
  position: relative;
  text-decoration: none;
  transition: color 140ms ease;
}
nav a.label::after, footer.site a::after {
  content: "";
  position: absolute; left: 0; bottom: -3px; height: 1px; width: 0;
  background: var(--accent);
  transition: width 180ms cubic-bezier(.4, 0, .2, 1);
}
nav a.label:hover, footer.site a:hover { color: var(--text-primary); }
nav a.label:hover::after, footer.site a:hover::after { width: 100%; }

/* ── Check rows report the pointer on the mark, not just the row ─────────── */
.check-row .check-mark { transition: transform 120ms ease; }
.check-row:hover .check-mark { transform: scale(1.15); }

/* ═══════════════════════════════════════════════════════════════════════════
   THE CHAIN DIAGRAM
   ═══════════════════════════════════════════════════════════════════════════
   Inline SVG so it inherits the palette and stays sharp at any density — no
   image request, and FRONTEND_SPEC 10 rules out fetching one anyway. Text is
   styled here rather than with presentation attributes so the type tokens
   apply. */
/* At phone widths a 920-unit drawing scales its 11px type down to about 4px.
   It scrolls in its own container instead, and the three blocks under it carry
   the same content in prose — so a reader who never scrolls it loses nothing. */
.dgm-scroll { overflow-x: auto; }
.dgm { width: 100%; min-width: 640px; height: auto; display: block; }
.dgm text { font-family: var(--font-mono); fill: var(--text-muted); }
.dgm .t-lab   { font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; }
.dgm .t-hash  { font-size: 10.5px; fill: var(--text-secondary); }
.dgm .t-node  { font-size: 12px; fill: var(--text-primary); letter-spacing: 0.02em; }
.dgm .box     { fill: var(--surface-raised); stroke: var(--border-default); }
.dgm .box-hi  { fill: var(--surface-overlay); stroke: color-mix(in srgb, var(--accent) 50%, transparent); }
.dgm .link    { stroke: var(--border-strong); fill: none; }
.dgm .link-hi { stroke: var(--accent); fill: none; }
.dgm .tick    { stroke: color-mix(in srgb, var(--accent) 60%, transparent); fill: none; }
/* Marker contents do not inherit from the element that references them, so the
   arrowheads are coloured here rather than by the path that uses them. */
.dgm .mk      { stroke: var(--border-strong); fill: none; }
.dgm .mk-hi   { stroke: var(--accent); fill: none; }

/* ═══════════════════════════════════════════════════════════════════════════
   AMBIENT FIELD
   ═══════════════════════════════════════════════════════════════════════════
   A very slow drift on the background gradients — the one loop on the site
   that reports nothing, added deliberately and against the rule stated above.
   The terms it is kept to:

     · it is BEHIND everything and touches no instrument surface, so nothing
       that reports state is ever animated;
     · 72 seconds a cycle and under 2% travel, which is below the rate at which
       a reader can watch it move — it registers as the page being lit rather
       than as something happening;
     · transform only, on a compositor layer, so it costs no layout and no
       repaint of any content;
     · off entirely under prefers-reduced-motion, via the global policy.

   If it ever reads as motion rather than as light, it is wrong and should go.
   The static mesh underneath it stands alone. */
.mesh-bg { position: relative; }
.mesh-bg::before {
  content: "";
  position: fixed;
  inset: -25%;
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(ellipse 42% 38% at 24% 28%, rgba(79, 99, 232, 0.075) 0%, transparent 62%),
    radial-gradient(ellipse 36% 32% at 78% 70%, rgba(14, 165, 233, 0.055) 0%, transparent 60%);
  animation: drift 72s ease-in-out infinite alternate;
  will-change: transform;
}
/* The field is a child, so the content has to be lifted over it. */
.mesh-bg > * { position: relative; z-index: 1; }

@keyframes drift {
  from { transform: translate3d(-1.4%, -0.9%, 0) scale(1.02); }
  to   { transform: translate3d(1.4%,  1.2%, 0) scale(1.06); }
}

@media (prefers-reduced-motion: reduce) {
  .mesh-bg::before { animation: none; transform: none; }
}
