Orbit. SaaS / Developer platform landing page.

A cosmic-technical SaaS launch page whose whole layout is an orbital system. The pinned hero is a rotating planetary system in which each of five orbits holds a product feature; scrolling flies the camera inward over a streaming canvas star field, outer rings slipping off-screen until the core fills the view. Below it: a sticky concentric-ring diagram that tracks the feature you are reading, a typing deploy terminal with counting stats and drawing sparklines, two counter-rotating rings of integrations that spin faster as you scroll, pricing as four concentric rings whose arcs fill to each plan's quota, and a pinned changelog spiral that draws inward through time. Deep navy, electric cyan and warm white; system fonts, no images.

  • Orbital camera zoom
  • Streaming star field
  • Pinned planetary hero
  • Sticky ring scrollspy
  • Typing terminal
  • Counter-rotating integration rings
  • Concentric pricing rings
  • Scroll-drawn changelog spiral
Full screen

The preview is interactive — scroll inside it to run the animations.

The whole page. One file, nothing left out.

1,052 lines · 68.8 KB · imports React only. This is the exact file behind the preview above — every section, interaction and style.

  1. 01Save it as orbit-launch.tsx in any React 18+ project.
  2. 02Render <Page /> on a route of its own — it brings its own nav and footer.
  3. 03Swap the copy and tokens. Keep the DESIGN.md next to it so your AI agent follows the system.
