import { PATTERNS, type BackgroundPatternId } from '$lib/desktop-patterns' export interface BackgroundConfig { pattern: BackgroundPatternId color: string // pattern foreground color, hex fillColor: string | null // fill behind the pattern (its "gaps"); null = transparent, i.e. the app's own theme background shows through opacity: number // 0..1 fade: number // 0..1 — 0 disables the vignette mask entirely scale: number // pattern tile-size multiplier; 1 = natural size rotation: number // degrees, 0..359 } const STORAGE_KEY = 'oikos-desktop-bg' const VALID_IDS = new Set(PATTERNS.map((p) => p.id)) // Matches the previous hardcoded desktop background (bubbles, brand blue, // tuned low so it reads as texture rather than noise) so this feature ships // as "now configurable" rather than a visual regression on first load. const DEFAULTS: BackgroundConfig = { pattern: 'bubbles', color: '#444cf7', fillColor: null, opacity: 0.08, fade: 0, scale: 1, rotation: 0 } function clamp01(v: unknown, fallback: number): number { const n = typeof v === 'number' ? v : Number(v) if (!Number.isFinite(n)) return fallback return Math.min(1, Math.max(0, n)) } function clampScale(v: unknown): number { const n = typeof v === 'number' ? v : Number(v) if (!Number.isFinite(n)) return DEFAULTS.scale return Math.min(3, Math.max(0.4, n)) } function clampRotation(v: unknown): number { const n = typeof v === 'number' ? v : Number(v) if (!Number.isFinite(n)) return DEFAULTS.rotation return ((n % 360) + 360) % 360 } function isHexColor(c: unknown): c is string { return typeof c === 'string' && /^#[0-9a-fA-F]{6}$/.test(c) } function storedConfig(): BackgroundConfig { if (typeof localStorage === 'undefined') return DEFAULTS const raw = localStorage.getItem(STORAGE_KEY) if (!raw) return DEFAULTS try { const parsed = JSON.parse(raw) return { pattern: VALID_IDS.has(parsed.pattern) ? parsed.pattern : DEFAULTS.pattern, color: isHexColor(parsed.color) ? parsed.color : DEFAULTS.color, fillColor: parsed.fillColor === null ? null : isHexColor(parsed.fillColor) ? parsed.fillColor : DEFAULTS.fillColor, opacity: clamp01(parsed.opacity, DEFAULTS.opacity), fade: clamp01(parsed.fade, DEFAULTS.fade), scale: clampScale(parsed.scale), rotation: clampRotation(parsed.rotation) } } catch { return DEFAULTS } } let config: BackgroundConfig = $state(storedConfig()) function persist(): void { if (typeof localStorage !== 'undefined') localStorage.setItem(STORAGE_KEY, JSON.stringify(config)) } export function getBackground(): BackgroundConfig { return config } export function setBackgroundPattern(pattern: BackgroundPatternId): void { config = { ...config, pattern } persist() } export function setPatternColor(color: string): void { if (!isHexColor(color)) return config = { ...config, color } persist() } // null clears back to "auto" (transparent — the app's theme background // shows through the pattern's gaps). export function setFillColor(color: string | null): void { if (color !== null && !isHexColor(color)) return config = { ...config, fillColor: color } persist() } export function setBackgroundOpacity(opacity: number): void { config = { ...config, opacity: clamp01(opacity, DEFAULTS.opacity) } persist() } export function setBackgroundFade(fade: number): void { config = { ...config, fade: clamp01(fade, DEFAULTS.fade) } persist() } export function setBackgroundScale(scale: number): void { config = { ...config, scale: clampScale(scale) } persist() } export function setBackgroundRotation(rotation: number): void { config = { ...config, rotation: clampRotation(rotation) } persist() }