Component library
Back to templates

Page template

Transport site — Request a quote

Quote page of the four-page freight site: contact-bar overlay header, photo banner with breadcrumb, a two-part shipment quote form beside a photo, and the footer.

quote pageformlogisticsmulti-pagestock photos

Open preview in a new tab

Use this template

Two ways to get Transport site — Request a quote into one of your own projects. The first two carry the template's files with them, so nothing has to fetch this page.

Start here

Placement prompt

The whole page: what to do, where it goes, what must be replaced, and all 7 files appended underneath — the page file plus every component it uses — each with the path it belongs at. Paste it into Claude Code inside the destination project.

Read it first — the files are added underneath when you copy
Build a page in this Astro project from the "Transport site — Request a quote" page template in my Astro Component Library.

Library page, for reference only:
https://astro.baysixmedia.com/templates/transport-request-quote/

Route:
[ENTER THE ROUTE THIS PAGE SHOULD BECOME, e.g. / or /services/drain-cleaning]

Business and content:
[ENTER THE BUSINESS NAME, SERVICES, SERVICE AREA, PHONE, AND ANY APPROVED COPY — OR POINT AT THE FILE THAT HOLDS THEM]

Every file you need is appended below, each fenced with the path it belongs at. The page file is src/pages/transport-request-quote.astro; every other file is a component it imports. 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.

1. Inspect this project first: its shared layout, global stylesheet, existing header and footer, and whether the target route already exists. Report what you found before writing anything.
2. Write the component files to their paths. If a file with the same name already exists and differs, stop and ask rather than overwriting it.
3. Put the page at the route above, not at src/pages/transport-request-quote.astro unless that is the route. If the route already has a page, replace it only after confirming with me. Update the page's relative imports to match wherever it ends up.
4. If this project has a shared layout, render the page through it instead of the template's own <html> shell, and keep the layout's head, analytics and scripts. Keep exactly one header, one footer and one <main>, and keep a working skip link.
5. Replace ALL demo content in the page's props — every name, heading, paragraph, testimonial, logo, link, image and form destination — with this business's real content. Do not ship demo copy, invented reviews, invented statistics or placeholder links. Where real content is missing, leave a clearly marked TODO rather than inventing it.
6. Map the header and footer links to this project's real routes. Every href must resolve.
7. Adapt colours and typography to the project through documented props where possible. Preserve each component's layout, responsive behaviour, accessibility, interactions and reduced-motion behaviour.

Test the page at 1440, 1280, 1024, 834, 390 and 320px: no horizontal overflow, keyboard operation throughout, and nothing hidden under the header. Run the project's check and build commands. Report the files written, the route, what content is still TODO, and anything you could not verify. Do not commit or deploy.

Install kit

The 7 files and nothing else: the page file and every component, each fenced with its path, followed by each component's verification status. For placing it yourself.

Read it first
Page template kit: "Transport site — Request a quote"
Library page: https://astro.baysixmedia.com/templates/transport-request-quote/
Page file: src/pages/transport-request-quote.astro
Files: 7 required

Write each block below to the path named in its header, relative to the project root. The page file imports every component by relative path, so keep the destinations as given or update its imports to match. A block marked (required) is one the page does not work without; (supporting) is documentation. Nothing here needs downloading except a block that says so in as many words.

===== FILE (required): src/pages/transport-request-quote.astro =====
---
/**
 * Transport site — Request a quote — a page template from the Astro Component Library.
 * https://astro.baysixmedia.com/templates/transport-request-quote/
 *
 * Every section is a library component with its demo content passed in as
 * props. Replace the props with this site's own content, and move the page
 * inside this project's own layout if it has one.
 */
import ContactBarOverlayHeader from "../components/ContactBarOverlayHeader.astro";
import BreadcrumbPageBanner from "../components/BreadcrumbPageBanner.astro";
import ShipmentQuoteForm from "../components/ShipmentQuoteForm.astro";
import NewsletterPanelFooter from "../components/NewsletterPanelFooter.astro";

const contactBarOverlayHeaderProps = {
	"items": [
		{
			"label": "Home",
			"children": [
				{
					"label": "Freight home",
					"href": "/"
				},
				{
					"label": "Trucking home",
					"href": "/trucking"
				},
				{
					"label": "Ocean home",
					"href": "/ocean"
				}
			]
		},
		{
			"label": "About Us",
			"href": "/about"
		},
		{
			"label": "Services",
			"children": [
				{
					"label": "Ocean freight",
					"href": "/services/ocean-freight"
				},
				{
					"label": "Road haulage",
					"href": "/services/road-haulage"
				},
				{
					"label": "Rail freight",
					"href": "/services/rail-freight"
				},
				{
					"label": "Warehousing",
					"href": "/services/warehousing"
				}
			]
		},
		{
			"label": "Pages",
			"children": [
				{
					"label": "Our team",
					"href": "/team"
				},
				{
					"label": "Pricing",
					"href": "/pricing"
				},
				{
					"label": "Request a quote",
					"href": "/request-quote"
				},
				{
					"label": "Shipping FAQ",
					"href": "/faq"
				}
			]
		},
		{
			"label": "News",
			"children": [
				{
					"label": "Latest news",
					"href": "/news"
				},
				{
					"label": "Lane reports",
					"href": "/news/lane-reports"
				}
			]
		},
		{
			"label": "Contact",
			"href": "/contact"
		}
	],
	"brandName": "Northpass",
	"cta": {
		"label": "Free Quote!",
		"href": "/request-quote"
	},
	"contacts": [
		{
			"icon": "phone",
			"label": "Phone: +1 (503) 555-0148",
			"href": "tel:+15035550148"
		},
		{
			"icon": "email",
			"label": "Email: dispatch@northpass.example",
			"href": "mailto:dispatch@northpass.example"
		},
		{
			"icon": "address",
			"label": "1180 Harbor Way, Portland OR",
			"href": "https://maps.google.com/?q=1180+Harbor+Way+Portland+OR"
		}
	],
	"social": [
		{
			"network": "facebook",
			"href": "https://facebook.com",
			"label": "Northpass on Facebook"
		},
		{
			"network": "x",
			"href": "https://x.com",
			"label": "Northpass on X"
		},
		{
			"network": "instagram",
			"href": "https://instagram.com",
			"label": "Northpass on Instagram"
		},
		{
			"network": "linkedin",
			"href": "https://linkedin.com",
			"label": "Northpass on LinkedIn"
		}
	],
	"currentPath": "/request-quote"
};

const breadcrumbPageBannerProps = {
	"image": {
		"src": "/breadcrumb-page-banner/ship-at-sea.webp",
		"alt": ""
	},
	"title": "Request Quote"
};

const shipmentQuoteFormProps = {
	"image": {
		"src": "/shipment-quote-form/truck-yard.webp",
		"alt": ""
	}
};

const newsletterPanelFooterProps = {
	"heading": "Subscribe Our Newsletter",
	"newsletter": {},
	"brandName": "Northpass",
	"blurb": "Freight moved on time, by people who answer the phone. Ocean, road, rail and storage, run from one dispatch desk since 2009.",
	"social": [
		{
			"network": "facebook",
			"href": "https://facebook.com",
			"label": "Northpass on Facebook"
		},
		{
			"network": "x",
			"href": "https://x.com",
			"label": "Northpass on X"
		},
		{
			"network": "instagram",
			"href": "https://instagram.com",
			"label": "Northpass on Instagram"
		},
		{
			"network": "linkedin",
			"href": "https://linkedin.com",
			"label": "Northpass on LinkedIn"
		}
	],
	"columns": [
		{
			"heading": "Quick Links",
			"links": [
				{
					"label": "Freight",
					"href": "/freight"
				},
				{
					"label": "Shipping",
					"href": "/shipping"
				},
				{
					"label": "About Northpass",
					"href": "/about"
				},
				{
					"label": "Our Team",
					"href": "/team"
				},
				{
					"label": "Lane Reports",
					"href": "/news"
				},
				{
					"label": "Pricing",
					"href": "/pricing"
				},
				{
					"label": "Contact Us",
					"href": "/contact"
				},
				{
					"label": "Client Stories",
					"href": "/stories"
				},
				{
					"label": "Case Studies",
					"href": "/case-studies"
				}
			]
		},
		{
			"heading": "Our Services",
			"links": [
				{
					"label": "Ocean Freight",
					"href": "/services/ocean-freight"
				},
				{
					"label": "Road Haulage",
					"href": "/services/road-haulage"
				},
				{
					"label": "Rail Freight",
					"href": "/services/rail-freight"
				},
				{
					"label": "Cross-Border Trucking",
					"href": "/services/cross-border"
				},
				{
					"label": "Less-Than-Truckload",
					"href": "/services/ltl"
				},
				{
					"label": "Warehousing",
					"href": "/services/warehousing"
				},
				{
					"label": "Cold-Chain Transport",
					"href": "/services/cold-chain"
				},
				{
					"label": "Project Cargo",
					"href": "/services/project-cargo"
				},
				{
					"label": "Customs Brokerage",
					"href": "/services/customs"
				}
			]
		}
	],
	"contacts": [
		{
			"icon": "email",
			"label": "dispatch@northpass.example",
			"href": "mailto:dispatch@northpass.example"
		},
		{
			"icon": "phone",
			"label": "+1 (503) 555-0148",
			"href": "tel:+15035550148"
		}
	],
	"copyright": "Copyright © {year} Northpass Logistics. All Rights Reserved."
};
---

<html lang="en">
	<head>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1" />
		<title>Request a Quote — [Business name]</title>
		<meta name="description" content="[One sentence inviting a shipper to request a rate, with the response time.]" />
		<style is:global>
			*,
			*::before,
			*::after {
				box-sizing: border-box;
			}

			body {
				margin: 0;
				background: #ffffff;
				font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
				color: #111;
			}

			.skip-link {
				position: absolute;
				left: 1rem;
				top: -100px;
				z-index: 1000;
				padding: 0.6rem 1rem;
				background: #111;
				color: #fff;
				border-radius: 6px;
			}

			.skip-link:focus {
				top: 1rem;
			}
		</style>
	</head>
	<body>
		<a class="skip-link" href="#main">Skip to content</a>

		<!-- Contact bar overlay header -->
		<ContactBarOverlayHeader {...contactBarOverlayHeaderProps} />

		<main id="main">
			<!-- Breadcrumb page banner -->
			<BreadcrumbPageBanner {...breadcrumbPageBannerProps} />

			<!-- Shipment quote form -->
			<ShipmentQuoteForm {...shipmentQuoteFormProps} />
		</main>

		<!-- Newsletter panel footer -->
		<NewsletterPanelFooter {...newsletterPanelFooterProps} />
	</body>
</html>

===== END src/pages/transport-request-quote.astro =====

===== FILE (required): src/components/ContactBarOverlayHeader.astro =====
---
/**
 * ContactBarOverlayHeader
 *
 * A two-tier header laid over a page banner: a 76px contact bar (phone,
 * email and address links with orange icons, "Follow Us:" and four social
 * circles), and under it a rounded, dark translucent nav bar inset from the
 * edges — logo, the same links and dropdowns as SolidBarHeader in white, and
 * an orange "Free Quote!" pill. Below 1200px the links fold into a menu
 * button and a navy card; below 810px the contact bar is hidden.
 *
 * `overlay` (default true) positions the root absolutely at the top of its
 * containing block, so the banner runs underneath it and it scrolls away
 * with the page. `overlay={false}` puts it in the flow on a navy band.
 *
 * Reads --tg-* design tokens from any ancestor, with fallbacks.
 */

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

export interface ContactBarOverlayHeaderItem {
	label: string;
	/** Ignored when `children` is present: the item becomes a dropdown trigger. */
	href?: string;
	/** Renders the item as a real <button> that opens a single-column panel. */
	children?: ContactBarOverlayHeaderLink[];
}

export interface ContactBarOverlayHeaderContact {
	icon: "phone" | "email" | "address";
	label: string;
	href: string;
}

export type ContactBarOverlayHeaderNetwork = "facebook" | "x" | "instagram" | "linkedin" | "youtube" | "link";

export interface ContactBarOverlayHeaderSocial {
	network: ContactBarOverlayHeaderNetwork;
	href: string;
	/** Accessible name, e.g. "Your Business on LinkedIn". */
	label: string;
}

