A closing call to action over a city at dusk with a few windows lit. Point at "Get on board" or tab to it and the whole city switches its lights on, one window at a time; press it and a train leaves across the line.
registry/blocks/skyline-cta.tsx
"use client";
import * as React from "react";
import { Skyline } from "@/components/ui/skyline";
/* ── Content ─────────────────────────────────────────────────
* Everything you'd change to make the section yours: the
* palette, the copy and the city. The city waits at dusk with a
* few windows lit; point at "Get on board" (or tab to it) and
* the whole city switches its lights on, and pressing it sends
* a train across the line.
* ─────────────────────────────────────────────────────────── */
/** A night band in both schemes: the page's last section ends on a city with its lights coming on. */
const PALETTE = {
light: { paper: "#0b0d19", ink: "#f4f4f6", accent: "#8f9dff", tint: "#2a39a6", window: "#ffd27a" },
dark: { paper: "#06070b", ink: "#f4f4f6", accent: "#8292ff", tint: "#212d82", window: "#ffd27a" },
};
/** Dark towers, a shade off the sky, so the lit windows read as light. The sun is setting. */
const TOWERS = { light: "#1d2134", dark: "#171a27" };
const SUN = "#ff6a2b";
const HEADLINE = { before: "The next train leaves", accent: "Tuesday" };
const BODY =
"Point Milepost at your repo today and your first release rides on the next timetable. Free for teams of up to ten.";
const PRIMARY_CTA = "Get on board";
const SECONDARY_CTA = { label: "Read the timetable docs", href: "#docs" };
/** Share of the windows lit while the city waits, and once someone's boarding. */
const LIGHTS = { waiting: 0.24, boarding: 0.96 };
const CITY = { seed: 17, sun: 0.22 };
/* ── Section ─────────────────────────────────────────────── */
export function SkylineCta() {
const [boarding, setBoarding] = React.useState(false);
const [trains, setTrains] = React.useState(0);
return (
<section
style={
{
"--paper-light": PALETTE.light.paper,
"--ink-light": PALETTE.light.ink,
"--accent-light": PALETTE.light.accent,
"--tint-light": PALETTE.light.tint,
"--window-light": PALETTE.light.window,
"--towers-light": TOWERS.light,
"--paper-dark": PALETTE.dark.paper,
"--ink-dark": PALETTE.dark.ink,
"--accent-dark": PALETTE.dark.accent,
"--tint-dark": PALETTE.dark.tint,
"--window-dark": PALETTE.dark.window,
"--towers-dark": TOWERS.dark,
} as React.CSSProperties
}
className="relative overflow-hidden bg-(--paper) text-(--ink) [--accent:var(--accent-light)] [--ink:var(--ink-light)] [--paper:var(--paper-light)] [--tint:var(--tint-light)] [--towers:var(--towers-light)] [--window:var(--window-light)] dark:[--accent:var(--accent-dark)] dark:[--ink:var(--ink-dark)] dark:[--paper:var(--paper-dark)] dark:[--tint:var(--tint-dark)] dark:[--towers:var(--towers-dark)] dark:[--window:var(--window-dark)]"
>
<div className="mx-auto flex max-w-2xl flex-col items-center px-6 pt-24 text-center sm:pt-28">
<h2 className="font-serif text-4xl leading-[1.04] font-normal tracking-[-0.02em] text-balance sm:text-6xl">
{HEADLINE.before} <em className="text-(--accent)">{HEADLINE.accent}</em>
</h2>
<p className="mt-5 max-w-lg text-base leading-relaxed text-pretty opacity-70 sm:text-[17px]">{BODY}</p>
<div className="mt-8 flex flex-wrap items-center justify-center gap-x-6 gap-y-4">
<button
type="button"
onPointerEnter={(e) => e.pointerType === "mouse" && setBoarding(true)}
onPointerLeave={(e) => e.pointerType === "mouse" && setBoarding(false)}
onFocus={() => setBoarding(true)}
onBlur={() => setBoarding(false)}
onClick={() => {
setBoarding(true);
setTrains((n) => n + 1);
}}
className="inline-flex h-11 items-center gap-2 rounded-full bg-(--ink) px-6 text-sm font-medium text-(--paper) transition-[opacity,scale] duration-150 hover:opacity-90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--accent) active:scale-[0.97]"
>
{PRIMARY_CTA}
<svg viewBox="0 0 16 16" fill="none" aria-hidden className="size-3.5">
<path d="M3 8h10m-4-4 4 4-4 4" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</button>
<a
href={SECONDARY_CTA.href}
className="rounded-sm text-sm font-medium underline decoration-current/30 underline-offset-4 transition-[text-decoration-color] duration-150 hover:decoration-current focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-(--accent)"
>
{SECONDARY_CTA.label}
</a>
</div>
</div>
<Skyline
seed={CITY.seed}
sun={CITY.sun}
train
headway={0}
departures={trains}
lights={boarding ? LIGHTS.boarding : LIGHTS.waiting}
style={{ "--skyline-sun": SUN } as React.CSSProperties}
className="mt-14 h-[clamp(12rem,32svh,19rem)] text-(--towers) [--skyline-light:var(--window)] [--skyline-tint:var(--tint)]"
/>
</section>
);
}
Live preview and the components it's built with: https://keyframeui.com/blocks/cta-sections/skyline-cta