/* ==========================================================================
   100XD — motion.css

   The shared cinematic layer. Two halves:

     1. PRIMITIVES   Declarative reveal / depth / lighting behaviours that any
                     section opts into with a data attribute. Written once so
                     every section rebuilt from here on costs markup, not CSS.

     2. THE ACT      The first section rebuilt on those primitives — the 100XD
                     Model, as a dolly along a lit rail.

   Everything is driven by two custom properties that motion.js writes:

     --p     0 → 1   a section's own scroll progress
     --near  0 → 1   how close an element is to the camera (viewport centre)

   CSS consumes them; JS never touches style beyond those two numbers. That
   separation is what keeps the motion editable without reading the engine.
   ========================================================================== */

/* --------------------------------------------------------------------------
   0. Motion tokens
   -------------------------------------------------------------------------- */

:root {
	/* Camera */
	--cam-perspective: 1400px;
	--cam-lift:        26px;   /* how far a near element rises toward the lens */
	--cam-recede:      -90px;  /* how far a far element falls away in Z        */

	/* Lighting */
	--lit-key:    rgba(255, 255, 255, 0.92);
	--lit-fill:   rgba(26, 123, 255, 0.16);
	--lit-rim:    rgba(255, 193, 7, 0.55);
	--lit-ambient: rgba(10, 14, 26, 0.05);

	/* Cinematic timing — slower than UI easing, closer to a camera move */
	--ease-cam:  cubic-bezier(0.22, 1, 0.36, 1);
	--ease-lens: cubic-bezier(0.65, 0, 0.35, 1);
	--dur-cam:   0.9s;
}

/* --------------------------------------------------------------------------
   1. Reveal primitives

   Every element starts at a VISIBLE resting state and is *displaced* from it,
   never parked at opacity:0 waiting on an observer. If the engine never runs —
   no JS, blocked CDN, a crawler — the page still reads, because .m-ready is
   what arms the displacement and only JS adds it.
   -------------------------------------------------------------------------- */

.m-ready [data-m] {
	opacity: 0;
	will-change: transform, opacity;
	transition:
		opacity   var(--dur-cam) var(--ease-cam) var(--d, 0ms),
		transform var(--dur-cam) var(--ease-cam) var(--d, 0ms),
		filter    var(--dur-cam) var(--ease-cam) var(--d, 0ms),
		clip-path var(--dur-cam) var(--ease-cam) var(--d, 0ms);
}

/* rise — the default. A line lifting into its own mask. */
.m-ready [data-m="rise"]  { transform: translate3d(0, 34px, 0); }

/* lift — larger travel, for a whole block entering */
.m-ready [data-m="lift"]  { transform: translate3d(0, 64px, 0); }

/* clip — a wipe. Reads as a reveal rather than a fade. */
.m-ready [data-m="clip"]  { clip-path: inset(0 0 100% 0); opacity: 1; }

/* focus — a lens pull. The most cinematic of the four; use sparingly. */
.m-ready [data-m="focus"] { transform: scale(1.06); filter: blur(14px); }

/* dolly — the element arrives from depth, toward the camera. */
.m-ready [data-m="dolly"] {
	transform: perspective(var(--cam-perspective)) translate3d(0, 40px, -160px);
}

.m-ready [data-m].is-in {
	opacity: 1;
	transform: none;
	filter: none;
	clip-path: inset(0 0 0 0);
}

/* A mask wrapper, so a rising line is clipped by its own box rather than
   sliding over whatever sits above it.

   The bottom padding is in rem, not em: the mask inherits body font-size while
   the heading inside it runs up to 2.85rem, so an em value here is measured
   against the wrong type and clips the descenders of anything large. */
.m-mask { display: block; overflow: hidden; padding-block: 0.04rem 0.28rem; }

/* --------------------------------------------------------------------------
   2. Depth

   A [data-depth] child translates against its section's own progress. Rates
   are small on purpose: adjacent planes differing by 10–30% read as depth,
   while large differences read as elements sliding independently.
   -------------------------------------------------------------------------- */

[data-depth] { will-change: transform; }

/* --------------------------------------------------------------------------
   3. Lighting

   A pointer-tracked key light. motion.js writes --mx/--my as percentages of
   the element box; everything else is CSS, so the light costs one custom
   property write per frame and no layout.
   -------------------------------------------------------------------------- */

.lit { position: relative; isolation: isolate; }

.lit::before {
	content: "";
	position: absolute;
	inset: 0;
	z-index: 0;
	border-radius: inherit;
	background: radial-gradient(
		42% 62% at var(--mx, 50%) var(--my, 30%),
		var(--lit-key), transparent 72%);
	opacity: 0;
	transition: opacity 0.55s var(--ease-cam);
	pointer-events: none;
	mix-blend-mode: soft-light;
}

.lit:hover::before,
.lit:focus-within::before { opacity: 1; }

/* A rim light along the leading edge — the detail that makes a flat plate
   read as a lit object rather than a coloured rectangle. */
.rim { position: relative; }
.rim::after {
	content: "";
	position: absolute;
	inset: 0;
	z-index: 1;
	border-radius: inherit;
	padding: 1px;
	background: linear-gradient(
		150deg,
		var(--lit-key) 0%,
		transparent 38%,
		transparent 62%,
		var(--lit-rim) 100%);
	-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
	-webkit-mask-composite: xor;
	        mask-composite: exclude;
	opacity: 0.5;
	transition: opacity 0.5s var(--ease-cam);
	pointer-events: none;
}
.rim:hover::after { opacity: 1; }

/* --------------------------------------------------------------------------
   4. Reduced motion

   Not "animations off" — displacement off. The reveal states resolve
   instantly, the camera holds still, and every section renders at its
   finished composition.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
	.m-ready [data-m],
	.m-ready [data-m].is-in {
		opacity: 1 !important;
		transform: none !important;
		filter: none !important;
		clip-path: none !important;
		transition: none !important;
	}
	[data-depth] { transform: none !important; }
	.lit::before { display: none; }
}

/* ==========================================================================
   THE ACT — the 100XD Model

   Seven stages travelling laterally while the page scrolls down. The upgrade
   over a plain pan rail is that the section now has a camera and a subject:

     · a lit spine runs behind the cards and FILLS as you travel, so progress
       through the model is a visible fact rather than a caption
     · each card carries three planes at different rates — a back plate, the
       card, and a ghost numeral in front — so the row has depth
     · the card nearest the viewport centre is the one in focus: it lifts
       toward the lens, takes the key light, and draws its own glyph. Its
       neighbours recede in Z and desaturate. That focal travel IS the camera
       move; nothing is animated for its own sake
     · the rail closes on a return arc back to stage 01, because "one
       continuous loop" should be something you watch happen
   ========================================================================== */

.act {
	position: relative;
	isolation: isolate;
	clear: both;

	/* One number places the spine, the nodes hanging on it, and the top of
	   every card. Three things that must agree, defined once. */
	--spine-y: 1.375rem;
}

/* ---- ambient plate ------------------------------------------------------ */

.act__ambient {
	position: absolute;
	inset: 0;
	z-index: 0;
	overflow: hidden;
	pointer-events: none;
}

/* A soft key light that drifts across the act as it plays. Driven by --p, so
   it is locked to the reader's scroll rather than running on a timer. */
.act__beam {
	position: absolute;
	inset: -20% -10%;
	background:
		radial-gradient(38% 46% at calc(14% + var(--p, 0) * 64%) 34%,
			var(--lit-fill), transparent 68%),
		radial-gradient(32% 40% at calc(86% - var(--p, 0) * 52%) 72%,
			rgba(255, 193, 7, 0.13), transparent 70%);
	filter: blur(14px);
}

/* A faint horizon that lifts as the act plays — the floor the rail sits on. */
.act__floor {
	position: absolute;
	left: 0;
	right: 0;
	bottom: calc(6% + var(--p, 0) * 4%);
	height: 1px;
	background: linear-gradient(90deg,
		transparent, var(--ink-12) 18%, var(--ink-12) 82%, transparent);
	opacity: calc(0.35 + var(--p, 0) * 0.45);
}

/* ---- stage -------------------------------------------------------------- */

.act__stage {
	position: relative;
	z-index: 1;
	isolation: isolate;
	height: 100vh;
	height: 100svh;
	display: flex;
	flex-direction: column;
	justify-content: center;
	gap: clamp(1.25rem, 3vh, 2.25rem);
	overflow: hidden;
}

.act__head {
	display: grid;
	gap: 0.85rem;
	max-width: 46ch;
}

.act__title { font-size: var(--fs-h2); font-weight: 500; letter-spacing: -0.038em; }

/* A live read of where the camera is, in the model's own terms. */
.act__meta {
	display: flex;
	align-items: baseline;
	gap: 0.75rem;
	font-family: var(--font-mono);
	font-size: var(--fs-xs);
	letter-spacing: 0.08em;
	color: var(--ink-45);
}
.act__meta b {
	font-size: var(--fs-h4);
	font-weight: 600;
	color: var(--ink);
	font-variant-numeric: tabular-nums;
}
.act__meta span { color: var(--blue-600); font-weight: 600; }

/* ---- rail --------------------------------------------------------------- */

.act__viewport { position: relative; overflow: hidden; padding-block: 0 1rem; }

/* The spine is a fixed track belonging to the viewport, not to the rail — the
   cards travel beneath it while the lit fill advances with --p. Putting it on
   the rail instead would make the track itself slide away, which reads as the
   progress bar running off the screen. */
.act__spine {
	position: absolute;
	left: var(--gutter);
	right: var(--gutter);
	top: var(--spine-y);
	margin-top: -1px;
	height: 2px;
	background: var(--ink-07);
	z-index: 2;
	pointer-events: none;
}
.act__spine::after {
	content: "";
	position: absolute;
	inset: 0 auto 0 0;
	width: calc(var(--p, 0) * 100%);
	background: linear-gradient(90deg, var(--blue-600), var(--blue-500) 62%, var(--gold-500));
	box-shadow: 0 0 18px rgba(26, 123, 255, 0.45);
}

.act__rail {
	position: relative;
	/* max-content, so the flex box is as wide as its cards rather than as wide
	   as the viewport — the pan engine measures scrollWidth against it. */
	width: max-content;
	display: flex;
	align-items: stretch;
	gap: clamp(1rem, 2vw, 1.75rem);
	padding-inline: var(--gutter);
	will-change: transform;
}

/* ---- one stage ---------------------------------------------------------- */

.stage {
	position: relative;
	z-index: 1;
	flex: 0 0 clamp(16rem, 23vw, 21.5rem);
	display: flex;
	align-items: stretch;
	/* Clears the spine, so the node hangs on the track above the card. */
	padding-top: calc(var(--spine-y) + 1.5rem);
	perspective: var(--cam-perspective);
	--near: 0;
}

/* The node that sits on the spine. Lights when its stage takes focus. */
.stage__node {
	position: absolute;
	top: var(--spine-y);
	left: 2.4rem;
	width: 11px;
	height: 11px;
	margin-top: -5.5px;
	border-radius: 50%;
	background: var(--ground-raise);
	box-shadow: 0 0 0 2px var(--ink-12);
	transition: box-shadow 0.5s var(--ease-cam), transform 0.5s var(--ease-cam);
	z-index: 2;
}
.stage.is-near .stage__node {
	background: var(--blue-600);
	box-shadow: 0 0 0 3px rgba(11, 95, 234, 0.2), 0 0 20px rgba(11, 95, 234, 0.55);
	transform: scale(1.35);
}

/* Plane 1 — the back plate. Recedes, and carries the coloured wash. */
.stage__plate {
	position: absolute;
	inset: calc(var(--spine-y) + 2.2rem) -4% 6% 4%;
	border-radius: var(--r-lg);
	background: linear-gradient(150deg,
		rgba(26, 123, 255, calc(0.05 + var(--near) * 0.09)),
		rgba(255, 193, 7, calc(0.02 + var(--near) * 0.06)));
	filter: blur(calc(6px + (1 - var(--near)) * 10px));
	opacity: calc(0.35 + var(--near) * 0.65);
	transition: opacity 0.6s var(--ease-cam), filter 0.6s var(--ease-cam);
	pointer-events: none;
}

/* Plane 2 — the card itself. This is the plane the camera focuses on. */
.stage__card {
	position: relative;
	z-index: 1;
	width: 100%;
	display: grid;
	align-content: start;
	gap: 0.7rem;
	padding: clamp(1.4rem, 2.2vw, 2rem);
	border-radius: var(--r-lg);
	background: var(--ground-raise);
	box-shadow:
		inset 0 0 0 1px var(--line-soft),
		0 calc(2px + var(--near) * 10px) calc(10px + var(--near) * 44px)
			rgba(10, 14, 26, calc(0.04 + var(--near) * 0.09));
	transform:
		translate3d(0, calc(var(--near) * var(--cam-lift) * -1), calc((1 - var(--near)) * var(--cam-recede)))
		scale(calc(0.955 + var(--near) * 0.045));
	opacity: calc(0.46 + var(--near) * 0.54);
	transition:
		transform  0.65s var(--ease-cam),
		opacity    0.65s var(--ease-cam),
		box-shadow 0.65s var(--ease-cam);
}

/* Plane 3 — the ghost numeral, in front of the card and travelling faster.
   This is the parallax the eye reads as depth without noticing why. */
.stage__ghost {
	position: absolute;
	right: 0.55rem;
	bottom: -0.35rem;
	z-index: 2;
	font-family: var(--font-mono);
	font-size: clamp(3.6rem, 6vw, 5.4rem);
	font-weight: 700;
	line-height: 0.8;
	letter-spacing: -0.06em;
	color: transparent;
	-webkit-text-stroke: 1.5px rgba(10, 14, 26, 0.17);
	opacity: calc(0.3 + var(--near) * 0.7);
	transform: translate3d(calc(var(--near) * 10px), 0, 0);
	transition: opacity 0.6s var(--ease-cam), transform 0.6s var(--ease-cam);
	pointer-events: none;
	user-select: none;
}

/* ---- card contents ------------------------------------------------------ */

.stage__glyph {
	width: 48px;
	height: 48px;
	color: var(--blue-600);
	opacity: calc(0.4 + var(--near) * 0.6);
	transition: opacity 0.6s var(--ease-cam);
}
/* CSS beats the presentation attribute, so weight is tuneable here rather than
   in eight SVGs. 1.9 is what it takes for a 48px open stroke to hold its own
   against the card's type at this scale. */
.stage__glyph svg {
	width: 100%;
	height: 100%;
	display: block;
	overflow: visible;
	stroke-width: 1.9;
}

/* The glyph draws itself when its stage takes focus. Each path carries its own
   length in --len, written by motion.js, so the dash maths is exact rather
   than a guessed constant that clips long paths. */
.stage__glyph path,
.stage__glyph circle,
.stage__glyph line,
.stage__glyph polyline {
	stroke-dasharray: var(--len, 200);
	stroke-dashoffset: var(--len, 200);
	transition: stroke-dashoffset 1.1s var(--ease-lens) var(--gd, 0ms);
}
.stage.is-near .stage__glyph path,
.stage.is-near .stage__glyph circle,
.stage.is-near .stage__glyph line,
.stage.is-near .stage__glyph polyline { stroke-dashoffset: 0; }