orbit-launch.tsx
1/**
2 * Orbit — SaaS / developer-platform launch page, laid out as an orbital system.
3 *
4 * Cosmic Technical Dark: deep navy, electric cyan, warm white. No images, no
5 * web fonts, no network — the star field is a canvas, the planets are CSS
6 * spheres, every diagram is inline SVG.
7 *
8 * Motion
9 * - Hero: a pinned planetary system. Five orbits each hold a product feature;
10 * scrolling flies the camera inward (log-interpolated zoom, a streaming
11 * star field, outer rings falling off-screen) until the core fills the view.
12 * - Anatomy: a sticky ring diagram follows the feature you are reading.
13 * - Telemetry: a terminal types a deploy, counters + sparklines draw in.
14 * - Integrations: two counter-rotating rings of tools; scroll adds spin.
15 * - Pricing: plans as concentric rings whose arcs fill to their quota.
16 * - Changelog: a pinned spiral draws inward through time as you scroll.
17 *
18 * Reduced motion: no pinning, no rotation, no typing — a complete static
19 * composition (system at rest, spiral fully drawn, all changelog entries).
20 * SSR-safe: no window/document access during render.
21 */
22import { useEffect, useRef, useState, type CSSProperties, type FormEvent, type ReactNode } from "react";
23
24/* ------------------------------------------------------------------ */
25/* Data */
26/* ------------------------------------------------------------------ */
27const sv = (o: Record<string, string | number>) => o as CSSProperties;
28const clamp = (v: number, a = 0, b = 1) => Math.min(b, Math.max(a, v));
29const ease = (t: number) => t * t * (3 - 2 * t);
30
31type GlyphKey = "bolt" | "wave" | "eye" | "shield" | "layers";
32const GLYPHS: Record<GlyphKey, string> = {
33 bolt: "M13 2 4 14h6l-1 8 9-12h-6z",
34 wave: "M2 12c2-7 4-7 6 0s4 7 6 0 4-7 6 0 1 3 2 0",
35 eye: "M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12zM12 9a3 3 0 1 0 0 6 3 3 0 0 0 0-6z",
36 shield: "M12 2 4 5v6c0 5 3.5 9 8 11 4.5-2 8-6 8-11V5z",
37 layers: "M12 3 2 8l10 5 10-5zM2 13l10 5 10-5",
38};
39function Glyph({ k, size = 24 }: { k: GlyphKey; size?: number }) {
40 return (
41 <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
42 <path d={GLYPHS[k]} />
43 </svg>
44 );
45}
46
47/** Orbits listed outer → inner; the camera visits them in that order. */
48const ORBITS: {
49 name: string; short: string; r: number; speed: number; phase: number; size: number;
50 glyph: GlyphKey; tint: string; tint2: string; line: string; stat: string; body: string;
51 bullets: string[]; code: string;
52}[] = [
53 { name: "Edge Functions", short: "EDGE", r: 430, speed: 0.05, phase: 0.55, size: 50, glyph: "bolt", tint: "#7DF0FF", tint2: "#0B6C86", line: "Run code in 312 cities.", stat: "4.7 ms cold start",
54 body: "Deploy TypeScript to every point of presence with one command. Requests land on the nearest node and start in under five milliseconds.",
55 bullets: ["312 points of presence, 14 core regions", "Streaming responses and WebSockets", "Region pinning for data residency"],
56 code: "export default {\n async fetch(req, env) {\n const geo = req.cf.city;\n return Response.json({ hello: geo });\n },\n};" },
57 { name: "Realtime Sync", short: "SYNC", r: 330, speed: 0.08, phase: 2.35, size: 46, glyph: "wave", tint: "#BFE6FF", tint2: "#3556C8", line: "Every client, same state.", stat: "38 ms p99 fan-out",
58 body: "Presence, live cursors and conflict-free documents over a single socket. Subscribe to a query and the diff arrives before the user blinks.",
59 bullets: ["CRDT documents with offline merge", "Presence for 100k concurrent viewers", "Typed channels, generated from your schema"],
60 code: "const room = orbit.room(\"doc:42\");\nroom.on(\"presence\", (peers) => draw(peers));\nroom.doc.text(\"body\").insert(0, \"Hello\");" },
61 { name: "Observability", short: "TRACE", r: 240, speed: 0.12, phase: 4.1, size: 52, glyph: "eye", tint: "#FBF3E4", tint2: "#9C7B4A", line: "See every request, end to end.", stat: "1 trace, 0 config",
62 body: "Traces, logs and metrics are correlated by default. Click a slow span and jump straight to the log line, the deploy and the commit that caused it.",
63 bullets: ["Automatic distributed tracing", "30-day retention on every plan above Probe", "Alerts that open with the failing trace attached"],
64 code: "$ orbit trace --slow 400ms\n▸ POST /checkout 612ms\n ├ auth.verify 8ms\n ├ db.query 481ms ← orders_by_user\n └ queue.publish 12ms" },
65 { name: "Auth & Access", short: "AUTH", r: 160, speed: 0.19, phase: 1.2, size: 44, glyph: "shield", tint: "#7DF0FF", tint2: "#1F3E9E", line: "Passkeys to policy, one file.", stat: "SOC 2 Type II",
66 body: "Passkeys, SSO and row-level policy declared next to your code. Roles are evaluated at the edge, so a denied request never reaches your database.",
67 bullets: ["Passkeys, magic links, OIDC and SAML", "SCIM provisioning for Station and above", "Policies tested in CI with orbit policy test"],
68 code: "policy(\"orders\", {\n read: (u, row) => row.owner === u.id,\n write: (u) => u.role === \"admin\",\n});" },
69 { name: "Queues & Cron", short: "QUEUE", r: 90, speed: 0.31, phase: 5.2, size: 40, glyph: "layers", tint: "#38E1FF", tint2: "#0A4F9E", line: "Jobs that run exactly once.", stat: "0 lost messages",
70 body: "Durable queues with exactly-once delivery, retries with jitter and cron triggers that survive deploys. The innermost orbit, closest to your data.",
71 bullets: ["Exactly-once delivery with idempotency keys", "Dead-letter inspector and one-click replay", "Cron with timezone and DST awareness"],
72 code: "queue(\"emails\").process(async (job) => {\n await send(job.data);\n}, { retries: 5, backoff: \"jitter\" });" },
73];
74
75/** Camera zoom keyframes. Hold windows sit at the chapter centres. */
76const CH_C = (c: number) => 0.17 + c * 0.14;
77const Z_KEYS: [number, number][] = (() => {
78 const zs = [1, 1.3, 1.79, 2.69, 4.78, 9.5];
79 const k: [number, number][] = [[0, 1]];
80 zs.forEach((z, c) => { k.push([CH_C(c) - 0.035, z]); k.push([CH_C(c) + 0.035, z]); });
81 k.push([1, 11]);
82 return k;
83})();
84function zAt(p: number) {
85 const k = Z_KEYS;
86 if (p <= k[0][0]) return k[0][1];
87 for (let i = 1; i < k.length; i++) {
88 if (p <= k[i][0]) {
89 const [p0, z0] = k[i - 1]; const [p1, z1] = k[i];
90 const u = ease((p - p0) / (p1 - p0 || 1));
91 return Math.exp(Math.log(z0) + (Math.log(z1) - Math.log(z0)) * u);
92 }
93 }
94 return k[k.length - 1][1];
95}
96const CHAPTERS = [
97 ...ORBITS.map((o, i) => ({ k: `ORBIT 0${i + 1} / 05`, t: o.name, b: o.line + " " + o.stat + ".", g: o.glyph })),
98 { k: "CORE", t: "One deploy command", b: "Everything above resolves to a single line: orbit deploy. Welcome to the centre of the system.", g: "bolt" as GlyphKey },
99];
100
101const INTEGRATIONS: { n: string; d: string; c: string; ring: 0 | 1 }[] = [
102 { n: "Marrow", d: "Postgres-compatible serverless database. Branch it per pull request.", c: "#38E1FF", ring: 0 },
103 { n: "Kestrel", d: "Streaming event bus. Two-way sync with Orbit queues.", c: "#BFE6FF", ring: 0 },
104 { n: "Tessera", d: "Enterprise SSO directory. SAML and SCIM, ready in minutes.", c: "#FBF3E4", ring: 0 },
105 { n: "Pylon", d: "Global CDN. Purge from a deploy hook.", c: "#7DF0FF", ring: 0 },
106 { n: "Quill", d: "Transactional email with typed templates.", c: "#9FB4FF", ring: 0 },
107 { n: "Harbor", d: "Container registry. Pull images straight into edge workers.", c: "#38E1FF", ring: 0 },
108 { n: "Sable", d: "Secrets manager. Rotate keys without a redeploy.", c: "#FBF3E4", ring: 0 },
109 { n: "Ledger", d: "Payments and invoicing with webhooks signed at the edge.", c: "#BFE6FF", ring: 0 },
110 { n: "Northstar", d: "Product analytics streamed from request logs.", c: "#7DF0FF", ring: 1 },
111 { n: "Relay", d: "Team chat alerts with the failing trace attached.", c: "#9FB4FF", ring: 1 },
112 { n: "Cinder", d: "CI runner. Preview environments per commit.", c: "#FBF3E4", ring: 1 },
113 { n: "Fathom", d: "Log search with a SQL-like query language.", c: "#38E1FF", ring: 1 },
114 { n: "Lattice", d: "Feature flags evaluated at the edge in microseconds.", c: "#BFE6FF", ring: 1 },
115 { n: "Vellum", d: "Docs hosting with versioned API references.", c: "#7DF0FF", ring: 1 },
116];
117
118const PLANS = [
119 { n: "Probe", sub: "For side projects and first light.", m: 0, a: 0, q: 22, feats: ["100k requests / month", "3 regions, community support", "1 project, 7-day logs"] },
120 { n: "Satellite", sub: "For small teams shipping weekly.", m: 39, a: 31, q: 48, feats: ["5M requests / month", "14 regions, email support", "10 projects, 14-day traces"] },
121 { n: "Station", sub: "For products that cannot go down.", m: 199, a: 159, q: 76, feats: ["100M requests / month", "312 PoPs, SSO and audit log", "99.95% SLA, unlimited projects"] },
122 { n: "Deep Space", sub: "For regulated, planet-scale workloads.", m: -1, a: -1, q: 100, feats: ["Dedicated cells, private links", "99.99% SLA, named engineer", "SOC 2, HIPAA, regional residency"] },
123];
124
125const CHANGELOG = [
126 { v: "4.2", d: "Oct 2026", t: "Perihelion", b: "Region pinning for edge functions and cold starts below five milliseconds on the new V9 isolate pool.", f: 0.04 },
127 { v: "4.1", d: "Aug 2026", t: "Passkey-first auth", b: "Passkeys become the default sign-in. SCIM provisioning ships for Station and above.", f: 0.17 },
128 { v: "4.0", d: "Jun 2026", t: "Event horizon", b: "A rewritten scheduler. Queues reach general availability with exactly-once delivery.", f: 0.3 },
129 { v: "3.8", d: "Apr 2026", t: "Trace to log", b: "Spans link to log lines and deploys. Thirty-day retention becomes the default.", f: 0.43 },
130 { v: "3.5", d: "Jan 2026", t: "Presence v2", b: "Rebuilt presence channel scales to 100k viewers. CRDT documents enter beta.", f: 0.55 },
131 { v: "3.0", d: "Sep 2025", t: "Marrow bindings", b: "Multi-region Postgres bindings with automatic read replicas near your users.", f: 0.67 },
132 { v: "2.0", d: "Mar 2025", t: "Edge runtime", b: "The edge runtime goes public in fourteen regions. The first thousand teams arrive in a week.", f: 0.8 },
133 { v: "0.1", d: "Nov 2023", t: "First light", b: "A command line tool, a single region and a very long list of ideas.", f: 0.93 },
134];
135
136/* Archimedean spiral drawn newest (outside) → oldest (centre) */
137const SPIRAL = (() => {
138 const N = 600, turns = 2.7, th0 = turns * Math.PI * 2, r0 = 22, r1 = 352;
139 const xs: number[] = [], ys: number[] = [], cum: number[] = [0];
140 for (let i = 0; i <= N; i++) {
141 const th = th0 * (1 - i / N);
142 const r = r0 + (r1 - r0) * (th / th0);
143 const a = th - 0.9;
144 xs.push(r * Math.cos(a)); ys.push(r * Math.sin(a));
145 if (i) cum.push(cum[i - 1] + Math.hypot(xs[i] - xs[i - 1], ys[i] - ys[i - 1]));
146 }
147 const total = cum[N];
148 const at = (f: number) => {
149 const target = clamp(f) * total;
150 let lo = 0, hi = N;
151 while (lo < hi - 1) { const m = (lo + hi) >> 1; if (cum[m] <= target) lo = m; else hi = m; }
152 return { x: xs[lo], y: ys[lo] };
153 };
154 const d = xs.map((x, i) => `${i ? "L" : "M"}${x.toFixed(1)} ${ys[i].toFixed(1)}`).join("");
155 return { d, at };
156})();
157
158/* ------------------------------------------------------------------ */
159/* Small hooks / pieces */
160/* ------------------------------------------------------------------ */
161function useInView<T extends Element>(opts?: { once?: boolean; margin?: string }) {
162 const ref = useRef<T>(null);
163 const [on, setOn] = useState(false);
164 useEffect(() => {
165 const el = ref.current; if (!el) return;
166 if (typeof IntersectionObserver === "undefined") { setOn(true); return; }
167 const io = new IntersectionObserver(([e]) => {
168 if (e.isIntersecting) { setOn(true); if (opts?.once !== false) io.disconnect(); } else if (opts?.once === false) setOn(false);
169 }, { rootMargin: opts?.margin ?? "0px 0px -12% 0px", threshold: 0.01 });
170 io.observe(el);
171 return () => io.disconnect();
172 }, []); // eslint-disable-line react-hooks/exhaustive-deps
173 return [ref, on] as const;
174}
175
176const prefersReduced = () => typeof window !== "undefined" && !!window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
177
178function Count({ to, dec = 0, pre = "", suf = "" }: { to: number; dec?: number; pre?: string; suf?: string }) {
179 const [ref, on] = useInView<HTMLSpanElement>();
180 const [v, setV] = useState(0);
181 useEffect(() => {
182 if (!on) return;
183 if (prefersReduced()) { setV(to); return; }
184 let raf = 0; const t0 = performance.now();
185 const f = (n: number) => { const u = clamp((n - t0) / 1600); setV(to * (1 - Math.pow(1 - u, 4))); if (u < 1) raf = requestAnimationFrame(f); };
186 raf = requestAnimationFrame(f);
187 return () => cancelAnimationFrame(raf);
188 }, [on, to]);
189 return <span ref={ref}>{pre}{(on ? v : 0).toFixed(dec)}{suf}</span>;
190}
191
192function Spark({ pts, on }: { pts: number[]; on: boolean }) {
193 const w = 160, h = 44;
194 const d = pts.map((p, i) => `${i ? "L" : "M"}${(i / (pts.length - 1)) * w} ${h - 4 - p * (h - 8)}`).join("");
195 return (
196 <svg className={"orb-spark" + (on ? " on" : "")} viewBox={`0 0 ${w} ${h}`} preserveAspectRatio="none" aria-hidden="true">
197 <path d={`${d}L${w} ${h}L0 ${h}Z`} className="orb-spark-fill" />
198 <path d={d} pathLength={1} className="orb-spark-line" />
199 </svg>
200 );
201}
202
203function Eyebrow({ children }: { children: ReactNode }) {
204 return <div className="orb-eyebrow"><i />{children}</div>;
205}
206
207const NOOP = (e: { preventDefault: () => void }) => e.preventDefault();
208
209/* ------------------------------------------------------------------ */
210/* Page */
211/* ------------------------------------------------------------------ */
212const NAV = [
213 ["orb-top", "Launch"], ["orb-anatomy", "Anatomy"], ["orb-telemetry", "Telemetry"], ["orb-integrations", "Integrations"],
214 ["orb-pricing", "Pricing"], ["orb-changelog", "Changelog"], ["orb-touchdown", "Touchdown"],
215] as const;
216
217export function Page() {
218 const rootRef = useRef<HTMLDivElement>(null);
219 const heroRef = useRef<HTMLElement | null>(null);
220 const starRef = useRef<HTMLCanvasElement>(null);
221 const sysRef = useRef<HTMLDivElement>(null);
222 const svgRef = useRef<SVGSVGElement>(null);
223 const ringRefs = useRef<(SVGEllipseElement | null)[]>([]);
224 const dashRefs = useRef<(SVGEllipseElement | null)[]>([]);
225 const plRefs = useRef<(HTMLDivElement | null)[]>([]);
226 const coreRef = useRef<HTMLDivElement>(null);
227 const copyRef = useRef<HTMLDivElement>(null);
228 const hudRef = useRef<HTMLSpanElement>(null);
229 const barRef = useRef<HTMLSpanElement>(null);
230 const hintRef = useRef<HTMLDivElement>(null);
231 const intRef = useRef<HTMLElement | null>(null);
232 const intBoxRef = useRef<HTMLDivElement>(null);
233 const nodeRefs = useRef<(HTMLButtonElement | null)[]>([]);
234 const chgRef = useRef<HTMLElement | null>(null);
235 const cometRef = useRef<SVGGElement>(null);
236 const spiralRef = useRef<SVGPathElement>(null);
237 const secRefs = useRef<(HTMLElement | null)[]>([]);
238
239 const [ch, setCh] = useState(0);
240 const [feat, setFeat] = useState(0);
241 const [sel, setSel] = useState(-1);
242 const [plan, setPlan] = useState(1);
243 const [annual, setAnnual] = useState(true);
244 const [shown, setShown] = useState(31);
245 const [reached, setReached] = useState(0);
246 const [rail, setRail] = useState(0);
247 const [navSolid, setNavSolid] = useState(false);
248 const [done, setDone] = useState(false);
249 const [email, setEmail] = useState("");
250
251 const [pricingRef, pricingOn] = useInView<HTMLDivElement>();
252 const [termRef, termOn] = useInView<HTMLDivElement>();
253 const [teleRef, teleOn] = useInView<HTMLDivElement>();
254 const [typed, setTyped] = useState(0);
255
256 const TERM = [
257 { c: "cmd", t: "$ orbit init acme-api --runtime edge" },
258 { c: "ok", t: "✓ linked project acme-api (fra, iad, sin)" },
259 { c: "cmd", t: "$ orbit deploy" },
260 { c: "dim", t: "▸ bundling 38 modules ............ 412 ms" },
261 { c: "dim", t: "▸ shipping to 312 points of presence" },
262 { c: "ok", t: "✓ live at https://acme-api.orbit.run" },
263 { c: "dim", t: " p50 11 ms · p99 38 ms · 0 errors" },
264 ];
265 const TERM_LEN = TERM.reduce((a, l) => a + l.t.length + 1, 0);
266
267 /* terminal typing */
268 useEffect(() => {
269 if (!termOn) return;
270 if (prefersReduced()) { setTyped(TERM_LEN); return; }
271 let n = 0;
272 const id = window.setInterval(() => { n += 2; setTyped(n); if (n >= TERM_LEN) window.clearInterval(id); }, 24);
273 return () => window.clearInterval(id);
274 }, [termOn, TERM_LEN]);
275
276 /* pricing number tween */
277 useEffect(() => {
278 const P = PLANS[plan]; const target = P.m < 0 ? 0 : annual ? P.a : P.m;
279 if (prefersReduced()) { setShown(target); return; }
280 let raf = 0; const from = shown; const t0 = performance.now();
281 const f = (n: number) => { const u = clamp((n - t0) / 520); setShown(Math.round(from + (target - from) * (1 - Math.pow(1 - u, 3)))); if (u < 1) raf = requestAnimationFrame(f); };
282 raf = requestAnimationFrame(f);
283 return () => cancelAnimationFrame(raf);
284 }, [plan, annual]); // eslint-disable-line react-hooks/exhaustive-deps
285
286 /* feature scrollspy */
287 useEffect(() => {
288 const root = rootRef.current; if (!root) return;
289 const els = Array.from(root.querySelectorAll<HTMLElement>("[data-feat]"));
290 if (typeof IntersectionObserver === "undefined") return;
291 const io = new IntersectionObserver((es) => {
292 es.forEach((e) => { if (e.isIntersecting) setFeat(Number((e.target as HTMLElement).dataset.feat)); });
293 }, { rootMargin: "-45% 0px -45% 0px" });
294 els.forEach((e) => io.observe(e));
295 return () => io.disconnect();
296 }, []);
297
298 /* generic reveals */
299 useEffect(() => {
300 const root = rootRef.current; if (!root) return;
301 root.classList.add("orb-js");
302 const els = Array.from(root.querySelectorAll<HTMLElement>("[data-rv]"));
303 if (typeof IntersectionObserver === "undefined") { els.forEach((e) => e.classList.add("in")); return; }
304 const io = new IntersectionObserver((es) => es.forEach((e) => { if (e.isIntersecting) { e.target.classList.add("in"); io.unobserve(e.target); } }), { rootMargin: "0px 0px -10% 0px" });
305 els.forEach((e) => io.observe(e));
306 return () => io.disconnect();
307 }, []);
308
309 /* master scroll / frame loop */
310 useEffect(() => {
311 const root = rootRef.current, hero = heroRef.current, sys = sysRef.current, svg = svgRef.current, cvs = starRef.current;
312 if (!root || !hero || !sys || !svg || !cvs) return;
313 const rm = prefersReduced();
314 const ctx = cvs.getContext("2d");
315 let vw = 0, vh = 0, S = 0, TILT = 0.34, cy = 0, dpr = 1, intW = 600;
316 const ROT = (-14 * Math.PI) / 180, cosA = Math.cos(ROT), sinA = Math.sin(ROT);
317 const stars = Array.from({ length: 300 }, (_, i) => {
318 const a = (i * 2.399963) % (Math.PI * 2), rr = 0.05 + ((i * 7919) % 1000) / 1000 * 1.1;
319 return { x: Math.cos(a) * rr, y: Math.sin(a) * rr, d: ((i * 104729) % 1000) / 1000, s: 0.5 + ((i * 31) % 10) / 9, w: (i % 5) * 1.3 };
320 });
321 let chCur = -1, featRail = -1, solid = false, reachedCur = -1;
322
323 const resize = () => {
324 vw = window.innerWidth; vh = window.innerHeight; dpr = Math.min(window.devicePixelRatio || 1, 2);
325 S = Math.min(vw * 0.98, 1500);
326 TILT = vw < 700 ? 0.52 : 0.34;
327 cy = vw < 700 ? vh * 0.63 : vh * 0.58;
328 cvs.width = Math.round(vw * dpr); cvs.height = Math.round(vh * dpr);
329 sys.style.top = cy + "px";
330 svg.style.width = S + "px"; svg.style.height = S + "px";
331 ORBITS.forEach((o, i) => {
332 [ringRefs.current[i], dashRefs.current[i]].forEach((el) => el && el.setAttribute("ry", String(o.r * TILT)));
333 });
334 if (intBoxRef.current) intW = intBoxRef.current.clientWidth;
335 };
336
337 const drawStars = (p: number, t: number) => {
338 if (!ctx) return;
339 ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
340 ctx.clearRect(0, 0, vw, vh);
341 const cx = vw / 2, spread = Math.max(vw, vh) * 0.5;
342 for (const s of stars) {
343 let zz = (s.d - p * 2.4 - t * 0.004) % 1; if (zz < 0) zz += 1;
344 zz = zz * 0.95 + 0.05;
345 const x = cx + (s.x * spread) / (zz * 1.6), y = cy + (s.y * spread) / (zz * 1.6);
346 if (x < -4 || x > vw + 4 || y < -4 || y > vh + 4) continue;
347 const k = 1 - zz, tw = 0.75 + 0.25 * Math.sin(t * 1.4 + s.w);
348 ctx.globalAlpha = clamp(0.25 + k * 0.9) * tw;
349 ctx.fillStyle = s.d > 0.8 ? "#FBF3E4" : s.d > 0.4 ? "#BFE6FF" : "#7DF0FF";
350 const sz = s.s * (0.6 + k * 1.6);
351 ctx.fillRect(x, y, sz, sz);
352 }
353 ctx.globalAlpha = 1;
354 };
355
356 const renderHero = (p: number, t: number) => {
357 const z = zAt(p), s = S / 1000;
358 svg.style.transform = `translate(-50%,-50%) scale(${z})`;
359 const cc = clamp(Math.round((p - 0.17) / 0.14), 0, 5);
360 if (cc !== chCur) { chCur = cc; setCh(cc); }
361 ORBITS.forEach((o, i) => {
362 const rx = o.r * s * z;
363 const fade = clamp(1 - (rx - vw * 0.62) / (vw * 0.4));
364 const ring = ringRefs.current[i], dash = dashRefs.current[i];
365 if (ring) ring.style.opacity = String(fade * (i === cc ? 1 : 0.42));
366 if (dash) dash.style.opacity = String(fade * (i === cc ? 0.9 : 0.25));
367 const el = plRefs.current[i]; if (!el) return;
368 const th = o.phase + t * o.speed;
369 const ux = o.r * Math.cos(th), uy = o.r * TILT * Math.sin(th);
370 const x = (ux * cosA - uy * sinA) * s * z, y = (ux * sinA + uy * cosA) * s * z;
371 const depth = Math.sin(th);
372 const ps = Math.min(1.55, Math.pow(z, 0.32)) * (0.62 + 0.38 * (depth + 1) / 2) * (vw < 700 ? 0.8 : 1);
373 const edge = clamp(1 - (Math.abs(x) - vw * 0.6) / (vw * 0.15));
374 el.style.transform = `translate3d(${x.toFixed(1)}px,${y.toFixed(1)}px,0) translate(-50%,-50%) scale(${ps.toFixed(3)})`;
375 el.style.opacity = String(edge * (depth < 0 ? 0.62 : 1) * (i === cc || p < 0.12 ? 1 : 0.75));
376 el.style.zIndex = depth > 0 ? "6" : "2";
377 });
378 const core = coreRef.current;
379 if (core) {
380 const cs = 1 + (z - 1) * 1.05;
381 core.style.transform = `translate(-50%,-50%) scale(${cs.toFixed(3)})`;
382 core.style.opacity = String(1 - clamp((p - 0.955) / 0.045));
383 }
384 if (copyRef.current) {
385 copyRef.current.style.opacity = String(1 - clamp(p / 0.08));
386 copyRef.current.style.transform = `translate3d(0,${(-p * 380).toFixed(1)}px,0)`;
387 }
388 if (hintRef.current) hintRef.current.style.opacity = String(1 - clamp(p / 0.03));
389 if (hudRef.current) hudRef.current.textContent = z.toFixed(2) + "×";
390 if (barRef.current) barRef.current.style.transform = `scaleY(${p.toFixed(3)})`;
391 drawStars(p, t);
392 };
393
394 const renderInt = (t: number, sy: number) => {
395 const box = intBoxRef.current; if (!box) return;
396 const outer = INTEGRATIONS.filter((n) => n.ring === 0).length, inner = INTEGRATIONS.length - outer;
397 let io = 0, ii = 0;
398 INTEGRATIONS.forEach((n, i) => {
399 const el = nodeRefs.current[i]; if (!el) return;
400 const isOuter = n.ring === 0;
401 const idx = isOuter ? io++ : ii++;
402 const cnt = isOuter ? outer : inner;
403 const R = intW * (isOuter ? 0.42 : 0.25);
404 const dir = isOuter ? 1 : -1;
405 const a = (idx / cnt) * Math.PI * 2 + dir * (t * 0.045 + sy * 0.00045) + (isOuter ? 0 : 0.5);
406 el.style.transform = `translate(-50%,-50%) translate3d(${(Math.cos(a) * R).toFixed(1)}px,${(Math.sin(a) * R).toFixed(1)}px,0)`;
407 });
408 };
409
410 const renderSpiral = (p: number) => {
411 const draw = clamp(p * 1.12 - 0.02);
412 if (spiralRef.current) spiralRef.current.style.strokeDashoffset = String(1 - draw);
413 const head = SPIRAL.at(draw);
414 if (cometRef.current) cometRef.current.setAttribute("transform", `translate(${head.x.toFixed(1)} ${head.y.toFixed(1)})`);
415 let r = 0; CHANGELOG.forEach((c, i) => { if (draw >= c.f) r = i; });
416 if (r !== reachedCur) { reachedCur = r; setReached(r); }
417 };
418
419 const rect = (el: HTMLElement) => el.getBoundingClientRect();
420 const frame = (now: number) => {
421 const t = (now - t0) / 1000;
422 const sy = window.scrollY;
423 const hr = rect(hero);
424 if (hr.bottom > -50 && hr.top < vh) renderHero(clamp(-hr.top / (hr.height - vh)), t);
425 const ir = intRef.current && rect(intRef.current);
426 if (ir && ir.bottom > -100 && ir.top < vh + 100) renderInt(t, sy);
427 const cr = chgRef.current && rect(chgRef.current);
428 if (cr && cr.bottom > -50 && cr.top < vh) renderSpiral(clamp(-cr.top / (cr.height - vh)));
429 const sol = sy > vh * 0.4; if (sol !== solid) { solid = sol; setNavSolid(sol); }
430 let act = 0; secRefs.current.forEach((el, i) => { if (el && rect(el).top <= vh * 0.5) act = i; });
431 if (act !== featRail) { featRail = act; setRail(act); }
432 raf = requestAnimationFrame(frame);
433 };
434
435 let raf = 0; const t0 = performance.now();
436 resize();
437 if (rm) {
438 renderHero(0, 3); renderInt(2, 0); renderSpiral(1);
439 const onR = () => { resize(); renderHero(0, 3); renderInt(2, 0); };
440 window.addEventListener("resize", onR);
441 return () => window.removeEventListener("resize", onR);
442 }
443 raf = requestAnimationFrame(frame);
444 window.addEventListener("resize", resize);
445 return () => { cancelAnimationFrame(raf); window.removeEventListener("resize", resize); };
446 }, []);
447
448 const go = (id: string) => (e: { preventDefault: () => void }) => {
449 e.preventDefault();
450 const el = document.getElementById(id); if (!el) return;
451 el.scrollIntoView({ behavior: prefersReduced() ? "auto" : "smooth", block: "start" });
452 };
453
454 const P = PLANS[plan];
455 const price = P.m < 0 ? "Custom" : "$" + shown;
456 const activeInt = sel >= 0 ? INTEGRATIONS[sel] : null;
457 const termText = (() => {
458 let left = typed; return TERM.map((l) => { const n = clamp(left, 0, l.t.length); left -= l.t.length + 1; return { c: l.c, t: l.t.slice(0, n) }; });
459 })();
460 const typingDone = typed >= TERM_LEN;
461
462 return (
463 <div ref={rootRef} className="orb" id="orb-top">
464 <style>{CSS}</style>
465
466 {/* ───────────── nav ───────────── */}
467 <header className={"orb-nav" + (navSolid ? " solid" : "")}>
468 <a href="#orb-top" className="orb-brand" onClick={go("orb-top")} aria-label="Orbit home">
469 <svg width="26" height="26" viewBox="0 0 26 26" aria-hidden="true">
470 <ellipse cx="13" cy="13" rx="11.5" ry="4.8" transform="rotate(-24 13 13)" fill="none" stroke="#38E1FF" strokeWidth="1.4" />
471 <circle cx="13" cy="13" r="3.6" fill="#FBF3E4" />
472 <circle cx="22" cy="8.4" r="1.9" fill="#38E1FF" />
473 </svg>
474 <span>Orbit</span>
475 </a>
476 <nav className="orb-links" aria-label="Sections">
477 {NAV.slice(1, 6).map(([id, l]) => <a key={id} href={"#" + id} onClick={go(id)}>{l}</a>)}
478 </nav>
479 <div className="orb-nav-cta">
480 <a href="#" onClick={NOOP} className="orb-link-quiet">Sign in</a>
481 <a href="#orb-touchdown" onClick={go("orb-touchdown")} className="orb-btn sm">Start building</a>
482 </div>
483 </header>
484
485 <nav className="orb-rail" aria-label="Depth">
486 {NAV.map(([id, l], i) => (
487 <a key={id} href={"#" + id} onClick={go(id)} className={i === rail ? "on" : ""} aria-label={l}><span>{l}</span><i /></a>
488 ))}
489 </nav>
490
491 {/* ───────────── hero: the system ───────────── */}
492 <section className="orb-hero" ref={(el) => { heroRef.current = el; secRefs.current[0] = el; }} aria-label="Orbit, the orbital platform">
493 <div className="orb-stick">
494 <div className="orb-nebula" aria-hidden="true" />
495 <canvas ref={starRef} className="orb-stars" aria-hidden="true" />
496 <div className="orb-sys" ref={sysRef} aria-hidden="true">
497 <svg aria-hidden="true" focusable="false" ref={svgRef} className="orb-rings" viewBox="-500 -500 1000 1000">
498 <g transform="rotate(-14)" fill="none">
499 {ORBITS.map((o, i) => (
500 <g key={o.name}>
501 <ellipse ref={(el) => { ringRefs.current[i] = el; }} rx={o.r} ry={o.r * 0.34} stroke="#38E1FF" strokeWidth="1.2" vectorEffect="non-scaling-stroke" />
502 <ellipse ref={(el) => { dashRefs.current[i] = el; }} rx={o.r + 7} ry={(o.r + 7) * 0.34} stroke="#BFE6FF" strokeWidth="1" strokeDasharray="2 9" vectorEffect="non-scaling-stroke" />
503 </g>
504 ))}
505 </g>
506 </svg>
507 <div className="orb-core" ref={coreRef}><i /><b /></div>
508 {ORBITS.map((o, i) => (
509 <div key={o.name} className="orb-pl" ref={(el) => { plRefs.current[i] = el; }}>
510 <div className="orb-pl-body" style={sv({ "--t1": o.tint, "--t2": o.tint2, width: o.size, height: o.size })}>
511 {i === 2 && <em className="orb-pl-ring" />}
512 <Glyph k={o.glyph} size={o.size * 0.46} />
513 </div>
514 <span className="orb-pl-tag" style={{ marginTop: o.size / 2 + 6 }}>{o.short}</span>
515 </div>
516 ))}
517 </div>
518
519 <div className="orb-copy" ref={copyRef}>
520 <div className="orb-pill"><i /> Orbit 4.2 is live · sub-5 ms cold starts</div>
521 <h1>Ship at <span>orbital</span> velocity.</h1>
522 <p>The developer platform where edge compute, realtime, auth and queues share one orbit. Deploy once, land everywhere.</p>
523 <div className="orb-cta">
524 <a href="#orb-touchdown" onClick={go("orb-touchdown")} className="orb-btn">Start building free</a>
525 <a href="#orb-anatomy" onClick={go("orb-anatomy")} className="orb-btn ghost">Explore the system</a>
526 </div>
527 </div>
528
529 <div className="orb-hint" ref={hintRef}><span /> Scroll to fly inward</div>
530
531 <div className="orb-hud">
532 <div className="orb-hud-cap">
533 {CHAPTERS.map((c, i) => (
534 <div key={c.t} className={"orb-hud-card" + (i === ch ? " on" : "")} aria-hidden={i !== ch}>
535 <div className="k">{c.k}</div>
536 <h2><Glyph k={c.g} size={20} />{c.t}</h2>
537 <p>{c.b}</p>
538 </div>
539 ))}
540 </div>
541 <div className="orb-hud-meter" aria-hidden="true">
542 <span className="lab">ZOOM</span>
543 <span className="val" ref={hudRef}>1.00×</span>
544 <span className="trk"><span ref={barRef} /></span>
545 </div>
546 </div>
547 </div>
548 </section>
549
550 {/* ───────────── anatomy ───────────── */}
551 <section className="orb-sec orb-anat" id="orb-anatomy" ref={(el) => { secRefs.current[1] = el; }}>
552 <div className="orb-wrap orb-anat-grid">
553 <div className="orb-anat-side">
554 <Eyebrow>Anatomy of the system</Eyebrow>
555 <h2 className="orb-h2">Five orbits. <em>One gravity.</em></h2>
556 <p className="orb-lede">Each capability sits in its own orbit but pulls on the same core: one config, one deploy, one bill. Scroll the system.</p>
557 <svg className="orb-anat-svg" viewBox="-200 -200 400 400" role="img" aria-label={`Orbit diagram, ${ORBITS[feat].name} highlighted`}>
558 <defs>
559 <radialGradient id="orbg-core"><stop offset="0" stopColor="#FBF3E4" /><stop offset=".4" stopColor="#7DF0FF" /><stop offset="1" stopColor="#38E1FF" stopOpacity="0" /></radialGradient>
560 </defs>
561 <circle r="34" fill="url(#orbg-core)" opacity=".9" />
562 {[...ORBITS].reverse().map((o, k) => {
563 const i = ORBITS.length - 1 - k, r = 48 + k * 33, on = i === feat;
564 return (
565 <g key={o.name} className={"orb-anat-ring" + (on ? " on" : "")} style={{ animationDuration: 26 - k * 3 + "s" }}>
566 <circle r={r} fill="none" stroke={on ? "#38E1FF" : "#7aa5d8"} strokeOpacity={on ? 1 : 0.28} strokeWidth={on ? 1.6 : 1} strokeDasharray={on ? "none" : "3 6"} />
567 <circle cx={r} cy="0" r={on ? 7 : 4} fill={on ? "#FBF3E4" : "#8CA2C6"} />
568 </g>
569 );
570 })}
571 </svg>
572 </div>
573 <div className="orb-anat-list">
574 {ORBITS.map((o, i) => (
575 <article key={o.name} data-feat={i} data-rv className={"orb-feat" + (i === feat ? " on" : "")}>
576 <div className="orb-feat-k"><Glyph k={o.glyph} size={18} /> ORBIT 0{i + 1} · {o.stat}</div>
577 <h3>{o.name}</h3>
578 <p>{o.body}</p>
579 <ul>{o.bullets.map((b) => <li key={b}>{b}</li>)}</ul>
580 <pre className="orb-code" aria-label={o.name + " example"}><code>{o.code}</code></pre>
581 </article>
582 ))}
583 </div>
584 </div>
585 </section>
586
587 {/* ───────────── telemetry ───────────── */}
588 <section className="orb-sec orb-tele" id="orb-telemetry" ref={(el) => { secRefs.current[2] = el; }}>
589 <div className="orb-wrap">
590 <div className="orb-tele-head" data-rv>
591 <Eyebrow>Launch sequence</Eyebrow>
592 <h2 className="orb-h2">T-minus <em>one command.</em></h2>
593 </div>
594 <div className="orb-tele-grid">
595 <div className="orb-term" ref={termRef} data-rv>
596 <div className="orb-term-bar"><i /><i /><i /><span>~/acme-api — orbit</span></div>
597 <div className="orb-term-body" aria-label="Terminal session deploying to Orbit">
598 {termText.map((l, i) => (
599 <div key={i} className={"ln " + l.c}>{l.t || " "}{!typingDone && l.t.length > 0 && l.t.length < TERM[i].t.length && <b className="orb-caret" />}</div>
600 ))}
601 {typingDone && <div className="ln cmd">$ <b className="orb-caret" /></div>}
602 </div>
603 </div>
604 <div className="orb-stats" ref={teleRef}>
605 {[
606 { l: "Global p99 latency", to: 38, suf: " ms", dec: 0, pts: [0.7, 0.62, 0.66, 0.5, 0.44, 0.48, 0.36, 0.3, 0.28, 0.22] },
607 { l: "Points of presence", to: 312, suf: "", dec: 0, pts: [0.1, 0.14, 0.2, 0.3, 0.38, 0.5, 0.62, 0.74, 0.86, 0.95] },
608 { l: "Uptime, trailing 12 months", to: 99.995, suf: "%", dec: 3, pts: [0.8, 0.84, 0.82, 0.88, 0.9, 0.9, 0.94, 0.95, 0.96, 0.97] },
609 { l: "Requests served per day", to: 2.1, suf: "B", dec: 1, pts: [0.15, 0.2, 0.28, 0.3, 0.42, 0.52, 0.6, 0.7, 0.83, 0.92] },
610 ].map((s, i) => (
611 <div key={s.l} className="orb-stat" data-rv style={sv({ "--d": i * 0.08 + "s" })}>
612 <div className="orb-stat-l">{s.l}</div>
613 <div className="orb-stat-v"><Count to={s.to} dec={s.dec} suf={s.suf} /></div>
614 <Spark pts={s.pts} on={teleOn} />
615 </div>
616 ))}
617 </div>
618 </div>
619 </div>
620 </section>
621
622 {/* ───────────── integrations ring ───────────── */}
623 <section className="orb-sec orb-int" id="orb-integrations" ref={(el) => { intRef.current = el; secRefs.current[3] = el; }}>
624 <div className="orb-wrap orb-center">
625 <div data-rv>
626 <Eyebrow>Integrations</Eyebrow>
627 <h2 className="orb-h2">Plays well with <em>everything in range.</em></h2>
628 </div>
629 </div>
630 <div className="orb-ring-box" ref={intBoxRef}>
631 <svg className="orb-ring-svg" viewBox="0 0 100 100" aria-hidden="true">
632 <circle cx="50" cy="50" r="42" fill="none" stroke="#38E1FF" strokeOpacity=".28" strokeWidth="1" vectorEffect="non-scaling-stroke" />
633 <circle cx="50" cy="50" r="25" fill="none" stroke="#BFE6FF" strokeOpacity=".22" strokeWidth="1" strokeDasharray="1 4" vectorEffect="non-scaling-stroke" />
634 <circle cx="50" cy="50" r="11" fill="none" stroke="#38E1FF" strokeOpacity=".18" strokeWidth="1" vectorEffect="non-scaling-stroke" />
635 </svg>
636 <div className="orb-ring-mid" aria-live="polite">
637 {activeInt ? (
638 <><div className="orb-ring-n" style={{ color: activeInt.c }}>{activeInt.n}</div><p>{activeInt.d}</p></>
639 ) : (
640 <><div className="orb-ring-n">{INTEGRATIONS.length}+ native</div><p>Hover or focus a node. Hundreds more through webhooks and the open connector spec.</p></>
641 )}
642 </div>
643 {INTEGRATIONS.map((n, i) => (
644 <button key={n.n} type="button" className={"orb-node" + (n.ring ? " in2" : "") + (i === sel ? " on" : "")}
645 ref={(el) => { nodeRefs.current[i] = el; }}
646 style={sv({ "--c": n.c })}
647 onMouseEnter={() => setSel(i)} onFocus={() => setSel(i)} onMouseLeave={() => setSel(-1)} onBlur={() => setSel(-1)}
648 aria-label={n.n + ": " + n.d}>
649 <span>{n.n.slice(0, 1)}</span><em>{n.n}</em>
650 </button>
651 ))}
652 </div>
653 </section>
654
655 {/* ───────────── pricing rings ───────────── */}
656 <section className="orb-sec orb-price" id="orb-pricing" ref={(el) => { secRefs.current[4] = el; }}>
657 <div className="orb-wrap">
658 <div className="orb-tele-head" data-rv>
659 <Eyebrow>Pricing</Eyebrow>
660 <h2 className="orb-h2">Pick your <em>altitude.</em></h2>
661 </div>
662 <div className="orb-price-grid">
663 <div className={"orb-price-fig" + (pricingOn ? " on" : "")} ref={pricingRef}>
664 <svg viewBox="0 0 520 520" role="img" aria-label={`Pricing rings, ${P.n} selected`}>
665 <defs>
666 <filter id="orbg-glow" x="-30%" y="-30%" width="160%" height="160%"><feGaussianBlur stdDeviation="5" result="b" /><feMerge><feMergeNode in="b" /><feMergeNode in="SourceGraphic" /></feMerge></filter>
667 </defs>
668 {PLANS.map((pl, i) => {
669 const r = 72 + i * 46, on = i === plan;
670 return (
671 <g key={pl.n} className={"orb-pr" + (on ? " on" : "")} onClick={() => setPlan(i)}>
672 <circle cx="260" cy="260" r={r} className="orb-pr-track" fill="none" strokeWidth="22" />
673 <circle cx="260" cy="260" r={r} className="orb-pr-arc" fill="none" strokeWidth="22" strokeLinecap="butt" pathLength={100}
674 transform="rotate(-90 260 260)" style={sv({ "--q": pl.q })} filter={on ? "url(#orbg-glow)" : undefined} />
675 <circle cx="260" cy="260" r={r} fill="none" stroke="transparent" strokeWidth="40" style={{ cursor: "pointer" }} />
676 <text x="260" y={260 - r + 4} textAnchor="middle" className="orb-pr-lab">{pl.n.toUpperCase()}</text>
677 </g>
678 );
679 })}
680 <text x="260" y="256" textAnchor="middle" className="orb-pr-price">{price}</text>
681 <text x="260" y="278" textAnchor="middle" className="orb-pr-per">{P.m < 0 ? "talk to us" : P.m === 0 ? "forever" : "per month" + (annual ? ", billed yearly" : "")}</text>
682 </svg>
683 </div>
684 <div className="orb-price-info" data-rv>
685 <div className="orb-seg" role="group" aria-label="Billing period">
686 <button type="button" aria-pressed={!annual} className={!annual ? "on" : ""} onClick={() => setAnnual(false)}>Monthly</button>
687 <button type="button" aria-pressed={annual} className={annual ? "on" : ""} onClick={() => setAnnual(true)}>Yearly <small>−20%</small></button>
688 </div>
689 <div className="orb-plans" role="tablist" aria-label="Plans">
690 {PLANS.map((pl, i) => (
691 <button key={pl.n} type="button" role="tab" aria-selected={i === plan} className={i === plan ? "on" : ""} onClick={() => setPlan(i)}>{pl.n}</button>
692 ))}
693 </div>
694 <div className="orb-plan-card" key={plan}>
695 <h3>{P.n}</h3>
696 <p className="sub">{P.sub}</p>
697 <ul>{P.feats.map((f) => <li key={f}>{f}</li>)}</ul>
698 <a href="#orb-touchdown" onClick={go("orb-touchdown")} className="orb-btn">{P.m < 0 ? "Talk to an engineer" : P.m === 0 ? "Launch for free" : "Start " + P.n}</a>
699 </div>
700 </div>
701 </div>
702 </div>
703 </section>
704
705 {/* ───────────── changelog spiral ───────────── */}
706 <section className="orb-chg" id="orb-changelog" ref={(el) => { chgRef.current = el; secRefs.current[5] = el; }}>
707 <div className="orb-chg-stick">
708 <div className="orb-chg-fig">
709 <svg viewBox="-400 -400 800 800" role="img" aria-label="Spiral timeline of Orbit releases">
710 <defs>
711 <filter id="orbg-glow2" x="-50%" y="-50%" width="200%" height="200%"><feGaussianBlur stdDeviation="6" result="b" /><feMerge><feMergeNode in="b" /><feMergeNode in="SourceGraphic" /></feMerge></filter>
712 </defs>
713 <path d={SPIRAL.d} fill="none" stroke="#7aa5d8" strokeOpacity=".16" strokeWidth="1.5" strokeDasharray="2 7" />
714 <path ref={spiralRef} d={SPIRAL.d} pathLength={1} fill="none" stroke="#38E1FF" strokeWidth="2.4" strokeLinecap="round" className="orb-spiral" filter="url(#orbg-glow2)" />
715 {CHANGELOG.map((c, i) => {
716 const pt = SPIRAL.at(c.f), on = i <= reached, cur = i === reached;
717 return (
718 <g key={c.v} transform={`translate(${pt.x.toFixed(1)} ${pt.y.toFixed(1)})`} className={"orb-spn" + (on ? " on" : "") + (cur ? " cur" : "")}>
719 <circle r={cur ? 9 : 5.5} />
720 <text y={-15} textAnchor="middle">v{c.v}</text>
721 </g>
722 );
723 })}
724 <g ref={cometRef} className="orb-comet"><circle r="14" fill="#38E1FF" opacity=".25" /><circle r="5" fill="#FBF3E4" /></g>
725 </svg>
726 </div>
727 <div className="orb-chg-info">
728 <Eyebrow>Changelog</Eyebrow>
729 <h2 className="orb-h2">Fall back <em>through time.</em></h2>
730 <div className="orb-chg-cards">
731 {CHANGELOG.map((c, i) => (
732 <div key={c.v} className={"orb-chg-card" + (i === reached ? " on" : "")}>
733 <div className="meta"><b>v{c.v}</b><span>{c.d}</span></div>
734 <h3>{c.t}</h3>
735 <p>{c.b}</p>
736 </div>
737 ))}
738 </div>
739 <div className="orb-chg-count" aria-hidden="true">{String(reached + 1).padStart(2, "0")} / {String(CHANGELOG.length).padStart(2, "0")}</div>
740 </div>
741 </div>
742 </section>
743
744 {/* ───────────── touchdown ───────────── */}
745 <section className="orb-sec orb-land" id="orb-touchdown" ref={(el) => { secRefs.current[6] = el; }}>
746 <div className="orb-land-sun" aria-hidden="true"><i /><i /><i /></div>
747 <div className="orb-wrap orb-center">
748 <div data-rv>
749 <Eyebrow>Touchdown</Eyebrow>
750 <h2 className="orb-h3big">Land your next <em>release.</em></h2>
751 <p className="orb-lede">Free forever for side projects. No card, no countdown. The first deploy takes about ninety seconds.</p>
752 </div>
753 <form className="orb-form" data-rv onSubmit={(e: FormEvent) => { e.preventDefault(); if (email.includes("@")) setDone(true); }}>
754 {done ? (
755 <div className="orb-done" role="status"><span /> You are on the manifest. Check {email} for your launch key.</div>
756 ) : (
757 <>
758 <label className="orb-sr" htmlFor="orb-email">Work email</label>
759 <input id="orb-email" type="email" required placeholder="you@company.dev" value={email} onChange={(e) => setEmail(e.target.value)} />
760 <button type="submit" className="orb-btn">Get my launch key</button>
761 </>
762 )}
763 </form>
764 </div>
765 <footer className="orb-foot">
766 <div className="orb-wrap orb-foot-grid">
767 <div>
768 <div className="orb-brand static"><svg width="22" height="22" viewBox="0 0 26 26" aria-hidden="true"><ellipse cx="13" cy="13" rx="11.5" ry="4.8" transform="rotate(-24 13 13)" fill="none" stroke="#38E1FF" strokeWidth="1.4" /><circle cx="13" cy="13" r="3.6" fill="#FBF3E4" /></svg><span>Orbit</span></div>
769 <p className="orb-foot-note">Kepler Works, Inc. Built for teams who would rather ship than configure.</p>
770 </div>
771 {[
772 ["Platform", ["Edge Functions", "Realtime Sync", "Observability", "Auth & Access", "Queues & Cron"]],
773 ["Company", ["About", "Careers", "Customers", "Press kit"]],
774 ["Resources", ["Documentation", "API reference", "Status", "Security"]],
775 ].map(([h, ls]) => (
776 <div key={h as string}>
777 <h4>{h as string}</h4>
778 <ul>{(ls as string[]).map((l) => <li key={l}><a href="#" onClick={NOOP}>{l}</a></li>)}</ul>
779 </div>
780 ))}
781 </div>
782 <div className="orb-wrap orb-legal"><span>© 2026 Kepler Works, Inc.</span><span>Privacy · Terms · DPA</span></div>
783 </footer>
784 </section>
785 </div>
786 );
787}
788
789export default Page;
790
791/* ------------------------------------------------------------------ */
792/* Styles */
793/* ------------------------------------------------------------------ */
794const CSS = `
795.orb{--orb-bg:#040A18;--orb-bg2:#071230;--orb-panel:#0A1A3A;--orb-line:rgba(125,190,255,.16);--orb-cyan:#38E1FF;--orb-cyan2:#7DF0FF;--orb-ice:#BFE6FF;--orb-warm:#FBF3E4;--orb-mute:#8CA2C6;--orb-indigo:#5B6BFF;
796--orb-sans:"Space Grotesk","Inter",ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
797--orb-mono:"JetBrains Mono","SF Mono",ui-monospace,Menlo,Consolas,"Liberation Mono",monospace;
798--orb-out:cubic-bezier(.22,1,.36,1);
799position:relative;box-sizing:border-box;width:100%;background:var(--orb-bg);color:var(--orb-warm);font-family:var(--orb-sans);font-size:16px;line-height:1.55;font-weight:400;text-align:left;overflow-x:clip;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
800.orb *,.orb *::before,.orb *::after{box-sizing:border-box}
801:where(.orb) :where(h1,h2,h3,h4,p,ul,li,pre,code,figure,form){margin:0;padding:0;font:inherit;letter-spacing:inherit}
802:where(.orb) :where(ul){list-style:none}
803:where(.orb) :where(a){color:inherit;text-decoration:none}
804:where(.orb) :where(button,input){font:inherit;color:inherit;margin:0;border:0;background:none;padding:0;appearance:none;-webkit-appearance:none;border-radius:0}
805:where(.orb) :where(button){cursor:pointer}
806.orb :focus-visible{outline:2px solid var(--orb-cyan);outline-offset:3px;border-radius:6px}
807:where(.orb) :where(svg){display:block;max-width:100%}
808.orb .orb-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
809
810.orb-wrap{width:100%;max-width:1240px;margin:0 auto;padding:0 40px}
811.orb-center{text-align:center}
812.orb-sec{position:relative;padding:150px 0}
813.orb-eyebrow{display:inline-flex;align-items:center;gap:10px;font-family:var(--orb-mono);font-size:12px;letter-spacing:.2em;text-transform:uppercase;color:var(--orb-cyan);margin-bottom:22px}
814.orb-eyebrow i{width:26px;height:1px;background:var(--orb-cyan);box-shadow:0 0 10px var(--orb-cyan)}
815.orb-h2{font-size:clamp(2.2rem,5vw,4.2rem);line-height:1.04;letter-spacing:-.035em;font-weight:600;text-wrap:balance}
816.orb-h2 em,.orb-h3big em{font-style:normal;background:linear-gradient(100deg,var(--orb-cyan2),var(--orb-cyan) 45%,#8da2ff);-webkit-background-clip:text;background-clip:text;color:transparent}
817.orb-h3big{font-size:clamp(2.8rem,8vw,7rem);line-height:.98;letter-spacing:-.045em;font-weight:600;text-wrap:balance}
818.orb-lede{max-width:34em;margin-top:22px;font-size:1.15rem;line-height:1.6;color:var(--orb-mute)}
819.orb-center .orb-lede{margin-left:auto;margin-right:auto}
820
821.orb-btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;height:52px;padding:0 28px;border-radius:999px;background:var(--orb-cyan);color:#021018;font-weight:600;font-size:15px;letter-spacing:.005em;box-shadow:0 0 0 1px rgba(255,255,255,.2) inset,0 10px 40px -8px rgba(56,225,255,.6);transition:transform .35s var(--orb-out),box-shadow .35s,background .2s}
822.orb-btn:hover{transform:translateY(-2px);background:var(--orb-cyan2);box-shadow:0 0 0 1px rgba(255,255,255,.3) inset,0 16px 50px -8px rgba(56,225,255,.8)}
823.orb-btn.ghost{background:rgba(125,190,255,.06);color:var(--orb-warm);box-shadow:0 0 0 1px rgba(160,210,255,.28) inset}
824.orb-btn.ghost:hover{background:rgba(125,190,255,.14)}
825.orb-btn.sm{height:38px;padding:0 18px;font-size:14px}
826
827/* nav */
828.orb-nav{position:fixed;z-index:50;top:0;left:0;right:0;display:flex;align-items:center;justify-content:space-between;gap:20px;padding:16px 40px;transition:background .4s,backdrop-filter .4s,border-color .4s;border-bottom:1px solid transparent}
829.orb-nav.solid{background:rgba(4,10,24,.72);backdrop-filter:blur(16px) saturate(1.4);-webkit-backdrop-filter:blur(16px) saturate(1.4);border-color:var(--orb-line)}
830.orb-brand{display:inline-flex;align-items:center;gap:10px;font-weight:600;font-size:20px;letter-spacing:-.02em}
831.orb-links{display:flex;gap:30px;font-size:14px;color:var(--orb-ice)}
832.orb-links a{opacity:.72;transition:opacity .2s}.orb-links a:hover{opacity:1}
833.orb-nav-cta{display:flex;align-items:center;gap:20px}
834.orb-link-quiet{font-size:14px;color:var(--orb-ice);opacity:.8}
835.orb-rail{position:fixed;z-index:40;right:22px;top:50%;transform:translateY(-50%);display:flex;flex-direction:column;gap:14px;align-items:flex-end}
836.orb-rail a{display:flex;align-items:center;gap:12px;font-family:var(--orb-mono);font-size:10px;letter-spacing:.16em;text-transform:uppercase;color:var(--orb-mute)}
837.orb-rail span{opacity:0;transform:translateX(6px);transition:opacity .3s,transform .3s var(--orb-out)}
838.orb-rail a:hover span,.orb-rail a.on span,.orb-rail a:focus-visible span{opacity:1;transform:none}
839.orb-rail i{width:7px;height:7px;border-radius:50%;border:1px solid var(--orb-mute);transition:all .3s}
840.orb-rail a.on{color:var(--orb-cyan)}.orb-rail a.on i{background:var(--orb-cyan);border-color:var(--orb-cyan);box-shadow:0 0 12px var(--orb-cyan);transform:scale(1.35)}
841
842/* hero */
843.orb-hero{position:relative;height:640vh}
844.orb-stick{position:sticky;top:0;height:100vh;overflow:hidden;background:radial-gradient(120% 90% at 50% 62%,#0B1D4A 0%,#06112B 42%,#030815 100%)}
845.orb-nebula{position:absolute;inset:0;background:radial-gradient(40% 30% at 18% 24%,rgba(91,107,255,.22),transparent 70%),radial-gradient(34% 28% at 84% 70%,rgba(56,225,255,.16),transparent 70%),radial-gradient(26% 20% at 70% 14%,rgba(251,243,228,.06),transparent 70%)}
846.orb-stars{position:absolute;inset:0;width:100%;height:100%}
847.orb-sys{position:absolute;left:50%;top:58%;width:0;height:0}
848.orb .orb-rings{position:absolute;left:0;top:0;transform:translate(-50%,-50%);overflow:visible;max-width:none}
849.orb-core{position:absolute;left:0;top:0;width:84px;height:84px;border-radius:50%;z-index:4;transform:translate(-50%,-50%)}
850.orb-core i{position:absolute;inset:0;border-radius:50%;background:radial-gradient(circle at 42% 38%,#fff 0%,#FBF3E4 22%,#8FF3FF 52%,#1CB7DD 82%,#0A6E9A 100%);box-shadow:0 0 30px 6px rgba(56,225,255,.45)}
851.orb-core b{position:absolute;inset:-110%;border-radius:50%;background:radial-gradient(circle,rgba(125,240,255,.4),rgba(56,225,255,.12) 38%,transparent 66%);animation:orbPulse 5s ease-in-out infinite}
852@keyframes orbPulse{50%{transform:scale(1.16);opacity:.7}}
853.orb-pl{position:absolute;left:0;top:0;width:0;height:0;will-change:transform,opacity}
854.orb-pl-body{position:absolute;left:0;top:0;transform:translate(-50%,-50%);border-radius:50%;display:grid;place-items:center;color:#021018;
855background:radial-gradient(circle at 34% 28%,#fff 0,var(--t1) 28%,var(--t2) 100%);box-shadow:inset -8px -10px 18px rgba(2,8,24,.55),inset 5px 5px 10px rgba(255,255,255,.35),0 0 30px -4px var(--t1)}
856.orb-pl-ring{position:absolute;left:50%;top:50%;width:190%;height:52%;border-radius:50%;border:2px solid rgba(251,243,228,.7);transform:translate(-50%,-50%) rotate(-22deg);box-shadow:0 0 14px rgba(251,243,228,.35)}
857.orb-pl-tag{position:absolute;left:0;top:0;transform:translateX(-50%);white-space:nowrap;font-family:var(--orb-mono);font-size:10px;letter-spacing:.2em;color:var(--orb-ice);text-shadow:0 0 8px #040A18,0 0 3px #040A18}
858.orb-copy{position:absolute;left:0;right:0;top:calc(var(--orb-navh,0px) + 12vh);padding:0 24px;text-align:center;will-change:transform,opacity;pointer-events:none;z-index:10}
859.orb-copy>*{pointer-events:auto}
860.orb-pill{display:inline-flex;align-items:center;gap:10px;padding:7px 16px;border-radius:999px;border:1px solid rgba(160,210,255,.25);background:rgba(10,26,58,.55);backdrop-filter:blur(8px);font-size:13px;color:var(--orb-ice)}
861.orb-pill i{width:7px;height:7px;border-radius:50%;background:var(--orb-cyan);box-shadow:0 0 10px var(--orb-cyan);animation:orbBlink 2s infinite}
862@keyframes orbBlink{50%{opacity:.35}}
863.orb-copy h1{margin:22px auto 0;max-width:11em;font-size:clamp(2.7rem,8.4vw,7.6rem);line-height:.95;letter-spacing:-.05em;font-weight:600;text-wrap:balance;text-shadow:0 4px 60px rgba(4,10,24,.8)}
864.orb-copy h1 span{background:linear-gradient(100deg,#fff,var(--orb-cyan2) 40%,var(--orb-cyan));-webkit-background-clip:text;background-clip:text;color:transparent}
865.orb-copy p{max-width:36em;margin:22px auto 0;font-size:clamp(1rem,1.5vw,1.25rem);color:var(--orb-ice);opacity:.85}
866.orb-cta{display:flex;gap:14px;justify-content:center;flex-wrap:wrap;margin-top:32px}
867.orb-hint{position:absolute;left:50%;bottom:26px;transform:translateX(-50%);display:flex;align-items:center;gap:12px;font-family:var(--orb-mono);font-size:11px;letter-spacing:.2em;text-transform:uppercase;color:var(--orb-mute);white-space:nowrap}
868.orb-hint span{width:1px;height:28px;background:linear-gradient(var(--orb-cyan),transparent);animation:orbDrop 2s infinite}
869@keyframes orbDrop{0%{transform:scaleY(0);transform-origin:top}50%{transform:scaleY(1);transform-origin:top}51%{transform-origin:bottom}100%{transform:scaleY(0);transform-origin:bottom}}
870.orb-hud{position:absolute;left:0;right:0;bottom:0;padding:0 40px 38px;display:flex;justify-content:space-between;align-items:flex-end;gap:24px;pointer-events:none}
871.orb-hud-cap{position:relative;width:min(430px,100%);height:150px}
872.orb-hud-card{position:absolute;left:0;bottom:0;width:100%;padding:20px 22px;border-radius:18px;border:1px solid rgba(160,210,255,.2);background:rgba(6,16,40,.66);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);opacity:0;transform:translateY(14px);transition:opacity .5s var(--orb-out),transform .6s var(--orb-out)}
873.orb-hud-card.on{opacity:1;transform:none}
874.orb-hud-card .k{font-family:var(--orb-mono);font-size:11px;letter-spacing:.2em;color:var(--orb-cyan)}
875.orb-hud-card h2{display:flex;align-items:center;gap:10px;margin-top:8px;font-size:1.45rem;font-weight:600;letter-spacing:-.02em}
876.orb-hud-card h2 svg{color:var(--orb-cyan)}
877.orb-hud-card p{margin-top:6px;font-size:.92rem;line-height:1.5;color:var(--orb-mute)}
878.orb-hud-meter{display:grid;grid-template-columns:auto auto;grid-template-rows:auto 1fr;gap:4px 14px;align-items:end;font-family:var(--orb-mono);font-size:11px;letter-spacing:.14em;color:var(--orb-mute);height:96px}
879.orb-hud-meter .val{font-size:22px;color:var(--orb-warm);letter-spacing:0;text-align:right;font-variant-numeric:tabular-nums}
880.orb-hud-meter .trk{grid-row:1/3;grid-column:3;width:2px;height:100%;background:rgba(160,210,255,.2);position:relative;overflow:hidden}
881.orb-hud-meter .trk span{position:absolute;inset:0;background:var(--orb-cyan);box-shadow:0 0 10px var(--orb-cyan);transform:scaleY(0);transform-origin:top}
882
883/* reveals */
884.orb.orb-js [data-rv]{opacity:0;transform:translate3d(0,34px,0);transition:opacity .9s var(--orb-out) var(--d,0s),transform .9s var(--orb-out) var(--d,0s)}
885.orb.orb-js [data-rv].in{opacity:1;transform:none}
886
887/* anatomy */
888.orb-anat{background:linear-gradient(180deg,#030815,var(--orb-bg) 18%)}
889.orb-anat-grid{display:grid;grid-template-columns:minmax(0,5fr) minmax(0,6fr);gap:70px;align-items:start}
890.orb-anat-side{position:sticky;top:12vh}
891.orb-anat-svg{width:min(430px,100%);margin-top:36px}
892.orb-anat-ring{transform-box:view-box;transform-origin:0 0;animation:orbSpin linear infinite}
893.orb-anat-ring:nth-of-type(even){animation-direction:reverse}
894@keyframes orbSpin{to{transform:rotate(360deg)}}
895.orb-anat-ring circle{transition:stroke .5s,stroke-opacity .5s,r .5s}
896.orb-anat-list{display:flex;flex-direction:column;gap:18vh;padding:10vh 0 6vh}
897.orb-feat{padding:36px;border-radius:24px;border:1px solid var(--orb-line);background:linear-gradient(160deg,rgba(14,32,72,.55),rgba(7,18,48,.35));transition:border-color .5s,box-shadow .5s,background .5s}
898.orb-feat.on{border-color:rgba(56,225,255,.5);box-shadow:0 0 0 1px rgba(56,225,255,.12),0 30px 80px -30px rgba(56,225,255,.35)}
899.orb-feat-k{display:flex;align-items:center;gap:10px;font-family:var(--orb-mono);font-size:11px;letter-spacing:.16em;color:var(--orb-cyan)}
900.orb-feat h3{margin-top:14px;font-size:clamp(1.8rem,3vw,2.6rem);line-height:1.05;letter-spacing:-.03em;font-weight:600}
901.orb-feat p{margin-top:14px;color:var(--orb-mute);font-size:1.05rem}
902.orb-feat ul{margin-top:20px;display:grid;gap:9px}
903.orb-feat li{position:relative;padding-left:22px;color:var(--orb-ice);font-size:.98rem}
904.orb-feat li::before{content:"";position:absolute;left:0;top:.62em;width:9px;height:9px;border-radius:50%;border:1.5px solid var(--orb-cyan)}
905.orb-code{margin-top:24px;padding:18px 20px;border-radius:14px;background:#030a1b;border:1px solid var(--orb-line);font-family:var(--orb-mono);font-size:12.5px;line-height:1.7;color:var(--orb-ice);overflow-x:auto;white-space:pre}
906.orb-code code{font:inherit}
907
908/* telemetry */
909.orb-tele{background:radial-gradient(70% 60% at 80% 20%,rgba(56,225,255,.07),transparent 70%),var(--orb-bg)}
910.orb-tele-head{margin-bottom:56px}
911.orb-tele-grid{display:grid;grid-template-columns:minmax(0,7fr) minmax(0,5fr);gap:28px}
912.orb-term{border-radius:20px;overflow:hidden;border:1px solid var(--orb-line);background:#030a1b;box-shadow:0 40px 100px -40px rgba(56,225,255,.3)}
913.orb-term-bar{display:flex;align-items:center;gap:8px;padding:14px 18px;border-bottom:1px solid var(--orb-line);font-family:var(--orb-mono);font-size:12px;color:var(--orb-mute)}
914.orb-term-bar i{width:11px;height:11px;border-radius:50%;background:#1b2c52}
915.orb-term-bar span{margin-left:10px}
916.orb-term-body{padding:24px 24px 30px;font-family:var(--orb-mono);font-size:clamp(11.5px,1.3vw,14px);line-height:2;min-height:320px;overflow-x:auto}
917.orb-term-body .ln{white-space:pre}
918.orb-term-body .cmd{color:var(--orb-warm)}.orb-term-body .ok{color:var(--orb-cyan)}.orb-term-body .dim{color:var(--orb-mute)}
919.orb-caret{display:inline-block;width:.6em;height:1.1em;vertical-align:-.18em;margin-left:2px;background:var(--orb-cyan);animation:orbBlink 1s steps(2) infinite}
920.orb-stats{display:grid;grid-template-columns:1fr 1fr;gap:16px}
921.orb-stat{padding:22px 22px 14px;border-radius:20px;border:1px solid var(--orb-line);background:linear-gradient(160deg,rgba(14,32,72,.5),rgba(7,18,48,.3));display:flex;flex-direction:column;overflow:hidden}
922.orb-stat-l{font-size:12.5px;color:var(--orb-mute);min-height:2.6em}
923.orb-stat-v{margin-top:6px;font-size:clamp(1.9rem,3vw,2.7rem);font-weight:600;letter-spacing:-.04em;font-variant-numeric:tabular-nums;color:var(--orb-warm)}
924.orb-spark{width:calc(100% + 44px);margin:12px -22px 0;height:44px}
925.orb-spark-line{fill:none;stroke:var(--orb-cyan);stroke-width:2;vector-effect:non-scaling-stroke;stroke-dasharray:1;stroke-dashoffset:1;transition:stroke-dashoffset 1.8s var(--orb-out) .3s}
926.orb-spark-fill{fill:rgba(56,225,255,.1);opacity:0;transition:opacity 1.2s 1s}
927.orb-spark.on .orb-spark-line{stroke-dashoffset:0}.orb-spark.on .orb-spark-fill{opacity:1}
928
929/* integrations */
930.orb-int{padding-bottom:120px;background:radial-gradient(60% 50% at 50% 70%,rgba(91,107,255,.14),transparent 70%),var(--orb-bg);overflow:hidden}
931.orb-ring-box{position:relative;width:min(760px,calc(100vw - 20px));aspect-ratio:1;margin:30px auto 0}
932.orb-ring-svg{position:absolute;inset:0;width:100%;height:100%}
933.orb-ring-mid{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:30%;text-align:center}
934.orb-ring-n{font-size:clamp(1rem,2.2vw,1.6rem);font-weight:600;letter-spacing:-.02em;color:var(--orb-warm)}
935.orb-ring-mid p{margin-top:6px;font-size:clamp(.66rem,1.15vw,.86rem);line-height:1.4;color:var(--orb-mute)}
936.orb-node{position:absolute;left:50%;top:50%;width:62px;height:62px;border-radius:18px;display:grid;place-items:center;border:1px solid rgba(160,210,255,.4);background:linear-gradient(150deg,#16346f,#0a1c48);color:var(--c);box-shadow:0 10px 30px -10px rgba(0,0,0,.7);will-change:transform;transition:border-color .3s,box-shadow .3s,background .3s}
937.orb-node.in2{width:48px;height:48px;border-radius:14px}
938.orb-node span{font-size:22px;font-weight:700;letter-spacing:-.04em}
939.orb-node.in2 span{font-size:17px}
940.orb-node em{position:absolute;top:calc(100% + 8px);left:50%;transform:translateX(-50%);font-style:normal;font-family:var(--orb-mono);font-size:10px;letter-spacing:.08em;color:var(--orb-ice);opacity:.7;white-space:nowrap}
941.orb-node:hover,.orb-node.on{border-color:var(--c);box-shadow:0 0 30px -4px var(--c);background:linear-gradient(150deg,#12307a,#0a1a44);z-index:3}
942
943/* pricing */
944.orb-price{background:linear-gradient(180deg,var(--orb-bg),#050d22 50%,var(--orb-bg))}
945.orb-price-grid{display:grid;grid-template-columns:minmax(0,6fr) minmax(0,5fr);gap:60px;align-items:center}
946.orb-price-fig svg{width:min(560px,100%);margin:0 auto;overflow:visible}
947.orb-pr-track{stroke:rgba(125,190,255,.07)}
948.orb-pr-arc{stroke:#6f93c9;stroke-opacity:.32;stroke-dasharray:0 100;transition:stroke-dasharray 1.6s var(--orb-out),stroke .4s,stroke-opacity .4s}
949.orb-price-fig.on .orb-pr-arc{stroke-dasharray:var(--q) 100}
950.orb-pr.on .orb-pr-arc{stroke:var(--orb-cyan);stroke-opacity:1}
951.orb-pr{cursor:pointer}
952.orb-pr:hover .orb-pr-arc{stroke-opacity:.8}
953.orb-pr-lab{font-family:var(--orb-mono);font-size:9.5px;letter-spacing:.2em;fill:#021018;font-weight:700;pointer-events:none}
954.orb-pr:not(.on) .orb-pr-lab{fill:var(--orb-ice)}
955.orb-pr-price{font-size:40px;font-weight:600;letter-spacing:-.04em;fill:var(--orb-warm)}
956.orb-pr-per{font-family:var(--orb-mono);font-size:10px;letter-spacing:.1em;fill:var(--orb-mute)}
957.orb-seg{display:inline-flex;padding:4px;border-radius:999px;border:1px solid var(--orb-line);background:rgba(10,26,58,.5)}
958.orb-seg button{height:36px;padding:0 18px;border-radius:999px;font-size:14px;color:var(--orb-mute);transition:background .3s,color .3s}
959.orb-seg button.on{background:var(--orb-cyan);color:#021018;font-weight:600}
960.orb-seg small{font-family:var(--orb-mono);font-size:11px;margin-left:4px}
961.orb-plans{display:flex;gap:8px;flex-wrap:wrap;margin:20px 0}
962.orb-plans button{height:38px;padding:0 16px;border-radius:12px;border:1px solid var(--orb-line);font-size:14px;color:var(--orb-ice);transition:all .3s}
963.orb-plans button.on{border-color:var(--orb-cyan);color:var(--orb-cyan);background:rgba(56,225,255,.08)}
964.orb-plan-card{padding:32px;border-radius:24px;border:1px solid rgba(56,225,255,.35);background:linear-gradient(160deg,rgba(14,32,72,.65),rgba(7,18,48,.4));animation:orbIn .6s var(--orb-out)}
965@keyframes orbIn{from{opacity:0;transform:translateY(12px)}}
966.orb-plan-card h3{font-size:2rem;font-weight:600;letter-spacing:-.03em}
967.orb-plan-card .sub{margin-top:6px;color:var(--orb-mute)}
968.orb-plan-card ul{margin:22px 0 28px;display:grid;gap:11px}
969.orb-plan-card li{position:relative;padding-left:26px;color:var(--orb-ice)}
970.orb-plan-card li::before{content:"";position:absolute;left:3px;top:.45em;width:6px;height:11px;border:solid var(--orb-cyan);border-width:0 2px 2px 0;transform:rotate(40deg)}
971
972/* changelog */
973.orb-chg{position:relative;height:480vh;background:radial-gradient(60% 50% at 30% 50%,rgba(56,225,255,.07),transparent 70%),var(--orb-bg)}
974.orb-chg-stick{position:sticky;top:0;height:100vh;max-width:1240px;margin:0 auto;padding:80px 40px 40px;display:grid;grid-template-columns:minmax(0,1fr) minmax(0,.85fr);gap:50px;align-items:center}
975.orb-chg-fig svg{width:min(78vh,100%);margin:0 auto;overflow:visible}
976.orb-spiral{stroke-dasharray:1;stroke-dashoffset:1}
977.orb-spn circle{fill:var(--orb-bg);stroke:#4d6ea6;stroke-width:1.5;transition:all .5s}
978.orb-spn text{font-family:var(--orb-mono);font-size:15px;fill:#6c85b0;paint-order:stroke;stroke:#040A18;stroke-width:5px;transition:fill .5s}
979.orb-spn.on circle{stroke:var(--orb-cyan);fill:var(--orb-cyan)}.orb-spn.on text{fill:var(--orb-ice)}
980.orb-spn.cur circle{fill:var(--orb-warm);stroke:var(--orb-cyan);stroke-width:3}.orb-spn.cur text{fill:var(--orb-warm);font-weight:700}
981.orb-chg-cards{position:relative;display:grid;margin-top:34px}
982.orb-chg-card{grid-area:1/1;padding:28px;border-radius:22px;border:1px solid rgba(56,225,255,.35);background:linear-gradient(160deg,rgba(14,32,72,.65),rgba(7,18,48,.4));opacity:0;transform:translateY(16px);transition:opacity .5s var(--orb-out),transform .6s var(--orb-out);pointer-events:none}
983.orb-chg-card.on{opacity:1;transform:none;pointer-events:auto}
984.orb-chg-card .meta{display:flex;align-items:center;gap:14px;font-family:var(--orb-mono);font-size:12px;color:var(--orb-mute)}
985.orb-chg-card .meta b{padding:4px 10px;border-radius:8px;background:rgba(56,225,255,.12);color:var(--orb-cyan);font-weight:600}
986.orb-chg-card h3{margin-top:14px;font-size:1.9rem;font-weight:600;letter-spacing:-.03em}
987.orb-chg-card p{margin-top:10px;color:var(--orb-mute)}
988.orb-chg-count{margin-top:18px;font-family:var(--orb-mono);font-size:12px;letter-spacing:.2em;color:var(--orb-mute)}
989
990/* touchdown */
991.orb-land{padding:170px 0 0;background:var(--orb-bg);overflow:hidden}
992.orb-land-sun{position:absolute;left:50%;top:-18%;width:min(1100px,150vw);aspect-ratio:1;transform:translateX(-50%);pointer-events:none}
993.orb-land-sun i{position:absolute;inset:0;border-radius:50%;border:1px solid rgba(56,225,255,.2)}
994.orb-land-sun i:nth-child(1){background:radial-gradient(circle,rgba(56,225,255,.28),rgba(56,225,255,.04) 55%,transparent 70%)}
995.orb-land-sun i:nth-child(2){inset:14%;border-style:dashed;animation:orbSpin 80s linear infinite}
996.orb-land-sun i:nth-child(3){inset:28%;animation:orbSpin 55s linear infinite reverse;border-color:rgba(251,243,228,.18)}
997.orb-land .orb-wrap{position:relative}
998.orb-form{display:flex;gap:12px;justify-content:center;margin:42px auto 0;max-width:520px;flex-wrap:wrap}
999.orb-form input{flex:1 1 240px;height:52px;padding:0 22px;border-radius:999px;border:1px solid rgba(160,210,255,.3);background:rgba(10,26,58,.6);backdrop-filter:blur(8px);font-size:15px;color:var(--orb-warm)}
1000.orb-form input::placeholder{color:#6f86ad}
1001.orb-done{display:flex;align-items:center;gap:12px;padding:16px 24px;border-radius:999px;border:1px solid rgba(56,225,255,.45);background:rgba(56,225,255,.08);color:var(--orb-ice)}
1002.orb-done span{width:10px;height:10px;border-radius:50%;background:var(--orb-cyan);box-shadow:0 0 12px var(--orb-cyan);flex:none}
1003.orb-foot{position:relative;margin-top:150px;border-top:1px solid var(--orb-line);background:rgba(3,8,21,.7);padding:60px 0 30px}
1004.orb-foot-grid{display:grid;grid-template-columns:1.6fr 1fr 1fr 1fr;gap:40px}
1005.orb-brand.static{font-size:18px}
1006.orb-foot-note{margin-top:14px;max-width:22em;color:var(--orb-mute);font-size:14px}
1007.orb-foot h4{font-family:var(--orb-mono);font-size:11px;letter-spacing:.2em;text-transform:uppercase;color:var(--orb-cyan);margin-bottom:16px}
1008.orb-foot li{margin:9px 0}.orb-foot li a{color:var(--orb-ice);opacity:.75;font-size:14px;transition:opacity .2s}.orb-foot li a:hover{opacity:1}
1009.orb-legal{display:flex;justify-content:space-between;gap:16px;flex-wrap:wrap;margin-top:50px;padding-top:24px;border-top:1px solid var(--orb-line);font-size:13px;color:var(--orb-mute)}
1010
1011/* responsive */
1012@media (max-width:1100px){.orb-rail{display:none}}
1013@media (max-width:900px){
1014 .orb-wrap{padding:0 20px}.orb-nav{padding:12px 20px}.orb-links,.orb-link-quiet{display:none}
1015 .orb-sec{padding:100px 0}
1016 .orb-anat-grid,.orb-tele-grid,.orb-price-grid{grid-template-columns:1fr;gap:40px}
1017 .orb-anat-side{position:sticky;top:62px;z-index:3;background:var(--orb-bg);padding:12px 0;display:flex;align-items:center;justify-content:space-between;gap:12px;border-bottom:1px solid var(--orb-line)}
1018 .orb-anat-side .orb-lede,.orb-anat-side .orb-eyebrow{display:none}
1019 .orb-anat-svg{width:76px;margin:0;flex:none}
1020 .orb-anat-side .orb-h2{font-size:1.7rem}
1021 .orb-cta .ghost{display:none}.orb-copy p{font-size:.95rem;margin-top:16px}.orb-cta{margin-top:22px}
1022 .orb-anat-list{gap:30px;padding:0}
1023 .orb-feat{padding:24px}
1024 .orb-hud{padding:0 16px 22px;flex-direction:column;align-items:stretch;gap:10px}
1025 .orb-hud-cap{width:100%;height:136px}
1026 .orb-hud-meter{display:none}
1027 .orb-hint{display:none}
1028 .orb-copy{top:11vh}
1029 .orb-chg-stick{grid-template-columns:1fr;padding:70px 16px 16px;gap:8px;align-content:start}
1030 .orb-chg-fig svg{width:min(42vh,100%)}
1031 .orb-chg-info .orb-h2{font-size:1.7rem}.orb-chg-info .orb-eyebrow{margin-bottom:10px}
1032 .orb-chg-cards{margin-top:14px}.orb-chg-card{padding:18px}.orb-chg-card h3{font-size:1.35rem}.orb-chg-count{margin-top:10px}
1033 .orb-foot-grid{grid-template-columns:1fr 1fr}
1034 .orb-stats{grid-template-columns:1fr 1fr}
1035 .orb-node{width:46px;height:46px;border-radius:13px}.orb-node.in2{width:38px;height:38px}.orb-node span{font-size:17px}.orb-node.in2 span{font-size:14px}.orb-node em{display:none}
1036 .orb-ring-mid{width:34%}
1037}
1038@media (max-width:520px){.orb-stats{grid-template-columns:1fr}.orb-stat-l{min-height:0}.orb-foot-grid{grid-template-columns:1fr}}
1039
1040@media (prefers-reduced-motion:reduce){
1041 .orb *{animation:none!important}
1042 .orb.orb-js [data-rv]{opacity:1;transform:none;transition:none}
1043 .orb-hero{height:100vh}.orb-hint,.orb-hud,.orb-rail{display:none}
1044 .orb-chg{height:auto;padding:100px 0}
1045 .orb-chg-stick{position:static;height:auto}
1046 .orb-chg-cards{gap:14px}.orb-chg-card{grid-area:auto;opacity:1;transform:none;pointer-events:auto}
1047 .orb-chg-count{display:none}
1048 .orb-pr-arc,.orb-spark-line,.orb-spark-fill{transition:none}
1049 .orb-anat-side{position:static}
1050}
1051`;

