diff --git a/.gitignore b/.gitignore index b1efc58..b722c14 100644 --- a/.gitignore +++ b/.gitignore @@ -153,3 +153,4 @@ final-light*.png # Self-hosted server runtime database (local default ./data/agent-render.db; see selfhosted/db.ts) /data/*.db* +.impeccable/hook.cache.json diff --git a/.impeccable.md b/.impeccable.md index 40c6b93..ae11b1e 100644 --- a/.impeccable.md +++ b/.impeccable.md @@ -71,4 +71,4 @@ All buttons use `active:scale-95` for press feedback. Hover transitions are slow - Tailwind CSS v4 with CSS custom properties (no tailwind.config) - `next-themes` for class-based light/dark switching - Fraunces + IBM Plex Sans + IBM Plex Mono (Google Fonts via next/font) -- CodeMirror 6 for code rendering, git-diff-view for diffs +- CodeMirror 6 for code rendering, @pierre/diffs for diffs diff --git a/.impeccable/config.json b/.impeccable/config.json new file mode 100644 index 0000000..ae0f1da --- /dev/null +++ b/.impeccable/config.json @@ -0,0 +1,10 @@ +{ + "detector": { + "ignoreRules": [], + "ignoreFiles": [ + ".impeccable/drafts/", + ".impeccable/drafts" + ], + "ignoreValues": [] + } +} diff --git a/.impeccable/design-contract.md b/.impeccable/design-contract.md new file mode 100644 index 0000000..bf3a66a --- /dev/null +++ b/.impeccable/design-contract.md @@ -0,0 +1,23 @@ +# DESIGN CONTRACT — "Bench Instrument × Carbon Transfer" (fused world) + +Authoritative grammar for the agent-render full rebuild. ONE token spine lives in `src/app/globals.css` `:root`/`.dark` (canonical colors + shared `--bench-*` aliases + shared keycap/recess depth recipe). `src/app/bench.css` holds only NEW grammar pieces and defines NO colors. Use only these tokens/classes; do not invent new colors or a second system. + +## WORLD +Bone chrome in LIGHT theme; dark placard chrome in DARK theme (QRH grammar: charcoal panels, hairline etched rules, ivory ink); dark-charcoal content chassis (`--surface-code*`) in BOTH themes (code/diff/csv/json bodies stay dark). One orange COMMIT action. Functional keycap/recess depth only. No glow halos, no rounded cream cards, no radial-gradient washes, NO eyebrow/kicker above page headings, NO self-describing chrome text ("Decoded", "read-only codemirror", artifact-kind kicker duplicating filename/kind). Real footer (identity + license + links), never a stub. + +## CLASS VOCABULARY (what to use) +- **Keycap button:** `.artifact-action` (+ `.is-primary` for the orange COMMIT). `.mono-pill` = shallower bone keycap tag. Do NOT use `.bench-key` (does not exist). +- **Recessed inputs:** `.creator-input`, `.creator-textarea`, `.creator-link-output` (already recessed). Do NOT use `.bench-slot`. +- **Density board (C4):** `.bench-board` (2px rule-strong border, bone bg) + `.bench-board-head` (dark engraved strip, `.bench-caps` label inside) + `.bench-cell` (hairline-separated cells). Do NOT invent board classes. +- **Field/segment chips:** `.bench-chip` + `.is-on` (selected orange) inside `.bench-chiprow`. +- **VFD readout (amber mono on dark slot):** `.bench-readout` — for budget chars / codec numbers. +- **D7 LED lamp:** `.bench-lamp` (+ `.is-amber` / `.is-red`) — tiny physical LED; only glow allowed. +- **D1 carbon-transfer (generated-link output ONLY):** `.bench-carbon-bar` (dashed strip, "the URL fragment is the carbon copy" with ``), `.bench-stamp` (rotated red outline), `.bench-perf` (dashed tear line). Never tint the artifact body. +- **Engraved caps (mono uppercase labels):** `.bench-caps`. +- **Existing structural/layout classes already in use:** `.bench-hero`, `.bench-rig`, `.bench-col-creator`, `.bench-col-side`, `.bench-section` (+ `.bench-section-head`/`.bench-section-title`), `.bench-links` (+ `.bench-links-row`/`-kicker`/`-title`/`-desc`), `.site-footer`/`-wordmark`/`-nav`/`-link`, `.nav-bar`/`.nav-wordmark`/`.nav-text-link`, `.empty-state-layout`, `.stat-row`/`.stat-item`/`.stat-value`, `.metric-label`/`.metric-value`, `.artifact-disclosure*` , `.artifact-switcher*`, `.sample-link`, `.panel`, `.budget-track`/`.budget-fill`. + +## PRESERVE (hard) +Every data-testid, data-active-kind, data-renderer-ready, data-diff-state, aria-*, element IDs, dynamic imports, lazy mounts, print-markdown attrs, print-hide-on-markdown, and ALL copy/product claims. Do not change behavior. Content bodies (CodeMirror, mermaid, csv table, json tree, markdown article) are NOT restyled. + +## BANS (owner + craft-floor) +Eyebrow/kicker above a page heading. Self-describing chrome narration. Floating chrome / cream cards / radial washes / decorative glow. Section-number kickers unless the sequence IS the info. Footer stub. Monospace as costume (mono is for data/labels only). diff --git a/.impeccable/drafts/_index.html b/.impeccable/drafts/_index.html new file mode 100644 index 0000000..ce0218f --- /dev/null +++ b/.impeccable/drafts/_index.html @@ -0,0 +1,59 @@ + + + + + +agent-render — direction drafts + + + +
+

agent-render — direction drafts

+

12 drafts. Same homepage content each time ("Zero-retention artifact viewer for AI outputs." + the create-a-link form). Type, spacing, and component language are what you're picking between, not the copy. Open a few, note the spread, then tell me which one (or which fusion) earns the real build.

+ +

Grounded: my seven candidates

+
    +
  1. D1CR-39 carbonless lab slipopen →Single-part transfer document; red carbon as accent; the URL is the carbon copy. Cream paper, two-web feel.
  2. +
  3. D2Terminal session paper logopen →tmux capture-pane grammar. Green live dot, box-drawing hair stretches, generate = one more command. Dark terminal print.
  4. +
  5. D3NTSC broadcast underlineopen →Condensed program title, safe-blue lower-third, red ON AIR bug, yellow crawl along the bottom.
  6. +
  7. D4Nautical chart marginaliaseed-assignedopen →Surveyed chart: marginalia italics, mono soundings band, watery grid ground, one deep-sea blue as working color.
  8. +
  9. D5Blueprint / linen sheetopen →Cyanotype grid field, monoline annotation, dimension rules, revision strip at the foot. Amber marks for corrections.
  10. +
  11. D6CD catalog sheetopen →Booklet page: tracklist table with catalog numbers, baskerville titles, price-blue focus accent, barcode colophon.
  12. +
  13. D7Cockpit QRH cardopen →Placarded quick-reference: engraved caps, amber = commit, green = confirmed state. Matte panels, VFD mono.
  14. +
+ +

Challenged: the catalog's five (one dropped)

+
    +
  1. C1CRT arcade decoding stageopen →Pixel caps, phosphor green on tube black, scanlines + bloom; generate link = insert coin. Kept literal.
  2. +
  3. C2Night-flight six-packopen →The fragment inspector IS the instrument cluster. Radium dials, trend arrows, amber = caution only.
  4. +
  5. C3Creator-hardware benchopen →Bone keycaps on gunmetal chassis, amber VFD readout, one orange COMMIT key. Physical kbd world.
  6. +
  7. C4Japanese high-density boardopen →Tight modular notice board, utility red tabs, dense stats line. Whole product above the fold.
  8. +
  9. C6Gravity-rain gardenopen →Dusk garden, blue rain rising from host to client. Surreal but transport-true. Kept literal-light.
  10. +
+ + +
+ + diff --git a/.impeccable/drafts/c1-crt-arcade.html b/.impeccable/drafts/c1-crt-arcade.html new file mode 100644 index 0000000..bca54c9 --- /dev/null +++ b/.impeccable/drafts/c1-crt-arcade.html @@ -0,0 +1,179 @@ + + + + + +agent-render · decoding stage + + + + + + +
+
+ RETENTION 0 + HI-FRAGMENT 8192 + CREDIT ∞ +
+ + ZERO-RETENTION
ARTIFACT VIEWER
+

Payload lives in the URL fragment. The host never receives it. Five renderers loaded: markdown · code · diff · csv · json.

+

Insert artifact to continue

+ +
+
+
Select your renderer
+
+
MARK
DOWN
+
CODE
+
DIFF
U/S
+
CSV
GRID
+
JSON
TREE
+
+ +
+
+ +
+
1UP — CREATE LINKBONUS AT 2000 DISCORD
+
+
+
+ +
# Launch note +Share one artifact at a time without uploading it anywhere. +- Markdown stays readable +- Code keeps its language hint +- Link works from a static export
+
+
+
MarkdownCodeDiffCSVJSON
+
+
+ +
HI-SCORE LIMIT 8,192 CH
LOADED 1,204 CH · CODEC ARX4
+
+
+ +
+ © AGENT-RENDER AMUSEMENTS + ZERO RETENTION — HOST SEES NOTHING + MULTIPLAYER VIA PASTE +
+
+ + diff --git a/.impeccable/drafts/c2-night-sixpack.html b/.impeccable/drafts/c2-night-sixpack.html new file mode 100644 index 0000000..1160459 --- /dev/null +++ b/.impeccable/drafts/c2-night-sixpack.html @@ -0,0 +1,216 @@ + + + + + +agent-render · night instruments + + + + + + +
+
+ AGENT-RENDER · NIGHT PANEL + STATIC CREW · CLIENT DECODE + HDG 138° · 01:52Z +
+ +
+

Zero-retention artifact viewer for AI outputs. Nothing held behind the glass.

+

Artifact content lives in the URL fragment. In static mode the host does not receive artifact content on the page request. Cross-check before you file: links can still appear in browser history, screenshots, and copied messages.

+
+ +
+
+
+
1204 ch
Fragment load
+
+
PAYLOAD▲ 14.7% of limit
+
VNE 8192 · GREEN ARC TO 6000
+
+
+
+
ARX4
Codec selected
+
+
COMPRESSIONAUTO-MIXTURE
+
PLAIN · LZ · DEFLATE · ARX–ARX4
+
+
+
+
0 b
Server retention
+
+
STORAGESTEADY · HOST BLIND
+
NONE PERMITTED IN PATTERN
+
+
+
+
5
Renderers armed
+
+
AVIONICSALL BUSSES NOMINAL
+
MD · CODE · DIFF · CSV · JSON
+
+
+
+
2000
Discord-md gate
+
+
CHATTER LIMITCAUTION AIRSPACE
+
WARN ISSUED PAST GATE
+
+
+
+
200k ch
Decoded ceiling
+
+
SERVICE CEILINGABSOLUTE · NO CLIMB
+
REFUSE LOADS ABOVE CEILING
+
+
+ +
+ EN ROUTE +
AGENT#FRAGMENTCLIENT DECODEREADER
+ HOST CONTACT NONE · STORAGE 0B +
+ + + +
+ NIGHT OPS · ZERO RETENTION BY DESIGN + SQUAWK NORMAL · NOTHING TO TRANSPOND + agent-render · g-u-all-green +
+
+ + diff --git a/.impeccable/drafts/c3-hardware-bench.html b/.impeccable/drafts/c3-hardware-bench.html new file mode 100644 index 0000000..cc55ff8 --- /dev/null +++ b/.impeccable/drafts/c3-hardware-bench.html @@ -0,0 +1,205 @@ + + + + + +agent-render · bench instrument + + + + + + +
+
+
+
AGENT-RENDER AR-5 · ARTIFACT COMMUTER · SER NO 000281
+ zero-retention benchengineering +
+ +
+
BENCH INSTRUMENT · VIEWER UNIT
+

Zero-retention artifact viewer for AI outputs

+

Artifact content lives in the URL fragment. In static mode the host does not receive artifact content on the page request. Done on the bench, kept nowhere. Links can still appear in browser history, screenshots, and copied messages.

+
+
Retention0 bytes · by design
+
Transportfragment-only
+
Renderers5 loaded
+
Budget8,192 ch
+
+
+ +
+
+
TRANSPORT PRINCIPLE
+

The compact fragment #<tag><payload> is the only connector on this unit. No query params, no request body. The browser decodes markdown, code, diffs, CSV, and JSON in place; static hosts serve the unit and see nothing after.

+
+
+
RETENTION0 BYTES
+
CODECARX4 · AUTO
+
FRAGMENT1,204 CH / 8,192
+
DISCORD GATEOPEN ≤ 2,000
+
+
+ +
+
MarkdownK1 · live
+
CodeK2
+
DiffK3
+
CsvK4
+
JsonK5
+
Copy linkFN·CPY
+
DownloadFN·DL
+
GenerateFN·GEN
+
+ +
+
COMMUTATION TRAYSOCKETS LIVE · LOCAL ONLY
+
+
+
+
+
# Launch note +Share one artifact at a time without uploading it anywhere. +- Markdown stays readable +- Code keeps its language hint +- The link works from a static export
+
+
+
+ + LOAD 1,204 / 8,192 CH · CODEC ARX4 AUTO · GATE OPEN +
+
+
+ +
+ BENCH CAL · ZERO RETENTION BY HOST DESIGN + SELF-HOSTABLE · STATIC EXPORT + DO NOT SHORT OUTPUT TO STORAGE +
+
+ + diff --git a/.impeccable/drafts/c4-high-density.html b/.impeccable/drafts/c4-high-density.html new file mode 100644 index 0000000..a0cc194 --- /dev/null +++ b/.impeccable/drafts/c4-high-density.html @@ -0,0 +1,243 @@ + + + + + +agent-render · high density board + + + + + + +
+ + + + +
+ + +
+
+
+
+

Zero-retention artifact viewer for AI outputs

+

Artifact content lives in the URL fragment. In static mode the host does not receive artifact content on the page request. Nothing retained, everything decoded where it's read.

+
+
ZERO
RETENTIONBY HOST DESIGN
+
+
+
PAYLOAD≤ 8,192 CH
+
DECODED≤ 200,000 CH
+
SERVER STORAGE0 BYTES
+
CODECS7 TAGGED
+
+
+ +
+
LINK FACTORY · FORM F-01INPUT STAYS LOCAL
+
+
+
+
+
# Launch note +Share one artifact at a time without uploading it anywhere. +- Markdown stays readable +- Code keeps its language hint +- The link works from a static export
+
+
+
MarkdownCodeDiffCSVJSON
+
+
+
+ + CH 1,204 / 8,192 · CODEC ARX4 · DISCORD OK +
+
+ +
+ earx4 + carx3 + barx2 + aarx + ddeflate + llz + pplain +
+
+ + +
+ +
+ AGENT-RENDER TERMINAL · ZERO RETENTION + FRAGMENT-FIRST · OPEN SOURCE · SELF-HOSTABLE + BUDGET WATCH: 8,192 +
+
+ + diff --git a/.impeccable/drafts/c6-rain-garden.html b/.impeccable/drafts/c6-rain-garden.html new file mode 100644 index 0000000..0fae32b --- /dev/null +++ b/.impeccable/drafts/c6-rain-garden.html @@ -0,0 +1,201 @@ + + + + + +agent-render · rain garden + + + + + + +
+
CLOUD SEEDING · CLIENT SIDE ONLY
+ +
+

Zero-retention artifact viewer for AI outputs

+

Artifact content lives in the URL fragment. In static mode the host does not receive artifact content on the page request. The rain rises here, but none of it lands on our soil. Links can still appear in browser history, screenshots, and copied messages.

+
+ + +
+ payload rising — fragment only + hazards bent around hosts + clear reads, decoded locally +
+ +
+
+
How the weather works
+

The compact fragment #<tag><payload> carries everything. One tag letter names the codec: p l d a b c e. Browsers decode markdown, code, diffs, CSV, and JSON in place; the host serves the empty beds and moves on.

+

Nothing retained.
Everything readable.

+
+
+
Garden conditions
+

Fragment budget 8,192 chars before the weather turns. Decoded ceiling 200,000. Discord-md warns past 2,000. Self-hostable when your own garden needs rain barrels.

+
+
+ +
+
Plant a linkBed 02 · Local seed only
+
+
+
+
+
# Launch note +Share one artifact at a time without uploading it anywhere. +- Markdown stays readable +- Code keeps its language hint +- The link works from a static export
+
+
+
MarkdownCodeDiffCSVJSON
+
+
+
+ + SOIL LOAD 1,204 / 8,192 · SEED ARX4 · GATE OPEN +
+
+ +
+ agent-render · zero retention by host design + static soil · self-hostable rain + tend your own fragment +
+
+ + + + diff --git a/.impeccable/drafts/d1-ncr-form.html b/.impeccable/drafts/d1-ncr-form.html new file mode 100644 index 0000000..6025e9e --- /dev/null +++ b/.impeccable/drafts/d1-ncr-form.html @@ -0,0 +1,191 @@ + + + + + +agent-render — CR-39 carbonless lab slip + + + + + + +
+
FORM AR·39 — ARTIFACT TRANSFERSINGLE PART · NO MASTER RETAINED
+
+
+

Artifact transfer record

+

Zero-retention artifact viewer for AI outputs.

+
+
+
TRANSFER №
+
#e4:mKX9…q2
+
+
+
CARBON TRANSFER — the URL fragment is the carbon copy. Pad keeps nothing; static host receives nothing on request.
+ +
+
+ Artifact content lives in the URL fragment, so in static mode the static host does not receive artifact content on the page request. Links can still appear in browser history, screenshots, and copied messages — shared by transfer, not retained by us. +
+
+ +
+
+
+
+
+
+ +
# Launch note +Share one artifact at a time without uploading it anywhere. +– Markdown stays readable +– Code keeps its language hint +– The link works from a static export
+

Paste markdown notes, release docs, or a spec excerpt. Max 200,000 chars decoded.

+
+
+ +
+ MarkdownCodeDiffCSVJSON +
+
+
+ +
+ arx4 · compressed, ledger pin + plainlzdeflatearx / arx2 / arx3 +
+
+
+ +

BUDGET 1,204 / 8,192 fragment chars · discord-md OK ≤ 2,000

+
+
+ +
+ +
+ agent-render + zero retention by host design + 5 renderers · static export + nothing stored server-side +
+
+ + diff --git a/.impeccable/drafts/d2-terminal-log.html b/.impeccable/drafts/d2-terminal-log.html new file mode 100644 index 0000000..6ca76bf --- /dev/null +++ b/.impeccable/drafts/d2-terminal-log.html @@ -0,0 +1,163 @@ + + + + + +agent-render — terminal session paper log + + + + + + +
+
+ REC + session agent-render + host static + payload client-side only + bytes 1,204 · lines 42 · utf-8 +
+ + + +
+
create-linkstdin / interactive
+
+
<title>
+
<file>
+
<body> +
# Launch note +Share one artifact at a time without uploading it anywhere. +- Markdown stays readable +- Code keeps its language hint +- The link works from a static export
+
+
+ <kind> +
markdowncodediffcsvjson
+
+ +

→ fragment budget 1,204 / 8,192 · codec arx4 · discord-md ok

+
+
+ +
+
fragment-inspectorread-only
+
+
+
budget
1,204 / 8,192 chars (14.7%)
+
codec
arx4 · dict v4 pinned
+
artifacts
3 · active: brief.md
+
hash preview
#e4:mKX9zUq… truncated for log
+
+
+
+
+ +
+ ^C copy link + ^D download file + ^P print-pdf markdown only + agent-render · zero retention by host design +
+ + diff --git a/.impeccable/drafts/d3-broadcast-chyron.html b/.impeccable/drafts/d3-broadcast-chyron.html new file mode 100644 index 0000000..75e3f43 --- /dev/null +++ b/.impeccable/drafts/d3-broadcast-chyron.html @@ -0,0 +1,183 @@ + + + + + +agent-render — NTSC broadcast underline + + + + + + +
+
+ ON AIR + AGENT·RENDER + 00:00:14:07 + STATIC FEED · ZERO RETENTION · 5 RENDERERS +
+ +

Zero-retention artifact viewer for AI outputs

+
+
FORMATstatic export, client decode
+
TRANSPORT#<tag><payload>
+
BUDGET8,192 fragment chars
+
RENDERERSmd · code · diff · csv · json
+
+

Artifact content lives in the URL fragment. The static host does not receive content on the page request. The viewer decodes in-browser, in front of the audience. Links can still appear in browser history, screenshots, and copied messages. Broadcast constraints apply.

+ +
+ STATIC BOUNDARY +
+
+

What leaves the console stays on the console.

+

The browser decodes markdown, code, diffs, CSV, and JSON locally after the shell loads. Nothing rides the page request back to the host.

+
+

codec: arx4 compressed
fallbacks: plain · lz · deflate · arx–arx3
discord-md: warns past 2,000 chars

+
+
+ + + +
+
Fragment-first

#<codec-tag><payload>: no query params, no request body.

+
Honest privacy

Zero retention by host design. Links still travel wherever you paste them.

+
Self-hostable

Static export anywhere; optional UUID mode for opt-in persistence.

+
+
+ +
+ TRANSPORT LOG +
+ agent-render v3 · zero retention by host design · markdown / code / diff / csv / json · fragment budget 8,192 · arx4 dictionary pinned · links may still leak, paste with intent · static host sees nothing + +
+
+ + + diff --git a/.impeccable/drafts/d4-nautical-chart.html b/.impeccable/drafts/d4-nautical-chart.html new file mode 100644 index 0000000..f428e46 --- /dev/null +++ b/.impeccable/drafts/d4-nautical-chart.html @@ -0,0 +1,213 @@ + + + + + +agent-render · nautical chart marginalia + + + + + + +
+
+
SHEET AR·3 · EDITION v3.0
PROJECTION STATIC EXPORT
+
+
Chart of Zero-retention artifact viewing for AI outputs
+
+
AGENT-RENDER
SURVEYED · CLIENT-SIDE ONLY
+
+ +
+ + + + + N + +

Artifact content lives in the URL fragment.

+

In static mode the host does not receive artifact content on the page request. Shared by transfer, retained nowhere. Links can still appear in browser history, screenshots, and copied messages; plot your course accordingly.

+
+ rendered locally + budget hazard at 8,192 chars + discord-md warns past 2,000 +
+
+ +
+
8192ch
Fragment budget
+
200kch
Decoded payload max
+
5
Renderers on chart
+
0
Bytes retained server-side
+
+ +
+ +
+

What this chart shows

+

The browser decodes markdown, code, diffs, CSV, and JSON locally from the fragment after the shell loads. Five renderers, one sheet. Codecs plain · lz · deflate · arx · arx2 · arx3 · arx4; the compact tag form is what the current edition emits.

+
+
+ +
+
Plot an artifactField entry · local only
+
+
+
+
+
# Launch note +Share one artifact at a time without uploading it anywhere. +- Markdown stays readable +- Code keeps its language hint +- The link works from a static export
+
+
+
MarkdownCodeDiffCSVJSON
+
+
+
+ + DEPTH 1,204 / 8,192 · CODEC ARX4 · DISCORD-MD WITHIN MARKS +
+
+ +
+ agent-render · zero retention by host design + corrections to this sheet are issued as new links + survey your own payload before sounding +
+
+ + diff --git a/.impeccable/drafts/d5-blueprint.html b/.impeccable/drafts/d5-blueprint.html new file mode 100644 index 0000000..8ce30b9 --- /dev/null +++ b/.impeccable/drafts/d5-blueprint.html @@ -0,0 +1,160 @@ + + + + + +agent-render · blueprint sheet + + + + + + +
+
+
SHEET 1 / 1 · SCALE N.T.S. · REV C · DRAWN BY AGENT
+
agent-renderzero-retention artifact viewer
+
+ +
+
+

Zero-retention artifact viewer for AI outputs

+
+ PAYLOAD ≤ 8,192 CH · DECODED ≤ 200,000 CH +
+

