/* ============================================================================
   Vector Risk — bespoke design system
   Dark, engineered, orange. Replaces the HTML5 UP "Escape Velocity" template.
   ========================================================================== */

/* ---------- Design tokens ------------------------------------------------- */
:root {
	/* Palette — OKLCH, neutrals faintly warmed toward the brand hue (~47) */
	--bg:            oklch(0.15 0.008 55);
	--bg-2:          oklch(0.185 0.010 55);
	--surface:       oklch(0.205 0.011 55);
	--surface-2:     oklch(0.245 0.013 55);
	--line:          oklch(0.32 0.012 55);
	--line-soft:     oklch(1 0 0 / 0.07);

	--ink:           oklch(0.97 0.006 80);
	--ink-dim:       oklch(0.80 0.014 70);   /* body secondary — >8:1 on --bg */
	--ink-faint:     oklch(0.62 0.012 65);   /* metadata only, never body */

	--brand:         oklch(0.678 0.187 43);  /* the logo orange, #F26722 (matches the SVG art + canvas) */
	--brand-bright:  oklch(0.80 0.165 58);
	--brand-tint:    oklch(from var(--brand) l c h / 0.12);

	--on-brand:      oklch(0.16 0.02 55);    /* near-black text on orange */

	/* Type */
	--font-display: "Archivo", "Helvetica Neue", Arial, sans-serif;
	--font-sans:    "Archivo", "Helvetica Neue", Arial, sans-serif;
	--font-mono:    "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;

	--step--1: clamp(0.82rem, 0.79rem + 0.14vw, 0.9rem);
	--step-0:  clamp(1rem, 0.96rem + 0.2vw, 1.12rem);
	--step-1:  clamp(1.2rem, 1.1rem + 0.5vw, 1.5rem);
	--step-2:  clamp(1.5rem, 1.32rem + 0.9vw, 2.1rem);
	--step-3:  clamp(1.95rem, 1.6rem + 1.7vw, 3rem);
	--step-4:  clamp(2.5rem, 1.9rem + 3vw, 4.4rem);
	--step-5:  clamp(3rem, 2rem + 5vw, 5.75rem);   /* hero — capped < 6rem */

	/* Spacing */
	--gutter: clamp(1.25rem, 0.9rem + 1.8vw, 2.5rem);
	--section-y: clamp(4.5rem, 3rem + 7vw, 9rem);
	--maxw: 78rem;

	/* Radius */
	--r-sm: 0.4rem;
	--r-md: 0.7rem;
	--r-lg: 1.1rem;

	/* Depth */
	--shadow-2: 0 1.5em 3.5em -1.5em oklch(0 0 0 / 0.75);
	--glow: 0 0 2.5em -0.3em oklch(from var(--brand) l c h / 0.55);

	/* Motion */
	--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
	--ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);
	--dur-1: 0.35s;
	--dur-2: 0.6s;
	--dur-3: 1s;

	/* Semantic z-index scale */
	--z-base: 0;
	--z-raised: 10;
	--z-nav: 100;
	--z-overlay: 200;
	--z-modal: 300;

	color-scheme: dark;
}

/* ---------- sRGB fallback for browsers without oklch() -------------------- *
   Every colour above is OKLCH, and a custom property holding a function the
   browser cannot parse does not fall back - it becomes "invalid at
   computed-value time", which for `background` means transparent and for
   `color` means inherited. The whole site would render as black text on white.

   The usual two-declaration trick (`--bg: #0e0a08; --bg: oklch(...)`) does not
   help: the custom-property grammar accepts any token stream, so the oklch line
   is stored happily by a browser that cannot use it and simply fails later.
   @supports is the only mechanism that works, so the fallbacks live here.

   Who this is for: Edge 109 is the last version for Windows 7/8.1/Server 2012
   and is pinned there permanently - a real configuration in the locked-down
   bank and treasury estates this site sells into. oklch() landed in Edge 111.

   These are the exact sRGB values the browser itself computes from the OKLCH
   above, so the two palettes cannot drift by eye. If you change a token above,
   change its twin here. --brand is the one literal shared with the SVG art and
   hero.js - see CLAUDE.md "The orange lives in four places".                  */
@supports not (color: oklch(0 0 0)) {
	:root {
		--bg:           #0e0a08;
		--bg-2:         #16120e;
		--surface:      #1b1612;
		--surface-2:    #261f1b;
		--line:         #38312d;
		--line-soft:    rgba(255, 255, 255, 0.07);
		--ink:          #f7f5f1;
		--ink-dim:      #c4bcb4;
		--ink-faint:    #8b857f;
		--brand:        #f26722;
		--brand-bright: #ff9f40;
		--brand-tint:   rgba(242, 103, 34, 0.12);
		--on-brand:     #140b06;
		--glow:         0 0 2.5em -0.3em rgba(242, 103, 34, 0.55);
	}
}

/* Relative colour syntax - `oklch(from var(--brand) l c h / 0.12)` - arrived
   well after oklch() itself: Chrome 119 against 111, Firefox 128 against 113.
   A browser in that window passes the @supports test above, skips the whole
   fallback, and still cannot parse --brand-tint or --glow, so the tint goes
   transparent and the glow disappears. Narrow, but a real released window. */
@supports (color: oklch(0 0 0)) and (not (color: oklch(from red l c h))) {
	:root {
		--brand-tint: rgba(242, 103, 34, 0.12);
		--glow:       0 0 2.5em -0.3em rgba(242, 103, 34, 0.55);
	}
}

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

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
html[data-motion="calm"] { scroll-behavior: auto; }

body {
	font-family: var(--font-sans);
	font-size: var(--step-0);
	line-height: 1.6;
	font-weight: 400;
	color: var(--ink-dim);
	background: var(--bg);
	-webkit-font-smoothing: antialiased;
	overflow-x: clip;
}