The design brief. For you and your AI agent.

Tokens, type, layout, components and motion — written from the real values in the file, so a model can extend the page without drifting.

Orbit — DESIGN.md

SaaS / developer-platform launch page whose entire layout is an orbital system. Cosmic Technical Dark: deep navy field, electric cyan as the only saturated colour, warm white for text and the star. Everything is procedural: a canvas star field, CSS-gradient planets, inline SVG rings, spirals and sparklines. No images, no web fonts, no network.

All CSS lives in Page.tsx under the .orb prefix and every element is reset explicitly, so the page looks the same with or without a CSS reset.

1. Palette

RoleTokenHex
Page background--orb-bg#040A18
Raised navy--orb-bg2 / --orb-panel#071230 / #0A1A3A
Primary accent (electric cyan)--orb-cyan#38E1FF
Bright cyan (hover, gradients)--orb-cyan2#7DF0FF
Ice (secondary text on dark, planet tint)--orb-ice#BFE6FF
Warm white (headings, the star)--orb-warm#FBF3E4
Muted text--orb-mute#8CA2C6
Nebula indigo (glow only)--orb-indigo#5B6BFF
Hairlines--orb-linergba(125,190,255,.16)
Hero field—radial #0B1D4A → #06112B → #030815

Contrast: warm white on navy about 17:1, muted #8CA2C6 on navy about 7:1, cyan on navy about 11:1. Dark text on cyan buttons is #021018.

