Component library
Back to catalog

Mission stat about

An about band: eyebrow pill, a large display statement with an outline button, then a photo, a navy mission card with award badges, and an outlined vision card led by a stat that counts up once when scrolled into view.

aboutmissionvisionstatcount-upcards

Open preview in a new tab

Use this component

Three ways to get Mission stat about 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 1 file 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 Mission stat about from my Astro Component Library to this Astro project.

Library page, for reference only:
https://astro.baysixmedia.com/components/mission-stat-about/

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 1 file 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: "Mission stat about"
Library page: https://astro.baysixmedia.com/components/mission-stat-about/
Files: 1 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/MissionStatAbout.astro =====
---
/**
 * MissionStatAbout
 *
 * About section: an eyebrow pill on the left, a large display statement and
 * an outline button on the right, then a row of three cards: a photo, a navy
 * "mission" card with award badges, and an outlined "vision" card led by a
 * stat that counts up once when it scrolls into view.
 *
 * The real figure is in the HTML. The script only rewinds it to zero once it
 * knows it can animate it back, so without JavaScript, under reduced motion
 * and to a screen reader the number is always the final one.
 */
/**
 * A photo slot. With `src` it renders the <img>; without it, a labelled
 * striped placeholder of the same box, describing the photo to find.
 */
interface ImageSlot {
	src?: string;
	/** Empty marks the image decorative. */
	alt?: string;
	/** What the photo should show. Used by the placeholder only. */
	hint?: string;
}

interface Badge {
	src?: string;
	/** Badges carry information (the award's name), so give them real alt. */
	alt: string;
	/** Width at 40px tall. */
	width?: number;
	hint?: string;
}

interface Props {
	id?: string;
	eyebrow?: string;
	statement?: string;
	headingLevel?: "h2" | "h3";
	/** Omit (the default) or pass an empty href to drop the button. */
	button?: { label: string; href: string };
	/** Omitted (the default): no photo card. Passed without `src`: a labelled placeholder. */
	image?: ImageSlot;
	/** Omitted (the default): no mission card. */
	mission?: { title: string; text: string; badges?: Badge[] };
	stat?: {
		value: number;
		prefix?: string;
		suffix?: string;
		label: string;
	};
	/** Omitted (the default): no vision copy. */
	vision?: { title: string; text: string; image?: ImageSlot };
	/** How long the count-up runs, in milliseconds. */
	countDuration?: number;
	/** Drops the top padding, for when the section above already closes with space (as in the reference). */
	flushTop?: boolean;
	fontStylesheet?: string;
}

/*
 * The statement, button, photo, mission, stat and vision are the caller's:
 * nothing about a business is filled in here. "About us" is a generic label
 * and never renders the section on its own. The catalog demo passes its
 * sample content through the registry's `props`.
 */
const {
	id,
	eyebrow = "About us",
	statement = "",
	headingLevel = "h2",
	button,
	image,
	mission,
	stat,
	vision,
	countDuration = 1600,
	flushTop = false,
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Poppins:wght@400;500;600&display=swap",
} = Astro.props;

/* A figure only with a real number: a partial stat (a suffix, a label) renders nothing, never "undefined". */
const figure = stat && typeof stat.value === "number" && Number.isFinite(stat.value) ? stat : undefined;
const Heading = headingLevel;
const Sub = headingLevel === "h2" ? "h3" : "h4";
const statText = figure ? `${figure.prefix ?? ""}${figure.value}${figure.suffix ?? ""}` : "";
/* Constant per slot, from the reference measurements: what to pick a photo against. */
const PHOTO_SPEC = "~4:3 · 1200 × 900 px";
const VISION_SPEC = "~7:5 · 400 × 280 px, transparent PNG";
const decimals = figure ? (String(figure.value).split(".")[1] ?? "").length : 0;

const hasLead = Boolean(statement || button?.href);
const hasRow = Boolean(image || mission || figure || vision);
/* Nothing to say → nothing rendered: the generic eyebrow alone is not a section. */
const hasContent = hasLead || hasRow;
---

