/**
 * GD Web — scroll-driven motion.
 *
 * Every rule here reads `--p` (0→1), written each frame by scroll.js from the
 * element's own scroll position. Nothing below is a keyframe animation: the
 * scrollbar is the timeline, so scrolling back runs it backwards.
 *
 * Guarded by .gd-js so that without JavaScript `--p` never arrives, pins never
 * engage, and every section falls back to ordinary stacked content.
 */

/* ============================================================
   The Thread — one continuous line down the document
   ============================================================ */

.gd-thread {
	position: absolute;
	top: 0;
	left: 0;
	z-index: 0;
	width: 100%;
	height: 100%;
	pointer-events: none;
	overflow: hidden;
	/*
	 * Fade out where it meets the header and the footer. Fixed distances, not
	 * percentages: a mobile page is roughly twice as tall for the same content,
	 * so 3% blanked the first ~280px and the thread appeared to start late.
	 */
	mask-image: linear-gradient(to bottom, transparent 0, #000 88px, #000 calc(100% - 220px), transparent 100%);
	-webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 88px, #000 calc(100% - 220px), transparent 100%);
}

.gd-thread__svg {
	display: block;
	width: 100%;
	height: 100%;
	overflow: visible;
}

/* The unlit path: present but barely there, so the drawn line has a route. */
.gd-thread__track {
	fill: none;
	stroke: rgba(255, 255, 255, 0.045);
	stroke-width: 1.25;
	stroke-linecap: round;
}

/*
 * Present but quiet. It should read as something running through the page, not
 * as a line competing with the content it passes behind — so it is thinner,
 * dimmed, and its glow is a soft bloom rather than a hard halo.
 */
.gd-thread__line {
	fill: none;
	stroke: url(#gd-thread-gradient);
	stroke-width: 1.5;
	stroke-linecap: round;
	opacity: 0.42;
	filter: drop-shadow(0 0 6px rgba(124, 92, 255, 0.35));
}

.gd-thread__glow {
	fill: rgba(124, 92, 255, 0.2);
	filter: blur(11px);
}

/* The head stays the brightest point — it is the part worth noticing. */
.gd-thread__comet {
	fill: #fff;
	opacity: 0.8;
	filter: drop-shadow(0 0 7px rgba(167, 139, 250, 0.75)) drop-shadow(0 0 18px rgba(124, 92, 255, 0.5));
}

/*
 * Content sits above the thread, but by document order rather than by giving
 * .gd-main a z-index of its own.
 *
 * A stacking context on .gd-main would trap everything inside it above the
 * thread — including the hero's full-bleed photograph, which needs to sit
 * *under* the line. Without the context, the picture's own negative z-index
 * resolves against the root and drops below it.
 *
 * The header is deliberately left out: it is already positioned and stacked in
 * main.css, and re-declaring `position` here would overwrite its `sticky`.
 */
.gd-main,
.gd-footer {
	position: relative;
}

/* The footer still needs to clear the line explicitly. */
.gd-footer {
	z-index: 1;
}

@media (max-width: 860px) {
	/*
	 * Was 0.5 here, which multiplied with the line's own 0.42 to an effective
	 * 0.21 — legible over the dark mid-page, invisible over the hero photo. A
	 * narrow screen has less line on show at once, so it needs more weight, not
	 * less, to read as one continuous thread.
	 */
	.gd-thread { opacity: 0.85; }

	/*
	 * 0.85 x 0.5 lands at desktop's own effective 0.42 — the aim is parity, not
	 * more presence. The extra stroke weight is what buys legibility on a small
	 * screen; raising the opacity past desktop just makes it loud.
	 */
	.gd-thread__line {
		stroke-width: 1.75;
		opacity: 0.5;
	}

	.gd-thread__track { stroke: rgba(255, 255, 255, 0.07); }
}

/* ============================================================
   Global atmosphere, driven by page progress
   ============================================================ */

/*
 * The background field drifts and shifts hue across the whole document, so
 * scrolling feels like moving through one space rather than past separate
 * sections.
 */
.gd-js body::before {
	transform: translate3d(0, calc(var(--page-p, 0) * -6vh), 0);
	filter: hue-rotate(calc(var(--page-p, 0) * 42deg));
}

/* ============================================================
   Hero — scrubbed exit
   ============================================================ */

/*
 * Rather than simply scrolling away, the hero recedes: it tips back, drifts
 * down and dims in step with the scroll, handing the page over to the section
 * beneath it.
 */
.gd-js .gd-hero[data-scroll] .gd-hero__copy {
	transform: translate3d(0, calc(var(--p, 0) * 14vh), 0);
	opacity: calc(1 - var(--p, 0) * 1.35);
}

/*
 * The funnel tips further back and sinks away as the hero exits, so the page
 * reads as descending past the machine rather than the machine scrolling off.
 * Its resting 9deg tilt is the baseline the scrub adds to.
 */
.gd-js .gd-hero[data-scroll] .gd-funnel__inner {
	transform:
		perspective(1500px)
		translate3d(0, calc(var(--p, 0) * 7vh), calc(var(--p, 0) * -220px))
		rotateX(calc(9deg + var(--p, 0) * 24deg));
	opacity: calc(1 - var(--p, 0) * 1.2);
}

.gd-js .gd-hero[data-scroll] .gd-hero__banner {
	transform:
		perspective(1600px)
		translate3d(0, calc(var(--p, 0) * 8vh), calc(var(--p, 0) * -220px))
		rotateX(calc(var(--p, 0) * 22deg));
	opacity: calc(1 - var(--p, 0) * 1.2);
}

.gd-js .gd-hero[data-scroll] .gd-hero__grid {
	transform: perspective(340px) rotateX(58deg) translate3d(0, calc(var(--p, 0) * -22%), 0);
	opacity: calc(1 - var(--p, 0) * 0.8);
}

.gd-js .gd-scrollcue {
	opacity: calc(1 - var(--page-p, 0) * 14);
}

/* ============================================================
   Pinned sections
   ============================================================ */

/*
 * A pin is a tall spacer containing a sticky viewport-height stage. The page
 * scrolls; the stage stays put and its contents advance on `--p`.
 */
.gd-pin { position: relative; }

.gd-js .gd-pin {
	height: var(--gd-pin-h, 300vh);
}

.gd-pin__stage {
	display: flex;
	flex-direction: column;
	justify-content: center;
	overflow: hidden;
}

.gd-js .gd-pin__stage {
	position: sticky;
	top: 0;
	height: 100vh;
	height: 100dvh;
}

/*
 * Without JS the pin is inert: no fixed height, no sticky, and the inner
 * content lays out as an ordinary section.
 */
html:not(.gd-js) .gd-pin__stage { height: auto; overflow: visible; }

/* --- Horizontal case studies ------------------------------------------ */

.gd-rail {
	display: flex;
	gap: var(--gd-space-3);
	padding-inline: var(--gd-gutter);
	will-change: transform;
}

.gd-js .gd-rail {
	/*
	 * Travel is the track's overflow beyond the viewport, expressed by the
	 * template as --gd-rail-travel so the distance always matches the number
	 * of cards rather than a hard-coded guess.
	 */
	transform: translate3d(calc(var(--p, 0) * var(--gd-rail-travel, -60%)), 0, 0);
}

.gd-rail > * {
	flex: 0 0 min(440px, 80vw);
}

html:not(.gd-js) .gd-rail {
	flex-wrap: wrap;
	justify-content: center;
}

html:not(.gd-js) .gd-rail > * { flex: 1 1 300px; }

.gd-rail__head {
	flex: 0 0 min(400px, 82vw);
	align-self: center;
}

/*
 * With only a couple of cards there may be nothing to travel; centre the row
 * so it reads as a deliberate composition rather than a rail that failed.
 */
.gd-rail { justify-content: safe center; }

/* Progress rail under a pinned section. */
.gd-pinbar {
	position: relative;
	height: 2px;
	margin: var(--gd-space-4) var(--gd-gutter) 0;
	border-radius: 999px;
	background: var(--gd-glass-line);
	overflow: hidden;
}

.gd-pinbar::after {
	content: "";
	position: absolute;
	inset: 0;
	border-radius: inherit;
	background: linear-gradient(90deg, var(--gd-violet), var(--gd-cyan));
	transform: scaleX(var(--p, 0));
	transform-origin: 0 50%;
}

/* --- Pinned process timeline ------------------------------------------ */

.gd-flow {
	position: relative;
	display: grid;
	gap: var(--gd-space-4);
	align-content: center;
}

/*
 * No rail of its own across this breakpoint.
 *
 * The page's thread is routed through the row and is the rail now, so drawing
 * a second line here put two lines through the same nodes — which is what made
 * the thread appear to collide with the row at its left end. Below the
 * breakpoint the steps stack and keep their own vertical rail, since the
 * thread cannot run down a column that it weaves past.
 */
@media (min-width: 900px) {
	.gd-flow { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--gd-space-3); }

	/*
	 * No padding of its own. The stage already centres its contents, so extra
	 * space here only pushed the row away from the heading it belongs to — and
	 * the thread eases in over the full height of the stage, not through this
	 * box, so it never needed the room.
	 */

	/*
	 * No lift on the waiting steps here. The thread is drawn through their
	 * layout position, so a 10px drop would leave every unlit node sitting
	 * just under the line it is supposed to be threaded onto. Dimming alone
	 * carries the waiting state; the node's own ring still marks the active one.
	 */
	.gd-js .gd-home-section--flow .gd-flow__item { transform: none; }
}

.gd-flow__item {
	position: relative;
	/* The node is 68px tall and sits at top:0, so content must clear it. */
	padding-top: calc(68px + var(--gd-space-3));
	text-align: center;
	/* Above the rail and the spark, so the line passes behind the nodes. */
	z-index: 2;
	transition: opacity 0.5s var(--gd-ease), transform 0.5s var(--gd-ease);
}

.gd-js .gd-flow__item {
	opacity: 0.32;
	transform: translateY(10px) scale(0.97);
}

.gd-js .gd-flow__item.is-past { opacity: 0.55; transform: none; }

.gd-js .gd-flow__item.is-active {
	opacity: 1;
	transform: none;
}

.gd-flow__num {
	position: absolute;
	top: 0;
	left: 50%;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 68px;
	height: 68px;
	margin-left: -34px;
	border: 1px solid var(--gd-glass-line-2);
	border-radius: 50%;
	background:
		linear-gradient(160deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.02) 55%),
		rgba(11, 14, 28, 0.95);
	box-shadow:
		inset 0 2px 0 rgba(255, 255, 255, 0.35),
		0 20px 44px -18px rgba(0, 0, 0, 0.9);
	font-size: 1.35rem;
	font-weight: 700;
	letter-spacing: -0.04em;
	transition: box-shadow 0.5s var(--gd-ease), transform 0.5s var(--gd-spring);
}