img, svg, canvas { display: block; max-width: 100%; }
img { height: auto; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
:focus-visible { outline: 2px solid var(--brand-bright); outline-offset: 3px; border-radius: 2px; }

/* Selection carries the brand */
::selection { background: var(--brand); color: var(--on-brand); }

/* Skip link — off-screen until keyboard focus */
.skip-link {
	position: fixed;
	top: 0.75rem; left: 0.75rem;
	z-index: var(--z-modal);
	transform: translateY(-160%);
	background: var(--surface); color: var(--ink);
	box-shadow: inset 0 0 0 1px var(--line), var(--shadow-2);
	border-radius: var(--r-sm);
	padding: 0.7rem 1.1rem;
	font-weight: 600; font-size: 0.9rem;
	transition: transform var(--dur-1) var(--ease-out-expo);
}
.skip-link:focus { transform: translateY(0); }

/* ---------- Typography ---------------------------------------------------- */
h1, h2, h3, h4 {
	font-family: var(--font-display);
	color: var(--ink);
	line-height: 1.04;
	font-weight: 800;
	letter-spacing: -0.03em;
	text-wrap: balance;
}
h1, h2 { font-weight: 900; }
h2 { font-size: var(--step-3); }
h3 { font-size: var(--step-1); letter-spacing: -0.02em; line-height: 1.15; }
p { text-wrap: pretty; }
strong, b { color: var(--ink); font-weight: 700; }

.mono {
	font-family: var(--font-mono);
	font-feature-settings: "tnum" 1, "zero" 1;
	letter-spacing: -0.01em;
}

/* Single deliberate brand kicker — used ONCE, on the hero only */
.kicker {
	font-family: var(--font-mono);
	font-size: var(--step--1);
	letter-spacing: 0.28em;
	text-transform: uppercase;
	color: var(--brand);
	display: inline-flex;
	align-items: center;
	gap: 0.7em;
}
.kicker::before {
	content: "";
	width: 2.2em; height: 1px;
	background: currentColor;
	opacity: 0.6;
}

/* ---------- Layout primitives -------------------------------------------- */
.container {
	width: 100%;
	max-width: var(--maxw);
	margin-inline: auto;
	padding-inline: var(--gutter);
}
.section { padding-block: var(--section-y); position: relative; }
.section-head { max-width: 54rem; margin-bottom: clamp(2.5rem, 1.5rem + 3vw, 4.5rem); }
.section-head h2 { font-size: var(--step-4); margin-bottom: 0.45em; }
.section-head p { font-size: var(--step-1); color: var(--ink-dim); line-height: 1.45; max-width: 40ch; }

/* ---------- Buttons ------------------------------------------------------- */
.btn {
	display: inline-flex;
	align-items: center;
	gap: 0.6em;
	padding: 0.85em 1.5em;
	border-radius: var(--r-sm);
	font-weight: 600;
	font-size: var(--step-0);
	letter-spacing: -0.01em;
	transition: transform var(--dur-1) var(--ease-out-expo),
		background-color var(--dur-1) ease, box-shadow var(--dur-1) ease, color var(--dur-1) ease;
}
.btn:hover, .btn:focus-visible { will-change: transform; }
.btn svg { width: 1em; height: 1em; transition: transform var(--dur-1) var(--ease-out-expo); }
.btn--primary { background: var(--brand); color: var(--on-brand); box-shadow: var(--glow); }
.btn--primary:hover { background: var(--brand-bright); transform: translateY(-2px); }
.btn--primary:hover svg { transform: translateX(3px); }
/* --ink-faint, not --line: this is the only button with no fill, so the 1px
   ring is the entire visual boundary of the control. --line reads 1.55:1
   against --bg, under the 3:1 that WCAG 1.4.11 asks of a component boundary,
   and the button reads as a floating caption next to the filled primary. */
.btn--ghost { color: var(--ink); box-shadow: inset 0 0 0 1px var(--ink-faint); }
.btn--ghost:hover { box-shadow: inset 0 0 0 1px var(--brand); color: var(--brand-bright); transform: translateY(-2px); }
/* Press acknowledgment: quick compress, then the slow expo lift resumes on release */
.btn:active { transform: translateY(0) scale(0.98); transition-duration: 0.06s; }

/* ---------- Top navigation ------------------------------------------------ */
.nav {
	position: fixed;
	inset: 0 0 auto 0;
	z-index: var(--z-nav);
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 2rem;
	padding: 1rem var(--gutter);
	transition: background-color var(--dur-2) ease, border-color var(--dur-2) ease;
	border-bottom: 1px solid transparent;
}
/* Over the hero: a solid translucent bar — no backdrop blur, which would force
   the compositor to re-blur the live-animating simulation canvas every frame. */
.nav[data-scrolled="true"] {
	background: oklch(0.15 0.008 55 / 0.9);
	border-bottom-color: var(--line-soft);
}
/* ?glass=off - the backdrop-filter below is the most expensive thing the nav
   does, and a full-width blur recomputed on every scroll frame is a classic
   source of scroll jank on integrated graphics. It is already kept away from
   the hero, where the canvas is animating behind it; this switch is for
   measuring what it costs over the rest of the page. */
html[data-pv-glass="off"] .nav[data-past-hero="true"] {
	backdrop-filter: none;
	-webkit-backdrop-filter: none;
}

/* Glass only once scrolled past the hero, where the content behind is static. */
.nav[data-past-hero="true"] {
	background: oklch(0.15 0.008 55 / 0.72);
	backdrop-filter: blur(12px) saturate(1.2);
	-webkit-backdrop-filter: blur(12px) saturate(1.2);
}
.nav__brand { display: flex; align-items: center; gap: 0.7rem; flex-shrink: 0; }
.nav__brand img { height: 2.15rem; width: auto; }
.nav__brand-name {
	font-family: var(--font-mono);
	font-size: 0.8rem;
	letter-spacing: 0.16em;
	text-transform: uppercase;
	color: var(--ink);
}
.nav__links { display: flex; align-items: center; gap: 0.35rem; list-style: none; }
.nav__links > li { position: relative; }
.nav__links a, .nav__links button {
	display: inline-flex;
	align-items: center;
	gap: 0.4em;
	padding: 0.55em 0.85em;
	font-size: 0.85rem;
	font-weight: 500;
	color: var(--ink-dim);
	border-radius: var(--r-sm);
	transition: color var(--dur-1) ease, background-color var(--dur-1) ease;
	white-space: nowrap;
}
.nav__links a:hover, .nav__links button:hover, .nav__links li:hover > a, .nav__links li:hover > button { color: var(--ink); }
.nav__links .caret { width: 0.7em; height: 0.7em; opacity: 0.6; transition: transform var(--dur-1) var(--ease-out-expo); }
.nav__links li:hover .caret, .nav__links li:focus-within .caret { transform: rotate(180deg); }

/* Dropdowns — fixed-position friendly, escape any clipping context */
.nav__menu {
	position: absolute;
	top: calc(100% + 0.4rem);
	left: 0;
	min-width: 13rem;
	list-style: none;
	padding: 0.5rem;
	background: var(--surface);
	border: 1px solid var(--line);
	border-radius: var(--r-md);
	box-shadow: var(--shadow-2);
	opacity: 0;
	visibility: hidden;
	transform: translateY(-6px);
	transition: opacity var(--dur-1) ease, transform var(--dur-1) var(--ease-out-expo), visibility var(--dur-1);
}
.nav__links li:hover > .nav__menu, .nav__links li:focus-within > .nav__menu { opacity: 1; visibility: visible; transform: translateY(0); }
.nav__menu a { display: flex; justify-content: space-between; padding: 0.6em 0.75em; font-size: 0.85rem; color: var(--ink-dim); border-radius: var(--r-sm); }
.nav__menu a:hover { background: var(--surface-2); color: var(--ink); }
.nav__menu a .ext { color: var(--ink-dim); font-family: var(--font-mono); font-size: 0.75em; }
.nav__cta { margin-left: 0.5rem; }
.nav__toggle { display: none; }

/* ---------- Hero ---------------------------------------------------------- */
.hero {
	position: relative;
	min-height: 100svh;
	display: flex;
	align-items: center;
	padding-block: 8rem 4rem;
	overflow: clip;
	isolation: isolate;
}
.hero__canvas, .hero__overlay {
	position: absolute;
	/* top/left rather than inset:0 — hero.js sets an explicit top and
	   height to centre the aspect-capped scene band, and a fixed bottom
	   would over-constrain that. These are the pre-script defaults. */
	top: 0; left: 0;
	width: 100%; height: 100%;
	z-index: var(--z-base);
}
/* screen-blend so the distribution fuses into the simulation's glow (same light,
   no "layer on top" seam) rather than compositing opaquely over it */
.hero__overlay { pointer-events: none; mix-blend-mode: screen; }
/* Fallback field if WebGL is unavailable */
.hero__canvas { background: radial-gradient(120% 90% at 78% 50%, var(--brand-tint), transparent 60%); }
/* Contrast scrim so headline stays legible over the simulation */
.hero::before {
	content: "";
	position: absolute; inset: 0;
	z-index: var(--z-raised);
	background:
		linear-gradient(100deg, var(--bg) 4%, oklch(0.15 0.008 55 / 0.82) 32%, transparent 62%),
		linear-gradient(0deg, var(--bg), transparent 22%);
	pointer-events: none;
}
.hero .container { position: relative; z-index: var(--z-overlay); }
.hero__content { max-width: 40rem; }
.hero h1 {
	font-size: var(--step-5);
	font-weight: 900;
	letter-spacing: -0.04em;
	margin: 1.1rem 0 0;
}
.hero h1 em { font-style: normal; color: var(--brand); }
/* Two authored headline variants, one switch: data-hero on .hero picks which one
   shows. Both live inside the single #hero-title, so the section's
   aria-labelledby still resolves to one h1 — and because accessible-name
   computation skips display:none descendants, the hidden line never leaks into
   that name. The wordmark inherits Archivo 900 / --step-5 / -0.04em from
   .hero h1 and takes its orange from the same <em> hook as the sentence, so
   there is no second type treatment to keep in sync. */
.hero__head { display: block; }
/* The statement is 45 characters where the wordmark is 11, so it cannot take
   --step-5 or the sub's 40rem measure: at 92px inside 640px it broke over four
   lines and pushed the desktop hero to 1131px inside a 900px viewport, which
   letterboxed the simulation and dropped the spec strip below the fold. Sized
   and measured so the two authored sentences land on two lines — the
   composition the <br> is asking for — with room to spare for a fallback face
   wider than Archivo: "One number that matters." needs 710px of the 768 here.
   The clamp bottoms out at 2rem, which is the phone size, so the media query
   below no longer has to restate it. */
.hero[data-hero="sentence"] h1 { font-size: clamp(2rem, 1rem + 3.05vw, 3.5rem); }
/* The column, not just the heading: .hero__content's 40rem clamps the h1 to
   640px whatever max-width the heading itself carries. .hero__sub keeps its own
   34rem, so widening this moves the headline and the spec strip and leaves the
   body copy's measure exactly where it was. 48rem still stops ~290px short of
   the fan's bell at 0.82 of the width, so nothing crowds the simulation. */
.hero[data-hero="sentence"] .hero__content { max-width: 48rem; }
.hero[data-hero="wordmark"] .hero__head--sentence,
.hero[data-hero="sentence"] .hero__head--wordmark { display: none; }
/* Survey live-preview override (see the head bootstrap script): when a ?hero=
   query param sets data-pv-hero on <html>, it wins over the authored attribute.
   !important is deliberate — this is a preview mechanism, not a design state,
   and it must beat the authored rule above without a specificity arms race. */
html[data-pv-hero="wordmark"] .hero__head--sentence,
html[data-pv-hero="sentence"] .hero__head--wordmark { display: none !important; }
html[data-pv-hero="wordmark"] .hero__head--wordmark,
html[data-pv-hero="sentence"] .hero__head--sentence { display: block !important; }
.hero__sub {
	font-size: var(--step-1);
	color: var(--ink-dim);
	line-height: 1.5;
	max-width: 34rem;
	margin-top: 1.4rem;
}
.hero__sub strong { color: var(--ink); }
.hero__actions { display: flex; flex-wrap: wrap; gap: 0.9rem; margin-top: 2.2rem; }
.hero__spec {
	display: flex; flex-wrap: wrap;
	gap: 1.4rem 2.2rem;
	margin-top: 3rem;
	padding-top: 1.6rem;
	border-top: 1px solid var(--line-soft);
	max-width: 34rem;
}
.hero__spec div { display: flex; flex-direction: column; gap: 0.15rem; }
.hero__spec dt { font-family: var(--font-mono); font-size: 1.35rem; color: var(--ink); font-weight: 600; letter-spacing: -0.02em; }
.hero__spec dd { font-size: 0.78rem; color: var(--ink-faint); text-transform: uppercase; letter-spacing: 0.1em; }

.hero__legend {
	position: absolute;
	right: var(--gutter);
	bottom: clamp(1.5rem, 4vh, 3rem);
	z-index: var(--z-overlay);
	font-family: var(--font-mono);
	font-size: 0.72rem;
	color: var(--ink-faint);
	text-align: right;
	letter-spacing: 0.04em;
	line-height: 1.7;
	pointer-events: none;
}
.hero__legend b { color: var(--brand); font-weight: 500; }
/* Live path counter: reserve the final "10,000" width so the line doesn't drift */
.hero__legend .count { display: inline-block; min-width: 6ch; text-align: right; }

/* ---------- Marquee / trust band ----------------------------------------- */
.band {
	border-block: 1px solid var(--line-soft);
	background: var(--bg-2);
	padding-block: 1.4rem;
	overflow: hidden;
}
.band__track {
	display: flex;
	align-items: center;
	gap: 3.5rem;
	width: max-content;
	animation: marquee 42s linear infinite;
	animation-play-state: paused;
}
.band.is-inview .band__track { animation-play-state: running; }
.band__track span {
	font-family: var(--font-mono);
	font-size: 0.9rem;
	color: var(--ink-dim);
	letter-spacing: 0.02em;
	display: inline-flex; align-items: center; gap: 3.5rem;
	white-space: nowrap;
}
.band__track span::after { content: "◆"; color: var(--brand); font-size: 0.6em; }
.band.is-inview:hover .band__track { animation-play-state: paused; }
@keyframes marquee { to { transform: translateX(-50%); } }

/* Skip rendering offscreen sections while the hero animates (not .hero/.band). */
.section, .footer { content-visibility: auto; contain-intrinsic-size: auto 800px; }

/* ---------- Models explorer ----------------------------------------------- */
.models { background: var(--bg); }
.explorer { display: grid; grid-template-columns: minmax(15rem, 20rem) 1fr; gap: clamp(1.5rem, 1rem + 2vw, 3.5rem); align-items: start; }

.explorer__list { list-style: none; display: flex; flex-direction: column; gap: 0.15rem; position: sticky; top: 6rem; }
.explorer__tab {
	width: 100%;
	display: flex;
	align-items: baseline;
	gap: 0.9rem;
	padding: 1rem 1.1rem;
	border-radius: var(--r-md);
	text-align: left;
	color: var(--ink-dim);
	transition: background-color var(--dur-1) ease, color var(--dur-1) ease, box-shadow var(--dur-1) ease;
	position: relative;
}
.explorer__tab .code { font-family: var(--font-mono); font-size: 0.78rem; color: var(--ink-faint); width: 3.2em; flex-shrink: 0; transition: color var(--dur-1) ease; }
.explorer__tab .label { font-family: var(--font-display); font-weight: 600; font-size: 1.05rem; color: inherit; letter-spacing: -0.01em; }
.explorer__tab:hover { background: var(--surface); color: var(--ink); }
/* Selected = a solid brand chip. Decisive, readable at a glance from the panel. */
.explorer__tab[aria-selected="true"] { background: var(--brand); color: var(--on-brand); box-shadow: var(--glow); }
.explorer__tab[aria-selected="true"] .code { color: var(--on-brand); }

.explorer__panels { position: relative; min-height: 22rem; }
.panel { display: none; }
.panel[data-active="true"] { display: block; }
.panel__code { font-family: var(--font-mono); color: var(--brand); font-size: 0.8rem; letter-spacing: 0.12em; text-transform: uppercase; }
.panel h3 { font-size: var(--step-2); margin: 0.5rem 0 1.2rem; }
.panel__body { columns: 2; column-gap: 2.5rem; color: var(--ink-dim); }
.panel__body p { margin-bottom: 1rem; break-inside: avoid; }
.panel__body p:last-child { margin-bottom: 0; }
/* ---- Which figure a model panel shows ------------------------------------ *
   Every panel ships BOTH presentations. The default is a quiet thematic motif
   that carries the shape of the calculation without explaining it — the same
   stance as the hero's Monte Carlo. The whole explorer flips with ONE attribute
   in index.html:

       <div class="explorer" data-model-figures="art">      <- motifs (default)
       <div class="explorer" data-model-figures="detail">   <- annotated diagrams

   Only the motif is in the page markup. The annotated set lives in
   assets-vr/model-details.html and app.js fetches it when detail mode is on —
   it was more than half of index.html while being hidden on a default visit.
   So the switch above is read by JS, not by CSS, and the motif steps aside on
   .details-in once the diagrams have actually landed. A failed fetch therefore
   leaves a complete page rather than an empty frame, and the same is true with
   JS off. Survey preview (?figures=…) goes through the same gate.
   See DESIGN.md → "The model figures".                                        */
.explorer.details-in .panel__figure--art { display: none; }

/* ---------- Shipped from the survey, and the variants it beat ------------
   The winners are authored behaviour now, so the stinger, the bar race and
   the button burst below render on an ordinary visit and their html[data-pv-*]
   rules are the LOSING option's override (?load=plain, ?stat=plain,
   ?cta=shift). What nobody voted in — the progress curve, the cursor trail
   — stays gated the other way round and renders for no one unless a link
   asks for it. See DESIGN.md → "Live-preview query params". */

/* Arriving on the site: the brackets snap in, the mark assembles, the overlay
   fades through to the page — about 1.85s end to end. Gated on nothing, so
   it is on screen at first paint with no JS to wait for, and lsOut ends in
   visibility:hidden rather than mere transparency. aria-hidden and
   pointer-events:none throughout: it never takes a click or reaches a screen
   reader. Calm motion drops it — a full-screen overlay is precisely the
   large-travel choreography that policy is about. */
.load-sting {
	display: flex; position: fixed; inset: 0; z-index: var(--z-modal);
	align-items: center; justify-content: center; gap: 18px;
	background: var(--bg); pointer-events: none;
	animation: lsOut 1.1s ease 0.75s forwards;
}
html[data-pv-load="plain"] .load-sting { display: none; }
.load-sting i { display: block; width: 16px; height: 64px; border: 4px solid var(--brand); }
.load-sting i:first-child { border-right: 0; animation: lsL 0.4s var(--ease-out-expo) backwards; }
.load-sting i:last-child { border-left: 0; animation: lsR 0.4s var(--ease-out-expo) backwards; }
.load-sting b { font-weight: 900; font-size: 1.8rem; letter-spacing: 0.12em; color: var(--ink); animation: lsB 0.45s ease 0.18s backwards; }
@keyframes lsL { from { transform: translateX(-44px); opacity: 0; } }
@keyframes lsR { from { transform: translateX(44px); opacity: 0; } }
@keyframes lsB { from { opacity: 0; } }
@keyframes lsOut { to { opacity: 0; visibility: hidden; } }

/* The 80× bar race: scalar vs vectorised, filling on scroll-in.
   Injected by app.js unless ?stat=plain. */
.pv-race { display: flex; flex-direction: column; gap: 8px; margin: 0.9rem 0 0.4rem; max-width: 21rem; }
.pv-race__track { display: block; height: 8px; border-radius: 4px; background: oklch(0.24 0.015 55); overflow: hidden; }
.pv-race__fill { display: block; height: 100%; width: 100%; transform: scaleX(0); transform-origin: left; }
.pv-race__fill--scalar { background: oklch(0.42 0.03 55); }
.pv-race__fill--vector { background: var(--brand); }
.pv-race.is-run .pv-race__fill--scalar { transform: scaleX(0.0125); transition: transform 2s linear; }
.pv-race.is-run .pv-race__fill--vector { transform: scaleX(1); transition: transform 2s var(--ease-out-quart); }

/* progress=curve — the P&L curve drawn along the top edge by scroll position */
/* Anchored to the BOTTOM edge. A distribution rises from its baseline, so the
   baseline has to be an edge — at the top it either hung upside down or left
   the rule floating over the nav. The bottom edge is empty chrome-wise, so the
   curve reads as a quiet swell rising out of it and competes with nothing. */
.pv-progress { position: fixed; inset: auto 0 0 0; height: 30px; z-index: var(--z-modal); pointer-events: none; }
.pv-progress svg { display: block; width: 100%; height: 100%; }
.pv-progress path { stroke: var(--brand); stroke-width: 2; filter: drop-shadow(0 0 6px oklch(from var(--brand) l c h / 0.6)); }

/* cursor=trail — fading matrix cells in the pointer's wake */
.pv-trail { position: fixed; inset: 0; z-index: var(--z-modal); pointer-events: none; }
/* Small, soft and overlapping: cells are laid every 8px but drawn 11px wide, so
   consecutive ones merge into a continuous wake instead of reading as a dotted
   line. --s is set per cell by app.js to taper the tail. The longer, eased fade
   is what makes it feel like a trail rather than a row of blocks blinking out. */
.pv-trail__cell {
	position: absolute; width: 11px; height: 11px; margin: -5.5px 0 0 -5.5px;
	border-radius: 3px; background: var(--brand);
	animation: pvcell 1.05s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
}
@keyframes pvcell {
	0%   { opacity: 0.62; transform: scale(calc(var(--s, 1) * 1)); }
	35%  { opacity: 0.34; }
	100% { opacity: 0; transform: scale(calc(var(--s, 1) * 0.35)); }
}

/* The primary button "computes" on hover: a ring, then a scatter of results.
   Shipped from the survey, which took it 7–1 over the plain colour shift.
   Both effects are pseudo-elements on the button itself, so there is no extra
   markup and nothing to clean up; ?cta=shift and calm motion stop the
   animations while leaving the hover's own colour and lift alone. */
.btn--primary { position: relative; }
.btn--primary::after {
	content: ""; position: absolute; inset: -4px; border-radius: inherit;
	border: 1px solid var(--brand-bright); opacity: 0; pointer-events: none;
}
.btn--primary:hover::after { animation: pvring 0.55s ease-out; }
@keyframes pvring { 0% { opacity: 0.9; transform: scale(0.85); } 100% { opacity: 0; transform: scale(1.45); } }
.btn--primary::before {
	content: ""; position: absolute; top: 50%; left: 50%; width: 4px; height: 4px;
	border-radius: 1px; background: transparent; opacity: 0; pointer-events: none;
}
.btn--primary:hover::before { animation: pvscatter 0.6s ease-out; }
/* ?cta=shift — the plain hover this was surveyed against. */
html[data-pv-cta="shift"] .btn--primary:hover::after,
html[data-pv-cta="shift"] .btn--primary:hover::before { animation: none; }
@keyframes pvscatter {
	0% { opacity: 0.95; background: var(--brand-bright); box-shadow: 0 0 0 0 var(--brand-bright), 0 0 0 0 var(--brand-bright), 0 0 0 0 var(--brand-bright), 0 0 0 0 var(--brand-bright), 0 0 0 0 var(--brand-bright), 0 0 0 0 var(--brand-bright); }
	100% { opacity: 0; background: transparent; box-shadow: 34px -22px 0 0 var(--brand-bright), -30px -26px 0 0 var(--brand-bright), 44px 10px 0 0 var(--brand-bright), -42px 14px 0 0 var(--brand-bright), 14px -34px 0 0 var(--brand-bright), -12px 32px 0 0 var(--brand-bright); }
}

.panel__figure--art img { display: block; width: 100%; height: auto; }

/* ---- Per-model instrument diagram (the "detail" mode) -------------------- *
   Each model panel carries a bespoke figure of that calculation's actual
   mechanic, drawn to the regulation. These are INLINE SVG rather than an image
   reference: an externally-referenced SVG is its own document and can see
   neither the page's mono face nor the brand tokens, so labels would fall back
   to system mono and the orange would be hard-coded. Inline = one source.
   Geometry lives in the markup; every fill, weight and size lives here.        */
.panel__figure {
	margin-top: 1.8rem; border-radius: var(--r-md); overflow: hidden;
	border: 1px solid var(--line); background: oklch(0.128 0.008 55);
}
/* min-width:0 all the way down: a grid item defaults to min-width:auto, which
   would let the diagram's floor width stretch the whole panel (and the caption)
   past the viewport instead of scrolling inside the figure. */
.explorer__panels, .panel, .panel__figure { min-width: 0; }
.viz-scroll { overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain; max-width: 100%; }
/* The scroller takes tabindex="0" in model-details.html so it can be panned by
   keyboard - a scroll container is not focusable by default, so arrow keys have
   nothing to act on and the clipped right-hand side of every figure is
   unreachable without a mouse. Focus has to be visible for that to be usable. */
.viz-scroll:focus-visible { outline: 2px solid var(--brand-bright); outline-offset: 3px; }
.viz { display: block; width: 100%; height: auto; }
/* Sizes are in viewBox units. The figures are authored 900 wide and render at
   roughly 810-900 CSS px, i.e. a scale of ~0.9-1.0, so every size here is set
   about 15% above its target CSS size to stay legible at the smaller end. */
.viz text { font-family: var(--font-mono); fill: var(--ink-dim); font-size: 14px; }
/* uppercase written literally in the markup — text-transform is unreliable on SVG text */
.viz .t-hd { font-size: 14px; letter-spacing: 0.13em; fill: var(--ink-dim); }
.viz .t-cite { font-size: 12.5px; letter-spacing: 0.04em; fill: var(--ink-faint); }
.viz .t-lab { font-size: 13.5px; fill: var(--ink-dim); }
.viz .t-sm { font-size: 12.5px; fill: var(--ink-faint); }
.viz .t-xs { font-size: 12px; fill: var(--ink-faint); }
.viz .t-val { font-size: 14.5px; font-weight: 600; fill: var(--brand-bright); }
.viz .t-key { font-size: 14px; font-weight: 600; fill: var(--ink); }
.viz .t-mid { fill: var(--ink); }
/* rules, axes, plot furniture */
.viz .ax { stroke: var(--line); stroke-width: 1; fill: none; }
.viz .gr { stroke: oklch(1 0 0 / 0.055); stroke-width: 1; fill: none; }
.viz .rule { stroke: var(--line-soft); stroke-width: 1; }
/* data marks */
.viz .bar { fill: var(--brand); }
.viz .bar-2 { fill: oklch(0.678 0.187 43 / 0.34); }
.viz .bar-3 { fill: oklch(0.678 0.187 43 / 0.14); }
.viz .cell { fill: oklch(0.678 0.187 43 / 0.10); stroke: oklch(0.678 0.187 43 / 0.30); stroke-width: 1; }
.viz .cell-on { fill: oklch(0.678 0.187 43 / 0.42); stroke: var(--brand); stroke-width: 1; }
.viz .ln { fill: none; stroke: var(--brand); stroke-width: 2.5; stroke-linejoin: round; stroke-linecap: round; }
.viz .ln-2 { fill: none; stroke: var(--brand-bright); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.viz .ln-3 { fill: none; stroke: var(--brand-bright); stroke-width: 2; stroke-dasharray: 6 4; stroke-linecap: round; }
.viz .ln-dash { fill: none; stroke: var(--ink-faint); stroke-width: 1; stroke-dasharray: 3 4; }
.viz .fillA { fill: oklch(0.678 0.187 43 / 0.16); }
.viz .dot { fill: var(--brand-bright); }
.viz .arrow { stroke: var(--ink-faint); stroke-width: 1.2; fill: none; }
.viz .box { fill: none; stroke: var(--line); stroke-width: 1; }
.viz .box-on { fill: oklch(0.678 0.187 43 / 0.07); stroke: var(--brand); stroke-width: 1.4; }

/* The FRTB PLA traffic light is the ONE place this orange-only palette earns a
   second and third hue: "green zone / amber zone / red zone" is the regulator's
   own terminology (MAR32.42), not decoration. Desaturated to sit in the dark
   surface, and every zone is also named in text so colour is never the sole
   carrier of meaning. Used nowhere else. */
.viz .z-green { fill: oklch(0.62 0.11 152 / 0.20); stroke: oklch(0.74 0.13 152 / 0.65); stroke-width: 1; }
.viz .z-amber { fill: oklch(0.678 0.187 43 / 0.14); stroke: oklch(0.72 0.16 50 / 0.5); stroke-width: 1; }
.viz .z-red { fill: oklch(0.55 0.17 25 / 0.22); stroke: oklch(0.66 0.19 25 / 0.6); stroke-width: 1; }
.viz .t-green { fill: oklch(0.80 0.13 152); font-size: 12.5px; font-weight: 600; }
.viz .t-amber { fill: var(--brand-bright); font-size: 12.5px; font-weight: 600; }
.viz .t-red { fill: oklch(0.72 0.17 27); font-size: 12.5px; font-weight: 600; }

/* The caption is not decoration — it carries the formula or definition the
   diagram draws, so it is real readable text, not a whisper. */
.panel__figcap {
	font-family: var(--font-mono); font-size: 0.78rem; line-height: 1.65;
	color: var(--ink-dim); padding: 0.75rem 0.95rem;
	border-top: 1px solid var(--line-soft); background: var(--surface);
	margin: 0;
}
.panel__figcap b { color: var(--brand-bright); font-weight: 600; }
.panel__figcap i { color: var(--ink-faint); font-style: normal; }

.panel__tags { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 1.5rem; }
.tag {
	font-family: var(--font-mono);
	font-size: 0.72rem;
	letter-spacing: 0.04em;
	color: var(--ink-dim);
	padding: 0.35em 0.7em;
	border: 1px solid var(--line);
	border-radius: 2em;
}

/* When JS is off, show every panel stacked with its heading */
html:not(.js) .explorer__list { display: none; }
html:not(.js) .panel { display: block; margin-bottom: 3rem; }
html:not(.js) .explorer { grid-template-columns: 1fr; }

/* ---------- Feature pillars (alternating) --------------------------------- */
.features { background: var(--bg-2); }
.pillar {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: clamp(1.5rem, 1rem + 3vw, 4.5rem);
	align-items: center;
	padding-block: clamp(2.5rem, 1.5rem + 3vw, 4.5rem);
}
.pillar + .pillar { border-top: 1px solid var(--line-soft); }
.pillar:nth-child(even) .pillar__media { order: 2; }
.pillar__media { position: relative; border-radius: var(--r-lg); overflow: hidden; border: 1px solid var(--line); box-shadow: var(--shadow-2); }
.pillar__media img { width: 100%; aspect-ratio: 3 / 2; object-fit: cover; }
.pillar__body h3 { font-size: var(--step-2); margin: 0.7rem 0 1rem; }
.pillar__body p { color: var(--ink-dim); margin-bottom: 1rem; }
.pillar__body p:last-child { margin-bottom: 0; }

.bignum { display: flex; align-items: baseline; gap: 0.6rem; margin-bottom: 0.6rem; }
.bignum b { font-family: var(--font-mono); font-weight: 700; font-size: clamp(3rem, 2rem + 5vw, 5rem); color: var(--brand); line-height: 1; letter-spacing: -0.04em; }
.bignum span { font-size: var(--step-0); color: var(--ink-dim); max-width: 12ch; }

/* ---------- Stats band ---------------------------------------------------- */
.stats { background: var(--bg); }
.stats__grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px; background: var(--line-soft); border: 1px solid var(--line-soft); border-radius: var(--r-lg); overflow: hidden; }
.stat { background: var(--bg); padding: clamp(1.5rem, 1rem + 2vw, 2.75rem); display: flex; flex-direction: column; gap: 0.5rem; }
.stat dt { font-family: var(--font-mono); font-weight: 700; font-size: clamp(2rem, 1.4rem + 2.4vw, 3.2rem); color: var(--ink); letter-spacing: -0.03em; line-height: 1; }
.stat dt b { color: var(--brand); font-weight: 700; }
.stat dd { color: var(--ink-dim); font-size: 0.95rem; line-height: 1.4; }

/* ---------- Contact / footer --------------------------------------------- */
.contact { background: var(--bg-2); }
.contact__grid { display: grid; grid-template-columns: 1.1fr 0.9fr; gap: clamp(1.5rem, 1rem + 3vw, 4rem); align-items: center; }
.contact__media { border-radius: var(--r-lg); overflow: hidden; border: 1px solid var(--line); box-shadow: var(--shadow-2); }
.contact__media img { width: 100%; aspect-ratio: 3 / 2; object-fit: cover; }
.contact__body h2 { margin-bottom: 0.5rem; }
.contact__body > p { color: var(--ink-dim); font-size: var(--step-1); max-width: 34ch; margin-bottom: 2rem; }
.contact__details { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: 1.5rem 2rem; margin-bottom: 2rem; }
.contact__item h3 { font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--brand); font-weight: 500; margin-bottom: 0.5rem; }
/* A second label in the same cell (Email, then Phone) needs separating from the
   line above it. This was an inline style attribute; it moved here so the CSP
   can forbid inline CSS outright rather than allow it for one margin. */
