Component library
Back to catalog

Title block footer

A light footer drawn as one ruled sheet: a heavy frame, hairline rules and a field for every fact. The name in a display serif, the number in the one inverted field (the main call path) with the action under it, Email, Visit and Hours in equal fields, link groups as full-width index rows with one link per track, and every row divided on one six-track spine. Every fact only when supplied; empty fields and rows are never drawn.

footerlightruledgridcontactclick-to-callhoursaddressprofessional

Open preview in a new tab

Use this component

Three ways to get Title block footer into one of your own projects. All pre-filled with this component's name and library page.

Or, for the design alone

Placement prompt

What to do, where it goes and what must not change. Paste it into Claude Code inside the destination project.

Read it first
Add Title block footer from my Astro Component Library to this Astro project.

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

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

Position:
[ENTER WHERE IT SHOULD APPEAR]

First read the component source, README, documented props, dependencies, and required assets from the library page. Copy the component and every required companion asset locally into this project. Do not import files from the deployed library at runtime.

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.

--- VERIFIED IN THE COMPONENT LIBRARY ---
Enrolled for verification but never run.
No verification run has been recorded.
No checks have passed yet.
This record pre-dates source fingerprinting, so it is not tied to a specific revision.

--- 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
“Title block footer” from my Astro Component Library:
https://astro.baysixmedia.com/components/title-block-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.

src/components/library/TitleBlockFooter.astro

Installing this one is more than pasting a section

  • installOne required file, plus supporting files worth taking:
    • src/components/TitleBlockFooter.astrocomponentrequired
    • src/components/TitleBlockFooter.mdreadmesupporting
    Use Copy header kit above to get them all, each one labelled with the path it belongs at.
---
/**
 * TitleBlockFooter (candidate — original, role `footer`)
 *
 * A light footer drawn as one ruled sheet, the way a drawing's title block is: a heavy frame, hairline
 * rules, and every piece of information in its own field. Nothing floats in columns; every field is a
 * cell with its label at the top and its value under it, so values across a row share a first line.
 *
 * From tablet up the whole sheet sits on one spine of six equal tracks, and every row divides on it,
 * so no rule stands on its own: the action column's edge, the fact fields and the legal split all
 * fall on the same lines, and index links start where the fact values start.
 *
 *   ┌───────────────────────────────┬───────────────┐
 *   │ Business name (display face)  │ Call          │  ← the call cell is the one inverted field:
 *   │ one or two sentences          │ (650) 555-…   │    the number is the footer's main path
 *   │                               ├───────────────┤
 *   │                               │ Book …    [→] │  ← the action sits directly under it
 *   ├───────────────┬───────────────┼───────────────┤
 *   │ Email         │ Visit         │ Hours         │  ← facts take equal fields on the spine
 *   ├───────┬───────┴───────┬───────┴───────┬───────┤
 *   │ Pages │ link  │ link  │ link  │ link  │ link  │  ← link groups are index rows on the same
 *   │ Serv. │ link  │ link  │ link  │       │       │    tracks, not columns
 *   ├───────┴───────────────────────┬───────┴───────┤
 *   │ © year name                   │ Privacy Terms │
 *   └───────────────────────────────┴───────────────┘
 *   (the inner track lines of the index rows are drawn here for reading only; the sheet draws none)
 *
 * On a tablet the call and action share a row (four tracks and two), facts keep their fields, and
 * each index row sets its title over links on three tracks. On a phone the sheet is one column:
 * name, the call cell, the action, facts as label/value rows, link groups two-up, legal.
 *
 * Truth rules. Every fact renders only when supplied: no number, address, hours or social profile is
 * ever drawn without one. tel:/mailto: from supplied values; an address links only to a supplied https
 * maps URL. Empty cells and empty groups are never drawn, and the sheet re-divides itself for what is
 * left. With no number, the action takes the inverted cell; with neither, the name spans the sheet.
 *
 * Reads, never declares: --tbf-bg, --tbf-text, --tbf-text-muted, --tbf-brand, --tbf-brand-text,
 * --tbf-border, --tbf-family, --tbf-display, --tbf-grid-max, --tbf-grid-gutter.
 */
