# Landing page (`site/`) `site/` is the project's public landing page: one scrolling page — a daylight hero, the 37 studies as demo cards, the repository's own counters, how a sentence becomes a scene, installation, footer — which then serves the built studies at `/examples//`. It is a **presentation** of this repository, not a second source of truth: every counter is read at build time from `skills/`, `docs/`, `evals/` and `evidence/`; every card's title, blurb and chapter is read from [`examples/README.md`](../examples/README.md) joined with `examples/shared/catalog.js`, so a number or a name cannot drift from the data behind it. Stack: React 19 · Vite 7.1.5 · Tailwind v4 · TypeScript strict (`tsc --noEmit` runs in `prebuild`). Visual system: the maintainer's "Daylight" design (phase 8, 2026-09-10) — cream `#fffaf2`, ink `#1c2a23`, green `#0b7553`, Outfit + JetBrains Mono — with tokens authored in `site/scripts/build-data.mjs`. The hero is the showcase frame `docs/demos/showcase/village-overcast-morning.jpg`, re-encoded to `media/hero/overcast-morning.{jpg,webp}`; the live Three.js village of phases 3–7 is still in the tree but gated off (`HERO_LIVE_LOOK = null` in `site/src/hero/hero-looks.ts`), so no three.js chunk ships. Component and script details are in [`site/README.md`](../site/README.md). What a card does: hovering (or focusing) it plays a 4 s muted loop recorded from the study; **Show 3D** opens the study in a modal `` with an iframe on `/examples//` and an "Open in new tab" link. Under `prefers-reduced-motion` no clip is mounted and the counters do not count up. ## Build it From the repository root, in order: ```sh pnpm --dir examples install --frozen-lockfile bash site/scripts/bundle-examples.sh # builds examples/ and copies it into site/public/examples/ pnpm --dir site install --frozen-lockfile pnpm --dir site build # build-data → tsc --noEmit → vite build pnpm --dir site test # 16 node tests over the generated data node site/scripts/check-budgets.mjs # byte budgets, measured on the build output pnpm --dir site preview # http://127.0.0.1:4173 (Safari/WebKit refuse port 4190) ``` The build must run from a **full checkout**: `site/scripts/build-data.mjs` reads files one level above `site/`. A shallow copy of `site/` alone cannot build. The modal iframe needs the bundled examples, so it 404s under `vite dev` and works under `preview`. ### Generated versus committed | Path | State | | --- | --- | | `site/src/generated/*.json`, `theme.css` | generated by `pnpm build-data` on every dev/build; git-ignored | | `site/public/examples/` | generated by `bundle-examples.sh` (≈ 170 MiB); git-ignored | | `site/public/media/**` | **committed** (≈ 9 MiB: 37 stills, 74 clip files, 2 hero posters, plus the key-art and origin media no section renders since phase 8), hashed in `media-manifest.json` / `hero-manifest.json`, so the site builds without a GPU | | `site/public/fonts/*.woff2` | committed (Outfit + JetBrains Mono latin variable subsets, OFL) | | build output (`site/dist/`) | generated; git-ignored | ## Regenerate the media All of these need a GPU-capable headless Chromium (Playwright) and ffmpeg, and none of them is part of the build — the outputs are committed. Pass `--python` (or set `PLAYWRIGHT_PYTHON`) when the system interpreter has no Playwright. | Command (from the repository root) | Produces | | --- | --- | | `python3 site/scripts/site-media.py` | all four capture stages below plus `site/public/media/media-manifest.json` | | `python3 site/scripts/site-media.py --stills` | the 37 study stills, `?plate=1` chrome-free, view-quality checked | | `python3 site/scripts/site-media.py --clips` | one hover clip per study (37 × webm + mp4, 640 × 360, 4 s). A study whose frames `MOTION_MS` apart score ≥ 42 dB PSNR (still to the eye) is recorded under a pointer-drag orbit instead; the manifest records `motion: native \| orbit` per clip (16 native, 21 orbit on 2026-09-10) | | `python3 site/scripts/site-media.py --keyart` | the 21 showcase frames as full + thumb JPEGs (not rendered since phase 8; kept for the pipeline) | | `python3 site/scripts/site-media.py --origin` | the Harness Village clip from `docs/demos/harness-village.gif` (not rendered since phase 8) | | `python3 site/scripts/hero-posters.py --from-frame docs/demos/showcase/village-overcast-morning.jpg --look overcast-morning` | the hero poster pair from the showcase frame, no browser needed | | `python3 site/scripts/social-assets.py` | the favicons, `apple-touch-icon.png` and the 1200 × 630 share image `site/public/og/3dviz-pro-max.jpg`; needs Pillow + cairosvg, no browser | | `python3 site/scripts/site-media.py --hero --url …` / `hero-poster-diff.py` | the live-look poster route and its pixel check; only meaningful once `HERO_LIVE_LOOK` names a look again | `--print-commands` prints every ffmpeg argv a run would use without running one. **Icons and the share card.** All of them are rendered from `site/public/favicon.svg` (the header's logo mark on a cream tile) and from the same showcase frame the hero uses, by `site/scripts/social-assets.py`, and all of them are committed. `site/index.html` carries the canonical URL, the Open Graph and Twitter card tags, a `SoftwareSourceCode` JSON-LD block and `site/public/site.webmanifest`; the card image is referenced by absolute URL under `https://3dviz.dev` (`SITE_URL` in `site/src/lib/site-links.ts`), so the origin above has to be right before the first link is shared. `check-budgets.mjs` holds the share image to 200 KiB on its own line — it is fetched by crawlers, never by the page, so it stays out of the landing-transfer budget. ## Deploy (Cloudflare Pages) The Pages project is **`3dviz-pro-max`** in the maintainer's Cloudflare account (production alias `https://3dviz-pro-max.pages.dev`, custom domain `https://3dviz.dev`), first deployed 2026-09-10 with `wrangler pages deploy site/ --project-name 3dviz-pro-max --branch main` from a local build. Anatomy geometry is served from the R2 bucket `3dviz-assets` at `https://assets.3dviz.dev/` (see `examples/assets-manifest.json`). For a Git-connected build these are the settings: | Setting | Value | | --- | --- | | Root directory | `/` — the **repository root**, because the data build reads `skills/`, `docs/`, `evals/`, `evidence/` and `examples/` | | Build command | `pnpm --dir examples install --frozen-lockfile && bash site/scripts/bundle-examples.sh && pnpm --dir site install --frozen-lockfile && pnpm --dir site build` | | Build output directory | `site/dist` | | Node version | 24 (`NODE_VERSION=24`); pnpm 10.30.1 via `packageManager` | | Environment variables | none — `bundle-examples.sh` exports `VITE_ASSET_BASE` itself, from the `base` field of `examples/assets-manifest.json`. Set it in the Pages project only to override that value. | ### Deploy size (measured 2026-09-10, Apple M4 Max) | Part | Files | Bytes | | --- | --- | --- | | Site itself (HTML, JS/CSS, fonts, committed media incl. the 37-clip set) | 176 | 10,115,150 (9.6 MiB) | | Bundled examples (`/examples/**`) — code only, no geometry | 92 | 1,074,793 (1.02 MiB) | | **Total upload** | **268** | **11,189,943 (10.7 MiB)** | Largest single file: `examples/assets/catalog-*.js`, 842 KB — far under the 25 MiB per-asset limit that `bundle-examples.sh` enforces, and the file count is far under the 20,000-asset limit. **Anatomy geometry is not deployed.** The 108 files (106 STL + two `attribution.json`, 168.6 MiB) live in the R2 bucket `3dviz-assets`, served from `https://assets.3dviz.dev/` (its `pub-…r2.dev` URL also answers), with GET/HEAD CORS for the site origins. `examples/assets-manifest.json` lists every key with its size and sha256 and holds the base URL that both `bundle-examples.sh` (for the deploy build) and `examples/scripts/fetch-assets.sh` (for a local copy) read — change the URL there and nothing else. The anatomy studies (`heart`, `brain`, `muscle-atlas`, `knee`) each pull several megabytes of STL from that origin when opened in the modal or a tab, so they are still slow on a cold cache. The landing page itself never pays it: clips are `preload="none"` and fetched only on hover, and no study geometry is requested until a study opens. Uploading to the bucket needs `--remote`: `wrangler r2 object put 3dviz-assets/ --file=… --remote`. Without it wrangler 4 writes to a local miniflare `.wrangler/` directory and the keys never reach the bucket. ### Byte budgets `node site/scripts/check-budgets.mjs` measures the build output — text at gzip -9, media at file size — and exits 1 on any breach. Measured 2026-09-10 on Apple M4 Max: | Budget | Limit | Measured | | --- | --- | --- | | Entry JS (gzip) | 120 KiB | 70.8 KiB | | Hero chunk (gzip) | 250 KiB, or absent | absent — the hero is the baked frame | | Landing transfer: HTML + CSS + entry JS (gzip) + heaviest poster + both fonts | 900 KiB | 386.1 KiB | | Any study still | 120 KiB | 106.7 KiB (`night-mill.jpg`) | | Any hover clip (76 files) | 600 KiB | 258.8 KiB (`probability.webm`) | | Origin clip | 700 KiB | 357.3 KiB (`harness-village.mp4`) | | Any key-art frame | 250 KiB | 210.1 KiB (`stone-bridge-over-water.jpg`) | | Any hero poster (4 files) | 250 KiB | 245.8 KiB (`overcast-morning.jpg`; desktop browsers fetch the 206.9 KiB webp, phones the 59.3 KiB 960-wide webp) | | Share image (`og/3dviz-pro-max.jpg`, crawlers only) | 200 KiB | 100.9 KiB | ## Lighthouse Run against the production preview, one page, no throttling changes beyond the preset: ```sh pnpm --dir site preview # http://127.0.0.1:4173 pnpm dlx lighthouse http://127.0.0.1:4173/ --preset=desktop --chrome-flags="--headless=new" pnpm dlx lighthouse http://127.0.0.1:4173/ --chrome-flags="--headless=new" # mobile, the default preset ``` ### Measured 2026-09-10 after phase 8 (Daylight), Apple M4 Max, Lighthouse 12.8.2, headless Chrome, loopback preview | Run | Performance | LCP | CLS | TBT | | --- | --- | --- | --- | --- | | Desktop | **97** | 1.2 s | 0 | 0 ms | | Mobile (2 runs) | **93** | 3.2 s | 0 | 0 ms | The LCP element is the hero poster in both. Desktop lost its blocking time with the live hero (phase 7 scored 68–85 because the village was built on the main thread). Mobile was 88 with the 1920-wide poster alone (LCP 3.9 s); a 960 × 540 pair (`overcast-morning-960.{webp,jpg}`, 59/73 KiB) behind `` brought it to 93. High-DPR phones get ≈ 2.4× density from that file; add a 1440-wide step if it reads soft on a device. Earlier measurements (phases 5–7, 2026-09-09) are in the CHANGELOG entry and the phase files; they describe a page that no longer exists. ## Caveats - **Licensing.** The repository's own code, data and authored media are [MIT licensed](../LICENSE) and the footer says so. Third-party material is not covered: BodyParts3D anatomy meshes (CC BY-SA 2.1 Japan), the bundled fonts (OFL 1.1, `site/public/fonts/OFL.txt`) and the author-supplied Harness Village recording, whose redistribution rights are still to be confirmed. - **Compatibility labels are the documented ones.** The install card's WORKS WITH rows carry EVALUATED (Codex) and OBSERVED (Claude Code) from [docs/compatibility.md](compatibility.md), not the mock-up's "SUPPORTED". - **Blender, Three.js and Chromium marks are trademarks of their owners, used to identify the tools.** They are inlined monochrome in the STEP 05 diagram (`site/src/components/brand-logos.tsx`), which names its sources. - **BodyParts3D attribution** for the anatomy STLs stays in the page footer and in the examples. - The Harness Village recording (`site/public/media/origin/`) is still committed although no section plays it since phase 8; its redistribution rights remain to be confirmed and it is outside the MIT grant ([docs/demos/README.md](demos/README.md)). - The site adds no external request: fonts, media and scripts are all self-hosted, and no analytics, tag manager or font CDN is loaded. - Repository links (`GitHub`, `READ THE PROMPTS`, `Installation guide`, `License`) are built from `REPO_FILE_BASE`/`GITHUB_REPO` in `site/src/lib/site-links.ts`, now `https://github.com/viettranx/3dviz-pro-max`; change them there if the repository moves. ## Follow-ups - **STL → GLB (Draco/meshopt)** for the hosted anatomy geometry: no longer a deploy-size item (the 168.6 MiB moved to R2 on 2026-09-10), but still the reason the anatomy studies are slow to open over a network — compression would cut what each visitor downloads from the bucket. - Drop or keep the unrendered key-art and origin media (2.3 MiB committed, two pipeline stages). - Two native clips move only faintly at card size (`night-doorway` flame and smoke, `night-bridge` water shimmer); lowering `MOTION_PSNR_DB` in `site_media_clips.py` to ≈ 40 would orbit them. - Create the Cloudflare Pages project — after the Harness Village redistribution rights are confirmed, not before.