.stage__n {
	font-family: var(--font-mono);
	font-size: var(--fs-xs);
	letter-spacing: 0.1em;
	color: var(--ink-30);
}

.stage__name {
	font-size: var(--fs-h3);
	font-weight: 500;
	letter-spacing: -0.035em;
}

.stage__line { font-size: var(--fs-sm); font-weight: 600; color: var(--blue-600); }
.stage__body { font-size: var(--fs-sm); color: var(--ink-60); line-height: 1.55; }

/* ---- the loop closes ---------------------------------------------------- */

.stage--loop .stage__card {
	background:
		radial-gradient(120% 130% at 100% 0%, rgba(255, 193, 7, 0.16), transparent 62%),
		var(--ground-raise);
	justify-items: start;
}

.stage__return { width: 100%; height: 56px; color: var(--gold-500); }
.stage__return svg { width: 100%; height: 100%; display: block; overflow: visible; }
.stage__return path {
	stroke-dasharray: var(--len, 300);
	stroke-dashoffset: var(--len, 300);
	transition: stroke-dashoffset 1.4s var(--ease-lens);
}
.stage--loop.is-near .stage__return path { stroke-dashoffset: 0; }

/* ---- degraded / narrow: a real scroll region ---------------------------- */

.act.is-static .act__stage { height: auto; display: block; padding-block: var(--sec-y); }
.act.is-static .act__viewport { overflow: visible; padding-block: 2rem 0; }
.act.is-static .act__rail {
	width: auto;
	overflow-x: auto;
	scroll-snap-type: x proximity;
	transform: none !important;
	padding-bottom: 1.25rem;
	-webkit-overflow-scrolling: touch;
}
.act.is-static .stage { scroll-snap-align: start; --near: 1; padding-top: 1rem; }
.act.is-static .act__spine, .act.is-static .stage__node { display: none; }

@media (max-width: 900px) {
	.act__stage { height: auto !important; display: block !important; padding-block: var(--sec-y); }
	.act__viewport { overflow: visible; }
	.act__rail {
		width: auto;
		flex-direction: column;
		transform: none !important;
		padding-inline: 0;
	}
	.act__spine, .stage__node, .stage__ghost { display: none; }
	.stage { flex: none; --near: 1; padding-top: 0; }
	.stage__card { transform: none; opacity: 1; }
	.stage__plate { display: none; }
}

@media (prefers-reduced-motion: reduce) {
	.act__beam { display: none; }
	.stage { --near: 1 !important; }
	.stage__card { transform: none !important; opacity: 1 !important; }
	.stage__glyph path, .stage__glyph circle,
	.stage__glyph line, .stage__glyph polyline,
	.stage__return path { stroke-dashoffset: 0 !important; transition: none !important; }
}

/* ==========================================================================
   THE BUILD — Value-Driven Education

   Two lines and a widening gap. Everything here is driven by the section's own
   --p, so the build is locked to the reader's scroll rather than running on a
   timer they did not start.
   ========================================================================== */

.vbuild { position: relative; }

.vbuild__frame {
	margin-top: clamp(2.5rem, 5vw, 3.75rem);
	padding: clamp(1.5rem, 3vw, 2.5rem);
	border-radius: var(--r-lg);
	background: var(--ground-raise);
	box-shadow: inset 0 0 0 1px var(--line-soft), var(--sh-sm);
}

/* ---- chart -------------------------------------------------------------- */

.vbuild__chart {
	position: relative;
	/* Room above and below for the two axis labels, so neither collides with
	   the curve as it reaches the top-right corner. */
	padding-block: 1.6rem 1.5rem;
	color: var(--ink-45);
}

.vbuild__chart svg {
	width: 100%;
	height: clamp(140px, 18vw, 215px);
	display: block;
	overflow: visible;
}

/* The area between the two lines opens as the argument is made. */
.vbuild__gap {
	opacity: calc(var(--p, 0) * 0.9);
	transition: opacity 0.3s linear;
	/* Clipped to however far the curve has been drawn, so the fill never runs
	   ahead of the line that bounds it. */
	clip-path: inset(0 calc(100% - var(--p, 0) * 100%) 0 0);
}

/* The flat line has to be legible enough to be read as the other half of the
   argument — too faint and the chart looks like one line floating in space. */
.vbuild__flat { opacity: 1; }

/* The curve draws itself across the section. --len is measured by motion.js. */
.vbuild__curve {
	stroke-dasharray: var(--len, 1000);
	stroke-dashoffset: calc(var(--len, 1000) * (1 - var(--p, 0)));
}

.vbuild__node {
	fill: var(--ground-raise);
	stroke: var(--ink-12);
	stroke-width: 2;
	transition: fill 0.45s var(--ease-cam), stroke 0.45s var(--ease-cam);
}

.vbuild__marker-halo { fill: rgba(26, 123, 255, 0.18); }
.vbuild__marker-dot  { fill: var(--blue-600); }

/* Fades in as soon as the build starts. Opacity clamps at 1, so multiplying
   --p is enough to get a quick fade without a second variable. */
.vbuild__marker { opacity: calc(var(--p, 0) * 5); }

.vbuild__axis {
	position: absolute;
	right: 0;
	font-size: var(--fs-xs);
	font-weight: 600;
	letter-spacing: 0.02em;
	white-space: nowrap;
}
.vbuild__axis--rise { top: 0; color: var(--blue-600); }
.vbuild__axis--flat { bottom: 0; color: var(--ink-45); }

/* ---- phases ------------------------------------------------------------- */

.vbuild__lede {
	margin-top: clamp(1.75rem, 3vw, 2.5rem);
	font-size: var(--fs-sm);
	color: var(--ink-45);
}

.vbuild__phases {
	display: grid;
	grid-template-columns: repeat(var(--cols, 5), minmax(0, 1fr));
	gap: clamp(0.75rem, 1.5vw, 1.25rem);
	margin-top: 1rem;
	counter-reset: none;
}

.vphase {
	display: grid;
	align-content: start;
	gap: 0.4rem;
	padding: 1rem 0.9rem 1.1rem;
	border-radius: var(--r-md);
	border-top: 2px solid var(--ink-07);
	transition:
		background 0.5s var(--ease-cam),
		border-color 0.5s var(--ease-cam),
		opacity 0.5s var(--ease-cam);
	opacity: 0.5;
}

/* A phase already passed reads as settled; the current one reads as live.
   Two states, because "done" and "not yet" should not look the same. */
.vphase.is-past { opacity: 1; border-top-color: var(--blue-500); }
.vphase.is-on {
	opacity: 1;
	border-top-color: var(--gold-500);
	background: linear-gradient(180deg, rgba(255, 193, 7, 0.09), transparent 70%);
}

.vphase__n {
	font-family: var(--font-mono);
	font-size: var(--fs-xs);
	letter-spacing: 0.1em;
	color: var(--ink-30);
}
.vphase__name { font-size: var(--fs-h4); font-weight: 600; letter-spacing: -0.03em; }
.vphase__note { font-size: var(--fs-xs); color: var(--ink-45); line-height: 1.45; }

.vphase__adds { display: grid; gap: 0.35rem; margin-top: 0.45rem; }

/* Each component arrives with its phase. This is the "build": the reader
   watches capability being assembled out of named parts. */
.vchip {
	font-size: var(--fs-xs);
	font-weight: 600;
	line-height: 1.3;
	padding: 0.4rem 0.6rem;
	border-radius: var(--r-sm);
	background: var(--ground);
	box-shadow: inset 0 0 0 1px var(--line-soft);
	opacity: 0;
	transform: translate3d(0, 9px, 0);
	transition:
		opacity   0.55s var(--ease-cam) var(--d, 0ms),
		transform 0.55s var(--ease-cam) var(--d, 0ms);
}
.vphase.is-on .vchip,
.vphase.is-past .vchip { opacity: 1; transform: none; }

.vphase.is-on .vchip {
	background: #fff;
	box-shadow: inset 0 0 0 1px rgba(11, 95, 234, 0.22), var(--sh-sm);
}

.vbuild__pull {
	max-width: 26ch;
	margin: clamp(2.5rem, 5vw, 4rem) auto 0;
}

/* ---- narrow ------------------------------------------------------------- */

@media (max-width: 860px) {
	/* The chart's whole point is the gap opening left to right; at phone width
	   there is no width for it to open across, so it goes and the phases carry
	   the section on their own. */
	.vbuild__chart { display: none; }
	.vbuild__frame { padding: clamp(1.1rem, 4vw, 1.5rem); }
	.vbuild__lede { margin-top: 0; }
	.vbuild__phases { grid-template-columns: minmax(0, 1fr); }
	.vphase { opacity: 1; padding: 0.9rem 0 1rem; border-radius: 0; }
	.vchip { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
	.vbuild__curve { stroke-dashoffset: 0 !important; }
	.vbuild__gap { opacity: 0.9 !important; clip-path: none !important; }
	.vphase { opacity: 1 !important; }
	.vchip { opacity: 1 !important; transform: none !important; transition: none !important; }
}

/* ==========================================================================
   THE ROUTER — How can 100XD help you?
   ========================================================================== */

.router__grid {
	display: grid;
	grid-template-columns: minmax(0, 20rem) minmax(0, 1fr);
	gap: clamp(1.25rem, 3vw, 2.5rem);
	margin-top: clamp(2rem, 4vw, 3rem);
	align-items: start;
}

.router__list { display: grid; gap: 0.3rem; }

.rcard {
	position: relative;
	display: grid;
	gap: 0.12rem;
	padding: 0.7rem 0.9rem 0.7rem 1.1rem;
	border-radius: var(--r-md);
	text-align: left;
	transition: background 0.4s var(--ease-cam), transform 0.4s var(--ease-cam);
}
.rcard:hover { background: var(--ground-raise); }
.rcard.is-on {
	background: var(--ground-raise);
	box-shadow: var(--sh-sm), inset 0 0 0 1px var(--line-soft);
	transform: translateX(3px);
}

.rcard__label {
	font-size: var(--fs-sm);
	font-weight: 700;
	letter-spacing: -0.02em;
	transition: color 0.4s var(--ease-cam);
}
.rcard.is-on .rcard__label { color: var(--blue-600); }

.rcard__line { font-size: var(--fs-xs); color: var(--ink-45); }

/* The rail that marks the live choice — grows from nothing rather than
   appearing, so the eye follows the selection down the list. */
.rcard__rail {
	position: absolute;
	left: 0;
	top: 50%;
	width: 3px;
	height: 0;
	border-radius: 3px;
	background: linear-gradient(180deg, var(--blue-600), var(--gold-500));
	transform: translateY(-50%);
	transition: height 0.45s var(--ease-cam);
}
.rcard.is-on .rcard__rail { height: 68%; }

/* ---- the console -------------------------------------------------------- */

.router__console {
	position: relative;
	border-radius: var(--r-lg);
	background: var(--ground-raise);
	box-shadow: inset 0 0 0 1px var(--line-soft), var(--sh-md);
	overflow: hidden;
}

/* Window chrome. It is what makes the box read as a running application
   rather than a styled div, and it costs three dots and a title bar. */
.rcon__chrome {
	display: flex;
	align-items: center;
	gap: 0.7rem;
	padding: 0.7rem clamp(1rem, 2vw, 1.4rem);
	border-bottom: 1px solid var(--line-soft);
	background: linear-gradient(180deg, rgba(10, 14, 26, 0.035), transparent);
}
.rcon__dots { display: flex; gap: 5px; }
.rcon__dots i { width: 8px; height: 8px; border-radius: 50%; background: var(--ink-12); }
.rcon__dots i:first-child { background: rgba(255, 99, 99, 0.5); }
.rcon__dots i:nth-child(2) { background: rgba(255, 193, 7, 0.6); }

.rcon__name {
	font-family: var(--font-mono);
	font-size: var(--fs-xs);
	color: var(--ink-45);
}

/* Says out loud what this is. A mock console that does not admit to being one
   is a claim about a product that does not exist yet. */
.rcon__badge {
	margin-left: auto;
	padding: 0.2rem 0.55rem;
	border-radius: var(--r-pill);
	font-size: 0.625rem;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.08em;
	color: var(--ink-45);
	background: var(--ground);
	box-shadow: inset 0 0 0 1px var(--line-soft);
}

/* `display` on a class beats the UA stylesheet's [hidden] { display: none },
   so without this every one of the nine panels renders at once. Any rule that
   sets display on something toggled with [hidden] needs its own opt-out. */
.rpanel[hidden] { display: none; }

.rpanel {
	display: grid;
	align-content: start;
	gap: 0.9rem;
	padding: clamp(1.1rem, 2.5vw, 1.7rem);
}

.rcon__block { display: grid; gap: 0.5rem; }

.rcon__label {
	display: flex;
	align-items: center;
	gap: 0.45rem;
	font-size: var(--fs-xs);
	font-weight: 700;
	letter-spacing: 0.1em;
	text-transform: uppercase;
	color: var(--ink-30);
}
.rcon__label b {
	padding: 0.1rem 0.4rem;
	border-radius: var(--r-sm);
	font-size: 0.625rem;
	color: var(--blue-600);
	background: rgba(26, 123, 255, 0.1);
}

/* ---- 1. the passport fills in ------------------------------------------- */

.rfields { display: grid; gap: 0.3rem; margin: 0; }

/*
 * Staged content uses TRANSITIONS, not keyframes.
 *
 * A keyframe animation with a per-item delay has to be restarted by hand every
 * time a panel reopens, and anything that re-touches the element mid-flight
 * restarts it again -- which left every item after the first stuck at its
 * from-state while the first looped forever. A transition is idempotent: add
 * the class and it plays, re-add it and nothing happens, remove it and it
 * plays back. Nothing to choreograph.
 */
.rfield {
	display: grid;
	grid-template-columns: 5.5rem minmax(0, 1fr) 1.1rem;
	align-items: baseline;
	gap: 0.6rem;
	padding: 0.42rem 0.6rem;
	border-radius: var(--r-sm);
	background: var(--ground);
	opacity: 0;
	transform: translate3d(0, 6px, 0);
	transition:
		opacity   0.42s var(--ease-cam) var(--d, 0ms),
		transform 0.42s var(--ease-cam) var(--d, 0ms);
}
.rpanel.is-filling .rfield { opacity: 1; transform: none; }

.rfield dt {
	font-family: var(--font-mono);
	font-size: 0.6875rem;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--ink-30);
}
.rfield dd { margin: 0; font-size: var(--fs-sm); font-weight: 600; color: var(--ink); }
.rfield--ask dd { color: var(--blue-600); }

.rfield__tick {
	color: #10b981;
	opacity: 0;
	transform: scale(0.4);
	transition:
		opacity   0.3s var(--ease-cam) calc(var(--d, 0ms) + 260ms),
		transform 0.3s var(--ease-cam) calc(var(--d, 0ms) + 260ms);
}
.rfield__tick svg { width: 12px; height: 12px; display: block; }
.rpanel.is-filling .rfield__tick { opacity: 1; transform: none; }

