Component library
Back to catalog

Dashboard glass hero

An 860px full-bleed photo hero under a dark glass wash: lime-dot eyebrow, a four-line headline with alternate lime lines, two pill buttons, tracked labels, a network-signal pill and an avatar trust card, beside a translucent operations dashboard with stat cells, a live-network row and an expandable forecast row.

herofull-bleedphotoglassdashboardstatsdisclosurelogistics

Open preview in a new tab

Use this component

Three ways to get Dashboard glass hero into one of your own projects. The first two carry the component's files with them, so nothing has to fetch this page.

Start here

Placement prompt

The whole thing: what to do, where it goes, what must not change, and every one of the component's 5 files appended underneath with the path each belongs at. Paste it into Claude Code inside the destination project — it fetches nothing and needs nothing else.

Read it first — the files are added underneath when you copy
Add Dashboard glass hero from my Astro Component Library to this Astro project.

Library page, for reference only:
https://astro.baysixmedia.com/components/dashboard-glass-hero/

Place it in:
[ENTER PAGE, FILE, OR ROUTE]

Position:
[ENTER WHERE IT SHOULD APPEAR]

Every file you need is appended below, each fenced with the path it belongs at. Write them there, relative to the project root. Do not import anything from the deployed library at runtime, and do not fetch the library page — the files below are the source of truth.

Adapt its demo content, links, colors, typography, and images to this project through documented props where possible. Preserve its responsive behavior, accessibility, interaction logic, reduced-motion behavior, and support for multiple instances. Avoid changing unrelated project code.

Test it at desktop, tablet, and mobile widths. Run the project's available check and build commands. Report the files changed and any differences from the library version. Do not commit or deploy.

Install kit

The 5 files this component needs and nothing else, each fenced with the path it belongs at, followed by what the library's own verification covered and what it did not. No instructions attached — for placing it yourself, or handing it to something that has its own.

Read it first
Install kit: "Dashboard glass hero"
Library page: https://astro.baysixmedia.com/components/dashboard-glass-hero/
Files: 5 required, 1 supporting

Write each block below to the path named in its header, relative to the project root. A block marked (required) is one the component does not work without; (supporting) is documentation. Nothing here needs downloading except a block that says so in as many words.

===== FILE (required): src/components/DashboardGlassHero.astro =====
---
/**
 * DashboardGlassHero
 *
 * An 860px full-bleed photo hero under a dark glass wash. Left: a pill
 * eyebrow, a four-line display headline whose alternate lines are lime, a
 * paragraph, two pill buttons, a row of tracked labels, a "network signal"
 * pill and a trust card with three round avatars. Right: a translucent glass
 * operations dashboard — status title, four stat cells, a four-up live
 * network row and an expandable forecast row.
 *
 * Layout: side by side from 1200px, dashboard stacked under the copy from
 * 810px, dashboard hidden below 810px (as the reference does on a phone).
 * Leaves 88px at the top for an overlay header.
 *
 * No script. The load-in is a CSS animation that ends at rest (so no-JS
 * shows everything) and is skipped under prefers-reduced-motion. The
 * forecast row is a native <details> disclosure.
 *
 * Reads --nv-* design tokens from any ancestor, with fallbacks.
 */

type IconName =
	| "shield"
	| "clock"
	| "leaf"
	| "box"
	| "road"
	| "ship"
	| "plane"
	| "warehouse";

export interface DashboardGlassHeroStat {
	label: string;
	value: string;
	note?: string;
	icon?: IconName;
	/** Any CSS colour. Defaults cycle lime, aqua, green, purple. */
	color?: string;
}

export interface DashboardGlassHeroNetworkItem {
	label: string;
	value: string;
	icon?: IconName;
	color?: string;
}

export interface DashboardGlassHeroProps {
	eyebrow?: string;
	/**
	 * One entry per line. Lines alternate white / lime, starting white.
	 * Trailing punctuation on a line is drawn white, as measured.
	 */
	headline?: string[];
	body?: string;
	primaryCta?: { label: string; href: string } | null;
	secondaryCta?: { label: string; href: string } | null;
	/** The tracked labels under the buttons, separated by lime dots. */
	labels?: string[];
	/** The "network signal" pill. "" drops it. */
	signal?: string;
	/**
	 * The trust card: real social proof only. Omit it (or pass `null`) and
	 * there is no card. Nothing in it has a default — no title, no line, no
	 * stars, no faces — because a claim nobody made is not a placeholder.
	 */
	trust?: {
		title?: string;
		text?: string;
		/** Five stars after the title. Only when `true`: a rating is never assumed. */
		stars?: boolean;
		avatars?: { src?: string; alt?: string }[];
	} | null;
	/** The photo behind everything. Omitted, or `src: ""`, renders a labelled placeholder. */
	background?: { src?: string; alt?: string };
	/**
	 * `null` drops the dashboard; the copy stays left-aligned. So does a
	 * dashboard with no eyebrow, title, stats, network or insight: nothing in
	 * it is filled in for you.
	 */
	dashboard?: {
		eyebrow?: string;
		title?: string;
		stats?: DashboardGlassHeroStat[];
		networkTitle?: string;
		network?: DashboardGlassHeroNetworkItem[];
		/**
		 * The forecast row. With `detail` it is a disclosure that reveals the
		 * detail; with `href` it is a link; with neither it is static text.
		 */
		insight?: { label: string; text: string; detail?: string; href?: string } | null;
	} | null;
	/** Plays the load-in rise. Skipped under reduced motion regardless. */
	animate?: boolean;
	fontStylesheet?: string;
	id?: string;
}

interface Props extends DashboardGlassHeroProps {}

/* No copy, figures, photo or links of its own: the catalog demo passes them
   through the registry's props, so a copied hero never ships an invented
   business or an invented number. */
const {
	eyebrow = "",
	headline = [],
	body = "",
	primaryCta = null,
	secondaryCta = null,
	labels = [],
	signal = "",
	trust = null,
	background = {},
	dashboard = {},
	animate = true,
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Inter:wght@400;500;600;700;800&display=swap",
	id,
} = Astro.props;

/* Only the photo passed. */
const bg = { src: background?.src ?? "", alt: background?.alt ?? "" };

/* A button needs both halves: a label with no destination, or the reverse, is not drawn. */
const link = (l: { label: string; href: string } | null | undefined) => (l && l.label && l.href ? l : null);
const primary = link(primaryCta);
const secondary = link(secondaryCta);

/*
 * Only what the caller supplied. A card with nothing in it is not drawn, and
 * stars appear only on an explicit `stars: true` - the component never
 * manufactures a rating, a customer count or a face.
 */
const trustData =
	trust && (trust.title || trust.text || (trust.avatars && trust.avatars.length > 0))
		? { title: trust.title, text: trust.text, stars: trust.stars === true, avatars: trust.avatars ?? [] }
		: null;

const TONES = ["#c8ff38", "#73f5df", "#73ef80", "#a88cff"];

/*
 * The dashboard shows only what the caller passed: no status line, figure,
 * network count or forecast is ever filled in, so an invented 98.6% never
 * sits beside real numbers. The network heading is generic chrome and is
 * the one thing that still merges. A dashboard with nothing in it is not
 * drawn.
 */
const dash = dashboard
	? {
			networkTitle: "Live network",
			...dashboard,
		}
	: null;
const showDash =
	!!dash && !!(dash.eyebrow || dash.title || (dash.stats && dash.stats.length > 0) || (dash.network && dash.network.length > 0) || dash.insight);

const uid = id ?? `dgh-${Math.random().toString(36).slice(2, 8)}`;
const headingId = `${uid}-title`;
const insightId = `${uid}-insight`;

/* Split trailing punctuation off a headline line so it can be drawn white. */
const lines = headline.map((line, i) => {
	const m = line.match(/^(.*?)([.!?,;:…]+)$/);
	return { word: m ? m[1] : line, punct: m ? m[2] : "", accent: i % 2 === 1 };
});

const ICONS: Record<IconName, string> = {
	shield: '<path d="M12 3l7 3v5.2c0 4.3-2.9 7.8-7 9.8-4.1-2-7-5.5-7-9.8V6l7-3z"/><path d="M9 12l2 2 4-4"/>',
	clock: '<circle cx="12" cy="12" r="8"/><path d="M12 8v4l2.8 1.8"/>',
	leaf: '<path d="M5 19c0-8 6-14 14-14 0 8-6 14-14 14z"/><path d="M5 19l7.5-7.5"/>',
	box: '<path d="M12 3l8 4.5v9L12 21l-8-4.5v-9L12 3z"/><path d="M4 7.5l8 4.5 8-4.5M12 12v9"/>',
	road: '<path d="M9 3L5 21M15 3l4 18M12 4v2.5M12 10.5v3M12 17.5v3"/>',
	ship: '<path d="M3 15.5l2.2 4.5h13.6l2.2-4.5H3z"/><path d="M6 15.5V10h12v5.5M9.5 10V6.5h5V10M12 3.5v3"/>',
	plane: '<path d="M10.5 20l1.5-.8 1.5.8M12 3.5c.8 0 1.3.7 1.3 1.6V10l7.2 4v1.8l-7.2-2.1v4.3l2 1.5V20L12 19.2 9.2 20v-.5l2-1.5v-4.3L4 15.8V14l7.2-4V5.1c0-.9.5-1.6.8-1.6z"/>',
	warehouse: '<path d="M3 21V9l9-5 9 5v12"/><path d="M7 21v-8h10v8M7 17h10"/>',
};
const iconFor = (name: IconName | undefined, fallback: IconName) => ICONS[name ?? fallback] ?? ICONS[fallback];
const STAT_FALLBACK: IconName[] = ["shield", "clock", "leaf", "box"];
const NET_FALLBACK: IconName[] = ["road", "ship", "plane", "warehouse"];

const arrow = '<path d="M7 17L17 7M8.5 7H17v8.5"/>';

/* A photo alone is not a hero: with no copy, link, proof or dashboard, render nothing. */
const hasUtility = labels.length > 0 || !!signal || !!trustData;
const hasContent = !!eyebrow || lines.length > 0 || !!body || !!primary || !!secondary || hasUtility || showDash;
---

