Skip to content

1440 × 900

Halcyon

A whole SaaS landing page for a made-up on-call tool — a horizon rising off the hero, the product standing on it, and every other section quiet.

A complete landing page — nav, hero, features, how it works, pricing, FAQ, close and footer — in the category's standard order, so a visitor uses it without learning it. Its one idea is a horizon: Iris's Horizon field rises off the bottom of the hero with its reading guard on the copy, the product window stands on it, and the page closes on the same light, drawn in CSS rather than run a second time. Everything else is plain type, hairlines and one raised surface, so the horizon is what's remembered.

Self-contained: every colour comes from a handful of variables scoped to .halcyon, it takes your site's typeface, and it adds no JavaScript of its own — the phone menu and the FAQ are native <details>. No logo wall, testimonials or usage numbers: add them when you have real ones. Replace the copy and the product mock with your own.

Details

Kind
Landing
Stack
React · TypeScript
Depends on
Nothing
Licence
Free
Added
28 Sep 2026
Tags
saas, landing-page, pricing, faq, webgl-hero, responsive

Its light

  • 45%
  • 23%
  • 14%
  • 8%
  • 10%

Code

Install

No installation needed — self-contained, paste-in code.

Usage

example.tsx
import { HalcyonLanding } from "./halcyon/HalcyonLanding";

// The whole page — drop it into a route and replace the copy.
export default function Page() {
  return <HalcyonLanding />;
}

Component

The real source, exactly as it ships — multiple files, kept together.

import type { ReactNode } from "react";
import { HorizonField } from "@/components/iris/visuals/backgrounds/HorizonField";
import "./halcyon.css";

/**
 * Halcyon — a SaaS landing page, from Iris's Templates.
 *
 * A whole page for a (made-up) on-call tool, built around one thing: a
 * horizon. Iris's `HorizonField` rises off the bottom of the hero, the
 * product sits on it, and the page closes on the same light, drawn. Every
 * other section is quiet — plain type, hairlines, one raised surface —
 * so the horizon is the one thing a visitor remembers.
 *
 * Replace the copy and the product mock with your own; the structure is
 * the category's standard (nav, hero, features, how it works, pricing,
 * FAQ, close, footer) so visitors use it without learning it. No logo
 * wall, testimonials or usage numbers — add them when you have real ones.
 *
 * No client JavaScript of its own: the mobile menu and the FAQ are native
 * <details>. The field runs WebGL over a CSS floor, and holds still under
 * reduced motion.
 */

const NAV = [
  { label: "Product", href: "#product" },
  { label: "How it works", href: "#how" },
  { label: "Pricing", href: "#pricing" },
  { label: "FAQ", href: "#faq" },
];

function Mark({ className = "size-6" }: { className?: string }) {
  // A sun on its horizon.
  return (
    <svg viewBox="0 0 24 24" aria-hidden="true" className={className}>
      <path d="M4 15a8 8 0 0 1 16 0" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
      <path d="M2 19h20" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
    </svg>
  );
}

function Label({ children }: { children: ReactNode }) {
  return <p className="text-[0.75rem] font-medium tracking-[0.12em] text-[color:var(--hc-faint)] uppercase">{children}</p>;
}

function Title({ id, children, className = "" }: { id?: string; children: ReactNode; className?: string }) {
  return (
    <h2
      id={id}
      className={`mt-4 text-[clamp(2rem,1.3rem+2.4vw,3.25rem)] leading-[1.04] font-semibold tracking-[-0.035em] text-balance ${className}`}
    >
      {children}
    </h2>
  );
}

/* ---- nav ------------------------------------------------------------ */