/* ---- 2. the matcher sweeps ---------------------------------------------- */

.rcon__scan {
	display: grid;
	grid-template-columns: auto auto minmax(0, 1fr);
	align-items: center;
	gap: 0.55rem;
	padding: 0.5rem 0.7rem;
	border-radius: var(--r-sm);
	background: rgba(26, 123, 255, 0.07);
	opacity: 0;
	max-height: 0;
	padding-block: 0;
	overflow: hidden;
	transition:
		opacity 0.3s var(--ease-cam),
		max-height 0.3s var(--ease-cam),
		padding-block 0.3s var(--ease-cam);
}
.rpanel.is-matching .rcon__scan { opacity: 1; max-height: 3rem; padding-block: 0.5rem; }
/* Once results are in, the search collapses out of the way. */
.rpanel.is-matched .rcon__scan { opacity: 0; max-height: 0; padding-block: 0; }

.rcon__spinner {
	width: 13px;
	height: 13px;
	border-radius: 50%;
	border: 2px solid rgba(26, 123, 255, 0.25);
	border-top-color: var(--blue-600);
	animation: rspin 0.7s linear infinite;
}
@keyframes rspin { to { transform: rotate(360deg); } }

.rcon__scan-text {
	font-family: var(--font-mono);
	font-size: 0.6875rem;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--blue-700);
	white-space: nowrap;
}

.rcon__scan-bar {
	height: 3px;
	border-radius: 3px;
	background: rgba(26, 123, 255, 0.16);
	overflow: hidden;
}
.rcon__scan-bar i {
	display: block;
	width: 38%;
	height: 100%;
	border-radius: inherit;
	background: linear-gradient(90deg, transparent, var(--blue-600), transparent);
	animation: rsweep 1.1s var(--ease-io) infinite;
}
@keyframes rsweep {
	from { transform: translateX(-110%); }
	to   { transform: translateX(310%); }
}

/* ---- 3. the pathways land ----------------------------------------------- */

.rcon__results { opacity: 0; transition: opacity 0.3s var(--ease-cam); }
.rpanel.is-matched .rcon__results { opacity: 1; }

.rmatch { display: grid; gap: 0.35rem; }

.rmatch__row {
	display: grid;
	grid-template-columns: minmax(0, 1fr) 5rem 2.5rem;
	align-items: center;
	gap: 0.6rem;
	padding: 0.42rem 0.6rem;
	border-radius: var(--r-sm);
	background: var(--ground);
	font-size: var(--fs-xs);
	font-weight: 600;
	opacity: 0;
	transform: translate3d(0, 8px, 0);
	transition:
		opacity   0.45s var(--ease-cam) var(--d, 0ms),
		transform 0.45s var(--ease-cam) var(--d, 0ms);
}
.rpanel.is-matched .rmatch__row { opacity: 1; transform: none; }
.rmatch__row:first-child {
	background: rgba(26, 123, 255, 0.08);
	box-shadow: inset 0 0 0 1px rgba(26, 123, 255, 0.18);
}

.rmatch__name { color: var(--ink); }

.rmatch__meter {
	height: 5px;
	border-radius: 5px;
	background: var(--ink-07);
	overflow: hidden;
}
.rmatch__meter i {
	display: block;
	width: 0;
	height: 100%;
	border-radius: inherit;
	background: linear-gradient(90deg, var(--blue-600), var(--blue-400));
	transition: width 0.8s var(--ease-cam) calc(var(--d, 0ms) + 120ms);
}
.rpanel.is-matched .rmatch__meter i { width: var(--w, 0%); }
.rmatch__row:first-child .rmatch__meter i {
	background: linear-gradient(90deg, var(--blue-600), var(--gold-500));
}

.rmatch__pct {
	font-family: var(--font-mono);
	font-size: 0.6875rem;
	font-variant-numeric: tabular-nums;
	text-align: right;
	color: var(--blue-700);
}
.rmatch__pct::after { content: "%"; opacity: 0.55; }

@keyframes rmatch-in {
	from { opacity: 0; transform: translate3d(0, 8px, 0); }
	to   { opacity: 1; transform: none; }
}

/* ---- 4. the route ------------------------------------------------------- */

.rcon__route { opacity: 0; transition: opacity 0.3s var(--ease-cam) 0.15s; }
.rpanel.is-matched .rcon__route { opacity: 1; }

.rroute {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.45rem;
	margin-top: 0.15rem;
}

.rroute__step {
	display: inline-flex;
	align-items: center;
	gap: 0.42rem;
	padding: 0.42rem 0.75rem;
	border-radius: var(--r-pill);
	background: var(--ground);
	box-shadow: inset 0 0 0 1px var(--line-soft);
	font-size: var(--fs-xs);
	font-weight: 600;
}

/*
 * Entry animations are scoped to .is-on, NOT declared on the element.
 *
 * Declared on the element with fill-mode:both, they run once at page load --
 * inside the eight panels that are still [hidden] -- finish, and then hold
 * their from-state (opacity 0) forever. Showing that panel later does not
 * restart a finished animation, so the pills never appear. Scoping to .is-on
 * means the animation starts the moment a panel is shown, every time.
 */
.rroute__step {
	opacity: 0;
	transform: translate3d(-8px, 0, 0);
	transition:
		opacity   0.5s var(--ease-cam) var(--d, 0ms),
		transform 0.5s var(--ease-cam) var(--d, 0ms);
}
.rpanel.is-matched .rroute__step { opacity: 1; transform: none; }

.rroute__dot {
	width: 6px;
	height: 6px;
	border-radius: 50%;
	background: var(--blue-500);
	flex: none;
}

.rroute__link {
	width: 16px;
	height: 1px;
	background: var(--ink-12);
	transform-origin: left center;
}
.rroute__link {
	transform: scaleX(0);
	transition: transform 0.4s var(--ease-cam) var(--d, 0ms);
}
.rpanel.is-matched .rroute__link { transform: scaleX(1); }

@keyframes rroute-in {
	from { opacity: 0; transform: translate3d(-8px, 0, 0); }
	to   { opacity: 1; transform: none; }
}
@keyframes rroute-link {
	from { transform: scaleX(0); }
	to   { transform: scaleX(1); }
}

.rfocus { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.15rem; }
.rfocus li {
	padding: 0.35rem 0.7rem;
	border-radius: var(--r-sm);
	font-size: var(--fs-xs);
	font-weight: 600;
	color: var(--blue-700);
	background: rgba(26, 123, 255, 0.08);
}
.rpanel.is-on .rfocus li {
	animation: rroute-in 0.5s var(--ease-cam) var(--d, 0ms) both;
}

.rpanel__cta { margin-top: 1.1rem; }

@media (max-width: 860px) {
	.router__grid { grid-template-columns: minmax(0, 1fr); }
	/* The list becomes a horizontal chooser above the panel, which is the
	   shape this interaction already has on a phone. */
	.router__list {
		grid-auto-flow: column;
		grid-auto-columns: minmax(11rem, auto);
		overflow-x: auto;
		scroll-snap-type: x proximity;
		padding-bottom: 0.5rem;
		-webkit-overflow-scrolling: touch;
	}
	.rcard { scroll-snap-align: start; }
	.rcard.is-on { transform: none; }
	.rcard__rail { left: 0.9rem; right: 0.9rem; top: auto; bottom: 0; width: auto; height: 0; transform: none; }
	.rcard.is-on .rcard__rail { height: 3px; }
}

@media (prefers-reduced-motion: reduce) {
	/* The console still shows its finished state — every field, every match,
	   the whole route — it just arrives at once instead of playing. */
	.rfield, .rfield__tick, .rmatch__row,
	.rpanel.is-on .rroute__step, .rpanel.is-on .rroute__link {
		animation: none !important;
		opacity: 1 !important;
		transform: none !important;
	}
	.rcon__results, .rcon__route { opacity: 1 !important; }
	.rmatch__meter i { width: var(--w, 0%) !important; transition: none !important; }
	.rcon__scan { display: none !important; }
	.rcon__spinner, .rcon__scan-bar i { animation: none !important; }
	.rcard.is-on { transform: none; }
}

/* ==========================================================================
   FOOTER — brand line and social row
   ========================================================================== */

.foot__brand { display: grid; gap: 0.9rem; align-content: start; }

.foot__tag {
	max-width: 26ch;
	font-size: var(--fs-sm);
	font-weight: 600;
	line-height: 1.45;
	letter-spacing: -0.018em;
	color: var(--ink);
}

.foot__social {
	display: flex;
	flex-wrap: wrap;
	gap: 0.45rem;
	margin-top: 0.15rem;
}

.foot__social a {
	display: grid;
	place-items: center;
	width: 38px;
	height: 38px;
	border-radius: 50%;
	color: var(--ink-60);
	background: var(--ground-raise);
	box-shadow: inset 0 0 0 1px var(--line-soft);
	transition:
		color 0.3s var(--ease-cam),
		background 0.3s var(--ease-cam),
		box-shadow 0.3s var(--ease-cam),
		transform 0.3s var(--ease-cam);
}
.foot__social svg { width: 18px; height: 18px; display: block; }

.foot__social a:hover {
	color: #fff;
	background: var(--blue-600);
	box-shadow: 0 4px 14px rgba(11, 95, 234, 0.32);
	transform: translateY(-2px);
}

@media (prefers-reduced-motion: reduce) {
	.foot__social a:hover { transform: none; }
}

/* ==========================================================================
   INNER PAGES — rendered by template-parts/page-builder.php

   Four block shapes cover all seventeen framework pages: a category grid, a
   process chain, numbered stages, and an emphasised statement. Styling them
   once is what stops the pages drifting apart again.
   ========================================================================== */

.ipage__hero {
	padding-block: clamp(5.5rem, 11vw, 8.5rem) clamp(2.5rem, 5vw, 4rem);
}

.ipage__hero-grid {
	display: grid;
	grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
	gap: clamp(2rem, 5vw, 4rem);
	align-items: center;
}

/* ---- the hero deck ------------------------------------------------------ */

.ihero {
	position: relative;
	display: grid;
	gap: 1.1rem;
	padding: clamp(1.5rem, 3vw, 2.25rem);
	border-radius: var(--r-lg);
	background: var(--ground-raise);
	box-shadow: inset 0 0 0 1px var(--line-soft), var(--sh-md);
	isolation: isolate;
	overflow: hidden;
}

.ihero__glow {
	position: absolute;
	inset: -30% -20% auto -20%;
	height: 75%;
	z-index: -1;
	background: radial-gradient(60% 70% at 50% 0%,
		rgba(26, 123, 255, 0.16), transparent 70%);
	filter: blur(24px);
	pointer-events: none;
}

/* The stack. Height is fixed so the cards behind have somewhere to sit and
   the hero does not resize as the front card changes. */
.ihero__deck {
	position: relative;
	height: 9.5rem;
	perspective: 900px;
}

.ihcard {
	position: absolute;
	inset: 0 0 auto 0;
	display: grid;
	gap: 0.45rem;
	padding: 1.1rem 1.25rem 1.25rem;
	border-radius: var(--r-md);
	background: #fff;
	box-shadow: inset 0 0 0 1px var(--line-soft), var(--sh-sm);

	/* --i is distance from the front, written by motion.js. Each step back
	   drops the card a little, shrinks it and fades it, which is what makes
	   a flat stack read as depth. */
	transform:
		translate3d(0, calc(var(--i, 0) * 14px), 0)
		scale(calc(1 - var(--i, 0) * 0.045));
	opacity: calc(1 - var(--i, 0) * 0.28);
	z-index: calc(10 - var(--i, 0));
	transition:
		transform 0.62s var(--ease-cam),
		opacity   0.62s var(--ease-cam),
		box-shadow 0.62s var(--ease-cam);
}

.ihcard.is-front {
	box-shadow: inset 0 0 0 1px rgba(26, 123, 255, 0.24), var(--sh-md);
}

.ihcard__n {
	font-family: var(--font-mono);
	font-size: 0.625rem;
	letter-spacing: 0.1em;
	color: var(--ink-30);
}
.ihcard.is-front .ihcard__n { color: var(--blue-600); }

.ihcard__name {
	font-size: var(--fs-h4);
	font-weight: 700;
	letter-spacing: -0.028em;
	line-height: 1.2;
}

.ihcard__rail {
	height: 3px;
	width: 2.5rem;
	border-radius: 3px;
	background: var(--ink-12);
	transition: width 0.62s var(--ease-cam), background 0.62s var(--ease-cam);
}
.ihcard.is-front .ihcard__rail {
	width: 4.5rem;
	background: linear-gradient(90deg, var(--blue-600), var(--gold-500));
}

.ihero__meta {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	font-family: var(--font-mono);
	font-size: 0.6875rem;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--ink-45);
}
.ihero__meta b {
	margin-left: auto;
	font-variant-numeric: tabular-nums;
	color: var(--blue-600);
}
.ihero__dot {
	width: 7px;
	height: 7px;
	border-radius: 50%;
	background: var(--blue-600);
	animation: cmpx-pulse 2.4s var(--ease-io) infinite;
}

@media (max-width: 900px) {
	.ipage__hero-grid { grid-template-columns: minmax(0, 1fr); }
	.ihero { order: 2; }
	.ihero__deck { height: 8.5rem; }
}

@media (prefers-reduced-motion: reduce) {
	.ihcard { transition: none !important; }
	.ihero__dot { animation: none; }
}

.ipage__title {
	font-size: var(--fs-h1);
	font-weight: 500;
	letter-spacing: -0.04em;
	line-height: 1.05;
	max-width: 18ch;
	text-wrap: balance;
}

.ipage__lede {
	margin-top: 1.25rem;
	max-width: 58ch;
	font-size: var(--fs-lede);
	color: var(--ink-60);
	line-height: 1.6;
}

.ipage__cta { margin-top: 2rem; }

.ipage__label {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	margin-bottom: 1.25rem;
	font-size: var(--fs-xs);
	font-weight: 700;
	letter-spacing: 0.1em;
	text-transform: uppercase;
	color: var(--ink-30);
}
.ipage__label b {
	padding: 0.1rem 0.42rem;
	border-radius: var(--r-sm);
	font-family: var(--font-mono);
	font-size: 0.625rem;
	color: var(--blue-600);
	background: rgba(26, 123, 255, 0.1);
}
.ipage__label--center { justify-content: center; }

/* ---- category grid ------------------------------------------------------ */

.icats {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
	gap: 0.5rem;
}

.icat {
	display: grid;
	grid-template-columns: 1.9rem minmax(0, 1fr) 1.1rem;
	align-items: center;
	gap: 0.6rem;
	padding: 0.85rem 1rem;
	border-radius: var(--r-md);
	background: var(--ground-raise);
	box-shadow: inset 0 0 0 1px var(--line-soft);
	opacity: 0;
	transform: translate3d(0, 10px, 0);
	transition:
		opacity   0.45s var(--ease-cam) var(--d, 0ms),
		transform 0.45s var(--ease-cam) var(--d, 0ms),
		box-shadow 0.3s var(--ease-cam),
		background 0.3s var(--ease-cam);
}
.icats.is-in .icat,
html:not(.m-ready) .icat { opacity: 1; transform: none; }

