Colonnade Beacon
The anatomy `colonnade` was measured from, rebuilt around it: an eyebrow pill, one short claim, one line of support copy, an email field with its button inside the same pill-shaped track, and a four-unit countdown underneath. Everything sits in the centre the two pillar banks leave clear, so nothing has to fight the field for contrast.
Honest by construction. There's no waitlist behind this page, so the form says so when you submit it rather than pretending to sign you up. The reference's "700+ signups" avatar row is dropped instead of invented, and the countdown is labelled a demo, counting to a date 30 days from when the page opened. Move the cursor over either bank: the pillars reach for it, and their twins on the far side answer.
Install
No installation needed — self-contained, paste-in code.
Usage
Drop it straight into a page.
"use client";
import { useEffect, useState } from "react";
import { ColonnadeField } from "./ColonnadeField";
const LAUNCH = new Date("2026-12-01T00:00:00Z").getTime(); // your launch date
export default function Example() {
const [left, setLeft] = useState<number | null>(null);
useEffect(() => {
const tick = () => setLeft(Math.max(LAUNCH - Date.now(), 0));
tick();
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, []);
const s = Math.floor((left ?? 0) / 1000);
const units = [
["Days", Math.floor(s / 86400)],
["Hours", Math.floor((s % 86400) / 3600)],
["Minutes", Math.floor((s % 3600) / 60)],
["Seconds", s % 60],
] as const;
return (
<header className="relative isolate min-h-[40rem] overflow-hidden bg-[#0b0908] flex flex-col items-center justify-center text-center">
<div className="absolute inset-0 -z-10">
<ColonnadeField />
</div>
<div className="mx-auto max-w-xl px-8 text-[#f5f5f5]">
<span className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/5 px-3.5 py-1.5 text-sm text-white/80">
<span className="h-2 w-2 rounded-[3px] bg-[#f26a1b]" />
Early access opens soon
</span>
<h1 className="mt-5 text-6xl font-medium tracking-tight">Get early access</h1>
<p className="mt-5 text-white/75">One line on what people get for signing up early.</p>
<form
action="/api/waitlist" // point at your own list
method="post"
className="mx-auto mt-9 flex max-w-md items-center gap-2 rounded-full border border-white/10 bg-[#1a1716]/80 p-1.5 pl-5"
>
<label htmlFor="email" className="sr-only">Email address</label>
<input id="email" name="email" type="email" required placeholder="Enter your best email…" className="min-w-0 flex-1 bg-transparent outline-none placeholder:text-white/45" />
<button className="rounded-full border border-[#f26a1b]/60 bg-[#f26a1b]/15 px-5 py-2.5 text-sm font-medium text-[#ffb27a]">
Join waitlist
</button>
</form>
<div className="mt-10 flex justify-center gap-10" role="timer">
{units.map(([label, value]) => (
<div key={label} className="flex flex-col items-center gap-1.5">
<span className="text-3xl tabular-nums">{left == null ? "--" : String(value).padStart(2, "0")}</span>
<span className="text-xs uppercase tracking-widest text-white/50">{label}</span>
</div>
))}
</div>
</div>
</header>
);
}Component
The real source, exactly as it ships — multiple files, kept together.
"use client";
import Link from "next/link";
import { useEffect, useId, useState, type FormEvent } from "react";
import type { CategoryDef, CreationCard } from "@/lib/iris";
import { ColonnadeField } from "../visuals/backgrounds";
/**
* The real `colonnade-beacon` section, full scale — the waitlist-hero
* anatomy `colonnade` itself was measured from: an eyebrow pill, one short
* claim, one line of support copy, an inline email field with its button,
* and a live countdown underneath, all held in the empty centre the two
* pillar banks leave clear.
*
* Honest by construction (product-spec.md §3.1 / iris-dna.md's Honesty
* rule): there's no waitlist behind this page, so the form says so on
* submit instead of pretending to sign anyone up; the reference's "700+
* signups" avatar row is dropped rather than invented; and the countdown
* is labelled a demo, counting to a date 30 days from when the page opened.
*/
const DEMO_DAYS = 30;
function useCountdown(days: number) {
const [left, setLeft] = useState<number | null>(null);
useEffect(() => {
/* Set on mount, not at render, so the server and client never disagree
about the clock. */
const target = Date.now() + days * 86_400_000;
const tick = () => setLeft(Math.max(target - Date.now(), 0));
tick();
const id = window.setInterval(tick, 1000);
return () => window.clearInterval(id);
}, [days]);
if (left == null) return null;
const s = Math.floor(left / 1000);
return {
days: Math.floor(s / 86_400),
hours: Math.floor((s % 86_400) / 3600),
minutes: Math.floor((s % 3600) / 60),
seconds: s % 60,
};
}
export function ColonnadeBeacon({
category,
creation,
}: {
category: CategoryDef;
creation: CreationCard;
}) {
const emailId = useId();
const statusId = useId();
const [sent, setSent] = useState(false);
const left = useCountdown(DEMO_DAYS);
const onSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setSent(true);
};
const units = [
{ label: "Days", value: left?.days },
{ label: "Hours", value: left?.hours },
{ label: "Minutes", value: left?.minutes },
{ label: "Seconds", value: left?.seconds },
];
return (
<header className="iris-hero iris-hero--field relative isolate [overflow:clip] mt-[calc(-3.5rem_-_1px)] min-h-[min(92vh,820px)] flex flex-col justify-center [padding-block:calc(clamp(3rem,8vh,6rem)+3.5rem)_clamp(4rem,10vh,7rem)]">
<div className="absolute inset-0 z-[-1] overflow-hidden bg-[oklch(0.1_0.004_50)]" aria-hidden="true">
<ColonnadeField />
</div>
<div className="iris-shell flex flex-col items-center relative text-center">
<p className="flex flex-wrap items-center gap-[0.4rem] text-[length:0.8125rem] text-[var(--field-ink-dim)] [&_a]:text-inherit [&_a:hover]:text-[color:var(--field-ink)]">
<Link href="/iris">Iris</Link>
<span aria-hidden="true">/</span>
<Link href={`/iris/${category.slug}`}>{category.label}</Link>
<span aria-hidden="true">/</span>
<span>{creation.title}</span>
</p>
<span className="mt-6 inline-flex items-center gap-2 rounded-[var(--iris-radius-pill)] border border-[oklch(1_0_0_/_0.1)] bg-[oklch(1_0_0_/_0.04)] px-3.5 py-1.5 text-[length:0.8125rem] font-medium text-[color:var(--field-ink-dim)]">
<span className="h-2 w-2 rounded-[3px] bg-[oklch(0.7_0.19_45)] shadow-[0_0_10px_oklch(0.7_0.19_45_/_0.8)]" aria-hidden="true" />
Waitlist anatomy, over Colonnade
</span>
<h1 className="mt-5 font-[family-name:var(--font-iris-display)] font-normal text-[length:clamp(2.75rem,1.6rem+5vw,5.5rem)] leading-[1] tracking-[-0.03em] text-balance text-[color:var(--field-ink)]">
Get early access
</h1>
<p className="mt-5 max-w-[44ch] text-[length:1.0625rem] leading-[1.55] text-[color:var(--field-ink-dim)]">
Pill, claim, email field, countdown: the whole waitlist hero, framed
by pillars that reach for your cursor.
</p>
<form
onSubmit={onSubmit}
className="mt-9 flex w-full max-w-[27rem] items-center gap-2 rounded-[var(--iris-radius-pill)] border border-[oklch(1_0_0_/_0.08)] bg-[oklch(0.16_0.004_50_/_0.8)] p-1.5 pl-5 focus-within:border-[oklch(0.7_0.19_45_/_0.6)]"
>
<label htmlFor={emailId} className="sr-only">
Email address
</label>
<input
id={emailId}
type="email"
required
autoComplete="email"
placeholder="Enter your best email…"
aria-describedby={statusId}
onChange={() => setSent(false)}
className="min-w-0 flex-1 bg-transparent text-[length:0.9375rem] text-[color:var(--field-ink)] placeholder:text-[oklch(0.98_0.004_90_/_0.45)] outline-none"
/>
<button
type="submit"
className="shrink-0 rounded-[var(--iris-radius-pill)] border border-[oklch(0.7_0.19_45_/_0.55)] bg-[oklch(0.3_0.09_45_/_0.35)] px-5 py-2.5 text-[length:0.875rem] font-medium leading-none text-[oklch(0.82_0.14_55)] transition-[background-color,border-color] duration-[120ms] hover:bg-[oklch(0.38_0.12_45_/_0.5)] hover:border-[oklch(0.75_0.19_50)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[oklch(0.75_0.19_50)]"
>
Join waitlist
</button>
</form>
<p id={statusId} aria-live="polite" className="mt-3 min-h-[1.25rem] text-[length:0.8125rem] text-[color:var(--field-ink-dim)]">
{sent ? "Demo only: nothing was sent. Point this form at your own list." : ""}
</p>
<div className="mt-8 flex flex-col items-center gap-3">
<p className="text-[length:0.6875rem] font-medium uppercase tracking-[0.14em] text-[oklch(0.98_0.004_90_/_0.5)]">
Demo launch, {DEMO_DAYS} days out
</p>
<div className="flex items-start gap-8 sm:gap-12" role="timer" aria-live="off">
{units.map((unit) => (
<div key={unit.label} className="flex min-w-[3.25rem] flex-col items-center gap-1.5">
<span className="font-[family-name:var(--font-iris-display)] text-[length:clamp(1.5rem,1.1rem+1.4vw,2.125rem)] leading-none tabular-nums tracking-[-0.02em] text-[color:var(--field-ink)]">
{unit.value == null ? "--" : String(unit.value).padStart(2, "0")}
</span>
<span className="text-[length:0.6875rem] uppercase tracking-[0.12em] text-[oklch(0.98_0.004_90_/_0.5)]">
{unit.label}
</span>
</div>
))}
</div>
</div>
</div>
</header>
);
}