From e306a943649e0243bb6655c6b625038ccacbbd1b Mon Sep 17 00:00:00 2001 From: Cmochance <3216202644@qq.com> Date: Mon, 1 Jun 2026 15:27:02 +0800 Subject: [PATCH 1/2] feat(antigravity): polish theme rendering + per-wallpaper scrim tuning Antigravity (shadcn / Catppuccin-Latte) template fixes in theme.rs: - reskin conversation-transcript markdown (blockquote, inline code, PrismJS syntax tokens, GFM alerts) from Latte-light leftovers to warm dark glass - repaint the per-turn sticky-header ::after and bottom scroll-fade overlays (both used the Latte light --background) to a warm-dark scrim fade - override --sidebar-secondary so the active conversation pill stops rendering as a near-white color(srgb) Catppuccin pill Per-theme scrim calibration: theme-forge shipped every theme with the same default scrim, leaving bright wallpapers under-darkened on Antigravity (chat text washed out). Tuned scrimTop/Mid/Bot per wallpaper brightness for the 8 bright themes (azurlane, sonata, zani, nailin, rose, frost, nocturne, duet); studio/carton kept the default (naturally dark wallpapers). Adds the antigravity-theme-tune workflow used to iterate the look live. --- .claude/workflows/antigravity-theme-tune.js | 187 ++++++++ src-tauri/src/theme.rs | 458 +++++++++++++++++--- themes/azurlane/theme.json | 6 +- themes/duet/theme.json | 6 +- themes/frost/theme.json | 6 +- themes/nailin/theme.json | 6 +- themes/nocturne/theme.json | 6 +- themes/rose/theme.json | 6 +- themes/sonata/theme.json | 6 +- themes/zani/theme.json | 6 +- 10 files changed, 603 insertions(+), 90 deletions(-) create mode 100644 .claude/workflows/antigravity-theme-tune.js diff --git a/.claude/workflows/antigravity-theme-tune.js b/.claude/workflows/antigravity-theme-tune.js new file mode 100644 index 0000000..5d28b0e --- /dev/null +++ b/.claude/workflows/antigravity-theme-tune.js @@ -0,0 +1,187 @@ +export const meta = { + name: 'antigravity-theme-tune', + description: 'Iteratively tune the Antigravity background theme against the live app via CDP screenshots', + whenToUse: 'Refine the translucent/background Antigravity theme to visual perfection: multi-lens critique of live screenshots, then synthesize structural CSS adjustments, looping until scores converge. Edits ONLY the Antigravity-private template; never touches Codex files or theme.json.', + phases: [ + { title: 'Setup', detail: 'render CSS from the Antigravity template, inject into live Antigravity, capture baseline shots + light/text scans' }, + { title: 'Critique', detail: 'parallel lenses score the screenshots and propose template adjustments' }, + { title: 'Synthesize', detail: 'merge adjustments, edit antigravity.template.css, re-render, re-inject, re-capture' }, + { title: 'Final', detail: 'capture final shots and write a report' }, + ], +}; + +// ---- config (args override) ---- +const REPO = (args && args.repoRoot) || '/Users/alysechen/alysechen/github/agent-theme'; +const LAB = `${REPO}/.theme-lab`; +const THEME = (args && args.theme) || 'changli'; +// The STRUCTURE template (Antigravity-private). The synthesizer edits ONLY this. +const TMPL = `${LAB}/antigravity.template.css`; +// Knob VALUES come from this theme.json — READ ONLY (shared with Codex; editing +// it would change Codex's look, which is explicitly forbidden). +const THEME_JSON = `${REPO}/themes/${THEME}/theme.json`; +const CSSOUT = `${LAB}/css/${THEME}-ag.css`; +// Antigravity harness: occluded-safe (NO_ACTIVATE) so we never steal the user's focus. +const HARNESS= `AGENT=antigravity NO_ACTIVATE=1 node ${LAB}/cdp.mjs`; +const RENDER = `node ${LAB}/render-antigravity.mjs ${THEME} ${CSSOUT}`; +const SHOTS = `${LAB}/shots/ag`; +const SRCIMG = `${LAB}/src/${THEME}-768.png`; +const ROUNDS = (args && args.rounds) ?? 4; +const THRESHOLD = (args && args.threshold) ?? 88; + +const LENSES = [ + { key: 'composition', title: 'Background composition & framing', + focus: `How the ${THEME} wallpaper reads behind the Antigravity agent UI: the centered chat-input card and the left conversation sidebar sit over the image — is the focal point balanced vs where text lands, is the legibility scrim (#root linear-gradient via --cl-scrim-top/-mid/-bot) balanced top-to-bottom? background-position is fixed by theme.json (do NOT change it). Knobs you MAY change in the template: the #root scrim gradient stops/opacities, any extra overlay rules.` }, + { key: 'readability', title: 'Text readability & contrast', + focus: `Legibility of every text level over the wallpaper + glass: sidebar items ("New Conversation", "Conversation History", "Settings"), the "Projects" section labels, project/conversation names, the grey timestamps (9d/10d), the chat placeholder ("Ask anything, @ to mention"), the model selector ("Gemini 3.5 Flash"), "Open IDE". Read the textscan json for any dark-on-dark text. Knobs: text-shadow rules in the template (.text-foreground / .text-muted-foreground / placeholder), the --cl-scrim-* darkening, and which --cl-ink level / glass token each surface maps to. Do NOT edit theme.json ink values.` }, + { key: 'glass', title: 'Glass panel quality & cohesion', + focus: `Frosted-glass surfaces: the conversation sidebar (.bg-sidebar), the centered chat-input card (.bg-card-border frame + .bg-card field), dropdowns/menus/dialogs ([role=menu], .bg-popover). Blur amount, tint opacity, border crispness, drop shadow — do they read as distinct elevated surfaces or muddy/over-transparent? Knobs: the backdrop-filter blur expressions, box-shadow, border rules, and which --cl-glass / --cl-glass-soft / --cl-glass-strong each maps to.` }, + { key: 'accent', title: 'Accent colour cohesion', + focus: `Antigravity's brand accent is mauve #8839EF; the theme remaps --primary and links/focus/selection to the ${THEME} accent via the accent block. Check: primary buttons, the send button, links, focus ring, ::selection, the model/agent selector chips — do they harmonise with the warm wallpaper or clash? Is on-accent text (--primary-foreground) legible? Knobs: the accent block rules (the accent VALUES come from theme.json; you may adjust which elements get the accent and the --primary-foreground contrast strategy in the template).` }, + { key: 'seams', title: 'Seams, leftover light surfaces & artifacts', + focus: `Hunt for any surface that did NOT get themed: read the lightscan json for opaque LIGHT rectangles (un-overridden shadcn/vscode tokens still showing Catppuccin Latte light colours), stray hard edges between sidebar↔main, the top bar (40px), the card border frame, scrollbars. Each light surface = a token/selector you must add to the template (override its shadcn --token to a --cl-glass* / transparent, or add a selector rule). Knobs: add token overrides in the html{} block, add module selector rules, border rules.` }, +]; + +const CRITIQUE_SCHEMA = { + type: 'object', additionalProperties: false, + required: ['dimension', 'score', 'summary', 'issues', 'adjustments'], + properties: { + dimension: { type: 'string' }, + score: { type: 'integer', minimum: 0, maximum: 100, description: '100 = perfect, nothing to improve on this dimension' }, + summary: { type: 'string' }, + issues: { type: 'array', items: { type: 'string' }, description: 'concrete problems seen in the screenshots / scans' }, + adjustments: { + type: 'array', + items: { + type: 'object', additionalProperties: false, + required: ['target', 'change', 'reason'], + properties: { + target: { type: 'string', description: 'a knob name like --cl-scrim-bot, a shadcn token like --muted, or a CSS selector to add/modify in antigravity.template.css' }, + change: { type: 'string', description: 'precise new value or rule, e.g. "rgba(13,9,6,.70)" or "add: backdrop-filter blur(10px)"' }, + reason: { type: 'string' }, + }, + }, + }, + }, +}; + +const SETUP_SCHEMA = { + type: 'object', additionalProperties: false, + required: ['ok', 'shotPrefix', 'notes'], + properties: { + ok: { type: 'boolean' }, + shotPrefix: { type: 'string', description: 'absolute path prefix of captured shots, without the -full.png suffix' }, + notes: { type: 'string' }, + }, +}; + +const SYNTH_SCHEMA = { + type: 'object', additionalProperties: false, + required: ['shotPrefix', 'appliedChanges', 'notes'], + properties: { + shotPrefix: { type: 'string', description: 'absolute path prefix of the NEW shots captured after re-injecting' }, + appliedChanges: { type: 'array', items: { type: 'string' } }, + notes: { type: 'string' }, + }, +}; + +const FINAL_SCHEMA = { + type: 'object', additionalProperties: false, + required: ['summary', 'reportPath', 'perDimension'], + properties: { + summary: { type: 'string' }, + reportPath: { type: 'string' }, + perDimension: { type: 'array', items: { + type: 'object', additionalProperties: false, + required: ['dimension', 'score'], + properties: { dimension: { type: 'string' }, score: { type: 'integer' } }, + } }, + }, +}; + +// ============================ run ============================ +phase('Setup'); +const setup = await agent( + `You tune a live Antigravity theme. Run EXACTLY these shell steps from ${REPO}: + 1. ${RENDER} + 2. ${HARNESS} port (must print a number; if it prints NONE/NO_PORT, STOP and return ok:false — Antigravity is not running with a debug port, or its window is not visible) + 3. ${HARNESS} inject ${CSSOUT} agent-theme-lab + 4. sleep 1 + 5. ${HARNESS} capture ${SHOTS}/r0 + 6. ${HARNESS} lightscan ${SHOTS}/r0-light.json + 7. ${HARNESS} textscan ${SHOTS}/r0-text.json + This writes ${SHOTS}/r0-full.png, ${SHOTS}/r0-sidebar.png, ${SHOTS}/r0-center.png + the two scans. + If any capture step HANGS or produces no file, the Antigravity window is occluded/minimised — return ok:false with that note. + Return ok:true and shotPrefix="${SHOTS}/r0".`, + { label: 'setup', phase: 'Setup', schema: SETUP_SCHEMA }, +); + +if (!setup || !setup.ok) { + return { aborted: true, reason: 'Setup failed (Antigravity debug port down or window not visible?). ' + (setup?.notes || ''), setup }; +} + +let shotPrefix = setup.shotPrefix; +const history = []; + +for (let r = 1; r <= ROUNDS; r++) { + phase('Critique'); + const crits = (await parallel(LENSES.map((L) => () => + agent( + `Critique the CURRENT Antigravity theme screenshots for ONE dimension: "${L.title}". + Look critically at these images (Read them): + - ${shotPrefix}-full.png (whole window) + - ${shotPrefix}-sidebar.png (left conversation sidebar, retina) + - ${shotPrefix}-center.png (centered chat-input card + main area, retina) + Also Read these scans for machine-detected problems: + - ${shotPrefix}-light.json (opaque LIGHT surfaces = un-themed Catppuccin-Latte leftovers) + - ${shotPrefix}-text.json (dark text that may be invisible) + Reference target art (the source character image): ${SRCIMG} + The tunable STRUCTURE is ${TMPL} — Read it so your adjustments name REAL selectors/tokens/knobs. + The knob VALUES resolve from ${THEME_JSON} — Read it to know the concrete colours, but you must NOT edit it (it is shared with Codex). + Focus ONLY on: ${L.focus} + Goal: a polished, cohesive, readable warm-dark "${THEME}" wallpaper theme for the Antigravity agent manager where the sidebar + centered chat card read as crisp frosted glass over the image, every text level is comfortably legible, the accent harmonises with the art, and NO light Catppuccin surfaces leak through. + Score 0-100 (be strict; 100 = truly nothing to improve on THIS dimension). List concrete issues you actually see, and precise adjustments naming knobs/tokens/selectors from ${TMPL}. dimension = "${L.key}".`, + { label: `crit:${L.key}@r${r}`, phase: 'Critique', schema: CRITIQUE_SCHEMA }, + ), + ))).filter(Boolean); + + if (!crits.length) { history.push({ round: r, error: 'no critiques' }); break; } + const scores = crits.map((c) => c.score); + const minScore = Math.min(...scores); + const avgScore = Math.round(scores.reduce((a, b) => a + b, 0) / scores.length); + history.push({ round: r, minScore, avgScore, dims: crits.map((c) => ({ d: c.dimension, s: c.score })) }); + log(`round ${r}: min=${minScore} avg=${avgScore} [${crits.map((c) => c.dimension + ':' + c.score).join(', ')}]`); + + if (minScore >= THRESHOLD) { log(`converged at round ${r} (min ${minScore} >= ${THRESHOLD})`); break; } + if (r === ROUNDS) break; // last round: critique only, no further synth + + phase('Synthesize'); + const allAdj = crits.flatMap((c) => c.adjustments.map((a) => ({ dim: c.dimension, ...a }))); + const synth = await agent( + `You are the synthesizer for round ${r} of Antigravity theme tuning. Merge these critique adjustments into the Antigravity STRUCTURE template and re-render. + Critiques (JSON): ${JSON.stringify(crits.map((c) => ({ dimension: c.dimension, score: c.score, issues: c.issues, adjustments: c.adjustments })))} + HARD ISOLATION RULES (must obey): + - Edit ONLY ${TMPL}. Do NOT edit ${THEME_JSON}, any themes/*/theme.json, theme.rs, codex.template.css, or ANY Codex/shipped file. This tuning must not change Codex's look at all. + - Keep every value expressed via the --cl-* knobs (or color-mix/calc of them) so the theme stays modular/per-theme. Only hardcode rgba when a knob genuinely cannot express it. + Steps: + 1. Read ${TMPL} and ${THEME_JSON} (for the concrete knob values, read-only). + 2. Decide a coherent merged set of changes. Resolve conflicts between lenses sensibly (readability wants darker scrim, composition wants the image visible — find balance). Prefer adjusting the html{} token overrides, the #root scrim, the module selector rules (blur/shadow/border), and text-shadow rules. Add NEW shadcn/vscode token overrides or NEW selector rules to kill any light leftovers found in the lightscan. Keep changes tasteful and incremental — do NOT overhaul everything at once. + 3. Apply them with the Edit tool to ${TMPL}. Keep it valid CSS. Keep the __INK__/__GLASS__/… placeholders and __HERO__/__ACCENT_BLOCK__/__BASECOLOR__/__POS__/__FIT__ intact (render-antigravity.mjs fills them). + 4. Run: cd ${REPO} && ${RENDER} && ${HARNESS} inject ${CSSOUT} agent-theme-lab && sleep 1 && ${HARNESS} capture ${SHOTS}/r${r} && ${HARNESS} lightscan ${SHOTS}/r${r}-light.json && ${HARNESS} textscan ${SHOTS}/r${r}-text.json + Return shotPrefix="${SHOTS}/r${r}" and the list of appliedChanges (human-readable). + Total candidate adjustments this round: ${allAdj.length}.`, + { label: `synth@r${r}`, phase: 'Synthesize', schema: SYNTH_SCHEMA }, + ); + if (!synth || !synth.shotPrefix) { history.push({ round: r, error: 'synth failed' }); break; } + shotPrefix = synth.shotPrefix; +} + +phase('Final'); +const final = await agent( + `Final review of the tuned Antigravity theme. + Read ${shotPrefix}-full.png, ${shotPrefix}-sidebar.png, ${shotPrefix}-center.png and the template ${TMPL}. + Write a concise markdown report to ${LAB}/REPORT-antigravity.md covering: final look per surface (wallpaper/sidebar/chat-card/text/accent/menus), the iteration score history (${JSON.stringify(history)}), the final set of shadcn/vscode token overrides + module rules, and any remaining nits (incl. the code-editor/Monaco caveat — syntax colours stay light unless the user sets Antigravity to a dark colour theme). + Return perDimension final scores (composition, readability, glass, accent, seams), a one-paragraph summary, and reportPath="${LAB}/REPORT-antigravity.md".`, + { label: 'final-report', phase: 'Final', schema: FINAL_SCHEMA }, +); + +return { history, final, lastShotPrefix: shotPrefix }; diff --git a/src-tauri/src/theme.rs b/src-tauri/src/theme.rs index 3943b09..6f922ae 100644 --- a/src-tauri/src/theme.rs +++ b/src-tauri/src/theme.rs @@ -239,20 +239,6 @@ fn wrap_injection_css(css: &str, agent_label: &str) -> String { ) } -/// Common CSS snippet for body background image. -fn body_bg_css(bg_data_uri: &str) -> String { - format!( - r#"body {{ - background-image: url('{bg}') !important; - background-size: cover !important; - background-position: center !important; - background-repeat: no-repeat !important; - background-attachment: fixed !important; - }}"#, - bg = bg_data_uri, - ) -} - /// Concrete style values after applying defaults. Missing knobs fall back to a /// neutral dark-glass look so themes without a `style` block still render well. struct ResolvedStyle { @@ -522,60 +508,350 @@ fn generate_codex_injection_script(theme: &Theme) -> Result { Ok(wrap_injection_css(&css, "Codex")) } +/// Modular, design-token-driven theme for Antigravity (a VS Code fork running a +/// Tailwind/shadcn agent UI seeded in Catppuccin Latte LIGHT mode). Overriding +/// the ~12 shadcn semantic tokens (`--background`/`--card`/`--sidebar`/...) plus +/// the key `--vscode-*` workbench surfaces reskins the whole agent UI to a dark +/// glass over the wallpaper, self-contained (no dependence on the user's Monaco +/// colour theme). Wallpaper is a `position:fixed` `html::before` pseudo-element +/// (NOT `background-attachment:fixed`, which deadlocks `Page.captureScreenshot` +/// when a `backdrop-filter` samples it). The `--cl-*` knobs are the SAME ones the +/// Codex theme consumes, so each theme is colour-matched across both agents. +/// Placeholders are filled from `ResolvedStyle`; structure tuned in `.theme-lab`. +const ANTIGRAVITY_CSS_TEMPLATE: &str = r#":root{ +--cl-ink:__INK__;--cl-ink-2:__INK2__;--cl-ink-3:__INK3__;--cl-ink-4:__INK4__; +--cl-surface:__SURFACE__;--cl-glass:__GLASS__;--cl-glass-soft:__GLASS_SOFT__;--cl-glass-strong:__GLASS_STRONG__; +--cl-border:__BORDER__;--cl-border-soft:__BORDER_SOFT__;--cl-border-strong:__BORDER_STRONG__; +--cl-blur:__BLUR__;--cl-hover:__HOVER__;--cl-selection:__SELECTION__; +--cl-scrim-top:__SCRIM_TOP__;--cl-scrim-mid:__SCRIM_MID__;--cl-scrim-bot:__SCRIM_BOT__; +} +html{ +color-scheme:dark !important; +/* visible warm hover/interaction wash — Antigravity's row hovers use bg-muted / + bg-sidebar-muted / bg-secondary etc. The dark glass we map them to is an almost + invisible darkening, and the unmapped sidebar-* seeds fall back to Catppuccin's + LIGHT value (the bright pill bug). Derive one assertive wash from the theme ink + so hover/active reads clearly on every theme without touching the shared knobs. */ +--cl-hover-strong:color-mix(in srgb,var(--cl-ink) 15%,transparent); +/* shadcn semantic tokens — the whole Antigravity agent UI is driven by these. + Override the seeds and every bg-*/text-* utility reskins at once. */ +--background:transparent !important; +--foreground:var(--cl-ink) !important; +--card:var(--cl-glass-soft) !important; +--card-foreground:var(--cl-ink) !important; +--card-border:var(--cl-border-strong) !important; +--popover:var(--cl-glass-strong) !important; +--popover-foreground:var(--cl-ink) !important; +--secondary:var(--cl-glass-soft) !important; +--secondary-foreground:var(--cl-ink-2) !important; +--muted:var(--cl-glass-soft) !important; +--muted-foreground:var(--cl-ink-3) !important; +--accent:var(--cl-hover-strong) !important; +--accent-foreground:var(--cl-ink) !important; +--border:var(--cl-border) !important; +/* inner chat field uses the denser glass(.60) not glass-soft(.52) so the placeholder + ink keeps a stable dark base where the bright hair shows through behind the card */ +--input:var(--cl-glass) !important; +--ring:var(--cl-border-strong) !important; +--sidebar:var(--cl-glass) !important; +--sidebar-foreground:var(--cl-ink-2) !important; +--sidebar-border:var(--cl-border-soft) !important; +--sidebar-accent:var(--cl-hover-strong) !important; +--sidebar-accent-foreground:var(--cl-ink) !important; +/* the ACTIVE conversation row uses `bg-sidebar-secondary` — a token we never overrode, + so it fell back to Catppuccin-Latte LIGHT (color(srgb .84 .85 .86) ≈ a near-white pill). + It hid from the lightscan because that token is written in color(srgb 0-1) notation, which + the 0-255 luminance parser read as near-black. Map it to a warm accent wash so the selected + conversation reads as an on-theme highlight, not a bright pill. */ +--sidebar-secondary:color-mix(in srgb,var(--cl-accent,var(--cl-ink)) 22%,transparent) !important; +--sidebar-secondary-foreground:var(--cl-ink) !important; +/* vscode workbench surfaces (standalone-window chrome + any IDE bits) */ +--vscode-foreground:var(--cl-ink) !important; +--vscode-editor-background:transparent !important; +--vscode-sideBar-background:var(--cl-glass) !important; +--vscode-sideBarSectionHeader-background:transparent !important; +--vscode-input-background:var(--cl-glass-soft) !important; +--vscode-dropdown-background:var(--cl-glass-strong) !important; +--vscode-menu-background:var(--cl-glass-strong) !important; +--vscode-quickInput-background:var(--cl-glass-strong) !important; +--vscode-list-hoverBackground:var(--cl-hover) !important; +--vscode-list-activeSelectionBackground:var(--cl-selection) !important; +--vscode-icon-foreground:var(--cl-ink-2) !important; +--vscode-descriptionForeground:var(--cl-ink-3) !important; +--vscode-titleBar-activeBackground:transparent !important; +--vscode-titleBar-inactiveBackground:transparent !important; +} +/* wallpaper as a position:fixed layer (NOT background-attachment:fixed — that + deadlocks Page.captureScreenshot when a backdrop-filter samples it; a fixed + pseudo-element composites cleanly and blur still picks it up). */ +html{background:__BASECOLOR__ !important;} +html::before{ +content:'';position:fixed;inset:0;z-index:-1;pointer-events:none; +background:url('__HERO__') __POS__ / __FIT__ no-repeat; +} +body,body.theme-light,body.theme-dark{background:transparent !important;} +#root,#root>div,#root>div>div,.bg-background{background-color:transparent !important;} +/* legibility scrim over the wallpaper (sits behind the glass panels). Three layers, + all folded into #root (a normal-flow element — adding a SECOND position:fixed layer + would deadlock Page.captureScreenshot when the card backdrop-filter samples it): + (1) a top-band damper — the lit hair/face highlight sits upper-center under the + thinnest scrim, so an extra darkening band over the top ~36% rebalances the + top-heavy image brightness; (2) a radial plate centered where the chat card lands + (~50%/62%) so the focal card rests on calmer value instead of the bright neck; + (3) the base linear, mid darkening pulled up to 40% (brightest content is + upper-center) with the very bottom softened via color-mix so the warm city-light + bokeh survives as atmosphere. All via the shared --cl-scrim-* knobs. */ +#root{ +background: + linear-gradient(180deg,color-mix(in srgb,var(--cl-scrim-bot) 55%,transparent) 0%,transparent 36%), + radial-gradient(120% 75% at 50% 62%, color-mix(in srgb,var(--cl-scrim-bot) 55%,transparent) 0%, transparent 60%), + linear-gradient(180deg,var(--cl-scrim-top) 0%,var(--cl-scrim-mid) 40%,color-mix(in srgb,var(--cl-scrim-bot) 82%,transparent) 92%,color-mix(in srgb,var(--cl-scrim-bot) 82%,transparent) 100%) !important; +} +/* ── hover affordance. Antigravity's interactive rows/buttons hover via the + Tailwind utilities below; some resolve to a dark glass (imperceptible darkening), + the sidebar-* ones were never overridden so they fell back to Catppuccin's LIGHT + seed (the bright pill). Force one clearly-visible warm wash on the actual :hover + state. Attribute selectors ([class~="hover:bg-…"]) are used on purpose — escaped + class selectors (.hover\:bg-…) get mangled when the CSS is injected through a JS + template literal, but the colon survives fine inside an attribute string. These + utilities have ZERO static (non-hover) usage, so this only ever paints on hover. */ +html [class~="hover:bg-muted"]:hover, +html [class~="hover:bg-sidebar-muted"]:hover, +html [class~="hover:bg-secondary"]:hover, +html [class~="hover:bg-sidebar-secondary"]:hover, +html [class~="hover:bg-accent"]:hover, +html [class~="hover:bg-sidebar-accent"]:hover{ +background-color:var(--cl-hover-strong) !important; +} +/* settings (and any) modal: the card is `bg-background` which we force transparent + app-wide, so the whole panel went see-through and the home view bled through it + (it is NOT role="dialog", so the popover rule never caught it). Frost the modal + card itself, and darken the weak bg-black/30 backdrop so nothing behind shows. */ +html .bg-background.rounded-2xl.shadow-2xl{ +background:var(--cl-glass-strong) !important; +-webkit-backdrop-filter:blur(calc(var(--cl-blur) + 6px)) saturate(120%);backdrop-filter:blur(calc(var(--cl-blur) + 6px)) saturate(120%); +} +html [class*="inset-0"][class*="bg-black/"]{background-color:var(--cl-scrim-bot) !important;} +/* conversation sidebar — bump the right edge from border-soft(.07) to border(.14) + so the sidebar↔main seam stays legible up into the lighter upper hero, and + stack the scrim-bot wash under the glass so the dim 9d/10d timestamps that live + here get a consistent darker substrate. +2px blur to match the upgraded card. */ +html [role="navigation"][aria-label="Sidebar"],html .bg-sidebar{ +background:linear-gradient(var(--cl-scrim-bot),var(--cl-scrim-bot)),var(--cl-glass) !important;border-right:1px solid var(--cl-border) !important; +-webkit-backdrop-filter:blur(calc(var(--cl-blur) + 2px)) saturate(118%);backdrop-filter:blur(calc(var(--cl-blur) + 2px)) saturate(118%); +} +/* the centered agent input card: bg-card-border is the 1px frame, bg-card the + field. Blur the INNER field only — blurring the outer frame (which wraps the + text-input control) deadlocks Page.captureScreenshot. The frame is just a + crisp border colour + drop shadow. */ +html .bg-card-border{ +background:transparent !important; +border:1px solid var(--cl-border-strong) !important; +box-shadow:0 0 0 1px var(--cl-border-soft),0 12px 34px rgba(0,0,0,.5),inset 0 1px 0 rgba(255,255,255,.08) !important; +} +/* focus-within accent ring on the chat card: makes the warm --cl-focus gold appear + in normal use (no focused control was previously visible to verify the ring). */ +html .bg-card-border:focus-within{ +border-color:color-mix(in srgb,var(--cl-focus) 55%,transparent) !important; +box-shadow:0 0 0 1px color-mix(in srgb,var(--cl-focus) 45%,transparent),0 12px 34px rgba(0,0,0,.5),inset 0 1px 0 rgba(255,255,255,.08) !important; +} +/* the focal input field must read as a CONFIDENT frosted plane, not a transparent + smudge: it is centered over the single brightest, highest-frequency region of the + wallpaper (the hair). Jump from glass-soft(.52) to glass-strong(.78) — the denser + fill is what actually hides the hair here. Keep blur at the known-good +4px: + raising the card backdrop-filter beyond that deadlocks the retina center capture, + and at .78 fill the extra blur is no longer needed to sell the frosted read. */ +html .bg-card{ +background:var(--cl-glass-strong) !important; +-webkit-backdrop-filter:blur(calc(var(--cl-blur) + 4px)) saturate(120%);backdrop-filter:blur(calc(var(--cl-blur) + 4px)) saturate(120%); +} +/* dialogs / menus / popovers / dropdowns — frosted glass (portaled to body, so + not nested in the sidebar/card; safe to blur). */ +html [role="menu"],html [role="listbox"],html .bg-popover,html [data-radix-popper-content-wrapper] [role="menu"]{ +background-color:var(--cl-glass-strong) !important; +-webkit-backdrop-filter:blur(calc(var(--cl-blur) + 6px)) saturate(120%);backdrop-filter:blur(calc(var(--cl-blur) + 6px)) saturate(120%); +box-shadow:0 8px 24px rgba(0,0,0,.40) !important; +} +/* text legibility over the hero area. Stronger solid-core component so the bare-hero + 'antigravity-pets' header glyphs detach cleanly from high-contrast hair edges + (the old .45/.35 diffuse halo was too soft to anchor them on the busiest backdrop). */ +html .text-foreground{text-shadow:0 1px 3px rgba(0,0,0,.6),0 0 2px rgba(0,0,0,.5),0 0 8px rgba(0,0,0,.35);} +/* tight near-opaque 2px core for the dim grey 9d/10d timestamps + placeholder so the + worst-readable ~2.75:1 text gets a hard dark outline that survives over bright hair, + without lightening the shared theme.json ink tokens. */ +html .text-muted-foreground,html [class*="placeholder"]{text-shadow:0 1px 2px rgba(0,0,0,.85),0 0 6px rgba(0,0,0,.65),0 1px 10px rgba(0,0,0,.5);} +/* top-bar seam: an app-shell header/toolbar wrapper composites an additive lighter + wash over the hero (measured ~145,99,81 bar vs ~89,50,37 hero), so the 40px bar + reads as a brighter band rather than the same frosted glass. Force it transparent + with the body blur, and replace the accidental brightness step with an intentional + changli hairline under the bar. */ +/* keep the bar TRANSPARENT (no new backdrop-filter here: blurring the top wrapper, + which can enclose the captured surface, deadlocks Page.captureScreenshot). The body + blur already shows through; we just kill the additive wash + add an intentional + hairline so the bar reads as the same glass rather than a brighter band. */ +html [class*="titlebar"],html [class*="title-bar"],html [role="toolbar"]:first-of-type{ +background:transparent !important; +box-shadow:none !important;border-bottom:1px solid var(--cl-border-soft) !important; +} +/* reskin the cool grey 'Open IDE' / secondary pills (measured ~101,86,81) onto warm + changli glass + ink so the top-right control harmonises with the art instead of + reading as a leftover neutral default surface. */ +html [aria-label*="Open IDE" i],html button[class*="secondary"]{ +background:var(--cl-glass-strong) !important;color:var(--cl-ink) !important;border:1px solid var(--cl-border) !important; +} +__ACCENT_BLOCK__ +/* ── accent harmony (uses --cl-accent/--cl-accent-soft/--cl-focus from the block + above). The Antigravity composer is a React/shadcn UI that does NOT read + --vscode-button-background or .bg-primary, so the primary action + selector chips + carried zero brand colour. Target their real selectors so the warm accent actually + renders and the --primary-foreground on-accent contrast gets exercised. ── */ +html button[aria-label="Send" i],html button[aria-label*="send" i],html button[data-testid*="send" i],html .composer-send-button{ +background:var(--cl-accent) !important;color:var(--primary-foreground) !important; +border-color:color-mix(in srgb,var(--cl-accent) 45%,transparent) !important; +} +html button[aria-label="Send" i] svg,html button[aria-label*="send" i] svg,html button[data-testid*="send" i] svg,html .composer-send-button svg{ +color:var(--primary-foreground) !important;opacity:1 !important; +} +/* model/agent selector chips: warm tint on the active/checked icon + border instead + of inert grey; also finally puts --cl-accent-soft to use. */ +html [data-state="checked"],html [data-state="on"],html [aria-selected="true"]{ +border-color:color-mix(in srgb,var(--cl-accent) 50%,transparent) !important;color:var(--cl-accent-soft) !important; +} +html [data-state="checked"] svg,html [data-state="on"] svg{color:var(--cl-accent) !important;} +/* ── conversation-transcript markdown prose. Antigravity renders message bodies with + GitHub-flavoured-markdown + PrismJS seeded for a LIGHT colour theme, so three things + leak through over the dark glass: a blockquote gets an opaque rgb(242,242,242) panel + (a glaring bright box), inline uses #a31515 dark-red (lum 51 — near-invisible + on dark), and fenced code blocks colour their Prism tokens with a light syntax theme + (property #990055 magenta / string #669900 olive — low contrast). Every in the + transcript is INLINE (fenced blocks render via .token s, never ), so + `html code` reskins inline code only. Warm changli knobs with literal fallbacks so + accent-less themes still get legible colours. ── */ +/* blockquote: kill the light panel → calm dark-glass plate with a warm accent rule + (the seed bar was a cool #007acc blue). */ +html blockquote{ +background:var(--cl-glass) !important; +border-left:3px solid var(--cl-accent,#e08a55) !important;border-radius:0 8px 8px 0 !important; +} +/* inline code: warm chip — recolour the dark-red token to soft warm ink on a faint + warm fill + hairline so it still reads as code, not body text. */ +html code{ +color:var(--cl-accent-soft,#e6b48a) !important; +background:color-mix(in srgb,var(--cl-ink) 9%,transparent) !important; +border:1px solid var(--cl-border-soft) !important;border-radius:5px !important;padding:1px 5px !important; +} +/* fenced code block: faint contained plate so the syntax tokens sit on a consistent + darker substrate (the block bg was transparent → tokens floated on the wallpaper). */ +html pre{ +background:var(--cl-glass) !important;border:1px solid var(--cl-border-soft) !important;border-radius:8px !important; +} +/* re-map the light PrismJS syntax theme to a warm-dark changli palette. Tokens are + class-based (.token.*, which only ever exist inside code) so this is safe: gold + keywords/keys, sage strings, orange numbers, peach functions, muted comments/punct. */ +html .token.comment,html .token.prolog,html .token.doctype,html .token.cdata{color:var(--cl-ink-3) !important;font-style:italic;} +html .token.punctuation,html .token.operator{color:var(--cl-ink-2) !important;} +html .token.property,html .token.tag,html .token.keyword,html .token.selector,html .token.attr-name,html .token.boolean{color:var(--cl-focus,#ffce86) !important;} +html .token.string,html .token.char,html .token.attr-value,html .token.inserted,html .token.regex{color:#a8c187 !important;} +html .token.number,html .token.constant,html .token.symbol{color:var(--cl-accent,#e08a55) !important;} +html .token.function,html .token.class-name,html .token.builtin,html .token.atrule{color:var(--cl-accent-soft,#e6b48a) !important;} +html .token.important,html .token.bold{font-weight:600;} +/* GFM alert callouts (NOTE/TIP/IMPORTANT/WARNING/CAUTION): contained glass plate for + legibility on any backdrop; warm the brand-mauve "important" accent (rgb(130,80,223) + = the #8839EF brand purple we de-brand everywhere else) to the changli focus gold. + Other alert types keep their semantic hue but gain the plate. */ +html .markdown-alert{background:var(--cl-glass) !important;border-radius:0 8px 8px 0 !important;} +html .markdown-alert-important{border-left-color:var(--cl-focus,#ffce86) !important;} +html .markdown-alert-important .markdown-alert-title{color:var(--cl-focus,#ffce86) !important;} +/* ── per-turn sticky-header scroll-fade. Each message group has a `sticky top-0` + header whose ::after is a ~28px scroll-fade built with Tailwind's `after:from-background`. + That gradient stop resolves to the Catppuccin-Latte LIGHT --background (rgb 234,236,240) + regardless of our `--background:transparent` override, so a BRIGHT horizontal bar + flashed under every turn (the "venetian-blind" banding over the wallpaper). Pseudo-element + gradients are invisible to both the shadcn-token overrides and the lightscan, which is why + this leaked the longest. Repaint the fade from a soft warm-dark scrim → transparent so + content dissolves into the theme instead of a light band. ── */ +html [class*="after:from-background"]::after{ +background-image:linear-gradient(color-mix(in srgb,var(--cl-scrim-bot) 78%,transparent),transparent) !important; +} +/* bottom-of-transcript scroll-fade: an `absolute bottom-0 inset-x-4 pointer-events-none` + overlay dissolves the last messages into the composer with + `linear-gradient(to top, rgb(234,236,240), transparent)` — the Latte LIGHT --background + AGAIN, but here the colour is INLINED (no `from-background` class) and the layer is + pointer-events:none, so it dodged BOTH the class scan AND elementsFromPoint (which skips + pointer-events:none). Found only via a full-tree light-stop-gradient sweep. Repaint it as + a warm-dark fade so the transcript dissolves into the bottom scrim, not a bright bar above + the input. */ +html [class~="bottom-0"][class~="inset-x-4"][class~="pointer-events-none"]{ +background-image:linear-gradient(to top,color-mix(in srgb,var(--cl-scrim-bot) 85%,transparent),transparent) !important; +} +"#; + +/// Accent-cohesion block for Antigravity, emitted only when the theme declares +/// `style.accent`. Remaps Antigravity's brand `--primary` (#8839EF mauve) and the +/// link/focus/selection accents to the theme accent; on-accent text uses the +/// theme's dark `base_color` for contrast. Omitted entirely for accent-less themes. +const ANTIGRAVITY_ACCENT_BLOCK: &str = r#":root{--cl-accent:__ACCENT__;--cl-accent-soft:__ACCENT_SOFT__;--cl-focus:__FOCUS__;} +html{ +--primary:var(--cl-accent) !important; +--primary-foreground:__BASECOLOR__ !important; +--ring:var(--cl-focus) !important; +--sidebar-primary:var(--cl-accent) !important; +--sidebar-ring:var(--cl-focus) !important; +--vscode-textLink-foreground:var(--cl-accent) !important; +--vscode-textLink-activeForeground:var(--cl-accent) !important; +--vscode-focusBorder:var(--cl-focus) !important; +--vscode-button-background:var(--cl-accent) !important; +--vscode-progressBar-background:var(--cl-accent) !important; +} +html a,html .text-primary{color:var(--cl-accent) !important;} +html .bg-primary{background-color:var(--cl-accent) !important;} +::selection{background:color-mix(in srgb,var(--cl-accent) 30%,transparent);}"#; + fn generate_antigravity_injection_script(theme: &Theme) -> Result { let bg = encode_background(theme)?; - let css = format!( - r#"{body_bg} - - #root, - #root > div, - #root > div > div, - #root > div > div > div {{ - background-color: transparent !important; - }} - - [role="navigation"][aria-label="Sidebar"], - .bg-sidebar {{ - background-color: rgba(26, 26, 26, 0.75) !important; - backdrop-filter: blur(12px) !important; - -webkit-backdrop-filter: blur(12px) !important; - border-right: 1px solid rgba(255, 255, 255, 0.1) !important; - }} - - .bg-background {{ - background-color: transparent !important; - }} - - :root {{ - --background: rgba(0, 0, 0, 0) !important; - --foreground: rgba(255, 255, 255, 0.95) !important; - --sidebar: rgba(26, 26, 26, 0.75) !important; - }} - - [role="dialog"], - .bg-card {{ - background-color: rgba(26, 26, 26, 0.75) !important; - backdrop-filter: blur(12px) !important; - -webkit-backdrop-filter: blur(12px) !important; - border: 1px solid rgba(255, 255, 255, 0.1) !important; - }} - - #root::before {{ - content: ''; - position: fixed; - top: 0; left: 0; - width: 100vw; height: 100vh; - background-color: rgba(0, 0, 0, 0.4) !important; - pointer-events: none; - z-index: 1; - }} - - #root > * {{ - position: relative; - z-index: 2; - }}"#, - body_bg = body_bg_css(&bg), - ); + let st = resolve_style(&theme.style); + let pos = theme + .background_position + .clone() + .unwrap_or_else(|| "center top".to_string()); + let fit = theme + .background_fit + .clone() + .unwrap_or_else(|| "cover".to_string()); + + let accent_block = match &st.accent { + Some(a) => ANTIGRAVITY_ACCENT_BLOCK + .replace("__ACCENT_SOFT__", &st.accent_soft) + .replace("__FOCUS__", &st.focus) + .replace("__BASECOLOR__", &st.base_color) + .replace("__ACCENT__", a), + None => String::new(), + }; + + let css = ANTIGRAVITY_CSS_TEMPLATE + .replace("__HERO__", &bg) + .replace("__BASECOLOR__", &st.base_color) + .replace("__POS__", &pos) + .replace("__FIT__", &fit) + .replace("__INK2__", &st.ink2) + .replace("__INK3__", &st.ink3) + .replace("__INK4__", &st.ink4) + .replace("__INK__", &st.ink) + .replace("__SURFACE__", &st.surface) + .replace("__GLASS_STRONG__", &st.glass_strong) + .replace("__GLASS_SOFT__", &st.glass_soft) + .replace("__GLASS__", &st.glass) + .replace("__BORDER_STRONG__", &st.border_strong) + .replace("__BORDER_SOFT__", &st.border_soft) + .replace("__BORDER__", &st.border) + .replace("__BLUR__", &st.blur) + .replace("__HOVER__", &st.hover) + .replace("__SELECTION__", &st.selection) + .replace("__SCRIM_TOP__", &st.scrim_top) + .replace("__SCRIM_MID__", &st.scrim_mid) + .replace("__SCRIM_BOT__", &st.scrim_bot) + .replace("__ACCENT_BLOCK__", &accent_block); + Ok(wrap_injection_css(&css, "Antigravity")) } @@ -663,4 +939,54 @@ mod tests { // default background position assert!(script.contains("center top")); } + + #[test] + fn antigravity_script_applies_modular_style() { + let theme = load_changli(); + let script = generate_injection_script(&theme, &AgentKind::Antigravity).unwrap(); + // background image inlined as a data URI + assert!(script.contains("data:image/jpeg;base64,")); + // the same --cl-* knobs as Codex feed the shadcn semantic tokens + assert!(script.contains("--card:var(--cl-glass-soft)")); + assert!(script.contains("--sidebar:var(--cl-glass)")); + assert!(script.contains("--cl-ink:#f4ebdf")); + // real Antigravity shadcn/vscode selectors (not the Codex ones) + assert!(script.contains(r#"[role="navigation"][aria-label="Sidebar"]"#)); + assert!(script.contains(".bg-card")); + // wallpaper is painted by the fixed pseudo-element layer + assert!(script.contains("html::before")); + // warm accent from style.accent remaps the brand --primary + send button + assert!(script.contains("#e08a55")); + assert!(script.contains("--primary:var(--cl-accent)")); + assert!(script.contains(r#"button[aria-label="Send" i]"#)); + // background position from theme.json + assert!(script.contains("50% 4%")); + // conversation-transcript markdown prose is reskinned: blockquote plate, + // inline-code chip, Prism token remap, and the de-branded "important" alert + assert!(script.contains("html blockquote{")); + assert!(script.contains(".token.property")); + assert!(script.contains("html .markdown-alert-important{border-left-color:var(--cl-focus")); + // the per-turn sticky-header ::after scroll-fade light bar is repainted dark + assert!(script.contains(r#"[class*="after:from-background"]::after"#)); + // ISOLATION: must NOT carry any Codex-only selectors/tokens + assert!(!script.contains(".app-shell-left-panel")); + assert!(!script.contains("--color-token-main-surface-primary")); + if let Ok(path) = std::env::var("DUMP_ANTIGRAVITY_SCRIPT") { + std::fs::write(path, &script).unwrap(); + } + } + + #[test] + fn antigravity_script_falls_back_to_neutral_defaults_without_style() { + let mut theme = load_changli(); + theme.style = None; + theme.background_position = None; + let script = generate_injection_script(&theme, &AgentKind::Antigravity).unwrap(); + // neutral default ink colour is used + assert!(script.contains("--cl-ink:#f1ece4")); + // no accent declared -> accent block omitted (brand --primary remap absent) + assert!(!script.contains("--primary:var(--cl-accent)")); + // default background position + assert!(script.contains("center top")); + } } diff --git a/themes/azurlane/theme.json b/themes/azurlane/theme.json index 7263b7f..4f97759 100644 --- a/themes/azurlane/theme.json +++ b/themes/azurlane/theme.json @@ -23,9 +23,9 @@ "blur": "6px", "hover": "rgba(206,224,244,0.10)", "selection": "rgba(206,224,244,0.16)", - "scrimTop": "rgba(12,18,28,0.26)", - "scrimMid": "rgba(12,18,28,0.34)", - "scrimBot": "rgba(12,18,28,0.60)", + "scrimTop": "rgba(10,15,24,0.60)", + "scrimMid": "rgba(9,13,22,0.68)", + "scrimBot": "rgba(7,11,18,0.74)", "baseColor": "#0c121c" } } diff --git a/themes/duet/theme.json b/themes/duet/theme.json index 93bf5a5..2bb203e 100644 --- a/themes/duet/theme.json +++ b/themes/duet/theme.json @@ -23,9 +23,9 @@ "blur": "6px", "hover": "rgba(238,241,247,0.10)", "selection": "rgba(238,241,247,0.16)", - "scrimTop": "rgba(10,13,22,0.26)", - "scrimMid": "rgba(10,13,22,0.34)", - "scrimBot": "rgba(10,13,22,0.60)", + "scrimTop": "rgba(8,11,18,0.46)", + "scrimMid": "rgba(7,10,16,0.54)", + "scrimBot": "rgba(6,8,14,0.66)", "baseColor": "#0a0d16" } } diff --git a/themes/frost/theme.json b/themes/frost/theme.json index 7d57339..03fef96 100644 --- a/themes/frost/theme.json +++ b/themes/frost/theme.json @@ -23,9 +23,9 @@ "blur": "6px", "hover": "rgba(206,214,235,0.10)", "selection": "rgba(206,214,235,0.16)", - "scrimTop": "rgba(14,16,24,0.26)", - "scrimMid": "rgba(14,16,24,0.34)", - "scrimBot": "rgba(14,16,24,0.60)", + "scrimTop": "rgba(12,14,22,0.54)", + "scrimMid": "rgba(11,13,20,0.62)", + "scrimBot": "rgba(9,11,18,0.72)", "baseColor": "#0e1018" } } diff --git a/themes/nailin/theme.json b/themes/nailin/theme.json index 25fdfae..e7b3dfb 100644 --- a/themes/nailin/theme.json +++ b/themes/nailin/theme.json @@ -23,9 +23,9 @@ "blur": "6px", "hover": "rgba(247,233,218,0.10)", "selection": "rgba(247,233,218,0.16)", - "scrimTop": "rgba(10,6,4,0.26)", - "scrimMid": "rgba(10,6,4,0.34)", - "scrimBot": "rgba(10,6,4,0.60)", + "scrimTop": "rgba(11,7,4,0.58)", + "scrimMid": "rgba(10,6,3,0.68)", + "scrimBot": "rgba(8,5,3,0.82)", "baseColor": "#0a0604" } } diff --git a/themes/nocturne/theme.json b/themes/nocturne/theme.json index db519c9..6ac02dc 100644 --- a/themes/nocturne/theme.json +++ b/themes/nocturne/theme.json @@ -23,9 +23,9 @@ "blur": "6px", "hover": "rgba(205,221,228,0.10)", "selection": "rgba(124,197,214,0.16)", - "scrimTop": "rgba(13,17,21,0.26)", - "scrimMid": "rgba(13,17,21,0.34)", - "scrimBot": "rgba(13,17,21,0.60)", + "scrimTop": "rgba(11,14,20,0.48)", + "scrimMid": "rgba(10,13,18,0.56)", + "scrimBot": "rgba(8,11,16,0.68)", "baseColor": "#0d1115" } } diff --git a/themes/rose/theme.json b/themes/rose/theme.json index 54a12be..ecb9eff 100644 --- a/themes/rose/theme.json +++ b/themes/rose/theme.json @@ -23,9 +23,9 @@ "blur": "6px", "hover": "rgba(248,224,224,0.10)", "selection": "rgba(248,224,224,0.16)", - "scrimTop": "rgba(18,11,14,0.26)", - "scrimMid": "rgba(18,11,14,0.34)", - "scrimBot": "rgba(18,11,14,0.60)", + "scrimTop": "rgba(16,9,12,0.40)", + "scrimMid": "rgba(15,8,11,0.48)", + "scrimBot": "rgba(12,7,9,0.66)", "baseColor": "#120b0e" } } diff --git a/themes/sonata/theme.json b/themes/sonata/theme.json index 4fd825f..5830d18 100644 --- a/themes/sonata/theme.json +++ b/themes/sonata/theme.json @@ -23,9 +23,9 @@ "blur": "6px", "hover": "rgba(216,224,242,0.10)", "selection": "rgba(216,224,242,0.16)", - "scrimTop": "rgba(15,18,30,0.26)", - "scrimMid": "rgba(15,18,30,0.34)", - "scrimBot": "rgba(15,18,30,0.60)", + "scrimTop": "rgba(13,16,28,0.42)", + "scrimMid": "rgba(12,15,26,0.50)", + "scrimBot": "rgba(10,12,22,0.64)", "baseColor": "#0f121e" } } diff --git a/themes/zani/theme.json b/themes/zani/theme.json index 9506e78..cc7c8a2 100644 --- a/themes/zani/theme.json +++ b/themes/zani/theme.json @@ -23,9 +23,9 @@ "blur": "6px", "hover": "rgba(241,238,242,0.10)", "selection": "rgba(241,238,242,0.16)", - "scrimTop": "rgba(16,12,18,0.26)", - "scrimMid": "rgba(16,12,18,0.34)", - "scrimBot": "rgba(16,12,18,0.60)", + "scrimTop": "rgba(14,10,16,0.44)", + "scrimMid": "rgba(13,9,15,0.60)", + "scrimBot": "rgba(10,7,12,0.82)", "baseColor": "#100c12" } } From a8428874d1207af48f8de3281ef4e538a750d4b9 Mon Sep 17 00:00:00 2001 From: Cmochance <3216202644@qq.com> Date: Mon, 1 Jun 2026 15:27:42 +0800 Subject: [PATCH 2/2] docs: restructure README + add Antigravity theme showcase Reorganize README.md / README.en.md following the codex-app-transfer layout: NOTE callout, nav + badges, theme showcase, features, install, quick start, theme management (full theme.json schema + --cl-* knob reference), architecture (updated to Svelte + Vite), dev guide, FAQ, disclaimer, license, activity. - correct the theme count (5 -> 11) and list all built-in themes - update the stale architecture section (web/ Vanilla JS -> src/ Svelte + Vite) - add a disclaimer covering the character artwork + CDP runtime safety - add two Antigravity hero screenshots (changli / frost) under docs/antigravity/; sidebar + input text blurred for privacy --- README.en.md | 252 ++++++++++++++++------------------ README.md | 256 ++++++++++++++++------------------- docs/antigravity/changli.jpg | Bin 0 -> 70579 bytes docs/antigravity/frost.jpg | Bin 0 -> 70785 bytes 4 files changed, 230 insertions(+), 278 deletions(-) create mode 100644 docs/antigravity/changli.jpg create mode 100644 docs/antigravity/frost.jpg diff --git a/README.en.md b/README.en.md index b5ee67f..c02cef8 100644 --- a/README.en.md +++ b/README.en.md @@ -1,109 +1,88 @@ # Agent Theme > [!NOTE] -> 🎨 **Agent Theme** is a standalone theme companion app for Codex Desktop / Antigravity. -> It injects custom CSS into the agent's WebView via Chrome DevTools Protocol, creating frosted-glass + character background visual themes. -> Comes with 5 built-in themes, supports custom upload & crop — one-click theming without modifying agent source code. +> 🎨 **Agent Theme** is a standalone theming companion for **Codex Desktop / Antigravity**. +> It injects CSS into the agent's WebView at runtime via the Chrome DevTools Protocol to render a "character wallpaper + frosted-glass panels" look — **without touching the agent's source, fully reversible**. +> Ships **11** built-in anime themes, each colour-matched to its own background image, plus custom upload & crop. One click to reskin.