.contact__item h3:not(:first-of-type) { margin-top: 1.2rem; }
.contact__item p { color: var(--ink); line-height: 1.55; }
.contact__item a { color: var(--ink); display: inline-flex; align-items: center; min-height: 44px; }
.contact__item a:hover { color: var(--brand-bright); }

.footer { border-top: 1px solid var(--line-soft); background: var(--bg); }
.footer__inner { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 1.5rem; padding-block: 2.5rem; }
.footer__brand { display: flex; align-items: center; gap: 1.1rem; }
/* The official lockup, at a size it can actually be read at. It is a portrait
   logo - brackets around `vector risk` stacked two letters to a row, 351x594 -
   so height is what governs it and 2rem left each letter about three pixels
   wide. 4rem gives roughly 38px across, which holds the letterforms.

   `width: auto` is load-bearing, not tidying: the <img> carries width/height
   attributes so the box is reserved before the file arrives, and an attribute
   width is a presentational hint that applies whenever no author rule sets
   width. Setting the height alone left width pinned at the attribute's value
   and stretched the mark flat. Pair them, as `.nav__brand img` does — any rule
   that sizes an <img> on one axis needs the other set to auto. */
.footer__brand img { height: 4rem; width: auto; }
.footer__brand small { color: var(--ink-faint); font-size: 0.8rem; }
.footer__links { display: flex; flex-wrap: wrap; gap: 1.5rem; list-style: none; }
.footer__links a { color: var(--ink-dim); font-size: 0.85rem; transition: color var(--dur-1) ease; display: inline-flex; align-items: center; min-height: 44px; }
.footer__links a:hover { color: var(--brand-bright); }