function Nav() {
  return (
    <header className="absolute inset-x-0 top-0 z-20">
      <nav aria-label="Main" className="mx-auto flex h-16 max-w-7xl items-center justify-between px-5 sm:px-8">
        <a href="#top" className="flex items-center gap-2 text-[1.0625rem] font-semibold tracking-[-0.01em]">
          <Mark />
          Halcyon
        </a>
        <ul className="hidden items-center gap-8 text-[0.9375rem] text-[color:var(--hc-dim)] md:flex">
          {NAV.map((n) => (
            <li key={n.href}>
              <a href={n.href} className="transition-colors duration-150 hover:text-[color:var(--hc-fg)]">
                {n.label}
              </a>
            </li>
          ))}
        </ul>
        <div className="hidden items-center gap-5 md:flex">
          <a href="#" className="text-[0.9375rem] text-[color:var(--hc-dim)] transition-colors duration-150 hover:text-[color:var(--hc-fg)]">
            Sign in
          </a>
          <a href="#pricing" className="hc-primary rounded-full px-4 py-2 text-[0.875rem] font-medium">
            Start free
          </a>
        </div>
        {/* A phone's menu, with no script: the browser's own disclosure. */}
        <details className="relative md:hidden">
          <summary className="flex size-10 cursor-pointer items-center justify-center rounded-full" aria-label="Menu">
            <svg viewBox="0 0 24 24" aria-hidden="true" className="size-5">
              <path d="M4 8h16M4 16h16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />
            </svg>
          </summary>
          <div className="hc-panel absolute right-0 mt-2 w-60 rounded-2xl p-2">
            {NAV.map((n) => (
              <a key={n.href} href={n.href} className="block rounded-xl px-3 py-2.5 text-[0.9375rem] text-[color:var(--hc-dim)] hover:text-[color:var(--hc-fg)]">
                {n.label}
              </a>
            ))}
            <a href="#pricing" className="hc-primary mt-2 block rounded-xl px-3 py-2.5 text-center text-[0.9375rem] font-medium">
              Start free
            </a>
          </div>
        </details>
      </nav>
    </header>
  );
}

/* ---- hero ------------------------------------------------------------ */

const TONIGHT = [
  { time: "02:14", what: "db-replica lag over 30s", how: "Held until 08:00", tone: "held" },
  { time: "02:31", what: "checkout 5xx above 2%", how: "Paged Idris · acknowledged in 1m 12s", tone: "paged" },
  { time: "02:33", what: "INC-482 opened", how: "Timeline started, status page drafted", tone: "auto" },
  { time: "03:02", what: "Resolved in 29 minutes", how: "One person woken, eleven slept", tone: "done" },
] as const;

const DOT: Record<(typeof TONIGHT)[number]["tone"], string> = {
  held: "bg-[color:var(--hc-faint)]",
  paged: "bg-[color:var(--hc-warn)]",
  auto: "bg-[color:var(--hc-accent)]",
  done: "bg-[color:var(--hc-fg)]",
};

function ProductWindow() {
  return (
    <div className="hc-panel overflow-hidden rounded-2xl shadow-[0_40px_120px_-40px_rgb(0_0_0/0.9)]">
      <div className="flex items-center justify-between border-b border-[color:var(--hc-line)] px-4 py-3">
        <span className="font-mono text-[0.75rem] text-[color:var(--hc-faint)]">halcyon.app / tonight</span>
        <span className="inline-flex items-center gap-2 rounded-full bg-[color:var(--hc-accent-soft)] px-2.5 py-1 text-[0.75rem] text-[color:var(--hc-accent)]">
          <span aria-hidden="true" className="size-1.5 rounded-full bg-[color:var(--hc-accent)]" />
          Calm
        </span>
      </div>
      <div className="grid grid-cols-1 md:grid-cols-[15rem_minmax(0,1fr)]">
        <aside className="hidden border-r border-[color:var(--hc-line)] p-4 md:block">
          <p className="text-[0.75rem] text-[color:var(--hc-faint)]">On call now</p>
          <ul className="mt-3 flex flex-col gap-2.5 text-[0.875rem]">
            {[
              ["Payments", "Idris"],
              ["Platform", "Maya"],
              ["Web", "Sora"],
            ].map(([team, who]) => (
              <li key={team} className="flex items-center justify-between">
                <span className="text-[color:var(--hc-dim)]">{team}</span>
                <span className="text-[color:var(--hc-fg)]">{who}</span>
              </li>
            ))}
          </ul>
          <p className="mt-6 text-[0.75rem] text-[color:var(--hc-faint)]">Quiet hours</p>
          <p className="mt-2 text-[0.875rem] text-[color:var(--hc-dim)]">23:00 – 08:00, urgent only</p>
        </aside>
        <ol className="flex flex-col p-2 sm:p-3">
          {TONIGHT.map((row) => (
            <li key={row.time} className="grid grid-cols-[3.25rem_minmax(0,1fr)] gap-3 rounded-xl px-3 py-3 sm:grid-cols-[4rem_minmax(0,1fr)]">
              <span className="font-mono text-[0.8125rem] tabular-nums text-[color:var(--hc-faint)]">{row.time}</span>
              <span className="min-w-0">
                <span className="flex items-center gap-2 text-[0.9375rem] text-[color:var(--hc-fg)]">
                  <span aria-hidden="true" className={`size-1.5 shrink-0 rounded-full ${DOT[row.tone]}`} />
                  {row.what}
                </span>
                <span className="mt-0.5 block text-[0.8125rem] text-[color:var(--hc-dim)]">{row.how}</span>
              </span>
            </li>
          ))}
        </ol>
      </div>
    </div>
  );
}