{hasContent && fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
{hasContent && (
<section
	class:list={["dgh", { "dgh--animate": animate }]}
	id={id}
	aria-labelledby={lines.length > 0 ? headingId : undefined}
>
	<div class="dgh-bg">
		{
			bg.src ? (
				<img
					src={bg.src}
					alt={bg.alt ?? ""}
					width="2000"
					height="1200"
					loading="eager"
					fetchpriority="high"
					decoding="async"
				/>
			) : (
				<div class="dgh-ph" role="img" aria-label="Image placeholder: wide landscape photo">
					<div class="dgh-ph-text" aria-hidden="true">
						<span class="dgh-ph-label">Background photo</span>
						<span class="dgh-ph-hint">Wide landscape photo</span>
						<span class="dgh-ph-spec">5:3 · 2000 × 1200 px</span>
					</div>
				</div>
			)
		}
	</div>
	<div class="dgh-wash" aria-hidden="true"></div>

	<div class="dgh-inner">
		<div class="dgh-copy">
			{
				eyebrow && (
					<p class="dgh-eyebrow">
						<span class="dgh-dot" aria-hidden="true" />
						{eyebrow}
					</p>
				)
			}
			{lines.length > 0 && (
			<h1 class="dgh-title" id={headingId}>
				{
					lines.map((l) => (
						<span class:list={["dgh-line", { "dgh-line--accent": l.accent }]}>
							{l.word}{l.punct && <span class="dgh-punct">{l.punct}</span>}
						</span>
					))
				}
			</h1>
			)}
			{body && <p class="dgh-body">{body}</p>}
			{
				(primary || secondary) && (
					<div class="dgh-ctas">
						{primary && (
							<a class="dgh-btn dgh-btn--primary" href={primary.href}>
								<span>{primary.label}</span>
								<svg class="dgh-btn-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false" set:html={arrow} />
							</a>
						)}
						{secondary && (
							<a class="dgh-btn dgh-btn--secondary" href={secondary.href}>
								<span>{secondary.label}</span>
								<svg class="dgh-btn-icon dgh-btn-icon--sm" viewBox="0 0 24 24" aria-hidden="true" focusable="false" set:html={arrow} />
							</a>
						)}
					</div>
				)
			}
			{hasUtility && (
			<div class="dgh-utility">
				{
					labels.length > 0 && (
						<ul class="dgh-labels">
							{labels.map((label) => (
								<li>{label}</li>
							))}
						</ul>
					)
				}
				{
					signal && (
						<p class="dgh-signal">
							<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
								<circle cx="12" cy="12" r="1.8" fill="currentColor" stroke="none" />
								<path class="dgh-wave dgh-wave--1" d="M8.6 8.6a4.8 4.8 0 0 0 0 6.8M15.4 8.6a4.8 4.8 0 0 1 0 6.8" />
								<path class="dgh-wave dgh-wave--2" d="M5.8 5.8a8.8 8.8 0 0 0 0 12.4M18.2 5.8a8.8 8.8 0 0 1 0 12.4" />
							</svg>
							<span>{signal}</span>
						</p>
					)
				}
				{
					trustData && (
						<div class="dgh-trust">
							{trustData.avatars && trustData.avatars.length > 0 && (
								<ul class="dgh-avatars">
									{trustData.avatars.map((a) => (
										<li class="dgh-avatar">
											{a.src ? (
												<img src={a.src} alt={a.alt ?? ""} width="34" height="34" loading="eager" decoding="async" />
											) : (
												<span class="dgh-avatar-ph" role="img" aria-label="Avatar placeholder: customer headshot, square 160 × 160 px" />
											)}
										</li>
									))}
								</ul>
							)}
							<div class="dgh-trust-copy">
								{(trustData.title || trustData.stars) && (
									<p class="dgh-trust-title">
										{trustData.title}
										{trustData.stars && <span class="dgh-stars" aria-hidden="true">★★★★★</span>}
									</p>
								)}
								{trustData.text && <p class="dgh-trust-text">{trustData.text}</p>}
							</div>
						</div>
					)
				}
			</div>
			)}
		</div>

		{
			showDash && dash && (
				<div class="dgh-dash">
					<span class="dgh-glow dgh-glow--lime" aria-hidden="true" />
					<span class="dgh-glow dgh-glow--aqua" aria-hidden="true" />
					<div class="dgh-dash-head">
						{dash.eyebrow && (
							<p class="dgh-dash-eyebrow">
								<span class="dgh-dot dgh-dot--sm" aria-hidden="true" />
								{dash.eyebrow}
							</p>
						)}
						<svg class="dgh-pulse" viewBox="0 0 224 27" aria-hidden="true" focusable="false">
							<path
								class="dgh-pulse-line"
								pathLength="1"
								d="M1 15.5H58l4-6 4.5 11 4-16 4.5 18 3.5-7H116l3.5-4 3.5 8 4.5-12.5 4 14.5 3.5-6H176l4.5-8.5 4 11.5 3.5-3H219"
							/>
							<circle class="dgh-pulse-dot" cx="219" cy="15.5" r="2.6" />
						</svg>
					</div>
					{dash.title && <h2 class="dgh-dash-title">{dash.title}</h2>}

					{dash.stats && dash.stats.length > 0 && (
						<ul class="dgh-stats">
							{dash.stats.map((s, i) => (
								<li class="dgh-stat" style={`--dgh-tone: ${s.color ?? TONES[i % TONES.length]};`}>
									<span class="dgh-stat-icon" aria-hidden="true">
										<svg viewBox="0 0 24 24" focusable="false" set:html={iconFor(s.icon, STAT_FALLBACK[i % 4])} />
									</span>
									<span class="dgh-stat-label">{s.label}</span>
									<strong class="dgh-stat-value">{s.value}</strong>
									{s.note && <span class="dgh-stat-note">{s.note}</span>}
								</li>
							))}
						</ul>
					)}

					{dash.network && dash.network.length > 0 && (
						<div class="dgh-net">
							{dash.networkTitle && <h3 class="dgh-net-title">{dash.networkTitle}</h3>}
							<ul class="dgh-net-grid">
								{dash.network.map((n, i) => (
									<li class="dgh-net-item" style={`--dgh-tone: ${n.color ?? TONES[i % TONES.length]};`}>
										<svg class="dgh-net-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false" set:html={iconFor(n.icon, NET_FALLBACK[i % 4])} />
										<strong class="dgh-net-value">{n.value}</strong>
										<span class="dgh-net-label">{n.label}</span>
									</li>
								))}
							</ul>
						</div>
					)}

					{dash.insight &&
						(dash.insight.detail && !dash.insight.href ? (
							<details class="dgh-insight">
								<summary class="dgh-insight-row">
									<span class="dgh-insight-tag">
										<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
											<path d="M12 3v5M12 16v5M3 12h5M16 12h5M6.3 6.3l2.5 2.5M15.2 15.2l2.5 2.5M6.3 17.7l2.5-2.5M15.2 8.8l2.5-2.5" />
										</svg>
										{dash.insight.label}
									</span>
									<span class="dgh-insight-text">{dash.insight.text}</span>
									<span class="dgh-insight-chev" aria-hidden="true">
										<svg viewBox="0 0 24 24" focusable="false"><path d="M9.5 6l6 6-6 6" /></svg>
									</span>
								</summary>
								<p class="dgh-insight-detail" id={insightId}>{dash.insight.detail}</p>
							</details>
						) : dash.insight.href ? (
							<a class="dgh-insight-row dgh-insight-row--link" href={dash.insight.href}>
								<span class="dgh-insight-tag">
									<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
										<path d="M12 3v5M12 16v5M3 12h5M16 12h5M6.3 6.3l2.5 2.5M15.2 15.2l2.5 2.5M6.3 17.7l2.5-2.5M15.2 8.8l2.5-2.5" />
									</svg>
									{dash.insight.label}
								</span>
								<span class="dgh-insight-text">{dash.insight.text}</span>
								<span class="dgh-insight-chev" aria-hidden="true">
									<svg viewBox="0 0 24 24" focusable="false"><path d="M9.5 6l6 6-6 6" /></svg>
								</span>
							</a>
						) : (
							<div class="dgh-insight-row dgh-insight-row--static">
								<span class="dgh-insight-tag">{dash.insight.label}</span>
								<span class="dgh-insight-text">{dash.insight.text}</span>
							</div>
						))}
				</div>
			)
		}
	</div>
</section>
)}

<style>
	.dgh {
		--dgh-ink: var(--nv-ink, #08110f);
		--dgh-lime: var(--nv-lime, #c8ff38);
		--dgh-text: #f7faf7;
		--dgh-font: var(--nv-font, Manrope, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif);
		--dgh-ui: var(--nv-font-ui, Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif);
		--dgh-ease: cubic-bezier(0.22, 1, 0.36, 1);

		position: relative;
		box-sizing: border-box;
		display: flex;
		flex-direction: column;
		justify-content: center;
		min-height: 800px;
		padding: 96px 20px 56px;
		overflow: hidden;
		isolation: isolate;
		background: var(--dgh-ink);
		color: var(--dgh-text);
		font-family: var(--dgh-font);
	}

	.dgh *,
	.dgh *::before,
	.dgh *::after {
		box-sizing: border-box;
	}

	/* Zero-specificity resets, so every class below wins over them. */
	:where(.dgh) :where(p, h1, h2, h3, ul) {
		margin: 0;
	}

	:where(.dgh) :where(ul) {
		padding: 0;
		list-style: none;
	}

	/* ---- background ---- */

	.dgh-bg {
		position: absolute;
		inset: 0;
		z-index: -2;
	}

	.dgh-bg img {
		display: block;
		width: 100%;
		height: 100%;
		object-fit: cover;
		object-position: center 70%;
	}

	/* The measured wash runs 0.25 → 0.08 left to right. The default dusk photo
	   is already dark, so the wash sits close to that: a little deeper behind
	   the copy column (0.42) for white type, near-clear on the right where the
	   glass dashboard carries its own contrast. See README → Approximations. */
	.dgh-wash {
		position: absolute;
		inset: 0;
		z-index: -1;
		background:
			linear-gradient(180deg, rgb(6 16 13 / 0.2) 0%, rgb(6 16 13 / 0) 26%, rgb(6 16 13 / 0) 70%, rgb(6 16 13 / 0.35) 100%),
			linear-gradient(90deg, rgb(6 16 13 / 0.42) 0%, rgb(6 16 13 / 0.25) 38%, rgb(6 16 13 / 0.08) 100%);
		-webkit-backdrop-filter: blur(1px);
		backdrop-filter: blur(1px);
	}

	.dgh-ph {
		display: grid;
		place-items: end;
		width: 100%;
		height: 100%;
		padding: 20px 24px;
		text-align: right;
		background: repeating-linear-gradient(135deg, #1a2522 0 12px, #202d29 12px 24px);
		color: rgb(247 250 247 / 0.6);
	}

	.dgh-ph-text {
		display: flex;
		flex-direction: column;
		align-items: flex-end;
		gap: 6px;
	}

	.dgh-ph-label {
		font: 600 12px/1 var(--dgh-ui);
		letter-spacing: 0.08em;
		text-transform: uppercase;
	}

	.dgh-ph-hint {
		max-width: 30ch;
		font: 500 14px/1.4 var(--dgh-ui);
	}

	.dgh-ph-spec {
		font: 400 12px/1.2 var(--dgh-ui);
	}

	/* ---- layout ---- */

	.dgh-inner {
		display: flex;
		flex-direction: column;
		gap: 28px;
		width: 100%;
		max-width: 1310px;
		margin: 0 auto;
	}

	.dgh-copy {
		display: flex;
		flex-direction: column;
		align-items: flex-start;
		width: 100%;
		max-width: 490px;
		min-width: 0;
	}

	/* ---- copy ---- */

	.dgh-eyebrow {
		display: inline-flex;
		align-items: center;
		gap: 8px;
		min-height: 34px;
		max-width: 100%;
		padding: 7px 15px;
		border-radius: 999px;
		background: rgb(8 17 15 / 0.54);
		-webkit-backdrop-filter: blur(10px);
		backdrop-filter: blur(10px);
		color: var(--dgh-lime);
		font: 800 12px/14.4px var(--dgh-font);
		letter-spacing: 0.72px;
		text-transform: uppercase;
	}

	.dgh-dot {
		flex: none;
		width: 8px;
		height: 8px;
		border-radius: 999px;
		background: var(--dgh-lime);
	}

	.dgh-dot--sm {
		width: 6px;
		height: 6px;
	}

	.dgh-title {
		margin-top: 26px;
		color: #fff;
		font-family: var(--dgh-font);
		font-weight: 800;
		font-size: clamp(44px, 16.4vw, 62px);
		line-height: 1.02;
		letter-spacing: -0.055em;
	}

	.dgh-line {
		display: block;
	}

	.dgh-line--accent {
		color: var(--dgh-lime);
	}

	.dgh-punct {
		color: #fff;
	}

	.dgh-body {
		margin-top: 28px;
		color: rgb(255 255 255 / 0.78);
		font: 400 16px/24.8px var(--dgh-font);
	}

	.dgh-ctas {
		display: flex;
		flex-direction: column;
		gap: 12px;
		width: 100%;
		margin-top: 32px;
	}

	.dgh-btn {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		gap: 10px;
		min-height: 56px;
		padding: 0 22px;
		border-radius: 999px;
		font: 800 15px/18px var(--dgh-font);
		text-decoration: none;
		transition:
			transform 0.3s var(--dgh-ease),
			background-color 0.3s var(--dgh-ease);
	}

	.dgh-btn--primary {
		background: var(--dgh-lime);
		color: var(--dgh-ink);
	}

	.dgh-btn--secondary {
		background: rgb(16 27 24 / 0.92);
		-webkit-backdrop-filter: blur(12px);
		backdrop-filter: blur(12px);
		color: #f7fafc;
	}

	.dgh-btn:hover {
		transform: scale(1.1) translateY(-2px);
	}

	.dgh-btn:focus-visible {
		outline: 2px solid var(--dgh-lime);
		outline-offset: 3px;
	}

	.dgh-btn--primary:focus-visible {
		outline-color: #fff;
	}

	.dgh-btn-icon {
		flex: none;
		width: 18px;
		height: 18px;
		fill: none;
		stroke: currentColor;
		stroke-width: 2;
		stroke-linecap: round;
		stroke-linejoin: round;
	}

	.dgh-btn-icon--sm {
		width: 16px;
		height: 16px;
	}

	.dgh-utility {
		display: flex;
		flex-direction: column;
		align-items: flex-start;
		gap: 14px;
		width: 100%;
		margin-top: 28px;
	}

	.dgh-labels {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		gap: 6px 7px;
		color: #fff;
		font: 700 12px/14.4px var(--dgh-font);
		letter-spacing: 0.6px;
		text-transform: uppercase;
	}

	.dgh-labels li {
		display: inline-flex;
		align-items: center;
		gap: 7px;
	}

	.dgh-labels li + li::before {
		content: "";
		width: 5px;
		height: 5px;
		border-radius: 999px;
		background: var(--dgh-lime);
	}

	.dgh-signal {
		display: inline-flex;
		align-items: center;
		gap: 10px;
		min-height: 38px;
		padding: 10px 14px;
		border-radius: 999px;
		background: rgb(8 17 15 / 0.66);
		-webkit-backdrop-filter: blur(16px);
		backdrop-filter: blur(16px);
		color: #f7fafc;
		font: 800 10px/12px var(--dgh-font);
		letter-spacing: 0.7px;
		text-transform: uppercase;
	}

	.dgh-signal svg {
		flex: none;
		width: 16px;
		height: 16px;
		color: var(--dgh-lime);
		fill: none;
		stroke: currentColor;
		stroke-width: 1.8;
		stroke-linecap: round;
	}

	.dgh-trust {
		display: flex;
		align-items: center;
		gap: 8px;
		width: 100%;
		max-width: 410px;
		min-height: 66px;
		padding: 10px 12px;
		border-radius: 18px;
		background: rgb(8 17 15 / 0.58);
		-webkit-backdrop-filter: blur(14px);
		backdrop-filter: blur(14px);
	}

	.dgh-avatars {
		display: flex;
		flex: none;
		gap: 8px;
	}

	.dgh-avatar img,
	.dgh-avatar-ph {
		display: block;
		width: 34px;
		height: 34px;
		border-radius: 999px;
		object-fit: cover;
	}

	.dgh-avatar-ph {
		background: repeating-linear-gradient(135deg, #2b3934 0 5px, #34443e 5px 10px);
		box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.18);
	}

	.dgh-trust-copy {
		display: flex;
		flex-direction: column;
		gap: 2px;
		min-width: 0;
	}

	.dgh-trust-title {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		gap: 0 7px;
		color: #f7fafc;
		font: 700 10px/12px var(--dgh-font);
		letter-spacing: 0.5px;
		text-transform: uppercase;
	}

	.dgh-stars {
		color: var(--dgh-lime);
		font: 700 9px/11px var(--dgh-ui);
		letter-spacing: 1.32px;
	}

	.dgh-trust-text {
		color: #f7fafc;
		font: 600 11px/13.2px var(--dgh-font);
	}

	/* ---- dashboard ---- */

	.dgh-dash {
		position: relative;
		display: none;
		flex-direction: column;
		width: 100%;
		max-width: 668px;
		min-height: 478px;
		padding: 20px;
		overflow: hidden;
		isolation: isolate;
		border: 0.67px solid rgb(255 255 255 / 0.18);
		border-radius: 22px;
		background-color: rgb(8 15 13 / 0.66);
		background-image: linear-gradient(145deg, rgb(255 255 255 / 0.075), rgb(255 255 255 / 0.02));
		box-shadow:
			0 22px 63px rgb(0 0 0 / 0.42),
			inset 0 1px 0 rgb(255 255 255 / 0.1),
			inset 0 -1px 0 rgb(255 255 255 / 0.024);
		-webkit-backdrop-filter: blur(18px) saturate(1.35);
		backdrop-filter: blur(18px) saturate(1.35);
		opacity: 0.98;
		font-family: var(--dgh-ui);
	}

	.dgh-glow {
		position: absolute;
		z-index: -1;
		border-radius: 50%;
		pointer-events: none;
	}

	.dgh-glow--lime {
		top: -153px;
		right: -97px;
		width: 252px;
		height: 252px;
		background: radial-gradient(circle, rgb(200 255 56 / 0.19), transparent 68%);
	}

	.dgh-glow--aqua {
		bottom: -146px;
		left: -62px;
		width: 203px;
		height: 203px;
		background: radial-gradient(circle, rgb(115 245 223 / 0.13), transparent 70%);
	}

	.dgh-dash-head {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: 17px;
		min-height: 42px;
		padding-bottom: 15px;
	}

	.dgh-dash-eyebrow {
		display: flex;
		align-items: center;
		gap: 7px;
		color: var(--dgh-lime);
		font: 800 8px/1.2 var(--dgh-ui);
		letter-spacing: 0.96px;
		text-transform: uppercase;
		white-space: nowrap;
	}

	.dgh-pulse {
		flex: 0 1 224px;
		min-width: 0;
		height: 27px;
		filter: drop-shadow(0 0 5px rgb(200 255 56 / 0.36));
	}

	.dgh-pulse-line {
		fill: none;
		stroke: var(--dgh-lime);
		stroke-width: 1.3;
		stroke-linejoin: round;
		stroke-linecap: round;
	}

	.dgh-pulse-dot {
		fill: var(--dgh-lime);
	}

	.dgh-dash-title {
		color: var(--dgh-text);
		font: 750 32px/32px var(--dgh-ui);
		letter-spacing: -1.44px;
	}

	.dgh-stats {
		display: grid;
		grid-template-columns: repeat(4, minmax(0, 1fr));
		margin-top: 18px;
		border-top: 1px solid rgb(255 255 255 / 0.08);
		border-bottom: 1px solid rgb(255 255 255 / 0.08);
	}

	.dgh-stat {
		display: flex;
		flex-direction: column;
		align-items: center;
		padding: 17px 13px 15px;
		text-align: center;
	}

	.dgh-stat + .dgh-stat,
	.dgh-net-item + .dgh-net-item {
		border-left: 1px solid rgb(255 255 255 / 0.08);
	}

	.dgh-stat-icon {
		display: grid;
		place-items: center;
		width: 45px;
		height: 45px;
		border: 0.67px solid rgb(255 255 255 / 0.11);
		border-radius: 50%;
		background: rgb(255 255 255 / 0.035);
		box-shadow: inset 0 0 17px rgb(255 255 255 / 0.024);
		color: var(--dgh-tone);
	}

	.dgh-stat-icon svg,
	.dgh-net-icon {
		width: 20px;
		height: 20px;
		fill: none;
		stroke: currentColor;
		stroke-width: 1.7;
		stroke-linecap: round;
		stroke-linejoin: round;
	}

	.dgh-stat-label {
		display: flex;
		align-items: center;
		justify-content: center;
		min-height: 24px;
		margin-top: 12px;
		color: rgb(247 250 247 / 0.62);
		font: 750 8px/11.2px var(--dgh-ui);
		letter-spacing: 0.64px;
		text-transform: uppercase;
	}

	.dgh-stat-value {
		margin-top: 6px;
		color: var(--dgh-tone);
		font: 820 20px/20px var(--dgh-ui);
		letter-spacing: -0.7px;
	}

	.dgh-stat-note {
		margin-top: 4px;
		color: rgb(247 250 247 / 0.72);
		font: 400 8px/10.8px var(--dgh-ui);
	}

	.dgh-net {
		padding-top: 18px;
	}

	.dgh-net-title {
		color: rgb(247 250 247 / 0.72);
		font: 800 8px/10px var(--dgh-ui);
		letter-spacing: 0.88px;
		text-transform: uppercase;
	}

	.dgh-net-grid {
		display: grid;
		grid-template-columns: repeat(4, minmax(0, 1fr));
		margin-top: 13px;
	}

	.dgh-net-item {
		display: flex;
		flex-direction: column;
		align-items: center;
		padding: 0 13px 15px;
		text-align: center;
	}

	.dgh-net-icon {
		width: 24px;
		height: 24px;
		color: var(--dgh-tone);
	}

	.dgh-net-value {
		margin-top: 10px;
		color: var(--dgh-text);
		font: 800 19px/19px var(--dgh-ui);
		letter-spacing: -0.665px;
	}

	.dgh-net-label {
		margin-top: 5px;
		color: rgb(247 250 247 / 0.62);
		font: 400 8px/10px var(--dgh-ui);
	}

	/* ---- forecast row ---- */

	.dgh-insight {
		margin-top: 0;
	}

	.dgh-insight-row {
		display: grid;
		grid-template-columns: auto minmax(0, 1fr) 24px;
		align-items: center;
		gap: 10px;
		min-height: 44px;
		padding: 8px 10px 8px 8px;
		border: 0.67px solid rgb(255 255 255 / 0.11);
		border-radius: 999px;
		background: rgb(255 255 255 / 0.035);
		color: inherit;
		text-decoration: none;
		cursor: pointer;
		list-style: none;
		transition:
			border-color 0.18s ease,
			background-color 0.18s ease,
			transform 0.18s ease;
	}

	.dgh-insight-row::-webkit-details-marker {
		display: none;
	}

	.dgh-insight-row--static {
		grid-template-columns: auto minmax(0, 1fr);
		cursor: default;
	}

	.dgh-insight-row:not(.dgh-insight-row--static):hover {
		background: rgb(255 255 255 / 0.07);
		transform: translateX(1px);
	}

	.dgh-insight-row:focus-visible {
		outline: 2px solid var(--dgh-lime);
		outline-offset: 2px;
	}

	.dgh-insight-tag {
		display: inline-flex;
		align-items: center;
		gap: 5px;
		min-height: 24px;
		padding: 0 8px;
		border-radius: 999px;
		background: rgb(200 255 56 / 0.08);
		color: var(--dgh-lime);
		font: 850 8px/1 var(--dgh-ui);
		letter-spacing: 0.56px;
		text-transform: uppercase;
		white-space: nowrap;
	}

	.dgh-insight-tag svg {
		width: 11px;
		height: 11px;
		fill: none;
		stroke: currentColor;
		stroke-width: 2;
		stroke-linecap: round;
	}

	.dgh-insight-text {
		color: rgb(247 250 247 / 0.78);
		font: 400 8px/11.6px var(--dgh-ui);
	}

	.dgh-insight-chev {
		display: grid;
		place-items: center;
		width: 24px;
		height: 24px;
		border-radius: 50%;
		color: var(--dgh-text);
		transition: transform 0.18s ease;
	}

	.dgh-insight-chev svg {
		width: 13px;
		height: 13px;
		fill: none;
		stroke: currentColor;
		stroke-width: 2;
		stroke-linecap: round;
		stroke-linejoin: round;
	}

	.dgh-insight[open] .dgh-insight-chev {
		transform: rotate(90deg);
	}

	.dgh-insight-detail {
		padding: 10px 16px 0;
		color: rgb(247 250 247 / 0.72);
		font: 400 10px/15px var(--dgh-ui);
	}

	/* ---- load-in ----
	   A CSS animation, not a script: it always finishes at rest, so without
	   JavaScript the content still ends visible, and nothing is hidden when
	   motion is reduced because the animation never applies. */
	@media (prefers-reduced-motion: no-preference) {
		.dgh--animate .dgh-copy {
			animation: dgh-rise 0.5s var(--dgh-ease) both;
		}

		.dgh--animate .dgh-dash {
			animation: dgh-rise 0.6s var(--dgh-ease) 0.12s both;
		}

		.dgh--animate .dgh-pulse-line {
			stroke-dasharray: 1;
			animation: dgh-draw 1.4s ease-out 0.35s both;
		}

		.dgh-wave--1 {
			animation: dgh-wave 2.4s ease-in-out infinite;
		}

		.dgh-wave--2 {
			animation: dgh-wave 2.4s ease-in-out 0.3s infinite;
		}
	}

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

	@keyframes dgh-draw {
		from {
			stroke-dashoffset: 1;
		}
		to {
			stroke-dashoffset: 0;
		}
	}

	@keyframes dgh-wave {
		0%,
		100% {
			opacity: 1;
		}
		50% {
			opacity: 0.35;
		}
	}

	@media (prefers-reduced-motion: reduce) {
		.dgh-btn:hover,
		.dgh-insight-row:not(.dgh-insight-row--static):hover {
			transform: none;
		}

		.dgh-insight-chev {
			transition: none;
		}
	}

	/* Phone: the reference keeps only the first two labels on one line
	   rather than wrapping a third under them. */
	@media (max-width: 809.98px) {
		.dgh-labels li:nth-child(n + 3) {
			display: none;
		}
	}

	/* ---- tablet: dashboard stacks under the copy ---- */
	@media (min-width: 810px) {
		.dgh {
			min-height: 860px;
			padding: 88px 40px 92px;
		}

		.dgh-inner {
			gap: 48px;
		}

		.dgh-title {
			font-size: 66px;
			line-height: 61.38px;
			letter-spacing: -3.6px;
		}

		.dgh-ctas {
			flex-direction: row;
			flex-wrap: wrap;
			gap: 16px;
			width: auto;
		}

		.dgh-labels {
			gap: 10px;
		}

		.dgh-labels li {
			gap: 10px;
		}

		.dgh-dash {
			display: flex;
		}
	}

	/* ---- desktop: side by side ---- */
	@media (min-width: 1200px) {
		/* Measured: the copy starts 168px down and the dashboard 267px down
		   in an 860px hero. The overlay header occupies the top 78px. */
		.dgh {
			padding: 151px 64px 29px;
		}

		.dgh-inner {
			flex-direction: row;
			align-items: center;
			justify-content: space-between;
			gap: 42px;
		}

		.dgh-copy {
			flex: 0 0 490px;
		}

		.dgh-dash {
			flex: 0 1 668px;
			margin-top: 32px;
		}
	}
</style>

===== END src/components/DashboardGlassHero.astro =====

===== FILE (supporting): src/components/DashboardGlassHero.md =====
# Dashboard glass hero

An 860px full-bleed photo hero under a dark glass wash. Copy on the left:
pill eyebrow, four-line headline with alternate lime lines, paragraph, two
pill buttons, tracked labels, a network-signal pill and an avatar trust card.
On the right, a translucent operations dashboard: status title, four stat
cells, a live-network row and an expandable forecast row.

Catalog page: `/components/dashboard-glass-hero`

## Purpose

The opening section of a freight, logistics or operations-software homepage,
where the product is a control tower and the hero should look like one. It
sits directly under a ticker and leaves room at the top for an overlay header.

Use a simpler hero if you have no numbers worth showing: the dashboard only
earns its space when the stats are real.

## Files required

**`DashboardGlassHero.astro`**: one file, the whole component.

- No imports, no npm packages, no icon library, no framework.
- No script. Icons are inline SVG.
- Loads Manrope and Inter from Google Fonts unless `fontStylesheet=""`.

`DashboardGlassHero.md`: this file. Documentation, not required to run.

Photos (the catalog demo's, optional; see **Images and credits**):
`public/dashboard-glass-hero/desert-highway.webp`, `avatar-1.webp`,
`avatar-2.webp`, `avatar-3.webp`. The component has no default photo.

**The component ships no copy, figures or photo of its own.** Eyebrow,
headline, paragraph, buttons, labels, signal pill, background and every
dashboard field default to empty, so a copied hero never carries the catalog's
fictional control tower, its lane counts, its 98.6% or its forecast. The
catalog demo passes them through the registry's `props`. Each block is drawn
only when its data is present; a dashboard with nothing in it is not drawn;
and with no copy, link, proof or dashboard at all the hero renders nothing.

## Basic usage

```astro
---
import DashboardGlassHero from "../components/DashboardGlassHero.astro";
---

<DashboardGlassHero
  headline={["Ship", "anything.", "See", "everything."]}
  background={{ src: "/img/your-photo.webp", alt: "" }}
  primaryCta={{ label: "Plan your shipment", href: "/quote" }}
  secondaryCta={{ label: "Open route brief", href: "#platform" }}
  dashboard={{
    title: "All lanes running clear",
    stats: [{ label: "On-time arrivals", value: "98.6%", icon: "clock" }],
  }}
/>
```

No client directive and no wrapper. It renders the page's `<h1>`.

## Props

| Prop | Type | Default | What it does |
| :--- | :--- | :--- | :--- |
| `eyebrow` | `string?` | `""` | Pill above the headline. `""` drops it. |
| `headline` | `string[]?` | `[]` | One entry per line. Lines alternate white / lime, starting white. Trailing punctuation is drawn white. `[]` drops the `<h1>` (and the section's `aria-labelledby`). |
| `body` | `string?` | `""` | `""` drops it. |
| `primaryCta` | `{ label, href } \| null` | `null` | Lime pill. |
| `secondaryCta` | `{ label, href } \| null` | `null` | Dark glass pill. |
| `labels` | `string[]?` | `[]` | Tracked labels with lime dot separators. Only the first two show on a phone. |
| `signal` | `string?` | `""` | The signal pill. `""` drops it. |
| `trust` | `{ title?, text?, stars?, avatars? } \| null` | none — no card | Real social proof only; nothing in it has a default. Stars need an explicit `stars: true`. Each avatar is `{ src?, alt? }`; `src: ""` shows a placeholder circle. The catalog demo passes its card explicitly. |
| `background` | `{ src?, alt? }` | `{}` — none | Omitted, or `src: ""`, renders a generic labelled placeholder. |
| `dashboard` | object \| `null` | `{}` — none drawn | `null`, or a dashboard with no `eyebrow`, `title`, `stats`, `network` or `insight`, drops the dashboard. Keys below. Only what you pass is drawn: no figure is ever filled in. |
| `animate` | `boolean?` | `true` | The load-in rise and heartbeat draw. Off under reduced motion regardless. |
| `fontStylesheet` | `string?` | Manrope + Inter | `""` to skip. |
| `id` | `string?` | none | Root id; also seeds the heading and forecast ids. |

`dashboard` keys (each optional, none has a default except `networkTitle`):

| Key | Type | What it does |
| :--- | :--- | :--- |
| `eyebrow` | `string` | Lime label beside the heartbeat line. Omitted: no label. |
| `title` | `string` | The big status line (`<h2>`). Omitted: no status line. |
| `stats` | `{ label, value, note?, icon?, color? }[]` | The four stat cells. `icon`: `shield`, `clock`, `leaf`, `box`, `road`, `ship`, `plane`, `warehouse`. `color` is any CSS colour; defaults cycle lime, aqua, green, purple. |
| `networkTitle` | `string` | Label over the network row (`<h3>`). Defaults to the generic `"Live network"`; shown only with a `network` row. |
| `network` | `{ label, value, icon?, color? }[]` | The four network cells. |
| `insight` | `{ label, text, detail?, href? } \| null` | The forecast row. See below. |

## Behaviour

**Layout.** Side by side from 1200px (copy 490px, dashboard up to 668px,
space-between, dashboard 16px lower than centre as measured). From 810px to
1199px the dashboard stacks under the copy. Below 810px it is hidden and the
buttons go full width and stack, matching the reference phone capture.

**Hover** (all transitions ease-out, transforms off under reduced motion):

| Element | Change |
| :--- | :--- |
| Both hero buttons | `scale(1.1) translateY(-2px)`, 0.3s |
| Forecast row | background `rgba(255,255,255,.035)` → `.07`, `translateX(1px)`, 0.18s (the measured transition) |

**Animation.**

- Load-in: the copy column rises 18px and fades in over 0.5s; the dashboard
  follows 0.12s later over 0.6s. A CSS animation with `both` fill, so it always
  finishes at rest and needs no script.
- The heartbeat line draws itself once (1.4s) after the dashboard lands.
- The two arcs of the signal icon pulse softly (2.4s loop).
- Under `prefers-reduced-motion: reduce` none of these run.

**Forecast row.** With `detail` it is a `<details>`/`<summary>` disclosure:
the row is the summary, the chevron turns 90° when open, and the detail
sentence appears under it. A disclosure was chosen over a link because the
insight is a sentence of context, not a destination; keeping it in place
avoids a dead link in a demo. Pass `href` instead to make the row a link.

## Accessibility

- `<section aria-labelledby>` pointing at the `<h1>` (omitted when there is no headline).
- The headline is one `<h1>`; each line is a block `<span>`, so it reads as a
  sentence.
- The forecast disclosure uses native `<summary>`: focusable, Enter/Space to
  toggle, expanded state announced without ARIA.
- Visible `:focus-visible` rings (lime on dark, white on the lime button).
- Icons, dots, glows, stars and the heartbeat are `aria-hidden`. The trust
  title already says the rating in words.
- Buttons are 56px tall; the forecast row is at least 44px.
- The dashboard's 8px labels are the measured size. They are small; the
  dashboard is hidden on phones, where they would be hardest to read.

## Integration notes

- Leaves the top clear for an overlay header at top 14px: 88px padding up to
  1199px, 151px at desktop.
- Renders the page's `<h1>`. Use it once per page.
- Theme with `--nv-ink`, `--nv-lime`, `--nv-font`, `--nv-font-ui` on any
  ancestor. The component never sets them.

## Images and credits

| File | Slot | Size | Credit |
| :--- | :--- | :--- | :--- |
| `public/dashboard-glass-hero/desert-highway.webp` | Background: a mountain range at dusk under cloud, a highway with a guardrail along the bottom, a white truck cab lower right | 2000 × 1200 | Photo: Unsplash xJte6gX3mQE, Unsplash License |
| `public/dashboard-glass-hero/avatar-1.webp` | Trust card avatar 1 | 160 × 160 | Photo: Unsplash iFgRcqHznqg, Unsplash License |
| `public/dashboard-glass-hero/avatar-2.webp` | Trust card avatar 2 | 160 × 160 | Photo: Unsplash mEZ3PoFGs_k, Unsplash License |
| `public/dashboard-glass-hero/avatar-3.webp` | Trust card avatar 3 | 160 × 160 | Photo: Unsplash 7YVZYZeITc8, Unsplash License |

The background is framed with `object-position: center 70%` so the highway
and cab stay in view. It is decorative (`alt=""`). If you use a photo that
carries meaning, pass `background={{ src, alt: "…" }}`. The avatars are also
decorative, because the trust line beside them carries the meaning.

## Approximations

- **Overlay is slightly deeper than measured.** The reference wash is
  `linear-gradient(90deg, rgba(6,16,13,.25) 33%, .08)`. This one runs
  `.42 → .25 (38%) → .08` plus a light top (.2) and bottom (.35) vignette, so
  white type stays legible over any photo swapped in. A much brighter photo
  may need the left stop raised again.
- **Headline colour.** Measured lime on the headline is `#b8ff26` and on the
  stars `#beff22`; both use the one `--nv-lime` token (`#c8ff38`).
- **Tablet.** No tablet capture exists. The stacked dashboard between 810 and
  1199px is inferred.
- **Phone.** The reference crops its own headline at the top (the content is
  taller than its fixed 800px). This version grows instead of cropping, and
  sizes the headline `clamp(44px, 16.4vw, 62px)` so "everything." stays on one
  line down to 320px. Line height is estimated from the screenshot.
- **Avatars** are spaced 8px apart as measured, not overlapping.
- **Stat cell separators** (1px, 8% white) are read off the screenshot; the
  dump reports the borders as mixed.
- **Signal-wave pulse** is inferred: the pill was caught at opacity 0.74 in
  the dump, which suggests a loop. The pulse is on the icon arcs only, so the
  text keeps full contrast.
- **Heartbeat path** is an original drawing at the measured 224 × 27 box and
  drop-shadow, not traced.
- **Icons** are original line drawings, not the reference set.

===== END src/components/DashboardGlassHero.md =====

===== FILE (required): public/dashboard-glass-hero/desert-highway.webp =====
[binary asset — download https://astro.baysixmedia.com/dashboard-glass-hero/desert-highway.webp and save it to public/dashboard-glass-hero/desert-highway.webp]

===== FILE (required): public/dashboard-glass-hero/avatar-1.webp =====
[binary asset — download https://astro.baysixmedia.com/dashboard-glass-hero/avatar-1.webp and save it to public/dashboard-glass-hero/avatar-1.webp]

===== FILE (required): public/dashboard-glass-hero/avatar-2.webp =====
[binary asset — download https://astro.baysixmedia.com/dashboard-glass-hero/avatar-2.webp and save it to public/dashboard-glass-hero/avatar-2.webp]

===== FILE (required): public/dashboard-glass-hero/avatar-3.webp =====
[binary asset — download https://astro.baysixmedia.com/dashboard-glass-hero/avatar-3.webp and save it to public/dashboard-glass-hero/avatar-3.webp]

--- VERIFIED IN THE COMPONENT LIBRARY ---
Enrolled for verification but never run.
No verification run has been recorded.
No checks have passed yet.
No usable source fingerprint, so these results cannot be tied to the files below.

--- NOT PROVEN ---
- ClientRouter: not applicable — this component ships no script.

--- WHAT THIS DOES NOT CERTIFY ---
Verification was performed on the unmodified component, in an isolated fixture, in the
library. It says nothing about this project's CSS, routes, layout or data, and nothing
about the component once you adapt it. A verified component is a good starting point,
not a guarantee about the installation you are about to build.

--- TEST IN THIS PROJECT AFTER INSTALLING OR CUSTOMISING ---
- Horizontal overflow at 1440, 1280, 1024, 834, 390 and 320px.
- Keyboard operation end to end, including visible focus.
- The no-JavaScript render.
- prefers-reduced-motion: reduce.
- Two instances on one page, if this project renders more than one.
- If this project uses <ClientRouter />: teardown and reinitialisation across a real navigation.

Component reference

A fragment for a request you write yourself, e.g. “I want to add [paste] to Section 3 of my homepage.” It names the component, links its page, and fixes what must not change — the design build and its effects — while leaving branding and content open. It does not carry the code.

Read it first
“Dashboard glass hero” from my Astro Component Library:
https://astro.baysixmedia.com/components/dashboard-glass-hero/

Use the existing library component as the implementation source. Preserve its design structure, layout, proportions, spacing system, responsive behavior, visual effects, animations, transitions, hover states, interactions, and accessibility.

The copy, colors, typography, images, and other content may be adapted to match the destination website. Do not redesign, simplify, or reinterpret the component’s underlying design build or effects.

Component readiness

Judged on recorded evidence, not on how it looks in the frame above. A description of a component is not a result.

Interactivity
Static
Profile
core only
Installation files
5 required + 1 supporting
Verified
Never
Client navigation
Not applicable — ships no script
Source fingerprint
No fingerprint recorded
Production ready
Not yet

Not yet verified

Enrolled for verification, but never run. Silence is reported as silence rather than assumed to be fine.

Open the verification fixture

src/components/library/DashboardGlassHero.astro

This component is not one file on its own

  • assetsCopy these across too — they are referenced by URL, so nothing in the source above points at them: public/dashboard-glass-hero/desert-highway.webp, public/dashboard-glass-hero/avatar-1.webp, public/dashboard-glass-hero/avatar-2.webp, public/dashboard-glass-hero/avatar-3.webp. Leave them behind and those URLs resolve to nothing on the other end. What each component does without them is in its README.
---
/**
 * DashboardGlassHero
 *
 * An 860px full-bleed photo hero under a dark glass wash. Left: a pill
 * eyebrow, a four-line display headline whose alternate lines are lime, a
 * paragraph, two pill buttons, a row of tracked labels, a "network signal"
 * pill and a trust card with three round avatars. Right: a translucent glass
 * operations dashboard — status title, four stat cells, a four-up live
 * network row and an expandable forecast row.
 *
 * Layout: side by side from 1200px, dashboard stacked under the copy from
 * 810px, dashboard hidden below 810px (as the reference does on a phone).
 * Leaves 88px at the top for an overlay header.
 *
 * No script. The load-in is a CSS animation that ends at rest (so no-JS
 * shows everything) and is skipped under prefers-reduced-motion. The
 * forecast row is a native <details> disclosure.
 *
 * Reads --nv-* design tokens from any ancestor, with fallbacks.
 */

type IconName =
	| "shield"
	| "clock"
	| "leaf"
	| "box"
	| "road"
	| "ship"
	| "plane"
	| "warehouse";

export interface DashboardGlassHeroStat {
	label: string;
	value: string;
	note?: string;
	icon?: IconName;
	/** Any CSS colour. Defaults cycle lime, aqua, green, purple. */
	color?: string;
}

export interface DashboardGlassHeroNetworkItem {
	label: string;
	value: string;
	icon?: IconName;
	color?: string;
}

export interface DashboardGlassHeroProps {
	eyebrow?: string;
	/**
	 * One entry per line. Lines alternate white / lime, starting white.
	 * Trailing punctuation on a line is drawn white, as measured.
	 */
	headline?: string[];
	body?: string;
	primaryCta?: { label: string; href: string } | null;
	secondaryCta?: { label: string; href: string } | null;
	/** The tracked labels under the buttons, separated by lime dots. */
	labels?: string[];
	/** The "network signal" pill. "" drops it. */
	signal?: string;
	/**
	 * The trust card: real social proof only. Omit it (or pass `null`) and
	 * there is no card. Nothing in it has a default — no title, no line, no
	 * stars, no faces — because a claim nobody made is not a placeholder.
	 */
	trust?: {
		title?: string;
		text?: string;
		/** Five stars after the title. Only when `true`: a rating is never assumed. */
		stars?: boolean;
		avatars?: { src?: string; alt?: string }[];
	} | null;
	/** The photo behind everything. Omitted, or `src: ""`, renders a labelled placeholder. */
	background?: { src?: string; alt?: string };
	/**
	 * `null` drops the dashboard; the copy stays left-aligned. So does a
	 * dashboard with no eyebrow, title, stats, network or insight: nothing in
	 * it is filled in for you.
	 */
	dashboard?: {
		eyebrow?: string;
		title?: string;
		stats?: DashboardGlassHeroStat[];
		networkTitle?: string;
		network?: DashboardGlassHeroNetworkItem[];
		/**
		 * The forecast row. With `detail` it is a disclosure that reveals the
		 * detail; with `href` it is a link; with neither it is static text.
		 */
		insight?: { label: string; text: string; detail?: string; href?: string } | null;
	} | null;
	/** Plays the load-in rise. Skipped under reduced motion regardless. */
	animate?: boolean;
	fontStylesheet?: string;
	id?: string;
}

interface Props extends DashboardGlassHeroProps {}

/* No copy, figures, photo or links of its own: the catalog demo passes them
   through the registry's props, so a copied hero never ships an invented
   business or an invented number. */
const {
	eyebrow = "",
	headline = [],
	body = "",
	primaryCta = null,
	secondaryCta = null,
	labels = [],
	signal = "",
	trust = null,
	background = {},
	dashboard = {},
	animate = true,
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Inter:wght@400;500;600;700;800&display=swap",
	id,
} = Astro.props;

/* Only the photo passed. */
const bg = { src: background?.src ?? "", alt: background?.alt ?? "" };

/* A button needs both halves: a label with no destination, or the reverse, is not drawn. */
const link = (l: { label: string; href: string } | null | undefined) => (l && l.label && l.href ? l : null);
const primary = link(primaryCta);
const secondary = link(secondaryCta);

/*
 * Only what the caller supplied. A card with nothing in it is not drawn, and
 * stars appear only on an explicit `stars: true` - the component never
 * manufactures a rating, a customer count or a face.
 */
const trustData =
	trust && (trust.title || trust.text || (trust.avatars && trust.avatars.length > 0))
		? { title: trust.title, text: trust.text, stars: trust.stars === true, avatars: trust.avatars ?? [] }
		: null;

const TONES = ["#c8ff38", "#73f5df", "#73ef80", "#a88cff"];

/*
 * The dashboard shows only what the caller passed: no status line, figure,
 * network count or forecast is ever filled in, so an invented 98.6% never
 * sits beside real numbers. The network heading is generic chrome and is
 * the one thing that still merges. A dashboard with nothing in it is not
 * drawn.
 */
const dash = dashboard
	? {
			networkTitle: "Live network",
			...dashboard,
		}
	: null;
const showDash =
	!!dash && !!(dash.eyebrow || dash.title || (dash.stats && dash.stats.length > 0) || (dash.network && dash.network.length > 0) || dash.insight);

const uid = id ?? `dgh-${Math.random().toString(36).slice(2, 8)}`;
const headingId = `${uid}-title`;
const insightId = `${uid}-insight`;

/* Split trailing punctuation off a headline line so it can be drawn white. */
const lines = headline.map((line, i) => {
	const m = line.match(/^(.*?)([.!?,;:…]+)$/);
	return { word: m ? m[1] : line, punct: m ? m[2] : "", accent: i % 2 === 1 };
});

const ICONS: Record<IconName, string> = {
	shield: '<path d="M12 3l7 3v5.2c0 4.3-2.9 7.8-7 9.8-4.1-2-7-5.5-7-9.8V6l7-3z"/><path d="M9 12l2 2 4-4"/>',
	clock: '<circle cx="12" cy="12" r="8"/><path d="M12 8v4l2.8 1.8"/>',
	leaf: '<path d="M5 19c0-8 6-14 14-14 0 8-6 14-14 14z"/><path d="M5 19l7.5-7.5"/>',
	box: '<path d="M12 3l8 4.5v9L12 21l-8-4.5v-9L12 3z"/><path d="M4 7.5l8 4.5 8-4.5M12 12v9"/>',
	road: '<path d="M9 3L5 21M15 3l4 18M12 4v2.5M12 10.5v3M12 17.5v3"/>',
	ship: '<path d="M3 15.5l2.2 4.5h13.6l2.2-4.5H3z"/><path d="M6 15.5V10h12v5.5M9.5 10V6.5h5V10M12 3.5v3"/>',
	plane: '<path d="M10.5 20l1.5-.8 1.5.8M12 3.5c.8 0 1.3.7 1.3 1.6V10l7.2 4v1.8l-7.2-2.1v4.3l2 1.5V20L12 19.2 9.2 20v-.5l2-1.5v-4.3L4 15.8V14l7.2-4V5.1c0-.9.5-1.6.8-1.6z"/>',
	warehouse: '<path d="M3 21V9l9-5 9 5v12"/><path d="M7 21v-8h10v8M7 17h10"/>',
};
const iconFor = (name: IconName | undefined, fallback: IconName) => ICONS[name ?? fallback] ?? ICONS[fallback];
const STAT_FALLBACK: IconName[] = ["shield", "clock", "leaf", "box"];
const NET_FALLBACK: IconName[] = ["road", "ship", "plane", "warehouse"];

const arrow = '<path d="M7 17L17 7M8.5 7H17v8.5"/>';

/* A photo alone is not a hero: with no copy, link, proof or dashboard, render nothing. */
const hasUtility = labels.length > 0 || !!signal || !!trustData;
const hasContent = !!eyebrow || lines.length > 0 || !!body || !!primary || !!secondary || hasUtility || showDash;
---

{hasContent && fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
{hasContent && (
<section
	class:list={["dgh", { "dgh--animate": animate }]}
	id={id}
	aria-labelledby={lines.length > 0 ? headingId : undefined}
>
	<div class="dgh-bg">
		{
			bg.src ? (
				<img
					src={bg.src}
					alt={bg.alt ?? ""}
					width="2000"
					height="1200"
					loading="eager"
					fetchpriority="high"
					decoding="async"
				/>
			) : (
				<div class="dgh-ph" role="img" aria-label="Image placeholder: wide landscape photo">
					<div class="dgh-ph-text" aria-hidden="true">
						<span class="dgh-ph-label">Background photo</span>
						<span class="dgh-ph-hint">Wide landscape photo</span>
						<span class="dgh-ph-spec">5:3 · 2000 × 1200 px</span>
					</div>
				</div>
			)
		}
	</div>
	<div class="dgh-wash" aria-hidden="true"></div>

	<div class="dgh-inner">
		<div class="dgh-copy">
			{
				eyebrow && (
					<p class="dgh-eyebrow">
						<span class="dgh-dot" aria-hidden="true" />
						{eyebrow}
					</p>
				)
			}
			{lines.length > 0 && (
			<h1 class="dgh-title" id={headingId}>
				{
					lines.map((l) => (
						<span class:list={["dgh-line", { "dgh-line--accent": l.accent }]}>
							{l.word}{l.punct && <span class="dgh-punct">{l.punct}</span>}
						</span>
					))
				}
			</h1>
			)}
			{body && <p class="dgh-body">{body}</p>}
			{
				(primary || secondary) && (
					<div class="dgh-ctas">
						{primary && (
							<a class="dgh-btn dgh-btn--primary" href={primary.href}>
								<span>{primary.label}</span>
								<svg class="dgh-btn-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false" set:html={arrow} />
							</a>
						)}
						{secondary && (
							<a class="dgh-btn dgh-btn--secondary" href={secondary.href}>
								<span>{secondary.label}</span>
								<svg class="dgh-btn-icon dgh-btn-icon--sm" viewBox="0 0 24 24" aria-hidden="true" focusable="false" set:html={arrow} />
							</a>
						)}
					</div>
				)
			}
			{hasUtility && (
			<div class="dgh-utility">
				{
					labels.length > 0 && (
						<ul class="dgh-labels">
							{labels.map((label) => (
								<li>{label}</li>
							))}
						</ul>
					)
				}
				{
					signal && (
						<p class="dgh-signal">
							<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
								<circle cx="12" cy="12" r="1.8" fill="currentColor" stroke="none" />
								<path class="dgh-wave dgh-wave--1" d="M8.6 8.6a4.8 4.8 0 0 0 0 6.8M15.4 8.6a4.8 4.8 0 0 1 0 6.8" />
								<path class="dgh-wave dgh-wave--2" d="M5.8 5.8a8.8 8.8 0 0 0 0 12.4M18.2 5.8a8.8 8.8 0 0 1 0 12.4" />
							</svg>
							<span>{signal}</span>
						</p>
					)
				}
				{
					trustData && (
						<div class="dgh-trust">
							{trustData.avatars && trustData.avatars.length > 0 && (
								<ul class="dgh-avatars">
									{trustData.avatars.map((a) => (
										<li class="dgh-avatar">
											{a.src ? (
												<img src={a.src} alt={a.alt ?? ""} width="34" height="34" loading="eager" decoding="async" />
											) : (
												<span class="dgh-avatar-ph" role="img" aria-label="Avatar placeholder: customer headshot, square 160 × 160 px" />
											)}
										</li>
									))}
								</ul>
							)}
							<div class="dgh-trust-copy">
								{(trustData.title || trustData.stars) && (
									<p class="dgh-trust-title">
										{trustData.title}
										{trustData.stars && <span class="dgh-stars" aria-hidden="true">★★★★★</span>}
									</p>
								)}
								{trustData.text && <p class="dgh-trust-text">{trustData.text}</p>}
							</div>
						</div>
					)
				}
			</div>
			)}
		</div>

		{
			showDash && dash && (
				<div class="dgh-dash">
					<span class="dgh-glow dgh-glow--lime" aria-hidden="true" />
					<span class="dgh-glow dgh-glow--aqua" aria-hidden="true" />
					<div class="dgh-dash-head">
						{dash.eyebrow && (
							<p class="dgh-dash-eyebrow">
								<span class="dgh-dot dgh-dot--sm" aria-hidden="true" />
								{dash.eyebrow}
							</p>
						)}
						<svg class="dgh-pulse" viewBox="0 0 224 27" aria-hidden="true" focusable="false">
							<path
								class="dgh-pulse-line"
								pathLength="1"
								d="M1 15.5H58l4-6 4.5 11 4-16 4.5 18 3.5-7H116l3.5-4 3.5 8 4.5-12.5 4 14.5 3.5-6H176l4.5-8.5 4 11.5 3.5-3H219"
							/>
							<circle class="dgh-pulse-dot" cx="219" cy="15.5" r="2.6" />
						</svg>
					</div>
					{dash.title && <h2 class="dgh-dash-title">{dash.title}</h2>}

					{dash.stats && dash.stats.length > 0 && (
						<ul class="dgh-stats">
							{dash.stats.map((s, i) => (
								<li class="dgh-stat" style={`--dgh-tone: ${s.color ?? TONES[i % TONES.length]};`}>
									<span class="dgh-stat-icon" aria-hidden="true">
										<svg viewBox="0 0 24 24" focusable="false" set:html={iconFor(s.icon, STAT_FALLBACK[i % 4])} />
									</span>
									<span class="dgh-stat-label">{s.label}</span>
									<strong class="dgh-stat-value">{s.value}</strong>
									{s.note && <span class="dgh-stat-note">{s.note}</span>}
								</li>
							))}
						</ul>
					)}

					{dash.network && dash.network.length > 0 && (
						<div class="dgh-net">
							{dash.networkTitle && <h3 class="dgh-net-title">{dash.networkTitle}</h3>}
							<ul class="dgh-net-grid">
								{dash.network.map((n, i) => (
									<li class="dgh-net-item" style={`--dgh-tone: ${n.color ?? TONES[i % TONES.length]};`}>
										<svg class="dgh-net-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false" set:html={iconFor(n.icon, NET_FALLBACK[i % 4])} />
										<strong class="dgh-net-value">{n.value}</strong>
										<span class="dgh-net-label">{n.label}</span>
									</li>
								))}
							</ul>
						</div>
					)}

					{dash.insight &&
						(dash.insight.detail && !dash.insight.href ? (
							<details class="dgh-insight">
								<summary class="dgh-insight-row">
									<span class="dgh-insight-tag">
										<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
											<path d="M12 3v5M12 16v5M3 12h5M16 12h5M6.3 6.3l2.5 2.5M15.2 15.2l2.5 2.5M6.3 17.7l2.5-2.5M15.2 8.8l2.5-2.5" />
										</svg>
										{dash.insight.label}
									</span>
									<span class="dgh-insight-text">{dash.insight.text}</span>
									<span class="dgh-insight-chev" aria-hidden="true">
										<svg viewBox="0 0 24 24" focusable="false"><path d="M9.5 6l6 6-6 6" /></svg>
									</span>
								</summary>
								<p class="dgh-insight-detail" id={insightId}>{dash.insight.detail}</p>
							</details>
						) : dash.insight.href ? (
							<a class="dgh-insight-row dgh-insight-row--link" href={dash.insight.href}>
								<span class="dgh-insight-tag">
									<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
										<path d="M12 3v5M12 16v5M3 12h5M16 12h5M6.3 6.3l2.5 2.5M15.2 15.2l2.5 2.5M6.3 17.7l2.5-2.5M15.2 8.8l2.5-2.5" />
									</svg>
									{dash.insight.label}
								</span>
								<span class="dgh-insight-text">{dash.insight.text}</span>
								<span class="dgh-insight-chev" aria-hidden="true">
									<svg viewBox="0 0 24 24" focusable="false"><path d="M9.5 6l6 6-6 6" /></svg>
								</span>
							</a>
						) : (
							<div class="dgh-insight-row dgh-insight-row--static">
								<span class="dgh-insight-tag">{dash.insight.label}</span>
								<span class="dgh-insight-text">{dash.insight.text}</span>
							</div>
						))}
				</div>
			)
		}
	</div>
</section>
)}

<style>
	.dgh {
		--dgh-ink: var(--nv-ink, #08110f);
		--dgh-lime: var(--nv-lime, #c8ff38);
		--dgh-text: #f7faf7;
		--dgh-font: var(--nv-font, Manrope, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif);
		--dgh-ui: var(--nv-font-ui, Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif);
		--dgh-ease: cubic-bezier(0.22, 1, 0.36, 1);

		position: relative;
		box-sizing: border-box;
		display: flex;
		flex-direction: column;
		justify-content: center;
		min-height: 800px;
		padding: 96px 20px 56px;
		overflow: hidden;
		isolation: isolate;
		background: var(--dgh-ink);
		color: var(--dgh-text);
		font-family: var(--dgh-font);
	}

	.dgh *,
	.dgh *::before,
	.dgh *::after {
		box-sizing: border-box;
	}

	/* Zero-specificity resets, so every class below wins over them. */
	:where(.dgh) :where(p, h1, h2, h3, ul) {
		margin: 0;
	}

	:where(.dgh) :where(ul) {
		padding: 0;
		list-style: none;
	}

	/* ---- background ---- */

	.dgh-bg {
		position: absolute;
		inset: 0;
		z-index: -2;
	}

	.dgh-bg img {
		display: block;
		width: 100%;
		height: 100%;
		object-fit: cover;
		object-position: center 70%;
	}

	/* The measured wash runs 0.25 → 0.08 left to right. The default dusk photo
	   is already dark, so the wash sits close to that: a little deeper behind
	   the copy column (0.42) for white type, near-clear on the right where the
	   glass dashboard carries its own contrast. See README → Approximations. */
	.dgh-wash {
		position: absolute;
		inset: 0;
		z-index: -1;
		background:
			linear-gradient(180deg, rgb(6 16 13 / 0.2) 0%, rgb(6 16 13 / 0) 26%, rgb(6 16 13 / 0) 70%, rgb(6 16 13 / 0.35) 100%),
			linear-gradient(90deg, rgb(6 16 13 / 0.42) 0%, rgb(6 16 13 / 0.25) 38%, rgb(6 16 13 / 0.08) 100%);
		-webkit-backdrop-filter: blur(1px);
		backdrop-filter: blur(1px);
	}

	.dgh-ph {
		display: grid;
		place-items: end;
		width: 100%;
		height: 100%;
		padding: 20px 24px;
		text-align: right;
		background: repeating-linear-gradient(135deg, #1a2522 0 12px, #202d29 12px 24px);
		color: rgb(247 250 247 / 0.6);
	}

	.dgh-ph-text {
		display: flex;
		flex-direction: column;
		align-items: flex-end;
		gap: 6px;
	}

	.dgh-ph-label {
		font: 600 12px/1 var(--dgh-ui);
		letter-spacing: 0.08em;
		text-transform: uppercase;
	}

	.dgh-ph-hint {
		max-width: 30ch;
		font: 500 14px/1.4 var(--dgh-ui);
	}

	.dgh-ph-spec {
		font: 400 12px/1.2 var(--dgh-ui);
	}

	/* ---- layout ---- */

	.dgh-inner {
		display: flex;
		flex-direction: column;
		gap: 28px;
		width: 100%;
		max-width: 1310px;
		margin: 0 auto;
	}

	.dgh-copy {
		display: flex;
		flex-direction: column;
		align-items: flex-start;
		width: 100%;
		max-width: 490px;
		min-width: 0;
	}

	/* ---- copy ---- */

	.dgh-eyebrow {
		display: inline-flex;
		align-items: center;
		gap: 8px;
		min-height: 34px;
		max-width: 100%;
		padding: 7px 15px;
		border-radius: 999px;
		background: rgb(8 17 15 / 0.54);
		-webkit-backdrop-filter: blur(10px);
		backdrop-filter: blur(10px);
		color: var(--dgh-lime);
		font: 800 12px/14.4px var(--dgh-font);
		letter-spacing: 0.72px;
		text-transform: uppercase;
	}

	.dgh-dot {
		flex: none;
		width: 8px;
		height: 8px;
		border-radius: 999px;
		background: var(--dgh-lime);
	}

	.dgh-dot--sm {
		width: 6px;
		height: 6px;
	}

	.dgh-title {
		margin-top: 26px;
		color: #fff;
		font-family: var(--dgh-font);
		font-weight: 800;
		font-size: clamp(44px, 16.4vw, 62px);
		line-height: 1.02;
		letter-spacing: -0.055em;
	}

	.dgh-line {
		display: block;
	}

	.dgh-line--accent {
		color: var(--dgh-lime);
	}

	.dgh-punct {
		color: #fff;
	}

	.dgh-body {
		margin-top: 28px;
		color: rgb(255 255 255 / 0.78);
		font: 400 16px/24.8px var(--dgh-font);
	}

	.dgh-ctas {
		display: flex;
		flex-direction: column;
		gap: 12px;
		width: 100%;
		margin-top: 32px;
	}

	.dgh-btn {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		gap: 10px;
		min-height: 56px;
		padding: 0 22px;
		border-radius: 999px;
		font: 800 15px/18px var(--dgh-font);
		text-decoration: none;
		transition:
			transform 0.3s var(--dgh-ease),
			background-color 0.3s var(--dgh-ease);
	}

	.dgh-btn--primary {
		background: var(--dgh-lime);
		color: var(--dgh-ink);
	}

	.dgh-btn--secondary {
		background: rgb(16 27 24 / 0.92);
		-webkit-backdrop-filter: blur(12px);
		backdrop-filter: blur(12px);
		color: #f7fafc;
	}

	.dgh-btn:hover {
		transform: scale(1.1) translateY(-2px);
	}

	.dgh-btn:focus-visible {
		outline: 2px solid var(--dgh-lime);
		outline-offset: 3px;
	}

	.dgh-btn--primary:focus-visible {
		outline-color: #fff;
	}

	.dgh-btn-icon {
		flex: none;
		width: 18px;
		height: 18px;
		fill: none;
		stroke: currentColor;
		stroke-width: 2;
		stroke-linecap: round;
		stroke-linejoin: round;
	}

	.dgh-btn-icon--sm {
		width: 16px;
		height: 16px;
	}

	.dgh-utility {
		display: flex;
		flex-direction: column;
		align-items: flex-start;
		gap: 14px;
		width: 100%;
		margin-top: 28px;
	}

	.dgh-labels {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		gap: 6px 7px;
		color: #fff;
		font: 700 12px/14.4px var(--dgh-font);
		letter-spacing: 0.6px;
		text-transform: uppercase;
	}

	.dgh-labels li {
		display: inline-flex;
		align-items: center;
		gap: 7px;
	}

	.dgh-labels li + li::before {
		content: "";
		width: 5px;
		height: 5px;
		border-radius: 999px;
		background: var(--dgh-lime);
	}

	.dgh-signal {
		display: inline-flex;
		align-items: center;
		gap: 10px;
		min-height: 38px;
		padding: 10px 14px;
		border-radius: 999px;
		background: rgb(8 17 15 / 0.66);
		-webkit-backdrop-filter: blur(16px);
		backdrop-filter: blur(16px);
		color: #f7fafc;
		font: 800 10px/12px var(--dgh-font);
		letter-spacing: 0.7px;
		text-transform: uppercase;
	}

	.dgh-signal svg {
		flex: none;
		width: 16px;
		height: 16px;
		color: var(--dgh-lime);
		fill: none;
		stroke: currentColor;
		stroke-width: 1.8;
		stroke-linecap: round;
	}

	.dgh-trust {
		display: flex;
		align-items: center;
		gap: 8px;
		width: 100%;
		max-width: 410px;
		min-height: 66px;
		padding: 10px 12px;
		border-radius: 18px;
		background: rgb(8 17 15 / 0.58);
		-webkit-backdrop-filter: blur(14px);
		backdrop-filter: blur(14px);
	}

	.dgh-avatars {
		display: flex;
		flex: none;
		gap: 8px;
	}

	.dgh-avatar img,
	.dgh-avatar-ph {
		display: block;
		width: 34px;
		height: 34px;
		border-radius: 999px;
		object-fit: cover;
	}

	.dgh-avatar-ph {
		background: repeating-linear-gradient(135deg, #2b3934 0 5px, #34443e 5px 10px);
		box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.18);
	}

	.dgh-trust-copy {
		display: flex;
		flex-direction: column;
		gap: 2px;
		min-width: 0;
	}

	.dgh-trust-title {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		gap: 0 7px;
		color: #f7fafc;
		font: 700 10px/12px var(--dgh-font);
		letter-spacing: 0.5px;
		text-transform: uppercase;
	}

	.dgh-stars {
		color: var(--dgh-lime);
		font: 700 9px/11px var(--dgh-ui);
		letter-spacing: 1.32px;
	}

	.dgh-trust-text {
		color: #f7fafc;
		font: 600 11px/13.2px var(--dgh-font);
	}

	/* ---- dashboard ---- */

	.dgh-dash {
		position: relative;
		display: none;
		flex-direction: column;
		width: 100%;
		max-width: 668px;
		min-height: 478px;
		padding: 20px;
		overflow: hidden;
		isolation: isolate;
		border: 0.67px solid rgb(255 255 255 / 0.18);
		border-radius: 22px;
		background-color: rgb(8 15 13 / 0.66);
		background-image: linear-gradient(145deg, rgb(255 255 255 / 0.075), rgb(255 255 255 / 0.02));
		box-shadow:
			0 22px 63px rgb(0 0 0 / 0.42),
			inset 0 1px 0 rgb(255 255 255 / 0.1),
			inset 0 -1px 0 rgb(255 255 255 / 0.024);
		-webkit-backdrop-filter: blur(18px) saturate(1.35);
		backdrop-filter: blur(18px) saturate(1.35);
		opacity: 0.98;
		font-family: var(--dgh-ui);
	}

	.dgh-glow {
		position: absolute;
		z-index: -1;
		border-radius: 50%;
		pointer-events: none;
	}

	.dgh-glow--lime {
		top: -153px;
		right: -97px;
		width: 252px;
		height: 252px;
		background: radial-gradient(circle, rgb(200 255 56 / 0.19), transparent 68%);
	}

	.dgh-glow--aqua {
		bottom: -146px;
		left: -62px;
		width: 203px;
		height: 203px;
		background: radial-gradient(circle, rgb(115 245 223 / 0.13), transparent 70%);
	}

	.dgh-dash-head {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: 17px;
		min-height: 42px;
		padding-bottom: 15px;
	}

	.dgh-dash-eyebrow {
		display: flex;
		align-items: center;
		gap: 7px;
		color: var(--dgh-lime);
		font: 800 8px/1.2 var(--dgh-ui);
		letter-spacing: 0.96px;
		text-transform: uppercase;
		white-space: nowrap;
	}

	.dgh-pulse {
		flex: 0 1 224px;
		min-width: 0;
		height: 27px;
		filter: drop-shadow(0 0 5px rgb(200 255 56 / 0.36));
	}

	.dgh-pulse-line {
		fill: none;
		stroke: var(--dgh-lime);
		stroke-width: 1.3;
		stroke-linejoin: round;
		stroke-linecap: round;
	}

	.dgh-pulse-dot {
		fill: var(--dgh-lime);
	}

	.dgh-dash-title {
		color: var(--dgh-text);
		font: 750 32px/32px var(--dgh-ui);
		letter-spacing: -1.44px;
	}

	.dgh-stats {
		display: grid;
		grid-template-columns: repeat(4, minmax(0, 1fr));
		margin-top: 18px;
		border-top: 1px solid rgb(255 255 255 / 0.08);
		border-bottom: 1px solid rgb(255 255 255 / 0.08);
	}

	.dgh-stat {
		display: flex;
		flex-direction: column;
		align-items: center;
		padding: 17px 13px 15px;
		text-align: center;
	}

	.dgh-stat + .dgh-stat,
	.dgh-net-item + .dgh-net-item {
		border-left: 1px solid rgb(255 255 255 / 0.08);
	}

	.dgh-stat-icon {
		display: grid;
		place-items: center;
		width: 45px;
		height: 45px;
		border: 0.67px solid rgb(255 255 255 / 0.11);
		border-radius: 50%;
		background: rgb(255 255 255 / 0.035);
		box-shadow: inset 0 0 17px rgb(255 255 255 / 0.024);
		color: var(--dgh-tone);
	}

	.dgh-stat-icon svg,
	.dgh-net-icon {
		width: 20px;
		height: 20px;
		fill: none;
		stroke: currentColor;
		stroke-width: 1.7;
		stroke-linecap: round;
		stroke-linejoin: round;
	}

	.dgh-stat-label {
		display: flex;
		align-items: center;
		justify-content: center;
		min-height: 24px;
		margin-top: 12px;
		color: rgb(247 250 247 / 0.62);
		font: 750 8px/11.2px var(--dgh-ui);
		letter-spacing: 0.64px;
		text-transform: uppercase;
	}

	.dgh-stat-value {
		margin-top: 6px;
		color: var(--dgh-tone);
		font: 820 20px/20px var(--dgh-ui);
		letter-spacing: -0.7px;
	}

	.dgh-stat-note {
		margin-top: 4px;
		color: rgb(247 250 247 / 0.72);
		font: 400 8px/10.8px var(--dgh-ui);
	}

	.dgh-net {
		padding-top: 18px;
	}

	.dgh-net-title {
		color: rgb(247 250 247 / 0.72);
		font: 800 8px/10px var(--dgh-ui);
		letter-spacing: 0.88px;
		text-transform: uppercase;
	}

	.dgh-net-grid {
		display: grid;
		grid-template-columns: repeat(4, minmax(0, 1fr));
		margin-top: 13px;
	}

	.dgh-net-item {
		display: flex;
		flex-direction: column;
		align-items: center;
		padding: 0 13px 15px;
		text-align: center;
	}

	.dgh-net-icon {
		width: 24px;
		height: 24px;
		color: var(--dgh-tone);
	}

	.dgh-net-value {
		margin-top: 10px;
		color: var(--dgh-text);
		font: 800 19px/19px var(--dgh-ui);
		letter-spacing: -0.665px;
	}

	.dgh-net-label {
		margin-top: 5px;
		color: rgb(247 250 247 / 0.62);
		font: 400 8px/10px var(--dgh-ui);
	}

	/* ---- forecast row ---- */

	.dgh-insight {
		margin-top: 0;
	}

	.dgh-insight-row {
		display: grid;
		grid-template-columns: auto minmax(0, 1fr) 24px;
		align-items: center;
		gap: 10px;
		min-height: 44px;
		padding: 8px 10px 8px 8px;
		border: 0.67px solid rgb(255 255 255 / 0.11);
		border-radius: 999px;
		background: rgb(255 255 255 / 0.035);
		color: inherit;
		text-decoration: none;
		cursor: pointer;
		list-style: none;
		transition:
			border-color 0.18s ease,
			background-color 0.18s ease,
			transform 0.18s ease;
	}

	.dgh-insight-row::-webkit-details-marker {
		display: none;
	}

	.dgh-insight-row--static {
		grid-template-columns: auto minmax(0, 1fr);
		cursor: default;
	}

	.dgh-insight-row:not(.dgh-insight-row--static):hover {
		background: rgb(255 255 255 / 0.07);
		transform: translateX(1px);
	}

	.dgh-insight-row:focus-visible {
		outline: 2px solid var(--dgh-lime);
		outline-offset: 2px;
	}

	.dgh-insight-tag {
		display: inline-flex;
		align-items: center;
		gap: 5px;
		min-height: 24px;
		padding: 0 8px;
		border-radius: 999px;
		background: rgb(200 255 56 / 0.08);
		color: var(--dgh-lime);
		font: 850 8px/1 var(--dgh-ui);
		letter-spacing: 0.56px;
		text-transform: uppercase;
		white-space: nowrap;
	}

	.dgh-insight-tag svg {
		width: 11px;
		height: 11px;
		fill: none;
		stroke: currentColor;
		stroke-width: 2;
		stroke-linecap: round;
	}

	.dgh-insight-text {
		color: rgb(247 250 247 / 0.78);
		font: 400 8px/11.6px var(--dgh-ui);
	}

	.dgh-insight-chev {
		display: grid;
		place-items: center;
		width: 24px;
		height: 24px;
		border-radius: 50%;
		color: var(--dgh-text);
		transition: transform 0.18s ease;
	}

	.dgh-insight-chev svg {
		width: 13px;
		height: 13px;
		fill: none;
		stroke: currentColor;
		stroke-width: 2;
		stroke-linecap: round;
		stroke-linejoin: round;
	}

	.dgh-insight[open] .dgh-insight-chev {
		transform: rotate(90deg);
	}

	.dgh-insight-detail {
		padding: 10px 16px 0;
		color: rgb(247 250 247 / 0.72);
		font: 400 10px/15px var(--dgh-ui);
	}

	/* ---- load-in ----
	   A CSS animation, not a script: it always finishes at rest, so without
	   JavaScript the content still ends visible, and nothing is hidden when
	   motion is reduced because the animation never applies. */
	@media (prefers-reduced-motion: no-preference) {
		.dgh--animate .dgh-copy {
			animation: dgh-rise 0.5s var(--dgh-ease) both;
		}

		.dgh--animate .dgh-dash {
			animation: dgh-rise 0.6s var(--dgh-ease) 0.12s both;
		}

		.dgh--animate .dgh-pulse-line {
			stroke-dasharray: 1;
			animation: dgh-draw 1.4s ease-out 0.35s both;
		}

		.dgh-wave--1 {
			animation: dgh-wave 2.4s ease-in-out infinite;
		}

		.dgh-wave--2 {
			animation: dgh-wave 2.4s ease-in-out 0.3s infinite;
		}
	}

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

	@keyframes dgh-draw {
		from {
			stroke-dashoffset: 1;
		}
		to {
			stroke-dashoffset: 0;
		}
	}

	@keyframes dgh-wave {
		0%,
		100% {
			opacity: 1;
		}
		50% {
			opacity: 0.35;
		}
	}

	@media (prefers-reduced-motion: reduce) {
		.dgh-btn:hover,
		.dgh-insight-row:not(.dgh-insight-row--static):hover {
			transform: none;
		}

		.dgh-insight-chev {
			transition: none;
		}
	}

	/* Phone: the reference keeps only the first two labels on one line
	   rather than wrapping a third under them. */
	@media (max-width: 809.98px) {
		.dgh-labels li:nth-child(n + 3) {
			display: none;
		}
	}

	/* ---- tablet: dashboard stacks under the copy ---- */
	@media (min-width: 810px) {
		.dgh {
			min-height: 860px;
			padding: 88px 40px 92px;
		}

		.dgh-inner {
			gap: 48px;
		}

		.dgh-title {
			font-size: 66px;
			line-height: 61.38px;
			letter-spacing: -3.6px;
		}

		.dgh-ctas {
			flex-direction: row;
			flex-wrap: wrap;
			gap: 16px;
			width: auto;
		}

		.dgh-labels {
			gap: 10px;
		}

		.dgh-labels li {
			gap: 10px;
		}

		.dgh-dash {
			display: flex;
		}
	}

	/* ---- desktop: side by side ---- */
	@media (min-width: 1200px) {
		/* Measured: the copy starts 168px down and the dashboard 267px down
		   in an 860px hero. The overlay header occupies the top 78px. */
		.dgh {
			padding: 151px 64px 29px;
		}

		.dgh-inner {
			flex-direction: row;
			align-items: center;
			justify-content: space-between;
			gap: 42px;
		}

		.dgh-copy {
			flex: 0 0 490px;
		}

		.dgh-dash {
			flex: 0 1 668px;
			margin-top: 32px;
		}
	}
</style>