Component library
Back to catalog

Lime contact footer

A dark green-black footer: logo tile and two-line wordmark, tagline and icon links, Explore and Company columns, a contact column with a lime pill, and a hairline-boxed legal bar. Static, no script.

footernavlinkscontactstatic

Open preview in a new tab

Use this component

Three ways to get Lime contact footer into one of your own projects. The first two carry the component's files with them, so nothing has to fetch this page.

Start here

Placement prompt

The whole thing: what to do, where it goes, what must not change, and every one of the component's 1 file appended underneath with the path each belongs at. Paste it into Claude Code inside the destination project — it fetches nothing and needs nothing else.

Read it first — the files are added underneath when you copy
Add Lime contact footer from my Astro Component Library to this Astro project.

Library page, for reference only:
https://astro.baysixmedia.com/components/lime-contact-footer/

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

Position:
[ENTER WHERE IT SHOULD APPEAR]

Every file you need is appended below, each fenced with the path it belongs at. Write them there, relative to the project root. Do not import anything from the deployed library at runtime, and do not fetch the library page — the files below are the source of truth.

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

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

Install kit

The 1 file this component needs and nothing else, each fenced with the path it belongs at, followed by what the library's own verification covered and what it did not. No instructions attached — for placing it yourself, or handing it to something that has its own.

Read it first
Install kit: "Lime contact footer"
Library page: https://astro.baysixmedia.com/components/lime-contact-footer/
Files: 1 required, 1 supporting

Write each block below to the path named in its header, relative to the project root. A block marked (required) is one the component does not work without; (supporting) is documentation. Nothing here needs downloading except a block that says so in as many words.

===== FILE (required): src/components/LimeContactFooter.astro =====
---
/**
 * LimeContactFooter
 *
 * A dark green-black footer: logo tile, two-line wordmark, tagline and icon
 * links on the left; Explore and Company link columns and a Contact column
 * (phone, email, address, a lime pill) on the right; a hairline-boxed bottom
 * bar with legal links and the copyright. Static markup, no script.
 *
 * Nothing about the business is assumed: no name, tagline, link, contact
 * detail, pill or legal link is shown unless it is passed, a block with
 * nothing in it is not drawn, and with no content at all nothing renders.
 *
 * Reads --nv-* tokens from any ancestor, with fallbacks.
 */

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

export interface LimeFooterColumn {
	heading: string;
	links: LimeFooterLink[];
}

export type LimeFooterGlyph = "globe" | "play" | "chat" | "mail" | "phone" | "pin" | "rss" | "link";

export interface LimeFooterSocial {
	/** Accessible name, e.g. "Halcyon on video". */
	label: string;
	href: string;
	glyph: LimeFooterGlyph;
	/** Icon colour. Each defaults to its own hue, as in the reference. */
	color?: string;
}

export interface LimeFooterContact {
	heading?: string;
	phone?: string;
	email?: string;
	/** Lines of the postal address. */
	address?: string[];
}

export interface LimeContactFooterProps {
	/** First wordmark line. No name and no logo drops the logo link. */
	brandName?: string;
	/** Second, lime line of the wordmark. `""` drops it. */
	brandSub?: string;
	brandHref?: string;
	/** Replaces the drawn logo tile and wordmark with an image. */
	logo?: { src: string; alt: string; width?: number; height?: number };
	tagline?: string;
	social?: LimeFooterSocial[];
	columns?: LimeFooterColumn[];
	/** `null` drops the whole contact column. */
	contact?: LimeFooterContact | null;
	/** The lime pill under the contact details. `null` drops it. */
	cta?: { label: string; href: string } | null;
	legalLinks?: LimeFooterLink[];
	/** Name in the copyright line. Defaults to the brand; with neither, no line. */
	copyrightName?: string;
	/** Defaults to the year the site was built. */
	year?: number;
	navLabel?: string;
	fontStylesheet?: string;
	id?: string;
}

interface Props extends LimeContactFooterProps {}

const {
	brandName = "",
	brandSub = "",
	brandHref = "/",
	logo,
	tagline = "",
	social = [],
	columns = [],
	contact = null,
	cta = null,
	legalLinks = [],
	copyrightName,
	year = new Date().getFullYear(),
	navLabel = "Footer",
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Inter:wght@400;500;600;700;800&display=swap",
	id,
} = Astro.props;

/* A dialable href from a displayed number: keep a leading + and the digits. */
const telHref = (number: string) => {
	const trimmed = number.trim();
	const digits = trimmed.replace(/\D/g, "");
	return digits ? `tel:${trimmed.startsWith("+") ? "+" : ""}${digits}` : "";
};

/* No name, no copyright line: never "Copyright 2026 ." */
const owner = copyrightName ?? (brandName ? (brandSub ? `${brandName} ${brandSub}` : brandName) : "");
/* A call link only with somewhere to dial: words in `phone` are not a number. */
const callHref = contact?.phone ? telHref(contact.phone) : "";
const hasDetails = Boolean(callHref || contact?.email || (contact?.address && contact.address.length > 0));
const showContact = Boolean(contact && (hasDetails || cta));
const hasMark = Boolean(logo || brandName);
const hasBrand = Boolean(hasMark || tagline || social.length > 0);
const hasCols = columns.length > 0 || showContact || Boolean(cta);
const hasBottom = legalLinks.length > 0 || Boolean(owner);
const hasContent = hasBrand || hasCols || hasBottom;

