From 4bdb7bfc510d4a848a08fbc7248e8bd4a370db91 Mon Sep 17 00:00:00 2001 From: ares <285551516+New1Direction@users.noreply.github.com> Date: Sun, 14 Jun 2026 20:21:20 -0700 Subject: [PATCH] =?UTF-8?q?feat(site):=20"The=20Case=20File"=20redesign=20?= =?UTF-8?q?=E2=80=94=20drop=20the=20AI-terminal=20look?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-skin the marketing site around a detective/case-file identity that ties the lens-detective mascot, the magnifying-glass name, and the investigate→verdict product framing together — and away from the generic dark + mono + gradient-text "AI terminal" template. Design - Warm manila/ink/amber palette, light-default; warm "night stakeout" dark. Removed the multi-palette (data-palette) machinery entirely — one intentional pair of themes instead of swatch soup. - Fraunces serif for display type (was Space Grotesk); real pairing against Inter/JetBrains Mono. - Verdict fit-call rendered as a rotated rubber stamp; killed gradient text (.grad-text, health number) — both AI tells — for solid accent. - Detective-framed section eyebrows: the verdict / the case file / the procedure / the detective. Mascot expressions relabelled to case beats (On the case, Reading, Deep dive, Cleared, Suspicious, Off duty). - All token references (accent/warm weak+line, shadows) defined in both :root and .dark — verified no transparent surfaces. Docs & housekeeping - README: v3.0.0 + 730+ tests badges, detective tagline, the v2/v3 surfaces (Triage, Evaluate/compare, Discover) and a v3-forward "What's new" replacing the stale v1.x wall. - Move audit report to docs/audits/; delete dead PaletteSwatches; ignore Playwright MCP scratch output. Build + lint green; both themes pass WCAG AA on body text. --- .gitignore | 4 + README.md | 11 +- docs/audits/audit-v3.0.0.html | 412 ++++++++++++++++++++ website/app/(home)/styles/home.css | 33 +- website/app/(home)/styles/shell.css | 6 +- website/app/global.css | 248 ++++++------ website/app/layout.tsx | 31 +- website/components/home/FeatureBento.tsx | 2 +- website/components/home/HowItWorks.tsx | 2 +- website/components/home/ThemeShowcase.tsx | 28 +- website/components/home/VerdictDemo.tsx | 2 +- website/components/site/PaletteSwatches.tsx | 65 --- 12 files changed, 586 insertions(+), 258 deletions(-) create mode 100644 docs/audits/audit-v3.0.0.html delete mode 100644 website/components/site/PaletteSwatches.tsx diff --git a/.gitignore b/.gitignore index 1461fed..aeb294f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,7 @@ coverage/ # superpowers skill state .superpowers/ + +# playwright/chrome-devtools MCP scratch output +.playwright-mcp/ +*-screenshot.png diff --git a/README.md b/README.md index fd76479..ce31730 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,15 @@ # 🔭 RepoLens -### One click turns any repo into a plain-English briefing. +### One click opens the case file on any repo. -**What it is · whether it's a good fit · how it's actually built · what it connects to.** +**The verdict · the evidence · the red flags · how it's actually built — in plain English, before the README's pitch.** ![Chrome Manifest V3](https://img.shields.io/badge/Chrome-Manifest_V3-1a73e8?logo=googlechrome&logoColor=white) ![Zero build](https://img.shields.io/badge/build-none-0e1722) ![Vanilla ES modules](https://img.shields.io/badge/vanilla-ES_modules-f7df1e?logo=javascript&logoColor=black) -![Tests](https://img.shields.io/badge/tests-420%2B_passing-4ade80) -![Version](https://img.shields.io/badge/version-1.7.0-7c5cff) +![Tests](https://img.shields.io/badge/tests-730%2B_passing-2f7d34) +![Version](https://img.shields.io/badge/version-3.0.0-c2691c) ![Storage](https://img.shields.io/badge/storage-in--browser_IndexedDB-38bdf8) @@ -32,6 +32,9 @@ A scan opens to a **verdict landing** and fans out into focused tabs: | ⚖️ | **Verdict** | Fit call (strong / solid / care / risky), a one-line bottom line, measured facts, and the top things worth noting — first thing you see. | | 🧠 | **Deep Dive** | The core concepts → how they build on each other → a plain-English ("explain it like I'm five") walkthrough. Optionally grounded by **measured facts** from the local runner. | | 📚 | **Library** | Every repo you've analyzed, as a sortable / filterable triage grid with fit chips, a stats bar, **bulk multi-select delete**, and one-click **Export / Import / Backup**. | +| 🗂️ | **Triage & decide** | Keyboard-first **Adopt / Trial / Hold / Reject**, a Tech Radar, Boards, fit-delta tracking, notes, and daily **drift alerts** when repos go stale. | +| ★ | **Evaluate & compare** | Score repos **1–5** against your own rubric, grade docs **A–F**, and put any **2–10** side-by-side in a decision matrix (CSV / Markdown export). | +| 🔍 | **Discover** | Search GitHub from inside the extension, or get **recommendations** from the repos you've already adopted. | | 🕸️ | **Connections** | A walkable map centred on the current repo, showing how it relates to the others you've scanned. | | 🤝 | **Synergies** · **Versus** · **Combinator** | Complements, head-to-heads, and fused project ideas — grounded in *your* library. | diff --git a/docs/audits/audit-v3.0.0.html b/docs/audits/audit-v3.0.0.html new file mode 100644 index 0000000..3787ff6 --- /dev/null +++ b/docs/audits/audit-v3.0.0.html @@ -0,0 +1,412 @@ + + + + + +RepoLens v3.0.0 — Engineering Audit + + + + + + +
+ +
+
+ + Engineering Audit · v3.0.0 +
+

