src/components/landing-page.tsx
Linked repository file from this build.
import Link from "next/link";
import type { ReactNode } from "react";
import { ArrowRight } from "lucide-react";
import { Brand, IrisMark } from "@/components/brand";
import { Button } from "@/components/ui/button";
const steps = [
{
title: "Work the sample",
body: "Examiners open reviews from Home. Household facts, coded review fields and the mailed documents sit on one desk, with explicit Save and optional autosave.",
},
{
title: "Compute to the cent",
body: "Four computation columns save explicitly against a bound rule version, so an allotment can be reproduced months later.",
},
{
title: "Decide and record",
body: "Supervisors review submitted work and return corrections with a written reason. The history keeps each recorded decision.",
},
];
const newReviews = [
{ name: "Rivera household", number: "60001" },
{ name: "Nguyen household", number: "60014" },
{ name: "Patel household", number: "60017" },
];
const awaiting = [
{ name: "Brooks household", number: "60022" },
{ name: "Okafor household", number: "60031" },
];
const events = [
{ label: "Approved", number: "60009" },
{ label: "Needs changes", number: "60012" },
];
export function LandingPage() {
return (
<div className="flex min-h-dvh flex-col bg-background text-foreground">
<header className="border-b">
<div className="mx-auto flex h-16 w-full max-w-6xl items-center justify-between gap-6 px-6">
<Brand />
<nav aria-label="Landing" className="flex items-center gap-1 sm:gap-2">
<Button variant="ghost" asChild>
<Link href="/docs" prefetch={false}>
Documentation
</Link>
</Button>
<Button asChild>
<Link href="/sign-in">Sign in</Link>
</Button>
</nav>
</div>
</header>
<main className="flex-1">
<section className="mx-auto grid w-full max-w-6xl gap-12 px-6 pt-16 pb-20 lg:grid-cols-[minmax(0,7fr)_minmax(0,5fr)] lg:items-center lg:gap-16 lg:pt-24 lg:pb-28">
<div className="max-w-2xl">
<p className="text-sm font-medium text-muted-foreground">
SNAP Negative quality control
</p>
<h1 className="mt-4 text-4xl leading-[1.08] text-balance sm:text-5xl lg:text-[3.5rem]">
Quality control for SNAP denials, worked in one place.
</h1>
<p className="mt-6 max-w-prose text-lg leading-relaxed text-muted-foreground text-pretty">
Iris is the review desk for a SNAP Negative sample. Examiners work the household and
the computation, supervisors and PEC record their decisions, and every review keeps
its own history.
</p>
<div className="mt-8 flex flex-wrap items-center gap-x-6 gap-y-3">
<Button size="lg" asChild>
<Link href="/sign-in">
Sign in
<ArrowRight aria-hidden="true" />
</Link>
</Button>
<Link
href="/docs/first-day"
prefetch={false}
className="text-sm font-medium underline-offset-4 hover:underline focus-visible:underline"
>
Explore the codebase
</Link>
</div>
<p className="mt-10 max-w-prose border-l-2 border-border pl-4 text-sm leading-relaxed text-muted-foreground">
This deployment is a synthetic demo. Reviews, households and PDFs are seeded; it is
not production scope and holds no live caseload.
</p>
</div>
<HomePreview />
</section>
<section aria-labelledby="how-it-works" className="border-t bg-card text-card-foreground">
<div className="mx-auto w-full max-w-6xl px-6 py-16 lg:py-20">
<h2 id="how-it-works" className="text-2xl sm:text-3xl">
How a review moves through Iris
</h2>
<ol className="mt-10 grid gap-10 sm:grid-cols-3 sm:gap-8">
{steps.map((step, index) => (
<li key={step.title} className="border-t pt-5">
<p className="font-mono text-xs tabular-nums text-muted-foreground">
{String(index + 1).padStart(2, "0")}
</p>
<h3 className="mt-3 text-lg font-semibold tracking-tight">{step.title}</h3>
<p className="mt-2 text-sm leading-relaxed text-muted-foreground text-pretty">
{step.body}
</p>
</li>
))}
</ol>
</div>
</section>
</main>
<footer className="border-t">
<div className="mx-auto flex w-full max-w-6xl flex-col gap-3 px-6 py-8 text-sm text-muted-foreground sm:flex-row sm:items-center sm:justify-between">
<p className="inline-flex items-center gap-2">
<IrisMark className="size-4" />
Iris · SNAP Negative vertical slice
</p>
<p>Seeded reviews and documented calculation assumptions. Not a live caseload.</p>
</div>
</footer>
</div>
);
}
/** A still of the signed-in Home page, drawn with the same list styling the app uses. */
function HomePreview() {
return (
<figure className="mx-auto w-full max-w-md lg:max-w-none">
<div
role="img"
aria-label="Preview of the Iris Home page: lists of new reviews, reviews awaiting decision and recent events"
className="overflow-hidden rounded-lg border bg-card text-card-foreground shadow-md"
>
<div className="flex h-10 items-center gap-3 border-b bg-sidebar px-3 text-sidebar-foreground">
<span className="inline-grid size-6 place-items-center rounded-md bg-sidebar-primary text-sidebar-primary-foreground">
<IrisMark className="size-4" />
</span>
<span className="text-xs font-semibold">Home</span>
<span className="ml-auto rounded-md border border-sidebar-border px-2 py-0.5 text-[0.7rem] text-muted-foreground">
Search reviews
</span>
</div>
<div className="space-y-4 p-4">
<div>
<p className="text-[0.7rem] font-medium tracking-wide text-muted-foreground uppercase">
Good morning, Alex · Monday, September 14
</p>
<p className="mt-1 text-xl font-semibold tracking-tight">Home</p>
</div>
<div className="grid gap-3 sm:grid-cols-2">
<PreviewList title="New" total={3}>
{newReviews.map((row) => (
<PreviewRow key={row.number} tone="muted" label={row.name} value={row.number} />
))}
</PreviewList>
<PreviewList title="Awaiting my review" total={2}>
{awaiting.map((row) => (
<PreviewRow key={row.number} tone="muted" label={row.name} value={row.number} />
))}
</PreviewList>
<PreviewList title="Since last visit" total={2} className="sm:col-span-2">
{events.map((row) => (
<PreviewRow
key={row.number}
tone={row.label === "Approved" ? "success" : "destructive"}
label={row.label}
value={row.number}
/>
))}
</PreviewList>
</div>
</div>
</div>
<figcaption className="mt-3 text-center text-xs text-muted-foreground lg:text-left">
Home for an examiner on the September 2026 sample.
</figcaption>
</figure>
);
}
function PreviewList({
title,
total,
className = "",
children,
}: {
title: string;
total: number;
className?: string;
children: ReactNode;
}) {
return (
<div className={`overflow-hidden rounded-md border bg-card ${className}`}>
<div className="flex h-8 items-center justify-between border-b bg-muted/40 px-2.5">
<span className="text-xs font-semibold">{title}</span>
<span className="rounded-full bg-background px-1.5 text-[0.7rem] font-medium tabular-nums text-muted-foreground">
{total}
</span>
</div>
<ul className="divide-y">{children}</ul>
</div>
);
}
function PreviewRow({
label,
value,
tone,
}: {
label: string;
value: string;
tone: "muted" | "success" | "destructive";
}) {
const dot = {
muted: "bg-muted-foreground",
success: "bg-success",
destructive: "bg-destructive",
}[tone];
return (
<li className="flex h-8 items-center gap-2 px-2.5 text-xs">
<span aria-hidden="true" className={`size-1.5 shrink-0 rounded-full ${dot}`} />
<span className="min-w-0 flex-1 truncate font-medium">{label}</span>
<span className="shrink-0 font-mono text-[0.7rem] tabular-nums text-muted-foreground">
{value}
</span>
</li>
);
}