Component library
Back to catalog

Method sequence

A process section drawn as a descending ledger: every step is a full-width ruled row, and a short ink segment carrying the step — a hanging numeral, its title, detail and any note — advances one position per row, so the method descends diagonally from the heading to one closing action and a call link. Steps only as supplied, in order; numerals are positions, never timings. No cards, dots, icons or photographs. Designed for 3–6 steps.

processhow it worksstepsmethodtypographiceditoriallightlocal service

Open preview in a new tab

Use this component

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

Library page, for reference only:
https://astro.baysixmedia.com/components/method-sequence/

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: "Method sequence"
Library page: https://astro.baysixmedia.com/components/method-sequence/
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/MethodSequence.astro =====
---
/**
 * MethodSequence (candidate, Step 40)
 *
 * A process section drawn as a descending ledger. Every step is one
 * full-width ruled row; on each row a short ink segment carries the step —
 * a hanging numeral, the title, the detail and any note — and the segment
 * advances along the measure from row to row, so the sequence descends
 * diagonally from the heading at top left to the closing action at bottom
 * right. The hairlines keep running the full width, so the empty side of
 * each row reads as ruled ground, not as a gap. No cards, dots, icons,
 * arrows or photographs; the numerals and the advancing segment are the
 * whole device.
 *
 * Hierarchy: section heading > step title > detail > note. The header splits
 * on desktop: eyebrow and heading over the first step, the intro over the
 * last step's column, so header, steps and action share one diagonal.
 *
 * Truth rules:
 * - No default steps, titles, details, notes, timings or claims. Steps render
 *   in the order supplied; a step without a title is dropped; past six the
 *   rest are not shown. No steps, no section (not even the heading).
 * - The numerals are positions in the list (the <ol> carries them for
 *   assistive technology; the drawn numerals are aria-hidden). They never
 *   stand for days, stages or durations.
 * - A note is the business's own remark on that step, shown only when passed.
 * - The action renders only from `cta` with a safe href; the call link only
 *   from a real `phone.number` (tel: built from its digits).
 *
 * Responsive (container queries on the section's own width):
 * - < 640px: no cascade. A ledger margin: numerals in a left gutter beside
 *   one vertical margin line, every step on a full-width rule with an ink tab
 *   over its numeral; header stacked; action and call full width under the
 *   steps' column.
 * - 640–959px: the cascade at 78% step width; header stacked.
 * - ≥ 960px: the cascade at 62 / 60 / 57 / 54% step width for 3 / 4 / 5 / 6
 *   steps (so six steps advance by about one numeral column per row); the
 *   header splits over the first and last columns; site grid padding.
 *
 * Hooks read (never declared): --ms-bg, --ms-text, --ms-text-muted,
 * --ms-brand, --ms-brand-text, --ms-border, --ms-family, --ms-display,
 * --ms-grid-max, --ms-grid-gutter, --ms-heading-size.
 * No script. Motion: only the action's colour eases on hover; none under
 * reduced motion.
 */

export interface MethodSequenceStep {
	/** The step's name, as the business states it. */
	title: string;
	/** What happens in the step, as supplied. */
	detail?: string;
	/** A quiet remark the business adds to this step. */
	note?: string;
}

export interface MethodSequenceProps {
	heading: string;
	headingLevel?: "h2" | "h3";
	eyebrow?: string;
	intro?: string;
	steps?: readonly MethodSequenceStep[];
	/** One closing action after the last step. */
	cta?: { label: string; href: string } | null;
	/** A real number for a call link beside the action. No number, no link. */
	phone?: { number: string; label?: string } | null;
	fontStylesheet?: string;
	class?: string;
	id?: string;
}

interface Props extends MethodSequenceProps {}

const {
	heading,
	headingLevel = "h2",
	eyebrow = "",
	intro = "",
	steps = [],
	cta = null,
	phone = null,
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@300;400;500;600;700&display=swap",
	class: className,
	id,
} = Astro.props;