.gd-flow__item.is-active .gd-flow__num {
	transform: scale(1.08);
	box-shadow:
		inset 0 2px 0 rgba(255, 255, 255, 0.5),
		0 0 0 1px rgba(124, 92, 255, 0.55),
		0 0 46px -6px rgba(124, 92, 255, 0.9);
}

.gd-flow__num span {
	background: linear-gradient(180deg, #fff, #9fa8cc);
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
}

.gd-flow__title { margin-bottom: 0.6rem; font-size: var(--gd-text-lg); }
.gd-flow__text { margin: 0; color: var(--gd-text-2); font-size: var(--gd-text-sm); }

/*
 * Below the desktop breakpoint the same idea runs vertically.
 *
 * Stacking the three columns as-is overran the pinned stage — 940px of content
 * in an 844px window on a phone, 852px in 667px on a small one, so the third
 * step was simply cut off — and the rail and spark were desktop-only, which
 * left the section with none of the scroll-driven behaviour it exists for.
 * Turning the rail on its side fits the space a phone actually has.
 */
@media (max-width: 899px) {
	/*
	 * Every other head on a phone stacks left-aligned, and the rail below this
	 * one is inherently left-aligned too — a centred head here reads as the odd
	 * one out in both directions.
	 */
	.gd-pin__stage .gd-section__head {
		max-width: none;
		margin-inline: 0;
		text-align: left;
	}

	.gd-pin__stage .gd-section__foot { justify-content: flex-start; }

	/*
	 * Carries the :not() from the version stylesheets' own centred rule.
	 *
	 * Those load after this file — they depend on it — so a tie goes to them,
	 * and their three-class selector was quietly beating the two here: the head
	 * went left on a phone while the rule under it stayed centred.
	 */
	.gd-pin__stage .gd-section__head:not(.gd-section__head--split) .gd-section__rule {
		background: linear-gradient(90deg, rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0.045) 55%, rgba(255, 255, 255, 0.09));
		transform-origin: 0 50%;
	}

	.gd-pin__stage .gd-section__head:not(.gd-section__head--split) .gd-section__rule::before {
		left: 0;
		width: clamp(120px, 20vw, 240px);
		transform: none;
		background: linear-gradient(90deg, var(--gd-violet), var(--gd-cyan), transparent);
	}

	.gd-flow { gap: 1.15rem; }

	.gd-flow::before,
	.gd-flow::after {
		content: "";
		position: absolute;
		z-index: 0;
		top: 20px;
		bottom: 20px;
		left: 20px;
		width: 1px;
	}

	.gd-flow::before { background: var(--gd-glass-line); }

	/* The lit portion of the rail tracks the pinned progress. */
	.gd-flow::after {
		background: linear-gradient(180deg, var(--gd-violet), var(--gd-cyan));
		transform: scaleY(var(--p, 0));
		transform-origin: 50% 0;
		box-shadow: 0 0 12px rgba(124, 92, 255, 0.9);
	}

	.gd-flow__item {
		display: grid;
		grid-template-columns: 40px minmax(0, 1fr);
		grid-template-areas:
			"num title"
			"gap text";
		align-items: start;
		column-gap: 1rem;
		padding-top: 0;
		text-align: left;
	}

	.gd-flow__num {
		position: static;
		grid-area: num;
		width: 40px;
		height: 40px;
		margin-left: 0;
		font-size: 0.95rem;
	}

	.gd-flow__title {
		grid-area: title;
		align-self: center;
		margin-bottom: 0.35rem;
		font-size: var(--gd-text-md);
	}

	.gd-flow__text {
		grid-area: text;
		font-size: 0.85rem;
		line-height: 1.55;
	}

	.gd-js .gd-flow__item { transform: translateY(6px); }
	.gd-js .gd-flow__item.is-past,
	.gd-js .gd-flow__item.is-active { transform: none; }

	/* Travels down the rail rather than across it. */
	.gd-js .gd-flow__spark {
		display: block;
		position: absolute;
		z-index: 1;
		left: 20px;
		top: calc(20px + var(--p, 0) * (100% - 40px));
		width: 10px;
		height: 10px;
		margin: -5px 0 0 -5px;
		border-radius: 50%;
		background: #fff;
		box-shadow:
			0 0 10px 2px rgba(167, 139, 250, 1),
			0 0 28px 6px rgba(124, 92, 255, 0.85),
			0 0 60px 16px rgba(34, 211, 238, 0.4);
		will-change: top;
	}
}

