diff --git a/.omo/evidence/design-system-fidelity-v2.md b/.omo/evidence/design-system-fidelity-v2.md new file mode 100644 index 0000000..2bebb8c --- /dev/null +++ b/.omo/evidence/design-system-fidelity-v2.md @@ -0,0 +1,39 @@ +# Design System Fidelity v2 Evidence + +## Scope + +- Branch: `code-yeongyu/lazycodex-design-system-fidelity` +- Base: `origin/main` at `4a15494c5bff789a45047c753d167dac60512579` +- Worktree: `/Users/yeongyu/local-workspaces/lazycodex-wt/design-system-fidelity-v2` +- Control baseline: clean `origin/main` production build + +## Design System Work + +- Extracted the existing Tailwind theme, root CSS tokens, base layer, and shared card-gradient utilities from `packages/web/app/globals.css` into `packages/web/app/styles/design-system.css`. +- Kept `globals.css` as the import hub for Tailwind, shared design-system CSS, and page composition CSS. +- Updated `packages/web/DESIGN.md` so the documented token values and component families match the current rendered green identity. +- Preserved current visual output, behavior, and page experience; no redesign was introduced. + +## Visual QA + +- Browser harness: `.omo/scripts/design-system-fidelity/capture-and-drive.mjs` +- Evidence directory: `.omo/ulw-loop/evidence/design-system-fidelity-v2/` +- Routes compared: `/`, `/docs` +- Viewports compared: `390x844`, `768x1024`, `1280x800` +- Pixel-diff summary: all 6 comparisons passed with `diffPixels: 0`, `diffRatio: 0`, `similarityScore: 100`, `dimensionsMatch: true`, and `alphaChannelIntact: true`. +- Interaction checks passed: copy install command, landing docs navigation, docs mobile menu, Command-K search focus, docs search filtering, and docs hash navigation. +- Console/page health: no console warnings/errors and no page errors recorded during the capture run. + +## Automated Verification + +- `pnpm install --frozen-lockfile` passed in `packages/web`. +- `pnpm run build` passed in `packages/web`. +- `pnpm run lint` passed in `packages/web`. +- `pnpm run type-check` passed in `packages/web`. +- `pnpm run test:e2e` passed in `packages/web` after refreshing a stale docs-copy assertion that also failed on clean `origin/main`. +- Lighthouse in the e2e suite reached 100/100/100/100 on both mobile and desktop using real Chrome. + +## Notes + +- The stale docs-copy assertion was updated from old rendered copy to the current `content/docs/skills.md` wording. +- The old closed draft PR #70 is superseded by this fresh branch from current `origin/main`. diff --git a/.omo/scripts/design-system-fidelity/capture-and-drive.mjs b/.omo/scripts/design-system-fidelity/capture-and-drive.mjs new file mode 100644 index 0000000..0242747 --- /dev/null +++ b/.omo/scripts/design-system-fidelity/capture-and-drive.mjs @@ -0,0 +1,181 @@ +import { mkdirSync, writeFileSync } from "node:fs" +import { createRequire } from "node:module" +import { join } from "node:path" + +const require = createRequire(new URL("../../../packages/web/package.json", import.meta.url)) +const { chromium } = require("@playwright/test") + +const [, , controlUrl, currentUrl, outDir] = process.argv + +if (!controlUrl || !currentUrl || !outDir) { + throw new Error("usage: capture-and-drive.mjs ") +} + +const viewports = [ + { id: "mobile", width: 390, height: 844 }, + { id: "tablet", width: 768, height: 1024 }, + { id: "desktop", width: 1280, height: 800 }, +] + +const routes = [ + { id: "landing", path: "/" }, + { id: "docs", path: "/docs" }, +] + +const starsPayload = { + stars: 12345, + formatted: "12.3k", + source: "github", +} + +function ensureDir(path) { + mkdirSync(path, { recursive: true }) +} + +async function withPage(context, events, callback) { + const page = await context.newPage() + page.on("console", (message) => { + if (["error", "warning"].includes(message.type())) { + events.console.push({ type: message.type(), text: message.text() }) + } + }) + page.on("pageerror", (error) => { + events.pageErrors.push(error.message) + }) + try { + await callback(page) + } finally { + await page.close() + } +} + +async function routeStableStars(context) { + await context.route("**/api/github-stars", async (route) => { + await route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify(starsPayload), + }) + }) +} + +async function captureSurface(browser, label, baseUrl) { + const surfaceDir = join(outDir, "screenshots", label) + ensureDir(surfaceDir) + const context = await browser.newContext() + await routeStableStars(context) + const events = { console: [], pageErrors: [] } + const captures = [] + + for (const viewport of viewports) { + for (const routeSpec of routes) { + await withPage(context, events, async (page) => { + await page.setViewportSize({ width: viewport.width, height: viewport.height }) + const target = new URL(routeSpec.path, baseUrl).toString() + await page.goto(target, { waitUntil: "networkidle" }) + await page.waitForTimeout(950) + const overflow = await page.evaluate(() => ({ + scrollWidth: document.documentElement.scrollWidth, + clientWidth: document.documentElement.clientWidth, + overflowX: document.documentElement.scrollWidth - document.documentElement.clientWidth, + })) + const fileName = `${routeSpec.id}-${viewport.id}-${viewport.width}x${viewport.height}.png` + const screenshotPath = join(surfaceDir, fileName) + await page.screenshot({ path: screenshotPath, fullPage: true }) + captures.push({ + route: routeSpec.path, + viewport, + screenshotPath, + overflow, + }) + }) + } + } + + await context.close() + return { label, baseUrl, captures, events } +} + +async function driveCurrent(browser) { + const interactionDir = join(outDir, "screenshots", "interactions") + ensureDir(interactionDir) + const context = await browser.newContext({ viewport: { width: 390, height: 844 } }) + await routeStableStars(context) + const events = { console: [], pageErrors: [] } + const checks = [] + + await withPage(context, events, async (page) => { + await page.goto(new URL("/", currentUrl).toString(), { waitUntil: "networkidle" }) + await page.waitForTimeout(950) + await page.getByRole("button", { name: "Copy install command" }).click() + const copied = await page.getByText("Copied", { exact: true }).isVisible() + checks.push({ id: "copy-install-command", passed: copied }) + await page.getByRole("link", { name: /docs/i }).first().click() + await page.waitForURL("**/docs") + checks.push({ id: "landing-docs-navigation", passed: page.url().endsWith("/docs") }) + }) + + await withPage(context, events, async (page) => { + await page.goto(new URL("/docs", currentUrl).toString(), { waitUntil: "networkidle" }) + await page.getByRole("button", { name: "Menu" }).click() + const expanded = await page.getByRole("button", { name: "Close Menu" }).getAttribute("aria-expanded") + checks.push({ id: "docs-mobile-menu-opens", passed: expanded === "true" }) + await page.screenshot({ + path: join(interactionDir, "docs-mobile-menu-open-390x844.png"), + fullPage: true, + }) + }) + + await withPage(context, events, async (page) => { + await page.setViewportSize({ width: 1280, height: 800 }) + await page.goto(new URL("/docs", currentUrl).toString(), { waitUntil: "networkidle" }) + await page.keyboard.press("Meta+K") + const focused = await page.locator("#docs-search-input").evaluate((node) => document.activeElement === node) + checks.push({ id: "docs-command-k-focuses-search", passed: focused }) + await page.locator("#docs-search-input").fill("feature") + const skillsVisible = await page + .getByLabel("Documentation", { exact: true }) + .getByRole("link", { name: "Feature coverage" }) + .isVisible() + const noMatchesHidden = (await page.getByText("No matches.").count()) === 0 + checks.push({ id: "docs-search-filters-skills", passed: skillsVisible && noMatchesHidden }) + await page.locator("#docs-search-input").fill("") + await page.getByLabel("Documentation", { exact: true }).getByRole("link", { name: "$ulw-loop" }).click() + await page.waitForURL(/#ulw-loop$/) + const inViewport = await page.locator("#ulw-loop").evaluate((node) => { + const rect = node.getBoundingClientRect() + return rect.top < window.innerHeight && rect.bottom > 0 + }) + checks.push({ id: "docs-hash-navigation", passed: inViewport }) + await page.screenshot({ + path: join(interactionDir, "docs-desktop-search-hash-1280x800.png"), + fullPage: true, + }) + }) + + await context.close() + return { checks, events } +} + +const browser = await chromium.launch({ channel: "chrome", headless: true }) +try { + ensureDir(outDir) + const control = await captureSurface(browser, "control", controlUrl) + const current = await captureSurface(browser, "current", currentUrl) + const interactions = await driveCurrent(browser) + const allChecksPassed = interactions.checks.every((check) => check.passed) + const report = { + generatedAt: new Date().toISOString(), + control, + current, + interactions, + allChecksPassed, + cleanup: "closed Playwright Chrome contexts and browser", + } + writeFileSync(join(outDir, "capture-report.json"), `${JSON.stringify(report, null, 2)}\n`) + if (!allChecksPassed) { + throw new Error("one or more interaction checks failed") + } +} finally { + await browser.close() +} diff --git a/.omo/ulw-loop/evidence/design-system-fidelity-v2/capture-report.json b/.omo/ulw-loop/evidence/design-system-fidelity-v2/capture-report.json new file mode 100644 index 0000000..cf804e8 --- /dev/null +++ b/.omo/ulw-loop/evidence/design-system-fidelity-v2/capture-report.json @@ -0,0 +1,225 @@ +{ + "generatedAt": "2026-06-24T05:40:35.854Z", + "control": { + "label": "control", + "baseUrl": "http://127.0.0.1:3213", + "captures": [ + { + "route": "/", + "viewport": { + "id": "mobile", + "width": 390, + "height": 844 + }, + "screenshotPath": ".omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/control/landing-mobile-390x844.png", + "overflow": { + "scrollWidth": 390, + "clientWidth": 390, + "overflowX": 0 + } + }, + { + "route": "/docs", + "viewport": { + "id": "mobile", + "width": 390, + "height": 844 + }, + "screenshotPath": ".omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/control/docs-mobile-390x844.png", + "overflow": { + "scrollWidth": 390, + "clientWidth": 390, + "overflowX": 0 + } + }, + { + "route": "/", + "viewport": { + "id": "tablet", + "width": 768, + "height": 1024 + }, + "screenshotPath": ".omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/control/landing-tablet-768x1024.png", + "overflow": { + "scrollWidth": 768, + "clientWidth": 768, + "overflowX": 0 + } + }, + { + "route": "/docs", + "viewport": { + "id": "tablet", + "width": 768, + "height": 1024 + }, + "screenshotPath": ".omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/control/docs-tablet-768x1024.png", + "overflow": { + "scrollWidth": 768, + "clientWidth": 768, + "overflowX": 0 + } + }, + { + "route": "/", + "viewport": { + "id": "desktop", + "width": 1280, + "height": 800 + }, + "screenshotPath": ".omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/control/landing-desktop-1280x800.png", + "overflow": { + "scrollWidth": 1280, + "clientWidth": 1280, + "overflowX": 0 + } + }, + { + "route": "/docs", + "viewport": { + "id": "desktop", + "width": 1280, + "height": 800 + }, + "screenshotPath": ".omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/control/docs-desktop-1280x800.png", + "overflow": { + "scrollWidth": 1280, + "clientWidth": 1280, + "overflowX": 0 + } + } + ], + "events": { + "console": [], + "pageErrors": [] + } + }, + "current": { + "label": "current", + "baseUrl": "http://127.0.0.1:3212", + "captures": [ + { + "route": "/", + "viewport": { + "id": "mobile", + "width": 390, + "height": 844 + }, + "screenshotPath": ".omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/current/landing-mobile-390x844.png", + "overflow": { + "scrollWidth": 390, + "clientWidth": 390, + "overflowX": 0 + } + }, + { + "route": "/docs", + "viewport": { + "id": "mobile", + "width": 390, + "height": 844 + }, + "screenshotPath": ".omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/current/docs-mobile-390x844.png", + "overflow": { + "scrollWidth": 390, + "clientWidth": 390, + "overflowX": 0 + } + }, + { + "route": "/", + "viewport": { + "id": "tablet", + "width": 768, + "height": 1024 + }, + "screenshotPath": ".omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/current/landing-tablet-768x1024.png", + "overflow": { + "scrollWidth": 768, + "clientWidth": 768, + "overflowX": 0 + } + }, + { + "route": "/docs", + "viewport": { + "id": "tablet", + "width": 768, + "height": 1024 + }, + "screenshotPath": ".omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/current/docs-tablet-768x1024.png", + "overflow": { + "scrollWidth": 768, + "clientWidth": 768, + "overflowX": 0 + } + }, + { + "route": "/", + "viewport": { + "id": "desktop", + "width": 1280, + "height": 800 + }, + "screenshotPath": ".omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/current/landing-desktop-1280x800.png", + "overflow": { + "scrollWidth": 1280, + "clientWidth": 1280, + "overflowX": 0 + } + }, + { + "route": "/docs", + "viewport": { + "id": "desktop", + "width": 1280, + "height": 800 + }, + "screenshotPath": ".omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/current/docs-desktop-1280x800.png", + "overflow": { + "scrollWidth": 1280, + "clientWidth": 1280, + "overflowX": 0 + } + } + ], + "events": { + "console": [], + "pageErrors": [] + } + }, + "interactions": { + "checks": [ + { + "id": "copy-install-command", + "passed": true + }, + { + "id": "landing-docs-navigation", + "passed": true + }, + { + "id": "docs-mobile-menu-opens", + "passed": true + }, + { + "id": "docs-command-k-focuses-search", + "passed": true + }, + { + "id": "docs-search-filters-skills", + "passed": true + }, + { + "id": "docs-hash-navigation", + "passed": true + } + ], + "events": { + "console": [], + "pageErrors": [] + } + }, + "allChecksPassed": true, + "cleanup": "closed Playwright Chrome contexts and browser" +} diff --git a/.omo/ulw-loop/evidence/design-system-fidelity-v2/diffs/docs-desktop-1280x800.json b/.omo/ulw-loop/evidence/design-system-fidelity-v2/diffs/docs-desktop-1280x800.json new file mode 100644 index 0000000..097515a --- /dev/null +++ b/.omo/ulw-loop/evidence/design-system-fidelity-v2/diffs/docs-desktop-1280x800.json @@ -0,0 +1,19 @@ +{ + "command": "image-diff", + "dimensionsMatch": true, + "reference": { + "width": 1280, + "height": 43033 + }, + "actual": { + "width": 1280, + "height": 43033 + }, + "totalPixels": 55082240, + "diffPixels": 0, + "diffRatio": 0, + "similarityScore": 100, + "alphaChannelIntact": true, + "hotspots": [], + "summary": "100/100 similarity; 0/55082240 pixels differ." +} diff --git a/.omo/ulw-loop/evidence/design-system-fidelity-v2/diffs/docs-mobile-390x844.json b/.omo/ulw-loop/evidence/design-system-fidelity-v2/diffs/docs-mobile-390x844.json new file mode 100644 index 0000000..a9c0591 --- /dev/null +++ b/.omo/ulw-loop/evidence/design-system-fidelity-v2/diffs/docs-mobile-390x844.json @@ -0,0 +1,19 @@ +{ + "command": "image-diff", + "dimensionsMatch": true, + "reference": { + "width": 390, + "height": 61427 + }, + "actual": { + "width": 390, + "height": 61427 + }, + "totalPixels": 23956530, + "diffPixels": 0, + "diffRatio": 0, + "similarityScore": 100, + "alphaChannelIntact": true, + "hotspots": [], + "summary": "100/100 similarity; 0/23956530 pixels differ." +} diff --git a/.omo/ulw-loop/evidence/design-system-fidelity-v2/diffs/docs-tablet-768x1024.json b/.omo/ulw-loop/evidence/design-system-fidelity-v2/diffs/docs-tablet-768x1024.json new file mode 100644 index 0000000..88ef10c --- /dev/null +++ b/.omo/ulw-loop/evidence/design-system-fidelity-v2/diffs/docs-tablet-768x1024.json @@ -0,0 +1,19 @@ +{ + "command": "image-diff", + "dimensionsMatch": true, + "reference": { + "width": 768, + "height": 43887 + }, + "actual": { + "width": 768, + "height": 43887 + }, + "totalPixels": 33705216, + "diffPixels": 0, + "diffRatio": 0, + "similarityScore": 100, + "alphaChannelIntact": true, + "hotspots": [], + "summary": "100/100 similarity; 0/33705216 pixels differ." +} diff --git a/.omo/ulw-loop/evidence/design-system-fidelity-v2/diffs/landing-desktop-1280x800.json b/.omo/ulw-loop/evidence/design-system-fidelity-v2/diffs/landing-desktop-1280x800.json new file mode 100644 index 0000000..4e51c87 --- /dev/null +++ b/.omo/ulw-loop/evidence/design-system-fidelity-v2/diffs/landing-desktop-1280x800.json @@ -0,0 +1,19 @@ +{ + "command": "image-diff", + "dimensionsMatch": true, + "reference": { + "width": 1280, + "height": 4654 + }, + "actual": { + "width": 1280, + "height": 4654 + }, + "totalPixels": 5957120, + "diffPixels": 0, + "diffRatio": 0, + "similarityScore": 100, + "alphaChannelIntact": true, + "hotspots": [], + "summary": "100/100 similarity; 0/5957120 pixels differ." +} diff --git a/.omo/ulw-loop/evidence/design-system-fidelity-v2/diffs/landing-mobile-390x844.json b/.omo/ulw-loop/evidence/design-system-fidelity-v2/diffs/landing-mobile-390x844.json new file mode 100644 index 0000000..3600401 --- /dev/null +++ b/.omo/ulw-loop/evidence/design-system-fidelity-v2/diffs/landing-mobile-390x844.json @@ -0,0 +1,19 @@ +{ + "command": "image-diff", + "dimensionsMatch": true, + "reference": { + "width": 390, + "height": 6697 + }, + "actual": { + "width": 390, + "height": 6697 + }, + "totalPixels": 2611830, + "diffPixels": 0, + "diffRatio": 0, + "similarityScore": 100, + "alphaChannelIntact": true, + "hotspots": [], + "summary": "100/100 similarity; 0/2611830 pixels differ." +} diff --git a/.omo/ulw-loop/evidence/design-system-fidelity-v2/diffs/landing-tablet-768x1024.json b/.omo/ulw-loop/evidence/design-system-fidelity-v2/diffs/landing-tablet-768x1024.json new file mode 100644 index 0000000..67183f8 --- /dev/null +++ b/.omo/ulw-loop/evidence/design-system-fidelity-v2/diffs/landing-tablet-768x1024.json @@ -0,0 +1,19 @@ +{ + "command": "image-diff", + "dimensionsMatch": true, + "reference": { + "width": 768, + "height": 5110 + }, + "actual": { + "width": 768, + "height": 5110 + }, + "totalPixels": 3924480, + "diffPixels": 0, + "diffRatio": 0, + "similarityScore": 100, + "alphaChannelIntact": true, + "hotspots": [], + "summary": "100/100 similarity; 0/3924480 pixels differ." +} diff --git a/.omo/ulw-loop/evidence/design-system-fidelity-v2/diffs/summary.json b/.omo/ulw-loop/evidence/design-system-fidelity-v2/diffs/summary.json new file mode 100644 index 0000000..6813332 --- /dev/null +++ b/.omo/ulw-loop/evidence/design-system-fidelity-v2/diffs/summary.json @@ -0,0 +1,56 @@ +[ + { + "file": "docs-desktop-1280x800.json", + "dimensionsMatch": true, + "diffPixels": 0, + "diffRatio": 0, + "similarityScore": 100, + "alphaChannelIntact": true, + "hotspots": 0 + }, + { + "file": "docs-mobile-390x844.json", + "dimensionsMatch": true, + "diffPixels": 0, + "diffRatio": 0, + "similarityScore": 100, + "alphaChannelIntact": true, + "hotspots": 0 + }, + { + "file": "docs-tablet-768x1024.json", + "dimensionsMatch": true, + "diffPixels": 0, + "diffRatio": 0, + "similarityScore": 100, + "alphaChannelIntact": true, + "hotspots": 0 + }, + { + "file": "landing-desktop-1280x800.json", + "dimensionsMatch": true, + "diffPixels": 0, + "diffRatio": 0, + "similarityScore": 100, + "alphaChannelIntact": true, + "hotspots": 0 + }, + { + "file": "landing-mobile-390x844.json", + "dimensionsMatch": true, + "diffPixels": 0, + "diffRatio": 0, + "similarityScore": 100, + "alphaChannelIntact": true, + "hotspots": 0 + }, + { + "file": "landing-tablet-768x1024.json", + "dimensionsMatch": true, + "diffPixels": 0, + "diffRatio": 0, + "similarityScore": 100, + "alphaChannelIntact": true, + "hotspots": 0 + } +] diff --git a/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/control/docs-desktop-1280x800.png b/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/control/docs-desktop-1280x800.png new file mode 100644 index 0000000..6d8b7fa Binary files /dev/null and b/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/control/docs-desktop-1280x800.png differ diff --git a/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/control/docs-mobile-390x844.png b/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/control/docs-mobile-390x844.png new file mode 100644 index 0000000..7336ec8 Binary files /dev/null and b/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/control/docs-mobile-390x844.png differ diff --git a/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/control/docs-tablet-768x1024.png b/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/control/docs-tablet-768x1024.png new file mode 100644 index 0000000..3e3bda8 Binary files /dev/null and b/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/control/docs-tablet-768x1024.png differ diff --git a/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/control/landing-desktop-1280x800.png b/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/control/landing-desktop-1280x800.png new file mode 100644 index 0000000..6b673c5 Binary files /dev/null and b/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/control/landing-desktop-1280x800.png differ diff --git a/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/control/landing-mobile-390x844.png b/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/control/landing-mobile-390x844.png new file mode 100644 index 0000000..dbb41f9 Binary files /dev/null and b/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/control/landing-mobile-390x844.png differ diff --git a/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/control/landing-tablet-768x1024.png b/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/control/landing-tablet-768x1024.png new file mode 100644 index 0000000..05607bf Binary files /dev/null and b/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/control/landing-tablet-768x1024.png differ diff --git a/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/current/docs-desktop-1280x800.png b/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/current/docs-desktop-1280x800.png new file mode 100644 index 0000000..6d8b7fa Binary files /dev/null and b/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/current/docs-desktop-1280x800.png differ diff --git a/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/current/docs-mobile-390x844.png b/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/current/docs-mobile-390x844.png new file mode 100644 index 0000000..7336ec8 Binary files /dev/null and b/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/current/docs-mobile-390x844.png differ diff --git a/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/current/docs-tablet-768x1024.png b/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/current/docs-tablet-768x1024.png new file mode 100644 index 0000000..3e3bda8 Binary files /dev/null and b/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/current/docs-tablet-768x1024.png differ diff --git a/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/current/landing-desktop-1280x800.png b/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/current/landing-desktop-1280x800.png new file mode 100644 index 0000000..6b673c5 Binary files /dev/null and b/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/current/landing-desktop-1280x800.png differ diff --git a/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/current/landing-mobile-390x844.png b/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/current/landing-mobile-390x844.png new file mode 100644 index 0000000..dbb41f9 Binary files /dev/null and b/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/current/landing-mobile-390x844.png differ diff --git a/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/current/landing-tablet-768x1024.png b/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/current/landing-tablet-768x1024.png new file mode 100644 index 0000000..05607bf Binary files /dev/null and b/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/current/landing-tablet-768x1024.png differ diff --git a/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/interactions/docs-desktop-search-hash-1280x800.png b/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/interactions/docs-desktop-search-hash-1280x800.png new file mode 100644 index 0000000..d0a7512 Binary files /dev/null and b/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/interactions/docs-desktop-search-hash-1280x800.png differ diff --git a/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/interactions/docs-mobile-menu-open-390x844.png b/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/interactions/docs-mobile-menu-open-390x844.png new file mode 100644 index 0000000..51a0c13 Binary files /dev/null and b/.omo/ulw-loop/evidence/design-system-fidelity-v2/screenshots/interactions/docs-mobile-menu-open-390x844.png differ diff --git a/packages/web/DESIGN.md b/packages/web/DESIGN.md index 00ac960..8a34253 100644 --- a/packages/web/DESIGN.md +++ b/packages/web/DESIGN.md @@ -1,5 +1,10 @@ # LazyCodex Design System +Implementation sources: +- Browser CSS tokens and shared utility layers live in `app/styles/design-system.css`, imported before page-specific styles by `app/globals.css`. +- Social preview tokens live in `app/og-image-theme.ts` and intentionally mirror the browser palette. +- Page-specific composition styles live in `app/styles/landing.css` and `app/styles/docs.css`. + ## 1. Core Philosophy - **Complex-Codebase Harness Tone**: A near-black canvas with a faint cool-green undertone, centered on a glowing emerald card that presents LazyCodex as the Codex agent harness for serious repositories. The brand color is green — emerald/mint — not the earlier teal. - **Card-in-Canvas Architecture**: The hero content lives inside a 1200x630px card with a complex radial gradient; the OpenGraph image mirrors that HTML card instead of using a separate visual language. @@ -11,21 +16,21 @@ Surfaces (near-black, cool-green undertone): - `--surface-1`: `rgba(255,255,255,0.018)` · `--surface-2`: `rgba(255,255,255,0.035)` · `--surface-3`: `rgba(255,255,255,0.055)` - `--card-base` / `--surface-panel`: `#0E1411` · `--surface-panel-alt`: `#0C1310` · `--surface-panel-deep`: `#0D1310` -Brand (emerald core): -- `--brand-core`: `#10b981` · `--brand-mid`: `#059669` · `--brand-outer`: `#065f46` +Brand (green core): +- `--brand-core`: `#22c55e` · `--brand-mid`: `#16a34a` · `--brand-outer`: `#15803d` Accent (the live-wire green): -- `--accent-primary`: `#34d399` · `--accent-primary-soft`: `rgba(52,211,153,0.1)` · `--accent-primary-border`: `rgba(52,211,153,0.22)` -- `--accent-mint`: `#6ee7b7` · `--accent-glow`: `#6ee7b7` +- `--accent-primary`: `#4ade80` · `--accent-primary-soft`: `rgba(74,222,128,0.1)` · `--accent-primary-border`: `rgba(74,222,128,0.24)` +- `--accent-mint`: `#86efac` · `--accent-glow`: `#86efac` - Legacy aliases `--accent-cyan` / `--accent-teal` are mapped to the green values for compatibility; new code uses `--accent-primary` / `--accent-mint`. Text: -- `--text-primary`: `#ffffff` · `--text-secondary`: `#9aa6a0` · `--text-tertiary`: `#7a857f` -- `--text-muted`: `rgba(255,255,255,0.72)` · `--text-soft`: `#d1fae5` (mint-tinted) +- `--text-primary`: `#ffffff` · `--text-secondary`: `#b8c2bc` · `--text-tertiary`: `#8b9690` +- `--text-muted`: `rgba(255,255,255,0.74)` · `--text-soft`: `#dcfce7` (mint-tinted) Borders / status: - `--border-subtle`: `rgba(255,255,255,0.06)` · `--border-default`: `rgba(255,255,255,0.1)` -- `--status-success`: `#10b981` · `--status-warning`: `#f59e0b` · `--status-error`: `#ef4444` +- `--status-success`: `#22c55e` · `--status-warning`: `#f59e0b` · `--status-error`: `#ef4444` ## 3. Brand Mark - A rounded-square emerald mark with an "L" stroke and a mint dot — a clean geometric identity replacing the earlier boulder. Inline SVG in the header (zero network bytes), mirrored in the favicon and OG image. @@ -36,7 +41,7 @@ Borders / status: - **Docs**: three-column grid on desktop — `260px` sidebar | fluid content | `220px` right ToC. Collapses to two columns (hide ToC) under 1100px and to a single column with a mobile menu toggle under 768px. Every container uses `min-h-[100dvh]` (never `h-screen`) and `dvh` for iOS Safari stability. No flexbox percentage math; CSS Grid for multi-column. ## 5. Gradients & Effects -- **Base Gradient**: radial from `#10b981` through `#059669` and `#065f46` into `#0a0c0b`. +- **Base Gradient**: radial from `#22c55e` through `#16a34a` and `#15803d` into `#0a0c0b`. - **Beam**: screen blend mode, soft mint light pouring from top-left. - **Sheen**: screen blend mode, diagonal linear gradients with blur. - **Pools**: screen blend mode, subtle emerald/mint pools at bottom-left and top-right. @@ -61,7 +66,14 @@ The docs are a single richly-sectioned page with a grouped sidebar, ⌘K search, - Explicit `width`/`height` on every `` to prevent CLS; `loading`/`fetchPriority`/`decoding` driven by `priority`. - Per-route `metadata`, JSON-LD `SoftwareApplication`, `sitemap.ts`, `robots.ts`, and OG/Twitter image routes. -## 9. Anti-Patterns +## 9. Component Families +- **Brand mark**: inline SVG rounded square, `var(--card-base)` fill, `var(--accent-primary)` stroke, mint dot. Do not replace it with raster imagery above the fold. +- **Hero card**: `var(--card-base)` surface, 20px browser radius, layered `.card-gradient-*` utilities, large left-aligned wordmark, right-anchored mark on desktop. +- **Command cards**: `var(--surface-panel)` surface, subtle white border, mono heading/code, green icon well, and dot-list facts. +- **Docs shell**: CSS Grid desktop layout (`260px | content | 220px`), sticky sidebar and ToC, mobile menu toggle under 768px, active nav indicated by a straight vertical bar. +- **Interactive states**: short color/transform transitions, focus rings from `:focus-visible`, no layout-property animation. + +## 10. Anti-Patterns - Generic AI-SaaS slop copy. Use concrete product language. - Teal/cyan brand colors (replaced by emerald/mint). The earlier teal identity is gone; green is the brand. - `export const runtime = "edge"` (incompatible with `@opennextjs/cloudflare`). diff --git a/packages/web/app/globals.css b/packages/web/app/globals.css index fc68e39..6eb9f03 100644 --- a/packages/web/app/globals.css +++ b/packages/web/app/globals.css @@ -1,114 +1,3 @@ @import "tailwindcss"; +@import "./styles/design-system.css"; @import "./styles/landing.css"; - -@theme { - /* A native system font stack — no webfont. This keeps the render-critical - path (and Lighthouse's Lantern LCP graph) free of any font download, so - LCP lands at FCP. On Apple this renders as SF Pro, on Windows Segoe UI: - a clean, premium grotesk that suits the Codex aesthetic. */ - --font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, - Helvetica, Arial, sans-serif; - --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; -} - -@layer base { - :root { - /* Surfaces — near-black canvas with a faint cool-green undertone. */ - --surface-base: #0a0c0b; - --surface-0: #0a0c0b; - --surface-1: rgba(255, 255, 255, 0.018); - --surface-2: rgba(255, 255, 255, 0.035); - --surface-3: rgba(255, 255, 255, 0.055); - --card-base: #0E1411; - --surface-night: #0a0c0b; - --surface-panel: #0E1411; - --surface-panel-alt: #0C1310; - --surface-panel-deep: #0D1310; - - /* Brand — green core (green-500 family), clearly green not teal. */ - --brand-core: #22c55e; - --brand-mid: #16a34a; - --brand-outer: #15803d; - - /* Text. */ - --text-primary: #ffffff; - --text-secondary: #b8c2bc; - --text-tertiary: #8b9690; - --text-muted: rgba(255, 255, 255, 0.74); - --text-soft: #dcfce7; - - /* Accent — the live-wire green (green-400/300, unambiguously green). */ - --accent-primary: #4ade80; - --accent-primary-soft: rgba(74, 222, 128, 0.1); - --accent-primary-border: rgba(74, 222, 128, 0.24); - --accent-cyan: #4ade80; - --accent-teal: #22c55e; - --accent-mint: #86efac; - --accent-glow: #86efac; - - /* Borders. */ - --border-subtle: rgba(255, 255, 255, 0.06); - --border-default: rgba(255, 255, 255, 0.1); - - /* Status. */ - --status-success: #22c55e; - --status-warning: #f59e0b; - --status-error: #ef4444; - } - - html { - background-color: var(--surface-base); - color-scheme: dark; - } - - body { - background-color: var(--surface-base); - color: var(--text-primary); - font-feature-settings: "rlig" 1, "calt" 1, "ss01" 1; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - text-rendering: optimizeLegibility; - } - - ::selection { - background-color: var(--brand-outer); - color: var(--text-primary); - } - - a { - color: inherit; - } - - :focus-visible { - border-radius: 6px; - outline: 2px solid var(--accent-primary); - outline-offset: 3px; - } -} - -@layer utilities { - .card-gradient-base { - background: radial-gradient(120% 100% at 60% 65%, #22c55e 0%, #16a34a 35%, #15803d 70%, #0a0c0b 100%); - } - - .card-gradient-beam { - background: radial-gradient(55% 55% at 38% -8%, rgba(134,239,172,0.55) 0%, rgba(74,222,128,0.22) 35%, rgba(255,255,255,0) 65%), - radial-gradient(32% 28% at 55% -5%, rgba(134,239,172,0.38) 0%, rgba(255,255,255,0) 70%); - mix-blend-mode: screen; - } - - .card-gradient-sheen { - background: linear-gradient(118deg, transparent 18%, rgba(134,239,172,0.16) 26%, rgba(134,239,172,0.30) 30%, rgba(134,239,172,0.12) 35%, transparent 45%), - linear-gradient(112deg, transparent 8%, rgba(74,222,128,0.12) 15%, transparent 28%); - filter: blur(20px); - mix-blend-mode: screen; - opacity: 0.85; - } - - .card-gradient-pools { - background: radial-gradient(55% 50% at 8% 95%, rgba(34,197,94,0.26), transparent 70%), - radial-gradient(45% 45% at 95% 40%, rgba(134,239,172,0.20), transparent 70%); - mix-blend-mode: screen; - } - -} diff --git a/packages/web/app/styles/design-system.css b/packages/web/app/styles/design-system.css new file mode 100644 index 0000000..7b554c9 --- /dev/null +++ b/packages/web/app/styles/design-system.css @@ -0,0 +1,110 @@ +@theme { + /* A native system font stack — no webfont. This keeps the render-critical + path (and Lighthouse's Lantern LCP graph) free of any font download, so + LCP lands at FCP. On Apple this renders as SF Pro, on Windows Segoe UI: + a clean, premium grotesk that suits the Codex aesthetic. */ + --font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, + Helvetica, Arial, sans-serif; + --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; +} + +@layer base { + :root { + /* Surfaces — near-black canvas with a faint cool-green undertone. */ + --surface-base: #0a0c0b; + --surface-0: #0a0c0b; + --surface-1: rgba(255, 255, 255, 0.018); + --surface-2: rgba(255, 255, 255, 0.035); + --surface-3: rgba(255, 255, 255, 0.055); + --card-base: #0E1411; + --surface-night: #0a0c0b; + --surface-panel: #0E1411; + --surface-panel-alt: #0C1310; + --surface-panel-deep: #0D1310; + + /* Brand — green core (green-500 family), clearly green not teal. */ + --brand-core: #22c55e; + --brand-mid: #16a34a; + --brand-outer: #15803d; + + /* Text. */ + --text-primary: #ffffff; + --text-secondary: #b8c2bc; + --text-tertiary: #8b9690; + --text-muted: rgba(255, 255, 255, 0.74); + --text-soft: #dcfce7; + + /* Accent — the live-wire green (green-400/300, unambiguously green). */ + --accent-primary: #4ade80; + --accent-primary-soft: rgba(74, 222, 128, 0.1); + --accent-primary-border: rgba(74, 222, 128, 0.24); + --accent-cyan: #4ade80; + --accent-teal: #22c55e; + --accent-mint: #86efac; + --accent-glow: #86efac; + + /* Borders. */ + --border-subtle: rgba(255, 255, 255, 0.06); + --border-default: rgba(255, 255, 255, 0.1); + + /* Status. */ + --status-success: #22c55e; + --status-warning: #f59e0b; + --status-error: #ef4444; + } + + html { + background-color: var(--surface-base); + color-scheme: dark; + } + + body { + background-color: var(--surface-base); + color: var(--text-primary); + font-feature-settings: "rlig" 1, "calt" 1, "ss01" 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; + } + + ::selection { + background-color: var(--brand-outer); + color: var(--text-primary); + } + + a { + color: inherit; + } + + :focus-visible { + border-radius: 6px; + outline: 2px solid var(--accent-primary); + outline-offset: 3px; + } +} + +@layer utilities { + .card-gradient-base { + background: radial-gradient(120% 100% at 60% 65%, #22c55e 0%, #16a34a 35%, #15803d 70%, #0a0c0b 100%); + } + + .card-gradient-beam { + background: radial-gradient(55% 55% at 38% -8%, rgba(134,239,172,0.55) 0%, rgba(74,222,128,0.22) 35%, rgba(255,255,255,0) 65%), + radial-gradient(32% 28% at 55% -5%, rgba(134,239,172,0.38) 0%, rgba(255,255,255,0) 70%); + mix-blend-mode: screen; + } + + .card-gradient-sheen { + background: linear-gradient(118deg, transparent 18%, rgba(134,239,172,0.16) 26%, rgba(134,239,172,0.30) 30%, rgba(134,239,172,0.12) 35%, transparent 45%), + linear-gradient(112deg, transparent 8%, rgba(74,222,128,0.12) 15%, transparent 28%); + filter: blur(20px); + mix-blend-mode: screen; + opacity: 0.85; + } + + .card-gradient-pools { + background: radial-gradient(55% 50% at 8% 95%, rgba(34,197,94,0.26), transparent 70%), + radial-gradient(45% 45% at 95% 40%, rgba(134,239,172,0.20), transparent 70%); + mix-blend-mode: screen; + } +} diff --git a/packages/web/e2e/docs.spec.ts b/packages/web/e2e/docs.spec.ts index a29e7a2..cf0865b 100644 --- a/packages/web/e2e/docs.spec.ts +++ b/packages/web/e2e/docs.spec.ts @@ -64,13 +64,13 @@ test.describe("docs page — structure", () => { await page.goto("/docs") const body = page.locator("body") - await expect(body).toContainText("Built-in workflows") + await expect(body).toContainText("Skills are specialist playbooks") await expect(body).toContainText("$init-deep") await expect(body).toContainText("Feature coverage") await expect(body).toContainText("review-work") await expect(body).toContainText("remove-ai-slops") await expect(body).toContainText("frontend") - await expect(body).toContainText("Most skills auto-activate") + await expect(body).toContainText("They auto-activate") await expect(body).toContainText("Maximum-saturation research") await expect(body).toContainText("LSP") await expect(body).toContainText("AST-grep")