diff --git a/DESIGN.md b/DESIGN.md index 6963ca3..68f4793 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -90,6 +90,7 @@ How every table of facts is set, in docs, on the landing page and in Studio: - The hero is the mechanism: the ⅃ as a machined monolith whose foot becomes the global stream, events moving along it, the live head in ochre. - Real-time 3D in code (three.js), art-directed, in Basalt or Bone. No AI photography. +- The scene runs in a worker on an OffscreenCanvas wherever the browser allows it, so the page's own thread never loads, compiles or renders it; the page only sends the beat and pins the callouts. Where it cannot (older Safari) the same scene runs on the page. - **The object takes the logo's colour** (`--text` on `--bg`): satin aluminium on Basalt, black anodised on Bone. Each mode has its own studio; the geometry and the choreography are the same. - Glows are ochre. On Basalt they add light; on Bone they tint, because added light disappears on a pale ground. - Red appears in a scene only for a refused command or an error. @@ -108,6 +109,7 @@ It is not a marketing website. It answers what Bounda is and why; anything about - **One scene, one accent, at most three annotations on screen.** Light in the scene means something; no decorative glows, particles or neon. - **No metaphors, no "enterprise transformation" language, no exaggerated claims.** Say how stable it is: before 1.0, a minor release can change the API. - **Nothing depends on the 3D.** The page reads before the scene loads; without WebGL a drawing stands in, and with reduced motion the story is a still and a grid. +- **The hero is painted on the first frame.** On a first visit it settles into place from a few pixels below, in order, but it never fades in or waits: the intro (the stream catching up, the readout counting to the head) is the scene's loading state, drawn in the stage beside the copy, and gives way to the scene when it is ready or after about two seconds. - **Inspirations, by feeling and not by copying:** Vercel (clarity, spacing), Resend (confidence, restraint), Linear (focus, calm UI), EventStoreDB's docs (technical seriousness), and scroll-driven product films (one manufactured object that plays its own mechanism as you scroll). ## Voice @@ -116,6 +118,6 @@ Plain, technical, short. Active voice. The vocabulary of the product: event stor ## Where it lives -- Landing: `bounda-dev/bounda-website`, `src/styles/tokens.css`; the scene is `src/scripts/landing/scene.ts`. +- Landing: `bounda-dev/bounda-website`, `src/styles/tokens.css`; the scene is `src/scripts/landing/scene.ts`, started by `scene-client.ts` in a worker (`scene-worker.ts`) or on the page. - Docs: `docs/src/styles/bounda.css` in `bounda-dev/bounda`, mirroring the same tokens on Starlight's variables. - Studio: the same tokens; the ledger grammar for every table. diff --git a/README.md b/README.md index 7ced6a5..f0ccaf5 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,12 @@ pnpm dev # http://localhost:4321 pnpm build # dist/ ``` +The 3D scene (`src/scripts/landing/scene.ts`) runs in a Web Worker on an OffscreenCanvas where the +browser supports it, and on the page otherwise; `scene-client.ts` decides and `scene-worker.ts` is the +worker. Either way the page paints its hero on the first frame and never blocks on three.js, which is +what keeps Lighthouse at 100 on mobile. Measure with `pnpm build && pnpm preview` and Lighthouse +against `http://localhost:4321/`; the Cloudflare beacon is the one request that fails offline. + pnpm, like the framework repository, and not only for consistency: `npm ci` cannot install this tree on Linux from a lockfile resolved on macOS, because rolldown ships its native bindings as per-platform optional dependencies. The pnpm lockfile carries all of them. @@ -40,7 +46,9 @@ A Worker with static assets, built by Cloudflare on every push to `main`: | `NODE_VERSION` | `24`, since Astro 7 requires `>=22.12.0` | | `PNPM_VERSION` | `12.4.2`, because `allowBuilds` in `pnpm-workspace.yaml` is pnpm 12 | -`wrangler.jsonc` serves `dist/` and answers anything missing with `404.html`. The build image ships +`wrangler.jsonc` serves `dist/` and answers anything missing with `404.html`. `public/_headers` sets the +response headers: a year's immutable cache for the hashed files under `/_astro/`, and the usual safety +headers for everything. The build image ships pnpm 10, which reads `pnpm-workspace.yaml` as a workspace definition, so that file declares `packages` and names the built dependencies under both `allowBuilds` (pnpm 12) and `onlyBuiltDependencies` (pnpm 10). diff --git a/astro.config.mjs b/astro.config.mjs index ce2a176..b7e0da0 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -5,13 +5,15 @@ import { defineConfig, fontProviders } from "astro/config"; // so it is requested at that one weight across the width axis it uses (100% to 125%). export default defineConfig({ site: "https://bounda.dev", - prefetch: true, - integrations: [sitemap()], + // Every link on the page leaves the site (docs, demo, GitHub), so there is nothing to prefetch. + prefetch: false, + // The site is rebuilt on every push, so the build is the last change. + integrations: [sitemap({ lastmod: new Date() })], build: { inlineStylesheets: "always", }, vite: { - // three.js is one chunk, loaded after first paint and only where WebGL 2 is available. + // three.js is one chunk, run in a worker where the browser allows it (see src/scripts/landing/scene-client.ts). build: { chunkSizeWarningLimit: 650 }, // The scene imports three.js dynamically, so the dev server would only find it on first use, // re-optimize, and fail that import as an outdated dependency. Pre-bundle it from the start. diff --git a/public/_headers b/public/_headers new file mode 100644 index 0000000..065e4f7 --- /dev/null +++ b/public/_headers @@ -0,0 +1,12 @@ +# Served by Cloudflare's static assets (wrangler.jsonc). Hashed build output never changes under its +# name, so it may be cached for good; everything else revalidates. + +/* + X-Content-Type-Options: nosniff + X-Frame-Options: DENY + Referrer-Policy: strict-origin-when-cross-origin + Permissions-Policy: camera=(), microphone=(), geolocation=(), interest-cohort=() + Cross-Origin-Opener-Policy: same-origin + +/_astro/* + Cache-Control: public, max-age=31536000, immutable diff --git a/src/components/install.astro b/src/components/install.astro index 302e35e..882cd31 100644 --- a/src/components/install.astro +++ b/src/components/install.astro @@ -5,7 +5,8 @@ const command = "npm create bounda@latest my-app";
{command} - + +