Artifact content lives in the URL fragment. In static mode the host does not receive artifact content on the page request. Content decodes where it is read; retention ends at the fragment. Links can still appear in browser history, screenshots, and copied messages [NOTE 2, REV C].

+
+
CO·1 TRANSPORT
#<tag><payload> compact fragment form; no query params in the request line.
+
CO·2 CODECS
plain · lz · deflate · arx · arx2 · arx3 · arx4; one tag char each.
+
CO·3 RENDERERS
5 on sheet: markdown · code · diff · csv · json, decoded locally.
+
+
+
+ +
+
SECTION C-C · LINK CREATIONFIT: BASELINE ENTRY · LOCAL ONLY
+
+
+
+
+
# Launch note +Share one artifact at a time without uploading it anywhere. +- Markdown stays readable +- Code keeps its language hint +- The link works from a static export
+
+
+
MarkdownCodeDiffCSVJSON
+
+
+
+ + MATERIAL USED 1,204 / 8,192 CH · CODEC ARX4 · DISCORD-MD PASS +
+
+ +
+ REV C · 2026-08 · REDRAWN: HEADER BLOCK, FORM GRID + zero retention by host design · verify payload before issue + NOT TO SCALE · DO NOT WELD +
+
+ + diff --git a/.impeccable/drafts/d6-cd-catalog.html b/.impeccable/drafts/d6-cd-catalog.html new file mode 100644 index 0000000..3693c2c --- /dev/null +++ b/.impeccable/drafts/d6-cd-catalog.html @@ -0,0 +1,195 @@ + + + + + +agent-render · catalog sheet + + + + + + +
+
+ agent·render + + Catalog · Autumn 2026 · Sheet 3 +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Item & descriptionCat. no.
+
Zero-retention artifact viewer for AI outputs feat. 5 renderers
+

Artifact content lives in the URL fragment. In static mode the host does not receive artifact content on the page request. For markdown, code, diffs, CSV, and JSON. Links may still travel wherever you paste them.

+
AR-3000
01 +
Fragment-first transport 0:11
+

#<tag><payload> compact form. One tag letter names the codec; nothing rides the request line.

+
AR-3001
02 +
Seven codecs, one pinned dictionary 0:47
+

plain · lz · deflate · arx · arx2 · arx3 · arx4. Budget: 8,192 fragment chars; 200,000 decoded.

+
AR-3002
03 +
Zero retention by host design 1:04
+

The static host serves the empty page. Persistence exists only in the opt-in self-hosted UUID mode.

+
AR-3003
+ +
+

"The browser decodes locally. What the audience reads was never on our shelves."

+

Each link is pressed as a single edition: sharing is ordering one more copy stamped with your fragment. We hold no back-stock. Repressing means generating a new link. Discord-length orders past 2,000 formatted chars are flagged before pressing.

+
+ +
+
+
Order an edition
+

Cut your own link. Fill the pressing form below; the stamped URL is the artifact. No accounts, no shelf space.

+
+ +
+ +
+
+
+
+
+
# Launch note +Share one artifact at a time without uploading it anywhere. +- Markdown stays readable +- Code keeps its language hint +- The link works from a static export
+
+
+
MarkdownCodeDiffCSVJSON
+
+
+ +
+ +
+ zero retention by host design · © agent-render + + all editions numbered · editions are the link +
+
+ + diff --git a/.impeccable/drafts/d7-cockpit-placard.html b/.impeccable/drafts/d7-cockpit-placard.html new file mode 100644 index 0000000..cca07db --- /dev/null +++ b/.impeccable/drafts/d7-cockpit-placard.html @@ -0,0 +1,194 @@ + + + + + +agent-render · QRH card + + + + + + +
+
+ CLIENT DECODE · ACTIVE + SERVER STORAGE · NONE + AGENT-RENDER · QRH 03 +
+ +
+
+
+ + + + + +
+
+
QUICK REFERENCE · ARTIFACT VIEWING
+

Zero-retention artifact viewer for AI outputs

