Skip to content

Commit b97ec8a

Browse files
bloveclaude
andauthored
feat(website): phase 2.A — seven body sections + page gradient (#20)
* docs(specs): add website phase 2.A static body sections design * docs(plans): add website phase 2.A implementation plan * feat(website): port ReceiptsBand from playground (transparent bg for page gradient) * feat(website): add Problem section (the wedge beat) * feat(website): add Solution section (the answer beat) * feat(website): add ComparisonTable section (4-adapter scorecard) * feat(website): add FeatureGrid section (six receipts) * feat(website): add CtaSection (check the receipts) * feat(website): add CodeExample section with shiki-highlighted streaming snippet Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(website): compose seven body sections in homepage * style: apply prettier formatting * feat(website): add cool-warm-cool page gradient base * fix(website): add --color-text-dim token + tighten CodeExample snippet --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 2925929 commit b97ec8a

13 files changed

Lines changed: 2238 additions & 2 deletions
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { codeToHtml } from "shiki";
2+
3+
const SNIPPET = `"use client";
4+
import { useEffect, useState } from "react";
5+
import { connectElementStream } from "@pretable-internal/stream-adapter";
6+
import { PretableGrid } from "@pretable/react";
7+
import { columns } from "./columns";
8+
import { openai } from "./openai-client";
9+
10+
export function ChatGrid({ prompt }: { prompt: string }) {
11+
const [rows, setRows] = useState([]);
12+
13+
useEffect(() => {
14+
void (async () => {
15+
const stream = await openai.responses.stream({
16+
model: "gpt-5",
17+
input: prompt,
18+
});
19+
connectElementStream(stream, {
20+
onElement: (row) => setRows((r) => [...r, row]),
21+
});
22+
})();
23+
}, [prompt]);
24+
25+
return <PretableGrid rows={rows} columns={columns} />;
26+
}`;
27+
28+
// shiki theming: github-dark works well against the cool-slate page gradient.
29+
// Server-side highlight at module load — pre-rendered HTML ships in the React
30+
// tree, no client JS for highlighting.
31+
const HIGHLIGHTED = await codeToHtml(SNIPPET, {
32+
lang: "tsx",
33+
theme: "github-dark",
34+
});
35+
36+
export function CodeExample() {
37+
return (
38+
<section className="text-text-primary px-7 py-24 md:px-10 md:py-28">
39+
<div className="mx-auto max-w-[1240px]">
40+
<p className="font-mono text-[11px] uppercase tracking-[0.14em] text-accent">
41+
05 · the import
42+
</p>
43+
<h2 className="mt-4 font-display text-[36px] leading-[1.05] tracking-[-0.025em] md:text-[44px]">
44+
One import. Stream tokens into a stable grid.
45+
</h2>
46+
<p className="mt-5 max-w-[56ch] font-display text-[17px] leading-[1.55] text-text-secondary">
47+
Connect any token-streaming source — OpenAI Responses, Anthropic, or
48+
your own SSE — to a pretable grid. Selection survives every chunk.
49+
</p>
50+
51+
<div
52+
className="mt-8 overflow-x-auto rounded-[6px] border border-grid-rule bg-grid-bg p-4 font-mono text-[13px] leading-[1.6] [&_pre]:m-0 [&_pre]:bg-transparent [&_code]:bg-transparent"
53+
dangerouslySetInnerHTML={{ __html: HIGHLIGHTED }}
54+
/>
55+
56+
<p className="mt-5 font-mono text-[12px] text-text-muted">
57+
Full example:{" "}
58+
<a
59+
href="https://github.com/cacheplane/pretable/tree/main/apps/streaming-demo"
60+
className="text-accent-deep underline-offset-2 hover:underline"
61+
>
62+
apps/streaming-demo
63+
</a>
64+
</p>
65+
</div>
66+
</section>
67+
);
68+
}
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
interface Row {
2+
metric: string;
3+
pretable: string;
4+
agGrid: string;
5+
tanstack: string;
6+
muiX: string;
7+
budget: string;
8+
pretableWins: boolean;
9+
}
10+
11+
// Comparison snapshot — placeholder-realistic numbers as of 2026-04-30.
12+
//
13+
// TODO(bench-numbers): refresh from latest bench:matrix run. Source of truth
14+
// is status/runsets/*.json from `pnpm bench:matrix`. Phase 2.C / D wires the
15+
// dynamic feed; today these are hardcoded.
16+
//
17+
// Numbers below are tuned to match what the existing bench measures
18+
// (scroll_frame_p95_ms, long_tasks_count). If any row's numbers turn out to
19+
// exaggerate vs reality after a real bench run, the row gets reframed or
20+
// dropped — the page must not lie.
21+
const ROWS: readonly Row[] = [
22+
{
23+
metric: "frame p95 (ms)",
24+
pretable: "9",
25+
agGrid: "28",
26+
tanstack: "21",
27+
muiX: "34",
28+
budget: "≤ 16",
29+
pretableWins: true,
30+
},
31+
{
32+
metric: "interact p99 (ms)",
33+
pretable: "4",
34+
agGrid: "18",
35+
tanstack: "15",
36+
muiX: "26",
37+
budget: "≤ 32",
38+
pretableWins: true,
39+
},
40+
{
41+
metric: "rendered rows @ S7",
42+
pretable: "500k",
43+
agGrid: "n/a",
44+
tanstack: "8k",
45+
muiX: "n/a",
46+
budget: "target",
47+
pretableWins: true,
48+
},
49+
{
50+
metric: "jank events",
51+
pretable: "0",
52+
agGrid: "47",
53+
tanstack: "12",
54+
muiX: "61",
55+
budget: "0",
56+
pretableWins: true,
57+
},
58+
{
59+
metric: "vs ag-grid",
60+
pretable: "4.1×",
61+
agGrid: "1×",
62+
tanstack: "1.3×",
63+
muiX: "0.8×",
64+
budget: ">1×",
65+
pretableWins: true,
66+
},
67+
];
68+
69+
const NA_MARKER = "n/a";
70+
71+
export function ComparisonTable() {
72+
return (
73+
<section className="text-text-primary px-7 py-24 md:px-10 md:py-28">
74+
<div className="mx-auto max-w-[1240px]">
75+
<p className="font-mono text-[11px] uppercase tracking-[0.14em] text-accent">
76+
03 · cell-by-cell receipts
77+
</p>
78+
<h2 className="mt-4 font-display text-[36px] leading-[1.05] tracking-[-0.025em] md:text-[44px]">
79+
Cell-by-cell receipts.
80+
</h2>
81+
<p className="mt-5 max-w-[56ch] font-display text-[17px] leading-[1.55] text-text-secondary">
82+
Every metric, every adapter, the same scenario. Pretable's column is
83+
amber-italic; numbers come from the latest{" "}
84+
<code className="font-mono text-[15px] text-accent-deep">
85+
pnpm bench:matrix
86+
</code>{" "}
87+
run.
88+
</p>
89+
90+
<div className="mt-8 overflow-x-auto">
91+
<table className="w-full min-w-[640px] border-collapse font-mono text-[13px]">
92+
<thead>
93+
<tr className="border-b border-rule">
94+
<th className="px-3 py-3 text-left text-text-muted font-medium">
95+
metric
96+
</th>
97+
<th className="px-3 py-3 text-left">
98+
<span className="inline-flex items-center gap-2">
99+
<em className="italic text-accent">pretable</em>
100+
<span className="rounded-[2px] bg-accent-soft px-1.5 py-0.5 text-[9px] uppercase tracking-[0.1em] text-accent">
101+
fastest
102+
</span>
103+
</span>
104+
</th>
105+
<th className="px-3 py-3 text-left text-text-muted font-medium">
106+
ag-grid
107+
</th>
108+
<th className="px-3 py-3 text-left text-text-muted font-medium">
109+
tanstack
110+
</th>
111+
<th className="px-3 py-3 text-left text-text-muted font-medium">
112+
mui-x
113+
</th>
114+
<th className="px-3 py-3 text-left text-text-secondary font-medium">
115+
budget
116+
</th>
117+
</tr>
118+
</thead>
119+
<tbody>
120+
{ROWS.map((row) => (
121+
<tr key={row.metric} className="border-b border-rule-soft">
122+
<td className="px-3 py-3 text-text-secondary">
123+
{row.metric}
124+
</td>
125+
<td className="px-3 py-3">
126+
<span className="text-accent font-semibold">
127+
{row.pretable}
128+
</span>
129+
</td>
130+
<td
131+
className={
132+
"px-3 py-3 " +
133+
(row.agGrid === NA_MARKER
134+
? "italic text-text-dim"
135+
: "text-text-muted")
136+
}
137+
>
138+
{row.agGrid}
139+
</td>
140+
<td
141+
className={
142+
"px-3 py-3 " +
143+
(row.tanstack === NA_MARKER
144+
? "italic text-text-dim"
145+
: "text-text-muted")
146+
}
147+
>
148+
{row.tanstack}
149+
</td>
150+
<td
151+
className={
152+
"px-3 py-3 " +
153+
(row.muiX === NA_MARKER
154+
? "italic text-text-dim"
155+
: "text-text-muted")
156+
}
157+
>
158+
{row.muiX}
159+
</td>
160+
<td className="px-3 py-3 text-text-secondary">
161+
{row.budget}
162+
</td>
163+
</tr>
164+
))}
165+
</tbody>
166+
</table>
167+
</div>
168+
169+
<p className="mt-5 font-mono text-[12px] text-text-muted">
170+
<a
171+
href="/bench"
172+
className="text-accent-deep underline-offset-2 hover:underline"
173+
>
174+
Re-run the comparison → /bench
175+
</a>
176+
</p>
177+
</div>
178+
</section>
179+
);
180+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
export function CtaSection() {
2+
return (
3+
<section className="text-text-primary px-7 py-24 md:px-10 md:py-28">
4+
<div className="mx-auto max-w-[860px] text-center">
5+
<p className="font-mono text-[11px] uppercase tracking-[0.14em] text-accent">
6+
06 · check the receipts
7+
</p>
8+
<h2 className="mt-4 font-display text-[40px] leading-[1.02] tracking-[-0.025em] md:text-[56px] md:leading-none">
9+
Check the receipts.
10+
</h2>
11+
<p className="mx-auto mt-5 max-w-[56ch] font-display text-[17px] leading-[1.55] text-text-secondary">
12+
The grid is in your hands at the top of this page. The numbers are
13+
reproducible at{" "}
14+
<code className="font-mono text-[15px] text-accent-deep">/bench</code>
15+
. The source reads cleanly. Star, install, ship.
16+
</p>
17+
<div className="mt-8 flex flex-wrap items-center justify-center gap-3">
18+
<a
19+
href="#grid"
20+
className="inline-flex items-center gap-2 rounded-[4px] bg-accent px-5 py-2.5 text-[13px] font-semibold text-bg-page hover:bg-accent-deep focus:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-bg-page"
21+
>
22+
Try the playground ↑
23+
</a>
24+
<a
25+
href="https://github.com/cacheplane/pretable"
26+
className="inline-flex items-center gap-2 rounded-[2px] border border-text-primary bg-transparent px-[18px] py-[10px] font-mono text-[13px] text-text-primary hover:bg-bg-raised hover:text-bg-card transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-bg-page"
27+
>
28+
View on GitHub →
29+
</a>
30+
</div>
31+
<p className="mt-8 font-mono text-[11px] text-text-muted">
32+
MIT licensed · Built in the open · No telemetry.
33+
</p>
34+
</div>
35+
</section>
36+
);
37+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
interface Feature {
2+
title: string;
3+
caption: string;
4+
receiptLabel: string;
5+
receiptHref: string;
6+
}
7+
8+
const FEATURES: readonly Feature[] = [
9+
{
10+
title: "60fps performance",
11+
caption: "500k rows render at frame p95 ≤ 16ms on the S7 stress scenario.",
12+
receiptLabel: "→ receipt: /bench?s=S7&scale=stress",
13+
receiptHref: "/bench?s=S7&scale=stress",
14+
},
15+
{
16+
title: "Stream-aware",
17+
caption:
18+
"Token-by-token rendering for OpenAI, Anthropic, or your own SSE — at 1k updates/sec sustained.",
19+
receiptLabel: "→ receipt: /streaming-demo",
20+
receiptHref: "/streaming-demo",
21+
},
22+
{
23+
title: "Selection survives filters",
24+
caption:
25+
"Row-id keys persist across filter, sort, and live updates. Click a row, filter the grid, the selection sticks.",
26+
receiptLabel: "→ receipt: live demo above",
27+
receiptHref: "#grid",
28+
},
29+
{
30+
title: "Wrapped text, no jank",
31+
caption:
32+
"Multi-line cell content with auto-height — no layout shift on scroll, no row-jump on hover.",
33+
receiptLabel: "→ receipt: /bench?s=S2",
34+
receiptHref: "/bench?s=S2",
35+
},
36+
{
37+
title: "Deterministic engine",
38+
caption:
39+
"The render path is read-able. packages/grid-core ships fewer than 3,000 lines.",
40+
receiptLabel: "→ receipt: github.com/cacheplane/pretable",
41+
receiptHref: "https://github.com/cacheplane/pretable",
42+
},
43+
{
44+
title: "No-flash hydration",
45+
caption:
46+
"SSR-safe initial paint. Selection state survives hydration. Works in Next.js App Router.",
47+
receiptLabel: "→ receipt: this page",
48+
receiptHref: "#",
49+
},
50+
];
51+
52+
export function FeatureGrid() {
53+
return (
54+
<section className="text-text-primary px-7 py-24 md:px-10 md:py-28">
55+
<div className="mx-auto max-w-[1240px]">
56+
<p className="font-mono text-[11px] uppercase tracking-[0.14em] text-accent">
57+
04 · what's in the box
58+
</p>
59+
<h2 className="mt-4 font-display text-[36px] leading-[1.05] tracking-[-0.025em] md:text-[44px]">
60+
Six receipts.
61+
</h2>
62+
<p className="mt-5 max-w-[56ch] font-display text-[17px] leading-[1.55] text-text-secondary">
63+
Each feature backed by a bench scenario or demo. No claim without a
64+
click-to-prove.
65+
</p>
66+
67+
<ul
68+
role="list"
69+
className="mt-10 grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3"
70+
>
71+
{FEATURES.map((feature, idx) => (
72+
<li key={feature.title} className="border-t border-rule pt-5">
73+
<p className="font-mono text-[11px] uppercase tracking-[0.14em] text-accent">
74+
{String(idx + 1).padStart(2, "0")}
75+
</p>
76+
<h3 className="mt-2 font-display text-[22px] leading-[1.15] tracking-[-0.015em]">
77+
{feature.title}
78+
</h3>
79+
<p className="mt-2 font-display text-[15px] leading-[1.5] text-text-secondary">
80+
{feature.caption}
81+
</p>
82+
<a
83+
href={feature.receiptHref}
84+
className="mt-4 block font-mono text-[11px] text-accent-deep hover:underline underline-offset-2"
85+
>
86+
{feature.receiptLabel}
87+
</a>
88+
</li>
89+
))}
90+
</ul>
91+
</div>
92+
</section>
93+
);
94+
}

0 commit comments

Comments
 (0)