.icat:hover {
	background: #fff;
	box-shadow: inset 0 0 0 1px rgba(26, 123, 255, 0.28), var(--sh-sm);
}

.icat__n {
	font-family: var(--font-mono);
	font-size: 0.625rem;
	color: var(--ink-30);
}
.icat__name { font-size: var(--fs-sm); font-weight: 600; }

.icat__go {
	width: 13px;
	height: 13px;
	color: var(--ink-30);
	opacity: 0;
	transform: translateX(-4px);
	transition: opacity 0.3s var(--ease-cam), transform 0.3s var(--ease-cam), color 0.3s var(--ease-cam);
}
.icat__go svg { width: 100%; height: 100%; display: block; }
.icat:hover .icat__go { opacity: 1; transform: none; color: var(--blue-600); }

/* ---- process chain ------------------------------------------------------ */

.iflow {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.45rem;
}

.iflow__step {
	display: inline-flex;
	align-items: center;
	gap: 0.45rem;
	padding: 0.5rem 0.9rem;
	border-radius: var(--r-pill);
	background: var(--ground-raise);
	box-shadow: inset 0 0 0 1px var(--line-soft);
	font-size: var(--fs-xs);
	font-weight: 600;
	opacity: 0;
	transform: translate3d(-8px, 0, 0);
	transition:
		opacity   0.45s var(--ease-cam) var(--d, 0ms),
		transform 0.45s var(--ease-cam) var(--d, 0ms);
}
.iflow.is-in .iflow__step,
html:not(.m-ready) .iflow__step { opacity: 1; transform: none; }

.iflow__dot {
	width: 6px;
	height: 6px;
	border-radius: 50%;
	background: var(--blue-500);
	flex: none;
}

.iflow__link {
	width: 18px;
	height: 1px;
	background: var(--ink-12);
	transform-origin: left center;
	transform: scaleX(0);
	transition: transform 0.4s var(--ease-cam) var(--d, 0ms);
}
.iflow.is-in .iflow__link,
html:not(.m-ready) .iflow__link { transform: scaleX(1); }

/* The "traditional model" chain the framework contrasts against — present,
   but visibly the one being argued with. */
.iflow--muted .iflow__step { background: var(--ground); color: var(--ink-45); }
.iflow--muted .iflow__dot { background: var(--ink-30); }

/* ---- numbered stages ---------------------------------------------------- */

.isteps { display: grid; gap: 0.6rem; }

.istep {
	display: grid;
	grid-template-columns: 3rem minmax(0, 1fr);
	gap: clamp(0.75rem, 2vw, 1.5rem);
	padding: clamp(1rem, 2.5vw, 1.6rem);
	border-radius: var(--r-lg);
	background: var(--ground-raise);
	box-shadow: inset 0 0 0 1px var(--line-soft);
	opacity: 0;
	transform: translate3d(0, 12px, 0);
	transition:
		opacity   0.5s var(--ease-cam) var(--d, 0ms),
		transform 0.5s var(--ease-cam) var(--d, 0ms);
}
.isteps.is-in .istep,
html:not(.m-ready) .istep { opacity: 1; transform: none; }

.istep__n {
	font-family: var(--font-mono);
	font-size: var(--fs-h4);
	font-weight: 700;
	color: var(--blue-600);
	line-height: 1;
}

.istep__name {
	font-size: var(--fs-h4);
	font-weight: 700;
	letter-spacing: -0.028em;
	margin-bottom: 0.6rem;
}

.istep__list { display: flex; flex-wrap: wrap; gap: 0.35rem; }
.istep__list li {
	padding: 0.28rem 0.6rem;
	border-radius: var(--r-sm);
	font-size: 0.6875rem;
	font-weight: 600;
	color: var(--ink-60);
	background: var(--ground);
}

/* ---- feature: the overview ---------------------------------------------- */

.ifeature {
	display: grid;
	grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
	gap: clamp(1.5rem, 4vw, 3.5rem);
	align-items: start;
}

.ifeature__title {
	font-size: var(--fs-h2);
	font-weight: 500;
	letter-spacing: -0.038em;
	line-height: 1.1;
	max-width: 20ch;
	text-wrap: balance;
}

.ifeature__body {
	margin-top: 1.1rem;
	font-size: var(--fs-body);
	color: var(--ink-60);
	line-height: 1.65;
	max-width: 58ch;
}

.ipoints {
	display: grid;
	gap: 0.5rem;
	padding: clamp(1.1rem, 2.5vw, 1.6rem);
	border-radius: var(--r-lg);
	background: var(--ground-raise);
	box-shadow: inset 0 0 0 1px var(--line-soft), var(--sh-sm);
}

.ipoint {
	display: grid;
	grid-template-columns: 1.15rem minmax(0, 1fr);
	gap: 0.6rem;
	align-items: start;
	font-size: var(--fs-sm);
	color: var(--ink);
	line-height: 1.5;
	opacity: 0;
	transform: translate3d(0, 8px, 0);
	transition:
		opacity   0.45s var(--ease-cam) var(--d, 0ms),
		transform 0.45s var(--ease-cam) var(--d, 0ms);
}
.ipoints.is-in .ipoint,
html:not(.m-ready) .ipoint { opacity: 1; transform: none; }