/* ---------- Scroll reveals (progressive enhancement) ---------------------- */
/* Default = fully visible. Initial hidden state applies ONLY when JS is on. */
.js [data-reveal] { opacity: 0; transform: translateY(22px); transition: opacity 0.8s var(--ease-out-quart), transform 0.8s var(--ease-out-expo); }
.js [data-reveal].is-in { opacity: 1; transform: none; }
.js [data-reveal-stagger] > * { opacity: 0; transform: translateY(18px); transition: opacity 0.7s var(--ease-out-quart), transform 0.7s var(--ease-out-expo); }
.js [data-reveal-stagger].is-in > * { opacity: 1; transform: none; }
/* Failsafe: if app.js never runs, nothing ships hidden (see head-script timeout). */
.reveals-fallback [data-reveal], .reveals-fallback [data-reveal-stagger] > * { opacity: 1 !important; transform: none !important; }

/* The hero is the exception to the rule above, and has to be.

   Everything else with [data-reveal] is below the fold, so gating it on the
   observer costs nothing - it is offscreen anyway. The hero is not: its kicker,
   h1, sub, actions and spec strip are the first thing on screen, and the h1 is
   almost certainly the LCP element (canvases are not LCP candidates, and an
   element at opacity 0 is excluded from LCP entirely). Gating it on JS means
   the headline waits for motion.js + hero.js + app.js - about 48 KB, run in
   series after parse - before it can be painted at all. On a slow corporate
   proxy that is a visibly blank hero, bounded only by the 2.5s failsafe.

   So the hero plays the same entrance from CSS, starting at first paint with no
   JS involved. app.js still adds .is-in when its observer runs; the animation's
   `both` fill and the .is-in rule agree on the end state, so whichever lands
   first the hero settles once and never moves twice. */