/*
 * A short phone still has room for the rail, but not for the head's full
 * breathing space above it — reclaim that rather than dropping the section to
 * the un-pinned fallback on a size as common as a 667px screen.
 */
@media (max-width: 899px) and (max-height: 740px) {
	.gd-pin__stage .gd-section__head { margin-bottom: var(--gd-space-3); }
	.gd-pin__stage .gd-flow { gap: 0.85rem; }
}

/*
 * A landscape phone has no room to pin anything: the stage would clip whatever
 * the layout. Let the section flow and stand fully lit instead.
 */
@media (max-width: 899px) and (max-height: 660px) {
	.gd-js .gd-pin { height: auto; }
	.gd-js .gd-pin__stage { position: static; height: auto; overflow: visible; }
	.gd-js .gd-flow__item { opacity: 1; transform: none; }
	.gd-js .gd-flow__spark { display: none; }
	.gd-flow::after { transform: scaleY(1); }
}

/* ============================================================
   Scrubbed section entrances
   ============================================================ */

/*
 * A gentler, always-reversible alternative to the one-shot reveals in anim.js:
 * these track scroll position continuously, so the section breathes as it
 * moves through the viewport instead of snapping into place once.
 */
.gd-js [data-scroll-rise] {
	transform: translate3d(0, calc((1 - var(--p, 0)) * 60px), 0);
	opacity: clamp(0, calc(var(--p, 0) * 2.2), 1);
}

