Component library
Back to catalog

Wordmark marquee footer

A rounded navy footer panel over a full-bleed photograph: brand, blurb and icon links, three link columns, a copyright bar, and a huge display wordmark scrolling sideways along the bottom.

footernavlinksmarqueewordmarkstatic

Open preview in a new tab

Use this component

Three ways to get Wordmark marquee footer 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 Wordmark marquee footer from my Astro Component Library to this Astro project.

Library page, for reference only:
https://astro.baysixmedia.com/components/wordmark-marquee-footer/

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: "Wordmark marquee footer"
Library page: https://astro.baysixmedia.com/components/wordmark-marquee-footer/
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/WordmarkMarqueeFooter.astro =====
---
/**
 * WordmarkMarqueeFooter
 *
 * A site footer built as a rounded navy panel set into the page over a
 * full-bleed photograph, which shows round the panel's edges and faintly
 * through it. Inside: the brand, a short blurb and a row of icon links,
 * link columns, a copyright bar — and along the bottom a huge display-font
 * wordmark that scrolls sideways forever, with the brand mark between each
 * repeat.
 *
 * Static: no script. The marquee is a CSS animation over two identical
 * groups, translated by exactly one group's width, so the loop has no seam.
 * It stops under reduced motion, pauses on hover and focus-within, and
 * carries a native pause toggle so it can be stopped without a pointer.
 *
 * Reads --lx-* design tokens from any ancestor, with fallbacks.
 */

export type WordmarkFooterIcon = "rss" | "play" | "chat" | "mail" | "phone" | "pin" | "link";

export interface WordmarkFooterLink {
	label: string;
	href: string;
}

export interface WordmarkFooterColumn {
	heading: string;
	links: WordmarkFooterLink[];
}

export interface WordmarkFooterSocial {
	/** The accessible name. Never shown; the icon is the visible part. */
	label: string;
	href: string;
	icon: WordmarkFooterIcon;
}

export interface WordmarkMarqueeFooterProps {
	brandName?: string;
	brandHref?: string;
	blurb?: string;
	socialLabel?: string;
	social?: WordmarkFooterSocial[];
	columns?: WordmarkFooterColumn[];
	/** Defaults to the year the site was built. */
	year?: number;
	legalLinks?: WordmarkFooterLink[];
	/** The scrolling wordmark. */
	wordmark?: string;
	/** The brand mark between wordmark repeats. */
	showMark?: boolean;
	/** Seconds for one full loop at desktop width. Phone runs at half. */
	marqueeDuration?: number;
	/** A native pause toggle over the marquee. */
	showPauseControl?: boolean;
	pauseLabel?: string;
	/**
	 * The photograph under the panel. With no `src` a labelled placeholder
	 * fills the same full-bleed box. `null` leaves a plain navy page edge.
	 */
	image?: { src?: string; alt?: string; hint?: string } | null;
	/** Opacity of the navy layer over the photograph inside the panel. */
	overlayOpacity?: number;
	navLabel?: string;
	fontStylesheet?: string;
	id?: string;
}

interface Props extends WordmarkMarqueeFooterProps {}

/*
 * Nothing about the business is assumed: brand, blurb, icon links, columns,
 * legal links, the scrolling wordmark and the photograph all default to
 * nothing, and a block with nothing in it is not drawn. No brand means no
 * logo and no copyright line; no wordmark means no marquee. Pass nothing at
 * all and the footer renders nothing.
 */
const {
	brandName,
	brandHref = "/",
	blurb,
	socialLabel = "Follow us:",
	social = [],
	columns = [],
	year = new Date().getFullYear(),
	legalLinks = [],
	wordmark,
	showMark = true,
	marqueeDuration = 40,
	showPauseControl = true,
	pauseLabel = "Pause the scrolling wordmark",
	image = null,
	overlayOpacity = 0.9,
	navLabel = "Footer",
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Poppins:wght@400;500;600&display=swap",
	id,
} = Astro.props;

/* Generic glyphs on a 24 grid, stroke 2. No third-party logos: this library
   does not redistribute other companies' marks. Add your own here. */
const GLYPHS: Record<WordmarkFooterIcon, string> = {
	rss: "M5 5a14 14 0 0 1 14 14M5 11a8 8 0 0 1 8 8M6 19h.01",
	play: "M4 6.5A2.5 2.5 0 0 1 6.5 4h11A2.5 2.5 0 0 1 20 6.5v11a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 4 17.5zM10 9l5 3-5 3z",
	chat: "M20 12a8 8 0 0 1-11.6 7.1L4 20l1-4.2A8 8 0 1 1 20 12z",
	mail: "M4 6h16v12H4zM4 7l8 6 8-6",
	phone: "M5 4h4l2 5-2.5 1.5a11 11 0 0 0 5 5L15 13l5 2v4a2 2 0 0 1-2 2A16 16 0 0 1 3 6a2 2 0 0 1 2-2",
	pin: "M12 21s-7-6.2-7-11a7 7 0 0 1 14 0c0 4.8-7 11-7 11zM12 12.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5",
	link: "M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1",
};

/* Three repeats per group is enough to overfill the widest panel (1200px)
   at every breakpoint; the second group is the seamless copy. */
const REPEATS = 3;
const repeats = Array.from({ length: REPEATS }, (_, i) => i);
const duration = Math.max(4, Number(marqueeDuration) || 40);
const opacity = Math.min(1, Math.max(0, Number(overlayOpacity)));

/* Measured underlay: 1425 x 920 at desktop (~16:10). Cover-cropped hard on a
   phone, where the footer is 375 x 1153 — keep the subject central. */
const IMAGE_SPEC = "~16:10 · 2400 × 1500 px";

const hasBrand = Boolean(brandName || blurb || social.length > 0);
const hasRow = hasBrand || columns.length > 0;
const hasBottom = Boolean(brandName || legalLinks.length > 0);
const hasTop = hasRow || hasBottom;
const hasContent = hasTop || Boolean(wordmark);
---