2. Typography

  • Sans (--orb-sans): "Space Grotesk", "Inter", ui-sans-serif, -apple-system, "Segoe UI", Roboto, sans-serif. Headings 600, tracking −0.035em to −0.05em, balanced wrapping.
  • Mono (--orb-mono): "JetBrains Mono", "SF Mono", ui-monospace, Menlo, Consolas, monospace for eyebrows, HUD, terminal, code and version tags.
StyleSpec
Hero H1sans 600, clamp(2.7rem, 8.4vw, 7.6rem) / 0.95, −0.05em; the word "orbital" is a white→cyan gradient clip
H2clamp(2.2rem, 5vw, 4.2rem) / 1.04, −0.035em; emphasis words use a cyan→indigo gradient clip
Closing Hclamp(2.8rem, 8vw, 7rem) / 0.98, −0.045em
Lede1.15rem / 1.6, muted
Eyebrowmono 12px, 0.2em, uppercase, cyan, with a glowing 26px rule
HUD / tagsmono 10–11px, 0.14–0.2em

3. Spacing, layout, radii

  • Container max-width: 1240px, side padding 40px (20px at ≤900px). Section padding 150px (100px mobile).
  • Radii: cards 20–24px, code blocks 14px, controls and buttons are pills (999px), integration nodes 18px (14px for the inner ring).
  • Cards are translucent navy gradients with a 1px --orb-line border; the active card gains a cyan border and a cyan underglow.