function Hero() {
  return (
    <section id="top" aria-labelledby="hero-title" className="relative isolate overflow-hidden pt-16">
      <div aria-hidden="true" className="absolute inset-0 -z-10">
        <HorizonField guardSelector="#halcyon-hero-copy" />
      </div>
      <div className="mx-auto max-w-7xl px-5 pt-[clamp(4rem,12vh,8rem)] sm:px-8">
        <div id="halcyon-hero-copy" className="relative isolate mx-auto max-w-3xl text-center">
          {/* A pool of the page's own ground under the copy, where the rim
              runs behind it — measured: without it the small type drops
              under 4.5 : 1 where the ring is brightest. */}
          <div
            aria-hidden="true"
            className="absolute -inset-x-[12%] -inset-y-[18%] -z-10 [background:radial-gradient(farthest-side,rgb(2_7_12/0.8)_50%,rgb(2_7_12/0.55)_75%,transparent)]"
          />
          <p className="inline-flex items-center gap-2 font-mono text-[0.8125rem] text-[color:var(--hc-dim)]">
            <span aria-hidden="true" className="size-1.5 rounded-full bg-[color:var(--hc-accent)]" />0 pages since 23:00
          </p>
          <h1
            id="hero-title"
            className="mt-6 text-[clamp(2.75rem,1.6rem+4.4vw,5.25rem)] leading-[0.98] font-semibold tracking-[-0.045em] text-balance"
          >
            On-call, without the 3 a.m. dread.
          </h1>
          <p className="mx-auto mt-6 max-w-[40ch] text-[clamp(1.0625rem,1rem+0.3vw,1.25rem)] leading-[1.55] text-pretty text-[color:var(--hc-dim)]">
            Halcyon wakes the one person who can fix it, holds the rest until morning, and writes the timeline while
            you work.
          </p>
          <div className="mt-9 flex flex-wrap items-center justify-center gap-x-6 gap-y-3">
            <a href="#pricing" className="hc-primary rounded-full px-6 py-3 text-[0.9375rem] font-medium">
              Start free
            </a>
            <a href="#how" className="group inline-flex items-center gap-1.5 text-[0.9375rem] font-medium text-[color:var(--hc-fg)]">
              See how it routes
              <span aria-hidden="true" className="text-[color:var(--hc-faint)] transition-transform duration-150 group-hover:translate-x-0.5">
                →
              </span>
            </a>
          </div>
        </div>
        {/* The product, standing on the horizon. */}
        <div id="product" className="mx-auto mt-[clamp(3.5rem,9vh,6rem)] max-w-5xl scroll-mt-24 pb-[clamp(3rem,8vh,5rem)]">
          <ProductWindow />
        </div>
      </div>
    </section>
  );
}

/* ---- features -------------------------------------------------------- */

function Cell({ title, body, children, wide = false }: { title: string; body: string; children: ReactNode; wide?: boolean }) {
  return (
    <li className={`hc-panel flex flex-col rounded-3xl p-6 sm:p-8 ${wide ? "lg:col-span-2" : ""}`}>
      <div className="flex min-h-[11rem] items-center justify-center" aria-hidden="true">
        {children}
      </div>
      <h3 className="mt-6 text-[1.1875rem] font-semibold tracking-[-0.015em]">{title}</h3>
      <p className="mt-2 max-w-[44ch] text-[0.9375rem] leading-[1.6] text-[color:var(--hc-dim)]">{body}</p>
    </li>
  );
}

function Chip({ children, tone = "plain" }: { children: ReactNode; tone?: "plain" | "accent" | "warn" }) {
  const tones = {
    plain: "text-[color:var(--hc-dim)] shadow-[inset_0_0_0_1px_var(--hc-line-strong)]",
    accent: "bg-[color:var(--hc-accent-soft)] text-[color:var(--hc-accent)]",
    warn: "text-[color:var(--hc-warn)] shadow-[inset_0_0_0_1px_rgb(241_184_106/0.35)]",
  };
  return <span className={`inline-flex h-8 items-center gap-2 rounded-full px-3 text-[0.8125rem] whitespace-nowrap ${tones[tone]}`}>{children}</span>;
}

