diff --git a/frontend/src/lib/components/ContextHeader.svelte b/frontend/src/lib/components/ContextHeader.svelte index 0d30183..bde1b65 100644 --- a/frontend/src/lib/components/ContextHeader.svelte +++ b/frontend/src/lib/components/ContextHeader.svelte @@ -42,6 +42,10 @@ theme?: ThemeConfig; /** Callback when theme changes */ onThemeChange?: (theme: ThemeConfig) => void; + /** User's saved theme slots (up to 6) */ + savedThemes?: ThemeConfig[]; + /** Callback when saved themes change */ + onSavedThemesChange?: (themes: ThemeConfig[]) => void; } let { @@ -57,6 +61,8 @@ activeTraits, theme = DEFAULT_THEME, onThemeChange, + savedThemes = [], + onSavedThemesChange, }: Props = $props(); const isPersonalWorkspace = $derived(!collectionId); @@ -266,6 +272,19 @@ onThemeChange?.(preset); } + const MAX_SAVED = 6; + + function saveCurrentTheme() { + if (savedThemes.length >= MAX_SAVED) return; + const newSaved = [...savedThemes, { ...theme }]; + onSavedThemesChange?.(newSaved); + } + + function deleteSavedTheme(index: number) { + const newSaved = savedThemes.filter((_, i) => i !== index); + onSavedThemesChange?.(newSaved); + } + // ========================================================================= // Click outside // ========================================================================= @@ -498,6 +517,29 @@ {/each} +
+ {#each savedThemes as saved, i (i)} + + {/each} + {#if savedThemes.length < MAX_SAVED} + + {/if} +
@@ -811,6 +853,18 @@ } .settings-preset:hover { border-color: var(--color-accent, #6366f1); } .settings-preset-swatch { width: 28px; height: 28px; border-radius: 6px; display: flex; align-items: center; justify-content: center; font-size: 14px; } + .settings-user-themes { margin-top: 4px; } + .settings-saved-preset .settings-preset-swatch { font-size: 11px; font-weight: 600; color: white; text-shadow: 0 1px 2px rgba(0,0,0,0.5); } + .settings-save-swatch { + background: transparent !important; + border: 2px dashed var(--color-border, #2a2a2e); + color: var(--color-text-dim, #5a5a66); + font-size: 16px; + } + .settings-save-btn:hover .settings-save-swatch { + border-color: var(--color-accent, #6366f1); + color: var(--color-text-muted, #8a8a96); + } /* Per-color controls */ .settings-color-group { margin-bottom: 8px; } diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index b243ced..3b5d447 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -87,6 +87,9 @@ if (typeof meta.gridEnabled === 'boolean') { gridEnabled = meta.gridEnabled; } + if (Array.isArray(meta.savedThemes)) { + savedThemes = meta.savedThemes as ThemeConfig[]; + } } }) .catch((err) => { @@ -110,6 +113,7 @@ let layoutInitialized = $state(false); let savedCamera = $state({ x: 0, y: 0, zoom: 1.0 }); let gridEnabled = $state(false); + let savedThemes = $state([]); // When workspace node appears in store (after creation), load its layout $effect(() => { @@ -156,6 +160,7 @@ workspace_layout: layout, camera: savedCamera, gridEnabled, + savedThemes, preferences: { ...(currentMeta.preferences ?? {}), theme: themeToMetadata(currentTheme), @@ -246,6 +251,11 @@ persistMetadata(); } + function handleSavedThemesChange(themes: ThemeConfig[]) { + savedThemes = themes; + persistMetadata(); + } + function loadThemeFromMeta(meta: Record) { const loaded = loadThemeFromMetadata(meta); if (loaded) { @@ -347,6 +357,8 @@ activeTraits={activeLayoutTraits} theme={currentTheme} onThemeChange={handleThemeChange} + {savedThemes} + onSavedThemesChange={handleSavedThemesChange} />