/**
 * GD Web — homepage sections.
 *
 * Loaded on the front page only. Depends on the tokens and motion primitives in
 * main.css.
 *
 * The 3D here is real CSS 3D, not a drawing of it: a perspective container with
 * preserve-3d children on separate Z planes, so parallax and tilt move the
 * layers at genuinely different rates.
 */

/* ============================================================
   Hero
   ============================================================ */

/*
 * No `isolation` here: it would form a stacking context and trap the hero's
 * background picture above the thread. The picture sits at z-index -1 and must
 * resolve against the root so the line can pass over it.
 */
.gd-hero {
	position: relative;
	padding-block: clamp(3.5rem, 9vh, 6.5rem) clamp(2rem, 4vh, 3rem);
	overflow: hidden;
}

/* --- Atmosphere layers ------------------------------------------------ */

.gd-hero__bg {
	position: absolute;
	inset: -20% -10% 0;
	z-index: -1;
	pointer-events: none;
}

/* Blurred colour spheres. Each drifts on its own timing so the field never
   settles into a recognisable loop. */
.gd-orb {
	position: absolute;
	border-radius: 50%;
	filter: blur(70px);
	opacity: 0.55;
	will-change: transform;
	transform: translate3d(0, var(--gd-py, 0), 0);
}

.gd-orb--1 {
	top: 4%;
	left: -6%;
	width: 44vw;
	height: 44vw;
	max-width: 620px;
	max-height: 620px;
	background: radial-gradient(circle at 32% 32%, #8b6bff, #3b1fa8 62%, transparent 72%);
	animation: gd-drift-a 22s var(--gd-ease-in-out) infinite alternate;
}

.gd-orb--2 {
	top: -8%;
	right: -8%;
	width: 38vw;
	height: 38vw;
	max-width: 540px;
	max-height: 540px;
	background: radial-gradient(circle at 60% 40%, #22d3ee, #0e7490 60%, transparent 72%);
	opacity: 0.4;
	animation: gd-drift-b 27s var(--gd-ease-in-out) infinite alternate;
}

@keyframes gd-drift-a {
	to { transform: translate3d(6%, calc(var(--gd-py, 0px) + 4%), 0) scale(1.12); }
}

@keyframes gd-drift-b {
	to { transform: translate3d(-7%, calc(var(--gd-py, 0px) + 6%), 0) scale(1.08); }
}

@keyframes gd-drift-c {
	to { transform: translate3d(5%, calc(var(--gd-py, 0px) - 5%), 0) scale(1.15); }
}

/* Perspective floor grid — the strongest single cue that the page has depth. */
.gd-hero__grid {
	position: absolute;
	inset: auto 0 0;
	height: 26vh;
	z-index: -1;
	background-image:
		linear-gradient(to right, rgba(124, 92, 255, 0.16) 1px, transparent 1px),
		linear-gradient(to bottom, rgba(124, 92, 255, 0.16) 1px, transparent 1px);
	background-size: 62px 62px;
	transform: perspective(340px) rotateX(58deg);
	transform-origin: 50% 100%;
	mask-image: linear-gradient(to top, #000 0%, transparent 88%);
	-webkit-mask-image: linear-gradient(to top, #000 0%, transparent 88%);
	pointer-events: none;
}

.gd-hero__actions {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: var(--gd-space-2);
	margin-bottom: 0;
}

.gd-hero__banner {
	margin-top: var(--gd-space-5);
}

.gd-hero__banner img {
	width: 100%;
	border: 1px solid var(--gd-glass-line-2);
	border-radius: var(--gd-radius-lg);
	box-shadow: var(--gd-glow-lift);
}

/* ============================================================
   The funnel — the hero's centrepiece and the thread's origin
   ============================================================ */

.gd-funnel {
	position: relative;
	margin-top: clamp(2.5rem, 6vh, 4.5rem);
}

.gd-funnel__inner {
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	align-items: stretch;
	gap: 0;
	/* A shallow tilt so the row reads as a physical apparatus, not a table. */
	transform: perspective(1500px) rotateX(9deg);
	transform-style: preserve-3d;
}

@media (max-width: 860px) {
	.gd-funnel__inner {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		transform: none;
	}}

@media (max-width: 860px) {
	.gd-funnel__inner { gap: 0.6rem; }}

.gd-funnel::after {
	content: "";
	position: absolute;
	left: 50%;
	bottom: calc(var(--gd-space-4) * -1);
	width: 1px;
	height: var(--gd-space-4);
	background: linear-gradient(180deg, var(--gd-violet), transparent);
	opacity: 0.7;
}

/* Magnetic offset, fed by JS. */
[data-magnetic] {
	transform: translate3d(var(--gd-mx, 0), var(--gd-my, 0), 0);
	transition: transform 0.35s var(--gd-ease), box-shadow 0.25s var(--gd-ease);
}

[data-magnetic]:hover { transition-duration: 0.1s; }

.gd-stat {
	padding: 0.75rem 0.85rem;
	border: 1px solid var(--gd-glass-line);
	border-radius: 12px;
	background: rgba(255, 255, 255, 0.035);
}

.gd-scrollcue {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 0.6rem;
	margin-top: var(--gd-space-4);
	color: var(--gd-text-3);
	font-size: var(--gd-text-xs);
	letter-spacing: 0.16em;
	text-transform: uppercase;
	text-decoration: none;
}

.gd-scrollcue:hover { color: var(--gd-text); }

@keyframes gd-wheel {
	0% { opacity: 0; transform: translateY(0); }
	30% { opacity: 1; }
	100% { opacity: 0; transform: translateY(16px); }
}

/* ============================================================
   Marquee
   ============================================================ */

.gd-marquee {
	position: relative;
	display: flex;
	gap: var(--gd-space-5);
	overflow: hidden;
	padding-block: var(--gd-space-2);
	mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
	-webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
}

.gd-marquee__track {
	display: flex;
	flex: none;
	align-items: center;
	gap: var(--gd-space-5);
	padding-right: var(--gd-space-5);
	animation: gd-scroll var(--gd-marquee-duration, 42s) linear infinite;
}

.gd-marquee:hover .gd-marquee__track { animation-play-state: paused; }

@keyframes gd-scroll {
	to { transform: translateX(-100%); }
}

.gd-marquee__item {
	display: flex;
	align-items: center;
	gap: 0.6rem;
	color: var(--gd-text-3);
	font-size: clamp(1rem, 0.9rem + 0.5vw, 1.35rem);
	font-weight: 600;
	letter-spacing: -0.02em;
	white-space: nowrap;
	transition: color 0.3s var(--gd-ease);
}

.gd-marquee__item:hover { color: var(--gd-text); }

.gd-marquee__item svg { color: var(--gd-violet); opacity: 0.7; }

.gd-marquee__item img {
	max-height: 34px;
	width: auto;
	filter: grayscale(1) brightness(2.4);
	opacity: 0.5;
}

/* ============================================================
   Section framing used on the homepage
   ============================================================ */

/*
 * One rhythm for every section on the page. The banded ones need the room most
 * — their edges are visible, so content sitting close to them reads cramped —
 * but the spacing is set here rather than on them, so the page keeps one
 * measure throughout.
 */
.gd-home-section {
	position: relative;
	padding-block: clamp(4.75rem, 6.5vw, 6.25rem);
}

/* Consecutive sections would otherwise add two full paddings into one void. */
.gd-home-section + .gd-home-section { padding-top: clamp(3.25rem, 4.5vw, 4.5rem); }

.gd-home-section--glow::before {
	content: "";
	position: absolute;
	inset: 0;
	z-index: -1;
	background: radial-gradient(60rem 32rem at 50% 0%, rgba(124, 92, 255, 0.14), transparent 62%);
	pointer-events: none;
}

.gd-card--case-hero .gd-card__media {
	aspect-ratio: 16 / 9;
	background:
		radial-gradient(28rem 18rem at 30% 20%, rgba(124, 92, 255, 0.4), transparent 65%),
		radial-gradient(22rem 16rem at 85% 90%, rgba(34, 211, 238, 0.28), transparent 65%),
		linear-gradient(150deg, var(--gd-slate), var(--gd-deep));
}

.gd-card--case-hero .gd-card__media::after {
	background: linear-gradient(180deg, transparent 55%, rgba(4, 5, 12, 0.65));
}

.gd-card__hero {
	position: relative;
	z-index: 1;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	height: 100%;
	padding: var(--gd-space-2);
	text-align: center;
}

.gd-card__hero-value {
	font-size: clamp(2.4rem, 1.8rem + 2.4vw, 3.4rem);
	font-weight: 750;
	letter-spacing: -0.05em;
	line-height: 1;
	font-variant-numeric: tabular-nums;
	background: linear-gradient(160deg, #fff 25%, #a78bfa 100%);
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
}

.gd-card__hero-label {
	margin-top: 0.5rem;
	color: var(--gd-text-2);
	font-size: var(--gd-text-xs);
	font-weight: 600;
	letter-spacing: 0.09em;
	text-transform: uppercase;
}

.gd-quiz {
	position: relative;
	max-width: 860px;
	margin: clamp(2.5rem, 6vh, 4rem) auto 0;
	padding: clamp(1.75rem, 4vw, 3rem);
	border: 1px solid var(--gd-glass-line-2);
	border-radius: var(--gd-radius-xl);
	background:
		linear-gradient(180deg, rgba(255, 255, 255, 0.09), rgba(255, 255, 255, 0.02) 45%, rgba(255, 255, 255, 0) 100%),
		rgba(11, 14, 28, 0.72);
	backdrop-filter: blur(26px) saturate(150%);
	-webkit-backdrop-filter: blur(26px) saturate(150%);
	box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18), var(--gd-glow-lift);
	overflow: hidden;
	isolation: isolate;
}

.gd-quiz__question {
	margin: 0 0 var(--gd-space-4);
	font-size: clamp(1.4rem, 1.1rem + 1.3vw, 2.1rem);
	letter-spacing: -0.03em;
}

.gd-quiz__question:focus { outline: none; }

.gd-quiz__wave {
	position: absolute;
	z-index: -1;
	width: 10px;
	height: 10px;
	margin: -5px 0 0 -5px;
	border-radius: 50%;
	opacity: 0;
	pointer-events: none;
}

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

.gd-quiz__wave.is-firing { animation: gd-wake-flash 0.75s var(--gd-ease) both; }
.gd-quiz__wave.is-firing::before { animation: gd-wake-ring 1.1s var(--gd-ease) both; }
.gd-quiz__wave.is-firing::after { animation: gd-wake-ring 1.1s var(--gd-ease) 0.16s both; }

/*
 * Three parts on a fixed grid: the heading holds the left column, the
 * supporting line the right, and a band beneath carries the rule and any
 * controls.
 *
 * The controls used to stack under the subtitle, which left the heading, the
 * subtitle and the arrows on three unrelated baselines — the arrows read as
 * dropped in rather than placed. Sharing a band with the rule gives them
 * something to line up against.
 */
.gd-section__head--split {
	display: grid;
	gap: var(--gd-space-3);
	max-width: none;
	margin-bottom: var(--gd-space-5);
	text-align: left;
	grid-template-areas: "lead" "note" "foot";
}

.gd-section__head--split .gd-section__lead { grid-area: lead; }
.gd-section__head--split .gd-section__subtitle { grid-area: note; max-width: 46ch; }
.gd-section__head--split .gd-section__foot { grid-area: foot; }

@media (min-width: 900px) {
	.gd-section__head--split {
		grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
		grid-template-areas:
			"lead note"
			"foot foot";
		align-items: start;
		column-gap: clamp(2rem, 5vw, 5rem);
		row-gap: var(--gd-space-4);
	}
}

/* The band: rule at one end, controls at the other. */
/* Fixed height, controls or not. Otherwise the arrows set the band's height
   and the rule drops ~25px in carousel sections only — so the heads stop
   lining up with each other as you scroll between them. */
.gd-section__foot {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--gd-space-3);
	width: 100%;
	min-height: 46px;
}

.gd-section__head:not(.gd-section__head--split) .gd-section__foot {
	justify-content: center;
}

/* The rule spans the whole band and terminates at the controls, so the two
   read as one horizontal element rather than two objects sharing a baseline.
   The bright brand segment stays where it was — the hairline is the new part. */
.gd-section__rule {
	position: relative;
	flex: 1 1 auto;
	min-width: 0;
	height: 1px;
	background: linear-gradient(90deg, rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0.045) 55%, rgba(255, 255, 255, 0.09));
	transform: scaleX(0);
	transform-origin: 0 50%;
	opacity: 0;
}

.gd-section__rule::before {
	content: "";
	position: absolute;
	inset-block: 0;
	left: 0;
	width: clamp(120px, 20vw, 240px);
	background: linear-gradient(90deg, var(--gd-violet), var(--gd-cyan), transparent);
	box-shadow: 0 0 14px rgba(124, 92, 255, 0.7);
}

.gd-section__head:not(.gd-section__head--split) .gd-section__rule {
	background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1) 22%, rgba(255, 255, 255, 0.1) 78%, transparent);
	transform-origin: 50% 50%;
}

.gd-section__head:not(.gd-section__head--split) .gd-section__rule::before {
	left: 50%;
	width: clamp(160px, 26vw, 300px);
	transform: translateX(-50%);
	background: linear-gradient(90deg, transparent, var(--gd-violet), var(--gd-cyan), transparent);
}

/*
 * The rules run wider than the content they separate.
 *
 * With the row and its content on the same line, the number and the arrow sat
 * hard against the ends of every hairline. Bleeding the list outward and
 * padding the link back by the same amount leaves that room without moving the
 * content — the numbers still start on the container's line, level with every
 * heading on the page.
 */
.gd-index {
	margin: 0 calc(-1 * var(--gd-index-inset));
	padding: 0;
	list-style: none;
	border-top: 1px solid var(--gd-glass-line);
	--gd-index-inset: clamp(1rem, 2vw, 1.75rem);
}

.gd-index__row { border-bottom: 1px solid var(--gd-glass-line); }

.gd-index__link {
	position: relative;
	display: grid;
	align-items: center;
	gap: var(--gd-space-3);
	grid-template-columns: auto 1fr auto auto;
	padding: clamp(1.1rem, 2.4vw, 1.9rem) var(--gd-index-inset);
	color: var(--gd-text);
	text-decoration: none;
	isolation: isolate;
	transition: color 0.3s var(--gd-ease);
}

/*
 * The row's content slides in on hover via transform rather than padding.
 * Padding would relayout the row — and therefore everything below it — on every
 * frame; a transform is composited and touches no layout at all.
 */
.gd-index__num,
.gd-index__body {
	transition: transform 0.4s var(--gd-ease);
}

.gd-index__link:hover .gd-index__num,
.gd-index__link:focus-visible .gd-index__num,
.gd-index__link:hover .gd-index__body,
.gd-index__link:focus-visible .gd-index__body {
	transform: translateX(14px);
}

/* A wash that sweeps in from the left on hover, in place of a card surface. */
.gd-index__link::before {
	content: "";
	position: absolute;
	inset: 0 -1.25rem;
	z-index: -1;
	border-radius: var(--gd-radius);
	background: linear-gradient(90deg, rgba(124, 92, 255, 0.18), rgba(34, 211, 238, 0.06) 55%, transparent);
	opacity: 0;
	transform: translateX(-14px);
	transition: opacity 0.35s var(--gd-ease), transform 0.35s var(--gd-ease);
}

.gd-index__link:hover::before,
.gd-index__link:focus-visible::before { opacity: 1; transform: none; }

.gd-index__num {
	color: var(--gd-text-3);
	font-family: var(--gd-font-mono);
	font-size: var(--gd-text-xs);
	letter-spacing: 0.1em;
}

.gd-index__body { display: grid; gap: 0.3rem; }

.gd-index__name {
	font-size: clamp(1.25rem, 0.95rem + 1.3vw, 2rem);
	font-weight: 650;
	letter-spacing: -0.035em;
	line-height: 1.1;
}

.gd-index__note {
	color: var(--gd-text-3);
	font-size: var(--gd-text-sm);
	max-width: 52ch;
}

.gd-index__mark {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	border: 1px solid var(--gd-glass-line);
	border-radius: 50%;
	color: var(--gd-text-3);
	transition: color 0.3s var(--gd-ease), border-color 0.3s var(--gd-ease),
		transform 0.35s var(--gd-spring);
}

.gd-index__link:hover .gd-index__mark {
	/* Was #fff, which vanishes on the light palettes. */
	color: var(--gd-text);
	border-color: var(--gd-accent);
	transform: rotate(-8deg) scale(1.06);
}

.gd-index__go { color: var(--gd-text-3); transition: transform 0.35s var(--gd-spring), color 0.3s var(--gd-ease); }
.gd-index__link:hover .gd-index__go { color: var(--gd-text); transform: translateX(6px); }

@media (max-width: 700px) {
	.gd-index__link { grid-template-columns: auto 1fr auto; }
	.gd-index__mark { display: none; }}

.gd-voice { margin: 0; }

.gd-voice__quote {
	margin: 0 0 var(--gd-space-3);
	font-size: clamp(1.05rem, 0.95rem + 0.55vw, 1.45rem);
	font-weight: 500;
	line-height: 1.42;
	letter-spacing: -0.015em;
	color: var(--gd-text);
	text-wrap: pretty;
}

/* A quote mark sized to sit beside the text, not to shout over it. */
.gd-voice__quote::before {
	content: "\201C";
	display: block;
	margin-bottom: -0.36em;
	color: var(--gd-violet);
	font-size: 2.6em;
	line-height: 1;
	opacity: 0.4;
}

.gd-voice__by { display: grid; gap: 0.2rem; }

.gd-voice__name { font-size: var(--gd-text-lg); font-weight: 650; }
.gd-voice__role { color: var(--gd-text-3); font-size: var(--gd-text-sm); }

/* ============================================================
   Carousel
   ============================================================ */

.gd-carousel { position: relative; }

/*
 * Native scroll-snap does the hard part: touch swipe, momentum and keyboard
 * all come free, and the script only adds autoplay and the controls.
 */
.gd-carousel__track {
	/*
	 * Aligned left, bleeding right.
	 *
	 * The lead matches where the container's content starts, so the first card
	 * sits on the same line as every heading on the page. The trailing side has
	 * no padding at all, so the row runs off the edge of the screen rather than
	 * stopping at a gutter.
	 *
	 * Measured from `100%` — the track's own width — and not from `100vw`,
	 * which counts the scrollbar and would leave the cards a few pixels out of
	 * line with the text above them.
	 */
	--gd-lead: max(var(--gd-gutter), calc((100% - var(--gd-container)) / 2 + var(--gd-gutter)));
	--gd-slide-gap: var(--gd-space-3);

	display: flex;
	gap: var(--gd-slide-gap);
	padding-inline: var(--gd-lead) 0;
	/* Room for the cards' shadow: a scroll container crops on both axes,
	   so at 0.5rem the shadow was being sliced off square. */
	padding-block: 1.4rem;
	overflow-x: auto;
	scroll-snap-type: x mandatory;
	scroll-padding-left: var(--gd-lead);
	scrollbar-width: none;
	-ms-overflow-style: none;
	overscroll-behavior-x: contain;
}

.gd-carousel__track::-webkit-scrollbar { display: none; }

/*
 * Slides are sized from the space actually on screen rather than to a fixed
 * width, so the row always ends on a half card: `--gd-shown` cards across,
 * `--gd-gaps` gaps between them, and half of one more hanging off the edge.
 * That half is the affordance — it says there is more without needing a label.
 */
.gd-carousel__slide {
	flex: 0 0 auto;
	scroll-snap-align: start;
	width: calc((100% - var(--gd-gaps) * var(--gd-slide-gap)) / var(--gd-shown));
}

.gd-carousel__track--cases { --gd-shown: 3.5; --gd-gaps: 3; }
.gd-carousel__track--voices { --gd-shown: 2.5; --gd-gaps: 2; }

@media (max-width: 1200px) {
	.gd-carousel__track--cases { --gd-shown: 2.5; --gd-gaps: 2; }
}

@media (max-width: 900px) {
	.gd-carousel__track--voices { --gd-shown: 1.5; --gd-gaps: 1; }
}

@media (max-width: 700px) {
	/* One card and a half. The gap tightens so the full one keeps its width. */
	.gd-carousel__track {
		--gd-shown: 1.5;
		--gd-gaps: 1;
		--gd-slide-gap: 0.9rem;
	}
}

.gd-carousel__nav { display: flex; gap: 0.6rem; }

.gd-carousel__arrow {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 46px;
	height: 46px;
	border: 1px solid var(--gd-glass-line-2);
	border-radius: 50%;
	background: var(--gd-glass);
	color: var(--gd-text);
	cursor: pointer;
	transition: border-color 0.25s var(--gd-ease), background 0.25s var(--gd-ease),
		transform 0.25s var(--gd-spring), opacity 0.25s var(--gd-ease);
}

.gd-carousel__arrow:hover:not(:disabled) {
	border-color: rgba(124, 92, 255, 0.7);
	background: rgba(124, 92, 255, 0.16);
	transform: translateY(-2px);
}

.gd-carousel__arrow:disabled { opacity: 0.3; cursor: default; }

.gd-carousel__dots {
	display: flex;
	justify-content: center;
	gap: 0.45rem;
	margin-top: var(--gd-space-3);
}

.gd-carousel__dot {
	width: 22px;
	height: 3px;
	padding: 0;
	border: 0;
	border-radius: 999px;
	background: rgba(255, 255, 255, 0.14);
	cursor: pointer;
	transition: background 0.3s var(--gd-ease), width 0.3s var(--gd-ease);
}

.gd-carousel__dot.is-on {
	width: 34px;
	background: linear-gradient(90deg, var(--gd-violet), var(--gd-cyan));
}

/* Testimonial slides get their own surface, distinct from the case cards. */
.gd-voice {
	display: flex;
	flex-direction: column;
	gap: 0.5rem;
	margin: 0;
	padding: var(--gd-space-3);
	border-left: 2px solid rgba(124, 92, 255, 0.55);
	border-radius: 0 var(--gd-radius) var(--gd-radius) 0;
	background: linear-gradient(120deg, rgba(255, 255, 255, 0.055), rgba(255, 255, 255, 0));
}

/*
 * White on white is nothing. On the dark palette the card lifts off the ground
 * with a wash of light; on paper it needs the opposite — a surface that sits
 * above the band, with a hairline to hold its edge.
 */
:root[data-theme="light"] .gd-voice {
	border-top: 1px solid var(--gd-glass-line);
	border-right: 1px solid var(--gd-glass-line);
	border-bottom: 1px solid var(--gd-glass-line);
	/*
	 * Translucent rather than solid: the thread runs behind these, and a solid
	 * fill blanked it out for the length of the row. At 0.62 the card still
	 * reads as a surface against the band while the line stays legible through
	 * it.
	 */
	background: rgba(255, 255, 255, 0.62);
	box-shadow: var(--gd-glow-soft);
}

.gd-voice .gd-stars { margin-bottom: 0.25rem; }
.gd-voice__by { display: grid; gap: 0.15rem; margin-top: auto; padding-top: var(--gd-space-2); }
.gd-voice__name { font-size: var(--gd-text-sm); font-weight: 700; }
.gd-voice__role { color: var(--gd-text-3); font-size: var(--gd-text-xs); }

/* ============================================================
   Services index — expansion and the thread's spark
   ============================================================ */

/*
 * Collapsed by default. `grid-template-rows: 0fr → 1fr` animates a block of
 * unknown height without hard-coding a max-height that would either clip long
 * lists or leave a gap under short ones.
 */
.gd-index__more {
	display: grid;
	grid-template-rows: 0fr;
	opacity: 0;
	transition: grid-template-rows 0.42s var(--gd-ease), opacity 0.3s var(--gd-ease);
}

/*
 * The spacing lives inside the animated track rather than on a margin of its
 * own, so the open state animates a single property instead of two competing
 * layout values.
 */
.gd-index__list {
	overflow: hidden;
	display: flex;
	flex-wrap: wrap;
	gap: 0.4rem 1.25rem;
	padding-top: 0.7rem;
}

.gd-index__link:hover .gd-index__more,
.gd-index__link:focus-visible .gd-index__more {
	grid-template-rows: 1fr;
	opacity: 1;
}

.gd-index__item {
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
	color: var(--gd-text-2);
	font-size: var(--gd-text-sm);
}

.gd-index__item svg { color: var(--gd-signal); flex: none; }

/* The row's own state once the thread has passed through it. */
.gd-index__row {
	position: relative;
	/*
	 * `overflow: hidden` cropped the mark, which grows and tilts on hover, and
	 * clipped the sweep at the row's ends. `clip` with an inset does the job the
	 * hidden was there for — keeping the expansion inside the row — while
	 * leaving the sides open for both.
	 */
	overflow: clip;
	overflow-clip-margin: 14px;
	/* Confines the expansion's reflow to this row. */
	contain: layout;
}

.gd-js .gd-index__row .gd-index__name,
.gd-js .gd-index__row .gd-index__num,
.gd-js .gd-index__row .gd-index__note {
	opacity: 0.4;
	transition: opacity 0.6s var(--gd-ease);
}

.gd-js .gd-index__row.is-lit .gd-index__name,
.gd-js .gd-index__row.is-lit .gd-index__num { opacity: 1; }
.gd-js .gd-index__row.is-lit .gd-index__note { opacity: 0.82; }

/* The wave, breaking from wherever the thread crossed this row. */
.gd-index__spark {
	position: absolute;
	top: 50%;
	left: var(--touch-x, 50%);
	width: 8px;
	height: 8px;
	margin: -4px 0 0 -4px;
	border-radius: 50%;
	opacity: 0;
	pointer-events: none;
}

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

.gd-index__row.is-lit .gd-index__spark { animation: gd-wake-flash 0.8s var(--gd-ease) both; }
.gd-index__row.is-lit .gd-index__spark::before { animation: gd-row-ring 1.2s var(--gd-ease) both; }
.gd-index__row.is-lit .gd-index__spark::after { animation: gd-row-ring 1.2s var(--gd-ease) 0.18s both; }

/* A sweep of light along the row as the charge lands. */
.gd-index__row.is-lit::after {
	content: "";
	position: absolute;
	inset: 0;
	background: linear-gradient(90deg, transparent, rgba(124, 92, 255, 0.22), transparent);
	transform: translateX(-100%);
	animation: gd-row-sweep 1.1s var(--gd-ease) both;
	pointer-events: none;
}

@keyframes gd-row-ring {
	0% { opacity: 0.9; transform: scale(1); }
	100% { opacity: 0; transform: scale(26); border-color: rgba(34, 211, 238, 0); }
}

@keyframes gd-row-sweep {
	0% { transform: translateX(-100%); }
	100% { transform: translateX(100%); }
}

@media (prefers-reduced-motion: reduce) {
	.gd-js .gd-index__row .gd-index__name,
	.gd-js .gd-index__row .gd-index__num,
	.gd-js .gd-index__row .gd-index__note { opacity: 1 !important; }

	.gd-index__spark { display: none; }
	.gd-index__row.is-lit::after { animation: none; display: none; }
	.gd-index__more { transition: none; }}

	.gd-facts__value { font-size: clamp(1.15rem, 0.9rem + 0.7vw, 1.7rem); }
	.gd-facts__label { font-size: 0.66rem; }
	.gd-hero__grid { height: 18vh; }

.gd-hero__inner {
	display: flex;
	flex: 1;
	flex-direction: column;
}

.gd-statement {
	max-width: 17ch;
	margin: 0 0 var(--gd-space-3);
	font-size: clamp(2.1rem, min(1rem + 4.4vw, 10.5vh), 5.6rem);
	font-weight: 700;
	line-height: 1.02;
	letter-spacing: -0.05em;
	text-wrap: balance;
}

.gd-hero__lede {
	max-width: 46ch;
	margin: 0 0 var(--gd-space-4);
	color: var(--gd-text-2);
	font-size: clamp(1.0625rem, 0.95rem + 0.4vw, 1.35rem);
}

/* --- Facts ------------------------------------------------------------- */

.gd-facts__item { margin: 0; }

.gd-facts__value {
	margin: 0;
	font-size: clamp(1.4rem, 1.15rem + 1vw, 2.4rem);
	font-weight: 700;
	letter-spacing: -0.04em;
	font-variant-numeric: tabular-nums;
}

.gd-facts__label {
	margin: 0.3rem 0 0;
	color: var(--gd-text-3);
	font-size: var(--gd-text-xs);
	letter-spacing: 0.09em;
	text-transform: uppercase;
}

/* --- Narrow and short screens ------------------------------------------ */

@media (max-width: 900px) {
	.gd-hero__copy { max-width: none; }

	.gd-arc {
		right: -18%;
		top: auto;
		bottom: 18%;
		width: 118%;
		transform: none;
	}

	.gd-arc__svg { opacity: 0.3; stroke-width: 3.4; }
}

@media (max-width: 620px) {
	/* Two even rows rather than three-then-one stranded on its own. */
	.gd-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }}

@media (max-height: 820px) {
	.gd-statement { margin-bottom: var(--gd-space-2); }
	.gd-hero__lede { margin-bottom: var(--gd-space-3); font-size: var(--gd-text-md); }
	.gd-hero__copy { margin-bottom: var(--gd-space-3); }
	.gd-facts { padding-top: var(--gd-space-2); }}

@media (max-height: 700px) {
	.gd-facts__value { font-size: clamp(1.15rem, 0.95rem + 0.7vw, 1.7rem); }
	.gd-facts__label { font-size: 0.66rem; }}

/* ============================================================
   Hero — full-bleed photograph
   ============================================================ */

/*
 * A full-height hero is justified here because the picture fills it. The
 * problem with the earlier layouts was forcing 100vh around a fixed amount of
 * copy, which always left a hole; an image has no such gap to open.
 */
/*
 * The hero starts at the very top of the document, with the picture running up
 * behind the header. The negative margin pulls it under the header's place in
 * the flow; the matching top padding keeps the copy clear of it.
 */
.gd-hero--shot {
	position: relative;
	display: flex;
	flex-direction: column;
	margin-top: calc(-1 * var(--gd-header-rest, 105px));
	min-height: 100svh;
	padding-block: var(--gd-header-rest, 105px) clamp(1.5rem, 3.5vh, 2.5rem);
	overflow: hidden;
	/*
	 * Deliberately not isolated: the photograph below needs to resolve its
	 * negative z-index against the root so the thread can pass over it.
	 */
}

/* Beneath the thread, which is z-index 0 at the root. */
.gd-shotbg {
	position: absolute;
	inset: 0;
	z-index: -1;
}

/*
 * Deliberately wider than the frame.
 *
 * At these proportions the photograph fills the box exactly on the horizontal,
 * which leaves no overflow — and with nothing to spare, `object-position` has
 * no effect on where the subject lands. Oversizing creates that slack so the
 * subject can be placed in the open column beside the copy.
 */
.gd-shotbg__img {
	position: absolute;
	top: 0;
	left: var(--shot-left, -1%);
	width: var(--shot-width, 128%);
	max-width: none;
	height: 100%;
	object-fit: cover;
	object-position: 50% var(--shot-y, 40%);
}

/*
 * Legibility comes from a scrim rather than a darkened file, so the image can
 * be swapped in the Customizer without anyone preparing it first.
 */
.gd-shotbg__scrim {
	position: absolute;
	inset: 0;
	background:
		linear-gradient(100deg,
			rgba(4, 5, 12, 0.97) 0%,
			rgba(4, 5, 12, 0.9) 26%,
			rgba(4, 5, 12, 0.62) 46%,
			rgba(4, 5, 12, 0.22) 66%,
			rgba(4, 5, 12, 0.35) 100%),
		linear-gradient(180deg, rgba(4, 5, 12, 0.72) 0%, transparent 26%);
}

/* Grounds the facts strip and joins the hero to the section beneath it. */
.gd-shotbg__foot {
	position: absolute;
	inset: auto 0 0;
	height: 46%;
	background: linear-gradient(180deg, transparent, rgba(4, 5, 12, 0.86) 62%, var(--gd-void) 100%);
}

/* --- Copy -------------------------------------------------------------- */

.gd-hero--shot .gd-hero__inner {
	position: relative;
	z-index: 1;
	display: flex;
	flex: 1;
	flex-direction: column;
	align-items: flex-start;
}

/*
 * Auto margins top and bottom, so the copy settles midway between the header
 * and the facts strip instead of being pinned to either edge.
 */
.gd-hero--shot .gd-hero__copy {
	max-width: min(620px, 52%);
	margin-block: auto;
}

.gd-statement {
	max-width: 16ch;
	margin: 0 0 var(--gd-space-3);
	/* Bounded harder by height: at 1280x720 and 1600x900 the width-only
	   size left the facts strip a few pixels past the fold. */
	font-size: clamp(2rem, min(1rem + 4.2vw, 8.4vh), 5.2rem);
	font-weight: 700;
	line-height: 1.02;
	letter-spacing: -0.05em;
	text-wrap: balance;
	text-shadow: 0 2px 40px rgba(4, 5, 12, 0.8);
}

.gd-hero__lede {
	max-width: 44ch;
	margin: 0 0 var(--gd-space-4);
	color: var(--gd-text-2);
	font-size: clamp(1.0625rem, 0.95rem + 0.4vw, 1.3rem);
}

.gd-hero--shot .gd-hero__actions {
	display: flex;
	flex-wrap: wrap;
	justify-content: flex-start;
	gap: var(--gd-space-2);
	margin: 0;
}

/* --- Facts ------------------------------------------------------------- */

.gd-facts {
	display: grid;
	gap: var(--gd-space-3);
	width: 100%;
	/* Rides the bottom edge of the frame, over the foot gradient. */
	margin: 0;
	padding-top: var(--gd-space-3);
	border-top: 1px solid rgba(255, 255, 255, 0.16);
	grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
}

.gd-facts__item { margin: 0; }

.gd-facts__value {
	margin: 0;
	font-size: clamp(1.4rem, 1.15rem + 1vw, 2.4rem);
	font-weight: 700;
	letter-spacing: -0.04em;
	font-variant-numeric: tabular-nums;
}

.gd-facts__label {
	margin: 0.3rem 0 0;
	color: var(--gd-text-3);
	font-size: var(--gd-text-xs);
	letter-spacing: 0.09em;
	text-transform: uppercase;
}

/* --- Responsive -------------------------------------------------------- */

@media (min-width: 1700px) {
	/* The container stops growing at 1240 but the image does not, so the
	   subject creeps right of the column centre without this. */
	.gd-shotbg__img { --shot-left: -7%; }
}

@media (max-width: 900px) {
	.gd-hero--shot .gd-hero__copy { max-width: none; }

	/* The subject moves out of the way and the scrim takes over vertically. */
	.gd-shotbg__img { --shot-left: -16%; --shot-width: 134%; --shot-y: 34%; }

	.gd-shotbg__scrim {
		background:
			linear-gradient(180deg, rgba(4, 5, 12, 0.88) 0%, rgba(4, 5, 12, 0.72) 45%, rgba(4, 5, 12, 0.5) 100%);
	}
}

@media (max-width: 620px) {
	.gd-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }}

@media (max-height: 820px) {
	.gd-hero--shot {
		padding-block: calc(var(--gd-header-rest, 105px) + clamp(0.5rem, 1.5vh, 1rem)) clamp(1rem, 2.5vh, 1.75rem);
	}
	.gd-hero--shot .gd-eyebrow { margin-bottom: var(--gd-space-1); }
	.gd-statement { margin-bottom: var(--gd-space-2); }
	.gd-hero__lede { margin-bottom: var(--gd-space-3); font-size: var(--gd-text-md); }

	.gd-facts { padding-top: var(--gd-space-2); gap: var(--gd-space-2); }}

@media (max-height: 700px) {
	.gd-hero--shot .gd-btn--lg { padding: 0.8rem 1.4rem; font-size: var(--gd-text-sm); }
	.gd-facts__value { font-size: clamp(1.15rem, 0.95rem + 0.7vw, 1.7rem); }
	.gd-facts__label { font-size: 0.66rem; }}
/* ============================================================
   Hero — opening sequence
   ============================================================ */

/*
 * The hero assembles itself on load rather than appearing all at once: the
 * photograph settles out of a slow push-in, the scrim resolves over it, a rule
 * draws across the base and the figures arrive in sequence.
 *
 * Pure CSS, so it starts with the first paint — there is no script to wait for
 * and nothing is hidden if one fails to run. The copy's own reveals are handled
 * by the shared motion system and land in the same window.
 */

@keyframes gd-open-image {
	from { opacity: 0; transform: scale(1.08); }
	to { opacity: 1; transform: scale(1); }
}

@keyframes gd-open-veil {
	from { opacity: 0; }
	to { opacity: 1; }
}

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

@keyframes gd-open-fact {
	from { opacity: 0; transform: translateY(16px); }
	to { opacity: 1; transform: none; }
}

/* A single pass of brand light across the top edge, then gone. */
@keyframes gd-open-sweep {
	0% { opacity: 0; transform: translateX(-100%); }
	28% { opacity: 1; }
	100% { opacity: 0; transform: translateX(100%); }
}

.gd-shotbg__img {
	animation: gd-open-image 2s var(--gd-ease) both;
}

.gd-shotbg__scrim,
.gd-shotbg__foot {
	animation: gd-open-veil 1.5s var(--gd-ease) 0.15s both;
}

