diff --git a/STATUS.md b/STATUS.md index 3b9b0f9..9294fa4 100644 --- a/STATUS.md +++ b/STATUS.md @@ -4,9 +4,10 @@ Single source of truth for "what's next." One milestone per PR/run. Autopilot: pick the one task under **NEXT**, ship it, stop. Do **not** start anything under **BLOCKED**. -_Last updated: 2026-07-04 — Milestone 6 (Web Worker WASM inference) done; two -user-directed polish items (boot loading bar, then a black+green "coding" -aesthetic) landed on top; queue empty pending an M7 proposal._ +_Last updated: 2026-07-04 — Milestone 6 (Web Worker WASM inference) done; three +user-directed polish items (boot loading bar, a black+green "coding" aesthetic, +then a persistent title + author credit) landed on top; queue empty pending an +M7 proposal._ --- @@ -195,6 +196,26 @@ aesthetic) landed on top; queue empty pending an M7 proposal._ 30.7fps). Deployed to the linked Netlify site (`live-depth-field`). Pre-change HEAD (rollback) `d455809`. +- **Polish — persistent title + author credit (user-directed, not a milestone).** + The "Live Depth Field" name previously lived only on the boot loader; it now + also sits **persistently at the top-middle of the running app**, with the + author credit **"by Noah Federovitch"** beneath it. Both are static + presentational chrome — an inline `#app-title` overlay in `index.html` (like + `#boot-loader`, so it's in the page from the first paint and testable in the + raw server bytes) styled in `src/style.css` to mirror the boot loader's + phosphor-green `.boot-title` (uppercase name in `--accent` with a green glow, + dimmer `--accent-dim` credit line, monospace). `pointer-events: none` keeps the + overlay from ever intercepting orbit drags on the canvas underneath, and + `z-index: 5` (below the loader's 10) means the loader still fully covers it + during boot. **No JS** — `src/main.js`, the render loop, OrbitControls, the + geometry/shader, and the M4 decoupling machinery are byte-unchanged. Smoke test + asserts both strings ship in the raw HTML AND the live title is visible, + horizontally centered near the top, and does not capture pointer events over + the canvas (`elementFromPoint` at its center returns the canvas). Proven + non-tautological (RED on the prior HTML: no `#app-title`). Verified visually on + a real build (centered green title + credit above the orbiting cloud, ~21fps). + Pre-change HEAD (rollback) `d5546e8`. + **Carry-over facts from M3/M4 (do not re-derive):** - Depth output contract: `depth` RawImage, Uint8, 1 channel, input W×H, 0–255 diff --git a/index.html b/index.html index bb2924b..0eb44f8 100644 --- a/index.html +++ b/index.html @@ -83,6 +83,17 @@
+ +
+
Live Depth Field
+
by Noah Federovitch
+
diff --git a/src/style.css b/src/style.css index 29eeb2e..b6f855c 100644 --- a/src/style.css +++ b/src/style.css @@ -21,6 +21,37 @@ canvas { display: block; } +/* Persistent title + author credit, docked top-middle over the canvas. Mirrors + the boot loader's phosphor-green .boot-title so the name carries over from the + loading screen. pointer-events:none so the overlay never steals orbit drags + from the canvas underneath; z-index sits below #boot-loader (10) so the loader + still fully covers it during boot. */ +#app-title { + position: fixed; + top: 12px; + left: 50%; + transform: translateX(-50%); + z-index: 5; + text-align: center; + pointer-events: none; + user-select: none; +} + +#app-title .app-title-name { + font: 600 15px/1 var(--mono); + letter-spacing: 0.2em; + text-transform: uppercase; + color: var(--accent); + text-shadow: 0 0 12px rgba(0, 255, 156, 0.6); +} + +#app-title .app-title-credit { + margin-top: 5px; + font: 11px/1 var(--mono); + letter-spacing: 0.08em; + color: var(--accent-dim); +} + /* Milestone 2: live splat-uniform sliders, docked top-left over the canvas. */ #controls { position: fixed; diff --git a/tests/smoke.spec.js b/tests/smoke.spec.js index 0d29b7a..68cf0ef 100644 --- a/tests/smoke.spec.js +++ b/tests/smoke.spec.js @@ -717,6 +717,68 @@ test('aesthetic: pre-upload cloud tint reads green (coding aesthetic)', async ({ expect(errors, `unexpected page errors:\n${errors.join('\n')}`).toEqual([]); }); +// Title + credit (user-directed, not a milestone). The "Live Depth Field" name +// from the boot loader must ALSO live on the running app — persistently, at the +// top-middle of the screen — and the project must carry the author credit +// "by Noah Federovitch". Both are static presentational chrome inlined in the +// initial HTML (like the boot loader), so this asserts (a) they ship in the raw +// server bytes AND (b) the name sits horizontally centered near the top of the +// live page, and (c) the title overlay never intercepts pointer input (so it +// can't block orbiting the cloud underneath it). Non-tautological: no #app-title +// exists before this change — every assertion is RED on the prior HTML. +test('title: Live Depth Field name + Noah Federovitch credit shown at top center', async ({ + page, +}) => { + const errors = []; + page.on('pageerror', (e) => errors.push(`pageerror: ${e}`)); + page.on('console', (m) => { + if (m.type() === 'error') errors.push(`console.error: ${m.text()}`); + }); + + // (a) Both strings ship in the exact bytes the server sends — static chrome, + // not JS-injected. Fetched raw so a scripted node can't satisfy it. + const html = await (await page.request.get('/')).text(); + expect(html, 'initial HTML must contain an #app-title overlay').toMatch( + /id=["']app-title["']/, + ); + expect(html, 'the title must show the app name').toContain('Live Depth Field'); + expect(html, 'the project must carry the author credit').toContain( + 'by Noah Federovitch', + ); + + // (b) Live: the title is visible and its text is present once the page loads. + await page.goto('/'); + const title = page.locator('#app-title'); + await expect(title).toBeVisible(); + await expect(title).toContainText('Live Depth Field'); + await expect(title).toContainText(/by Noah Federovitch/i); + + // Top-middle placement: horizontally centered on the viewport, near the top. + const box = await title.boundingBox(); + const viewport = page.viewportSize(); + const titleCenter = box.x + box.width / 2; + const viewportCenter = viewport.width / 2; + expect( + Math.abs(titleCenter - viewportCenter), + `title must be horizontally centered (center ${titleCenter} vs ${viewportCenter})`, + ).toBeLessThan(40); + expect(box.y, 'title must sit near the top of the screen').toBeLessThan(80); + + // (c) The overlay sits over the canvas but must not steal orbit input — the + // element under its center point must be the canvas, not the title. + const underCenter = await page.evaluate(() => { + const el = document.getElementById('app-title'); + const r = el.getBoundingClientRect(); + const hit = document.elementFromPoint(r.left + r.width / 2, r.top + r.height / 2); + return hit ? hit.tagName.toLowerCase() : null; + }); + expect(underCenter, 'title must not capture pointer events over the canvas').toBe( + 'canvas', + ); + + expect(errors, `unexpected page errors:\n${errors.join('\n')}`).toEqual([]); +}); + // Milestone 6: WASM inference moves into a Web Worker so the main thread stays // responsive during a pass. The worker itself can't run in CI (it loads the // real ~100MB model — the worker-path proof is a local real-model probe, like