1import type { ReactNode } from "react";
2
3const backdropClassNames = {
4 pairing:
5 "absolute inset-x-0 top-0 h-44 bg-[radial-gradient(44rem_16rem_at_top,color-mix(in_srgb,var(--color-emerald-500)_14%,transparent),transparent)]",
6 error:
7 "absolute inset-x-0 top-0 h-44 bg-[radial-gradient(44rem_16rem_at_top,color-mix(in_srgb,var(--color-red-500)_16%,transparent),transparent)]",
8 brand:
9 "absolute inset-x-0 top-0 h-72 bg-[radial-gradient(48rem_20rem_at_top,color-mix(in_srgb,var(--color-blue-500)_12%,transparent),transparent)]",
10};
11
12/** Shared page and card geometry for entry points outside the app shell. */
13export function StandalonePage({
14 tone,
15 masthead,
16 children,
17}: {
18 readonly tone: keyof typeof backdropClassNames;
19 readonly masthead?: ReactNode;
20 readonly children: ReactNode;
21}) {
22 return (
23 <div className="relative flex min-h-screen items-center justify-center overflow-hidden bg-background px-4 py-10 text-foreground sm:px-6">
24 <div className="pointer-events-none absolute inset-0 opacity-80" aria-hidden>
25 <div className={backdropClassNames[tone]} />
26 {tone === "pairing" ? (
27 <div className="absolute inset-y-0 left-0 w-72 bg-[radial-gradient(28rem_18rem_at_left,color-mix(in_srgb,var(--color-sky-500)_10%,transparent),transparent)]" />
28 ) : null}
29 <div
30 className={
31 tone === "brand"
32 ? "absolute inset-0 bg-[linear-gradient(145deg,color-mix(in_srgb,var(--background)_94%,var(--color-black))_0%,var(--background)_62%)]"
33 : "absolute inset-0 bg-[linear-gradient(145deg,color-mix(in_srgb,var(--background)_90%,var(--color-black))_0%,var(--background)_55%)]"
34 }
35 />
36 </div>
37
38 <section
39 className={`relative w-full max-w-xl rounded-2xl border border-border/80 shadow-2xl shadow-black/20 backdrop-blur-md ${tone === "brand" ? "overflow-hidden bg-card/94" : "bg-card/90"}`}
40 >
41 {masthead}
42 <div className="p-6 sm:p-8">{children}</div>
43 </section>
44 </div>
45 );
46}
47
48/** Entry-page headings keep their typography and spacing together. */
49export function StandalonePageHeader({
50 eyebrow,
51 title,
52 description,
53}: {
54 readonly eyebrow: ReactNode;
55 readonly title: ReactNode;
56 readonly description: ReactNode;
57}) {
58 return (
59 <>
60 <p className="text-[11px] font-semibold tracking-[0.18em] text-muted-foreground uppercase">
61 {eyebrow}
62 </p>
63 <h1 className="mt-3 text-2xl font-semibold tracking-tight sm:text-3xl">{title}</h1>
64 <p className="mt-2 text-sm leading-relaxed text-muted-foreground">{description}</p>
65 </>
66 );
67}