diff --git a/CHANGELOG.md b/CHANGELOG.md index 97c2e53..5b24252 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.1.1 — 2026-08-10 + +Presentation consistency patch for the public-ready repository. + +- Replaced the command center, side panel, boot state, and Queue Lab CSS logo approximations with the canonical transparent QueueScope app icon. +- Added browser assertions that verify both application surfaces load the packaged 128×128 icon asset. +- Regenerated and manually reviewed every tracked extension screenshot, including fresh 420×840 side-panel captures with the full header and footer visible. + ## 0.1.0 — 2026-08-10 First public-ready developer-mode alpha. diff --git a/README.md b/README.md index cbd1a5c..ac02b37 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ This is the retailer-neutral public edition. It contains no site-specific adapte ![QueueScope command center](assets/screenshots/command-center.png) -## What works in v0.1.0 +## What works in v0.1.1 - One-time, daily, weekday, weekend, and custom-weekday schedules. - Passive watches with user-controlled cadence and no attempt ceiling. @@ -54,7 +54,7 @@ Queue Lab proves product, queue, challenge, availability, position/ETA, and post ### From the release archive -1. Download `QueueScope-0.1.0-unpacked.zip` from the release assets. +1. Download `QueueScope-0.1.1-unpacked.zip` from the release assets. 2. Extract it to a permanent local folder. 3. Open your Chromium browser's extensions page (for Chrome, `chrome://extensions`). 4. Enable **Developer mode**. @@ -117,7 +117,7 @@ See [Privacy and safety](docs/PRIVACY_AND_SAFETY.md), [Architecture](docs/ARCHIT ## Validation -The v0.1.0 release gate covers: +The v0.1.1 release gate covers: - ESLint and TypeScript. - 26 deterministic unit tests. @@ -131,7 +131,7 @@ The detailed evidence is in [Test report](docs/TEST_REPORT.md), [Security review ## Project status -v0.1.0 is a functional developer-mode alpha. The private repository is ready for its owner to review and manually change to public. The current scope intentionally favors transparent, user-authored visible-page rules over bundled site integrations. +v0.1.1 is a functional developer-mode alpha. The private repository is ready for its owner to review and manually change to public. The current scope intentionally favors transparent, user-authored visible-page rules over bundled site integrations. - [Plan and completed scope](PLAN.md) - [Changelog](CHANGELOG.md) diff --git a/assets/screenshots/command-center.png b/assets/screenshots/command-center.png index ee18008..d7b42a0 100644 Binary files a/assets/screenshots/command-center.png and b/assets/screenshots/command-center.png differ diff --git a/assets/screenshots/live-run.png b/assets/screenshots/live-run.png index 2e979b0..2ace955 100644 Binary files a/assets/screenshots/live-run.png and b/assets/screenshots/live-run.png differ diff --git a/assets/screenshots/queue-lab-waiting.png b/assets/screenshots/queue-lab-waiting.png index e695ad2..bb54c42 100644 Binary files a/assets/screenshots/queue-lab-waiting.png and b/assets/screenshots/queue-lab-waiting.png differ diff --git a/assets/screenshots/queue-lab.png b/assets/screenshots/queue-lab.png index 132b768..aa9a22b 100644 Binary files a/assets/screenshots/queue-lab.png and b/assets/screenshots/queue-lab.png differ diff --git a/assets/screenshots/side-panel-watches.png b/assets/screenshots/side-panel-watches.png index 6bf2e38..f753ebd 100644 Binary files a/assets/screenshots/side-panel-watches.png and b/assets/screenshots/side-panel-watches.png differ diff --git a/assets/screenshots/side-panel.png b/assets/screenshots/side-panel.png index 5d52f44..391803c 100644 Binary files a/assets/screenshots/side-panel.png and b/assets/screenshots/side-panel.png differ diff --git a/assets/screenshots/watch-builder.png b/assets/screenshots/watch-builder.png index 376658d..6f92e26 100644 Binary files a/assets/screenshots/watch-builder.png and b/assets/screenshots/watch-builder.png differ diff --git a/docs/RELEASE_CHECKLIST.md b/docs/RELEASE_CHECKLIST.md index 9ded581..3a9c65e 100644 --- a/docs/RELEASE_CHECKLIST.md +++ b/docs/RELEASE_CHECKLIST.md @@ -1,4 +1,4 @@ -# v0.1.0 release checklist +# v0.1.1 release checklist ## Source and trust boundary @@ -25,7 +25,8 @@ - [x] Production artifact verifier passes. - [x] Disposable-profile unpacked-extension E2E passes. - [x] Side panel reviewed at 420×840. -- [x] Screenshots regenerated from v0.1.0 and manually reviewed. +- [x] Canonical transparent app icon rendered consistently across all extension surfaces. +- [x] Screenshots regenerated from v0.1.1 and manually reviewed. - [x] Dependency audit reports zero vulnerabilities. - [x] Security, CSP, permissions, secret, and neutrality reviews complete. diff --git a/docs/SECURITY_REVIEW.md b/docs/SECURITY_REVIEW.md index 8e7cd81..30f9dce 100644 --- a/docs/SECURITY_REVIEW.md +++ b/docs/SECURITY_REVIEW.md @@ -1,4 +1,4 @@ -# v0.1.0 security review +# v0.1.1 security review Date: 2026-08-10 diff --git a/docs/TEST_REPORT.md b/docs/TEST_REPORT.md index 00b059d..f23c989 100644 --- a/docs/TEST_REPORT.md +++ b/docs/TEST_REPORT.md @@ -1,4 +1,4 @@ -# v0.1.0 test report +# v0.1.1 test report Date: 2026-08-10 Environment: Windows, Node.js 24.11.1, disposable Chromium profile, 1440×1000 dashboard, 420×840 side panel. @@ -19,6 +19,7 @@ Environment: Windows, Node.js 24.11.1, disposable Chromium profile, 1440×1000 d - Loaded the production `dist` folder in a new temporary Chromium profile. - Opened the dashboard with tab title `QueueScope`. +- Confirmed the dashboard and standalone Queue Lab render the packaged `/icons/icon-128.png` asset at its canonical 128×128 intrinsic dimensions. - Confirmed the Queue Lab orbit marker remained within 3 CSS pixels of the orbit radius. - Opened the synthetic Queue Lab and transitioned from product to queue. - Persisted classification `queue`, position `428`, and provider ETA `00:08:30`. @@ -29,11 +30,11 @@ Environment: Windows, Node.js 24.11.1, disposable Chromium profile, 1440×1000 d - Confirmed side-panel footer was pinned to the viewport bottom. - Seeded a future watch in the side panel, toggled auto-start, copied it as a paused watch, and deleted the copy. - Opened the watch builder and confirmed passive mode displays no attempt ceiling. -- Captured browser-generated release screenshots without console/page errors. +- Captured browser-generated release screenshots without console/page errors, with side-panel header and footer geometry asserted before capture. ## Manual visual review -Reviewed command center, expanded live run, Queue Lab overview, Queue Lab waiting state, watch builder, and 420×840 side panel at original resolution. Verified hierarchy, no clipped status indicators, collapsed-card density, pinned panel footer, and responsive orbit alignment. The screenshots tracked in `assets/screenshots` are the reviewed release captures. +Reviewed command center, expanded live run, Queue Lab overview, Queue Lab waiting state, watch builder, and both 420×840 side-panel states at original resolution. Verified canonical icon consistency, hierarchy, no clipped status indicators, collapsed-card density, complete panel headers, pinned panel footers, and responsive orbit alignment. The screenshots tracked in `assets/screenshots` are the reviewed release captures. ## Boundaries diff --git a/harness/index.html b/harness/index.html index fa22c06..8d4bced 100644 --- a/harness/index.html +++ b/harness/index.html @@ -9,7 +9,7 @@
-
QueueScopeQUEUE LAB
SYNTHETIC · LOCAL
+
QueueScopeQUEUE LAB
SYNTHETIC · LOCAL
01STATE
CONTROLLED REHEARSAL

