Component library
Back to catalog

Guided inquiry

A two-stage lead form with a composed support column: the form asks what the visitor needs first (choice cards built on native radios, each with a one-line hint) and how to reach them second, in numbered fieldsets; the direct line sits beside the heading in large type, and a light support column ruled to the form's own lines carries a response promise, a confidence line, a dotted 'what happens next' list and an email alternative. Without a verified endpoint the form renders an honest preview mode; without a form the support column becomes a full-width band.

lead forminquiryquoteconsultationchoice cardsnext stepsclick-to-callpreview modelight

Open preview in a new tab

Use this component

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

Library page, for reference only:
https://astro.baysixmedia.com/components/guided-inquiry/

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: "Guided inquiry"
Library page: https://astro.baysixmedia.com/components/guided-inquiry/
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/GuidedInquiry.astro =====
---
/**
 * GuidedInquiry (candidate, Step 36)
 *
 * A two-stage inquiry set as one ruled sheet. The form is the primary action
 * and asks in order: stage 1 "What do you need?" as choice cards (native
 * radios, each card a label with an optional one-line hint), stage 2 "How to
 * reach you" as the contact fields — two fieldsets, both visible, each led by
 * a display numeral in the form's left margin. Those two numerals are the
 * section's one numbered sequence; the "What happens next" steps run on a
 * dotted rail.
 *
 * Beside the form, a light, rule-based support column on the section's own
 * ground (no slab): the direct line set large beside the heading, then ruled
 * bands that meet the form's own rules — the response promise and confidence
 * line beside stage one, the next steps beside stage two, the email
 * alternative beside the send row. Every rule in the column sits on the same
 * line as a rule of the form, so the column is balanced whatever the form's
 * height.
 *
 * Truth rules:
 * - The section renders only with a heading. No business fact has a default:
 *   the phone renders only from a real number (tel: from its digits); hours,
 *   the response promise, the confidence line, email, steps and choices only
 *   when passed. Choices are the supplied options only (2–6; fewer than two
 *   and the choice stage is left out). Steps: at most 4.
 * - The form renders only with a verified `action` (a normal POST form) or a
 *   non-empty `previewNotice` (PREVIEW MODE: no action attribute, the send
 *   disabled and aria-disabled, the notice as role="note"). With neither,
 *   there is no form: the direct line stays beside the heading and the next
 *   steps take the main column — the phone alone still reads as finished.
 *
 * Responsive (container queries on the section's own width):
 * - < 600px: heading, direct line, promise, the form (one column, cards
 *   stacked, the numeral beside each question), next steps, email.
 * - 600–959px: direct line | promise side by side; the numerals move into the
 *   form's margin; cards and fields pair; the steps run as a horizontal rail.
 * - ≥ 960px: form + 344px ruled column as above (subgrid rows).
 *
 * No script: validation is the browser's own; a disabled send blocks
 * submission. Reduced motion removes every transition.
 *
 * Hooks read (never declared): --gin-bg, --gin-card-bg, --gin-text,
 * --gin-text-muted, --gin-brand, --gin-brand-text, --gin-border,
 * --gin-family, --gin-display, --gin-grid-max, --gin-grid-gutter,
 * --gin-heading-size.
 */

export interface GuidedInquiryChoice {
	/** The option, as shown. */
	label: string;
	/** One optional line under it. */
	hint?: string;
	/** Submitted value; the label when absent. */
	value?: string;
}

export interface GuidedInquiryField {
	name: string;
	label: string;
	type: "text" | "email" | "tel" | "textarea" | "select";
	required?: boolean;
	options?: string[];
	placeholder?: string;
	autocomplete?: string;
}

export interface GuidedInquiryStep {
	title: string;
	detail?: string;
}

export interface GuidedInquiryProps {
	heading: string;
	headingLevel?: "h2" | "h3";
	eyebrow?: string;
	intro?: string;
	/** The direct line. Renders only from a real number. */
	phone?: { number: string; label?: string } | null;
	/** Hours lines, as supplied. */
	hours?: string | string[];
	/** The response promise, only when the business states one. */
	responsePromise?: string;
	/** A short confidence line from verified facts. */
	assurance?: string;
	email?: string;
	/** "What happens next": 2–4 ordered steps. */
	steps?: GuidedInquiryStep[];
	stepsHeading?: string;
	/** Stage 1 options, supplied only (2–6). */
	choices?: GuidedInquiryChoice[];
	choiceLegend?: string;
	choiceName?: string;
	choiceRequired?: boolean;
	contactLegend?: string;
	/** Stage 2 fields. Default: name, phone, email, message. */
	fields?: GuidedInquiryField[];
	submitLabel?: string;
	/** The verified form endpoint. Absent: preview mode (with previewNotice) or no form. */
	action?: string;
	previewNotice?: string;
	labels?: {
		directLine?: string;
		hours?: string;
		email?: string;
		notConnected?: string;
		optional?: string;
		choose?: string;
	};
	fontStylesheet?: string;
	class?: string;
	id?: string;
}

interface Props extends GuidedInquiryProps {}

const {
	heading,
	headingLevel = "h2",
	eyebrow = "",
	intro = "",
	phone = null,
	hours = [],
	responsePromise = "",
	assurance = "",
	email = "",
	steps = [],
	stepsHeading = "What happens next",
	choices = [],
	choiceLegend = "What do you need?",
	choiceName = "inquiry",
	choiceRequired = true,
	contactLegend = "How to reach you",
	fields: fieldsProp,
	submitLabel = "Send request",
	action: actionProp,
	previewNotice = "",
	labels = {},
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Schibsted+Grotesk:wght@400;500;600;700&display=swap",
	class: className,
	id,
} = Astro.props as Props;

const L = {
	directLine: "Direct line",
	hours: "Lines open",
	email: "Prefer to write?",
	notConnected: "Not connected",
	optional: "Optional",
	choose: "Choose one",
	...labels,
};