.gd-js [data-scroll-scale] {
	transform: scale(calc(0.92 + var(--p, 0) * 0.08));
	opacity: clamp(0, calc(var(--p, 0) * 2.4), 1);
}

/* Depth parallax between a section's layers. */
.gd-js [data-scroll-drift] {
	transform: translate3d(0, calc((var(--p, 0) - 0.5) * var(--gd-drift, 60px)), 0);
}

@media (prefers-reduced-motion: reduce) {
	.gd-js .gd-hero[data-scroll] .gd-hero__copy,
	.gd-js .gd-hero[data-scroll] .gd-funnel__inner,
	.gd-js .gd-hero[data-scroll] .gd-hero__banner,
	.gd-js .gd-hero[data-scroll] .gd-hero__grid,
	.gd-js [data-scroll-rise],
	.gd-js [data-scroll-scale],
	.gd-js [data-scroll-drift],
	.gd-js .gd-rail {
		transform: none !important;
		opacity: 1 !important;
	}

	.gd-js .gd-pin { height: auto !important; }
	.gd-js .gd-pin__stage { position: static; height: auto; overflow: visible; }
	.gd-js .gd-flow__item { opacity: 1 !important; transform: none !important; }
	.gd-js body::before { transform: none !important; filter: none !important; }
	.gd-js .gd-scrollcue { opacity: 1 !important; }
}

