Add 4 switchable themes (Anthropic / Instrument / Editorial / Retro CRT)
- @fontsource-variable/{fraunces,jetbrains-mono,newsreader} + @fontsource/{dm-sans,ibm-plex-mono} bundled offline.
- styles.css restructured: theme-agnostic base + 4 [data-theme="..."] overrides driving CSS custom props (--bg, --fg, --accent, --font-display, --font-body, --atmosphere).
- Each theme has its own typographic + chromatic personality:
* Anthropic: warm cream-on-charcoal, Newsreader display + DM Sans body, sunset orange + claude purple.
* Instrument: synth panel, JetBrains Mono throughout, chartreuse on slate, ring tick marks, faint scanlines, bracket corners on title bar.
* Editorial: magazine artifact, Fraunces variable serif (opsz axis), saffron on warm charcoal, hairline rules.
* Retro CRT: phosphor green on near-black, IBM Plex Mono, scanlines + vignette, blink-cursor in corner, [bracketed] header label.
- Settings panel: 4-up theme picker (each card renders a sample percentage in that theme's actual fonts/colors). Click = live preview; Cancel reverts; Save persists.
- BlockRing big % bumped to 38px logical with theme-specific font-variation-settings.
- TitleBar voice differs per theme without changing underlying string.
- Default theme: Anthropic (warmest first impression).
This commit is contained in:
parent
5200caf21f
commit
8a7ebd60b1
9 changed files with 575 additions and 39 deletions
|
|
@ -11,6 +11,11 @@
|
|||
"tauri": "tauri"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource-variable/fraunces": "^5.0.0",
|
||||
"@fontsource-variable/jetbrains-mono": "^5.0.0",
|
||||
"@fontsource-variable/newsreader": "^5.0.0",
|
||||
"@fontsource/dm-sans": "^5.0.0",
|
||||
"@fontsource/ibm-plex-mono": "^5.0.0",
|
||||
"@tauri-apps/api": "^2.0.0",
|
||||
"@tauri-apps/plugin-autostart": "^2.0.0",
|
||||
"@tauri-apps/plugin-dialog": "^2.0.0",
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ pub struct Settings {
|
|||
pub claude_command: Option<String>,
|
||||
/// How often (seconds) to refetch `/usage`. Defaults to 300 (5 min).
|
||||
pub cli_refresh_secs: u64,
|
||||
/// One of: "anthropic" (default), "instrument", "editorial", "retro".
|
||||
pub theme: String,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
|
|
@ -54,6 +56,7 @@ impl Default for Settings {
|
|||
autostart: false,
|
||||
claude_command: None,
|
||||
cli_refresh_secs: 300,
|
||||
theme: "anthropic".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,10 +22,18 @@
|
|||
let showSettings = $state(false);
|
||||
/** True when Claude Code can't be found anywhere on this machine. */
|
||||
let claudeMissing = $state(false);
|
||||
/** Active theme; applied as data-theme on the document root. */
|
||||
let theme = $state<string>("anthropic");
|
||||
|
||||
let unlisten1: (() => void) | null = null;
|
||||
let unlisten2: (() => void) | null = null;
|
||||
|
||||
// Whenever theme changes, propagate it to the document root so CSS
|
||||
// [data-theme="..."] selectors take effect.
|
||||
$effect(() => {
|
||||
document.documentElement.setAttribute("data-theme", theme);
|
||||
});
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
snap = await getSnapshot();
|
||||
|
|
@ -44,9 +52,10 @@
|
|||
console.error("listen failed", e);
|
||||
}
|
||||
|
||||
// Probe whether claude is reachable at all.
|
||||
// Probe whether claude is reachable at all + load theme preference.
|
||||
try {
|
||||
const settings = await getSettings();
|
||||
theme = settings.theme || "anthropic";
|
||||
const hasOverride = !!(settings.claude_command && settings.claude_command.trim());
|
||||
const auto = await autodetectClaudeCommand();
|
||||
claudeMissing = !hasOverride && !auto;
|
||||
|
|
|
|||
|
|
@ -52,6 +52,23 @@
|
|||
transform={`rotate(-90 ${SIZE / 2} ${SIZE / 2})`}
|
||||
class:pulse
|
||||
/>
|
||||
<!-- Quarter-position tick marks; only the instrument theme shows them. -->
|
||||
<g class="ticks" aria-hidden="true">
|
||||
{#each [0, 90, 180, 270] as deg (deg)}
|
||||
{@const a = (deg - 90) * Math.PI / 180}
|
||||
{@const r1 = R - STROKE / 2 - 2}
|
||||
{@const r2 = R + STROKE / 2 + 2}
|
||||
<line
|
||||
x1={SIZE / 2 + r1 * Math.cos(a)}
|
||||
y1={SIZE / 2 + r1 * Math.sin(a)}
|
||||
x2={SIZE / 2 + r2 * Math.cos(a)}
|
||||
y2={SIZE / 2 + r2 * Math.sin(a)}
|
||||
stroke="currentColor"
|
||||
stroke-width="1"
|
||||
/>
|
||||
{/each}
|
||||
</g>
|
||||
|
||||
<text x={SIZE / 2} y={SIZE / 2 - 6} text-anchor="middle" class="big">
|
||||
{bar ? `${bar.percent}%` : fallbackText}
|
||||
</text>
|
||||
|
|
@ -83,13 +100,53 @@
|
|||
max-width: 220px; /* don't get absurdly large in a wide window */
|
||||
max-height: 220px;
|
||||
}
|
||||
text { fill: var(--fg); font-family: inherit; }
|
||||
text.big { font-size: 26px; font-weight: 600; }
|
||||
text.small { font-size: 11px; fill: var(--fg-dim); }
|
||||
text.resets { font-size: 10px; fill: var(--fg-dim); }
|
||||
text { fill: var(--fg); }
|
||||
text.big {
|
||||
font-size: 38px;
|
||||
font-weight: 600;
|
||||
font-family: var(--font-display);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
text.small {
|
||||
font-size: 11px;
|
||||
fill: var(--fg-dim);
|
||||
font-family: var(--font-body);
|
||||
text-transform: lowercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
text.resets {
|
||||
font-size: 10px;
|
||||
fill: var(--fg-dim);
|
||||
font-family: var(--font-body);
|
||||
}
|
||||
.pulse { animation: pulse 1.6s ease-in-out infinite; }
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.55; }
|
||||
}
|
||||
|
||||
/* Per-theme touches on the ring text */
|
||||
:global([data-theme="editorial"]) text.big {
|
||||
font-variation-settings: "opsz" 144, "SOFT" 50;
|
||||
font-weight: 400;
|
||||
}
|
||||
:global([data-theme="editorial"]) text.small {
|
||||
font-style: italic;
|
||||
}
|
||||
:global([data-theme="retro"]) text.big {
|
||||
font-weight: 700;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
:global([data-theme="instrument"]) text.big {
|
||||
font-weight: 700;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
:global([data-theme="anthropic"]) text.big {
|
||||
font-variation-settings: "opsz" 36;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Tick marks: hidden everywhere except instrument theme */
|
||||
.ticks { color: var(--fg-dim); display: none; }
|
||||
:global([data-theme="instrument"]) .ticks { display: block; }
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -24,6 +24,19 @@
|
|||
let busy = $state(false);
|
||||
let testingCli = $state(false);
|
||||
let err = $state<string | null>(null);
|
||||
/** Theme captured on mount so Cancel can revert a live preview. */
|
||||
let originalTheme = "anthropic";
|
||||
|
||||
const THEMES = [
|
||||
{ id: "anthropic", label: "Anthropic", blurb: "Warm cream-on-charcoal · serif" },
|
||||
{ id: "instrument", label: "Instrument", blurb: "Synth panel · mono · chartreuse" },
|
||||
{ id: "editorial", label: "Editorial", blurb: "Magazine artifact · saffron" },
|
||||
{ id: "retro", label: "Retro CRT", blurb: "Phosphor green · scanlines" },
|
||||
] as const;
|
||||
|
||||
function applyThemeLive(id: string) {
|
||||
document.documentElement.setAttribute("data-theme", id);
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
|
|
@ -31,6 +44,7 @@
|
|||
distros = await listDistros();
|
||||
roots = await getRoots();
|
||||
cli = await getCliUsage();
|
||||
originalTheme = settings.theme || "anthropic";
|
||||
const enabled = await isAutostartEnabled();
|
||||
if (settings && settings.autostart !== enabled) {
|
||||
settings = { ...settings, autostart: enabled };
|
||||
|
|
@ -56,6 +70,8 @@
|
|||
|
||||
try {
|
||||
await setSettings(settings);
|
||||
// Successful save: lock in the live-previewed theme.
|
||||
originalTheme = settings.theme;
|
||||
onClose();
|
||||
} catch (e) {
|
||||
err = `${e}`;
|
||||
|
|
@ -64,6 +80,18 @@
|
|||
}
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
// Revert any live theme preview the user was sampling.
|
||||
applyThemeLive(originalTheme);
|
||||
onClose();
|
||||
}
|
||||
|
||||
function pickTheme(id: string) {
|
||||
if (!settings) return;
|
||||
settings = { ...settings, theme: id as any };
|
||||
applyThemeLive(id);
|
||||
}
|
||||
|
||||
async function testCli() {
|
||||
if (!settings) return;
|
||||
testingCli = true;
|
||||
|
|
@ -84,12 +112,32 @@
|
|||
<div class="panel">
|
||||
<div class="row spread">
|
||||
<strong>Settings</strong>
|
||||
<button class="icon" onclick={onClose} aria-label="Close settings">×</button>
|
||||
<button class="icon" onclick={cancel} aria-label="Close settings">×</button>
|
||||
</div>
|
||||
|
||||
{#if err}<div class="error">{err}</div>{/if}
|
||||
|
||||
{#if settings}
|
||||
<div class="field">
|
||||
<div class="label">theme</div>
|
||||
<div class="theme-grid">
|
||||
{#each THEMES as t (t.id)}
|
||||
<button
|
||||
type="button"
|
||||
class="theme-card"
|
||||
class:selected={settings.theme === t.id}
|
||||
data-theme-preview={t.id}
|
||||
onclick={() => pickTheme(t.id)}
|
||||
aria-pressed={settings.theme === t.id}
|
||||
>
|
||||
<span class="tp-name">{t.label}</span>
|
||||
<span class="tp-sample">42%</span>
|
||||
<span class="tp-blurb">{t.blurb}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="label">claude command</div>
|
||||
<input
|
||||
|
|
@ -166,7 +214,7 @@
|
|||
{/if}
|
||||
|
||||
<div class="row spread actions">
|
||||
<button onclick={onClose}>Cancel</button>
|
||||
<button onclick={cancel}>Cancel</button>
|
||||
<button onclick={save} disabled={busy}>{busy ? "Saving…" : "Save"}</button>
|
||||
</div>
|
||||
{:else}
|
||||
|
|
@ -199,6 +247,116 @@
|
|||
.roots { margin: 0; padding-left: 14px; max-height: 60px; overflow: auto; }
|
||||
.roots code { font-size: 10px; word-break: break-all; }
|
||||
.hint { font-size: 10px; line-height: 1.3; }
|
||||
|
||||
/* ---- Theme picker ---- */
|
||||
.theme-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 6px;
|
||||
}
|
||||
.theme-card {
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
gap: 2px;
|
||||
padding: 8px 10px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border);
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
min-height: 64px;
|
||||
}
|
||||
.theme-card .tp-name {
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
opacity: 0.65;
|
||||
}
|
||||
.theme-card .tp-sample {
|
||||
font-size: 22px;
|
||||
line-height: 1;
|
||||
font-weight: 600;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.theme-card .tp-blurb {
|
||||
font-size: 9px;
|
||||
opacity: 0.55;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.theme-card.selected {
|
||||
outline: 1.5px solid var(--accent);
|
||||
outline-offset: -1.5px;
|
||||
}
|
||||
.theme-card.selected::after {
|
||||
content: "✓";
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 6px;
|
||||
font-size: 11px;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
/* Per-theme card visuals (preview in their own typography & palette) */
|
||||
.theme-card[data-theme-preview="anthropic"] {
|
||||
background: linear-gradient(180deg, #1a1815 0%, #1a1815 100%);
|
||||
color: #faf9f5;
|
||||
font-family: "DM Sans", sans-serif;
|
||||
}
|
||||
.theme-card[data-theme-preview="anthropic"] .tp-sample {
|
||||
font-family: "Newsreader Variable", "Newsreader", Georgia, serif;
|
||||
font-weight: 500;
|
||||
color: #cc785c;
|
||||
}
|
||||
|
||||
.theme-card[data-theme-preview="instrument"] {
|
||||
background:
|
||||
repeating-linear-gradient(180deg, transparent 0 2px, rgba(212,255,61,0.04) 2px 3px),
|
||||
#0e1014;
|
||||
color: #d6e2d8;
|
||||
font-family: "JetBrains Mono Variable", monospace;
|
||||
border-color: rgba(212, 255, 61, 0.25);
|
||||
}
|
||||
.theme-card[data-theme-preview="instrument"] .tp-sample {
|
||||
color: #d4ff3d;
|
||||
font-family: "JetBrains Mono Variable", monospace;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.theme-card[data-theme-preview="editorial"] {
|
||||
background: #1a1813;
|
||||
color: #f5f1e8;
|
||||
font-family: "Fraunces Variable", Georgia, serif;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.theme-card[data-theme-preview="editorial"] .tp-name {
|
||||
font-style: italic;
|
||||
text-transform: none;
|
||||
letter-spacing: 0;
|
||||
font-size: 11px;
|
||||
}
|
||||
.theme-card[data-theme-preview="editorial"] .tp-sample {
|
||||
color: #e8a02f;
|
||||
font-family: "Fraunces Variable", serif;
|
||||
font-weight: 500;
|
||||
font-variation-settings: "opsz" 144;
|
||||
}
|
||||
|
||||
.theme-card[data-theme-preview="retro"] {
|
||||
background:
|
||||
repeating-linear-gradient(180deg, transparent 0 1px, rgba(0,0,0,0.4) 1px 2px),
|
||||
#0a0e0a;
|
||||
color: #b8f0b8;
|
||||
font-family: "IBM Plex Mono", monospace;
|
||||
border-color: rgba(122, 240, 122, 0.3);
|
||||
border-radius: 3px;
|
||||
text-shadow: 0 0 4px rgba(122,240,122,0.4);
|
||||
}
|
||||
.theme-card[data-theme-preview="retro"] .tp-sample {
|
||||
color: #7af07a;
|
||||
font-family: "IBM Plex Mono", monospace;
|
||||
font-weight: 700;
|
||||
}
|
||||
pre.raw {
|
||||
margin: 4px 0 0;
|
||||
padding: 6px;
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@
|
|||
letter-spacing: 0.8px;
|
||||
color: var(--fg-dim);
|
||||
cursor: grab;
|
||||
font-family: var(--font-body);
|
||||
}
|
||||
.actions { display: inline-flex; gap: 2px; }
|
||||
.icon.spin { animation: spin 1.2s linear infinite; }
|
||||
|
|
@ -67,4 +68,48 @@
|
|||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Per-theme title voice ------------------------------------------------ */
|
||||
|
||||
/* Anthropic: serif italic, soft and warm */
|
||||
:global([data-theme="anthropic"]) .title {
|
||||
font-family: var(--font-display);
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
text-transform: none;
|
||||
letter-spacing: 0;
|
||||
font-size: 13px;
|
||||
color: var(--fg);
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
/* Instrument: tracked monospace caps, accent colored */
|
||||
:global([data-theme="instrument"]) .title {
|
||||
color: var(--accent);
|
||||
font-weight: 700;
|
||||
letter-spacing: 1.6px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
/* Editorial: italic display serif */
|
||||
:global([data-theme="editorial"]) .title {
|
||||
font-family: var(--font-display);
|
||||
font-style: italic;
|
||||
text-transform: none;
|
||||
letter-spacing: 0.2px;
|
||||
font-size: 14px;
|
||||
font-variation-settings: "opsz" 18, "SOFT" 50;
|
||||
color: var(--fg);
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
/* Retro: bracketed CLI-style label */
|
||||
:global([data-theme="retro"]) .title {
|
||||
font-weight: 700;
|
||||
color: var(--accent);
|
||||
letter-spacing: 1px;
|
||||
font-size: 11px;
|
||||
}
|
||||
:global([data-theme="retro"]) .title::before { content: "[ "; opacity: 0.6; }
|
||||
:global([data-theme="retro"]) .title::after { content: " ]"; opacity: 0.6; }
|
||||
</style>
|
||||
|
|
|
|||
14
src/main.ts
14
src/main.ts
|
|
@ -1,5 +1,19 @@
|
|||
import { mount } from "svelte";
|
||||
import App from "./components/App.svelte";
|
||||
|
||||
// Theme fonts — bundled offline so the widget renders identically without a
|
||||
// network connection. Variable-axis fonts where available so we can tune
|
||||
// weight/optical size from CSS.
|
||||
import "@fontsource-variable/jetbrains-mono";
|
||||
import "@fontsource-variable/fraunces";
|
||||
import "@fontsource-variable/newsreader";
|
||||
import "@fontsource/dm-sans/400.css";
|
||||
import "@fontsource/dm-sans/500.css";
|
||||
import "@fontsource/dm-sans/600.css";
|
||||
import "@fontsource/ibm-plex-mono/400.css";
|
||||
import "@fontsource/ibm-plex-mono/500.css";
|
||||
import "@fontsource/ibm-plex-mono/700.css";
|
||||
|
||||
import "./styles.css";
|
||||
|
||||
const app = mount(App, { target: document.getElementById("app")! });
|
||||
|
|
|
|||
304
src/styles.css
304
src/styles.css
|
|
@ -1,25 +1,11 @@
|
|||
/* Glass / always-on-top widget look. */
|
||||
/* ============================================================
|
||||
Base structure (theme-agnostic).
|
||||
Theme tokens live in the [data-theme="..."] blocks below.
|
||||
============================================================ */
|
||||
|
||||
:root {
|
||||
--bg: rgba(18, 20, 26, 0.93);
|
||||
--bg-card: rgba(255, 255, 255, 0.04);
|
||||
--border: rgba(255, 255, 255, 0.08);
|
||||
--fg: #e8eaf0;
|
||||
--fg-dim: #9aa0aa;
|
||||
--accent: #b08bff; /* Anthropic-ish purple */
|
||||
--opus: #b08bff;
|
||||
--sonnet: #6ec1ff;
|
||||
--haiku: #7ee0a3;
|
||||
--other: #d8d8d8;
|
||||
--warn: #ffb454;
|
||||
--danger: #ff6b6b;
|
||||
|
||||
--radius: 10px;
|
||||
--pad: 10px;
|
||||
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
font-size: 13px;
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
|
|
@ -29,10 +15,16 @@ html, body {
|
|||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: transparent; /* honor Tauri transparent:true */
|
||||
overflow: hidden; /* viewport never scrolls; sections handle their own */
|
||||
background: transparent;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
font-family: var(--font-body);
|
||||
font-size: 13px;
|
||||
color: var(--fg);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
#app {
|
||||
|
|
@ -41,18 +33,27 @@ html, body {
|
|||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
overflow: hidden; /* the window itself never scrolls */
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
backdrop-filter: blur(14px);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Each child of #app gets a sensible flex behavior so resizing reflows
|
||||
instead of overflowing. TitleBar and section panels are flex:0 (size
|
||||
to content); the BlockRing wrap is flex:1 (claims remaining space). */
|
||||
/* Optional decorative atmosphere layer per theme — set --atmosphere
|
||||
to a `background` value (e.g. a gradient + noise) to enable. */
|
||||
#app::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background: var(--atmosphere, none);
|
||||
opacity: var(--atmosphere-opacity, 0);
|
||||
mix-blend-mode: var(--atmosphere-blend, normal);
|
||||
z-index: 0;
|
||||
}
|
||||
#app > * { position: relative; z-index: 1; }
|
||||
|
||||
/* Hide scrollbars unless content really overflows; when they do appear,
|
||||
make them subtle. */
|
||||
::-webkit-scrollbar { width: 6px; height: 6px; }
|
||||
::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.15); border-radius: 3px; }
|
||||
::-webkit-scrollbar-track { background: transparent; }
|
||||
|
|
@ -65,8 +66,9 @@ button {
|
|||
border-radius: 6px;
|
||||
padding: 4px 8px;
|
||||
cursor: pointer;
|
||||
font-family: var(--font-body);
|
||||
}
|
||||
button:hover { background: rgba(255, 255, 255, 0.06); }
|
||||
button:hover { background: var(--hover); }
|
||||
button.icon {
|
||||
border: none;
|
||||
padding: 4px;
|
||||
|
|
@ -82,12 +84,12 @@ button.icon:hover { color: var(--fg); }
|
|||
input[type="number"], input[type="text"], select {
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
background: var(--input-bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 4px 6px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
font-family: var(--font-body);
|
||||
}
|
||||
|
||||
.section {
|
||||
|
|
@ -98,9 +100,9 @@ input[type="number"], input[type="text"], select {
|
|||
.section:first-child { border-top: none; }
|
||||
|
||||
.label {
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.6px;
|
||||
font: var(--label-font);
|
||||
text-transform: var(--label-transform, uppercase);
|
||||
letter-spacing: var(--label-tracking, 0.6px);
|
||||
color: var(--fg-dim);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
|
@ -108,3 +110,241 @@ input[type="number"], input[type="text"], select {
|
|||
.row { display: flex; align-items: center; gap: 8px; }
|
||||
.row.spread { justify-content: space-between; }
|
||||
.muted { color: var(--fg-dim); font-size: 11px; }
|
||||
|
||||
/* Tabular numerals everywhere a number can change */
|
||||
.num, .big, text.big, .muted, .label, .bar-row .num {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Theme: ANTHROPIC (default)
|
||||
Warm charcoal + cream + sunset orange + claude purple.
|
||||
Newsreader serif for display, DM Sans for body.
|
||||
============================================================ */
|
||||
|
||||
[data-theme="anthropic"] {
|
||||
--font-display: "Newsreader Variable", "Newsreader", Georgia, serif;
|
||||
--font-body: "DM Sans", -apple-system, "Segoe UI", sans-serif;
|
||||
--font-mono: "DM Sans", monospace;
|
||||
|
||||
--bg: #1a1815;
|
||||
--bg-card: rgba(250, 249, 245, 0.04);
|
||||
--border: rgba(250, 249, 245, 0.10);
|
||||
--hover: rgba(250, 249, 245, 0.06);
|
||||
--input-bg: rgba(0, 0, 0, 0.20);
|
||||
|
||||
--fg: #faf9f5;
|
||||
--fg-dim: #b8ad9e;
|
||||
--accent: #cc785c; /* sunset orange */
|
||||
--accent-2: #c084fc; /* claude purple */
|
||||
|
||||
--opus: #c084fc;
|
||||
--sonnet: #6ec1ff;
|
||||
--haiku: #a3d9a5;
|
||||
--other: #d8d8d8;
|
||||
--warn: #e8a02f;
|
||||
--danger: #d97559;
|
||||
|
||||
--label-font: 500 10px/1 var(--font-body);
|
||||
--atmosphere: radial-gradient(60% 50% at 50% 0%, rgba(204, 120, 92, 0.18), transparent 60%);
|
||||
--atmosphere-opacity: 1;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Theme: INSTRUMENT
|
||||
Modular synth panel. JetBrains Mono. Chartreuse on slate.
|
||||
Faint scanlines, tick marks on the ring.
|
||||
============================================================ */
|
||||
|
||||
[data-theme="instrument"] {
|
||||
--font-display: "JetBrains Mono Variable", "JetBrains Mono", ui-monospace, monospace;
|
||||
--font-body: "JetBrains Mono Variable", "JetBrains Mono", ui-monospace, monospace;
|
||||
--font-mono: "JetBrains Mono Variable", monospace;
|
||||
|
||||
--bg: #0e1014;
|
||||
--bg-card: rgba(255, 255, 255, 0.03);
|
||||
--border: rgba(212, 255, 61, 0.18);
|
||||
--hover: rgba(212, 255, 61, 0.06);
|
||||
--input-bg: rgba(0, 0, 0, 0.45);
|
||||
|
||||
--fg: #d6e2d8;
|
||||
--fg-dim: #6f8478;
|
||||
--accent: #d4ff3d; /* chartreuse */
|
||||
--accent-2: #5dd8ff; /* cyan secondary */
|
||||
|
||||
--opus: #d4ff3d;
|
||||
--sonnet: #5dd8ff;
|
||||
--haiku: #ff6fb5;
|
||||
--other: #9aa0aa;
|
||||
--warn: #ffb454;
|
||||
--danger: #ff3b88;
|
||||
|
||||
--label-font: 600 9px/1 var(--font-mono);
|
||||
--label-tracking: 1.4px;
|
||||
|
||||
/* Faint horizontal scanlines + a subtle grid */
|
||||
--atmosphere:
|
||||
repeating-linear-gradient(
|
||||
180deg,
|
||||
transparent 0px,
|
||||
transparent 2px,
|
||||
rgba(212, 255, 61, 0.025) 2px,
|
||||
rgba(212, 255, 61, 0.025) 3px
|
||||
),
|
||||
radial-gradient(80% 60% at 50% 0%, rgba(212, 255, 61, 0.05), transparent 70%);
|
||||
--atmosphere-opacity: 1;
|
||||
}
|
||||
|
||||
[data-theme="instrument"] #app {
|
||||
border-color: rgba(212, 255, 61, 0.22);
|
||||
}
|
||||
|
||||
/* Bracket-corners on the title bar in instrument mode */
|
||||
[data-theme="instrument"] header::before,
|
||||
[data-theme="instrument"] header::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 6px; height: 6px;
|
||||
border: 1px solid var(--accent);
|
||||
opacity: 0.5;
|
||||
}
|
||||
[data-theme="instrument"] header { position: relative; }
|
||||
[data-theme="instrument"] header::before {
|
||||
top: 4px; left: 4px;
|
||||
border-right: 0; border-bottom: 0;
|
||||
}
|
||||
[data-theme="instrument"] header::after {
|
||||
top: 4px; right: 4px;
|
||||
border-left: 0; border-bottom: 0;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Theme: EDITORIAL
|
||||
Magazine artifact. Fraunces serif. Saffron on warm charcoal.
|
||||
Hairline rules, generous spacing.
|
||||
============================================================ */
|
||||
|
||||
[data-theme="editorial"] {
|
||||
--font-display: "Fraunces Variable", "Fraunces", Georgia, serif;
|
||||
--font-body: "Fraunces Variable", "Fraunces", Georgia, serif;
|
||||
--font-mono: "Fraunces Variable", monospace;
|
||||
|
||||
--bg: #1a1813;
|
||||
--bg-card: rgba(245, 241, 232, 0.03);
|
||||
--border: rgba(245, 241, 232, 0.12);
|
||||
--hover: rgba(245, 241, 232, 0.05);
|
||||
--input-bg: rgba(0, 0, 0, 0.25);
|
||||
|
||||
--fg: #f5f1e8;
|
||||
--fg-dim: #9b9183;
|
||||
--accent: #e8a02f; /* saffron */
|
||||
--accent-2: #d97559;
|
||||
|
||||
--opus: #e8a02f;
|
||||
--sonnet: #c8987a;
|
||||
--haiku: #a3a87a;
|
||||
--other: #9b9183;
|
||||
--warn: #d97559;
|
||||
--danger: #b54a3c;
|
||||
|
||||
--label-font: italic 400 11px/1 var(--font-display);
|
||||
--label-transform: none;
|
||||
--label-tracking: 0.2px;
|
||||
}
|
||||
|
||||
/* Editorial uses small-caps italic for labels — let the variable axis breathe */
|
||||
[data-theme="editorial"] .label {
|
||||
font-variation-settings: "opsz" 14, "SOFT" 50;
|
||||
}
|
||||
|
||||
[data-theme="editorial"] #app {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
[data-theme="editorial"] .section {
|
||||
border-top-style: solid;
|
||||
border-top-width: 0.5px;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Theme: RETRO CRT
|
||||
1980s home computer. IBM Plex Mono. Phosphor green.
|
||||
Scanlines, bracket header, blink cursor.
|
||||
============================================================ */
|
||||
|
||||
[data-theme="retro"] {
|
||||
--font-display: "IBM Plex Mono", ui-monospace, monospace;
|
||||
--font-body: "IBM Plex Mono", ui-monospace, monospace;
|
||||
--font-mono: "IBM Plex Mono", monospace;
|
||||
|
||||
--bg: #0a0e0a;
|
||||
--bg-card: rgba(122, 240, 122, 0.03);
|
||||
--border: rgba(122, 240, 122, 0.20);
|
||||
--hover: rgba(122, 240, 122, 0.08);
|
||||
--input-bg: rgba(0, 0, 0, 0.55);
|
||||
|
||||
--fg: #b8f0b8;
|
||||
--fg-dim: #5a8a5a;
|
||||
--accent: #7af07a; /* phosphor green */
|
||||
--accent-2: #ffb454; /* amber for warnings */
|
||||
|
||||
--opus: #7af07a;
|
||||
--sonnet: #5dd8ff;
|
||||
--haiku: #ffe066;
|
||||
--other: #b8f0b8;
|
||||
--warn: #ffb454;
|
||||
--danger: #ff5a5a;
|
||||
|
||||
--label-font: 700 9px/1 var(--font-mono);
|
||||
--label-tracking: 1.5px;
|
||||
|
||||
/* Strong scanlines + vignette + faint phosphor glow */
|
||||
--atmosphere:
|
||||
repeating-linear-gradient(
|
||||
180deg,
|
||||
transparent 0px,
|
||||
transparent 1px,
|
||||
rgba(0, 0, 0, 0.35) 1px,
|
||||
rgba(0, 0, 0, 0.35) 2px
|
||||
),
|
||||
radial-gradient(120% 90% at 50% 50%, transparent 50%, rgba(0, 0, 0, 0.55) 100%),
|
||||
radial-gradient(60% 40% at 50% 30%, rgba(122, 240, 122, 0.10), transparent 70%);
|
||||
--atmosphere-opacity: 1;
|
||||
}
|
||||
|
||||
[data-theme="retro"] #app {
|
||||
border-radius: 4px;
|
||||
border-color: rgba(122, 240, 122, 0.30);
|
||||
text-shadow: 0 0 6px rgba(122, 240, 122, 0.35);
|
||||
}
|
||||
|
||||
[data-theme="retro"] header { border-bottom-style: dashed; }
|
||||
|
||||
/* Blink cursor in the bottom-right of the widget — pure decoration */
|
||||
[data-theme="retro"] #app::after {
|
||||
content: "█";
|
||||
position: absolute;
|
||||
bottom: 6px;
|
||||
right: 10px;
|
||||
color: var(--accent);
|
||||
opacity: 0.8;
|
||||
animation: retro-blink 1.1s steps(2, end) infinite;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
}
|
||||
@keyframes retro-blink {
|
||||
50% { opacity: 0; }
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Reduced-motion accommodation: kill all the breathing effects.
|
||||
============================================================ */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after {
|
||||
animation-duration: 0.001ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.001ms !important;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,12 +45,17 @@ export interface UsageSnapshot {
|
|||
generated_at: string;
|
||||
}
|
||||
|
||||
export type Theme = "anthropic" | "instrument" | "editorial" | "retro";
|
||||
|
||||
export interface Settings {
|
||||
caps: Caps;
|
||||
wsl_distro_override: string | null;
|
||||
include_native: boolean;
|
||||
window_pos: [number, number] | null;
|
||||
autostart: boolean;
|
||||
claude_command: string | null;
|
||||
cli_refresh_secs: number;
|
||||
theme: Theme;
|
||||
}
|
||||
|
||||
export interface ResolvedRoots {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue