/* ==========================================================================
   Gemial Digital — Motion system
   --------------------------------------------------------------------------
   One orchestrated idea rather than scattered effects: the page behaves like
   an instrument coming online. Content writes itself in on load, live sites
   carry a heartbeat, figures count up when measured, and a status feed runs
   along the bottom of the hero.

   Every animated state is declared inside a
   (prefers-reduced-motion: no-preference) block, so the reduced-motion and
   no-JS paths both render fully visible, static content. Nothing here can
   leave an element stuck at opacity 0.
   ========================================================================== */


/* Shared keyframes
   ========================================================================== */

@keyframes gd-rise-in {
	from { opacity: 0; transform: translateY(18px); }
	to   { opacity: 1; transform: none; }
}

@keyframes gd-line-in {
	from { transform: translateY(105%); }
	to   { transform: none; }
}

@keyframes gd-pulse {
	0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--gd-lime) 55%, transparent); }
	70%  { box-shadow: 0 0 0 8px color-mix(in srgb, var(--gd-lime) 0%, transparent); }
	100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--gd-lime) 0%, transparent); }
}

@keyframes gd-ticker-run {
	from { transform: translate3d(0, 0, 0); }
	to   { transform: translate3d(-50%, 0, 0); }
}

@keyframes gd-sweep {
	from { transform: translateY(-100%); opacity: 0; }
	35%  { opacity: 1; }
	to   { transform: translateY(100%); opacity: 0; }
}


/* Page-load choreography
   --------------------------------------------------------------------------
   .gd-rise elements animate on load, ordered by --gd-i. Used for the hero
   only; everything below the fold uses the scroll reveal instead.
   ========================================================================== */

@media (prefers-reduced-motion: no-preference) {

	.gd-rise {
		opacity: 0;
		animation: gd-rise-in 620ms var(--gd-ease-out) forwards;
		animation-delay: calc(var(--gd-i, 0) * 90ms);
	}

	/* Headline that writes itself in, one line at a time. Each .gd-line is a
	   clipping mask; the inner span slides up from beneath it. */
	.gd-line {
		display: block;
		overflow: hidden;
		padding-block-end: 0.06em;
		margin-block-end: -0.06em;
	}

	.gd-line > span {
		display: block;
		transform: translateY(105%);
		animation: gd-line-in 780ms var(--gd-ease-out) forwards;
		animation-delay: calc(120ms + var(--gd-i, 0) * 110ms);
	}

	/* Scroll reveal. JS adds .is-visible. */
	.gd-reveal {
		opacity: 0;
		transform: translateY(16px);
		transition: opacity var(--gd-dur-slow) var(--gd-ease-out),
			transform var(--gd-dur-slow) var(--gd-ease-out);
	}

	.gd-reveal.is-visible { opacity: 1; transform: none; }

	/* Register rows cascade as the table scrolls into view. */
	.gd-cascade > * {
		opacity: 0;
		transform: translateY(12px);
		transition: opacity 520ms var(--gd-ease-out),
			transform 520ms var(--gd-ease-out);
		transition-delay: calc(var(--gd-i, 0) * 60ms);
	}

	.gd-cascade.is-visible > * { opacity: 1; transform: none; }

	/* Live status heartbeat. The one piece of ambient motion on the page, and
	   it is semantic: it marks sites that are online right now. */
	.gd-pulse { animation: gd-pulse 2.6s var(--gd-ease) infinite; }

	.gd-pulse--slow { animation-duration: 3.8s; }

	/* Single scan sweep across the register when it first enters view. */
	.gd-sweep::after {
		content: "";
		position: absolute;
		inset-inline: 0;
		inset-block-start: 0;
		block-size: 40%;
		pointer-events: none;
		background: linear-gradient(
			to bottom,
			transparent,
			color-mix(in srgb, var(--gd-lime) 7%, transparent),
			transparent
		);
		opacity: 0;
	}

	.gd-sweep.is-visible::after {
		animation: gd-sweep 1500ms var(--gd-ease) 220ms 1;
	}
}