/* Generic 24px line glyphs, no third-party logos. */
const GLYPHS: Record<LimeFooterGlyph, string> = {
	globe: "M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18Zm-9 9h18M12 3c2.5 2.6 3.8 5.6 3.8 9s-1.3 6.4-3.8 9c-2.5-2.6-3.8-5.6-3.8-9S9.5 5.6 12 3Z",
	play: "M4.5 6.5A2.5 2.5 0 0 1 7 4h10a2.5 2.5 0 0 1 2.5 2.5v11A2.5 2.5 0 0 1 17 20H7a2.5 2.5 0 0 1-2.5-2.5v-11ZM10 9v6l5-3-5-3Z",
	chat: "M4 5.5A1.5 1.5 0 0 1 5.5 4h13A1.5 1.5 0 0 1 20 5.5v9a1.5 1.5 0 0 1-1.5 1.5H10l-4.5 4v-4h0A1.5 1.5 0 0 1 4 14.5v-9ZM8 9h8M8 12h5",
	mail: "M3.5 6.5A1.5 1.5 0 0 1 5 5h14a1.5 1.5 0 0 1 1.5 1.5v11A1.5 1.5 0 0 1 19 19H5a1.5 1.5 0 0 1-1.5-1.5v-11Zm.5.5 8 6 8-6",
	phone: "M6.5 3.5h3l1.5 4-2 1.5a11 11 0 0 0 6 6l1.5-2 4 1.5v3a2 2 0 0 1-2 2A16.5 16.5 0 0 1 4.5 5.5a2 2 0 0 1 2-2Z",
	pin: "M12 21s-6.5-5.7-6.5-11a6.5 6.5 0 0 1 13 0c0 5.3-6.5 11-6.5 11Zm0-8.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z",
	rss: "M5 5a14 14 0 0 1 14 14M5 11a8 8 0 0 1 8 8M6 19a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z",
	link: "M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1",
};
---