@keyframes hero-reveal { from { opacity: 0; transform: translateY(22px); } to { opacity: 1; transform: none; } }
@keyframes hero-reveal-calm { from { opacity: 0; } to { opacity: 1; } }
.js .hero [data-reveal] { animation: hero-reveal 0.8s var(--ease-out-quart) both; }
/* calm drops the travel but keeps the fade, matching the reveal rules below. */
html[data-motion="calm"].js .hero [data-reveal] { animation-name: hero-reveal-calm; }


/* ---------- Legal pages ---------------------------------------------------
   legal.html is the one long-form reading page on the site: ~3,200 words of
   policy that someone may actually have to work through, rather than marketing
   copy they scan. So it optimises for reading, not for impact - a narrow
   measure, generous leading, and headings that are obviously navigable rather
   than large. It shares every token, the nav and the footer with index.html;
   nothing here is a second design system.

   It used to be an HTML5UP template carrying jQuery, dropotron, Font Awesome
   and a second stylesheet, none of which the rest of the site needs. */

.legal__head { padding-block: clamp(7rem, 5rem + 8vw, 11rem) 0; }
.legal__head h1 { font-size: var(--step-4); letter-spacing: -0.035em; }
.legal__lead {
	font-size: var(--step-1);
	color: var(--ink-dim);
	line-height: 1.45;
	max-width: 42ch;
	margin-top: 1.1rem;
}

/* Jump list. Mono and rule-separated so it reads as apparatus, not as prose. */
.legal__toc {
	display: flex;
	flex-wrap: wrap;
	list-style: none;
	padding: 0;
	gap: 0.6rem 1.8rem;
	margin-top: 2.2rem;
	padding-top: 1.4rem;
	border-top: 1px solid var(--line-soft);
	font-family: var(--font-mono);
	font-size: var(--step--1);
}
.legal__toc a { color: var(--ink-dim); text-decoration: none; }
.legal__toc a:hover, .legal__toc a:focus-visible { color: var(--brand-bright); }

.legal { padding-block: clamp(3rem, 2rem + 4vw, 5rem); }
/* 68 characters is the readable measure for body text; the policies are long
   enough that a full-width line would genuinely cost comprehension.
   It constrains the CHILDREN, not .legal__body itself: that element also
   carries .container, so a max-width on it would beat .container's 78rem and
   then be centred by its margin-inline:auto — which pushed the policy text
   230px right of the page heading above it. */
.legal__body > * { max-width: 68ch; }
/* The contact grid is layout, not prose, and wants the full column. */
.legal__contact { max-width: none; }
.legal__body h2 {
	font-size: var(--step-3);
	letter-spacing: -0.03em;
	padding-bottom: 0.5em;
	border-bottom: 1px solid var(--line);
	margin-bottom: 1.6rem;
}
/* Sub-headings are set in mono rather than at display size: at this density a
   large heading every few paragraphs reads as noise, while the mono face marks
   a boundary without shouting. */
.legal__body h3 {
	font-family: var(--font-mono);
	font-size: var(--step-0);
	font-weight: 600;
	color: var(--brand-bright);
	letter-spacing: -0.01em;
	margin-top: 2.6rem;
	margin-bottom: 0.7rem;
}
.legal__body h3:first-of-type { margin-top: 1.8rem; }
.legal__body p {
	color: var(--ink-dim);
	line-height: 1.65;
	margin-bottom: 1rem;
}
.legal__body ul {
	list-style: none;
	margin: 0 0 1.4rem;
	padding: 0;
}
.legal__body li {
	position: relative;
	color: var(--ink-dim);
	line-height: 1.6;
	padding-left: 1.4rem;
	margin-bottom: 0.5rem;
}
/* An em-dash marker rather than a disc: it matches the mono/rule register the
   rest of the page uses and does not add a second bullet shape to the site. */
.legal__body li::before {
	content: "";
	position: absolute;
	left: 0;
	top: 0.75em;
	width: 0.65rem;
	height: 1px;
	background: var(--line);
}
.legal__body strong { color: var(--ink); font-weight: 600; }
/* Underlined, not just tinted: the site-wide reset strips underlines, and in a
   wall of policy prose colour alone is the only thing marking a link - which
   fails WCAG 1.4.1 and is invisible to a colour-blind reader. */
.legal__body a { color: var(--brand-bright); text-decoration: underline; text-underline-offset: 0.2em; }

/* Contact block: the office photo beside the details, collapsing to one column
   when there is no room for two. */