export interface ContactBarOverlayHeaderProps {
	items?: ContactBarOverlayHeaderItem[];
	/** Pass Astro.url.pathname. Matching links get aria-current="page". */
	currentPath?: string;
	brandName?: string;
	brandHref?: string;
	/** Replaces the drawn mark and wordmark with an image (use a light logo). */
	logo?: { src: string; alt: string; width?: number; height?: number };
	cta?: { label: string; href: string } | null;
	/** The contact bar's links. `[]` with `social: []` drops the bar. */
	contacts?: ContactBarOverlayHeaderContact[];
	socialLabel?: string;
	social?: ContactBarOverlayHeaderSocial[];
	/** Absolute over the banner (true) or in the flow on a navy band (false). */
	overlay?: boolean;
	navLabel?: string;
	contactLabel?: string;
	menuLabel?: string;
	fontStylesheet?: string;
	id?: string;
}

interface Props extends ContactBarOverlayHeaderProps {}

/* No invented business: links, brand, CTA, contact details and social
   profiles are all the caller's. Each block renders only when supplied;
   with none of them, nothing renders. */
const {
	items = [],
	currentPath,
	brandName,
	brandHref = "/",
	logo,
	cta = null,
	contacts = [],
	socialLabel = "Follow Us :",
	social = [],
	overlay = true,
	navLabel = "Primary",
	contactLabel = "Contact details",
	menuLabel = "Menu",
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&family=DM+Sans:wght@400;500;600&display=swap",
	id,
} = Astro.props;

const seed = id ? id : `cbo-${Math.random().toString(36).slice(2, 8)}`;
const menuId = `${seed}-menu`;
const hasTopBar = contacts.length > 0 || social.length > 0;
const brand = brandName?.trim() ?? "";
const hasBrand = Boolean(logo || brand);
const hasItems = items.length > 0;
/* The phone menu carries the links, the CTA and the contact details. */
const hasMenu = hasItems || Boolean(cta) || contacts.length > 0;
const hasBar = hasBrand || hasMenu;
const hasContent = hasTopBar || hasBar;

const norm = (p: string) => (p.length > 1 ? p.replace(/\/+$/, "") : p);
const isCurrent = (href?: string) =>
	Boolean(href && currentPath && norm(href) === norm(currentPath));
const hasCurrentChild = (item: ContactBarOverlayHeaderItem) =>
	Boolean(item.children?.some((c) => isCurrent(c.href)));
const isExternal = (href: string) => /^https?:\/\//.test(href);

/* Contact-bar icons, drawn on a 16px grid. */
const CONTACT_ICONS: Record<ContactBarOverlayHeaderContact["icon"], string> = {
	phone:
		'<path d="M4.2 1.5 6.3 4a1 1 0 0 1-.1 1.3L5 6.5a8.4 8.4 0 0 0 4.5 4.5l1.2-1.2a1 1 0 0 1 1.3-.1l2.5 2.1a1 1 0 0 1 .1 1.4l-1 1.1c-.7.7-1.7.9-2.6.6A14 14 0 0 1 1.1 4.9c-.3-.9-.1-1.9.6-2.6l1-1a1 1 0 0 1 1.5.2z" fill="currentColor"/>',
	email:
		'<path d="M1 4.2A1.7 1.7 0 0 1 2.7 2.5h10.6A1.7 1.7 0 0 1 15 4.2v.3L8 9 1 4.5z" fill="currentColor"/><path d="M1 6.3l6.5 4.2a1 1 0 0 0 1 0L15 6.3v5.5a1.7 1.7 0 0 1-1.7 1.7H2.7A1.7 1.7 0 0 1 1 11.8z" fill="currentColor"/>',
	address:
		'<path d="M8 .8a5.6 5.6 0 0 0-5.6 5.6c0 4.1 5 8.5 5.2 8.7a.6.6 0 0 0 .8 0c.2-.2 5.2-4.6 5.2-8.7A5.6 5.6 0 0 0 8 .8zm0 7.9a2.3 2.3 0 1 1 0-4.6 2.3 2.3 0 0 1 0 4.6z" fill="currentColor"/>',
};

/* Generic social glyphs on a 24px grid. */
const GLYPHS: Record<ContactBarOverlayHeaderNetwork, string> = {
	facebook:
		'<path d="M13.6 21v-7.6h2.6l.4-3.1h-3V8.4c0-.9.3-1.5 1.5-1.5h1.6V4.1c-.3 0-1.2-.1-2.3-.1-2.3 0-3.9 1.4-3.9 4v2.3H7.9v3.1h2.6V21z" fill="currentColor"/>',
	x: '<path d="M4 4h4.6l11.4 16h-4.6z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/><path d="M19.4 4l-6.2 7M10.8 13l-6.2 7" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/>',
	instagram:
		'<rect x="4" y="4" width="16" height="16" rx="4.6" fill="none" stroke="currentColor" stroke-width="1.8"/><circle cx="12" cy="12" r="3.7" fill="none" stroke="currentColor" stroke-width="1.8"/><circle cx="16.6" cy="7.4" r="1.1" fill="currentColor"/>',
	linkedin:
		'<rect x="4.2" y="9.2" width="3.3" height="10.6" fill="currentColor"/><circle cx="5.85" cy="5.9" r="1.95" fill="currentColor"/><path d="M10 9.2h3.2v1.5c.5-.9 1.7-1.8 3.5-1.8 3.4 0 4 2.2 4 5.1v5.8h-3.3v-5.1c0-1.3 0-2.9-1.8-2.9s-2.1 1.4-2.1 2.8v5.2H10z" fill="currentColor"/>',
	youtube:
		'<rect x="3" y="6" width="18" height="12.5" rx="3.6" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M10.4 9.4v5.7l4.9-2.85z" fill="currentColor"/>',
	link: '<path d="M10 14l4-4M8.5 11.5 6.6 13.4a3.2 3.2 0 0 0 4.5 4.5l1.9-1.9M15.5 12.5l1.9-1.9a3.2 3.2 0 0 0-4.5-4.5L11 8" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>',
};
---