{hasContent && fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
{hasContent && (
<footer class="lcf" id={id} data-lcf-root>
	{(hasBrand || hasCols) && (
	<div class="lcf-top">
		{hasBrand && (
		<div class="lcf-brand">
			{hasMark && (
			<a class="lcf-logo" href={brandHref} aria-label={logo ? undefined : `${brandName} home`}>
				{
					logo ? (
						<img
							class="lcf-logo-img"
							src={logo.src}
							alt={logo.alt}
							width={logo.width ?? 190}
							height={logo.height ?? 50}
						/>
					) : (
						<>
							<span class="lcf-tile" aria-hidden="true">
								<svg viewBox="0 0 32 32" focusable="false">
									<rect x="4" y="4" width="7" height="24" rx="2" fill="var(--nv-lime, #c8ff38)" />
									<rect x="21" y="4" width="7" height="24" rx="2" fill="#fff" />
									<path d="M11 18.5 21 10v6.2L11 24.7z" fill="var(--nv-lime, #c8ff38)" />
								</svg>
							</span>
							<span class="lcf-wordmark" aria-hidden="true">
								<span class="lcf-word">{brandName}</span>
								{brandSub && <span class="lcf-sub">{brandSub}</span>}
							</span>
						</>
					)
				}
			</a>
			)}
			{tagline && <p class="lcf-tagline">{tagline}</p>}
			{
				social.length > 0 && (
					<ul class="lcf-social" role="list">
						{social.map((s) => (
							<li>
								<a class="lcf-social-link" href={s.href} aria-label={s.label} style={s.color ? `--lcf-glyph:${s.color}` : undefined}>
									<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
										<path d={GLYPHS[s.glyph]} fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
									</svg>
								</a>
							</li>
						))}
					</ul>
				)
			}
		</div>
		)}

		{hasCols && (
		<nav class="lcf-cols" aria-label={navLabel}>
			{
				columns.map((col) => (
					<div class="lcf-col">
						<h2 class="lcf-heading">{col.heading}</h2>
						<ul class="lcf-links" role="list">
							{col.links.map((link) => (
								<li>
									<a class="lcf-link" href={link.href}>{link.label}</a>
								</li>
							))}
						</ul>
					</div>
				))
			}
			{
				contact && showContact && (
					<div class="lcf-col lcf-col--contact">
						{contact.heading && <h2 class="lcf-heading lcf-heading--lime">{contact.heading}</h2>}
						{hasDetails && (
						<address class="lcf-address">
							{callHref && <a class="lcf-detail" href={callHref}>{contact.phone}</a>}
							{contact.email && <a class="lcf-detail" href={`mailto:${contact.email}`}>{contact.email}</a>}
							{contact.address && contact.address.length > 0 && (
								<span class="lcf-postal">
									{contact.address.map((line, i) => (
										<>
											{line}
											{i < contact.address!.length - 1 && <br />}
										</>
									))}
								</span>
							)}
						</address>
						)}
						{cta && <a class="lcf-cta" href={cta.href}>{cta.label}</a>}
					</div>
				)
			}
			{!showContact && cta && <div class="lcf-col lcf-col--contact"><a class="lcf-cta" href={cta.href}>{cta.label}</a></div>}
		</nav>
		)}
	</div>
	)}

	{hasBottom && (
	<div class="lcf-bottom">
		{
			legalLinks.length > 0 && (
				<nav class="lcf-legal" aria-label="Legal">
					<ul role="list">
						{legalLinks.map((link) => (
							<li>
								<a class="lcf-legal-link" href={link.href}>{link.label}</a>
							</li>
						))}
					</ul>
				</nav>
			)
		}
		{owner && <p class="lcf-copy">Copyright {year} {owner}.</p>}
	</div>
	)}
</footer>
)}

<style>
	.lcf {
		--lcf-ink: var(--nv-ink, #08110f);
		--lcf-lime: var(--nv-lime, #c8ff38);
		--lcf-font: var(--nv-font, Manrope, ui-sans-serif, system-ui, sans-serif);
		--lcf-font-ui: var(--nv-font-ui, Inter, ui-sans-serif, system-ui, sans-serif);

		box-sizing: border-box;
		display: flex;
		flex-direction: column;
		align-items: center;
		gap: 40px;
		width: 100%;
		padding: 64px 20px 28px;
		overflow: hidden;
		background: linear-gradient(135deg, #07100d 0%, #08150f 62%, #10241a 100%);
		color: #fff;
		font-family: var(--lcf-font);
	}

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

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

	.lcf p,
	.lcf h2 {
		margin: 0;
	}

	/* Listed by class rather than `.lcf a`: Astro's scoping attribute would
	   give a descendant `a` rule more weight than each link's own colour. */
	.lcf-logo,
	.lcf-social-link,
	.lcf-link,
	.lcf-detail,
	.lcf-legal-link,
	.lcf-cta {
		text-decoration: none;
	}

	.lcf-logo {
		color: #fff;
	}

	/* ---- top: brand + columns ---- */
	.lcf-top {
		display: flex;
		flex-direction: column;
		gap: 28px;
		width: 100%;
		max-width: 1180px;
		padding: 36px 0 28px;
	}

	.lcf-brand {
		display: flex;
		flex-direction: column;
		align-items: flex-start;
		gap: 14px;
		max-width: 350px;
	}

	.lcf-logo {
		display: inline-flex;
		align-items: center;
		gap: 10px;
		border-radius: 8px;
	}

	.lcf-tile {
		display: grid;
		place-items: center;
		width: 50px;
		height: 50px;
		flex: none;
		border-radius: 8px;
		background: rgb(255 255 255 / 0.06);
	}

	.lcf-tile svg {
		width: 38px;
		height: 38px;
	}

	.lcf-logo-img {
		display: block;
		height: 50px;
		width: auto;
	}

	.lcf-wordmark {
		display: flex;
		flex-direction: column;
		font-family: var(--lcf-font-ui);
		font-weight: 700;
		text-transform: uppercase;
	}

	.lcf-word {
		font-size: 25px;
		line-height: 30px;
		letter-spacing: 1.75px;
		color: #fff;
	}

	.lcf-sub {
		font-size: 17px;
		line-height: 20.4px;
		letter-spacing: 4.08px;
		color: #b7ff39;
	}

	.lcf-tagline {
		font-size: 19px;
		font-weight: 500;
		line-height: 27.55px;
		color: rgb(255 255 255 / 0.66);
	}

	/* 24px glyphs in 44px targets. The row pulls itself back by the target
	   padding so the first glyph still lines up with the tagline. */
	.lcf .lcf-social {
		display: flex;
		align-items: center;
		margin: -6px 0 -6px -10px;
	}

	.lcf-social-link {
		display: grid;
		place-items: center;
		width: 44px;
		height: 44px;
		border-radius: 10px;
		color: var(--lcf-glyph, rgb(255 255 255 / 0.78));
	}

	.lcf-social-link svg {
		width: 24px;
		height: 24px;
	}

	/* ---- columns ---- */
	.lcf-cols {
		display: grid;
		grid-template-columns: repeat(2, minmax(0, 155px));
		gap: 26px 20px;
		padding: 10px;
	}

	.lcf-col {
		display: flex;
		flex-direction: column;
		align-items: flex-start;
		gap: 14px;
		min-width: 0;
	}

	.lcf-heading {
		font-size: 12px;
		font-weight: 800;
		line-height: 14.4px;
		letter-spacing: 0.08em;
		text-transform: uppercase;
		color: rgb(255 255 255 / 0.48);
	}

	.lcf-heading--lime {
		letter-spacing: 0.075em;
		color: rgb(200 255 56 / 0.78);
	}

	/* 16px type on the measured 35px row pitch. Each row's whole pitch is
	   its target, with no dead space between rows; the list pulls back by
	   half the gap so the first label sits the measured 14px under the
	   heading. */
	.lcf .lcf-links {
		display: flex;
		flex-direction: column;
		margin: -7px 0;
	}

	.lcf-link {
		display: inline-flex;
		align-items: center;
		min-height: 35px;
		border-radius: 6px;
		font-size: 16px;
		font-weight: 500;
		line-height: 20.8px;
		color: rgb(255 255 255 / 0.84);
		transition: color 0.25s ease;
	}

	.lcf-col--contact {
		gap: 9px;
		grid-column: 1;
	}

	.lcf-address {
		display: flex;
		flex-direction: column;
		align-items: flex-start;
		gap: 9px;
		font-style: normal;
		font-size: 12px;
		font-weight: 600;
		line-height: 14.4px;
		color: rgb(255 255 255 / 0.78);
	}

	.lcf-detail {
		display: inline-flex;
		align-items: center;
		min-height: 24px;
		margin: -5px 0;
		color: inherit;
		border-radius: 4px;
		transition: color 0.25s ease;
	}

	.lcf-postal {
		line-height: 17.4px;
	}

	.lcf-cta {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		min-width: 165px;
		height: 40px;
		margin-top: 9px;
		padding: 0 18px;
		border-radius: 999px;
		background: var(--lcf-lime);
		color: var(--lcf-ink);
		font-size: 12px;
		font-weight: 800;
		line-height: 14.4px;
		white-space: nowrap;
	}

	/* ---- bottom bar ---- */
	.lcf-bottom {
		display: flex;
		flex-direction: column;
		gap: 14px;
		width: calc(100% + 40px);
		max-width: none;
		margin: 0 -20px;
		padding: 18px 20px 28px;
		border-top: 1px solid rgb(255 255 255 / 0.08);
		border-bottom: 1px solid rgb(255 255 255 / 0.08);
	}

	.lcf .lcf-legal ul {
		display: grid;
		grid-template-columns: repeat(2, minmax(0, 160px));
		gap: 10px;
		padding: 10px;
	}

	.lcf-legal-link {
		display: inline-flex;
		align-items: center;
		min-height: 24px;
		margin: -4px 0;
		border-radius: 4px;
		font-size: 12px;
		font-weight: 500;
		line-height: 15.6px;
		color: rgb(255 255 255 / 0.62);
		transition: color 0.25s ease;
	}

	.lcf-copy {
		font-size: 12px;
		font-weight: 500;
		line-height: 15.6px;
		color: rgb(255 255 255 / 0.5);
	}

	/* ---- links: no hover was measured. A quiet brighten on hover (an
	   approximation) and a clear lime ring plus full white on focus. ---- */
	@media (hover: hover) {
		.lcf-link:hover,
		.lcf-detail:hover,
		.lcf-legal-link:hover {
			color: #fff;
		}
	}

	.lcf a:focus-visible {
		outline: 2px solid var(--lcf-lime);
		outline-offset: 3px;
		color: #fff;
	}

	.lcf .lcf-cta:focus-visible {
		outline-color: #fff;
		color: var(--lcf-ink);
	}

	/* ---- tablet: brand above, three columns in one row ---- */
	@media (min-width: 810px) {
		.lcf-cols {
			display: flex;
			align-items: flex-start;
			gap: 56px;
		}

		.lcf-col {
			width: 132px;
		}

		.lcf-col--contact {
			width: 190px;
		}

		.lcf-bottom {
			flex-direction: row;
			align-items: center;
			justify-content: space-between;
			width: 100%;
			max-width: 1180px;
			margin: 0;
			/* 12px, not the 18px Framer reports: its 24px legal row carries
			   10px of padding the box does not grow by, and the measured box
			   is 60px tall. */
			padding: 12px 10px 12px 0;
			border: 1px solid rgb(255 255 255 / 0.08);
		}

		.lcf .lcf-legal ul {
			display: flex;
			align-items: center;
			gap: 18px;
		}
	}

	/* ---- desktop: brand left, columns right, as measured ---- */
	@media (min-width: 960px) {
		.lcf-top {
			flex-direction: row;
			justify-content: space-between;
			align-items: flex-start;
			gap: 40px;
			padding: 38px 0 30px;
		}

		.lcf-brand {
			width: 220px;
			flex: none;
		}

		.lcf-cols {
			gap: 68px;
			flex: none;
		}
	}

	@media (min-width: 960px) and (max-width: 1099.98px) {
		.lcf-cols {
			gap: 44px;
		}
	}

	@media (prefers-reduced-motion: reduce) {
		.lcf a {
			transition: none;
		}
	}

	@media (forced-colors: active) {
		.lcf-cta,
		.lcf-tile {
			border: 1px solid CanvasText;
		}
	}
</style>

===== END src/components/LimeContactFooter.astro =====

===== FILE (supporting): src/components/LimeContactFooter.md =====
# Lime contact footer

A dark green-black footer: logo tile, two-line wordmark, tagline and icon
links on the left; Explore and Company link columns and a Contact column
(phone, email, address and a lime pill) on the right; a hairline-boxed bottom
bar with legal links and the copyright.

Catalog page: `/components/lime-contact-footer`

## Purpose

The site footer for the Halcyon/Naviora-style page: navigation repeated,
direct contact details, one call to action. Static — no script.

## Files required

**`LimeContactFooter.astro`** — one file. No imports, no script, no npm
packages, no images.

`LimeContactFooter.md` — this file. Documentation, not required to run.

## Basic usage

```astro
---
import LimeContactFooter from "../components/LimeContactFooter.astro";
---

<LimeContactFooter
  brandName="Halcyon"
  brandSub="Logistics"
  tagline="Freight that moves on time, and data that shows you it did."
  columns={[{ heading: "Company", links: [{ label: "About", href: "/about" }, { label: "Contact", href: "/contact" }] }]}
  contact={{ heading: "Contact", phone: "+1 (415) 555-0188", email: "desk@example.com", address: ["48 Harbor Point Road,", "Oakland, CA 94607, USA"] }}
  cta={{ label: "Talk to dispatch", href: "/contact" }}
  legalLinks={[{ label: "Privacy", href: "/privacy" }]}
/>
```

## Props

| Prop | Type | Default | What it does |
| :--- | :--- | :--- | :--- |
| `brandName` | `string` | — | First wordmark line; home link's accessible name. No name and no `logo` drops the logo link. |
| `brandSub` | `string` | — | Lime second line. |
| `brandHref` | `string` | `"/"` | |
| `logo` | `{ src, alt, width?, height? }` | — | Replaces the tile and wordmark with an image (50px tall). |
| `tagline` | `string` | — | |
| `social` | `{ label, href, glyph, color? }[]` | `[]` | Icon links. `glyph`: globe, play, chat, mail, phone, pin, rss, link. Empty drops the row. |
| `columns` | `{ heading, links: { label, href }[] }[]` | `[]` | Link columns. |
| `contact` | `{ heading?, phone?, email?, address?: string[] } \| null` | `null` | The contact column. Phone becomes `tel:` (no digits, no line), email `mailto:`. |
| `cta` | `{ label, href } \| null` | `null` | The lime pill. |
| `legalLinks` | `{ label, href }[]` | `[]` | Empty drops the legal nav. |
| `copyrightName` | `string` | brand + sub | Name in the copyright line. No name and no brand: no copyright line. |
| `year` | `number` | build year | |
| `navLabel` | `string` | `"Footer"` | `aria-label` of the column nav. |
| `fontStylesheet` | `string` | Manrope + Inter on Google Fonts | `""` skips the `<link>`. |
| `id` | `string` | — | Root id. |

## Business details

Nothing about the business is built in. With no props the footer renders
nothing; each block (logo link, tagline, icon row, columns, contact column,
pill, legal nav, copyright, the bottom bar itself) appears only when it has
data. The catalog preview's Halcyon content is demo data from the registry,
not a default.

## Behaviour

- **Desktop (960px+).** 1180px wide, brand block (220px) left, three columns
  right with 68px gaps (132 / 132 / 190px), 64px top padding, bottom bar in a
  1px `rgba(255,255,255,.08)` box. Background
  `linear-gradient(135deg, #07100d, #08150f 62%, #10241a)`.
- **Tablet (810–959px).** Brand block above, the three columns in one row.
- **Phone (<810px).** Brand block, then a 2 × 155px grid (Explore, Company,
  Contact under Explore), then legal links in a 2 × 160px grid and the
  copyright; the bottom bar's hairlines run edge to edge.
- **Hover.** None measured on links. A quiet brighten to full white on hover
  (hover-capable pointers only) is this library's approximation. Focus
  shows a 2px lime ring and full white text. The pill has no hover.
- **Motion.** Colour transitions only (0.25s); removed under reduced motion.

## Accessibility

- `<footer>`; the columns are a `<nav aria-label="Footer">` with `<h2>`
  headings; legal links a second `<nav aria-label="Legal">`; contact details
  in `<address>`.
- Icon links have `aria-label`s; glyphs are `aria-hidden`. Each is a 44px
  target.
- Column links fill the measured 35px row pitch as their target, with no dead
  space between rows. Contact and legal links are 24px targets (WCAG 2.5.8).
- Pill text is ink on lime.

## Integration notes

- Goes at the end of the layout, after `<main>`, as a direct child of
  `<body>` so it is the page's contentinfo landmark.
- Paints its own full-bleed background; no wrapper.
- The default copyright year is the build year. A long-lived static deploy
  shows a stale year; pass `year`.
- Ships no third-party logos: the icons are generic glyphs. Add paths to the
  `GLYPHS` map in your copy for real network marks.
- The column headings are `<h2>`. If your page outline needs another level,
  change the tag in your copy.

## Images and credits

None. The logo tile is an original inline SVG.

## Approximations

- **Pill text colour.** The reference's "Get in Touch" is `#0099ff` blue on
  lime (a Framer default link colour, about 2.6:1). It is ink here.
- **Social icons.** The reference shows four real network logos in brand
  colours. These are generic glyphs, each keeping a similar hue (blue, grey,
  blue, red), and each sits in a 44px target, so the row is 44px apart
  rather than the reference's 34px.
- **Link weights.** The reference mixes 500/84% and 600/78% links (plain text
  versus links in Framer). All links here are 500/84%.
- **Bottom bar.** The box border is read from the screenshot (the dump
  reports none), and the copyright gets 10px right padding to mirror the left.
- Tablet (810–1199px) was not captured; the 810–959px layout is interpolated.

===== END src/components/LimeContactFooter.md =====

--- VERIFIED IN THE COMPONENT LIBRARY ---
Enrolled for verification but never run.
No verification run has been recorded.
No checks have passed yet.
No usable source fingerprint, so these results cannot be tied to the files below.

--- NOT PROVEN ---
- ClientRouter: not applicable — this component ships no script.

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

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

Component reference

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

Read it first
“Lime contact footer” from my Astro Component Library:
https://astro.baysixmedia.com/components/lime-contact-footer/

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

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

Component readiness

Judged on recorded evidence, not on how it looks in the frame above. A description of a component is not a result.

Interactivity
Static
Profile
core only
Installation files
1 required + 1 supporting
Verified
Never
Client navigation
Not applicable — ships no script
Source fingerprint
No fingerprint recorded
Production ready
Not yet

Not yet verified

Enrolled for verification, but never run. Silence is reported as silence rather than assumed to be fine.

Open the verification fixture

src/components/library/LimeContactFooter.astro

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

---
/**
 * LimeContactFooter
 *
 * A dark green-black footer: logo tile, two-line wordmark, tagline and icon
 * links on the left; Explore and Company link columns and a Contact column
 * (phone, email, address, a lime pill) on the right; a hairline-boxed bottom
 * bar with legal links and the copyright. Static markup, no script.
 *
 * Nothing about the business is assumed: no name, tagline, link, contact
 * detail, pill or legal link is shown unless it is passed, a block with
 * nothing in it is not drawn, and with no content at all nothing renders.
 *
 * Reads --nv-* tokens from any ancestor, with fallbacks.
 */

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

export interface LimeFooterColumn {
	heading: string;
	links: LimeFooterLink[];
}

export type LimeFooterGlyph = "globe" | "play" | "chat" | "mail" | "phone" | "pin" | "rss" | "link";

export interface LimeFooterSocial {
	/** Accessible name, e.g. "Halcyon on video". */
	label: string;
	href: string;
	glyph: LimeFooterGlyph;
	/** Icon colour. Each defaults to its own hue, as in the reference. */
	color?: string;
}

export interface LimeFooterContact {
	heading?: string;
	phone?: string;
	email?: string;
	/** Lines of the postal address. */
	address?: string[];
}

export interface LimeContactFooterProps {
	/** First wordmark line. No name and no logo drops the logo link. */
	brandName?: string;
	/** Second, lime line of the wordmark. `""` drops it. */
	brandSub?: string;
	brandHref?: string;
	/** Replaces the drawn logo tile and wordmark with an image. */
	logo?: { src: string; alt: string; width?: number; height?: number };
	tagline?: string;
	social?: LimeFooterSocial[];
	columns?: LimeFooterColumn[];
	/** `null` drops the whole contact column. */
	contact?: LimeFooterContact | null;
	/** The lime pill under the contact details. `null` drops it. */
	cta?: { label: string; href: string } | null;
	legalLinks?: LimeFooterLink[];
	/** Name in the copyright line. Defaults to the brand; with neither, no line. */
	copyrightName?: string;
	/** Defaults to the year the site was built. */
	year?: number;
	navLabel?: string;
	fontStylesheet?: string;
	id?: string;
}

interface Props extends LimeContactFooterProps {}

const {
	brandName = "",
	brandSub = "",
	brandHref = "/",
	logo,
	tagline = "",
	social = [],
	columns = [],
	contact = null,
	cta = null,
	legalLinks = [],
	copyrightName,
	year = new Date().getFullYear(),
	navLabel = "Footer",
	fontStylesheet = "https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Inter:wght@400;500;600;700;800&display=swap",
	id,
} = Astro.props;

/* A dialable href from a displayed number: keep a leading + and the digits. */
const telHref = (number: string) => {
	const trimmed = number.trim();
	const digits = trimmed.replace(/\D/g, "");
	return digits ? `tel:${trimmed.startsWith("+") ? "+" : ""}${digits}` : "";
};

/* No name, no copyright line: never "Copyright 2026 ." */
const owner = copyrightName ?? (brandName ? (brandSub ? `${brandName} ${brandSub}` : brandName) : "");
/* A call link only with somewhere to dial: words in `phone` are not a number. */
const callHref = contact?.phone ? telHref(contact.phone) : "";
const hasDetails = Boolean(callHref || contact?.email || (contact?.address && contact.address.length > 0));
const showContact = Boolean(contact && (hasDetails || cta));
const hasMark = Boolean(logo || brandName);
const hasBrand = Boolean(hasMark || tagline || social.length > 0);
const hasCols = columns.length > 0 || showContact || Boolean(cta);
const hasBottom = legalLinks.length > 0 || Boolean(owner);
const hasContent = hasBrand || hasCols || hasBottom;

/* Generic 24px line glyphs, no third-party logos. */
const GLYPHS: Record<LimeFooterGlyph, string> = {
	globe: "M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18Zm-9 9h18M12 3c2.5 2.6 3.8 5.6 3.8 9s-1.3 6.4-3.8 9c-2.5-2.6-3.8-5.6-3.8-9S9.5 5.6 12 3Z",
	play: "M4.5 6.5A2.5 2.5 0 0 1 7 4h10a2.5 2.5 0 0 1 2.5 2.5v11A2.5 2.5 0 0 1 17 20H7a2.5 2.5 0 0 1-2.5-2.5v-11ZM10 9v6l5-3-5-3Z",
	chat: "M4 5.5A1.5 1.5 0 0 1 5.5 4h13A1.5 1.5 0 0 1 20 5.5v9a1.5 1.5 0 0 1-1.5 1.5H10l-4.5 4v-4h0A1.5 1.5 0 0 1 4 14.5v-9ZM8 9h8M8 12h5",
	mail: "M3.5 6.5A1.5 1.5 0 0 1 5 5h14a1.5 1.5 0 0 1 1.5 1.5v11A1.5 1.5 0 0 1 19 19H5a1.5 1.5 0 0 1-1.5-1.5v-11Zm.5.5 8 6 8-6",
	phone: "M6.5 3.5h3l1.5 4-2 1.5a11 11 0 0 0 6 6l1.5-2 4 1.5v3a2 2 0 0 1-2 2A16.5 16.5 0 0 1 4.5 5.5a2 2 0 0 1 2-2Z",
	pin: "M12 21s-6.5-5.7-6.5-11a6.5 6.5 0 0 1 13 0c0 5.3-6.5 11-6.5 11Zm0-8.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z",
	rss: "M5 5a14 14 0 0 1 14 14M5 11a8 8 0 0 1 8 8M6 19a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z",
	link: "M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1",
};
---

{hasContent && fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
{hasContent && (
<footer class="lcf" id={id} data-lcf-root>
	{(hasBrand || hasCols) && (
	<div class="lcf-top">
		{hasBrand && (
		<div class="lcf-brand">
			{hasMark && (
			<a class="lcf-logo" href={brandHref} aria-label={logo ? undefined : `${brandName} home`}>
				{
					logo ? (
						<img
							class="lcf-logo-img"
							src={logo.src}
							alt={logo.alt}
							width={logo.width ?? 190}
							height={logo.height ?? 50}
						/>
					) : (
						<>
							<span class="lcf-tile" aria-hidden="true">
								<svg viewBox="0 0 32 32" focusable="false">
									<rect x="4" y="4" width="7" height="24" rx="2" fill="var(--nv-lime, #c8ff38)" />
									<rect x="21" y="4" width="7" height="24" rx="2" fill="#fff" />
									<path d="M11 18.5 21 10v6.2L11 24.7z" fill="var(--nv-lime, #c8ff38)" />
								</svg>
							</span>
							<span class="lcf-wordmark" aria-hidden="true">
								<span class="lcf-word">{brandName}</span>
								{brandSub && <span class="lcf-sub">{brandSub}</span>}
							</span>
						</>
					)
				}
			</a>
			)}
			{tagline && <p class="lcf-tagline">{tagline}</p>}
			{
				social.length > 0 && (
					<ul class="lcf-social" role="list">
						{social.map((s) => (
							<li>
								<a class="lcf-social-link" href={s.href} aria-label={s.label} style={s.color ? `--lcf-glyph:${s.color}` : undefined}>
									<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
										<path d={GLYPHS[s.glyph]} fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
									</svg>
								</a>
							</li>
						))}
					</ul>
				)
			}
		</div>
		)}

		{hasCols && (
		<nav class="lcf-cols" aria-label={navLabel}>
			{
				columns.map((col) => (
					<div class="lcf-col">
						<h2 class="lcf-heading">{col.heading}</h2>
						<ul class="lcf-links" role="list">
							{col.links.map((link) => (
								<li>
									<a class="lcf-link" href={link.href}>{link.label}</a>
								</li>
							))}
						</ul>
					</div>
				))
			}
			{
				contact && showContact && (
					<div class="lcf-col lcf-col--contact">
						{contact.heading && <h2 class="lcf-heading lcf-heading--lime">{contact.heading}</h2>}
						{hasDetails && (
						<address class="lcf-address">
							{callHref && <a class="lcf-detail" href={callHref}>{contact.phone}</a>}
							{contact.email && <a class="lcf-detail" href={`mailto:${contact.email}`}>{contact.email}</a>}
							{contact.address && contact.address.length > 0 && (
								<span class="lcf-postal">
									{contact.address.map((line, i) => (
										<>
											{line}
											{i < contact.address!.length - 1 && <br />}
										</>
									))}
								</span>
							)}
						</address>
						)}
						{cta && <a class="lcf-cta" href={cta.href}>{cta.label}</a>}
					</div>
				)
			}
			{!showContact && cta && <div class="lcf-col lcf-col--contact"><a class="lcf-cta" href={cta.href}>{cta.label}</a></div>}
		</nav>
		)}
	</div>
	)}

	{hasBottom && (
	<div class="lcf-bottom">
		{
			legalLinks.length > 0 && (
				<nav class="lcf-legal" aria-label="Legal">
					<ul role="list">
						{legalLinks.map((link) => (
							<li>
								<a class="lcf-legal-link" href={link.href}>{link.label}</a>
							</li>
						))}
					</ul>
				</nav>
			)
		}
		{owner && <p class="lcf-copy">Copyright {year} {owner}.</p>}
	</div>
	)}
</footer>
)}

<style>
	.lcf {
		--lcf-ink: var(--nv-ink, #08110f);
		--lcf-lime: var(--nv-lime, #c8ff38);
		--lcf-font: var(--nv-font, Manrope, ui-sans-serif, system-ui, sans-serif);
		--lcf-font-ui: var(--nv-font-ui, Inter, ui-sans-serif, system-ui, sans-serif);

		box-sizing: border-box;
		display: flex;
		flex-direction: column;
		align-items: center;
		gap: 40px;
		width: 100%;
		padding: 64px 20px 28px;
		overflow: hidden;
		background: linear-gradient(135deg, #07100d 0%, #08150f 62%, #10241a 100%);
		color: #fff;
		font-family: var(--lcf-font);
	}

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

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

	.lcf p,
	.lcf h2 {
		margin: 0;
	}

	/* Listed by class rather than `.lcf a`: Astro's scoping attribute would
	   give a descendant `a` rule more weight than each link's own colour. */
	.lcf-logo,
	.lcf-social-link,
	.lcf-link,
	.lcf-detail,
	.lcf-legal-link,
	.lcf-cta {
		text-decoration: none;
	}

	.lcf-logo {
		color: #fff;
	}

	/* ---- top: brand + columns ---- */
	.lcf-top {
		display: flex;
		flex-direction: column;
		gap: 28px;
		width: 100%;
		max-width: 1180px;
		padding: 36px 0 28px;
	}

	.lcf-brand {
		display: flex;
		flex-direction: column;
		align-items: flex-start;
		gap: 14px;
		max-width: 350px;
	}

	.lcf-logo {
		display: inline-flex;
		align-items: center;
		gap: 10px;
		border-radius: 8px;
	}

	.lcf-tile {
		display: grid;
		place-items: center;
		width: 50px;
		height: 50px;
		flex: none;
		border-radius: 8px;
		background: rgb(255 255 255 / 0.06);
	}

	.lcf-tile svg {
		width: 38px;
		height: 38px;
	}

	.lcf-logo-img {
		display: block;
		height: 50px;
		width: auto;
	}

	.lcf-wordmark {
		display: flex;
		flex-direction: column;
		font-family: var(--lcf-font-ui);
		font-weight: 700;
		text-transform: uppercase;
	}

	.lcf-word {
		font-size: 25px;
		line-height: 30px;
		letter-spacing: 1.75px;
		color: #fff;
	}

	.lcf-sub {
		font-size: 17px;
		line-height: 20.4px;
		letter-spacing: 4.08px;
		color: #b7ff39;
	}

	.lcf-tagline {
		font-size: 19px;
		font-weight: 500;
		line-height: 27.55px;
		color: rgb(255 255 255 / 0.66);
	}

	/* 24px glyphs in 44px targets. The row pulls itself back by the target
	   padding so the first glyph still lines up with the tagline. */
	.lcf .lcf-social {
		display: flex;
		align-items: center;
		margin: -6px 0 -6px -10px;
	}

	.lcf-social-link {
		display: grid;
		place-items: center;
		width: 44px;
		height: 44px;
		border-radius: 10px;
		color: var(--lcf-glyph, rgb(255 255 255 / 0.78));
	}

	.lcf-social-link svg {
		width: 24px;
		height: 24px;
	}

	/* ---- columns ---- */
	.lcf-cols {
		display: grid;
		grid-template-columns: repeat(2, minmax(0, 155px));
		gap: 26px 20px;
		padding: 10px;
	}

	.lcf-col {
		display: flex;
		flex-direction: column;
		align-items: flex-start;
		gap: 14px;
		min-width: 0;
	}

	.lcf-heading {
		font-size: 12px;
		font-weight: 800;
		line-height: 14.4px;
		letter-spacing: 0.08em;
		text-transform: uppercase;
		color: rgb(255 255 255 / 0.48);
	}

	.lcf-heading--lime {
		letter-spacing: 0.075em;
		color: rgb(200 255 56 / 0.78);
	}

	/* 16px type on the measured 35px row pitch. Each row's whole pitch is
	   its target, with no dead space between rows; the list pulls back by
	   half the gap so the first label sits the measured 14px under the
	   heading. */
	.lcf .lcf-links {
		display: flex;
		flex-direction: column;
		margin: -7px 0;
	}

	.lcf-link {
		display: inline-flex;
		align-items: center;
		min-height: 35px;
		border-radius: 6px;
		font-size: 16px;
		font-weight: 500;
		line-height: 20.8px;
		color: rgb(255 255 255 / 0.84);
		transition: color 0.25s ease;
	}

	.lcf-col--contact {
		gap: 9px;
		grid-column: 1;
	}

	.lcf-address {
		display: flex;
		flex-direction: column;
		align-items: flex-start;
		gap: 9px;
		font-style: normal;
		font-size: 12px;
		font-weight: 600;
		line-height: 14.4px;
		color: rgb(255 255 255 / 0.78);
	}

	.lcf-detail {
		display: inline-flex;
		align-items: center;
		min-height: 24px;
		margin: -5px 0;
		color: inherit;
		border-radius: 4px;
		transition: color 0.25s ease;
	}

	.lcf-postal {
		line-height: 17.4px;
	}

	.lcf-cta {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		min-width: 165px;
		height: 40px;
		margin-top: 9px;
		padding: 0 18px;
		border-radius: 999px;
		background: var(--lcf-lime);
		color: var(--lcf-ink);
		font-size: 12px;
		font-weight: 800;
		line-height: 14.4px;
		white-space: nowrap;
	}

	/* ---- bottom bar ---- */
	.lcf-bottom {
		display: flex;
		flex-direction: column;
		gap: 14px;
		width: calc(100% + 40px);
		max-width: none;
		margin: 0 -20px;
		padding: 18px 20px 28px;
		border-top: 1px solid rgb(255 255 255 / 0.08);
		border-bottom: 1px solid rgb(255 255 255 / 0.08);
	}

	.lcf .lcf-legal ul {
		display: grid;
		grid-template-columns: repeat(2, minmax(0, 160px));
		gap: 10px;
		padding: 10px;
	}

	.lcf-legal-link {
		display: inline-flex;
		align-items: center;
		min-height: 24px;
		margin: -4px 0;
		border-radius: 4px;
		font-size: 12px;
		font-weight: 500;
		line-height: 15.6px;
		color: rgb(255 255 255 / 0.62);
		transition: color 0.25s ease;
	}

	.lcf-copy {
		font-size: 12px;
		font-weight: 500;
		line-height: 15.6px;
		color: rgb(255 255 255 / 0.5);
	}

	/* ---- links: no hover was measured. A quiet brighten on hover (an
	   approximation) and a clear lime ring plus full white on focus. ---- */
	@media (hover: hover) {
		.lcf-link:hover,
		.lcf-detail:hover,
		.lcf-legal-link:hover {
			color: #fff;
		}
	}

	.lcf a:focus-visible {
		outline: 2px solid var(--lcf-lime);
		outline-offset: 3px;
		color: #fff;
	}

	.lcf .lcf-cta:focus-visible {
		outline-color: #fff;
		color: var(--lcf-ink);
	}

	/* ---- tablet: brand above, three columns in one row ---- */
	@media (min-width: 810px) {
		.lcf-cols {
			display: flex;
			align-items: flex-start;
			gap: 56px;
		}

		.lcf-col {
			width: 132px;
		}

		.lcf-col--contact {
			width: 190px;
		}

		.lcf-bottom {
			flex-direction: row;
			align-items: center;
			justify-content: space-between;
			width: 100%;
			max-width: 1180px;
			margin: 0;
			/* 12px, not the 18px Framer reports: its 24px legal row carries
			   10px of padding the box does not grow by, and the measured box
			   is 60px tall. */
			padding: 12px 10px 12px 0;
			border: 1px solid rgb(255 255 255 / 0.08);
		}

		.lcf .lcf-legal ul {
			display: flex;
			align-items: center;
			gap: 18px;
		}
	}

	/* ---- desktop: brand left, columns right, as measured ---- */
	@media (min-width: 960px) {
		.lcf-top {
			flex-direction: row;
			justify-content: space-between;
			align-items: flex-start;
			gap: 40px;
			padding: 38px 0 30px;
		}

		.lcf-brand {
			width: 220px;
			flex: none;
		}

		.lcf-cols {
			gap: 68px;
			flex: none;
		}
	}

	@media (min-width: 960px) and (max-width: 1099.98px) {
		.lcf-cols {
			gap: 44px;
		}
	}

	@media (prefers-reduced-motion: reduce) {
		.lcf a {
			transition: none;
		}
	}

	@media (forced-colors: active) {
		.lcf-cta,
		.lcf-tile {
			border: 1px solid CanvasText;
		}
	}
</style>