Component library
Back to templates

Page template

Tideway homepage

A navy-and-orange freight homepage: fixed pill header, tag-headline photo hero, stat overview, photo features, hover service list, sticky stacking projects, pricing, steps, portrait testimonials, FAQ, logo marquee, blog cards and a photo CTA footer. Ships with stock photos.

homepagelogisticsfreightnavyorangestock photostideway

Open preview in a new tab

Use this template

Two ways to get Tideway homepage 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 40 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 "Tideway homepage" page template in my Astro Component Library.

Library page, for reference only:
https://astro.baysixmedia.com/templates/tideway-homepage/

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/tideway-homepage.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/tideway-homepage.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 40 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: "Tideway homepage"
Library page: https://astro.baysixmedia.com/templates/tideway-homepage/
Page file: src/pages/tideway-homepage.astro
Files: 40 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/tideway-homepage.astro =====
---
/**
 * Tideway homepage — a page template from the Astro Component Library.
 * https://astro.baysixmedia.com/templates/tideway-homepage/
 *
 * 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 ScrollPillHeader from "../components/ScrollPillHeader.astro";
import TagHeadlineHero from "../components/TagHeadlineHero.astro";
import FlankedStatOverview from "../components/FlankedStatOverview.astro";
import PhotoFeatureTrio from "../components/PhotoFeatureTrio.astro";
import HoverServiceList from "../components/HoverServiceList.astro";
import StickyProjectStack from "../components/StickyProjectStack.astro";
import FreightPricingPlans from "../components/FreightPricingPlans.astro";
import ChamferStepCards from "../components/ChamferStepCards.astro";
import PortraitRatingTestimonials from "../components/PortraitRatingTestimonials.astro";
import NumberedFaqPhoto from "../components/NumberedFaqPhoto.astro";
import LogoMarqueeStrip from "../components/LogoMarqueeStrip.astro";
import ChamferBlogCards from "../components/ChamferBlogCards.astro";
import ShipCtaFooter from "../components/ShipCtaFooter.astro";

const scrollPillHeaderProps = {
	"items": [
		{
			"label": "Home",
			"href": "/",
			"children": [
				{
					"label": "Homepage 1",
					"href": "/"
				},
				{
					"label": "Homepage 2",
					"href": "/home-2"
				}
			]
		},
		{
			"label": "About",
			"href": "/about"
		},
		{
			"label": "Services",
			"href": "/services"
		},
		{
			"label": "Contact",
			"href": "/contact"
		}
	],
	"brandName": "Tideway",
	"cta": {
		"label": "Get a Quote",
		"href": "/contact"
	},
	"currentPath": "/"
};

const tagHeadlineHeroProps = {
	"lineOne": "Global",
	"tag": "Freight",
	"lineTwo": "Solutions",
	"text": "Moving cargo for growing businesses with dependable road, sea and air freight, planned end to end.",
	"cta": {
		"label": "See Our Services",
		"href": "/services"
	},
	"background": {
		"src": "/tag-headline-hero/hero-truck.webp",
		"alt": ""
	},
	"thumbnail": {
		"src": "/tag-headline-hero/card-truck.webp",
		"alt": "A blue semi truck approaching on a tree-lined highway"
	},
	"eyebrow": "{ Freight partner since 2009 }"
};

const flankedStatOverviewProps = {
	"heading": "Data-driven freight, *coast to coast*",
	"text": "We plan every load around the lane, the deadline and the cargo, then track it until it is signed for. Fewer handoffs, clearer updates, and freight that arrives when the schedule says it will.",
	"cta": {
		"label": "More About Us",
		"href": "/about"
	},
	"images": [
		{
			"src": "/flanked-stat-overview/ship.webp",
			"alt": "Aerial view of a loaded container ship crossing open water"
		},
		{
			"src": "/flanked-stat-overview/trucks.webp",
			"alt": "Two semi trucks travelling a highway at dusk"
		}
	],
	"stats": [
		{
			"value": "99%",
			"label": "On-time delivery rate",
			"icon": "burst",
			"tone": "navy"
		},
		{
			"value": "480+",
			"label": "Shipping partners nationwide",
			"icon": "people",
			"tone": "orange"
		},
		{
			"value": "+2.5K",
			"label": "Loads moved every month",
			"icon": "truck",
			"tone": "navy"
		}
	]
};

const photoFeatureTrioProps = {
	"eyebrow": "Why shippers stay",
	"heading": "Freight handled with *care* at every mile",
	"features": [
		{
			"title": "Fast and reliable delivery",
			"icon": "box",
			"image": {
				"src": "/photo-feature-trio/courier.webp",
				"alt": "Smiling courier in a yellow cap holding a parcel"
			}
		},
		{
			"title": "Secure cargo handling",
			"icon": "shield",
			"image": {
				"src": "/photo-feature-trio/truck-dusk.webp",
				"alt": "Semi truck with its lights on driving a road at dusk"
			}
		},
		{
			"title": "Wide network coverage",
			"icon": "route",
			"image": {
				"src": "/photo-feature-trio/container-ship.webp",
				"alt": "Container ship stacked with colourful boxes at sea"
			}
		}
	]
};

const hoverServiceListProps = {
	"heading": "Reliable freight and cargo *services*",
	"intro": "Door-to-door freight with secure handling, careful route planning and one team accountable from pickup to proof of delivery.",
	"image": {
		"src": "/hover-service-list/highway-semi.webp",
		"alt": "A semi truck hauling a trailer along a highway"
	},
	"caption": "One coordinator for every load: booking, customs paperwork, tracking and delivery sign-off, handled in one place.",
	"services": [
		{
			"title": "Ocean freight solutions",
			"href": "/services/ocean",
			"tags": [
				"Freight",
				"Cargo"
			],
			"icon": "ship"
		},
		{
			"title": "Freight forwarding",
			"href": "/services/forwarding",
			"tags": [
				"Customs",
				"Import"
			],
			"icon": "truck"
		},
		{
			"title": "Express cargo delivery",
			"href": "/services/express",
			"tags": [
				"Same day",
				"Tracked"
			],
			"icon": "box"
		},
		{
			"title": "Global shipping solution",
			"href": "/services/global",
			"tags": [
				"Air",
				"Sea"
			],
			"icon": "send"
		}
	]
};

const stickyProjectStackProps = {
	"heading": "Explore our real world *freight* projects",
	"projects": [
		{
			"title": "Cross-country retail freight",
			"text": "A weekly full-truckload program moving store inventory across four states, with dock appointments booked two days ahead and live ETAs for every stop.",
			"category": "Road",
			"meta": "2026",
			"cta": {
				"label": "View Details",
				"href": "/projects/retail-freight"
			},
			"image": {
				"src": "/sticky-project-stack/desert-truck.webp",
				"alt": "A blue semi truck on a desert highway"
			}
		},
		{
			"title": "Ocean freight consolidation",
			"text": "Less-than-container loads from six suppliers combined into shared containers, cutting per-unit shipping cost while keeping one bill of lading per order.",
			"category": "Ocean",
			"meta": "2026",
			"cta": {
				"label": "View Details",
				"href": "/projects/ocean-consolidation"
			},
			"image": {
				"src": "/sticky-project-stack/aerial-ship.webp",
				"alt": "Aerial view of a container ship on dark water"
			}
		},
		{
			"title": "Express cargo delivery",
			"text": "Time-critical replacement parts collected, cleared and delivered inside forty-eight hours, with a named coordinator on call until each crate was signed for.",
			"category": "Express",
			"meta": "2026",
			"cta": {
				"label": "View Details",
				"href": "/projects/express-cargo"
			},
			"image": {
				"src": "/sticky-project-stack/container-ship.webp",
				"alt": "A fully loaded container ship under way"
			}
		}
	]
};

const freightPricingPlansProps = {
	"heading": "Flexible pricing plans for *every shipper*",
	"image": {
		"src": "/freight-pricing-plans/forklift.webp",
		"alt": "A forklift lifting a shipping container in a port yard"
	},
	"plans": [
		{
			"name": "Basic",
			"pitch": "For small businesses shipping weekly",
			"price": "$199",
			"period": "/ Monthly",
			"cta": {
				"label": "Select Plan",
				"href": "/contact?plan=basic"
			},
			"features": [
				"Standard local delivery",
				"Basic cargo insurance",
				"Email support",
				"Real-time tracking",
				"Monthly report"
			]
		},
		{
			"name": "Standard",
			"pitch": "For teams shipping every day",
			"price": "$499",
			"period": "/ Monthly",
			"badge": "Popular",
			"cta": {
				"label": "Select Plan",
				"href": "/contact?plan=standard"
			},
			"features": [
				"Priority domestic shipping",
				"Comprehensive cargo insurance",
				"24/7 dedicated support",
				"Advanced real-time tracking",
				"Multi-location logistics"
			]
		}
	]
};

const chamferStepCardsProps = {
	"heading": "Easy steps to move your *freight*",
	"intro": "Three steps from first message to signed delivery, with one coordinator and live updates the whole way.",
	"steps": [
		{
			"title": "Get an instant quote",
			"text": "Share the lane, the cargo and the date, and get a clear price back the same day.",
			"image": {
				"src": "/chamfer-step-cards/containers.webp",
				"alt": "Stacks of shipping containers in a port yard"
			}
		},
		{
			"title": "Secure cargo transit",
			"text": "We book the carrier, handle the paperwork and keep every handoff documented.",
			"image": {
				"src": "/chamfer-step-cards/train.webp",
				"alt": "A freight train carrying stacked containers"
			}
		},
		{
			"title": "Live shipment tracking",
			"text": "Follow the load in real time until it reaches the final destination.",
			"image": {
				"src": "/chamfer-step-cards/courier.webp",
				"alt": "Courier in a red shirt carrying two parcels"
			}
		}
	]
};

const portraitRatingTestimonialsProps = {
	"testimonials": [
		{
			"quote": "They took over our weekly store deliveries and the missed-appointment calls simply stopped. Every load shows up in the tracker before we think to ask.",
			"name": "Marcus Hale",
			"role": "Operations Manager",
			"score": "5.0",
			"avatar": {
				"src": "/portrait-rating-testimonials/avatar-1.webp",
				"alt": ""
			}
		},
		{
			"quote": "Customs paperwork used to cost us a week per container. Now it is cleared before the ship docks, and our retail branches stopped running short.",
			"name": "Leila Moreno",
			"role": "Retail Logistics Lead",
			"score": "5.0",
			"avatar": {
				"src": "/portrait-rating-testimonials/avatar-2.webp",
				"alt": ""
			}
		},
		{
			"quote": "Their cold-chain loads arrive documented, on temperature and on time. From pickup confirmation to unloading, nothing gets lost in a handoff.",
			"name": "Owen Castillo",
			"role": "Distribution Coordinator",
			"score": "5.0",
			"avatar": {
				"src": "/portrait-rating-testimonials/avatar-3.webp",
				"alt": ""
			}
		},
		{
			"quote": "Building materials now reach our job sites on the day we plan for. The drivers call ahead and the site crews are never left waiting.",
			"name": "Hannah Brooks",
			"role": "Procurement Head",
			"score": "5.0",
			"avatar": {
				"src": "/portrait-rating-testimonials/avatar-4.webp",
				"alt": ""
			}
		}
	],
	"portrait": {
		"src": "/portrait-rating-testimonials/portrait.webp",
		"alt": "A smiling site worker in a hard hat and high-visibility vest"
	},
	"rating": {
		"score": "4.9/5.0",
		"count": "(2K Reviews)"
	}
};

const numberedFaqPhotoProps = {
	"heading": "Freight *questions* answered",
	"items": [
		{
			"question": "How long does freight delivery take?",
			"answer": "It depends on the lane, the cargo and the customs route. We confirm a realistic transit time before pickup and update you if anything on the road changes it."
		},
		{
			"question": "Can I track my shipment in real time?",
			"answer": "Yes. Every active shipment has a live tracking link with the current position, the next milestone and the latest estimated arrival."
		},
		{
			"question": "Do you handle customs and documentation?",
			"answer": "We prepare and file the commercial invoice, packing list and customs entries, and we flag anything missing before the cargo leaves the origin."
		},
		{
			"question": "What types of cargo can you move?",
			"answer": "General freight, palletised goods, temperature-controlled loads and oversized project cargo. Hazardous goods are quoted case by case."
		}
	],
	"image": {
		"src": "/numbered-faq-photo/white-semi.webp",
		"alt": "Two semi trucks travelling a highway under a hazy sky"
	}
};

const logoMarqueeStripProps = {
	"logos": [
		{
			"name": "Northfield",
			"glyph": "sun",
			"color": "#2f6fe4"
		},
		{
			"name": "Arcline",
			"glyph": "arch",
			"color": "#14b8a6"
		},
		{
			"name": "Keystone",
			"glyph": "print",
			"color": "#8b5cf6"
		},
		{
			"name": "Verdana Co",
			"glyph": "leaf",
			"color": "#22a45d"
		},
		{
			"name": "Summitra",
			"glyph": "wave",
			"color": "#ff6d45"
		},
		{
			"name": "Stackwell",
			"glyph": "stack",
			"color": "#0b4654"
		}
	],
	"highlight": "10,000+",
	"caption": "trusted customers all over the world"
};

const chamferBlogCardsProps = {
	"heading": "Read our latest *insights* from the road",
	"posts": [
		{
			"title": "How real-time tracking cuts missed deliveries",
			"href": "/blog/real-time-tracking",
			"date": "May 2, 2026",
			"datetime": "2026-05-02",
			"author": {
				"name": "Leila Moreno",
				"avatar": "/chamfer-blog-cards/author-1.webp"
			},
			"image": {
				"src": "/chamfer-blog-cards/mountain-truck.webp",
				"alt": "A truck crossing a valley road below the mountains"
			}
		},
		{
			"title": "Ten ways to lower your shipping costs",
			"href": "/blog/lower-shipping-costs",
			"date": "April 18, 2026",
			"datetime": "2026-04-18",
			"author": {
				"name": "Daniel Price",
				"avatar": "/chamfer-blog-cards/author-2.webp"
			},
			"image": {
				"src": "/chamfer-blog-cards/winter-train.webp",
				"alt": "A freight locomotive hauling containers through snow"
			}
		}
	]
};

const shipCtaFooterProps = {
	"heading": "Explore our *real world* freight services",
	"cta": {
		"label": "Get a Quote",
		"href": "/contact"
	},
	"brandName": "Tideway",
	"tagline": "A freight partner that plans every load and answers the phone when it matters.",
	"newsletter": {
		"action": "/subscribe",
		"label": "Email address",
		"placeholder": "you@company.com",
		"button": "Submit"
	},
	"columns": [
		{
			"heading": "Services",
			"links": [
				{
					"label": "Freight Services",
					"href": "/services/freight"
				},
				{
					"label": "Cargo Transportation",
					"href": "/services/cargo"
				},
				{
					"label": "Road Transport",
					"href": "/services/road"
				},
				{
					"label": "Global Shipping",
					"href": "/services/global"
				}
			]
		},
		{
			"heading": "Company",
			"links": [
				{
					"label": "About Us",
					"href": "/about"
				},
				{
					"label": "Contact",
					"href": "/contact"
				},
				{
					"label": "Careers",
					"href": "/careers"
				}
			]
		}
	],
	"contact": [
		{
			"label": "(555) 014-2290",
			"href": "tel:+15550142290"
		},
		{
			"label": "hello@tideway.example",
			"href": "mailto:hello@tideway.example"
		},
		{
			"label": "1200 Harbor Way, Oakland, CA",
			"href": "https://maps.google.com/?q=1200+Harbor+Way+Oakland+CA"
		}
	],
	"legalLinks": [
		{
			"label": "Terms & Conditions",
			"href": "/terms"
		},
		{
			"label": "Privacy Policy",
			"href": "/privacy"
		}
	],
	"social": [
		{
			"network": "facebook",
			"href": "https://facebook.com"
		},
		{
			"network": "x",
			"href": "https://x.com"
		},
		{
			"network": "instagram",
			"href": "https://instagram.com"
		}
	],
	"background": {
		"src": "/ship-cta-footer/ship-at-sea.webp"
	}
};
---

<html lang="en">
	<head>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1" />
		<title>[Business name] — [Freight service] across [Region]</title>
		<meta name="description" content="[One-sentence description of the carrier, its lanes and its offer.]" />
		<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>

		<!-- Scroll pill header -->
		<ScrollPillHeader {...scrollPillHeaderProps} />

		<main id="main">
			<!-- Tag headline hero -->
			<TagHeadlineHero {...tagHeadlineHeroProps} />

			<!-- Flanked stat overview -->
			<FlankedStatOverview {...flankedStatOverviewProps} />

			<!-- Photo feature trio -->
			<PhotoFeatureTrio {...photoFeatureTrioProps} />

			<!-- Hover service list -->
			<HoverServiceList {...hoverServiceListProps} />

			<!-- Sticky project stack -->
			<StickyProjectStack {...stickyProjectStackProps} />

			<!-- Freight pricing plans -->
			<FreightPricingPlans {...freightPricingPlansProps} />

			<!-- Chamfer step cards -->
			<ChamferStepCards {...chamferStepCardsProps} />

			<!-- Portrait rating testimonials -->
			<PortraitRatingTestimonials {...portraitRatingTestimonialsProps} />

			<!-- Numbered FAQ photo -->
			<NumberedFaqPhoto {...numberedFaqPhotoProps} />

			<!-- Logo marquee strip -->
			<LogoMarqueeStrip {...logoMarqueeStripProps} />

			<!-- Chamfer blog cards -->
			<ChamferBlogCards {...chamferBlogCardsProps} />
		</main>

		<!-- Ship CTA footer -->
		<ShipCtaFooter {...shipCtaFooterProps} />
	</body>
</html>

===== END src/pages/tideway-homepage.astro =====

===== FILE (required): src/components/ScrollPillHeader.astro =====
---
/**
 * ScrollPillHeader
 *
 * A 1350px pill bar fixed 20px from the top of the viewport. Over the hero it
 * is a faint white wash with white type; once the page scrolls past
 * `threshold` it turns solid white with navy type. Logo on the left, links in
 * the middle (an item with `children` opens a small dropdown card), an orange
 * "Get a Quote" pill on the right. Below 1000px it folds to the logo and a
 * round menu button that opens a white card under the bar.
 *
 * Reads the --tw-* tokens from any ancestor, with fallbacks.
 */

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

export interface ScrollPillHeaderItem {
	label: string;
	/** Required for a plain link; ignored on a dropdown trigger. */
	href?: string;
	/** Turns the item into a dropdown trigger. */
	children?: ScrollPillHeaderLink[];
	/**
	 * Mega-menu shaped data from other headers in this library. The links of
	 * every group are flattened into one dropdown list; headings are dropped.
	 */
	groups?: { heading?: string; links: ScrollPillHeaderLink[] }[];
}

export interface ScrollPillHeaderProps {
	items?: ScrollPillHeaderItem[];
	/** Pass Astro.url.pathname. Matching links get aria-current="page". */
	currentPath?: string;
	brandName?: string;
	brandHref?: string;
	/** Replaces the drawn mark and wordmark. Give a light and a dark version. */
	logo?: { src: string; srcOnDark?: string; alt: string; width?: number; height?: number };
	/** The orange pill. `null` drops it. */
	cta?: ScrollPillHeaderLink | null;
	/** Always solid white, for pages whose top is not a dark hero. */
	solid?: boolean;
	/** Scroll distance in px after which the bar turns solid. */
	threshold?: number;
	navLabel?: string;
	menuLabel?: string;
	fontStylesheet?: string;
	id?: string;
}

interface Props extends ScrollPillHeaderProps {}

/* No invented site: links, brand and CTA are all the caller's. Each block
   renders only when its data is supplied; with none, nothing renders. */
const {
	items: rawItems = [],
	currentPath,
	brandName,
	brandHref = "/",
	logo,
	cta = null,
	solid = false,
	threshold = 8,
	navLabel = "Primary",
	menuLabel = "Menu",
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Outfit:wght@500;600;700;800;900&family=Noto+Sans:wght@400;500;600;700&family=Inter:wght@500;600;700&display=swap",
	id,
} = Astro.props;

const items = rawItems.map((item) => ({
	label: item.label,
	href: item.href ?? "#",
	children: item.children ?? item.groups?.flatMap((g) => g.links),
}));

const seed = id ? id : `sph-${Math.random().toString(36).slice(2, 8)}`;
const menuId = `${seed}-menu`;