/* ============================================================
   Column drift
   ============================================================ */

/*
 * Each column travels at a slightly different rate as its section crosses the
 * viewport. The offsets are small on purpose — enough that the grid feels alive
 * and layered while scrolling, not so much that rows stop reading as rows.
 *
 * The wrapper carries the drift so it never competes with the card's own
 * pointer-tilt transform.
 */
.gd-drift { will-change: transform; }

.gd-grid--drift > *:nth-child(3n + 1) { --gd-drift: 74px; }
.gd-grid--drift > *:nth-child(3n + 2) { --gd-drift: 22px; }
.gd-grid--drift > *:nth-child(3n + 3) { --gd-drift: 54px; }

@media (max-width: 919px) {
	/* Stacked columns make differential drift read as jitter. */
	.gd-grid--drift > * { --gd-drift: 0px; }
}

/* ============================================================
   Process — the thread powering each step
   ============================================================ */

/*
 * A bright point of energy running the rail, positioned by the pinned
 * section's own progress. It is what visibly hands power from one step to the
 * next, rather than the steps simply toggling.
 */
.gd-flow__spark {
	display: none;
}

/* Inactive steps sit genuinely unpowered. */
.gd-js .gd-flow__item .gd-flow__num {
	filter: saturate(0.3) brightness(0.8);
	transition: filter 0.5s var(--gd-ease), box-shadow 0.5s var(--gd-ease),
		transform 0.5s var(--gd-spring);
}

.gd-js .gd-flow__item.is-past .gd-flow__num { filter: saturate(0.7) brightness(0.95); }
.gd-js .gd-flow__item.is-active .gd-flow__num { filter: none; }

/* A ring that expands once as a step receives power. */
.gd-flow__num::after {
	content: "";
	position: absolute;
	inset: -6px;
	border: 1px solid rgba(124, 92, 255, 0.9);
	border-radius: 50%;
	opacity: 0;
	pointer-events: none;
}

.gd-js .gd-flow__item.is-active .gd-flow__num::after {
	animation: gd-pulse-ring 1.6s var(--gd-ease) 1;
}

@keyframes gd-pulse-ring {
	0% { opacity: 0.9; transform: scale(1); }
	70% { opacity: 0; transform: scale(1.65); }
	100% { opacity: 0; transform: scale(1.65); }
}

@media (prefers-reduced-motion: reduce) {
	.gd-js .gd-flow__spark { display: none; }
	.gd-js .gd-flow__item .gd-flow__num { filter: none; }
	.gd-js .gd-flow__item.is-active .gd-flow__num::after { animation: none; }
}

/* ============================================================
   Awakening — what the thread sets off on contact
   ============================================================ */