Product page observed

The page is loaded and no configured attention signal is present.

CLASSIFICATIONproduct
POSITION
PROVIDER ETA
diff --git a/harness/lab.css b/harness/lab.css index 7109539..5938f8d 100644 --- a/harness/lab.css +++ b/harness/lab.css @@ -1,6 +1,6 @@ :root { color-scheme: dark; font-family: Inter, "Segoe UI Variable", "Segoe UI", system-ui, sans-serif; background: #050b09; color: #f1f8f5; --mint: #49f5bd; --line: #1c322b; --muted: #8ca29a; } * { box-sizing: border-box; } body { margin: 0; min-height: 100vh; background: radial-gradient(circle at 45% 20%, rgba(73,245,189,.09), transparent 30rem), #050b09; } button { font: inherit; color: inherit; } -main { width: min(1180px, calc(100% - 34px)); margin: auto; padding: 24px 0 50px; } header { display: flex; justify-content: space-between; align-items: center; padding-bottom: 22px; border-bottom: 1px solid var(--line); }.lab-brand { display: flex; align-items: center; gap: 11px; }.lab-brand strong { display: block; font-size: 15px; }.lab-brand span { display: block; color: var(--mint); font: 600 8px/1 monospace; letter-spacing: .15em; margin-top: 4px; }.scope-mark { width: 37px; height: 37px; border: 1px solid #2b5548; border-radius: 11px; position: relative; }.scope-mark::before { content: ""; position: absolute; inset: 8px; border: 2px solid var(--mint); border-right-color: transparent; border-radius: 50%; }.scope-mark i { position: absolute; width: 5px; height: 5px; right: 6px; top: 7px; border-radius: 50%; background: var(--mint); box-shadow: 0 0 9px var(--mint); }.local-pill { border: 1px solid #2c5b4b; color: var(--mint); padding: 7px 9px; font: 600 8px monospace; letter-spacing: .1em; } +main { width: min(1180px, calc(100% - 34px)); margin: auto; padding: 24px 0 50px; } header { display: flex; justify-content: space-between; align-items: center; padding-bottom: 22px; border-bottom: 1px solid var(--line); }.lab-brand { display: flex; align-items: center; gap: 11px; }.lab-brand strong { display: block; font-size: 15px; }.lab-brand span { display: block; color: var(--mint); font: 600 8px/1 monospace; letter-spacing: .15em; margin-top: 4px; }.scope-mark { width: 37px; height: 37px; display: grid !important; place-items: center; margin-top: 0 !important; }.scope-mark img { width: 43px; height: 43px; display: block; object-fit: contain; }.local-pill { border: 1px solid #2c5b4b; color: var(--mint); padding: 7px 9px; font: 600 8px monospace; letter-spacing: .1em; } .lab-console { min-height: 480px; display: grid; grid-template-columns: 1fr 1.1fr; align-items: center; gap: 70px; padding: 42px clamp(20px,5vw,70px); border: 1px solid var(--line); border-top: 0; background: linear-gradient(135deg, rgba(73,245,189,.035), transparent 60%); }.radar { width: min(360px, 75vw); aspect-ratio: 1; border: 1px solid #2a594a; border-radius: 50%; position: relative; display: grid; place-items: center; justify-self: center; overflow: hidden; background: repeating-radial-gradient(circle, transparent 0 58px, rgba(73,245,189,.06) 59px 60px); }.radar::before, .radar::after { content: ""; position: absolute; background: linear-gradient(90deg, transparent, #2e6251, transparent); height: 1px; left: 0; right: 0; top: 50%; }.radar::after { transform: rotate(90deg); }.sweep { position: absolute; inset: 0; border-radius: 50%; background: conic-gradient(from 0deg, rgba(73,245,189,.25), transparent 20%, transparent); animation: sweep 4s linear infinite; }.ring { position: absolute; border-radius: 50%; border: 1px solid rgba(73,245,189,.16); }.ring-one { inset: 18%; }.ring-two { inset: 36%; }.center { width: 92px; height: 92px; border-radius: 50%; position: relative; z-index: 2; background: #0b1814; border: 1px solid var(--mint); display: grid; place-items: center; align-content: center; gap: 4px; box-shadow: 0 0 38px rgba(73,245,189,.12); }.center strong { font: 500 28px/1 monospace; }.center span { color: var(--mint); font: 600 8px monospace; letter-spacing: .15em; }.lab-node { width: 10px; height: 10px; position: absolute; top: -5px; left: calc(50% - 5px); background: var(--mint); border-radius: 50%; box-shadow: 0 0 14px var(--mint); transform-origin: 5px 185px; animation: orbit 7s linear infinite; z-index: 3; } @keyframes orbit { to { transform: rotate(360deg); } } @keyframes sweep { to { transform: rotate(360deg); } } .kicker { color: var(--mint); font: 600 9px monospace; letter-spacing: .13em; }.stage-copy h1 { font-size: clamp(35px,5vw,56px); line-height: .95; letter-spacing: -.055em; margin: 13px 0 17px; }.stage-copy > p { color: var(--muted); font-size: 13px; line-height: 1.65; max-width: 540px; }.telemetry { display: grid; grid-template-columns: repeat(3, 1fr); border: 1px solid var(--line); margin-top: 25px; }.telemetry > div { padding: 14px; display: grid; gap: 7px; border-right: 1px solid var(--line); }.telemetry > div:last-child { border: 0; }.telemetry span { color: #60776e; font: 600 8px monospace; }.telemetry strong { font-size: 11px; text-transform: capitalize; } .stage-controls { display: grid; grid-template-columns: repeat(5, 1fr); margin-top: 12px; border: 1px solid var(--line); }.stage-controls button { min-height: 100px; border: 0; border-right: 1px solid var(--line); background: #091310; text-align: left; padding: 16px; display: grid; grid-template-columns: auto 1fr; gap: 4px 9px; cursor: pointer; }.stage-controls button:last-child { border-right: 0; }.stage-controls button:hover { background: #0d1b16; }.stage-controls button.active { background: rgba(73,245,189,.09); box-shadow: inset 0 -2px var(--mint); }.stage-controls button > span { color: var(--mint); font: 600 9px monospace; grid-row: 1 / span 2; }.stage-controls strong { font-size: 11px; }.stage-controls small { color: var(--muted); font-size: 8px; }.explanation { border: 1px solid var(--line); margin-top: 12px; padding: 17px; display: flex; gap: 16px; align-items: start; }.explanation strong { color: var(--mint); font: 600 9px monospace; letter-spacing: .08em; white-space: nowrap; }.explanation p { margin: 0; color: var(--muted); font-size: 10px; line-height: 1.6; } #signal, #available-control { position: fixed; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); opacity: .001; } diff --git a/package-lock.json b/package-lock.json index 8a06bf4..44ac238 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "queuescope", - "version": "0.1.0", + "version": "0.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "queuescope", - "version": "0.1.0", + "version": "0.1.1", "license": "MIT", "dependencies": { "lucide-react": "0.468.0", diff --git a/package.json b/package.json index 79bdc6f..f119721 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "queuescope", - "version": "0.1.0", + "version": "0.1.1", "private": true, "description": "Retailer-neutral local-first browser operations console for scheduled page observation and virtual-queue awareness.", "type": "module", diff --git a/public/manifest.json b/public/manifest.json index dc41223..d0fe15c 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "QueueScope", - "version": "0.1.0", + "version": "0.1.1", "description": "A retailer-neutral local-first console for scheduled page observation and virtual-queue awareness.", "minimum_chrome_version": "120", "permissions": [ diff --git a/release/QueueScope-0.1.1-unpacked.sha256.txt b/release/QueueScope-0.1.1-unpacked.sha256.txt new file mode 100644 index 0000000..7af3634 --- /dev/null +++ b/release/QueueScope-0.1.1-unpacked.sha256.txt @@ -0,0 +1 @@ +64a57b7201c139d45b7971a98bda8fdadce75283d1da014e860ef699135125c2 QueueScope-0.1.1-unpacked.zip diff --git a/scripts/e2e-extension.mjs b/scripts/e2e-extension.mjs index 52075f4..5644175 100644 --- a/scripts/e2e-extension.mjs +++ b/scripts/e2e-extension.mjs @@ -40,6 +40,15 @@ try { await dashboard.goto(`chrome-extension://${id}/dashboard.html`); await dashboard.getByRole("heading", { name: "Observe the moment. Preserve the tab." }).waitFor(); if ((await dashboard.title()) !== "QueueScope") throw new Error("Dashboard tab title is not QueueScope."); + const dashboardBrand = await dashboard.locator(".brand-mark img").first().evaluate((image) => ({ + complete: image.complete, + naturalWidth: image.naturalWidth, + naturalHeight: image.naturalHeight, + file: new URL(image.src).pathname + })); + if (!dashboardBrand.complete || dashboardBrand.naturalWidth !== 128 || dashboardBrand.naturalHeight !== 128 || dashboardBrand.file !== "/icons/icon-128.png") { + throw new Error(`Dashboard did not render the canonical app icon: ${JSON.stringify(dashboardBrand)}`); + } await dashboard.screenshot({ path: join(results, "command-center.png"), fullPage: true }); await dashboard.getByRole("button", { name: "Queue Lab", exact: true }).click(); @@ -61,6 +70,15 @@ try { } if (!lab) throw new Error(`Queue Lab tab was not exposed to Playwright. Pages: ${context.pages().map((page) => page.url()).join(", ")}`); await lab.getByRole("heading", { name: "Product page observed" }).waitFor(); + const labBrand = await lab.locator(".scope-mark img").evaluate((image) => ({ + complete: image.complete, + naturalWidth: image.naturalWidth, + naturalHeight: image.naturalHeight, + file: new URL(image.src).pathname + })); + if (!labBrand.complete || labBrand.naturalWidth !== 128 || labBrand.naturalHeight !== 128 || labBrand.file !== "/icons/icon-128.png") { + throw new Error(`Queue Lab did not render the canonical app icon: ${JSON.stringify(labBrand)}`); + } await lab.getByRole("button", { name: /Queue Lock navigation/ }).click(); await lab.getByRole("heading", { name: "Waiting room detected" }).waitFor(); await lab.screenshot({ path: join(results, "queue-lab-waiting.png"), fullPage: true }); @@ -92,7 +110,7 @@ try { const footer = await panel.locator(".panel-footer").boundingBox(); const viewport = panel.viewportSize(); if (!footer || !viewport || Math.abs(footer.y + footer.height - viewport.height) > 2) throw new Error("Side-panel footer is not pinned to the viewport."); - await panel.screenshot({ path: join(results, "side-panel.png"), fullPage: true }); + await panel.screenshot({ path: join(results, "side-panel.png") }); const panelWatchName = "Synthetic recurring watch"; const savedWatch = await dashboard.evaluate(async (name) => { @@ -118,8 +136,22 @@ try { await panelCopy.getByTitle("Delete watch").click(); await panelCopy.waitFor({ state: "detached" }); await panel.waitForTimeout(4_200); - await panel.evaluate(() => window.scrollTo({ top: 0, left: 0, behavior: "instant" })); - await panel.screenshot({ path: join(results, "side-panel-watches.png"), fullPage: true }); + await panel.goto(`chrome-extension://${id}/sidepanel.html`); + await panel.getByRole("button", { name: "Watches", exact: true }).evaluate((button) => button.click()); + await panel.locator(".watch-card").filter({ hasText: panelWatchName }).waitFor(); + await panel.evaluate(() => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)))); + const watchViewport = await panel.evaluate(() => ({ + scrollY: window.scrollY, + documentTop: document.documentElement.scrollTop, + bodyTop: document.body.scrollTop, + visualTop: window.visualViewport?.pageTop ?? 0 + })); + if (Object.values(watchViewport).some((value) => Math.abs(value) > 1)) throw new Error(`Side-panel watch viewport was not reset: ${JSON.stringify(watchViewport)}`); + const watchHeader = await panel.locator(".panel-header").boundingBox(); + if (!watchHeader || Math.abs(watchHeader.y) > 2) throw new Error(`Side-panel watch capture retained a ${watchHeader?.y ?? "missing"}px header offset.`); + const watchFooter = await panel.locator(".panel-footer").boundingBox(); + if (!watchFooter || !viewport || Math.abs(watchFooter.y + watchFooter.height - viewport.height) > 2) throw new Error("Side-panel watch footer is not pinned to the viewport."); + await panel.screenshot({ path: join(results, "side-panel-watches.png") }); await dashboard.getByRole("button", { name: "Watches", exact: true }).click(); await dashboard.getByRole("button", { name: "New watch" }).click(); diff --git a/scripts/package-release.ps1 b/scripts/package-release.ps1 index f4008d1..02ce8bb 100644 --- a/scripts/package-release.ps1 +++ b/scripts/package-release.ps1 @@ -2,8 +2,8 @@ $ErrorActionPreference = "Stop" $root = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path $dist = (Resolve-Path (Join-Path $root "dist")).Path $release = Join-Path $root "release" -$archive = Join-Path $release "QueueScope-0.1.0-unpacked.zip" -$checksum = Join-Path $release "QueueScope-0.1.0-unpacked.sha256.txt" +$archive = Join-Path $release "QueueScope-0.1.1-unpacked.zip" +$checksum = Join-Path $release "QueueScope-0.1.1-unpacked.sha256.txt" if ($dist -eq $root -or -not $dist.StartsWith($root, [System.StringComparison]::OrdinalIgnoreCase)) { throw "Refusing to package an unsafe distribution path." @@ -12,6 +12,6 @@ New-Item -ItemType Directory -Force -Path $release | Out-Null if (Test-Path -LiteralPath $archive) { Remove-Item -LiteralPath $archive -Force } Compress-Archive -Path (Join-Path $dist "*") -DestinationPath $archive -CompressionLevel Optimal $hash = (Get-FileHash -LiteralPath $archive -Algorithm SHA256).Hash.ToLowerInvariant() -Set-Content -LiteralPath $checksum -Value "$hash QueueScope-0.1.0-unpacked.zip" -Encoding utf8 +Set-Content -LiteralPath $checksum -Value "$hash QueueScope-0.1.1-unpacked.zip" -Encoding utf8 Write-Output "Packaged $archive" Write-Output "SHA256 $hash" diff --git a/scripts/verify-build.mjs b/scripts/verify-build.mjs index bc7c272..24d09ed 100644 --- a/scripts/verify-build.mjs +++ b/scripts/verify-build.mjs @@ -11,7 +11,7 @@ for (const file of required) await access(resolve(root, file)); const manifest = JSON.parse(await readFile(resolve(root, "manifest.json"), "utf8")); if (manifest.manifest_version !== 3) throw new Error("Release manifest is not MV3."); -if (manifest.version !== "0.1.0") throw new Error("Release manifest version does not match 0.1.0."); +if (manifest.version !== "0.1.1") throw new Error("Release manifest version does not match 0.1.1."); if (!Array.isArray(manifest.optional_host_permissions) || manifest.optional_host_permissions.length !== 2) throw new Error("Origin access is not optional and bounded as expected."); if (manifest.content_scripts) throw new Error("Public build must not inject globally declared content scripts."); diff --git a/src/ui/App.tsx b/src/ui/App.tsx index 8a6b15d..5a195ee 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -108,7 +108,7 @@ export function App() { void act({ type: "SAVE_WATCH", watch: copy }, "Paused copy saved."); } - if (!snapshot) return

Establishing local scope…

; + if (!snapshot) return

Establishing local scope…

; if (editor && !panel) return setEditor(undefined)} onSave={saveWatch}/>; const currentNav = panel ? panelNav : nav; @@ -129,14 +129,15 @@ export function App() { } function AppFrame({ panel, page, setPage, mobileNav, setMobileNav, children }: { panel: boolean; page: Page; setPage: (page: Page) => void; mobileNav: boolean; setMobileNav: (open: boolean) => void; children: React.ReactNode }) { - if (panel) return
setMobileNav(!mobileNav)}/>{mobileNav &&
{panelNav.map((item) => { setPage(item.id); setMobileNav(false); }}/>) }
}
{children}
Safety lock readyv0.1.0
; + if (panel) return
setMobileNav(!mobileNav)}/>{mobileNav &&
{panelNav.map((item) => { setPage(item.id); setMobileNav(false); }}/>) }
}
{children}
Safety lock readyv0.1.1
; return
{children}
; } -function Brand() { return
QueueScopeLOCAL OBSERVER
; } +function BrandMark() { return ; } +function Brand() { return
QueueScopeLOCAL OBSERVER
; } function PanelHeader({ onMenu }: { onMenu: () => void }) { return
; } function NavButton({ item, active, onClick }: { item: typeof nav[number]; active: boolean; onClick: () => void }) { const Icon = item.icon; return ; } diff --git a/src/ui/styles.css b/src/ui/styles.css index 3bfd393..c22f119 100644 --- a/src/ui/styles.css +++ b/src/ui/styles.css @@ -31,10 +31,11 @@ a { color: inherit; text-decoration: none; } .app-shell { min-height: 100vh; display: grid; grid-template-columns: 248px minmax(0, 1fr); } .sidebar { position: fixed; inset: 0 auto 0 0; width: 248px; border-right: 1px solid var(--line); background: rgba(5, 12, 9, .94); backdrop-filter: blur(20px); display: flex; flex-direction: column; padding: 22px 14px; z-index: 20; } .brand { display: flex; align-items: center; gap: 11px; min-width: 0; padding: 0 8px 24px; } -.brand-mark { position: relative; width: 36px; height: 36px; border: 1px solid #285044; border-radius: 11px; background: #07110e; flex: 0 0 auto; box-shadow: inset 0 0 18px rgba(73, 245, 189, .05); } -.brand-mark::before { content: ""; position: absolute; width: 16px; height: 16px; border: 2px solid var(--mint); border-right-color: transparent; border-radius: 50%; left: 8px; top: 8px; } -.brand-mark::after { content: ""; position: absolute; width: 8px; height: 2px; background: var(--mint); left: 19px; top: 23px; transform: rotate(35deg); border-radius: 9px; } -.brand-mark span { position: absolute; width: 4px; height: 4px; border-radius: 50%; background: var(--mint); left: 25px; top: 10px; box-shadow: 0 0 10px var(--mint); } +.brand-mark { width: 36px; height: 36px; display: grid; place-items: center; flex: 0 0 auto; } +.brand-mark img { width: 42px; height: 42px; display: block; object-fit: contain; } +.boot-screen { min-height: 100vh; display: grid; place-items: center; align-content: center; gap: 12px; color: var(--muted); font-size: 11px; } +.boot-screen .brand-mark { width: 52px; height: 52px; } +.boot-screen .brand-mark img { width: 60px; height: 60px; } .brand strong { font-size: 16px; letter-spacing: -.03em; display: block; } .brand small { display: block; font: 500 8px/1 DM Mono, monospace; letter-spacing: .13em; color: var(--mint); margin-top: 4px; } .sidebar nav { display: grid; gap: 5px; }