diff --git a/STATUS.md b/STATUS.md index 9294fa4..1c3e65b 100644 --- a/STATUS.md +++ b/STATUS.md @@ -4,10 +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; three +_Last updated: 2026-07-04 — Milestone 6 (Web Worker WASM inference) done; four 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._ +a persistent title + author credit, then a bottom "Built with Claude Fable 5" +build credit) landed on top; queue empty pending an M7 proposal._ --- @@ -216,6 +216,18 @@ M7 proposal._ a real build (centered green title + credit above the orbiting cloud, ~21fps). Pre-change HEAD (rollback) `d5546e8`. +- **Polish — bottom "Built with Claude Fable 5" build credit (user-directed, not + a milestone).** A bottom-middle build-attribution line, the counterpart to the + top `#app-title`. Same treatment: an inline `#build-credit` div in `index.html` + styled in `src/style.css` (`position: fixed; bottom: 12px`, centered, dim + `--accent-dim` monospace), `pointer-events: none` + `z-index: 5` so it never + intercepts orbit drags and the boot loader still covers it during boot. **No + JS.** Smoke test asserts the string ships in the raw HTML AND the live credit + is visible, horizontally centered near the bottom, and doesn't capture pointer + events over the canvas. Proven non-tautological (RED on the prior HTML: no + `#build-credit`). Verified visually on a real build. Pre-change HEAD (rollback) + `94b4084`. + **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 0eb44f8..fd0186e 100644 --- a/index.html +++ b/index.html @@ -94,6 +94,12 @@
Live Depth Field
by Noah Federovitch
+ +
Built with Claude Fable 5
diff --git a/src/style.css b/src/style.css index b6f855c..879cc3d 100644 --- a/src/style.css +++ b/src/style.css @@ -52,6 +52,23 @@ canvas { color: var(--accent-dim); } +/* Build attribution, docked bottom-middle over the canvas — counterpart to + #app-title. Same pointer-events:none / below-boot-loader rules so it never + steals orbit drags and the loader still covers it during boot. */ +#build-credit { + position: fixed; + bottom: 12px; + left: 50%; + transform: translateX(-50%); + z-index: 5; + text-align: center; + pointer-events: none; + user-select: none; + 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 68cf0ef..a33d9f7 100644 --- a/tests/smoke.spec.js +++ b/tests/smoke.spec.js @@ -779,6 +779,67 @@ test('title: Live Depth Field name + Noah Federovitch credit shown at top center expect(errors, `unexpected page errors:\n${errors.join('\n')}`).toEqual([]); }); +// Build credit (user-directed, not a milestone). A "Built with Claude Fable 5" +// attribution must sit at the bottom-middle of the screen — the counterpart to +// the top #app-title. Static presentational chrome inlined in the initial HTML, +// so this asserts (a) it ships in the raw server bytes, (b) it's visible, +// horizontally centered near the BOTTOM of the live page, and (c) the overlay +// never intercepts pointer input (so it can't block orbiting the cloud beneath +// it). Non-tautological: no #build-credit exists before this change. +test('build credit: "Built with Claude Fable 5" shown at bottom 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) The credit ships 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 a #build-credit overlay').toMatch( + /id=["']build-credit["']/, + ); + expect(html, 'the credit must name the build tool').toContain( + 'Built with Claude Fable 5', + ); + + // (b) Live: the credit is visible and its text is present once the page loads. + await page.goto('/'); + const credit = page.locator('#build-credit'); + await expect(credit).toBeVisible(); + await expect(credit).toContainText(/Built with Claude Fable 5/i); + + // Bottom-middle placement: horizontally centered, near the bottom edge. + const box = await credit.boundingBox(); + const viewport = page.viewportSize(); + const creditCenter = box.x + box.width / 2; + const viewportCenter = viewport.width / 2; + expect( + Math.abs(creditCenter - viewportCenter), + `credit must be horizontally centered (center ${creditCenter} vs ${viewportCenter})`, + ).toBeLessThan(40); + expect( + box.y + box.height, + 'credit must sit near the bottom of the screen', + ).toBeGreaterThan(viewport.height - 80); + + // (c) The overlay must not steal orbit input — the element under its center + // point must be the canvas, not the credit. + const underCenter = await page.evaluate(() => { + const el = document.getElementById('build-credit'); + 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, 'credit 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