diff --git a/website/app/layout.ts b/website/app/layout.ts index 011866707..152f891d2 100644 --- a/website/app/layout.ts +++ b/website/app/layout.ts @@ -43,7 +43,7 @@ const TITLE = 'WebJs: Full-stack web components framework, no build step'; // The true version of that idea is about LOCATION, not volume: the source sits // in the app's own node_modules at the installed version, so an agent opens // the file it needs instead of recalling an API from training data. -const DESCRIPTION = 'An AI-first full-stack JavaScript web components framework with no build step. Production-ready architecture from your very first prompt. Node 24+ or Bun.'; +const DESCRIPTION = 'A full-stack JavaScript web components framework with no build step. Production-ready architecture from your very first prompt. Node 24+ or Bun.'; const NAV = [ { label: 'Docs', href: DOCS_START_PATH, ext: false }, diff --git a/website/app/page.ts b/website/app/page.ts index 69df58a60..f106f0dcb 100644 --- a/website/app/page.ts +++ b/website/app/page.ts @@ -42,7 +42,7 @@ export const metadata = { '@id': `${SITE_URL}#website`, name: 'WebJs', url: SITE_URL, - description: 'An AI-first, web-components-first full-stack web framework with no build step.', + description: 'A web-components-first full-stack web framework with no build step.', publisher: { '@id': `${SITE_URL}#organization` }, }, { @@ -187,6 +187,13 @@ export const metadata = { // Chips for the progressive-enhancement section: the concrete things that // keep working with JavaScript disabled, because the server sends real HTML. +// The chips run in the order the section argues: what a component IS, then +// what the browser does with it, then what that buys. The last two are the +// progressive-enhancement and elision claims, which the section keeps even +// though its heading no longer leads with them, because they are consequences +// of the component model rather than a separate topic, and the P.S. dare below +// invites the reader to check the fourth one in their own browser. +// // 'No whole-page hydration' is the precise form and the ONLY one to use here. // WebJs does hydrate: a shipping component loads @webjsdev/core, and // createInstance() in render-client.js does container.replaceChildren(...), @@ -196,7 +203,7 @@ export const metadata = { // all, and elided components never ship. Anything shorter ("no hydration // runtime", "no hydration overhead") reads as zero cost and is refuted by one // look at the network tab. -const PE_CHIPS = ['No whole-page hydration', 'Content reads', 'Links navigate', 'Forms submit', 'Display components ship 0 KB']; +const PE_CHIPS = ['Standard custom elements', 'No virtual DOM', 'Server-rendered, then upgraded', 'No whole-page hydration', 'Reads and submits before JS', 'Display components ship 0 KB']; // The hero stage shows this source beside the very component it declares, // running. Keep the two in step: the panel to its right is a real @@ -246,92 +253,6 @@ function sourceWindow(title: string, sample: string) { `; } -/** - * The landing page's intro video, self-hosted rather than embedded. - * - * The player is the browser's own, so it works with scripting disabled. The - * YouTube iframe this replaced could not, because its player needs JS inside - * the frame, so the section used to hide itself from a JS-off reader rather - * than show a broken embed. - * - * REPLACING THE VIDEO, for whoever does it next. - * - * The bytes live in the Cloudflare R2 bucket `webjs-videos`, served from - * videos.webjs.dev. The key carries no version, so a new cut overwrites - * `intro.mp4` in place and nothing in this file changes: - * - * env -u CLOUDFLARE_API_TOKEN npx wrangler r2 object put \ - * webjs-videos/intro.mp4 --file \ - * --content-type video/mp4 \ - * --cache-control "public, max-age=86400, s-maxage=31536000" \ - * --remote - * - * Then purge that URL at the edge (Caching, then Purge Cache, then the custom - * single-file purge), or the old bytes keep serving for the `s-maxage` year. - * - * Three things about this are easy to get wrong. - * - * 1. R2 object metadata cannot be edited after upload. `Cache-Control` is set - * at write time or not at all, so every upload has to pass it again. There - * is no bucket-level setting, and the dashboard uploader has no field for - * it, which is why an upload made there serves no header of its own. - * - * 2. The `env -u` is load bearing. A `CLOUDFLARE_API_TOKEN` in the environment - * overrides wrangler's OAuth credentials, and that token carries no R2 - * permission, so the upload fails with an authentication error that names - * nothing. Unsetting it for the one call falls back to the OAuth login. - * - * 3. A purge clears the edge and never a browser. That is why `max-age` is a - * day rather than a year, and why the header is deliberately not - * `immutable`: anyone holding the old file needs a way to pick the new one - * up, and 24 hours is that way. `s-maxage` keeps the edge copy long lived - * regardless, since the edge can be purged and a browser cannot. - * - * A versioned key (`intro-2026-08-21.mp4`) is the other valid shape. Take it - * and the tradeoff inverts: put `immutable` back, raise `max-age` to a year, - * and update the src below on every cut. - * - * The thumbnail at `intro-thumbnail.webp` is uploaded the same way, with - * `--content-type image/webp`. It is 1920 wide because the box is capped at - * max-w-3xl (about 718 CSS px), so that already covers a 2x display and a - * wider encode buys nothing a viewport can show. - */ -const INTRO_VIDEO = html` -
-
- -
- -
-
-
-`; - - export default function LandingPage() { return html`