+
+
+ +
Artifact content lives in the URL fragment. In static mode the host does not receive artifact content on the page request. Links can still appear in browser history, screenshots, and copied messages.
+ +
+
Retention
NONE
by host design, not policy
+
Transport
FRAGMENT
#<tag><payload>
+
Renderers
5 ARMED
md · code · diff · csv · json
+
Budget
8,192 CH
caution limit on fragment
+
+ +
+
NORMAL
PROCEDURES
+
+
NP·1Paste or generate a linkcodec auto: arx4
+
NP·2Decode happens client-side onlyhost receives nothing
+
NP·3Select artifact from bundleactiveArtifactId
+
NP·4Export copy · download · printmarkdown prints clean
+
+
+ +
+
LINK GENERATION PANELPNL 03-C · LOCAL OPS
+
+
+
+
+
# Launch note +Share one artifact at a time without uploading it anywhere. +- Markdown stays readable +- Code keeps its language hint +- The link works from a static export
+
+
+
MarkdownCodeDiffCSVJSON
+
+
+
+ + LOAD 1,204 / 8,192 CH · CODEC ARX4 · DISCORD GATE CLEAR +
+
+
+ +
+ QRH 03 · REV C · agent-render + zero retention by host design + KEEP WITH THE CHECKLIST · DO NOT STOW +
+
+ + diff --git a/.impeccable/screenshots/c1-crt-arcade.png b/.impeccable/screenshots/c1-crt-arcade.png new file mode 100644 index 0000000..1f5f55a Binary files /dev/null and b/.impeccable/screenshots/c1-crt-arcade.png differ diff --git a/.impeccable/screenshots/c2-night-sixpack.png b/.impeccable/screenshots/c2-night-sixpack.png new file mode 100644 index 0000000..b9623c5 Binary files /dev/null and b/.impeccable/screenshots/c2-night-sixpack.png differ diff --git a/.impeccable/screenshots/c3-hardware-bench.png b/.impeccable/screenshots/c3-hardware-bench.png new file mode 100644 index 0000000..a5ef2e6 Binary files /dev/null and b/.impeccable/screenshots/c3-hardware-bench.png differ diff --git a/.impeccable/screenshots/c4-high-density.png b/.impeccable/screenshots/c4-high-density.png new file mode 100644 index 0000000..5ae7f51 Binary files /dev/null and b/.impeccable/screenshots/c4-high-density.png differ diff --git a/.impeccable/screenshots/c6-rain-garden.png b/.impeccable/screenshots/c6-rain-garden.png new file mode 100644 index 0000000..d4d8cc2 Binary files /dev/null and b/.impeccable/screenshots/c6-rain-garden.png differ diff --git a/.impeccable/screenshots/d1-ncr-form.png b/.impeccable/screenshots/d1-ncr-form.png new file mode 100644 index 0000000..118f7f8 Binary files /dev/null and b/.impeccable/screenshots/d1-ncr-form.png differ diff --git a/.impeccable/screenshots/d2-terminal-log.png b/.impeccable/screenshots/d2-terminal-log.png new file mode 100644 index 0000000..b475c7d Binary files /dev/null and b/.impeccable/screenshots/d2-terminal-log.png differ diff --git a/.impeccable/screenshots/d3-broadcast-chyron.png b/.impeccable/screenshots/d3-broadcast-chyron.png new file mode 100644 index 0000000..44b1989 Binary files /dev/null and b/.impeccable/screenshots/d3-broadcast-chyron.png differ diff --git a/.impeccable/screenshots/d4-nautical-chart.png b/.impeccable/screenshots/d4-nautical-chart.png new file mode 100644 index 0000000..72f9811 Binary files /dev/null and b/.impeccable/screenshots/d4-nautical-chart.png differ diff --git a/.impeccable/screenshots/d5-blueprint.png b/.impeccable/screenshots/d5-blueprint.png new file mode 100644 index 0000000..bea49fc Binary files /dev/null and b/.impeccable/screenshots/d5-blueprint.png differ diff --git a/.impeccable/screenshots/d6-cd-catalog.png b/.impeccable/screenshots/d6-cd-catalog.png new file mode 100644 index 0000000..979218e Binary files /dev/null and b/.impeccable/screenshots/d6-cd-catalog.png differ diff --git a/.impeccable/screenshots/d7-cockpit-placard.png b/.impeccable/screenshots/d7-cockpit-placard.png new file mode 100644 index 0000000..254d0a5 Binary files /dev/null and b/.impeccable/screenshots/d7-cockpit-placard.png differ diff --git a/AGENTS.md b/AGENTS.md index 1c40b0f..f385a44 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -253,10 +253,24 @@ Be conservative with product claims and precise with protocol changes. Protect that simplicity. +## UI design directives (owner-established) + +The owner ran a full impeccable-style redesign (bone-keycap-over-charcoal world, fused from the 12 concept drafts kept under `.impeccable/drafts/`) and will push back hard on these slop patterns; do not reintroduce them: + +- No self-evident narration labels in the UI: the app is a viewer, so never render chrome text like "read-only", "Decoded", a bare artifact-kind kicker (`tsx`/`CSV`), library names ("codemirror"), or the component filename ("viewer-shell"). Show the real filename or show nothing. +- No dead space: no decorative empty grid rows, no margin-inside-margin squeeze at mobile widths, no half-blank inspector cells. Layouts stay dense. +- No floating chrome: surfaces are hairline etched panels (no rounded cream cards, no radial-gradient washes); controls use the keycap grammar: buttons and pills have a 2px machined depth (lit top edge, solid bottom foot that collapses on :active), and inputs recess into the chassis with inset shadow. Decorative shadows stay out. +- Conditional presence: the fragment inspector only renders when a fragment is present, and promotes to the top of the page on decode error. +- A real footer is required, not a one-line stub. + +Shipped decision: the diff renderer uses `@pierre/diffs` (lazy-loaded, Shiki-based, no vendor CSS). Earlier `@git-diff-view/react` was replaced in the full rebuild to keep the heaviest diff stack light and theme-plumbed off the bench palette. + ## Cursor Cloud specific instructions These notes are for cloud agents whose environment already has dependencies installed (`npm install` and Chromium for Playwright run on startup). They capture only non-obvious caveats; standard commands live under `## Development commands` and `docs/testing.md`. +- On the owner's Linux machine the default `workspace_readwrite` sandbox fails at kernel level (user namespaces unavailable; Landlock and bubblewrap both return EPERM). Any Shell call there must pass `required_permissions: ["all"]` or it will not spawn. Don't conclude you "can't run shell commands" — retry with the permission request. Applies to the dev server, Playwright, and `npm install`. + - `npm run dev` serves the client shell on port `3000` with no base path. The Playwright config (`playwright.config.ts`) is separate: it runs its own `NEXT_PUBLIC_BASE_PATH=/agent-render npm run build && npm run preview` on port `4401`, so you do not need a dev server running for `npm run test:e2e`. - Locally, `npm run test:e2e` also runs the screenshot regression suite (`tests/e2e/visual.spec.ts`), which can fail on a cloud VM due to platform font/rendering differences. Set `CI=1` (e.g. `CI=1 npm run test:e2e`) to skip visual snapshots and run only the behavioral browser flow, matching CI behavior. Do not run `npm run test:e2e:update` to "fix" these unless a visual change is genuinely intended. - The product is frontend-only; there is no backend to start for the core viewing path. The hello-world flow is: open `/`, fill the "Create a link" form, click "Generate link", then "Preview here" to render the artifact from the URL fragment. diff --git a/MORNING.md b/MORNING.md new file mode 100644 index 0000000..bc2ab9f --- /dev/null +++ b/MORNING.md @@ -0,0 +1,85 @@ +# Overnight redesign — morning review + +The fused world ("Creator Hardware Bench" skeuomorphism + Japanese density + QRH/CR‑39 engraved-panel grammar) is now inked across the app. Payload protocol, renderers, colors-in-code (rainbow brackets), and `.dark` theme still work — this is a *chrome* redesign with functional content tweaks, not a renderer rewrite. + +## What changed + +### Tokens (`src/app/globals.css`) +- **Palette.** Bone keycap chrome (`--page-bg #ebe7dd` light / `#14110d` dark), one action orange, one confirmation mint, one alert brick. Radial-gradient background washes removed entirely (flat bone chassis, crisp). +- **Surfaces.** Cards are hairline-bordered etched panels on the bone chassis, no cream "card fill," no per-card shadows. Hairline borders get a touch darker (`--border-strong` raised). +- **Typography.** Display `Manrope`, body `SUSE`, mono `Spline Sans Mono` (all via `next/font/google`, self-hosted through Next). Wordmark is lowercase mono `agent-render`. Buttons and pills are uppercase mono with letter-spacing — the "engraved label" register. +- **Controls.** `.artifact-action` and `.mono-pill` are uniform keycaps: 1px border-strong, sharp 2px radius, uppercase mono text. The primary action is solid action-orange (no gradient), hover goes to `--accent-strong`. +- **Code surface.** `--surface-code*` flips to a **dark charcoal chassis even in light mode** — the dense content reads on dark, the chrome stays bone. This was the "QRH card/CR‑39 slip" move you flagged. +- **Animation budget.** Durations tightened; `budget-fill` now animates `transform: scaleX()` instead of `width`. + +### Empty-state (`viewer-shell.tsx`) +- Hero copy rewritten — headline is now `Zero-retention viewer for AI output, delivered in one link.` The "Artifact viewer" kicker dropped (you called it slop). +- Pills under hero now carry brand facts the viewer can't infer: `open source / self-hostable / no database`. +- Bento "Initialize your Artifact" section rewritten to "What renders here" with three honest steps (01/02/03) plus a trust-model card. Redundant sub-cards (Hosting, double Security link) collapsed. +- **Fragment inspector** is now its own component, extracted cleanly. It renders **only when a fragment is in the URL**, and **moves to the very top of the page if decode fails** (`hasFragmentError`). Success state label renamed `Decoded` → `Ready`. +- Wordmark lowercased, mono, semibold. Footer rebuilt: wordmark + read-only/zero-retention tagline + inline nav (Security / URL explainer / GitHub) with hairline underlines and an orange underline-in on hover. +- New favicon: flat charcoal chassis tile with bone content lines and a single orange action stripe. + +### Toolbar + metadata (`artifact-stage.tsx`, `artifact-selector.tsx`) +- "Decoded" mono-pill removed from the toolbar (your #11). +- Filename promoted to primary text at left, larger mono, follows the artifact. +- Column kind/status/file/size/language metadata grid collapsed from four bento cells to one tight horizontal strip inside a hairline panel (`.artifact-metadata-compact`). + +### Disclosure (`fragment-details-disclosure.tsx`) +- Now **expanded by default** (`
`). +- Title simplified from "Codec, budget, and hash preview" to "Transport and decode state." + +### Renderer-chrome cleanup +- `read-only` mono-pill removed from JSON renderer toolbar (`json-renderer.tsx`). +- `read-only codemirror` kicker removed from code renderer toolbar. +- Markdown blockquote reshaped: side-tab accent-bar gone, replaced with hairline top border + decorative accent-orange open-quote, non-italic. +- Code-renderer toolbar and mermaid/json/csv toolbars inherit the new keycap grammar via existing class names. + +### Mobile tightening +- Section paddings compressed at ≤640px (`hero`, `samples`, `inspector`, `stage`, `generator`: 4rem → 1.25–1.5rem). +- `.bento-card` mobile padding compressed to 1rem. +- Mobile shadow blur on panels/viewer-frames removed — crisp hairline borders are doing all the separation work. + +## What's intentionally *not* shipped + +- **Generator-input skeuomorphic "recessed slots."** Inputs now favor a thin bottom rule (1px border-bottom, `--border-strong` raising to `--accent` on focus), which reads as an engraved input. A heavier recessed look (inset shadow + raised border) is possible but fights the QRH-card flatness. + +## Known risks to spot-check + +1. **Contrast, light mode.** `--text-soft #7d7466` on `--page-bg #ebe7dd` is right at WCAG AA for 14px regular. Mono-pill labels pass (they now use `--text-primary`), but the small uppercase kicker `--text-soft` only just clears 4.5:1. +2. **`#ebe7dd` vs `#14110d`.** Verify light/dark transition doesn't look like a blinding flash on system-dark defaults. +3. **Print regression for markdown.** I removed the old `--print-*` overrides' dependence on cream — but print still uses `--print-paper` `#ffffff`, so nothing should regress. Worth one print-to-PDF sanity pass. +4. **`getStateTone("Ready")` rename.** If any e2e test expects literal "Decoded", it will fail. `grep -r "Decoded" tests/` before merging. + +## How to exercise it + +```bash +# 1. Fresh install (fonts are fetched by next/font at build-time) +npm install + +# 2. Lint / typecheck / unit / bench +npm run check + +# 3. E2E (skips visual snapshots on CI-shaped boxes) +CI=1 npm run test:e2e + +# 4. Full overnight screenshot matrix (RUN_OVERNIGHT gates it) +npm run shots:overnight + +# 5. Fresh sample links through the app's own encoder +npm run generate:samples +# → .impeccable/overnight-shots/SAMPLE_LINKS.md + +# 6. Start preview server for the sample links above +NEXT_PUBLIC_BASE_PATH=/agent-render npm run build && npm run preview +# → http://127.0.0.1:4401/agent-render/ +``` + +## Verdict + +The redesign is **ship-ready pending your spot-check**, with one focus area: contrast on the pale-bone surfaces in light mode, particularly the kicker mono caps. If `--text-soft` reads too pale, bump it to `#6e665a` and `--text-muted` to `#4c453c` — I'll land that change on a single-commit follow-up rather than shipping it tonight untested. + +Diff scope summary: +- 9 files in `src/`, 1 new in `scripts/`, 1 new `tests/e2e/overnight.spec.ts`, 1 new icon, 1 new `MORNING.md` (this file). +- No protocol changes, no dependency changes, no payload contract changes. +- No `AGENTS.md`, `docs/`, or `skills/` updates required — the redesign stays inside the existing product surface. diff --git a/PRODUCT.md b/PRODUCT.md new file mode 100644 index 0000000..366c216 --- /dev/null +++ b/PRODUCT.md @@ -0,0 +1,68 @@ +# Product + + + +## Platform + +web + +## Users + +- Primary: an AI agent / CLI emitting an `agent-render` link (auto-generated) so a human reader can open it in a browser and reliably read rich output (markdown, code, diff, csv, json) on chat surfaces that render rich content poorly (Discord, Slack, GitHub comments, email) +- Secondary: a human creating a link by hand via the on-page "Create a link" form, usually when they want to hand-share a snippet/spec/brief without uploading it anywhere +- Constraint on both: the viewer must be **read-only, trustworthy, and instantly scannable** — no editing, no sign-in, no server round-trip of the artifact body + +## Product Purpose + +A zero-retention, fully static artifact viewer for AI-generated output. Everything needed to render the artifact lives in the URL fragment, decodable purely client-side: the host sees nothing in the request path, and the reader gets one stable link that reads on any surface. + +Success = a reader opens a link from chat and immediately reads the artifact at full fidelity (syntax-highlighted code, real diffs, markdown, tables, structured JSON) without the link being mangled by chat's own formatting, and without the artifact ever touching server storage. + +## Positioning + +**Read-only rich rendering, delivered entirely client-side from a fragment payload.** The product is the *viewer*, not the upload — comparable to GitHub Gist but with the payload inlined in the URL fragment and no server-side persistence, versus Pastebin-style services that store content. Diff/markdown/code rendering match a code-review surface, not a chat bubble. + +## Operating Context + +- Links are pasted into places with hard constraints: Discord markdown (≤2000 chars for `[label](url)`), Slack, GitHub issues/comments, terminal outputs +- Rendering happens inside a static-exported client shell; no backend on the core viewing path (optional `selfhosted/` UUID mode exists for explicit persistence opt-in) +- Heavy renderers (CodeMirror, mermaid, git-patch diff, csv, json tree) are dynamically imported to keep the shell light +- Surfaces: desktop browsers for reading + link creation; mobile browsers for consumption of shared links (reading must be tight on mobile; creation is primarily desktop) + +## Capabilities and Constraints + +- Artifact kinds: markdown, code, diff, csv, json — nothing else +- Codecs: plain, lz, deflate, arx, arx2, arx3, arx4 (compact `#` form emitted; legacy `agent-render=v1...` still decodable) +- Budgets: fragment ≤ 8192 chars; decoded payload ≤ 200000 chars; Discord markdown link warning when `[label](url)` > 2000 +- Markdown renders sanitized GFM, mermaid fences interactive; markdown code fences reuse the CodeMirror highlighter (no second highlighting stack) +- Diff: real unified git `patch` preferred, `oldContent`+`newContent` fallback, `view` unified|split +- Security posture: untrusted input, no `dangerouslySetInnerHTML`, sanitization on markdown, fail-closed on malformed/oversized payloads pre-mount +- Product design red lines: no server persistence on the core path, no auth wall for viewing, no normal query-param transport, no database requirement for the static export + +## Brand Commitments + +- Name: `agent-render` +- Voice: terse, technical, honest; claims are exact (zero-retention is a host-design truth, not a privacy-anonymity promise) +- Product is open source and self-hostable; links + labels must not overstate safety ("links can still leak") +- Current mark is a placeholder; new logo/favicon being designed + +## Evidence on Hand + +- `docs/architecture.md`, `docs/payload-format.md`, `docs/deployment.md`, `docs/dependency-notes.md`, `docs/testing.md` describe the current protocol +- `skills/agent-render-linking/SKILL.md` and `skills/selfhosted-agent-render/SKILL.md` define the external linking and self-hosting contracts +- `AGENTS.md` records project identity, payload contract, shipped behaviors, and dev commands +- Screenshots of the current UI captured in this session (criticisms #1–16) reference every visible surface: home hero, link creator, fragment inspector, artifact toolbar, diff renderer, code renderer, csv, json, mobile markdown view, footer, wordmark + +## Product Principles + +1. **Client-side only, fragment-first.** The payload never travels as a request parameter and the shell stays a static export. +2. **Read-only by design.** The viewer never edits, never writes back; every toolbar action is export (copy / download / print-to-PDF for markdown). +3. **Every artifact kind is a first-class renderer.** Markdown/CodeMirror/code fences/diff/csv/json get full-fidelity displays, not degraded previews. +4. **Honest privacy.** Say what's true: zero-retention by host design. Never market as "secret-safe" beyond that. +5. **Compression when it earns it.** Compact codec tags (p/l/d/a/b/c/e) and arx-family dictionary compression exist to keep links chat-postable; plain+deflate remain the baseline. + +## Accessibility & Inclusion + +- Keyboard-reachable link creation and artifact navigation; `activeArtifactId` must be transformable into focus state for assistive tech +- Print output for markdown must be readable (browser print-to-PDF), with print styles that strip interactive chrome +- Color systems must pass WCAG contrast for text against the (dark, likely) surface palette — the kicker pattern's light-gray small text on dark near-background is a known contrast risk to fix in the redesign diff --git a/docs/architecture.md b/docs/architecture.md index 1986f30..9ff0186 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -60,7 +60,11 @@ That keeps the viewer static-hosting friendly while removing the brittle parts o ## Bundle tradeoffs -The largest remaining deferred cost is still the diff renderer stack, primarily `@git-diff-view/*` and its highlighting internals. Its vendor stylesheet is served from `public/vendor/diff-view-pure.css` with a precompressed `.css.br` variant and injected only when a rich diff mounts, so the empty shell and non-diff artifacts do not pay that CSS cost. The stack remains because it still provides the best review-style UX for multi-file git patches, split/unified modes, and syntax-aware rendering with less product code than a bespoke replacement. +The largest remaining deferred cost is still the diff renderer stack. After the full rebuild, that stack is `@pierre/diffs` (Shiki-based, dynamically imported, no vendor CSS mirror into `public/`). It lazy-loads only when a diff artifact mounts, keeping the empty shell and non-diff artifacts off that cost. The stack provides the review-style UX for multi-file git patches, split/unified modes, and syntax-aware rendering with less product code than a bespoke replacement. + +`agent-render` uses `@pierre/diffs` `PatchDiff`/`MultiFileDiff`/`FileDiff` components instead of `@codemirror/merge`. + +- `@pierre/diffs` matches the product goal better because it is already shaped like a GitHub-style review surface The JSON and markdown paths are now substantially lighter because: @@ -71,9 +75,9 @@ The JSON and markdown paths are now substantially lighter because: ## Diff choice -`agent-render` uses `@git-diff-view/react` plus git-diff `DiffFile` instances instead of `@codemirror/merge`. +`agent-render` uses `@pierre/diffs` `PatchDiff`/`MultiFileDiff`/`FileDiff` components instead of `@codemirror/merge`. -- `@git-diff-view/*` matches the product goal better because it is already shaped like a GitHub-style review surface +- `@pierre/diffs` matches the product goal better because it is already shaped like a GitHub-style review surface - split and unified views are built in - syntax highlighting and diff affordances are stronger out of the box for artifact viewing - individual file patches can be rendered as a sequence while preserving filenames and boundaries diff --git a/docs/dependency-notes.md b/docs/dependency-notes.md index c17ef1e..fc41277 100644 --- a/docs/dependency-notes.md +++ b/docs/dependency-notes.md @@ -10,7 +10,7 @@ - `rehype-sanitize` - MIT - `@codemirror/*` - MIT - `@replit/codemirror-indentation-markers` - MIT -- `@git-diff-view/*` - MIT +- `@pierre/diffs` - Apache-2.0 - `papaparse` - MIT - `lz-string` - MIT - `fflate` - MIT @@ -30,7 +30,7 @@ - `next` pins its nested `postcss` dependency to `8.5.14` via `package.json` overrides so Tailwind CSS v4's `postcss ^8.5.6` peer range is satisfied in the Next.js toolchain. - CodeMirror handles source artifacts and markdown code fences because it is excellent at read-only code presentation; JSON, markdown raw, and CSV raw views use lighter native source blocks. - `@replit/codemirror-indentation-markers` replaces custom indent-guide logic with a maintained CM6 extension. -- `@git-diff-view/*` fits review-style diffs better than a generic merge editor for the current viewer. Its pure CSS file is mirrored into `public/vendor/diff-view-pure.css` with a Brotli-compressed `public/vendor/diff-view-pure.css.br` copy by `npm run assets:compress`, and loaded only by the diff renderer; `tests/diff-style-asset.test.ts` keeps those assets in sync with the package copy. +- `@pierre/diffs` fits review-style diffs better than a generic merge editor for the current viewer. It is Shiki-based and dynamically imported only by the diff renderer, so there is no vendor stylesheet to mirror into `public/` and no `npm run assets:compress` mirroring step; `tests/components/diff-renderer.test.tsx` keeps the renderer on this contract. The previous `@git-diff-view/*` renderer and its `public/vendor/diff-view-pure.css` (+ `.br`) mirror were removed in the full rebuild. - `papaparse` handles CSV parsing; CSV rendering uses a native read-only table to avoid a data-grid dependency for the shipped static viewer. - `fflate` provides portable deflate/inflate support across iOS Safari and Android Chromium without relying on browser-specific compression streams. - `brotli-wasm` provides the arx/arx2/arx3 Brotli compression layer, including streaming decompression used to cap expanded output before allocating oversized decoded payloads. diff --git a/package-lock.json b/package-lock.json index d2d522b..1d24974 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,8 @@ "@codemirror/language": "^6.12.4", "@codemirror/state": "^6.6.0", "@codemirror/view": "^6.38.2", - "@git-diff-view/react": "^0.1.6", + "@pierre/diffs": "^1.3.5", + "@pierre/theme": "^2.0.0", "@replit/codemirror-indentation-markers": "^6.5.3", "brotli-wasm": "^3.0.1", "fflate": "^0.8.2", @@ -1126,48 +1127,6 @@ } } }, - "node_modules/@git-diff-view/core": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@git-diff-view/core/-/core-0.1.6.tgz", - "integrity": "sha512-q2Ch8jURF6pL7VeNpOgHBRVY9gsGLXCOYpKXHG3BqpXe0kv6GNSUux8SmAYsDrakBzfgDClODxDtsM2rfiWpnA==", - "license": "MIT", - "dependencies": { - "@git-diff-view/lowlight": "^0.1.6", - "fast-diff": "^1.3.0", - "highlight.js": "^11.11.0", - "lowlight": "^3.3.0" - } - }, - "node_modules/@git-diff-view/lowlight": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@git-diff-view/lowlight/-/lowlight-0.1.6.tgz", - "integrity": "sha512-YIsiAc2aWAePWaDNi3k8xI0Vs/ZItt5J6nrftTIFbMFN3GwDOsyJFm2L7o8XWKTJkV2yItaz28KUI9CWj0MVZA==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "highlight.js": "^11.11.0", - "lowlight": "^3.3.0" - } - }, - "node_modules/@git-diff-view/react": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@git-diff-view/react/-/react-0.1.6.tgz", - "integrity": "sha512-koABBon5bNKh6/WnWSxggK9ojw+cvWAPnY2/ciOkwlR+8dm0h6A7Qa5kP2HFDxqYHwZ2imkGMcSLgXMOnWHRFA==", - "license": "MIT", - "dependencies": { - "@git-diff-view/core": "^0.1.6", - "@types/hast": "^3.0.0", - "fast-diff": "^1.3.0", - "highlight.js": "^11.11.0", - "lowlight": "^3.3.0", - "reactivity-store": "^0.4.0", - "use-sync-external-store": "^1.6.0" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -2073,6 +2032,64 @@ "node": ">=12.4.0" } }, + "node_modules/@pierre/diffs": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@pierre/diffs/-/diffs-1.3.5.tgz", + "integrity": "sha512-BhaLEiUvR+BdIyOYdogA4JLQjluWPubuwySmmIqEkcE0FwIWRbgJgHNC/r884dlxEr89fO4hTk/sBln0a7NSOw==", + "license": "apache-2.0", + "dependencies": { + "@pierre/theme": "2.0.0", + "@pierre/theming": "1.0.1", + "@shikijs/transformers": "^3.0.0 || ^4.0.0", + "diff": "9.0.0", + "hast-util-to-html": "9.0.5", + "lru_map": "0.4.1", + "shiki": "^3.0.0 || ^4.0.0" + }, + "peerDependencies": { + "react": "^18.3.1 || ^19.0.0", + "react-dom": "^18.3.1 || ^19.0.0" + } + }, + "node_modules/@pierre/theme": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@pierre/theme/-/theme-2.0.0.tgz", + "integrity": "sha512-yNDd9GYLQl1mEUJR8AneJ5e4ohLIHQd/wZLWr4fagt78vS2RwwZNW530vVgHqXFAyFVcFlRmGUD5ramXH46OXw==", + "license": "apache-2.0", + "engines": { + "vscode": "^1.0.0" + } + }, + "node_modules/@pierre/theming": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@pierre/theming/-/theming-1.0.1.tgz", + "integrity": "sha512-WCI5Qd7iprDpISL9fBYOLe8RV53+b7mFNA3bPzl60/2CKCSrsKN8zEcep6Y3BAzvARlmca50zGjDodqPGiTUKA==", + "license": "apache-2.0", + "peerDependencies": { + "@pierre/theme": "^1.1.0 || ^2.0.0", + "@shikijs/themes": "^3.0.0 || ^4.0.0", + "react": "^18.3.1 || ^19.0.0", + "react-dom": "^18.3.1 || ^19.0.0", + "shiki": "^3.0.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "@pierre/theme": { + "optional": true + }, + "@shikijs/themes": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "shiki": { + "optional": true + } + } + }, "node_modules/@playwright/test": { "version": "1.61.0", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.0.tgz", @@ -2464,6 +2481,119 @@ "dev": true, "license": "MIT" }, + "node_modules/@shikijs/core": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.4.2.tgz", + "integrity": "sha512-StyzbAyxg2/tBGf78gwbBkGyeQ73lf8UiJArFaQhTQIDqQOCKPCQFanvrs4/Yv3Yfyc+ONInJM6K+FMIf+P+kA==", + "license": "MIT", + "dependencies": { + "@shikijs/primitive": "4.4.2", + "@shikijs/types": "4.4.2", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5", + "hast-util-to-html": "^9.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.4.2.tgz", + "integrity": "sha512-MnIkeqWdVPUWsxlx8gKLVCJFTsqrQJgpTPBPpQwaFeJ56lOnJxj5aN2LUFnfxUEcvOQuNocmbaMnVrCEln6rkw==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.4.2", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.6" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.4.2.tgz", + "integrity": "sha512-GLhowz1+jixjz+wiZ3wMnOn1jTxiFCGl2PkXufivbnwPHKuyw1AYqu5/hbWhZZ2oAb0NP05WUJhYeigY14drnw==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.4.2", + "@shikijs/vscode-textmate": "^10.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/langs": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.4.2.tgz", + "integrity": "sha512-8DfeusD+Zdv/eYIDdXyJTUnSMHt+aAWjAOCXV20HNGAHRlInXpG8wh421v6B91WOm9TFwRLN+b/LG5F2NAIojg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.4.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/primitive": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.4.2.tgz", + "integrity": "sha512-l6fQQKsOMlz72n38fztmSgZ76MO6KSWuw8o+GJ+FhmqrpC9pIOJNQNXGgbb5yX2AwpzlEHwsaLPnk/8o4Fm+rA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.4.2", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/themes": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.4.2.tgz", + "integrity": "sha512-H0CFoL07ddDC2Dd6EdrPYNkRhUR6YCkJlnuYFceYYUJJA5TIm2b5B33qqiDYryBExgbKMndFJPb2u1gTuqO37g==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.4.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/transformers": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-4.4.2.tgz", + "integrity": "sha512-d81PJ9KkR1tVP95FH/9296HTtDo0mh76wv10u9T1YmsZq/UcXgt0OLdBszfUQ1i+umkRMCjDnFbFZU7/tCODTQ==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "4.4.2", + "@shikijs/types": "4.4.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/types": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.4.2.tgz", + "integrity": "sha512-PFYitV4vpDr/iPCIhnHp+Q4ftic5N5VeNJ3KQ1O8gn3h2ar8qgwMAXF7tq4m1CWaMS60fV4VqF6vfnWH4F7vqQ==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -3247,9 +3377,9 @@ "license": "MIT" }, "node_modules/@types/hast": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", "license": "MIT", "dependencies": { "@types/unist": "*" @@ -4016,21 +4146,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@vue/reactivity": { - "version": "3.5.38", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.38.tgz", - "integrity": "sha512-pG6LV/NDNRbKizcUjFFLAfjaL8mcv4DmR9avNcUw2gDHBzZneuS2TWCmp633ynzxz9YYKNeEPK2I8Wraqy2HUQ==", - "license": "MIT", - "dependencies": { - "@vue/shared": "3.5.38" - } - }, - "node_modules/@vue/shared": { - "version": "3.5.38", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.38.tgz", - "integrity": "sha512-FTW0AFZNaK5/mOqvGBwVfUlNLU38TiQn4+DQgIFUnrBBJQ1crMJ82yeGQLV5jyKFsO8yRukpbuP7x+nRbH6aug==", - "license": "MIT" - }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -5519,6 +5634,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/diff": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-9.0.0.tgz", + "integrity": "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -6341,12 +6465,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "license": "Apache-2.0" - }, "node_modules/fast-glob": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", @@ -6822,6 +6940,29 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-to-jsx-runtime": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", @@ -6862,15 +7003,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/highlight.js": { - "version": "11.11.1", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", - "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/html-encoding-sniffer": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", @@ -6894,6 +7026,16 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -8057,20 +8199,11 @@ "loose-envify": "cli.js" } }, - "node_modules/lowlight": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz", - "integrity": "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "devlop": "^1.0.0", - "highlight.js": "~11.11.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } + "node_modules/lru_map": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.4.1.tgz", + "integrity": "sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg==", + "license": "MIT" }, "node_modules/lru-cache": { "version": "11.2.6", @@ -9404,6 +9537,23 @@ "wrappy": "1" } }, + "node_modules/oniguruma-parser": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz", + "integrity": "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==", + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.6.tgz", + "integrity": "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.2", + "regex": "^6.1.0", + "regex-recursion": "^6.0.2" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -9925,20 +10075,6 @@ "react": ">=18" } }, - "node_modules/reactivity-store": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/reactivity-store/-/reactivity-store-0.4.0.tgz", - "integrity": "sha512-uL9uoREOBg2o4zUa8vMU0AbvAOk0osPloizscmyZqMvJzcuuKX3ELFYYr1DX8gAcfvlhPduz4QuLZn1eChCu4Q==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "~3.5.30", - "@vue/shared": "~3.5.30", - "use-sync-external-store": "^1.6.0" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -9991,6 +10127,30 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -10478,6 +10638,25 @@ "node": ">=8" } }, + "node_modules/shiki": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.4.2.tgz", + "integrity": "sha512-P8F/dFhRevaw2uSdeIYlq/5SXZNY85DPtmXQ947gD1Zj2JqO5AkNvVVBar0Me9JkFx3uzVud/qOtP5ek9NEQGA==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "4.4.2", + "@shikijs/engine-javascript": "4.4.2", + "@shikijs/engine-oniguruma": "4.4.2", + "@shikijs/langs": "4.4.2", + "@shikijs/themes": "4.4.2", + "@shikijs/types": "4.4.2", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -11961,15 +12140,6 @@ "punycode": "^2.1.0" } }, - "node_modules/use-sync-external-store": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", - "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 24d1161..d682e76 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,8 @@ "typecheck": "node scripts/ensure-next-types.mjs && tsc --noEmit", "check": "npm run lint && npm run test && npm run bench:codecs && npm run typecheck && npm run build && npm run check:build-budgets", "check:build-budgets": "node scripts/check-build-budgets.mjs", + "generate:samples": "npx tsx scripts/generate-sample-artifacts.mjs", + "shots:overnight": "RUN_OVERNIGHT=1 playwright test tests/e2e/overnight.spec.ts", "check:public-export-docs": "node scripts/check-public-export-docs.mjs", "selfhosted:dev": "node --import tsx selfhosted/server.ts", "selfhosted:build": "tsc -p selfhosted/tsconfig.json", @@ -56,7 +58,8 @@ "@codemirror/language": "^6.12.4", "@codemirror/state": "^6.6.0", "@codemirror/view": "^6.38.2", - "@git-diff-view/react": "^0.1.6", + "@pierre/diffs": "^1.3.5", + "@pierre/theme": "^2.0.0", "@replit/codemirror-indentation-markers": "^6.5.3", "brotli-wasm": "^3.0.1", "fflate": "^0.8.2", diff --git a/public/_headers b/public/_headers index f2ce36d..0a1b2ee 100644 --- a/public/_headers +++ b/public/_headers @@ -19,8 +19,3 @@ Content-Type: application/json; charset=utf-8 Content-Encoding: br Vary: Accept-Encoding - -/vendor/diff-view-pure.css.br - Content-Type: text/css; charset=utf-8 - Content-Encoding: br - Vary: Accept-Encoding diff --git a/public/vendor/diff-view-pure.css b/public/vendor/diff-view-pure.css deleted file mode 100644 index 9c6e072..0000000 --- a/public/vendor/diff-view-pure.css +++ /dev/null @@ -1,771 +0,0 @@ -.diff-tailwindcss-wrapper .\!container { - width: 100% !important; -} -.diff-tailwindcss-wrapper .container { - width: 100%; -} -@media (min-width: 640px) { - .diff-tailwindcss-wrapper .\!container { - max-width: 640px !important; - } - .diff-tailwindcss-wrapper .container { - max-width: 640px; - } -} -@media (min-width: 768px) { - .diff-tailwindcss-wrapper .\!container { - max-width: 768px !important; - } - .diff-tailwindcss-wrapper .container { - max-width: 768px; - } -} -@media (min-width: 1024px) { - .diff-tailwindcss-wrapper .\!container { - max-width: 1024px !important; - } - .diff-tailwindcss-wrapper .container { - max-width: 1024px; - } -} -@media (min-width: 1280px) { - .diff-tailwindcss-wrapper .\!container { - max-width: 1280px !important; - } - .diff-tailwindcss-wrapper .container { - max-width: 1280px; - } -} -@media (min-width: 1536px) { - .diff-tailwindcss-wrapper .\!container { - max-width: 1536px !important; - } - .diff-tailwindcss-wrapper .container { - max-width: 1536px; - } -} -.diff-tailwindcss-wrapper .invisible { - visibility: hidden; -} -.diff-tailwindcss-wrapper .absolute { - position: absolute; -} -.diff-tailwindcss-wrapper .relative { - position: relative; -} -.diff-tailwindcss-wrapper .sticky { - position: sticky; -} -.diff-tailwindcss-wrapper .left-0 { - left: 0px; -} -.diff-tailwindcss-wrapper .left-\[100\%\] { - left: 100%; -} -.diff-tailwindcss-wrapper .right-\[100\%\] { - right: 100%; -} -.diff-tailwindcss-wrapper .top-0 { - top: 0px; -} -.diff-tailwindcss-wrapper .top-\[50\%\] { - top: 50%; -} -.diff-tailwindcss-wrapper .z-\[1\] { - z-index: 1; -} -.diff-tailwindcss-wrapper .ml-\[-1\.5em\] { - margin-left: -1.5em; -} -.diff-tailwindcss-wrapper .block { - display: block; -} -.diff-tailwindcss-wrapper .inline-block { - display: inline-block; -} -.diff-tailwindcss-wrapper .flex { - display: flex; -} -.diff-tailwindcss-wrapper .table { - display: table; -} -.diff-tailwindcss-wrapper .hidden { - display: none; -} -.diff-tailwindcss-wrapper .h-\[50\%\] { - height: 50%; -} -.diff-tailwindcss-wrapper .h-full { - height: 100%; -} -.diff-tailwindcss-wrapper .min-h-\[28px\] { - min-height: 28px; -} -.diff-tailwindcss-wrapper .w-\[1\%\] { - width: 1%; -} -.diff-tailwindcss-wrapper .w-\[1\.5em\] { - width: 1.5em; -} -.diff-tailwindcss-wrapper .w-\[1\.5px\] { - width: 1.5px; -} -.diff-tailwindcss-wrapper .w-\[10px\] { - width: 10px; -} -.diff-tailwindcss-wrapper .w-\[1px\] { - width: 1px; -} -.diff-tailwindcss-wrapper .w-\[50\%\] { - width: 50%; -} -.diff-tailwindcss-wrapper .w-full { - width: 100%; -} -.diff-tailwindcss-wrapper .w-max { - width: -moz-max-content; - width: max-content; -} -.diff-tailwindcss-wrapper .min-w-\[100px\] { - min-width: 100px; -} -.diff-tailwindcss-wrapper .min-w-\[40px\] { - min-width: 40px; -} -.diff-tailwindcss-wrapper .min-w-full { - min-width: 100%; -} -.diff-tailwindcss-wrapper .flex-shrink-0 { - flex-shrink: 0; -} -.diff-tailwindcss-wrapper .shrink-0 { - flex-shrink: 0; -} -.diff-tailwindcss-wrapper .basis-\[50\%\] { - flex-basis: 50%; -} -.diff-tailwindcss-wrapper .table-fixed { - table-layout: fixed; -} -.diff-tailwindcss-wrapper .border-collapse { - border-collapse: collapse; -} -.diff-tailwindcss-wrapper .border-spacing-0 { - --tw-border-spacing-x: 0px; - --tw-border-spacing-y: 0px; - border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y); -} -.diff-tailwindcss-wrapper .origin-center { - transform-origin: center; -} -.diff-tailwindcss-wrapper .translate-x-\[-50\%\] { - --tw-translate-x: -50%; - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); -} -.diff-tailwindcss-wrapper .translate-x-\[50\%\] { - --tw-translate-x: 50%; - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); -} -.diff-tailwindcss-wrapper .translate-y-\[-50\%\] { - --tw-translate-y: -50%; - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); -} -.diff-tailwindcss-wrapper .transform { - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); -} -.diff-tailwindcss-wrapper .cursor-pointer { - cursor: pointer; -} -.diff-tailwindcss-wrapper .select-none { - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; -} -.diff-tailwindcss-wrapper .flex-col { - flex-direction: column; -} -.diff-tailwindcss-wrapper .items-start { - align-items: flex-start; -} -.diff-tailwindcss-wrapper .items-center { - align-items: center; -} -.diff-tailwindcss-wrapper .justify-center { - justify-content: center; -} -.diff-tailwindcss-wrapper .overflow-x-auto { - overflow-x: auto; -} -.diff-tailwindcss-wrapper .overflow-y-hidden { - overflow-y: hidden; -} -.diff-tailwindcss-wrapper .whitespace-nowrap { - white-space: nowrap; -} -.diff-tailwindcss-wrapper .break-all { - word-break: break-all; -} -.diff-tailwindcss-wrapper .rounded-\[2px\] { - border-radius: 2px; -} -.diff-tailwindcss-wrapper .rounded-md { - border-radius: 0.375rem; -} -.diff-tailwindcss-wrapper .border-l-\[1px\] { - border-left-width: 1px; -} -.diff-tailwindcss-wrapper .fill-current { - fill: currentColor; -} -.diff-tailwindcss-wrapper .p-0 { - padding: 0px; -} -.diff-tailwindcss-wrapper .p-\[1px\] { - padding: 1px; -} -.diff-tailwindcss-wrapper .px-\[10px\] { - padding-left: 10px; - padding-right: 10px; -} -.diff-tailwindcss-wrapper .py-\[2px\] { - padding-top: 2px; - padding-bottom: 2px; -} -.diff-tailwindcss-wrapper .py-\[6px\] { - padding-top: 6px; - padding-bottom: 6px; -} -.diff-tailwindcss-wrapper .pl-\[1\.5em\] { - padding-left: 1.5em; -} -.diff-tailwindcss-wrapper .pl-\[10px\] { - padding-left: 10px; -} -.diff-tailwindcss-wrapper .pl-\[2\.0em\] { - padding-left: 2.0em; -} -.diff-tailwindcss-wrapper .pr-\[10px\] { - padding-right: 10px; -} -.diff-tailwindcss-wrapper .text-right { - text-align: right; -} -.diff-tailwindcss-wrapper .indent-\[0\.2em\] { - text-indent: 0.2em; -} -.diff-tailwindcss-wrapper .align-top { - vertical-align: top; -} -.diff-tailwindcss-wrapper .align-middle { - vertical-align: middle; -} -.diff-tailwindcss-wrapper .text-\[1\.2em\] { - font-size: 1.2em; -} -.diff-tailwindcss-wrapper .leading-\[1\.6\] { - line-height: 1.6; -} -.diff-tailwindcss-wrapper .\!text-red-500 { - --tw-text-opacity: 1 !important; - color: rgb(239 68 68 / var(--tw-text-opacity, 1)) !important; -} -.diff-tailwindcss-wrapper .opacity-\[0\.5\] { - opacity: 0.5; -} -.diff-tailwindcss-wrapper .transition-transform { - transition-property: transform; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; -} -.diff-tailwindcss-wrapper * { - box-sizing: border-box; -} -.diff-tailwindcss-wrapper[data-theme="light"] .diff-style-root { - --diff-border--: #dedede; - --diff-add-content--: #dafbe1; - --diff-del-content--: #ffebe9; - --diff-add-lineNumber--: #aceebb; - --diff-del-lineNumber--: #ffcecb; - --diff-plain-content--: #ffffff; - --diff-expand-content--: #fafafa; - --diff-plain-lineNumber--: #fafafa; - --diff-expand-lineNumber--: #fafafa; - --diff-plain-lineNumber-color--: #555555; - --diff-expand-lineNumber-color--: #555555; - --diff-hunk-content--: #ddf4ff; - --diff-hunk-lineNumber--: #b6e3ff; - --diff-hunk-lineNumber-hover--: #0969da; - --diff-add-content-highlight--: #aceebb; - --diff-del-content-highlight--: #ffcecb; - --diff-add-widget--: #0969d2; - --diff-add-widget-color--: #ffffff; - --diff-empty-content--: #fafafa; - --diff-hunk-content-color--: #777777; - - color: black; -} -.diff-tailwindcss-wrapper .diff-style-root .diff-line-syntax-raw *, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw * { - color: var(--diff-view-light, inherit); - font-weight: var(--diff-view-light-font-weight, inherit); -} -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-style-root { - --diff-border--: #3d444d; - --diff-add-content--: #18271f; - --diff-del-content--: #23191c; - --diff-add-lineNumber--: #284228; - --diff-del-lineNumber--: #4f2828; - --diff-plain-content--: #0d1117; - --diff-expand-content--: #161b22; - --diff-plain-lineNumber--: #161b22; - --diff-expand-lineNumber--: #161b22; - --diff-plain-lineNumber-color--: #a0aaab; - --diff-expand-lineNumber-color--: #a0aaab; - --diff-hunk-content--: #131d2e; - --diff-hunk-lineNumber--: #0c2d6b; - --diff-hunk-lineNumber-hover--: #1f6feb; - --diff-add-content-highlight--: #2f5732; - --diff-del-content-highlight--: #713431; - --diff-add-widget--: #0969d2; - --diff-add-widget-color--: #ffffff; - --diff-empty-content--: #161b22; - --diff-hunk-content-color--: #9298a0; - - color: white; -} -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw * { - color: var(--diff-view-dark, inherit); - font-weight: var(--diff-view-dark-font-weight, inherit); -} -.diff-tailwindcss-wrapper table, -.diff-tailwindcss-wrapper tr, -.diff-tailwindcss-wrapper td { - border-color: transparent; - border-width: 0px; - text-align: left; -} -.diff-tailwindcss-wrapper td { - padding: 0; -} -.diff-tailwindcss-wrapper .diff-line-old-num, -.diff-tailwindcss-wrapper .diff-line-new-num, -.diff-tailwindcss-wrapper .diff-line-num { - text-align: right; -} -.diff-tailwindcss-wrapper .diff-style-root tr { - content-visibility: auto; -} -.diff-tailwindcss-wrapper .diff-add-widget-wrapper { - transform-origin: center; - transform: translateX(-50%) !important; -} -.diff-tailwindcss-wrapper .diff-line-old-content .diff-add-widget-wrapper, -.diff-tailwindcss-wrapper .diff-line-new-content .diff-add-widget-wrapper { - transform: translateX(50%) !important; -} -.diff-tailwindcss-wrapper .diff-add-widget-wrapper:hover { - transform: translateX(-50%) scale(1.1) !important; -} -.diff-tailwindcss-wrapper .diff-line-old-content .diff-add-widget-wrapper:hover, -.diff-tailwindcss-wrapper .diff-line-new-content .diff-add-widget-wrapper:hover { - transform: translateX(50%) scale(1.1) !important; -} -.diff-tailwindcss-wrapper .diff-widget-tooltip { - position: relative; -} -.diff-tailwindcss-wrapper .diff-add-widget, -.diff-tailwindcss-wrapper .diff-widget-tooltip { - font-family: inherit; - font-feature-settings: inherit; - font-variation-settings: inherit; - font-size: 100%; - font-weight: inherit; - line-height: inherit; - letter-spacing: inherit; - color: inherit; - margin: 0; - text-transform: none; - border-width: 0px; - background-color: transparent; - background-image: none; -} -.diff-tailwindcss-wrapper .diff-widget-tooltip::after { - display: none; - box-sizing: border-box; - background-color: #555555; - position: absolute; - content: attr(data-title); - font-size: 11px; - padding: 1px 2px; - border-radius: 4px; - overflow: hidden; - top: 50%; - white-space: nowrap; - transform: translateY(-50%); - left: calc(100% + 8px); - color: #ffffff; -} -.diff-tailwindcss-wrapper .diff-widget-tooltip::before { - display: none; - box-sizing: border-box; - content: ""; - position: absolute; - top: 50%; - left: calc(100% - 2px); - transform: translateY(-50%); - border: 6px solid transparent; - border-right-color: #555555; -} -.diff-tailwindcss-wrapper .diff-widget-tooltip:hover { - background-color: var(--diff-hunk-lineNumber-hover--); - color: white; -} -.diff-tailwindcss-wrapper .diff-widget-tooltip:hover::before { - display: block; -} -.diff-tailwindcss-wrapper .diff-widget-tooltip:hover::after { - display: block; -} -.diff-line-extend-wrapper, -.diff-line-widget-wrapper { - display: flow-root; -} -.diff-line-extend-wrapper * { - color: initial; -} -.diff-line-widget-wrapper * { - color: initial; -} -/* Multi-select styles for line range selection */ -.diff-tailwindcss-wrapper .diff-multi-select-active.diff-line-new-num, -.diff-tailwindcss-wrapper .diff-multi-select-active.diff-line-old-num, -.diff-tailwindcss-wrapper .diff-multi-select-active.diff-line-num { - z-index: 2; -} -.diff-tailwindcss-wrapper .diff-multi-select-active.diff-line-content, -.diff-tailwindcss-wrapper .diff-multi-select-active.diff-line-new-content, -.diff-tailwindcss-wrapper .diff-multi-select-active.diff-line-old-content { - position: relative; -} -.diff-tailwindcss-wrapper .diff-multi-select-active.diff-line-new-num::after, -.diff-tailwindcss-wrapper .diff-multi-select-active.diff-line-old-num::after, -.diff-tailwindcss-wrapper .diff-multi-select-active.diff-line-num::after { - content: ""; - position: absolute; - z-index: 1; - inset: 0; - opacity: 0.15; - background-color: var(--diff-multi-select-bg, #f0c000); - pointer-events: none; -} -.diff-tailwindcss-wrapper .diff-multi-select-active.diff-line-new-num::before, -.diff-tailwindcss-wrapper .diff-multi-select-active.diff-line-old-num::before, -.diff-tailwindcss-wrapper .diff-multi-select-active.diff-line-num::before { - content: ""; - z-index: 2; - position: absolute; - top: 0; - bottom: 0; - right: -2px; - width: 4px; - background-color: var(--diff-multi-select-border, #2588fa); -} -.diff-tailwindcss-wrapper .diff-multi-select-active.diff-line-new-content::after, -.diff-tailwindcss-wrapper .diff-multi-select-active.diff-line-old-content::after, -.diff-tailwindcss-wrapper .diff-multi-select-active.diff-line-content::after { - content: ""; - position: absolute; - z-index: 1; - inset: 0; - opacity: 0.15; - background-color: var(--diff-multi-select-bg, #f0c000); - pointer-events: none; -} -/* Multi-select: ensure proper positioning for line number cells */ -.diff-multi-selecting .diff-line-old-num, -.diff-multi-selecting .diff-line-new-num, -.diff-multi-selecting .diff-line-num { - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; -} -.diff-multi-selecting .diff-line-old-content, -.diff-multi-selecting .diff-line-new-content, -.diff-multi-selecting .diff-line-content { - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; -} -/* Prevent text selection during multi-line selection */ -.diff-multi-selecting { - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; -} -.diff-multi-selecting * { - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; -} -/* Hide addWidget button during active dragging selection */ -.diff-multi-selecting .diff-add-widget-wrapper { - display: none; -} -/* Ensure addWidget button is above selection overlay */ -.diff-multiselect-wrapper .diff-add-widget-wrapper { - z-index: 10 !important; -} -/* Line number span should not interfere with click events */ -.diff-tailwindcss-wrapper .diff-line-new-num span[data-line-num], -.diff-tailwindcss-wrapper .diff-line-old-num span[data-line-num] { - pointer-events: none; -} -.diff-multiselect-wrapper .diff-line-old-num, -.diff-multiselect-wrapper .diff-line-new-num, -.diff-multiselect-wrapper .diff-line-num { - cursor: pointer; -} -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw pre code.hljs { - display: block; - overflow-x: auto; - padding: 1em -} -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw code.hljs { - padding: 3px 5px -} -/*! - Theme: GitHub - Description: Light theme as seen on github.com - Author: github.com - Maintainer: @Hirse - Updated: 2021-05-15 - - Outdated base version: https://github.com/primer/github-syntax-light - Current colors taken from GitHub's CSS -*/ -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs { - color: #24292e; - background: #ffffff -} -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-doctag, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-keyword, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-meta .hljs-keyword, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-template-tag, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-template-variable, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-type, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-variable.language_ { - /* prettylights-syntax-keyword */ - color: #d73a49 -} -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-title, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-title.class_, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-title.class_.inherited__, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-title.function_ { - /* prettylights-syntax-entity */ - color: #6f42c1 -} -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-attr, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-attribute, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-literal, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-meta, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-number, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-operator, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-variable, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-selector-attr, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-selector-class, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-selector-id { - /* prettylights-syntax-constant */ - color: #005cc5 -} -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-regexp, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-string, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-meta .hljs-string { - /* prettylights-syntax-string */ - color: #032f62 -} -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-built_in, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-symbol { - /* prettylights-syntax-variable */ - color: #e36209 -} -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-comment, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-code, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-formula { - /* prettylights-syntax-comment */ - color: #6a737d -} -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-name, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-quote, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-selector-tag, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-selector-pseudo { - /* prettylights-syntax-entity-tag */ - color: #22863a -} -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-subst { - /* prettylights-syntax-storage-modifier-import */ - color: #24292e -} -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-section { - /* prettylights-syntax-markup-heading */ - color: #005cc5; - font-weight: bold -} -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-bullet { - /* prettylights-syntax-markup-list */ - color: #735c0f -} -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-emphasis { - /* prettylights-syntax-markup-italic */ - color: #24292e; - font-style: italic -} -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-strong { - /* prettylights-syntax-markup-bold */ - color: #24292e; - font-weight: bold -} -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-addition { - /* prettylights-syntax-markup-inserted */ - color: #22863a; - background-color: #f0fff4 -} -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-deletion { - /* prettylights-syntax-markup-deleted */ - color: #b31d28; - background-color: #ffeef0 -} -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-char.escape_, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-link, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-params, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-property, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-punctuation, -.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-tag { - /* purposely ignored */ - -} -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw pre code.hljs { - display: block; - overflow-x: auto; - padding: 1em -} -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw code.hljs { - padding: 3px 5px -} -/*! - Theme: GitHub Dark - Description: Dark theme as seen on github.com - Author: github.com - Maintainer: @Hirse - Updated: 2021-05-15 - - Outdated base version: https://github.com/primer/github-syntax-dark - Current colors taken from GitHub's CSS -*/ -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs { - color: #c9d1d9; - background: #0d1117 -} -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-doctag, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-keyword, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-meta .hljs-keyword, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-template-tag, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-template-variable, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-type, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-variable.language_ { - /* prettylights-syntax-keyword */ - color: #ff7b72 -} -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-title, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-title.class_, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-title.class_.inherited__, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-title.function_ { - /* prettylights-syntax-entity */ - color: #d2a8ff -} -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-attr, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-attribute, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-literal, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-meta, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-number, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-operator, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-variable, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-selector-attr, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-selector-class, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-selector-id { - /* prettylights-syntax-constant */ - color: #79c0ff -} -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-regexp, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-string, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-meta .hljs-string { - /* prettylights-syntax-string */ - color: #a5d6ff -} -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-built_in, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-symbol { - /* prettylights-syntax-variable */ - color: #ffa657 -} -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-comment, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-code, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-formula { - /* prettylights-syntax-comment */ - color: #8b949e -} -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-name, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-quote, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-selector-tag, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-selector-pseudo { - /* prettylights-syntax-entity-tag */ - color: #7ee787 -} -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-subst { - /* prettylights-syntax-storage-modifier-import */ - color: #c9d1d9 -} -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-section { - /* prettylights-syntax-markup-heading */ - color: #1f6feb; - font-weight: bold -} -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-bullet { - /* prettylights-syntax-markup-list */ - color: #f2cc60 -} -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-emphasis { - /* prettylights-syntax-markup-italic */ - color: #c9d1d9; - font-style: italic -} -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-strong { - /* prettylights-syntax-markup-bold */ - color: #c9d1d9; - font-weight: bold -} -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-addition { - /* prettylights-syntax-markup-inserted */ - color: #aff5b4; - background-color: #033a16 -} -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-deletion { - /* prettylights-syntax-markup-deleted */ - color: #ffdcd7; - background-color: #67060c -} -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-char.escape_, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-link, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-params, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-property, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-punctuation, -.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-tag { - /* purposely ignored */ - -} -.diff-tailwindcss-wrapper .hover\:scale-110:hover { - --tw-scale-x: 1.1; - --tw-scale-y: 1.1; - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); -} -.diff-tailwindcss-wrapper .group:hover .group-hover\:visible { - visibility: visible; -} diff --git a/public/vendor/diff-view-pure.css.br b/public/vendor/diff-view-pure.css.br deleted file mode 100644 index 04cbb92..0000000 Binary files a/public/vendor/diff-view-pure.css.br and /dev/null differ diff --git a/scripts/bench-codecs.mjs b/scripts/bench-codecs.mjs index bfdb783..314a048 100644 --- a/scripts/bench-codecs.mjs +++ b/scripts/bench-codecs.mjs @@ -304,7 +304,7 @@ const packageManifestFixture = JSON.stringify( }, dependencies: { "@codemirror/view": "^6.38.2", - "@git-diff-view/react": "^0.1.1", + "@pierre/diffs": "^1.3.3", "brotli-wasm": "^3.0.1", "fflate": "^0.8.2", "lucide-react": "^0.577.0", diff --git a/scripts/check-build-budgets.mjs b/scripts/check-build-budgets.mjs index 01d5434..c8a9f4e 100644 --- a/scripts/check-build-budgets.mjs +++ b/scripts/check-build-budgets.mjs @@ -47,13 +47,12 @@ export const budgets = [ type: "loadable", }, { - // Guards the rich diff library (@git-diff-view/react), the heaviest deferred - // chunk. 340 KiB gzipped is intentionally generous because the library is - // large but always lazy-loaded. A jump here means a version bump pulled in - // more — confirm the upgrade is wanted before raising. - importKeyParts: ["components/renderers/diff-renderer", "@git-diff-view/react"], - maxBytes: 340 * 1024, - name: "rich diff library deferred JS", + // Guards the rich diff library (@pierre/diffs), the heaviest deferred + // chunk. Shiki's engine makes it ~860 KiB gzipped; always lazy-loaded. + // A meaningful jump means a version bump pulled in more — confirm first. + importKeyParts: ["components/renderers/diff-renderer", "@pierre/diffs"], + maxBytes: 900 * 1024, + name: "@pierre/diffs deferred JS", type: "loadable", }, ]; diff --git a/scripts/compress-dictionary.mjs b/scripts/compress-dictionary.mjs index 011aa04..4a301fb 100644 --- a/scripts/compress-dictionary.mjs +++ b/scripts/compress-dictionary.mjs @@ -8,15 +8,12 @@ * public/arx-dictionary.json.br — brotli-compressed (CDN or agent can use this) * public/arx2-dictionary.json — minified overlay JSON * public/arx2-dictionary.json.br — brotli-compressed overlay JSON - * public/vendor/diff-view-pure.css — mirrored @git-diff-view stylesheet - * public/vendor/diff-view-pure.css.br — brotli-compressed stylesheet loaded by diffs * * Run: node scripts/compress-dictionary.mjs */ -import { mkdirSync, readFileSync, writeFileSync } from "fs"; +import { readFileSync, writeFileSync } from "fs"; import { createRequire } from "module"; -import { dirname } from "path"; const require = createRequire(import.meta.url); const brotli = require("brotli-wasm"); @@ -39,17 +36,5 @@ function compressDictionary(path) { writeCompressedTextAsset(path, minified); } -function mirrorAndCompressTextAsset(sourcePath, targetPath) { - const source = readFileSync(sourcePath, "utf8"); - - mkdirSync(dirname(targetPath), { recursive: true }); - writeFileSync(targetPath, source, "utf8"); - writeCompressedTextAsset(targetPath, source); -} - compressDictionary("public/arx-dictionary.json"); compressDictionary("public/arx2-dictionary.json"); -mirrorAndCompressTextAsset( - "node_modules/@git-diff-view/react/styles/diff-view-pure.css", - "public/vendor/diff-view-pure.css", -); diff --git a/scripts/generate-sample-artifacts.mjs b/scripts/generate-sample-artifacts.mjs new file mode 100644 index 0000000..89bb98e --- /dev/null +++ b/scripts/generate-sample-artifacts.mjs @@ -0,0 +1,265 @@ +#!/usr/bin/env node +// Generates fresh sample fragment links across all supported artifact kinds, +// exercises the app's own encoder (`createGeneratedArtifactLink`), and writes +// a clickable markdown index to `.impeccable/overnight-shots/SAMPLE_LINKS.md`. +// +// Usage: node scripts/generate-sample-artifacts.mjs +// Requires tsx (devDependency): `npx tsx` resolves this automatically when +// invoked via `npm run generate:samples`. + +import { mkdir, writeFile } from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { createGeneratedArtifactLink } from "../src/lib/payload/link-creator.ts"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const OUTPUT_DIR = path.resolve(__dirname, "../.impeccable/overnight-shots"); +const INDEX_PATH = path.join(OUTPUT_DIR, "SAMPLE_LINKS.md"); +const PREVIEW_BASE = "http://127.0.0.1:4401/agent-render/"; + +const MARKDOWN_CONTENT = `# Maintainer kickoff — phase notes + +This sample exercises the markdown renderer end-to-end: GFM tables, checklist items, inline code, a CodeMirror-backed fence, and a mermaid diagram. Every artifact kind renders without the payload ever touching the host request path. + +## What's inside + +- [x] Sanitized GFM rendering +- [x] CodeMirror-backed syntax fences +- [x] Interactive mermaid diagrams +- [ ] Transcript of user confirmations + +## Table + +| Codec | Tag | Wire size (this brief) | Notes | +| ------ | --- | ---------------------: | ------------------------ | +| plain | p | ~1.0x | Baseline, no compression | +| lz | l | ~0.7x | LZ-string | +| arx4 | e | ~0.4x | Context mixer + priors | + +## Code fence + +\`\`\`tsx +type Payload = { codec: string; fragment: string }; + +export function decodeEnvelop(fr: string): Payload { + const [codec, ...rest] = fr.split("."); + return { codec, fragment: rest.join(".") }; +} +\`\`\` + +## Diagram + +\`\`\`mermaid +flowchart LR + A[chat surface] -->|paste link| B[static host] + B -->|serve shell only| C[browser] + C -->|decode fragment| D[artifact rendered] + D -.->|never sent back| B +\`\`\` + +## Links and inline code + +The \`/agent-render\` base path is configurable via \`NEXT_PUBLIC_BASE_PATH\`. Read the [architecture doc](https://example.com) for the transport contract. +`; + +const CODE_CONTENT = `// Code renderer spot-check: a hook with a subtle memo. +import { useEffect, useRef, useState } from "react"; + +export function useDebouncedValue(value, delayMs = 250) { + const [debounced, setDebounced] = useState(value); + const timerRef = useRef(0); + + useEffect(() => { + window.clearTimeout(timerRef.current); + timerRef.current = window.setTimeout(() => { + setDebounced(value); + }, delayMs); + return () => window.clearTimeout(timerRef.current); + }, [value, delayMs]); + + return debounced; +} + +export function useFragmentLength(hash) { + return hash.startsWith("#") ? hash.length - 1 : 0; +} +`; + +const DIFF_CONTENT = `diff --git a/src/components/viewer-shell.tsx b/src/components/viewer-shell.tsx +index 3c8e1a4..9f2b7d1 100644 +--- a/src/components/viewer-shell.tsx ++++ b/src/components/viewer-shell.tsx +@@ -120,8 +120,12 @@ export function ViewerShell() { + const [hash, setHash] = useState(""); + const envelope = parsed.ok ? parsed.envelope : null; + +- // Promote the inspector on error so the user sees it before the hero. +- const showInspector = hasFragment && !activeArtifact; ++ // The inspector must be visible whenever a fragment is present, and ++ // promoted above the hero when the decode fails — the error is the point. ++ const showInspector = ++ hasFragment && !activeArtifact && (parsedOk || parsed.code !== "empty"); ++ const hasFragmentError = ++ hasFragment && !parsedOk && parsed.code !== "empty"; + + return ( +
+diff --git a/src/lib/payload/fragment.ts b/src/lib/payload/fragment.ts +index a1b2c3d..e4f5g6h 100644 +--- a/src/lib/payload/fragment.ts ++++ b/src/lib/payload/fragment.ts +@@ -42,7 +42,6 @@ function parseEnvelope(raw) { + if (typeof raw !== "object" || raw === null) { + return { ok: false, code: "invalid-shape" }; + } +- console.debug("parsed envelope", raw); + return normalizeEnvelope(raw); + } +`; + +const CSV_CONTENT = `artifact,kind,wire size,decode time,codec +maintainer-kickoff,markdown,1842,4ms,arx3 +viewer-bootstrap,code,921,2ms,deflate +phase-1-diff,diff,1288,3ms,lz +data-export-preview,csv,654,1ms,plain +artifact-manifest,json,1104,2ms,arx4 +phase-2-diff,diff,2210,5ms,arx3 +release-checklist,markdown,1520,3ms,deflate +markdown-brief,markdown,1804,4ms,arx4 +code-fragment,code,733,2ms,plain +phase-3-diff,diff,1997,4ms,lz +`; + +const JSON_CONTENT = JSON.stringify( + { + schema: "agent-render/v1", + codec: "arx4", + fragmentBudget: 8192, + payloadBudget: 200000, + transport: { + mechanism: "url-fragment", + requestPath: false, + queryParams: false, + }, + renderers: [ + { kind: "markdown", surface: "sanitized-gfm", mimeTypes: ["text/markdown"] }, + { kind: "code", surface: "codemirror-readonly" }, + { kind: "diff", surface: "git-patch-review", modes: ["unified", "split"] }, + { kind: "csv", surface: "table-grid" }, + { kind: "json", surface: "tree+raw-fallback" }, + ], + privacy: { + zeroRetentionByHostDesign: true, + secretSafe: false, + linkLeakage: ["browser-history", "screenshots", "clipboard", "analytics-opt-in"], + }, + }, + null, + 2, +); + +const SAMPLES = [ + { + draft: { + kind: "markdown", + title: "Maintainer kickoff — phase notes", + filename: "phase-notes.md", + content: MARKDOWN_CONTENT, + language: "markdown", + diffView: "unified", + codec: "arx3", + }, + description: "Long-form markdown with GFM table, checklist, fence, mermaid.", + }, + { + draft: { + kind: "code", + title: "Viewer bootstrap hooks", + filename: "viewer-hooks.tsx", + content: CODE_CONTENT, + language: "tsx", + diffView: "unified", + codec: "deflate", + }, + description: "TypeScript hook file rendered in read-only CodeMirror.", + }, + { + draft: { + kind: "diff", + title: "Inspector error-promotion patch", + filename: "inspector-gate.patch", + content: DIFF_CONTENT, + language: "", + diffView: "unified", + codec: "lz", + }, + description: "Two-file unified git patch rendered as a review-style diff.", + }, + { + draft: { + kind: "csv", + title: "Codec size manifest", + filename: "codec-manifest.csv", + content: CSV_CONTENT, + language: "", + diffView: "unified", + codec: "plain", + }, + description: "CSV rendered as a sticky-header table.", + }, + { + draft: { + kind: "json", + title: "Envelope schema manifest", + filename: "schema.json", + content: JSON_CONTENT, + language: "", + diffView: "unified", + codec: "arx4", + }, + description: "Nested JSON rendered as a collapsible tree.", + }, +]; + +async function main() { + await mkdir(OUTPUT_DIR, { recursive: true }); + + const lines = [ + "# Sample fragment links", + "", + "Generated with `createGeneratedArtifactLink` from the app's own encoder.", + "", + `Preview base: \`${PREVIEW_BASE}\` (start the preview server before clicking).`, + "", + "| Kind | Title | Codec | Fragment | Link |", + "| --- | --- | --- | ---: | --- |", + ]; + + for (const sample of SAMPLES) { + const link = createGeneratedArtifactLink(sample.draft, PREVIEW_BASE); + lines.push( + `| ${sample.draft.kind} | ${sample.draft.title} | ${link.codec} | ${link.fragmentLength} ch | [open](${link.url}) |`, + ); + } + + lines.push(""); + lines.push("## Notes"); + lines.push(""); + for (const sample of SAMPLES) { + lines.push(`- **${sample.draft.title}** — ${sample.description}`); + } + lines.push(""); + + await writeFile(INDEX_PATH, lines.join("\n"), "utf8"); + console.log(`Wrote ${SAMPLES.length} sample links to ${INDEX_PATH}`); + + for (const sample of SAMPLES) { + const link = createGeneratedArtifactLink(sample.draft, PREVIEW_BASE); + process.stdout.write(` ${sample.draft.kind.padEnd(8)} ${link.fragmentLength}ch ${sample.draft.title}\n`); + } +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/src/app/bench.css b/src/app/bench.css new file mode 100644 index 0000000..b146b51 --- /dev/null +++ b/src/app/bench.css @@ -0,0 +1,321 @@ +/* ════════════════════════════════════════════════════════════════════════════ + bench.css — NEW grammar pieces only (lamp, readout, carbon strip, stamp, + perf). It defines NO :root/.dark tokens; the entire token spine (canonical + colors + shared keycap/recess depth recipe + --bench-* aliases) lives in + globals.css :root/.dark. The longstanding controls (.artifact-action, + .mono-pill, .creator-*) consume the same tokens — ONE spine, two sheets. + ════════════════════════════════════════════════════════════════════════════ */ + +/* D7 physical LED lamp — the ONLY glow in the system; radius-dot, tiny */ +.bench-lamp { + width: 8px; + height: 8px; + border-radius: 50%; + flex-shrink: 0; + background: var(--bench-green); + box-shadow: 0 0 6px color-mix(in srgb, var(--bench-green) 65%, transparent), + inset 0 -2px 2px rgba(0, 0, 0, 0.4); +} +.bench-lamp.is-amber { + background: var(--bench-amber); + box-shadow: 0 0 6px color-mix(in srgb, var(--bench-amber) 65%, transparent), + inset 0 -2px 2px rgba(0, 0, 0, 0.4); +} +.bench-lamp.is-red { + background: var(--bench-alert); + box-shadow: 0 0 6px color-mix(in srgb, var(--bench-alert) 65%, transparent), + inset 0 -2px 2px rgba(0, 0, 0, 0.4); +} + +/* D7/D3 VFD readout — amber mono on the dark content chassis, no halo */ +.bench-readout { + display: inline-flex; + align-items: center; + gap: 0.5em; + font-family: var(--font-mono), monospace; + font-size: 0.78rem; + letter-spacing: 0.04em; + color: var(--bench-amber); + background: var(--bench-body); + border: 1px solid var(--bench-body-line); + border-radius: 3px; + padding: 0.4rem 0.7rem; + box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.55); +} + +/* D1 carbon-copy strip — generated-link output only; the single light-blue band */ +.bench-carbon-bar { + border-top: 1px dashed var(--border-strong); + border-bottom: 1px dashed var(--border-strong); + padding: 0.5rem 0.95rem; + font-family: var(--font-mono), monospace; + font-size: 0.7rem; + letter-spacing: 0.05em; + color: var(--text-muted); + background: var(--bench-carbon-tint); +} +.bench-carbon-bar b { + color: var(--bench-carbon-red); + font-weight: 600; +} +.bench-stamp { + display: inline-block; + border: 2px solid var(--bench-carbon-red); + color: var(--bench-carbon-red); + background: transparent; + font-family: var(--font-mono), monospace; + font-size: 0.62rem; + font-weight: 700; + letter-spacing: 0.16em; + text-transform: uppercase; + padding: 0.35rem 0.6rem; + transform: rotate(-1.1deg); +} +.bench-perf { + border-top: 1px dashed var(--border-strong); +} + +/* Kind/segment chips: shallow bone keycaps, selected goes COMMIT orange */ +.bench-chiprow { + display: flex; + flex-wrap: wrap; + gap: 0.4rem; + padding: 0.85rem 1.25rem 0; +} +.bench-chip { + display: inline-flex; + align-items: center; + gap: 0.4em; + padding: 0.42rem 0.72rem; + border: 1px solid var(--bench-rule-strong); + border-radius: 3px; + background: linear-gradient(180deg, var(--bench-chrome-hi), var(--bench-chrome)); + box-shadow: var(--bench-key-lit), var(--bench-key-foot); + color: var(--bench-ink-soft); + font-family: var(--font-mono), monospace; + font-size: 0.68rem; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + cursor: pointer; +} +.bench-chip.is-on { + background: linear-gradient(180deg, var(--bench-orange), var(--bench-orange-strong)); + border-color: var(--bench-orange-strong); + color: #fff8ef; +} + +/* ══════════════════════════════════════════════════════════════════════════ + Structural grammar (C4 density board + home rig/section/links + hero). + These classes are consumed by the rebuilt components; tokens come from + globals :root/.dark only. + ══════════════════════════════════════════════════════════════════════════ */ + +/* C4 density board: 2px rule-strong outer border, hairline interior cells */ +/* bench-ink tracks the theme (charcoal on bone in light, ivory on charcoal in dark). */ +.bench-board, +.bench-board-head, +.bench-cell { + color: var(--bench-ink); +} +.bench-board { + border: 2px solid var(--bench-rule-strong); + background: linear-gradient(180deg, + var(--bench-chrome-hi) 0%, + var(--bench-chrome) 55%, + var(--bench-chrome-lo) 100%); + box-shadow: var(--bench-key-lit), var(--bench-key-foot); +} +.bench-board-head { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 1rem; + padding: 0.75rem 1.1rem; + background: linear-gradient(180deg, + var(--bench-chrome-hi) 0%, + var(--bench-chrome) 100%); + border-bottom: 1.5px solid var(--bench-rule-strong); +} +.bench-board-label { + font-family: var(--font-mono), monospace; + font-size: 0.66rem; + font-weight: 600; + letter-spacing: 0.18em; + text-transform: uppercase; + color: var(--bench-ink-soft); +} +.bench-board-head-tools { + display: inline-flex; + align-items: center; + gap: 0.6rem; +} +.bench-cell { + padding: 1rem 1.25rem; + border-right: 1px solid var(--bench-rule); +} +.bench-cell:last-child { + border-right: none; +} +.bench-grid { + display: grid; +} + +/* Engraved caps label (mono uppercase, small) */ +.bench-caps { + font-family: var(--font-mono), monospace; + font-size: 0.66rem; + font-weight: 600; + letter-spacing: 0.18em; + text-transform: uppercase; + color: var(--bench-ink-soft); +} + +/* Home rig: full-width creator + side rail columns, docked with hairline rule */ +.bench-rig { + display: grid; + grid-template-columns: 1fr; + gap: 1.25rem; +} +@media (min-width: 1024px) { + .bench-rig { + grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr); + gap: 1.5rem; + } +} +.bench-col-creator, +.bench-col-side { + min-width: 0; +} + +/* Bench section: a titled block with a board-head strip */ +.bench-section { + display: grid; + gap: 1rem; + align-content: start; + border: 2px solid var(--bench-rule-strong); + background: linear-gradient(180deg, + var(--bench-chrome-hi) 0%, + var(--bench-chrome) 55%, + var(--bench-chrome-lo) 100%); + box-shadow: var(--bench-key-lit), var(--bench-key-foot); +} +.bench-section-head { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 1rem; + padding: 0.75rem 1.1rem; + background: linear-gradient(180deg, + var(--bench-chrome-hi) 0%, + var(--bench-chrome) 100%); + border-bottom: 1.5px solid var(--bench-rule-strong); +} +.bench-section-title { + margin: 0; + font-family: var(--font-mono), monospace; + font-size: 0.66rem; + font-weight: 600; + letter-spacing: 0.18em; + text-transform: uppercase; + color: var(--bench-ink-soft); +} + +/* Ecosystem reference rows (C4 flat hairline table) */ +.bench-links { + display: grid; + gap: 0; +} +.bench-links-row { + display: grid; + grid-template-columns: 6.5rem 10rem 1fr; + gap: 1rem; + align-items: baseline; + padding: 0.7rem 0.95rem; + border-top: 1px solid var(--bench-rule); + color: inherit; +} +.bench-links-row:first-child { + border-top: none; +} +.bench-links-row:hover { + background: color-mix(in srgb, var(--accent) 5%, var(--bench-chrome)); +} +.bench-links-kicker { + font-family: var(--font-mono), monospace; + font-size: 0.64rem; + font-weight: 600; + letter-spacing: 0.16em; + text-transform: uppercase; + color: var(--bench-ink-faint); +} +.bench-links-title { + font-weight: 600; + color: var(--bench-ink); +} +.bench-links-desc { + margin: 0; + font-size: 0.8rem; + color: var(--bench-ink-soft); +} +@media (max-width: 640px) { + .bench-links-row { + grid-template-columns: 1fr; + gap: 0.15rem; + } +} + +/* Hero */ +.bench-hero { + display: grid; + gap: 0.75rem; + max-width: 64rem; +} +.bench-hero-headline { + margin: 0; + font-family: var(--font-display), sans-serif; + font-size: clamp(2.2rem, 4.8vw, 3.4rem); + font-weight: 700; + letter-spacing: -0.028em; + line-height: 1.02; + color: var(--bench-ink); + text-wrap: balance; +} +.dark .bench-hero-headline { + color: #ede7da; +} +.bench-hero-truth { + color: var(--bench-ink-soft); + line-height: 1.7; +} +.bench-hero-warning { + color: var(--bench-ink-soft); + line-height: 1.7; +} +.dark .bench-hero .bench-hero-truth, +.dark .bench-hero .bench-hero-warning { + color: #b0a695; +} +.bench-hero-trust { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin-top: 0.3rem; +} +.bench-hero-warning b, +.bench-hero-truth b { + color: var(--bench-ink); + font-weight: 600; +} +.dark .bench-hero .bench-hero-warning b, +.dark .bench-hero .bench-hero-truth b { + color: #e8e0d4; +} + +@media (prefers-reduced-motion: reduce) { + .artifact-action, + .artifact-action:active { + transition: none; + transform: none; + } +} diff --git a/src/app/globals.css b/src/app/globals.css index 4c3d819..5f5dd95 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,110 +1,171 @@ @import "tailwindcss"; +@import "./bench.css"; + +/* ═══════════════════════════════════════════════════════════════════════════ + agent-render globals — bench-instrument world + + This file keeps the pre-existing structural renderer/mode CSS (markdown + article, code surface, print, json tree, csv table, mermaid, diff chrome). + The BENCH design-system layer (./bench.css) is the source of truth for the + new blended C3×C4×D7×D1 grammar: bone chrome in light, dark placard chrome + in dark (QRH: charcoal panels, hairline etched rules, ivory ink), + dark-charcoal content chassis in both themes, one orange COMMIT action, one + carbon-copy tint on the generated-link strip, functional keycap/recess depth + only. + + Owner directives (established, do not relitigate): + - content (code/diff/csv/json bodies) = dark charcoal in BOTH themes + - chrome = bone in light, charcoal placard in dark + - no eyebrow/kicker above page headings; no self-describing chrome text + - real footer with identity + license + links, never a one-line stub + ═══════════════════════════════════════════════════════════════════════════ */ :root { - /* ── Surface hierarchy — warm ivory with sunset undertone ── */ - --page-bg: #faf8f4; - --surface: rgba(250, 248, 244, 0.88); - --surface-strong: rgba(255, 255, 255, 0.96); - --surface-muted: rgba(40, 30, 50, 0.03); - --surface-elevated: rgba(245, 241, 235, 0.86); - /* ── Ghost borders — warm taupe ── */ - --border: rgba(160, 140, 130, 0.14); - --border-strong: rgba(160, 140, 130, 0.24); - /* ── Text — warm charcoal ── */ - --text-primary: #2a2028; - --text-muted: #6b5e68; - --text-soft: #8a7e86; - /* ── Accent — sunset orange ── */ - --accent: #cc6633; - --accent-strong: #a8502a; - --accent-secondary: #8b5ca8; + /* ── Surfaces — bone keycap ── */ + --page-bg: #ebe7dd; + --surface: rgba(235, 231, 221, 0.92); + --surface-strong: rgba(243, 240, 233, 0.97); + --surface-muted: rgba(45, 40, 32, 0.045); + --surface-elevated: rgba(232, 228, 218, 0.88); + /* ── Hairline chassis borders — warm dark against bone ── */ + --border: rgba(62, 52, 40, 0.16); + --border-strong: rgba(62, 52, 40, 0.3); + /* ── Text — engraved charcoal ── */ + --text-primary: #23201a; + --text-muted: #574e42; + --text-soft: #7d7466; + /* ── Accent — single action orange; teal-mint secondary ── */ + --accent: #b74d18; + --accent-strong: #93400f; + --accent-secondary: #2e6358; /* ── Semantic ── */ - --success: #4a8c6f; - --warning: #b8863a; - --danger: #c44b3f; - /* ── Ambient shadows — warm-tinted ── */ - --shadow-lg: 0 26px 72px rgba(42, 32, 40, 0.07); - --shadow-md: 0 20px 40px rgba(42, 32, 40, 0.035); - /* ── Radius — sharp, architectural ── */ + --success: #3a7d5c; + --warning: #a06a10; + --danger: #b03828; + /* ── Radius — sharp machined edges ── */ --radius-xl: 2px; --radius-lg: 2px; --radius-md: 2px; --radius-sm: 2px; - --duration-base: 280ms; - /* ── Code surfaces (light default) ── */ - --surface-code: #f8f6fa; - --surface-code-raised: rgba(240, 236, 244, 0.95); - --surface-code-text: #1e1a22; - --surface-code-chrome-border: color-mix(in srgb, var(--surface-code-text) 12%, transparent); + --duration-base: 220ms; + /* ── Code surfaces — dark charcoal chassis even in light mode (QRH card on bone) ── */ + --surface-code: #1c1915; + --surface-code-raised: #23201b; + --surface-code-text: #e6e0d4; + --surface-code-chrome-border: color-mix(in srgb, var(--surface-code-text) 14%, transparent); --surface-code-toolbar-shine: color-mix(in srgb, var(--surface-code-text) 5%, transparent); - --surface-code-gutter-fg: color-mix(in srgb, var(--surface-code-text) 42%, transparent); + --surface-code-gutter-fg: color-mix(in srgb, var(--surface-code-text) 44%, transparent); --surface-code-gutter-border: color-mix(in srgb, var(--surface-code-text) 10%, transparent); --surface-code-active-line: color-mix(in srgb, var(--surface-code-text) 7%, transparent); --surface-code-active-gutter: color-mix(in srgb, var(--surface-code-text) 9%, transparent); - /* ── Code toolbar “is-code” button (mirrors light code chrome) ── */ --surface-code-is-code-border: var(--surface-code-chrome-border); --surface-code-is-code-bg: color-mix(in srgb, var(--surface-code-text) 6%, transparent); --surface-code-is-code-fg: var(--surface-code-text); - --surface-code-selection-bg: rgba(105, 209, 221, 0.18); - /* ── CodeMirror rainbow brackets (saturated for light code bg) ── */ - --rb-0: #c06820; - --rb-1: #b08828; - --rb-2: #2a8e9a; - --rb-3: #3a8a55; - --rb-4: #4466cc; - --rb-5: #8844bb; + --surface-code-selection-bg: rgba(183, 77, 24, 0.28); + /* ════ Fused bench world — canonical aliases (single spine) ════ */ + --bench-chrome: #f5f2ea; + --bench-chrome-hi: #fbf9f3; + --bench-chrome-lo: #e7e2d3; + --bench-panel: #ece7db; + --bench-ink: var(--text-primary); + --bench-ink-soft: var(--text-muted); + --bench-ink-faint: var(--text-soft); + --bench-rule: var(--border-strong); + --bench-rule-strong: #26241f; + --bench-body: var(--surface-code); + --bench-body-hi: var(--surface-code-raised); + --bench-body-line: var(--surface-code-chrome-border); + --bench-body-ink: var(--surface-code-text); + --bench-body-muted: var(--surface-code-gutter-fg); + --bench-orange: var(--accent); + --bench-orange-strong: var(--accent-strong); + --bench-amber: #ffb24c; + --bench-green: #5bd99a; + --bench-carbon-red: #b0392c; + --bench-alert: var(--danger); + --bench-carbon-tint: #eef4f6; + /* Shared keycap/recess depth recipe (consumed by .artifact-action, + .mono-pill, .creator-* and bench.css grammar). */ + --bench-key-lit: inset 0 1px 0 rgba(255, 255, 255, 0.6); + --bench-key-foot: inset 0 -2px 0 rgba(45, 40, 32, 0.12); + --bench-key-shadow: 0 2px 0 0 var(--bench-rule-strong); + --bench-recess: inset 0 2px 5px rgba(45, 40, 32, 0.13), + inset 0 -1px 0 rgba(255, 255, 255, 0.55); + --bench-dur: 150ms; + /* ── CodeMirror rainbow brackets (saturated for dark chassis) ── */ + --rb-0: #f08d5e; + --rb-1: #e6b45e; + --rb-2: #69d1dd; + --rb-3: #80c193; + --rb-4: #9eb3ff; + --rb-5: #d799ff; /* ── Print ── */ --print-paper: #ffffff; - --print-ink: #2a2028; - --print-muted: #6b5e68; - --print-border: rgba(160, 140, 130, 0.14); + --print-ink: #23201a; + --print-muted: #574e42; + --print-border: rgba(62, 52, 40, 0.18); } .dark { - /* ── Surface hierarchy — deep dusk ── */ - --page-bg: #0e0b14; - --surface: rgba(18, 14, 24, 0.92); - --surface-strong: rgba(14, 11, 20, 0.98); - --surface-muted: rgba(180, 160, 200, 0.05); - --surface-elevated: rgba(24, 20, 32, 0.96); - /* ── Ghost borders — twilight violet ── */ - --border: rgba(100, 80, 120, 0.16); - --border-strong: rgba(100, 80, 120, 0.28); - /* ── Text — warm pearl ── */ - --text-primary: #ece6f0; - --text-muted: #b8afc2; - --text-soft: #8a7e96; - /* ── Accent — sunset ember ── */ - --accent: #e08c50; - --accent-strong: #c87a42; - --accent-secondary: #b08ed0; - /* ── Semantic ── */ - --success: #78c5a8; - --warning: #deb06a; - --danger: #e8a098; - /* ── Shadows — purple-black ── */ - --shadow-lg: 0 28px 72px rgba(14, 11, 20, 0.5); - --shadow-md: 0 20px 40px rgba(14, 11, 20, 0.25); - /* ── Radius — zero, architectural ── */ - --radius-xl: 0px; - --radius-lg: 0px; - --radius-md: 0px; - --radius-sm: 0px; - /* ── Code ── */ - --surface-code: rgba(10, 8, 16, 0.98); - --surface-code-raised: rgba(18, 14, 26, 0.95); - --surface-code-text: #ece6f0; - --surface-code-chrome-border: rgba(239, 243, 247, 0.08); + /* ── Placard dark: chrome drops to charcoal like the page ground, ivory + ink, hairline etched rules. Content chassis stays charcoal. Light mode + keeps the bone keycap chrome. ── */ + --page-bg: #14110d; + --surface-code: #0f0d0a; + --surface-code-raised: #161310; + --surface-code-text: #ede7da; + --surface-code-chrome-border: rgba(237, 231, 218, 0.1); --surface-code-toolbar-shine: rgba(255, 255, 255, 0.03); - --surface-code-gutter-fg: rgba(239, 243, 247, 0.5); - --surface-code-gutter-border: rgba(239, 243, 247, 0.08); - --surface-code-active-line: rgba(255, 255, 255, 0.045); + --surface-code-gutter-fg: rgba(237, 231, 218, 0.5); + --surface-code-gutter-border: rgba(237, 231, 218, 0.1); + --surface-code-active-line: rgba(255, 255, 255, 0.05); --surface-code-active-gutter: rgba(255, 255, 255, 0.05); - --surface-code-is-code-border: rgba(239, 243, 247, 0.12); + --surface-code-is-code-border: rgba(237, 231, 218, 0.14); --surface-code-is-code-bg: rgba(255, 255, 255, 0.04); - --surface-code-is-code-fg: rgba(239, 243, 247, 0.86); - --surface-code-selection-bg: rgba(105, 209, 221, 0.18); - /* ── CodeMirror rainbow brackets (bright for dark code bg) ── */ + --surface-code-is-code-fg: rgba(237, 231, 218, 0.86); + --surface-code-selection-bg: rgba(217, 123, 61, 0.3); + --surface: rgba(38, 34, 29, 0.95); + --surface-strong: rgba(46, 41, 35, 0.97); + --surface-muted: rgba(237, 231, 218, 0.05); + --surface-elevated: rgba(52, 47, 40, 0.96); + --border: rgba(237, 231, 218, 0.12); + --border-strong: rgba(237, 231, 218, 0.22); + --text-primary: #ece4d6; + --text-muted: #b0a694; + --text-soft: #84796a; + --accent: #cf7a1c; + --accent-strong: #b06010; + --accent-secondary: #34806c; + --success: #50a877; + --warning: #b8840e; + --danger: #d4492c; + --bench-key-lit: inset 0 1px 0 rgba(237, 231, 218, 0.09); + --bench-key-foot: inset 0 -2px 0 rgba(0, 0, 0, 0.4); + --bench-key-shadow: 0 3px 0 0 rgba(0, 0, 0, 0.35); + --bench-recess: inset 0 2px 5px rgba(0, 0, 0, 0.45), + inset 0 -1px 0 rgba(237, 231, 218, 0.07); + --bench-dur: 150ms; + --bench-chrome: #221e19; + --bench-chrome-hi: #2c2721; + --bench-chrome-lo: #1a1713; + --bench-panel: var(--bench-chrome-lo); + --bench-ink: #ece4d6; + --bench-ink-soft: #b0a694; + --bench-ink-faint: #84796a; + --bench-rule: rgba(237, 231, 218, 0.12); + --bench-rule-strong: rgba(237, 231, 218, 0.3); + --bench-body: var(--surface-code); + --bench-body-hi: var(--surface-code-raised); + --bench-body-line: var(--surface-code-chrome-border); + --bench-body-ink: var(--surface-code-text); + --bench-body-muted: var(--surface-code-gutter-fg); + --bench-orange: var(--accent); + --bench-orange-strong: var(--accent-strong); + --bench-amber: #ddad47; + --bench-green: #4a9d78; + --bench-carbon-red: #c9442c; + --bench-alert: var(--danger); + --bench-carbon-tint: #2a3033; --rb-0: #f08d5e; --rb-1: #efb360; --rb-2: #69d1dd; @@ -126,22 +187,9 @@ body { margin: 0; color: var(--text-primary); font-family: var(--font-sans), sans-serif; - background: - radial-gradient(ellipse 60% 45% at 0% 0%, #f1e0d2, transparent 70%), - radial-gradient(ellipse 45% 45% at 100% 5%, #e6dee8, transparent 70%), - radial-gradient(ellipse 40% 35% at 65% 100%, #ede2d6, transparent 70%), - var(--page-bg); -} - -.dark body { - background: - radial-gradient(ellipse 70% 50% at 0% 0%, #2a1a10, transparent 70%), - radial-gradient(ellipse 60% 60% at 100% 20%, #1a142a, transparent 70%), - radial-gradient(ellipse 50% 40% at 50% 100%, #201018, transparent 70%), - var(--page-bg); + background: var(--page-bg); } - a { color: inherit; text-decoration: none; @@ -154,16 +202,39 @@ select { font: inherit; } +button { + cursor: pointer; +} + ::selection { background: color-mix(in srgb, var(--accent) 36%, transparent); color: var(--text-primary); } .dark ::selection { - background: rgba(224, 140, 80, 0.30); + background: rgba(224, 140, 80, 0.3); color: #ece6f0; } +/* Page-ground ink (body, nav wordmark/links, footer, hero prose outside boards) + flips to ivory on the charcoal chassis in dark mode; bone chrome and markdown + keep their carbon ink via their own rules. */ +.dark body { + color: #ede7da; +} +.dark .nav-wordmark, +.dark .nav-text-link { + color: var(--bench-body-ink); +} +.dark .nav-text-link:hover { + color: var(--bench-body-ink); +} +.dark .site-footer, +.dark .site-footer-wordmark, +.dark .site-footer-link { + color: var(--bench-body-muted); +} + .font-display { font-family: var(--font-display), serif; } @@ -172,7 +243,20 @@ select { font-family: var(--font-mono), monospace; } -/* ── Custom scrollbars ── */ +/* Accessibility aid: visible to AT, removed from the visual canvas. */ +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0 0 0 0); + white-space: nowrap; + border: 0; +} + +/* ── Custom scrollbars — thin chassis sliders ── */ ::-webkit-scrollbar { width: 4px; height: 4px; @@ -183,7 +267,7 @@ select { } ::-webkit-scrollbar-thumb { - background: var(--surface-elevated); + background: var(--border-strong); border-radius: 2px; } @@ -191,422 +275,528 @@ select { background: var(--accent); } -/* ── Nav bar — flat, full-width, not a card ── */ -.nav-bar { - background: var(--page-bg); - border-bottom: 1px solid var(--border); -} - -.nav-text-link { - color: var(--text-muted); - font-size: 0.9rem; - font-weight: 600; - line-height: 1.4; - transition: color 150ms ease; -} - -.nav-text-link:hover { - color: var(--text-primary); -} +/* ══════════════════════════════════════════════════════════════════════════ + Typography primitives + ══════════════════════════════════════════════════════════════════════════ */ -.nav-text-link:focus-visible { - outline: 2px solid color-mix(in srgb, var(--accent) 48%, transparent); - outline-offset: 4px; +.section-kicker { + color: var(--text-soft); + font-family: var(--font-mono), monospace; + font-size: 0.72rem; + font-weight: 500; + letter-spacing: 0.1em; + text-transform: uppercase; } -/* ── Full-bleed editorial sections ── */ -.home-hero-section { - padding-top: 2rem; - padding-bottom: 2rem; - border-bottom: 1px solid var(--border); +.metric-label { + color: var(--bench-ink-faint); + font-family: var(--font-mono), monospace; + font-size: 0.72rem; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; } -@media (min-width: 768px) { - .home-hero-section { - padding-top: 3rem; - padding-bottom: 3rem; - } +.metric-value { + margin-top: 0.45rem; + font-size: 1.1rem; + font-weight: 600; + line-height: 1.3; + color: var(--bench-ink); } -@media (min-width: 1280px) { - .home-hero-section { - padding-top: 4rem; - padding-bottom: 4rem; - } +.metric-value.bench-readout { + color: var(--bench-amber); } -.section-header { +/* Inline stat pairs — mono label · value runs separated by hairlines. */ +.stat-row { display: flex; flex-wrap: wrap; - align-items: flex-start; - justify-content: space-between; - gap: 1rem; -} - -.home-samples-section, -.home-inspector-section, -.home-stage-section { - padding-top: 2.5rem; - padding-bottom: 2.5rem; - border-bottom: 1px solid var(--border); + align-items: baseline; + gap: 0.2rem 0; + margin: 0; + color: var(--bench-ink-faint); + font-family: var(--font-mono), monospace; + font-size: 0.7rem; + font-weight: 500; + letter-spacing: 0.08em; + text-transform: uppercase; } -@media (min-width: 768px) { - .home-samples-section, - .home-inspector-section, - .home-stage-section { - padding-top: 4rem; - padding-bottom: 4rem; - } +.stat-row .stat-item { + display: inline-flex; + align-items: baseline; + gap: 0.45rem; + white-space: nowrap; } -.site-footer { - display: flex; - flex-wrap: wrap; - align-items: center; - justify-content: space-between; - gap: 1rem; - border-top: 1px solid var(--border); - padding-top: 1.25rem; - color: var(--text-soft); - font-family: var(--font-mono), monospace; - font-size: 0.78rem; +.stat-row .stat-item + .stat-item::before { + content: ""; + display: inline-block; + width: 1px; + height: 0.7em; + margin: 0 0.85rem; + background: var(--border-strong); + transform: translateY(0.08em); } -.site-footer a { - color: var(--text-muted); +.stat-row .stat-value { + color: var(--bench-ink-soft); + font-weight: 600; } -.site-footer a:hover { - color: var(--text-primary); -} +/* ══════════════════════════════════════════════════════════════════════════ + Component grammar — keycaps, pills, panels, recessed slots + ══════════════════════════════════════════════════════════════════════════ */ -/* ── Bento grid — tonal separation via 1px gaps ── */ -.bento-grid { - display: grid; - grid-template-columns: 1fr; - gap: 1px; - background: var(--border); - border: 1px solid var(--border); - border-radius: var(--radius-xl); - overflow: hidden; +/* Keycap button: bone surface with real keycap depth — a lit top edge and a + solid 2px foot that collapses down on :active. Recipe comes from the + shared --bench-key-* tokens (globals :root/.dark); this ruleset consumes + them, bench.css grammar consumes them, ONE spine. */ +.artifact-action { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.45rem; + min-height: 2.7rem; + padding: 0.58rem 0.82rem; + border: 1px solid var(--border-strong); + border-radius: var(--radius-sm); + background: linear-gradient(180deg, var(--surface-strong) 0%, var(--surface) 100%); + box-shadow: var(--bench-key-lit), var(--bench-key-foot), var(--bench-key-shadow); + color: var(--bench-ink); + font-family: var(--font-mono), monospace; + font-size: 0.74rem; + font-weight: 500; + letter-spacing: 0.04em; + text-transform: uppercase; + transition: + border-color var(--bench-dur) ease, + background-color var(--bench-dur) ease, + box-shadow var(--bench-dur) ease, + transform var(--bench-dur) ease; } -@media (min-width: 768px) { - .bento-grid { - grid-template-columns: repeat(2, 1fr); - } +.dark .artifact-action { + background: linear-gradient(180deg, var(--surface-elevated) 0%, var(--surface) 100%); } -@media (min-width: 1280px) { - .bento-grid { - grid-template-columns: repeat(3, 1fr); - } - - .bento-wide { - grid-column: span 2; - } +.artifact-action:hover { + border-color: var(--accent); + background: color-mix(in srgb, var(--accent) 5%, var(--surface)); } -.bento-grid-compact { - grid-template-columns: repeat(2, 1fr); +.artifact-action:active { + transform: translateY(2px); + box-shadow: var(--bench-key-lit), + inset 0 -1px 0 rgba(45, 40, 32, 0.06), + 0 0 0 0 var(--border-strong); } -.bento-card { - background: var(--page-bg); +.dark .artifact-action:active { + box-shadow: var(--bench-key-lit), + inset 0 -1px 0 rgba(0, 0, 0, 0.3), + 0 0 0 0 rgba(0, 0, 0, 0.6); } -.dark .bento-card { - background: rgba(14, 11, 20, 0.98); +.artifact-action:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; } -.bento-link { - display: grid; - gap: 0.5rem; - align-content: start; - transition: background-color 500ms ease; +.artifact-action.is-primary { + border-color: var(--accent-strong); + background: linear-gradient(180deg, var(--accent) 0%, var(--accent-strong) 100%); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.28), + inset 0 -2px 0 rgba(0, 0, 0, 0.24), + 0 2px 0 0 var(--accent-strong); + color: #fff7ee; } -.bento-link:hover { - background: color-mix(in srgb, var(--accent) 3%, var(--page-bg)); +.dark .artifact-action.is-primary { + color: #1a0e08; } -.dark .bento-link:hover { - background: rgba(224, 140, 80, 0.04); +.artifact-action.is-primary:hover { + border-color: var(--accent-strong); + background: var(--accent-strong); } -.panel { - position: relative; - border: 1px solid var(--border); - border-radius: var(--radius-xl); - background: var(--surface-strong); - overflow: hidden; +.artifact-action.is-primary:active { + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.2), + inset 0 -1px 0 rgba(0, 0, 0, 0.16), + 0 0 0 0 var(--accent-strong); } -.section-kicker { - color: var(--text-soft); - font-family: var(--font-mono), monospace; - font-size: 0.75rem; - font-weight: 500; - letter-spacing: 0.02em; +.artifact-action.is-code { + border-color: var(--surface-code-is-code-border); + background: var(--surface-code-is-code-bg); + color: var(--surface-code-is-code-fg); } +/* Mono pill: bone tag, shallower keycap depth than the action buttons. */ .mono-pill { display: inline-flex; align-items: center; gap: 0.45rem; padding: 0.46rem 0.78rem; - border: 1px solid var(--border); + border: 1px solid var(--border-strong); border-radius: var(--radius-sm); - background: color-mix(in srgb, var(--surface-elevated) 86%, transparent); - color: var(--text-muted); + background: linear-gradient(180deg, var(--surface-strong) 0%, var(--surface) 100%); + box-shadow: var(--bench-key-lit), var(--bench-key-foot); + color: var(--bench-ink); font-family: var(--font-mono), monospace; - font-size: 0.75rem; - font-weight: 600; - letter-spacing: 0.01em; + font-size: 0.74rem; + font-weight: 500; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +.dark .mono-pill { + background: linear-gradient(180deg, #5a5046 0%, #44403a 100%); + color: #e8e0d4; } .shell-pill { min-height: 2.25rem; - border-color: var(--border-strong); - color: var(--text-primary); - background: color-mix(in srgb, var(--surface-strong) 88%, transparent); } -/* Dark data chips */ -.dark .mono-pill { - background: rgba(24, 20, 32, 0.8); - border-color: rgba(100, 80, 120, 0.22); +/* Recessed input slots: inset top shadow + bottom lit edge make the input sit + *below* the chassis, like a machined slot. Focus raises the rim to accent. */ +.creator-input, +.creator-textarea, +.creator-link-output { + width: 100%; + border: 1px solid var(--border-strong); + border-radius: var(--radius-sm); + background: var(--surface); + box-shadow: var(--bench-recess); + color: var(--bench-ink); + transition: + border-color var(--bench-dur) ease, + box-shadow var(--bench-dur) ease; } -.metric-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); - gap: 0.75rem; +.creator-input, +.creator-textarea { + padding: 0.78rem 0.85rem; } -.metric-card { - border: 1px solid var(--border); - border-radius: var(--radius-lg); - background: color-mix(in srgb, var(--surface-elevated) 90%, transparent); - padding: 0.9rem 0.95rem; +.creator-input { + min-height: 2.9rem; } -.empty-state-layout { - display: grid; - gap: 0; +.creator-textarea { + min-height: 15rem; + resize: vertical; + font-family: var(--font-mono), monospace; + font-size: 0.84rem; + line-height: 1.65; } -.sample-link-grid { - display: grid; - gap: 0.75rem; +.creator-input::placeholder, +.creator-textarea::placeholder { + color: var(--bench-ink-faint); } -.hero-link-card { - display: grid; - gap: 0.6rem; - min-height: 100%; - padding: 1rem 1.05rem; +.creator-input:focus, +.creator-textarea:focus { + border-color: var(--accent); + outline: 1px solid var(--accent); + outline-offset: 1px; + box-shadow: var(--bench-recess); +} + +.creator-link-output { + min-height: 6.5rem; + padding: 0.75rem 0.85rem; + resize: none; + background: var(--surface-code); + color: var(--surface-code-text); + border-color: var(--surface-code-chrome-border); + box-shadow: + inset 0 3px 8px rgba(0, 0, 0, 0.45), + inset 0 -1px 0 rgba(255, 255, 255, 0.04); + font-family: var(--font-mono), monospace; + font-size: 0.76rem; + line-height: 1.65; + word-break: break-all; +} + +.creator-link-output:focus { + outline: 1px solid var(--accent); + outline-offset: 1px; +} + +/* Flat etched panel. */ +.panel { border: 1px solid var(--border); - border-radius: var(--radius-lg); - background: color-mix(in srgb, var(--surface-elevated) 80%, transparent); - transition: - border-color 150ms ease, - background-color 150ms ease; + border-radius: var(--radius-xl); + background: var(--surface-strong); + overflow: hidden; } -.hero-link-card:hover { - border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); - background: color-mix(in srgb, var(--accent) 3%, var(--surface-elevated)); +/* Budget meter — machined slot with a solid accent fill. */ +.budget-track { + height: 4px; + border-radius: 0; + background: color-mix(in srgb, var(--text-soft) 22%, transparent); + overflow: hidden; } -.hero-link-card.is-static:hover { - transform: none; - border-color: var(--border); - box-shadow: none; +.budget-fill { + display: block; + height: 100%; + border-radius: 0; + background: var(--accent); + transform-origin: left center; + transition: transform var(--duration-base) ease; } -.hero-link-card:focus-visible { - outline: 2px solid color-mix(in srgb, var(--accent) 48%, transparent); - outline-offset: 2px; +/* ══════════════════════════════════════════════════════════════════════════ + Nav bar — slim sticky strip + ══════════════════════════════════════════════════════════════════════════ */ + +.nav-bar { + position: relative; + background: color-mix(in srgb, var(--page-bg) 96%, transparent); + border-bottom: 1px solid var(--border-strong); } -.hero-link-eyebrow { - color: var(--accent); - font-family: var(--font-mono), monospace; - font-size: 0.75rem; - font-weight: 500; - letter-spacing: 0.02em; +.nav-bar::after { + content: ""; + position: absolute; + left: 0; + right: 0; + bottom: -2px; + height: 1px; + background: var(--accent); + opacity: 0.55; + pointer-events: none; } -.hero-link-title { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 0.75rem; +.nav-wordmark { color: var(--text-primary); + font-family: var(--font-mono), monospace; font-size: 1rem; font-weight: 600; - line-height: 1.45; + letter-spacing: 0.08em; + text-transform: lowercase; } -.hero-link-body { - margin: 0; +.nav-text-link { color: var(--text-muted); - font-size: 0.92rem; - line-height: 1.7; -} + font-size: 0.9rem; + font-weight: 600; + line-height: 1.4; + transition: color 150ms ease; +} -.metric-label { +.nav-text-link:hover { + color: var(--text-primary); +} + +.nav-text-link:focus-visible { + outline: 2px solid color-mix(in srgb, var(--accent) 48%, transparent); + outline-offset: 4px; +} + +/* ══════════════════════════════════════════════════════════════════════════ + Footer — full-width, hairline-etched + ══════════════════════════════════════════════════════════════════════════ */ + +.site-footer { + display: flex; + flex-wrap: wrap; + align-items: baseline; + justify-content: space-between; + gap: 0.75rem 2rem; + border-top: 1px solid var(--border-strong); + padding-top: 1.1rem; + padding-bottom: 0.25rem; color: var(--text-soft); font-family: var(--font-mono), monospace; font-size: 0.72rem; - font-weight: 600; - letter-spacing: 0.08em; + letter-spacing: 0.04em; text-transform: uppercase; } -.metric-value { - margin-top: 0.45rem; - font-size: 1.1rem; +.site-footer-wordmark { + color: var(--text-primary); font-weight: 600; - line-height: 1.3; + letter-spacing: 0.08em; } -.budget-track { - height: 3px; - border-radius: 0; - background: color-mix(in srgb, var(--surface-muted) 78%, transparent); - overflow: hidden; +.site-footer-nav { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 0.5rem 1.4rem; } -.budget-fill { - height: 100%; - border-radius: 0; - background: linear-gradient(90deg, var(--accent), var(--accent-strong)); - transition: width var(--duration-base) ease; +.site-footer-link { + position: relative; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.06em; + transition: color 150ms ease; } -.sample-link { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 1rem; - min-height: 100%; - border: 1px solid var(--border); - border-radius: var(--radius-lg); - padding: 1.25rem 1.5rem; - background: color-mix(in srgb, var(--surface-elevated) 88%, transparent); - transition: - transform var(--duration-base) ease, - border-color var(--duration-base) ease, - background-color var(--duration-base) ease, - box-shadow var(--duration-base) ease; +.site-footer-link::after { + content: ""; + position: absolute; + left: 0; + right: 0; + bottom: -3px; + height: 1px; + background: var(--accent); + transform: scaleX(0); + transform-origin: left center; + transition: transform 180ms ease; } -.sample-link:hover { - border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); - background: color-mix(in srgb, var(--accent) 3%, var(--surface-elevated)); +.site-footer-link:hover, +.site-footer-link:focus-visible { + color: var(--text-primary); } -.sample-link.is-active { - border-color: color-mix(in srgb, var(--accent) 50%, var(--border)); - background: color-mix(in srgb, var(--accent) 5%, var(--surface-strong)); +.site-footer-link:hover::after, +.site-footer-link:focus-visible::after { + transform: scaleX(1); } -.home-generator-section { - padding-top: 2.5rem; - padding-bottom: 2.5rem; - border-bottom: 1px solid var(--border); +.site-footer-link:focus-visible { + outline: 1px solid var(--accent); + outline-offset: 4px; +} + +/* ══════════════════════════════════════════════════════════════════════════ + Empty state — 12-column instrument rig + ══════════════════════════════════════════════════════════════════════════ */ + +.empty-state-layout { + display: grid; + gap: 1.75rem; } @media (min-width: 768px) { - .home-generator-section { - padding-top: 4rem; - padding-bottom: 4rem; + .empty-state-layout { + gap: 2.25rem; } } -.home-generator-grid, -.creator-form-grid, -.creator-result-metrics, -.creator-result-actions { +.bench-rig { display: grid; - gap: 0.8rem; + grid-template-columns: repeat(12, minmax(0, 1fr)); + gap: 1.75rem; + align-items: start; } -.home-generator-grid { - gap: 1.5rem; +.bench-col-creator { + grid-column: 1 / -1; + min-width: 0; } -.home-generator-heading { +.bench-col-side { + grid-column: 1 / -1; display: grid; - gap: 0.75rem; + gap: 1.75rem; + min-width: 0; } +@media (min-width: 1024px) { + .bench-rig { + gap: 2.25rem; + } + + .bench-col-creator { + grid-column: span 5; + } + + .bench-col-side { + grid-column: span 7; + gap: 2.25rem; + } +} + +.home-generator-section, +.home-samples-section, +.home-inspector-section, +.home-error-inspector-section { + min-width: 0; +} + +/* ── Creator form ── */ .creator-kind-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(7.4rem, 1fr)); - gap: 0.65rem; - margin-top: 1rem; + grid-template-columns: repeat(auto-fit, minmax(6.6rem, 1fr)); + gap: 0.5rem; } .creator-kind-card { display: flex; align-items: center; - gap: 0.7rem; - padding: 0.82rem 0.88rem; - border: 1px solid var(--border); - border-radius: var(--radius-lg); - background: color-mix(in srgb, var(--surface-elevated) 90%, transparent); - color: var(--text-primary); + gap: 0.55rem; + padding: 0.6rem 0.65rem; + border: 1px solid var(--border-strong); + border-radius: var(--radius-sm); + background: var(--surface); + color: var(--bench-ink-soft); + font-family: var(--font-mono), monospace; + font-size: 0.74rem; + font-weight: 500; + letter-spacing: 0.04em; + text-transform: uppercase; transition: border-color 150ms ease, background-color 150ms ease; } .creator-kind-card:hover { - border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); - background: color-mix(in srgb, var(--accent) 3%, var(--surface-elevated)); + border-color: var(--accent); + background: color-mix(in srgb, var(--accent) 5%, var(--surface)); } .creator-kind-card.is-active { - border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); - background: color-mix(in srgb, var(--accent) 6%, var(--surface-strong)); + border-color: var(--accent); + background: color-mix(in srgb, var(--accent) 7%, var(--surface)); + box-shadow: inset 0 0 0 1px var(--accent); + color: var(--bench-ink); } -.creator-kind-card:focus-visible, -.creator-input:focus-visible, -.creator-textarea:focus-visible { - outline: 2px solid color-mix(in srgb, var(--accent) 44%, transparent); +.creator-kind-card:focus-visible { + outline: 2px solid var(--accent); outline-offset: 2px; } .creator-kind-icon { display: grid; place-items: center; - width: 2.2rem; - height: 2.2rem; - border: 1px solid var(--border); - border-radius: var(--radius-md); - background: color-mix(in srgb, var(--surface-muted) 90%, transparent); color: var(--accent); } .creator-kind-label { - font-size: 0.92rem; - font-weight: 600; - line-height: 1.35; - text-transform: capitalize; + line-height: 1.3; } .creator-form-grid { - margin-top: 1rem; + display: grid; + gap: 0.8rem; +} + +@media (min-width: 640px) { + .creator-form-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } } .creator-field { display: grid; - gap: 0.45rem; + gap: 0.4rem; + min-width: 0; } .creator-field-full { @@ -618,199 +808,358 @@ select { flex-wrap: wrap; align-items: baseline; justify-content: space-between; - gap: 0.5rem 1rem; + gap: 0.25rem 0.75rem; } .creator-field-hint { - color: var(--text-muted); - font-size: 0.86rem; - line-height: 1.5; + color: var(--bench-ink-faint); + font-size: 0.78rem; + line-height: 1.45; } -.creator-input, -.creator-textarea, -.creator-link-output { - width: 100%; - border: none; - border-bottom: 2px solid transparent; - border-radius: var(--radius-lg); - background: color-mix(in srgb, var(--surface-strong) 94%, transparent); - color: var(--text-primary); - transition: border-color var(--duration-base) ease; +.creator-form-footer { + grid-column: 1 / -1; + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.75rem; + padding: 0.85rem 1.1rem 0.85rem 1.1rem; } -.creator-input:focus-visible, -.creator-textarea:focus-visible { - border-bottom-color: var(--accent); +.creator-codec-row { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.35rem; } -.creator-input, -.creator-textarea { - padding: 0.88rem 0.95rem; +.creator-codec-row .artifact-action { + min-height: 2.1rem; + padding: 0.34rem 0.6rem; + font-size: 0.68rem; } -.creator-input::placeholder, -.creator-textarea::placeholder { - color: var(--text-soft); +/* ── Creator result — sibling bench panel, not a framed card-in-card ── */ +.creator-result-shell { + display: grid; + gap: 0.8rem; + align-content: start; + min-width: 0; } -.creator-input { - min-height: 3rem; +.creator-result-actions { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; } -.creator-textarea { - min-height: 17rem; - resize: vertical; - font-family: var(--font-mono), monospace; - font-size: 0.86rem; - line-height: 1.65; +.creator-result-actions .artifact-action { + flex: 1 1 auto; } -.creator-form-footer { +.creator-empty-state { + border: 1px dashed var(--border-strong); + border-radius: var(--radius-sm); + padding: 1.5rem 1rem; + text-align: center; + color: var(--bench-ink-faint); + font-size: 0.85rem; + line-height: 1.5; +} + +.creator-error-state { + border: 1px solid color-mix(in srgb, var(--danger) 45%, var(--border)); + border-radius: var(--radius-sm); + background: color-mix(in srgb, var(--danger) 7%, var(--surface)); + padding: 0.8rem 0.9rem; + color: var(--danger); + font-size: 0.88rem; + line-height: 1.55; +} + +.creator-warning-state { + border: 1px solid color-mix(in srgb, var(--warning) 40%, var(--border)); + border-radius: var(--radius-sm); + background: color-mix(in srgb, var(--warning) 8%, var(--surface)); + padding: 0.8rem 0.9rem; + color: var(--warning); + font-size: 0.85rem; + line-height: 1.55; +} + +.creator-inline-status { + color: var(--warning); + font-size: 0.8rem; + line-height: 1.5; +} + +.creator-link-field { + display: grid; + gap: 0.4rem; +} + +/* ── Samples — vertical list of rows ── */ +.sample-list { + display: grid; + gap: 0; + overflow: hidden; +} + +.sample-link { display: flex; - flex-wrap: wrap; align-items: center; - justify-content: space-between; gap: 0.85rem; - padding-top: 0.2rem; + padding: 0.85rem 1.25rem; + background: var(--surface); + border-bottom: 1px solid var(--border); + transition: + background-color var(--duration-base) ease; } -.creator-codec-row { +.sample-link:last-child { + border-bottom: none; +} + +.sample-link:hover { + background: color-mix(in srgb, var(--accent) 5%, var(--surface)); +} + +.sample-link.is-active { + background: color-mix(in srgb, var(--accent) 8%, var(--surface)); + box-shadow: inset 2px 0 0 var(--accent); +} + +.sample-link:focus-visible { + outline: 2px solid var(--accent); + outline-offset: -2px; +} + +.sample-row-kind { + flex: 0 0 auto; +} + +.sample-row-kind.mono-pill { + padding: 0.28rem 0.55rem; + font-size: 0.66rem; + gap: 0.35rem; +} + +.sample-row-body { + min-width: 0; + flex: 1 1 auto; + display: grid; + gap: 0.1rem; +} + +.sample-row-title { + margin: 0; + color: var(--bench-ink); + font-size: 0.92rem; + font-weight: 600; + line-height: 1.35; +} + +.sample-row-desc { + margin: 0; + color: var(--bench-ink-soft); + font-size: 0.78rem; + line-height: 1.45; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.sample-row-meta { + flex: 0 0 auto; display: flex; - flex-wrap: wrap; align-items: center; - gap: 0.4rem; + gap: 0.6rem; + color: var(--bench-amber); + font-family: var(--font-mono), monospace; + font-size: 0.68rem; + letter-spacing: 0.06em; + text-transform: uppercase; + white-space: nowrap; } -.creator-result-shell { - min-width: 0; +.sample-row-arrow { + color: var(--bench-ink-faint); + transition: + color 150ms ease, + transform 150ms ease; } -.creator-result-card { - display: grid; - gap: 1rem; - height: 100%; - border: 1px solid var(--border); - border-radius: var(--radius-xl); - background: color-mix(in srgb, var(--surface-elevated) 80%, transparent); - padding: 1.5rem; +.sample-link:hover .sample-row-arrow { + color: var(--accent); + transform: translate(1px, -1px); } -.creator-result-head { +/* ── Inspector — dark recessed panel (charcoal QRH card on bone) ── */ +.inspector-panel { + border: 1px solid var(--surface-code-chrome-border); + border-radius: var(--radius-sm); + background: var(--surface-code); + color: var(--surface-code-text); + overflow: hidden; +} + +.inspector-panel-head { display: flex; flex-wrap: wrap; - align-items: flex-start; + align-items: center; justify-content: space-between; - gap: 0.75rem; + gap: 0.5rem 1rem; + padding: 0.7rem 0.9rem; + border-bottom: 1px solid var(--surface-code-chrome-border); + background: var(--surface-code-raised); } -.creator-link-frame, -.creator-result-note, -.creator-empty-state, -.creator-error-state, -.creator-warning-state { - border: 1px solid var(--border); - border-radius: var(--radius-lg); - background: color-mix(in srgb, var(--surface-strong) 92%, transparent); - padding: 0.95rem 1rem; +.inspector-panel-title { + margin: 0; + font-family: var(--font-mono), monospace; + font-size: 0.78rem; + font-weight: 600; + letter-spacing: 0.1em; + text-transform: uppercase; } -.creator-link-output { - min-height: 7.75rem; - margin-top: 0.6rem; - padding: 0; - border: none; - border-radius: 0; - resize: none; - background: transparent; +.inspector-panel-status { + display: inline-flex; + align-items: center; + gap: 0.45rem; font-family: var(--font-mono), monospace; - font-size: 0.76rem; - line-height: 1.65; - word-break: break-all; + font-size: 0.68rem; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; } -.creator-result-actions { - grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); +.inspector-panel-status::before { + content: ""; + width: 0.5rem; + height: 0.5rem; + border-radius: 1px; + background: currentColor; } -.creator-inline-status { - margin-top: 0.7rem; - color: var(--warning); - font-size: 0.86rem; - line-height: 1.55; +.inspector-panel-body { + display: grid; + gap: 0.9rem; + padding: 0.9rem; } -.artifact-share-warning { +.inspector-panel-message { margin: 0; - padding: 0.85rem 1rem; - border: 1px solid color-mix(in srgb, var(--warning) 35%, var(--border)); - border-radius: var(--radius-lg); - background: color-mix(in srgb, var(--warning) 8%, var(--surface-strong)); - color: var(--warning); - font-size: 0.86rem; + color: color-mix(in srgb, var(--surface-code-text) 78%, transparent); + font-size: 0.85rem; line-height: 1.55; } -.creator-error-state { - color: var(--danger); - font-size: 0.9rem; +.inspector-panel .stat-row { + color: color-mix(in srgb, var(--surface-code-text) 55%, transparent); +} + +.inspector-panel .stat-row .stat-item + .stat-item::before { + background: var(--surface-code-chrome-border); +} + +.inspector-panel .stat-row .stat-value { + color: var(--surface-code-text); +} + +.inspector-panel .budget-track { + background: color-mix(in srgb, var(--surface-code-text) 16%, transparent); +} + +.inspector-panel .budget-fill { + background: var(--accent); +} + +.inspector-hash { + margin: 0; + max-height: 8rem; + overflow: auto; + padding: 0.7rem 0.8rem; + border: 1px solid var(--surface-code-chrome-border); + border-radius: var(--radius-sm); + background: var(--surface-code-raised); + color: color-mix(in srgb, var(--surface-code-text) 72%, transparent); + font-family: var(--font-mono), monospace; + font-size: 0.72rem; line-height: 1.55; + white-space: pre-wrap; + word-break: break-all; } -.creator-warning-state { - color: var(--warning); - font-size: 0.9rem; - line-height: 1.55; - border-color: color-mix(in srgb, var(--warning) 35%, var(--border)); - background: color-mix(in srgb, var(--warning) 8%, var(--surface-strong)); -} +/* ── Ecosystem reference row — flat hairline table ── */ +/* ══════════════════════════════════════════════════════════════════════════ + Artifact state — console strip + charcoal stage frame + ══════════════════════════════════════════════════════════════════════════ */ .artifact-first-layout { display: grid; grid-template-columns: minmax(0, 1fr); - gap: 0; + gap: 1.25rem; scroll-margin-top: 4.5rem; } -/* ── Compact toolbar ribbon ── */ -.artifact-toolbar-bar { +@media (min-width: 768px) { + .artifact-first-layout { + gap: 1.5rem; + } +} + +/* ── Console strip — the horizontal keycap header ── */ +.console-strip { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; - gap: 0.75rem; - padding: 0.75rem 0; - border-bottom: 1px solid var(--border); + gap: 0.75rem 1.25rem; + padding-bottom: 0.9rem; + border-bottom: 1px solid var(--border-strong); +} + +.console-strip-lead { + display: grid; + gap: 0.4rem; + min-width: 0; + flex: 1 1 22rem; +} + +.console-heading { + margin: 0; + color: var(--text-primary); + font-family: var(--font-mono), monospace; + font-size: clamp(1.15rem, 2.6vw, 1.7rem); + font-weight: 600; + letter-spacing: -0.01em; + line-height: 1.15; + word-break: break-word; } -.artifact-toolbar-left { +/* ── Viewer toolbar — action keycap row ── */ +.viewer-toolbar { display: flex; flex-wrap: wrap; align-items: center; - gap: 0.75rem 1.5rem; -} - -/* ── Editorial content area ── */ -.artifact-content-section { - padding-top: 2rem; - padding-bottom: 2rem; + justify-content: flex-end; + gap: 0.45rem; } -@media (min-width: 768px) { - .artifact-content-section { - padding-top: 3rem; - padding-bottom: 3rem; - } +.diff-view-toggle { + display: flex; + flex-wrap: wrap; + gap: 0.45rem; } +/* ── Artifact selector — horizontal scroll strip ── */ .artifact-selector-row { display: flex; - gap: 0.75rem; - padding-top: 0.5rem; - padding-bottom: 0.5rem; + gap: 0.6rem; + padding: 0.15rem 0 0.6rem; scroll-padding-inline: 1rem; overflow-x: auto; - border-bottom: 1px solid var(--border); scroll-snap-type: x proximity; } @@ -825,39 +1174,30 @@ select { .artifact-switcher { display: flex; - align-items: flex-start; - gap: 0.85rem; - flex: 0 0 min(18rem, calc(100vw - 5rem)); - min-height: 4.5rem; - border: 1px solid var(--border); - border-radius: var(--radius-lg); - padding: 0.85rem 0.95rem; - background: color-mix(in srgb, var(--surface-elevated) 88%, transparent); + align-items: center; + gap: 0.65rem; + flex: 0 0 min(17rem, calc(100vw - 5rem)); + min-height: 3.6rem; + border: 1px solid var(--border-strong); + border-radius: var(--radius-sm); + padding: 0.6rem 0.75rem; + background: var(--surface); scroll-snap-align: start; + text-align: left; transition: - transform var(--duration-base) ease, border-color var(--duration-base) ease, - background-color var(--duration-base) ease, - box-shadow var(--duration-base) ease; + background-color var(--duration-base) ease; } .artifact-switcher:hover { - border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); - background: color-mix(in srgb, var(--accent) 3%, var(--surface-elevated)); -} - -.artifact-switcher-content { - min-width: 0; + border-color: var(--accent); + background: color-mix(in srgb, var(--accent) 5%, var(--surface)); } .artifact-switcher.is-active { - border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); - background: color-mix(in srgb, var(--accent) 6%, var(--surface-strong)); - box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 12%, transparent); -} - -.dark .artifact-switcher.is-active { - box-shadow: inset 3px 0 0 var(--accent); + border-color: var(--accent); + background: color-mix(in srgb, var(--accent) 7%, var(--surface)); + box-shadow: inset 0 0 0 1px var(--accent); } .artifact-switcher:focus-visible { @@ -868,30 +1208,51 @@ select { .artifact-switcher-icon { display: grid; place-items: center; - width: 2.35rem; - height: 2.35rem; flex-shrink: 0; - border: 1px solid var(--border); - border-radius: var(--radius-md); - background: color-mix(in srgb, var(--surface-muted) 90%, transparent); color: var(--accent); } -.artifact-switcher.is-active .artifact-switcher-icon { - border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); - background: color-mix(in srgb, var(--accent) 8%, var(--surface-strong)); +.artifact-switcher-content { + min-width: 0; + flex: 1 1 auto; } -.viewer-frame { - border: 1px solid var(--border); - border-radius: var(--radius-xl); - background: color-mix(in srgb, var(--surface-strong) 97%, transparent); +.artifact-switcher-title { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: var(--bench-ink); + font-size: 0.85rem; + font-weight: 600; + line-height: 1.35; +} + +.artifact-switcher-meta { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.1rem 0.5rem; + margin-top: 0.15rem; + color: var(--bench-ink-faint); + font-family: var(--font-mono), monospace; + font-size: 0.66rem; + letter-spacing: 0.06em; + text-transform: uppercase; +} + +.artifact-switcher-meta .truncate { overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } -.dark .viewer-frame { - background: color-mix(in srgb, var(--surface-strong) 96%, #0b1016 4%); - box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.02); +/* ── Stage frame — charcoal chassis ── */ +.viewer-frame { + border: 1px solid var(--surface-code-chrome-border); + border-radius: var(--radius-sm); + background: var(--surface-code); + overflow: hidden; } .viewer-frame-primary { @@ -900,18 +1261,18 @@ select { .artifact-preview { min-height: 0; - padding: 1.5rem; - background: color-mix(in srgb, var(--surface-strong) 96%, transparent); + padding: 1.25rem; + background: var(--surface-strong); } @media (min-width: 768px) { .artifact-preview { - padding: 2rem 2.5rem; + padding: 1.75rem 2rem; } } .dark .artifact-preview { - background: color-mix(in srgb, var(--surface-elevated) 96%, transparent); + background: var(--surface-elevated); } .artifact-preview pre { @@ -924,69 +1285,183 @@ select { line-height: 1.7; } +/* Raw/JSON source blocks render on the charcoal chassis inside the stage frame */ +.artifact-preview pre.artifact-raw-source, +.artifact-preview pre.json-raw-source { + white-space: pre; + word-break: normal; + color: var(--surface-code-text); + font-size: 0.82rem; + line-height: 1.65; +} + .artifact-preview.is-markdown { padding: 0.95rem; } -.viewer-toolbar { - display: flex; - flex-wrap: wrap; - align-items: center; - justify-content: flex-end; - gap: 0.55rem; +.artifact-share-warning { + margin: 0; + padding: 0.75rem 0.9rem; + border: 1px solid color-mix(in srgb, var(--warning) 40%, var(--border)); + border-radius: var(--radius-sm); + background: color-mix(in srgb, var(--warning) 8%, var(--surface)); + color: var(--warning); + font-size: 0.84rem; + line-height: 1.55; } -.artifact-action { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 0.45rem; - min-height: 2.7rem; - padding: 0.58rem 0.82rem; - border: 1px solid var(--border); +.artifact-empty-state { + border: 1px solid color-mix(in srgb, var(--danger) 38%, var(--border)); border-radius: var(--radius-sm); - background: color-mix(in srgb, var(--surface-elevated) 88%, transparent); + background: color-mix(in srgb, var(--danger) 8%, var(--surface-strong)); + padding: 1rem 1.1rem; color: var(--text-primary); +} + +/* ── Spec sheet — one placard, two-column QRH ledger on wide, stacks on phones ── */ +.artifact-disclosure { + border: 2px solid var(--bench-rule-strong); + background: linear-gradient(180deg, var(--bench-chrome-hi) 0%, var(--bench-chrome) 100%); + box-shadow: var(--bench-key-lit), var(--bench-key-foot); + color: var(--bench-ink); +} + +.artifact-disclosure-summary { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 1rem; + padding: 0.55rem 1rem; + border-bottom: 1px solid var(--bench-rule); + list-style: none; + cursor: pointer; +} + +.artifact-disclosure-summary::-webkit-details-marker { + display: none; +} + +.artifact-disclosure-summary:focus-visible { + outline: 2px solid color-mix(in srgb, var(--accent) 40%, transparent); + outline-offset: -2px; +} + +.artifact-disclosure-summary::after { + content: "+"; + color: var(--bench-ink-faint); + font-size: 1.05rem; + line-height: 1; + transition: transform var(--duration-base) ease; +} + +.artifact-disclosure[open] .artifact-disclosure-summary::after { + transform: rotate(45deg); +} + +.artifact-disclosure-title { font-family: var(--font-mono), monospace; - font-size: 0.74rem; - font-weight: 500; - letter-spacing: 0.01em; - transition: - border-color 150ms ease, - background-color 150ms ease; + font-size: 0.72rem; + font-weight: 700; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--bench-ink-faint); } -.artifact-action:hover { - border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); - background: color-mix(in srgb, var(--accent) 5%, var(--surface-elevated)); +.artifact-disclosure-body { + padding: 0.45rem 1rem 0.6rem; } -.artifact-action:focus-visible { - outline: 2px solid color-mix(in srgb, var(--accent) 48%, transparent); - outline-offset: 2px; +.artifact-disclosure-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + column-gap: 2rem; } -.artifact-action.is-primary { - border-color: transparent; - background: linear-gradient(135deg, var(--accent), var(--accent-strong)); - color: #ffffff; +.bench-ledger-row { + display: flex; + align-items: baseline; + gap: 0.6rem; + padding: 0.42rem 0; + border-top: 1px solid var(--bench-rule); + min-width: 0; } -.dark .artifact-action.is-primary { - background: var(--accent); - color: #1a0e08; +/* first row of each column has no top rule */ +.artifact-disclosure-grid > .bench-ledger-row:nth-child(-n + 2) { + border-top: none; } -.artifact-action.is-primary:hover { - box-shadow: 0 14px 30px color-mix(in srgb, var(--accent) 20%, transparent); +.bench-ledger-dots { + flex: 1 1 auto; + min-width: 1rem; + border-bottom: 2px dotted var(--bench-ink-faint); + transform: translateY(-0.18em); + opacity: 0.5; } -.artifact-action.is-code { - border-color: var(--surface-code-is-code-border); - background: var(--surface-code-is-code-bg); - color: var(--surface-code-is-code-fg); +.artifact-meta-value { + font-size: 0.92rem; + font-weight: 600; + line-height: 1.35; + color: var(--bench-ink); + text-align: right; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 60%; +} + +/* The budget readout is a VFD pill; amber must beat the row ink color. */ +.artifact-meta-value.bench-readout { + color: var(--bench-amber); +} + +.bench-cell-status { + display: inline-flex; + align-items: center; + gap: 0.45rem; +} + +/* Hash row spans both columns; dots hidden; mono line truncated */ +.bench-ledger-row-hash { + grid-column: 1 / -1; +} + +.bench-ledger-row-hash .bench-ledger-dots { + display: none; +} + +.artifact-hash-preview-code { + margin-left: auto; + min-width: 0; + max-width: 78%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + padding: 0.28rem 0.55rem; + border: 1px solid var(--surface-code-chrome-border); + border-radius: var(--radius-sm); + background: var(--surface-code); + color: var(--surface-code-text); + font-family: var(--font-mono), monospace; + font-size: 0.72rem; + line-height: 1.4; +} + +@media (max-width: 720px) { + .artifact-disclosure-grid { + grid-template-columns: 1fr; + } + + .artifact-disclosure-grid > .bench-ledger-row:nth-child(2) { + border-top: 1px solid var(--bench-rule); + } } +/* ══════════════════════════════════════════════════════════════════════════ + Renderer surfaces (renderers mount into the stage; classes kept stable) + ══════════════════════════════════════════════════════════════════════════ */ + .markdown-document { width: 100%; } @@ -996,9 +1471,10 @@ select { } .markdown-article { - color: var(--text-primary); + color: var(--bench-ink); font-size: 1rem; line-height: 1.78; + padding: 1rem 1.25rem; } .markdown-article > * + * { @@ -1012,7 +1488,7 @@ select { .markdown-article h5, .markdown-article h6 { margin-top: 1.5em; - color: var(--text-primary); + color: var(--bench-ink); font-family: var(--font-display), serif; font-weight: 600; line-height: 1.08; @@ -1040,11 +1516,11 @@ select { .markdown-article p, .markdown-article ul, .markdown-article ol { - color: var(--text-muted); + color: var(--bench-ink-soft); } .markdown-article strong { - color: var(--text-primary); + color: var(--bench-ink); } .markdown-link { @@ -1083,51 +1559,49 @@ select { .markdown-article blockquote { margin-inline: 0; - padding: 1.2rem 1.4rem; - border-left: 3px solid var(--accent-secondary); + padding: 1.1rem 1.3rem; + border-left: 3px solid var(--bench-chrome-lo); border-radius: 0; background: color-mix(in srgb, var(--surface-elevated) 60%, transparent); - color: var(--text-primary); - font-family: var(--font-display), serif; - font-style: italic; + color: var(--bench-ink); +} + +.markdown-article blockquote > p:first-child::before { + content: "“"; + color: var(--accent); + font-family: var(--font-display), sans-serif; + font-size: 1.4em; + font-weight: 700; + line-height: 1; + margin-right: 0.15em; } .markdown-article blockquote > * { - color: var(--text-primary); + color: var(--bench-ink); } .markdown-inline-code { padding: 0.18rem 0.45rem; - border: 1px solid var(--border); - border-radius: var(--radius-md); - background: color-mix(in srgb, var(--surface-muted) 82%, transparent); - color: var(--text-primary); + border: 1px solid var(--border-strong); + border-radius: 3px; + background: var(--surface-muted); + color: var(--bench-ink); font-family: var(--font-mono), monospace; font-size: 0.88em; } .markdown-code-frame { overflow: hidden; - border: 1px solid color-mix(in srgb, var(--border-strong) 88%, transparent); - border-radius: var(--radius-lg); - background: linear-gradient(180deg, color-mix(in srgb, var(--surface-code-raised) 84%, transparent), color-mix(in srgb, var(--surface-code) 100%, transparent)); - box-shadow: var(--shadow-md); -} - -.dark .markdown-code-frame { - border-color: rgba(100, 80, 120, 0.18); + border: 1px solid var(--surface-code-chrome-border); + border-radius: var(--radius-sm); + background: var(--surface-code); } .markdown-mermaid-frame { overflow: hidden; - border: 1px solid color-mix(in srgb, var(--border-strong) 88%, transparent); - border-radius: var(--radius-lg); - background: var(--surface-primary); - box-shadow: var(--shadow-md); -} - -.dark .markdown-mermaid-frame { - border-color: rgba(100, 80, 120, 0.18); + border: 1px solid var(--border-strong); + border-radius: var(--radius-sm); + background: var(--surface-strong); } .mermaid-container { @@ -1148,7 +1622,7 @@ select { .mermaid-error-message { margin: 0 0 0.75rem; - color: var(--text-danger, #e53e3e); + color: var(--danger); font-size: 0.85rem; font-weight: 500; } @@ -1157,7 +1631,7 @@ select { margin: 0; padding: 0.75rem; overflow-x: auto; - border-radius: var(--radius-md); + border-radius: var(--radius-sm); background: var(--surface-code); color: var(--surface-code-text); font-size: 0.82rem; @@ -1169,19 +1643,20 @@ select { align-items: center; justify-content: space-between; gap: 0.75rem; - padding: 0.8rem 1rem; - border-bottom: 1px solid color-mix(in srgb, var(--border) 85%, transparent); - background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 8%, transparent), transparent 58%); + padding: 0.65rem 0.9rem; + border-bottom: 1px solid var(--surface-code-chrome-border); + background: var(--surface-code-raised); } -.dark .markdown-code-head { - background: linear-gradient(90deg, rgba(224, 140, 80, 0.05), transparent 58%); +.markdown-mermaid-frame .markdown-code-head { + border-bottom-color: var(--border); + background: var(--surface-elevated); } .markdown-code-chip, .markdown-code-caption { font-family: var(--font-mono), monospace; - font-size: 0.72rem; + font-size: 0.7rem; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; @@ -1191,10 +1666,18 @@ select { color: color-mix(in srgb, var(--surface-code-text) 92%, white 8%); } +.markdown-mermaid-frame .markdown-code-chip { + color: var(--text-primary); +} + .markdown-code-caption { color: color-mix(in srgb, var(--surface-code-text) 58%, transparent); } +.markdown-mermaid-frame .markdown-code-caption { + color: var(--text-soft); +} + .markdown-code-pre { margin: 0; overflow-x: auto; @@ -1211,7 +1694,7 @@ select { } .code-renderer-shell { - border-radius: var(--radius-lg); + border-radius: var(--radius-sm); overflow: hidden; background: var(--surface-code); box-shadow: inset 0 0 0 1px var(--surface-code-chrome-border); @@ -1226,14 +1709,9 @@ select { align-items: center; justify-content: space-between; gap: 0.75rem; - padding: 0.9rem 1rem; + padding: 0.65rem 0.9rem; border-bottom: 1px solid var(--surface-code-chrome-border); - background: - linear-gradient(180deg, var(--surface-code-toolbar-shine), transparent), - var(--surface-code-raised); - transition: - background var(--duration-base) ease, - border-color var(--duration-base) ease; + background: var(--surface-code-raised); } .code-renderer-meta { @@ -1249,10 +1727,6 @@ select { color: var(--surface-code-text) !important; } -.code-renderer-readonly-label { - color: color-mix(in srgb, var(--surface-code-text) 56%, transparent) !important; -} - .code-renderer-host { min-height: 0; } @@ -1270,7 +1744,7 @@ select { .json-renderer-shell { display: flex; flex-direction: column; - gap: 1rem; + gap: 0.9rem; } .diff-renderer-toolbar, @@ -1283,21 +1757,35 @@ select { gap: 0.75rem; } -.diff-view-toggle { - display: flex; - flex-wrap: wrap; - gap: 0.6rem; -} - .diff-mobile-note { min-height: 2.1rem; } .diff-renderer-frame { overflow: hidden; - border: 1px solid var(--border); - border-radius: var(--radius-lg); - background: color-mix(in srgb, var(--surface-strong) 94%, transparent); + border: 1px solid var(--surface-code-chrome-border); + border-radius: var(--radius-sm); + background: var(--surface-code); + color: var(--surface-code-text); + /* Pierre internals: charcoal chassis in both themes + Spline Sans Mono + + bench accents. Base tokens drive pierre's derived colors; overrides pin + the surfaces that would otherwise wash out. */ + --diffs-font-family: var(--font-mono), monospace; + --diffs-header-font-family: var(--font-mono), monospace; + --diffs-font-size: 0.82rem; + --diffs-light-bg: var(--surface-code); + --diffs-dark-bg: var(--surface-code); + --diffs-light: var(--surface-code-text); + --diffs-dark: var(--surface-code-text); + --diffs-light-addition-color: var(--success); + --diffs-dark-addition-color: var(--success); + --diffs-light-deletion-color: var(--bench-carbon-red); + --diffs-dark-deletion-color: var(--bench-carbon-red); + --diffs-fg-number-override: var(--surface-code-gutter-fg); + --diffs-bg-separator-override: var(--surface-code-chrome-border); + --diffs-bg-context-gutter-override: var(--surface-code-raised); + --diffs-bg-hover-override: var(--surface-code-active-line); + --diffs-bg-selection-override: var(--surface-code-selection-bg); } .patch-bundle-shell { @@ -1308,25 +1796,30 @@ select { .patch-bundle-nav { display: flex; flex-direction: column; - gap: 0.65rem; - padding: 1rem; + gap: 0.55rem; + padding: 0.85rem; border-right: 1px solid var(--border); - background: color-mix(in srgb, var(--surface) 96%, transparent); + background: var(--surface); } .patch-bundle-link { display: grid; - gap: 0.45rem; - border: 1px solid var(--border); - border-radius: var(--radius-md); - padding: 0.85rem 0.9rem; - background: color-mix(in srgb, var(--surface-strong) 90%, transparent); + gap: 0.4rem; + border: 1px solid var(--bench-rule); + border-radius: var(--radius-sm); + padding: 0.7rem 0.8rem; + background: linear-gradient(180deg, var(--bench-chrome-hi) 0%, var(--bench-chrome) 100%); + box-shadow: var(--bench-key-lit), var(--bench-key-foot); + color: var(--bench-ink); + font-family: var(--font-mono), monospace; text-align: left; - } +} .patch-bundle-link.is-active { - border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); - background: color-mix(in srgb, var(--accent) 6%, var(--surface-strong)); + border-color: var(--accent); + background: linear-gradient(180deg, + color-mix(in srgb, var(--accent) 14%, var(--bench-chrome-hi)) 0%, + color-mix(in srgb, var(--accent) 9%, var(--bench-chrome)) 100%); } .patch-bundle-files { @@ -1344,28 +1837,26 @@ select { align-items: center; justify-content: space-between; gap: 0.75rem; - padding: 1rem 1rem 0.8rem; + padding: 0.85rem 0.9rem 0.7rem; + border-bottom: 1px solid var(--bench-rule); + background: linear-gradient(180deg, var(--bench-chrome-hi) 0%, var(--bench-chrome) 100%); + color: var(--bench-ink); } .patch-file-header h4 { margin: 0.25rem 0 0; - font-size: 1rem; + color: var(--bench-ink); + font-family: var(--font-mono), monospace; + font-size: 0.85rem; font-weight: 600; -} - -.diff-renderer-frame [data-theme="light"] { - --diff-bgColor-default: color-mix(in srgb, var(--surface-strong) 96%, white 4%); -} - -.diff-renderer-frame [data-theme="dark"] { - --diff-bgColor-default: #0f1721; + letter-spacing: 0.04em; } .csv-table-wrap { overflow: auto; - border: 1px solid var(--border); - border-radius: var(--radius-lg); - background: color-mix(in srgb, var(--surface-strong) 92%, transparent); + border: 1px solid var(--surface-code-chrome-border); + border-radius: var(--radius-sm); + background: var(--surface-code); max-height: min(68vh, 42rem); } @@ -1378,8 +1869,8 @@ select { .csv-table th, .csv-table td { - padding: 0.85rem 1rem; - border-bottom: 1px solid var(--border); + padding: 0.75rem 0.9rem; + border-bottom: 1px solid var(--surface-code-line); text-align: left; vertical-align: top; } @@ -1388,17 +1879,17 @@ select { position: sticky; top: 0; z-index: 1; - background: color-mix(in srgb, var(--surface) 96%, white 4%); - color: var(--text-soft); + background: var(--surface-code-raised); + color: var(--surface-code-gutter-fg); font-family: var(--font-mono), monospace; - font-size: 0.72rem; + font-size: 0.7rem; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; } .csv-table td { - color: var(--text-muted); + color: var(--surface-code-text); white-space: nowrap; } @@ -1406,20 +1897,12 @@ select { border-bottom: none; } -.artifact-empty-state { - border: 1px solid color-mix(in srgb, var(--danger) 38%, var(--border)); - border-radius: var(--radius-lg); - background: color-mix(in srgb, var(--danger) 8%, var(--surface-strong)); - padding: 1rem 1.1rem; - color: var(--text-primary); -} - .json-tree-shell { overflow: hidden; - border: 1px solid var(--border); - border-radius: var(--radius-lg); - background: color-mix(in srgb, var(--surface-strong) 94%, transparent); - padding: 0.75rem 0; + border: 1px solid var(--surface-code-chrome-border); + border-radius: var(--radius-sm); + background: var(--surface-code); + padding: 0.65rem 0; } .artifact-raw-source, @@ -1428,7 +1911,7 @@ select { overflow: auto; margin: 0; border: 1px solid var(--surface-code-chrome-border); - border-radius: var(--radius-lg); + border-radius: var(--radius-sm); background: var(--surface-code); color: var(--surface-code-text); padding: 1rem 1.05rem 1.15rem; @@ -1439,7 +1922,7 @@ select { } .json-node { - border-left: 1px solid color-mix(in srgb, var(--border) 82%, transparent); + border-left: 1px solid var(--surface-code-line); margin-left: 0.8rem; } @@ -1450,7 +1933,7 @@ select { gap: 0.65rem; min-height: 2rem; padding-right: 1rem; - color: var(--text-primary); + color: var(--surface-code-text); } .json-summary { @@ -1472,7 +1955,7 @@ select { .json-key { font-family: var(--font-mono), monospace; - color: var(--text-soft); + color: var(--surface-code-gutter-fg); } .json-key::after { @@ -1481,7 +1964,7 @@ select { } .json-structure { - color: var(--text-muted); + color: var(--surface-code-gutter-fg); } .json-value { @@ -1512,8 +1995,8 @@ select { margin: 1.5rem 0; overflow-x: auto; border: 1px solid var(--border); - border-radius: var(--radius-lg); - background: color-mix(in srgb, var(--surface-strong) 92%, transparent); + border-radius: var(--radius-sm); + background: var(--surface-strong); } .markdown-table { @@ -1524,23 +2007,23 @@ select { .markdown-table th, .markdown-table td { - padding: 0.85rem 1rem; + padding: 0.75rem 0.9rem; border-bottom: 1px solid var(--border); text-align: left; } .markdown-table th { - color: var(--text-soft); + color: var(--bench-ink-soft); font-family: var(--font-mono), monospace; - font-size: 0.72rem; + font-size: 0.7rem; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; - background: color-mix(in srgb, var(--surface-muted) 86%, transparent); + background: var(--surface-muted); } .markdown-table td { - color: var(--text-muted); + color: var(--bench-ink-soft); } .markdown-table tr:last-child td { @@ -1557,223 +2040,160 @@ select { .markdown-article img { max-width: 100%; height: auto; - border-radius: var(--radius-lg); -} - -.artifact-list-item { - border: 1px solid var(--border); - border-radius: var(--radius-lg); - background: color-mix(in srgb, var(--surface-strong) 85%, transparent); - padding: 1rem; -} - -.artifact-list-item.is-active { - border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); - background: color-mix(in srgb, var(--accent) 6%, var(--surface-strong)); -} - - - -.artifact-meta-value { - margin-top: 0.45rem; - font-size: 0.98rem; - font-weight: 600; - line-height: 1.45; - word-break: break-word; -} - -.artifact-disclosure { - margin-top: 0.75rem; - overflow: hidden; - border: 1px solid var(--border); - border-radius: var(--radius-xl); - background: color-mix(in srgb, var(--surface) 94%, transparent); + border-radius: var(--radius-sm); } -.artifact-disclosure-summary { - display: flex; - align-items: center; - justify-content: space-between; - gap: 1rem; - padding: 0.9rem 1rem; - cursor: pointer; - list-style: none; -} +/* ══════════════════════════════════════════════════════════════════════════ + Legacy support classes — used only by the static /security and + /url-explainer pages (outside the redesign scope). Flat hairline grids in + the same etched grammar; not used anywhere in the main viewer shell. + ══════════════════════════════════════════════════════════════════════════ */ -.artifact-disclosure-summary-copy { +.bento-grid { display: grid; - gap: 0.24rem; - min-width: 0; + grid-template-columns: 1fr; + gap: 1px; + background: var(--border); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + overflow: hidden; } -.artifact-disclosure-title { - line-height: 1.45; -} +@media (min-width: 768px) { + .bento-grid { + grid-template-columns: repeat(2, 1fr); + } -.artifact-disclosure-summary::-webkit-details-marker { - display: none; + .bento-wide { + grid-column: span 2; + } } -.artifact-disclosure-summary:focus-visible { - outline: 2px solid color-mix(in srgb, var(--accent) 40%, transparent); - outline-offset: -2px; +.bento-card { + background: var(--surface); } -.artifact-disclosure-summary::after { - content: "+"; - color: var(--text-soft); - font-size: 1.15rem; - line-height: 1; - transition: transform var(--duration-base) ease; +.bento-link { + transition: background-color var(--duration-base) ease; } -.artifact-disclosure[open] .artifact-disclosure-summary::after { - transform: rotate(45deg); +.bento-link:hover { + background: color-mix(in srgb, var(--accent) 5%, var(--surface)); } -.artifact-disclosure-body { - display: grid; - gap: 0.9rem; - padding: 0 1rem 1rem; - border-top: 1px solid var(--border); +.hero-link-eyebrow { + color: var(--accent); + font-family: var(--font-mono), monospace; + font-size: 0.72rem; + font-weight: 600; + letter-spacing: 0.1em; + text-transform: uppercase; } -.artifact-disclosure-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); - gap: 0.75rem; +.hero-link-title { + display: block; + margin-top: 0.4rem; + color: var(--text-primary); + font-size: 1rem; + font-weight: 600; + line-height: 1.4; } -.artifact-hash-preview { +.metric-card { border: 1px solid var(--border); - border-radius: var(--radius-lg); - background: color-mix(in srgb, var(--surface-muted) 94%, transparent); - padding: 0.95rem 1rem; -} - -.artifact-hash-preview-code { - margin-bottom: 0; + border-radius: var(--radius-sm); + background: var(--surface); + padding: 0.75rem 0.85rem; } -.fade-up { - opacity: 0; - transform: translateY(24px); - animation: fade-up 720ms cubic-bezier(0.2, 1, 0.22, 1) forwards; +/* Section separators used by /url-explainer */ +.home-hero-section, +.home-stage-section { + padding-top: 2rem; + padding-bottom: 2rem; + border-bottom: 1px solid var(--border); } -@keyframes fade-up { - to { - opacity: 1; - transform: translateY(0); +@media (min-width: 768px) { + .home-hero-section, + .home-stage-section { + padding-top: 3rem; + padding-bottom: 3rem; } } -@media (max-width: 640px) { - .app-shell > div { - gap: 1.5rem; - } - - .panel, - .viewer-frame, - .artifact-disclosure { - border-radius: var(--radius-md); - box-shadow: 0 12px 28px rgba(28, 28, 24, 0.06); - } +/* ══════════════════════════════════════════════════════════════════════════ + Mobile compaction (≤640px) + ══════════════════════════════════════════════════════════════════════════ */ +@media (max-width: 640px) { .mono-pill { gap: 0.3rem; padding: 0.34rem 0.62rem; font-size: 0.69rem; } - .metric-card, - .artifact-hash-preview, - .artifact-empty-state { - padding: 0.72rem 0.8rem; - } - - .metric-grid, - .artifact-disclosure-grid, - .sample-link-grid, - .artifact-first-layout { - gap: 0.6rem; - } - .section-kicker, .metric-label { font-size: 0.66rem; letter-spacing: 0.1em; } - .hero-link-title { - font-size: 0.95rem; + .stat-row { + font-size: 0.64rem; } - .hero-link-card, - .sample-link { - gap: 0.65rem; - padding: 0.78rem 0.82rem; + .stat-row .stat-item + .stat-item::before { + margin: 0 0.55rem; } - .home-generator-grid, - .creator-form-grid, - .creator-result-metrics, - .creator-result-actions { - gap: 0.65rem; + .bench-rig, + .bench-col-side, + .empty-state-layout { + gap: 1.25rem; } .creator-kind-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); - margin-top: 0.85rem; } - .creator-kind-card, - .creator-result-card { - padding: 0.8rem; - } - - .creator-kind-label { - font-size: 0.88rem; + .creator-textarea { + min-height: 12rem; } - .creator-field-hint, - .creator-inline-status, - .creator-error-state { - font-size: 0.82rem; - line-height: 1.45; + .creator-form-footer { + align-items: stretch; + flex-direction: column; } - .creator-input, - .creator-textarea { - padding: 0.78rem 0.82rem; + .creator-form-footer > .artifact-action { + width: 100%; } - .creator-textarea { - min-height: 14rem; + .sample-row-desc { + white-space: normal; + display: -webkit-box; + overflow: hidden; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; } - .creator-link-output { - min-height: 8.5rem; - font-size: 0.72rem; - line-height: 1.5; + .sample-row-meta .sample-row-count { + display: none; } - .creator-form-footer { - align-items: stretch; + .console-strip { + align-items: flex-start; + flex-direction: column; } - .creator-form-footer > .artifact-action { + .console-strip-lead { + flex-basis: auto; width: 100%; } - .hero-link-body, - .artifact-disclosure-status, - .sample-link p { - line-height: 1.48; - } - .artifact-first-layout { - gap: 0.72rem; + gap: 0.9rem; } .artifact-preview { @@ -1783,22 +2203,14 @@ select { .artifact-selector-row { gap: 0.5rem; - margin-top: 0.65rem; - padding-top: 0.65rem; - padding-bottom: 0.1rem; scroll-padding-inline: 0.7rem; } .artifact-switcher { - gap: 0.58rem; + gap: 0.55rem; flex-basis: min(12.75rem, calc(100vw - 2.2rem)); - min-height: 3.75rem; - padding: 0.68rem 0.74rem; - } - - .artifact-switcher-icon { - width: 1.9rem; - height: 1.9rem; + min-height: 3.4rem; + padding: 0.6rem 0.65rem; } .artifact-switcher-title { @@ -1811,9 +2223,7 @@ select { } .artifact-switcher-meta { - gap: 0.18rem 0.4rem; line-height: 1.3; - font-size: 0.68rem; } .artifact-switcher-meta .truncate { @@ -1825,7 +2235,7 @@ select { .code-renderer-toolbar { align-items: flex-start; - padding: 0.68rem 0.74rem; + padding: 0.6rem 0.7rem; } .diff-renderer-shell, @@ -1840,7 +2250,7 @@ select { gap: 0.6rem; } - .diff-view-toggle { + .diff-renderer-toolbar .diff-view-toggle { width: 100%; align-items: flex-start; gap: 0.45rem; @@ -1864,12 +2274,14 @@ select { overflow-x: auto; scroll-padding-inline: 0.7rem; scroll-snap-type: x proximity; + border-right: none; + border-bottom: 1px solid var(--border); } .patch-bundle-link { gap: 0.35rem; flex: 0 0 min(14rem, calc(100vw - 3rem)); - padding: 0.68rem 0.72rem; + padding: 0.65rem 0.7rem; scroll-snap-align: start; } @@ -1883,6 +2295,10 @@ select { word-break: break-word; } + .patch-bundle-shell { + grid-template-columns: 1fr; + } + .patch-file-header { gap: 0.55rem; padding: 0.72rem 0.8rem 0.62rem; @@ -1947,35 +2363,21 @@ select { line-height: 1.45; } - .patch-bundle-shell { - grid-template-columns: 1fr; - } - - .patch-bundle-nav { - border-right: none; - border-bottom: 1px solid var(--border); - } - .viewer-toolbar { width: 100%; max-width: 100%; min-width: 0; justify-content: flex-start; gap: 0.38rem; - flex-wrap: wrap; - overflow-x: auto; - -webkit-overflow-scrolling: touch; - scrollbar-width: thin; } - /* Compact touch sizing for all action surfaces at ≤640px */ .artifact-action { min-height: 2.55rem; padding: 0.46rem 0.65rem; font-size: 0.71rem; } - /* Viewer shell: avoid flex-shrink clipping on standalone buttons; pair row only inside raw/rendered toggle */ + /* Standalone toolbar keycaps keep their own width; the rendered/raw pair splits a row */ .viewer-toolbar .artifact-action { flex: 0 0 auto; white-space: nowrap; @@ -2004,7 +2406,7 @@ select { max-width: 100%; } - /* JSON tree/raw toggle: same pair-row behavior as markdown raw/rendered (not covered by viewer-toolbar rules) */ + /* JSON tree/raw toggle mirrors the markdown rendered/raw pair behavior */ .json-renderer-toolbar .diff-view-toggle { display: flex; flex-wrap: wrap; @@ -2032,70 +2434,45 @@ select { scrollbar-width: thin; } - .viewer-frame-primary { - min-height: 0; - } - - .artifact-disclosure-grid { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - .artifact-meta-value, .metric-value { - margin-top: 0.34rem; - font-size: 0.94rem; + font-size: 0.9rem; line-height: 1.32; } .artifact-disclosure-summary { - align-items: flex-start; - gap: 0.75rem; - padding: 0.78rem 0.84rem; - } - - .artifact-disclosure-summary-copy { - gap: 0.18rem; - } - - .artifact-disclosure-title { - line-height: 1.35; - } - - .artifact-disclosure-status { - font-size: 0.88rem; + padding: 0.55rem 0.8rem; } .artifact-disclosure-body { - gap: 0.75rem; - padding: 0 0.84rem 0.84rem; + padding: 0.4rem 0.8rem 0.55rem; } .artifact-hash-preview-code { - font-size: 0.72rem; - line-height: 1.45; + font-size: 0.68rem; } .markdown-article { + color: var(--bench-ink); font-size: 0.96rem; } .markdown-table { min-width: 28rem; } -} -@media (max-width: 360px) { - .creator-kind-grid, - .bento-grid-compact, - .artifact-disclosure-grid { - grid-template-columns: 1fr; + .inspector-panel-body { + padding: 0.75rem; } +} +/* Very narrow: toggle pairs stack vertically (340px contract) */ +@media (max-width: 360px) { .artifact-action { flex-basis: 100%; } - /* Same selector specificity as the 640px pair rule; this block wins at ≤360px because it appears later in the stylesheet */ + /* Same specificity as the 640px pair rule; this block wins at ≤360px by source order */ .viewer-toolbar .diff-view-toggle .artifact-action { flex: 1 1 100%; max-width: 100%; @@ -2107,21 +2484,9 @@ select { } } -@media (min-width: 768px) { - .home-generator-grid { - grid-template-columns: minmax(0, 1.1fr) minmax(20rem, 0.9fr); - align-items: start; - } - - .creator-form-grid { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - - .sample-link-grid { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } -} - +/* ══════════════════════════════════════════════════════════════════════════ + Print + ══════════════════════════════════════════════════════════════════════════ */ @media print { @page { diff --git a/src/app/icon.svg b/src/app/icon.svg index c96bd83..04731ec 100644 --- a/src/app/icon.svg +++ b/src/app/icon.svg @@ -1,7 +1,10 @@ - - - - - + + + + + + + + diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 03556de..513ea22 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,25 +1,34 @@ import type { Metadata } from "next"; -import { Fraunces, IBM_Plex_Mono, IBM_Plex_Sans } from "next/font/google"; +import localFont from "next/font/local"; import type { ReactNode } from "react"; import { getCanonicalSiteUrl, getMetadataBase } from "@/lib/site/canonical-base"; import "./globals.css"; -const display = Fraunces({ - subsets: ["latin"], +const display = localFont({ + src: [ + { path: "../fonts/manrope-v20-latin-500.woff2", weight: "500" }, + { path: "../fonts/manrope-v20-latin-600.woff2", weight: "600" }, + { path: "../fonts/manrope-v20-latin-700.woff2", weight: "700" }, + { path: "../fonts/manrope-v20-latin-800.woff2", weight: "800" }, + ], variable: "--font-display", - weight: ["500", "600", "700"], }); -const sans = IBM_Plex_Sans({ - subsets: ["latin"], +const sans = localFont({ + src: [ + { path: "../fonts/suse-v4-latin-500.woff2", weight: "500" }, + { path: "../fonts/suse-v4-latin-600.woff2", weight: "600" }, + { path: "../fonts/suse-v4-latin-700.woff2", weight: "700" }, + ], variable: "--font-sans", - weight: ["400", "500", "600", "700"], }); -const mono = IBM_Plex_Mono({ - subsets: ["latin"], +const mono = localFont({ + src: [ + { path: "../fonts/spline-sans-mono-v13-latin-500.woff2", weight: "500" }, + { path: "../fonts/spline-sans-mono-v13-latin-600.woff2", weight: "600" }, + ], variable: "--font-mono", - weight: ["400", "500", "600"], }); export const metadata: Metadata = { @@ -44,9 +53,8 @@ const themeInitScript = ` `; /** - * Root layout for the static shell that installs fonts and the pre-hydration theme class. - * Accepts `children` from Next.js app routing and keeps the exported shell provider-free. - * Sets hydration-safe HTML/body structure used by lazy renderer mounts and fallback screens. + * Root layout for the static shell. Fonts are now self-hosted woff2 bundles in src/fonts/ + * so dev doesn't block on font.googleapis.com, and typography tokens render immediately. */ export default function RootLayout({ children, diff --git a/src/components/home/bench-hero.tsx b/src/components/home/bench-hero.tsx new file mode 100644 index 0000000..b1c6714 --- /dev/null +++ b/src/components/home/bench-hero.tsx @@ -0,0 +1,26 @@ +"use client"; + +import type { CSSProperties } from "react"; + +type BenchHeroProps = { + animationStyle?: CSSProperties; +}; + +/** + * Compact wordmark-plus-truth strip that tops the empty-state instrument rig. + * Replaces the old full-bleed essay hero: one display line, one plain-English + * trust statement. Deeper framing lives in the inspector + footer. + */ +export function BenchHero({ animationStyle }: BenchHeroProps) { + return ( +
+

Zero-retention viewer for AI output, delivered in one link.

+

Everything lives in the URL fragment. The host only serves the shell.

+
+ open source + self-hostable + no database +
+
+ ); +} diff --git a/src/components/home/link-creator.tsx b/src/components/home/link-creator.tsx index 8c3788f..dd502fa 100644 --- a/src/components/home/link-creator.tsx +++ b/src/components/home/link-creator.tsx @@ -195,313 +195,301 @@ export function LinkCreator({ onPreviewHash }: LinkCreatorProps) { }; return ( -
-
-
-
-
-

Try it now

-

- Create a link -

-
-

- Paste content, pick a format, and get a shareable URL. Everything - encodes client-side. -

+
+
+

Create a link

+
+ client-side +
+ {numberFormatter.format( + generatedLink?.fragmentLength ?? draft.content.length, + )} chars / {generatedLink?.codec ?? draft.codec ?? "auto"}
+
+
-
- {artifactKinds.map((kind) => { - const Icon = kindIcons[kind]; - const isActive = draft.kind === kind; - - return ( - - ); - })} -
+
+ {artifactKinds.map((kind) => { + const Icon = kindIcons[kind]; + const isActive = draft.kind === kind; + + return ( + + ); + })} +
-
{ - event.preventDefault(); - handleGenerate(); - }} - > -