export interface TitleBlockFooterLink {
	label: string;
	href: string;
}

interface Props {
	businessName?: string;
	logo?: { src: string; alt?: string; width?: number; height?: number } | null;
	homeHref?: string;
	description?: string;
	/** The one action, in the action column under the number (or in the inverted cell when there is no number). */
	cta?: TitleBlockFooterLink | null;
	primaryLinks?: readonly TitleBlockFooterLink[];
	serviceLinks?: readonly TitleBlockFooterLink[];
	legalLinks?: readonly TitleBlockFooterLink[];
	social?: readonly TitleBlockFooterLink[];
	phone?: { number: string } | null;
	email?: string;
	address?: string | readonly string[];
	addressHref?: string;
	hours?: string | readonly string[];
	labels?: { pages?: string; services?: string; social?: string; call?: string; email?: string; visit?: string; hours?: string };
	copyright?: string;
	fontStylesheet?: string;
	class?: string;
	id?: string;
}

const {
	businessName = "",
	logo = null,
	homeHref = "/",
	description = "",
	cta = null,
	primaryLinks = [],
	serviceLinks = [],
	legalLinks = [],
	social = [],
	phone = null,
	email = "",
	address,
	addressHref,
	hours,
	labels = {},
	copyright,
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Newsreader:opsz,wght@6..72,400;6..72,500&family=Inter+Tight:wght@400;500;600&display=swap",
	class: className,
	id,
} = Astro.props;

const safeHref = (href: string | undefined) => {
	const h = (href ?? "").trim();
	return /^(https?:\/\/|mailto:|tel:|#|\/(?!\/))/i.test(h) ? h : null;
};
const clean = (list: readonly TitleBlockFooterLink[]) =>
	list.map((l) => ({ label: (l?.label ?? "").trim(), href: safeHref(l?.href) })).filter((l): l is { label: string; href: string } => Boolean(l.label && l.href));

const name = businessName.trim();
const blurb = description.trim();
const home = safeHref(homeHref);
const hasLogo = Boolean(logo?.src);
const action = cta && cta.label?.trim() && safeHref(cta.href) ? { label: cta.label.trim(), href: safeHref(cta.href)! } : null;

const digits = (phone?.number ?? "").replace(/[^\d+]/g, "");
const call = phone && phone.number?.trim() && digits.replace(/\D/g, "").length >= 7 ? { number: phone.number.trim(), href: `tel:${digits}` } : null;
const mail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim()) ? email.trim() : "";
const lines = (v: string | readonly string[] | undefined) => (Array.isArray(v) ? v : v ? [v] : []).map((x) => String(x).trim()).filter(Boolean);
const addressLines = lines(address);
const hourLines = lines(hours);
const mapHref = addressHref && /^https:\/\//i.test(addressHref.trim()) ? addressHref.trim() : null;