/*
 * The thread's arrival is a trigger, not a dial. `.is-live` is added once, by
 * scroll.js, at the moment the thread's head reaches the section — and the
 * whole arrival then plays through on its own clock. Scroll speed, scroll
 * direction and stopping mid-way have no effect on it.
 *
 * `--touch-x` is the point where the thread actually crossed, so the wave
 * breaks from the contact rather than from an assumed centre.
 */

[data-awaken] { position: relative; }

/* Dormant. Only ever applied when JS is present to undo it. */
.gd-js [data-awaken] .gd-section__title,
.gd-js [data-awaken] .gd-rail__head .gd-section__title,
.gd-js [data-awaken] .gd-section__subtitle,
.gd-js [data-awaken] .gd-eyebrow,
.gd-js [data-awaken] .gd-flow__title,
.gd-js [data-awaken] .gd-flow__text {
	opacity: 0.16;
	filter: saturate(0.15);
}

.gd-js [data-awaken] .gd-card,
.gd-js [data-awaken] .gd-metrics--band {
	opacity: 0.2;
	filter: saturate(0.1) brightness(0.7);
}

/* --- The wave --------------------------------------------------------- */

.gd-wake {
	position: absolute;
	top: 70px;
	left: var(--touch-x, 50%);
	z-index: 0;
	width: 10px;
	height: 10px;
	margin: -5px 0 0 -5px;
	border-radius: 50%;
	opacity: 0;
	pointer-events: none;
}

.gd-wake::before,
.gd-wake::after {
	content: "";
	position: absolute;
	inset: 0;
	border-radius: 50%;
	border: 1px solid rgba(124, 92, 255, 0.9);
}

.is-live .gd-wake { animation: gd-wake-flash 0.9s var(--gd-ease) both; }
.is-live .gd-wake::before { animation: gd-wake-ring 1.5s var(--gd-ease) both; }
.is-live .gd-wake::after { animation: gd-wake-ring 1.5s var(--gd-ease) 0.22s both; }

@keyframes gd-wake-flash {
	0% { opacity: 1; box-shadow: 0 0 0 0 rgba(167, 139, 250, 0.9), 0 0 40px 12px rgba(124, 92, 255, 0.8); }
	100% { opacity: 0; box-shadow: 0 0 0 0 rgba(167, 139, 250, 0), 0 0 90px 30px rgba(124, 92, 255, 0); }
}

@keyframes gd-wake-ring {
	0% { opacity: 0.85; transform: scale(1); border-color: rgba(167, 139, 250, 0.95); }
	100% { opacity: 0; transform: scale(46); border-color: rgba(34, 211, 238, 0); }
}

/* --- What the wave brings to life -------------------------------------- */

.gd-js [data-awaken].is-live .gd-eyebrow {
	animation: gd-wake-in 0.7s var(--gd-ease) 0.05s both;
}

.gd-js [data-awaken].is-live .gd-section__title,
.gd-js [data-awaken].is-live .gd-rail__head .gd-section__title {
	animation: gd-wake-title 1.1s var(--gd-ease) 0.14s both;
}

.gd-js [data-awaken].is-live .gd-section__subtitle {
	animation: gd-wake-in 0.8s var(--gd-ease) 0.3s both;
}

.gd-js [data-awaken].is-live .gd-flow__title { animation: gd-wake-in 0.7s var(--gd-ease) 0.2s both; }
.gd-js [data-awaken].is-live .gd-flow__text { animation: gd-wake-in 0.7s var(--gd-ease) 0.32s both; }

.gd-js [data-awaken].is-live .gd-metrics--band {
	animation: gd-wake-panel 1s var(--gd-ease) 0.2s both;
}

/* Cards arrive in sequence, as if the charge is running along the row. */
.gd-js [data-awaken].is-live .gd-card {
	animation: gd-wake-panel 0.85s var(--gd-ease) both;
}