简体中文 | - English + English | + Releases

GitHub stars License Rust - Tauri + Tauri Platform

-## Table of Contents +Agent Theme is a standalone desktop app (Tauri v2). The agent (Codex Desktop / Antigravity) is launched with `--remote-debugging-port` to expose a CDP port; this app connects over WebSocket and uses `Page.addScriptToEvaluateOnNewDocument` to inject a script before the page loads — adding a background layer, overriding the agent UI's design tokens, and applying `backdrop-filter` frosted glass to each panel. The whole theme lives only at runtime: turn off the toggle or restart the agent and it disappears, **without modifying the agent's binary or any config file**. -- [Overview](#overview) -- [Features](#features) -- [Built-in Themes](#built-in-themes) -- [Quick Start](#quick-start) -- [Theme Management](#theme-management) -- [Architecture](#architecture) -- [Development](#development) -- [FAQ](#faq) -- [License](#license) +## Theme Showcase -## Overview +Every theme is **colour-matched individually** to its own background — glass tinted from the image's dark tones, accent taken from the character's signature colour, and the legibility scrim calibrated per-wallpaper by brightness — so chat text stays readable on any image instead of hiding behind a uniform dark overlay. Below is the actual look on Antigravity (sidebar & input text blurred for privacy): -Agent Theme is a standalone desktop application (Tauri v2) that works alongside Codex Desktop or Antigravity. It dynamically injects CSS styles via Chrome DevTools Protocol (CDP) to personalize the agent's appearance without modifying any source code. +| 长离 · Changli | 霜银 · Frost | +|---|---| +| ![Changli](docs/antigravity/changli.jpg) | ![Frost](docs/antigravity/frost.jpg) | -**How it works:** The agent is launched with the `--remote-debugging-port` flag to expose a CDP port. Agent Theme connects to this port via WebSocket, uses `Page.addScriptToEvaluateOnNewDocument` to inject JavaScript that automatically inserts a background image and semi-transparent CSS overlay on every page load, achieving a frosted-glass visual effect. +**11** built-in themes (backgrounds are the respective character artworks — see [Disclaimer](#disclaimer)): -## Features - -- 🎨 **5 Built-in Themes:** Changli, Nailin, Zani, Azur Lane, Carton — switch with one click -- 🖼️ **Custom Themes:** Drag-and-drop image upload, crop, and save as custom themes -- 🔄 **Multi-Agent Support:** Works with both Codex Desktop and Antigravity, freely switchable -- 🚀 **Scoped Restart:** Companion can restart the selected Codex or Antigravity app with a local debug port -- 🔌 **CDP Injection:** Themes are injected via Chrome DevTools Protocol — safe, reversible, no source modification -- 📊 **Live Status:** Real-time display of agent process status and CDP port binding -- 💾 **Persistent Config:** All settings saved to `~/.codex/agent-theme/config.json`, survives restarts -- 🔒 **Single Instance:** Prevents running multiple companion windows simultaneously - -## Built-in Themes - -| Theme ID | Chinese | English | Preview | -|----------|---------|---------|---------| -| `changli` | 长离 | Changli | ![Changli](themes/changli/preview.jpg) | -| `nailin` | 奈琳 | Nailin | ![Nailin](themes/nailin/preview.jpg) | -| `zani` | 扎妮 | Zani | ![Zani](themes/zani/preview.jpg) | -| `azurlane` | 碧蓝航线 | Azur Lane | ![Azur Lane](themes/azurlane/preview.jpg) | -| `carton` | 纸箱 | Carton | ![Carton](themes/carton/preview.jpg) | +| ID | English | ID | English | +|---|---|---|---| +| `changli` | Changli | `nocturne` | Nocturne | +| `azurlane` | Azur Lane | `duet` | Duet | +| `sonata` | Sonata | `rose` | Rose | +| `zani` | Zani | `studio` | Studio | +| `nailin` | Nailin | `carton` | Carton | +| `frost` | Frost | | | -## Quick Start +> The same theme applies to both **Codex Desktop** and **Antigravity** — the two agents share theme.json's colour knobs, each injecting platform-appropriate token overrides. -### Prerequisites +## Features -- **macOS** (currently macOS-only; Windows/Linux support planned) -- **Codex Desktop** or **Antigravity** installed +- 🎨 **11 built-in themes** — each derives dark glass + accent + tiered text colours from its own background; one-click switch +- 🖼️ **Custom themes** — drag-drop an image → 1:1 crop → save as a local theme +- 🔄 **Dual-agent support** — works with both **Codex Desktop** and **Antigravity**, switchable from the top bar +- 🚀 **Scoped restart** — restart the current agent with the debug-port flag attached; only the two supported agents are touched, nothing else +- 🔌 **CDP runtime injection** — injected via Chrome DevTools Protocol, no source changes, clears back to the original look +- 📊 **Live status** — shows agent run state and CDP port binding in real time +- 💾 **Persistent config** — settings saved to `~/.codex/agent-theme/config.json` +- 🔒 **Single-instance guard** — prevents conflicting companion windows -### Installation +## Install 1. Download the latest `.dmg` from [Releases](https://github.com/Cmochance/agent-theme/releases) -2. Drag `Agent Theme Companion.app` into your Applications folder -3. On first launch, macOS Gatekeeper may block: right-click the app → choose "Open"; or go to `System Settings → Privacy & Security` and click "Open Anyway" -4. Launch Agent Theme Companion — the interface will display the agent's running status - -### Basic Usage +2. Drag `Agent Theme Companion.app` into Applications +3. If macOS Gatekeeper blocks it on first launch ("unidentified developer"): `right-click → Open` once, or go to `System Settings → Privacy & Security` and click "Open Anyway" +4. Launch — the UI shows the agent's run state -1. **Select Agent:** Choose Codex or Antigravity from the top switch bar -2. **Prepare Debug Port:** If the UI shows `No debug port`, click `Restart App`; the companion will restart the selected agent with local debug-port arguments -3. **Pick a Theme:** Click any theme card in the grid to preview and apply -4. **Toggle Switch:** The "Theme" toggle controls whether styles are injected; turn off to restore the agent's original appearance +> **macOS only** for now (`agent.rs` process detection & paths depend on `~/Library/Application Support/`). Windows / Linux support is planned. -### Local Debug Port Requirement +## Quick Start -If the agent is already running without a CDP port, Agent Theme cannot inject themes. Clicking `Restart App` stops the currently selected Codex or Antigravity process and starts it again with `--remote-debugging-port=0`. +1. **Pick an agent** — Codex or Antigravity from the top switcher +2. **Get a debug port** — if the UI shows `No debug port`, click `Restart App`; the companion stops the current agent and relaunches it with `--remote-debugging-port=0` +3. **Pick a theme** — click any card in the theme grid to preview & apply instantly +4. **Toggle** — the "Theme" switch controls injection; turning it off restores the agent's original look -Process management is scoped to the two supported agent apps. The companion does not clean lock files in the agent app data directory and does not modify the agent app bundle. Once the port is available, keep the "Theme" toggle enabled and click a theme card to inject it. +> Themes are injected via `Page.addScriptToEvaluateOnNewDocument` and take effect **only on page navigation / refresh**. They are lost when the agent restarts; the app re-injects automatically once a port is available — just keep the toggle on. ## Theme Management -### Using Built-in Themes - -Built-in themes are stored in the app's `themes/` resource directory. No additional configuration needed — select a theme and it takes effect immediately. - -### Creating Custom Themes +### Create a custom theme 1. Click the "+" card in the theme grid -2. Drag an image into the upload area (JPG/PNG, max 20MB) -3. Use the crop tool to adjust the background area -4. Click "Save & Apply" — the theme is saved to `~/.codex/agent-theme/themes/` - -### Deleting Custom Themes +2. Drop an image (JPG/PNG, ≤ 20MB) +3. Adjust the background region with the crop tool +4. "Save & Apply" — the theme is written to `~/.codex/agent-theme/themes/` -Hover over a custom theme card and click the delete button. Built-in themes cannot be deleted. +### theme.json structure -### Theme File Structure - -```json +```jsonc { "id": "changli", "displayName": { "zh": "长离 (Changli)", "en": "Changli" }, @@ -112,118 +91,115 @@ Hover over a custom theme card and click the delete button. Built-in themes cann "backgroundFit": "cover", "backgroundPosition": "50% 4%", "style": { - "ink": "#f4ebdf", - "accent": "#e08a55", - "glass": "rgba(30,21,14,.52)", - "glassStrong": "rgba(22,15,10,.72)", - "glassSoft": "rgba(26,18,12,.80)", - "blur": "26px", - "scrimTop": "rgba(18,12,8,.26)", - "scrimMid": "rgba(17,11,7,.34)", - "scrimBot": "rgba(11,7,5,.60)", + "ink": "#f4ebdf", "ink2": "rgba(244,235,223,.74)", + "ink3": "rgba(244,235,223,.56)", "ink4": "rgba(244,235,223,.40)", + "accent": "#e08a55", "accentSoft": "#e6b48a", "focus": "#ffce86", + "surface": "rgba(26,18,12,.50)", + "glass": "rgba(30,21,14,.60)", "glassSoft": "rgba(34,24,16,.52)", "glassStrong": "rgba(22,15,10,.78)", + "border": "rgba(255,228,201,.14)", "borderSoft": "rgba(255,228,201,.07)", "borderStrong": "rgba(255,228,201,.26)", + "blur": "6px", "hover": "rgba(255,236,210,.10)", "selection": "rgba(255,236,210,.16)", + "scrimTop": "rgba(18,12,8,.26)", "scrimMid": "rgba(17,11,7,.34)", "scrimBot": "rgba(11,7,5,.60)", "baseColor": "#160f0a" } } ``` -`background`/`backgroundFit`/`backgroundPosition` control the image itself; the optional `style` block drives the **modular Codex theme**: +`background` / `backgroundFit` / `backgroundPosition` control the wallpaper itself; the optional `style` block is a set of agent-agnostic **colour knobs** (`--cl-*`) that the Codex and Antigravity injectors each translate into platform-specific token overrides: -- **Token override** — for current Codex (Tailwind v4 + `--color-token-*` design tokens) the injection overrides semantic tokens rather than fixed container selectors, so it tracks app updates; main surfaces are made transparent to reveal the background image. -- **Per-module frosted glass** — sidebar (`glass`), composer input (`glassSoft`), and dialogs/menus (`glassStrong`) each use a `backdrop-filter` at a distinct opacity + `blur`, sampling the same background image behind them, so **each module's background matches perfectly by construction**. -- **Per-level text colours** — `ink`/`ink2`/`ink3`/`ink4` tune primary/secondary/tertiary/disabled text; `accent` unifies links, focus, and the send button into the character's signature colour. -- **Readability scrim** — `scrimTop`/`scrimMid`/`scrimBot` define a top-to-bottom darkening gradient that keeps text legible over the art. +- **Token overrides** — override the agent UI's semantic design tokens (not hard-coded container selectors), so they survive agent updates; the main surface is made transparent to reveal the wallpaper. +- **Per-panel frosted glass** — sidebar / input / dialogs each use a different `glass*` opacity + `blur` as a `backdrop-filter` that **samples the same wallpaper behind the panel** in real time, so each panel matches the background perfectly. +- **Tiered text colours** — `ink` / `ink2` / `ink3` / `ink4` map to body / secondary / tertiary / disabled; `accent` / `focus` unify links, focus, and the send button around the character's primary colour. +- **Legibility scrim** — `scrimTop` / `scrimMid` / `scrimBot` define a top-to-bottom darkening gradient. **Bright wallpapers need a stronger scrim** (or the chat text washes out), so each theme is calibrated per-image by brightness. -Omit `style` to fall back to neutral dark-glass defaults (works with any background image). The local, untracked `.theme-lab/` holds `build-assets.sh` (slices the source image into per-module crops at different sizes/opacities/masks) and `cdp.mjs` (the inject + screenshot tuning harness). +Omit `style` to fall back to neutral dark-glass defaults (works on any background). ## Architecture ``` agent-theme/ -├── src-tauri/ # Rust backend (Tauri v2) -│ ├── src/ -│ │ ├── main.rs # App entry point -│ │ ├── lib.rs # Tauri commands registration, lifecycle -│ │ ├── agent.rs # Agent process detection, launch, management -│ │ ├── cdp.rs # CDP WebSocket connection, theme inject/clear -│ │ ├── config.rs # Config read/write (AppConfig) -│ │ └── theme.rs # Theme discovery, CSS generation, custom theme CRUD -│ └── Cargo.toml -├── web/ # Frontend (Vanilla JS + esbuild) -│ ├── app.js # Main logic (status polling, theme switching, crop UI) -│ ├── index.html # Page structure -│ ├── style.css # Dark red-gold glassmorphism design system -│ └── dist/ # Build output (bundle.js) -└── themes/ # Built-in theme assets - ├── changli/ # Changli - ├── nailin/ # Nailin - ├── zani/ # Zani - ├── azurlane/ # Azur Lane - └── carton/ # Carton +├── src-tauri/ # Rust backend (Tauri v2) +│ └── src/ +│ ├── lib.rs # Tauri commands, lifecycle, state +│ ├── agent.rs # agent detect / launch / restart-with-debug-port +│ ├── cdp.rs # CDP WebSocket, theme inject / clear +│ ├── config.rs # config read/write (AppConfig) +│ └── theme.rs # theme discovery, CSS generation, custom-theme CRUD +├── src/ # frontend source (TypeScript + Svelte) +│ ├── App.svelte # root component +│ └── lib/ # types / tauri-commands / stores / actions / polling / components +├── index.html # Vite entry +├── web/ # Vite build output (Tauri frontendDist) +└── themes// # built-in theme assets (bg.jpg + preview.jpg + theme.json) ``` **Core tech:** -- **Backend:** Rust + Tauri 2.11.2, using `tokio-tungstenite` for CDP WebSocket, `sysinfo` for process detection, `reqwest` for HTTP probes -- **Frontend:** Vanilla JavaScript + esbuild bundling, calling Tauri commands via `@tauri-apps/api` -- **Injection mechanism:** Uses CDP `Page.addScriptToEvaluateOnNewDocument` to inject JavaScript before page load, dynamically creating `