.legal__contact {
	display: grid;
	grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
	gap: clamp(1.5rem, 1rem + 2vw, 3rem);
	align-items: start;
	margin-top: 2rem;
}
.legal__contact img {
	display: block;
	width: 100%;
	height: auto;
	/* The same photo on index.html sits in .contact__media, which sets a ratio.
	   Without one here the text below it jumps when the image lands. */
	aspect-ratio: 3 / 2;
	object-fit: cover;
	border-radius: var(--r-sm);
	border: 1px solid var(--line-soft);
}
.legal__details { margin: 0; }
.legal__details dt {
	font-family: var(--font-mono);
	font-size: var(--step--1);
	color: var(--ink-faint);
	text-transform: uppercase;
	letter-spacing: 0.1em;
	margin-bottom: 0.35rem;
}
.legal__details dd {
	margin: 0 0 1.5rem;
	color: var(--ink-dim);
	line-height: 1.6;
}
.legal__details dd a { color: var(--brand-bright); }

@media (max-width: 52rem) {
	.legal__contact { grid-template-columns: 1fr; }
}

/* ---------- Not found (404) ----------------------------------------------- *
 * Served by Azure for any address that does not resolve (responseOverrides in
 * staticwebapp.config.json). It reuses the legal page's shell - the same nav,
 * the same `.legal__head` block, the same footer - so this site has one
 * secondary-page layout rather than two, and a 404 never looks like it belongs
 * to a different website. The template's 404 was a 659-line copy of the
 * homepage that drifted out of step with it; this is the whole page. */
.notfound__code {
	font-family: var(--font-mono);
	font-size: var(--step-1);
	letter-spacing: 0.22em;
	color: var(--brand-bright);
}

.notfound__actions {
	display: flex;
	flex-wrap: wrap;
	gap: 0.9rem;
	margin-top: 2.4rem;
	/* The page is short, so the footer would otherwise ride up under the
	   buttons on a tall window. */
	padding-bottom: clamp(6rem, 4rem + 8vw, 10rem);
}

/* ---------- Responsive ---------------------------------------------------- */
@media (max-width: 60rem) {
	.explorer { grid-template-columns: 1fr; }
	.explorer__list { flex-direction: row; overflow-x: auto; position: static; padding-bottom: 0.6rem; gap: 0.4rem; scrollbar-width: thin; scroll-snap-type: x proximity; }
	.explorer__tab { flex-shrink: 0; flex-direction: column; align-items: flex-start; gap: 0.25rem; padding: 0.65rem 0.95rem; scroll-snap-align: start; }
	.explorer__tab .label { white-space: nowrap; font-size: 0.95rem; }
	.explorer__tab .code { width: auto; }
	.panel__body { columns: 1; }
	/* The model diagrams are dense and label-heavy. Letting them shrink with the
	   viewport would take the smallest labels to ~6px, so instead they hold a
	   legible floor and pan inside their own scroller — the page body never
	   scrolls sideways. The caption sits outside the scroller and wraps normally. */
	.viz { min-width: 720px; }
	.contact__grid { grid-template-columns: 1fr; }
	.contact__media { order: 2; }
}

@media (max-width: 52rem) {
	.nav__links { display: none; }
	.nav__brand-name { display: none; }              /* logo mark only — room for the persistent CTA */
	.nav__cta { padding: 0.6em 1.05em; }             /* keep the conversion CTA in the bar on mobile */
	.nav[data-menu-open="true"] .nav__links {
		display: flex; flex-direction: column; align-items: stretch;
		position: fixed; inset: 4.5rem var(--gutter) auto var(--gutter);
		background: var(--surface); border: 1px solid var(--line);
		border-radius: var(--r-md); padding: 0.75rem; gap: 0.4rem; box-shadow: var(--shadow-2);
	}
	.nav[data-menu-open="true"] .nav__menu { position: static; opacity: 1; visibility: visible; transform: none; box-shadow: none; border: 0; background: transparent; padding: 0.25rem 0 0.5rem 1rem; }
	.nav[data-menu-open="true"] .nav__links a, .nav[data-menu-open="true"] .nav__links button { min-height: 44px; padding-block: 0.7rem; }
	.nav__toggle { display: inline-flex; align-items: center; justify-content: center; width: 2.75rem; height: 2.75rem; border-radius: var(--r-sm); color: var(--ink); }
	.nav__toggle:hover { background: var(--surface); }
	.pillar { grid-template-columns: 1fr; }
	.pillar:nth-child(even) .pillar__media { order: 0; }
	.pillar__media { order: -1; }
	.stats__grid { grid-template-columns: repeat(2, 1fr); }
	.hero__legend { display: none; }
}

@media (max-width: 34rem) {
	.stats__grid { grid-template-columns: 1fr; }
	.hero__actions .btn { flex: 1 1 auto; justify-content: center; }

	/* Keeping the hero inside one phone screen. min-height:100svh is never
	   the binding constraint — the content is, and at 375–812 it ran 918px
	   against 812. Every value below was measured rather than guessed, and
	   the order is largest saving first:
	     headline  166 —> 100  (three lines instead of four: the statement's
	                            own clamp bottoms out at 2rem here, where
	                            2.1rem still wrapped to a fourth line)
	     sub copy  204 —> 144  (body size; --step-1 is ~19.5px, generous
	                            for 333px of measure)
	     spec strip 281 —> 153  (one row per figure, value and caption side
	                            by side, not a column whose uppercase caption
	                            wrapped and cost ~70px each)
	     padding    192 —> 136  (6rem top still clears the 78px fixed nav
	                            with room to spare)
	   Nothing here touches a wider viewport: the query is the phone break,
	   and the desktop hero is unchanged at every size above it. */
	.hero { padding-block: 6rem 2.5rem; }
	.hero h1 { margin-top: 0.8rem; }
	/* Drop the authored break between the statement's two sentences. With
	   333px of measure neither half fits a line anyway, so the break only
	   guarantees two wraps each — four lines, 133px. Reflowed as one block
	   it is three, 100px. (At 430 and 500px it makes no difference: by then
	   the second sentence fits a line of its own either way. This is a
	   375px-class win, not a general one.) The space before the <br> in
	   index.html is what keeps "futures." and "One" apart here. */
	.hero h1 br { display: none; }
	.hero__sub { font-size: 1rem; margin-top: 1.1rem; }
	.hero__actions { margin-top: 1.6rem; }
	.hero__spec { gap: 0.9rem 1.6rem; margin-top: 2rem; padding-top: 1.2rem; }
	/* Each figure on its own line, value and caption side by side, instead
	   of a stacked column whose uppercase caption wrapped to two lines and
	   cost ~70px per item. */
	.hero__spec div { flex: 1 1 100%; flex-direction: row; align-items: baseline; gap: 0.6rem; }
}

/* ---------- Short viewports, not narrow ones ------------------------------
   The 1366×768 laptop is the common one, and the hero's vertical rhythm is
   tuned for ~900px: at 768 it overflows and the spec strip goes under the
   fold. The statement headline is what exposed it — 116px over two lines
   against the wordmark's 57 on one — so the target here is that the headline
   costs nothing against the wordmark, which itself ran ~38px over at this
   height. Padding and the strip's lead-in give that up; the type does not,
   since shrinking it to suit a short window is what makes a hero look
   cramped. min-width keeps this clear of the phone block above, which owns
   everything below 34rem and sets its own padding. */
@media (min-width: 34rem) and (max-height: 850px) {
	.hero { padding-block: 6.5rem 3rem; }
	.hero__spec { margin-top: 2rem; padding-top: 1.2rem; }
}

/* ---------- Calm motion --------------------------------------------------- *
   Everything the motion policy drops, in one place. Keyed off <html
   data-motion="calm">, which index.html's head bootstrap sets before first
   paint. Only two things select it: a choice the visitor made with the footer
   control (remembered per browser) and ?motion=calm. The OS preference is no
   longer one of them - the page animates by default everywhere.

   This block used to be `@media (prefers-reduced-motion: reduce)` containing a
   blanket `animation-duration: 0.001ms !important` on `*`, which killed every
   animation AND every transition on the page — hover states, the tab explorer,
   the mobile menu included. Combined with the same media query appearing in
   sixteen other files, any machine with Windows' animation effects switched off
   got a completely dead site. The preference does not ask for that; it asks for
   the vestibular triggers to go. So what is dropped here is scroll-coupled
   motion, continuous travel, and large translations. What stays is opacity,
   colour, count-ups, and the contained instrument loops.
   See DESIGN.md -> "Motion policy".                                           */
/* Flat selectors, no CSS nesting — this file uses none, and a change whose
   whole purpose is working on older machines is the wrong place to start.
   Note `.js` compounds rather than descends: it is set on <html> itself. */

/* Continuous horizontal travel with no rest state: the clearest trigger on the
   page, and nothing is lost by stopping it — the logos stay readable. */
html[data-motion="calm"] .band__track { animation: none; }

/* Reveals keep the fade, lose the 22px rise. The fade is what makes the page
   feel composed; the travel is the part that provokes. */
html[data-motion="calm"].js [data-reveal],
html[data-motion="calm"].js [data-reveal-stagger] > * { transform: none; }
html[data-motion="calm"].js [data-reveal] { transition: opacity 0.5s var(--ease-out-quart); }
html[data-motion="calm"].js [data-reveal-stagger] > * { transition: opacity 0.45s var(--ease-out-quart); }

/* Preview-lab variants that are pure motion. */
html[data-motion="calm"] .pv-trail { display: none; }
html[data-motion="calm"] .pv-race__fill,
html[data-motion="calm"] .pv-race.is-run .pv-race__fill--scalar,
html[data-motion="calm"] .pv-race.is-run .pv-race__fill--vector { transition: none; }
html[data-motion="calm"] .load-sting { display: none; }
html[data-motion="calm"] .btn--primary:hover::before,
html[data-motion="calm"] .btn--primary:hover::after { animation: none; }