RepoLens is in strong shape.

+

A full audit of the v3.0.0 extension and the redesigned website across security, code & architecture, tests, and accessibility. The short version: zero critical issues, the security fundamentals that matter most are verified clean, and the work to do is concentrated and mostly known — starting with a one-line fix that turns CI green.

+
+ 2026-06-15 + 4 parallel specialist passes + ~18.8k LoC extension · Next.js site + adversarially verified +
+
+ +
+

Bottom line

+

0 CRITICAL across all four dimensions. Security is genuinely sound for a key-handling extension — keys are never logged, the settings export is provably allowlist-driven, PKCE + OAuth state are correct, there's no eval, and the content script is minimal. Test coverage is high on pure logic (57 of ~75 modules), accessibility intent is above average, and the website's motion/perf hygiene is exemplary. The issues cluster in five places: a red CI from a known flaky test (trivial fix), maintainability debt in three oversized files, accessibility polish (focus lifecycle + a few low-contrast tokens), attack-surface hardening (endpoint validation), and basic SEO (sitemap / OG image).

+
+ +
+
Security
2H5M4L
+
Code & Architecture
5H5M3L
+
Tests & Coverage
3H3M1L
+
Accessibility
5H6M3L
+
Website
3H3M6L
+
+ + +
+
00

The one to fix first

+

A single thread connects three of the four passes — and it's a one-line fix.

+
+
HIGH

CI is red on main — caused by a time-flaky test, not a missing script

tests/maintenance.test.js · maintenance.js:15,25
+

CI run #19 failed while #18 (24 min earlier) passed — exactly when daysSincePush(daysAgo(30)) tipped from 31→32 as the UTC date rolled to the 15th. The website pass flagged this as "no npm test script," but that was verified wrong: ci.yml runs at the repo root where npm test = vitest run exists. The real cause: bandFromSignals and daysSincePush call real Date.now() with no injection point, while the test pins NOW=2026-06-13 to build inputs — so measured age drifts +1 day/day. A latent sibling exists in diff-analysis.test.js (loose assertions hide it for now), and the band-boundary tests will break later (≈Aug 12 and Jan 13).

+

Fix: give bandFromSignals/daysSincePush an injectable today = Date.now() param (mirroring buildMaintenancePrompt, which already does this) and pass NOW in the test — or vi.useFakeTimers() + vi.setSystemTime('2026-06-13'). vi.useFakeTimers is currently used in zero test files; adopt it for any time-based test. This turns CI green.

+
+
+ + +
+
01

Security

+

An extension that holds 20+ provider keys + OAuth tokens and feeds untrusted README content to an LLM. The core handling is sound; the gaps are attack-surface and log hygiene.

+ +
+
HIGH

Custom-endpoint save can grant <all_urls> to any origin

manifest.json:41 · options-providers.js:51
+

Saving a custom endpoint calls chrome.permissions.request({ origins: [origin + '/*'] }) against the optional https://*/* grant. A social-engineered URL (e.g. https://company-intranet.corp/) could hand the extension persistent cross-origin fetch to an internal network.

+

Fix: validate the endpoint is a public host before requesting — block RFC-1918 ranges, localhost, and .internal/.local TLDs (Ollama already has static localhost perms).

+
+ +
+
HIGH

Unsanitized stored endpoint → localhost SSRF

manifest.json:38 · background.js (callCompat)
+

Static http://localhost/* + 127.0.0.1/* permissions plus a user/import-writable endpoint string mean a tampered endpoint (http://localhost:8080/internal-admin) is fetched with no further consent.

+

Fix: validate stored endpoints against the provider's registered host pattern before fetch; pin Ollama to port 11434.

+
+ +
+

Medium & low

+
    +
  • [MED] Prompt-injection homoglyph gapprompt.js:7 ASCII keyword filter is bypassable by small-caps/Cyrillic lookalikes; the structural delimiters are the real (probabilistic) guard. The changelog's "hardened" claim overstates it — soften the wording.
  • +
  • [MED] OAuth log hygienebackground.js:447,498,502,1557 log callback URLs + JSON.stringify(err) provider bodies; truncate/strip token-ish fields, gate behind a DEBUG flag. (Also flagged by the code pass.)
  • +
  • [MED] whats-new.html is web-accessible to <all_urls> — enables extension fingerprinting from any page; scope to chrome-extension://*/* or drop it.
  • +
  • [MED] xAI token stored in redundant flat keysoauth-xai.js:61; a partial clear leaves a stale xaiKey. Consolidate to structured creds after a migration window.
  • +
  • [MED] Backup import has no max string lengthbackup.js enforces MAX_ROWS but a 1 MB repoId passes; cap field lengths (~256).
  • +
  • [LOW] esc() doesn't escape single quotes (format.js:4) — latent for single-quoted attributes; align with safe-html.js's escapeHtml.
  • +
  • [LOW] Gemini key in URL query string (background.js:1435) — Google's REST design, not a bug, but more exposed than header auth.
  • +
  • [LOW] tabs permission broader than needed — the tabs.onUpdated OAuth listener duplicates the webNavigation one; dropping it could remove the tabs perm.
  • +
  • [LOW] No per-message rate limiting — mitigated by same-origin sendMessage + the aiChain serializer.
  • +
