Component library
Back to catalog

Contact bar overlay header

A two-tier header laid over a page banner: a 76px contact bar (phone, email and address with orange icons, "Follow Us:" and four social circles), then a rounded dark translucent nav bar inset from the edges with white links, four dropdowns and an orange "Free Quote!" pill that greys on hover. Menu button and navy card below 1200px; contact bar hidden on a phone.

navheaderoverlaydropdowntop-barcontactsocialglasslogistics

Open preview in a new tab

Use this component

Three ways to get Contact bar overlay header into one of your own projects. All pre-filled with this component's name and library page.

Or, for the design alone

Placement prompt

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

Read it first
Add Contact bar overlay header from my Astro Component Library to this Astro project.

Library page, for reference only:
https://astro.baysixmedia.com/components/contact-bar-overlay-header/

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

Position:
[ENTER WHERE IT SHOULD APPEAR]

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

Adapt its demo content, links, colors, typography, and images to this project through documented props where possible. Preserve its responsive behavior, accessibility, interaction logic, reduced-motion behavior, and support for multiple instances. Avoid changing unrelated project code.

Test it at desktop, tablet, and mobile widths. Run the project's available check and build commands. Report the files changed and any differences from the library version. Do not commit or deploy.

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

--- NOT PROVEN ---
- ClientRouter: event-tested only. Handlers respond to dispatched lifecycle events; this has NOT been certified against a real <ClientRouter /> transition.

--- WHAT THIS DOES NOT CERTIFY ---
Verification was performed on the unmodified component, in an isolated fixture, in the
library. It says nothing about this project's CSS, routes, layout or data, and nothing
about the component once you adapt it. A verified component is a good starting point,
not a guarantee about the installation you are about to build.

--- TEST IN THIS PROJECT AFTER INSTALLING OR CUSTOMISING ---
- Horizontal overflow at 1440, 1280, 1024, 834, 390 and 320px.
- Keyboard operation end to end, including visible focus.
- The no-JavaScript render.
- prefers-reduced-motion: reduce.
- Two instances on one page, if this project renders more than one.
- If this project uses <ClientRouter />: teardown and reinitialisation across a real navigation.

Component reference

A fragment for a request you write yourself, e.g. “I want to add [paste] to Section 3 of my homepage.” It names the component, links its page, and fixes what must not change — the design build and its effects — while leaving branding and content open. It does not carry the code.

Read it first
“Contact bar overlay header” from my Astro Component Library:
https://astro.baysixmedia.com/components/contact-bar-overlay-header/

Use the existing library component as the implementation source. Preserve its design structure, layout, proportions, spacing system, responsive behavior, visual effects, animations, transitions, hover states, interactions, and accessibility.

The copy, colors, typography, images, and other content may be adapted to match the destination website. Do not redesign, simplify, or reinterpret the component’s underlying design build or effects.

src/components/library/ContactBarOverlayHeader.astro

Installing this one is more than pasting a section

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

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

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

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

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

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

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

interface Props extends ContactBarOverlayHeaderProps {}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	.cbo-nav {
		display: none;
	}

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

	.cbo-item {
		position: relative;
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		let menuOpen = false;

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

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

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

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

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

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

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

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

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

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

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

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

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