/* ---- The footer motion control (added by motion.js) --------------------- */
.motion-toggle {
	font: inherit; color: inherit; background: none; border: 0; padding: 0;
	cursor: pointer; text-align: left;
	font-family: var(--font-mono); font-size: 0.78rem;
	letter-spacing: 0.02em; opacity: 0.72;
	transition: opacity 0.2s var(--ease-out-quart), color 0.2s var(--ease-out-quart);
}
.motion-toggle:hover, .motion-toggle:focus-visible { opacity: 1; color: var(--brand); }

/* ============================================================================
   Second signature moment — the Onboarding scene. A graphite MacBook opens onto
   a rotating hub globe that then grows out, engulfs the machine, and becomes a
   fully interactive orbit-able globe. Sticky-pinned; all choreography in
   globe.js is a pure function of scroll, so it is fully reversible.
   ========================================================================== */

.sr-only {
	position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
	overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Keep the features section always painted — content-visibility:auto would
   de-stick the pinned scene and nudge scroll position. */
.features { content-visibility: visible; contain-intrinsic-size: auto; }

/* ---- Static presentation = the AUTHORED state --------------------------- *
   The Onboarding section is authored as an ordinary pillar: art-onboarding.svg
   beside the same copy, matching the Security pillar next to it. globe.js adds
   .is-live to upgrade it into the pinned scene, so the static presentation is
   what you get for free from no-JS, reduced-motion, or the one-attribute swap
   data-globe-section="off". Nothing is duplicated: one set of prose, one image,
   two layouts. See DESIGN.md → "Removing the globe".
   Keep every scene-only rule below qualified with .is-live.                   */
.globe-scene { position: relative; }
.globe-scene:not(.is-live) { border-top: 1px solid var(--line-soft); }
.globe-scene:not(.is-live) .globe-sticky {
	display: grid; grid-template-columns: 1fr 1fr;
	gap: clamp(1.5rem, 1rem + 3vw, 4.5rem);
	align-items: center;
	padding-block: clamp(2.5rem, 1.5rem + 3vw, 4.5rem);
	min-height: 0;
}
.globe-scene:not(.is-live) .globe-caption {
	position: static; max-width: none; opacity: 1; transition: none;
	/* the scene sets pointer-events:none so drags pass through to the globe; as a
	   plain pillar the copy must be selectable again */
	pointer-events: auto;
}
.globe-scene:not(.is-live) .globe-caption__body { max-width: none; }
.globe-scene:not(.is-live) .globe-stage {
	position: relative; inset: auto; perspective: none; touch-action: auto;
	border-radius: var(--r-lg); overflow: hidden;
	border: 1px solid var(--line); box-shadow: var(--shadow-2);
}
.globe-scene:not(.is-live) .stage__fallback {
	position: static; inset: auto; margin: 0;
	width: 100%; max-width: none; aspect-ratio: 3 / 2; object-fit: cover;
	border: 0; border-radius: 0; box-shadow: none;
}
/* Globe-only chrome must be REMOVED, not just transparent: at opacity 0 the zoom
   buttons stay keyboard-focusable (advertising controls that do nothing) and the
   cue keeps animating forever. */
.globe-scene:not(.is-live) .globe-ui,
.globe-scene:not(.is-live) .globe-word,
.globe-scene:not(.is-live) .globe-cue { display: none; }
@media (max-width: 52rem) {
	/* stack like .pillar does — image above the copy */
	.globe-scene:not(.is-live) .globe-sticky { grid-template-columns: 1fr; }
	.globe-scene:not(.is-live) .globe-stage { order: -1; }
}

/* ---- Scene / sticky pin ------------------------------------------------- */
/* The tall scroll-track (and the pin) exist ONLY when JS has upgraded the
   section — no-JS / reduced-motion / swapped-out keep the pillar, no scroll-jack. */
.globe-sticky { position: relative; min-height: clamp(20rem, 26vw, 30rem); }
html:not([data-motion="calm"]) .globe-scene.is-live { min-height: 230vh; }
html:not([data-motion="calm"]) .globe-scene.is-live .globe-sticky {
	position: sticky; top: 0; height: 100vh; height: 100svh;
	min-height: min(34rem, 100svh); overflow: clip; contain: layout paint;
}
@media (pointer: coarse) {
	html:not([data-motion="calm"]) .globe-scene.is-live { min-height: 200vh; }
}

/* ---- Caption (pillar copy overlay; demotes as the globe engulfs) -------- */
.globe-caption {
	position: absolute; z-index: 4;
	top: clamp(5.5rem, 13vh, 8rem); left: 0;
	max-width: 32rem; pointer-events: none;
	opacity: var(--cap, 1); transition: opacity 0.25s linear;
}
.globe-caption h3 { font-size: var(--step-2); margin: 0.4rem 0 0.9rem; }
.globe-caption__body { color: var(--ink-dim); margin-bottom: 1rem; max-width: 32ch; }
.globe-caption__kicker {
	font-size: 0.72rem; letter-spacing: 0.12em; text-transform: uppercase;
	color: var(--brand); font-weight: 500;
}

/* ---- Stage (the perspective scene + pointer/drag surface) -------------- */
.globe-stage {
	position: absolute; inset: 0; z-index: 1;
	perspective: 1500px; perspective-origin: 50% 42%;
	touch-action: pan-y; outline: none;
}
.globe-stage:focus-visible { outline: 2px solid var(--brand-bright); outline-offset: -6px; border-radius: var(--r-lg); }
.globe-stage.is-grabbing { cursor: grabbing; }

.stage__fallback {
	position: absolute; inset: 0; margin: auto;
	max-width: min(28rem, 74%); height: auto;
	border-radius: var(--r-lg); border: 1px solid var(--line); box-shadow: var(--shadow-2);
}
.globe-scene.is-live .stage__fallback { display: none; }

.stage__canvas {
	position: absolute; inset: 0; z-index: 2; display: none;
	opacity: 0; pointer-events: none;
}
.globe-scene.is-live .stage__canvas { display: block; }

/* ---- Globe controls ---------------------------------------------------- */
.globe-ui {
	position: absolute; z-index: 5;
	right: clamp(1rem, 3vw, 2.5rem); bottom: clamp(1.25rem, 5vh, 3rem);
	display: flex; flex-direction: column; align-items: flex-end; gap: 0.5rem;
	opacity: var(--uiOn, 0); transition: opacity 0.3s ease;
}
.globe-ui__hint {
	order: -1; margin-bottom: 0.15rem;
	color: var(--ink-dim); font-size: 0.72rem; letter-spacing: 0.04em;
	padding: 0.22rem 0.55rem; border-radius: var(--r-sm);
	background: oklch(0.15 0.008 55 / 0.55); backdrop-filter: blur(6px);
	text-shadow: 0 1px 2px rgba(0, 0, 0, 0.65);
}
.globe-ui__btn {
	width: 2.75rem; height: 2.75rem; border-radius: 50%;   /* 44px — touch-target floor */
	display: inline-flex; align-items: center; justify-content: center;
	font-family: var(--font-mono); font-size: 1.25rem; line-height: 1; font-weight: 600;
	color: var(--ink); background: oklch(0.205 0.011 55 / 0.72);
	border: 1px solid var(--line); backdrop-filter: blur(8px);
	transition: background var(--dur-1) ease, color var(--dur-1) ease, transform var(--dur-1) var(--ease-out-expo);
}
.globe-ui__btn:hover { background: var(--surface-2); color: var(--brand-bright); }
.globe-ui__btn:active { transform: scale(0.94); }
/* Keyboard focus ring — the buttons float over a variable-brightness globe, so a
   dark halo carries the orange ring on both dark and bright patches. */
.globe-ui__btn:focus-visible {
	outline: 2px solid var(--brand-bright); outline-offset: 2px;
	box-shadow: 0 0 0 5px oklch(0.15 0.008 55 / 0.6);
}
.globe-ui__btn--reset { font-size: 1.05rem; }

/* The payoff word — rises in as the globe reaches full size (a pure function of
   scroll, so it reverses cleanly). The detailed caption dissolves into one line. */
.globe-word {
	position: absolute; z-index: 4; left: 0; right: 0; margin: 0;
	top: clamp(5.25rem, 13vh, 8.5rem);
	text-align: center; pointer-events: none; text-wrap: balance;
	font-family: var(--font-display); font-weight: 900;
	font-size: var(--step-4); letter-spacing: -0.04em; line-height: 1;
	color: var(--ink);
	opacity: var(--wordOn, 0);
	transform: translateY(calc((1 - var(--wordOn, 0)) * 16px));
	text-shadow: 0 2px 20px rgba(0, 0, 0, 0.6);
}
.globe-word b { color: var(--brand); font-weight: 900; }

/* Scroll-on affordance at the interactive plateau — the globe deliberately
   stops well short of the frame, and this chevron says the page continues. */
.globe-cue {
	position: absolute; z-index: 5; left: 50%; bottom: 0.9rem;
	width: 2rem; height: 2rem; margin-left: -1rem;
	display: flex; align-items: center; justify-content: center;
	color: var(--ink-dim); opacity: var(--cueOn, 0); pointer-events: none;
	animation: cue-bob 1.8s ease-in-out infinite;
}
.globe-cue svg { width: 1.05rem; height: 1.05rem; }
@keyframes cue-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(7px); } }