+
+
+ + +
+
02

Code & Architecture

+

Three files hold most of the logic — library.js (2,593), output-tab.js (2,576), background.js (1,567), all ~3× the size guideline. The HIGH findings are duplication that has already silently diverged.

+ +
+
HIGH

render() and getVisibleRows() duplicate the filter/sort pipeline — and have diverged

library.js:177 vs 1834
+

The export path re-implements the filter→sort pipeline and has already dropped the eval sort, the NL-filter dedup, and parts of the collection/decision filters — so exports can return the wrong rows today.

+

Fix: extract one pure applyFilters(rows, state, maps) that both call. Highest-leverage refactor in the codebase.

+
+ +
+
HIGH

11 near-identical session-patch helpers with a read-modify-write race

background.js:771–1169
+

Every runXxx redefines a 3-line read-merge-write against a session sub-key. Two concurrent lens writers to the same key can clobber each other.

+

Fix: a makeSessionPatcher(key, subKey) factory — removes 33 lines and gives one place to serialize writes.

+
+ +
+
HIGH

Duplicated LANG_COLORS and FIT_ORDER — both already drifting

library.js:25,113… · output-tab.js:617
+

LANG_COLORS is copied in both big files (the copies diverge in palette); FIT_ORDER is re-declared inside functions, one of which adds 'unrated' so ordering is inconsistent.

+

Fix: a shared lang-colors.js; hoist FIT_ORDER to module scope once.

+
+ +
+
HIGH

Global callAI queue serializes unrelated flows

background.js:1201
+

One module-level promise chain throttles all AI calls — so a multi-minute deep-dive blocks a quick ASK_CACHED for its full duration; the keep-alive .catch(()=>{}) also swallows rejections.

+

Fix: exempt low-latency parts (ask) from the deep-dive queue; document the throttling intent at the call site.

+
+ +
+

Medium & low

+
    +
  • [MED] Silent catch {} at output-tab.js:2573 swallows a whole render block with no signal — add a console.warn + visible fallback.
  • +
  • [MED] RERUN set has no .catch (background.js:198) — a storage reject leaves the output tab polling to its 90s timeout; add error propagation to sendResponse.
  • +
  • [MED] Listener leaksopenNote/openQuickAsk (library.js:353,465) re-add keydown handlers on every open (the comment says it's fixed; it isn't). Use { once: true }.
  • +
  • [MED] Redundant storage reads — each runXxx re-reads PROVIDER_KEYS (~10 IPC round-trips on "Run All"); pass keys down from one read.
  • +
  • [MED] 14 mutable module globals in library.js — the root cause of the render/getVisibleRows drift; consolidate into one state object incrementally.
  • +
  • [LOW] window.prompt fallback (library.js:1318) — blocked in some extension contexts; remove the dead path.
  • +
  • [LOW] Implicit message contract — 20+ msg.type string literals with no shared enum; a typo is a silent no-op. Export a MSG_TYPES constant.
  • +
+
+
+ + +
+
03

Tests & Coverage

+

Strong by design — ~60 vitest files cover 57 of ~75 modules, targeting pure logic (the model: extract library-data.js out of library.js and test it). The three big files are untested deliberately (zero exports, explicitly excluded) — not fake coverage. The gaps are real pure logic still trapped inside, plus two security-relevant modules.

+ +
+
HIGH

evaluations.js → computeScore has zero tests

evaluations.js
+

A pure weighted-average with null-guards, range-filtering and zero-weight fallback — used by both the eval panel and the N-way compare matrix, yet untested. The single highest-value, lowest-effort add.

+

Fix: unit-test computeScore; mock the chrome.storage CRUD exactly as oauth-openai.test.js does.

+
+ +
+
HIGH

OAuth helpers oauth-pkce.js & oauth-xai.js are untested

oauth-pkce.js · oauth-xai.js
+

Security-sensitive: base64url/createPkcePair are pure/deterministic, and the full xAI device-code flow (request/poll/store/refresh) has no coverage — while the sibling oauth-openai.js is tested.

+

Fix: add deterministic tests for the PKCE primitives and the xAI token lifecycle.

+
+ +
+

Also

+
    +
  • [HIGH] The maintenance flake — see "the one to fix first." Root cause + the latent diff-analysis.test.js sibling; adopt vi.useFakeTimers (used in 0 files today).
  • +
  • [MED] Extractable pure logic trapped in library.jsradarToMarkdown, exportCompareMatrix, exportDecisionMatrix, exportDigest are pure builders that could move to a library-export.js and be tested like library-data.js.
  • +
  • [MED] The E2E driver isn't enforced.verify/drive.mjs is a real Playwright harness (~20 assertions, loads the unpacked MV3 extension, 12 screenshot stages) but it's gitignored, playwright isn't a dep, and CI never runs it. Promote it: add the dep + an npm script + non-zero exit + CI wiring.
  • +
  • [LOW] Minor hygienestore.test.js doesn't uniformly clear collections; vitest.config.js sets no clearMocks/restoreMocks.
  • +