function Features() {
  return (
    <section aria-labelledby="features-title" className="mx-auto max-w-7xl px-5 py-[clamp(6rem,13vw,10rem)] sm:px-8">
      <div className="max-w-2xl">
        <Label>What it does</Label>
        <Title id="features-title">Fewer people awake. Nothing missed.</Title>
      </div>
      <ul className="mt-14 grid grid-cols-1 gap-5 lg:grid-cols-3">
        <Cell
          wide
          title="One page, one person"
          body="Every alert knows which service it belongs to and who owns that service tonight. Halcyon pages them, and only them."
        >
          <div className="flex flex-wrap items-center justify-center gap-3">
            <Chip tone="warn">checkout 5xx above 2%</Chip>
            <span className="text-[color:var(--hc-faint)]">→</span>
            <Chip>Payments</Chip>
            <span className="text-[color:var(--hc-faint)]">→</span>
            <Chip tone="accent">Idris, on call</Chip>
          </div>
        </Cell>
        <Cell title="Quiet hours that hold" body="Anything that can wait, waits — grouped, deduplicated and handed over at 08:00.">
          <div className="w-full max-w-[16rem] space-y-2">
            <div className="flex items-center justify-between rounded-xl px-3 py-2.5 shadow-[inset_0_0_0_1px_var(--hc-line)]">
              <span className="text-[0.8125rem]">Hold non-urgent</span>
              <span className="flex h-5 w-9 items-center justify-end rounded-full bg-[color:var(--hc-accent)] p-0.5">
                <span className="size-4 rounded-full bg-[color:var(--hc-ink)]" />
              </span>
            </div>
            {["disk 81% on worker-3", "cert renews in 9 days"].map((a) => (
              <div key={a} className="flex items-center justify-between px-3 text-[0.8125rem] text-[color:var(--hc-faint)]">
                <span>{a}</span>
                <span className="font-mono">08:00</span>
              </div>
            ))}
          </div>
        </Cell>
        <Cell title="The timeline writes itself" body="Pages, acknowledgements, deploys and messages land in one record as they happen.">
          <ol className="w-full max-w-[16rem] space-y-2.5 border-l border-[color:var(--hc-line-strong)] pl-4 text-[0.8125rem]">
            {[
              ["02:31", "Paged Idris"],
              ["02:32", "Acknowledged"],
              ["02:40", "Rolled back v2.14"],
              ["03:02", "Resolved"],
            ].map(([t, e]) => (
              <li key={t} className="flex gap-3">
                <span className="font-mono tabular-nums text-[color:var(--hc-faint)]">{t}</span>
                <span className="text-[color:var(--hc-dim)]">{e}</span>
              </li>
            ))}
          </ol>
        </Cell>
        <Cell
          wide
          title="Hand-offs that don't drop"
          body="Rotations follow time zones and holidays. The next person gets a written hand-off, not a Slack scroll."
        >
          <div className="grid w-full max-w-md grid-cols-7 gap-1.5 text-center text-[0.75rem]">
            {["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"].map((d, i) => (
              <div key={d} className="flex flex-col gap-1.5">
                <span className="text-[color:var(--hc-faint)]">{d}</span>
                <span
                  className={`rounded-lg py-2 ${
                    i < 3 ? "bg-[color:var(--hc-accent-soft)] text-[color:var(--hc-accent)]" : "text-[color:var(--hc-dim)] shadow-[inset_0_0_0_1px_var(--hc-line)]"
                  }`}
                >
                  {i < 3 ? "Maya" : i < 5 ? "Sora" : "Idris"}
                </span>
              </div>
            ))}
          </div>
        </Cell>
      </ul>
    </section>
  );
}

/* ---- how it works ---------------------------------------------------- */

const STEPS = [
  { n: "01", title: "Connect your alerts", body: "Point your metrics, logs, uptime checks and webhooks at Halcyon. Nothing to install on your servers." },
  { n: "02", title: "Say who owns what", body: "Map services to teams and set each team's rotation and quiet hours. It takes an afternoon, once." },
  { n: "03", title: "Go to sleep", body: "Urgent pages reach the owner. Everything else waits for morning, grouped and explained." },
];