{hasContent && (
<section class="msa" id={id} data-msa-root data-flush-top={flushTop ? "" : undefined}>
	{fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
	<div class="msa-container">
		{(eyebrow || hasLead) && (
		<div class="msa-top">
			{eyebrow && <p class="msa-eyebrow">{eyebrow}</p>}
			{hasLead && (
			<div class="msa-lead">
				{statement && <Heading class="msa-statement">{statement}</Heading>}
				{
					button?.href && (
						<a class="msa-button" href={button.href}>
							<span>{button.label}</span>
							<span class="msa-button-icon" aria-hidden="true">
								<svg viewBox="0 0 24 24" width="24" height="24" focusable="false"><path d="M5 12h14M13 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /></svg>
							</span>
						</a>
					)
				}
			</div>
			)}
		</div>
		)}

		{hasRow && (
		<div class="msa-row">
			{
				image && !image.src && (
					<div class="msa-photo msa-ph" role="img" aria-label={`Image placeholder: ${image.hint ?? "About section photo"}`}>
						<span class="msa-ph-text">
							<span class="msa-ph-label">Image</span>
							<span class="msa-ph-hint">{image.hint ?? "About section photo"}</span>
							<span class="msa-ph-spec">{PHOTO_SPEC}</span>
						</span>
					</div>
				)
			}
			{
				image?.src && (
					<div class="msa-photo">
						<img src={image.src} alt={image.alt ?? ""} width="1200" height="900" loading="lazy" decoding="async" />
					</div>
				)
			}

			{
				mission && (
					<div class="msa-mission">
						<div class="msa-copy">
							<Sub class="msa-card-title">{mission.title}</Sub>
							<p class="msa-card-text">{mission.text}</p>
						</div>
						{mission.badges && mission.badges.length > 0 && (
							<ul class="msa-badges" role="list">
								{mission.badges.map((badge) => (
									<li>
										{badge.src ? (
											<img src={badge.src} alt={badge.alt} height="40" width={badge.width} loading="lazy" decoding="async" />
										) : (
											/* Too small to label: stripes only. */
											<span
												class="msa-ph msa-ph--dark msa-badge-ph"
												role="img"
												aria-label={`Image placeholder: ${badge.hint ?? "Award badge"} (${badge.alt})`}
												style={`width: ${badge.width ?? 60}px;`}
											/>
										)}
									</li>
								))}
							</ul>
						)}
					</div>
				)
			}

			{
				(figure || vision) && (
					<div class="msa-vision">
						{figure && (
							<div class="msa-stat">
								<p class="msa-stat-value">
									<span class="msa-sr-only">{statText}</span>
									<span
										aria-hidden="true"
										data-msa-count
										data-value={figure.value}
										data-decimals={decimals}
										data-prefix={figure.prefix ?? ""}
										data-suffix={figure.suffix ?? ""}
										data-duration={countDuration}
									>
										{statText}
									</span>
								</p>
								<p class="msa-stat-label">{figure.label}</p>
							</div>
						)}
						{vision && (
							<div class="msa-copy msa-vision-copy">
								<Sub class="msa-card-title">{vision.title}</Sub>
								<p class="msa-card-text">{vision.text}</p>
							</div>
						)}
						{vision?.image?.src && (
							<img class="msa-vision-img" src={vision.image.src} alt={vision.image.alt ?? ""} width="400" height="280" loading="lazy" decoding="async" />
						)}
						{vision?.image && !vision.image.src && (
							<div class="msa-vision-img msa-ph msa-vision-ph" role="img" aria-label={`Image placeholder: ${vision.image.hint ?? "Vision card picture"}`}>
								<span class="msa-ph-text">
									<span class="msa-ph-label">Image</span>
									<span class="msa-ph-hint">{vision.image.hint ?? "Vision card picture"}</span>
									<span class="msa-ph-spec">{VISION_SPEC}</span>
								</span>
							</div>
						)}
					</div>
				)
			}
		</div>
		)}
	</div>
</section>
)}

<style>
	.msa {
		--msa-navy: var(--lx-navy, #0a2540);
		--msa-accent: var(--lx-accent, #ff5f00);
		--msa-surface: var(--lx-surface, #f6f6f6);
		--msa-ink-soft: var(--lx-ink-soft, #555555);
		--msa-display: var(--lx-display, "Bebas Neue", Impact, "Arial Narrow", sans-serif);
		--msa-body: var(--lx-body, Poppins, ui-sans-serif, system-ui, sans-serif);
		--msa-hairline: rgba(10, 37, 64, 0.14);

		display: flex;
		justify-content: center;
		box-sizing: border-box;
		padding: 60px 20px;
		background: #fff;
		color: var(--msa-navy);
		font-family: var(--msa-body);
	}

	.msa[data-flush-top] {
		padding-top: 0;
	}

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

	.msa-container {
		display: flex;
		flex-direction: column;
		gap: 40px;
		width: 100%;
		max-width: 1200px;
	}

	/* ---------- top ---------- */

	.msa-top {
		display: flex;
		flex-direction: column;
		align-items: flex-start;
		gap: 20px;
	}

	.msa-eyebrow {
		flex: none;
		margin: 0;
		padding: 7px 15px;
		border: 1px solid var(--msa-hairline);
		border-radius: 100px;
		font: 500 14px/14px var(--msa-body);
		text-transform: uppercase;
		color: var(--msa-navy);
	}

	.msa-lead {
		display: flex;
		flex-direction: column;
		align-items: flex-start;
		gap: 24px;
		min-width: 0;
	}

	.msa-statement {
		margin: 0;
		font: 400 30px/30px var(--msa-display);
		letter-spacing: -0.02em;
		text-transform: uppercase;
		color: var(--msa-navy);
		text-wrap: pretty;
	}

	.msa-button {
		display: inline-flex;
		align-items: center;
		gap: 24px;
		min-height: 50px;
		padding: 4px 4px 4px 18px;
		border: 1px solid var(--msa-hairline);
		border-radius: 100px;
		background: #fff;
		color: var(--msa-navy);
		font: 600 14px/14px var(--msa-body);
		text-decoration: none;
		transition:
			background-color 0.25s ease,
			border-color 0.25s ease;
	}

	.msa-button-icon {
		display: grid;
		place-items: center;
		width: 40px;
		height: 40px;
		border-radius: 50%;
		background: var(--msa-navy);
		color: #fff;
	}

	.msa-button-icon svg {
		transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
	}

	.msa-button:hover {
		border-color: rgba(10, 37, 64, 0.3);
		background: var(--msa-surface);
	}

	.msa-button:hover .msa-button-icon svg {
		transform: translateX(3px);
	}

	.msa-button:focus-visible {
		outline: 2px solid var(--msa-accent);
		outline-offset: 3px;
	}

	/* ---------- cards ---------- */

	.msa-row {
		display: grid;
		grid-template-columns: minmax(0, 1fr);
		gap: 20px;
	}

	.msa-photo {
		overflow: hidden;
		border-radius: 10px;
		aspect-ratio: 335 / 191;
		background: var(--msa-surface);
	}

	.msa-photo img {
		display: block;
		width: 100%;
		height: 100%;
		object-fit: cover;
	}

	.msa-mission {
		display: flex;
		flex-direction: column;
		justify-content: space-between;
		gap: 40px;
		padding: 20px;
		border-radius: 10px;
		background: var(--msa-navy);
		color: #fff;
	}

	.msa-copy {
		display: flex;
		flex-direction: column;
		gap: 12px;
	}

	.msa-card-title {
		margin: 0;
		font: 400 20px/20px var(--msa-display);
		letter-spacing: -0.02em;
		text-transform: uppercase;
		color: inherit;
	}

	.msa-card-text {
		margin: 0;
		font: 400 16px/25.6px var(--msa-body);
		color: var(--msa-ink-soft);
	}

	.msa-mission .msa-card-text {
		color: rgba(255, 255, 255, 0.8);
	}

	.msa-badges {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		gap: 16px;
		margin: 0;
		padding: 0;
		list-style: none;
	}

	.msa-badges img {
		display: block;
		width: auto;
		height: 40px;
	}

	.msa-vision {
		position: relative;
		display: flex;
		flex-direction: column;
		gap: 24px;
		overflow: hidden;
		padding: 10px 10px 90px;
		border: 1px solid var(--msa-hairline);
		border-radius: 10px;
		background: #fff;
		color: var(--msa-navy);
	}

	.msa-stat {
		position: relative;
		z-index: 1;
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: 16px;
		padding: 12px 14px;
		border-radius: 10px;
		background: var(--msa-surface);
	}

	.msa-stat-value {
		margin: 0;
		font: 400 34px/34px var(--msa-display);
		letter-spacing: -0.02em;
		color: var(--msa-navy);
		font-variant-numeric: tabular-nums;
		white-space: nowrap;
	}

	.msa-stat-label {
		max-width: 110px;
		margin: 0;
		font: 500 14px/14px var(--msa-body);
		text-transform: uppercase;
		color: var(--msa-ink-soft);
	}

	.msa-vision-copy {
		position: relative;
		z-index: 1;
		padding: 0 10px;
	}

	/* Hangs off the card's right edge and is clipped by it, as in the
	   reference. */
	.msa-vision-img {
		position: absolute;
		right: -8px;
		bottom: -2px;
		z-index: 0;
		width: 140px;
		height: auto;
	}

	/* ---------- image placeholders ---------- */

	.msa-ph {
		display: grid;
		place-items: center;
		padding: 16px;
		container-type: size;
		border: 1px dashed rgba(10, 37, 64, 0.22);
		background: repeating-linear-gradient(135deg, #e6e9ee 0 12px, #edf0f3 12px 24px);
		color: rgba(10, 37, 64, 0.62);
		font-family: var(--msa-body);
		text-align: center;
	}

	.msa-ph--dark {
		border-color: rgba(255, 255, 255, 0.25);
		background: repeating-linear-gradient(135deg, rgba(255, 255, 255, 0.06) 0 12px, rgba(255, 255, 255, 0.1) 12px 24px);
		color: rgba(255, 255, 255, 0.72);
	}

	.msa-ph-text {
		display: flex;
		flex-direction: column;
		align-items: center;
		gap: 6px;
		max-width: 28ch;
	}

	.msa-ph-label {
		font: 500 11px/1 var(--msa-body);
		letter-spacing: 0.08em;
		text-transform: uppercase;
	}

	.msa-ph-hint {
		font: 500 13px/1.35 var(--msa-body);
	}

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

	@container (max-height: 100px) {
		.msa-ph-label {
			display: none;
		}
	}

	@container (max-height: 80px) {
		.msa-ph-spec {
			display: none;
		}
	}

	.msa-badge-ph {
		display: block;
		height: 40px;
		padding: 0;
		border-radius: 4px;
	}

	/* The picture box hangs off the card's corner, so the placeholder's text
	   is nudged into the part that stays visible. */
	.msa-vision-ph {
		bottom: -12px;
		aspect-ratio: 400 / 280;
		padding: 6px 14px 16px 6px;
		border-radius: 6px 0 0 0;
	}

	.msa-sr-only {
		position: absolute;
		width: 1px;
		height: 1px;
		margin: -1px;
		padding: 0;
		overflow: hidden;
		clip: rect(0 0 0 0);
		white-space: nowrap;
		border: 0;
	}

	/* ---------- tablet ---------- */

	@media (min-width: 810px) {
		.msa {
			padding: 90px 30px;
		}

		.msa-container {
			gap: 60px;
		}

		.msa-statement {
			font-size: 40px;
			line-height: 40px;
		}

		.msa-lead {
			gap: 40px;
		}

		.msa-row {
			grid-template-columns: repeat(2, minmax(0, 1fr));
		}

		.msa-photo {
			grid-column: 1 / -1;
			aspect-ratio: 16 / 7;
		}

		.msa-mission,
		.msa-vision {
			min-height: 340px;
		}

		.msa-mission {
			padding: 30px;
		}

		.msa-vision {
			gap: 30px;
			padding: 10px 10px 125px;
		}

		.msa-card-title {
			font-size: 32px;
			line-height: 32px;
		}

		.msa-stat {
			padding: 16px 20px;
		}

		.msa-stat-value {
			font-size: 48px;
			line-height: 48px;
		}

		.msa-vision-copy {
			padding: 0 20px;
		}

		.msa-vision-img {
			right: -12px;
			bottom: -4px;
			width: 197px;
		}

		/* Same box as the reference picture: 20px of it below the card. */
		.msa-vision-ph {
			bottom: -20px;
			padding: 8px 20px 28px 8px;
		}
	}

	/* ---------- desktop ---------- */

	@media (min-width: 1200px) {
		.msa {
			padding: 120px 30px;
		}

		.msa-top {
			flex-direction: row;
			justify-content: space-between;
			gap: 40px;
		}

		.msa-lead {
			flex: 0 1 690px;
			gap: 60px;
		}

		.msa-statement {
			font-size: 48px;
			line-height: 48px;
		}

		/* 460 / 350 / 350 at 1200. */
		.msa-row {
			grid-template-columns: minmax(0, 46fr) minmax(0, 35fr) minmax(0, 35fr);
			height: 340px;
		}

		.msa-photo {
			grid-column: auto;
			aspect-ratio: auto;
		}
	}

	@media (prefers-reduced-motion: reduce) {
		.msa-button,
		.msa-button-icon svg {
			transition: none;
		}
	}

	@media (forced-colors: active) {
		.msa-mission,
		.msa-stat,
		.msa-button {
			border: 1px solid CanvasText;
		}
	}
</style>

<script>
	/**
	 * MissionStatAbout controller: the count-up, and nothing else.
	 *
	 * One closure per instance, lookups scoped to its root, the observer and
	 * listeners on one AbortController, torn down before a client-side
	 * navigation swaps the document. The served markup already holds the final
	 * number; this rewinds it to zero only when it can play it forward, and
	 * puts the final number back if it is torn down mid-count.
	 */
	interface Instance {
		destroy(): void;
	}

	function initMissionStatAbout(root: HTMLElement): Instance | undefined {
		/* Idempotent: booting twice is a no-op. */
		if (root.dataset.msaReady === "") return undefined;

		const counter = root.querySelector<HTMLElement>("[data-msa-count]");
		if (!counter) return undefined;

		const value = Number(counter.dataset.value);
		const decimals = Number(counter.dataset.decimals) || 0;
		const prefix = counter.dataset.prefix ?? "";
		const suffix = counter.dataset.suffix ?? "";
		const duration = Math.max(200, Number(counter.dataset.duration) || 1600);
		const finalText = counter.textContent?.trim() ?? "";
		const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

		/* Nothing to animate, or not allowed to: the served number stands. */
		if (!Number.isFinite(value) || reduced || !("IntersectionObserver" in window)) return undefined;

		root.dataset.msaReady = "";
		const controller = new AbortController();
		const { signal } = controller;
		let frame = 0;

		const format = (n: number) => `${prefix}${n.toFixed(decimals)}${suffix}`;
		counter.textContent = format(0);

		function run() {
			const start = performance.now();
			const tick = (now: number) => {
				const t = Math.min(1, (now - start) / duration);
				const eased = 1 - Math.pow(1 - t, 3);
				counter!.textContent = t < 1 ? format(value * eased) : finalText;
				if (t < 1) frame = requestAnimationFrame(tick);
			};
			frame = requestAnimationFrame(tick);
		}

		const observer = new IntersectionObserver(
			(entries) => {
				if (entries.some((entry) => entry.isIntersecting)) {
					observer.disconnect();
					run();
				}
			},
			{ threshold: 0.5 },
		);
		observer.observe(counter);

		function destroy() {
			observer.disconnect();
			cancelAnimationFrame(frame);
			counter!.textContent = finalText;
			controller.abort();
			delete root.dataset.msaReady;
		}

		document.addEventListener("astro:before-swap", destroy, { once: true, signal });

		return { destroy };
	}

	const boot = () =>
		document.querySelectorAll<HTMLElement>("[data-msa-root]").forEach(initMissionStatAbout);

	if (document.readyState === "loading") {
		document.addEventListener("DOMContentLoaded", boot, { once: true });
	} else {
		boot();
	}

	document.addEventListener("astro:page-load", boot);
</script>

===== END src/components/MissionStatAbout.astro =====

===== FILE (supporting): src/components/MissionStatAbout.md =====
# Mission stat about

An about band: eyebrow pill, a large display statement with an outline
button, then a photo, a navy mission card with award badges, and an outlined
vision card led by a stat that counts up once when scrolled into view.

Catalog page: `/components/mission-stat-about`

## Purpose

The "who we are" block on a homepage: one sentence of positioning, a photo,
a mission, a vision and a single number that proves it. Three cards is the
whole layout, so the copy has to be short.

## Files required

**`MissionStatAbout.astro`**: one file.

- No imports, no npm packages.
- One small `<script>` for the count-up. Everything else is CSS.
- Colours and fonts read `--lx-*` custom properties with fallbacks.
- Loads Bebas Neue and Poppins from Google Fonts unless you pass
  `fontStylesheet=""`.

`MissionStatAbout.md`: this file. Not needed to run.

No image files. A picture slot you pass without `src` renders a labelled
placeholder; a slot you omit is not drawn.

## Basic usage

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

<MissionStatAbout
  statement="We move freight across North America with the planning, people and live tracking that keep your shipments on schedule."
  button={{ label: "Our story", href: "/about" }}
  image={{ src: "/img/truck-sunrise.jpg", alt: "Our trailer on the highway at sunrise" }}
  stat={{ value: 97, suffix: "%", label: "On-time delivery rate" }}
  mission={{ title: "Our mission", text: "To give every shipper a carrier they never have to chase." }}
  vision={{ title: "Our vision", text: "A supply chain where every load is visible from dock to doorstep." }}
/>
```

Every value above is demo content. Nothing about a business is a default:
with no statement, button, photo, mission, stat or vision the component
renders nothing, and the "About us" eyebrow never renders on its own.

## Props

| Prop | Type | Default | What it does |
| :--- | :--- | :--- | :--- |
| `id` | `string?` | none | Root id. |
| `eyebrow` | `string?` | `"About us"` | The outlined pill. `""` drops it. |
| `statement` | `string?` | `""` | The display statement. Hidden when empty. |
| `headingLevel` | `"h2" \| "h3"?` | `"h2"` | Card titles use the next level down. |
| `button` | `{ label, href }?` | none | Omitted or an empty `href` drops it. |
| `image` | `ImageSlot?` | none | The wide photo card. Omitted: no photo card. Without `src`: a labelled placeholder. |
| `mission` | `{ title, text, badges? }?` | none | The navy card. Omitted: no mission card. Award badges only when the business holds them. No trust claim is shown unless you pass one. |
| `stat` | `{ value, prefix?, suffix?, label }?` | none | The counted number. `value` is a number; decimals are kept. |
| `vision` | `{ title, text, image? }?` | none | The outlined card's copy and picture. The card itself shows while there is a `stat` or a `vision`. |
| `countDuration` | `number?` | `1600` | Count-up length in ms. |
| `flushTop` | `boolean?` | `false` | No top padding, as in the reference, where the band above supplies the space. |
| `fontStylesheet` | `string?` | Google Fonts URL | `""` to skip. |

```ts
interface ImageSlot { src?: string; alt?: string; hint?: string }
interface Badge { src?: string; alt: string; width?: number; hint?: string }
```

## Images

| Slot | Fallback hint (when `hint` is omitted) | Ratio · size |
| :--- | :--- | :--- |
| `image` | About section photo | ~4:3 · 1200 × 900 px |
| `vision.image` | Vision card picture | ~7:5 · 400 × 280 px, transparent PNG |
| `mission.badges[]` | Award badge | 40px tall, width as passed (60 if omitted) · SVG |

The vision picture hangs off the card's right and bottom edges and is
clipped, like the reference; its placeholder nudges the text into the part
that stays visible. On phones it shows the hint only. Badge placeholders are
stripes only (too small to label), with the hint and the badge's alt in the
accessible name. Badges carry information, so `alt` is required on them.

## Behaviour

The stat counts from zero to its value (ease-out cubic, `countDuration`)
the first time it is half on screen, then never again.

## Accessibility

- **The final number is in the HTML.** A visually hidden copy carries it for
  screen readers; the digits that animate are `aria-hidden`, so nobody hears
  "0%, 12%, 40%...".
- The statement is the section heading (h2 by default); mission and vision
  titles are one level below.
- The outline button has an accent `:focus-visible` ring and a 50px height.
- Badges are a list of images with real alt text.

## Reduced motion

The script does nothing: the served final number stands. Button transitions
are off.

## Without JavaScript

The final number shows, because the script is the only thing that ever sets
it to zero. Everything else is static.

## Responsive

| Width | Top | Cards | Statement |
| :--- | :--- | :--- | :--- |
| ≥ 1200 | eyebrow left, statement and button in a 690px column right | one row, 460/350/350, 340 tall | 48/48 |
| 810–1199 | stacked | photo full width (16:7), mission and vision side by side, min 340 tall | 40/40 |
| < 810 | stacked | all stacked; photo 335:191 | 30/30 |

## Approximations

- The eyebrow pill, outline button and vision card borders are drawn as
  1px `rgba(10,37,64,0.14)` hairlines, read from the screenshot; the dump
  reports no border (Framer draws them another way).
- The reference cross-fades a second heading into place (its "0%" h3 sits
  under a hidden "98%" one); this counts the one number instead.
- The count duration (1.6s) and easing are estimates.
- Tablet (810–1199) was not captured; the two-up card layout and 40px
  statement are interpolated.
- The phone stat size (34px) was read from the 37 × 34 box in the dump.
- The button hover (surface fill, arrow nudges right) is not measured.
- Default top padding is 120px so the section stands alone; the reference's
  zero is `flushTop`.

===== END src/components/MissionStatAbout.md =====

--- 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: event-tested only. Handlers respond to dispatched lifecycle events; this has NOT been certified against a real <ClientRouter /> transition.

--- 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
“Mission stat about” from my Astro Component Library:
https://astro.baysixmedia.com/components/mission-stat-about/

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
Ships script
Profile
core only
Installation files
1 required + 1 supporting
Verified
Never
Client navigation
Event-tested only — not certified against a real router
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 fixtureOpen the ClientRouter fixture

src/components/library/MissionStatAbout.astro

one fileNo imports, no companion files, no packages. Copy MissionStatAbout.astro into any Astro project's components folder and render it.

---
/**
 * MissionStatAbout
 *
 * About section: an eyebrow pill on the left, a large display statement and
 * an outline button on the right, then a row of three cards: a photo, a navy
 * "mission" card with award badges, and an outlined "vision" card led by a
 * stat that counts up once when it scrolls into view.
 *
 * The real figure is in the HTML. The script only rewinds it to zero once it
 * knows it can animate it back, so without JavaScript, under reduced motion
 * and to a screen reader the number is always the final one.
 */
/**
 * A photo slot. With `src` it renders the <img>; without it, a labelled
 * striped placeholder of the same box, describing the photo to find.
 */
interface ImageSlot {
	src?: string;
	/** Empty marks the image decorative. */
	alt?: string;
	/** What the photo should show. Used by the placeholder only. */
	hint?: string;
}

interface Badge {
	src?: string;
	/** Badges carry information (the award's name), so give them real alt. */
	alt: string;
	/** Width at 40px tall. */
	width?: number;
	hint?: string;
}

interface Props {
	id?: string;
	eyebrow?: string;
	statement?: string;
	headingLevel?: "h2" | "h3";
	/** Omit (the default) or pass an empty href to drop the button. */
	button?: { label: string; href: string };
	/** Omitted (the default): no photo card. Passed without `src`: a labelled placeholder. */
	image?: ImageSlot;
	/** Omitted (the default): no mission card. */
	mission?: { title: string; text: string; badges?: Badge[] };
	stat?: {
		value: number;
		prefix?: string;
		suffix?: string;
		label: string;
	};
	/** Omitted (the default): no vision copy. */
	vision?: { title: string; text: string; image?: ImageSlot };
	/** How long the count-up runs, in milliseconds. */
	countDuration?: number;
	/** Drops the top padding, for when the section above already closes with space (as in the reference). */
	flushTop?: boolean;
	fontStylesheet?: string;
}

/*
 * The statement, button, photo, mission, stat and vision are the caller's:
 * nothing about a business is filled in here. "About us" is a generic label
 * and never renders the section on its own. The catalog demo passes its
 * sample content through the registry's `props`.
 */
const {
	id,
	eyebrow = "About us",
	statement = "",
	headingLevel = "h2",
	button,
	image,
	mission,
	stat,
	vision,
	countDuration = 1600,
	flushTop = false,
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Poppins:wght@400;500;600&display=swap",
} = Astro.props;

/* A figure only with a real number: a partial stat (a suffix, a label) renders nothing, never "undefined". */
const figure = stat && typeof stat.value === "number" && Number.isFinite(stat.value) ? stat : undefined;
const Heading = headingLevel;
const Sub = headingLevel === "h2" ? "h3" : "h4";
const statText = figure ? `${figure.prefix ?? ""}${figure.value}${figure.suffix ?? ""}` : "";
/* Constant per slot, from the reference measurements: what to pick a photo against. */
const PHOTO_SPEC = "~4:3 · 1200 × 900 px";
const VISION_SPEC = "~7:5 · 400 × 280 px, transparent PNG";
const decimals = figure ? (String(figure.value).split(".")[1] ?? "").length : 0;

const hasLead = Boolean(statement || button?.href);
const hasRow = Boolean(image || mission || figure || vision);
/* Nothing to say → nothing rendered: the generic eyebrow alone is not a section. */
const hasContent = hasLead || hasRow;
---

{hasContent && (
<section class="msa" id={id} data-msa-root data-flush-top={flushTop ? "" : undefined}>
	{fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
	<div class="msa-container">
		{(eyebrow || hasLead) && (
		<div class="msa-top">
			{eyebrow && <p class="msa-eyebrow">{eyebrow}</p>}
			{hasLead && (
			<div class="msa-lead">
				{statement && <Heading class="msa-statement">{statement}</Heading>}
				{
					button?.href && (
						<a class="msa-button" href={button.href}>
							<span>{button.label}</span>
							<span class="msa-button-icon" aria-hidden="true">
								<svg viewBox="0 0 24 24" width="24" height="24" focusable="false"><path d="M5 12h14M13 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /></svg>
							</span>
						</a>
					)
				}
			</div>
			)}
		</div>
		)}

		{hasRow && (
		<div class="msa-row">
			{
				image && !image.src && (
					<div class="msa-photo msa-ph" role="img" aria-label={`Image placeholder: ${image.hint ?? "About section photo"}`}>
						<span class="msa-ph-text">
							<span class="msa-ph-label">Image</span>
							<span class="msa-ph-hint">{image.hint ?? "About section photo"}</span>
							<span class="msa-ph-spec">{PHOTO_SPEC}</span>
						</span>
					</div>
				)
			}
			{
				image?.src && (
					<div class="msa-photo">
						<img src={image.src} alt={image.alt ?? ""} width="1200" height="900" loading="lazy" decoding="async" />
					</div>
				)
			}

			{
				mission && (
					<div class="msa-mission">
						<div class="msa-copy">
							<Sub class="msa-card-title">{mission.title}</Sub>
							<p class="msa-card-text">{mission.text}</p>
						</div>
						{mission.badges && mission.badges.length > 0 && (
							<ul class="msa-badges" role="list">
								{mission.badges.map((badge) => (
									<li>
										{badge.src ? (
											<img src={badge.src} alt={badge.alt} height="40" width={badge.width} loading="lazy" decoding="async" />
										) : (
											/* Too small to label: stripes only. */
											<span
												class="msa-ph msa-ph--dark msa-badge-ph"
												role="img"
												aria-label={`Image placeholder: ${badge.hint ?? "Award badge"} (${badge.alt})`}
												style={`width: ${badge.width ?? 60}px;`}
											/>
										)}
									</li>
								))}
							</ul>
						)}
					</div>
				)
			}

			{
				(figure || vision) && (
					<div class="msa-vision">
						{figure && (
							<div class="msa-stat">
								<p class="msa-stat-value">
									<span class="msa-sr-only">{statText}</span>
									<span
										aria-hidden="true"
										data-msa-count
										data-value={figure.value}
										data-decimals={decimals}
										data-prefix={figure.prefix ?? ""}
										data-suffix={figure.suffix ?? ""}
										data-duration={countDuration}
									>
										{statText}
									</span>
								</p>
								<p class="msa-stat-label">{figure.label}</p>
							</div>
						)}
						{vision && (
							<div class="msa-copy msa-vision-copy">
								<Sub class="msa-card-title">{vision.title}</Sub>
								<p class="msa-card-text">{vision.text}</p>
							</div>
						)}
						{vision?.image?.src && (
							<img class="msa-vision-img" src={vision.image.src} alt={vision.image.alt ?? ""} width="400" height="280" loading="lazy" decoding="async" />
						)}
						{vision?.image && !vision.image.src && (
							<div class="msa-vision-img msa-ph msa-vision-ph" role="img" aria-label={`Image placeholder: ${vision.image.hint ?? "Vision card picture"}`}>
								<span class="msa-ph-text">
									<span class="msa-ph-label">Image</span>
									<span class="msa-ph-hint">{vision.image.hint ?? "Vision card picture"}</span>
									<span class="msa-ph-spec">{VISION_SPEC}</span>
								</span>
							</div>
						)}
					</div>
				)
			}
		</div>
		)}
	</div>
</section>
)}

<style>
	.msa {
		--msa-navy: var(--lx-navy, #0a2540);
		--msa-accent: var(--lx-accent, #ff5f00);
		--msa-surface: var(--lx-surface, #f6f6f6);
		--msa-ink-soft: var(--lx-ink-soft, #555555);
		--msa-display: var(--lx-display, "Bebas Neue", Impact, "Arial Narrow", sans-serif);
		--msa-body: var(--lx-body, Poppins, ui-sans-serif, system-ui, sans-serif);
		--msa-hairline: rgba(10, 37, 64, 0.14);

		display: flex;
		justify-content: center;
		box-sizing: border-box;
		padding: 60px 20px;
		background: #fff;
		color: var(--msa-navy);
		font-family: var(--msa-body);
	}

	.msa[data-flush-top] {
		padding-top: 0;
	}

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

	.msa-container {
		display: flex;
		flex-direction: column;
		gap: 40px;
		width: 100%;
		max-width: 1200px;
	}

	/* ---------- top ---------- */

	.msa-top {
		display: flex;
		flex-direction: column;
		align-items: flex-start;
		gap: 20px;
	}

	.msa-eyebrow {
		flex: none;
		margin: 0;
		padding: 7px 15px;
		border: 1px solid var(--msa-hairline);
		border-radius: 100px;
		font: 500 14px/14px var(--msa-body);
		text-transform: uppercase;
		color: var(--msa-navy);
	}

	.msa-lead {
		display: flex;
		flex-direction: column;
		align-items: flex-start;
		gap: 24px;
		min-width: 0;
	}

	.msa-statement {
		margin: 0;
		font: 400 30px/30px var(--msa-display);
		letter-spacing: -0.02em;
		text-transform: uppercase;
		color: var(--msa-navy);
		text-wrap: pretty;
	}

	.msa-button {
		display: inline-flex;
		align-items: center;
		gap: 24px;
		min-height: 50px;
		padding: 4px 4px 4px 18px;
		border: 1px solid var(--msa-hairline);
		border-radius: 100px;
		background: #fff;
		color: var(--msa-navy);
		font: 600 14px/14px var(--msa-body);
		text-decoration: none;
		transition:
			background-color 0.25s ease,
			border-color 0.25s ease;
	}

	.msa-button-icon {
		display: grid;
		place-items: center;
		width: 40px;
		height: 40px;
		border-radius: 50%;
		background: var(--msa-navy);
		color: #fff;
	}

	.msa-button-icon svg {
		transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
	}

	.msa-button:hover {
		border-color: rgba(10, 37, 64, 0.3);
		background: var(--msa-surface);
	}

	.msa-button:hover .msa-button-icon svg {
		transform: translateX(3px);
	}

	.msa-button:focus-visible {
		outline: 2px solid var(--msa-accent);
		outline-offset: 3px;
	}

	/* ---------- cards ---------- */

	.msa-row {
		display: grid;
		grid-template-columns: minmax(0, 1fr);
		gap: 20px;
	}

	.msa-photo {
		overflow: hidden;
		border-radius: 10px;
		aspect-ratio: 335 / 191;
		background: var(--msa-surface);
	}

	.msa-photo img {
		display: block;
		width: 100%;
		height: 100%;
		object-fit: cover;
	}

	.msa-mission {
		display: flex;
		flex-direction: column;
		justify-content: space-between;
		gap: 40px;
		padding: 20px;
		border-radius: 10px;
		background: var(--msa-navy);
		color: #fff;
	}

	.msa-copy {
		display: flex;
		flex-direction: column;
		gap: 12px;
	}

	.msa-card-title {
		margin: 0;
		font: 400 20px/20px var(--msa-display);
		letter-spacing: -0.02em;
		text-transform: uppercase;
		color: inherit;
	}

	.msa-card-text {
		margin: 0;
		font: 400 16px/25.6px var(--msa-body);
		color: var(--msa-ink-soft);
	}

	.msa-mission .msa-card-text {
		color: rgba(255, 255, 255, 0.8);
	}

	.msa-badges {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		gap: 16px;
		margin: 0;
		padding: 0;
		list-style: none;
	}

	.msa-badges img {
		display: block;
		width: auto;
		height: 40px;
	}

	.msa-vision {
		position: relative;
		display: flex;
		flex-direction: column;
		gap: 24px;
		overflow: hidden;
		padding: 10px 10px 90px;
		border: 1px solid var(--msa-hairline);
		border-radius: 10px;
		background: #fff;
		color: var(--msa-navy);
	}

	.msa-stat {
		position: relative;
		z-index: 1;
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: 16px;
		padding: 12px 14px;
		border-radius: 10px;
		background: var(--msa-surface);
	}

	.msa-stat-value {
		margin: 0;
		font: 400 34px/34px var(--msa-display);
		letter-spacing: -0.02em;
		color: var(--msa-navy);
		font-variant-numeric: tabular-nums;
		white-space: nowrap;
	}

	.msa-stat-label {
		max-width: 110px;
		margin: 0;
		font: 500 14px/14px var(--msa-body);
		text-transform: uppercase;
		color: var(--msa-ink-soft);
	}

	.msa-vision-copy {
		position: relative;
		z-index: 1;
		padding: 0 10px;
	}

	/* Hangs off the card's right edge and is clipped by it, as in the
	   reference. */
	.msa-vision-img {
		position: absolute;
		right: -8px;
		bottom: -2px;
		z-index: 0;
		width: 140px;
		height: auto;
	}

	/* ---------- image placeholders ---------- */

	.msa-ph {
		display: grid;
		place-items: center;
		padding: 16px;
		container-type: size;
		border: 1px dashed rgba(10, 37, 64, 0.22);
		background: repeating-linear-gradient(135deg, #e6e9ee 0 12px, #edf0f3 12px 24px);
		color: rgba(10, 37, 64, 0.62);
		font-family: var(--msa-body);
		text-align: center;
	}

	.msa-ph--dark {
		border-color: rgba(255, 255, 255, 0.25);
		background: repeating-linear-gradient(135deg, rgba(255, 255, 255, 0.06) 0 12px, rgba(255, 255, 255, 0.1) 12px 24px);
		color: rgba(255, 255, 255, 0.72);
	}

	.msa-ph-text {
		display: flex;
		flex-direction: column;
		align-items: center;
		gap: 6px;
		max-width: 28ch;
	}

	.msa-ph-label {
		font: 500 11px/1 var(--msa-body);
		letter-spacing: 0.08em;
		text-transform: uppercase;
	}

	.msa-ph-hint {
		font: 500 13px/1.35 var(--msa-body);
	}

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

	@container (max-height: 100px) {
		.msa-ph-label {
			display: none;
		}
	}

	@container (max-height: 80px) {
		.msa-ph-spec {
			display: none;
		}
	}

	.msa-badge-ph {
		display: block;
		height: 40px;
		padding: 0;
		border-radius: 4px;
	}

	/* The picture box hangs off the card's corner, so the placeholder's text
	   is nudged into the part that stays visible. */
	.msa-vision-ph {
		bottom: -12px;
		aspect-ratio: 400 / 280;
		padding: 6px 14px 16px 6px;
		border-radius: 6px 0 0 0;
	}

	.msa-sr-only {
		position: absolute;
		width: 1px;
		height: 1px;
		margin: -1px;
		padding: 0;
		overflow: hidden;
		clip: rect(0 0 0 0);
		white-space: nowrap;
		border: 0;
	}

	/* ---------- tablet ---------- */

	@media (min-width: 810px) {
		.msa {
			padding: 90px 30px;
		}

		.msa-container {
			gap: 60px;
		}

		.msa-statement {
			font-size: 40px;
			line-height: 40px;
		}

		.msa-lead {
			gap: 40px;
		}

		.msa-row {
			grid-template-columns: repeat(2, minmax(0, 1fr));
		}

		.msa-photo {
			grid-column: 1 / -1;
			aspect-ratio: 16 / 7;
		}

		.msa-mission,
		.msa-vision {
			min-height: 340px;
		}

		.msa-mission {
			padding: 30px;
		}

		.msa-vision {
			gap: 30px;
			padding: 10px 10px 125px;
		}

		.msa-card-title {
			font-size: 32px;
			line-height: 32px;
		}

		.msa-stat {
			padding: 16px 20px;
		}

		.msa-stat-value {
			font-size: 48px;
			line-height: 48px;
		}

		.msa-vision-copy {
			padding: 0 20px;
		}

		.msa-vision-img {
			right: -12px;
			bottom: -4px;
			width: 197px;
		}

		/* Same box as the reference picture: 20px of it below the card. */
		.msa-vision-ph {
			bottom: -20px;
			padding: 8px 20px 28px 8px;
		}
	}

	/* ---------- desktop ---------- */

	@media (min-width: 1200px) {
		.msa {
			padding: 120px 30px;
		}

		.msa-top {
			flex-direction: row;
			justify-content: space-between;
			gap: 40px;
		}

		.msa-lead {
			flex: 0 1 690px;
			gap: 60px;
		}

		.msa-statement {
			font-size: 48px;
			line-height: 48px;
		}

		/* 460 / 350 / 350 at 1200. */
		.msa-row {
			grid-template-columns: minmax(0, 46fr) minmax(0, 35fr) minmax(0, 35fr);
			height: 340px;
		}

		.msa-photo {
			grid-column: auto;
			aspect-ratio: auto;
		}
	}

	@media (prefers-reduced-motion: reduce) {
		.msa-button,
		.msa-button-icon svg {
			transition: none;
		}
	}

	@media (forced-colors: active) {
		.msa-mission,
		.msa-stat,
		.msa-button {
			border: 1px solid CanvasText;
		}
	}
</style>

<script>
	/**
	 * MissionStatAbout controller: the count-up, and nothing else.
	 *
	 * One closure per instance, lookups scoped to its root, the observer and
	 * listeners on one AbortController, torn down before a client-side
	 * navigation swaps the document. The served markup already holds the final
	 * number; this rewinds it to zero only when it can play it forward, and
	 * puts the final number back if it is torn down mid-count.
	 */
	interface Instance {
		destroy(): void;
	}

	function initMissionStatAbout(root: HTMLElement): Instance | undefined {
		/* Idempotent: booting twice is a no-op. */
		if (root.dataset.msaReady === "") return undefined;

		const counter = root.querySelector<HTMLElement>("[data-msa-count]");
		if (!counter) return undefined;

		const value = Number(counter.dataset.value);
		const decimals = Number(counter.dataset.decimals) || 0;
		const prefix = counter.dataset.prefix ?? "";
		const suffix = counter.dataset.suffix ?? "";
		const duration = Math.max(200, Number(counter.dataset.duration) || 1600);
		const finalText = counter.textContent?.trim() ?? "";
		const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

		/* Nothing to animate, or not allowed to: the served number stands. */
		if (!Number.isFinite(value) || reduced || !("IntersectionObserver" in window)) return undefined;

		root.dataset.msaReady = "";
		const controller = new AbortController();
		const { signal } = controller;
		let frame = 0;

		const format = (n: number) => `${prefix}${n.toFixed(decimals)}${suffix}`;
		counter.textContent = format(0);

		function run() {
			const start = performance.now();
			const tick = (now: number) => {
				const t = Math.min(1, (now - start) / duration);
				const eased = 1 - Math.pow(1 - t, 3);
				counter!.textContent = t < 1 ? format(value * eased) : finalText;
				if (t < 1) frame = requestAnimationFrame(tick);
			};
			frame = requestAnimationFrame(tick);
		}

		const observer = new IntersectionObserver(
			(entries) => {
				if (entries.some((entry) => entry.isIntersecting)) {
					observer.disconnect();
					run();
				}
			},
			{ threshold: 0.5 },
		);
		observer.observe(counter);

		function destroy() {
			observer.disconnect();
			cancelAnimationFrame(frame);
			counter!.textContent = finalText;
			controller.abort();
			delete root.dataset.msaReady;
		}

		document.addEventListener("astro:before-swap", destroy, { once: true, signal });

		return { destroy };
	}

	const boot = () =>
		document.querySelectorAll<HTMLElement>("[data-msa-root]").forEach(initMissionStatAbout);

	if (document.readyState === "loading") {
		document.addEventListener("DOMContentLoaded", boot, { once: true });
	} else {
		boot();
	}

	document.addEventListener("astro:page-load", boot);
</script>