/* Live status feed
   --------------------------------------------------------------------------
   A thin monospace band of the sites currently under management. It is a
   marquee, but a truthful one: it is a status feed, not decoration.
   The track is duplicated so the loop is seamless; the copy is hidden from
   assistive tech.
   ========================================================================== */

.gd-feed {
	position: relative;
	border-block: 1px solid var(--gd-ink-3);
	background: var(--gd-ink);
	padding-block: var(--gd-space-3);
	overflow: hidden;
	-webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
	mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}

.gd-feed__track {
	display: flex;
	inline-size: max-content;
	gap: 0;
}

@media (prefers-reduced-motion: no-preference) {
	.gd-feed__track { animation: gd-ticker-run 46s linear infinite; }
	.gd-feed:hover .gd-feed__track { animation-play-state: paused; }
}

/* Reduced motion: no scroll, and the overflow simply clips. */
@media (prefers-reduced-motion: reduce) {
	.gd-feed__track { animation: none; }
}

.gd-feed__set { display: flex; align-items: center; flex: none; }

.gd-feed__item {
	display: inline-flex;
	align-items: center;
	gap: var(--gd-space-2);
	padding-inline: var(--gd-space-6);
	font-family: var(--gd-font-mono);
	font-size: var(--gd-text-xs);
	letter-spacing: 0.04em;
	color: var(--gd-text-3);
	white-space: nowrap;
}

.gd-feed__item b {
	font-weight: var(--gd-weight-medium);
	color: var(--gd-text-on-dark-2);
}

.gd-feed__dot {
	inline-size: 5px;
	block-size: 5px;
	border-radius: var(--gd-radius-pill);
	background: var(--gd-lime);
	flex: none;
}

.gd-feed__sep { color: var(--gd-ink-3); }


/* Counters
   --------------------------------------------------------------------------
   JS writes the value; CSS only guarantees the digits do not jitter as they
   change width. Markup carries the final value as text so a no-JS reader
   still sees the real number.
   ========================================================================== */

.gd-count {
	font-variant-numeric: tabular-nums;
	font-feature-settings: "tnum" 1;
}


/* Hover micro-interactions
   ========================================================================== */

.gd-arrow {
	transition: transform var(--gd-dur) var(--gd-ease);
	flex: none;
}

a:hover > .gd-arrow,
button:hover > .gd-arrow,
.gd-hoverable:hover .gd-arrow { transform: translateX(4px); }

/* Primary button: lime wipes across from the left on hover. */
.gd-btn--primary { position: relative; overflow: hidden; z-index: 0; }

.gd-btn--primary::before {
	content: "";
	position: absolute;
	inset: 0;
	z-index: -1;
	background: var(--gd-forest-deep);
	transform: scaleX(0);
	transform-origin: 0 50%;
	transition: transform var(--gd-dur) var(--gd-ease);
}

.gd-btn--primary:hover::before { transform: scaleX(1); }

/* Cards lift and their top hairline turns lime. */
.gd-lift {
	position: relative;
	transition: transform var(--gd-dur) var(--gd-ease),
		box-shadow var(--gd-dur) var(--gd-ease),
		border-color var(--gd-dur) var(--gd-ease);
}

.gd-lift::before {
	content: "";
	position: absolute;
	inset-block-start: -1px;
	inset-inline: -1px;
	block-size: 2px;
	background: var(--gd-lime);
	transform: scaleX(0);
	transform-origin: 0 50%;
	transition: transform var(--gd-dur) var(--gd-ease);
	border-start-start-radius: var(--gd-radius-md);
	border-start-end-radius: var(--gd-radius-md);
}

.gd-lift:hover {
	transform: translateY(-3px);
	box-shadow: var(--gd-shadow-lg);
	border-color: var(--gd-line-strong);
}

.gd-lift:hover::before { transform: scaleX(1); }


/* Reduced motion — belt and braces
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
	.gd-rise,
	.gd-reveal,
	.gd-cascade > *,
	.gd-line > span {
		opacity: 1 !important;
		transform: none !important;
		animation: none !important;
		transition: none !important;
	}

	.gd-pulse { animation: none !important; }
	.gd-sweep::after { display: none; }
	.gd-lift:hover { transform: none; }
}