+
+
+ + +
+
04

Accessibility

+

Above-average intent — global :focus-visible, single-key handlers that guard typing fields, an exemplary aria-hidden mascot, real live regions on the library, and strong command-palette ARIA. Two consistent gaps: focus lifecycle on modals and announcement on the main output surface. Plus a cross-product contrast theme (the website pass found the same class of issue in its tokens).

+ +
+
HIGH

The output tab never announces scan progress or completion

output-tab.html:763 · 27 tab panels
+

The primary analysis surface cycles loading copy and injects into 27 panels with no aria-live/role="status" — a screen-reader user gets no signal a scan is running or done. (WCAG 4.1.3)

+

Fix: role="status" aria-live="polite" on #loading-msg + one polite sr-only region announcing start/complete/error.

+
+ +
+
HIGH

The 27-tab strip has no tab semantics; j/k nav is visual-only

output-tab.html:832 · library.js:2066
+

Tabs are plain buttons with an .active class (no role="tablist/tab/tabpanel", no aria-selected). And library setJkFocus toggles a class + scrolls but never calls .focus() on the <div> cards — SRs announce nothing, and Tab jumps away. (WCAG 4.1.2, 2.4.3)

+

Fix: add tablist roles + aria-selected; give cards tabindex="-1"/role="article" and move real focus on j/k.

+
+ +
+
HIGH

Command palette & modals: no focus trap or restoration

palette.js:95 · library.js (compare/boards)
+

Modals have good role="dialog"+aria-modal+Escape, but none trap Tab or restore focus to the trigger on close, and the palette listbox lacks aria-activedescendant (arrowing is silent to SRs). (WCAG 2.1.2, 4.1.3)

+

Fix: a shared open/close focus-lifecycle helper (move focus in, trap Tab, restore on close) applied to every popover/modal; add aria-activedescendant to the palette.

+
+ +
+
HIGH

Low-contrast text fails WCAG AA — extension themes and the website

themes.css (--text-muted) · website global.css (--faint/--accent/--muted)
+

Extension: --text-muted (descriptions/hints, 65 uses) fails AA on 5 of 13 themes — including the default midnight (4.15:1) and worst rosepine (3.41:1); --accent-as-text fails on solarized/claude/bmw. Website: --faint body text fails AA in both themes (2.20 latte / 2.69 dark — .hero-foot, footer), and small --accent/--muted labels are AA-large-only.

+

Fix: bump --text-muted/--faint to ≥4.5:1 per theme (darken light, lighten dark); route --accent-as-text through the darker --accent-deep/--accent-2.

+
+ +
+

Medium & low

+
    +
  • [MED] No reduced-motion guard on batch.html & stack-tab.html — the only two pages missing it; both run infinite pulse loaders. Add the same @media (prefers-reduced-motion: reduce) reset (better: one shared a11y.css).
  • +
  • [MED] :focus-visible suppressed on focusable widgets — decision-popover buttons, eval badge, palette input get programmatic focus but show no ring (library.html:481, palette.css:46). (WCAG 2.4.11)
  • +
  • [MED] Icon-only buttons rely on title, not aria-label — card actions (pin/compare/note/copy/remove) + header buttons; inconsistent with #settings which has a label.
  • +
  • [MED] Boards popover has no role/label — a bare <div> (its rows are real buttons, so still operable).
  • +
  • [LOW] Inline //? symbols without text alternatives in a few spots.
  • +
  • [LOW] Output-tab loading bar animates width over 25s — perf nit vs the team's own rules; disabled under reduced-motion, so no a11y issue.
  • +
+
+
+ + +
+
05

Website

+

The fresh redesign's motion/perf hygiene is genuinely exemplary (GSAP fully lazy + 100% behind prefers-reduced-motion, correct reduced-motion video fallback, clean heading order, CLS well-defended with explicit dimensions, the case-sensitive /RepoLens basePath handled). The gaps are SEO basics and a couple of perf nits.

+ +
+
HIGH

No sitemap.xml or robots.txt

add app/sitemap.ts · app/robots.ts
+

Nothing tells crawlers the canonical URL set for a freshly launched marketing site. Next 15 emits both under output: export.

+

Fix: add app/sitemap.ts + app/robots.ts with absolute /RepoLens URLs enumerating /, /changelog, and the 10 docs routes.

+
+ +
+

Medium & low

