Skip to content

Commit ccd9a57

Browse files
authored
chore(📚): better hero unit in the docs (#393)
1 parent c30ed8f commit ccd9a57

17 files changed

Lines changed: 1716 additions & 414 deletions

File tree

‎apps/docs/app/(home)/page.tsx‎

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
import dynamic from "next/dynamic";
44
import Link from "next/link";
5-
import { useTheme } from "next-themes";
5+
import { useEffect, useState } from "react";
66

7+
import { SHADERS } from "@/components/hero/registry";
78
import { homeLinks } from "@/lib/layout.shared";
89

910
const HeroShader = dynamic(
@@ -14,46 +15,57 @@ const HeroShader = dynamic(
1415
const links = homeLinks();
1516

1617
export default function HomePage() {
17-
const { resolvedTheme } = useTheme();
18-
const isLight = resolvedTheme === "light";
18+
// Pick a shader at random per page load (client-only to avoid hydration
19+
// mismatch). No auto-rotation: reload for a different one.
20+
const [entry, setEntry] = useState(SHADERS[0]!);
21+
useEffect(() => {
22+
setEntry(SHADERS[Math.floor(Math.random() * SHADERS.length)]!);
23+
}, []);
24+
25+
// Overlay contrast follows the shader's appearance, not the site theme.
26+
const dark = entry.appearance !== "light";
1927

2028
return (
2129
<main className="relative min-h-0 flex-1 w-full overflow-hidden">
22-
<HeroShader className="absolute inset-0 h-full w-full" />
30+
<HeroShader entry={entry} className="absolute inset-0 h-full w-full" />
2331

2432
<div className="pointer-events-none absolute inset-0 z-10 flex flex-col items-center justify-center px-6 text-center">
2533
<h1
2634
className={`max-w-3xl text-4xl font-semibold tracking-tight sm:text-5xl md:text-6xl ${
27-
isLight ? "text-neutral-900" : "text-white drop-shadow-[0_1px_12px_rgba(0,0,0,0.55)]"
35+
dark
36+
? "text-white drop-shadow-[0_1px_12px_rgba(0,0,0,0.55)]"
37+
: "text-neutral-900"
2838
}`}
2939
>
3040
React Native WebGPU
3141
</h1>
3242
<p
3343
className={`mt-5 max-w-xl text-base sm:text-lg ${
34-
isLight ? "text-neutral-600" : "text-white/70 drop-shadow-[0_1px_8px_rgba(0,0,0,0.45)]"
44+
dark
45+
? "text-white/90 drop-shadow-[0_1px_6px_rgba(0,0,0,1)]"
46+
: "text-neutral-600"
3547
}`}
3648
>
37-
The most powerful GPU library for React Native. Dawn-backed WebGPU on
38-
iOS, Android, macOS, and visionOS.
49+
WebGPU compliant binding powered by Dawn.<br />
50+
Runs on iOS, Android, macOS, visionOS, and Web.
3951
</p>
4052
<div className="pointer-events-auto mt-10 flex flex-wrap items-center justify-center gap-3">
4153
<Link
4254
href={links.gettingStarted}
4355
className={
44-
isLight
45-
? "rounded-lg bg-neutral-900 px-5 py-2.5 text-sm font-medium text-white transition hover:bg-neutral-800"
46-
: "rounded-lg bg-white px-5 py-2.5 text-sm font-medium text-neutral-900 transition hover:bg-white/90"
56+
dark
57+
? "rounded-lg bg-white px-5 py-2.5 text-sm font-medium text-neutral-900 transition hover:bg-white/90"
58+
: "rounded-lg bg-neutral-900 px-5 py-2.5 text-sm font-medium text-white transition hover:bg-neutral-800"
4759
}
4860
>
4961
Get Started
5062
</Link>
5163
<Link
5264
href={links.api}
5365
className={
54-
isLight
55-
? "rounded-lg border border-neutral-300 bg-white/70 px-5 py-2.5 text-sm font-medium text-neutral-900 backdrop-blur transition hover:bg-white"
56-
: "rounded-lg border border-white/25 bg-white/5 px-5 py-2.5 text-sm font-medium text-white backdrop-blur transition hover:bg-white/10"
66+
dark
67+
? "rounded-lg border border-white/25 bg-white/5 px-5 py-2.5 text-sm font-medium text-white backdrop-blur transition hover:bg-white/10"
68+
: "rounded-lg border border-neutral-300 bg-white/70 px-5 py-2.5 text-sm font-medium text-neutral-900 backdrop-blur transition hover:bg-white"
5769
}
5870
>
5971
API Reference

‎apps/docs/next-env.d.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// <reference types="next" />
22
/// <reference types="next/image-types/global" />
3-
import "./.next/types/routes.d.ts";
3+
import "./.next/dev/types/routes.d.ts";
44

55
// NOTE: This file should not be edited
66
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

‎apps/docs/package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"build": "fumadocs-mdx && next build",
88
"build:docs": "fumadocs-mdx && next build",
99
"start": "next start",
10+
"fetch:shaders": "node scripts/fetch-computetoys.mjs",
1011
"lint": "eslint . --max-warnings 0",
1112
"tsc": "fumadocs-mdx && next typegen && tsc --noEmit"
1213
},
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#!/usr/bin/env node
2+
// Fetch compute.toys shaders by id and generate hero ShaderEntry modules.
3+
//
4+
// Usage:
5+
// node scripts/fetch-computetoys.mjs 202 31 ... # fetch these ids
6+
// node scripts/fetch-computetoys.mjs # refetch ids in manifest
7+
//
8+
// For each id it pulls /view/<id>/wgsl (source) and /view/<id>/json (uniforms +
9+
// metadata), then writes src/components/hero/shaders/generated/ct-<id>.ts and
10+
// regenerates the generated/index.ts barrel. Generated files are committed, so
11+
// the docs build needs no network access.
12+
13+
import { mkdir, readFile, writeFile } from "node:fs/promises";
14+
import { dirname, join } from "node:path";
15+
import { fileURLToPath } from "node:url";
16+
17+
const __dirname = dirname(fileURLToPath(import.meta.url));
18+
const GEN_DIR = join(__dirname, "../src/components/hero/shaders/generated");
19+
const MANIFEST = join(GEN_DIR, "manifest.json");
20+
21+
async function readManifest() {
22+
try {
23+
return JSON.parse(await readFile(MANIFEST, "utf8"));
24+
} catch {
25+
return [];
26+
}
27+
}
28+
29+
async function fetchToy(id) {
30+
const [wgslRes, jsonRes] = await Promise.all([
31+
fetch(`https://compute.toys/view/${id}/wgsl`),
32+
fetch(`https://compute.toys/view/${id}/json`),
33+
]);
34+
if (!wgslRes.ok || !jsonRes.ok) {
35+
throw new Error(`Failed to fetch toy ${id} (${wgslRes.status}/${jsonRes.status})`);
36+
}
37+
const code = await wgslRes.text();
38+
const meta = await jsonRes.json();
39+
return { code, meta };
40+
}
41+
42+
function generateModule(id, code, meta) {
43+
const uniforms = meta.body?.uniforms ?? [];
44+
const customOrder = uniforms.map((u) => u.name);
45+
const custom = Object.fromEntries(uniforms.map((u) => [u.name, u.value]));
46+
const passF32 = Boolean(meta.body?.float32Enabled);
47+
const title = meta.name ?? `compute.toys #${id}`;
48+
const author = meta.profile?.username ?? "unknown";
49+
50+
return `// AUTO-GENERATED by scripts/fetch-computetoys.mjs from
51+
// https://compute.toys/view/${id} — do not edit by hand. Re-run the script to
52+
// update. Per-hero tuning (orbitPeriod, maxDimension) belongs in the registry.
53+
//
54+
// "${title}" by ${author}. compute.toys exposes no explicit license; used with
55+
// attribution. Confirm reuse terms with the author if needed.
56+
import type { ShaderEntry } from "../../types";
57+
58+
export const ct${id}: ShaderEntry = {
59+
id: "ct-${id}",
60+
title: ${JSON.stringify(title)},
61+
author: ${JSON.stringify(author)},
62+
sourceUrl: "https://compute.toys/view/${id}",
63+
license: "compute.toys",
64+
shader: {
65+
kind: "computetoys",
66+
passF32: ${passF32},
67+
customOrder: ${JSON.stringify(customOrder)},
68+
custom: ${JSON.stringify(custom, null, 6).replace(/\n/g, "\n ")},
69+
code: ${JSON.stringify(code)},
70+
},
71+
};
72+
`;
73+
}
74+
75+
function generateIndex(ids) {
76+
const sorted = [...ids].sort((a, b) => a - b);
77+
const imports = sorted.map((id) => `import { ct${id} } from "./ct-${id}";`).join("\n");
78+
const list = sorted.map((id) => `ct${id}`).join(", ");
79+
return `// AUTO-GENERATED by scripts/fetch-computetoys.mjs — do not edit by hand.
80+
import type { ShaderEntry } from "../../types";
81+
82+
${imports}
83+
84+
export { ${list} };
85+
86+
export const GENERATED_SHADERS: ShaderEntry[] = [${list}];
87+
`;
88+
}
89+
90+
async function main() {
91+
const argIds = process.argv.slice(2).map((s) => parseInt(s, 10)).filter((n) => !Number.isNaN(n));
92+
const manifestIds = await readManifest();
93+
const ids = [...new Set([...manifestIds, ...argIds])];
94+
95+
if (ids.length === 0) {
96+
console.error("No ids. Pass ids as args, e.g. node scripts/fetch-computetoys.mjs 202");
97+
process.exit(1);
98+
}
99+
100+
await mkdir(GEN_DIR, { recursive: true });
101+
102+
for (const id of ids) {
103+
process.stdout.write(`Fetching compute.toys/${id} ... `);
104+
const { code, meta } = await fetchToy(id);
105+
await writeFile(join(GEN_DIR, `ct-${id}.ts`), generateModule(id, code, meta));
106+
console.log(`ok ("${meta.name}" by ${meta.profile?.username ?? "?"})`);
107+
}
108+
109+
await writeFile(MANIFEST, JSON.stringify([...ids].sort((a, b) => a - b), null, 2) + "\n");
110+
await writeFile(join(GEN_DIR, "index.ts"), generateIndex(ids));
111+
console.log(`\nGenerated ${ids.length} shader(s) in ${GEN_DIR}`);
112+
}
113+
114+
main().catch((err) => {
115+
console.error(err);
116+
process.exit(1);
117+
});

0 commit comments

Comments
 (0)