function Steps() {
  return (
    <section id="how" aria-labelledby="how-title" className="scroll-mt-24 border-t border-[color:var(--hc-line)]">
      <div className="mx-auto max-w-7xl px-5 py-[clamp(6rem,13vw,10rem)] sm:px-8">
        <div className="max-w-2xl">
          <Label>How it works</Label>
          <Title id="how-title">Set it up in an afternoon. Sleep for years.</Title>
        </div>
        <ol className="mt-14 grid grid-cols-1 gap-10 md:grid-cols-3">
          {STEPS.map((s) => (
            <li key={s.n} className="border-t border-[color:var(--hc-line-strong)] pt-6">
              <span className="font-mono text-[0.8125rem] text-[color:var(--hc-accent)]">{s.n}</span>
              <h3 className="mt-3 text-[1.1875rem] font-semibold tracking-[-0.015em]">{s.title}</h3>
              <p className="mt-2 max-w-[36ch] text-[0.9375rem] leading-[1.6] text-[color:var(--hc-dim)]">{s.body}</p>
            </li>
          ))}
        </ol>
      </div>
    </section>
  );
}

/* ---- pricing --------------------------------------------------------- */

const PLANS = [
  { name: "Starter", price: "$0", note: "up to 5 people", points: ["Routing and quiet hours", "One rotation per team", "7-day timeline history"], cta: "Start free" },
  {
    name: "Team",
    price: "$9",
    note: "per person, per month",
    points: ["Everything in Starter", "Unlimited rotations and overrides", "Written hand-offs", "Status page drafts"],
    cta: "Start a 14-day trial",
    featured: true,
  },
  { name: "Scale", price: "Talk to us", note: "for 100+ people", points: ["Everything in Team", "SSO and audit log", "Custom retention"], cta: "Book a call" },
];

function Pricing() {
  return (
    <section id="pricing" aria-labelledby="pricing-title" className="scroll-mt-24 border-t border-[color:var(--hc-line)]">
      <div className="mx-auto max-w-7xl px-5 py-[clamp(6rem,13vw,10rem)] sm:px-8">
        <div className="max-w-2xl">
          <Label>Pricing</Label>
          <Title id="pricing-title">Priced per person who might get paged.</Title>
        </div>
        <ul className="mt-14 grid grid-cols-1 gap-5 md:grid-cols-3">
          {PLANS.map((p) => (
            <li
              key={p.name}
              className={`flex flex-col rounded-3xl p-7 sm:p-8 ${p.featured ? "hc-panel" : "shadow-[inset_0_0_0_1px_var(--hc-line)]"}`}
            >
              <h3 className="text-[1.0625rem] font-semibold">{p.name}</h3>
              <p className="mt-4 flex items-baseline gap-2">
                <span className="text-[2.5rem] leading-none font-semibold tracking-[-0.04em]">{p.price}</span>
                <span className="text-[0.875rem] text-[color:var(--hc-faint)]">{p.note}</span>
              </p>
              <ul className="mt-8 flex flex-col gap-3 border-t border-[color:var(--hc-line)] pt-7 text-[0.9375rem] text-[color:var(--hc-dim)]">
                {p.points.map((pt) => (
                  <li key={pt} className="flex gap-3">
                    <svg viewBox="0 0 16 16" aria-hidden="true" className="mt-[0.3rem] size-3.5 shrink-0 text-[color:var(--hc-accent)]">
                      <path d="m3 8.5 3 3 7-7" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
                    </svg>
                    {pt}
                  </li>
                ))}
              </ul>
              <div className="mt-auto pt-10">
                <a
                  href="#"
                  className={`block rounded-full px-5 py-3 text-center text-[0.9375rem] font-medium ${
                    p.featured ? "hc-primary" : "shadow-[inset_0_0_0_1px_var(--hc-line-strong)] transition-colors duration-150 hover:bg-[color:var(--hc-raised)]"
                  }`}
                >
                  {p.cta}
                </a>
              </div>
            </li>
          ))}
        </ul>
      </div>
    </section>
  );
}

/* ---- FAQ -------------------------------------------------------------- */

const FAQ = [
  { q: "Does it replace our monitoring?", a: "No. Keep the tools that detect problems; Halcyon decides who hears about them, and when." },
  { q: "What counts as urgent?", a: "You decide per service — by severity, by customer impact, or by a rule you write. Everything else is held for the morning." },
  { q: "What if the person paged doesn't answer?", a: "It escalates to the next person on the rotation after the delay you set, and says so in the timeline." },
  { q: "Can we try it without moving everything?", a: "Yes. Route one service through Halcyon, leave the rest where they are, and move over when you trust it." },
  { q: "Where is our data kept?", a: "In the region you choose at sign-up. Timeline history is kept for as long as your plan says, then deleted." },
];