/* ---- MacBook Pro 2026 — pure CSS 3D, space-black flat-edge aluminium ---- */
.globe-stage .laptop { display: none; }
.globe-scene.is-live .laptop { display: block; }
.laptop {
	position: absolute; inset: 0; margin: auto;
	width: min(30rem, 58%); aspect-ratio: 6 / 5;
	transform-style: preserve-3d;              /* let the stage perspective reach the scene */
	transform: translateZ(calc(var(--recede, 0) * -520px)) scale(calc(1 - var(--recede, 0) * 0.3));
	opacity: calc(1 - clamp(var(--recede, 0) / 0.5, 0, 1));   /* opacity < 1 flattens 3D — only during recede, where it's fine */
}
.laptop__scene {
	position: absolute; inset: 0;
	transform-style: preserve-3d;
	transform: rotateX(var(--view-tilt, 11deg)) rotateY(var(--scene-yaw, 10deg));
}

/* lid / screen — thin uniform bezel, rounded corners, notch */
.laptop__lid {
	position: absolute; left: 8%; right: 8%; top: 2%; height: 60%;
	transform-origin: 50% 100%;
	transform: rotateX(var(--lid-angle, -106deg));
	transform-style: preserve-3d;
	border-radius: 14px;
}
.laptop__screen, .laptop__lidback {
	position: absolute; inset: 0; border-radius: 14px;
	backface-visibility: hidden; overflow: hidden;
}
/* Slab thickness: the glass face sits +4.5px off the lid plane and the shell
   -4.5px behind it, with a slightly-larger dark core plane between them — from
   any angle the 9px sandwich and its edge ring read as a real aluminium slab. */
.laptop__lid::before {
	content: ""; position: absolute; inset: -2px; border-radius: 16px;
	background: #101113;
	box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.07);
}
.laptop__screen {
	background: #0a0a0b; padding: 3.2%;
	transform: translateZ(4.5px);
	box-shadow:
		inset 0 0 0 1px rgba(255, 255, 255, 0.05),
		inset 0 1px 0 rgba(255, 255, 255, 0.10),
		0 0 calc(var(--screen-lum, 0) * 2.4rem) rgba(242, 103, 34, calc(var(--screen-lum, 0) * 0.28));
}
.laptop__globe { width: 100%; height: 100%; display: block; border-radius: 9px; background: #050403; opacity: var(--lid-alpha, 1); }
.laptop__notch {
	position: absolute; top: 0; left: 50%; transform: translateX(-50%);
	width: 16%; height: 3.4%; background: #050506; border-radius: 0 0 7px 7px; z-index: 2;
	display: flex; align-items: center; justify-content: center;
}
.laptop__cam { width: 5px; height: 5px; border-radius: 50%; background: radial-gradient(circle at 40% 35%, #33474a, #000); }
.laptop__gloss {
	position: absolute; inset: 0; pointer-events: none; z-index: 1;
	background: linear-gradient(118deg, rgba(255, 255, 255, 0.12) 0%, rgba(255, 255, 255, 0.03) 16%, transparent 40%);
	mix-blend-mode: screen; opacity: calc(0.3 + var(--screen-lum, 0) * 0.5);
}
.laptop__lidback {
	transform: rotateY(180deg) translateZ(4.5px);
	background: linear-gradient(158deg, #3a3a3d 0%, #2b2b2e 42%, #202023 72%, #161618 100%);
	box-shadow:
		inset 0 1px 0 rgba(255, 255, 255, 0.10), inset 0 0 0 1px rgba(0, 0, 0, 0.5),
		inset 2px 0 3px rgba(255, 255, 255, 0.04), inset -2px 0 3px rgba(0, 0, 0, 0.35);
	display: flex; align-items: center; justify-content: center;
}
.laptop__mark {
	transform: scaleX(-1);                 /* un-mirror on the flipped face */
	font-family: var(--font-mono); font-weight: 700;
	font-size: clamp(1.1rem, 0.8rem + 1.4vw, 1.8rem); letter-spacing: 0.1em;
	color: oklch(from var(--brand) l c h / 0.5);
}

/* base / deck — thin profile, big trackpad, speaker grilles */
.laptop__base {
	position: absolute; left: 8%; right: 8%; top: 63%; height: 30%;   /* same width as the lid — one unibody */
	transform-origin: 50% 0%;
	transform: rotateX(var(--base-angle, 74deg));
	transform-style: preserve-3d;
}
/* Base thickness: a near-vertical front lip hangs from the deck's leading edge,
   so the machine reads as a solid unibody wedge instead of a paper sheet. */
.laptop__base::after {
	content: ""; position: absolute; left: 1%; right: 1%; top: 100%; height: 10px;
	transform-origin: 50% 0%;
	transform: rotateX(-90deg);
	background: linear-gradient(180deg, #2c2e31 0%, #1a1b1e 45%, #0d0e10 100%);
	border-radius: 3px 3px 12px 12px;
	box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
.laptop__hinge { position: absolute; left: 10%; right: 10%; top: -2.4%; height: 5.5%; border-radius: 5px; background: linear-gradient(180deg, #0a0a0b, #26282b 45%, #0c0d0e); }
.laptop__deck {
	position: absolute; inset: 0; border-radius: 6px 6px 16px 16px;
	background: linear-gradient(180deg, #26282b 0%, #1c1d20 55%, #141517 100%);
	box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.09), inset 0 -2px 3px rgba(0, 0, 0, 0.55), inset 0 0 0 1px rgba(0, 0, 0, 0.4);
	overflow: hidden;
}
.laptop__keys {
	position: absolute; left: 9%; right: 9%; top: 10%; height: 52%; border-radius: 3px; opacity: 0.8;
	background:
		repeating-linear-gradient(90deg, rgba(0, 0, 0, 0.6) 0 5.2%, rgba(255, 255, 255, 0.03) 5.2% 6.4%),
		repeating-linear-gradient(180deg, rgba(0, 0, 0, 0.55) 0 15%, rgba(255, 255, 255, 0.03) 15% 20%);
}
.laptop__grille {
	position: absolute; top: 12%; width: 6%; height: 48%; opacity: 0.5;
	background: radial-gradient(circle, rgba(0, 0, 0, 0.7) 30%, transparent 32%) 0 0 / 3px 3px;
}
.laptop__grille--l { left: 2.5%; }
.laptop__grille--r { right: 2.5%; }
.laptop__trackpad {
	position: absolute; left: 27%; right: 27%; bottom: 6%; height: 36%; border-radius: 8px;
	border: 1px solid rgba(255, 255, 255, 0.06);
	background: linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(0, 0, 0, 0.16));
	box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.3);
}
.laptop__shadow {
	position: absolute; left: 6%; right: 6%; bottom: 1%; height: 12%;
	background: radial-gradient(50% 62% at 50% 50%, rgba(0, 0, 0, 0.6), transparent 72%);
	filter: blur(8px); transform: translateZ(-70px);
}

/* Flanking pillar (Security) mirrors its media to the opposite side. This holds
   in BOTH states: the globe sits right, and so does the static SVG. */
.globe-scene + .pillar .pillar__media { order: -1; }

/* Scene-only responsive tuning — qualified so it can never reach the static
   pillar, which follows the ordinary .pillar breakpoints instead. */
@media (max-width: 60rem) {
	.globe-scene.is-live .globe-caption { max-width: 24rem; }
	.globe-scene.is-live .laptop { width: min(26rem, 74%); }
}
@media (max-width: 52rem) {
	/* stack: caption at the top, laptop anchored to the lower area */
	.globe-scene.is-live .globe-caption { max-width: min(92%, 30rem); top: clamp(4.5rem, 9vh, 6rem); }
	.globe-scene.is-live .globe-caption__body { max-width: none; }
	.globe-scene.is-live .laptop { width: min(24rem, 84%); inset: auto 0 10% 0; margin-inline: auto; }
}


/* ---------- Print ---------------------------------------------------------- *
   legal.html is the one page here that gets printed: a privacy policy and an
   acceptable-use policy that a counterparty's compliance reader files or marks
   up on paper. Everything above is tuned for a near-black background, and
   Chrome, Edge and Firefox all default "Background graphics" to OFF - they drop
   the background and keep the foreground colour, so without this block the page
   prints near-white text on white paper. Not faint: blank.

   So print re-inks rather than restyles. Same content, same order, black on
   white, with the screen-only furniture removed.                              */
@media print {
	:root {
		--bg: #fff; --bg-2: #fff; --surface: #fff; --surface-2: #fff;
		--ink: #000; --ink-dim: #1a1a1a; --ink-faint: #4a4a4a;
		--line: #bbb; --line-soft: #ddd;
		--brand: #a03c00; --brand-bright: #a03c00; --on-brand: #000;
		--brand-tint: transparent; --glow: none;
	}
	html, body { background: #fff !important; color: #000 !important; }
	/* Screen furniture: navigation, the canvas hero, and the decorative scenes. */
	.nav, .footer, .skip-link, .hero__canvas, .hero__overlay, .hero__legend,
	.globe-scene, .load-sting, .legal__toc, .motion-toggle { display: none !important; }
	/* The 68ch measure is a screen reading aid; paper has its own column. */
	.legal__body > *, .container { max-width: none !important; }
	.legal__head { padding-block: 0 !important; }
	/* Nothing animates onto paper, and nothing may print while still invisible. */
	*, *::before, *::after { animation: none !important; transition: none !important; }
	[data-reveal], [data-reveal-stagger] > * { opacity: 1 !important; transform: none !important; }
	/* Keep headings with their text, and never split a policy clause. */
	h1, h2, h3 { break-after: avoid; color: #000 !important; }
	p, li { break-inside: avoid; }
	/* A printed policy loses its links unless the target is spelled out. */
	.legal__body a::after { content: " (" attr(href) ")"; font-size: 0.85em; color: #4a4a4a; }
	.legal__body a[href^="#"]::after { content: ""; }
}