const pages = clean(primaryLinks);
const services = clean(serviceLinks);
const socials = clean(social).filter((l) => /^https?:\/\//i.test(l.href));
const legal = clean(legalLinks);

const groups = [
	...(pages.length ? [{ title: labels.pages ?? "Pages", links: pages, external: false }] : []),
	...(services.length ? [{ title: labels.services ?? "Services", links: services, external: false }] : []),
	...(socials.length ? [{ title: labels.social ?? "Follow", links: socials, external: true }] : []),
];

type Fact = { key: string; values: string[]; href?: string; kind: string };
const facts: Fact[] = [
	...(mail ? [{ key: labels.email ?? "Email", values: [mail], href: `mailto:${mail}`, kind: "email" }] : []),
	...(addressLines.length ? [{ key: labels.visit ?? "Visit", values: addressLines, href: mapHref ?? undefined, kind: "visit" }] : []),
	...(hourLines.length ? [{ key: labels.hours ?? "Hours", values: hourLines, kind: "hours" }] : []),
];

/* The inverted cell carries the number; with no number it carries the action instead. */
const lead = call ? "call" : action ? "action" : null;
const second = call && action ? action : null;
const hasIdentity = Boolean(name || hasLogo || blurb);
/* At desktop the action column (call over action) sits beside the identity field. */
const split = Boolean(lead && hasIdentity);

const year = new Date().getFullYear();
const rights = copyright === undefined ? (name ? `© ${year} ${name}` : "") : copyright.trim();
const hasLegal = Boolean(rights || legal.length);
const brandLabel = name || logo?.alt || "";
---

{(hasIdentity || lead || facts.length > 0 || groups.length > 0) && (
	<footer class:list={["tbf", className]} id={id}>
		{fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
		<div class="tbf-inner">
			<div class:list={["tbf-sheet", { "tbf-sheet--split": split }]}>
				{hasIdentity && (
					<div class="tbf-cell tbf-identity">
						{(name || hasLogo) && (home ? (
							<a class="tbf-brand" href={home}>
								{hasLogo ? <img class="tbf-logo" src={logo!.src} alt={logo!.alt || name} width={logo!.width} height={logo!.height} /> : <span class="tbf-wordmark">{name}</span>}
							</a>
						) : (
							<span class="tbf-brand">
								{hasLogo ? <img class="tbf-logo" src={logo!.src} alt={logo!.alt || name} width={logo!.width} height={logo!.height} /> : <span class="tbf-wordmark">{name}</span>}
							</span>
						))}
						{blurb && <p class="tbf-description">{blurb}</p>}
					</div>
				)}

				{lead && (
					<div class:list={["tbf-action", { "tbf-action--pair": Boolean(second) }]}>
						{lead === "call" && call ? (
							<a class="tbf-cell tbf-lead tbf-lead--call" href={call.href}>
								<span class="tbf-lead-label">{labels.call ?? "Call"}{brandLabel ? <span class="tbf-sr"> {brandLabel}</span> : null}</span>
								<span class="tbf-number">{call.number}</span>
							</a>
						) : action ? (
							<a class="tbf-cell tbf-lead tbf-lead--action" href={action.href}>
								<span class="tbf-lead-text">{action.label}</span>
								<svg class="tbf-arrow" aria-hidden="true" viewBox="0 0 24 24" width="24" height="24"><path d="M4 12h15M13 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="1.8" /></svg>
							</a>
						) : null}
						{second && (
							<a class="tbf-cell tbf-go" href={second.href}>
								<span class="tbf-go-text">{second.label}</span>
								<span class="tbf-go-mark" aria-hidden="true">
									<svg class="tbf-arrow" viewBox="0 0 24 24" width="22" height="22"><path d="M4 12h15M13 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="1.8" /></svg>
								</span>
							</a>
						)}
					</div>
				)}

				{facts.length > 0 && (
					<dl class="tbf-facts" data-count={String(facts.length)}>
						{facts.map((f) => (
							<div class:list={["tbf-cell", "tbf-fact", `tbf-fact--${f.kind}`]}>
								<dt class="tbf-label">{f.key}</dt>
								<dd>
									{f.href ? (
										<a href={f.href}>{f.values.map((v) => <span>{v}</span>)}</a>
									) : (
										f.values.map((v) => <span>{v}</span>)
									)}
								</dd>
							</div>
						))}
					</dl>
				)}

				{groups.length > 0 && (
					<div class="tbf-index">
						{groups.map((g) => (
							<nav class="tbf-row" aria-label={g.title}>
								<p class="tbf-cell tbf-row-title tbf-label" aria-hidden="true">{g.title}</p>
								<ul class="tbf-cell tbf-row-links" role="list" data-many={g.links.length > 4 ? "" : undefined}>
									{g.links.map((l) => (
										<li>
											<a href={l.href} rel={g.external ? "me noopener" : undefined}>{l.label}</a>
										</li>
									))}
								</ul>
							</nav>
						))}
					</div>
				)}

				{hasLegal && (
					<div class:list={["tbf-legal", { "tbf-legal--split": Boolean(rights) && legal.length > 0 }]}>
						{rights && <p class="tbf-cell tbf-rights">{rights}</p>}
						{legal.length > 0 && (
							<ul class="tbf-cell tbf-policies" role="list">
								{legal.map((l) => <li><a href={l.href}>{l.label}</a></li>)}
							</ul>
						)}
					</div>
				)}
			</div>
		</div>
	</footer>
)}

<style>
	.tbf {
		--tbf-paper: var(--tbf-bg, #f2f0e9);
		--tbf-ink: var(--tbf-text, #15171a);
		--tbf-soft: var(--tbf-text-muted, #3b3f45);
		--tbf-accent: var(--tbf-brand, #1f3d2e);
		--tbf-accent-ink: var(--tbf-brand-text, #f4f2ea);
		--tbf-rule: var(--tbf-border, #8a8474);
		--tbf-font: var(--tbf-family, "Inter Tight", ui-sans-serif, system-ui, sans-serif);
		--tbf-font-display: var(--tbf-display, "Newsreader", ui-serif, Georgia, serif);
		--tbf-pad-x: 20px;
		--tbf-pad-y: 20px;
		container-type: inline-size;
		background: var(--tbf-paper);
		color: var(--tbf-ink);
		font-family: var(--tbf-font);
		-webkit-font-smoothing: antialiased;
	}
	.tbf-inner {
		box-sizing: border-box;
		max-width: var(--tbf-grid-max, 1232px);
		margin: 0 auto;
		padding: 40px 16px 32px;
	}
	.tbf a {
		color: inherit;
		text-decoration: none;
	}
	.tbf a:focus-visible {
		outline: 2px solid var(--tbf-ink);
		outline-offset: 2px;
		border-radius: 1px;
	}
	.tbf-sr {
		position: absolute;
		width: 1px;
		height: 1px;
		overflow: hidden;
		clip-path: inset(50%);
		white-space: nowrap;
	}

	/* The sheet: a heavy frame; the 1px gaps between cells show the rule colour behind them. */
	.tbf-sheet {
		display: grid;
		grid-template-columns: minmax(0, 1fr);
		gap: 1px;
		border: 2px solid var(--tbf-ink);
		background: var(--tbf-rule);
	}
	.tbf-cell {
		box-sizing: border-box;
		margin: 0;
		min-width: 0;
		background: var(--tbf-paper);
		padding: var(--tbf-pad-y) var(--tbf-pad-x);
	}
	.tbf-label {
		margin: 0;
		font-size: 13.5px;
		font-weight: 600;
		line-height: 20px;
		letter-spacing: 0.005em;
		color: var(--tbf-soft);
	}

	/* Identity */
	.tbf-identity {
		display: flex;
		flex-direction: column;
		justify-content: space-between;
		gap: 18px;
		padding-top: 24px;
		padding-bottom: 26px;
	}
	.tbf-brand {
		display: inline-flex;
		align-items: center;
		align-self: flex-start;
		min-height: 44px;
		max-width: 100%;
	}
	.tbf-wordmark {
		font-family: var(--tbf-font-display);
		font-size: 34px;
		font-weight: 400;
		line-height: 1.04;
		letter-spacing: -0.022em;
		overflow-wrap: anywhere;
		text-wrap: balance;
	}
	.tbf-logo {
		display: block;
		height: 44px;
		width: auto;
		max-width: 260px;
		object-fit: contain;
	}
	.tbf-description {
		margin: 0;
		max-width: 44ch;
		font-size: 16.5px;
		line-height: 1.55;
		color: var(--tbf-soft);
		text-wrap: pretty;
	}

	/* Action column: the inverted call cell, then the one action. */
	.tbf-action {
		display: grid;
		gap: 1px;
		min-width: 0;
	}
	.tbf .tbf-lead {
		display: flex;
		flex-direction: column;
		justify-content: space-between;
		gap: 22px;
		min-height: 128px;
		background: var(--tbf-accent);
		color: var(--tbf-accent-ink);
	}
	.tbf-lead-label {
		font-size: 13.5px;
		font-weight: 600;
		line-height: 20px;
		color: color-mix(in srgb, var(--tbf-accent-ink) 84%, var(--tbf-accent));
	}
	.tbf-number {
		font-family: var(--tbf-font-display);
		font-size: 34px;
		font-weight: 500;
		line-height: 1.05;
		letter-spacing: -0.012em;
		font-variant-numeric: lining-nums tabular-nums;
		overflow-wrap: anywhere;
		text-decoration: underline;
		text-decoration-thickness: 1px;
		text-underline-offset: 0.18em;
		text-decoration-color: transparent;
		transition: text-decoration-color 180ms ease;
	}
	.tbf .tbf-lead:hover .tbf-number {
		text-decoration-color: currentColor;
	}
	/* With no number the action is the inverted field; on its own row it needs less height. */
	.tbf .tbf-lead--action {
		flex-direction: row;
		align-items: flex-end;
		min-height: 96px;
		font-family: var(--tbf-font-display);
		font-size: 26px;
		font-weight: 500;
		line-height: 1.15;
		letter-spacing: -0.014em;
	}
	.tbf-lead-text {
		overflow-wrap: anywhere;
	}
	.tbf-lead--action .tbf-arrow {
		margin-bottom: 4px;
	}
	.tbf .tbf-lead:focus-visible {
		outline: 2px solid var(--tbf-accent-ink);
		outline-offset: -6px;
	}
	/* The second action: the display face and a solid brand mark, so it reads as the next path, not a link. */
	.tbf .tbf-go {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: 16px;
		min-height: 80px;
		padding-block: 16px;
		font-family: var(--tbf-font-display);
		font-size: 23px;
		font-weight: 500;
		line-height: 1.15;
		letter-spacing: -0.012em;
		transition: background-color 160ms ease;
	}
	.tbf-go-text {
		min-width: 0;
		overflow-wrap: anywhere;
		text-decoration: underline;
		text-decoration-thickness: 1px;
		text-underline-offset: 0.2em;
		text-decoration-color: transparent;
		transition: text-decoration-color 180ms ease;
	}
	.tbf-go-mark {
		flex: none;
		display: grid;
		place-items: center;
		width: 48px;
		height: 48px;
		background: var(--tbf-accent);
		color: var(--tbf-accent-ink);
	}
	.tbf .tbf-go:hover {
		background: color-mix(in srgb, var(--tbf-ink) 5%, var(--tbf-paper));
	}
	.tbf .tbf-go:hover .tbf-go-text {
		text-decoration-color: currentColor;
	}
	.tbf .tbf-go:focus-visible {
		outline-offset: -5px;
	}
	.tbf-arrow {
		flex: none;
		transition: transform 180ms ease;
	}
	.tbf .tbf-go:hover .tbf-arrow,
	.tbf .tbf-lead--action:hover .tbf-arrow {
		transform: translateX(3px);
	}

	/* Facts: label/value rows on a phone, equal fields on the spine from tablet up. */
	.tbf-facts {
		display: grid;
		gap: 1px;
		margin: 0;
		min-width: 0;
	}
	.tbf-fact {
		display: grid;
		grid-template-columns: 5.5rem minmax(0, 1fr);
		column-gap: 12px;
		align-items: start;
		padding-block: 14px;
	}
	.tbf-fact dt {
		padding-top: 2px;
	}
	.tbf-fact dd {
		margin: 0;
		font-size: 16.5px;
		font-weight: 500;
		line-height: 1.45;
		overflow-wrap: anywhere;
	}
	.tbf-fact dd span {
		display: block;
		text-wrap: pretty;
	}
	.tbf-fact dd a {
		display: inline-flex;
		flex-direction: column;
		justify-content: center;
		min-height: 44px;
		margin-block: -10px;
		text-decoration: underline;
		text-decoration-thickness: 1px;
		text-underline-offset: 4px;
		text-decoration-color: var(--tbf-rule);
		transition: text-decoration-color 160ms ease;
	}
	.tbf-fact dd a:hover {
		text-decoration-color: currentColor;
	}

	/* Link groups as index rows. */
	.tbf-index {
		display: grid;
		gap: 1px;
		min-width: 0;
	}
	.tbf-row {
		display: grid;
		background: var(--tbf-paper);
		min-width: 0;
	}
	.tbf-row-title {
		padding-bottom: 2px;
	}
	.tbf-row-links {
		display: grid;
		grid-template-columns: repeat(2, minmax(0, 1fr));
		column-gap: 16px;
		list-style: none;
		padding-top: 2px;
		padding-bottom: 10px;
	}
	.tbf-row-links li {
		min-width: 0;
	}
	.tbf-row-links a,
	.tbf-policies a {
		display: inline-flex;
		align-items: center;
		min-height: 44px;
		padding-block: 4px;
		text-decoration: underline;
		text-decoration-thickness: 1px;
		text-underline-offset: 5px;
		text-decoration-color: transparent;
		transition: text-decoration-color 180ms ease;
	}
	.tbf-row-links a {
		box-sizing: border-box;
		align-items: flex-start;
		padding-block: 11px;
		font-size: 16px;
		font-weight: 500;
		line-height: 1.3;
		overflow-wrap: anywhere;
	}
	.tbf-row-links a:hover,
	.tbf-policies a:hover {
		text-decoration-color: currentColor;
	}

	/* Legal */
	.tbf-legal {
		display: grid;
		gap: 1px;
		min-width: 0;
	}
	.tbf-legal > .tbf-cell {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		align-content: center;
		min-height: 60px;
		padding-block: 8px;
	}
	.tbf-rights {
		font-size: 14px;
		line-height: 1.5;
		color: var(--tbf-soft);
	}
	.tbf-policies li {
		display: flex;
	}
	.tbf-policies {
		gap: 0 24px;
		list-style: none;
		font-size: 14px;
		font-weight: 500;
		color: var(--tbf-ink);
	}

	/* Narrow phones: a fact's label sits over its value so an address or email keeps whole words. */
	@container (max-width: 379px) {
		.tbf-fact {
			grid-template-columns: minmax(0, 1fr);
			row-gap: 6px;
		}
		.tbf-fact dd a {
			margin-block: -8px;
		}
	}

	/*
	 * Tablet up: every row divides on one spine of six equal tracks. Each row is full width and uses the
	 * same track template and the same 1px gap, so its divisions fall exactly on the sheet's lines.
	 */
	@container (min-width: 640px) {
		.tbf-inner {
			--tbf-pad-x: 24px;
			--tbf-pad-y: 22px;
			padding: 56px var(--tbf-grid-gutter, 32px) 44px;
		}
		.tbf-sheet,
		.tbf-action--pair,
		.tbf-facts,
		.tbf-legal--split {
			grid-template-columns: repeat(6, minmax(0, 1fr));
		}
		.tbf-sheet > * {
			grid-column: 1 / -1;
		}
		.tbf-wordmark {
			font-size: 44px;
		}
		.tbf-number {
			font-size: 38px;
		}
		/* Call on four tracks, the action on two: its edge is the same line as the Visit/Hours rule. */
		.tbf-action--pair > .tbf-lead {
			grid-column: 1 / 5;
		}
		.tbf-action--pair > .tbf-go {
			grid-column: 5 / 7;
		}
		.tbf-fact {
			grid-column: span 2;
			display: flex;
			flex-direction: column;
			justify-content: flex-start;
			gap: 10px;
			padding-block: var(--tbf-pad-y);
		}
		.tbf-facts[data-count="2"] .tbf-fact {
			grid-column: span 3;
		}
		.tbf-facts[data-count="1"] .tbf-fact {
			grid-column: 1 / -1;
		}
		.tbf-fact dt {
			padding-top: 0;
		}
		/* Index rows: the title over links on three tracks, each two spine tracks wide, so every link starts where a fact value starts. */
		.tbf-row-links {
			grid-template-columns: repeat(3, minmax(0, 1fr));
			column-gap: 1px;
			padding-inline: 0;
		}
		.tbf-row-links a {
			padding-inline: var(--tbf-pad-x) 8px;
		}
		.tbf-legal--split > .tbf-rights {
			grid-column: 1 / 5;
		}
		.tbf-legal--split > .tbf-policies {
			grid-column: 5 / 7;
		}
	}

	/* Desktop: the action column (call over action) takes the last two tracks beside the identity. */
	@container (min-width: 1080px) {
		.tbf-inner {
			--tbf-pad-x: 28px;
			--tbf-pad-y: 26px;
			padding: 88px var(--tbf-grid-gutter, 48px) 64px;
		}
		.tbf-wordmark {
			font-size: clamp(48px, 4.4cqi, 64px);
		}
		.tbf-description {
			font-size: 17px;
		}
		.tbf-identity {
			padding-top: 30px;
			padding-bottom: 30px;
		}
		.tbf-sheet--split > .tbf-identity {
			grid-column: 1 / 5;
			grid-row: 1;
		}
		.tbf-sheet--split > .tbf-action {
			grid-column: 5 / 7;
			grid-row: 1;
			grid-template-columns: minmax(0, 1fr);
			grid-template-rows: minmax(0, 1fr) auto;
		}
		.tbf-sheet--split > .tbf-action > * {
			grid-column: 1;
		}
		.tbf .tbf-lead {
			min-height: 132px;
		}
		.tbf .tbf-sheet--split .tbf-go {
			min-height: 92px;
			font-size: 25px;
		}
		.tbf .tbf-lead--action {
			font-size: 30px;
		}
		.tbf-facts[data-count="1"] .tbf-fact {
			display: grid;
			grid-template-columns: repeat(6, minmax(0, 1fr));
			gap: 1px;
			padding: 0;
			background: var(--tbf-rule);
		}
		.tbf-facts[data-count="1"] .tbf-fact > * {
			display: flex;
			align-items: center;
			align-self: stretch;
			background: var(--tbf-paper);
			padding: 18px var(--tbf-pad-x);
		}
		.tbf-facts[data-count="1"] .tbf-fact dd {
			grid-column: 2 / -1;
			flex-direction: column;
			justify-content: center;
			align-items: flex-start;
			padding-block: 12px;
		}
		.tbf-facts[data-count="1"] .tbf-fact dd a {
			margin-block: 0;
		}
		.tbf-number {
			font-size: clamp(30px, 3cqi, 44px);
			white-space: nowrap;
		}
		/* Index rows: the title in the first track, links on the other five, one link per track. */
		.tbf-row {
			grid-template-columns: repeat(6, minmax(0, 1fr));
			gap: 1px;
			background: var(--tbf-rule);
		}
		.tbf-row-title {
			grid-column: 1;
			padding-block: 18px;
		}
		.tbf-row-links {
			grid-column: 2 / -1;
			grid-template-columns: repeat(5, minmax(0, 1fr));
			padding-block: 6px;
		}
	}

	@media (prefers-reduced-motion: reduce) {
		.tbf-number,
		.tbf-go,
		.tbf-go-text,
		.tbf-arrow,
		.tbf-fact dd a,
		.tbf-row-links a,
		.tbf-policies a {
			transition: none;
		}
		.tbf .tbf-go:hover .tbf-arrow,
		.tbf .tbf-lead--action:hover .tbf-arrow {
			transform: none;
		}
	}
	@media (forced-colors: active) {
		.tbf-sheet {
			border-color: CanvasText;
		}
		.tbf-cell {
			outline: 1px solid CanvasText;
		}
		.tbf-go-mark {
			border: 1px solid CanvasText;
		}
	}
</style>