.gd-hero--shot::before {
	content: "";
	position: absolute;
	top: 0;
	left: 0;
	z-index: 2;
	width: 100%;
	height: 2px;
	background: linear-gradient(90deg, transparent, var(--gd-brand-lift, #ef4b83), transparent);
	animation: gd-open-sweep 1.5s var(--gd-ease) both;
	pointer-events: none;
}

/*
 * The rule under the figures is drawn rather than simply present, so the strip
 * reads as being laid down last.
 */
.gd-hero--shot .gd-facts {
	position: relative;
	border-top: 0;
}

.gd-hero--shot .gd-facts::before {
	content: "";
	position: absolute;
	inset: 0 0 auto;
	height: 1px;
	background: rgba(255, 255, 255, 0.16);
	transform-origin: 0 50%;
	animation: gd-open-rule 1.1s var(--gd-ease) 0.55s both;
}

.gd-hero--shot .gd-facts__item {
	animation: gd-open-fact 0.85s var(--gd-ease) both;
}

.gd-hero--shot .gd-facts__item:nth-child(1) { animation-delay: 0.85s; }
.gd-hero--shot .gd-facts__item:nth-child(2) { animation-delay: 0.97s; }
.gd-hero--shot .gd-facts__item:nth-child(3) { animation-delay: 1.09s; }
.gd-hero--shot .gd-facts__item:nth-child(4) { animation-delay: 1.21s; }

@media (prefers-reduced-motion: reduce) {
	.gd-shotbg__img,
	.gd-shotbg__scrim,
	.gd-shotbg__foot,
	.gd-hero--shot .gd-facts::before,
	.gd-hero--shot .gd-facts__item {
		animation: none !important;
		opacity: 1;
		transform: none;
	}

	.gd-hero--shot::before { display: none; }
}

/*
 * Restored: this opt-out had been swallowed by a broken comment, so the strip
 * kept scrolling for readers who had asked for reduced motion.
 */
@media (prefers-reduced-motion: reduce) {
	.gd-marquee__track,
	.gd-tagline__track {
		animation: none !important;
	}
}

/* ============================================================
   Light mode — section rhythm
   ============================================================

   The dark palette separates its sections with light: glows, gradients and an
   atmosphere that shifts as you scroll. None of that survives on paper, which
   left every section sitting on one flat off-white.

   Banding does the same job in light. The two card-bearing sections drop onto
   the tint so their white cards have something to sit against; the list and
   timeline sections keep the page colour. Hero and footer are dark, so the
   page reads: anchor, paper, tint, paper, tint, paper, anchor.
   ============================================================ */

/*
 * The band is painted by a pseudo-element at a negative z-index, not by the
 * section's own background.
 *
 * .gd-main is deliberately not a stacking context, so a negative layer inside
 * it resolves against the root and drops below the thread. A background on the
 * section itself paints above the thread instead, which is what made the line
 * disappear across the case studies and the reviews.
 */
:root[data-theme="light"] .gd-home-section[data-carousel] {
	position: relative;
}

:root[data-theme="light"] .gd-home-section[data-carousel]::before {
	content: "";
	position: absolute;
	inset: 0;
	z-index: -1;
	background: var(--gd-slate);
	pointer-events: none;
}

/* A hairline at each change of ground, so the band reads as intended rather
   than as a rendering seam. */
:root[data-theme="light"] .gd-home-section[data-carousel] {
	border-block: 1px solid var(--gd-glass-line);
}

:root[data-theme="light"] .gd-cta {
	border-top: 1px solid var(--gd-glass-line);
}

/* ============================================================
   V3 — its own copy of the light palette
   ============================================================

   V3 is v1 standing on paper, so it starts from exactly the values v1 uses in
   light mode. They are re-declared here rather than inherited: sharing them
   would mean every colour change to v3 also changed v1's light mode, and the
   whole point of a version is that it can move on its own.

   Only light is forked. In dark, v3 falls through to the shared `:root`
   palette — which is v1's dark, and correct, since v3 is v1's design.
   ============================================================ */

:root[data-theme="light"] body.gd-v3,
:root[data-theme="light"] body.gd-story {
	--gd-void: #f6f7fb;
	--gd-abyss: #f6f7fb;
	--gd-deep: #ffffff;
	--gd-slate: #eef0f7;
	--gd-raise: #ffffff;

	--gd-glass: rgba(23, 29, 64, 0.035);
	--gd-glass-2: rgba(23, 29, 64, 0.06);
	--gd-glass-line: rgba(23, 29, 64, 0.12);
	--gd-glass-line-2: rgba(23, 29, 64, 0.22);

	/* Deepened for paper: the near-black tones read as pastel on white. */
	--gd-violet: #5b45f0;
	--gd-indigo: #4534c9;
	--gd-cyan: #0e7490;
	--gd-pink: #c0257a;

	--gd-text: #0f1330;
	--gd-text-2: #464e75;
	/* 4.95:1 on paper, 4.66:1 on the tinted band — muted text lands on both. */
	--gd-text-3: #626a8c;

	--gd-accent: var(--gd-violet);
	--gd-accent-deep: #4534c9;
	--gd-signal: #c0257a;
	--gd-danger: #c2334a;

	--gd-logo-mark: var(--gd-brand);

	--gd-glow-violet: 0 0 0 1px rgba(91, 69, 240, 0.18), 0 12px 34px -18px rgba(23, 29, 64, 0.4);
	--gd-glow-soft: 0 12px 32px -22px rgba(23, 29, 64, 0.3);
	--gd-glow-lift: 0 24px 54px -32px rgba(23, 29, 64, 0.35);
}

/* The current slide, in this version's own accent. */
:root[data-theme="light"] .gd-carousel__dot.is-on {
	background: var(--gd-accent);
}