{hasContent && fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
{hasContent && (
<footer
	class="wmf"
	id={id}
	style={`--wmf-duration: ${duration}s; --wmf-overlay: ${opacity};`}
	data-has-image={image ? "" : undefined}
>
	{
		image && (
			<div class="wmf-bg">
				{image.src ? (
					<img src={image.src} alt={image.alt ?? ""} loading="lazy" decoding="async" width="1440" height="920" />
				) : (
					<div class="wmf-ph" role="img" aria-label={`Image placeholder: ${image.hint ?? "photo"}`}>
						<div class="wmf-ph-text" aria-hidden="true">
							<span class="wmf-ph-label">Image</span>
							{image.hint && <span class="wmf-ph-hint">{image.hint}</span>}
							<span class="wmf-ph-spec">{IMAGE_SPEC}</span>
						</div>
					</div>
				)}
			</div>
		)
	}

	<div class="wmf-panel">
		<div class="wmf-overlay" aria-hidden="true"></div>

		<div class="wmf-content">
			{hasTop && (
			<div class="wmf-top">
				{hasRow && (
				<div class="wmf-row">
					{hasBrand && (
					<div class="wmf-brand">
						{brandName && (
						<a class="wmf-logo" href={brandHref} aria-label={`${brandName} home`}>
							<svg class="wmf-logo-mark" viewBox="0 0 40 40" aria-hidden="true" focusable="false">
								<rect width="40" height="40" rx="11" fill="var(--lx-accent, #ff5f00)" />
								<path d="M7 17.5c3.2-3 6.4-3 9.6 0s6.4 3 9.6 0 5.2-2.6 6.8-1.4" fill="none" stroke="#fff" stroke-width="3.4" stroke-linecap="round" />
								<path d="M7 25.5c3.2-3 6.4-3 9.6 0s6.4 3 9.6 0 5.2-2.6 6.8-1.4" fill="none" stroke="var(--lx-navy, #0a2540)" stroke-width="3.4" stroke-linecap="round" />
							</svg>
							<span class="wmf-logo-word" aria-hidden="true">{brandName}</span>
						</a>
						)}
						{blurb && <p class="wmf-blurb">{blurb}</p>}
						{
							social.length > 0 && (
								<div class="wmf-social">
									{socialLabel && <p class="wmf-social-label">{socialLabel}</p>}
									<ul class="wmf-social-list" role="list">
										{social.map((s) => (
											<li>
												<a class="wmf-social-link" href={s.href}>
													<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
														<path d={GLYPHS[s.icon]} />
													</svg>
													<span class="wmf-sr">{s.label}</span>
												</a>
											</li>
										))}
									</ul>
								</div>
							)
						}
					</div>
					)}

					{
						columns.length > 0 && (
							<nav class="wmf-cols" aria-label={navLabel}>
								{columns.map((col) => (
									<div class="wmf-col">
										<h2 class="wmf-col-heading">{col.heading}</h2>
										<ul class="wmf-links" role="list">
											{col.links.map((link) => (
												<li>
													<a class="wmf-link" href={link.href}>{link.label}</a>
												</li>
											))}
										</ul>
									</div>
								))}
							</nav>
						)
					}
				</div>
				)}

				{hasBottom && (
				<div class="wmf-bottom">
					{brandName && (
					<p class="wmf-copy">
						© {year} <span class="wmf-copy-brand">{brandName}.</span> All rights reserved.
					</p>
					)}
					{
						legalLinks.length > 0 && (
							<ul class="wmf-legal" role="list">
								{legalLinks.map((link) => (
									<li>
										<a class="wmf-link" href={link.href}>{link.label}</a>
									</li>
								))}
							</ul>
						)
					}
				</div>
				)}
			</div>
			)}

			{wordmark && (
			<div class="wmf-marquee">
				<div class="wmf-track">
					{
						[0, 1].map((group) => (
							<ul class="wmf-group" role="list" aria-hidden={group === 1 ? "true" : undefined}>
								{repeats.map((i) => (
									<li class="wmf-item" aria-hidden={group === 0 && i > 0 ? "true" : undefined}>
										<span class="wmf-word">{wordmark}</span>
										{showMark && (
											<svg class="wmf-mark" viewBox="0 0 40 40" aria-hidden="true" focusable="false">
												<rect width="40" height="40" rx="11" fill="var(--lx-accent, #ff5f00)" />
												<path d="M7 17.5c3.2-3 6.4-3 9.6 0s6.4 3 9.6 0 5.2-2.6 6.8-1.4" fill="none" stroke="#fff" stroke-width="3.4" stroke-linecap="round" />
												<path d="M7 25.5c3.2-3 6.4-3 9.6 0s6.4 3 9.6 0 5.2-2.6 6.8-1.4" fill="none" stroke="var(--lx-navy, #0a2540)" stroke-width="3.4" stroke-linecap="round" />
											</svg>
										)}
									</li>
								))}
							</ul>
						))
					}
				</div>
				{
					showPauseControl && (
						<label class="wmf-pause">
							<input class="wmf-pause-input" type="checkbox" />
							<span class="wmf-pause-face" aria-hidden="true">
								<svg class="wmf-pause-icon" viewBox="0 0 24 24" focusable="false">
									<path d="M9 6v12M15 6v12" />
								</svg>
								<svg class="wmf-play-icon" viewBox="0 0 24 24" focusable="false">
									<path d="M8 5.5v13l10-6.5z" />
								</svg>
							</span>
							<span class="wmf-sr">{pauseLabel}</span>
						</label>
					)
				}
			</div>
			)}
		</div>
	</div>
</footer>
)}

<style>
	.wmf {
		--wmf-navy: var(--lx-navy, #0a2540);
		--wmf-accent: var(--lx-accent, #ff5f00);
		--wmf-display: var(--lx-display, "Bebas Neue", Impact, "Arial Narrow", sans-serif);
		--wmf-body: var(--lx-body, Poppins, ui-sans-serif, system-ui, sans-serif);
		--wmf-soft: rgb(255 255 255 / 0.8);
		--wmf-dur: 200ms;
		/* Marquee geometry, phone first. */
		--wmf-word: 80px;
		--wmf-mark: 75px;
		--wmf-gap: 24px;
		--wmf-speed: calc(var(--wmf-duration) / 2);

		position: relative;
		box-sizing: border-box;
		padding: 50px 20px;
		background: var(--wmf-navy);
		font-family: var(--wmf-body);
		color: #fff;
		isolation: isolate;
		overflow: hidden;
	}

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

	.wmf ul {
		margin: 0;
		padding: 0;
		list-style: none;
	}

	.wmf p {
		margin: 0;
	}

	.wmf-bg {
		position: absolute;
		inset: 0;
		z-index: -1;
	}

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

	/* Placeholder for an unset `src`. The panel covers the middle of this
	   box, so the text sits in the strip above the panel, on one wrapping
	   row, where nothing covers it. */
	.wmf-ph {
		display: grid;
		place-items: start center;
		width: 100%;
		height: 100%;
		padding: 12px 16px;
		text-align: center;
		border: 1px dashed rgb(255 255 255 / 0.25);
		background: repeating-linear-gradient(135deg, rgb(255 255 255 / 0.06) 0 12px, rgb(255 255 255 / 0.1) 12px 24px);
		color: rgb(255 255 255 / 0.72);
	}

	.wmf-ph-text {
		display: flex;
		flex-wrap: wrap;
		justify-content: center;
		align-items: baseline;
		gap: 2px 12px;
	}

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

	/* A 50px strip on a phone holds the label and the spec, not the brief. */
	.wmf-ph-hint {
		display: none;
		font: 500 13px/1.4 var(--wmf-body);
	}

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

	@media (min-width: 810px) {
		.wmf-ph {
			padding-top: 20px;
		}

		.wmf-ph-hint {
			display: inline;
		}
	}

	.wmf-panel {
		position: relative;
		max-width: 1200px;
		margin: 0 auto;
		padding: 24px 0;
		border-radius: 10px;
		overflow: hidden;
		isolation: isolate;
	}

	.wmf-overlay {
		position: absolute;
		inset: 0;
		z-index: -1;
		background: var(--wmf-navy);
		opacity: var(--wmf-overlay);
	}

	/* Without a photograph there is nothing to see through, so the panel
	   is simply navy on navy — lift it a shade so its edge still reads. */
	.wmf:not([data-has-image]) .wmf-overlay {
		opacity: 1;
		background: color-mix(in srgb, var(--wmf-navy) 88%, #fff);
	}

	.wmf-content {
		display: flex;
		flex-direction: column;
		gap: 40px;
	}

	.wmf-top {
		display: flex;
		flex-direction: column;
		gap: 40px;
		padding: 0 24px;
	}

	.wmf-row {
		display: flex;
		flex-direction: column;
		gap: 40px;
	}

	/* ---- brand column ---- */
	.wmf-brand {
		display: flex;
		flex-direction: column;
		align-items: flex-start;
		gap: 24px;
	}

	.wmf-logo {
		display: inline-flex;
		align-items: center;
		gap: 8px;
		min-height: 44px;
		margin: -7px 0;
		color: #fff;
		text-decoration: none;
		border-radius: 8px;
	}

	.wmf-logo-mark {
		width: 30px;
		height: 30px;
	}

	.wmf-logo-word {
		font-family: var(--wmf-display);
		font-size: 30px;
		line-height: 1;
		letter-spacing: -0.02em;
		text-transform: uppercase;
		padding-top: 3px;
	}

	.wmf-blurb {
		max-width: 300px;
		font: 400 16px/25.6px var(--wmf-body);
		color: var(--wmf-soft);
	}

	.wmf-social {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		gap: 0 10px;
	}

	.wmf-social-label {
		font: 500 14px/14px var(--wmf-body);
		text-transform: uppercase;
		color: #fff;
		white-space: nowrap;
	}

	/* 20px glyphs in 44px targets. The pitch is 44 rather than the
	   reference's 28 — the price of a target a thumb can hit. */
	.wmf-social-list {
		display: flex;
		align-items: center;
	}

	.wmf-social-link {
		display: grid;
		place-items: center;
		width: 44px;
		height: 44px;
		border-radius: 50%;
		color: var(--wmf-soft);
		transition: color var(--wmf-dur) ease;
	}

	.wmf-social-link svg {
		width: 20px;
		height: 20px;
		fill: none;
		stroke: currentColor;
		stroke-width: 2;
		stroke-linecap: round;
		stroke-linejoin: round;
	}

	.wmf-social-link:hover {
		color: var(--wmf-accent);
	}

	/* ---- link columns ---- */
	.wmf-cols {
		display: flex;
		flex-direction: column;
		gap: 32px;
	}

	.wmf-col {
		display: flex;
		flex-direction: column;
		gap: 20px;
		min-width: 0;
	}

	.wmf-col-heading {
		margin: 0;
		font-family: var(--wmf-display);
		font-weight: 400;
		font-size: 16px;
		line-height: 1;
		letter-spacing: -0.02em;
		text-transform: uppercase;
		color: #fff;
	}

	.wmf-links {
		display: flex;
		flex-direction: column;
		align-items: flex-start;
		gap: 12px;
	}

	/* 25.6px lines in 44px targets; the margin gives the height back. */
	.wmf-link {
		display: inline-block;
		padding: 9px 0;
		margin: -9px 0;
		font: 400 16px/25.6px var(--wmf-body);
		color: var(--wmf-soft);
		text-decoration: none;
		overflow-wrap: anywhere;
		border-radius: 4px;
		transition: color var(--wmf-dur) ease;
	}

	.wmf-link:hover {
		color: #fff;
	}

	/* ---- copyright bar ---- */
	.wmf-bottom {
		display: flex;
		flex-direction: column;
		align-items: flex-start;
		gap: 12px;
		padding-top: 18px;
		border-top: 1px solid rgb(255 255 255 / 0.1);
	}

	.wmf-copy {
		font: 400 16px/25.6px var(--wmf-body);
		color: var(--wmf-soft);
	}

	.wmf-copy-brand {
		font-size: 14px;
		font-weight: 500;
		text-transform: uppercase;
		color: var(--wmf-accent);
	}

	.wmf-legal {
		display: flex;
		flex-wrap: wrap;
		gap: 12px 24px;
	}

	/* ---- the marquee ---- */
	.wmf-marquee {
		position: relative;
		overflow: hidden;
	}

	.wmf-track {
		display: flex;
		width: max-content;
		animation: wmf-scroll var(--wmf-speed) linear infinite;
	}

	.wmf-group {
		display: flex;
		flex: none;
	}

	/* Trailing padding rather than a flex gap, so every group is exactly
	   REPEATS x (item + gap) wide and -50% lands on a seam. */
	.wmf-item {
		display: flex;
		align-items: center;
		gap: var(--wmf-gap);
		padding-right: var(--wmf-gap);
		flex: none;
	}

	.wmf-word {
		font-family: var(--wmf-display);
		font-size: var(--wmf-word);
		line-height: 1;
		letter-spacing: -0.02em;
		text-transform: uppercase;
		white-space: nowrap;
		color: #fff;
	}

	.wmf-mark {
		width: var(--wmf-mark);
		height: var(--wmf-mark);
		flex: none;
	}

	@keyframes wmf-scroll {
		to {
			transform: translateX(-50%);
		}
	}

	.wmf-marquee:hover .wmf-track,
	.wmf-marquee:focus-within .wmf-track,
	.wmf-marquee:has(.wmf-pause-input:checked) .wmf-track {
		animation-play-state: paused;
	}

	/* The pause toggle: a native checkbox, so it is keyboard-operable and
	   announces its state with no script behind it. */
	.wmf-pause {
		position: absolute;
		right: 12px;
		bottom: 12px;
		display: block;
		width: 44px;
		height: 44px;
		cursor: pointer;
	}

	.wmf-pause-input {
		position: absolute;
		inset: 0;
		width: 100%;
		height: 100%;
		margin: 0;
		opacity: 0;
		cursor: pointer;
	}

	.wmf-pause-face {
		display: grid;
		place-items: center;
		width: 100%;
		height: 100%;
		border-radius: 50%;
		background: rgb(10 37 64 / 0.7);
		border: 1px solid rgb(255 255 255 / 0.25);
		color: #fff;
		pointer-events: none;
		transition: background-color var(--wmf-dur) ease;
	}

	.wmf-pause:hover .wmf-pause-face {
		background: var(--wmf-navy);
	}

	.wmf-pause-face svg {
		grid-area: 1 / 1;
		width: 18px;
		height: 18px;
		stroke: currentColor;
		stroke-width: 2.4;
		stroke-linecap: round;
		stroke-linejoin: round;
	}

	.wmf-pause-icon {
		fill: none;
	}

	.wmf-play-icon {
		fill: currentColor;
		display: none;
	}

	.wmf-pause-input:checked ~ .wmf-pause-face .wmf-pause-icon {
		display: none;
	}

	.wmf-pause-input:checked ~ .wmf-pause-face .wmf-play-icon {
		display: block;
	}

	/* ---- focus ---- */
	.wmf a:focus-visible {
		outline: 2px solid var(--wmf-accent);
		outline-offset: 2px;
	}

	.wmf-pause-input:focus-visible ~ .wmf-pause-face {
		outline: 2px solid var(--wmf-accent);
		outline-offset: 2px;
	}

	.wmf-sr {
		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) {
		.wmf {
			--wmf-word: 140px;
			--wmf-mark: 118px;
			--wmf-gap: 32px;
			--wmf-speed: calc(var(--wmf-duration) * 0.75);
			padding: 64px 30px;
		}

		.wmf-panel {
			padding: 64px 0 32px;
		}

		.wmf-content {
			gap: 56px;
		}

		.wmf-top {
			gap: 64px;
			padding: 0 48px;
		}

		.wmf-row {
			gap: 48px;
		}

		.wmf-cols {
			display: grid;
			grid-template-columns: repeat(3, minmax(0, auto));
			justify-content: space-between;
			column-gap: 40px;
		}

		.wmf-col {
			gap: 40px;
			max-width: 240px;
		}

		.wmf-col-heading {
			font-size: 24px;
		}

		.wmf-links {
			gap: 16px;
		}

		.wmf-bottom {
			flex-direction: row;
			justify-content: space-between;
			align-items: center;
			gap: 24px;
		}
	}

	/* ---- desktop ---- */
	@media (min-width: 1200px) {
		.wmf {
			--wmf-word: 200px;
			--wmf-mark: 168px;
			--wmf-gap: 40px;
			--wmf-speed: var(--wmf-duration);
			padding: 80px 30px;
		}

		.wmf-panel {
			padding: 80px 0 40px;
		}

		.wmf-content {
			gap: 60px;
		}

		.wmf-top {
			gap: 80px;
			padding: 0 80px;
		}

		.wmf-row {
			flex-direction: row;
			justify-content: space-between;
			align-items: stretch;
			gap: 48px;
		}

		.wmf-brand {
			justify-content: space-between;
			padding-bottom: 10px;
			max-width: 280px;
		}

		.wmf-cols {
			display: flex;
			flex-direction: row;
			gap: 64px;
		}
	}

	@media (prefers-reduced-motion: reduce) {
		.wmf {
			--wmf-dur: 1ms;
		}

		.wmf-track {
			animation: none;
		}

		/* Nothing moves, so there is nothing to pause. */
		.wmf-pause {
			display: none;
		}
	}

	@media (forced-colors: active) {
		.wmf-panel {
			border: 1px solid CanvasText;
		}

		.wmf-bottom {
			border-top-color: CanvasText;
		}
	}
</style>

===== END src/components/WordmarkMarqueeFooter.astro =====

===== FILE (supporting): src/components/WordmarkMarqueeFooter.md =====
# Wordmark marquee footer

A site footer built as a rounded navy panel set into the page over a
full-bleed photograph. Inside: the brand, a short blurb and a row of icon
links, three link columns, a copyright bar — and along the bottom a huge
display-font wordmark scrolling sideways in a continuous loop, with the
brand mark between each repeat.

Catalog page: `/components/wordmark-marquee-footer`

## Files required

**`WordmarkMarqueeFooter.astro`** — one file, the whole component. **No
script**: the marquee is a CSS animation. No imports, no packages. Reads
`--lx-navy`, `--lx-accent`, `--lx-display` and `--lx-body` from any ancestor.

`WordmarkMarqueeFooter.md` — this file. Documentation, not required to run.

No assets. The underlay photo is off by default and can be a labelled
placeholder — see Images.

## Basic usage

```astro
---
import WordmarkMarqueeFooter from "../components/WordmarkMarqueeFooter.astro";
---
<body>
  <main><slot /></main>
  <WordmarkMarqueeFooter
    brandName="Tidewater"
    wordmark="TIDEWATER"
    columns={[
      { heading: "Company", links: [{ label: "About", href: "/about" }] },
    ]}
  />
</body>
```

## Props

Nothing about the business is assumed. Brand, blurb, icon links, columns,
legal links, the wordmark and the photograph default to empty, and a block
with nothing in it is not drawn: no `brandName` means no logo and no
copyright line, no `wordmark` means no marquee. Pass nothing at all and the
component renders nothing (not even the font stylesheet). The "Tidewater"
freight demo in the catalog preview is passed from the catalog registry and
does not travel with the file.

| Prop | Type | Default | What it does |
| :--- | :--- | :--- | :--- |
| `brandName` | `string?` | — | Logo wordmark and the copyright name. Omitted hides both. |
| `brandHref` | `string?` | `"/"` | |
| `blurb` | `string?` | — | Under the logo. Omitted or `""` drops it. |
| `socialLabel` | `string?` | `"Follow us:"` | Before the icon links. |
| `social` | `{ label, href, icon }[]?` | `[]` | `icon` is one of `rss`, `play`, `chat`, `mail`, `phone`, `pin`, `link`. `label` is the accessible name. |
| `columns` | `{ heading, links }[]?` | `[]` | The link columns. |
| `year` | `number?` | build year | In the copyright line (shown only with a `brandName`). |
| `legalLinks` | `{ label, href }[]?` | `[]` | Right of the copyright. |
| `wordmark` | `string?` | — | The scrolling text. Omitted hides the marquee. |
| `showMark` | `boolean?` | `true` | The mark between repeats. |
| `marqueeDuration` | `number?` | `40` | Seconds per loop at desktop. Tablet runs at 0.75×, phone at 0.5×. |
| `showPauseControl` | `boolean?` | `true` | The native pause toggle over the marquee. |
| `pauseLabel` | `string?` | `"Pause the scrolling wordmark"` | Its accessible name. |
| `image` | `{ src?, alt?, hint? } \| null` | `null` | No `src` renders the placeholder; `null` gives a plain navy edge. |
| `overlayOpacity` | `number?` | `0.9` | The navy layer over the photo inside the panel. |
| `navLabel` | `string?` | `"Footer"` | Accessible name of the columns nav. |
| `fontStylesheet` | `string?` | Google Fonts URL | `""` turns it off. |
| `id` | `string?` | — | Root id. |

## Images

One slot, the full-bleed underlay. It is off by default (`image` is `null`,
a plain navy edge). Pass an object with no `src` and it renders a striped
placeholder in the same box (`role="img"`, named "Image placeholder: {hint}").
The panel covers its middle, so the IMAGE label, hint and size line sit on one
row in the strip above the panel; on a phone that strip is 50px, so only the
label and size line show there. The navy overlay still sits over it inside
the panel. Pass `src` and it is an `<img>`, `object-fit: cover`, lazy.

| Slot | Catalog demo hint | Ratio · size |
| :--- | :--- | :--- |
| `image` | Loaded semi truck on an open highway at sunset, low side angle, motion blur | ~16:10 · 2400 × 1500 px (box is 1425×920 at desktop; ~375×1150 on a phone, so it is cropped hard — keep the subject central) |

```astro
<WordmarkMarqueeFooter image={{ src: "/img/highway.jpg", alt: "" }} />
```

## Behaviour

- **Layout, 1200px+:** 80px/30px outer padding over the photo; a 1200px panel
  at a 10px radius with 80/0/40 padding; top row with the brand column
  (logo top, icon links bottom) opposite three columns 64px apart; a
  hairline-topped copyright bar 80px below; the marquee 60px below that at
  200px type, a 168px mark and 40px gaps.
- **810–1199:** columns become a three-up grid under the brand block;
  marquee at 140px.
- **Below 810:** everything stacks; column headings at 16px, links 12px
  apart, marquee at 80px type and a 75px mark.
- **Marquee.** Two identical groups of three repeats; the track translates
  by -50%, which is exactly one group, so the loop has no seam. Each item
  carries its gap as trailing padding so the arithmetic holds. It pauses on
  hover, while anything inside it has focus, and while the toggle is
  checked (via `:has()`). Under reduced motion it does not run at all and
  the toggle is hidden.

## Accessibility

- A real `<footer>`; the columns are a `<nav>` with a settable name; column
  headings are `<h2>`.
- The wordmark is announced once. Repeats and the second group are
  `aria-hidden`; the marks are `aria-hidden`.
- The pause control is a native checkbox with a visually hidden label, so it
  is keyboard-operable and announces its state with no script.
- Every link is a 44px target: text links are padded to 44px with the space
  given back by negative margin; icon links are 44×44.
- Visible accent focus ring on every link and on the toggle.
- Forced colours: the panel and the bar hairline get real borders.

## Approximations

- **Blurb.** The reference has no blurb; it is added per the brief between
  the logo and the icon links.
- **Icon links** are generic glyphs, not social-network logos, and sit at a
  44px pitch instead of the reference's 28px so each is a full touch target.
- **Column gap** is 64px, not 80px, so the added blurb fits beside the
  columns at 1200px.
- **Marquee speed** was not measurable from a still dump; 40s per loop at
  desktop is a judgement call.
- **Pause toggle** is an addition — the reference's marquee has no control.
- **Phone overlay opacity.** The phone dump shows the overlay without an
  opacity value; 0.9 is used at every width.
- **Tablet (810–1199)** was not captured and is interpolated.
- **Photo and mark.** The reference's photograph is not reproduced — the slot
  ships as a placeholder — and the mark is original to this library.
- **Placeholder hint hidden on phones**, where the visible strip above the
  panel is too short to hold it; the label and size line stay.

===== END src/components/WordmarkMarqueeFooter.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: 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
“Wordmark marquee footer” from my Astro Component Library:
https://astro.baysixmedia.com/components/wordmark-marquee-footer/

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
1 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/WordmarkMarqueeFooter.astro

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

---
/**
 * WordmarkMarqueeFooter
 *
 * A site footer built as a rounded navy panel set into the page over a
 * full-bleed photograph, which shows round the panel's edges and faintly
 * through it. Inside: the brand, a short blurb and a row of icon links,
 * link columns, a copyright bar — and along the bottom a huge display-font
 * wordmark that scrolls sideways forever, with the brand mark between each
 * repeat.
 *
 * Static: no script. The marquee is a CSS animation over two identical
 * groups, translated by exactly one group's width, so the loop has no seam.
 * It stops under reduced motion, pauses on hover and focus-within, and
 * carries a native pause toggle so it can be stopped without a pointer.
 *
 * Reads --lx-* design tokens from any ancestor, with fallbacks.
 */

export type WordmarkFooterIcon = "rss" | "play" | "chat" | "mail" | "phone" | "pin" | "link";

export interface WordmarkFooterLink {
	label: string;
	href: string;
}

export interface WordmarkFooterColumn {
	heading: string;
	links: WordmarkFooterLink[];
}

export interface WordmarkFooterSocial {
	/** The accessible name. Never shown; the icon is the visible part. */
	label: string;
	href: string;
	icon: WordmarkFooterIcon;
}

export interface WordmarkMarqueeFooterProps {
	brandName?: string;
	brandHref?: string;
	blurb?: string;
	socialLabel?: string;
	social?: WordmarkFooterSocial[];
	columns?: WordmarkFooterColumn[];
	/** Defaults to the year the site was built. */
	year?: number;
	legalLinks?: WordmarkFooterLink[];
	/** The scrolling wordmark. */
	wordmark?: string;
	/** The brand mark between wordmark repeats. */
	showMark?: boolean;
	/** Seconds for one full loop at desktop width. Phone runs at half. */
	marqueeDuration?: number;
	/** A native pause toggle over the marquee. */
	showPauseControl?: boolean;
	pauseLabel?: string;
	/**
	 * The photograph under the panel. With no `src` a labelled placeholder
	 * fills the same full-bleed box. `null` leaves a plain navy page edge.
	 */
	image?: { src?: string; alt?: string; hint?: string } | null;
	/** Opacity of the navy layer over the photograph inside the panel. */
	overlayOpacity?: number;
	navLabel?: string;
	fontStylesheet?: string;
	id?: string;
}

interface Props extends WordmarkMarqueeFooterProps {}

/*
 * Nothing about the business is assumed: brand, blurb, icon links, columns,
 * legal links, the scrolling wordmark and the photograph all default to
 * nothing, and a block with nothing in it is not drawn. No brand means no
 * logo and no copyright line; no wordmark means no marquee. Pass nothing at
 * all and the footer renders nothing.
 */
const {
	brandName,
	brandHref = "/",
	blurb,
	socialLabel = "Follow us:",
	social = [],
	columns = [],
	year = new Date().getFullYear(),
	legalLinks = [],
	wordmark,
	showMark = true,
	marqueeDuration = 40,
	showPauseControl = true,
	pauseLabel = "Pause the scrolling wordmark",
	image = null,
	overlayOpacity = 0.9,
	navLabel = "Footer",
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Poppins:wght@400;500;600&display=swap",
	id,
} = Astro.props;

/* Generic glyphs on a 24 grid, stroke 2. No third-party logos: this library
   does not redistribute other companies' marks. Add your own here. */
const GLYPHS: Record<WordmarkFooterIcon, string> = {
	rss: "M5 5a14 14 0 0 1 14 14M5 11a8 8 0 0 1 8 8M6 19h.01",
	play: "M4 6.5A2.5 2.5 0 0 1 6.5 4h11A2.5 2.5 0 0 1 20 6.5v11a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 4 17.5zM10 9l5 3-5 3z",
	chat: "M20 12a8 8 0 0 1-11.6 7.1L4 20l1-4.2A8 8 0 1 1 20 12z",
	mail: "M4 6h16v12H4zM4 7l8 6 8-6",
	phone: "M5 4h4l2 5-2.5 1.5a11 11 0 0 0 5 5L15 13l5 2v4a2 2 0 0 1-2 2A16 16 0 0 1 3 6a2 2 0 0 1 2-2",
	pin: "M12 21s-7-6.2-7-11a7 7 0 0 1 14 0c0 4.8-7 11-7 11zM12 12.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5",
	link: "M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1",
};

/* Three repeats per group is enough to overfill the widest panel (1200px)
   at every breakpoint; the second group is the seamless copy. */
const REPEATS = 3;
const repeats = Array.from({ length: REPEATS }, (_, i) => i);
const duration = Math.max(4, Number(marqueeDuration) || 40);
const opacity = Math.min(1, Math.max(0, Number(overlayOpacity)));

/* Measured underlay: 1425 x 920 at desktop (~16:10). Cover-cropped hard on a
   phone, where the footer is 375 x 1153 — keep the subject central. */
const IMAGE_SPEC = "~16:10 · 2400 × 1500 px";

const hasBrand = Boolean(brandName || blurb || social.length > 0);
const hasRow = hasBrand || columns.length > 0;
const hasBottom = Boolean(brandName || legalLinks.length > 0);
const hasTop = hasRow || hasBottom;
const hasContent = hasTop || Boolean(wordmark);
---

{hasContent && fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
{hasContent && (
<footer
	class="wmf"
	id={id}
	style={`--wmf-duration: ${duration}s; --wmf-overlay: ${opacity};`}
	data-has-image={image ? "" : undefined}
>
	{
		image && (
			<div class="wmf-bg">
				{image.src ? (
					<img src={image.src} alt={image.alt ?? ""} loading="lazy" decoding="async" width="1440" height="920" />
				) : (
					<div class="wmf-ph" role="img" aria-label={`Image placeholder: ${image.hint ?? "photo"}`}>
						<div class="wmf-ph-text" aria-hidden="true">
							<span class="wmf-ph-label">Image</span>
							{image.hint && <span class="wmf-ph-hint">{image.hint}</span>}
							<span class="wmf-ph-spec">{IMAGE_SPEC}</span>
						</div>
					</div>
				)}
			</div>
		)
	}

	<div class="wmf-panel">
		<div class="wmf-overlay" aria-hidden="true"></div>

		<div class="wmf-content">
			{hasTop && (
			<div class="wmf-top">
				{hasRow && (
				<div class="wmf-row">
					{hasBrand && (
					<div class="wmf-brand">
						{brandName && (
						<a class="wmf-logo" href={brandHref} aria-label={`${brandName} home`}>
							<svg class="wmf-logo-mark" viewBox="0 0 40 40" aria-hidden="true" focusable="false">
								<rect width="40" height="40" rx="11" fill="var(--lx-accent, #ff5f00)" />
								<path d="M7 17.5c3.2-3 6.4-3 9.6 0s6.4 3 9.6 0 5.2-2.6 6.8-1.4" fill="none" stroke="#fff" stroke-width="3.4" stroke-linecap="round" />
								<path d="M7 25.5c3.2-3 6.4-3 9.6 0s6.4 3 9.6 0 5.2-2.6 6.8-1.4" fill="none" stroke="var(--lx-navy, #0a2540)" stroke-width="3.4" stroke-linecap="round" />
							</svg>
							<span class="wmf-logo-word" aria-hidden="true">{brandName}</span>
						</a>
						)}
						{blurb && <p class="wmf-blurb">{blurb}</p>}
						{
							social.length > 0 && (
								<div class="wmf-social">
									{socialLabel && <p class="wmf-social-label">{socialLabel}</p>}
									<ul class="wmf-social-list" role="list">
										{social.map((s) => (
											<li>
												<a class="wmf-social-link" href={s.href}>
													<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
														<path d={GLYPHS[s.icon]} />
													</svg>
													<span class="wmf-sr">{s.label}</span>
												</a>
											</li>
										))}
									</ul>
								</div>
							)
						}
					</div>
					)}

					{
						columns.length > 0 && (
							<nav class="wmf-cols" aria-label={navLabel}>
								{columns.map((col) => (
									<div class="wmf-col">
										<h2 class="wmf-col-heading">{col.heading}</h2>
										<ul class="wmf-links" role="list">
											{col.links.map((link) => (
												<li>
													<a class="wmf-link" href={link.href}>{link.label}</a>
												</li>
											))}
										</ul>
									</div>
								))}
							</nav>
						)
					}
				</div>
				)}

				{hasBottom && (
				<div class="wmf-bottom">
					{brandName && (
					<p class="wmf-copy">
						© {year} <span class="wmf-copy-brand">{brandName}.</span> All rights reserved.
					</p>
					)}
					{
						legalLinks.length > 0 && (
							<ul class="wmf-legal" role="list">
								{legalLinks.map((link) => (
									<li>
										<a class="wmf-link" href={link.href}>{link.label}</a>
									</li>
								))}
							</ul>
						)
					}
				</div>
				)}
			</div>
			)}

			{wordmark && (
			<div class="wmf-marquee">
				<div class="wmf-track">
					{
						[0, 1].map((group) => (
							<ul class="wmf-group" role="list" aria-hidden={group === 1 ? "true" : undefined}>
								{repeats.map((i) => (
									<li class="wmf-item" aria-hidden={group === 0 && i > 0 ? "true" : undefined}>
										<span class="wmf-word">{wordmark}</span>
										{showMark && (
											<svg class="wmf-mark" viewBox="0 0 40 40" aria-hidden="true" focusable="false">
												<rect width="40" height="40" rx="11" fill="var(--lx-accent, #ff5f00)" />
												<path d="M7 17.5c3.2-3 6.4-3 9.6 0s6.4 3 9.6 0 5.2-2.6 6.8-1.4" fill="none" stroke="#fff" stroke-width="3.4" stroke-linecap="round" />
												<path d="M7 25.5c3.2-3 6.4-3 9.6 0s6.4 3 9.6 0 5.2-2.6 6.8-1.4" fill="none" stroke="var(--lx-navy, #0a2540)" stroke-width="3.4" stroke-linecap="round" />
											</svg>
										)}
									</li>
								))}
							</ul>
						))
					}
				</div>
				{
					showPauseControl && (
						<label class="wmf-pause">
							<input class="wmf-pause-input" type="checkbox" />
							<span class="wmf-pause-face" aria-hidden="true">
								<svg class="wmf-pause-icon" viewBox="0 0 24 24" focusable="false">
									<path d="M9 6v12M15 6v12" />
								</svg>
								<svg class="wmf-play-icon" viewBox="0 0 24 24" focusable="false">
									<path d="M8 5.5v13l10-6.5z" />
								</svg>
							</span>
							<span class="wmf-sr">{pauseLabel}</span>
						</label>
					)
				}
			</div>
			)}
		</div>
	</div>
</footer>
)}

<style>
	.wmf {
		--wmf-navy: var(--lx-navy, #0a2540);
		--wmf-accent: var(--lx-accent, #ff5f00);
		--wmf-display: var(--lx-display, "Bebas Neue", Impact, "Arial Narrow", sans-serif);
		--wmf-body: var(--lx-body, Poppins, ui-sans-serif, system-ui, sans-serif);
		--wmf-soft: rgb(255 255 255 / 0.8);
		--wmf-dur: 200ms;
		/* Marquee geometry, phone first. */
		--wmf-word: 80px;
		--wmf-mark: 75px;
		--wmf-gap: 24px;
		--wmf-speed: calc(var(--wmf-duration) / 2);

		position: relative;
		box-sizing: border-box;
		padding: 50px 20px;
		background: var(--wmf-navy);
		font-family: var(--wmf-body);
		color: #fff;
		isolation: isolate;
		overflow: hidden;
	}

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

	.wmf ul {
		margin: 0;
		padding: 0;
		list-style: none;
	}

	.wmf p {
		margin: 0;
	}

	.wmf-bg {
		position: absolute;
		inset: 0;
		z-index: -1;
	}

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

	/* Placeholder for an unset `src`. The panel covers the middle of this
	   box, so the text sits in the strip above the panel, on one wrapping
	   row, where nothing covers it. */
	.wmf-ph {
		display: grid;
		place-items: start center;
		width: 100%;
		height: 100%;
		padding: 12px 16px;
		text-align: center;
		border: 1px dashed rgb(255 255 255 / 0.25);
		background: repeating-linear-gradient(135deg, rgb(255 255 255 / 0.06) 0 12px, rgb(255 255 255 / 0.1) 12px 24px);
		color: rgb(255 255 255 / 0.72);
	}

	.wmf-ph-text {
		display: flex;
		flex-wrap: wrap;
		justify-content: center;
		align-items: baseline;
		gap: 2px 12px;
	}

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

	/* A 50px strip on a phone holds the label and the spec, not the brief. */
	.wmf-ph-hint {
		display: none;
		font: 500 13px/1.4 var(--wmf-body);
	}

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

	@media (min-width: 810px) {
		.wmf-ph {
			padding-top: 20px;
		}

		.wmf-ph-hint {
			display: inline;
		}
	}

	.wmf-panel {
		position: relative;
		max-width: 1200px;
		margin: 0 auto;
		padding: 24px 0;
		border-radius: 10px;
		overflow: hidden;
		isolation: isolate;
	}

	.wmf-overlay {
		position: absolute;
		inset: 0;
		z-index: -1;
		background: var(--wmf-navy);
		opacity: var(--wmf-overlay);
	}

	/* Without a photograph there is nothing to see through, so the panel
	   is simply navy on navy — lift it a shade so its edge still reads. */
	.wmf:not([data-has-image]) .wmf-overlay {
		opacity: 1;
		background: color-mix(in srgb, var(--wmf-navy) 88%, #fff);
	}

	.wmf-content {
		display: flex;
		flex-direction: column;
		gap: 40px;
	}

	.wmf-top {
		display: flex;
		flex-direction: column;
		gap: 40px;
		padding: 0 24px;
	}

	.wmf-row {
		display: flex;
		flex-direction: column;
		gap: 40px;
	}

	/* ---- brand column ---- */
	.wmf-brand {
		display: flex;
		flex-direction: column;
		align-items: flex-start;
		gap: 24px;
	}

	.wmf-logo {
		display: inline-flex;
		align-items: center;
		gap: 8px;
		min-height: 44px;
		margin: -7px 0;
		color: #fff;
		text-decoration: none;
		border-radius: 8px;
	}

	.wmf-logo-mark {
		width: 30px;
		height: 30px;
	}

	.wmf-logo-word {
		font-family: var(--wmf-display);
		font-size: 30px;
		line-height: 1;
		letter-spacing: -0.02em;
		text-transform: uppercase;
		padding-top: 3px;
	}

	.wmf-blurb {
		max-width: 300px;
		font: 400 16px/25.6px var(--wmf-body);
		color: var(--wmf-soft);
	}

	.wmf-social {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		gap: 0 10px;
	}

	.wmf-social-label {
		font: 500 14px/14px var(--wmf-body);
		text-transform: uppercase;
		color: #fff;
		white-space: nowrap;
	}

	/* 20px glyphs in 44px targets. The pitch is 44 rather than the
	   reference's 28 — the price of a target a thumb can hit. */
	.wmf-social-list {
		display: flex;
		align-items: center;
	}

	.wmf-social-link {
		display: grid;
		place-items: center;
		width: 44px;
		height: 44px;
		border-radius: 50%;
		color: var(--wmf-soft);
		transition: color var(--wmf-dur) ease;
	}

	.wmf-social-link svg {
		width: 20px;
		height: 20px;
		fill: none;
		stroke: currentColor;
		stroke-width: 2;
		stroke-linecap: round;
		stroke-linejoin: round;
	}

	.wmf-social-link:hover {
		color: var(--wmf-accent);
	}

	/* ---- link columns ---- */
	.wmf-cols {
		display: flex;
		flex-direction: column;
		gap: 32px;
	}

	.wmf-col {
		display: flex;
		flex-direction: column;
		gap: 20px;
		min-width: 0;
	}

	.wmf-col-heading {
		margin: 0;
		font-family: var(--wmf-display);
		font-weight: 400;
		font-size: 16px;
		line-height: 1;
		letter-spacing: -0.02em;
		text-transform: uppercase;
		color: #fff;
	}

	.wmf-links {
		display: flex;
		flex-direction: column;
		align-items: flex-start;
		gap: 12px;
	}

	/* 25.6px lines in 44px targets; the margin gives the height back. */
	.wmf-link {
		display: inline-block;
		padding: 9px 0;
		margin: -9px 0;
		font: 400 16px/25.6px var(--wmf-body);
		color: var(--wmf-soft);
		text-decoration: none;
		overflow-wrap: anywhere;
		border-radius: 4px;
		transition: color var(--wmf-dur) ease;
	}

	.wmf-link:hover {
		color: #fff;
	}

	/* ---- copyright bar ---- */
	.wmf-bottom {
		display: flex;
		flex-direction: column;
		align-items: flex-start;
		gap: 12px;
		padding-top: 18px;
		border-top: 1px solid rgb(255 255 255 / 0.1);
	}

	.wmf-copy {
		font: 400 16px/25.6px var(--wmf-body);
		color: var(--wmf-soft);
	}

	.wmf-copy-brand {
		font-size: 14px;
		font-weight: 500;
		text-transform: uppercase;
		color: var(--wmf-accent);
	}

	.wmf-legal {
		display: flex;
		flex-wrap: wrap;
		gap: 12px 24px;
	}

	/* ---- the marquee ---- */
	.wmf-marquee {
		position: relative;
		overflow: hidden;
	}

	.wmf-track {
		display: flex;
		width: max-content;
		animation: wmf-scroll var(--wmf-speed) linear infinite;
	}

	.wmf-group {
		display: flex;
		flex: none;
	}

	/* Trailing padding rather than a flex gap, so every group is exactly
	   REPEATS x (item + gap) wide and -50% lands on a seam. */
	.wmf-item {
		display: flex;
		align-items: center;
		gap: var(--wmf-gap);
		padding-right: var(--wmf-gap);
		flex: none;
	}

	.wmf-word {
		font-family: var(--wmf-display);
		font-size: var(--wmf-word);
		line-height: 1;
		letter-spacing: -0.02em;
		text-transform: uppercase;
		white-space: nowrap;
		color: #fff;
	}

	.wmf-mark {
		width: var(--wmf-mark);
		height: var(--wmf-mark);
		flex: none;
	}

	@keyframes wmf-scroll {
		to {
			transform: translateX(-50%);
		}
	}

	.wmf-marquee:hover .wmf-track,
	.wmf-marquee:focus-within .wmf-track,
	.wmf-marquee:has(.wmf-pause-input:checked) .wmf-track {
		animation-play-state: paused;
	}

	/* The pause toggle: a native checkbox, so it is keyboard-operable and
	   announces its state with no script behind it. */
	.wmf-pause {
		position: absolute;
		right: 12px;
		bottom: 12px;
		display: block;
		width: 44px;
		height: 44px;
		cursor: pointer;
	}

	.wmf-pause-input {
		position: absolute;
		inset: 0;
		width: 100%;
		height: 100%;
		margin: 0;
		opacity: 0;
		cursor: pointer;
	}

	.wmf-pause-face {
		display: grid;
		place-items: center;
		width: 100%;
		height: 100%;
		border-radius: 50%;
		background: rgb(10 37 64 / 0.7);
		border: 1px solid rgb(255 255 255 / 0.25);
		color: #fff;
		pointer-events: none;
		transition: background-color var(--wmf-dur) ease;
	}

	.wmf-pause:hover .wmf-pause-face {
		background: var(--wmf-navy);
	}

	.wmf-pause-face svg {
		grid-area: 1 / 1;
		width: 18px;
		height: 18px;
		stroke: currentColor;
		stroke-width: 2.4;
		stroke-linecap: round;
		stroke-linejoin: round;
	}

	.wmf-pause-icon {
		fill: none;
	}

	.wmf-play-icon {
		fill: currentColor;
		display: none;
	}

	.wmf-pause-input:checked ~ .wmf-pause-face .wmf-pause-icon {
		display: none;
	}

	.wmf-pause-input:checked ~ .wmf-pause-face .wmf-play-icon {
		display: block;
	}

	/* ---- focus ---- */
	.wmf a:focus-visible {
		outline: 2px solid var(--wmf-accent);
		outline-offset: 2px;
	}

	.wmf-pause-input:focus-visible ~ .wmf-pause-face {
		outline: 2px solid var(--wmf-accent);
		outline-offset: 2px;
	}

	.wmf-sr {
		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) {
		.wmf {
			--wmf-word: 140px;
			--wmf-mark: 118px;
			--wmf-gap: 32px;
			--wmf-speed: calc(var(--wmf-duration) * 0.75);
			padding: 64px 30px;
		}

		.wmf-panel {
			padding: 64px 0 32px;
		}

		.wmf-content {
			gap: 56px;
		}

		.wmf-top {
			gap: 64px;
			padding: 0 48px;
		}

		.wmf-row {
			gap: 48px;
		}

		.wmf-cols {
			display: grid;
			grid-template-columns: repeat(3, minmax(0, auto));
			justify-content: space-between;
			column-gap: 40px;
		}

		.wmf-col {
			gap: 40px;
			max-width: 240px;
		}

		.wmf-col-heading {
			font-size: 24px;
		}

		.wmf-links {
			gap: 16px;
		}

		.wmf-bottom {
			flex-direction: row;
			justify-content: space-between;
			align-items: center;
			gap: 24px;
		}
	}

	/* ---- desktop ---- */
	@media (min-width: 1200px) {
		.wmf {
			--wmf-word: 200px;
			--wmf-mark: 168px;
			--wmf-gap: 40px;
			--wmf-speed: var(--wmf-duration);
			padding: 80px 30px;
		}

		.wmf-panel {
			padding: 80px 0 40px;
		}

		.wmf-content {
			gap: 60px;
		}

		.wmf-top {
			gap: 80px;
			padding: 0 80px;
		}

		.wmf-row {
			flex-direction: row;
			justify-content: space-between;
			align-items: stretch;
			gap: 48px;
		}

		.wmf-brand {
			justify-content: space-between;
			padding-bottom: 10px;
			max-width: 280px;
		}

		.wmf-cols {
			display: flex;
			flex-direction: row;
			gap: 64px;
		}
	}

	@media (prefers-reduced-motion: reduce) {
		.wmf {
			--wmf-dur: 1ms;
		}

		.wmf-track {
			animation: none;
		}

		/* Nothing moves, so there is nothing to pause. */
		.wmf-pause {
			display: none;
		}
	}

	@media (forced-colors: active) {
		.wmf-panel {
			border: 1px solid CanvasText;
		}

		.wmf-bottom {
			border-top-color: CanvasText;
		}
	}
</style>