.gd-js [data-awaken].is-live .gd-grid > *:nth-child(1) .gd-card,
.gd-js [data-awaken].is-live .gd-rail > *:nth-child(2) { animation-delay: 0.30s; }
.gd-js [data-awaken].is-live .gd-grid > *:nth-child(2) .gd-card,
.gd-js [data-awaken].is-live .gd-rail > *:nth-child(3) { animation-delay: 0.40s; }
.gd-js [data-awaken].is-live .gd-grid > *:nth-child(3) .gd-card,
.gd-js [data-awaken].is-live .gd-rail > *:nth-child(4) { animation-delay: 0.50s; }
.gd-js [data-awaken].is-live .gd-grid > *:nth-child(4) .gd-card { animation-delay: 0.60s; }
.gd-js [data-awaken].is-live .gd-grid > *:nth-child(5) .gd-card { animation-delay: 0.70s; }
.gd-js [data-awaken].is-live .gd-grid > *:nth-child(6) .gd-card { animation-delay: 0.80s; }

@keyframes gd-wake-in {
	from { opacity: 0.16; filter: saturate(0.15); transform: translateY(10px); }
	to { opacity: 1; filter: none; transform: none; }
}

/* The title ignites past full brightness, then settles. */
@keyframes gd-wake-title {
	0% { opacity: 0.16; filter: saturate(0.15); transform: translateY(14px); text-shadow: none; }
	45% { opacity: 1; filter: brightness(1.35); text-shadow: 0 0 46px rgba(167, 139, 250, 0.95); }
	100% { opacity: 1; filter: none; transform: none; text-shadow: 0 0 22px rgba(124, 92, 255, 0.28); }
}

@keyframes gd-wake-panel {
	0% { opacity: 0.2; filter: saturate(0.1) brightness(0.7); transform: translateY(26px) scale(0.985); }
	60% { filter: saturate(1.1) brightness(1.08); }
	100% { opacity: 1; filter: none; transform: none; }
}

/*
 * The charge line, drawn once on arrival — the visible tap off the thread.
 *
 * It is a real element in the head's bottom band rather than a pseudo-element,
 * so it can share that row with the section's controls. Its geometry and
 * alignment live in home.css; only the arrival is driven here.
 */
.gd-js [data-awaken].is-live .gd-section__rule {
	animation: gd-wake-rule 1s var(--gd-ease) 0.42s both;
}

@keyframes gd-wake-rule {
	from { transform: scaleX(0); opacity: 0; }
	to { transform: scaleX(1); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
	.gd-js [data-awaken] .gd-section__title,
	.gd-js [data-awaken] .gd-section__subtitle,
	.gd-js [data-awaken] .gd-eyebrow,
	.gd-js [data-awaken] .gd-flow__title,
	.gd-js [data-awaken] .gd-flow__text,
	.gd-js [data-awaken] .gd-card,
	.gd-js [data-awaken] .gd-metrics--band {
		opacity: 1 !important;
		filter: none !important;
		transform: none !important;
		animation: none !important;
	}

	.gd-wake { display: none; }

	.gd-js [data-awaken] .gd-section__rule {
		animation: none !important;
		transform: scaleX(1);
		opacity: 1;
	}
}

/* ============================================================
   The pinned stage's thread
   ============================================================

   Its own length of line, drawn in the stage's coordinates so it holds still
   with the stage while the document scrolls past underneath. It is styled to
   match the page's thread exactly — including borrowing the same gradient —
   because the two are meant to read as one continuous line handing over at the
   stage's edges.
   ============================================================ */

.gd-flowline {
	position: absolute;
	inset: 0;
	z-index: 0;
	display: block;
	width: 100%;
	height: 100%;
	overflow: visible;
	pointer-events: none;
}

/*
 * Structure only. Weight, colour, opacity and glow are mirrored from the page
 * thread at build time — stating them here as well is what let the two drift
 * apart, so the paint is deliberately left to one owner.
 */
.gd-flowline__track,
.gd-flowline__line {
	fill: none;
	stroke: transparent;
	stroke-linecap: round;
}

/* The steps sit above the line it is threaded through. */
.gd-pin__stage > .gd-container {
	position: relative;
	z-index: 1;
}

@media (prefers-reduced-motion: reduce) {
	.gd-flowline__comet,
	.gd-flowline__glow { display: none; }
}

/* The flow's own rail stands down while the page thread is threaded through it. */
.gd-flow--threaded::before,
.gd-flow--threaded::after,
.gd-flow--threaded .gd-flow__spark { display: none; }