const str = (v: unknown) => (typeof v === "string" ? v.trim() : "");
const SAFE = /^(?:https?:|mailto:|tel:|[#/.?])/i;
const safeHref = (href: string) => (SAFE.test(href.trim()) ? href.trim() : "");
const telHref = (n: string) => {
	const digits = n.replace(/\D/g, "");
	return digits.length >= 3 ? `tel:${n.trim().startsWith("+") ? "+" : ""}${digits}` : "";
};

const number = str(phone?.number);
const callHref = number ? telHref(number) : "";
const mail = str(email);
const mailHref = mail && /^[^\s@]+@[^\s@]+$/.test(mail) ? safeHref(`mailto:${mail}`) : "";
const hourLines = (Array.isArray(hours) ? hours : [hours]).map(str).filter(Boolean);
const promise = str(responsePromise);
const confidence = str(assurance);
const stepList = (Array.isArray(steps) ? steps : [])
	.map((s) => ({ title: str(s?.title), detail: str(s?.detail) }))
	.filter((s) => s.title)
	.slice(0, 4);
const choiceList = (Array.isArray(choices) ? choices : [])
	.map((c) => ({ label: str(c?.label), hint: str(c?.hint), value: str(c?.value) || str(c?.label) }))
	.filter((c) => c.label)
	.slice(0, 6);
const hasChoices = choiceList.length >= 2;

/* Only an http(s) or relative endpoint; anything else is treated as absent. */
const rawAction = str(actionProp);
const action = rawAction && /^(?:https?:|[/.?])/i.test(rawAction) ? rawAction : "";
const preview = !action && Boolean(str(previewNotice));
const showForm = Boolean(action) || preview;

const DEFAULT_FIELDS: GuidedInquiryField[] = [
	{ name: "name", label: "Full name", type: "text", required: true, autocomplete: "name" },
	{ name: "phone", label: "Phone", type: "tel", required: true, autocomplete: "tel" },
	{ name: "email", label: "Email", type: "email", autocomplete: "email" },
	{ name: "message", label: "Anything we should know?", type: "textarea" },
];
const fieldList: GuidedInquiryField[] = (fieldsProp && fieldsProp.length ? fieldsProp : DEFAULT_FIELDS)
	.filter((f) => f && str(f.name) && str(f.label))
	.map((f) => ({ ...f, name: str(f.name), label: str(f.label), options: (f.options ?? []).map(str).filter(Boolean) }))
	.filter((f) => f.type !== "select" || (f.options?.length ?? 0) > 0);

const uid = `${(id ?? "gin").replace(/[^\w-]/g, "") || "gin"}-${Math.random().toString(36).slice(2, 7)}`;

/* Textareas take the row; other fields pair; an unpaired field takes the row. */
const laid = fieldList.map((f, i) => ({ ...f, id: `${uid}-f${i}`, wide: f.type === "textarea" }));
for (let i = 0; i < laid.length; ) {
	if (laid[i].wide) {
		i += 1;
		continue;
	}
	if (i + 1 < laid.length && !laid[i + 1].wide) {
		i += 2;
		continue;
	}
	laid[i].wide = true;
	i += 1;
}

const Heading = headingLevel;
const SubHeading = headingLevel === "h2" ? "h3" : "h4";
const headingId = `${uid}-h`;
const noticeId = `${uid}-note`;
const hasReach = Boolean(callHref || hourLines.length);
const hasPromise = Boolean(promise || confidence);
const hasNext = stepList.length >= 2;
const hasFoot = Boolean(mailHref);
const hasSupport = hasReach || hasPromise || hasNext || hasFoot;
const title = str(heading);
---

{fontStylesheet && title && <link rel="stylesheet" href={fontStylesheet} />}
{
	title && (
		<section
			class:list={[
				"gin",
				{
					"gin--split": showForm && hasSupport,
					"gin--noform": !showForm,
					"gin--solo": showForm && !hasSupport,
					"gin--staged": hasChoices,
					"gin--reach": hasReach,
					"gin--promise": hasPromise,
					"gin--next": hasNext,
					"gin--foot": hasFoot,
					"gin--preview": preview,
				},
				className,
			]}
			id={id}
		>
			<div class="gin-inner">
				<div class="gin-body">
					<header class="gin-lead">
						{eyebrow && <p class="gin-eyebrow">{eyebrow}</p>}
						<Heading class="gin-title" id={headingId}>
							{title}
						</Heading>
						{intro && <p class="gin-intro">{intro}</p>}
					</header>

					{showForm && (
						<form class="gin-form" method="post" action={action || undefined} aria-labelledby={headingId}>
							{hasChoices && (
								<fieldset class="gin-stage gin-stage--intent">
									<legend class="gin-legend">
										<span class="gin-numeral" aria-hidden="true">1</span>
										<span class="gin-legend-text">{choiceLegend}</span>
									</legend>
									<div class:list={["gin-choices", { "gin-choices--odd": choiceList.length % 2 === 1, "gin-choices--three": choiceList.length === 3 || choiceList.length === 6 }]}>
										{choiceList.map((c, i) => (
											<label class="gin-choice" for={`${uid}-c${i}`}>
												<input class="gin-radio" type="radio" id={`${uid}-c${i}`} name={choiceName} value={c.value} required={choiceRequired || undefined} />
												<span class="gin-choice-text">
													<span class="gin-choice-label">{c.label}</span>
													{c.hint && <span class="gin-choice-hint">{c.hint}</span>}
												</span>
											</label>
										))}
									</div>
								</fieldset>
							)}

							<fieldset class="gin-stage gin-stage--contact">
								<legend class="gin-legend">
									{hasChoices && (
										<span class="gin-numeral" aria-hidden="true">
											2
										</span>
									)}
									<span class="gin-legend-text">{contactLegend}</span>
								</legend>
								<div class="gin-fields">
									{laid.map((f) => (
										<div class:list={["gin-field", { "gin-field--wide": f.wide }]}>
											<label for={f.id}>
												{f.label}
												{!f.required && <span class="gin-opt">{L.optional}</span>}
											</label>
											{f.type === "textarea" ? (
												<textarea id={f.id} name={f.name} rows="4" required={f.required} placeholder={f.placeholder || undefined} autocomplete={f.autocomplete || undefined} />
											) : f.type === "select" ? (
												<select id={f.id} name={f.name} required={f.required} autocomplete={f.autocomplete || undefined}>
													<option value="">{L.choose}</option>
													{f.options?.map((o) => (
														<option value={o}>{o}</option>
													))}
												</select>
											) : (
												<input id={f.id} name={f.name} type={f.type} required={f.required} placeholder={f.placeholder || undefined} autocomplete={f.autocomplete || undefined} />
											)}
										</div>
									))}
								</div>
							</fieldset>

							<div class="gin-send">
								{preview && (
									<p class="gin-notice" role="note" id={noticeId}>
										<span class="gin-tag">
											<span class="gin-ring" aria-hidden="true" />
											{L.notConnected}
										</span>
										<span class="gin-notice-text">{str(previewNotice)}</span>
									</p>
								)}
								{preview ? (
									<button class="gin-submit gin-submit--inert" type="submit" disabled aria-disabled="true" aria-describedby={noticeId}>
										<span>{submitLabel}</span>
										<span class="gin-lock" aria-hidden="true" />
									</button>
								) : (
									<button class="gin-submit" type="submit">
										<span>{submitLabel}</span>
										<span class="gin-arrow" aria-hidden="true" />
									</button>
								)}
							</div>
						</form>
					)}

					{hasSupport && (
						<div class="gin-support">
							{hasReach && (
								<div class="gin-reach">
									{callHref && (
										<>
											<p class="gin-kicker">{phone?.label?.trim() || L.directLine}</p>
											<a class="gin-number" href={callHref}>
												{number}
											</a>
										</>
									)}
									{hourLines.length > 0 && (
										<p class="gin-hours">
											<span class="gin-hours-label">{L.hours}</span>
											{hourLines.map((h) => (
												<span class="gin-hours-line">{h}</span>
											))}
										</p>
									)}
								</div>
							)}
							{hasPromise && (
								<div class="gin-band gin-promise">
									{promise && <p class="gin-promise-text">{promise}</p>}
									{confidence && <p class="gin-assurance">{confidence}</p>}
								</div>
							)}
							{hasNext && (
								<div class="gin-band gin-next">
									<SubHeading class="gin-next-title">{stepsHeading}</SubHeading>
									<ol class:list={["gin-steps", `gin-steps--${stepList.length}`]}>
										{stepList.map((s) => (
											<li class="gin-step">
												<span class="gin-dot" aria-hidden="true" />
												<span class="gin-step-title">{s.title}</span>
												{s.detail && <span class="gin-step-detail">{s.detail}</span>}
											</li>
										))}
									</ol>
								</div>
							)}
							{hasFoot && (
								<div class="gin-band gin-foot">
									<p class="gin-mail">
										<span class="gin-kicker">{L.email}</span>
										<a href={mailHref}>{mail}</a>
									</p>
								</div>
							)}
						</div>
					)}
				</div>
			</div>
		</section>
	)
}

<style>
	.gin {
		--gin-surface: var(--gin-bg, #f2f2ef);
		--gin-paper: var(--gin-card-bg, #ffffff);
		--gin-ink: var(--gin-text, #111418);
		--gin-soft: var(--gin-text-muted, #474e55);
		--gin-accent: var(--gin-brand, #1f3a93);
		--gin-accent-ink: var(--gin-brand-text, #ffffff);
		--gin-line: var(--gin-border, #d6d6d0);
		--gin-font: var(--gin-family, "Schibsted Grotesk", ui-sans-serif, system-ui, sans-serif);
		--gin-font-display: var(--gin-display, "Schibsted Grotesk", ui-sans-serif, system-ui, sans-serif);
		/* The column's rules are ink, the form's hairlines are the border hook: the column reads as the ledger. */
		--gin-rule: color-mix(in srgb, var(--gin-ink) 82%, var(--gin-surface));
		--gin-tint: color-mix(in srgb, var(--gin-accent) 7%, var(--gin-paper));
		--gin-edge: color-mix(in srgb, var(--gin-ink) 24%, var(--gin-paper));
		--gin-rail: 344px;

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

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

	.gin-inner {
		max-width: var(--gin-grid-max, 1232px);
		margin: 0 auto;
		padding: 56px 16px;
	}

	/* Phones: heading, direct line, promise, the form, next steps, email. */
	.gin-body {
		display: grid;
		grid-template-columns: minmax(0, 1fr);
		grid-template-areas:
			"lead"
			"reach"
			"promise"
			"form"
			"next"
			"foot";
	}

	.gin-support {
		display: contents;
	}

	.gin-lead {
		grid-area: lead;
		min-width: 0;
		padding-bottom: 32px;
	}

	.gin-reach {
		grid-area: reach;
	}

	.gin-promise {
		grid-area: promise;
	}

	.gin-form {
		grid-area: form;
	}

	.gin-next {
		grid-area: next;
	}

	.gin-foot {
		grid-area: foot;
	}

	/* ---- lead ---- */

	.gin-eyebrow {
		margin: 0 0 14px;
		color: var(--gin-accent);
		font: 600 15px/1.4 var(--gin-font);
	}

	.gin-title {
		max-width: 18ch;
		margin: 0;
		font-family: var(--gin-font-display);
		font-size: var(--gin-heading-size, clamp(34px, 4.6vw, 58px));
		font-weight: 600;
		line-height: 1.02;
		letter-spacing: -0.03em;
		text-wrap: balance;
		overflow-wrap: anywhere;
	}

	.gin-intro {
		max-width: 48ch;
		margin: 20px 0 0;
		color: var(--gin-soft);
		font: 400 17px/1.6 var(--gin-font);
		overflow-wrap: anywhere;
	}

	/* ---- the support column: light, on the section's ground, divided by ink rules ---- */

	.gin-reach,
	.gin-band {
		min-width: 0;
	}

	.gin-reach {
		display: grid;
		gap: 6px;
		align-content: end;
		padding: 0 0 28px;
	}

	.gin-band {
		display: grid;
		gap: 12px;
		align-content: start;
		padding: 24px 0 28px;
		border-top: 1px solid var(--gin-rule);
	}

	.gin-kicker {
		margin: 0;
		color: var(--gin-soft);
		font: 500 14.5px/1.4 var(--gin-font);
	}

	/* The number: display type, underlined in the accent — the link is the number itself. */
	.gin-number {
		justify-self: start;
		display: inline-block;
		min-height: 48px;
		padding: 4px 0 8px;
		color: var(--gin-ink);
		font-family: var(--gin-font-display);
		font-size: clamp(30px, 9.5cqi, 42px);
		font-weight: 600;
		line-height: 1.1;
		letter-spacing: -0.03em;
		font-variant-numeric: lining-nums tabular-nums;
		/* A number fits on one line; longer link text wraps rather than pushing the column wide. */
		max-width: 100%;
		overflow-wrap: anywhere;
		text-decoration: underline;
		text-decoration-color: var(--gin-accent);
		text-decoration-thickness: 2px;
		text-underline-offset: 8px;
		transition: text-decoration-thickness 0.15s ease;
	}

	.gin-number:hover {
		text-decoration-thickness: 4px;
	}

	.gin-hours {
		display: flex;
		flex-wrap: wrap;
		gap: 2px 14px;
		margin: 10px 0 0;
		font: 500 15px/1.5 var(--gin-font);
	}

	.gin-hours-label {
		flex-basis: 100%;
		color: var(--gin-soft);
		font-weight: 400;
		font-size: 14.5px;
	}

	.gin-hours-line {
		white-space: nowrap;
	}

	.gin-promise-text {
		max-width: 24ch;
		margin: 0;
		font-family: var(--gin-font-display);
		font-size: 23px;
		font-weight: 600;
		line-height: 1.25;
		letter-spacing: -0.018em;
		text-wrap: pretty;
		overflow-wrap: anywhere;
	}

	.gin-assurance {
		max-width: 40ch;
		margin: 0;
		color: var(--gin-soft);
		font: 400 15.5px/1.55 var(--gin-font);
		overflow-wrap: anywhere;
	}

	.gin-mail {
		display: grid;
		gap: 2px;
		margin: 0;
	}

	.gin-mail a {
		justify-self: start;
		display: inline-block;
		padding: 12px 0;
		margin: -12px 0;
		color: var(--gin-ink);
		font: 600 16.5px/1.4 var(--gin-font);
		text-decoration: underline;
		text-decoration-color: var(--gin-accent);
		text-decoration-thickness: 1.5px;
		text-underline-offset: 5px;
		overflow-wrap: anywhere;
	}

	.gin-mail a:hover {
		text-decoration-thickness: 3px;
	}

	.gin-number:focus-visible,
	.gin-mail a:focus-visible {
		outline: 3px solid var(--gin-accent);
		outline-offset: 4px;
		border-radius: 2px;
	}

	/* ---- next steps: a dotted rail (the numerals belong to the form's stages) ---- */

	.gin-next-title {
		margin: 0 0 6px;
		color: var(--gin-ink);
		font: 600 16px/1.4 var(--gin-font);
		letter-spacing: 0;
	}

	.gin-steps {
		display: grid;
		margin: 0;
		padding: 0;
		list-style: none;
	}

	.gin-step {
		position: relative;
		display: grid;
		grid-template-columns: 12px minmax(0, 1fr);
		column-gap: 18px;
		row-gap: 2px;
		align-content: start;
		min-width: 0;
		padding-bottom: 20px;
	}

	.gin-step:last-child {
		padding-bottom: 0;
	}

	.gin-step:not(:last-child)::after {
		content: "";
		position: absolute;
		left: 5.5px;
		top: 18px;
		bottom: -2px;
		width: 1px;
		background: var(--gin-edge);
	}

	.gin-dot {
		grid-row: 1 / span 2;
		width: 12px;
		height: 12px;
		margin-top: 5px;
		border: 2px solid var(--gin-accent);
		border-radius: 50%;
		background: var(--gin-surface);
	}

	.gin-step:first-child .gin-dot {
		background: var(--gin-accent);
	}

	.gin-step-title {
		font: 600 16px/1.4 var(--gin-font);
		overflow-wrap: anywhere;
	}

	.gin-step-detail {
		grid-column: 2;
		color: var(--gin-soft);
		font: 400 15px/1.5 var(--gin-font);
		overflow-wrap: anywhere;
	}

	/* ---- the form: two numbered stages ---- */

	.gin-form {
		display: grid;
		align-content: start;
		min-width: 0;
		margin: 8px 0 28px;
		border: 1px solid var(--gin-line);
		background: var(--gin-paper);
	}

	.gin-stage {
		position: relative;
		min-width: 0;
		margin: 0;
		padding: 28px 20px 30px;
		border: 0;
	}

	.gin-stage + .gin-stage {
		border-top: 1px solid var(--gin-line);
	}

	.gin-legend {
		float: left;
		display: flex;
		align-items: baseline;
		gap: 14px;
		width: 100%;
		margin: 0 0 20px;
		padding: 0;
		font: 600 20px/1.3 var(--gin-font);
		letter-spacing: -0.012em;
	}

	.gin-legend + * {
		clear: both;
	}

	/* The stage numeral: display type in the accent — the section's one numbered sequence. */
	.gin-numeral {
		flex: none;
		color: var(--gin-accent);
		font-family: var(--gin-font-display);
		font-size: 30px;
		font-weight: 600;
		line-height: 1;
		letter-spacing: -0.03em;
		font-variant-numeric: lining-nums tabular-nums;
	}

	.gin-legend-text {
		min-width: 0;
		overflow-wrap: anywhere;
	}

	/* Choice cards: native radios, the whole card is the label. */
	.gin-choices {
		display: grid;
		grid-template-columns: minmax(0, 1fr);
		gap: 10px;
	}

	.gin-choice {
		display: grid;
		grid-template-columns: 24px minmax(0, 1fr);
		align-items: start;
		gap: 14px;
		min-width: 0;
		min-height: 64px;
		padding: 16px 18px;
		border: 1px solid var(--gin-edge);
		border-radius: 4px;
		background: var(--gin-paper);
		cursor: pointer;
		transition: border-color 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease;
	}

	.gin-radio {
		/* 24px: the whole card is the label, but the control itself also meets the touch-target floor. */
		width: 24px;
		height: 24px;
		margin: 0;
		flex: none;
		accent-color: var(--gin-accent);
		cursor: pointer;
	}

	.gin-choice-text {
		display: grid;
		gap: 3px;
		min-width: 0;
	}

	.gin-choice-label {
		font: 600 16.5px/1.35 var(--gin-font);
		overflow-wrap: anywhere;
	}

	.gin-choice-hint {
		color: var(--gin-soft);
		font: 400 15px/1.45 var(--gin-font);
		overflow-wrap: anywhere;
	}

	.gin-choice:hover {
		border-color: color-mix(in srgb, var(--gin-ink) 60%, var(--gin-paper));
	}

	.gin-choice:has(.gin-radio:checked) {
		border-color: var(--gin-accent);
		background: var(--gin-tint);
		box-shadow: inset 0 0 0 1px var(--gin-accent);
	}

	.gin-choice:has(.gin-radio:focus-visible) {
		outline: 3px solid var(--gin-accent);
		outline-offset: 2px;
	}

	.gin-radio:focus-visible {
		outline: 2px solid var(--gin-accent);
		outline-offset: 2px;
	}

	/* Fields */
	.gin-fields {
		display: grid;
		grid-template-columns: minmax(0, 1fr);
		gap: 18px 20px;
	}

	.gin-field {
		display: grid;
		gap: 7px;
		min-width: 0;
		align-content: start;
	}

	.gin-field label {
		display: flex;
		flex-wrap: wrap;
		align-items: baseline;
		gap: 4px 10px;
		font: 600 15px/1.35 var(--gin-font);
	}

	.gin-opt {
		color: var(--gin-soft);
		font: 500 14px/1.35 var(--gin-font);
	}

	.gin-field input,
	.gin-field select,
	.gin-field textarea {
		width: 100%;
		min-height: 50px;
		margin: 0;
		padding: 12px 14px;
		border: 1px solid var(--gin-edge);
		border-radius: 4px;
		background: var(--gin-paper);
		color: var(--gin-ink);
		font: 400 16px/1.4 var(--gin-font);
		transition: border-color 0.15s ease, box-shadow 0.15s ease;
	}

	.gin-field textarea {
		min-height: 112px;
		resize: vertical;
	}

	.gin-field select {
		appearance: none;
		padding-right: 44px;
		background-image: linear-gradient(45deg, transparent 50%, var(--gin-soft) 50%), linear-gradient(135deg, var(--gin-soft) 50%, transparent 50%);
		background-position: calc(100% - 22px) 50%, calc(100% - 16px) 50%;
		background-size: 6px 6px;
		background-repeat: no-repeat;
		cursor: pointer;
	}

	.gin-field input::placeholder,
	.gin-field textarea::placeholder {
		color: var(--gin-soft);
	}

	.gin-field input:hover,
	.gin-field select:hover,
	.gin-field textarea:hover {
		border-color: color-mix(in srgb, var(--gin-ink) 60%, var(--gin-paper));
	}

	.gin-field input:focus-visible,
	.gin-field select:focus-visible,
	.gin-field textarea:focus-visible {
		outline: 2px solid transparent;
		border-color: var(--gin-accent);
		box-shadow: 0 0 0 3px color-mix(in srgb, var(--gin-accent) 26%, transparent);
	}

	/* Send row */
	.gin-send {
		display: grid;
		gap: 14px;
		align-items: center;
		min-width: 0;
		padding: 22px 20px 26px;
		border-top: 1px solid var(--gin-line);
	}

	/* Preview mode: the same white row, an accent status line and a locked ink control. */
	.gin-notice {
		display: grid;
		gap: 4px;
		margin: 0;
		color: var(--gin-ink);
		font: 400 15px/1.5 var(--gin-font);
		overflow-wrap: anywhere;
	}

	.gin-tag {
		display: inline-flex;
		align-items: center;
		gap: 9px;
		color: var(--gin-accent);
		font: 600 14.5px/1.3 var(--gin-font);
	}

	.gin-ring {
		flex: none;
		width: 11px;
		height: 11px;
		border: 2px solid currentColor;
		border-radius: 50%;
	}

	.gin-notice-text {
		max-width: 46ch;
	}

	.gin-submit {
		display: inline-flex;
		align-items: center;
		justify-content: space-between;
		gap: 14px;
		width: 100%;
		min-height: 56px;
		padding: 15px 22px;
		border: 1.5px solid var(--gin-accent);
		border-radius: 4px;
		background: var(--gin-accent);
		color: var(--gin-accent-ink);
		font: 600 17px/1.2 var(--gin-font);
		text-align: left;
		cursor: pointer;
		transition: background-color 0.15s ease, border-color 0.15s ease;
	}

	.gin-submit:not([disabled]):hover {
		border-color: color-mix(in srgb, var(--gin-accent) 80%, var(--gin-ink));
		background: color-mix(in srgb, var(--gin-accent) 80%, var(--gin-ink));
	}

	.gin-submit:not([disabled]):hover .gin-arrow {
		transform: translateX(4px);
	}

	.gin-submit:focus-visible {
		outline: 3px solid var(--gin-accent);
		outline-offset: 3px;
	}

	/*
	 * Locked, not broken: a solid ink control at full size on the white row, white label, a drawn
	 * padlock where the arrow would be — confident, and plainly not the live accent button.
	 */
	.gin-submit--inert {
		border-color: var(--gin-ink);
		background: var(--gin-ink);
		color: var(--gin-paper);
		cursor: not-allowed;
	}

	.gin-lock {
		position: relative;
		flex: none;
		width: 13px;
		height: 10px;
		margin: 6px 3px 0 0;
		border-radius: 2px;
		background: currentColor;
	}

	.gin-lock::before {
		content: "";
		position: absolute;
		left: 2.5px;
		bottom: 9px;
		width: 8px;
		height: 7px;
		border: 1.75px solid currentColor;
		border-bottom: 0;
		border-radius: 5px 5px 0 0;
	}

	/* A drawn arrow: a shaft and a head. */
	.gin-arrow {
		position: relative;
		flex: none;
		width: 18px;
		height: 1.5px;
		margin-right: 3px;
		background: currentColor;
		transition: transform 0.2s ease;
	}

	.gin-arrow::after {
		content: "";
		position: absolute;
		right: 0;
		top: -3.75px;
		width: 8px;
		height: 8px;
		border: solid currentColor;
		border-width: 1.5px 1.5px 0 0;
		transform: rotate(45deg);
	}

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

	@container (min-width: 600px) {
		.gin-inner {
			padding: 80px 32px;
		}

		.gin-lead {
			padding-bottom: 40px;
		}

		/* Direct line | promise, side by side above the form. */
		.gin-body {
			grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
			column-gap: 40px;
			grid-template-areas:
				"lead lead"
				"reach promise"
				"form form"
				"next next"
				"foot foot";
		}

		.gin:not(.gin--promise) .gin-reach,
		.gin:not(.gin--reach) .gin-promise {
			grid-column: 1 / -1;
		}

		.gin--reach .gin-promise {
			align-self: end;
			padding-bottom: 28px;
		}

		.gin-reach + .gin-promise {
			border-top: 0;
			padding-top: 0;
		}

		.gin-form {
			margin: 12px 0 36px;
		}

		/* The numerals move into the form's left margin. */
		.gin--staged .gin-stage {
			padding: 34px 36px 36px 88px;
		}

		.gin--staged .gin-numeral {
			position: absolute;
			left: 36px;
			top: 32px;
			font-size: 40px;
		}

		.gin-stage {
			padding: 34px 36px;
		}

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

		.gin-choices--odd .gin-choice:last-child {
			grid-column: 1 / -1;
		}

		.gin-fields {
			grid-template-columns: repeat(2, minmax(0, 1fr));
			gap: 22px 20px;
		}

		.gin-field--wide {
			grid-column: 1 / -1;
		}

		.gin-send {
			grid-template-columns: minmax(0, 1fr) minmax(220px, 280px);
			gap: 16px 28px;
			padding: 24px 36px;
		}

		.gin--staged .gin-send {
			padding-left: 88px;
		}

		.gin-send > .gin-submit:only-child {
			grid-column: 2;
		}

		/* The steps run as a horizontal rail. */
		.gin-steps {
			grid-template-columns: repeat(var(--gin-n, 3), minmax(0, 1fr));
			column-gap: 28px;
			margin-top: 10px;
		}

		.gin-steps--2 {
			--gin-n: 2;
		}

		.gin-steps--3 {
			--gin-n: 3;
		}

		.gin-steps--4 {
			--gin-n: 4;
		}

		.gin-step {
			grid-template-columns: minmax(0, 1fr);
			row-gap: 4px;
			padding-bottom: 0;
		}

		.gin-dot {
			grid-row: auto;
			margin: 0 0 12px;
		}

		.gin-step-detail {
			grid-column: 1;
		}

		.gin-step:not(:last-child)::after {
			left: 20px;
			right: -8px;
			top: 5.5px;
			bottom: auto;
			width: auto;
			height: 1px;
		}
	}

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

	@container (min-width: 960px) {
		.gin-inner {
			padding-block: 112px;
			padding-inline: var(--gin-grid-gutter, 48px);
		}

		.gin-stage,
		.gin--staged .gin-stage {
			padding: 44px 48px 42px;
		}

		.gin--staged .gin-stage {
			padding-left: 104px;
		}

		.gin--staged .gin-numeral {
			left: 48px;
			top: 40px;
			font-size: 44px;
		}

		.gin-legend {
			margin-bottom: 24px;
			font-size: 21px;
		}

		.gin-send {
			grid-template-columns: minmax(0, 1fr) 280px;
			gap: 16px 32px;
			padding: 26px 48px;
		}

		.gin--staged .gin-send {
			padding-left: 104px;
		}

		/*
		 * Form + ruled column as one sheet. Rows: lead | stage one | stage two | send row. The heading
		 * sits beside the direct line; below, the form and the column are subgrids of the same rows, so
		 * each ink rule in the column — promise, next steps, email — lands on a rule of the form:
		 * the form's top edge, the stage divider, the send row. The column ends on the form's foot.
		 */
		.gin--split .gin-body {
			grid-template-columns: minmax(0, 1fr) var(--gin-rail);
			grid-template-rows: auto auto auto auto;
			grid-template-areas: none;
			column-gap: 56px;
		}

		.gin--split .gin-lead {
			grid-column: 1;
			grid-row: 1;
			padding-bottom: 56px;
		}

		.gin--split .gin-form {
			grid-column: 1;
			grid-row: 2 / span 3;
			grid-template-rows: subgrid;
			margin: 0;
		}

		.gin--split .gin-stage--intent {
			grid-row: 1;
		}

		.gin--split .gin-stage--contact {
			grid-row: 2;
		}

		.gin--split:not(.gin--staged) .gin-stage--contact {
			grid-row: 1 / span 2;
		}

		.gin--split .gin-send {
			grid-row: 3;
		}

		.gin--split .gin-support {
			grid-column: 2;
			grid-row: 1 / span 4;
			display: grid;
			grid-template-rows: subgrid;
			min-width: 0;
		}

		.gin--split .gin-reach {
			grid-row: 1;
			padding-bottom: 56px;
		}

		.gin--split .gin-promise {
			grid-row: 2;
			align-self: stretch;
			padding: 30px 0 34px;
			border-top: 1px solid var(--gin-rule);
		}

		.gin--split .gin-next {
			grid-row: 3;
			align-self: stretch;
			padding: 30px 0 34px;
		}

		.gin--split .gin-foot {
			grid-row: 4;
			align-self: stretch;
			align-content: center;
			padding: 20px 0;
			border-bottom: 1px solid var(--gin-rule);
		}

		/* A missing band hands its row to the band below, so no rule stands over an empty cell. */
		.gin--split:not(.gin--promise) .gin-next {
			grid-row: 2 / span 2;
		}

		.gin--split:not(.gin--next) .gin-promise {
			grid-row: 2 / span 2;
		}

		/* In the column the steps return to a vertical rail. */
		.gin--split .gin-steps {
			grid-template-columns: minmax(0, 1fr);
			margin-top: 12px;
		}

		.gin--split .gin-step {
			grid-template-columns: 12px minmax(0, 1fr);
			column-gap: 18px;
			row-gap: 2px;
			padding-bottom: 22px;
		}

		.gin--split .gin-step:last-child {
			padding-bottom: 0;
		}

		.gin--split .gin-dot {
			grid-row: 1 / span 2;
			margin: 5px 0 0;
		}

		.gin--split .gin-step-detail {
			grid-column: 2;
		}

		.gin--split .gin-step:not(:last-child)::after {
			left: 5.5px;
			right: auto;
			top: 18px;
			bottom: -2px;
			width: 1px;
			height: auto;
		}

		.gin--solo .gin-body {
			max-width: 920px;
		}

		/* A form with no support column: three cards to a row where the count divides. */
		.gin--solo .gin-choices--three {
			grid-template-columns: repeat(3, minmax(0, 1fr));
		}

		.gin--solo .gin-choices--three .gin-choice:last-child {
			grid-column: auto;
		}

		/*
		 * No form: the direct line stays beside the heading; below one ink rule, the next steps take
		 * the main column as a horizontal rail and the promise and email stack in the column.
		 */
		.gin--noform .gin-body {
			grid-template-columns: minmax(0, 1fr) var(--gin-rail);
			grid-template-rows: auto auto 1fr;
			column-gap: 56px;
			grid-template-areas:
				"lead reach"
				"next promise"
				"next foot";
		}

		.gin--noform .gin-lead {
			padding-bottom: 56px;
		}

		.gin--noform .gin-reach {
			grid-column: auto;
			padding-bottom: 56px;
		}

		.gin--noform .gin-reach + .gin-promise {
			padding-top: 30px;
			border-top: 1px solid var(--gin-rule);
		}

		.gin--noform .gin-promise {
			align-self: start;
			padding: 30px 0 30px;
		}

		.gin--noform .gin-next {
			padding: 30px 0 0;
		}

		.gin--noform:not(.gin--next) .gin-promise,
		.gin--noform:not(.gin--next) .gin-foot {
			grid-column: 1 / -1;
		}

		.gin--noform:not(.gin--reach) .gin-lead {
			grid-column: 1 / -1;
		}

		.gin--noform .gin-steps {
			margin-top: 18px;
		}
	}

	@media (prefers-reduced-motion: reduce) {
		.gin-number,
		.gin-mail a,
		.gin-choice,
		.gin-field input,
		.gin-field select,
		.gin-field textarea,
		.gin-submit,
		.gin-arrow {
			transition: none;
		}
	}

	@media (forced-colors: active) {
		.gin-choice:has(.gin-radio:checked) {
			border: 2px solid Highlight;
		}

		.gin-dot {
			border-color: CanvasText;
		}

		.gin-submit {
			border: 2px solid ButtonText;
		}

		.gin-submit[disabled] {
			border-color: GrayText;
			color: GrayText;
		}

		.gin-lock {
			background: CanvasText;
		}

		.gin-submit[disabled] .gin-lock {
			background: GrayText;
		}
	}
</style>

===== END src/components/GuidedInquiry.astro =====

===== FILE (supporting): src/components/GuidedInquiry.md =====
# Guided inquiry

A two-stage lead form with a composed support column: the form asks what the visitor needs first (choice cards built on native radios, each with a one-line hint) and how to reach them second, in numbered fieldsets; the direct line sits beside the heading in large type, and a light support column ruled to the form's own lines carries a response promise, a confidence line, a dotted 'what happens next' list and an email alternative. Without a verified endpoint the form renders an honest preview mode; without a form the support column becomes a full-width band.

## Integration

- A section heading (h2 by default); it also names the form. The next-steps heading is one level down.
- Stage 1 choice cards render only from 2–6 supplied options; without them the form is the contact fieldset alone (unnumbered).
- The form renders only with a verified https or relative `action` (a normal POST form) or with `previewNotice` (preview mode: no action attribute, the send disabled and aria-disabled, the notice as a role="note" block). With neither there is no form and the support column becomes a full-width band.
- Direct line only from a real `phone.number` (tel: from its digits); hours, response promise, confidence line, email and steps only when passed. Steps: 2–4.
- Container queries: stacked below 600px, paired cards/fields and a horizontal steps rail from 600px, form column + 344px support column from 960px. Reads --gin-* hooks including bg, text, text-muted, border, brand, brand-text, card-bg, family, display, grid-max, grid-gutter and heading-size.
- No script (the checked card uses :has). Loads Schibsted Grotesk by default — pass fontStylesheet="" when the site loads its fonts.

## Provenance

Staged as candidate `guided-inquiry` (Step 28) and admitted through the candidate pipeline. Source type: original. References (direction only): none. Quality history: quality/candidates/records/guided-inquiry.json.

===== END src/components/GuidedInquiry.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
“Guided inquiry” from my Astro Component Library:
https://astro.baysixmedia.com/components/guided-inquiry/

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

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

---
/**
 * GuidedInquiry (candidate, Step 36)
 *
 * A two-stage inquiry set as one ruled sheet. The form is the primary action
 * and asks in order: stage 1 "What do you need?" as choice cards (native
 * radios, each card a label with an optional one-line hint), stage 2 "How to
 * reach you" as the contact fields — two fieldsets, both visible, each led by
 * a display numeral in the form's left margin. Those two numerals are the
 * section's one numbered sequence; the "What happens next" steps run on a
 * dotted rail.
 *
 * Beside the form, a light, rule-based support column on the section's own
 * ground (no slab): the direct line set large beside the heading, then ruled
 * bands that meet the form's own rules — the response promise and confidence
 * line beside stage one, the next steps beside stage two, the email
 * alternative beside the send row. Every rule in the column sits on the same
 * line as a rule of the form, so the column is balanced whatever the form's
 * height.
 *
 * Truth rules:
 * - The section renders only with a heading. No business fact has a default:
 *   the phone renders only from a real number (tel: from its digits); hours,
 *   the response promise, the confidence line, email, steps and choices only
 *   when passed. Choices are the supplied options only (2–6; fewer than two
 *   and the choice stage is left out). Steps: at most 4.
 * - The form renders only with a verified `action` (a normal POST form) or a
 *   non-empty `previewNotice` (PREVIEW MODE: no action attribute, the send
 *   disabled and aria-disabled, the notice as role="note"). With neither,
 *   there is no form: the direct line stays beside the heading and the next
 *   steps take the main column — the phone alone still reads as finished.
 *
 * Responsive (container queries on the section's own width):
 * - < 600px: heading, direct line, promise, the form (one column, cards
 *   stacked, the numeral beside each question), next steps, email.
 * - 600–959px: direct line | promise side by side; the numerals move into the
 *   form's margin; cards and fields pair; the steps run as a horizontal rail.
 * - ≥ 960px: form + 344px ruled column as above (subgrid rows).
 *
 * No script: validation is the browser's own; a disabled send blocks
 * submission. Reduced motion removes every transition.
 *
 * Hooks read (never declared): --gin-bg, --gin-card-bg, --gin-text,
 * --gin-text-muted, --gin-brand, --gin-brand-text, --gin-border,
 * --gin-family, --gin-display, --gin-grid-max, --gin-grid-gutter,
 * --gin-heading-size.
 */

export interface GuidedInquiryChoice {
	/** The option, as shown. */
	label: string;
	/** One optional line under it. */
	hint?: string;
	/** Submitted value; the label when absent. */
	value?: string;
}

export interface GuidedInquiryField {
	name: string;
	label: string;
	type: "text" | "email" | "tel" | "textarea" | "select";
	required?: boolean;
	options?: string[];
	placeholder?: string;
	autocomplete?: string;
}

export interface GuidedInquiryStep {
	title: string;
	detail?: string;
}

export interface GuidedInquiryProps {
	heading: string;
	headingLevel?: "h2" | "h3";
	eyebrow?: string;
	intro?: string;
	/** The direct line. Renders only from a real number. */
	phone?: { number: string; label?: string } | null;
	/** Hours lines, as supplied. */
	hours?: string | string[];
	/** The response promise, only when the business states one. */
	responsePromise?: string;
	/** A short confidence line from verified facts. */
	assurance?: string;
	email?: string;
	/** "What happens next": 2–4 ordered steps. */
	steps?: GuidedInquiryStep[];
	stepsHeading?: string;
	/** Stage 1 options, supplied only (2–6). */
	choices?: GuidedInquiryChoice[];
	choiceLegend?: string;
	choiceName?: string;
	choiceRequired?: boolean;
	contactLegend?: string;
	/** Stage 2 fields. Default: name, phone, email, message. */
	fields?: GuidedInquiryField[];
	submitLabel?: string;
	/** The verified form endpoint. Absent: preview mode (with previewNotice) or no form. */
	action?: string;
	previewNotice?: string;
	labels?: {
		directLine?: string;
		hours?: string;
		email?: string;
		notConnected?: string;
		optional?: string;
		choose?: string;
	};
	fontStylesheet?: string;
	class?: string;
	id?: string;
}

interface Props extends GuidedInquiryProps {}

const {
	heading,
	headingLevel = "h2",
	eyebrow = "",
	intro = "",
	phone = null,
	hours = [],
	responsePromise = "",
	assurance = "",
	email = "",
	steps = [],
	stepsHeading = "What happens next",
	choices = [],
	choiceLegend = "What do you need?",
	choiceName = "inquiry",
	choiceRequired = true,
	contactLegend = "How to reach you",
	fields: fieldsProp,
	submitLabel = "Send request",
	action: actionProp,
	previewNotice = "",
	labels = {},
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Schibsted+Grotesk:wght@400;500;600;700&display=swap",
	class: className,
	id,
} = Astro.props as Props;

const L = {
	directLine: "Direct line",
	hours: "Lines open",
	email: "Prefer to write?",
	notConnected: "Not connected",
	optional: "Optional",
	choose: "Choose one",
	...labels,
};

const str = (v: unknown) => (typeof v === "string" ? v.trim() : "");
const SAFE = /^(?:https?:|mailto:|tel:|[#/.?])/i;
const safeHref = (href: string) => (SAFE.test(href.trim()) ? href.trim() : "");
const telHref = (n: string) => {
	const digits = n.replace(/\D/g, "");
	return digits.length >= 3 ? `tel:${n.trim().startsWith("+") ? "+" : ""}${digits}` : "";
};

const number = str(phone?.number);
const callHref = number ? telHref(number) : "";
const mail = str(email);
const mailHref = mail && /^[^\s@]+@[^\s@]+$/.test(mail) ? safeHref(`mailto:${mail}`) : "";
const hourLines = (Array.isArray(hours) ? hours : [hours]).map(str).filter(Boolean);
const promise = str(responsePromise);
const confidence = str(assurance);
const stepList = (Array.isArray(steps) ? steps : [])
	.map((s) => ({ title: str(s?.title), detail: str(s?.detail) }))
	.filter((s) => s.title)
	.slice(0, 4);
const choiceList = (Array.isArray(choices) ? choices : [])
	.map((c) => ({ label: str(c?.label), hint: str(c?.hint), value: str(c?.value) || str(c?.label) }))
	.filter((c) => c.label)
	.slice(0, 6);
const hasChoices = choiceList.length >= 2;

/* Only an http(s) or relative endpoint; anything else is treated as absent. */
const rawAction = str(actionProp);
const action = rawAction && /^(?:https?:|[/.?])/i.test(rawAction) ? rawAction : "";
const preview = !action && Boolean(str(previewNotice));
const showForm = Boolean(action) || preview;

const DEFAULT_FIELDS: GuidedInquiryField[] = [
	{ name: "name", label: "Full name", type: "text", required: true, autocomplete: "name" },
	{ name: "phone", label: "Phone", type: "tel", required: true, autocomplete: "tel" },
	{ name: "email", label: "Email", type: "email", autocomplete: "email" },
	{ name: "message", label: "Anything we should know?", type: "textarea" },
];
const fieldList: GuidedInquiryField[] = (fieldsProp && fieldsProp.length ? fieldsProp : DEFAULT_FIELDS)
	.filter((f) => f && str(f.name) && str(f.label))
	.map((f) => ({ ...f, name: str(f.name), label: str(f.label), options: (f.options ?? []).map(str).filter(Boolean) }))
	.filter((f) => f.type !== "select" || (f.options?.length ?? 0) > 0);

const uid = `${(id ?? "gin").replace(/[^\w-]/g, "") || "gin"}-${Math.random().toString(36).slice(2, 7)}`;

/* Textareas take the row; other fields pair; an unpaired field takes the row. */
const laid = fieldList.map((f, i) => ({ ...f, id: `${uid}-f${i}`, wide: f.type === "textarea" }));
for (let i = 0; i < laid.length; ) {
	if (laid[i].wide) {
		i += 1;
		continue;
	}
	if (i + 1 < laid.length && !laid[i + 1].wide) {
		i += 2;
		continue;
	}
	laid[i].wide = true;
	i += 1;
}

const Heading = headingLevel;
const SubHeading = headingLevel === "h2" ? "h3" : "h4";
const headingId = `${uid}-h`;
const noticeId = `${uid}-note`;
const hasReach = Boolean(callHref || hourLines.length);
const hasPromise = Boolean(promise || confidence);
const hasNext = stepList.length >= 2;
const hasFoot = Boolean(mailHref);
const hasSupport = hasReach || hasPromise || hasNext || hasFoot;
const title = str(heading);
---

{fontStylesheet && title && <link rel="stylesheet" href={fontStylesheet} />}
{
	title && (
		<section
			class:list={[
				"gin",
				{
					"gin--split": showForm && hasSupport,
					"gin--noform": !showForm,
					"gin--solo": showForm && !hasSupport,
					"gin--staged": hasChoices,
					"gin--reach": hasReach,
					"gin--promise": hasPromise,
					"gin--next": hasNext,
					"gin--foot": hasFoot,
					"gin--preview": preview,
				},
				className,
			]}
			id={id}
		>
			<div class="gin-inner">
				<div class="gin-body">
					<header class="gin-lead">
						{eyebrow && <p class="gin-eyebrow">{eyebrow}</p>}
						<Heading class="gin-title" id={headingId}>
							{title}
						</Heading>
						{intro && <p class="gin-intro">{intro}</p>}
					</header>

					{showForm && (
						<form class="gin-form" method="post" action={action || undefined} aria-labelledby={headingId}>
							{hasChoices && (
								<fieldset class="gin-stage gin-stage--intent">
									<legend class="gin-legend">
										<span class="gin-numeral" aria-hidden="true">1</span>
										<span class="gin-legend-text">{choiceLegend}</span>
									</legend>
									<div class:list={["gin-choices", { "gin-choices--odd": choiceList.length % 2 === 1, "gin-choices--three": choiceList.length === 3 || choiceList.length === 6 }]}>
										{choiceList.map((c, i) => (
											<label class="gin-choice" for={`${uid}-c${i}`}>
												<input class="gin-radio" type="radio" id={`${uid}-c${i}`} name={choiceName} value={c.value} required={choiceRequired || undefined} />
												<span class="gin-choice-text">
													<span class="gin-choice-label">{c.label}</span>
													{c.hint && <span class="gin-choice-hint">{c.hint}</span>}
												</span>
											</label>
										))}
									</div>
								</fieldset>
							)}

							<fieldset class="gin-stage gin-stage--contact">
								<legend class="gin-legend">
									{hasChoices && (
										<span class="gin-numeral" aria-hidden="true">
											2
										</span>
									)}
									<span class="gin-legend-text">{contactLegend}</span>
								</legend>
								<div class="gin-fields">
									{laid.map((f) => (
										<div class:list={["gin-field", { "gin-field--wide": f.wide }]}>
											<label for={f.id}>
												{f.label}
												{!f.required && <span class="gin-opt">{L.optional}</span>}
											</label>
											{f.type === "textarea" ? (
												<textarea id={f.id} name={f.name} rows="4" required={f.required} placeholder={f.placeholder || undefined} autocomplete={f.autocomplete || undefined} />
											) : f.type === "select" ? (
												<select id={f.id} name={f.name} required={f.required} autocomplete={f.autocomplete || undefined}>
													<option value="">{L.choose}</option>
													{f.options?.map((o) => (
														<option value={o}>{o}</option>
													))}
												</select>
											) : (
												<input id={f.id} name={f.name} type={f.type} required={f.required} placeholder={f.placeholder || undefined} autocomplete={f.autocomplete || undefined} />
											)}
										</div>
									))}
								</div>
							</fieldset>

							<div class="gin-send">
								{preview && (
									<p class="gin-notice" role="note" id={noticeId}>
										<span class="gin-tag">
											<span class="gin-ring" aria-hidden="true" />
											{L.notConnected}
										</span>
										<span class="gin-notice-text">{str(previewNotice)}</span>
									</p>
								)}
								{preview ? (
									<button class="gin-submit gin-submit--inert" type="submit" disabled aria-disabled="true" aria-describedby={noticeId}>
										<span>{submitLabel}</span>
										<span class="gin-lock" aria-hidden="true" />
									</button>
								) : (
									<button class="gin-submit" type="submit">
										<span>{submitLabel}</span>
										<span class="gin-arrow" aria-hidden="true" />
									</button>
								)}
							</div>
						</form>
					)}

					{hasSupport && (
						<div class="gin-support">
							{hasReach && (
								<div class="gin-reach">
									{callHref && (
										<>
											<p class="gin-kicker">{phone?.label?.trim() || L.directLine}</p>
											<a class="gin-number" href={callHref}>
												{number}
											</a>
										</>
									)}
									{hourLines.length > 0 && (
										<p class="gin-hours">
											<span class="gin-hours-label">{L.hours}</span>
											{hourLines.map((h) => (
												<span class="gin-hours-line">{h}</span>
											))}
										</p>
									)}
								</div>
							)}
							{hasPromise && (
								<div class="gin-band gin-promise">
									{promise && <p class="gin-promise-text">{promise}</p>}
									{confidence && <p class="gin-assurance">{confidence}</p>}
								</div>
							)}
							{hasNext && (
								<div class="gin-band gin-next">
									<SubHeading class="gin-next-title">{stepsHeading}</SubHeading>
									<ol class:list={["gin-steps", `gin-steps--${stepList.length}`]}>
										{stepList.map((s) => (
											<li class="gin-step">
												<span class="gin-dot" aria-hidden="true" />
												<span class="gin-step-title">{s.title}</span>
												{s.detail && <span class="gin-step-detail">{s.detail}</span>}
											</li>
										))}
									</ol>
								</div>
							)}
							{hasFoot && (
								<div class="gin-band gin-foot">
									<p class="gin-mail">
										<span class="gin-kicker">{L.email}</span>
										<a href={mailHref}>{mail}</a>
									</p>
								</div>
							)}
						</div>
					)}
				</div>
			</div>
		</section>
	)
}

<style>
	.gin {
		--gin-surface: var(--gin-bg, #f2f2ef);
		--gin-paper: var(--gin-card-bg, #ffffff);
		--gin-ink: var(--gin-text, #111418);
		--gin-soft: var(--gin-text-muted, #474e55);
		--gin-accent: var(--gin-brand, #1f3a93);
		--gin-accent-ink: var(--gin-brand-text, #ffffff);
		--gin-line: var(--gin-border, #d6d6d0);
		--gin-font: var(--gin-family, "Schibsted Grotesk", ui-sans-serif, system-ui, sans-serif);
		--gin-font-display: var(--gin-display, "Schibsted Grotesk", ui-sans-serif, system-ui, sans-serif);
		/* The column's rules are ink, the form's hairlines are the border hook: the column reads as the ledger. */
		--gin-rule: color-mix(in srgb, var(--gin-ink) 82%, var(--gin-surface));
		--gin-tint: color-mix(in srgb, var(--gin-accent) 7%, var(--gin-paper));
		--gin-edge: color-mix(in srgb, var(--gin-ink) 24%, var(--gin-paper));
		--gin-rail: 344px;

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

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

	.gin-inner {
		max-width: var(--gin-grid-max, 1232px);
		margin: 0 auto;
		padding: 56px 16px;
	}

	/* Phones: heading, direct line, promise, the form, next steps, email. */
	.gin-body {
		display: grid;
		grid-template-columns: minmax(0, 1fr);
		grid-template-areas:
			"lead"
			"reach"
			"promise"
			"form"
			"next"
			"foot";
	}

	.gin-support {
		display: contents;
	}

	.gin-lead {
		grid-area: lead;
		min-width: 0;
		padding-bottom: 32px;
	}

	.gin-reach {
		grid-area: reach;
	}

	.gin-promise {
		grid-area: promise;
	}

	.gin-form {
		grid-area: form;
	}

	.gin-next {
		grid-area: next;
	}

	.gin-foot {
		grid-area: foot;
	}

	/* ---- lead ---- */

	.gin-eyebrow {
		margin: 0 0 14px;
		color: var(--gin-accent);
		font: 600 15px/1.4 var(--gin-font);
	}

	.gin-title {
		max-width: 18ch;
		margin: 0;
		font-family: var(--gin-font-display);
		font-size: var(--gin-heading-size, clamp(34px, 4.6vw, 58px));
		font-weight: 600;
		line-height: 1.02;
		letter-spacing: -0.03em;
		text-wrap: balance;
		overflow-wrap: anywhere;
	}

	.gin-intro {
		max-width: 48ch;
		margin: 20px 0 0;
		color: var(--gin-soft);
		font: 400 17px/1.6 var(--gin-font);
		overflow-wrap: anywhere;
	}

	/* ---- the support column: light, on the section's ground, divided by ink rules ---- */

	.gin-reach,
	.gin-band {
		min-width: 0;
	}

	.gin-reach {
		display: grid;
		gap: 6px;
		align-content: end;
		padding: 0 0 28px;
	}

	.gin-band {
		display: grid;
		gap: 12px;
		align-content: start;
		padding: 24px 0 28px;
		border-top: 1px solid var(--gin-rule);
	}

	.gin-kicker {
		margin: 0;
		color: var(--gin-soft);
		font: 500 14.5px/1.4 var(--gin-font);
	}

	/* The number: display type, underlined in the accent — the link is the number itself. */
	.gin-number {
		justify-self: start;
		display: inline-block;
		min-height: 48px;
		padding: 4px 0 8px;
		color: var(--gin-ink);
		font-family: var(--gin-font-display);
		font-size: clamp(30px, 9.5cqi, 42px);
		font-weight: 600;
		line-height: 1.1;
		letter-spacing: -0.03em;
		font-variant-numeric: lining-nums tabular-nums;
		/* A number fits on one line; longer link text wraps rather than pushing the column wide. */
		max-width: 100%;
		overflow-wrap: anywhere;
		text-decoration: underline;
		text-decoration-color: var(--gin-accent);
		text-decoration-thickness: 2px;
		text-underline-offset: 8px;
		transition: text-decoration-thickness 0.15s ease;
	}

	.gin-number:hover {
		text-decoration-thickness: 4px;
	}

	.gin-hours {
		display: flex;
		flex-wrap: wrap;
		gap: 2px 14px;
		margin: 10px 0 0;
		font: 500 15px/1.5 var(--gin-font);
	}

	.gin-hours-label {
		flex-basis: 100%;
		color: var(--gin-soft);
		font-weight: 400;
		font-size: 14.5px;
	}

	.gin-hours-line {
		white-space: nowrap;
	}

	.gin-promise-text {
		max-width: 24ch;
		margin: 0;
		font-family: var(--gin-font-display);
		font-size: 23px;
		font-weight: 600;
		line-height: 1.25;
		letter-spacing: -0.018em;
		text-wrap: pretty;
		overflow-wrap: anywhere;
	}

	.gin-assurance {
		max-width: 40ch;
		margin: 0;
		color: var(--gin-soft);
		font: 400 15.5px/1.55 var(--gin-font);
		overflow-wrap: anywhere;
	}

	.gin-mail {
		display: grid;
		gap: 2px;
		margin: 0;
	}

	.gin-mail a {
		justify-self: start;
		display: inline-block;
		padding: 12px 0;
		margin: -12px 0;
		color: var(--gin-ink);
		font: 600 16.5px/1.4 var(--gin-font);
		text-decoration: underline;
		text-decoration-color: var(--gin-accent);
		text-decoration-thickness: 1.5px;
		text-underline-offset: 5px;
		overflow-wrap: anywhere;
	}

	.gin-mail a:hover {
		text-decoration-thickness: 3px;
	}

	.gin-number:focus-visible,
	.gin-mail a:focus-visible {
		outline: 3px solid var(--gin-accent);
		outline-offset: 4px;
		border-radius: 2px;
	}

	/* ---- next steps: a dotted rail (the numerals belong to the form's stages) ---- */

	.gin-next-title {
		margin: 0 0 6px;
		color: var(--gin-ink);
		font: 600 16px/1.4 var(--gin-font);
		letter-spacing: 0;
	}

	.gin-steps {
		display: grid;
		margin: 0;
		padding: 0;
		list-style: none;
	}

	.gin-step {
		position: relative;
		display: grid;
		grid-template-columns: 12px minmax(0, 1fr);
		column-gap: 18px;
		row-gap: 2px;
		align-content: start;
		min-width: 0;
		padding-bottom: 20px;
	}

	.gin-step:last-child {
		padding-bottom: 0;
	}

	.gin-step:not(:last-child)::after {
		content: "";
		position: absolute;
		left: 5.5px;
		top: 18px;
		bottom: -2px;
		width: 1px;
		background: var(--gin-edge);
	}

	.gin-dot {
		grid-row: 1 / span 2;
		width: 12px;
		height: 12px;
		margin-top: 5px;
		border: 2px solid var(--gin-accent);
		border-radius: 50%;
		background: var(--gin-surface);
	}

	.gin-step:first-child .gin-dot {
		background: var(--gin-accent);
	}

	.gin-step-title {
		font: 600 16px/1.4 var(--gin-font);
		overflow-wrap: anywhere;
	}

	.gin-step-detail {
		grid-column: 2;
		color: var(--gin-soft);
		font: 400 15px/1.5 var(--gin-font);
		overflow-wrap: anywhere;
	}

	/* ---- the form: two numbered stages ---- */

	.gin-form {
		display: grid;
		align-content: start;
		min-width: 0;
		margin: 8px 0 28px;
		border: 1px solid var(--gin-line);
		background: var(--gin-paper);
	}

	.gin-stage {
		position: relative;
		min-width: 0;
		margin: 0;
		padding: 28px 20px 30px;
		border: 0;
	}

	.gin-stage + .gin-stage {
		border-top: 1px solid var(--gin-line);
	}

	.gin-legend {
		float: left;
		display: flex;
		align-items: baseline;
		gap: 14px;
		width: 100%;
		margin: 0 0 20px;
		padding: 0;
		font: 600 20px/1.3 var(--gin-font);
		letter-spacing: -0.012em;
	}

	.gin-legend + * {
		clear: both;
	}

	/* The stage numeral: display type in the accent — the section's one numbered sequence. */
	.gin-numeral {
		flex: none;
		color: var(--gin-accent);
		font-family: var(--gin-font-display);
		font-size: 30px;
		font-weight: 600;
		line-height: 1;
		letter-spacing: -0.03em;
		font-variant-numeric: lining-nums tabular-nums;
	}

	.gin-legend-text {
		min-width: 0;
		overflow-wrap: anywhere;
	}

	/* Choice cards: native radios, the whole card is the label. */
	.gin-choices {
		display: grid;
		grid-template-columns: minmax(0, 1fr);
		gap: 10px;
	}

	.gin-choice {
		display: grid;
		grid-template-columns: 24px minmax(0, 1fr);
		align-items: start;
		gap: 14px;
		min-width: 0;
		min-height: 64px;
		padding: 16px 18px;
		border: 1px solid var(--gin-edge);
		border-radius: 4px;
		background: var(--gin-paper);
		cursor: pointer;
		transition: border-color 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease;
	}

	.gin-radio {
		/* 24px: the whole card is the label, but the control itself also meets the touch-target floor. */
		width: 24px;
		height: 24px;
		margin: 0;
		flex: none;
		accent-color: var(--gin-accent);
		cursor: pointer;
	}

	.gin-choice-text {
		display: grid;
		gap: 3px;
		min-width: 0;
	}

	.gin-choice-label {
		font: 600 16.5px/1.35 var(--gin-font);
		overflow-wrap: anywhere;
	}

	.gin-choice-hint {
		color: var(--gin-soft);
		font: 400 15px/1.45 var(--gin-font);
		overflow-wrap: anywhere;
	}

	.gin-choice:hover {
		border-color: color-mix(in srgb, var(--gin-ink) 60%, var(--gin-paper));
	}

	.gin-choice:has(.gin-radio:checked) {
		border-color: var(--gin-accent);
		background: var(--gin-tint);
		box-shadow: inset 0 0 0 1px var(--gin-accent);
	}

	.gin-choice:has(.gin-radio:focus-visible) {
		outline: 3px solid var(--gin-accent);
		outline-offset: 2px;
	}

	.gin-radio:focus-visible {
		outline: 2px solid var(--gin-accent);
		outline-offset: 2px;
	}

	/* Fields */
	.gin-fields {
		display: grid;
		grid-template-columns: minmax(0, 1fr);
		gap: 18px 20px;
	}

	.gin-field {
		display: grid;
		gap: 7px;
		min-width: 0;
		align-content: start;
	}

	.gin-field label {
		display: flex;
		flex-wrap: wrap;
		align-items: baseline;
		gap: 4px 10px;
		font: 600 15px/1.35 var(--gin-font);
	}

	.gin-opt {
		color: var(--gin-soft);
		font: 500 14px/1.35 var(--gin-font);
	}

	.gin-field input,
	.gin-field select,
	.gin-field textarea {
		width: 100%;
		min-height: 50px;
		margin: 0;
		padding: 12px 14px;
		border: 1px solid var(--gin-edge);
		border-radius: 4px;
		background: var(--gin-paper);
		color: var(--gin-ink);
		font: 400 16px/1.4 var(--gin-font);
		transition: border-color 0.15s ease, box-shadow 0.15s ease;
	}

	.gin-field textarea {
		min-height: 112px;
		resize: vertical;
	}

	.gin-field select {
		appearance: none;
		padding-right: 44px;
		background-image: linear-gradient(45deg, transparent 50%, var(--gin-soft) 50%), linear-gradient(135deg, var(--gin-soft) 50%, transparent 50%);
		background-position: calc(100% - 22px) 50%, calc(100% - 16px) 50%;
		background-size: 6px 6px;
		background-repeat: no-repeat;
		cursor: pointer;
	}

	.gin-field input::placeholder,
	.gin-field textarea::placeholder {
		color: var(--gin-soft);
	}

	.gin-field input:hover,
	.gin-field select:hover,
	.gin-field textarea:hover {
		border-color: color-mix(in srgb, var(--gin-ink) 60%, var(--gin-paper));
	}

	.gin-field input:focus-visible,
	.gin-field select:focus-visible,
	.gin-field textarea:focus-visible {
		outline: 2px solid transparent;
		border-color: var(--gin-accent);
		box-shadow: 0 0 0 3px color-mix(in srgb, var(--gin-accent) 26%, transparent);
	}

	/* Send row */
	.gin-send {
		display: grid;
		gap: 14px;
		align-items: center;
		min-width: 0;
		padding: 22px 20px 26px;
		border-top: 1px solid var(--gin-line);
	}

	/* Preview mode: the same white row, an accent status line and a locked ink control. */
	.gin-notice {
		display: grid;
		gap: 4px;
		margin: 0;
		color: var(--gin-ink);
		font: 400 15px/1.5 var(--gin-font);
		overflow-wrap: anywhere;
	}

	.gin-tag {
		display: inline-flex;
		align-items: center;
		gap: 9px;
		color: var(--gin-accent);
		font: 600 14.5px/1.3 var(--gin-font);
	}

	.gin-ring {
		flex: none;
		width: 11px;
		height: 11px;
		border: 2px solid currentColor;
		border-radius: 50%;
	}

	.gin-notice-text {
		max-width: 46ch;
	}

	.gin-submit {
		display: inline-flex;
		align-items: center;
		justify-content: space-between;
		gap: 14px;
		width: 100%;
		min-height: 56px;
		padding: 15px 22px;
		border: 1.5px solid var(--gin-accent);
		border-radius: 4px;
		background: var(--gin-accent);
		color: var(--gin-accent-ink);
		font: 600 17px/1.2 var(--gin-font);
		text-align: left;
		cursor: pointer;
		transition: background-color 0.15s ease, border-color 0.15s ease;
	}

	.gin-submit:not([disabled]):hover {
		border-color: color-mix(in srgb, var(--gin-accent) 80%, var(--gin-ink));
		background: color-mix(in srgb, var(--gin-accent) 80%, var(--gin-ink));
	}

	.gin-submit:not([disabled]):hover .gin-arrow {
		transform: translateX(4px);
	}

	.gin-submit:focus-visible {
		outline: 3px solid var(--gin-accent);
		outline-offset: 3px;
	}

	/*
	 * Locked, not broken: a solid ink control at full size on the white row, white label, a drawn
	 * padlock where the arrow would be — confident, and plainly not the live accent button.
	 */
	.gin-submit--inert {
		border-color: var(--gin-ink);
		background: var(--gin-ink);
		color: var(--gin-paper);
		cursor: not-allowed;
	}

	.gin-lock {
		position: relative;
		flex: none;
		width: 13px;
		height: 10px;
		margin: 6px 3px 0 0;
		border-radius: 2px;
		background: currentColor;
	}

	.gin-lock::before {
		content: "";
		position: absolute;
		left: 2.5px;
		bottom: 9px;
		width: 8px;
		height: 7px;
		border: 1.75px solid currentColor;
		border-bottom: 0;
		border-radius: 5px 5px 0 0;
	}

	/* A drawn arrow: a shaft and a head. */
	.gin-arrow {
		position: relative;
		flex: none;
		width: 18px;
		height: 1.5px;
		margin-right: 3px;
		background: currentColor;
		transition: transform 0.2s ease;
	}

	.gin-arrow::after {
		content: "";
		position: absolute;
		right: 0;
		top: -3.75px;
		width: 8px;
		height: 8px;
		border: solid currentColor;
		border-width: 1.5px 1.5px 0 0;
		transform: rotate(45deg);
	}

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

	@container (min-width: 600px) {
		.gin-inner {
			padding: 80px 32px;
		}

		.gin-lead {
			padding-bottom: 40px;
		}

		/* Direct line | promise, side by side above the form. */
		.gin-body {
			grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
			column-gap: 40px;
			grid-template-areas:
				"lead lead"
				"reach promise"
				"form form"
				"next next"
				"foot foot";
		}

		.gin:not(.gin--promise) .gin-reach,
		.gin:not(.gin--reach) .gin-promise {
			grid-column: 1 / -1;
		}

		.gin--reach .gin-promise {
			align-self: end;
			padding-bottom: 28px;
		}

		.gin-reach + .gin-promise {
			border-top: 0;
			padding-top: 0;
		}

		.gin-form {
			margin: 12px 0 36px;
		}

		/* The numerals move into the form's left margin. */
		.gin--staged .gin-stage {
			padding: 34px 36px 36px 88px;
		}

		.gin--staged .gin-numeral {
			position: absolute;
			left: 36px;
			top: 32px;
			font-size: 40px;
		}

		.gin-stage {
			padding: 34px 36px;
		}

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

		.gin-choices--odd .gin-choice:last-child {
			grid-column: 1 / -1;
		}

		.gin-fields {
			grid-template-columns: repeat(2, minmax(0, 1fr));
			gap: 22px 20px;
		}

		.gin-field--wide {
			grid-column: 1 / -1;
		}

		.gin-send {
			grid-template-columns: minmax(0, 1fr) minmax(220px, 280px);
			gap: 16px 28px;
			padding: 24px 36px;
		}

		.gin--staged .gin-send {
			padding-left: 88px;
		}

		.gin-send > .gin-submit:only-child {
			grid-column: 2;
		}

		/* The steps run as a horizontal rail. */
		.gin-steps {
			grid-template-columns: repeat(var(--gin-n, 3), minmax(0, 1fr));
			column-gap: 28px;
			margin-top: 10px;
		}

		.gin-steps--2 {
			--gin-n: 2;
		}

		.gin-steps--3 {
			--gin-n: 3;
		}

		.gin-steps--4 {
			--gin-n: 4;
		}

		.gin-step {
			grid-template-columns: minmax(0, 1fr);
			row-gap: 4px;
			padding-bottom: 0;
		}

		.gin-dot {
			grid-row: auto;
			margin: 0 0 12px;
		}

		.gin-step-detail {
			grid-column: 1;
		}

		.gin-step:not(:last-child)::after {
			left: 20px;
			right: -8px;
			top: 5.5px;
			bottom: auto;
			width: auto;
			height: 1px;
		}
	}

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

	@container (min-width: 960px) {
		.gin-inner {
			padding-block: 112px;
			padding-inline: var(--gin-grid-gutter, 48px);
		}

		.gin-stage,
		.gin--staged .gin-stage {
			padding: 44px 48px 42px;
		}

		.gin--staged .gin-stage {
			padding-left: 104px;
		}

		.gin--staged .gin-numeral {
			left: 48px;
			top: 40px;
			font-size: 44px;
		}

		.gin-legend {
			margin-bottom: 24px;
			font-size: 21px;
		}

		.gin-send {
			grid-template-columns: minmax(0, 1fr) 280px;
			gap: 16px 32px;
			padding: 26px 48px;
		}

		.gin--staged .gin-send {
			padding-left: 104px;
		}

		/*
		 * Form + ruled column as one sheet. Rows: lead | stage one | stage two | send row. The heading
		 * sits beside the direct line; below, the form and the column are subgrids of the same rows, so
		 * each ink rule in the column — promise, next steps, email — lands on a rule of the form:
		 * the form's top edge, the stage divider, the send row. The column ends on the form's foot.
		 */
		.gin--split .gin-body {
			grid-template-columns: minmax(0, 1fr) var(--gin-rail);
			grid-template-rows: auto auto auto auto;
			grid-template-areas: none;
			column-gap: 56px;
		}

		.gin--split .gin-lead {
			grid-column: 1;
			grid-row: 1;
			padding-bottom: 56px;
		}

		.gin--split .gin-form {
			grid-column: 1;
			grid-row: 2 / span 3;
			grid-template-rows: subgrid;
			margin: 0;
		}

		.gin--split .gin-stage--intent {
			grid-row: 1;
		}

		.gin--split .gin-stage--contact {
			grid-row: 2;
		}

		.gin--split:not(.gin--staged) .gin-stage--contact {
			grid-row: 1 / span 2;
		}

		.gin--split .gin-send {
			grid-row: 3;
		}

		.gin--split .gin-support {
			grid-column: 2;
			grid-row: 1 / span 4;
			display: grid;
			grid-template-rows: subgrid;
			min-width: 0;
		}

		.gin--split .gin-reach {
			grid-row: 1;
			padding-bottom: 56px;
		}

		.gin--split .gin-promise {
			grid-row: 2;
			align-self: stretch;
			padding: 30px 0 34px;
			border-top: 1px solid var(--gin-rule);
		}

		.gin--split .gin-next {
			grid-row: 3;
			align-self: stretch;
			padding: 30px 0 34px;
		}

		.gin--split .gin-foot {
			grid-row: 4;
			align-self: stretch;
			align-content: center;
			padding: 20px 0;
			border-bottom: 1px solid var(--gin-rule);
		}

		/* A missing band hands its row to the band below, so no rule stands over an empty cell. */
		.gin--split:not(.gin--promise) .gin-next {
			grid-row: 2 / span 2;
		}

		.gin--split:not(.gin--next) .gin-promise {
			grid-row: 2 / span 2;
		}

		/* In the column the steps return to a vertical rail. */
		.gin--split .gin-steps {
			grid-template-columns: minmax(0, 1fr);
			margin-top: 12px;
		}

		.gin--split .gin-step {
			grid-template-columns: 12px minmax(0, 1fr);
			column-gap: 18px;
			row-gap: 2px;
			padding-bottom: 22px;
		}

		.gin--split .gin-step:last-child {
			padding-bottom: 0;
		}

		.gin--split .gin-dot {
			grid-row: 1 / span 2;
			margin: 5px 0 0;
		}

		.gin--split .gin-step-detail {
			grid-column: 2;
		}

		.gin--split .gin-step:not(:last-child)::after {
			left: 5.5px;
			right: auto;
			top: 18px;
			bottom: -2px;
			width: 1px;
			height: auto;
		}

		.gin--solo .gin-body {
			max-width: 920px;
		}

		/* A form with no support column: three cards to a row where the count divides. */
		.gin--solo .gin-choices--three {
			grid-template-columns: repeat(3, minmax(0, 1fr));
		}

		.gin--solo .gin-choices--three .gin-choice:last-child {
			grid-column: auto;
		}

		/*
		 * No form: the direct line stays beside the heading; below one ink rule, the next steps take
		 * the main column as a horizontal rail and the promise and email stack in the column.
		 */
		.gin--noform .gin-body {
			grid-template-columns: minmax(0, 1fr) var(--gin-rail);
			grid-template-rows: auto auto 1fr;
			column-gap: 56px;
			grid-template-areas:
				"lead reach"
				"next promise"
				"next foot";
		}

		.gin--noform .gin-lead {
			padding-bottom: 56px;
		}

		.gin--noform .gin-reach {
			grid-column: auto;
			padding-bottom: 56px;
		}

		.gin--noform .gin-reach + .gin-promise {
			padding-top: 30px;
			border-top: 1px solid var(--gin-rule);
		}

		.gin--noform .gin-promise {
			align-self: start;
			padding: 30px 0 30px;
		}

		.gin--noform .gin-next {
			padding: 30px 0 0;
		}

		.gin--noform:not(.gin--next) .gin-promise,
		.gin--noform:not(.gin--next) .gin-foot {
			grid-column: 1 / -1;
		}

		.gin--noform:not(.gin--reach) .gin-lead {
			grid-column: 1 / -1;
		}

		.gin--noform .gin-steps {
			margin-top: 18px;
		}
	}

	@media (prefers-reduced-motion: reduce) {
		.gin-number,
		.gin-mail a,
		.gin-choice,
		.gin-field input,
		.gin-field select,
		.gin-field textarea,
		.gin-submit,
		.gin-arrow {
			transition: none;
		}
	}

	@media (forced-colors: active) {
		.gin-choice:has(.gin-radio:checked) {
			border: 2px solid Highlight;
		}

		.gin-dot {
			border-color: CanvasText;
		}

		.gin-submit {
			border: 2px solid ButtonText;
		}

		.gin-submit[disabled] {
			border-color: GrayText;
			color: GrayText;
		}

		.gin-lock {
			background: CanvasText;
		}

		.gin-submit[disabled] .gin-lock {
			background: GrayText;
		}
	}
</style>