const SAFE = /^(?:https?:|mailto:|tel:|[#/.?])/i;
const safeHref = (href?: string) => (href && SAFE.test(href.trim()) ? href.trim() : "#");

const norm = (p: string) => (p.length > 1 ? p.replace(/\/+$/, "") : p);
const isCurrent = (href?: string) => Boolean(href && currentPath && norm(href) === norm(currentPath));
const hasCurrentChild = (item: { children?: ScrollPillHeaderLink[] }) => (item.children ?? []).some((c) => isCurrent(c.href));

const brand = brandName?.trim() ?? "";
const hasBrand = Boolean(logo || brand);
const hasItems = items.length > 0;
const hasMenu = hasItems || Boolean(cta);
const hasContent = hasBrand || hasMenu;
---

{hasContent && fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
{hasContent && (
<header
	class="sph"
	id={id}
	data-header-root
	data-sph-root
	data-sph-threshold={threshold}
	data-solid={solid ? "" : undefined}
>
	<div class="sph-bar">
		{hasBrand && (
		<a class="sph-brand" href={safeHref(brandHref)} aria-label={logo ? undefined : `${brand} home`}>
			{
				logo ? (
					<>
						<img class="sph-logo sph-logo--light" src={logo.src} alt={logo.alt} width={logo.width ?? 167} height={logo.height ?? 43} />
						{logo.srcOnDark && (
							<img class="sph-logo sph-logo--dark" src={logo.srcOnDark} alt="" width={logo.width ?? 167} height={logo.height ?? 43} />
						)}
					</>
				) : (
					<>
						<svg class="sph-mark" viewBox="0 0 40 40" aria-hidden="true">
							<path d="M6 21a14 14 0 0 1 28 0h-6a8 8 0 0 0-16 0Z" fill="currentColor" />
							<path d="M20 17 11 30h6v7h6v-7h6Z" fill="currentColor" />
						</svg>
						<span class="sph-word">{brand}</span>
					</>
				)
			}
		</a>
		)}

		{hasItems && (
		<nav class="sph-nav" aria-label={navLabel}>
			<ul class="sph-list">
				{
					items.map((item, i) =>
						item.children?.length ? (
							<li class="sph-item sph-item--drop" data-sph-drop>
								<button
									type="button"
									class="sph-link sph-trigger"
									aria-expanded="false"
									aria-controls={`${seed}-drop-${i}`}
									data-sph-trigger
									data-current={hasCurrentChild(item) ? "" : undefined}
								>
									{item.label}
									<svg class="sph-chev" viewBox="0 0 12 12" aria-hidden="true">
										<path d="M2 4.25 6 8l4-3.75" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" />
									</svg>
								</button>
								<ul class="sph-drop" id={`${seed}-drop-${i}`} data-sph-panel>
									{item.children.map((child) => (
										<li>
											<a href={safeHref(child.href)} aria-current={isCurrent(child.href) ? "page" : undefined}>
												{child.label}
											</a>
										</li>
									))}
								</ul>
							</li>
						) : (
							<li class="sph-item">
								<a class="sph-link" href={safeHref(item.href)} aria-current={isCurrent(item.href) ? "page" : undefined}>
									{item.label}
								</a>
							</li>
						),
					)
				}
			</ul>
		</nav>
		)}

		{
			cta && (
				<a class="sph-cta" href={safeHref(cta.href)}>
					<span>{cta.label}</span>
					<span class="sph-cta-ic" aria-hidden="true">
						<svg viewBox="0 0 24 24"><path d="M8 5l10 7-10 7 3-7Z" fill="currentColor" /></svg>
					</span>
				</a>
			)
		}

		{hasMenu && (
		<button type="button" class="sph-burger" aria-expanded="false" aria-controls={menuId} data-sph-burger>
			<span class="sph-sr">{menuLabel}</span>
			<svg viewBox="0 0 24 24" aria-hidden="true">
				<circle cx="12" cy="12" r="9.25" fill="none" stroke="currentColor" stroke-width="1.5" />
				<path d="M12 7.5v9M7.5 12h9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
			</svg>
		</button>
		)}
	</div>

	{hasMenu && (
	<div class="sph-menu" id={menuId} data-sph-menu>
		{hasItems && (
		<ul>
			{
				items.flatMap((item) =>
					item.children?.length
						? item.children.map((child) => (
								<li>
									<a href={safeHref(child.href)} aria-current={isCurrent(child.href) ? "page" : undefined}>
										{child.label}
									</a>
								</li>
							))
						: [
								<li>
									<a href={safeHref(item.href)} aria-current={isCurrent(item.href) ? "page" : undefined}>
										{item.label}
									</a>
								</li>,
							],
				)
			}
		</ul>
		)}
		{
			cta && (
				<a class="sph-cta sph-cta--menu" href={safeHref(cta.href)}>
					<span>{cta.label}</span>
					<span class="sph-cta-ic" aria-hidden="true">
						<svg viewBox="0 0 24 24"><path d="M8 5l10 7-10 7 3-7Z" fill="currentColor" /></svg>
					</span>
				</a>
			)
		}
	</div>
	)}
</header>
)}

<style>
	.sph {
		--sph-navy: var(--tw-navy, #0b4654);
		--sph-deep: var(--tw-navy-deep, #052931);
		--sph-accent: var(--tw-accent, #ff6d45);
		--sph-body: var(--tw-body, "Noto Sans", ui-sans-serif, system-ui, sans-serif);
		--sph-display: var(--tw-display, Outfit, ui-sans-serif, system-ui, sans-serif);
		--sph-ease: cubic-bezier(0.44, 0, 0.56, 1);
		--sph-ink: #fff;

		position: fixed;
		top: 12px;
		left: 50%;
		z-index: 40;
		width: min(1350px, calc(100% - 24px));
		transform: translateX(-50%);
		font-family: var(--sph-body);
	}

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

	.sph[data-scrolled],
	.sph[data-solid],
	.sph[data-sph-open] {
		--sph-ink: var(--sph-navy);
	}

	.sph-bar {
		position: relative;
		display: flex;
		align-items: center;
		gap: 16px;
		height: 64px;
		padding: 0 12px 0 16px;
		border-radius: 999px;
		background: rgb(255 255 255 / 0.1);
		color: var(--sph-ink);
		/* No colour transition here: the links carry their own, and a second
		   one on the parent would chain behind it. */
		transition:
			background-color 0.4s var(--sph-ease),
			box-shadow 0.4s var(--sph-ease);
	}

	.sph[data-scrolled] .sph-bar,
	.sph[data-solid] .sph-bar,
	.sph[data-sph-open] .sph-bar {
		background: #fff;
		box-shadow: 0 0 0 1px rgb(11 70 84 / 0.08);
	}

	.sph-brand {
		display: inline-flex;
		align-items: center;
		gap: 8px;
		margin-right: auto;
		color: inherit;
		text-decoration: none;
		border-radius: 8px;
	}

	.sph-mark {
		width: 32px;
		height: 32px;
		color: var(--sph-accent);
	}

	.sph-word {
		font: 500 26px/1 var(--sph-display);
		letter-spacing: -0.5px;
	}

	.sph-logo {
		display: block;
		width: auto;
		height: 34px;
	}

	.sph-logo--dark {
		display: none;
	}

	.sph:not([data-scrolled]):not([data-solid]):not([data-sph-open]) .sph-logo--dark {
		display: block;
	}

	.sph:not([data-scrolled]):not([data-solid]):not([data-sph-open]) .sph-logo--dark ~ .sph-logo--light,
	.sph:not([data-scrolled]):not([data-solid]):not([data-sph-open]) .sph-logo--light:has(+ .sph-logo--dark) {
		display: none;
	}

	.sph-nav,
	.sph > .sph-bar > .sph-cta {
		display: none;
	}

	.sph-list {
		display: flex;
		align-items: center;
		gap: 35px;
		margin: 0;
		padding: 0;
		list-style: none;
	}

	.sph-item {
		position: relative;
	}

	.sph-link {
		display: inline-flex;
		align-items: center;
		gap: 4px;
		padding: 8px 0;
		border: 0;
		background: none;
		color: inherit;
		font: 500 16px/25.6px var(--sph-body);
		letter-spacing: -0.32px;
		text-decoration: none;
		cursor: pointer;
		transition: color 0.3s var(--sph-ease);
	}

	.sph-link:hover,
	.sph-item--drop[data-open] .sph-trigger,
	.sph-link[aria-current="page"],
	.sph-trigger[data-current] {
		color: var(--sph-accent);
	}

	.sph-chev {
		width: 14px;
		height: 14px;
		transition: transform 0.3s var(--sph-ease);
	}

	.sph-item--drop[data-open] .sph-chev {
		transform: rotate(180deg);
	}

	.sph-drop {
		position: absolute;
		top: calc(100% + 16px);
		left: 0;
		min-width: 290px;
		margin: 0;
		padding: 6px 10px;
		list-style: none;
		border-radius: 10px;
		background: #fff;
		box-shadow: 0 12px 30px rgb(5 41 49 / 0.14);
		opacity: 0;
		visibility: hidden;
		transform: translateY(6px);
		transition:
			opacity 0.25s var(--sph-ease),
			transform 0.25s var(--sph-ease),
			visibility 0s linear 0.25s;
	}

	/* A bridge over the gap, so moving the pointer from the trigger to the
	   card does not cross empty space and close it. */
	.sph-drop::before {
		content: "";
		position: absolute;
		inset: -18px 0 100% 0;
	}

	.sph-item--drop[data-open] .sph-drop {
		opacity: 1;
		visibility: visible;
		transform: none;
		transition-delay: 0s;
	}

	.sph-drop li + li {
		border-top: 1px solid rgb(11 70 84 / 0.1);
	}

	.sph-drop a {
		display: block;
		padding: 10px 0;
		color: var(--sph-navy);
		font: 500 16px/1.4 var(--sph-body);
		text-transform: uppercase;
		text-decoration: none;
		transition: color 0.3s var(--sph-ease);
	}

	.sph-drop a:hover,
	.sph-drop a[aria-current="page"] {
		color: var(--sph-accent);
	}

	.sph-cta {
		display: inline-flex;
		align-items: center;
		gap: 12px;
		height: 50px;
		padding: 0 8px 0 25px;
		border-radius: 999px;
		background: var(--sph-accent);
		color: #fff;
		font: 400 16px/25.6px var(--sph-body);
		letter-spacing: -0.32px;
		text-decoration: none;
		white-space: nowrap;
		transition: background-color 0.3s var(--sph-ease);
	}

	.sph-cta:hover {
		background: var(--sph-deep);
	}

	.sph-cta-ic {
		display: grid;
		place-items: center;
		width: 34px;
		height: 34px;
		border-radius: 50%;
		background: #fff;
		color: var(--sph-accent);
		transition: transform 0.3s var(--sph-ease);
	}

	.sph-cta-ic svg {
		width: 18px;
		height: 18px;
	}

	.sph-cta:hover .sph-cta-ic {
		transform: rotate(-45deg);
	}

	.sph-burger {
		display: grid;
		place-items: center;
		width: 44px;
		height: 44px;
		padding: 0;
		border: 0;
		border-radius: 50%;
		background: none;
		color: var(--sph-accent);
		cursor: pointer;
	}

	.sph-burger svg {
		width: 26px;
		height: 26px;
		transition: transform 0.3s var(--sph-ease);
	}

	.sph[data-sph-open] .sph-burger svg {
		transform: rotate(45deg);
	}

	.sph-menu {
		position: absolute;
		top: calc(100% + 10px);
		left: 0;
		right: 0;
		display: none;
		padding: 12px 20px 20px;
		border-radius: 20px;
		background: #fff;
		box-shadow: 0 16px 40px rgb(5 41 49 / 0.18);
	}

	.sph[data-sph-open] .sph-menu {
		display: block;
	}

	.sph-menu ul {
		margin: 0 0 16px;
		padding: 0;
		list-style: none;
	}

	.sph-menu li + li {
		border-top: 1px solid rgb(11 70 84 / 0.1);
	}

	.sph-menu a:not(.sph-cta) {
		display: block;
		padding: 12px 0;
		color: var(--sph-navy);
		font: 500 17px/1.4 var(--sph-body);
		text-decoration: none;
	}

	.sph-menu a:not(.sph-cta):hover,
	.sph-menu a[aria-current="page"] {
		color: var(--sph-accent);
	}

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

	.sph a:focus-visible,
	.sph button:focus-visible {
		outline: 2px solid var(--sph-accent);
		outline-offset: 3px;
	}

	/* No JavaScript: no menu button, and the phone card is shown under the bar
	   as a plain list so every link stays reachable. */
	.sph:not([data-sph-ready]) .sph-burger {
		display: none;
	}

	.sph:not([data-sph-ready]) .sph-menu {
		display: block;
	}

	.sph:not([data-sph-ready]) .sph-bar {
		background: #fff;
		--sph-ink: var(--sph-navy);
		color: var(--sph-navy);
	}

	.sph:not([data-sph-ready]) .sph-item--drop:focus-within .sph-drop,
	.sph:not([data-sph-ready]) .sph-item--drop:hover .sph-drop {
		opacity: 1;
		visibility: visible;
		transform: none;
		transition-delay: 0s;
	}

	@media (min-width: 810px) {
		.sph {
			top: 20px;
			width: min(1350px, calc(100% - 60px));
		}

		.sph-bar {
			height: 80px;
			padding: 0 16px 0 22px;
		}
	}

	@media (min-width: 1000px) {
		.sph {
			width: min(1350px, calc(100% - 90px));
		}

		.sph-bar {
			height: 90px;
			padding: 0 20px 0 22px;
		}

		.sph-nav {
			display: block;
			position: absolute;
			left: 50%;
			transform: translateX(-50%);
		}

		.sph > .sph-bar > .sph-cta {
			display: inline-flex;
		}

		.sph-burger,
		.sph:not([data-sph-ready]) .sph-menu,
		.sph .sph-menu {
			display: none !important;
		}

		.sph-mark {
			width: 38px;
			height: 38px;
		}

		.sph-word {
			font-size: 32px;
		}

		.sph-logo {
			height: 43px;
		}
	}

	@media (prefers-reduced-motion: reduce) {
		.sph-bar,
		.sph-link,
		.sph-chev,
		.sph-drop,
		.sph-cta,
		.sph-cta-ic,
		.sph-burger svg {
			transition: none;
		}

		.sph-cta:hover .sph-cta-ic {
			transform: none;
		}
	}

	@media (forced-colors: active) {
		.sph-bar,
		.sph-drop,
		.sph-menu {
			border: 1px solid CanvasText;
		}
	}
</style>

<script>
	/**
	 * ScrollPillHeader controller: the scroll swap, the desktop dropdowns and
	 * the phone menu. One closure per instance, every lookup scoped to its
	 * root, every listener on one AbortController aborted before a client-side
	 * navigation swaps the document. No body scroll lock.
	 */
	function initScrollPillHeader(root: HTMLElement) {
		if (root.dataset.sphReady === "") return;
		const burger = root.querySelector<HTMLButtonElement>("[data-sph-burger]");
		const menu = root.querySelector<HTMLElement>("[data-sph-menu]");

		root.dataset.sphReady = "";
		const controller = new AbortController();
		const { signal } = controller;
		const threshold = Number(root.dataset.sphThreshold) || 8;
		const desktop = window.matchMedia("(min-width: 1000px)");
		const hoverable = window.matchMedia("(hover: hover)");

		/* ---- scroll swap ---- */
		let ticking = false;
		const applyScroll = () => {
			ticking = false;
			root.toggleAttribute("data-scrolled", window.scrollY > threshold);
		};
		applyScroll();
		window.addEventListener(
			"scroll",
			() => {
				if (ticking) return;
				ticking = true;
				requestAnimationFrame(applyScroll);
			},
			{ passive: true, signal },
		);

		/* ---- dropdowns ---- */
		const drops = [...root.querySelectorAll<HTMLElement>("[data-sph-drop]")];
		const setDrop = (drop: HTMLElement, open: boolean, returnFocus = false) => {
			const trigger = drop.querySelector<HTMLButtonElement>("[data-sph-trigger]");
			if (!trigger || drop.hasAttribute("data-open") === open) return;
			drop.toggleAttribute("data-open", open);
			trigger.setAttribute("aria-expanded", String(open));
			if (!open && returnFocus) trigger.focus({ preventScroll: true });
		};
		const closeDrops = (except?: HTMLElement) => drops.forEach((d) => d !== except && setDrop(d, false));

		for (const drop of drops) {
			const trigger = drop.querySelector<HTMLButtonElement>("[data-sph-trigger]");
			if (!trigger) continue;
			trigger.addEventListener(
				"click",
				() => {
					const next = !drop.hasAttribute("data-open");
					closeDrops(drop);
					setDrop(drop, next);
				},
				{ signal },
			);
			drop.addEventListener(
				"pointerenter",
				(e) => {
					if (e.pointerType === "mouse" && hoverable.matches) {
						closeDrops(drop);
						setDrop(drop, true);
					}
				},
				{ signal },
			);
			drop.addEventListener(
				"pointerleave",
				(e) => {
					if (e.pointerType === "mouse" && hoverable.matches) setDrop(drop, false);
				},
				{ signal },
			);
			drop.addEventListener(
				"focusout",
				(e) => {
					const next = e.relatedTarget as Node | null;
					if (next && !drop.contains(next)) setDrop(drop, false);
				},
				{ signal },
			);
		}

		/* ---- phone menu ---- */
		let open = false;
		/* No links and no CTA means no menu button and no card: the scroll
		   swap and dropdowns above still run. */
		const setMenu = (next: boolean, returnFocus = false) => {
			if (!burger || !menu || open === next) return;
			open = next;
			root.toggleAttribute("data-sph-open", next);
			burger.setAttribute("aria-expanded", String(next));
			if (!next && returnFocus) burger.focus({ preventScroll: true });
		};
		burger?.addEventListener("click", () => setMenu(!open), { signal });
		menu?.addEventListener(
			"click",
			(e) => {
				if ((e.target as HTMLElement).closest("a[href]")) setMenu(false);
			},
			{ signal },
		);
		root.addEventListener(
			"focusout",
			(e) => {
				const next = e.relatedTarget as Node | null;
				if (open && next && !root.contains(next)) setMenu(false);
			},
			{ signal },
		);

		/* Escape and outside click on the document: after a client-side
		   navigation focus can sit outside the header. */
		document.addEventListener(
			"keydown",
			(e) => {
				if (e.key !== "Escape") return;
				const openDrop = drops.find((d) => d.hasAttribute("data-open"));
				if (openDrop) {
					e.preventDefault();
					setDrop(openDrop, false, openDrop.contains(document.activeElement));
				} else if (open) {
					e.preventDefault();
					setMenu(false, true);
				}
			},
			{ signal },
		);
		document.addEventListener(
			"pointerdown",
			(e) => {
				if (root.contains(e.target as Node)) return;
				closeDrops();
				setMenu(false);
			},
			{ signal },
		);
		desktop.addEventListener(
			"change",
			() => {
				setMenu(false);
				closeDrops();
			},
			{ signal },
		);

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

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

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

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

===== END src/components/ScrollPillHeader.astro =====

===== FILE (required): src/components/TagHeadlineHero.astro =====
---
/**
 * TagHeadlineHero
 *
 * A 900px full-bleed photo hero washed navy from the left: a translucent
 * eyebrow chip, a huge two-line uppercase headline with an orange tag box
 * between its words, and a bottom row pairing a framed photo thumbnail with a
 * sentence and an orange pill. Leaves room at the top for a fixed pill header.
 *
 * The headline rises in on load and the eyebrow and bottom row follow; both
 * are CSS animations that end at rest, so nothing is hidden without JS.
 *
 * Reads the --tw-* tokens from any ancestor, with fallbacks.
 */

export interface TagHeadlineHeroProps {
	eyebrow?: string;
	/** First headline line. */
	lineOne?: string;
	/** The orange tag box after the first line. `""` drops it. */
	tag?: string;
	/** Second headline line. */
	lineTwo?: string;
	text?: string;
	cta?: { label: string; href: string } | null;
	/** A call link beside the pill, outlined so the pill stays primary. The component builds the tel: link. Omitted or `null`: none. */
	phone?: { number: string; label?: string } | null;
	/** A quiet text link under the actions (e.g. to the services below). Omitted or `null`: none. */
	link?: { label: string; href: string } | null;
	/** Background photo. Omitted, or `src: ""`, renders a labelled placeholder. */
	background?: { src?: string; alt?: string };
	/** Framed thumbnail beside the sentence. Omitted or `null`: none. `src: ""` renders a placeholder. */
	thumbnail?: { src?: string; alt?: string } | null;
	/** Run the load-in animation. */
	animate?: boolean;
	fontStylesheet?: string;
	class?: string;
	id?: string;
}

interface Props extends TagHeadlineHeroProps {}

const {
	/* No copy, photos or link of its own: the catalog demo passes them through
	   the registry's props, so a copied hero never ships an invented business. */
	eyebrow = "",
	lineOne = "",
	tag = "",
	lineTwo = "",
	text = "",
	cta = null,
	phone = null,
	link = null,
	background: bgProp,
	thumbnail: thumbProp,
	animate = true,
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Outfit:wght@500;600;700;800;900&family=Noto+Sans:wght@400;500;600;700&family=Inter:wght@500;600;700&display=swap",
	class: className,
	id,
} = Astro.props;

/* Only the photos passed; a photo with no alt is decorative. No thumbnail unless one is given. */
const background = { src: bgProp?.src ?? "", alt: bgProp?.alt ?? "" };
const thumbnail = thumbProp ? { src: thumbProp.src ?? "", alt: thumbProp.alt ?? "" } : null;

const SAFE = /^(?:https?:|mailto:|tel:|[#/.?])/i;
const safeHref = (href: string) => (SAFE.test(href.trim()) ? href.trim() : "#");

/* A link needs both halves: a label with no destination, or the reverse, is not drawn. */
const ctaLink = cta && cta.label && cta.href ? cta : null;
const dial = phone?.number ? phone.number.replace(/[^\d+]/g, "") : "";
const secondaryLink = dial.length >= 7 ? { label: phone!.label ? `${phone!.label} ${phone!.number}` : phone!.number, href: `tel:${dial}` } : null;

/* A photo alone is not a hero: with no copy or link, render nothing. */
const hasTitle = !!lineOne || !!tag || !!lineTwo;
const textLink = link && link.label && link.href ? link : null;
const hasCopy = !!text || !!ctaLink || !!secondaryLink || !!textLink;
const hasContent = !!eyebrow || hasTitle || hasCopy;
---

{hasContent && fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
{hasContent && (
<section class:list={["thh", className]} id={id} data-animate={animate ? "" : undefined}>
	<div class="thh-bg">
		{
			background.src ? (
				<img src={background.src} alt={background.alt} width="1920" height="950" fetchpriority="high" decoding="async" />
			) : (
				<div class="thh-ph" role="img" aria-label="Image placeholder: wide landscape photo">
					<span>Image · wide landscape photo · 1920 × 950 px</span>
				</div>
			)
		}
	</div>
	<div class="thh-wash" aria-hidden="true"></div>

	<div class="thh-inner">
		{
			eyebrow && (
				<p class="thh-eyebrow thh-late">
					<span class="thh-eyebrow-ic" aria-hidden="true">
						<svg viewBox="0 0 24 24"><path d="M8 5l10 7-10 7 3-7Z" fill="currentColor" /></svg>
					</span>
					{eyebrow}
				</p>
			)
		}
		{hasTitle && (
		<h1 class="thh-title">
			{(lineOne || tag) && (
			<span class="thh-line">
				{lineOne && <span class="thh-word">{lineOne}</span>}
				{tag && <span class="thh-tag">{tag}</span>}
			</span>
			)}
			{lineTwo && (
			<span class="thh-line">
				<span class="thh-word">{lineTwo}</span>
			</span>
			)}
		</h1>
		)}

		{(thumbnail || hasCopy) && (
		<div class="thh-foot thh-late">
			{
				thumbnail && (
					<div class="thh-thumb">
						{thumbnail.src ? (
							<img src={thumbnail.src} alt={thumbnail.alt} width="428" height="300" loading="lazy" decoding="async" />
						) : (
							<div class="thh-ph thh-ph--thumb" role="img" aria-label="Image placeholder: landscape photo">
								<span>Image · 428 × 300</span>
							</div>
						)}
					</div>
				)
			}
			{hasCopy && (
			<div class="thh-copy">
				{text && <p class="thh-text">{text}</p>}
				{
					secondaryLink ? (
						<div class="thh-actions">
							{ctaLink && (
								<a class="thh-btn" href={safeHref(ctaLink.href)}>
									<span>{ctaLink.label}</span>
									<span class="thh-btn-ic" aria-hidden="true">
										<svg viewBox="0 0 24 24"><path d="M8 5l10 7-10 7 3-7Z" fill="currentColor" /></svg>
									</span>
								</a>
							)}
							<a class="thh-btn thh-btn--quiet" href={safeHref(secondaryLink.href)}>
								<span>{secondaryLink.label}</span>
							</a>
						</div>
					) : (
						ctaLink && (
							<a class="thh-btn" href={safeHref(ctaLink.href)}>
								<span>{ctaLink.label}</span>
								<span class="thh-btn-ic" aria-hidden="true">
									<svg viewBox="0 0 24 24"><path d="M8 5l10 7-10 7 3-7Z" fill="currentColor" /></svg>
								</span>
							</a>
						)
					)
				}
				{textLink && (
					<a class="thh-link" href={safeHref(textLink.href)}>{textLink.label}<span aria-hidden="true"> →</span></a>
				)}
			</div>
			)}
		</div>
		)}
	</div>
</section>
)}

<style>
	.thh {
		--thh-navy: var(--tw-navy, #0b4654);
		--thh-deep: var(--tw-navy-deep, #052931);
		--thh-accent: var(--tw-accent, #ff6d45);
		--thh-display: var(--tw-display, Outfit, ui-sans-serif, system-ui, sans-serif);
		--thh-body: var(--tw-body, "Noto Sans", ui-sans-serif, system-ui, sans-serif);
		--thh-ease: cubic-bezier(0.44, 0, 0.56, 1);

		position: relative;
		isolation: isolate;
		display: flex;
		min-height: 844px;
		overflow: clip;
		background: var(--thh-navy);
		color: #fff;
		font-family: var(--thh-body);
	}

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

	.thh-bg,
	.thh-wash {
		position: absolute;
		inset: 0;
		z-index: -1;
	}

	.thh-bg img {
		width: 100%;
		height: 100%;
		object-fit: cover;
		object-position: 70% center;
	}

	/* Phone: the photo sits behind a stronger, even wash so white type reads. */
	.thh-wash {
		background: linear-gradient(180deg in srgb, rgb(from var(--thh-navy) r g b / 0.55) 0%, rgb(from var(--thh-navy) r g b / 0.8) 60%, var(--thh-navy) 100%);
	}

	.thh-inner {
		display: flex;
		flex-direction: column;
		justify-content: center;
		gap: 22px;
		width: 100%;
		max-width: var(--tw-grid-max, 1440px);
		margin: 0 auto;
		padding: 130px 16px 64px;
	}

	.thh-eyebrow {
		display: inline-flex;
		align-items: center;
		gap: 8px;
		align-self: flex-start;
		margin: 0;
		padding: 5px 12px 5px 5px;
		border-radius: 999px;
		background: rgb(140 140 140 / 0.2);
		font: 600 14px/22.4px var(--thh-body);
		letter-spacing: -0.28px;
		text-transform: uppercase;
	}

	.thh-eyebrow-ic {
		display: grid;
		place-items: center;
		width: 22px;
		height: 22px;
		border-radius: 50%;
		background: var(--thh-accent);
	}

	.thh-eyebrow-ic svg {
		width: 12px;
		height: 12px;
	}

	.thh-title {
		display: flex;
		flex-direction: column;
		gap: 8px;
		margin: 0;
		font: 600 40px/1.1 var(--thh-display);
		letter-spacing: -0.8px;
		text-transform: uppercase;
	}

	.thh-line {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		gap: 8px 18px;
	}

	.thh-tag {
		display: inline-block;
		padding: 4px 16px;
		border-radius: 12px;
		background: var(--thh-accent);
		font: 700 28px/1.3 var(--thh-display);
		letter-spacing: -0.56px;
	}

	.thh-foot {
		display: flex;
		flex-direction: column;
		gap: 22px;
		margin-top: 18px;
	}

	.thh-thumb {
		width: min(230px, 100%);
		padding: 8px;
		border-radius: 10px;
		background: rgb(140 140 140 / 0.2);
	}

	.thh-thumb img,
	.thh-ph--thumb {
		display: block;
		width: 100%;
		height: auto;
		aspect-ratio: 214 / 150;
		border-radius: 10px;
		object-fit: cover;
	}

	.thh-copy {
		display: flex;
		flex-direction: column;
		align-items: flex-start;
		gap: 24px;
	}

	.thh-text {
		max-width: 416px;
		margin: 0;
		font: 400 16px/25.6px var(--thh-body);
		letter-spacing: -0.32px;
	}

	.thh-btn {
		display: inline-flex;
		align-items: center;
		gap: 12px;
		height: 50px;
		padding: 0 8px 0 25px;
		border-radius: 999px;
		background: var(--thh-accent);
		color: #fff;
		font: 400 16px/25.6px var(--thh-body);
		letter-spacing: -0.32px;
		text-decoration: none;
		transition: background-color 0.3s var(--thh-ease);
	}

	.thh-btn:hover {
		background: var(--thh-deep);
	}

	.thh-btn-ic {
		display: grid;
		place-items: center;
		width: 34px;
		height: 34px;
		border-radius: 50%;
		background: #fff;
		color: var(--thh-accent);
		transition: transform 0.3s var(--thh-ease);
	}

	.thh-btn-ic svg {
		width: 18px;
		height: 18px;
	}

	.thh-btn:hover .thh-btn-ic {
		transform: rotate(-45deg);
	}

	.thh-btn:focus-visible {
		outline: 2px solid #fff;
		outline-offset: 3px;
	}

	.thh-link {
		color: #fff;
		font: 500 15px/1.4 var(--thh-body);
		text-decoration: underline;
		text-underline-offset: 4px;
		text-decoration-color: rgb(255 255 255 / 0.5);
	}

	.thh-link:hover {
		text-decoration-color: #fff;
	}

	.thh-link:focus-visible {
		outline: 2px solid #fff;
		outline-offset: 3px;
	}

	.thh-actions {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		gap: 12px;
	}

	/* A second action: outlined, no icon, so the pill stays the primary step. */
	.thh-btn--quiet {
		padding: 0 25px;
		border: 1px solid rgb(255 255 255 / 0.55);
		background: transparent;
	}

	.thh-btn--quiet:hover {
		border-color: #fff;
		background: rgb(255 255 255 / 0.12);
	}

	.thh-ph {
		display: grid;
		place-items: center;
		width: 100%;
		height: 100%;
		padding: 16px;
		background: repeating-linear-gradient(135deg, #154f5c 0 12px, #1a5866 12px 24px);
		color: rgb(255 255 255 / 0.75);
		font: 500 13px/1.4 var(--thh-body);
		text-align: center;
	}

	/* ---- load-in: CSS animations that end at rest ---- */
	@keyframes thh-rise {
		from {
			opacity: 0;
			transform: translateY(var(--thh-from, 20px));
		}
	}

	.thh[data-animate] .thh-title {
		animation: thh-rise 0.8s var(--thh-ease) both;
	}

	.thh[data-animate] .thh-late {
		--thh-from: 40px;
		animation: thh-rise 1s var(--thh-ease) 0.5s both;
	}

	@media (min-width: 810px) {
		.thh {
			min-height: 900px;
		}

		.thh-wash {
			background: linear-gradient(90deg in srgb, var(--thh-navy) 0%, var(--thh-navy) 30%, rgb(from var(--thh-navy) r g b / 0.7) 48%, rgb(from var(--thh-navy) r g b / 0) 72%);
		}

		.thh-inner {
			padding: 170px 40px 80px;
		}

		.thh-title {
			font-size: 72px;
			letter-spacing: -1.44px;
		}

		.thh-tag {
			font-size: 40px;
		}

		.thh-foot {
			flex-direction: row;
			align-items: flex-start;
			gap: 20px;
		}
	}

	@media (min-width: 1200px) {
		.thh-inner {
			padding: 200px 0 110px;
			padding-inline: var(--tw-grid-gutter, 65px);
			gap: 0;
		}

		.thh-eyebrow {
			margin-bottom: 10px;
		}

		.thh-title {
			gap: 10px;
			font-size: 100px;
			line-height: 110px;
			letter-spacing: -2px;
		}

		.thh-line {
			gap: 24px;
		}

		.thh-tag {
			padding: 8px 18px;
			border-radius: 15px;
			font-size: 50px;
			line-height: 60px;
			letter-spacing: -1px;
		}

		.thh-foot {
			margin-top: 110px;
		}

		.thh-copy {
			padding-top: 16px;
		}
	}

	@media (prefers-reduced-motion: reduce) {
		.thh[data-animate] .thh-title,
		.thh[data-animate] .thh-late {
			animation: none;
		}

		.thh-btn,
		.thh-btn-ic {
			transition: none;
		}

		.thh-btn:hover .thh-btn-ic {
			transform: none;
		}
	}
</style>

===== END src/components/TagHeadlineHero.astro =====

===== FILE (required): public/tag-headline-hero/hero-truck.webp =====
[binary asset — download https://astro.baysixmedia.com/tag-headline-hero/hero-truck.webp and save it to public/tag-headline-hero/hero-truck.webp]

===== FILE (required): public/tag-headline-hero/card-truck.webp =====
[binary asset — download https://astro.baysixmedia.com/tag-headline-hero/card-truck.webp and save it to public/tag-headline-hero/card-truck.webp]

===== FILE (required): src/components/FlankedStatOverview.astro =====
---
/**
 * FlankedStatOverview
 *
 * A company overview: an outlined eyebrow, a big centred uppercase heading
 * with an orange phrase, a paragraph and an orange pill, flanked by two tall
 * photos with chamfered inner corners. Underneath, a row of three stat cards
 * (navy, orange, navy) with a line icon, a big figure and a caption.
 *
 * Heading words rise in one after another and the stat cards lift in, the
 * first time each scrolls into view.
 *
 * Heading syntax: wrap the orange words in asterisks, e.g. "Data-driven
 * logistic global *network*".
 *
 * Reads the --tw-* tokens from any ancestor, with fallbacks.
 */

export type FlankedStatIcon = "burst" | "people" | "truck" | "globe" | "clock" | "box";

export interface FlankedStat {
	value: string;
	label: string;
	icon?: FlankedStatIcon;
	tone?: "navy" | "orange";
}

export interface FlankedStatOverviewProps {
	eyebrow?: string;
	heading?: string;
	headingLevel?: "h2" | "h3";
	text?: string;
	cta?: { label: string; href: string } | null;
	/** Left and right photos. No `src` renders a labelled placeholder; `null` drops both. */
	images?: [{ src?: string; alt?: string }, { src?: string; alt?: string }] | null;
	stats?: FlankedStat[];
	background?: string;
	fontStylesheet?: string;
	class?: string;
	id?: string;
}

interface Props extends FlankedStatOverviewProps {}

/*
 * The heading, copy, button, photos and figures are the caller's: nothing
 * about a business is filled in here. "Company Overview" is a generic label
 * and never renders the section on its own. The catalog demo passes its
 * sample content through the registry's `props`.
 */
const {
	eyebrow = "Company Overview",
	heading = "",
	headingLevel = "h2",
	text = "",
	cta = null,
	images: imagesProp,
	stats = [],
	background = "#ffffff",
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Outfit:wght@500;600;700;800;900&family=Noto+Sans:wght@400;500;600;700&family=Inter:wght@500;600;700&display=swap",
	class: className,
	id,
} = Astro.props;

/* A photo given without alt is decorative. No photo: the neutral placeholder. */
const images =
	imagesProp === null
		? null
		: [0, 1].map((i) => ({ src: imagesProp?.[i]?.src ?? "", alt: imagesProp?.[i]?.alt ?? "" }));

const hasTop = Boolean(heading || text || cta);
/* Nothing to say → nothing rendered: the generic eyebrow alone is not a section. */
const hasContent = hasTop || stats.length > 0;

const words = heading
	.split("*")
	.flatMap((seg, i) => seg.split(/\s+/).filter(Boolean).map((w) => ({ w, accent: i % 2 === 1 })));

const Heading = headingLevel;
const SAFE = /^(?:https?:|mailto:|tel:|[#/.?])/i;
const safeHref = (href: string) => (SAFE.test(href.trim()) ? href.trim() : "#");

const ICONS: Record<FlankedStatIcon, string> = {
	burst: "M24 6l4.2 9.3 10.1 1.1-7.5 6.8 2.1 9.9L24 28l-8.9 5.1 2.1-9.9-7.5-6.8 10.1-1.1Z M24 40v4 M8 30l-3 2 M40 30l3 2 M10 8l2 3 M38 8l-2 3",
	people: "M24 20a5 5 0 1 0 0-10 5 5 0 0 0 0 10Z M15 38v-4a9 9 0 0 1 18 0v4Z M12 22a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z M36 22a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z M5 38v-3a7 7 0 0 1 9-6.7 M43 38v-3a7 7 0 0 0-9-6.7",
	truck: "M4 14h24v18H4Z M28 20h8l6 7v5H28 M12 38a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z M34 38a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z M1 20h8 M3 26h6",
	globe: "M24 42a18 18 0 1 0 0-36 18 18 0 0 0 0 36Z M6 24h36 M24 6c6 6 6 30 0 36 M24 6c-6 6-6 30 0 36",
	clock: "M24 42a18 18 0 1 0 0-36 18 18 0 0 0 0 36Z M24 14v10l7 5",
	box: "M24 6 40 14v20L24 42 8 34V14Z M8 14l16 8 16-8 M24 22v20",
};
---

{hasContent && fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
{hasContent && (
<section class:list={["fso", className]} id={id} style={`--fso-band:${background}`} data-fso-root>
	<div class="fso-inner">
		{hasTop && (
		<div class="fso-top" data-fso-reveal="up">
			{
				images && (
					<div class="fso-img fso-img--left">
						{images[0].src ? (
							<img src={images[0].src} alt={images[0].alt} width="490" height="624" loading="lazy" decoding="async" />
						) : (
							<div class="fso-ph" role="img" aria-label="Image placeholder: left photo, portrait">
								<span>Image · portrait · 490 × 624</span>
							</div>
						)}
					</div>
				)
			}
			<div class="fso-center">
				{eyebrow && <p class="fso-eyebrow">{eyebrow}</p>}
				{heading && (
				<Heading class="fso-heading" data-fso-reveal="words">
					{
						words.map((t, i) => (
							<>
								{i > 0 && " "}
								<span class:list={["fso-w", { "fso-accent": t.accent }]} style={`--i:${i}`}>
									{t.w}
								</span>
							</>
						))
					}
				</Heading>
				)}
				{text && <p class="fso-text">{text}</p>}
				{
					cta && (
						<a class="fso-btn" href={safeHref(cta.href)}>
							<span>{cta.label}</span>
							<span class="fso-btn-ic" aria-hidden="true">
								<svg viewBox="0 0 24 24"><path d="M8 5l10 7-10 7 3-7Z" fill="currentColor" /></svg>
							</span>
						</a>
					)
				}
			</div>
			{
				images && (
					<div class="fso-img fso-img--right">
						{images[1].src ? (
							<img src={images[1].src} alt={images[1].alt} width="490" height="624" loading="lazy" decoding="async" />
						) : (
							<div class="fso-ph" role="img" aria-label="Image placeholder: right photo, portrait">
								<span>Image · portrait · 490 × 624</span>
							</div>
						)}
					</div>
				)
			}
		</div>
		)}

		{
			stats.length > 0 && (
				<ul class="fso-stats">
					{stats.map((s, i) => (
						<li class:list={["fso-stat", `fso-stat--${s.tone ?? "navy"}`]} data-fso-reveal="up" style={`--d:${i * 0.12}s`}>
							<svg class="fso-ic" viewBox="0 0 48 48" aria-hidden="true">
								<path d={ICONS[s.icon ?? "box"]} fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
							</svg>
							<div>
								<p class="fso-value">{s.value}</p>
								<p class="fso-label">{s.label}</p>
							</div>
						</li>
					))}
				</ul>
			)
		}
	</div>
</section>
)}

<style>
	.fso {
		--fso-navy: var(--tw-navy, #0b4654);
		--fso-deep: var(--tw-navy-deep, #052931);
		--fso-accent: var(--tw-accent, #ff6d45);
		--fso-display: var(--tw-display, Outfit, ui-sans-serif, system-ui, sans-serif);
		--fso-body: var(--tw-body, "Noto Sans", ui-sans-serif, system-ui, sans-serif);
		--fso-ease: cubic-bezier(0.44, 0, 0.56, 1);
		--fso-cut: 36px;

		background: var(--fso-band);
		color: var(--fso-navy);
		font-family: var(--fso-body);
	}

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

	.fso-inner {
		max-width: 1440px;
		margin: 0 auto;
		padding: 64px 16px;
	}

	.fso-top {
		display: grid;
		justify-items: center;
		gap: 36px;
	}

	.fso-img {
		width: min(245px, 70%);
		aspect-ratio: 245 / 312;
	}

	.fso-img img,
	.fso-img .fso-ph {
		display: block;
		width: 100%;
		height: 100%;
		object-fit: cover;
		border-radius: 12px;
	}

	/* Chamfers on the inner corners, facing the copy. */
	.fso-img--left img,
	.fso-img--left .fso-ph {
		clip-path: polygon(0 0, calc(100% - var(--fso-cut)) 0, 100% var(--fso-cut), 100% calc(100% - var(--fso-cut)), calc(100% - var(--fso-cut)) 100%, 0 100%);
	}

	.fso-img--right img,
	.fso-img--right .fso-ph {
		clip-path: polygon(var(--fso-cut) 0, 100% 0, 100% 100%, var(--fso-cut) 100%, 0 calc(100% - var(--fso-cut)), 0 var(--fso-cut));
	}

	.fso-center {
		display: flex;
		flex-direction: column;
		align-items: center;
		text-align: center;
	}

	.fso-eyebrow {
		margin: 0 0 18px;
		padding: 4px 14px;
		border: 1px solid var(--fso-accent);
		border-radius: 999px;
		color: var(--fso-accent);
		font: 500 15px/24px var(--fso-body);
		letter-spacing: -0.3px;
		text-transform: uppercase;
	}

	.fso-heading {
		max-width: 620px;
		margin: 0;
		font: 700 28px/1.2 var(--fso-display);
		letter-spacing: -0.56px;
		text-transform: uppercase;
	}

	.fso-w {
		display: inline-block;
	}

	.fso-accent {
		color: var(--fso-accent);
	}

	.fso-text {
		max-width: 520px;
		margin: 30px 0 0;
		font: 400 16px/25.6px var(--fso-body);
		letter-spacing: -0.32px;
	}

	.fso-btn {
		display: inline-flex;
		align-items: center;
		gap: 12px;
		height: 50px;
		margin-top: 40px;
		padding: 0 8px 0 25px;
		border-radius: 999px;
		background: var(--fso-accent);
		color: #fff;
		font: 400 16px/25.6px var(--fso-body);
		letter-spacing: -0.32px;
		text-decoration: none;
		transition: background-color 0.3s var(--fso-ease);
	}

	.fso-btn:hover {
		background: var(--fso-deep);
	}

	.fso-btn-ic {
		display: grid;
		place-items: center;
		width: 34px;
		height: 34px;
		border-radius: 50%;
		background: #fff;
		color: var(--fso-accent);
		transition: transform 0.3s var(--fso-ease);
	}

	.fso-btn-ic svg {
		width: 18px;
		height: 18px;
	}

	.fso-btn:hover .fso-btn-ic {
		transform: rotate(-45deg);
	}

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

	.fso-stats {
		display: grid;
		gap: 16px;
		margin: 48px 0 0;
		padding: 0;
		list-style: none;
	}

	.fso-stat {
		display: flex;
		align-items: center;
		gap: 12px;
		min-height: 140px;
		padding: 24px 28px;
		border-radius: 10px;
		background: var(--fso-navy);
		color: #fff;
	}

	.fso-stat--orange {
		background: var(--fso-accent);
	}

	.fso-ic {
		flex: none;
		width: 56px;
		height: 56px;
		color: var(--fso-accent);
	}

	.fso-stat--orange .fso-ic {
		color: rgb(255 255 255 / 0.7);
	}

	.fso-value {
		margin: 0;
		font: 700 28px/1.2 var(--fso-display);
		letter-spacing: -0.56px;
		text-transform: uppercase;
	}

	.fso-label {
		margin: 0;
		font: 400 16px/25.6px var(--fso-body);
		letter-spacing: -0.32px;
		text-transform: uppercase;
	}

	.fso-ph {
		display: grid;
		place-items: center;
		padding: 12px;
		background: repeating-linear-gradient(135deg, #e3eef1 0 12px, #edf5f7 12px 24px);
		color: var(--fso-navy);
		font: 500 13px/1.4 var(--fso-body);
		text-align: center;
	}

	/* ---- appear on scroll: hidden poses exist only once the script runs ---- */
	[data-fso-reveal="up"] {
		transition:
			opacity 0.8s var(--fso-ease) var(--d, 0s),
			transform 0.8s var(--fso-ease) var(--d, 0s);
	}

	.fso[data-fso-ready] [data-fso-reveal="up"]:not([data-in]) {
		opacity: 0;
		transform: translateY(80px);
	}

	.fso-w {
		transition:
			opacity 0.6s var(--fso-ease) calc(var(--i) * 0.06s),
			transform 0.6s var(--fso-ease) calc(var(--i) * 0.06s);
	}

	.fso[data-fso-ready] [data-fso-reveal="words"]:not([data-in]) .fso-w {
		opacity: 0;
		transform: translateY(10px);
	}

	@media (min-width: 810px) {
		.fso-inner {
			padding: 90px 40px;
		}

		.fso-top {
			grid-template-columns: minmax(0, 200px) minmax(0, 1fr) minmax(0, 200px);
			align-items: start;
			gap: 24px;
		}

		.fso-img {
			width: 100%;
		}

		.fso-heading {
			font-size: 38px;
			letter-spacing: -0.76px;
		}

		.fso-stats {
			grid-template-columns: repeat(3, minmax(0, 1fr));
			margin-top: 50px;
		}

		.fso-stat {
			flex-direction: column;
			align-items: flex-start;
			padding: 24px;
		}

		.fso-value {
			font-size: 36px;
		}
	}

	@media (min-width: 1200px) {
		.fso-inner {
			padding: 105px 65px 100px;
		}

		.fso-top {
			grid-template-columns: 245px minmax(0, 1fr) 245px;
		}

		.fso-heading {
			max-width: 640px;
			font-size: 50px;
			line-height: 60px;
			letter-spacing: -1px;
		}

		.fso-text {
			max-width: 520px;
		}

		.fso-stats {
			gap: 30px;
		}

		.fso-stat {
			flex-direction: row;
			align-items: center;
			min-height: 166px;
			padding: 30px 40px;
			gap: 12px;
		}

		.fso-ic {
			width: 70px;
			height: 70px;
		}

		.fso-value {
			font-size: 50px;
			line-height: 60px;
			letter-spacing: -1px;
		}
	}

	@media (prefers-reduced-motion: reduce) {
		[data-fso-reveal="up"],
		.fso-w,
		.fso-btn,
		.fso-btn-ic {
			transition: none;
		}

		.fso-btn:hover .fso-btn-ic {
			transform: none;
		}
	}
</style>

<script>
	/*
	 * Appear-on-scroll. One IntersectionObserver per instance, scoped to its
	 * root, disconnected on astro:before-swap. Under reduced motion the root is
	 * never marked ready, so nothing starts hidden.
	 */
	function initFlankedStatOverview(root: HTMLElement) {
		if (root.dataset.fsoReady === "") return;
		if (window.matchMedia("(prefers-reduced-motion: reduce)").matches || !("IntersectionObserver" in window)) return;
		const targets = root.querySelectorAll<HTMLElement>("[data-fso-reveal]");
		const controller = new AbortController();
		root.dataset.fsoReady = "";
		const io = new IntersectionObserver(
			(entries) => {
				for (const entry of entries) {
					if (!entry.isIntersecting) continue;
					(entry.target as HTMLElement).dataset.in = "";
					io.unobserve(entry.target);
				}
			},
			{ threshold: 0.1, rootMargin: "0px 0px -5% 0px" },
		);
		targets.forEach((t) => io.observe(t));
		controller.signal.addEventListener("abort", () => io.disconnect());
		document.addEventListener(
			"astro:before-swap",
			() => {
				controller.abort();
				delete root.dataset.fsoReady;
			},
			{ once: true, signal: controller.signal },
		);
	}

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

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

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

===== END src/components/FlankedStatOverview.astro =====

===== FILE (required): public/flanked-stat-overview/ship.webp =====
[binary asset — download https://astro.baysixmedia.com/flanked-stat-overview/ship.webp and save it to public/flanked-stat-overview/ship.webp]

===== FILE (required): public/flanked-stat-overview/trucks.webp =====
[binary asset — download https://astro.baysixmedia.com/flanked-stat-overview/trucks.webp and save it to public/flanked-stat-overview/trucks.webp]

===== FILE (required): src/components/PhotoFeatureTrio.astro =====
---
/**
 * PhotoFeatureTrio
 *
 * A pale band with an outlined eyebrow on the left and a big uppercase
 * heading on the right, over three white cards. Each card has a two-line
 * uppercase title, a round orange icon in its corner and a landscape photo
 * filling the bottom.
 *
 * Heading words rise in one by one and the cards lift in, staggered, the first
 * time they scroll into view.
 *
 * Heading syntax: wrap the orange words in asterisks.
 *
 * Reads the --tw-* tokens from any ancestor, with fallbacks.
 */

export type PhotoFeatureIcon = "box" | "shield" | "route" | "clock" | "truck" | "globe";

export interface PhotoFeature {
	title: string;
	icon?: PhotoFeatureIcon;
	/** `src: ""` renders a labelled placeholder. */
	image?: { src?: string; alt?: string };
	/** Makes the whole card one link. */
	href?: string;
}

export interface PhotoFeatureTrioProps {
	eyebrow?: string;
	heading?: string;
	headingLevel?: "h2" | "h3";
	features?: PhotoFeature[];
	background?: string;
	fontStylesheet?: string;
	class?: string;
	id?: string;
}

interface Props extends PhotoFeatureTrioProps {}

const {
	eyebrow = "",
	heading = "",
	headingLevel = "h2",
	features: featuresProp = [],
	background = "#f0f8fa",
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Outfit:wght@500;600;700;800;900&family=Noto+Sans:wght@400;500;600;700&family=Inter:wght@500;600;700&display=swap",
	class: className,
	id,
} = Astro.props;

const words = heading
	.split("*")
	.flatMap((seg, i) => seg.split(/\s+/).filter(Boolean).map((w) => ({ w, accent: i % 2 === 1 })));

const Heading = headingLevel;
const Title = headingLevel === "h2" ? "h3" : "h4";

/* A card needs its title; one without is dropped. No cards, nothing renders. */
const features = featuresProp.filter((f) => f && f.title);
const SAFE = /^(?:https?:|mailto:|tel:|[#/.?])/i;
const safeHref = (href: string) => (SAFE.test(href.trim()) ? href.trim() : "#");

const ICONS: Record<PhotoFeatureIcon, string> = {
	box: "M12 3 20 7v10l-8 4-8-4V7Z M4 7l8 4 8-4 M12 11v10",
	shield: "M12 3 19 6v5c0 5-3 8.5-7 10-4-1.5-7-5-7-10V6Z M9 12l2 2 4-4",
	route: "M6 20a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z M18 8a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z M6 16V9a3 3 0 0 1 3-3h3 M18 8v7a3 3 0 0 1-3 3h-3",
	clock: "M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18Z M12 7v5l3 2",
	truck: "M2 7h12v9H2Z M14 10h4l3 3v3h-7 M6 19a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z M17 19a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z",
	globe: "M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18Z M3 12h18 M12 3c3 3 3 15 0 18 M12 3c-3 3-3 15 0 18",
};
---

{features.length > 0 && fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
{features.length > 0 && (
<section class:list={["pft", className]} id={id} style={`--pft-band:${background}`} data-pft-root>
	<div class="pft-inner">
		{(eyebrow || words.length > 0) && (
		<div class="pft-head">
			{eyebrow && <p class="pft-eyebrow">{eyebrow}</p>}
			{words.length > 0 && (
			<Heading class="pft-heading" data-pft-reveal="words">
				{
					words.map((t, i) => (
						<>
							{i > 0 && " "}
							<span class:list={["pft-w", { "pft-accent": t.accent }]} style={`--i:${i}`}>
								{t.w}
							</span>
						</>
					))
				}
			</Heading>
			)}
		</div>
		)}

		<ul class="pft-grid">
			{
				features.map((f, i) => {
					const img = f.image ?? { src: "" };
					return (
						<li class:list={["pft-card", { "pft-card--link": f.href }]} data-pft-reveal="up" style={`--d:${i * 0.12}s`}>
							<div class="pft-card-head">
								<Title class="pft-title">
									{f.href ? <a href={safeHref(f.href)}>{f.title}</a> : f.title}
								</Title>
								<span class="pft-ic" aria-hidden="true">
									<svg viewBox="0 0 24 24">
										<path d={ICONS[f.icon ?? "box"]} fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
									</svg>
								</span>
							</div>
							<div class="pft-media">
								{img.src ? (
									<img src={img.src} alt={img.alt ?? ""} width="766" height="604" loading="lazy" decoding="async" />
								) : (
									<div class="pft-ph" role="img" aria-label={`Image placeholder: ${f.title}`}>
										<span>Image · landscape · 766 × 604</span>
									</div>
								)}
							</div>
						</li>
					);
				})
			}
		</ul>
	</div>
</section>
)}

<style>
	.pft {
		--pft-navy: var(--tw-navy, #0b4654);
		--pft-accent: var(--tw-accent, #ff6d45);
		--pft-display: var(--tw-display, Outfit, ui-sans-serif, system-ui, sans-serif);
		--pft-body: var(--tw-body, "Noto Sans", ui-sans-serif, system-ui, sans-serif);
		--pft-card-font: var(--tw-card, Inter, ui-sans-serif, system-ui, sans-serif);
		--pft-ease: cubic-bezier(0.44, 0, 0.56, 1);

		background: var(--pft-band);
		color: var(--pft-navy);
		font-family: var(--pft-body);
	}

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

	.pft-inner {
		max-width: 1440px;
		margin: 0 auto;
		padding: 64px 16px;
	}

	.pft-head {
		display: flex;
		flex-direction: column;
		align-items: flex-start;
		gap: 18px;
	}

	.pft-eyebrow {
		flex: none;
		margin: 0;
		padding: 4px 14px;
		border: 1px solid var(--pft-accent);
		border-radius: 999px;
		color: var(--pft-accent);
		font: 500 15px/24px var(--pft-body);
		letter-spacing: -0.3px;
		text-transform: uppercase;
	}

	.pft-heading {
		max-width: 620px;
		margin: 0;
		font: 700 28px/1.2 var(--pft-display);
		letter-spacing: -0.56px;
		text-transform: uppercase;
	}

	.pft-w {
		display: inline-block;
	}

	.pft-accent {
		color: var(--pft-accent);
	}

	.pft-grid {
		display: grid;
		gap: 20px;
		margin: 40px 0 0;
		padding: 0;
		list-style: none;
	}

	.pft-card {
		position: relative;
		display: flex;
		flex-direction: column;
		gap: 24px;
		padding: 20px;
		border-radius: 10px;
		background: #fff;
	}

	.pft-card-head {
		display: flex;
		align-items: flex-start;
		justify-content: space-between;
		gap: 16px;
	}

	.pft-title {
		max-width: 260px;
		margin: 0;
		font: 700 22px/1.4 var(--pft-card-font);
		letter-spacing: -0.88px;
		text-transform: uppercase;
	}

	.pft-title a {
		color: inherit;
		text-decoration: none;
	}

	.pft-card--link .pft-title a::after {
		content: "";
		position: absolute;
		inset: 0;
		border-radius: 10px;
	}

	.pft-title a:focus-visible {
		outline: none;
	}

	.pft-card--link:has(a:focus-visible) {
		outline: 2px solid var(--pft-accent);
		outline-offset: 3px;
	}

	.pft-ic {
		display: grid;
		flex: none;
		place-items: center;
		width: 45px;
		height: 45px;
		border-radius: 50%;
		background: var(--pft-accent);
		color: #fff;
	}

	.pft-ic svg {
		width: 22px;
		height: 22px;
	}

	.pft-media {
		aspect-ratio: 383 / 302;
		overflow: hidden;
		border-radius: 10px;
	}

	.pft-media img,
	.pft-ph {
		display: block;
		width: 100%;
		height: 100%;
		object-fit: cover;
	}

	.pft-ph {
		display: grid;
		place-items: center;
		background: repeating-linear-gradient(135deg, #e3eef1 0 12px, #edf5f7 12px 24px);
		font: 500 13px/1.4 var(--pft-body);
	}

	[data-pft-reveal="up"] {
		transition:
			opacity 0.8s var(--pft-ease) var(--d, 0s),
			transform 0.8s var(--pft-ease) var(--d, 0s);
	}

	.pft[data-pft-ready] [data-pft-reveal="up"]:not([data-in]) {
		opacity: 0;
		transform: translateY(80px);
	}

	.pft-w {
		transition:
			opacity 0.6s var(--pft-ease) calc(var(--i) * 0.06s),
			transform 0.6s var(--pft-ease) calc(var(--i) * 0.06s);
	}

	.pft[data-pft-ready] [data-pft-reveal="words"]:not([data-in]) .pft-w {
		opacity: 0;
		transform: translateY(10px);
	}

	@media (min-width: 810px) {
		.pft-inner {
			padding: 90px 40px;
		}

		.pft-head {
			flex-direction: row;
			justify-content: space-between;
			align-items: flex-start;
		}

		.pft-eyebrow {
			margin-top: 42px;
		}

		.pft-heading {
			font-size: 38px;
			letter-spacing: -0.76px;
			width: 58%;
		}

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

	@media (min-width: 1200px) {
		.pft-inner {
			padding: 100px 65px;
		}

		.pft-eyebrow {
			margin: 48px 0 0 15px;
		}

		.pft-heading {
			width: 600px;
			margin-right: 0;
			font-size: 50px;
			line-height: 60px;
			letter-spacing: -1px;
		}

		.pft-grid {
			grid-template-columns: repeat(3, minmax(0, 1fr));
			margin-top: 50px;
		}

		.pft-title {
			font-size: 26px;
			line-height: 36.4px;
			letter-spacing: -1.04px;
		}
	}

	@media (prefers-reduced-motion: reduce) {
		[data-pft-reveal="up"],
		.pft-w {
			transition: none;
		}
	}
</style>

<script>
	/* Appear-on-scroll; one observer per instance, torn down on astro:before-swap. */
	function initPhotoFeatureTrio(root: HTMLElement) {
		if (root.dataset.pftReady === "") return;
		if (window.matchMedia("(prefers-reduced-motion: reduce)").matches || !("IntersectionObserver" in window)) return;
		const controller = new AbortController();
		root.dataset.pftReady = "";
		const io = new IntersectionObserver(
			(entries) => {
				for (const entry of entries) {
					if (!entry.isIntersecting) continue;
					(entry.target as HTMLElement).dataset.in = "";
					io.unobserve(entry.target);
				}
			},
			{ threshold: 0.1, rootMargin: "0px 0px -5% 0px" },
		);
		root.querySelectorAll<HTMLElement>("[data-pft-reveal]").forEach((t) => io.observe(t));
		controller.signal.addEventListener("abort", () => io.disconnect());
		document.addEventListener(
			"astro:before-swap",
			() => {
				controller.abort();
				delete root.dataset.pftReady;
			},
			{ once: true, signal: controller.signal },
		);
	}

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

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

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

===== END src/components/PhotoFeatureTrio.astro =====

===== FILE (required): public/photo-feature-trio/courier.webp =====
[binary asset — download https://astro.baysixmedia.com/photo-feature-trio/courier.webp and save it to public/photo-feature-trio/courier.webp]

===== FILE (required): public/photo-feature-trio/truck-dusk.webp =====
[binary asset — download https://astro.baysixmedia.com/photo-feature-trio/truck-dusk.webp and save it to public/photo-feature-trio/truck-dusk.webp]

===== FILE (required): public/photo-feature-trio/container-ship.webp =====
[binary asset — download https://astro.baysixmedia.com/photo-feature-trio/container-ship.webp and save it to public/photo-feature-trio/container-ship.webp]

===== FILE (required): src/components/HoverServiceList.astro =====
---
/**
 * HoverServiceList
 *
 * A services section: outlined eyebrow, a big uppercase heading and an intro
 * paragraph across the top; below, a tall photo carrying a frosted caption
 * with an orange rule, beside a stack of service rows. Each row has a navy
 * icon tile, an uppercase title, small tag pills and a round arrow button.
 * Hovering a row floods it orange, turns the icon tile white and the title
 * white, and tilts the arrow.
 *
 * Each row is one link (its title, stretched over the row).
 *
 * Heading syntax: wrap the orange words in asterisks.
 *
 * Reads the --tw-* tokens from any ancestor, with fallbacks.
 */

export type HoverServiceIcon = "ship" | "truck" | "box" | "send" | "plane" | "warehouse";

export interface HoverService {
	title: string;
	href: string;
	tags?: string[];
	icon?: HoverServiceIcon;
}

export interface HoverServiceListProps {
	eyebrow?: string;
	heading?: string;
	headingLevel?: "h2" | "h3";
	intro?: string;
	/** `src: ""` renders a labelled placeholder. */
	image?: { src?: string; alt?: string };
	/** The frosted caption on the photo. `""` drops it. */
	caption?: string;
	services?: HoverService[];
	background?: string;
	fontStylesheet?: string;
	class?: string;
	id?: string;
}

interface Props extends HoverServiceListProps {}

const {
	eyebrow = "Our Services",
	heading = "",
	headingLevel = "h2",
	intro = "",
	image: imageProp,
	caption = "",
	services = [],
	background = "#ffffff",
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Outfit:wght@500;600;700;800;900&family=Noto+Sans:wght@400;500;600;700&family=Inter:wght@500;600;700&display=swap",
	class: className,
	id,
} = Astro.props;

/* No photo by default: without a `src` the design's own labelled placeholder
   stands in. Alt is decorative ("") unless supplied. */
const image = { src: imageProp?.src ?? "", alt: imageProp?.alt ?? "" };

/* Each row is one link: it needs a title and an href, or it is dropped rather
   than drawn half-empty. No rows, no section: the heading, intro and photo
   alone never render. */
const list = services
	.filter((s) => s?.title && s?.href)
	.map((s) => ({ ...s, tags: s.tags?.filter(Boolean) }));

const words = heading
	.split("*")
	.flatMap((seg, i) => seg.split(/\s+/).filter(Boolean).map((w) => ({ w, accent: i % 2 === 1 })));

const Heading = headingLevel;
const Title = headingLevel === "h2" ? "h3" : "h4";
const SAFE = /^(?:https?:|mailto:|tel:|[#/.?])/i;
const safeHref = (href: string) => (SAFE.test(href.trim()) ? href.trim() : "#");

const ICONS: Record<HoverServiceIcon, string> = {
	ship: "M4 15h16l-2 5H6Z M7 15V9h10v6 M10 9V5h4v4 M3 21c1.5 0 1.5-1 3-1s1.5 1 3 1 1.5-1 3-1 1.5 1 3 1 1.5-1 3-1",
	truck: "M2 7h12v9H2Z M14 10h4l3 3v3h-7 M6 19a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z M17 19a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z",
	box: "M12 3 20 7v10l-8 4-8-4V7Z M4 7l8 4 8-4 M12 11v10",
	send: "M4 4l17 8-17 8 4-8Z M8 12h13",
	plane: "M2 13l20-8-6 16-4-7Z M12 14l4-4",
	warehouse: "M3 9l9-5 9 5v11H3Z M7 20v-7h10v7 M7 16h10",
};
---

{list.length > 0 && fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
{list.length > 0 && (
<section class:list={["hsl", className]} id={id} style={`--hsl-band:${background}`} data-hsl-root>
	<div class="hsl-inner">
		<div class="hsl-head">
			{eyebrow && <p class="hsl-eyebrow">{eyebrow}</p>}
			{words.length > 0 && (
			<Heading class="hsl-heading" data-hsl-reveal="words">
				{
					words.map((t, i) => (
						<>
							{i > 0 && " "}
							<span class:list={["hsl-w", { "hsl-accent": t.accent }]} style={`--i:${i}`}>
								{t.w}
							</span>
						</>
					))
				}
			</Heading>
			)}
			{intro && <p class="hsl-intro">{intro}</p>}
		</div>

		<div class="hsl-body" data-hsl-reveal="up">
			<div class="hsl-media">
				{
					image.src ? (
						<img src={image.src} alt={image.alt} width="1290" height="1116" loading="lazy" decoding="async" />
					) : (
						<div class="hsl-ph" role="img" aria-label="Image placeholder">
							<span>Image · 1290 × 1116</span>
						</div>
					)
				}
				{caption && <p class="hsl-caption">{caption}</p>}
			</div>

			<ul class="hsl-list">
				{
					list.map((s) => (
						<li class="hsl-row">
							<span class="hsl-ic" aria-hidden="true">
								<svg viewBox="0 0 24 24">
									<path d={ICONS[s.icon ?? "box"]} fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
								</svg>
							</span>
							<div class="hsl-row-copy">
								<Title class="hsl-title">
									<a href={safeHref(s.href)}>{s.title}</a>
								</Title>
								{s.tags && s.tags.length > 0 && (
									<ul class="hsl-tags" aria-label="Tags">
										{s.tags.map((tag) => (
											<li>{tag}</li>
										))}
									</ul>
								)}
							</div>
							<span class="hsl-arrow" aria-hidden="true">
								<svg viewBox="0 0 24 24">
									<path d="M5 12h14M13 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" />
								</svg>
							</span>
						</li>
					))
				}
			</ul>
		</div>
	</div>
</section>
)}

<style>
	.hsl {
		--hsl-navy: var(--tw-navy, #0b4654);
		--hsl-accent: var(--tw-accent, #ff6d45);
		--hsl-surface: var(--tw-surface, #f0f8fa);
		--hsl-muted: var(--tw-muted, #616161);
		--hsl-display: var(--tw-display, Outfit, ui-sans-serif, system-ui, sans-serif);
		--hsl-body: var(--tw-body, "Noto Sans", ui-sans-serif, system-ui, sans-serif);
		--hsl-ease: cubic-bezier(0.44, 0, 0.56, 1);

		background: var(--hsl-band);
		color: var(--hsl-navy);
		font-family: var(--hsl-body);
	}

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

	.hsl-inner {
		max-width: 1440px;
		margin: 0 auto;
		padding: 64px 16px;
	}

	.hsl-head {
		display: grid;
		gap: 18px;
		justify-items: start;
	}

	.hsl-eyebrow {
		margin: 0;
		padding: 4px 14px;
		border: 1px solid var(--hsl-accent);
		border-radius: 999px;
		color: var(--hsl-accent);
		font: 500 15px/24px var(--hsl-body);
		letter-spacing: -0.3px;
		text-transform: uppercase;
	}

	.hsl-heading {
		margin: 0;
		font: 700 28px/1.2 var(--hsl-display);
		letter-spacing: -0.56px;
		text-transform: uppercase;
	}

	.hsl-w {
		display: inline-block;
	}

	.hsl-accent {
		color: var(--hsl-accent);
	}

	.hsl-intro {
		max-width: 450px;
		margin: 0;
		font: 400 16px/25.6px var(--hsl-body);
		letter-spacing: -0.32px;
	}

	.hsl-body {
		display: grid;
		gap: 16px;
		margin-top: 40px;
	}

	.hsl-media {
		position: relative;
		min-height: 360px;
		overflow: hidden;
		border-radius: 15px;
	}

	.hsl-media img,
	.hsl-ph {
		position: absolute;
		inset: 0;
		width: 100%;
		height: 100%;
		object-fit: cover;
	}

	.hsl-ph {
		display: grid;
		place-items: center;
		background: repeating-linear-gradient(135deg, #e3eef1 0 12px, #edf5f7 12px 24px);
		font: 500 13px/1.4 var(--hsl-body);
	}

	.hsl-caption {
		position: absolute;
		left: 16px;
		right: 16px;
		bottom: 16px;
		margin: 0;
		padding: 14px 18px 14px 36px;
		border-radius: 10px;
		background: rgb(255 255 255 / 0.1);
		-webkit-backdrop-filter: blur(10px);
		backdrop-filter: blur(10px);
		color: #fff;
		font: 500 14px/1.6 var(--hsl-body);
	}

	.hsl-caption::before {
		content: "";
		position: absolute;
		left: 16px;
		top: 14px;
		bottom: 14px;
		width: 5px;
		border-radius: 3px;
		background: var(--hsl-accent);
	}

	.hsl-list {
		display: grid;
		gap: 15px;
		margin: 0;
		padding: 0;
		list-style: none;
	}

	.hsl-row {
		position: relative;
		display: grid;
		gap: 18px;
		padding: 24px;
		border-radius: 10px;
		background: var(--hsl-surface);
		transition: background-color 0.35s var(--hsl-ease);
	}

	.hsl-ic {
		display: grid;
		place-items: center;
		width: 58px;
		height: 58px;
		border-radius: 10px;
		background: var(--hsl-navy);
		color: var(--hsl-surface);
		transition:
			background-color 0.35s var(--hsl-ease),
			color 0.35s var(--hsl-ease);
	}

	.hsl-ic svg {
		width: 28px;
		height: 28px;
	}

	.hsl-title {
		margin: 0;
		font: 600 20px/32px var(--hsl-body);
		letter-spacing: -0.4px;
		text-transform: uppercase;
		transition: color 0.35s var(--hsl-ease);
	}

	.hsl-title a {
		color: inherit;
		text-decoration: none;
	}

	/* The title link covers the whole row. */
	.hsl-title a::after {
		content: "";
		position: absolute;
		inset: 0;
		z-index: 1;
		border-radius: 10px;
	}

	.hsl-title a:focus-visible {
		outline: none;
	}

	.hsl-row:has(a:focus-visible) {
		outline: 2px solid var(--hsl-navy);
		outline-offset: 3px;
	}

	.hsl-tags {
		display: flex;
		flex-wrap: wrap;
		gap: 6px;
		margin: 6px 0 0;
		padding: 0;
		list-style: none;
	}

	.hsl-tags li {
		padding: 6px 10px;
		border-radius: 16px;
		background: #fff;
		color: var(--hsl-muted);
		font: 400 12px/19.2px var(--hsl-body);
		letter-spacing: -0.24px;
		text-transform: uppercase;
	}

	.hsl-arrow {
		display: grid;
		place-items: center;
		width: 50px;
		height: 50px;
		border-radius: 50%;
		background: #fff;
		color: var(--hsl-navy);
		transition: transform 0.35s var(--hsl-ease);
	}

	.hsl-arrow svg {
		width: 22px;
		height: 22px;
	}

	.hsl-row:hover,
	.hsl-row:has(a:focus-visible) {
		background: var(--hsl-accent);
	}

	.hsl-row:hover .hsl-ic,
	.hsl-row:has(a:focus-visible) .hsl-ic {
		background: #fff;
		color: var(--hsl-navy);
	}

	.hsl-row:hover .hsl-title,
	.hsl-row:has(a:focus-visible) .hsl-title {
		color: #fff;
	}

	.hsl-row:hover .hsl-arrow,
	.hsl-row:has(a:focus-visible) .hsl-arrow {
		transform: rotate(-45deg);
	}

	[data-hsl-reveal="up"] {
		transition:
			opacity 0.8s var(--hsl-ease),
			transform 0.8s var(--hsl-ease);
	}

	.hsl[data-hsl-ready] [data-hsl-reveal="up"]:not([data-in]) {
		opacity: 0;
		transform: translateY(80px);
	}

	.hsl-w {
		transition:
			opacity 0.6s var(--hsl-ease) calc(var(--i) * 0.06s),
			transform 0.6s var(--hsl-ease) calc(var(--i) * 0.06s);
	}

	.hsl[data-hsl-ready] [data-hsl-reveal="words"]:not([data-in]) .hsl-w {
		opacity: 0;
		transform: translateY(10px);
	}

	@media (min-width: 810px) {
		.hsl-inner {
			padding: 90px 40px;
		}

		.hsl-head {
			grid-template-columns: auto minmax(0, 1fr);
			column-gap: 40px;
		}

		.hsl-eyebrow {
			margin-top: 40px;
		}

		.hsl-heading {
			font-size: 38px;
			letter-spacing: -0.76px;
		}

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

		.hsl-row {
			grid-template-columns: 58px minmax(0, 1fr) 50px;
			align-items: center;
			gap: 20px;
			padding: 35px 30px;
		}

		.hsl-caption {
			left: 50px;
			right: auto;
			bottom: 50px;
			max-width: 495px;
			padding: 16px 24px 16px 37px;
			font-size: 16px;
		}
	}

	@media (min-width: 1200px) {
		.hsl-inner {
			padding: 100px 65px;
		}

		.hsl-head {
			grid-template-columns: 195px 610px minmax(0, 1fr);
			column-gap: 0;
			align-items: start;
		}

		.hsl-eyebrow {
			margin: 48px 0 0 15px;
		}

		.hsl-heading {
			font-size: 50px;
			line-height: 60px;
			letter-spacing: -1px;
		}

		.hsl-intro {
			grid-column: 3;
			justify-self: end;
			margin-top: 22px;
		}

		.hsl-body {
			grid-template-columns: repeat(2, minmax(0, 1fr));
			gap: 20px;
			margin-top: 70px;
		}

		.hsl-media {
			min-height: 558px;
		}
	}

	@media (prefers-reduced-motion: reduce) {
		[data-hsl-reveal="up"],
		.hsl-w,
		.hsl-row,
		.hsl-ic,
		.hsl-title,
		.hsl-arrow {
			transition: none;
		}

		.hsl-row:hover .hsl-arrow,
		.hsl-row:has(a:focus-visible) .hsl-arrow {
			transform: none;
		}
	}
</style>

<script>
	/* Appear-on-scroll; one observer per instance, torn down on astro:before-swap. */
	function initHoverServiceList(root: HTMLElement) {
		if (root.dataset.hslReady === "") return;
		if (window.matchMedia("(prefers-reduced-motion: reduce)").matches || !("IntersectionObserver" in window)) return;
		const controller = new AbortController();
		root.dataset.hslReady = "";
		const io = new IntersectionObserver(
			(entries) => {
				for (const entry of entries) {
					if (!entry.isIntersecting) continue;
					(entry.target as HTMLElement).dataset.in = "";
					io.unobserve(entry.target);
				}
			},
			{ threshold: 0.1, rootMargin: "0px 0px -5% 0px" },
		);
		root.querySelectorAll<HTMLElement>("[data-hsl-reveal]").forEach((t) => io.observe(t));
		controller.signal.addEventListener("abort", () => io.disconnect());
		document.addEventListener(
			"astro:before-swap",
			() => {
				controller.abort();
				delete root.dataset.hslReady;
			},
			{ once: true, signal: controller.signal },
		);
	}

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

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

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

===== END src/components/HoverServiceList.astro =====

===== FILE (required): public/hover-service-list/highway-semi.webp =====
[binary asset — download https://astro.baysixmedia.com/hover-service-list/highway-semi.webp and save it to public/hover-service-list/highway-semi.webp]

===== FILE (required): src/components/StickyProjectStack.astro =====
---
/**
 * StickyProjectStack
 *
 * A navy projects section: outlined eyebrow on the left, a big white
 * uppercase heading on the right, then a stack of wide project cards. From
 * 810px each card is `position: sticky` 120px from the top, so scrolling
 * slides the next card up over the one before — pure CSS, no script needed
 * for the stacking. Each card carries two pills, a title, a paragraph, an
 * orange pill link and a photo with a chamfered top-left corner.
 *
 * Heading words rise in and the stack lifts in the first time it scrolls into
 * view.
 *
 * Heading syntax: wrap the orange words in asterisks.
 *
 * Reads the --tw-* tokens from any ancestor, with fallbacks.
 */

export interface StickyProject {
	title: string;
	text?: string;
	/** Orange pill. */
	category?: string;
	/** Translucent pill, e.g. a year. */
	meta?: string;
	cta?: { label: string; href: string } | null;
	/** `src: ""` renders a labelled placeholder. */
	image?: { src?: string; alt?: string };
}

export interface StickyProjectStackProps {
	eyebrow?: string;
	heading?: string;
	headingLevel?: "h2" | "h3";
	projects?: StickyProject[];
	/** Distance from the viewport top where each card sticks. Clear your fixed header. */
	stickyTop?: string;
	background?: string;
	fontStylesheet?: string;
	class?: string;
	id?: string;
}

interface Props extends StickyProjectStackProps {}

const {
	eyebrow = "Our Projects",
	heading = "",
	headingLevel = "h2",
	projects: rawProjects = [],
	stickyTop = "120px",
	background = "#0b4654",
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Outfit:wght@500;600;700;800;900&family=Noto+Sans:wght@400;500;600;700&family=Inter:wght@500;600;700&display=swap",
	class: className,
	id,
} = Astro.props;

const words = heading
	.split("*")
	.flatMap((seg, i) => seg.split(/\s+/).filter(Boolean).map((w) => ({ w, accent: i % 2 === 1 })));

/* A project without a title is dropped. No projects, no section: the eyebrow alone says nothing. */
const projects = rawProjects.filter((p) => p?.title);

const Heading = headingLevel;
const Title = headingLevel === "h2" ? "h3" : "h4";
const SAFE = /^(?:https?:|mailto:|tel:|[#/.?])/i;
const safeHref = (href: string) => (SAFE.test(href.trim()) ? href.trim() : "#");
---

{projects.length > 0 && (
<>
{fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
<section class:list={["sps", className]} id={id} style={`--sps-band:${background};--sps-top:${stickyTop}`} data-sps-root>
	<div class="sps-inner">
		<div class="sps-head">
			{eyebrow && <p class="sps-eyebrow">{eyebrow}</p>}
			{words.length > 0 && (
			<Heading class="sps-heading" data-sps-reveal="words">
				{
					words.map((t, i) => (
						<>
							{i > 0 && " "}
							<span class:list={["sps-w", { "sps-accent": t.accent }]} style={`--i:${i}`}>
								{t.w}
							</span>
						</>
					))
				}
			</Heading>
			)}
		</div>

		<ol class="sps-stack" data-sps-reveal="up">
			{
				projects.map((p, i) => {
					const img = p.image ?? { src: "" };
					return (
						<li class="sps-card" style={`--n:${i}`}>
							<div class="sps-copy">
								{(p.category || p.meta) && (
									<p class="sps-pills">
										{p.category && <span class="sps-pill sps-pill--accent">{p.category}</span>}
										{p.meta && <span class="sps-pill">{p.meta}</span>}
									</p>
								)}
								<Title class="sps-title">{p.title}</Title>
								{p.text && <p class="sps-text">{p.text}</p>}
								{p.cta && (
									<a class="sps-btn" href={safeHref(p.cta.href)}>
										<span>{p.cta.label}</span>
										<span class="sps-sr">: {p.title}</span>
										<span class="sps-btn-ic" aria-hidden="true">
											<svg viewBox="0 0 24 24"><path d="M8 5l10 7-10 7 3-7Z" fill="currentColor" /></svg>
										</span>
									</a>
								)}
							</div>
							<div class="sps-media">
								{img.src ? (
									<img src={img.src} alt={img.alt ?? ""} width="1160" height="934" loading="lazy" decoding="async" />
								) : (
									<div class="sps-ph" role="img" aria-label={`Image placeholder: ${p.title}`}>
										<span>Image · 1160 × 934</span>
									</div>
								)}
							</div>
						</li>
					);
				})
			}
		</ol>
	</div>
</section>
</>
)}

<style>
	.sps {
		--sps-navy: var(--tw-navy, #0b4654);
		--sps-card: var(--tw-navy-2, #15505e);
		--sps-deep: var(--tw-navy-deep, #052931);
		--sps-accent: var(--tw-accent, #ff6d45);
		--sps-display: var(--tw-display, Outfit, ui-sans-serif, system-ui, sans-serif);
		--sps-body: var(--tw-body, "Noto Sans", ui-sans-serif, system-ui, sans-serif);
		--sps-card-font: var(--tw-card, Inter, ui-sans-serif, system-ui, sans-serif);
		--sps-ease: cubic-bezier(0.44, 0, 0.56, 1);
		--sps-cut: 70px;

		background: var(--sps-band);
		color: #fff;
		font-family: var(--sps-body);
	}

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

	.sps-inner {
		max-width: 1440px;
		margin: 0 auto;
		padding: 64px 16px;
	}

	.sps-head {
		display: flex;
		flex-direction: column;
		align-items: center;
		gap: 18px;
		text-align: center;
	}

	.sps-eyebrow {
		margin: 0;
		padding: 4px 14px;
		border: 1px solid var(--sps-accent);
		border-radius: 999px;
		color: var(--sps-accent);
		font: 500 15px/24px var(--sps-body);
		letter-spacing: -0.3px;
		text-transform: uppercase;
	}

	.sps-heading {
		max-width: 680px;
		margin: 0;
		font: 700 28px/1.2 var(--sps-display);
		letter-spacing: -0.56px;
		text-transform: uppercase;
	}

	.sps-w {
		display: inline-block;
	}

	.sps-accent {
		color: var(--sps-accent);
	}

	.sps-stack {
		display: grid;
		gap: 20px;
		margin: 40px 0 0;
		padding: 0;
		list-style: none;
	}

	.sps-card {
		display: flex;
		flex-direction: column;
		gap: 28px;
		padding: 22px;
		border: 1px solid rgb(255 255 255 / 0.08);
		border-radius: 20px;
		background: var(--sps-card);
	}

	.sps-copy {
		display: flex;
		flex-direction: column;
		align-items: flex-start;
		min-width: 0;
	}

	.sps-pills {
		display: flex;
		gap: 10px;
		margin: 0 0 18px;
	}

	.sps-pill {
		padding: 4px 12px;
		border-radius: 18px;
		background: rgb(255 255 255 / 0.1);
		font: 400 12px/19.2px var(--sps-body);
		letter-spacing: -0.24px;
		text-transform: uppercase;
	}

	.sps-pill--accent {
		background: var(--sps-accent);
	}

	.sps-title {
		margin: 0;
		font: 700 24px/1.35 var(--sps-card-font);
		letter-spacing: -0.96px;
		text-transform: uppercase;
	}

	.sps-text {
		max-width: 580px;
		margin: 12px 0 0;
		font: 400 15px/24px var(--sps-body);
		letter-spacing: -0.3px;
	}

	.sps-btn {
		display: inline-flex;
		align-items: center;
		gap: 12px;
		height: 50px;
		margin-top: 28px;
		padding: 0 8px 0 25px;
		border-radius: 999px;
		background: var(--sps-accent);
		color: #fff;
		font: 400 16px/25.6px var(--sps-body);
		letter-spacing: -0.32px;
		text-decoration: none;
		transition: background-color 0.3s var(--sps-ease);
	}

	.sps-btn:hover {
		background: var(--sps-deep);
	}

	.sps-btn-ic {
		display: grid;
		place-items: center;
		width: 34px;
		height: 34px;
		border-radius: 50%;
		background: #fff;
		color: var(--sps-accent);
		transition: transform 0.3s var(--sps-ease);
	}

	.sps-btn-ic svg {
		width: 18px;
		height: 18px;
	}

	.sps-btn:hover .sps-btn-ic {
		transform: rotate(-45deg);
	}

	.sps-btn:focus-visible {
		outline: 2px solid #fff;
		outline-offset: 3px;
	}

	.sps-media {
		aspect-ratio: 580 / 467;
		min-width: 0;
	}

	.sps-media img,
	.sps-ph {
		display: block;
		width: 100%;
		height: 100%;
		object-fit: cover;
		border-radius: 20px;
		clip-path: polygon(var(--sps-cut) 0, 100% 0, 100% 100%, 0 100%, 0 var(--sps-cut));
	}

	.sps-ph {
		display: grid;
		place-items: center;
		background: repeating-linear-gradient(135deg, #1b5a68 0 12px, #20616f 12px 24px);
		font: 500 13px/1.4 var(--sps-body);
	}

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

	[data-sps-reveal="up"] {
		transition:
			opacity 0.8s var(--sps-ease),
			transform 0.8s var(--sps-ease);
	}

	.sps[data-sps-ready] [data-sps-reveal="up"]:not([data-in]) {
		opacity: 0;
		transform: translateY(80px);
	}

	.sps-w {
		transition:
			opacity 0.6s var(--sps-ease) calc(var(--i) * 0.06s),
			transform 0.6s var(--sps-ease) calc(var(--i) * 0.06s);
	}

	.sps[data-sps-ready] [data-sps-reveal="words"]:not([data-in]) .sps-w {
		opacity: 0;
		transform: translateY(10px);
	}

	@media (min-width: 810px) {
		.sps-inner {
			padding: 90px 40px;
		}

		.sps-head {
			flex-direction: row;
			align-items: flex-start;
			justify-content: space-between;
			text-align: left;
		}

		.sps-eyebrow {
			margin-top: 40px;
		}

		.sps-heading {
			width: 58%;
			font-size: 38px;
			letter-spacing: -0.76px;
		}

		/* The stack: every card sticks at the same offset, later ones on top. */
		.sps-card {
			position: sticky;
			top: var(--sps-top);
			flex-direction: row;
			gap: 40px;
			min-height: 460px;
			padding: 36px;
		}

		.sps-copy {
			flex: 1 1 0;
		}

		.sps-text {
			margin-top: auto;
		}

		.sps-title {
			margin-bottom: 40px;
			font-size: 30px;
		}

		.sps-media {
			flex: 1 1 0;
			aspect-ratio: auto;
		}
	}

	@media (min-width: 1200px) {
		.sps-inner {
			padding: 100px 65px 110px;
		}

		.sps-eyebrow {
			margin: 48px 0 0 15px;
		}

		.sps-heading {
			width: 700px;
			font-size: 50px;
			line-height: 60px;
			letter-spacing: -1px;
		}

		.sps-stack {
			margin-top: 110px;
		}

		.sps-card {
			min-height: 556px;
			padding: 45px 50px;
			gap: 50px;
		}

		.sps-copy {
			flex: 0 0 530px;
			padding-top: 5px;
		}

		.sps-title {
			font-size: 36px;
			line-height: 50.4px;
			letter-spacing: -1.44px;
		}

		.sps-media {
			flex: 1 1 auto;
			height: 467px;
		}
	}

	@media (prefers-reduced-motion: reduce) {
		[data-sps-reveal="up"],
		.sps-w,
		.sps-btn,
		.sps-btn-ic {
			transition: none;
		}

		.sps-btn:hover .sps-btn-ic {
			transform: none;
		}
	}
</style>

<script>
	/* Appear-on-scroll; one observer per instance, torn down on astro:before-swap.
	   The sticky stacking itself is CSS and needs no script. */
	function initStickyProjectStack(root: HTMLElement) {
		if (root.dataset.spsReady === "") return;
		if (window.matchMedia("(prefers-reduced-motion: reduce)").matches || !("IntersectionObserver" in window)) return;
		const controller = new AbortController();
		root.dataset.spsReady = "";
		const io = new IntersectionObserver(
			(entries) => {
				for (const entry of entries) {
					if (!entry.isIntersecting) continue;
					(entry.target as HTMLElement).dataset.in = "";
					io.unobserve(entry.target);
				}
			},
			{ threshold: 0, rootMargin: "0px 0px -10% 0px" },
		);
		root.querySelectorAll<HTMLElement>("[data-sps-reveal]").forEach((t) => io.observe(t));
		controller.signal.addEventListener("abort", () => io.disconnect());
		document.addEventListener(
			"astro:before-swap",
			() => {
				controller.abort();
				delete root.dataset.spsReady;
			},
			{ once: true, signal: controller.signal },
		);
	}

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

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

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

===== END src/components/StickyProjectStack.astro =====

===== FILE (required): public/sticky-project-stack/desert-truck.webp =====
[binary asset — download https://astro.baysixmedia.com/sticky-project-stack/desert-truck.webp and save it to public/sticky-project-stack/desert-truck.webp]

===== FILE (required): public/sticky-project-stack/aerial-ship.webp =====
[binary asset — download https://astro.baysixmedia.com/sticky-project-stack/aerial-ship.webp and save it to public/sticky-project-stack/aerial-ship.webp]

===== FILE (required): public/sticky-project-stack/container-ship.webp =====
[binary asset — download https://astro.baysixmedia.com/sticky-project-stack/container-ship.webp and save it to public/sticky-project-stack/container-ship.webp]

===== FILE (required): src/components/FreightPricingPlans.astro =====
---
/**
 * FreightPricingPlans
 *
 * A pale pricing band: outlined eyebrow, a big three-line uppercase heading
 * with an orange last line and a photo underneath on the left; two white plan
 * cards on the right. Each card has an orange asterisk, an optional outlined
 * badge, the plan name, a one-line pitch, a big price with its period, a
 * full-width orange pill and a checklist.
 *
 * Heading words rise in and the cards lift in, staggered, the first time they
 * scroll into view.
 *
 * Heading syntax: wrap the orange words in asterisks.
 *
 * Reads the --tw-* tokens from any ancestor, with fallbacks.
 */

export interface FreightPlan {
	name: string;
	pitch?: string;
	price: string;
	period?: string;
	/** Outlined badge in the card corner, e.g. "Popular". */
	badge?: string;
	cta?: { label: string; href: string } | null;
	features: string[];
}

export interface FreightPricingPlansProps {
	eyebrow?: string;
	heading?: string;
	headingLevel?: "h2" | "h3";
	/** `src: ""` renders a labelled placeholder; `null` drops it. */
	image?: { src?: string; alt?: string } | null;
	plans?: FreightPlan[];
	background?: string;
	fontStylesheet?: string;
	class?: string;
	id?: string;
}

interface Props extends FreightPricingPlansProps {}

const {
	eyebrow = "Pricing",
	heading = "",
	headingLevel = "h2",
	image: imageProp,
	/* Plans and prices are the caller's; none are defaulted. */
	plans = [],
	background = "#f0f8fa",
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Outfit:wght@500;600;700;800;900&family=Noto+Sans:wght@400;500;600;700&family=Inter:wght@500;600;700&display=swap",
	class: className,
	id,
} = Astro.props;

/* No default photo: the image is drawn only when passed, and never merged
   with a demo one. `src: ""` still draws the neutral labelled placeholder. */
const image = imageProp ? { src: imageProp.src ?? "", alt: imageProp.alt ?? "" } : null;

const filled = (v: unknown): v is string => typeof v === "string" && v.trim() !== "";
/* A plan needs a name and a price; a CTA needs a label and an href; blank
   features are skipped. No plans → no section. */
const planList = (plans ?? [])
	.filter((plan) => plan && filled(plan.name) && filled(plan.price))
	.map((plan) => ({
		...plan,
		cta: plan.cta && filled(plan.cta.label) && filled(plan.cta.href) ? plan.cta : null,
		features: (plan.features ?? []).filter(filled),
	}));

const words = (heading ?? "")
	.split("*")
	.flatMap((seg, i) => seg.split(/\s+/).filter(Boolean).map((w) => ({ w, accent: i % 2 === 1 })));

const Heading = headingLevel;
const Title = headingLevel === "h2" ? "h3" : "h4";
const SAFE = /^(?:https?:|mailto:|tel:|[#/.?])/i;
const safeHref = (href: string) => (SAFE.test(href.trim()) ? href.trim() : "#");
---

{planList.length > 0 && fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
{planList.length > 0 && (
<section class:list={["fpp", className]} id={id} style={`--fpp-band:${background}`} data-fpp-root>
	<div class="fpp-inner">
		<div class="fpp-side">
			{eyebrow && <p class="fpp-eyebrow">{eyebrow}</p>}
			{words.length > 0 && (
			<Heading class="fpp-heading" data-fpp-reveal="words">
				{
					words.map((t, i) => (
						<>
							{i > 0 && " "}
							<span class:list={["fpp-w", { "fpp-accent": t.accent }]} style={`--i:${i}`}>
								{t.w}
							</span>
						</>
					))
				}
			</Heading>
			)}
			{
				image && (
					<div class="fpp-media">
						{image.src ? (
							<img src={image.src} alt={image.alt} width="748" height="580" loading="lazy" decoding="async" />
						) : (
							<div class="fpp-ph" role="img" aria-label="Image placeholder">
								<span>Image · 748 × 580</span>
							</div>
						)}
					</div>
				)
			}
		</div>

		<ul class="fpp-plans">
			{
				planList.map((plan, i) => (
					<li class="fpp-card" data-fpp-reveal="up" style={`--d:${i * 0.12}s`}>
						<div class="fpp-card-top">
							<svg class="fpp-star" viewBox="0 0 30 30" aria-hidden="true">
								<path d="M12.6 1h4.8l-.9 9.6 7.9-5.6 2.4 4.2-8.8 4 8.8 4-2.4 4.2-7.9-5.6.9 9.6h-4.8l.9-9.6-7.9 5.6-2.4-4.2 8.8-4-8.8-4 2.4-4.2 7.9 5.6Z" fill="currentColor" />
							</svg>
							{plan.badge && <span class="fpp-badge">{plan.badge}</span>}
						</div>
						<Title class="fpp-name">{plan.name}</Title>
						{plan.pitch && <p class="fpp-pitch">{plan.pitch}</p>}
						<p class="fpp-price">
							<span class="fpp-amount">{plan.price}</span>
							{plan.period && <span class="fpp-period">{plan.period}</span>}
						</p>
						{plan.cta && (
							<a class="fpp-btn" href={safeHref(plan.cta.href)}>
								<span>
									{plan.cta.label}
									<span class="fpp-sr">: {plan.name}</span>
								</span>
								<span class="fpp-btn-ic" aria-hidden="true">
									<svg viewBox="0 0 24 24"><path d="M8 5l10 7-10 7 3-7Z" fill="currentColor" /></svg>
								</span>
							</a>
						)}
						{plan.features.length > 0 && (
						<ul class="fpp-list">
							{plan.features.map((f) => (
								<li>
									<svg viewBox="0 0 20 20" aria-hidden="true">
										<circle cx="10" cy="10" r="9" fill="currentColor" />
										<path d="m6 10.2 2.6 2.6L14 7.5" fill="none" stroke="#fff" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
									</svg>
									{f}
								</li>
							))}
						</ul>
						)}
					</li>
				))
			}
		</ul>
	</div>
</section>
)}

<style>
	.fpp {
		--fpp-navy: var(--tw-navy, #0b4654);
		--fpp-navy-2: var(--tw-navy-2, #15505e);
		--fpp-deep: var(--tw-navy-deep, #052931);
		--fpp-accent: var(--tw-accent, #ff6d45);
		--fpp-muted: var(--tw-muted, #616161);
		--fpp-display: var(--tw-display, Outfit, ui-sans-serif, system-ui, sans-serif);
		--fpp-body: var(--tw-body, "Noto Sans", ui-sans-serif, system-ui, sans-serif);
		--fpp-ease: cubic-bezier(0.44, 0, 0.56, 1);

		background: var(--fpp-band);
		color: var(--fpp-navy);
		font-family: var(--fpp-body);
	}

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

	.fpp-inner {
		display: grid;
		gap: 40px;
		max-width: 1440px;
		margin: 0 auto;
		padding: 64px 16px;
	}

	.fpp-side {
		display: flex;
		flex-direction: column;
		align-items: flex-start;
	}

	.fpp-eyebrow {
		margin: 0 0 22px;
		padding: 4px 14px;
		border: 1px solid var(--fpp-accent);
		border-radius: 999px;
		color: var(--fpp-accent);
		font: 500 15px/24px var(--fpp-body);
		letter-spacing: -0.3px;
		text-transform: uppercase;
	}

	.fpp-heading {
		max-width: 440px;
		margin: 0;
		font: 700 28px/1.2 var(--fpp-display);
		letter-spacing: -0.56px;
		text-transform: uppercase;
	}

	.fpp-w {
		display: inline-block;
	}

	.fpp-accent {
		color: var(--fpp-accent);
	}

	.fpp-media {
		width: min(374px, 100%);
		margin-top: 40px;
		aspect-ratio: 374 / 290;
	}

	.fpp-media img,
	.fpp-ph {
		display: block;
		width: 100%;
		height: 100%;
		object-fit: cover;
		border-radius: 20px;
	}

	.fpp-ph {
		display: grid;
		place-items: center;
		background: repeating-linear-gradient(135deg, #dbe9ed 0 12px, #e6f0f3 12px 24px);
		font: 500 13px/1.4 var(--fpp-body);
	}

	.fpp-plans {
		display: grid;
		gap: 20px;
		margin: 0;
		padding: 0;
		list-style: none;
	}

	.fpp-card {
		display: flex;
		flex-direction: column;
		padding: 30px;
		border-radius: 20px;
		background: #fff;
	}

	.fpp-card-top {
		display: flex;
		align-items: center;
		justify-content: space-between;
		min-height: 32px;
		margin-bottom: 22px;
	}

	.fpp-star {
		width: 30px;
		height: 30px;
		color: var(--fpp-accent);
	}

	.fpp-badge {
		padding: 3px 14px;
		border: 1px solid var(--fpp-accent);
		border-radius: 999px;
		color: var(--fpp-accent);
		font: 500 15px/24px var(--fpp-body);
		letter-spacing: -0.3px;
		text-transform: uppercase;
	}

	.fpp-name {
		margin: 0;
		color: var(--fpp-navy-2);
		font: 600 30px/42px var(--fpp-display);
		letter-spacing: -0.6px;
		text-transform: uppercase;
	}

	.fpp-pitch {
		margin: 4px 0 0;
		color: var(--fpp-muted);
		font: 400 16px/25.6px var(--fpp-body);
		letter-spacing: -0.32px;
	}

	.fpp-price {
		display: flex;
		align-items: baseline;
		gap: 10px;
		margin: 16px 0 0;
	}

	.fpp-amount {
		font: 700 50px/60px var(--fpp-display);
		letter-spacing: -1px;
	}

	.fpp-period {
		font: 400 16px/25.6px var(--fpp-body);
		letter-spacing: -0.32px;
	}

	.fpp-btn {
		display: flex;
		align-items: center;
		justify-content: space-between;
		height: 50px;
		margin-top: 20px;
		padding: 0 8px 0 25px;
		border-radius: 999px;
		background: var(--fpp-accent);
		color: #fff;
		font: 400 16px/25.6px var(--fpp-body);
		letter-spacing: -0.32px;
		text-decoration: none;
		transition: background-color 0.3s var(--fpp-ease);
	}

	.fpp-btn:hover {
		background: var(--fpp-deep);
	}

	.fpp-btn-ic {
		display: grid;
		place-items: center;
		width: 34px;
		height: 34px;
		border-radius: 50%;
		background: #fff;
		color: var(--fpp-accent);
		transition: transform 0.3s var(--fpp-ease);
	}

	.fpp-btn-ic svg {
		width: 18px;
		height: 18px;
	}

	.fpp-btn:hover .fpp-btn-ic {
		transform: rotate(-45deg);
	}

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

	.fpp-list {
		display: grid;
		gap: 14px;
		margin: 44px 0 0;
		padding: 0;
		list-style: none;
	}

	.fpp-list li {
		display: flex;
		align-items: center;
		gap: 12px;
		font: 400 16px/25.6px var(--fpp-body);
		letter-spacing: -0.32px;
	}

	.fpp-list svg {
		flex: none;
		width: 18px;
		height: 18px;
		color: var(--fpp-accent);
	}

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

	[data-fpp-reveal="up"] {
		transition:
			opacity 0.8s var(--fpp-ease) var(--d, 0s),
			transform 0.8s var(--fpp-ease) var(--d, 0s);
	}

	.fpp[data-fpp-ready] [data-fpp-reveal="up"]:not([data-in]) {
		opacity: 0;
		transform: translateY(80px);
	}

	.fpp-w {
		transition:
			opacity 0.6s var(--fpp-ease) calc(var(--i) * 0.06s),
			transform 0.6s var(--fpp-ease) calc(var(--i) * 0.06s);
	}

	.fpp[data-fpp-ready] [data-fpp-reveal="words"]:not([data-in]) .fpp-w {
		opacity: 0;
		transform: translateY(10px);
	}

	@media (min-width: 810px) {
		.fpp-inner {
			padding: 90px 40px;
		}

		.fpp-heading {
			font-size: 38px;
			letter-spacing: -0.76px;
		}

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

	@media (min-width: 1200px) {
		.fpp-inner {
			grid-template-columns: 487px minmax(0, 1fr);
			align-items: start;
			padding: 100px 65px;
		}

		.fpp-side {
			align-self: stretch;
		}

		.fpp-heading {
			font-size: 50px;
			line-height: 60px;
			letter-spacing: -1px;
		}

		.fpp-media {
			margin-top: auto;
		}
	}

	@media (prefers-reduced-motion: reduce) {
		[data-fpp-reveal="up"],
		.fpp-w,
		.fpp-btn,
		.fpp-btn-ic {
			transition: none;
		}

		.fpp-btn:hover .fpp-btn-ic {
			transform: none;
		}
	}
</style>

<script>
	/* Appear-on-scroll; one observer per instance, torn down on astro:before-swap. */
	function initFreightPricingPlans(root: HTMLElement) {
		if (root.dataset.fppReady === "") return;
		if (window.matchMedia("(prefers-reduced-motion: reduce)").matches || !("IntersectionObserver" in window)) return;
		const controller = new AbortController();
		root.dataset.fppReady = "";
		const io = new IntersectionObserver(
			(entries) => {
				for (const entry of entries) {
					if (!entry.isIntersecting) continue;
					(entry.target as HTMLElement).dataset.in = "";
					io.unobserve(entry.target);
				}
			},
			{ threshold: 0.1, rootMargin: "0px 0px -5% 0px" },
		);
		root.querySelectorAll<HTMLElement>("[data-fpp-reveal]").forEach((t) => io.observe(t));
		controller.signal.addEventListener("abort", () => io.disconnect());
		document.addEventListener(
			"astro:before-swap",
			() => {
				controller.abort();
				delete root.dataset.fppReady;
			},
			{ once: true, signal: controller.signal },
		);
	}

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

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

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

===== END src/components/FreightPricingPlans.astro =====

===== FILE (required): public/freight-pricing-plans/forklift.webp =====
[binary asset — download https://astro.baysixmedia.com/freight-pricing-plans/forklift.webp and save it to public/freight-pricing-plans/forklift.webp]

===== FILE (required): src/components/ChamferStepCards.astro =====
---
/**
 * ChamferStepCards
 *
 * A "how it works" section: outlined eyebrow, a big uppercase heading and an
 * intro paragraph across the top, then three step cards. Each card is a photo
 * with both top corners chamfered and a pale caption panel overlapping its
 * lower edge, carrying an orange "Step 01" pill, an uppercase title and a
 * sentence.
 *
 * Heading words rise in and the cards lift in, staggered, the first time they
 * scroll into view.
 *
 * Heading syntax: wrap the orange words in asterisks.
 *
 * Reads the --tw-* tokens from any ancestor, with fallbacks.
 */

export interface ChamferStep {
	title: string;
	text: string;
	/** Pill text. Defaults to "Step 01", "Step 02"... */
	label?: string;
	/** `src: ""` renders a labelled placeholder. */
	image?: { src?: string; alt?: string };
}

export interface ChamferStepCardsProps {
	eyebrow?: string;
	heading?: string;
	headingLevel?: "h2" | "h3";
	intro?: string;
	steps?: ChamferStep[];
	background?: string;
	fontStylesheet?: string;
	class?: string;
	id?: string;
}

interface Props extends ChamferStepCardsProps {}

const {
	eyebrow = "How It Works",
	heading = "",
	headingLevel = "h2",
	intro = "",
	steps = [],
	background = "#ffffff",
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Outfit:wght@500;600;700;800;900&family=Noto+Sans:wght@400;500;600;700&family=Inter:wght@500;600;700&display=swap",
	class: className,
	id,
} = Astro.props;

const words = heading
	.split("*")
	.flatMap((seg, i) => seg.split(/\s+/).filter(Boolean).map((w) => ({ w, accent: i % 2 === 1 })));

const Heading = headingLevel;
const Title = headingLevel === "h2" ? "h3" : "h4";
const pad = (n: number) => String(n).padStart(2, "0");

/* No demo content: steps, heading and intro are props. A step with no title
   is dropped; with no steps left the section renders nothing, and the
   generic eyebrow never makes it render on its own. */
const items = (steps ?? []).filter((s) => s?.title);
---

{items.length > 0 && (
<>{fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
<section class:list={["csc", className]} id={id} style={`--csc-band:${background}`} data-csc-root>
	<div class="csc-inner">
		<div class="csc-head">
			{eyebrow && <p class="csc-eyebrow">{eyebrow}</p>}
			{words.length > 0 && (
			<Heading class="csc-heading" data-csc-reveal="words">
				{
					words.map((t, i) => (
						<>
							{i > 0 && " "}
							<span class:list={["csc-w", { "csc-accent": t.accent }]} style={`--i:${i}`}>
								{t.w}
							</span>
						</>
					))
				}
			</Heading>
			)}
			{intro && <p class="csc-intro">{intro}</p>}
		</div>

		<ol class="csc-grid">
			{
				items.map((s, i) => {
					const img = s.image ?? { src: "" };
					return (
						<li class="csc-card" data-csc-reveal="up" style={`--d:${i * 0.12}s`}>
							<div class="csc-media">
								{img.src ? (
									<img src={img.src} alt={img.alt ?? ""} width="848" height="584" loading="lazy" decoding="async" />
								) : (
									<div class="csc-ph" role="img" aria-label={`Image placeholder: ${s.title}`}>
										<span>Image · 848 × 584</span>
									</div>
								)}
							</div>
							<div class="csc-panel">
								<span class="csc-pill">{s.label ?? `Step ${pad(i + 1)}`}</span>
								<Title class="csc-title">{s.title}</Title>
								{s.text && <p class="csc-text">{s.text}</p>}
							</div>
						</li>
					);
				})
			}
		</ol>
	</div>
</section></>
)}

<style>
	.csc {
		--csc-navy: var(--tw-navy, #0b4654);
		--csc-accent: var(--tw-accent, #ff6d45);
		--csc-surface: var(--tw-surface, #f0f8fa);
		--csc-muted: var(--tw-muted, #616161);
		--csc-display: var(--tw-display, Outfit, ui-sans-serif, system-ui, sans-serif);
		--csc-body: var(--tw-body, "Noto Sans", ui-sans-serif, system-ui, sans-serif);
		--csc-ease: cubic-bezier(0.44, 0, 0.56, 1);
		--csc-cut: 44px;

		background: var(--csc-band);
		color: var(--csc-navy);
		font-family: var(--csc-body);
	}

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

	.csc-inner {
		max-width: 1440px;
		margin: 0 auto;
		padding: 64px 16px;
	}

	.csc-head {
		display: grid;
		gap: 18px;
		justify-items: start;
	}

	.csc-eyebrow {
		margin: 0;
		padding: 4px 14px;
		border: 1px solid var(--csc-accent);
		border-radius: 999px;
		color: var(--csc-accent);
		font: 500 15px/24px var(--csc-body);
		letter-spacing: -0.3px;
		text-transform: uppercase;
	}

	.csc-heading {
		margin: 0;
		font: 700 28px/1.2 var(--csc-display);
		letter-spacing: -0.56px;
		text-transform: uppercase;
	}

	.csc-w {
		display: inline-block;
	}

	.csc-accent {
		color: var(--csc-accent);
	}

	.csc-intro {
		max-width: 400px;
		margin: 0;
		font: 400 16px/25.6px var(--csc-body);
		letter-spacing: -0.32px;
	}

	.csc-grid {
		display: grid;
		gap: 24px;
		margin: 40px 0 0;
		padding: 0;
		list-style: none;
	}

	.csc-card {
		display: flex;
		flex-direction: column;
	}

	.csc-media {
		aspect-ratio: 424 / 292;
	}

	.csc-media img,
	.csc-ph {
		display: block;
		width: 100%;
		height: 100%;
		object-fit: cover;
		border-radius: 14px;
		clip-path: polygon(var(--csc-cut) 0, calc(100% - var(--csc-cut)) 0, 100% var(--csc-cut), 100% 100%, 0 100%, 0 var(--csc-cut));
	}

	.csc-ph {
		display: grid;
		place-items: center;
		background: repeating-linear-gradient(135deg, #e3eef1 0 12px, #edf5f7 12px 24px);
		font: 500 13px/1.4 var(--csc-body);
	}

	.csc-panel {
		position: relative;
		margin: -52px 20px 0;
		padding: 16px 20px 20px;
		border-radius: 10px;
		background: var(--csc-surface);
	}

	.csc-pill {
		display: inline-block;
		padding: 4px 12px;
		border-radius: 18px;
		background: var(--csc-accent);
		color: #fff;
		font: 600 14px/22.4px var(--csc-body);
		letter-spacing: -0.28px;
		text-transform: uppercase;
	}

	.csc-title {
		margin: 12px 0 0;
		font: 600 20px/32px var(--csc-body);
		letter-spacing: -0.4px;
		text-transform: uppercase;
	}

	.csc-text {
		margin: 2px 0 0;
		color: var(--csc-muted);
		font: 400 16px/25.6px var(--csc-body);
		letter-spacing: -0.32px;
	}

	[data-csc-reveal="up"] {
		transition:
			opacity 0.8s var(--csc-ease) var(--d, 0s),
			transform 0.8s var(--csc-ease) var(--d, 0s);
	}

	.csc[data-csc-ready] [data-csc-reveal="up"]:not([data-in]) {
		opacity: 0;
		transform: translateY(80px);
	}

	.csc-w {
		transition:
			opacity 0.6s var(--csc-ease) calc(var(--i) * 0.06s),
			transform 0.6s var(--csc-ease) calc(var(--i) * 0.06s);
	}

	.csc[data-csc-ready] [data-csc-reveal="words"]:not([data-in]) .csc-w {
		opacity: 0;
		transform: translateY(10px);
	}

	@media (min-width: 810px) {
		.csc-inner {
			padding: 90px 40px;
		}

		.csc-head {
			grid-template-columns: auto minmax(0, 1fr);
			column-gap: 40px;
		}

		.csc-eyebrow {
			margin-top: 40px;
		}

		.csc-heading {
			font-size: 38px;
			letter-spacing: -0.76px;
		}

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

		.csc-grid {
			grid-template-columns: repeat(2, minmax(0, 1fr));
			row-gap: 36px;
		}
	}

	@media (min-width: 1200px) {
		.csc-inner {
			padding: 100px 65px;
		}

		.csc-head {
			grid-template-columns: 245px 640px minmax(0, 1fr);
			column-gap: 0;
			align-items: start;
		}

		.csc-eyebrow {
			margin: 48px 0 0 15px;
		}

		.csc-heading {
			font-size: 50px;
			line-height: 60px;
			letter-spacing: -1px;
		}

		.csc-intro {
			grid-column: 3;
			justify-self: end;
			margin-top: 22px;
		}

		.csc-grid {
			grid-template-columns: repeat(3, minmax(0, 1fr));
			gap: 20px;
			margin-top: 60px;
		}
	}

	@media (prefers-reduced-motion: reduce) {
		[data-csc-reveal="up"],
		.csc-w {
			transition: none;
		}
	}
</style>

<script>
	/* Appear-on-scroll; one observer per instance, torn down on astro:before-swap. */
	function initChamferStepCards(root: HTMLElement) {
		if (root.dataset.cscReady === "") return;
		if (window.matchMedia("(prefers-reduced-motion: reduce)").matches || !("IntersectionObserver" in window)) return;
		const controller = new AbortController();
		root.dataset.cscReady = "";
		const io = new IntersectionObserver(
			(entries) => {
				for (const entry of entries) {
					if (!entry.isIntersecting) continue;
					(entry.target as HTMLElement).dataset.in = "";
					io.unobserve(entry.target);
				}
			},
			{ threshold: 0.1, rootMargin: "0px 0px -5% 0px" },
		);
		root.querySelectorAll<HTMLElement>("[data-csc-reveal]").forEach((t) => io.observe(t));
		controller.signal.addEventListener("abort", () => io.disconnect());
		document.addEventListener(
			"astro:before-swap",
			() => {
				controller.abort();
				delete root.dataset.cscReady;
			},
			{ once: true, signal: controller.signal },
		);
	}

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

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

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

===== END src/components/ChamferStepCards.astro =====

===== FILE (required): public/chamfer-step-cards/containers.webp =====
[binary asset — download https://astro.baysixmedia.com/chamfer-step-cards/containers.webp and save it to public/chamfer-step-cards/containers.webp]

===== FILE (required): public/chamfer-step-cards/train.webp =====
[binary asset — download https://astro.baysixmedia.com/chamfer-step-cards/train.webp and save it to public/chamfer-step-cards/train.webp]

===== FILE (required): public/chamfer-step-cards/courier.webp =====
[binary asset — download https://astro.baysixmedia.com/chamfer-step-cards/courier.webp and save it to public/chamfer-step-cards/courier.webp]

===== FILE (required): src/components/PortraitRatingTestimonials.astro =====
---
/**
 * PortraitRatingTestimonials
 *
 * A centred testimonial section: outlined eyebrow and a big uppercase
 * heading over three columns. The outer columns each hold two quote cards
 * (orange quote mark, quote, avatar, name, role and a small star score); the
 * middle column is a tall portrait photo fading to navy at the foot, carrying
 * an overall rating with five stars and a review count.
 *
 * Heading words rise in and the cards lift in, staggered, the first time they
 * scroll into view.
 *
 * Reads the --tw-* tokens from any ancestor, with fallbacks.
 */

export interface PortraitTestimonial {
	quote: string;
	name: string;
	role?: string;
	/** Score shown in the pill, e.g. "5.0". Omit it (or `""`) and there is no pill. */
	score?: string;
	/** `src: ""` renders initials instead. */
	avatar?: { src?: string; alt?: string };
}

export interface PortraitRatingTestimonialsProps {
	eyebrow?: string;
	heading?: string;
	headingLevel?: "h2" | "h3";
	/** Four quotes: two left, two right. Extra ones are ignored. */
	testimonials?: PortraitTestimonial[];
	/** The centre photo. `src: ""` renders a labelled placeholder. */
	portrait?: { src?: string; alt?: string };
	/**
	 * The overall rating, from a real review source. Omit it (or pass `null`)
	 * and there is none: an aggregate rating and a review count are never
	 * supplied by the component. The stars follow the score's leading number.
	 */
	rating?: { score: string; count: string } | null;
	background?: string;
	/** Quote card fill. White on a pale band; pale on a white band. */
	cardBackground?: string;
	fontStylesheet?: string;
	class?: string;
	id?: string;
}

interface Props extends PortraitRatingTestimonialsProps {}

const {
	eyebrow = "Testimonials",
	heading = "What our *clients* say",
	headingLevel = "h2",
	/* No quotes of its own: they come from the caller or not at all. */
	testimonials = [],
	portrait: portraitProp,
	rating = null,
	background = "#f0f8fa",
	cardBackground = "#ffffff",
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Outfit:wght@500;600;700;800;900&family=Noto+Sans:wght@400;500;600;700&family=Inter:wght@500;600;700&display=swap",
	class: className,
	id,
} = Astro.props;

/* Whole stars for a score like "4.9/5.0": its leading number, rounded, 0–5. */
const starsFor = (score: string) => {
	const n = Number.parseFloat(score);
	return Number.isFinite(n) ? Math.min(Math.max(Math.round(n), 0), 5) : 0;
};

/* No photo of its own: unset (or `src: ""`) renders the labelled placeholder. */
const portrait = { src: "", alt: "", ...(portraitProp ?? {}) };

const words = heading
	.split("*")
	.flatMap((seg, i) => seg.split(/\s+/).filter(Boolean).map((w) => ({ w, accent: i % 2 === 1 })));

const Heading = headingLevel;
const left = testimonials.slice(0, 2);
const right = testimonials.slice(2, 4);
const initials = (name: string) =>
	name
		.split(/\s+/)
		.map((p) => p[0])
		.join("")
		.slice(0, 2)
		.toUpperCase();
---

{testimonials.length > 0 && (<>
{fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
<section
	class:list={["prt", className]}
	id={id}
	style={`--prt-band:${background};--prt-card-bg:${cardBackground}`}
	data-prt-root
>
	<div class="prt-inner">
		<div class="prt-head">
			{eyebrow && <p class="prt-eyebrow">{eyebrow}</p>}
			<Heading class="prt-heading" data-prt-reveal="words">
				{
					words.map((t, i) => (
						<>
							{i > 0 && " "}
							<span class:list={["prt-w", { "prt-accent": t.accent }]} style={`--i:${i}`}>
								{t.w}
							</span>
						</>
					))
				}
			</Heading>
		</div>

		<div class="prt-grid">
			{
				[left, right].map((col, c) => (
					<ul class:list={["prt-col", c === 0 ? "prt-col--left" : "prt-col--right"]}>
						{col.map((t, i) => (
							<li class="prt-card" data-prt-reveal="up" style={`--d:${(c * 2 + i) * 0.1}s`}>
								<svg class="prt-mark" viewBox="0 0 40 40" aria-hidden="true">
									<path d="M4 34V22C4 12 8 7 17 5l1 4c-5 2-7 5-7 9h6v16Zm19 0V22c0-10 4-15 13-17l1 4c-5 2-7 5-7 9h6v16Z" fill="currentColor" />
								</svg>
								<blockquote class="prt-quote">
									<p>{t.quote}</p>
								</blockquote>
								<div class="prt-person">
									{t.avatar?.src ? (
										<img class="prt-avatar" src={t.avatar.src} alt={t.avatar.alt ?? ""} width="40" height="40" loading="lazy" decoding="async" />
									) : (
										<span class="prt-avatar prt-avatar--initials" aria-hidden="true">
											{initials(t.name)}
										</span>
									)}
									<div class="prt-who">
										<p class="prt-name">{t.name}</p>
										{t.role && <p class="prt-role">{t.role}</p>}
									</div>
									{t.score && (
										<p class="prt-score">
											<svg viewBox="0 0 20 20" aria-hidden="true">
												<path d="m10 1.8 2.5 5.3 5.8.7-4.3 4 1.1 5.7L10 14.7l-5.1 2.8L6 11.8l-4.3-4 5.8-.7Z" fill="currentColor" />
											</svg>
											<span class="prt-sr">Rated </span>
											{t.score}
										</p>
									)}
								</div>
							</li>
						))}
					</ul>
				))
			}

			<figure class="prt-portrait" data-prt-reveal="up" style="--d:0.05s">
				{
					portrait.src ? (
						<img src={portrait.src} alt={portrait.alt} width="854" height="1254" loading="lazy" decoding="async" />
					) : (
						<div class="prt-ph" role="img" aria-label="Image placeholder: portrait of a site worker">
							<span>Image · portrait · 854 × 1254</span>
						</div>
					)
				}
				{
					rating && (
						<figcaption class="prt-rating">
							<span class="prt-rating-badge" aria-hidden="true">
								<svg viewBox="0 0 20 20"><path d="m10 1.8 2.5 5.3 5.8.7-4.3 4 1.1 5.7L10 14.7l-5.1 2.8L6 11.8l-4.3-4 5.8-.7Z" fill="currentColor" /></svg>
							</span>
							<span>
								<span class="prt-rating-score">{rating.score}</span>
								<span class="prt-rating-line">
									{starsFor(rating.score) > 0 && (
										<span class="prt-stars" aria-hidden="true">{"★".repeat(starsFor(rating.score))}</span>
									)}
									<span class="prt-rating-count">{rating.count}</span>
								</span>
							</span>
						</figcaption>
					)
				}
			</figure>
		</div>
	</div>
</section>
</>)}

<style>
	.prt {
		--prt-navy: var(--tw-navy, #0b4654);
		--prt-accent: var(--tw-accent, #ff6d45);
		--prt-muted: var(--tw-muted, #616161);
		--prt-display: var(--tw-display, Outfit, ui-sans-serif, system-ui, sans-serif);
		--prt-body: var(--tw-body, "Noto Sans", ui-sans-serif, system-ui, sans-serif);
		--prt-ease: cubic-bezier(0.44, 0, 0.56, 1);

		background: var(--prt-band);
		color: var(--prt-navy);
		font-family: var(--prt-body);
	}

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

	.prt-inner {
		max-width: 1440px;
		margin: 0 auto;
		padding: 64px 16px;
	}

	.prt-head {
		display: flex;
		flex-direction: column;
		align-items: center;
		gap: 18px;
		text-align: center;
	}

	.prt-eyebrow {
		margin: 0;
		padding: 4px 14px;
		border: 1px solid var(--prt-accent);
		border-radius: 999px;
		color: var(--prt-accent);
		font: 500 15px/24px var(--prt-body);
		letter-spacing: -0.3px;
		text-transform: uppercase;
	}

	.prt-heading {
		margin: 0;
		font: 700 28px/1.2 var(--prt-display);
		letter-spacing: -0.56px;
		text-transform: uppercase;
	}

	.prt-w {
		display: inline-block;
	}

	.prt-accent {
		color: var(--prt-accent);
	}

	.prt-grid {
		display: grid;
		grid-template-columns: minmax(0, 1fr);
		gap: 15px;
		margin-top: 40px;
	}

	.prt-col {
		display: grid;
		gap: 15px;
		margin: 0;
		padding: 0;
		list-style: none;
	}

	.prt-card {
		display: flex;
		flex-direction: column;
		min-width: 0;
		padding: 25px;
		border-radius: 20px;
		background: var(--prt-card-bg);
	}

	.prt-mark {
		width: 36px;
		height: 36px;
		color: var(--prt-accent);
	}

	.prt-quote {
		margin: 16px 0 0;
	}

	.prt-quote p {
		margin: 0;
		font: 500 16px/25.6px var(--prt-body);
		letter-spacing: -0.32px;
		/* A long unbroken word in a real review wraps rather than pushing the page sideways. */
		overflow-wrap: anywhere;
	}

	.prt-person {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		gap: 10px;
		margin-top: 24px;
	}

	.prt-avatar {
		flex: none;
		width: 40px;
		height: 40px;
		border-radius: 50%;
		object-fit: cover;
	}

	.prt-avatar--initials {
		display: grid;
		place-items: center;
		background: var(--prt-navy);
		color: #fff;
		font: 600 14px/1 var(--prt-body);
	}

	.prt-who {
		min-width: 0;
		margin-right: auto;
	}

	.prt-name {
		margin: 0;
		overflow: hidden;
		font: 500 18px/25.2px var(--prt-body);
		letter-spacing: -0.36px;
		text-overflow: ellipsis;
		text-transform: uppercase;
		white-space: nowrap;
	}

	.prt-role {
		margin: 0;
		color: var(--prt-muted);
		font: 400 15px/24px var(--prt-body);
		letter-spacing: -0.3px;
		overflow-wrap: anywhere;
	}

	.prt-score {
		display: inline-flex;
		flex: none;
		align-items: center;
		gap: 6px;
		margin: 0;
		padding: 2px 10px;
		border: 1px solid rgb(11 70 84 / 0.12);
		border-radius: 999px;
		color: var(--prt-muted);
		font: 600 14px/22.4px var(--prt-body);
	}

	.prt-score svg {
		width: 13px;
		height: 13px;
		color: var(--prt-accent);
	}

	.prt-portrait {
		position: relative;
		min-height: 420px;
		margin: 0;
		overflow: hidden;
		border-radius: 20px;
		background: var(--prt-accent);
	}

	.prt-portrait img,
	.prt-ph {
		position: absolute;
		inset: 0;
		width: 100%;
		height: 100%;
		object-fit: cover;
		object-position: center 20%;
	}

	.prt-portrait::after {
		content: "";
		position: absolute;
		inset: 0;
		background: linear-gradient(180deg, rgb(255 255 255 / 0) 40%, var(--prt-navy) 80%, #000 100%);
	}

	.prt-ph {
		display: grid;
		place-items: center;
		background: repeating-linear-gradient(135deg, #ff8a67 0 12px, #ff7a53 12px 24px);
		color: #fff;
		font: 500 13px/1.4 var(--prt-body);
	}

	.prt-rating {
		position: absolute;
		left: 25px;
		bottom: 30px;
		z-index: 1;
		display: flex;
		align-items: center;
		gap: 12px;
		color: #fff;
	}

	.prt-rating-badge {
		display: grid;
		place-items: center;
		width: 50px;
		height: 50px;
		border-radius: 50%;
		background: #fff;
		color: var(--prt-accent);
	}

	.prt-rating-badge svg {
		width: 26px;
		height: 26px;
	}

	.prt-rating-score {
		display: block;
		font: 600 30px/42px var(--prt-display);
		letter-spacing: -0.6px;
		text-transform: uppercase;
	}

	.prt-rating-line {
		display: flex;
		align-items: center;
		gap: 8px;
	}

	.prt-stars {
		color: var(--prt-accent);
		font-size: 16px;
		letter-spacing: 2px;
	}

	.prt-rating-count {
		font: 600 14px/22.4px var(--prt-body);
		letter-spacing: -0.28px;
		text-transform: uppercase;
	}

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

	[data-prt-reveal="up"] {
		transition:
			opacity 0.8s var(--prt-ease) var(--d, 0s),
			transform 0.8s var(--prt-ease) var(--d, 0s);
	}

	.prt[data-prt-ready] [data-prt-reveal="up"]:not([data-in]) {
		opacity: 0;
		transform: translateY(80px);
	}

	.prt-w {
		transition:
			opacity 0.6s var(--prt-ease) calc(var(--i) * 0.06s),
			transform 0.6s var(--prt-ease) calc(var(--i) * 0.06s);
	}

	.prt[data-prt-ready] [data-prt-reveal="words"]:not([data-in]) .prt-w {
		opacity: 0;
		transform: translateY(10px);
	}

	/* Phone: left pair, portrait, right pair. */
	.prt-col--left {
		order: 1;
	}

	.prt-portrait {
		order: 2;
	}

	.prt-col--right {
		order: 3;
	}

	@media (min-width: 810px) {
		.prt-inner {
			padding: 90px 40px;
		}

		.prt-heading {
			font-size: 38px;
			letter-spacing: -0.76px;
		}

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

		.prt-portrait {
			grid-column: 1 / -1;
			min-height: 520px;
		}
	}

	@media (min-width: 1200px) {
		.prt-inner {
			padding: 100px 65px;
		}

		.prt-heading {
			font-size: 50px;
			line-height: 60px;
			letter-spacing: -1px;
		}

		.prt-grid {
			grid-template-columns: repeat(3, minmax(0, 1fr));
			align-items: start;
			margin-top: 50px;
		}

		.prt-col--left {
			order: 1;
		}

		.prt-portrait {
			order: 2;
			grid-column: auto;
			align-self: stretch;
			min-height: 602px;
		}

		.prt-col--right {
			order: 3;
			padding-top: 12px;
		}
	}

	@media (prefers-reduced-motion: reduce) {
		[data-prt-reveal="up"],
		.prt-w {
			transition: none;
		}
	}
</style>

<script>
	/* Appear-on-scroll; one observer per instance, torn down on astro:before-swap. */
	function initPortraitRatingTestimonials(root: HTMLElement) {
		if (root.dataset.prtReady === "") return;
		if (window.matchMedia("(prefers-reduced-motion: reduce)").matches || !("IntersectionObserver" in window)) return;
		const controller = new AbortController();
		root.dataset.prtReady = "";
		const io = new IntersectionObserver(
			(entries) => {
				for (const entry of entries) {
					if (!entry.isIntersecting) continue;
					(entry.target as HTMLElement).dataset.in = "";
					io.unobserve(entry.target);
				}
			},
			{ threshold: 0.1, rootMargin: "0px 0px -5% 0px" },
		);
		root.querySelectorAll<HTMLElement>("[data-prt-reveal]").forEach((t) => io.observe(t));
		controller.signal.addEventListener("abort", () => io.disconnect());
		document.addEventListener(
			"astro:before-swap",
			() => {
				controller.abort();
				delete root.dataset.prtReady;
			},
			{ once: true, signal: controller.signal },
		);
	}

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

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

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

===== END src/components/PortraitRatingTestimonials.astro =====

===== FILE (required): public/portrait-rating-testimonials/portrait.webp =====
[binary asset — download https://astro.baysixmedia.com/portrait-rating-testimonials/portrait.webp and save it to public/portrait-rating-testimonials/portrait.webp]

===== FILE (required): public/portrait-rating-testimonials/avatar-1.webp =====
[binary asset — download https://astro.baysixmedia.com/portrait-rating-testimonials/avatar-1.webp and save it to public/portrait-rating-testimonials/avatar-1.webp]

===== FILE (required): public/portrait-rating-testimonials/avatar-2.webp =====
[binary asset — download https://astro.baysixmedia.com/portrait-rating-testimonials/avatar-2.webp and save it to public/portrait-rating-testimonials/avatar-2.webp]

===== FILE (required): public/portrait-rating-testimonials/avatar-3.webp =====
[binary asset — download https://astro.baysixmedia.com/portrait-rating-testimonials/avatar-3.webp and save it to public/portrait-rating-testimonials/avatar-3.webp]

===== FILE (required): public/portrait-rating-testimonials/avatar-4.webp =====
[binary asset — download https://astro.baysixmedia.com/portrait-rating-testimonials/avatar-4.webp and save it to public/portrait-rating-testimonials/avatar-4.webp]

===== FILE (required): src/components/NumberedFaqPhoto.astro =====
---
/**
 * NumberedFaqPhoto
 *
 * A centred FAQ: outlined eyebrow and a big uppercase heading, then a pale
 * panel of numbered question cards beside a large photo. Each question is a
 * native <details> in an exclusive group, so opening one closes the others;
 * its round orange "+" turns into an "×" when open. Where the browser supports
 * it, the answer's height animates open and closed.
 *
 * No script except the appear-on-scroll observer; the accordion works without
 * JavaScript.
 *
 * Heading syntax: wrap the orange words in asterisks.
 *
 * Reads the --tw-* tokens from any ancestor, with fallbacks.
 */

export interface NumberedFaqItem {
	question: string;
	answer: string;
}

export interface NumberedFaqPhotoProps {
	eyebrow?: string;
	heading?: string;
	headingLevel?: "h2" | "h3";
	items?: NumberedFaqItem[];
	/** Prefix each question with "01.", "02."... */
	numbered?: boolean;
	/** 1-based index of the question open on load. 0 opens none. */
	initialOpen?: number;
	/** `src: ""` renders a labelled placeholder; `null` drops it and the panel spans the row. */
	image?: { src?: string; alt?: string } | null;
	background?: string;
	fontStylesheet?: string;
	class?: string;
	id?: string;
}

interface Props extends NumberedFaqPhotoProps {}

const {
	eyebrow = "FAQ",
	heading = "",
	headingLevel = "h2",
	items = [],
	numbered = true,
	initialOpen = 1,
	image: imageProp,
	background = "#ffffff",
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Outfit:wght@500;600;700;800;900&family=Noto+Sans:wght@400;500;600;700&family=Inter:wght@500;600;700&display=swap",
	class: className,
	id,
} = Astro.props;

/* No photo unless one is passed: omitted or `null` lets the panel span the
   row. `{ src: "" }` still asks for the labelled placeholder. */
const image = imageProp ? { src: "", alt: "", ...imageProp } : null;
if (imageProp && imageProp.src !== undefined && imageProp.alt === undefined && image) image.alt = "";

/* A card needs a question and an answer; anything short of that is dropped.
   No cards, no section — the eyebrow and heading alone say nothing. */
const list = items.filter(
	(item) =>
		typeof item?.question === "string" &&
		item.question.trim() !== "" &&
		typeof item.answer === "string" &&
		item.answer.trim() !== "",
);

const group = `${id ?? "nfp"}-${Math.random().toString(36).slice(2, 8)}`;
const words = heading
	.split("*")
	.flatMap((seg, i) => seg.split(/\s+/).filter(Boolean).map((w) => ({ w, accent: i % 2 === 1 })));
const Heading = headingLevel;
const pad = (n: number) => String(n).padStart(2, "0");
---

{list.length > 0 && (<>{fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
<section class:list={["nfp", className]} id={id} style={`--nfp-band:${background}`} data-nfp-root>
	<div class="nfp-inner">
		{(eyebrow || words.length > 0) && (
		<div class="nfp-head">
			{eyebrow && <p class="nfp-eyebrow">{eyebrow}</p>}
			{words.length > 0 && (
			<Heading class="nfp-heading" data-nfp-reveal="words">
				{
					words.map((t, i) => (
						<>
							{i > 0 && " "}
							<span class:list={["nfp-w", { "nfp-accent": t.accent }]} style={`--i:${i}`}>
								{t.w}
							</span>
						</>
					))
				}
			</Heading>
			)}
		</div>
		)}

		<div class:list={["nfp-body", { "nfp-body--solo": !image }]}>
			<div class="nfp-panel" data-nfp-reveal="up">
				{
					list.map((item, i) => (
						<details class="nfp-item" name={group} open={initialOpen === i + 1}>
							<summary class="nfp-q">
								<span>
									{numbered && `${pad(i + 1)}. `}
									{item.question}
								</span>
								<span class="nfp-ic" aria-hidden="true">
									<svg viewBox="0 0 24 24">
										<path d="M12 6v12M6 12h12" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
									</svg>
								</span>
							</summary>
							<div class="nfp-a">
								<p>{item.answer}</p>
							</div>
						</details>
					))
				}
			</div>

			{
				image && (
					<div class="nfp-media" data-nfp-reveal="up" style="--d:0.12s">
						{image.src ? (
							<img src={image.src} alt={image.alt} width="1260" height="1056" loading="lazy" decoding="async" />
						) : (
							<div class="nfp-ph" role="img" aria-label="Image placeholder">
								<span>Image · 1260 × 1056</span>
							</div>
						)}
					</div>
				)
			}
		</div>
	</div>
</section></>)}

<style>
	.nfp {
		--nfp-navy: var(--tw-navy, #0b4654);
		--nfp-accent: var(--tw-accent, #ff6d45);
		--nfp-surface: var(--tw-surface, #f0f8fa);
		--nfp-muted: var(--tw-muted, #616161);
		--nfp-display: var(--tw-display, Outfit, ui-sans-serif, system-ui, sans-serif);
		--nfp-body: var(--tw-body, "Noto Sans", ui-sans-serif, system-ui, sans-serif);
		--nfp-ease: cubic-bezier(0.44, 0, 0.56, 1);

		background: var(--nfp-band);
		color: var(--nfp-navy);
		font-family: var(--nfp-body);
	}

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

	.nfp-inner {
		max-width: 1440px;
		margin: 0 auto;
		padding: 64px 16px;
	}

	.nfp-head {
		display: flex;
		flex-direction: column;
		align-items: center;
		gap: 18px;
		text-align: center;
	}

	.nfp-eyebrow {
		margin: 0;
		padding: 4px 14px;
		border: 1px solid var(--nfp-accent);
		border-radius: 999px;
		color: var(--nfp-accent);
		font: 500 15px/24px var(--nfp-body);
		letter-spacing: -0.3px;
		text-transform: uppercase;
	}

	.nfp-heading {
		max-width: 620px;
		margin: 0;
		font: 700 28px/1.2 var(--nfp-display);
		letter-spacing: -0.56px;
		text-transform: uppercase;
	}

	.nfp-w {
		display: inline-block;
	}

	.nfp-accent {
		color: var(--nfp-accent);
	}

	.nfp-body {
		display: grid;
		gap: 24px;
		margin-top: 40px;
	}

	.nfp-panel {
		display: grid;
		align-content: start;
		gap: 16px;
		padding: 16px;
		border-radius: 10px;
		background: var(--nfp-surface);
	}

	.nfp-item {
		border-radius: 10px;
		background: #fff;
	}

	.nfp-q {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: 16px;
		padding: 20px;
		list-style: none;
		cursor: pointer;
		font: 600 18px/1.5 var(--nfp-body);
		letter-spacing: -0.36px;
	}

	.nfp-q::-webkit-details-marker {
		display: none;
	}

	.nfp-q:focus-visible {
		outline: 2px solid var(--nfp-accent);
		outline-offset: 2px;
		border-radius: 10px;
	}

	.nfp-ic {
		display: grid;
		flex: none;
		place-items: center;
		width: 34px;
		height: 34px;
		border-radius: 50%;
		background: var(--nfp-accent);
		color: #fff;
		transition: transform 0.35s var(--nfp-ease);
	}

	.nfp-ic svg {
		width: 20px;
		height: 20px;
	}

	.nfp-item[open] .nfp-ic {
		transform: rotate(45deg);
	}

	.nfp-a p {
		margin: 0;
		padding: 0 20px 24px;
		color: var(--nfp-muted);
		font: 400 16px/25.6px var(--nfp-body);
		letter-spacing: -0.32px;
	}

	/* Height animation where the browser can animate to auto. */
	@supports selector(::details-content) {
		.nfp-item {
			interpolate-size: allow-keywords;
		}

		.nfp-item::details-content {
			height: 0;
			overflow: clip;
			transition:
				height 0.4s var(--nfp-ease),
				content-visibility 0.4s allow-discrete;
		}

		.nfp-item[open]::details-content {
			height: auto;
		}
	}

	.nfp-media {
		min-height: 300px;
		overflow: hidden;
		border-radius: 15px;
	}

	.nfp-media img,
	.nfp-ph {
		display: block;
		width: 100%;
		height: 100%;
		object-fit: cover;
	}

	.nfp-ph {
		display: grid;
		place-items: center;
		min-height: 300px;
		background: repeating-linear-gradient(135deg, #e3eef1 0 12px, #edf5f7 12px 24px);
		font: 500 13px/1.4 var(--nfp-body);
	}

	[data-nfp-reveal="up"] {
		transition:
			opacity 0.8s var(--nfp-ease) var(--d, 0s),
			transform 0.8s var(--nfp-ease) var(--d, 0s);
	}

	.nfp[data-nfp-ready] [data-nfp-reveal="up"]:not([data-in]) {
		opacity: 0;
		transform: translateY(80px);
	}

	.nfp-w {
		transition:
			opacity 0.6s var(--nfp-ease) calc(var(--i) * 0.06s),
			transform 0.6s var(--nfp-ease) calc(var(--i) * 0.06s);
	}

	.nfp[data-nfp-ready] [data-nfp-reveal="words"]:not([data-in]) .nfp-w {
		opacity: 0;
		transform: translateY(10px);
	}

	@media (min-width: 810px) {
		.nfp-inner {
			padding: 90px 40px;
		}

		.nfp-heading {
			font-size: 38px;
			letter-spacing: -0.76px;
		}

		.nfp-panel {
			gap: 20px;
			padding: 20px;
		}

		.nfp-q {
			padding: 26px 25px;
			font-size: 20px;
			line-height: 32px;
			letter-spacing: -0.4px;
		}

		.nfp-a p {
			padding: 0 25px 26px;
		}

		.nfp-media {
			min-height: 420px;
		}
	}

	@media (min-width: 1200px) {
		.nfp-inner {
			padding: 100px 65px;
		}

		.nfp-heading {
			font-size: 50px;
			line-height: 60px;
			letter-spacing: -1px;
		}

		.nfp-body {
			grid-template-columns: repeat(2, minmax(0, 1fr));
			gap: 50px;
			margin-top: 60px;
		}

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

		.nfp-media {
			min-height: 528px;
		}
	}

	@media (prefers-reduced-motion: reduce) {
		[data-nfp-reveal="up"],
		.nfp-w,
		.nfp-ic,
		.nfp-item::details-content {
			transition: none;
		}
	}
</style>

<script>
	/* Appear-on-scroll only; the accordion is native <details>. */
	function initNumberedFaqPhoto(root: HTMLElement) {
		if (root.dataset.nfpReady === "") return;
		if (window.matchMedia("(prefers-reduced-motion: reduce)").matches || !("IntersectionObserver" in window)) return;
		const controller = new AbortController();
		root.dataset.nfpReady = "";
		const io = new IntersectionObserver(
			(entries) => {
				for (const entry of entries) {
					if (!entry.isIntersecting) continue;
					(entry.target as HTMLElement).dataset.in = "";
					io.unobserve(entry.target);
				}
			},
			{ threshold: 0.1, rootMargin: "0px 0px -5% 0px" },
		);
		root.querySelectorAll<HTMLElement>("[data-nfp-reveal]").forEach((t) => io.observe(t));
		controller.signal.addEventListener("abort", () => io.disconnect());
		document.addEventListener(
			"astro:before-swap",
			() => {
				controller.abort();
				delete root.dataset.nfpReady;
			},
			{ once: true, signal: controller.signal },
		);
	}

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

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

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

===== END src/components/NumberedFaqPhoto.astro =====

===== FILE (required): public/numbered-faq-photo/white-semi.webp =====
[binary asset — download https://astro.baysixmedia.com/numbered-faq-photo/white-semi.webp and save it to public/numbered-faq-photo/white-semi.webp]

===== FILE (required): src/components/LogoMarqueeStrip.astro =====
---
/**
 * LogoMarqueeStrip
 *
 * A trust strip: a centred bold line ("10,000+ trusted customers…") between
 * two hairlines, over a row of logo cards that scrolls left forever. Two
 * looks: `"plain"` (white cards on the band) and `"tray"` (the cards sit in
 * a pale rounded tray that clips them at its edges).
 *
 * The marquee is a CSS animation over two copies of the list; the second copy
 * is hidden from assistive technology. Hovering or focusing inside pauses it.
 * Under reduced motion it stops and the logos wrap onto as many rows as they
 * need, so none are hidden off-screen.
 *
 * Ships no logos: pass `logos` (drawn glyph + name, or `src` for a real
 * client logo). With none it renders nothing.
 *
 * Reads the --tw-* tokens from any ancestor, with fallbacks.
 */

export type LogoGlyph = "sun" | "arch" | "print" | "leaf" | "stack" | "wave";

export interface MarqueeLogo {
	name: string;
	/** An image replaces the drawn glyph and name. */
	src?: string;
	glyph?: LogoGlyph;
	/** Glyph colour. */
	color?: string;
	href?: string;
}

export interface LogoMarqueeStripProps {
	/** Bold lead-in in orange, e.g. "10,000+". `""` drops it. */
	highlight?: string;
	/** The rest of the line. `""` drops the whole line. */
	caption?: string;
	logos?: MarqueeLogo[];
	variant?: "plain" | "tray";
	/** Seconds for one full loop. */
	duration?: number;
	background?: string;
	fontStylesheet?: string;
	class?: string;
	id?: string;
}

interface Props extends LogoMarqueeStripProps {}

const {
	highlight = "",
	caption = "",
	logos: rawLogos = [],
	variant = "plain",
	duration = 32,
	background = "#ffffff",
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Outfit:wght@500;600;700;800;900&family=Noto+Sans:wght@400;500;600;700&family=Inter:wght@500;600;700&display=swap",
	class: className,
	id,
} = Astro.props;

const SAFE = /^(?:https?:|mailto:|tel:|[#/.?])/i;
const safeHref = (href: string) => (SAFE.test(href.trim()) ? href.trim() : "#");

const GLYPHS: Record<LogoGlyph, string> = {
	sun: "M16 10a6 6 0 1 0 0 12 6 6 0 0 0 0-12Z M16 2v5 M16 25v5 M2 16h5 M25 16h5 M6 6l3.5 3.5 M22.5 22.5 26 26 M6 26l3.5-3.5 M22.5 9.5 26 6",
	arch: "M5 28V16a11 11 0 0 1 22 0v12 M10 28V16a6 6 0 0 1 12 0v12 M15 28V16a1 1 0 0 1 2 0v12",
	print: "M16 4a12 12 0 0 1 12 12v10 M16 9a7 7 0 0 1 7 7v10 M16 14a2 2 0 0 1 2 2v10 M16 4A12 12 0 0 0 4 16v10 M16 9a7 7 0 0 0-7 7v10 M14 16v10",
	leaf: "M16 3l4 6-4 6-4-6Z M16 17l4 6-4 6-4-6Z M3 16l6-4 6 4-6 4Z M17 16l6-4 6 4-6 4Z",
	stack: "M4 10l12-6 12 6-12 6Z M4 16l12 6 12-6 M4 22l12 6 12-6",
	wave: "M3 12c4-6 9-6 13 0s9 6 13 0 M3 20c4-6 9-6 13 0s9 6 13 0",
};

const renderList = [0, 1];
/* A logo without a name has no accessible name: dropped. No logos, no strip (the line alone would be a claim). */
const logos = rawLogos.filter((logo) => logo?.name);
---

{logos.length > 0 && (
<>
{fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
<section
	class:list={["lms", `lms--${variant}`, className]}
	id={id}
	style={`--lms-band:${background};--lms-duration:${duration}s`}
	aria-label={caption ? `${highlight} ${caption}`.trim() : "Customers"}
>
	{
		caption && (
			<p class="lms-caption">
				{highlight && <span class="lms-hl">{highlight}</span>} {caption}
			</p>
		)
	}
	<div class="lms-viewport">
		<div class="lms-track">
			{
				renderList.map((copy) => (
					<ul class="lms-list" aria-hidden={copy === 1 ? "true" : undefined}>
						{logos.map((logo) => {
							const inner = logo.src ? (
								<img src={logo.src} alt={copy === 1 ? "" : logo.name} height="40" loading="lazy" decoding="async" />
							) : (
								<>
									<svg viewBox="0 0 32 32" aria-hidden="true" style={`color:${logo.color ?? "currentColor"}`}>
										<path d={GLYPHS[logo.glyph ?? "sun"]} fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
									</svg>
									<span>{logo.name}</span>
								</>
							);
							return (
								<li class="lms-card">
									{logo.href ? (
										<a href={safeHref(logo.href)} tabindex={copy === 1 ? -1 : undefined}>
											{inner}
										</a>
									) : (
										<span class="lms-logo">{inner}</span>
									)}
								</li>
							);
						})}
					</ul>
				))
			}
		</div>
	</div>
</section>
</>
)}

<style>
	.lms {
		--lms-navy: var(--tw-navy, #0b4654);
		--lms-accent: var(--tw-accent, #ff6d45);
		--lms-surface: var(--tw-surface, #f0f8fa);
		--lms-card-font: var(--tw-card, Inter, ui-sans-serif, system-ui, sans-serif);
		--lms-gap: 10px;

		padding: 40px 0 48px;
		overflow: hidden;
		background: var(--lms-band);
		color: var(--lms-navy);
	}

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

	.lms-caption {
		display: flex;
		align-items: center;
		justify-content: center;
		gap: 0.3em;
		max-width: 1310px;
		margin: 0 auto 22px;
		padding: 0 16px;
		color: #000;
		font: 700 16px/1.4 var(--lms-card-font);
		letter-spacing: -0.64px;
		text-align: center;
	}

	.lms-caption::before,
	.lms-caption::after {
		content: "";
		flex: 1 1 0;
		max-width: 440px;
		height: 1px;
		background: rgb(11 70 84 / 0.14);
	}

	.lms-caption::before {
		margin-right: 20px;
	}

	.lms-caption::after {
		margin-left: 20px;
	}

	.lms-hl {
		color: var(--lms-accent);
	}

	.lms-viewport {
		overflow: hidden;
	}

	.lms--tray .lms-viewport {
		max-width: 1320px;
		margin: 0 16px;
		padding: 10px;
		border-radius: 20px;
		background: var(--lms-surface);
	}

	.lms--plain .lms-viewport {
		-webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
		mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
	}

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

	.lms:hover .lms-track,
	.lms:focus-within .lms-track {
		animation-play-state: paused;
	}

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

	.lms-list {
		display: flex;
		gap: var(--lms-gap);
		margin: 0;
		padding: 0 var(--lms-gap) 0 0;
		list-style: none;
	}

	.lms-card {
		flex: none;
		width: 220px;
		height: 120px;
		border-radius: 20px;
		background: #fff;
	}

	.lms--plain .lms-card {
		box-shadow: inset 0 0 0 1px rgb(11 70 84 / 0.06);
	}

	.lms-card a,
	.lms-logo {
		display: flex;
		align-items: center;
		justify-content: center;
		gap: 10px;
		width: 100%;
		height: 100%;
		border-radius: inherit;
		color: inherit;
		font: 700 22px/1 var(--lms-card-font);
		letter-spacing: -0.6px;
		text-decoration: none;
		text-transform: uppercase;
	}

	.lms-card a:focus-visible {
		outline: 2px solid var(--lms-accent);
		outline-offset: -2px;
	}

	.lms-card svg {
		flex: none;
		width: 36px;
		height: 36px;
	}

	.lms-card img {
		max-width: 80%;
		max-height: 44px;
		object-fit: contain;
	}

	@media (min-width: 810px) {
		.lms {
			padding: 50px 0 60px;
		}

		.lms-caption {
			font-size: 18px;
			letter-spacing: -0.72px;
		}

		.lms--tray .lms-viewport {
			margin: 0 40px;
		}

		.lms-card {
			width: 252px;
			height: 140px;
		}
	}

	@media (min-width: 1200px) {
		.lms--tray .lms-viewport {
			margin: 0 auto;
			width: min(1320px, calc(100% - 120px));
		}

		.lms--plain .lms-card {
			width: 276px;
			height: 153px;
		}
	}

	/* Reduced motion: no scroll, one copy, wrapped so every logo shows. */
	@media (prefers-reduced-motion: reduce) {
		.lms-track {
			width: auto;
			animation: none;
		}

		.lms-list {
			flex-wrap: wrap;
			justify-content: center;
			padding: 0;
		}

		.lms-list[aria-hidden="true"] {
			display: none;
		}

		.lms--plain .lms-viewport {
			-webkit-mask-image: none;
			mask-image: none;
		}
	}
</style>

===== END src/components/LogoMarqueeStrip.astro =====

===== FILE (required): src/components/ChamferBlogCards.astro =====
---
/**
 * ChamferBlogCards
 *
 * A centred "latest articles" section: outlined eyebrow and a big uppercase
 * heading over two wide white cards. Each card is one link: a landscape photo
 * with a chamfered top-left corner, an orange date, a title, a hairline and
 * the author's avatar and name.
 *
 * Heading words rise in and the cards lift in, staggered, the first time they
 * scroll into view.
 *
 * Heading syntax: wrap the orange words in asterisks.
 *
 * Reads the --tw-* tokens from any ancestor, with fallbacks.
 */

export interface ChamferPost {
	title: string;
	href: string;
	/** Display date, e.g. "May 2, 2026". */
	date?: string;
	/** Machine date for <time>, e.g. "2026-05-02". */
	datetime?: string;
	author?: { name: string; avatar?: string };
	/** `src: ""` renders a labelled placeholder. */
	image?: { src?: string; alt?: string };
}

export interface ChamferBlogCardsProps {
	eyebrow?: string;
	heading?: string;
	headingLevel?: "h2" | "h3";
	posts?: ChamferPost[];
	background?: string;
	fontStylesheet?: string;
	class?: string;
	id?: string;
}

interface Props extends ChamferBlogCardsProps {}

const {
	eyebrow = "Latest Blog",
	heading = "",
	headingLevel = "h2",
	posts: postsProp = [],
	background = "#f0f8fa",
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Outfit:wght@500;600;700;800;900&family=Noto+Sans:wght@400;500;600;700&family=Inter:wght@500;600;700&display=swap",
	class: className,
	id,
} = Astro.props;

/* A post needs a title and a link; anything less is dropped, never padded.
   An author with no name is treated as no author. */
const posts = postsProp
	.filter((p) => p?.title && p.href)
	.map((p) => (p.author && !p.author.name ? { ...p, author: undefined } : p));

const words = heading
	.split("*")
	.flatMap((seg, i) => seg.split(/\s+/).filter(Boolean).map((w) => ({ w, accent: i % 2 === 1 })));
const Heading = headingLevel;
const Title = headingLevel === "h2" ? "h3" : "h4";
const SAFE = /^(?:https?:|mailto:|tel:|[#/.?])/i;
const safeHref = (href: string) => (SAFE.test(href.trim()) ? href.trim() : "#");
---

{posts.length > 0 && fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
{posts.length > 0 && (
<section class:list={["cbc", className]} id={id} style={`--cbc-band:${background}`} data-cbc-root>
	<div class="cbc-inner">
		{(eyebrow || words.length > 0) && (
		<div class="cbc-head">
			{eyebrow && <p class="cbc-eyebrow">{eyebrow}</p>}
			{words.length > 0 && (
			<Heading class="cbc-heading" data-cbc-reveal="words">
				{
					words.map((t, i) => (
						<>
							{i > 0 && " "}
							<span class:list={["cbc-w", { "cbc-accent": t.accent }]} style={`--i:${i}`}>
								{t.w}
							</span>
						</>
					))
				}
			</Heading>
			)}
		</div>
		)}

		<ul class="cbc-grid">
			{
				posts.map((p, i) => {
					const img = p.image ?? { src: "" };
					return (
						<li class="cbc-card" data-cbc-reveal="up" style={`--d:${i * 0.12}s`}>
							<div class="cbc-media">
								{img.src ? (
									<img src={img.src} alt={img.alt ?? ""} width="1210" height="846" loading="lazy" decoding="async" />
								) : (
									<div class="cbc-ph" role="img" aria-label={`Image placeholder: ${p.title}`}>
										<span>Image · 1210 × 846</span>
									</div>
								)}
							</div>
							<div class="cbc-copy">
								{p.date && (
									<time class="cbc-date" datetime={p.datetime}>
										{p.date}
									</time>
								)}
								<Title class="cbc-title">
									<a href={safeHref(p.href)}>{p.title}</a>
								</Title>
								{p.author && (
									<p class="cbc-author">
										{p.author.avatar && (
											<img src={p.author.avatar} alt="" width="36" height="36" loading="lazy" decoding="async" />
										)}
										<span>{p.author.name}</span>
									</p>
								)}
							</div>
						</li>
					);
				})
			}
		</ul>
	</div>
</section>
)}

<style>
	.cbc {
		--cbc-navy: var(--tw-navy, #0b4654);
		--cbc-accent: var(--tw-accent, #ff6d45);
		--cbc-display: var(--tw-display, Outfit, ui-sans-serif, system-ui, sans-serif);
		--cbc-body: var(--tw-body, "Noto Sans", ui-sans-serif, system-ui, sans-serif);
		--cbc-ease: cubic-bezier(0.44, 0, 0.56, 1);
		--cbc-cut: 44px;

		background: var(--cbc-band);
		color: var(--cbc-navy);
		font-family: var(--cbc-body);
	}

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

	.cbc-inner {
		max-width: 1440px;
		margin: 0 auto;
		padding: 64px 16px;
	}

	.cbc-head {
		display: flex;
		flex-direction: column;
		align-items: center;
		gap: 18px;
		text-align: center;
	}

	.cbc-eyebrow {
		margin: 0;
		padding: 4px 14px;
		border: 1px solid var(--cbc-accent);
		border-radius: 999px;
		color: var(--cbc-accent);
		font: 500 15px/24px var(--cbc-body);
		letter-spacing: -0.3px;
		text-transform: uppercase;
	}

	.cbc-heading {
		max-width: 700px;
		margin: 0;
		font: 700 28px/1.2 var(--cbc-display);
		letter-spacing: -0.56px;
		text-transform: uppercase;
	}

	.cbc-w {
		display: inline-block;
	}

	.cbc-accent {
		color: var(--cbc-accent);
	}

	.cbc-grid {
		display: grid;
		gap: 20px;
		margin: 40px 0 0;
		padding: 0;
		list-style: none;
	}

	.cbc-card {
		position: relative;
		padding: 16px;
		border-radius: 15px;
		background: #fff;
	}

	.cbc-card:has(a:focus-visible) {
		outline: 2px solid var(--cbc-accent);
		outline-offset: 3px;
	}

	.cbc-media {
		aspect-ratio: 605 / 423;
	}

	.cbc-media img,
	.cbc-ph {
		display: block;
		width: 100%;
		height: 100%;
		object-fit: cover;
		border-radius: 15px;
		clip-path: polygon(var(--cbc-cut) 0, 100% 0, 100% 100%, 0 100%, 0 var(--cbc-cut));
	}

	.cbc-ph {
		display: grid;
		place-items: center;
		background: repeating-linear-gradient(135deg, #e3eef1 0 12px, #edf5f7 12px 24px);
		font: 500 13px/1.4 var(--cbc-body);
	}

	.cbc-copy {
		padding: 22px 8px 4px;
	}

	.cbc-date {
		display: block;
		color: var(--cbc-accent);
		font: 600 14px/22.4px var(--cbc-body);
		letter-spacing: -0.28px;
		text-transform: uppercase;
	}

	.cbc-title {
		margin: 8px 0 0;
		padding-bottom: 24px;
		border-bottom: 1px solid rgb(11 70 84 / 0.12);
		font: 500 22px/1.4 var(--cbc-display);
		letter-spacing: -0.44px;
	}

	.cbc-title a {
		color: inherit;
		text-decoration: none;
	}

	/* The title link covers the whole card. */
	.cbc-title a::after {
		content: "";
		position: absolute;
		inset: 0;
		border-radius: 15px;
	}

	.cbc-title a:focus-visible {
		outline: none;
	}

	.cbc-author {
		display: flex;
		align-items: center;
		gap: 10px;
		margin: 24px 0 0;
		font: 400 16px/25.6px var(--cbc-body);
		letter-spacing: -0.32px;
	}

	.cbc-author img {
		width: 36px;
		height: 36px;
		border-radius: 50%;
		object-fit: cover;
	}

	[data-cbc-reveal="up"] {
		transition:
			opacity 0.8s var(--cbc-ease) var(--d, 0s),
			transform 0.8s var(--cbc-ease) var(--d, 0s);
	}

	.cbc[data-cbc-ready] [data-cbc-reveal="up"]:not([data-in]) {
		opacity: 0;
		transform: translateY(80px);
	}

	.cbc-w {
		transition:
			opacity 0.6s var(--cbc-ease) calc(var(--i) * 0.06s),
			transform 0.6s var(--cbc-ease) calc(var(--i) * 0.06s);
	}

	.cbc[data-cbc-ready] [data-cbc-reveal="words"]:not([data-in]) .cbc-w {
		opacity: 0;
		transform: translateY(10px);
	}

	@media (min-width: 810px) {
		.cbc-inner {
			padding: 90px 40px;
		}

		.cbc-heading {
			font-size: 38px;
			letter-spacing: -0.76px;
		}

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

		.cbc-card {
			padding: 20px;
		}

		.cbc-copy {
			padding: 26px 15px 0;
		}
	}

	@media (min-width: 1200px) {
		.cbc-inner {
			padding: 125px 65px 120px;
		}

		.cbc-heading {
			font-size: 50px;
			line-height: 60px;
			letter-spacing: -1px;
		}

		.cbc-grid {
			margin-top: 60px;
		}

		.cbc-title {
			font-size: 28px;
			line-height: 39.2px;
			letter-spacing: -0.56px;
		}
	}

	@media (prefers-reduced-motion: reduce) {
		[data-cbc-reveal="up"],
		.cbc-w {
			transition: none;
		}
	}
</style>

<script>
	/* Appear-on-scroll; one observer per instance, torn down on astro:before-swap. */
	function initChamferBlogCards(root: HTMLElement) {
		if (root.dataset.cbcReady === "") return;
		if (window.matchMedia("(prefers-reduced-motion: reduce)").matches || !("IntersectionObserver" in window)) return;
		const controller = new AbortController();
		root.dataset.cbcReady = "";
		const io = new IntersectionObserver(
			(entries) => {
				for (const entry of entries) {
					if (!entry.isIntersecting) continue;
					(entry.target as HTMLElement).dataset.in = "";
					io.unobserve(entry.target);
				}
			},
			{ threshold: 0.1, rootMargin: "0px 0px -5% 0px" },
		);
		root.querySelectorAll<HTMLElement>("[data-cbc-reveal]").forEach((t) => io.observe(t));
		controller.signal.addEventListener("abort", () => io.disconnect());
		document.addEventListener(
			"astro:before-swap",
			() => {
				controller.abort();
				delete root.dataset.cbcReady;
			},
			{ once: true, signal: controller.signal },
		);
	}

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

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

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

===== END src/components/ChamferBlogCards.astro =====

===== FILE (required): public/chamfer-blog-cards/mountain-truck.webp =====
[binary asset — download https://astro.baysixmedia.com/chamfer-blog-cards/mountain-truck.webp and save it to public/chamfer-blog-cards/mountain-truck.webp]

===== FILE (required): public/chamfer-blog-cards/winter-train.webp =====
[binary asset — download https://astro.baysixmedia.com/chamfer-blog-cards/winter-train.webp and save it to public/chamfer-blog-cards/winter-train.webp]

===== FILE (required): public/chamfer-blog-cards/author-1.webp =====
[binary asset — download https://astro.baysixmedia.com/chamfer-blog-cards/author-1.webp and save it to public/chamfer-blog-cards/author-1.webp]

===== FILE (required): public/chamfer-blog-cards/author-2.webp =====
[binary asset — download https://astro.baysixmedia.com/chamfer-blog-cards/author-2.webp and save it to public/chamfer-blog-cards/author-2.webp]

===== FILE (required): src/components/ShipCtaFooter.astro =====
---
/**
 * ShipCtaFooter
 *
 * A tall dark footer over a photo washed navy-to-black. On top, a big
 * uppercase call to action with orange words and an orange pill; under a
 * hairline, the brand, a tagline and an email sign-up on the left, and
 * Services, Company and Contact columns split by hairlines on the right; at
 * the foot, copyright, two legal links and round social buttons.
 *
 * The sign-up is a real <form> posting to `newsletter.action`; the button
 * dims until the email field is valid, but it is never disabled.
 *
 * Heading syntax: wrap the orange words in asterisks.
 *
 * Nothing about the business is assumed: no name, heading, pill, link,
 * contact line, sign-up form, copyright or photo is shown unless it is
 * passed, a block with nothing in it is not drawn, and with no content at
 * all nothing renders.
 *
 * Reads the --tw-* tokens from any ancestor, with fallbacks.
 */

export type ShipFooterSocial = "facebook" | "x" | "instagram" | "linkedin" | "youtube";

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

export interface ShipCtaFooterProps {
	heading?: string;
	/** Heading level for the call to action. */
	headingLevel?: "h2" | "p";
	cta?: ShipFooterLink | null;
	brandName?: string;
	brandHref?: string;
	/** Replaces the drawn mark and wordmark. Use a light version. */
	logo?: { src: string; alt: string; width?: number; height?: number };
	tagline?: string;
	/** The sign-up form. Off by default; pass your handler as `action`. */
	newsletter?: { action: string; label?: string; placeholder?: string; button?: string } | null;
	columns?: { heading: string; links: ShipFooterLink[] }[];
	contactHeading?: string;
	contact?: ShipFooterLink[];
	copyright?: string;
	legalLinks?: ShipFooterLink[];
	social?: { network: ShipFooterSocial; href: string; label?: string }[];
	/** Background photo. None by default: the navy gradient alone. */
	background?: { src?: string };
	navLabel?: string;
	fontStylesheet?: string;
	class?: string;
	id?: string;
}

interface Props extends ShipCtaFooterProps {}

const {
	heading = "",
	headingLevel = "h2",
	cta = null,
	brandName = "",
	brandHref = "/",
	logo,
	tagline = "",
	newsletter = null,
	columns = [],
	contactHeading = "Contact",
	contact = [],
	copyright,
	legalLinks = [],
	social = [],
	background: bgProp,
	navLabel = "Footer",
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Outfit:wght@500;600;700;800;900&family=Noto+Sans:wght@400;500;600;700&family=Inter:wght@500;600;700&display=swap",
	class: className,
	id,
} = Astro.props;

const background = { src: "", ...(bgProp ?? {}) };
const year = new Date().getFullYear();
/* No name, no copyright line: never "© 2026 . All rights reserved." */
const copy = copyright ?? (brandName ? `© ${year} ${brandName}. All rights reserved.` : "");

const words = heading
	.split("*")
	.flatMap((seg, i) => seg.split(/\s+/).filter(Boolean).map((w) => ({ w, accent: i % 2 === 1 })));
const hasCta = words.length > 0 || Boolean(cta);
const hasMark = Boolean(logo || brandName);
const hasCols = columns.length > 0 || contact.length > 0;
const hasMain = hasMark || Boolean(tagline) || Boolean(newsletter) || hasCols;
const hasBottom = Boolean(copy) || legalLinks.length > 0 || social.length > 0;
const hasContent = hasCta || hasMain || hasBottom;
const Heading = headingLevel;
const SAFE = /^(?:https?:|mailto:|tel:|[#/.?])/i;
const safeHref = (href: string) => (SAFE.test(href.trim()) ? href.trim() : "#");
const external = (href: string) => /^https?:/i.test(href);
const inputId = `${id ?? "scf"}-email-${Math.random().toString(36).slice(2, 7)}`;

const SOCIAL: Record<ShipFooterSocial, { label: string; path: string }> = {
	facebook: { label: "Facebook", path: "M14 8h3V4h-3a4 4 0 0 0-4 4v2H8v4h2v6h4v-6h3l1-4h-4V8Z" },
	x: { label: "X", path: "M5 4h4l10 16h-4Z M19 4l-6 7 M5 20l6-7" },
	instagram: { label: "Instagram", path: "M8 4h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H8a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4Z M12 15.5a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7Z M17 7h.01" },
	linkedin: { label: "LinkedIn", path: "M5 9v10 M5 5v.01 M10 19v-6a3 3 0 0 1 6 0v6 M10 9v10" },
	youtube: { label: "YouTube", path: "M3 12s0-4 1-5 8-1 8-1 7 0 8 1 1 5 1 5 0 4-1 5-8 1-8 1-7 0-8-1-1-5-1-5Z M10 9l5 3-5 3Z" },
};
---

{hasContent && fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
{hasContent && (
<footer class:list={["scf", className]} id={id} data-scf-root>
	{background.src && <img class="scf-bg" src={background.src} alt="" width="1600" height="960" loading="lazy" decoding="async" />}
	<div class="scf-wash" aria-hidden="true"></div>

	<div class="scf-inner">
		{hasCta && (
		<div class="scf-cta">
			{words.length > 0 && (
			<Heading class="scf-heading" data-scf-reveal="words">
				{
					words.map((t, i) => (
						<>
							{i > 0 && " "}
							<span class:list={["scf-w", { "scf-accent": t.accent }]} style={`--i:${i}`}>
								{t.w}
							</span>
						</>
					))
				}
			</Heading>
			)}
			{
				cta && (
					<a class="scf-btn" href={safeHref(cta.href)}>
						<span>{cta.label}</span>
						<span class="scf-btn-ic" aria-hidden="true">
							<svg viewBox="0 0 24 24"><path d="M8 5l10 7-10 7 3-7Z" fill="currentColor" /></svg>
						</span>
					</a>
				)
			}
		</div>
		)}

		{hasMain && (
		<div class="scf-main" data-scf-reveal="up">
			<div class="scf-brand">
				{hasMark && (
				<a class="scf-logo" href={safeHref(brandHref)} aria-label={logo ? undefined : `${brandName} home`}>
					{
						logo ? (
							<img src={logo.src} alt={logo.alt} width={logo.width ?? 124} height={logo.height ?? 32} />
						) : (
							<>
								<svg viewBox="0 0 40 40" aria-hidden="true">
									<path d="M6 21a14 14 0 0 1 28 0h-6a8 8 0 0 0-16 0Z" fill="currentColor" />
									<path d="M20 17 11 30h6v7h6v-7h6Z" fill="currentColor" />
								</svg>
								<span>{brandName}</span>
							</>
						)
					}
				</a>
				)}
				{tagline && <p class="scf-tagline">{tagline}</p>}
				{
					newsletter && (
						<form class="scf-form" action={safeHref(newsletter.action)} method="post">
							<label class="scf-sr" for={inputId}>
								{newsletter.label ?? "Email address"}
							</label>
							<input id={inputId} type="email" name="email" required autocomplete="email" placeholder={newsletter.placeholder ?? "you@company.com"} />
							<button type="submit">{newsletter.button ?? "Submit"}</button>
						</form>
					)
				}
			</div>

			{hasCols && (
			<nav class="scf-cols" aria-label={navLabel}>
				{
					columns.map((col) => (
						<div class="scf-col">
							<h2 class="scf-col-title">{col.heading}</h2>
							<ul>
								{col.links.map((l) => (
									<li>
										<a href={safeHref(l.href)}>{l.label}</a>
									</li>
								))}
							</ul>
						</div>
					))
				}
				{
					contact.length > 0 && (
						<div class="scf-col scf-col--contact">
							<h2 class="scf-col-title">{contactHeading}</h2>
							<ul>
								{contact.map((l) => (
									<li>
										<a href={safeHref(l.href)} {...(external(l.href) ? { rel: "noopener" } : {})}>
											<svg viewBox="0 0 16 16" aria-hidden="true">
												<path d="M2 2l12 6-12 6 3-6Z" fill="currentColor" />
											</svg>
											{l.label}
										</a>
									</li>
								))}
							</ul>
						</div>
					)
				}
			</nav>
			)}
		</div>
		)}

		{hasBottom && (
		<div class="scf-bottom">
			{copy && <p class="scf-copy">{copy}</p>}
			{
				legalLinks.length > 0 && (
					<ul class="scf-legal">
						{legalLinks.map((l) => (
							<li>
								<a href={safeHref(l.href)}>{l.label}</a>
							</li>
						))}
					</ul>
				)
			}
			{
				social.length > 0 && (
					<ul class="scf-social">
						{social.map((s) => (
							<li>
								<a href={safeHref(s.href)} rel="noopener" aria-label={s.label ?? SOCIAL[s.network].label}>
									<svg viewBox="0 0 24 24" aria-hidden="true">
										<path d={SOCIAL[s.network].path} fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" />
									</svg>
								</a>
							</li>
						))}
					</ul>
				)
			}
		</div>
		)}
	</div>
</footer>
)}

<style>
	.scf {
		--scf-navy: var(--tw-navy, #0b4654);
		--scf-accent: var(--tw-accent, #ff6d45);
		--scf-display: var(--tw-display, Outfit, ui-sans-serif, system-ui, sans-serif);
		--scf-body: var(--tw-body, "Noto Sans", ui-sans-serif, system-ui, sans-serif);
		--scf-ease: cubic-bezier(0.44, 0, 0.56, 1);
		--scf-line: rgb(255 255 255 / 0.1);

		position: relative;
		isolation: isolate;
		overflow: hidden;
		background: #07252c;
		color: #fff;
		font-family: var(--scf-body);
	}

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

	.scf-bg,
	.scf-wash {
		position: absolute;
		inset: 0;
		z-index: -1;
		width: 100%;
		height: 100%;
	}

	.scf-bg {
		object-fit: cover;
	}

	.scf-wash {
		background: linear-gradient(180deg, rgb(11 70 84 / 0.88) 0%, rgb(8 45 54 / 0.9) 55%, rgb(2 10 12 / 0.97) 100%);
	}

	.scf-inner {
		max-width: 1440px;
		margin: 0 auto;
		padding: 64px 16px 32px;
	}

	.scf-cta {
		display: flex;
		flex-direction: column;
		align-items: flex-start;
		gap: 28px;
		padding-bottom: 48px;
		border-bottom: 1px solid var(--scf-line);
	}

	.scf-heading {
		max-width: 680px;
		margin: 0;
		font: 900 30px/1.2 var(--scf-display);
		letter-spacing: -0.6px;
		text-transform: uppercase;
	}

	.scf-w {
		display: inline-block;
	}

	.scf-accent {
		color: var(--scf-accent);
	}

	.scf-btn {
		display: inline-flex;
		flex: none;
		align-items: center;
		gap: 12px;
		height: 50px;
		padding: 0 8px 0 25px;
		border-radius: 999px;
		background: var(--scf-accent);
		color: #fff;
		font: 400 16px/25.6px var(--scf-body);
		letter-spacing: -0.32px;
		text-decoration: none;
		transition: background-color 0.3s var(--scf-ease);
	}

	.scf-btn:hover {
		background: #052931;
	}

	.scf-btn-ic {
		display: grid;
		place-items: center;
		width: 34px;
		height: 34px;
		border-radius: 50%;
		background: #fff;
		color: var(--scf-accent);
		transition: transform 0.3s var(--scf-ease);
	}

	.scf-btn-ic svg {
		width: 18px;
		height: 18px;
	}

	.scf-btn:hover .scf-btn-ic {
		transform: rotate(-45deg);
	}

	.scf-main {
		display: grid;
		grid-template-columns: minmax(0, 1fr);
		gap: 48px;
		padding: 56px 0;
	}

	.scf-logo {
		display: inline-flex;
		align-items: center;
		gap: 8px;
		color: #fff;
		font: 500 26px/1 var(--scf-display);
		letter-spacing: -0.5px;
		text-decoration: none;
	}

	.scf-logo svg {
		width: 30px;
		height: 30px;
		color: var(--scf-accent);
	}

	.scf-tagline {
		max-width: 300px;
		margin: 36px 0 0;
		font: 400 16px/25.6px var(--scf-body);
		letter-spacing: -0.32px;
	}

	.scf-form {
		display: flex;
		gap: 10px;
		max-width: 400px;
		margin-top: 40px;
	}

	.scf-form input {
		flex: 1 1 auto;
		min-width: 0;
		height: 54px;
		padding: 0 14px;
		border: 0;
		border-radius: 10px;
		background: #fff;
		color: var(--scf-navy);
		font: 400 15px/1 var(--scf-body);
	}

	.scf-form button {
		flex: none;
		width: 115px;
		height: 54px;
		border: 0;
		border-radius: 10px;
		background: var(--scf-accent);
		color: #fff;
		font: 500 16px/1 var(--scf-body);
		cursor: pointer;
		transition:
			opacity 0.3s var(--scf-ease),
			background-color 0.3s var(--scf-ease);
	}

	.scf-form:has(input:invalid) button {
		opacity: 0.5;
	}

	.scf-form button:hover {
		background: #052931;
		opacity: 1;
	}

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

	.scf-col--contact {
		grid-column: 1 / -1;
	}

	.scf-col-title {
		margin: 0 0 14px;
		color: rgb(255 255 255 / 0.7);
		font: 600 20px/32px var(--scf-body);
		letter-spacing: -0.4px;
		text-transform: uppercase;
	}

	.scf-col ul,
	.scf-legal,
	.scf-social {
		margin: 0;
		padding: 0;
		list-style: none;
	}

	.scf-col li + li {
		margin-top: 15px;
	}

	.scf-col a,
	.scf-legal a {
		display: inline-flex;
		align-items: center;
		gap: 10px;
		color: #fff;
		font: 500 16px/25.6px var(--scf-body);
		letter-spacing: -0.32px;
		text-decoration: none;
		transition: color 0.3s var(--scf-ease);
	}

	.scf-col a svg {
		flex: none;
		width: 16px;
		height: 16px;
		color: var(--scf-accent);
	}

	.scf-col a:hover,
	.scf-legal a:hover {
		color: var(--scf-accent);
	}

	.scf-bottom {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		justify-content: space-between;
		gap: 16px 24px;
		padding-top: 28px;
		border-top: 1px solid var(--scf-line);
	}

	.scf-copy {
		margin: 0;
		color: rgb(255 255 255 / 0.7);
		font: 400 15px/24px var(--scf-body);
	}

	.scf-legal {
		display: flex;
		flex-wrap: wrap;
		gap: 12px 40px;
	}

	.scf-legal a {
		color: rgb(255 255 255 / 0.7);
	}

	.scf-social {
		display: flex;
		gap: 10px;
	}

	.scf-social a {
		display: grid;
		place-items: center;
		width: 44px;
		height: 44px;
		border-radius: 50%;
		background: var(--scf-navy);
		color: #fff;
		transition: background-color 0.3s var(--scf-ease);
	}

	.scf-social a:hover {
		background: var(--scf-accent);
	}

	.scf-social svg {
		width: 18px;
		height: 18px;
	}

	.scf a:focus-visible,
	.scf button:focus-visible,
	.scf input:focus-visible {
		outline: 2px solid var(--scf-accent);
		outline-offset: 3px;
	}

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

	[data-scf-reveal="up"] {
		transition:
			opacity 0.8s var(--scf-ease),
			transform 0.8s var(--scf-ease);
	}

	.scf[data-scf-ready] [data-scf-reveal="up"]:not([data-in]) {
		opacity: 0;
		transform: translateY(80px);
	}

	.scf-w {
		transition:
			opacity 0.6s var(--scf-ease) calc(var(--i) * 0.06s),
			transform 0.6s var(--scf-ease) calc(var(--i) * 0.06s);
	}

	.scf[data-scf-ready] [data-scf-reveal="words"]:not([data-in]) .scf-w {
		opacity: 0;
		transform: translateY(10px);
	}

	@media (min-width: 810px) {
		.scf-inner {
			padding: 90px 40px 36px;
		}

		.scf-cta {
			flex-direction: row;
			align-items: center;
			justify-content: space-between;
			padding-bottom: 56px;
		}

		.scf-heading {
			font-size: 40px;
			letter-spacing: -0.8px;
		}

		.scf-cols {
			grid-template-columns: repeat(3, auto);
			justify-content: start;
			gap: 0;
		}

		.scf-col {
			padding: 0 40px;
			border-left: 1px solid var(--scf-line);
		}

		.scf-col:first-child {
			padding-left: 0;
			border-left: 0;
		}

		.scf-col--contact {
			grid-column: auto;
		}
	}

	@media (min-width: 1200px) {
		.scf-inner {
			padding: 100px 65px 36px;
		}

		.scf-heading {
			font-size: 50px;
			line-height: 60px;
			letter-spacing: -1px;
		}

		.scf-main {
			grid-template-columns: 485px minmax(0, 1fr);
			gap: 0;
			padding: 100px 0 90px;
		}

		.scf-cols {
			justify-content: space-between;
			padding-top: 20px;
		}

		.scf-col {
			padding: 0 50px;
		}

		.scf-col:first-child {
			padding-left: 50px;
			border-left: 1px solid var(--scf-line);
		}

		.scf-col--contact {
			padding-right: 0;
		}

		.scf-social a {
			width: 35px;
			height: 35px;
		}
	}

	@media (prefers-reduced-motion: reduce) {
		[data-scf-reveal="up"],
		.scf-w,
		.scf-btn,
		.scf-btn-ic,
		.scf-col a,
		.scf-legal a,
		.scf-social a,
		.scf-form button {
			transition: none;
		}

		.scf-btn:hover .scf-btn-ic {
			transform: none;
		}
	}
</style>

<script>
	/* Appear-on-scroll; one observer per instance, torn down on astro:before-swap. */
	function initShipCtaFooter(root: HTMLElement) {
		if (root.dataset.scfReady === "") return;
		if (window.matchMedia("(prefers-reduced-motion: reduce)").matches || !("IntersectionObserver" in window)) return;
		const controller = new AbortController();
		root.dataset.scfReady = "";
		const io = new IntersectionObserver(
			(entries) => {
				for (const entry of entries) {
					if (!entry.isIntersecting) continue;
					(entry.target as HTMLElement).dataset.in = "";
					io.unobserve(entry.target);
				}
			},
			{ threshold: 0.1, rootMargin: "0px 0px -5% 0px" },
		);
		root.querySelectorAll<HTMLElement>("[data-scf-reveal]").forEach((t) => io.observe(t));
		controller.signal.addEventListener("abort", () => io.disconnect());
		document.addEventListener(
			"astro:before-swap",
			() => {
				controller.abort();
				delete root.dataset.scfReady;
			},
			{ once: true, signal: controller.signal },
		);
	}

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

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

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

===== END src/components/ShipCtaFooter.astro =====

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

--- SECTIONS ---
- Scroll pill header (enrolled, not yet production-ready): https://astro.baysixmedia.com/components/scroll-pill-header/
- Tag headline hero (enrolled, not yet production-ready): https://astro.baysixmedia.com/components/tag-headline-hero/
- Flanked stat overview (enrolled, not yet production-ready): https://astro.baysixmedia.com/components/flanked-stat-overview/
- Photo feature trio (enrolled, not yet production-ready): https://astro.baysixmedia.com/components/photo-feature-trio/
- Hover service list (enrolled, not yet production-ready): https://astro.baysixmedia.com/components/hover-service-list/
- Sticky project stack (enrolled, not yet production-ready): https://astro.baysixmedia.com/components/sticky-project-stack/
- Freight pricing plans (enrolled, not yet production-ready): https://astro.baysixmedia.com/components/freight-pricing-plans/
- Chamfer step cards (enrolled, not yet production-ready): https://astro.baysixmedia.com/components/chamfer-step-cards/
- Portrait rating testimonials (enrolled, not yet production-ready): https://astro.baysixmedia.com/components/portrait-rating-testimonials/
- Numbered FAQ photo (enrolled, not yet production-ready): https://astro.baysixmedia.com/components/numbered-faq-photo/
- Logo marquee strip (enrolled, not yet production-ready): https://astro.baysixmedia.com/components/logo-marquee-strip/
- Chamfer blog cards (enrolled, not yet production-ready): https://astro.baysixmedia.com/components/chamfer-blog-cards/
- Ship CTA footer (enrolled, not yet production-ready): https://astro.baysixmedia.com/components/ship-cta-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

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

  1. HeaderScroll pill headerin verification
  2. SectionTag headline heroin verification
  3. SectionFlanked stat overviewin verification
  4. SectionPhoto feature trioin verification
  5. SectionHover service listin verification
  6. SectionSticky project stackin verification
  7. SectionFreight pricing plansin verification
  8. SectionChamfer step cardsin verification
  9. SectionPortrait rating testimonialsin verification
  10. SectionNumbered FAQ photoin verification
  11. SectionLogo marquee stripin verification
  12. SectionChamfer blog cardsin verification
  13. FooterShip CTA footerin verification

40 required files · page file src/pages/tideway-homepage.astro · no npm packages