+
    +
  • [MED] OG/Twitter card declared but no imagelayout.tsx sets summary_large_image with no images; link unfurls render bare. Add public/og.png (1200×630) or app/opengraph-image.tsx.
  • +
  • [MED] Hero video/poster not fetch-prioritized — the poster is the likely LCP but gets no preload; the 185 KB MP4 autoloads. Add preload="metadata" to the video + a poster <link rel=preload>.
  • +
  • [MED] Atmosphere paint cost — 3 fixed radial gradients + starfield + header backdrop-filter: blur; trace on a throttled device.
  • +
  • [LOW] Font-swap CLS on large display headings (no size-adjust; next/font mitigates); focus-ring contrast weak in latte; warm tab underline 1.88:1 (decorative card); fumadocs lazy-files coupling in lib/source.ts is version-fragile; the RSC .txt 404 stays cosmetic; stray untracked whats-new.html at repo root (doesn't ship).
  • +
+
+
+ + +
+
06

What's solid

+

An audit is only honest if it says what's done right. A lot is.

+
+
    +
  • API keys are never logged — passed straight to fetch headers.
  • +
  • Settings export is provably allowlist-drivenpickSafe() excludes keys/tokens on export and import.
  • +
  • PKCE is correct — CSPRNG verifier/state, SHA-256, base64url; OAuth state verified before exchange.
  • +
  • No eval / dynamic code anywhere.
  • +
  • Content script is minimal — sends one message, reads only the URL.
  • +
  • 57 of ~75 modules tested — pure logic extracted and tested deliberately.
  • +
  • Mascot a11y is exemplary — always aria-hidden, meaning carried by text/color.
  • +
  • Reduced-motion done right — extension and website both gate all motion; static fallbacks.
  • +
  • Command-palette ARIA — dialog/listbox/option roles, aria-selected.
  • +
  • Website motion is fully lazy — GSAP off the critical path, 110 kB First-Load.
  • +
  • CLS well-defended — explicit dimensions + aspect-ratio on media.
  • +
  • basePath fixed & correct — case-sensitive /RepoLens, manual asset prefixing.
  • +
+
+
+ + +
+
07

Prioritized action plan

+

In order of leverage-per-effort.

+
    +
  1. +

    Turn CI green HIGH · minutes

    +

    Inject today into bandFromSignals/daysSincePush (or vi.setSystemTime in the test); fix the latent diff-analysis sibling. One change, main goes green.

    +
  2. +
  3. +

    Extract applyFilters() HIGH

    +

    Collapse the render()/getVisibleRows() divergence — it's an active wrong-export bug source — then lift FIT_ORDER/LANG_COLORS to shared modules.

    +
  4. +
  5. +

    Add tests: computeScore + the OAuth helpers HIGH

    +

    High-value, low-effort, security-relevant — the biggest coverage holes that are trivially testable.

    +
  6. +
  7. +

    Accessibility: announce + focus HIGH

    +

    Add the output-tab live region + tab semantics, make j/k move real focus, and add a shared modal focus-trap/restore helper.

    +
  8. +
  9. +

    Fix contrast tokens HIGH

    +

    Bump --text-muted/--faint to AA on the failing themes (incl. default midnight) across both extension and site; stop suppressing focus rings on focusable widgets; add the reduced-motion reset to batch.html/stack-tab.html.

    +
  10. +
  11. +

    Harden endpoints + log hygiene MED

    +

    Validate custom/Ollama endpoints (block private ranges), scope whats-new.html, truncate OAuth logs, cap backup string lengths.

    +
  12. +
  13. +

    SEO basics MED

    +

    Add sitemap.ts + robots.ts + an OG image; preload the hero poster.

    +
  14. +
  15. +

    Pay down the big-file debt MED · ongoing

    +

    Split library.js/output-tab.js/background.js along their seams (export builders, session patcher, render vs. state) as growth demands. Promote .verify/drive.mjs to enforced E2E.

    +
  16. +
+
+ + + +
+ + diff --git a/website/app/(home)/styles/home.css b/website/app/(home)/styles/home.css index 0564cca..473c0bc 100644 --- a/website/app/(home)/styles/home.css +++ b/website/app/(home)/styles/home.css @@ -218,12 +218,9 @@ flex-shrink: 0; } .vd-health-n { - font: 800 2rem/1 var(--sans); + font: 800 2.1rem/1 var(--font-display, var(--sans)); letter-spacing: -0.02em; - background: var(--grad); - -webkit-background-clip: text; - background-clip: text; - color: transparent; + color: var(--accent); } .vd-health-l { font: 600 9px/1 var(--mono); @@ -253,22 +250,28 @@ border-bottom-color: var(--warm); } .vd-verdict { - margin: 16px 20px 0; + margin: 18px 20px 0; display: flex; align-items: center; - gap: 12px; + gap: 16px; padding: 14px 16px; - border-radius: 12px; - background: color-mix(in srgb, var(--ok) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--ok) 34%, transparent); + border-radius: 10px; + background: var(--bg-2); + border: 1px solid var(--border); } +/* The verdict — a rubber stamp, slammed at an angle, not a chip. */ .vd-fit { - font: 800 12px/1 var(--mono); + font: 800 13px/1 var(--mono); + letter-spacing: 0.12em; + text-transform: uppercase; color: var(--ok); - background: color-mix(in srgb, var(--ok) 18%, transparent); - border: 1px solid color-mix(in srgb, var(--ok) 45%, transparent); - border-radius: 7px; - padding: 7px 10px; + background: transparent; + border: 2.5px solid var(--ok); + border-radius: 5px; + padding: 6px 11px; + transform: rotate(-7deg); + opacity: 0.9; + box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--ok) 20%, transparent); } .vd-verdict-meta { font-size: 0.84rem; diff --git a/website/app/(home)/styles/shell.css b/website/app/(home)/styles/shell.css index fe8e71a..111e06e 100644 --- a/website/app/(home)/styles/shell.css +++ b/website/app/(home)/styles/shell.css @@ -348,11 +348,9 @@ max-width: 60ch; font-size: 1.02rem; } +/* No gradient text (an AI-template tell) — the key word just goes amber ink. */ .site-root .grad-text { - background: var(--grad); - -webkit-background-clip: text; - background-clip: text; - color: transparent; + color: var(--accent); } /* ── Vee mascot (token-aware SVG; expression = class swap) ────────────── */ diff --git a/website/app/global.css b/website/app/global.css index a0e8feb..4f55598 100644 --- a/website/app/global.css +++ b/website/app/global.css @@ -4,151 +4,141 @@ @source '../app'; @source '../components'; -/* ──────────────────────────────────────────────────────────────────────── - RepoLens brand tokens. - Two themes drive everything: latte (:root, light) and midnight (.dark). - next-themes (wired by fumadocs RootProvider, `class` strategy) flips .dark, - so both the marketing pages and the docs re-skin from a single toggle. - Accent palettes layer on top via [data-palette] — accent ramp only. +/* ════════════════════════════════════════════════════════════════════════ + "The Case File" — a detective/inspector identity for RepoLens. + Leads LIGHT (warm manila + ink), with an amber/brass lens-glint accent and a + detective red for risk. Dark survives as a warm "night stakeout" (.dark), not + a cold terminal. next-themes flips .dark; tokens drive marketing + docs. ──────────────────────────────────────────────────────────────────────── */ :root { - /* type + motion (theme-independent) */ - --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, monospace; - --sans: "SF Pro Display", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; + /* type + motion */ + --mono: "JetBrains Mono", ui-monospace, "SF Mono", SFMono-Regular, Menlo, monospace; + --sans: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; + /* --font-display is supplied by next/font (Fraunces); falls back to a serif. */ + --serif-fallback: "Fraunces", "Iowan Old Style", Georgia, "Times New Roman", serif; --dur-fast: 120ms; --dur: 200ms; --dur-slow: 320ms; --ease-out: cubic-bezier(0.16, 1, 0.3, 1); --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); + /* the stamp's heavy "slam" */ + --ease-stamp: cubic-bezier(0.5, 1.8, 0.4, 0.95); - /* Inspector accent ramp — royal blue → cyan, tied to the mascot. - [data-palette] overrides these; nothing else should redefine them. */ - --accent: #3b82f6; - --accent-2: #2563eb; - --accent-3: #38bdf8; - --grad: linear-gradient(120deg, #60a5fa 0%, #3b82f6 55%, #22d3ee 120%); - --grad-soft: linear-gradient(120deg, #3b82f6, #22d3ee); - --accent-glow: rgba(59, 130, 246, 0.4); - --accent-weak: rgba(59, 130, 246, 0.12); - --accent-line: rgba(59, 130, 246, 0.3); + /* Amber/brass lens-glint accent + detective red. One ramp tuned for paper; + .dark brightens it for the night-stakeout surfaces. */ + --accent: #9e530e; + --accent-2: #934a0c; + --accent-3: #d9822b; + --grad: linear-gradient(120deg, #d9822b 0%, #9e530e 60%, #934a0c 120%); + --grad-soft: linear-gradient(120deg, #d9822b, #9e530e); + --accent-glow: rgba(179, 94, 16, 0.35); + --accent-weak: rgba(179, 94, 16, 0.1); + --accent-line: rgba(179, 94, 16, 0.28); - /* Warm signature — the lens rim. Complementary to the blue; used sparingly - (the primary CTA, select highlights, the verdict tab underline). */ - --warm: #f59e0b; - --warm-2: #fb923c; + /* warm = the amber CTA; red = the RISKY stamp / red flags */ + --warm: #c2691c; + --warm-2: #d9822b; --warm-ink: #2a1605; - --warm-glow: rgba(245, 158, 11, 0.5); - --warm-weak: rgba(245, 158, 11, 0.14); - --warm-line: rgba(245, 158, 11, 0.36); + --warm-glow: rgba(194, 105, 28, 0.4); + --warm-weak: rgba(194, 105, 28, 0.12); + --warm-line: rgba(194, 105, 28, 0.34); + --red: #c0392b; + --red-weak: rgba(192, 57, 43, 0.12); - /* latte — warm paper, light */ - --bg: #f3f1ec; - --bg-2: #eae6dd; - --surface: #ffffff; - --surface-2: #faf8f3; - --border: #e4ded1; - --border-2: #d3ccba; - --text: #34322c; - --text-strong: #1a1813; - --sub: #5f5a4d; - --muted: #857f6d; - --faint: #aaa392; - --ok: #0f9d6a; - --info: #0284c7; - --warn: #b45309; - --bad: #dc2647; - --shadow-lg: 0 30px 80px -40px rgba(40, 34, 20, 0.28); - --shadow-md: 0 18px 40px -24px rgba(40, 34, 20, 0.22); - --starfield-opacity: 0; + /* Case File — warm manila paper, sepia ink */ + --bg: #f4ead4; + --bg-2: #ecdfc2; + --surface: #fbf5e7; + --surface-2: #f5ecd6; + --border: #ddccab; + --border-2: #c8b285; + --text: #2a2114; + --text-strong: #17110a; + --sub: #5d5034; + --muted: #756544; + --faint: #a89571; + --ok: #2c7631; + --info: #1d6fa3; + --warn: #b4690a; + --bad: #c0392b; + --shadow-lg: 0 26px 70px -34px rgba(60, 42, 16, 0.38); + --shadow-md: 0 16px 38px -22px rgba(60, 42, 16, 0.3); + --paper-grain: 0.5; - /* fumadocs remap → latte */ - --color-fd-background: #f3f1ec; - --color-fd-foreground: #34322c; - --color-fd-muted: #eae6dd; - --color-fd-muted-foreground: #6f6a5b; - --color-fd-popover: #ffffff; - --color-fd-popover-foreground: #1a1813; - --color-fd-card: #faf8f3; - --color-fd-card-foreground: #1a1813; - --color-fd-border: #e2ddd1; - --color-fd-primary: #2563eb; - --color-fd-primary-foreground: #ffffff; - --color-fd-secondary: #eee9df; - --color-fd-secondary-foreground: #34322c; - --color-fd-accent: #ece7dc; - --color-fd-accent-foreground: #1a1813; - --color-fd-ring: #3b82f6; + /* fumadocs remap → Case File light */ + --color-fd-background: #f4ead4; + --color-fd-foreground: #2a2114; + --color-fd-muted: #ecdfc2; + --color-fd-muted-foreground: #6a5c3d; + --color-fd-popover: #fbf5e7; + --color-fd-popover-foreground: #17110a; + --color-fd-card: #fbf5e7; + --color-fd-card-foreground: #17110a; + --color-fd-border: #ddccab; + --color-fd-primary: #9e530e; + --color-fd-primary-foreground: #fbf5e7; + --color-fd-secondary: #efe3ca; + --color-fd-secondary-foreground: #2a2114; + --color-fd-accent: #ecdfc2; + --color-fd-accent-foreground: #17110a; + --color-fd-ring: #9e530e; } .dark { - /* midnight — blue-black observatory, dark */ - --bg: #0a0e1a; - --bg-2: #0c1220; - --surface: #111a2e; - --surface-2: #16203a; - --border: #223149; - --border-2: #32486e; - --text: #eaeefb; - --text-strong: #ffffff; - --sub: #9aa6c2; - --muted: #6b7794; - --faint: #4b5570; - --ok: #34d399; - --info: #38bdf8; - --warn: #fbbf24; - --bad: #fb7185; - --shadow-lg: 0 30px 80px -40px rgba(0, 0, 0, 0.8); - --shadow-md: 0 20px 50px -28px rgba(0, 0, 0, 0.7); - --starfield-opacity: 0.5; + /* Night Stakeout — warm noir, not a cold terminal */ + --bg: #1b1610; + --bg-2: #221b12; + --surface: #2a2116; + --surface-2: #32281a; + --border: #43361f; + --border-2: #5e4b2c; + --text: #ece1cd; + --text-strong: #fff8ec; + --sub: #c3b094; + --muted: #9c8a6a; + --faint: #6f6045; - /* fumadocs remap → midnight */ - --color-fd-background: #0a0e1a; - --color-fd-foreground: #eaeefb; - --color-fd-muted: #111a2e; - --color-fd-muted-foreground: #9aa6c2; - --color-fd-popover: #0c1220; - --color-fd-popover-foreground: #eaeefb; - --color-fd-card: #111a2e; - --color-fd-card-foreground: #eaeefb; - --color-fd-border: #223149; - --color-fd-primary: #60a5fa; - --color-fd-primary-foreground: #0a0e1a; - --color-fd-secondary: #16203a; - --color-fd-secondary-foreground: #eaeefb; - --color-fd-accent: #18233c; - --color-fd-accent-foreground: #ffffff; - --color-fd-ring: #3b82f6; -} + /* brighter amber + red read better on the warm-dark surfaces */ + --accent: #e0934a; + --accent-2: #cf7a2a; + --accent-3: #f0aa63; + --grad: linear-gradient(120deg, #f0aa63 0%, #e0934a 60%, #cf7a2a 120%); + --grad-soft: linear-gradient(120deg, #f0aa63, #e0934a); + --accent-glow: rgba(224, 147, 74, 0.45); + --accent-weak: rgba(224, 147, 74, 0.14); + --accent-line: rgba(224, 147, 74, 0.32); + --warm: #e0934a; + --warm-2: #f0aa63; + --warm-ink: #2a1605; + --warm-glow: rgba(224, 147, 74, 0.45); + --warm-weak: rgba(224, 147, 74, 0.16); + --warm-line: rgba(224, 147, 74, 0.36); + --red: #e06a5c; + --red-weak: rgba(224, 106, 92, 0.16); + --ok: #5cc05f; + --info: #56b6e8; + --warn: #e0a341; + --bad: #e06a5c; + --shadow-lg: 0 26px 70px -34px rgba(0, 0, 0, 0.72); + --shadow-md: 0 16px 44px -26px rgba(0, 0, 0, 0.66); + --paper-grain: 0.7; -/* ── Accent palettes (showcase the theme engine) — accent ramp only ─────── */ -:root[data-palette="terminal"] { - --accent: #34d399; - --accent-2: #10b981; - --accent-3: #6ee7b7; - --grad: linear-gradient(120deg, #6ee7b7 0%, #10b981 55%, #059669 120%); - --grad-soft: linear-gradient(120deg, #34d399, #10b981); - --accent-glow: rgba(16, 185, 129, 0.5); - --accent-weak: rgba(16, 185, 129, 0.12); - --accent-line: rgba(16, 185, 129, 0.3); -} -:root[data-palette="nord"] { - --accent: #88c0d0; - --accent-2: #5e81ac; - --accent-3: #8fbcbb; - --grad: linear-gradient(120deg, #8fbcbb 0%, #88c0d0 50%, #5e81ac 120%); - --grad-soft: linear-gradient(120deg, #88c0d0, #5e81ac); - --accent-glow: rgba(94, 129, 172, 0.5); - --accent-weak: rgba(136, 192, 208, 0.14); - --accent-line: rgba(136, 192, 208, 0.32); -} -:root[data-palette="claude"] { - --accent: #d97757; - --accent-2: #cc785c; - --accent-3: #e8a87c; - --grad: linear-gradient(120deg, #e8a87c 0%, #d97757 55%, #c15f3c 120%); - --grad-soft: linear-gradient(120deg, #d97757, #c15f3c); - --accent-glow: rgba(217, 119, 87, 0.5); - --accent-weak: rgba(217, 119, 87, 0.13); - --accent-line: rgba(217, 119, 87, 0.3); + /* fumadocs remap → night stakeout */ + --color-fd-background: #1b1610; + --color-fd-foreground: #ece1cd; + --color-fd-muted: #2a2116; + --color-fd-muted-foreground: #c3b094; + --color-fd-popover: #221b12; + --color-fd-popover-foreground: #ece1cd; + --color-fd-card: #2a2116; + --color-fd-card-foreground: #ece1cd; + --color-fd-border: #43361f; + --color-fd-primary: #e0934a; + --color-fd-primary-foreground: #1b1610; + --color-fd-secondary: #32281a; + --color-fd-secondary-foreground: #ece1cd; + --color-fd-accent: #322619; + --color-fd-accent-foreground: #fff8ec; + --color-fd-ring: #e0934a; } diff --git a/website/app/layout.tsx b/website/app/layout.tsx index 1227222..fbb4e02 100644 --- a/website/app/layout.tsx +++ b/website/app/layout.tsx @@ -1,22 +1,22 @@ import type { ReactNode } from 'react'; import type { Metadata } from 'next'; -import { Space_Grotesk } from 'next/font/google'; +import { Fraunces } from 'next/font/google'; import { RootProvider } from 'fumadocs-ui/provider'; import './global.css'; -// Self-hosted at build time (works with output: export). Display face for -// headings only; body stays the system stack. Exposed as --font-display. -const display = Space_Grotesk({ +// On GitHub Pages the static export is served under //, so the static search +// index lives at /api/search. Empty when served at root (local dev). +const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? ''; + +// Fraunces — a characterful display serif (self-hosted at build; works with +// output: export). Display headings only; body stays the system/Inter stack. +const display = Fraunces({ subsets: ['latin'], weight: ['500', '600', '700'], variable: '--font-display', display: 'swap', }); -// On GitHub Pages the static export is served under //, so the static search -// index lives at /api/search. Empty when served at root (local dev). -const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? ''; - export const metadata: Metadata = { metadataBase: new URL('https://new1direction.github.io/RepoLens/'), title: { @@ -24,33 +24,24 @@ export const metadata: Metadata = { template: '%s · RepoLens', }, description: - 'A Chrome extension that turns any GitHub, GitLab, npm, or PyPI page into a plain-English, verdict-first briefing: what it is, whether it fits, and how it is actually built.', + 'A Chrome extension that investigates any GitHub, GitLab, npm, or PyPI repo and hands down a verdict — what it is, whether it fits, and how it is actually built. The case file on any dependency.', applicationName: 'RepoLens', openGraph: { title: 'RepoLens — read code before you trust it', description: - 'One click turns any repo into a verdict-first briefing — fit, health, and the real shape of the thing. Bring your own model. Nothing leaves your browser.', + 'One click opens the case file on any repo: a verdict, the evidence, the red flags. Bring your own model. Nothing leaves your browser.', type: 'website', siteName: 'RepoLens', }, twitter: { card: 'summary_large_image', title: 'RepoLens', description: 'Read code before you trust it.' }, }; -/** - * Applies the saved accent palette (data-palette) before first paint so the - * named palettes don't flash. Light/dark is handled by next-themes' own script. - */ -const PALETTE_SCRIPT = `(function(){try{var p=localStorage.getItem('repolens-palette');if(p&&p!=='inspector')document.documentElement.setAttribute('data-palette',p);}catch(e){}})();`; - export default function RootLayout({ children }: { children: ReactNode }) { return ( - -