4. Motion tokens

--orb-out: cubic-bezier(.22, 1, .36, 1) for every entrance. One requestAnimationFrame loop reads scrollY and getBoundingClientRect() and writes transform/opacity (plus SVG stroke-dashoffset for the spiral). Sections outside the viewport are skipped. Reveals use IntersectionObserver ([data-rv], 34px rise, 0.9s).

5. Sections and choreography

  1. Hero — the system (640vh, sticky stage). Five orbits (rings tilted −14°, ellipse ratio 0.34, 0.52 on phones) each carry a planet: Edge Functions, Realtime Sync, Observability (ringed), Auth & Access, Queues & Cron. Planets orbit at Kepler-like speeds and scale/dim by depth (front vs back of the ellipse). Scroll drives a log-interpolated camera zoom 1× → 11× through keyframes with hold windows at each orbit; the focused ring brightens, outer rings fade as they exceed the viewport, a canvas star field streams toward the viewer (depth-wrapped), and the core scales to fill the screen then fades. A HUD card per orbit crossfades bottom-left, with a zoom readout and progress bar bottom-right. Headline, pill, sub copy and CTAs fade and lift in the first 8% of scroll.
  2. Anatomy. Left column is sticky: a concentric-ring SVG whose rings spin; the ring matching the feature in view turns solid cyan with a bright planet. Right column: five feature cards (body, bullets, code sample) tracked by IntersectionObserver.
  3. Launch sequence. A terminal types a deploy session (2 chars / 24ms) when it enters view; four stat tiles count up (ease-out quart, 1.6s) with sparklines that draw their stroke then fade in the fill.
  4. Integrations. Fourteen tool nodes on two concentric rings rotating in opposite directions; scroll position adds to the angular velocity. Hover or focus a node to read its description in the centre of the ring.
  5. Pricing. Four plans as concentric rings (Probe innermost). Each ring's arc fills to the plan's quota when it enters view; selecting a ring or a tab lights it, tweens the price in the core, and swaps the plan card. Monthly / yearly segmented control (−20%).
  6. Changelog spiral (480vh, sticky). An Archimedean spiral (2.7 turns) draws from the newest release on the outside to the oldest at the centre; a glowing comet follows the stroke tip, release nodes light as the stroke reaches them, and the matching card crossfades in.
  7. Touchdown. Giant closing line over a pulsing, slowly rotating star-ring, a launch-key email form (success state), and a four-column footer.

Navigation: a fixed nav gains a blurred navy background after the first 40% of the viewport; a right-edge depth rail (hidden ≤1100px) shows the current section.

6. Responsive and accessibility

  • ≤900px: single-column grids, the anatomy diagram shrinks to a sticky 150px header, the hero HUD stacks under the system, the changelog spiral sits above its card.
  • Real buttons with aria-pressed / aria-selected, labelled diagrams (role="img"), aria-live for the integrations readout, visible focus rings.
  • prefers-reduced-motion: no pinning (hero is a single static screen with the system at rest), no rotation, no typing, no reveals, spiral fully drawn and every changelog entry listed.

Questions about Orbit

A developer-platform launch page built as a solar system — scroll to fly the camera inward through every orbit. It is a free saas / developer platform landing page for React with a matching DESIGN.md brief.

Open the page, click Copy full source or Download .tsx, and drop the single Page file into your React project. Copy the public/img folder next to it for the demo photography, or swap in your own images.

Yes. It is MIT-licensed: free for personal and commercial projects, no signup and no attribution required.

Yes. The DESIGN.md describes tokens, type scale, layout, components and motion so Claude Code, Cursor, Lovable or v0 can extend the page without drifting from its look.