function Faq() {
  return (
    <section id="faq" aria-labelledby="faq-title" className="scroll-mt-24 border-t border-[color:var(--hc-line)]">
      <div className="mx-auto grid grid-cols-1 max-w-7xl gap-12 px-5 py-[clamp(6rem,13vw,10rem)] sm:px-8 lg:grid-cols-[minmax(0,1fr)_minmax(0,1.4fr)]">
        <div>
          <Label>FAQ</Label>
          <Title id="faq-title">Before you trust it with your nights.</Title>
        </div>
        <div className="border-t border-[color:var(--hc-line)]">
          {FAQ.map((f) => (
            <details key={f.q} className="group border-b border-[color:var(--hc-line)]">
              <summary className="flex cursor-pointer items-center justify-between gap-6 py-5 text-[1.0625rem] font-medium">
                {f.q}
                <svg viewBox="0 0 16 16" aria-hidden="true" className="hc-plus size-4 shrink-0 text-[color:var(--hc-faint)]">
                  <path d="M8 2v12M2 8h12" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
                </svg>
              </summary>
              <p className="max-w-[60ch] pb-6 text-[0.9375rem] leading-[1.65] text-[color:var(--hc-dim)]">{f.a}</p>
            </details>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ---- close & footer --------------------------------------------------- */

function Close() {
  return (
    <section aria-labelledby="close-title" className="hc-dawn border-t border-[color:var(--hc-line)]">
      <div className="mx-auto max-w-3xl px-5 py-[clamp(7rem,15vw,11rem)] text-center sm:px-8">
        <Title id="close-title">Sleep through the next incident you didn&rsquo;t need to see.</Title>
        <p className="mx-auto mt-5 max-w-[42ch] text-[1.0625rem] text-[color:var(--hc-dim)]">
          Free for up to five people. No card, no install, and your first rotation set up in minutes.
        </p>
        <a href="#pricing" className="hc-primary mt-9 inline-block rounded-full px-6 py-3 text-[0.9375rem] font-medium">
          Start free
        </a>
      </div>
    </section>
  );
}

const FOOTER = [
  { heading: "Product", links: ["Routing", "Quiet hours", "Timelines", "Pricing"] },
  { heading: "Company", links: ["About", "Careers", "Security"] },
  { heading: "Resources", links: ["Docs", "Status", "Changelog"] },
];

function Footer() {
  return (
    <footer className="border-t border-[color:var(--hc-line)]">
      <div className="mx-auto flex max-w-7xl flex-wrap justify-between gap-12 px-5 py-14 sm:px-8">
        <div className="max-w-xs">
          <a href="#top" className="flex items-center gap-2 font-semibold">
            <Mark className="size-5" />
            Halcyon
          </a>
          <p className="mt-3 text-[0.875rem] text-[color:var(--hc-faint)]">On-call for teams who&rsquo;d rather be asleep.</p>
        </div>
        <div className="flex flex-wrap gap-x-16 gap-y-8">
          {FOOTER.map((col) => (
            <div key={col.heading}>
              <p className="text-[0.8125rem] font-medium">{col.heading}</p>
              <ul className="mt-3 flex flex-col gap-2 text-[0.875rem] text-[color:var(--hc-faint)]">
                {col.links.map((l) => (
                  <li key={l}>
                    <a href="#" className="transition-colors duration-150 hover:text-[color:var(--hc-fg)]">
                      {l}
                    </a>
                  </li>
                ))}
              </ul>
            </div>
          ))}
        </div>
      </div>
      <p className="mx-auto max-w-7xl px-5 pb-10 text-[0.8125rem] text-[color:var(--hc-faint)] sm:px-8">© 2026 Halcyon</p>
    </footer>
  );
}

export function HalcyonLanding() {
  return (
    <div className="halcyon relative">
      <Nav />
      <main>
        <Hero />
        <Features />
        <Steps />
        <Pricing />
        <Faq />
        <Close />
      </main>
      <Footer />
    </div>
  );
}

More landing templates

Start with a background.

The backgrounds are the most finished corner of the catalogue. Take one and drop it under your own copy.

Browse backgrounds