const MAX_STEPS = 6;
const SAFE = /^(?:https?:|mailto:|tel:|[#/.?])/i;
/* An unsafe or empty link is dropped, never replaced with "#". */
const safeHref = (href: string | undefined) => (href && SAFE.test(href.trim()) ? href.trim() : "");
const text = (v: unknown) => (typeof v === "string" ? v.trim() : "");

const items = (steps ?? [])
	.filter((s) => s && text(s.title))
	.slice(0, MAX_STEPS)
	.map((s, i) => ({ title: text(s.title), detail: text(s.detail), note: text(s.note), position: i + 1 }));
const count = items.length;
const span = Math.max(count - 1, 1);

const headingText = text(heading);
const eyebrowText = text(eyebrow);
const introText = text(intro);
const number = text(phone?.number);
const digits = number.replace(/\D/g, "");
const callHref = digits ? `tel:${number.startsWith("+") ? "+" : ""}${digits}` : "";
const callLabel = text(phone?.label) || "Call";
const ctaLabel = text(cta?.label);
const ctaHref = ctaLabel ? safeHref(cta?.href) : "";
const hasClose = Boolean(ctaHref || callHref);
const Heading = headingLevel;
const StepTitle = headingLevel === "h2" ? "h3" : "h4";
const tier = Math.min(Math.max(count, 3), MAX_STEPS);
---

{count > 0 && (
<>
{fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
<section class:list={["ms", `ms--${tier}`, className]} id={id} style={`--ms-span:${span};`}>
	<div class="ms-inner">
		{(headingText || eyebrowText || introText) && (
			<header class="ms-head">
				<div class="ms-lead">
					{eyebrowText && <p class="ms-eyebrow">{eyebrowText}</p>}
					{headingText && <Heading class="ms-title">{headingText}</Heading>}
				</div>
				{introText && <p class="ms-intro">{introText}</p>}
			</header>
		)}

		<ol class="ms-steps" role="list">
			{items.map((step, i) => (
				<li class="ms-step" style={`--ms-i:${i};`}>
					<span class="ms-num" aria-hidden="true">{step.position}</span>
					<div class="ms-body">
						<StepTitle class="ms-name">{step.title}</StepTitle>
						{step.detail && <p class="ms-detail">{step.detail}</p>}
						{step.note && <p class="ms-note">{step.note}</p>}
					</div>
				</li>
			))}
		</ol>

		{hasClose && (
			<div class="ms-close">
				{ctaHref && (
					<a class="ms-cta" href={ctaHref}>
						{ctaLabel}
					</a>
				)}
				{callHref && (
					<a class="ms-call" href={callHref}>
						<span class="ms-call-label">{callLabel}</span>
						<span class="ms-call-number">{number}</span>
					</a>
				)}
			</div>
		)}
	</div>
</section>
</>
)}

<style>
	.ms {
		--ms-surface: var(--ms-bg, #f4f6f8);
		--ms-ink: var(--ms-text, #172033);
		--ms-soft: var(--ms-text-muted, #4c5767);
		--ms-accent: var(--ms-brand, #2a48b8);
		--ms-accent-ink: var(--ms-brand-text, #ffffff);
		--ms-line: var(--ms-border, #d2d8e1);
		--ms-font: var(--ms-family, "Hanken Grotesk", ui-sans-serif, system-ui, sans-serif);
		--ms-font-display: var(--ms-display, "Hanken Grotesk", ui-sans-serif, system-ui, sans-serif);

		/* Layout tokens (internal): numeral gutter, step width, numeral size. */
		--ms-num: 52px;
		--ms-w: 100%;
		--ms-numeral: 30px;

		container-type: inline-size;
		display: block;
		background: var(--ms-surface);
		color: var(--ms-ink);
		font-family: var(--ms-font);
	}

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

	.ms-inner {
		max-width: var(--ms-grid-max, 1232px);
		margin: 0 auto;
		padding: 64px 20px 72px;
	}

	/* ---- header ---- */

	.ms-head {
		display: grid;
		gap: 20px;
		margin: 0 0 44px;
	}

	.ms-lead,
	.ms-intro {
		min-width: 0;
	}

	.ms-eyebrow {
		margin: 0 0 14px;
		color: var(--ms-accent);
		font: 600 16px/1.4 var(--ms-font);
		overflow-wrap: anywhere;
	}

	.ms-title {
		max-width: 20ch;
		margin: 0;
		font-family: var(--ms-font-display);
		font-size: var(--ms-heading-size, clamp(34px, 2.6vw + 19px, 56px));
		font-weight: 600;
		line-height: 1.04;
		letter-spacing: -0.034em;
		text-wrap: balance;
		overflow-wrap: anywhere;
	}

	.ms-intro {
		max-width: 56ch;
		margin: 0;
		color: var(--ms-soft);
		font: 400 18px/1.6 var(--ms-font);
		text-wrap: pretty;
		overflow-wrap: anywhere;
	}

	/* ---- the ledger ---- */

	.ms-steps {
		position: relative;
		margin: 0;
		padding: 0;
		list-style: none;
		border-bottom: 1px solid var(--ms-line);
	}

	/* Phone: the ledger's margin line, running beside the numerals. */
	.ms-steps::before {
		content: "";
		position: absolute;
		top: 0;
		bottom: 0;
		left: calc(var(--ms-num) - 14px);
		border-left: 1px solid var(--ms-line);
	}

	.ms-step {
		/* How far along the measure this row's segment sits (0 on phones). */
		--ms-off: 0%;
		/* How long the ink segment is: over the numeral on phones, the whole step on the cascade. */
		--ms-seg: calc(var(--ms-num) - 14px);

		position: relative;
		display: grid;
		grid-template-columns: var(--ms-num) minmax(0, 1fr);
		align-items: start;
		padding: 22px 0 34px;
		border-top: 1px solid var(--ms-line);
	}

	/* The ink segment on the row's rule. */
	.ms-step::after {
		content: "";
		position: absolute;
		top: -2px;
		left: var(--ms-off);
		width: var(--ms-seg);
		height: 0;
		border-top: 3px solid var(--ms-ink);
	}

	.ms-num {
		display: block;
		padding-right: 14px;
		color: var(--ms-accent);
		font-family: var(--ms-font-display);
		font-size: var(--ms-numeral);
		font-weight: 300;
		line-height: 0.8;
		letter-spacing: -0.04em;
		font-variant-numeric: lining-nums tabular-nums;
		text-align: left;
	}

	.ms-body {
		min-width: 0;
	}

	.ms-name {
		margin: 0;
		font-family: var(--ms-font-display);
		font-size: clamp(21px, 5.6cqi, 24px);
		font-weight: 600;
		line-height: 1.18;
		letter-spacing: -0.02em;
		text-wrap: balance;
		overflow-wrap: anywhere;
		hyphens: auto;
	}

	.ms-detail {
		max-width: 62ch;
		margin: 10px 0 0;
		color: var(--ms-soft);
		font: 400 16px/1.62 var(--ms-font);
		text-wrap: pretty;
		overflow-wrap: anywhere;
	}

	/* The note: a quiet aside under a short accent stroke. */
	.ms-note {
		max-width: 56ch;
		margin: 16px 0 0;
		color: var(--ms-soft);
		font: 500 15px/1.55 var(--ms-font);
		overflow-wrap: anywhere;
	}

	.ms-note::before {
		content: "";
		display: block;
		width: 20px;
		margin: 0 0 9px;
		border-top: 2px solid var(--ms-accent);
	}

	/* ---- closing action ---- */

	.ms-close {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		gap: 12px 28px;
		margin: 32px 0 0 var(--ms-num);
	}

	.ms-cta {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		min-height: 52px;
		padding: 12px 26px;
		border: 1px solid transparent;
		border-radius: 4px;
		background: var(--ms-accent);
		color: var(--ms-accent-ink);
		font: 600 16px/1.25 var(--ms-font);
		text-align: center;
		text-decoration: none;
		overflow-wrap: anywhere;
		transition: background-color 0.2s ease;
	}

	.ms-cta:hover {
		background: color-mix(in srgb, var(--ms-accent) 84%, var(--ms-ink));
	}

	.ms-call {
		display: inline-flex;
		align-items: baseline;
		gap: 8px;
		min-height: 44px;
		padding: 10px 0;
		color: var(--ms-ink);
		font: 400 16px/1.5 var(--ms-font);
		text-decoration: none;
	}

	.ms-call-label {
		color: var(--ms-soft);
	}

	.ms-call-number {
		font-weight: 600;
		font-variant-numeric: tabular-nums;
		text-decoration: underline;
		text-decoration-thickness: 1px;
		text-underline-offset: 4px;
		text-decoration-color: var(--ms-line);
		transition: text-decoration-color 0.2s ease;
	}

	.ms-call:hover .ms-call-number {
		text-decoration-color: currentColor;
	}

	.ms-cta:focus-visible,
	.ms-call:focus-visible {
		outline: 3px solid var(--ms-accent);
		outline-offset: 3px;
	}

	@media (max-width: 399px) {
		.ms-cta {
			width: 100%;
		}
	}

	/* ---- tablet: the cascade begins ---- */

	@container (min-width: 640px) {
		/* Tokens live on .ms-inner: a container query never styles the container itself. */
		.ms-inner {
			--ms-num: 80px;
			--ms-w: 78%;
			--ms-numeral: 52px;
		}

		.ms-inner {
			padding: 88px 32px 96px;
		}

		.ms-head {
			margin-bottom: 56px;
		}

		.ms-steps::before {
			display: none;
		}

		.ms-step {
			--ms-off: calc((100% - var(--ms-w)) * var(--ms-i) / var(--ms-span));
			--ms-seg: var(--ms-w);

			padding: 26px calc(100% - var(--ms-w) - var(--ms-off)) 44px var(--ms-off);
		}

		.ms-name {
			font-size: 26px;
		}

		.ms-detail {
			font-size: 17px;
		}

		.ms-close {
			margin-left: calc(100% - var(--ms-w) + var(--ms-num));
			margin-top: 36px;
		}
	}

	/* ---- desktop: the full cascade, the header split over it ---- */

	@container (min-width: 960px) {
		.ms-inner {
			--ms-num: 104px;
			--ms-numeral: 72px;
		}

		.ms--3 .ms-inner {
			--ms-w: 62%;
		}

		.ms--4 .ms-inner {
			--ms-w: 60%;
		}

		.ms--5 .ms-inner {
			--ms-w: 57%;
		}

		.ms--6 .ms-inner {
			--ms-w: 54%;
		}

		.ms-inner {
			padding: 120px var(--ms-grid-gutter, 48px) 128px;
		}

		.ms-head {
			grid-template-columns: minmax(0, calc(100% - var(--ms-w) + var(--ms-num))) minmax(0, 1fr);
			align-items: end;
			gap: 0;
			margin-bottom: 72px;
		}

		.ms-lead {
			padding-right: 48px;
		}

		.ms-intro {
			grid-column: 2;
		}

		.ms-step {
			padding-top: 30px;
			padding-bottom: 52px;
		}

		.ms-name {
			font-size: 28px;
		}

		.ms-close {
			margin-top: 40px;
		}
	}

	@media (prefers-reduced-motion: reduce) {
		.ms-cta,
		.ms-call-number {
			transition: none;
		}
	}

	@media (forced-colors: active) {
		.ms-cta {
			border-color: ButtonText;
		}

		.ms-num,
		.ms-eyebrow {
			color: CanvasText;
		}

		.ms-cta:focus-visible,
		.ms-call:focus-visible {
			outline-color: Highlight;
		}
	}
</style>

===== END src/components/MethodSequence.astro =====

===== FILE (supporting): src/components/MethodSequence.md =====
# Method sequence

A process section drawn as a descending ledger: every step is a full-width ruled row, and a short ink segment carrying the step — a hanging numeral, its title, detail and any note — advances one position per row, so the method descends diagonally from the heading to one closing action and a call link. Steps only as supplied, in order; numerals are positions, never timings. No cards, dots, icons or photographs. Designed for 3–6 steps.

## Integration

- A section heading (h2 by default); step titles are one level down.
- No steps, no section. Steps render in the order supplied; a step without a title is dropped; past six the rest are not shown.
- Numerals are drawn aria-hidden; the <ol> carries the positions for assistive technology.
- The action renders only from `cta` with a safe href; the call link only from a real `phone.number` (tel: built from its digits).
- Container queries on the section's own width: ledger margin below 640px, cascade at 78% step width from 640px, count-tuned cascade and split header from 960px.
- Loads Hanken Grotesk from Google Fonts by default. Pass fontStylesheet="" on a site that loads its own faces (via --ms-family / --ms-display).

## Provenance

Staged as candidate `method-sequence` (Step 28) and admitted through the candidate pipeline. Source type: original. References (direction only): https://www.samara.com/adus/how-it-works, https://www.functionhealth.com/how-it-works, https://linear.app/method, https://www.honeybook.com/. Quality history: quality/candidates/records/method-sequence.json.

===== END src/components/MethodSequence.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
“Method sequence” from my Astro Component Library:
https://astro.baysixmedia.com/components/method-sequence/

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/MethodSequence.astro

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

---
/**
 * MethodSequence (candidate, Step 40)
 *
 * A process section drawn as a descending ledger. Every step is one
 * full-width ruled row; on each row a short ink segment carries the step —
 * a hanging numeral, the title, the detail and any note — and the segment
 * advances along the measure from row to row, so the sequence descends
 * diagonally from the heading at top left to the closing action at bottom
 * right. The hairlines keep running the full width, so the empty side of
 * each row reads as ruled ground, not as a gap. No cards, dots, icons,
 * arrows or photographs; the numerals and the advancing segment are the
 * whole device.
 *
 * Hierarchy: section heading > step title > detail > note. The header splits
 * on desktop: eyebrow and heading over the first step, the intro over the
 * last step's column, so header, steps and action share one diagonal.
 *
 * Truth rules:
 * - No default steps, titles, details, notes, timings or claims. Steps render
 *   in the order supplied; a step without a title is dropped; past six the
 *   rest are not shown. No steps, no section (not even the heading).
 * - The numerals are positions in the list (the <ol> carries them for
 *   assistive technology; the drawn numerals are aria-hidden). They never
 *   stand for days, stages or durations.
 * - A note is the business's own remark on that step, shown only when passed.
 * - The action renders only from `cta` with a safe href; the call link only
 *   from a real `phone.number` (tel: built from its digits).
 *
 * Responsive (container queries on the section's own width):
 * - < 640px: no cascade. A ledger margin: numerals in a left gutter beside
 *   one vertical margin line, every step on a full-width rule with an ink tab
 *   over its numeral; header stacked; action and call full width under the
 *   steps' column.
 * - 640–959px: the cascade at 78% step width; header stacked.
 * - ≥ 960px: the cascade at 62 / 60 / 57 / 54% step width for 3 / 4 / 5 / 6
 *   steps (so six steps advance by about one numeral column per row); the
 *   header splits over the first and last columns; site grid padding.
 *
 * Hooks read (never declared): --ms-bg, --ms-text, --ms-text-muted,
 * --ms-brand, --ms-brand-text, --ms-border, --ms-family, --ms-display,
 * --ms-grid-max, --ms-grid-gutter, --ms-heading-size.
 * No script. Motion: only the action's colour eases on hover; none under
 * reduced motion.
 */

export interface MethodSequenceStep {
	/** The step's name, as the business states it. */
	title: string;
	/** What happens in the step, as supplied. */
	detail?: string;
	/** A quiet remark the business adds to this step. */
	note?: string;
}

export interface MethodSequenceProps {
	heading: string;
	headingLevel?: "h2" | "h3";
	eyebrow?: string;
	intro?: string;
	steps?: readonly MethodSequenceStep[];
	/** One closing action after the last step. */
	cta?: { label: string; href: string } | null;
	/** A real number for a call link beside the action. No number, no link. */
	phone?: { number: string; label?: string } | null;
	fontStylesheet?: string;
	class?: string;
	id?: string;
}

interface Props extends MethodSequenceProps {}

const {
	heading,
	headingLevel = "h2",
	eyebrow = "",
	intro = "",
	steps = [],
	cta = null,
	phone = null,
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@300;400;500;600;700&display=swap",
	class: className,
	id,
} = Astro.props;

const MAX_STEPS = 6;
const SAFE = /^(?:https?:|mailto:|tel:|[#/.?])/i;
/* An unsafe or empty link is dropped, never replaced with "#". */
const safeHref = (href: string | undefined) => (href && SAFE.test(href.trim()) ? href.trim() : "");
const text = (v: unknown) => (typeof v === "string" ? v.trim() : "");

const items = (steps ?? [])
	.filter((s) => s && text(s.title))
	.slice(0, MAX_STEPS)
	.map((s, i) => ({ title: text(s.title), detail: text(s.detail), note: text(s.note), position: i + 1 }));
const count = items.length;
const span = Math.max(count - 1, 1);

const headingText = text(heading);
const eyebrowText = text(eyebrow);
const introText = text(intro);
const number = text(phone?.number);
const digits = number.replace(/\D/g, "");
const callHref = digits ? `tel:${number.startsWith("+") ? "+" : ""}${digits}` : "";
const callLabel = text(phone?.label) || "Call";
const ctaLabel = text(cta?.label);
const ctaHref = ctaLabel ? safeHref(cta?.href) : "";
const hasClose = Boolean(ctaHref || callHref);
const Heading = headingLevel;
const StepTitle = headingLevel === "h2" ? "h3" : "h4";
const tier = Math.min(Math.max(count, 3), MAX_STEPS);
---

{count > 0 && (
<>
{fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
<section class:list={["ms", `ms--${tier}`, className]} id={id} style={`--ms-span:${span};`}>
	<div class="ms-inner">
		{(headingText || eyebrowText || introText) && (
			<header class="ms-head">
				<div class="ms-lead">
					{eyebrowText && <p class="ms-eyebrow">{eyebrowText}</p>}
					{headingText && <Heading class="ms-title">{headingText}</Heading>}
				</div>
				{introText && <p class="ms-intro">{introText}</p>}
			</header>
		)}

		<ol class="ms-steps" role="list">
			{items.map((step, i) => (
				<li class="ms-step" style={`--ms-i:${i};`}>
					<span class="ms-num" aria-hidden="true">{step.position}</span>
					<div class="ms-body">
						<StepTitle class="ms-name">{step.title}</StepTitle>
						{step.detail && <p class="ms-detail">{step.detail}</p>}
						{step.note && <p class="ms-note">{step.note}</p>}
					</div>
				</li>
			))}
		</ol>

		{hasClose && (
			<div class="ms-close">
				{ctaHref && (
					<a class="ms-cta" href={ctaHref}>
						{ctaLabel}
					</a>
				)}
				{callHref && (
					<a class="ms-call" href={callHref}>
						<span class="ms-call-label">{callLabel}</span>
						<span class="ms-call-number">{number}</span>
					</a>
				)}
			</div>
		)}
	</div>
</section>
</>
)}

<style>
	.ms {
		--ms-surface: var(--ms-bg, #f4f6f8);
		--ms-ink: var(--ms-text, #172033);
		--ms-soft: var(--ms-text-muted, #4c5767);
		--ms-accent: var(--ms-brand, #2a48b8);
		--ms-accent-ink: var(--ms-brand-text, #ffffff);
		--ms-line: var(--ms-border, #d2d8e1);
		--ms-font: var(--ms-family, "Hanken Grotesk", ui-sans-serif, system-ui, sans-serif);
		--ms-font-display: var(--ms-display, "Hanken Grotesk", ui-sans-serif, system-ui, sans-serif);

		/* Layout tokens (internal): numeral gutter, step width, numeral size. */
		--ms-num: 52px;
		--ms-w: 100%;
		--ms-numeral: 30px;

		container-type: inline-size;
		display: block;
		background: var(--ms-surface);
		color: var(--ms-ink);
		font-family: var(--ms-font);
	}

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

	.ms-inner {
		max-width: var(--ms-grid-max, 1232px);
		margin: 0 auto;
		padding: 64px 20px 72px;
	}

	/* ---- header ---- */

	.ms-head {
		display: grid;
		gap: 20px;
		margin: 0 0 44px;
	}

	.ms-lead,
	.ms-intro {
		min-width: 0;
	}

	.ms-eyebrow {
		margin: 0 0 14px;
		color: var(--ms-accent);
		font: 600 16px/1.4 var(--ms-font);
		overflow-wrap: anywhere;
	}

	.ms-title {
		max-width: 20ch;
		margin: 0;
		font-family: var(--ms-font-display);
		font-size: var(--ms-heading-size, clamp(34px, 2.6vw + 19px, 56px));
		font-weight: 600;
		line-height: 1.04;
		letter-spacing: -0.034em;
		text-wrap: balance;
		overflow-wrap: anywhere;
	}

	.ms-intro {
		max-width: 56ch;
		margin: 0;
		color: var(--ms-soft);
		font: 400 18px/1.6 var(--ms-font);
		text-wrap: pretty;
		overflow-wrap: anywhere;
	}

	/* ---- the ledger ---- */

	.ms-steps {
		position: relative;
		margin: 0;
		padding: 0;
		list-style: none;
		border-bottom: 1px solid var(--ms-line);
	}

	/* Phone: the ledger's margin line, running beside the numerals. */
	.ms-steps::before {
		content: "";
		position: absolute;
		top: 0;
		bottom: 0;
		left: calc(var(--ms-num) - 14px);
		border-left: 1px solid var(--ms-line);
	}

	.ms-step {
		/* How far along the measure this row's segment sits (0 on phones). */
		--ms-off: 0%;
		/* How long the ink segment is: over the numeral on phones, the whole step on the cascade. */
		--ms-seg: calc(var(--ms-num) - 14px);

		position: relative;
		display: grid;
		grid-template-columns: var(--ms-num) minmax(0, 1fr);
		align-items: start;
		padding: 22px 0 34px;
		border-top: 1px solid var(--ms-line);
	}

	/* The ink segment on the row's rule. */
	.ms-step::after {
		content: "";
		position: absolute;
		top: -2px;
		left: var(--ms-off);
		width: var(--ms-seg);
		height: 0;
		border-top: 3px solid var(--ms-ink);
	}

	.ms-num {
		display: block;
		padding-right: 14px;
		color: var(--ms-accent);
		font-family: var(--ms-font-display);
		font-size: var(--ms-numeral);
		font-weight: 300;
		line-height: 0.8;
		letter-spacing: -0.04em;
		font-variant-numeric: lining-nums tabular-nums;
		text-align: left;
	}

	.ms-body {
		min-width: 0;
	}

	.ms-name {
		margin: 0;
		font-family: var(--ms-font-display);
		font-size: clamp(21px, 5.6cqi, 24px);
		font-weight: 600;
		line-height: 1.18;
		letter-spacing: -0.02em;
		text-wrap: balance;
		overflow-wrap: anywhere;
		hyphens: auto;
	}

	.ms-detail {
		max-width: 62ch;
		margin: 10px 0 0;
		color: var(--ms-soft);
		font: 400 16px/1.62 var(--ms-font);
		text-wrap: pretty;
		overflow-wrap: anywhere;
	}

	/* The note: a quiet aside under a short accent stroke. */
	.ms-note {
		max-width: 56ch;
		margin: 16px 0 0;
		color: var(--ms-soft);
		font: 500 15px/1.55 var(--ms-font);
		overflow-wrap: anywhere;
	}

	.ms-note::before {
		content: "";
		display: block;
		width: 20px;
		margin: 0 0 9px;
		border-top: 2px solid var(--ms-accent);
	}

	/* ---- closing action ---- */

	.ms-close {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		gap: 12px 28px;
		margin: 32px 0 0 var(--ms-num);
	}

	.ms-cta {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		min-height: 52px;
		padding: 12px 26px;
		border: 1px solid transparent;
		border-radius: 4px;
		background: var(--ms-accent);
		color: var(--ms-accent-ink);
		font: 600 16px/1.25 var(--ms-font);
		text-align: center;
		text-decoration: none;
		overflow-wrap: anywhere;
		transition: background-color 0.2s ease;
	}

	.ms-cta:hover {
		background: color-mix(in srgb, var(--ms-accent) 84%, var(--ms-ink));
	}

	.ms-call {
		display: inline-flex;
		align-items: baseline;
		gap: 8px;
		min-height: 44px;
		padding: 10px 0;
		color: var(--ms-ink);
		font: 400 16px/1.5 var(--ms-font);
		text-decoration: none;
	}

	.ms-call-label {
		color: var(--ms-soft);
	}

	.ms-call-number {
		font-weight: 600;
		font-variant-numeric: tabular-nums;
		text-decoration: underline;
		text-decoration-thickness: 1px;
		text-underline-offset: 4px;
		text-decoration-color: var(--ms-line);
		transition: text-decoration-color 0.2s ease;
	}

	.ms-call:hover .ms-call-number {
		text-decoration-color: currentColor;
	}

	.ms-cta:focus-visible,
	.ms-call:focus-visible {
		outline: 3px solid var(--ms-accent);
		outline-offset: 3px;
	}

	@media (max-width: 399px) {
		.ms-cta {
			width: 100%;
		}
	}

	/* ---- tablet: the cascade begins ---- */

	@container (min-width: 640px) {
		/* Tokens live on .ms-inner: a container query never styles the container itself. */
		.ms-inner {
			--ms-num: 80px;
			--ms-w: 78%;
			--ms-numeral: 52px;
		}

		.ms-inner {
			padding: 88px 32px 96px;
		}

		.ms-head {
			margin-bottom: 56px;
		}

		.ms-steps::before {
			display: none;
		}

		.ms-step {
			--ms-off: calc((100% - var(--ms-w)) * var(--ms-i) / var(--ms-span));
			--ms-seg: var(--ms-w);

			padding: 26px calc(100% - var(--ms-w) - var(--ms-off)) 44px var(--ms-off);
		}

		.ms-name {
			font-size: 26px;
		}

		.ms-detail {
			font-size: 17px;
		}

		.ms-close {
			margin-left: calc(100% - var(--ms-w) + var(--ms-num));
			margin-top: 36px;
		}
	}

	/* ---- desktop: the full cascade, the header split over it ---- */

	@container (min-width: 960px) {
		.ms-inner {
			--ms-num: 104px;
			--ms-numeral: 72px;
		}

		.ms--3 .ms-inner {
			--ms-w: 62%;
		}

		.ms--4 .ms-inner {
			--ms-w: 60%;
		}

		.ms--5 .ms-inner {
			--ms-w: 57%;
		}

		.ms--6 .ms-inner {
			--ms-w: 54%;
		}

		.ms-inner {
			padding: 120px var(--ms-grid-gutter, 48px) 128px;
		}

		.ms-head {
			grid-template-columns: minmax(0, calc(100% - var(--ms-w) + var(--ms-num))) minmax(0, 1fr);
			align-items: end;
			gap: 0;
			margin-bottom: 72px;
		}

		.ms-lead {
			padding-right: 48px;
		}

		.ms-intro {
			grid-column: 2;
		}

		.ms-step {
			padding-top: 30px;
			padding-bottom: 52px;
		}

		.ms-name {
			font-size: 28px;
		}

		.ms-close {
			margin-top: 40px;
		}
	}

	@media (prefers-reduced-motion: reduce) {
		.ms-cta,
		.ms-call-number {
			transition: none;
		}
	}

	@media (forced-colors: active) {
		.ms-cta {
			border-color: ButtonText;
		}

		.ms-num,
		.ms-eyebrow {
			color: CanvasText;
		}

		.ms-cta:focus-visible,
		.ms-call:focus-visible {
			outline-color: Highlight;
		}
	}
</style>