{hasContent && fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
{hasContent && (
<header
	class:list={["cbo", overlay ? "cbo--overlay" : "cbo--flow", !hasTopBar && "cbo--no-top"]}
	id={id}
	data-header-root
	data-cbo-root
>
	{
		hasTopBar && (
			<div class="cbo-top">
				{contacts.length > 0 && (
					<ul class="cbo-contacts" role="list" aria-label={contactLabel}>
						{contacts.map((c) => (
							<li class={`cbo-contact cbo-contact--${c.icon}`}>
								<a
									class="cbo-contact-link"
									href={c.href}
									target={isExternal(c.href) ? "_blank" : undefined}
									rel={isExternal(c.href) ? "noopener noreferrer" : undefined}
								>
									<svg class="cbo-contact-icon" viewBox="0 0 16 16" aria-hidden="true" focusable="false" set:html={CONTACT_ICONS[c.icon]} />
									<span>{c.label}</span>
								</a>
							</li>
						))}
					</ul>
				)}
				{social.length > 0 && (
					<div class="cbo-follow">
						{socialLabel && <span class="cbo-follow-label" aria-hidden="true">{socialLabel}</span>}
						<ul class="cbo-social" role="list">
							{social.map((s) => (
								<li>
									<a
										class="cbo-social-link"
										href={s.href}
										aria-label={s.label}
										target={isExternal(s.href) ? "_blank" : undefined}
										rel={isExternal(s.href) ? "noopener noreferrer" : undefined}
									>
										<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" set:html={GLYPHS[s.network] ?? GLYPHS.link} />
									</a>
								</li>
							))}
						</ul>
					</div>
				)}
			</div>
		)
	}

	{hasBar && (
	<div class="cbo-barwrap">
		<div class="cbo-bar">
			{hasBrand && (
			<a class="cbo-brand" href={brandHref} aria-label={logo ? undefined : `${brand} home`}>
				{
					logo ? (
						<img
							class="cbo-logo-img"
							src={logo.src}
							alt={logo.alt}
							width={logo.width ?? 157}
							height={logo.height ?? 30}
						/>
					) : (
						<>
							<svg class="cbo-mark" viewBox="0 0 46 30" aria-hidden="true" focusable="false">
								<path d="M2 9h11M5 14h9M1 19h13" fill="none" stroke="var(--cbo-orange)" stroke-width="2.6" stroke-linecap="round" />
								<rect x="14" y="4" width="17" height="15" rx="2" fill="currentColor" />
								<path d="M32 8h6.2l5.3 5.6V19H32z" fill="currentColor" />
								<path d="M34.4 10.4h3.2l3 3.2h-6.2z" fill="var(--cbo-orange)" />
								<circle cx="20" cy="21.5" r="3.4" fill="currentColor" stroke="var(--cbo-navy)" stroke-width="1.6" />
								<circle cx="37.5" cy="21.5" r="3.4" fill="currentColor" stroke="var(--cbo-navy)" stroke-width="1.6" />
								<path d="M4 27.5h40" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" />
							</svg>
							<span class="cbo-wordmark" aria-hidden="true">{brand}</span>
						</>
					)
				}
			</a>
			)}

			{hasItems && (
			<nav class="cbo-nav" aria-label={navLabel}>
				<ul class="cbo-list" role="list">
					{
						items.map((item, i) => {
							if (item.children && item.children.length > 0) {
								const panelId = `${seed}-panel-${i}`;
								return (
									<li class="cbo-item cbo-item--dd" data-cbo-dd>
										<button
											type="button"
											class="cbo-link cbo-trigger"
											aria-expanded="false"
											aria-controls={panelId}
											data-cbo-trigger
											data-current={hasCurrentChild(item) ? "" : undefined}
										>
											{item.label}
											<svg class="cbo-plus" viewBox="0 0 11 11" aria-hidden="true" focusable="false">
												<path d="M5.5 1v9M1 5.5h9" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" />
											</svg>
										</button>
										<div class="cbo-panel" id={panelId} data-cbo-panel>
											<ul role="list">
												{item.children.map((child) => (
													<li>
														<a
															class="cbo-panel-link"
															href={child.href}
															aria-current={isCurrent(child.href) ? "page" : undefined}
														>
															{child.label}
														</a>
													</li>
												))}
											</ul>
										</div>
									</li>
								);
							}
							return (
								<li class="cbo-item">
									<a
										class="cbo-link"
										href={item.href ?? "#"}
										aria-current={isCurrent(item.href) ? "page" : undefined}
									>
										{item.label}
									</a>
								</li>
							);
						})
					}
				</ul>
			</nav>
			)}

			{
				cta && (
					<a class="cbo-cta" href={cta.href}>
						<span>{cta.label}</span>
						<svg class="cbo-arrow" viewBox="0 0 12 12" aria-hidden="true" focusable="false">
							<path d="M2 10L10 2M3.5 2H10v6.5" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" />
						</svg>
					</a>
				)
			}

			{hasMenu && (
			<button
				type="button"
				class="cbo-burger"
				aria-expanded="false"
				aria-controls={menuId}
				data-cbo-burger
			>
				<span class="cbo-sr">{menuLabel}</span>
				<span class="cbo-burger-lines" aria-hidden="true"><span></span><span></span><span></span></span>
			</button>
			)}
		</div>

		{hasMenu && (
		<nav class="cbo-menu" id={menuId} aria-label={navLabel} data-cbo-menu>
			{hasItems && (
			<ul class="cbo-menu-list" role="list">
				{
					items.map((item, i) =>
						item.children && item.children.length > 0 ? (
							<li class="cbo-menu-group" data-cbo-sub>
								<button
									type="button"
									class="cbo-menu-link cbo-menu-toggle"
									aria-expanded="true"
									aria-controls={`${seed}-sub-${i}`}
									data-cbo-subtoggle
									data-current={hasCurrentChild(item) ? "" : undefined}
								>
									{item.label}
									<svg class="cbo-plus" viewBox="0 0 11 11" aria-hidden="true" focusable="false">
										<path d="M5.5 1v9M1 5.5h9" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" />
									</svg>
								</button>
								<ul class="cbo-sublist" id={`${seed}-sub-${i}`} role="list" data-cbo-sublist>
									{item.children.map((child) => (
										<li>
											<a
												class="cbo-menu-link cbo-menu-sublink"
												href={child.href}
												aria-current={isCurrent(child.href) ? "page" : undefined}
											>
												{child.label}
											</a>
										</li>
									))}
								</ul>
							</li>
						) : (
							<li>
								<a
									class="cbo-menu-link"
									href={item.href ?? "#"}
									aria-current={isCurrent(item.href) ? "page" : undefined}
								>
									{item.label}
								</a>
							</li>
						),
					)
				}
			</ul>
			)}
			{
				cta && (
					<a class="cbo-cta cbo-cta--menu" href={cta.href}>
						<span>{cta.label}</span>
						<svg class="cbo-arrow" viewBox="0 0 12 12" aria-hidden="true" focusable="false">
							<path d="M2 10L10 2M3.5 2H10v6.5" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" />
						</svg>
					</a>
				)
			}
			{
				contacts.length > 0 && (
					<ul class="cbo-menu-contacts" role="list" aria-label={contactLabel}>
						{contacts.map((c) => (
							<li>
								<a class="cbo-contact-link" href={c.href}>
									<svg class="cbo-contact-icon" viewBox="0 0 16 16" aria-hidden="true" focusable="false" set:html={CONTACT_ICONS[c.icon]} />
									<span>{c.label}</span>
								</a>
							</li>
						))}
					</ul>
				)
			}
		</nav>
		)}
	</div>
	)}
</header>
)}

<style>
	.cbo {
		--cbo-navy: var(--tg-navy, #020e28);
		--cbo-orange: var(--tg-orange, #fd5523);
		--cbo-muted: var(--tg-muted, #61697d);
		--cbo-display: var(--tg-font-display, Outfit, ui-sans-serif, system-ui, sans-serif);
		--cbo-body: var(--tg-font-body, "DM Sans", ui-sans-serif, system-ui, sans-serif);
		--cbo-dd-hover: #ff6060;
		--cbo-dur: 0.3s;
		--cbo-ease: cubic-bezier(0.44, 0, 0.56, 1);

		z-index: 30;
		box-sizing: border-box;
		width: 100%;
		font-family: var(--cbo-body);
		color: #fff;
	}

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

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

	.cbo--overlay {
		position: absolute;
		inset: 0 0 auto 0;
		background: transparent;
	}

	.cbo--flow {
		position: relative;
		background: var(--cbo-navy);
		padding-bottom: 24px;
	}

	/* ---- contact bar ---- */
	.cbo-top {
		display: none;
	}

	.cbo .cbo-contacts {
		display: flex;
		align-items: center;
		gap: 16px;
		min-width: 0;
	}

	.cbo-contact-link {
		display: inline-flex;
		align-items: center;
		gap: 15px;
		min-height: 44px;
		color: #fff;
		font: 400 16px/21px var(--cbo-body);
		text-decoration: none;
		white-space: nowrap;
		border-radius: 4px;
		transition: color var(--cbo-dur) var(--cbo-ease);
	}

	.cbo-contact-link:hover {
		color: var(--cbo-orange);
	}

	.cbo-contact-icon {
		width: 16px;
		height: 16px;
		flex: none;
		color: var(--cbo-orange);
	}

	.cbo-follow {
		display: flex;
		align-items: center;
		gap: 16px;
		flex: none;
	}

	.cbo-follow-label {
		font: 700 16px/19px var(--cbo-display);
		white-space: nowrap;
	}

	.cbo .cbo-social {
		display: flex;
		align-items: center;
		gap: 16px;
	}

	.cbo-social-link {
		position: relative;
		display: grid;
		place-items: center;
		width: 36px;
		height: 36px;
		border-radius: 100px;
		background: rgb(255 255 255 / 0.15);
		color: #fff;
		transition: background-color var(--cbo-dur) var(--cbo-ease);
	}

	/* 36px circle as measured, 44px target. */
	.cbo-social-link::after {
		content: "";
		position: absolute;
		inset: -4px;
		border-radius: inherit;
	}

	.cbo-social-link svg {
		width: 15px;
		height: 15px;
	}

	.cbo-social-link:hover {
		background: var(--cbo-orange);
	}

	/* ---- the glass nav bar ---- */
	.cbo-barwrap {
		position: relative;
		padding: 24px 20px 0;
	}

	.cbo-bar {
		position: relative;
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: 16px;
		min-height: 76px;
		padding: 0 20px 0 24px;
		border-radius: 1000px;
		/* rgba(2,14,40,.8) at opacity .7, as measured, flattened into one fill
		   so the bar's contents stay fully opaque. */
		background: rgb(2 14 40 / 0.56);
		-webkit-backdrop-filter: blur(6px);
		backdrop-filter: blur(6px);
	}

	.cbo-brand {
		display: inline-flex;
		align-items: center;
		gap: 9px;
		min-height: 44px;
		color: #fff;
		text-decoration: none;
		border-radius: 6px;
		flex: none;
	}

	.cbo-mark {
		width: 46px;
		height: 30px;
		flex: none;
	}

	.cbo-wordmark {
		font: 600 24px/1 var(--cbo-display);
		letter-spacing: -0.01em;
	}

	.cbo-logo-img {
		display: block;
		height: 30px;
		width: auto;
	}

	.cbo-nav {
		display: none;
	}

	.cbo .cbo-list {
		display: flex;
		align-items: center;
		gap: clamp(28px, calc(5vw - 30px), 42px);
		padding: 9px 20px;
	}

	.cbo-item {
		position: relative;
	}

	.cbo-link {
		display: inline-flex;
		align-items: center;
		gap: 1px;
		padding: 9px 6px;
		margin: -9px -6px;
		border: 0;
		border-radius: 6px;
		background: none;
		color: #fff;
		font: 500 16px/26px var(--cbo-display);
		text-decoration: none;
		white-space: nowrap;
		cursor: pointer;
		transition: color var(--cbo-dur) var(--cbo-ease);
	}

	.cbo-link:hover,
	.cbo-link[aria-current="page"] {
		color: var(--cbo-orange);
	}

	.cbo-trigger:hover,
	.cbo-trigger[aria-expanded="true"],
	.cbo-trigger[data-current] {
		color: var(--cbo-dd-hover);
	}

	.cbo-plus {
		width: 11px;
		height: 11px;
		flex: none;
		transition: transform var(--cbo-dur) var(--cbo-ease);
	}

	.cbo-trigger:hover .cbo-plus,
	.cbo-trigger[aria-expanded="true"] .cbo-plus {
		transform: rotate(180deg);
	}

	.cbo-panel {
		position: absolute;
		top: calc(100% + 34px);
		left: -22px;
		min-width: 220px;
		padding: 14px 22px;
		border-radius: 10px;
		background: #fff;
		box-shadow: 0 18px 44px rgb(2 14 40 / 0.22);
		border-top: 3px solid var(--cbo-orange);
		opacity: 0;
		transform: translateY(10px);
		visibility: hidden;
		pointer-events: none;
		transition:
			opacity var(--cbo-dur) var(--cbo-ease),
			transform var(--cbo-dur) var(--cbo-ease),
			visibility 0s linear var(--cbo-dur);
		z-index: 2;
	}

	.cbo-panel::before {
		content: "";
		position: absolute;
		inset: -37px 0 100% 0;
	}

	.cbo:not([data-cbo-ready]) .cbo-item--dd:hover .cbo-panel,
	.cbo:not([data-cbo-ready]) .cbo-item--dd:focus-within .cbo-panel,
	.cbo[data-cbo-ready] .cbo-item--dd[data-open] .cbo-panel {
		opacity: 1;
		transform: none;
		visibility: visible;
		pointer-events: auto;
		transition:
			opacity var(--cbo-dur) var(--cbo-ease),
			transform var(--cbo-dur) var(--cbo-ease),
			visibility 0s;
	}

	.cbo-panel-link {
		display: flex;
		align-items: center;
		min-height: 40px;
		color: var(--cbo-navy);
		font: 500 16px/1.3 var(--cbo-display);
		text-decoration: none;
		white-space: nowrap;
		border-radius: 4px;
		transition:
			color var(--cbo-dur) var(--cbo-ease),
			transform var(--cbo-dur) var(--cbo-ease);
	}

	.cbo-panel-link:hover,
	.cbo-panel-link[aria-current="page"] {
		color: var(--cbo-orange);
	}

	.cbo-panel-link:hover {
		transform: translateX(4px);
	}

	/* ---- Free Quote pill ---- */
	.cbo-cta {
		display: none;
		align-items: center;
		gap: 10px;
		min-height: 56px;
		padding: 0 30px;
		border-radius: 100px;
		background: var(--cbo-orange);
		color: #fff;
		font: 600 14px/1 var(--cbo-body);
		text-decoration: none;
		white-space: nowrap;
		flex: none;
		transition: background-color var(--cbo-dur) var(--cbo-ease);
	}

	.cbo-arrow {
		width: 12px;
		height: 12px;
		flex: none;
		transition: transform var(--cbo-dur) var(--cbo-ease);
	}

	.cbo-cta:hover {
		background: var(--cbo-muted);
	}

	.cbo-cta:hover .cbo-arrow {
		transform: rotate(45deg);
	}

	/* ---- burger ---- */
	.cbo-burger {
		display: grid;
		place-items: center;
		width: 44px;
		height: 44px;
		padding: 0;
		border: 0;
		border-radius: 8px;
		background: none;
		color: #fff;
		cursor: pointer;
	}

	.cbo:not([data-cbo-ready]) .cbo-burger {
		display: none;
	}

	.cbo-burger-lines {
		position: relative;
		display: block;
		width: 24px;
		height: 18px;
	}

	.cbo-burger-lines span {
		position: absolute;
		left: 0;
		width: 24px;
		height: 2px;
		border-radius: 1px;
		background: currentColor;
		transition:
			transform var(--cbo-dur) var(--cbo-ease),
			opacity var(--cbo-dur) var(--cbo-ease);
	}

	.cbo-burger-lines span:nth-child(1) { top: 0; }
	.cbo-burger-lines span:nth-child(2) { top: 8px; }
	.cbo-burger-lines span:nth-child(3) { top: 16px; }

	.cbo[data-cbo-open] .cbo-burger-lines span:nth-child(1) { transform: translateY(8px) rotate(45deg); }
	.cbo[data-cbo-open] .cbo-burger-lines span:nth-child(2) { opacity: 0; }
	.cbo[data-cbo-open] .cbo-burger-lines span:nth-child(3) { transform: translateY(-8px) rotate(-45deg); }

	/* ---- small-screen menu card ---- */
	.cbo-menu {
		margin: 12px 0 0;
		padding: 12px 22px 22px;
		border-radius: 20px;
		background: var(--cbo-navy);
		border: 1px solid rgb(255 255 255 / 0.12);
		box-shadow: 0 22px 50px rgb(0 0 0 / 0.35);
		transition:
			opacity var(--cbo-dur) var(--cbo-ease),
			transform var(--cbo-dur) var(--cbo-ease),
			visibility 0s linear var(--cbo-dur);
	}

	.cbo[data-cbo-ready] .cbo-menu {
		position: absolute;
		top: calc(100% + 10px);
		left: 20px;
		right: 20px;
		margin: 0;
		max-height: calc(100vh - 140px);
		max-height: calc(100dvh - 140px);
		overflow-y: auto;
	}

	.cbo[data-cbo-ready]:not([data-cbo-open]) .cbo-menu {
		opacity: 0;
		transform: translateY(-8px);
		visibility: hidden;
		pointer-events: none;
	}

	.cbo[data-cbo-ready][data-cbo-open] .cbo-menu {
		transition:
			opacity var(--cbo-dur) var(--cbo-ease),
			transform var(--cbo-dur) var(--cbo-ease),
			visibility 0s;
	}

	.cbo-menu-list > li + li {
		border-top: 1px solid rgb(255 255 255 / 0.1);
	}

	.cbo-menu-link {
		display: flex;
		align-items: center;
		justify-content: space-between;
		width: 100%;
		min-height: 48px;
		padding: 0;
		border: 0;
		background: none;
		color: #fff;
		font: 500 17px/1.2 var(--cbo-display);
		text-align: left;
		text-decoration: none;
		cursor: pointer;
		border-radius: 4px;
		transition: color var(--cbo-dur) var(--cbo-ease);
	}

	.cbo-menu-link:hover,
	.cbo-menu-link[aria-current="page"] {
		color: var(--cbo-orange);
	}

	.cbo-menu-toggle:hover,
	.cbo-menu-toggle[data-current] {
		color: var(--cbo-dd-hover);
	}

	.cbo-menu-toggle .cbo-plus {
		width: 13px;
		height: 13px;
	}

	.cbo-menu-toggle[aria-expanded="true"] .cbo-plus {
		transform: rotate(45deg);
	}

	.cbo .cbo-sublist {
		padding: 0 0 10px 14px;
		margin-left: 2px;
		border-left: 2px solid rgb(255 255 255 / 0.14);
	}

	.cbo-sublist[hidden] {
		display: none;
	}

	.cbo-menu-sublink {
		min-height: 44px;
		color: #b0b2b8;
		font: 400 16px/1.3 var(--cbo-body);
	}

	.cbo-cta--menu {
		display: flex;
		justify-content: center;
		margin-top: 14px;
	}

	.cbo .cbo-menu-contacts {
		margin-top: 14px;
		padding-top: 8px;
		border-top: 1px solid rgb(255 255 255 / 0.1);
	}

	.cbo-menu-contacts .cbo-contact-link {
		gap: 12px;
		font-size: 15px;
		white-space: normal;
	}

	/* ---- focus ---- */
	.cbo a:focus-visible,
	.cbo button:focus-visible {
		outline: 2px solid var(--cbo-orange);
		outline-offset: 3px;
	}

	.cbo-cta:focus-visible {
		outline-color: #fff;
	}

	.cbo-panel a:focus-visible {
		outline-color: var(--cbo-orange);
	}

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

	/* ---- tablet and up: the contact bar appears ---- */
	@media (min-width: 810px) {
		.cbo-top {
			display: flex;
			align-items: center;
			justify-content: space-between;
			gap: 24px;
			min-height: 76px;
			padding: 19px 20px 21px;
		}

		/* The nav bar tucks 12px up into the contact bar, as measured
		   (bar at 64px under a 76px contact bar). */
		.cbo-barwrap {
			padding: 0 24px;
			margin-top: -12px;
		}

		.cbo--no-top .cbo-barwrap {
			margin-top: 0;
			padding-top: 24px;
		}

		.cbo-bar {
			min-height: 100px;
			padding: 22px 24px;
		}

		.cbo[data-cbo-ready] .cbo-menu {
			left: 24px;
			right: 24px;
			max-width: 480px;
			left: auto;
		}
	}

	@media (min-width: 810px) and (max-width: 1099.98px) {
		.cbo-contact--address {
			display: none;
		}
	}

	@media (min-width: 810px) and (max-width: 959.98px) {
		.cbo-follow-label {
			display: none;
		}
	}

	/* ---- desktop: links and pill appear, the menu goes ---- */
	@media (min-width: 1200px) {
		.cbo-brand {
			padding-right: clamp(24px, calc(20vw - 208px), 80px);
			box-sizing: content-box;
		}

		.cbo-nav {
			display: block;
			flex: 1 1 auto;
			min-width: 0;
		}

		.cbo-cta {
			display: inline-flex;
		}

		.cbo-burger,
		.cbo .cbo-menu {
			display: none;
		}
	}

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

		.cbo-trigger:hover .cbo-plus,
		.cbo-trigger[aria-expanded="true"] .cbo-plus,
		.cbo-cta:hover .cbo-arrow,
		.cbo-panel-link:hover {
			transform: none;
		}
	}

	@media (forced-colors: active) {
		.cbo-bar,
		.cbo-panel,
		.cbo-menu,
		.cbo-cta,
		.cbo-social-link {
			border: 1px solid CanvasText;
		}
	}
</style>

<script>
	/**
	 * ContactBarOverlayHeader controller. Same behaviour as SolidBarHeader,
	 * deliberately duplicated so each header is one self-contained file.
	 */
	function initContactBarOverlayHeader(root: HTMLElement) {
		if (root.dataset.cboReady === "") return;

		const burger = root.querySelector<HTMLButtonElement>("[data-cbo-burger]");
		const menu = root.querySelector<HTMLElement>("[data-cbo-menu]");
		const dropdowns = [...root.querySelectorAll<HTMLElement>("[data-cbo-dd]")];
		const subs = [...root.querySelectorAll<HTMLElement>("[data-cbo-sub]")];

		root.dataset.cboReady = "";
		const controller = new AbortController();
		const { signal } = controller;
		const desktop = window.matchMedia("(min-width: 1200px)");
		const finePointer = window.matchMedia("(hover: hover) and (pointer: fine)");

		let openDd: HTMLElement | null = null;
		let openedByHover = false;
		let hoverTimer = 0;

		const triggerOf = (dd: HTMLElement) =>
			dd.querySelector<HTMLButtonElement>("[data-cbo-trigger]");

		function closeDropdown(returnFocus = false) {
			window.clearTimeout(hoverTimer);
			if (!openDd) return;
			const dd = openDd;
			openDd = null;
			openedByHover = false;
			dd.removeAttribute("data-open");
			const trigger = triggerOf(dd);
			trigger?.setAttribute("aria-expanded", "false");
			if (returnFocus) trigger?.focus({ preventScroll: true });
		}

		function openDropdown(dd: HTMLElement, byHover = false) {
			window.clearTimeout(hoverTimer);
			if (openDd === dd) return;
			closeDropdown();
			openDd = dd;
			openedByHover = byHover;
			dd.setAttribute("data-open", "");
			triggerOf(dd)?.setAttribute("aria-expanded", "true");
		}

		dropdowns.forEach((dd) => {
			const trigger = triggerOf(dd);
			trigger?.addEventListener(
				"click",
				() => {
					if (openDd === dd && openedByHover) {
						openedByHover = false;
						return;
					}
					if (openDd === dd) closeDropdown();
					else openDropdown(dd);
				},
				{ signal },
			);
			dd.addEventListener(
				"pointerenter",
				(event) => {
					if (event.pointerType !== "mouse" || !finePointer.matches) return;
					if (openDd && openDd !== dd && !openedByHover) return;
					openDropdown(dd, true);
				},
				{ signal },
			);
			dd.addEventListener(
				"pointerleave",
				(event) => {
					if (event.pointerType !== "mouse" || openDd !== dd || !openedByHover) return;
					window.clearTimeout(hoverTimer);
					hoverTimer = window.setTimeout(() => {
						if (openDd === dd && openedByHover && !dd.contains(document.activeElement)) closeDropdown();
					}, 160);
				},
				{ signal },
			);
			dd.addEventListener(
				"focusout",
				(event) => {
					const next = event.relatedTarget as Node | null;
					if (openDd === dd && next && !dd.contains(next)) closeDropdown();
				},
				{ signal },
			);
		});

		let menuOpen = false;

		function setMenu(next: boolean, returnFocus = false) {
			if (!burger || !menu || menuOpen === next) return;
			menuOpen = next;
			root.toggleAttribute("data-cbo-open", next);
			burger.setAttribute("aria-expanded", String(next));
			if (!next && returnFocus) burger.focus({ preventScroll: true });
		}

		subs.forEach((sub) => {
			const toggle = sub.querySelector<HTMLButtonElement>("[data-cbo-subtoggle]");
			const list = sub.querySelector<HTMLElement>("[data-cbo-sublist]");
			if (!toggle || !list) return;
			const set = (open: boolean) => {
				toggle.setAttribute("aria-expanded", String(open));
				list.hidden = !open;
			};
			set(false);
			toggle.addEventListener(
				"click",
				() => set(toggle.getAttribute("aria-expanded") !== "true"),
				{ signal },
			);
			signal.addEventListener("abort", () => set(true));
		});

		burger?.addEventListener("click", () => setMenu(!menuOpen), { signal });

		menu?.addEventListener(
			"click",
			(event) => {
				if ((event.target as HTMLElement).closest("a[href]")) setMenu(false);
			},
			{ signal },
		);

		document.addEventListener(
			"keydown",
			(event) => {
				if (event.key !== "Escape") return;
				if (openDd) {
					event.preventDefault();
					const inside = openDd.contains(document.activeElement);
					closeDropdown(inside || document.activeElement === document.body);
				} else if (menuOpen) {
					event.preventDefault();
					setMenu(false, true);
				}
			},
			{ signal },
		);

		document.addEventListener(
			"pointerdown",
			(event) => {
				const target = event.target as Node;
				if (openDd && !openDd.contains(target)) closeDropdown();
				if (menuOpen && !root.contains(target)) setMenu(false);
			},
			{ signal },
		);

		menu?.addEventListener(
			"focusout",
			(event) => {
				const next = event.relatedTarget as Node | null;
				if (menuOpen && next && !root.contains(next)) setMenu(false);
			},
			{ signal },
		);

		desktop.addEventListener(
			"change",
			() => {
				setMenu(false);
				closeDropdown();
			},
			{ signal },
		);

		function destroy() {
			closeDropdown();
			setMenu(false);
			controller.abort();
			delete root.dataset.cboReady;
		}

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

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

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

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

===== END src/components/ContactBarOverlayHeader.astro =====

===== FILE (required): src/components/BreadcrumbPageBanner.astro =====
---
/**
 * BreadcrumbPageBanner
 *
 * A 550px full-bleed page banner for inner pages: a photo under a navy
 * wash, a big white page title and a breadcrumb trail ("Home » About Us")
 * with orange separators. The top of the banner is left clear for an
 * overlay header (ContactBarOverlayHeader is 164px tall at desktop), so the
 * title sits in the lower half.
 *
 * Static: no script. Reads --tg-* design tokens from any ancestor.
 */

export interface BreadcrumbPageBannerCrumb {
	label: string;
	/** Omit on the last crumb, which is the current page. */
	href?: string;
}

export interface BreadcrumbPageBannerProps {
	title?: string;
	/** The trail. The last crumb is the current page and gets aria-current. Defaults to Home » title. */
	crumbs?: BreadcrumbPageBannerCrumb[];
	/** `src: ""` renders a labelled placeholder instead of the photo. */
	image?: { src?: string; alt?: string };
	headingLevel?: "h1" | "h2";
	breadcrumbLabel?: string;
	fontStylesheet?: string;
	id?: string;
}

interface Props extends BreadcrumbPageBannerProps {}

/* No photo by default: without one the design's labelled placeholder shows. */
const DEFAULT_IMAGE = {
	src: "",
	alt: "",
};

const {
	title = "",
	crumbs,
	image = {},
	headingLevel = "h1",
	breadcrumbLabel = "Breadcrumb",
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&family=DM+Sans:wght@400;500;600&display=swap",
	id,
} = Astro.props;

const given = (crumbs ?? []).filter((c) => c?.label);
const trail: BreadcrumbPageBannerCrumb[] =
	given.length > 0 ? given : [{ label: "Home", href: "/" }, { label: title }];
const photo = { ...DEFAULT_IMAGE, ...image };
const Heading = headingLevel;
---

{title && (
<>
{fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
<section class="bpb" id={id} data-bpb-root>
	<div class="bpb-bg" aria-hidden={photo.src && photo.alt ? undefined : "true"}>
		{
			photo.src ? (
				<img
					class="bpb-img"
					src={photo.src}
					alt={photo.alt ?? ""}
					width="2000"
					height="770"
					loading="eager"
					fetchpriority="high"
					decoding="async"
				/>
			) : (
				<div class="bpb-ph">
					<span class="bpb-ph-label">Image</span>
					<span class="bpb-ph-hint">Wide banner photo</span>
					<span class="bpb-ph-spec">2000 × 770 · 26:10</span>
				</div>
			)
		}
		<div class="bpb-wash"></div>
	</div>

	<div class="bpb-inner">
		<Heading class="bpb-title">{title}</Heading>
		<nav class="bpb-crumbs" aria-label={breadcrumbLabel}>
			<ol role="list">
				{
					trail.map((crumb, i) => {
						const last = i === trail.length - 1;
						return (
							<li>
								{i > 0 && (
									<svg class="bpb-sep" viewBox="0 0 18 14" aria-hidden="true" focusable="false">
										<path d="M2 1.5 7.5 7 2 12.5M9.5 1.5 15 7l-5.5 5.5" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round" />
									</svg>
								)}
								{last ? (
									<span class="bpb-current" aria-current="page">{crumb.label}</span>
								) : crumb.href ? (
									<a class="bpb-link" href={crumb.href}>{crumb.label}</a>
								) : (
									<span class="bpb-plain">{crumb.label}</span>
								)}
							</li>
						);
					})
				}
			</ol>
		</nav>
	</div>
</section>
</>
)}

<style>
	.bpb {
		--bpb-navy: var(--tg-navy, #020e28);
		--bpb-orange: var(--tg-orange, #fd5523);
		--bpb-display: var(--tg-font-display, Outfit, ui-sans-serif, system-ui, sans-serif);
		--bpb-body: var(--tg-font-body, "DM Sans", ui-sans-serif, system-ui, sans-serif);

		position: relative;
		isolation: isolate;
		box-sizing: border-box;
		display: flex;
		align-items: flex-end;
		width: 100%;
		min-height: 255px;
		padding: 124px 20px 56px;
		overflow: hidden;
		background: var(--bpb-navy);
		color: #fff;
		font-family: var(--bpb-body);
	}

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

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

	.bpb-img {
		display: block;
		width: 100%;
		height: 100%;
		object-fit: cover;
		object-position: 60% 50%;
	}

	/* The measured stack is a navy layer plus a 60% navy layer over the
	   photo. The photo still reads through in the capture, so the solid layer
	   is treated as a blend; this gradient reproduces the result — deeper on
	   the title side, lighter over the ship. */
	.bpb-wash {
		position: absolute;
		inset: 0;
		background:
			linear-gradient(90deg, rgb(2 14 40 / 0.72) 0%, rgb(2 14 40 / 0.5) 55%, rgb(2 14 40 / 0.38) 100%),
			linear-gradient(180deg, rgb(2 14 40 / 0.25) 0%, rgb(2 14 40 / 0) 40%, rgb(2 14 40 / 0.2) 100%);
		mix-blend-mode: normal;
	}

	.bpb-wash::before {
		content: "";
		position: absolute;
		inset: 0;
		background: rgb(8 36 110 / 0.35);
		mix-blend-mode: multiply;
	}

	.bpb-ph {
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
		gap: 8px;
		width: 100%;
		height: 100%;
		padding: 16px;
		text-align: center;
		background: repeating-linear-gradient(135deg, #1a2742 0 12px, #213050 12px 24px);
		color: rgb(255 255 255 / 0.62);
	}

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

	.bpb-ph-hint {
		max-width: 28ch;
		font: 500 14px/1.4 var(--bpb-body);
	}

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

	.bpb-inner {
		width: 100%;
		max-width: 1224px;
		margin: 0 auto;
	}

	.bpb-title {
		margin: 0;
		color: #fff;
		font: 700 40px/1.3 var(--bpb-display);
		letter-spacing: normal;
		overflow-wrap: anywhere;
	}

	.bpb-crumbs ol {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		margin: 0;
		padding: 0;
		list-style: none;
	}

	.bpb-crumbs li {
		display: inline-flex;
		align-items: center;
		gap: 8px;
		margin-right: 8px;
		font: 500 16px/28px var(--bpb-display);
	}

	.bpb-sep {
		width: 18px;
		height: 14px;
		flex: none;
		color: var(--bpb-orange);
	}

	.bpb-link {
		display: inline-flex;
		align-items: center;
		min-height: 44px;
		margin: -8px 0;
		color: #fff;
		text-decoration: none;
		border-radius: 4px;
		transition: color 0.4s cubic-bezier(0.44, 0, 0.56, 1);
	}

	.bpb-link:hover {
		color: var(--bpb-orange);
	}

	.bpb-link:focus-visible {
		outline: 2px solid var(--bpb-orange);
		outline-offset: 3px;
	}

	.bpb-current,
	.bpb-plain {
		color: #fff;
	}

	/* ---- tablet ---- */
	@media (min-width: 810px) {
		.bpb {
			min-height: 440px;
			padding: 240px 30px 100px;
		}

		.bpb-title {
			font-size: 52px;
			line-height: 1.4;
		}

		.bpb-crumbs li {
			font-size: 18px;
		}
	}

	/* ---- desktop: 550px, content from 300px ---- */
	@media (min-width: 1200px) {
		.bpb {
			min-height: 550px;
			padding: 300px 20px 138px;
		}

		/* Step 39.4: --tg-grid-max / --tg-grid-gutter put the title and crumbs on a site grid at desktop; the fallbacks give the original 1224px row. */
		.bpb-inner {
			--bpb-grid-max: var(--tg-grid-max, 1264px);
			--bpb-grid-gutter: var(--tg-grid-gutter, 20px);
			max-width: calc(var(--bpb-grid-max) - 2 * var(--bpb-grid-gutter));
		}

		.bpb-title {
			font-size: 60px;
			line-height: 84px;
		}
	}

	@media (prefers-reduced-motion: reduce) {
		.bpb-link {
			transition: none;
		}
	}
</style>

===== END src/components/BreadcrumbPageBanner.astro =====

===== FILE (required): public/breadcrumb-page-banner/ship-at-sea.webp =====
[binary asset — download https://astro.baysixmedia.com/breadcrumb-page-banner/ship-at-sea.webp and save it to public/breadcrumb-page-banner/ship-at-sea.webp]

===== FILE (required): src/components/ShipmentQuoteForm.astro =====
---
/**
 * ShipmentQuoteForm
 *
 * A freight quote request in one rounded, hairline-bordered card: an optional
 * portrait photo on the left and a real form on the right, in two fieldsets —
 * "Personal Info" (name, email, phone) and "Shipment Info" (freight type,
 * city of departure, delivery city, incoterms, weight, height, width,
 * length) — separated by hairlines, then an orange "Request A Quote ↗" pill.
 *
 * Every field is a pill with a small orange icon or chevron. The design shows
 * placeholders only, so every control also has a real, visually hidden
 * <label>. `action` is unset by default: point it at your handler.
 *
 * Appear on scroll: the photo slides in from −40px, the two fieldsets and the
 * button rise 40px, staggered 0.1s. Without JS or under reduced motion
 * everything is at rest.
 *
 * Reads --tg-* design tokens from any ancestor, with fallbacks.
 */

export interface ShipmentQuoteLabels {
	name?: string;
	email?: string;
	phone?: string;
	freightType?: string;
	origin?: string;
	destination?: string;
	incoterm?: string;
	weight?: string;
	height?: string;
	width?: string;
	length?: string;
}

export interface ShipmentQuoteFormProps {
	/** Where the form posts. Unset: the browser submits to the current URL. */
	action?: string;
	method?: "get" | "post";
	formName?: string;
	personalLegend?: string;
	shipmentLegend?: string;
	submitLabel?: string;
	/** Freight type options (required select). `[]` drops the select. */
	freightTypes?: string[];
	/** Incoterm options (optional select). `[]` drops the select. */
	incoterms?: string[];
	/** Accessible labels (visually hidden). */
	labels?: ShipmentQuoteLabels;
	/** Placeholders — hints only, never the label. */
	placeholders?: ShipmentQuoteLabels;
	/** The portrait photo. Unset or `null`: none, and the form spans the card. `src: ""` renders the labelled placeholder. */
	image?: { src?: string; alt?: string } | null;
	/** The band around the card. */
	background?: string;
	fontStylesheet?: string;
	class?: string;
	/** Root id. Also seeds every field id. */
	id?: string;
}

interface Props extends ShipmentQuoteFormProps {}

const {
	action,
	method = "post",
	formName,
	personalLegend = "Personal Info",
	shipmentLegend = "Shipment Info",
	submitLabel = "Request A Quote",
	freightTypes = ["Air Freight", "Sea Freight", "Road Freight", "Rail Freight", "Warehousing"],
	incoterms = ["EXW", "FCA", "FOB", "CFR", "CIF", "DAP", "DDP"],
	labels = {},
	placeholders = {},
	image: imageProp,
	background = "#ffffff",
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&family=DM+Sans:wght@400;500;600&display=swap",
	class: className,
	id,
} = Astro.props;

/* No photo unless one is passed; a passed photo is taken as given, alt "" (decorative) if unset. */
const image = imageProp ? { src: imageProp.src ?? "", alt: imageProp.alt ?? "" } : null;

const uid = (id ?? `sqf-${Math.random().toString(36).slice(2, 9)}`).replace(/[^A-Za-z0-9_-]/g, "") || "sqf";
const fid = (name: string) => `${uid}-${name}`;

const L: Required<ShipmentQuoteLabels> = {
	name: "Your name",
	email: "Your email",
	phone: "Phone number",
	freightType: "Freight type",
	origin: "City of departure",
	destination: "Delivery city",
	incoterm: "Incoterms",
	weight: "Weight (kg)",
	height: "Height (cm)",
	width: "Width (cm)",
	length: "Length (cm)",
	...labels,
};

const P: Required<ShipmentQuoteLabels> = {
	name: "Your Name",
	email: "Your Email",
	phone: "Phone No:",
	freightType: "Freight Type",
	origin: "City Departure",
	destination: "Delivery City",
	incoterm: "Incoterms",
	weight: "Weight",
	height: "Height",
	width: "Width",
	length: "Length",
	...placeholders,
};

const dims = [
	{ key: "weight", name: "weight" },
	{ key: "height", name: "height" },
	{ key: "width", name: "width" },
	{ key: "length", name: "length" },
] as const;
---

{fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
<section class:list={["sqf", className]} id={id} data-sqf-root style={`--sqf-band: ${background};`}>
	<div class="sqf-inner">
		<div class:list={["sqf-card", { "sqf-card--solo": !image }]}>
			{
				image && (
					<div class="sqf-media" data-sqf-appear="left">
						{image.src ? (
							<img src={image.src} alt={image.alt} width="716" height="964" loading="lazy" decoding="async" />
						) : (
							<div class="sqf-ph" role="img" aria-label="Image placeholder: truck in a freight yard, portrait">
								<div class="sqf-ph-text" aria-hidden="true">
									<span class="sqf-ph-label">Image</span>
									<span class="sqf-ph-hint">Truck in a freight yard, portrait</span>
									<span class="sqf-ph-spec">~3:4 · 716 × 964 px</span>
								</div>
							</div>
						)}
					</div>
				)
			}

			<form class="sqf-form" action={action} method={method} name={formName}>
				<fieldset class="sqf-set" data-sqf-appear style="--sqf-i: 0">
					<legend class="sqf-legend">{personalLegend}</legend>
					<div class="sqf-row sqf-row--3">
						<div class="sqf-field">
							<label class="sqf-sr" for={fid("name")}>{L.name}</label>
							<input class="sqf-input" type="text" id={fid("name")} name="name" autocomplete="name" placeholder={P.name} required />
							<svg class="sqf-icon" viewBox="0 0 12 12" aria-hidden="true"><path fill="currentColor" d="M6 6a2.6 2.6 0 1 0 0-5.2A2.6 2.6 0 0 0 6 6Zm0 1.1c-2.4 0-4.6 1.2-4.6 2.9v1.2h9.2V10c0-1.7-2.2-2.9-4.6-2.9Z" /></svg>
						</div>
						<div class="sqf-field">
							<label class="sqf-sr" for={fid("email")}>{L.email}</label>
							<input class="sqf-input" type="email" id={fid("email")} name="email" autocomplete="email" placeholder={P.email} required />
							<svg class="sqf-icon" viewBox="0 0 12 12" aria-hidden="true"><path fill="currentColor" d="M1 2.5h10a.5.5 0 0 1 .5.5v.4L6 6.6.5 3.4V3a.5.5 0 0 1 .5-.5Zm-.5 2.1L6 7.8l5.5-3.2V9a.5.5 0 0 1-.5.5H1a.5.5 0 0 1-.5-.5Z" /></svg>
						</div>
						<div class="sqf-field">
							<label class="sqf-sr" for={fid("phone")}>{L.phone}</label>
							<input class="sqf-input" type="tel" id={fid("phone")} name="phone" autocomplete="tel" placeholder={P.phone} />
							<svg class="sqf-icon" viewBox="0 0 12 12" aria-hidden="true"><path fill="currentColor" d="M3.3 5.3a8 8 0 0 0 3.4 3.4l1.1-1.1a.5.5 0 0 1 .5-.13 5.7 5.7 0 0 0 1.8.29.5.5 0 0 1 .5.5V10a.5.5 0 0 1-.5.5A8.5 8.5 0 0 1 1.5 2a.5.5 0 0 1 .5-.5h1.75a.5.5 0 0 1 .5.5c0 .63.1 1.23.29 1.8a.5.5 0 0 1-.13.5Z" /></svg>
						</div>
					</div>
				</fieldset>

				<fieldset class="sqf-set" data-sqf-appear style="--sqf-i: 1">
					<legend class="sqf-legend">{shipmentLegend}</legend>
					<div class="sqf-row sqf-row--3">
						{freightTypes.length > 0 && (
						<div class="sqf-field sqf-field--select">
							<label class="sqf-sr" for={fid("freight")}>{L.freightType}</label>
							<select class="sqf-input sqf-select" id={fid("freight")} name="freightType" required>
								<option value="" selected disabled>{P.freightType}</option>
								{freightTypes.map((o) => <option value={o}>{o}</option>)}
							</select>
							<svg class="sqf-chevron" viewBox="0 0 16 16" aria-hidden="true"><path d="m3 6 5 5 5-5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" /></svg>
						</div>
						)}
						<div class="sqf-field">
							<label class="sqf-sr" for={fid("origin")}>{L.origin}</label>
							<input class="sqf-input sqf-input--plain" type="text" id={fid("origin")} name="originCity" autocomplete="address-level2" placeholder={P.origin} required />
						</div>
						<div class="sqf-field">
							<label class="sqf-sr" for={fid("destination")}>{L.destination}</label>
							<input class="sqf-input sqf-input--plain" type="text" id={fid("destination")} name="destinationCity" placeholder={P.destination} required />
						</div>
					</div>
					<div class="sqf-row sqf-row--split">
						{incoterms.length > 0 && (
						<div class="sqf-field sqf-field--select">
							<label class="sqf-sr" for={fid("incoterm")}>{L.incoterm}</label>
							<select class="sqf-input sqf-select" id={fid("incoterm")} name="incoterm">
								<option value="" selected>{P.incoterm}</option>
								{incoterms.map((o) => <option value={o}>{o}</option>)}
							</select>
							<svg class="sqf-chevron" viewBox="0 0 16 16" aria-hidden="true"><path d="m3 6 5 5 5-5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" /></svg>
						</div>
						)}
						<div class="sqf-row sqf-row--dims">
							{dims.map((d) => (
								<div class="sqf-field">
									<label class="sqf-sr" for={fid(d.name)}>{L[d.key]}</label>
									<input class="sqf-input sqf-input--plain sqf-input--dim" type="text" inputmode="decimal" id={fid(d.name)} name={d.name} placeholder={P[d.key]} />
								</div>
							))}
						</div>
					</div>
				</fieldset>

				<div class="sqf-submit-wrap" data-sqf-appear style="--sqf-i: 2">
					<button class="sqf-btn" type="submit">
						{submitLabel}
						<svg class="sqf-btn-arrow" viewBox="0 0 12 12" width="11" height="11" aria-hidden="true">
							<path d="M2 10 10 2M3.5 2H10v6.5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" />
						</svg>
					</button>
				</div>
			</form>
		</div>
	</div>
</section>

<style>
	.sqf {
		--sqf-navy: var(--tg-navy, #020e28);
		--sqf-orange: var(--tg-orange, #fd5523);
		--sqf-muted: var(--tg-muted, #61697d);
		--sqf-line: #d8dde1;
		--sqf-display: var(--tg-font-display, Outfit, "Outfit Placeholder", ui-sans-serif, system-ui, sans-serif);
		--sqf-body: var(--tg-font-body, "DM Sans", "DM Sans Placeholder", ui-sans-serif, system-ui, sans-serif);
		--sqf-ease: cubic-bezier(0.22, 1, 0.36, 1);

		box-sizing: border-box;
		padding: 60px 0;
		/* Contains the photo's −40px appear slide on narrow screens. */
		overflow-x: clip;
		background: var(--sqf-band);
		color: var(--sqf-navy);
		font-family: var(--sqf-body);
	}

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

	.sqf-inner {
		max-width: 1293px;
		margin: 0 auto;
		padding: 0 20px;
	}

	.sqf-card {
		display: flex;
		flex-direction: column;
		gap: 30px;
		padding: 16px;
		border: 1px solid var(--sqf-line);
		border-radius: 30px;
	}

	/* On phones and tablets the form comes first and the photo follows it,
	   as the reference does. The photo holds nothing focusable, so the swap
	   cannot scramble the tab order. */
	.sqf-media {
		order: 1;
		position: relative;
		aspect-ratio: 4 / 3.5;
		border-radius: 20px;
		overflow: hidden;
	}

	.sqf-media img {
		position: absolute;
		inset: 0;
		display: block;
		width: 100%;
		height: 100%;
		object-fit: cover;
	}

	.sqf-form {
		display: flex;
		flex-direction: column;
		gap: 16px;
		min-width: 0;
		margin: 0;
	}

	.sqf .sqf-set {
		min-width: 0;
		margin: 0;
		padding: 0 0 16px;
		border: 0;
		border-bottom: 1px solid var(--sqf-line);
	}

	.sqf .sqf-legend {
		float: left;
		width: 100%;
		margin: 0 0 16px;
		padding: 0;
		font: 500 20px/34px var(--sqf-display);
		color: var(--sqf-navy);
	}

	/* The floated legend needs its following rows cleared. */
	.sqf-legend + .sqf-row {
		clear: both;
	}

	.sqf-row {
		display: grid;
		gap: 12px;
		min-width: 0;
	}

	.sqf-row + .sqf-row {
		margin-top: 12px;
	}

	.sqf-row--dims {
		margin-top: 0;
	}

	.sqf-field {
		position: relative;
		min-width: 0;
	}

	.sqf-input {
		display: block;
		width: 100%;
		min-height: 44px;
		margin: 0;
		padding: 11px 44px 11px 18px;
		border: 1px solid var(--sqf-line);
		border-radius: 100px;
		background: #fff;
		color: var(--sqf-navy);
		font: 400 16px/20px var(--sqf-body);
		transition: border-color 0.3s ease-out;
	}

	.sqf-input--plain {
		padding-right: 18px;
	}

	.sqf-input::placeholder {
		color: var(--sqf-muted);
		opacity: 1;
	}

	.sqf-input:hover {
		border-color: #b9c0c7;
	}

	/* An outline, not a shadow: it survives forced colours. */
	.sqf-input:focus-visible,
	.sqf-input:focus {
		outline: 2px solid var(--sqf-orange);
		outline-offset: 2px;
	}

	.sqf-select {
		appearance: none;
		-webkit-appearance: none;
		cursor: pointer;
	}

	.sqf-select:has(option[value=""]:checked) {
		color: var(--sqf-muted);
	}

	.sqf-select option {
		color: var(--sqf-navy);
	}

	.sqf-icon,
	.sqf-chevron {
		position: absolute;
		top: 50%;
		right: 20px;
		width: 12px;
		height: 12px;
		transform: translateY(-50%);
		color: var(--sqf-orange);
		pointer-events: none;
	}

	.sqf-chevron {
		width: 18px;
		height: 18px;
		right: 18px;
		color: var(--sqf-muted);
	}

	.sqf-submit-wrap {
		padding-top: 8px;
	}

	.sqf-btn {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		gap: 6px;
		width: 100%;
		min-height: 50px;
		padding: 18px 20px;
		border: 0;
		border-radius: 100px;
		background: var(--sqf-orange);
		color: #fff;
		font: 600 14px/1 var(--sqf-body);
		cursor: pointer;
		transition: background-color 0.35s ease-out;
	}

	.sqf-btn-arrow {
		flex: none;
		transition: transform 0.35s ease-out;
	}

	.sqf-btn:hover {
		background: var(--sqf-navy);
	}

	.sqf-btn:hover .sqf-btn-arrow {
		transform: rotate(45deg);
	}

	.sqf-btn:focus-visible {
		outline: 2px solid var(--sqf-navy);
		outline-offset: 3px;
	}

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

	.sqf-ph {
		position: absolute;
		inset: 0;
		display: grid;
		place-items: center;
		padding: 16px;
		text-align: center;
		background: repeating-linear-gradient(135deg, #e6e9ee 0 12px, #edf0f3 12px 24px);
		color: rgb(2 14 40 / 0.62);
	}

	.sqf-ph-text {
		display: flex;
		flex-direction: column;
		align-items: center;
		gap: 8px;
	}

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

	.sqf-ph-hint {
		max-width: 28ch;
		font: 500 14px/1.4 var(--sqf-body);
	}

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

	/* ---- appear ---- */
	[data-sqf-appear] {
		transition:
			opacity 0.8s var(--sqf-ease),
			transform 0.8s var(--sqf-ease);
		transition-delay: calc(var(--sqf-i, 0) * 0.1s);
	}

	.sqf[data-sqf-anim] [data-sqf-appear]:not([data-in]) {
		opacity: 0;
		transform: translateY(40px);
	}

	.sqf[data-sqf-anim] [data-sqf-appear="left"]:not([data-in]) {
		transform: translateX(-40px);
	}

	/* ---- tablet: three-up rows, photo still below ---- */
	@media (min-width: 810px) {
		.sqf {
			padding: 90px 0;
		}

		.sqf-card {
			padding: 30px;
		}

		.sqf-form {
			gap: 25px;
		}

		.sqf-submit-wrap {
			padding-top: 11px;
		}

		.sqf .sqf-set {
			padding-bottom: 25px;
		}

		.sqf .sqf-legend {
			margin-bottom: 25px;
			font-size: 24px;
		}

		.sqf-row {
			gap: 25px 30px;
		}

		.sqf-row + .sqf-row {
			margin-top: 25px;
		}

		.sqf-row--dims {
			margin-top: 0;
		}

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

		.sqf-row--split {
			grid-template-columns: minmax(0, 228fr) minmax(0, 482fr);
		}

		.sqf-row--dims {
			grid-template-columns: repeat(4, minmax(0, 1fr));
		}

		/* Incoterms dropped: the four measurements take the whole row. */
		.sqf-row--split:has(> .sqf-row--dims:only-child) {
			grid-template-columns: minmax(0, 1fr);
		}

		.sqf-input {
			min-height: 56px;
			padding: 17px 44px 17px 24px;
		}

		.sqf-input--plain {
			padding-right: 24px;
		}

		.sqf-input--dim {
			padding-inline: 10px;
			text-align: center;
		}

		.sqf-icon {
			right: 24px;
		}

		.sqf-btn {
			width: 240px;
			min-height: 58px;
			padding: 22px 20px;
		}

		.sqf-media {
			aspect-ratio: 16 / 9;
		}
	}

	/* ---- desktop: photo left, form right ---- */
	@media (min-width: 1200px) {
		.sqf {
			padding: 120px 0;
		}

		.sqf-card {
			display: grid;
			grid-template-columns: minmax(0, 358fr) minmax(0, 740fr);
			justify-content: space-between;
			align-items: start;
			gap: 95px;
		}

		.sqf-card--solo {
			grid-template-columns: minmax(0, 1fr);
		}

		.sqf-media {
			order: 0;
			align-self: stretch;
			aspect-ratio: auto;
			min-height: 482px;
		}
	}

	@media (prefers-reduced-motion: reduce) {
		.sqf-btn:hover .sqf-btn-arrow {
			transform: none;
		}

		[data-sqf-appear] {
			transition: none;
		}
	}

	@media (forced-colors: active) {
		.sqf-btn {
			border: 1px solid ButtonText;
		}

		.sqf-icon,
		.sqf-chevron {
			color: CanvasText;
		}
	}
</style>

<script>
	/*
	 * Appear on scroll only. The form itself is native and needs no script.
	 * One IntersectionObserver per instance; each watched element gets
	 * `data-in` once. Released on astro:before-swap.
	 */
	function initShipmentQuoteForm(root: HTMLElement) {
		if (root.dataset.sqfReady !== undefined) return;
		root.dataset.sqfReady = "";
		if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
		if (!("IntersectionObserver" in window)) return;

		const controller = new AbortController();
		const { signal } = controller;
		root.dataset.sqfAnim = "";

		const observer = new IntersectionObserver(
			(entries) => {
				for (const entry of entries) {
					if (!entry.isIntersecting) continue;
					(entry.target as HTMLElement).dataset.in = "";
					observer.unobserve(entry.target);
				}
			},
			{ threshold: 0.1 },
		);
		root.querySelectorAll<HTMLElement>("[data-sqf-appear]").forEach((el) => observer.observe(el));
		signal.addEventListener("abort", () => observer.disconnect());

		document.addEventListener(
			"astro:before-swap",
			() => {
				controller.abort();
				delete root.dataset.sqfReady;
				delete root.dataset.sqfAnim;
			},
			{ once: true, signal },
		);
	}

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

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

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

===== END src/components/ShipmentQuoteForm.astro =====

===== FILE (required): public/shipment-quote-form/truck-yard.webp =====
[binary asset — download https://astro.baysixmedia.com/shipment-quote-form/truck-yard.webp and save it to public/shipment-quote-form/truck-yard.webp]

===== FILE (required): src/components/NewsletterPanelFooter.astro =====
---
/**
 * NewsletterPanelFooter
 *
 * A navy footer in two parts. On top, "Subscribe Our Newsletter" beside an
 * email field with an orange Subscribe button (a real form). Below, the
 * brand block — logo, a short blurb, four social circles — and three
 * columns: Quick Links and Our Services (links with » bullets) and Contact
 * Us (mail and phone). Headings carry a short white-and-orange underline.
 *
 * Two measured layouts:
 * - `variant="panel"` (default, the reference's inner pages): the brand and
 *   columns sit in a rounded panel with a slightly lighter fill and a
 *   gradient hairline, and a centred copyright line closes the footer.
 * - `variant="band"` (the reference's home page): a hairline divides the
 *   newsletter row from the columns, and the copyright sits on an orange band.
 *
 * Nothing about the business is assumed: no name, blurb, link, contact,
 * social profile, copyright or sign-up form is shown unless it is passed, a
 * block with nothing in it is not drawn, and with no content at all nothing
 * renders.
 *
 * The top content rises 40px and fades in once when scrolled into view.
 * Reads --tg-* design tokens from any ancestor, with fallbacks.
 */

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

export interface NewsletterPanelFooterColumn {
	heading: string;
	links: NewsletterPanelFooterLink[];
}

export interface NewsletterPanelFooterContact {
	icon: "email" | "phone" | "address";
	label: string;
	href: string;
}

export type NewsletterPanelFooterNetwork = "facebook" | "x" | "instagram" | "linkedin" | "youtube" | "link";

export interface NewsletterPanelFooterSocial {
	network: NewsletterPanelFooterNetwork;
	href: string;
	/** Accessible name, e.g. "Northpass on LinkedIn". */
	label: string;
}

export interface NewsletterPanelFooterProps {
	variant?: "panel" | "band";
	/** The newsletter heading. Unset together with no `newsletter` drops the row. */
	heading?: string;
	/** The subscribe form. Off by default; `{}` shows it with no `action` (posts to the current URL). */
	newsletter?: {
		action?: string;
		method?: "get" | "post";
		/** Field name as submitted. */
		name?: string;
		label?: string;
		placeholder?: string;
		buttonLabel?: string;
	} | null;
	brandName?: string;
	brandHref?: string;
	/** Replaces the drawn mark and wordmark with an image (use a light logo). */
	logo?: { src: string; alt: string; width?: number; height?: number };
	blurb?: string;
	social?: NewsletterPanelFooterSocial[];
	columns?: NewsletterPanelFooterColumn[];
	contactHeading?: string;
	contacts?: NewsletterPanelFooterContact[];
	/** Text of the copyright line. `{year}` is replaced with the build year. Defaults to one naming `brandName`; with neither, no line. */
	copyright?: string;
	/** Makes the copyright line a link. */
	copyrightHref?: string;
	year?: number;
	/** Scroll-in rise for the top content. */
	animate?: boolean;
	navLabel?: string;
	fontStylesheet?: string;
	id?: string;
}

interface Props extends NewsletterPanelFooterProps {}

const {
	variant = "panel",
	heading = "",
	newsletter = null,
	brandName = "",
	brandHref = "/",
	logo,
	blurb = "",
	social = [],
	columns = [],
	contactHeading = "Contact Us",
	contacts = [],
	copyright,
	copyrightHref,
	year = new Date().getFullYear(),
	animate = true,
	navLabel = "Footer",
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&family=DM+Sans:wght@400;500;600&display=swap",
	id,
} = Astro.props;

const seed = id ? id : `npf-${Math.random().toString(36).slice(2, 8)}`;
const emailId = `${seed}-email`;
const form = newsletter
	? {
			method: "post" as const,
			name: "email",
			label: "Email address",
			placeholder: "Email Address",
			buttonLabel: "Subscribe",
			...newsletter,
		}
	: null;
const hasTop = Boolean(heading || form);
const hasMark = Boolean(logo || brandName);
const hasBrand = Boolean(hasMark || blurb || social.length > 0);
const hasCols = columns.length > 0 || contacts.length > 0;
const hasPanel = hasBrand || hasCols;
/* No name, no copyright line: never a line naming nobody. */
const copyText = (copyright ?? (brandName ? `Copyright © {year} ${brandName}. All Rights Reserved.` : "")).replace("{year}", String(year));
const hasContent = hasTop || hasPanel || Boolean(copyText);
const isExternal = (href: string) => /^https?:\/\//.test(href);

const CONTACT_ICONS: Record<NewsletterPanelFooterContact["icon"], string> = {
	email:
		'<path d="M1 4.2A1.7 1.7 0 0 1 2.7 2.5h10.6A1.7 1.7 0 0 1 15 4.2v.3L8 9 1 4.5z" fill="currentColor"/><path d="M1 6.3l6.5 4.2a1 1 0 0 0 1 0L15 6.3v5.5a1.7 1.7 0 0 1-1.7 1.7H2.7A1.7 1.7 0 0 1 1 11.8z" fill="currentColor"/>',
	phone:
		'<path d="M4.2 1.5 6.3 4a1 1 0 0 1-.1 1.3L5 6.5a8.4 8.4 0 0 0 4.5 4.5l1.2-1.2a1 1 0 0 1 1.3-.1l2.5 2.1a1 1 0 0 1 .1 1.4l-1 1.1c-.7.7-1.7.9-2.6.6A14 14 0 0 1 1.1 4.9c-.3-.9-.1-1.9.6-2.6l1-1a1 1 0 0 1 1.5.2z" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/><path d="M9.6 1.6a5 5 0 0 1 4.8 4.8M9.4 4a2.6 2.6 0 0 1 2.6 2.6" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>',
	address:
		'<path d="M8 .8a5.6 5.6 0 0 0-5.6 5.6c0 4.1 5 8.5 5.2 8.7a.6.6 0 0 0 .8 0c.2-.2 5.2-4.6 5.2-8.7A5.6 5.6 0 0 0 8 .8zm0 7.9a2.3 2.3 0 1 1 0-4.6 2.3 2.3 0 0 1 0 4.6z" fill="currentColor"/>',
};

const GLYPHS: Record<NewsletterPanelFooterNetwork, string> = {
	facebook:
		'<path d="M13.6 21v-7.6h2.6l.4-3.1h-3V8.4c0-.9.3-1.5 1.5-1.5h1.6V4.1c-.3 0-1.2-.1-2.3-.1-2.3 0-3.9 1.4-3.9 4v2.3H7.9v3.1h2.6V21z" fill="currentColor"/>',
	x: '<path d="M4 4h4.6l11.4 16h-4.6z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/><path d="M19.4 4l-6.2 7M10.8 13l-6.2 7" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/>',
	instagram:
		'<rect x="4" y="4" width="16" height="16" rx="4.6" fill="none" stroke="currentColor" stroke-width="1.8"/><circle cx="12" cy="12" r="3.7" fill="none" stroke="currentColor" stroke-width="1.8"/><circle cx="16.6" cy="7.4" r="1.1" fill="currentColor"/>',
	linkedin:
		'<rect x="4.2" y="9.2" width="3.3" height="10.6" fill="currentColor"/><circle cx="5.85" cy="5.9" r="1.95" fill="currentColor"/><path d="M10 9.2h3.2v1.5c.5-.9 1.7-1.8 3.5-1.8 3.4 0 4 2.2 4 5.1v5.8h-3.3v-5.1c0-1.3 0-2.9-1.8-2.9s-2.1 1.4-2.1 2.8v5.2H10z" fill="currentColor"/>',
	youtube:
		'<rect x="3" y="6" width="18" height="12.5" rx="3.6" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M10.4 9.4v5.7l4.9-2.85z" fill="currentColor"/>',
	link: '<path d="M10 14l4-4M8.5 11.5 6.6 13.4a3.2 3.2 0 0 0 4.5 4.5l1.9-1.9M15.5 12.5l1.9-1.9a3.2 3.2 0 0 0-4.5-4.5L11 8" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>',
};
---

{hasContent && fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
{hasContent && (
<footer
	class:list={["npf", `npf--${variant}`]}
	id={id}
	data-npf-root
	data-animate={animate ? "" : undefined}
>
	{(hasTop || hasPanel) && (
	<div class="npf-main">
		{
			hasTop && (
				<div class="npf-top npf-rise" style="--npf-i: 0">
					{heading && <h2 class="npf-heading">{heading}</h2>}
					{form && (
						<form class="npf-form" action={form.action} method={form.method}>
							<label class="npf-sr" for={emailId}>{form.label}</label>
							<div class="npf-field">
								<input
									class="npf-input"
									id={emailId}
									type="email"
									name={form.name}
									placeholder={form.placeholder}
									autocomplete="email"
									required
								/>
								<button class="npf-submit" type="submit">
									<span>{form.buttonLabel}</span>
									<svg class="npf-plane" viewBox="0 0 22 21" aria-hidden="true" focusable="false">
										<path d="M20.6 1.2 1.6 8.6c-.8.3-.8 1.4 0 1.7l6 2.2 2.2 6.3c.3.8 1.4.9 1.8.1L21.9 2.5c.4-.8-.4-1.6-1.3-1.3zM9.1 12.2l7.7-6.9-6 8.1-.3 3.2z" fill="currentColor" />
									</svg>
								</button>
							</div>
						</form>
					)}
				</div>
			)
		}

		{variant === "band" && hasTop && hasPanel && <div class="npf-rule" aria-hidden="true" />}

		{hasPanel && (
		<div class="npf-panel">
			<div class="npf-links npf-rise" style="--npf-i: 1">
				{hasBrand && (
				<div class="npf-brandblock">
					{hasMark && (
					<a class="npf-brand" href={brandHref} aria-label={logo ? undefined : `${brandName} home`}>
						{
							logo ? (
								<img
									class="npf-logo-img"
									src={logo.src}
									alt={logo.alt}
									width={logo.width ?? 157}
									height={logo.height ?? 30}
									loading="lazy"
								/>
							) : (
								<>
									<svg class="npf-mark" viewBox="0 0 46 30" aria-hidden="true" focusable="false">
										<path d="M2 9h11M5 14h9M1 19h13" fill="none" stroke="var(--npf-orange)" stroke-width="2.6" stroke-linecap="round" />
										<rect x="14" y="4" width="17" height="15" rx="2" fill="currentColor" />
										<path d="M32 8h6.2l5.3 5.6V19H32z" fill="currentColor" />
										<path d="M34.4 10.4h3.2l3 3.2h-6.2z" fill="var(--npf-orange)" />
										<circle cx="20" cy="21.5" r="3.4" fill="currentColor" stroke="var(--npf-navy)" stroke-width="1.6" />
										<circle cx="37.5" cy="21.5" r="3.4" fill="currentColor" stroke="var(--npf-navy)" stroke-width="1.6" />
										<path d="M4 27.5h40" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" />
									</svg>
									<span class="npf-wordmark" aria-hidden="true">{brandName}</span>
								</>
							)
						}
					</a>
					)}
					{blurb && <p class="npf-blurb">{blurb}</p>}
					{
						social.length > 0 && (
							<ul class="npf-social" role="list">
								{social.map((s) => (
									<li>
										<a
											class="npf-social-link"
											href={s.href}
											aria-label={s.label}
											target={isExternal(s.href) ? "_blank" : undefined}
											rel={isExternal(s.href) ? "noopener noreferrer" : undefined}
										>
											<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" set:html={GLYPHS[s.network] ?? GLYPHS.link} />
										</a>
									</li>
								))}
							</ul>
						)
					}
				</div>
				)}

				{hasCols && (
				<nav class="npf-cols" aria-label={navLabel}>
					{
						columns.map((col) => (
							<div class="npf-col">
								<h3 class="npf-col-heading">{col.heading}</h3>
								<span class="npf-underline" aria-hidden="true"><span /><span /></span>
								<ul class="npf-list" role="list">
									{col.links.map((link) => (
										<li>
											<a class="npf-link" href={link.href}>
												<svg class="npf-bullet" viewBox="0 0 18 18" aria-hidden="true" focusable="false">
													<path d="M4.5 5l4 4-4 4M9.5 5l4 4-4 4" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" />
												</svg>
												<span>{link.label}</span>
											</a>
										</li>
									))}
								</ul>
							</div>
						))
					}
					{
						contacts.length > 0 && (
							<div class="npf-col npf-col--contact">
								<h3 class="npf-col-heading">{contactHeading}</h3>
								<span class="npf-underline" aria-hidden="true"><span /><span /></span>
								<ul class="npf-list npf-list--contact" role="list">
									{contacts.map((c) => (
										<li>
											<a class="npf-link" href={c.href}>
												<svg class="npf-contact-icon" viewBox="0 0 16 16" aria-hidden="true" focusable="false" set:html={CONTACT_ICONS[c.icon]} />
												<span>{c.label}</span>
											</a>
										</li>
									))}
								</ul>
							</div>
						)
					}
				</nav>
				)}
			</div>
		</div>
		)}
	</div>
	)}

	{copyText && (
	<div class="npf-bottom">
		<p class="npf-copy">
			{
				copyrightHref ? (
					<a class="npf-copy-link" href={copyrightHref}>
						{copyText}
					</a>
				) : (
					copyText
				)
			}
		</p>
	</div>
	)}
</footer>
)}

<style>
	.npf {
		--npf-navy: var(--tg-navy, #020e28);
		--npf-orange: var(--tg-orange, #fd5523);
		--npf-hover: #0e2d3a;
		--npf-soft: #b0b2b8;
		--npf-display: var(--tg-font-display, Outfit, ui-sans-serif, system-ui, sans-serif);
		--npf-body: var(--tg-font-body, "DM Sans", ui-sans-serif, system-ui, sans-serif);
		--npf-dur: 0.4s;
		--npf-ease: cubic-bezier(0.44, 0, 0.56, 1);

		box-sizing: border-box;
		width: 100%;
		background: var(--npf-navy);
		color: #fff;
		font-family: var(--npf-body);
		overflow: hidden;
	}

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

	.npf ul,
	.npf p,
	.npf h2,
	.npf h3 {
		margin: 0;
	}

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

	.npf-main {
		display: flex;
		flex-direction: column;
		gap: 48px;
		padding: 60px 20px 40px;
	}

	/* ---- newsletter row ---- */
	.npf-top {
		display: flex;
		flex-direction: column;
		gap: 30px;
		width: 100%;
		max-width: 1224px;
		margin: 0 auto;
	}

	.npf-heading {
		max-width: 420px;
		font: 600 32px/1.2 var(--npf-display);
		color: #fff;
		text-transform: capitalize;
	}

	.npf-form {
		width: 100%;
	}

	.npf-field {
		position: relative;
		display: flex;
		flex-direction: column;
		gap: 20px;
	}

	.npf-input {
		width: 100%;
		height: 48px;
		padding: 0 25px;
		border: 1px solid rgb(255 255 255 / 0.18);
		border-radius: 50px;
		background: rgb(255 255 255 / 0.07);
		color: #fff;
		font: 400 15px/1 var(--npf-body);
		transition:
			background-color var(--npf-dur) var(--npf-ease),
			border-color var(--npf-dur) var(--npf-ease),
			box-shadow var(--npf-dur) var(--npf-ease);
	}

	.npf-input::placeholder {
		color: #d4d6db;
		opacity: 1;
	}

	.npf-input:focus {
		outline: none;
		border-color: var(--npf-orange);
		background: rgb(255 255 255 / 0.1);
		box-shadow: 0 0 0 3px rgb(253 85 35 / 0.35);
	}

	.npf-submit {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		gap: 10px;
		height: 48px;
		padding: 0 19px 0 25px;
		border: 0;
		border-radius: 50px;
		background: var(--npf-orange);
		color: #fff;
		font: 600 16px/1 var(--npf-display);
		cursor: pointer;
		transition: background-color var(--npf-dur) var(--npf-ease);
	}

	.npf-submit:hover {
		background: var(--npf-hover);
	}

	.npf-plane {
		width: 20px;
		height: 20px;
		flex: none;
	}

	.npf-rule {
		width: 100%;
		max-width: 1224px;
		height: 1px;
		margin: 0 auto;
		background: rgb(255 255 255 / 0.2);
	}

	/* ---- brand block and columns ---- */
	.npf-links {
		display: flex;
		flex-direction: column;
		gap: 36px;
		width: 100%;
		max-width: 1224px;
		margin: 0 auto;
	}

	.npf-brandblock {
		display: flex;
		flex-direction: column;
		align-items: flex-start;
		gap: 24px;
		max-width: 320px;
	}

	.npf-brand {
		display: inline-flex;
		align-items: center;
		gap: 9px;
		min-height: 44px;
		color: #fff;
		text-decoration: none;
		border-radius: 6px;
	}

	.npf-mark {
		width: 46px;
		height: 30px;
		flex: none;
	}

	.npf-wordmark {
		font: 600 24px/1 var(--npf-display);
		letter-spacing: -0.01em;
	}

	.npf-logo-img {
		display: block;
		height: 30px;
		width: auto;
	}

	.npf-blurb {
		color: var(--npf-soft);
		font: 400 16px/21px var(--npf-body);
	}

	.npf .npf-social {
		display: flex;
		align-items: center;
		gap: 10px;
	}

	.npf-social-link {
		position: relative;
		display: grid;
		place-items: center;
		width: 36px;
		height: 36px;
		border-radius: 100px;
		background: rgb(255 255 255 / 0.07);
		color: #fff;
		transition: background-color var(--npf-dur) var(--npf-ease);
	}

	/* 36px circle as measured, 44px target. */
	.npf-social-link::after {
		content: "";
		position: absolute;
		inset: -4px;
		border-radius: inherit;
	}

	.npf-social-link svg {
		width: 15px;
		height: 15px;
	}

	.npf-social-link:hover {
		background: var(--npf-orange);
	}

	.npf-cols {
		display: grid;
		grid-template-columns: minmax(0, 1fr);
		gap: 36px;
	}

	.npf-col-heading {
		font: 700 24px/1 var(--npf-display);
		color: #fff;
	}

	.npf-underline {
		display: flex;
		gap: 6px;
		margin: 18px 0 20px;
	}

	.npf-underline span {
		display: block;
		width: 18px;
		height: 2px;
		background: #fff;
	}

	.npf-underline span + span {
		width: 83px;
		background: var(--npf-orange);
	}

	.npf .npf-list {
		display: flex;
		flex-direction: column;
		gap: 0;
	}

	.npf-link {
		display: inline-flex;
		align-items: center;
		gap: 8px;
		min-height: 44px;
		color: var(--npf-soft);
		font: 400 16px/21px var(--npf-body);
		text-decoration: none;
		border-radius: 4px;
		transition: color var(--npf-dur) var(--npf-ease);
	}

	.npf-link:hover {
		color: var(--npf-orange);
	}

	.npf-bullet {
		width: 18px;
		height: 18px;
		flex: none;
	}

	.npf-contact-icon {
		width: 16px;
		height: 16px;
		flex: none;
	}

	.npf-list--contact .npf-link {
		overflow-wrap: anywhere;
	}

	/* ---- the panel ---- */
	.npf--panel .npf-panel {
		position: relative;
		width: 100%;
		max-width: 1385px;
		margin: 0 auto;
		padding: 30px 16px;
		border-radius: 20px;
		/* A navy fill with a 10% white wash inside a 2px gradient hairline,
		   as measured: border-box gradient under a padding-box fill. */
		border: 2px solid transparent;
		background:
			linear-gradient(107deg, rgb(255 255 255 / 0.1), rgb(255 255 255 / 0.1)) padding-box,
			linear-gradient(var(--npf-navy), var(--npf-navy)) padding-box,
			linear-gradient(107deg, rgb(255 255 255 / 0.2), rgb(255 255 255 / 0.5)) border-box;
	}

	/* ---- copyright ---- */
	.npf-bottom {
		padding: 0 20px 40px;
		text-align: center;
	}

	.npf-copy {
		color: #fff;
		font: 400 16px/21px var(--npf-body);
	}

	.npf-copy-link {
		color: inherit;
		text-decoration: none;
		border-radius: 4px;
		transition: color var(--npf-dur) var(--npf-ease);
	}

	.npf-copy-link:hover {
		color: var(--npf-orange);
	}

	.npf--band .npf-main {
		padding-bottom: 60px;
	}

	.npf--band .npf-bottom {
		padding: 30px 20px;
		background: var(--npf-orange);
	}

	/* Orange on orange would vanish: on the band the link turns navy. */
	.npf--band .npf-copy-link:hover {
		color: var(--npf-navy);
	}

	/* ---- focus ---- */
	.npf a:focus-visible,
	.npf button:focus-visible {
		outline: 2px solid var(--npf-orange);
		outline-offset: 3px;
	}

	.npf--band .npf-copy-link:focus-visible,
	.npf-submit:focus-visible {
		outline-color: #fff;
	}

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

	/* ---- appear on scroll ----
	   The hidden start pose applies only once the script has marked the root
	   ready, so without JS or under reduced motion everything is at rest. */
	.npf-rise {
		transition:
			opacity 0.8s var(--npf-ease) calc(var(--npf-i, 0) * 0.1s),
			transform 0.8s var(--npf-ease) calc(var(--npf-i, 0) * 0.1s);
	}

	.npf[data-npf-ready]:not([data-in]) .npf-rise {
		opacity: 0;
		transform: translateY(40px);
	}

	/* ---- tablet ---- */
	@media (min-width: 810px) {
		.npf-main {
			gap: 64px;
			padding: 80px 30px 40px;
		}

		.npf-top {
			flex-direction: row;
			align-items: center;
			justify-content: space-between;
			gap: 40px;
		}

		.npf-heading {
			font-size: 44px;
			line-height: 1.2;
		}

		.npf-form {
			flex: 0 1 443px;
		}

		.npf-field {
			display: block;
		}

		.npf-input {
			height: 56px;
			padding-right: 160px;
		}

		.npf-submit {
			position: absolute;
			top: 0;
			right: 0;
			height: 56px;
		}

		.npf-links {
			gap: 56px;
		}

		.npf-cols {
			grid-template-columns: repeat(3, minmax(0, 1fr));
			gap: 30px;
		}

		.npf--panel .npf-panel {
			padding: 60px 40px;
			border-radius: 40px;
		}

		.npf .npf-list {
			gap: 2px;
		}
	}

	/* ---- desktop: brand block left, three columns right ---- */
	@media (min-width: 1200px) {
		.npf-main {
			gap: 80px;
			padding: 80px 20px 40px;
		}

		.npf--band .npf-main {
			padding-bottom: 88px;
		}

		.npf-heading {
			font-size: 48px;
			line-height: 57.6px;
		}

		.npf-links {
			flex-direction: row;
			justify-content: space-between;
			align-items: flex-start;
			gap: 40px;
		}

		.npf-brandblock {
			flex: 0 0 307px;
			gap: 40px;
		}

		.npf-brandblock .npf-blurb {
			margin-top: -10px;
		}

		.npf-cols {
			flex: 0 1 815px;
			gap: 0;
		}

		.npf--panel .npf-panel {
			padding: 78px 20px;
			border-radius: 50px;
		}

		.npf-top,
		.npf-links {
			max-width: 1224px;
		}
	}

	/* Band variant: the columns keep the reference's 80px rhythm, no panel. */
	.npf--band .npf-panel {
		width: 100%;
	}

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

		.npf-rise {
			transition: none;
		}
	}

	@media (forced-colors: active) {
		.npf-submit,
		.npf-social-link {
			border: 1px solid CanvasText;
		}
	}
</style>

<script>
	/*
	 * Appear-on-scroll for the footer's top content. One IntersectionObserver
	 * per instance, scoped to its root, torn down on astro:before-swap. It sets
	 * `data-in` once and stops watching. Under reduced motion, without
	 * IntersectionObserver, or with animate={false}, nothing ever starts hidden.
	 */
	function initNewsletterPanelFooter(root: HTMLElement) {
		if (root.dataset.npfReady === "") return;
		if (!root.hasAttribute("data-animate")) return;

		const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
		if (reduced || !("IntersectionObserver" in window)) {
			root.dataset.in = "";
			return;
		}

		const controller = new AbortController();
		const { signal } = controller;
		root.dataset.npfReady = "";

		const observer = new IntersectionObserver(
			(entries) => {
				if (entries.some((entry) => entry.isIntersecting)) {
					root.dataset.in = "";
					observer.disconnect();
				}
			},
			{ threshold: 0.12 },
		);
		observer.observe(root);
		signal.addEventListener("abort", () => observer.disconnect());

		document.addEventListener(
			"astro:before-swap",
			() => {
				controller.abort();
				delete root.dataset.npfReady;
			},
			{ once: true, signal },
		);
	}

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

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

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

===== END src/components/NewsletterPanelFooter.astro =====

--- SECTIONS ---
- Contact bar overlay header (enrolled, not yet production-ready): https://astro.baysixmedia.com/components/contact-bar-overlay-header/
- Breadcrumb page banner (enrolled, not yet production-ready): https://astro.baysixmedia.com/components/breadcrumb-page-banner/
- Shipment quote form (enrolled, not yet production-ready): https://astro.baysixmedia.com/components/shipment-quote-form/
- Newsletter panel footer (enrolled, not yet production-ready): https://astro.baysixmedia.com/components/newsletter-panel-footer/

Each component's README — its full prop reference — is on its library page above, and is not repeated here. The library verified each component on its own. Nobody has verified this page in your project: test the assembled page yourself.

What's on the page

4 sections, top to bottom. Each is an ordinary library component — open one for its props, its own preview and its verification record.

  1. HeaderContact bar overlay headerin verification
  2. SectionBreadcrumb page bannerin verification
  3. SectionShipment quote formin verification
  4. FooterNewsletter panel footerin verification

7 required files · page file src/pages/transport-request-quote.astro · no npm packages