.ipoint__mark { color: #10b981; padding-top: 0.15rem; }
.ipoint__mark svg { width: 14px; height: 14px; display: block; }

/* ---- detail: each item explained ---------------------------------------- */

.idetails {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
	gap: 0.75rem;
}

.idetail {
	display: grid;
	align-content: start;
	gap: 0.4rem;
	padding: clamp(1.1rem, 2.2vw, 1.5rem);
	border-radius: var(--r-lg);
	background: var(--ground-raise);
	box-shadow: inset 0 0 0 1px var(--line-soft);
	opacity: 0;
	transform: translate3d(0, 12px, 0);
	transition:
		opacity   0.5s var(--ease-cam) var(--d, 0ms),
		transform 0.5s var(--ease-cam) var(--d, 0ms),
		box-shadow 0.35s var(--ease-cam);
}
.idetails.is-in .idetail,
html:not(.m-ready) .idetail { opacity: 1; transform: none; }

.idetail:hover {
	box-shadow: inset 0 0 0 1px rgba(26, 123, 255, 0.28), var(--sh-md);
}

.idetail__n {
	font-family: var(--font-mono);
	font-size: 0.625rem;
	letter-spacing: 0.1em;
	color: var(--blue-600);
}
.idetail__name { font-size: var(--fs-h4); font-weight: 700; letter-spacing: -0.028em; }
.idetail__body { font-size: var(--fs-sm); color: var(--ink-60); line-height: 1.6; }

/* ---- faq ---------------------------------------------------------------- */

.ifaqs { display: grid; gap: 0.5rem; }

.ifaq {
	border-radius: var(--r-md);
	background: var(--ground-raise);
	box-shadow: inset 0 0 0 1px var(--line-soft);
	opacity: 0;
	transform: translate3d(0, 10px, 0);
	transition:
		opacity   0.45s var(--ease-cam) var(--d, 0ms),
		transform 0.45s var(--ease-cam) var(--d, 0ms);
}
.ifaqs.is-in .ifaq,
html:not(.m-ready) .ifaq { opacity: 1; transform: none; }

.ifaq__q {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	padding: 1rem clamp(1rem, 2vw, 1.4rem);
	font-size: var(--fs-sm);
	font-weight: 700;
	letter-spacing: -0.02em;
	cursor: pointer;
	list-style: none;
	transition: color 0.25s var(--ease-cam);
}
.ifaq__q::-webkit-details-marker { display: none; }
.ifaq__q:hover { color: var(--blue-600); }
.ifaq[open] .ifaq__q { color: var(--blue-600); }

.ifaq__chev {
	flex: none;
	width: 14px;
	height: 14px;
	color: var(--ink-30);
	transition: transform 0.3s var(--ease-cam), color 0.25s var(--ease-cam);
}
.ifaq__chev svg { width: 100%; height: 100%; display: block; }
.ifaq[open] .ifaq__chev { transform: rotate(180deg); color: var(--blue-600); }

.ifaq__a {
	padding: 0 clamp(1rem, 2vw, 1.4rem) 1.1rem;
	font-size: var(--fs-sm);
	color: var(--ink-60);
	line-height: 1.65;
}

@media (max-width: 860px) {
	.ifeature { grid-template-columns: minmax(0, 1fr); }
	.ifeature__title { max-width: none; }
}

@media (prefers-reduced-motion: reduce) {
	.ipoint, .idetail, .ifaq { opacity: 1 !important; transform: none !important; }
	.ifaq[open] .ifaq__chev { transition: none; }
}

/* ---- statement ---------------------------------------------------------- */

.istatement {
	font-size: var(--fs-h3);
	font-weight: 500;
	letter-spacing: -0.035em;
	line-height: 1.25;
	text-align: center;
	text-wrap: balance;
	max-width: 30ch;
	margin-inline: auto;
}

.inote { text-align: center; }

@media (max-width: 640px) {
	.icats { grid-template-columns: minmax(0, 1fr); }
	.istep { grid-template-columns: 2.25rem minmax(0, 1fr); }
	.ipage__title { max-width: none; }
}

@media (prefers-reduced-motion: reduce) {
	.icat, .iflow__step, .istep { opacity: 1 !important; transform: none !important; }
	.iflow__link { transform: scaleX(1) !important; }
}

/* ==========================================================================
   THE SILO MAP — What is 100XD?

   Five pillars, drawn identically on every card. Only the boundary moves, so
   the eye reads five providers looking at one part of the same picture — and
   the parts nobody is looking at stay dark.
   ========================================================================== */

.what-card__art--map {
	display: grid;
	align-content: center;
	padding: 0.9rem 0.75rem;
	min-height: 0;
}

.silomap { width: 100%; }
.silomap__list { display: grid; gap: 0.28rem; }

.silopill {
	position: relative;
	display: grid;
	grid-template-columns: 3px minmax(0, 1fr);
	align-items: center;
	gap: 0.5rem;
	padding: 0.34rem 0.55rem;
	border-radius: var(--r-sm);
	background: var(--ground);
	font-size: 0.6875rem;
	font-weight: 600;
	color: var(--ink-30);

	opacity: 0;
	transform: translate3d(0, 6px, 0);
	transition:
		opacity   0.45s var(--ease-cam) var(--d, 0ms),
		transform 0.45s var(--ease-cam) var(--d, 0ms),
		color     0.4s var(--ease-cam),
		background 0.4s var(--ease-cam);
}
.what-card.is-in .silopill,
.m-ready .what-card[data-reveal].is-in .silopill { opacity: 1; transform: none; }

/* No JS, no observer: the pillars must still be readable. */
html:not(.m-ready) .silopill { opacity: 1; transform: none; }

.silopill i {
	display: block;
	height: 100%;
	min-height: 0.9rem;
	border-radius: 3px;
	background: var(--ink-12);
	transition: background 0.4s var(--ease-cam), box-shadow 0.4s var(--ease-cam);
}

/* The one pillar this provider actually covers. */
.silopill.is-in {
	color: var(--ink);
	background: var(--ground-raise);
	box-shadow: inset 0 0 0 1px rgba(26, 123, 255, 0.28), var(--sh-sm);
}
.silopill.is-in i {
	background: linear-gradient(180deg, var(--blue-600), var(--blue-400));
	box-shadow: 0 0 10px rgba(26, 123, 255, 0.45);
}

/* The boundary. Dashed, because a silo's edge is a limitation rather than a
   feature — a solid frame would read as a badge. */
.silopill.is-in::after {
	content: "";
	position: absolute;
	inset: -4px;
	border-radius: calc(var(--r-sm) + 3px);
	border: 1px dashed rgba(26, 123, 255, 0.45);
	pointer-events: none;
}

/* A provider outside all five gets the boundary around nothing, drawn on the
   map itself — the Travel Provider case. */
.silomap--outside .silomap__list {
	position: relative;
	padding: 0.2rem;
}
.silomap--outside .silomap__list::after {
	content: "";
	position: absolute;
	inset: -3px;
	border-radius: var(--r-sm);
	border: 1px dashed var(--ink-12);
	pointer-events: none;
}

/* Hovering the card nudges the uncovered pillars away, so the gap the copy
   describes is something you can see open up. */
.what-card:hover .silopill:not(.is-in) { transform: translate3d(5px, 0, 0); opacity: 0.6; }
.what-card:hover .silopill.is-in { transform: translate3d(-2px, 0, 0); }

/* ---- the payoff: one boundary around all five --------------------------- */

.silomap--all { position: relative; padding: 0.45rem; }
.silomap--all .silomap__list { position: relative; gap: 0.3rem; }

/* Per-pillar boundaries would read as five silos again, so the individual
   dashes go and one solid frame takes their place. */
.silomap--all .silopill::after { display: none; }
.silomap--all .silopill {
	opacity: 1;
	transform: none;
	background: rgba(255, 255, 255, 0.72);
	color: var(--ink);
}

.silomap--all::after {
	content: "";
	position: absolute;
	inset: 0;
	border-radius: var(--r-md);
	border: 1.5px solid rgba(26, 123, 255, 0.4);
	box-shadow: 0 0 22px rgba(26, 123, 255, 0.2), inset 0 0 22px rgba(255, 255, 255, 0.5);
	pointer-events: none;
}

/* The spine that makes it one journey rather than five things in a box. */
.silomap--all .silomap__list::before {
	content: "";
	position: absolute;
	left: 0.55rem;
	top: 0.5rem;
	bottom: 0.5rem;
	width: 2px;
	border-radius: 2px;
	background: linear-gradient(180deg, var(--blue-600), var(--gold-500));
	opacity: 0.55;
}

@media (prefers-reduced-motion: reduce) {
	.silopill { opacity: 1 !important; transform: none !important; }
	.what-card:hover .silopill:not(.is-in),
	.what-card:hover .silopill.is-in { transform: none; opacity: 1; }
}

/* ==========================================================================
   THE GUIDANCE CONSOLE — For Parents / For Schools

   One dataset at two zoom levels. The scope pill slides, and the rows fan out
   or collapse — the only scale change on the page.
   ========================================================================== */

.fam__console {
	margin-top: clamp(2rem, 4vw, 3rem);
	border-radius: var(--r-lg);
	background: var(--ground-raise);
	box-shadow: inset 0 0 0 1px var(--line-soft), var(--sh-md);
	overflow: hidden;
}

.fam__chrome {
	display: flex;
	align-items: center;
	gap: 0.7rem;
	padding: 0.7rem clamp(1rem, 2vw, 1.4rem);
	border-bottom: 1px solid var(--line-soft);
	background: linear-gradient(180deg, rgba(10, 14, 26, 0.035), transparent);
}
.fam__chrome .rcon__badge { margin-left: 0; }

/* ---- the zoom control --------------------------------------------------- */

.fam__scopes {
	position: relative;
	display: flex;
	gap: 0.15rem;
	margin-left: auto;
	padding: 0.2rem;
	border-radius: var(--r-pill);
	background: var(--ground);
	box-shadow: inset 0 0 0 1px var(--line-soft);

	/* Without flex:none the chrome squeezes this below its content width and
	   the buttons overflow their own container to the LEFT — the first one
	   measured at offsetLeft -68, which put the sliding pill off the switch
	   entirely. A control must never be the thing that shrinks. */
	flex: none;
}
.fam__scope { white-space: nowrap; }

/* The pill travels rather than jumping, which is what makes the control read
   as one switch instead of two buttons. */
.fam__scopes-slide {
	position: absolute;
	top: 0.2rem;
	left: 0;
	height: calc(100% - 0.4rem);
	border-radius: var(--r-pill);
	background: var(--blue-600);
	box-shadow: 0 2px 8px rgba(11, 95, 234, 0.3);
	transition: transform 0.42s var(--ease-cam), width 0.42s var(--ease-cam);
}

.fam__scope {
	position: relative;
	z-index: 1;
	padding: 0.3rem 0.85rem;
	border-radius: var(--r-pill);
	font-size: var(--fs-xs);
	font-weight: 700;
	color: var(--ink-45);
	transition: color 0.3s var(--ease-cam);
}
.fam__scope.is-on { color: #fff; }

/* ---- views -------------------------------------------------------------- */

.fam__view[hidden] { display: none; }
.fam__view {
	display: grid;
	gap: 0.85rem;
	padding: clamp(1.1rem, 2.5vw, 1.7rem);
}

.fam__head { display: flex; align-items: center; gap: 0.9rem; }

/* A conic meter: cheap, precise, and it reads as a dashboard gauge. */
.fam__meter {
	position: relative;
	display: grid;
	place-items: center;
	width: 52px;
	height: 52px;
	flex: none;
	border-radius: 50%;
	background:
		conic-gradient(var(--blue-600) calc(var(--pct, 0) * 1%), var(--ink-07) 0);
}
.fam__meter::after {
	content: "";
	position: absolute;
	inset: 5px;
	border-radius: 50%;
	background: var(--ground-raise);
}
.fam__meter--cohort { background: conic-gradient(var(--gold-500) 100%, var(--ink-07) 0); }
.fam__meter-num {
	position: relative;
	z-index: 1;
	font-family: var(--font-mono);
	font-size: var(--fs-h4);
	font-weight: 700;
	font-variant-numeric: tabular-nums;
}

.fam__head-title { font-size: var(--fs-sm); font-weight: 700; letter-spacing: -0.02em; }
.fam__head-sub { font-size: var(--fs-xs); color: var(--ink-45); line-height: 1.5; margin-top: 0.15rem; }

/* ---- parent scope: the seven questions ---------------------------------- */

.fam__qs { display: grid; gap: 0.2rem; }

.famq {
	display: grid;
	grid-template-columns: 1.15rem minmax(0, 1fr) auto;
	align-items: center;
	gap: 0.6rem;
	padding: 0.42rem 0.6rem;
	border-radius: var(--r-sm);
	background: var(--ground);
	opacity: 0;
	transform: translate3d(0, 8px, 0);
	transition:
		opacity   0.45s var(--ease-cam) var(--d, 0ms),
		transform 0.45s var(--ease-cam) var(--d, 0ms);
}
.fam__view.is-on .famq { opacity: 1; transform: none; }
.famq.is-live { background: rgba(26, 123, 255, 0.08); }

.famq__mark { position: relative; width: 16px; height: 16px; }
.famq__mark > * { position: absolute; inset: 0; margin: auto; }

.famq__ring {
	width: 9px; height: 9px; border-radius: 50%;
	box-shadow: inset 0 0 0 1.5px var(--ink-12);
}
.famq__spin {
	width: 13px; height: 13px; border-radius: 50%;
	border: 2px solid rgba(26, 123, 255, 0.25);
	border-top-color: var(--blue-600);
	opacity: 0;
}
.famq__tick { width: 13px; height: 13px; color: #10b981; opacity: 0; }

.famq.is-live .famq__ring { opacity: 0; }
.famq.is-live .famq__spin { opacity: 1; animation: rspin 0.8s linear infinite; }
.famq.is-done .famq__ring { opacity: 0; }
.famq.is-done .famq__tick { opacity: 1; }

.famq__name { font-size: var(--fs-xs); font-weight: 600; color: var(--ink-45); }
.famq.is-done .famq__name { color: var(--ink); }
.famq.is-live .famq__name { color: var(--blue-700); }

.famq__state {
	font-family: var(--font-mono);
	font-size: 0.5625rem;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--ink-30);
}
.famq.is-done .famq__state { color: #0f7a5a; }
.famq.is-live .famq__state { color: var(--blue-600); }

/* ---- school scope: the cohort ------------------------------------------- */

.fam__table { display: grid; gap: 0.2rem; }

.fam__thead,
.famrow {
	display: grid;
	grid-template-columns: 6rem minmax(0, 1fr) 6.5rem 5.5rem;
	align-items: center;
	gap: 0.7rem;
	padding: 0.42rem 0.6rem;
}

.fam__thead {
	font-family: var(--font-mono);
	font-size: 0.5625rem;
	letter-spacing: 0.1em;
	text-transform: uppercase;
	color: var(--ink-30);
	border-bottom: 1px solid var(--line-soft);
}

.famrow {
	border-radius: var(--r-sm);
	background: var(--ground);
	opacity: 0;
	transform: translate3d(0, 10px, 0) scale(0.985);
	transition:
		opacity   0.5s var(--ease-cam) var(--d, 0ms),
		transform 0.5s var(--ease-cam) var(--d, 0ms),
		background 0.3s var(--ease-cam);
}
.fam__view.is-on .famrow { opacity: 1; transform: none; }
.famrow:hover { background: var(--ground-raise); }

.famrow__ref {
	font-family: var(--font-mono);
	font-size: 0.6875rem;
	font-weight: 600;
	color: var(--ink);
}
.famrow__focus { font-size: var(--fs-xs); color: var(--ink-45); }

.famrow__track { height: 5px; border-radius: 5px; background: var(--ink-07); overflow: hidden; }
.famrow__track i {
	display: block;
	width: 0;
	height: 100%;
	border-radius: inherit;
	background: linear-gradient(90deg, var(--blue-600), var(--blue-400));
	transition: width 0.7s var(--ease-cam) calc(var(--d, 0ms) + 200ms);
}
.fam__view.is-on .famrow__track i { width: var(--w, 0%); }

.famrow__stage {
	justify-self: start;
	padding: 0.16rem 0.5rem;
	border-radius: var(--r-pill);
	font-size: 0.5625rem;
	font-weight: 700;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--ink-60);
	background: var(--ink-07);
}
.famrow__stage[data-stage="3"] { color: var(--navy); background: var(--gold-500); }
.famrow__stage[data-stage="2"] { color: #fff; background: var(--blue-600); }

.fam__cohort-note,
.fam__note { font-size: var(--fs-xs); color: var(--ink-45); line-height: 1.5; }

.fam__label {
	font-size: var(--fs-xs);
	color: var(--ink-45);
	line-height: 1.5;
	margin: 0;
}

.fam__foot {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	flex-wrap: wrap;
	padding-top: 0.7rem;
	border-top: 1px solid var(--line-soft);
}

.fam__areas { display: flex; flex-wrap: wrap; gap: 0.35rem; }
.fam__areas li {
	padding: 0.28rem 0.6rem;
	border-radius: var(--r-sm);
	font-size: 0.6875rem;
	font-weight: 600;
	color: var(--ink-60);
	background: var(--ground);
	opacity: 0;
	transition: opacity 0.4s var(--ease-cam) var(--d, 0ms);
}
.fam__view.is-on .fam__areas li { opacity: 1; }

@media (max-width: 760px) {
	.fam__chrome { flex-wrap: wrap; }
	.fam__scopes { margin-left: 0; order: 3; width: 100%; justify-content: center; }
	.fam__thead { display: none; }
	.famrow {
		grid-template-columns: minmax(0, 1fr) auto;
		gap: 0.3rem 0.6rem;
	}
	.famrow__focus { grid-column: 1; font-size: 0.6875rem; }
	.famrow__track { grid-column: 1 / -1; }
	.famrow__stage { grid-row: 1; grid-column: 2; justify-self: end; }
}

@media (prefers-reduced-motion: reduce) {
	.famq, .famrow, .fam__areas li { opacity: 1 !important; transform: none !important; }
	.famq__spin { animation: none !important; }
	.fam__scopes-slide { transition: none !important; }
}

/* ==========================================================================
   THE ROI WORKSHEET — Student ROI

   A ledger that totals itself. Figures are tabular throughout, because a
   column of numbers that shifts while it counts reads as broken.
   ========================================================================== */

.roi__console {
	margin-top: clamp(2rem, 4vw, 3rem);
	border-radius: var(--r-lg);
	background: var(--ground-raise);
	box-shadow: inset 0 0 0 1px var(--line-soft), var(--sh-md);
	overflow: hidden;
}

.roi__chrome {
	display: flex;
	align-items: center;
	gap: 0.7rem;
	padding: 0.7rem clamp(1rem, 2vw, 1.4rem);
	border-bottom: 1px solid var(--line-soft);
	background: linear-gradient(180deg, rgba(10, 14, 26, 0.035), transparent);
}
.roi__chrome .rcon__badge { margin-left: auto; }

.roi__body {
	display: grid;
	grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
	gap: clamp(1rem, 2.5vw, 2rem);
	padding: clamp(1.1rem, 2.5vw, 1.7rem);
	align-items: start;
}

/* ---- bands -------------------------------------------------------------- */

.roi__inputs { display: grid; gap: 1rem; }
.roi__field { display: grid; gap: 0.45rem; }

.roi__label {
	display: flex;
	align-items: center;
	gap: 0.45rem;
	margin: 0;
	font-size: var(--fs-xs);
	font-weight: 700;
	letter-spacing: 0.1em;
	text-transform: uppercase;
	color: var(--ink-30);
}
.roi__label b {
	padding: 0.1rem 0.4rem;
	border-radius: var(--r-sm);
	font-family: var(--font-mono);
	font-size: 0.625rem;
	font-variant-numeric: tabular-nums;
	color: var(--blue-600);
	background: rgba(26, 123, 255, 0.1);
}
.roi__label--search { margin-top: 1.1rem; }

.roi__bands { display: flex; flex-wrap: wrap; gap: 0.35rem; }

.roi__band {
	padding: 0.4rem 0.75rem;
	border-radius: var(--r-pill);
	font-size: var(--fs-xs);
	font-weight: 600;
	font-variant-numeric: tabular-nums;
	color: var(--ink-60);
	background: var(--ground);
	box-shadow: inset 0 0 0 1px var(--line-soft);
	transition: all 0.3s var(--ease-cam);
}
.roi__band:hover { color: var(--ink); box-shadow: inset 0 0 0 1px var(--ink-12); }
.roi__band.is-on {
	color: #fff;
	background: var(--blue-600);
	box-shadow: 0 2px 10px rgba(11, 95, 234, 0.3);
}

/* ---- the ledger --------------------------------------------------------- */

.roi__ledger {
	padding: clamp(0.9rem, 2vw, 1.3rem);
	border-radius: var(--r-md);
	background: var(--ground);
	box-shadow: inset 0 0 0 1px var(--line-soft);
}

.roi__line,
.roi__total {
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto auto;
	align-items: baseline;
	gap: 0.75rem;
	padding-block: 0.4rem;
}

.roi__line-name { font-size: var(--fs-sm); font-weight: 600; color: var(--ink); }
.roi__line-calc {
	font-family: var(--font-mono);
	font-size: 0.6875rem;
	font-variant-numeric: tabular-nums;
	color: var(--ink-45);
}
.roi__line-sum {
	min-width: 5.5rem;
	text-align: right;
	font-family: var(--font-mono);
	font-size: var(--fs-sm);
	font-variant-numeric: tabular-nums;
	color: var(--ink);
}

/* Draws across as the ledger settles, the way a rule is ruled. */
.roi__rule {
	display: block;
	height: 1px;
	margin-block: 0.35rem;
	background: var(--ink-12);
	transform-origin: left center;
	animation: roi-rule 0.6s var(--ease-cam) both;
}
@keyframes roi-rule { from { transform: scaleX(0); } to { transform: scaleX(1); } }

.roi__total { padding-block: 0.55rem 0.2rem; }
.roi__total-name {
	grid-column: 1 / 3;
	font-size: var(--fs-sm);
	font-weight: 700;
	letter-spacing: -0.02em;
}
.roi__total-sum {
	min-width: 5.5rem;
	text-align: right;
	font-family: var(--font-mono);
	font-size: var(--fs-h4);
	font-weight: 700;
	font-variant-numeric: tabular-nums;
	color: var(--blue-600);
}

/* ---- the funding search ------------------------------------------------- */

.roi__sources { display: grid; gap: 0.18rem; }

.roi__source {
	display: grid;
	grid-template-columns: 1.1rem minmax(0, 1fr) auto;
	align-items: center;
	gap: 0.55rem;
	padding: 0.32rem 0.45rem;
	border-radius: var(--r-sm);
	transition: background 0.35s var(--ease-cam);
}
.roi__source.is-live { background: rgba(26, 123, 255, 0.08); }

.roi__source-mark { position: relative; width: 16px; height: 16px; }
.roi__source-mark > * { position: absolute; inset: 0; margin: auto; }

.roi__source-spin {
	width: 13px;
	height: 13px;
	border-radius: 50%;
	border: 2px solid rgba(26, 123, 255, 0.25);
	border-top-color: var(--blue-600);
	opacity: 0;
}
.roi__source.is-live .roi__source-spin { opacity: 1; animation: rspin 0.7s linear infinite; }

.roi__source-tick {
	width: 13px;
	height: 13px;
	color: #10b981;
	opacity: 0;
	transform: scale(0.4);
	transition: opacity 0.25s var(--ease-cam), transform 0.25s var(--ease-lens);
}
.roi__source.is-done .roi__source-tick { opacity: 1; transform: none; }

.roi__source-name {
	font-size: var(--fs-xs);
	font-weight: 600;
	color: var(--ink-30);
	transition: color 0.35s var(--ease-cam);
}
.roi__source.is-live .roi__source-name { color: var(--blue-700); }
.roi__source.is-done .roi__source-name { color: var(--ink); }

/* "Checked", never "secured" — the word matters more than the styling. */
.roi__source-state {
	font-family: var(--font-mono);
	font-size: 0.5625rem;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: #0f7a5a;
	opacity: 0;
	transition: opacity 0.25s var(--ease-cam);
}
.roi__source.is-done .roi__source-state { opacity: 1; }

.roi__outcome {
	margin-top: 0.9rem;
	padding-top: 0.75rem;
	border-top: 1px solid var(--line-soft);
	font-size: var(--fs-xs);
	line-height: 1.5;
	color: var(--ink-45);
}

/* ---- the thinking, and the questions ------------------------------------ */

.roi__after {
	display: grid;
	grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
	gap: clamp(1.25rem, 3vw, 2.5rem);
	margin-top: clamp(1.75rem, 3vw, 2.5rem);
	align-items: start;
}
.roi__chain-wrap, .roi__questions { display: grid; gap: 0.6rem; align-content: start; }

.roi__questions ul { display: grid; gap: 0; }
.roi__questions li {
	display: grid;
	grid-template-columns: 1.8rem minmax(0, 1fr);
	gap: 0.6rem;
	align-items: baseline;
	padding-block: 0.5rem;
	border-bottom: 1px solid var(--line-soft);
	font-size: var(--fs-sm);
	color: var(--ink-60);
	transition: color 0.3s var(--ease-cam), padding-left 0.3s var(--ease-cam);
}
.roi__questions li:hover { color: var(--ink); padding-left: 0.35rem; }
.roi__questions li i {
	font-family: var(--font-mono);
	font-style: normal;
	font-size: 0.625rem;
	color: var(--ink-30);
}

.roi__note { margin-top: clamp(1.5rem, 3vw, 2rem); }

@media (max-width: 900px) {
	.roi__body, .roi__after { grid-template-columns: minmax(0, 1fr); }
}

@media (prefers-reduced-motion: reduce) {
	.roi__rule { animation: none; }
	.roi__source-spin { animation: none !important; opacity: 0 !important; }
	.roi__source-tick { transition: none !important; }
}

/* ==========================================================================
   THE ADMISSIONS TRACKER — Global Admissions

   One destination, one arc, one label — and an application walking its nine
   stages on a playhead, taking a stamp at each.
   ========================================================================== */

.adm__console {
	margin-top: clamp(2rem, 4vw, 3rem);
	border-radius: var(--r-lg);
	background: var(--ground-raise);
	box-shadow: inset 0 0 0 1px var(--line-soft), var(--sh-md);
	overflow: hidden;
}

.adm__chrome {
	display: flex;
	align-items: center;
	gap: 0.7rem;
	padding: 0.7rem clamp(1rem, 2vw, 1.4rem);
	border-bottom: 1px solid var(--line-soft);
	background: linear-gradient(180deg, rgba(10, 14, 26, 0.035), transparent);
}

.adm__status {
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
	margin-left: auto;
	font-family: var(--font-mono);
	font-size: 0.6875rem;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--blue-700);
}
.adm__status-dot {
	width: 7px;
	height: 7px;
	border-radius: 50%;
	background: var(--blue-600);
	animation: cmpx-pulse 1.1s var(--ease-io) infinite;
}
.adm__status.is-done { color: #0f7a5a; }
.adm__status.is-done .adm__status-dot { background: #10b981; animation: none; }

.adm__body {
	display: grid;
	grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
	gap: clamp(1rem, 2.5vw, 2rem);
	padding: clamp(1.1rem, 2.5vw, 1.7rem);
	align-items: start;
}

.adm__left, .adm__right { display: grid; gap: 0.6rem; align-content: start; }

/* ---- destinations ------------------------------------------------------- */

.adm__dests { display: flex; flex-wrap: wrap; gap: 0.35rem; }

.adm__dest {
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
	padding: 0.38rem 0.7rem;
	border-radius: var(--r-pill);
	font-size: var(--fs-xs);
	font-weight: 600;
	color: var(--ink-60);
	background: var(--ground);
	box-shadow: inset 0 0 0 1px var(--line-soft);
	transition: all 0.3s var(--ease-cam);
}
.adm__dest:hover { color: var(--ink); box-shadow: inset 0 0 0 1px var(--ink-12); }
.adm__dest-dot {
	width: 6px;
	height: 6px;
	border-radius: 50%;
	background: var(--ink-12);
	transition: background 0.3s var(--ease-cam);
}
.adm__dest.is-on {
	color: #fff;
	background: var(--blue-600);
	box-shadow: 0 2px 10px rgba(11, 95, 234, 0.3);
}
.adm__dest.is-on .adm__dest-dot { background: var(--gold-500); }

/* ---- the map ------------------------------------------------------------ */

.adm__map {
	margin-top: 0.3rem;
	border-radius: var(--r-md);
	background: linear-gradient(160deg, rgba(26, 123, 255, 0.05), transparent 60%), var(--ground);
	box-shadow: inset 0 0 0 1px var(--line-soft);
	overflow: hidden;
}
.adm__map svg { width: 100%; height: auto; display: block; }

.adm__grid line { stroke: var(--ink-07); stroke-width: 1; }
.adm__pin { fill: var(--ink-12); }

.adm__arc { stroke-width: 2.2; stroke-linecap: round; }

.adm__src circle { fill: var(--ink); }
.adm__src text {
	font-family: var(--font-mono);
	font-size: 9px;
	fill: var(--ink-45);
	letter-spacing: 0.04em;
}

/* The travelling pulse and its trail. Positioned by motion.js from the arc's
   real geometry, so it follows the route rather than approximating it. */
.adm__pulse {
	fill: #fff;
	stroke: var(--blue-500);
	stroke-width: 2;
	opacity: 0;
	filter: drop-shadow(0 0 6px rgba(26, 123, 255, 0.9));
}
.adm__trail {
	fill: rgba(26, 123, 255, 0.35);
	opacity: 0;
	filter: blur(3px);
}

.adm__halo {
	fill: rgba(255, 193, 7, 0.28);
	transform-box: fill-box;
	transform-origin: center;
	animation: adm-ripple 2.6s var(--ease-io) infinite;
}
@keyframes adm-ripple {
	0%, 70%, 100% { transform: scale(1);   opacity: 1; }
	85%           { transform: scale(1.5); opacity: 0.35; }
}

.adm__dot  { fill: var(--gold-500); stroke: #fff; stroke-width: 1.5; }
.adm__dlabel {
	font-size: 11px;
	font-weight: 700;
	fill: var(--ink);
}

/* ---- the nine stages ---------------------------------------------------- */

.adm__stages { display: grid; gap: 0.22rem; }

.astage {
	position: relative;
	display: grid;
	grid-template-columns: 1.6rem 1.15rem minmax(0, 1fr) auto;
	align-items: center;
	gap: 0.55rem;
	padding: 0.42rem 0.6rem;
	border-radius: var(--r-sm);
	background: var(--ground);
	transition: background 0.4s var(--ease-cam), box-shadow 0.4s var(--ease-cam);
}
.astage.is-live {
	background: rgba(26, 123, 255, 0.08);
	box-shadow: inset 0 0 0 1px rgba(26, 123, 255, 0.2);
}
.astage.is-done { background: var(--ground-raise); }

.astage__n {
	font-family: var(--font-mono);
	font-size: 0.625rem;
	color: var(--ink-30);
}
.astage.is-done .astage__n { color: var(--ink-45); }
.astage.is-live .astage__n { color: var(--blue-600); }

/* One 18px box holds all three marks; only one is ever shown. */
.astage__mark { position: relative; width: 18px; height: 18px; }
.astage__mark > * {
	position: absolute;
	inset: 0;
	margin: auto;
	transition: opacity 0.3s var(--ease-cam), transform 0.3s var(--ease-cam);
}

.astage__pending {
	width: 9px;
	height: 9px;
	border-radius: 50%;
	box-shadow: inset 0 0 0 1.5px var(--ink-12);
	opacity: 1;
}
.astage__spin {
	width: 14px;
	height: 14px;
	border-radius: 50%;
	border: 2px solid rgba(26, 123, 255, 0.25);
	border-top-color: var(--blue-600);
	opacity: 0;
}
.astage__tick { width: 14px; height: 14px; color: #10b981; opacity: 0; transform: scale(0.4); }

.astage.is-live .astage__pending { opacity: 0; }
.astage.is-live .astage__spin { opacity: 1; animation: rspin 0.7s linear infinite; }
.astage.is-done .astage__pending { opacity: 0; }
.astage.is-done .astage__tick { opacity: 1; transform: none; }

.astage__name {
	font-size: var(--fs-xs);
	font-weight: 600;
	color: var(--ink-45);
	transition: color 0.4s var(--ease-cam);
}
.astage.is-done .astage__name { color: var(--ink); }
.astage.is-live .astage__name { color: var(--blue-700); }

/* The stamp. Lands rotated and slightly oversized, the way an inked one does. */
.astage__stamp {
	padding: 0.1rem 0.42rem;
	border-radius: 3px;
	font-size: 0.5rem;
	font-weight: 800;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: #0f7a5a;
	box-shadow: inset 0 0 0 1.5px rgba(16, 185, 129, 0.5);
	opacity: 0;
	transform: rotate(-14deg) scale(1.6);
	transition: opacity 0.28s var(--ease-cam), transform 0.28s var(--ease-lens);
}
.astage.is-done .astage__stamp { opacity: 1; transform: rotate(-7deg) scale(1); }

/* ---- what's covered ----------------------------------------------------- */

.adm__covers { margin-top: clamp(1.5rem, 3vw, 2.25rem); display: grid; gap: 0.6rem; }
.adm__covers ul { display: flex; flex-wrap: wrap; gap: 0.35rem; }
.adm__covers li {
	padding: 0.3rem 0.62rem;
	border-radius: var(--r-sm);
	font-size: 0.6875rem;
	font-weight: 600;
	color: var(--ink-60);
	background: var(--ground-raise);
	box-shadow: inset 0 0 0 1px var(--line-soft);
	transition: color 0.3s var(--ease-cam), box-shadow 0.3s var(--ease-cam);
}
.adm__covers li:hover {
	color: var(--blue-700);
	box-shadow: inset 0 0 0 1px rgba(26, 123, 255, 0.28);
}

@media (max-width: 900px) {
	.adm__body { grid-template-columns: minmax(0, 1fr); }
}

@media (prefers-reduced-motion: reduce) {
	.adm__status-dot, .astage.is-live .astage__spin,
	.adm__halo { animation: none !important; }
	.astage__stamp { transition: none !important; }
	.adm__arc { stroke-dashoffset: 0 !important; }
	.adm__pulse, .adm__trail { display: none; }
}

/* ==========================================================================
   THE COMPARATOR CONSOLE — Your 100XD Future Map

   Inputs on the left, ranked routes on the right, and the routes physically
   travel when the ranking changes. The travel is a FLIP animation driven from
   animated.js; everything else is here.
   ========================================================================== */

.cmpx {
	margin-top: clamp(2rem, 4vw, 3rem);
	border-radius: var(--r-lg);
	background: var(--ground-raise);
	box-shadow: inset 0 0 0 1px var(--line-soft), var(--sh-md);
	overflow: hidden;
}

.cmpx__chrome {
	display: flex;
	align-items: center;
	gap: 0.7rem;
	padding: 0.7rem clamp(1rem, 2vw, 1.4rem);
	border-bottom: 1px solid var(--line-soft);
	background: linear-gradient(180deg, rgba(10, 14, 26, 0.035), transparent);
}

/* The engine saying what it is doing. Idle green, working blue and pulsing. */
.cmpx__status {
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
	margin-left: auto;
	font-family: var(--font-mono);
	font-size: 0.6875rem;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--ink-45);
}
.cmpx__status-dot {
	width: 7px;
	height: 7px;
	border-radius: 50%;
	background: #10b981;
	transition: background 0.3s var(--ease-cam);
}
.cmpx__status.is-busy { color: var(--blue-700); }
.cmpx__status.is-busy .cmpx__status-dot {
	background: var(--blue-600);
	animation: cmpx-pulse 0.6s var(--ease-io) infinite;
}
@keyframes cmpx-pulse {
	0%, 100% { transform: scale(1);   opacity: 1; }
	50%      { transform: scale(1.7); opacity: 0.5; }
}

.cmpx__body {
	display: grid;
	grid-template-columns: minmax(0, 20rem) minmax(0, 1fr);
	gap: clamp(1rem, 2.5vw, 2rem);
	padding: clamp(1.1rem, 2.5vw, 1.7rem);
	align-items: start;
}

/* ---- inputs ------------------------------------------------------------- */

.cmpx__inputs { display: grid; gap: 1.1rem; }
.cmpx__field { display: grid; gap: 0.5rem; }

.cmpx__label {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: 0.5rem;
	margin: 0;
	font-size: var(--fs-xs);
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--ink-30);
}
/* The live value. Tabular so it does not jitter while a slider is dragged. */
.cmpx__label b {
	font-family: var(--font-mono);
	font-size: var(--fs-sm);
	font-variant-numeric: tabular-nums;
	letter-spacing: 0;
	text-transform: none;
	color: var(--blue-600);
}

.cmpx__range {
	-webkit-appearance: none;
	appearance: none;
	width: 100%;
	height: 4px;
	border-radius: 4px;
	background: linear-gradient(90deg, var(--blue-600), var(--gold-500));
	cursor: pointer;
}
.cmpx__range::-webkit-slider-thumb {
	-webkit-appearance: none;
	width: 20px;
	height: 20px;
	border-radius: 50%;
	background: #fff;
	box-shadow: 0 1px 3px rgba(10, 14, 26, 0.2), inset 0 0 0 2px var(--blue-600);
	cursor: grab;
	transition: transform 0.2s var(--ease-cam);
}
.cmpx__range::-webkit-slider-thumb:active { transform: scale(1.15); cursor: grabbing; }
.cmpx__range::-moz-range-thumb {
	width: 20px;
	height: 20px;
	border: 0;
	border-radius: 50%;
	background: #fff;
	box-shadow: 0 1px 3px rgba(10, 14, 26, 0.2), inset 0 0 0 2px var(--blue-600);
	cursor: grab;
}

.cmpx__goals { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.cmpx__goal {
	padding: 0.42rem 0.8rem;
	border-radius: var(--r-pill);
	font-size: var(--fs-xs);
	font-weight: 600;
	color: var(--ink-60);
	background: var(--ground);
	box-shadow: inset 0 0 0 1px var(--line-soft);
	transition: all 0.3s var(--ease-cam);
}
.cmpx__goal:hover { color: var(--ink); box-shadow: inset 0 0 0 1px var(--ink-12); }
.cmpx__goal.is-on {
	color: #fff;
	background: var(--blue-600);
	box-shadow: inset 0 0 0 1px var(--blue-600), 0 2px 10px rgba(11, 95, 234, 0.3);
}

.cmpx__axes { display: flex; flex-wrap: wrap; gap: 0.3rem; }
.cmpx__axes li {
	padding: 0.26rem 0.55rem;
	border-radius: var(--r-sm);
	font-size: 0.6875rem;
	font-weight: 600;
	color: var(--ink-45);
	background: var(--ground);
}

.cmpx__note { margin-top: 0.2rem; }

/* ---- results ------------------------------------------------------------ */

.cmpx__results { display: grid; gap: 0.6rem; }

.cmpx__reslabel {
	display: flex;
	align-items: center;
	gap: 0.45rem;
	font-size: var(--fs-xs);
	font-weight: 700;
	letter-spacing: 0.1em;
	text-transform: uppercase;
	color: var(--ink-30);
}
.cmpx__reslabel b {
	padding: 0.1rem 0.4rem;
	border-radius: var(--r-sm);
	font-size: 0.625rem;
	color: var(--blue-600);
	background: rgba(26, 123, 255, 0.1);
}

/* Flex column, because `order` is what the ranking writes. */
.cmpx__list { display: flex; flex-direction: column; gap: 0.6rem; }

.rt {
	display: grid;
	gap: 0.6rem;
	padding: 0.9rem 1rem;
	border-radius: var(--r-md);
	background: var(--ground);
	box-shadow: inset 0 0 0 1px var(--line-soft);
	will-change: transform;
	transition: box-shadow 0.4s var(--ease-cam), background 0.4s var(--ease-cam);
}
.rt.is-best {
	background: var(--ground-raise);
	box-shadow: inset 0 0 0 1px rgba(26, 123, 255, 0.28), var(--sh-sm);
}

.rt__head { display: flex; align-items: center; gap: 0.55rem; }

.rt__rank {
	font-family: var(--font-mono);
	font-size: 0.6875rem;
	font-variant-numeric: tabular-nums;
	color: var(--ink-30);
}

.rt__id {
	display: grid;
	place-items: center;
	width: 22px;
	height: 22px;
	border-radius: 50%;
	font-size: 0.6875rem;
	font-weight: 700;
	color: var(--ink-60);
	background: var(--ink-07);
	transition: all 0.4s var(--ease-cam);
}
.rt.is-best .rt__id { color: #fff; background: var(--blue-600); }

.rt__name { font-size: var(--fs-sm); font-weight: 700; letter-spacing: -0.02em; }

/* Only the leader carries the label, so it reads as a verdict. */
.rt__best {
	margin-left: auto;
	padding: 0.16rem 0.5rem;
	border-radius: var(--r-pill);
	font-size: 0.5625rem;
	font-weight: 700;
	letter-spacing: 0.1em;
	text-transform: uppercase;
	color: var(--navy);
	background: var(--gold-500);
	opacity: 0;
	transform: scale(0.8);
	transition: opacity 0.4s var(--ease-cam), transform 0.4s var(--ease-cam);
}
.rt.is-best .rt__best { opacity: 1; transform: none; }

.rt__steps { display: flex; flex-wrap: wrap; align-items: center; gap: 0.3rem; }
.rt__step {
	padding: 0.22rem 0.5rem;
	border-radius: var(--r-sm);
	font-size: 0.625rem;
	font-weight: 600;
	color: var(--ink-60);
	background: var(--ground-raise);
	box-shadow: inset 0 0 0 1px var(--line-soft);
}
.rt.is-best .rt__step { color: var(--blue-700); }
.rt__arrow { font-size: 0.625rem; color: var(--ink-30); }

.rt__metrics {
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	gap: 0.5rem 0.9rem;
}
@media (max-width: 560px) { .rt__metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.rt__metric { display: grid; gap: 0.25rem; }
.rt__mlabel {
	display: flex;
	justify-content: space-between;
	font-size: 0.5625rem;
	font-weight: 700;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--ink-30);
}
.rt__mlabel b { color: var(--ink); font-variant-numeric: tabular-nums; }

.rt__track { height: 4px; border-radius: 4px; background: var(--ink-07); overflow: hidden; }
.rt__fill {
	display: block;
	height: 100%;
	width: var(--v, 0%);
	border-radius: inherit;
	background: linear-gradient(90deg, var(--blue-600), var(--blue-400));
	transition: width 0.7s var(--ease-cam) var(--d, 0ms);
}
.rt.is-best .rt__fill { background: linear-gradient(90deg, var(--blue-600), var(--gold-500)); }

@media (max-width: 900px) {
	.cmpx__body { grid-template-columns: minmax(0, 1fr); }
}

@media (prefers-reduced-motion: reduce) {
	.cmpx__status.is-busy .cmpx__status-dot { animation: none; }
	.rt { transition: none !important; }
	.rt__fill { transition: none !important; }
}

/* ==========================================================================
   THE LAMP — Why 100XD?

   A light the reader carries. motion.js writes --lamp-y (where the light is)
   on the section and --lit (0→1) on each row; everything below is that one
   number turned into focus, lift, colour and a change of drawing.

   Pointer-driven on purpose. Every other instrument on this page is moved by
   the scrollbar, and the six points here have no order — a mechanic that
   marched through them would be asserting one.
   ========================================================================== */

.lamp {
	position: relative;
	isolation: isolate;
	--lamp-y: 50%;
}

/* The pool of light itself. Follows the pointer down the section and dims
   away entirely when the section is showing its resting, fully-lit state. */
.lamp__pool {
	position: absolute;
	left: 0;
	right: 0;
	top: 0;
	height: 100%;
	z-index: 0;
	pointer-events: none;
	opacity: 0;
	transition: opacity 0.5s var(--ease-cam);
	background: radial-gradient(
		60% 220px at 50% var(--lamp-y),
		rgba(255, 255, 255, 0.85) 0%,
		rgba(26, 123, 255, 0.07) 45%,
		transparent 72%);
}
.lamp.is-live .lamp__pool { opacity: 1; }

.lamp .wrap { position: relative; z-index: 1; }

.lamp__hint { font-size: var(--fs-sm); color: var(--ink-45); }
/* No pointer, no instruction to move one. */
@media (hover: none) { .lamp__hint { display: none; } }

.lamp__rows { margin-top: clamp(1.75rem, 3vw, 2.5rem); }

/* ---- one row ------------------------------------------------------------ */

.lrow {
	--lit: 1;
	position: relative;
	display: grid;
	grid-template-columns: 2.5rem 56px minmax(0, 1fr);
	align-items: start;
	gap: clamp(0.85rem, 2vw, 1.6rem);
	padding: clamp(1rem, 2vw, 1.5rem) clamp(0.75rem, 1.5vw, 1.25rem);
	border-radius: var(--r-md);
	border-bottom: 1px solid var(--line-soft);

	/* Lift toward the light, and take a little of it on the surface. */
	transform: translate3d(0, calc(var(--lit) * -3px), 0);
	background: rgba(255, 255, 255, calc(var(--lit) * 0.55));
	box-shadow: 0 calc(var(--lit) * 6px) calc(var(--lit) * 26px)
		rgba(10, 14, 26, calc(var(--lit) * 0.05));
	transition:
		transform  0.45s var(--ease-cam),
		background 0.45s var(--ease-cam),
		box-shadow 0.45s var(--ease-cam);
}

.lrow:focus-visible { outline: 2px solid var(--blue-600); outline-offset: 2px; }

.lrow__n {
	font-family: var(--font-mono);
	font-size: var(--fs-xs);
	color: var(--ink-30);
	padding-top: 0.3rem;
	transition: color 0.45s var(--ease-cam);
}
.lrow.is-lit .lrow__n { color: var(--blue-600); }

/* ---- the two drawings --------------------------------------------------- */

.lrow__glyph {
	position: relative;
	width: 56px;
	height: 40px;
	color: var(--ink-30);
	transition: color 0.45s var(--ease-cam);
}
.lrow.is-lit .lrow__glyph { color: var(--blue-600); }

.lrow__glyph svg { width: 100%; height: 100%; display: block; overflow: visible; }

/* Both drawings occupy the same box; the light decides which one is real.
   The one going out also shrinks slightly, so the swap reads as a change of
   shape rather than two pictures blinking. */
.lrow__glyph .g-usual,
.lrow__glyph .g-ours {
	transform-origin: 28px 20px;
	transition:
		opacity   0.5s var(--ease-lens),
		transform 0.5s var(--ease-lens);
}
.lrow__glyph .g-usual {
	opacity: calc(1 - var(--lit));
	transform: scale(calc(1 - var(--lit) * 0.12));
}
.lrow__glyph .g-ours {
	opacity: var(--lit);
	transform: scale(calc(0.88 + var(--lit) * 0.12));
}

/* ---- the two halves of the sentence ------------------------------------- */

.lrow__text { display: grid; gap: 0.3rem; }

.lrow__name {
	font-size: var(--fs-h4);
	font-weight: 700;
	letter-spacing: -0.028em;
	transition: color 0.45s var(--ease-cam);
}
.lrow.is-lit .lrow__name { color: var(--blue-600); }

.lrow__tag {
	display: inline-block;
	margin-right: 0.45rem;
	font-size: 0.625rem;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.1em;
	color: var(--ink-30);
	vertical-align: 0.08em;
}
.lrow__tag--ours { color: var(--blue-500); }

.lrow__usual,
.lrow__ours {
	font-size: var(--fs-sm);
	line-height: 1.55;
	transition: color 0.45s var(--ease-cam), opacity 0.45s var(--ease-cam);
}

/* The pattern recedes as its answer lands — colour and weight carry that, not
   a strike-through, which would read as "wrong" rather than "superseded". */
.lrow__usual { color: var(--ink-45); opacity: calc(1 - var(--lit) * 0.45); }
.lrow__ours  { color: var(--ink-60); opacity: calc(0.45 + var(--lit) * 0.55); }
.lrow.is-lit .lrow__ours { color: var(--ink); }

/* A hairline that grows down the lit row's leading edge. */
.lrow__edge {
	position: absolute;
	left: 0;
	top: 50%;
	width: 2px;
	height: calc(var(--lit) * 62%);
	border-radius: 2px;
	transform: translateY(-50%);
	background: linear-gradient(180deg, var(--blue-600), var(--gold-500));
	opacity: var(--lit);
	transition: height 0.45s var(--ease-cam), opacity 0.45s var(--ease-cam);
}

/* Resting state, before a pointer has ever arrived: everything readable, no
   row singled out. */
.lamp.is-flat .lrow { background: none; box-shadow: none; transform: none; }
.lamp.is-flat .lrow__edge { opacity: 0.35; }

@media (max-width: 720px) {
	.lrow {
		grid-template-columns: 2.25rem minmax(0, 1fr);
		gap: 0.5rem 0.75rem;
		padding-inline: 0;
	}
	.lrow__glyph { grid-column: 2; margin-bottom: 0.2rem; }
	.lrow__text  { grid-column: 2; }
}

@media (prefers-reduced-motion: reduce) {
	.lamp__pool { display: none; }
	.lrow { --lit: 1 !important; transform: none !important; }
	.lrow__glyph .g-usual { opacity: 0; }
	.lrow__glyph .g-ours  { opacity: 1; transform: none; }
}

/* ==========================================================================
   THE BRIDGE — Technology
   ========================================================================== */

.tbridge__frame {
	margin-top: clamp(2rem, 4vw, 3rem);
	padding: clamp(1.5rem, 3vw, 2.5rem);
	border-radius: var(--r-lg);
	background: var(--ground-raise);
	box-shadow: inset 0 0 0 1px var(--line-soft), var(--sh-sm);
	text-align: center;
}

.tbridge__lede,
.tbridge__result {
	font-size: var(--fs-sm);
	color: var(--ink-45);
}
.tbridge__result { margin-top: 1.4rem; }

.tbridge__graph {
	display: grid;
	grid-template-columns: auto minmax(0, 1fr) auto;
	align-items: center;
	gap: clamp(0.75rem, 2vw, 1.5rem);
	margin-top: 1.25rem;
	text-align: left;
}

.tnode { display: grid; gap: 0.35rem; justify-items: center; max-width: 9rem; }
.tnode__label {
	font-size: var(--fs-sm);
	font-weight: 700;
	letter-spacing: -0.02em;
	text-align: center;
	line-height: 1.25;
}

.tnode__dot {
	width: 13px;
	height: 13px;
	border-radius: 50%;
	background: var(--ground);
	box-shadow: 0 0 0 2px var(--ink-12);
	transition: box-shadow 0.5s var(--ease-cam), background 0.5s var(--ease-cam);
}
.tnode--from .tnode__dot { background: var(--ink); box-shadow: 0 0 0 3px var(--ink-12); }

/* The destination only resolves once every rail has lit — which is the
   section's actual claim: the connection is what produces the pathway.
   .is-complete is written by motion.js onto the [data-steps] host. */
.tbridge__graph.is-complete .tnode--to .tnode__dot {
	background: var(--gold-500);
	box-shadow: 0 0 0 3px rgba(255, 193, 7, 0.3), 0 0 22px rgba(255, 193, 7, 0.55);
}

.trails { display: grid; gap: 0.5rem; }

.trail {
	display: grid;
	grid-template-columns: minmax(6.5rem, auto) minmax(0, 1fr);
	align-items: center;
	gap: 0.75rem;
}

.trail__name {
	font-size: var(--fs-xs);
	font-weight: 600;
	color: var(--ink-45);
	transition: color 0.45s var(--ease-cam);
	text-align: right;
}
.trail.is-on .trail__name,
.trail.is-past .trail__name { color: var(--ink); }

.trail__track {
	position: relative;
	height: 2px;
	border-radius: 2px;
	background: var(--ink-07);
	overflow: hidden;
}

.trail__pulse {
	position: absolute;
	inset: 0 auto 0 0;
	width: 0;
	border-radius: inherit;
	background: linear-gradient(90deg, var(--blue-600), var(--gold-500));
	transition: width 0.7s var(--ease-cam);
}
.trail.is-on .trail__pulse,
.trail.is-past .trail__pulse { width: 100%; }

@media (max-width: 720px) {
	.tbridge__graph { grid-template-columns: minmax(0, 1fr); gap: 1rem; }
	.tnode { max-width: none; }
	.trail { grid-template-columns: minmax(5.5rem, auto) minmax(0, 1fr); }
}

@media (prefers-reduced-motion: reduce) {
	.trail__pulse { width: 100% !important; transition: none !important; }
	.trail__name { color: var(--ink) !important; }
}

/* ==========================================================================
 * Hero art — two bespoke instruments
 *
 * page-about.php and page-contact.php used to borrow the `roi` and `support`
 * Lottie files that the homepage service grid already shows. Reusing an
 * animation across two pages is the one thing this build is not allowed to do,
 * so both heroes now carry their own mechanic, hand-built and used nowhere
 * else:
 *
 *   .cvg    About    — six silos wiring themselves into one trajectory.
 *                      Radial accumulation: links draw inward one at a time
 *                      and stay lit, so the ecosystem visibly assembles.
 *   .rdesk  Contact  — an enquiry travelling a track, being classified, and
 *                      dropping into the right advisory lane. Lateral travel
 *                      plus a lane switch.
 *
 * Both are pure CSS over inline markup: no library, no JS, no IntersectionObserver
 * that can fail and leave a blank panel. They run on load and keep running.
 *
 * One rule governs the timing throughout. Where every element shares a state
 * (all six links hold, then all six reset together), the stagger is written
 * into explicit per-element keyframes, because animation-delay shifts an
 * element's WHOLE cycle and would make the first link fade out while the last
 * was still arriving. Where only one element is ever active (the routing
 * lanes, the caption ticker), animation-delay is correct and is used.
 * ========================================================================== */

/* --------------------------------------------------------------------------
 * About — convergence
 * -------------------------------------------------------------------------- */

.cvg {
	position: relative;
	display: flex;
	flex-direction: column;
	gap: 0.85rem;
	width: 100%;
}

.cvg__svg {
	display: block;
	width: 100%;
	height: auto;
	overflow: visible;
}

/* Core — the individual everything converges on. */
.cvg__halo {
	fill: var(--blue-600);
	transform-origin: 210px 160px;
	animation: cvgHalo 12s ease-in-out infinite;
}

.cvg__ring {
	fill: none;
	stroke: var(--blue-600);
	stroke-width: 1.5;
	transform-origin: 210px 160px;
	animation: cvgRing 12s ease-in-out infinite;
}

.cvg__disc {
	fill: var(--ground-raise);
	stroke: var(--line);
	stroke-width: 1;
}

.cvg__mark { fill: var(--blue-600); }

@keyframes cvgRing {
	0%, 4%   { transform: scale(0.93); opacity: 0.14; }
	58%, 88% { transform: scale(1);    opacity: 0.48; }
	100%     { transform: scale(0.93); opacity: 0.14; }
}

@keyframes cvgHalo {
	0%, 4%   { transform: scale(0.78); opacity: 0.03; }
	58%, 88% { transform: scale(1.06); opacity: 0.10; }
	100%     { transform: scale(0.78); opacity: 0.03; }
}

/*
 * Links. Six explicit blocks — see the timing rule at the top of this section.
 * Each draws in its own 1.08s slot, holds while the rest arrive, and all six
 * release together at 88%.
 */
.cvg__link {
	fill: none;
	stroke: var(--blue-600);
	stroke-width: 1.75;
	stroke-linecap: round;
	stroke-dasharray: var(--len, 200);
	stroke-dashoffset: var(--len, 200);
	opacity: 0;
}

.cvg__link--0 { animation: cvgDraw0 12s linear infinite; }
.cvg__link--1 { animation: cvgDraw1 12s linear infinite; }
.cvg__link--2 { animation: cvgDraw2 12s linear infinite; }
.cvg__link--3 { animation: cvgDraw3 12s linear infinite; }
.cvg__link--4 { animation: cvgDraw4 12s linear infinite; }
.cvg__link--5 { animation: cvgDraw5 12s linear infinite; }

@keyframes cvgDraw0 {
	0%, 4%    { stroke-dashoffset: var(--len); opacity: 0; }
	5%        { opacity: 0.8; }
	13%, 88%  { stroke-dashoffset: 0; opacity: 0.8; }
	96%, 100% { stroke-dashoffset: 0; opacity: 0; }
}
@keyframes cvgDraw1 {
	0%, 13%   { stroke-dashoffset: var(--len); opacity: 0; }
	14%       { opacity: 0.8; }
	22%, 88%  { stroke-dashoffset: 0; opacity: 0.8; }
	96%, 100% { stroke-dashoffset: 0; opacity: 0; }
}
@keyframes cvgDraw2 {
	0%, 22%   { stroke-dashoffset: var(--len); opacity: 0; }
	23%       { opacity: 0.8; }
	31%, 88%  { stroke-dashoffset: 0; opacity: 0.8; }
	96%, 100% { stroke-dashoffset: 0; opacity: 0; }
}
@keyframes cvgDraw3 {
	0%, 31%   { stroke-dashoffset: var(--len); opacity: 0; }
	32%       { opacity: 0.8; }
	40%, 88%  { stroke-dashoffset: 0; opacity: 0.8; }
	96%, 100% { stroke-dashoffset: 0; opacity: 0; }
}
@keyframes cvgDraw4 {
	0%, 40%   { stroke-dashoffset: var(--len); opacity: 0; }
	41%       { opacity: 0.8; }
	49%, 88%  { stroke-dashoffset: 0; opacity: 0.8; }
	96%, 100% { stroke-dashoffset: 0; opacity: 0; }
}
@keyframes cvgDraw5 {
	0%, 49%   { stroke-dashoffset: var(--len); opacity: 0; }
	50%       { opacity: 0.8; }
	58%, 88%  { stroke-dashoffset: 0; opacity: 0.8; }
	96%, 100% { stroke-dashoffset: 0; opacity: 0; }
}

/*
 * Signal flow. A short dash chasing along the same path, shown only once the
 * whole web is wired (58%-88%) — so it needs one shared block, not six. Built
 * from stroke-dasharray rather than offset-path: the dash trick works
 * everywhere, and a browser without offset-path support would park the bead
 * at the SVG origin instead of failing quietly.
 */
.cvg__flow {
	fill: none;
	stroke: var(--gold-500);
	stroke-width: 2.25;
	stroke-linecap: round;
	stroke-dasharray: 2 16;
	opacity: 0;
	animation: cvgFlowRun 1.6s linear infinite,
	           cvgFlowOn 12s linear infinite;
}

@keyframes cvgFlowRun {
	0%   { stroke-dashoffset: 36; }
	100% { stroke-dashoffset: 0; }
}

@keyframes cvgFlowOn {
	0%, 58%   { opacity: 0; }
	62%, 86%  { opacity: 0.9; }
	90%, 100% { opacity: 0; }
}

/*
 * Silo chips. These are the silos themselves — they exist whether or not
 * 100XD has wired them yet, so they fade in once on load and then stay. Only
 * the connections loop.
 */
.cvg__chip-box {
	fill: var(--ground-raise);
	stroke: var(--line);
	stroke-width: 1;
}

.cvg__chip-dot { fill: var(--blue-400); }

.cvg__chip-text {
	font-family: var(--font-sans);
	font-size: 11.5px;
	font-weight: 600;
	fill: var(--ink-60);
	letter-spacing: 0.01em;
}

.cvg__chip {
	opacity: 0;
	animation: cvgChip 0.7s var(--ease, ease-out) both;
	animation-delay: calc(var(--k) * 0.11s);
}

@keyframes cvgChip {
	from { opacity: 0; }
	to   { opacity: 1; }
}

/* Caption — a ticker naming each silo. Exactly one is visible at a time, so
   animation-delay is the right tool here. Six names, 2s each, 12s loop. */
.cvg__cap {
	display: flex;
	align-items: center;
	gap: 0.45rem;
	margin: 0;
	font-size: var(--fs-xs);
	color: var(--ink-45);
}

.cvg__now {
	position: relative;
	display: inline-block;
	min-width: 9.5rem;
	height: 1.35em;
	overflow: hidden;
}

.cvg__now b {
	position: absolute;
	inset: 0;
	font-weight: 700;
	color: var(--ink);
	opacity: 0;
	transform: translateY(0.55em);
	animation: cvgNow 12s var(--ease, ease-out) infinite;
	animation-delay: calc(var(--k) * 2s);
}

@keyframes cvgNow {
	0%        { opacity: 0; transform: translateY(0.55em); }
	1.5%, 14% { opacity: 1; transform: translateY(0); }
	16%, 100% { opacity: 0; transform: translateY(-0.55em); }
}

.cvg__tag {
	margin-left: auto;
	padding: 0.2rem 0.55rem;
	border: 1px solid var(--line);
	border-radius: var(--r-pill);
	font-size: 0.6875rem;
	font-weight: 600;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--ink-45);
	white-space: nowrap;
}

/* --------------------------------------------------------------------------
 * Contact — routing desk
 * -------------------------------------------------------------------------- */

.rdesk {
	--slot: 4s;
	--cycle: 12s;
	position: relative;
	width: 100%;
	border: 1px solid var(--line);
	border-radius: var(--r-lg);
	background: var(--ground-raise);
	box-shadow: var(--sh-sm);
	overflow: hidden;
}

.rdesk__bar {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	padding: 0.6rem 0.9rem;
	border-bottom: 1px solid var(--line-soft);
	font-size: 0.6875rem;
	font-weight: 700;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--ink-45);
}

.rdesk__dot {
	width: 7px;
	height: 7px;
	border-radius: 50%;
	background: var(--blue-600);
	animation: rdeskPulse 2s ease-in-out infinite;
}

@keyframes rdeskPulse {
	0%, 100% { opacity: 0.35; transform: scale(0.85); }
	50%      { opacity: 1;    transform: scale(1); }
}

.rdesk__live {
	margin-left: auto;
	padding: 0.1rem 0.45rem;
	border-radius: var(--r-pill);
	background: var(--blue-wash);
	color: var(--blue-700);
	letter-spacing: 0.05em;
}

/* The track the enquiry travels. */
.rdesk__track {
	position: relative;
	height: 52px;
	margin: 0.7rem 0.9rem 0;
	background:
		repeating-linear-gradient(90deg,
			var(--ink-07) 0 6px, transparent 6px 14px)
		0 50% / 100% 1px no-repeat;
}

.rdesk__gate {
	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
	padding: 0.2rem 0.5rem;
	border: 1px dashed var(--ink-30);
	border-radius: var(--r-pill);
	background: var(--ground-raise);
	font-size: 0.625rem;
	font-weight: 700;
	letter-spacing: 0.05em;
	text-transform: uppercase;
	color: var(--ink-45);
	white-space: nowrap;
}

/*
 * The token. One 12s timeline covers all three runs: the drop at the end of
 * each run targets a different lane, and that vertical difference cannot come
 * from a phase-shifted delay, so the three runs are written out.
 *
 * Position is animated with percentage left/top rather than translate, so the
 * travel is measured against the track and needs no container query unit.
 */
.rdesk__token {
	position: absolute;
	top: 50%;
	left: 0;
	padding: 0.3rem 0.6rem;
	border-radius: var(--r-pill);
	background: var(--blue-600);
	color: #fff;
	font-size: 0.6875rem;
	font-weight: 700;
	white-space: nowrap;
	box-shadow: 0 6px 18px rgba(11, 95, 234, 0.32);
	transform: translate(-50%, -50%);
	animation: rdeskToken 12s var(--ease-io, ease-in-out) infinite;
}

@keyframes rdeskToken {
	/* run 1 */
	0%      { left: 2%;  top: 50%;  opacity: 0; transform: translate(-50%, -50%) scale(0.88); }
	2.5%    { left: 9%;  top: 50%;  opacity: 1; transform: translate(-50%, -50%) scale(1); }
	9%      { left: 44%; top: 50%;  opacity: 1; transform: translate(-50%, -50%) scale(1); }
	12%     { left: 56%; top: 50%;  opacity: 1; transform: translate(-50%, -50%) scale(1); }
	16%     { left: 92%; top: 50%;  opacity: 1; transform: translate(-50%, -50%) scale(1); }
	19%     { left: 92%; top: 130%; opacity: 0; transform: translate(-50%, -50%) scale(0.8); }
	19.1%   { left: 2%;  top: 50%;  opacity: 0; transform: translate(-50%, -50%) scale(0.88); }
	/* run 2 */
	33.3%   { left: 2%;  top: 50%;  opacity: 0; transform: translate(-50%, -50%) scale(0.88); }
	35.8%   { left: 9%;  top: 50%;  opacity: 1; transform: translate(-50%, -50%) scale(1); }
	42.3%   { left: 44%; top: 50%;  opacity: 1; transform: translate(-50%, -50%) scale(1); }
	45.3%   { left: 56%; top: 50%;  opacity: 1; transform: translate(-50%, -50%) scale(1); }
	49.3%   { left: 92%; top: 50%;  opacity: 1; transform: translate(-50%, -50%) scale(1); }
	52.3%   { left: 92%; top: 130%; opacity: 0; transform: translate(-50%, -50%) scale(0.8); }
	52.4%   { left: 2%;  top: 50%;  opacity: 0; transform: translate(-50%, -50%) scale(0.88); }
	/* run 3 */
	66.6%   { left: 2%;  top: 50%;  opacity: 0; transform: translate(-50%, -50%) scale(0.88); }
	69.1%   { left: 9%;  top: 50%;  opacity: 1; transform: translate(-50%, -50%) scale(1); }
	75.6%   { left: 44%; top: 50%;  opacity: 1; transform: translate(-50%, -50%) scale(1); }
	78.6%   { left: 56%; top: 50%;  opacity: 1; transform: translate(-50%, -50%) scale(1); }
	82.6%   { left: 92%; top: 50%;  opacity: 1; transform: translate(-50%, -50%) scale(1); }
	85.6%   { left: 92%; top: 130%; opacity: 0; transform: translate(-50%, -50%) scale(0.8); }
	85.7%, 100% { left: 2%; top: 50%; opacity: 0; transform: translate(-50%, -50%) scale(0.88); }
}

.rdesk__lanes {
	list-style: none;
	margin: 0.5rem 0 0;
	padding: 0 0.55rem 0.3rem;
	display: grid;
	gap: 0.3rem;
}

/*
 * Lanes DO use a phase-shifted delay: only one is ever lit, so each lane's
 * cycle is identical and simply offset by one slot.
 */
.rdesk__lane {
	position: relative;
	display: grid;
	grid-template-columns: minmax(0, auto) minmax(0, 1fr);
	align-items: center;
	gap: 0.15rem 0.6rem;
	padding: 0.5rem 0.7rem;
	border: 1px solid transparent;
	border-radius: var(--r-sm);
	animation: rdeskLane var(--cycle) var(--ease, ease-out) infinite;
	animation-delay: calc(var(--k) * var(--slot));
}

@keyframes rdeskLane {
	0%          { background-color: transparent; border-color: transparent; }
	11%, 30%    { background-color: var(--blue-wash); border-color: rgba(11, 95, 234, 0.3); }
	33.4%, 100% { background-color: transparent; border-color: transparent; }
}

.rdesk__who {
	font-size: 0.8125rem;
	font-weight: 700;
	color: var(--ink);
}

.rdesk__desk {
	font-size: 0.75rem;
	color: var(--ink-45);
	text-align: right;
}

.rdesk__eta {
	grid-column: 1 / -1;
	overflow: hidden;
	font-size: 0.6875rem;
	color: var(--blue-700);
	opacity: 0;
	max-height: 0;
	animation: rdeskEta var(--cycle) var(--ease, ease-out) infinite;
	animation-delay: calc(var(--k) * var(--slot));
}

@keyframes rdeskEta {
	0%, 12%     { opacity: 0; max-height: 0; }
	16%, 30%    { opacity: 1; max-height: 2em; }
	33.4%, 100% { opacity: 0; max-height: 0; }
}

.rdesk__note {
	margin: 0;
	padding: 0 0.9rem 0.85rem;
	font-size: var(--fs-xs);
	color: var(--ink-45);
}

@media (max-width: 720px) {
	.rdesk__desk { text-align: left; }
	.rdesk__lane { grid-template-columns: minmax(0, 1fr); }
	.cvg__chip-text { font-size: 13px; }
	.cvg__now { min-width: 8rem; }
}

/*
 * Reduced motion: both instruments resolve to their finished state — the web
 * fully wired, the first lane shown as routed. A still frame that says what
 * the animation says.
 */
@media (prefers-reduced-motion: reduce) {
	.cvg__link,
	.cvg__flow,
	.cvg__chip,
	.cvg__ring,
	.cvg__halo,
	.cvg__now b,
	.rdesk__token,
	.rdesk__dot,
	.rdesk__lane,
	.rdesk__eta {
		animation: none !important;
	}

	.cvg__link { stroke-dashoffset: 0; opacity: 0.8; }
	.cvg__chip { opacity: 1; }
	.cvg__flow { opacity: 0; }
	.cvg__ring { opacity: 0.48; }
	.cvg__halo { opacity: 0.1; }
	.cvg__now b { opacity: 0; }
	.cvg__now b.is-still { opacity: 1; transform: none; }

	.rdesk__token { opacity: 0; }
	.rdesk__eta { opacity: 1; max-height: 2em; }
	.rdesk__lane.is-still {
		background-color: var(--blue-wash);
		border-color: rgba(11, 95, 234, 0.3);
	}
}
