import { create } from 'zustand' import { persist } from 'zustand/middleware' export type AccentName = 'amber' | 'teal' | 'coral' | 'green' | 'blue' export const ACCENTS: { name: AccentName; label: string; swatch: string }[] = [ { name: 'amber', label: 'Amber', swatch: '#f59e0b' }, { name: 'teal', label: 'Türkis', swatch: '#14b8a6' }, { name: 'coral', label: 'Koralle', swatch: '#fb7185' }, { name: 'green', label: 'Grün', swatch: '#22c55e' }, { name: 'blue', label: 'Blau', swatch: '#3b82f6' }, ] export const PLAYBACK_SPEEDS = [0.75, 1, 1.25, 1.5, 1.75, 2, 2.5, 3] as const interface SettingsState { accent: AccentName /** When set, a freely chosen hex color overrides the named preset. */ customAccent: string | null defaultSpeed: number sidebarCollapsed: boolean setAccent: (accent: AccentName) => void setCustomAccent: (hex: string) => void setDefaultSpeed: (speed: number) => void toggleSidebar: () => void } export const useSettingsStore = create()( persist( (set) => ({ accent: 'amber', customAccent: null, defaultSpeed: 1, sidebarCollapsed: false, setAccent: (accent) => { applyTheme(accent, null) set({ accent, customAccent: null }) }, setCustomAccent: (hex) => { applyTheme('amber', hex) set({ customAccent: hex }) }, setDefaultSpeed: (defaultSpeed) => set({ defaultSpeed }), toggleSidebar: () => set((s) => ({ sidebarCollapsed: !s.sidebarCollapsed })), }), { name: 'shelfless.settings', onRehydrateStorage: () => (state) => { if (state) applyTheme(state.accent, state.customAccent) }, }, ), ) // ── Theme application ───────────────────────────────────────────────────── function hexToRgb(hex: string): { r: number; g: number; b: number } | null { let h = hex.trim().replace(/^#/, '') if (h.length === 3) h = h.split('').map((c) => c + c).join('') if (!/^[0-9a-fA-F]{6}$/.test(h)) return null return { r: parseInt(h.slice(0, 2), 16), g: parseInt(h.slice(2, 4), 16), b: parseInt(h.slice(4, 6), 16), } } /** Pick readable foreground (near-black or near-white) for a given accent color. */ function onAccentFor(r: number, g: number, b: number): string { const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255 return luminance > 0.6 ? '#0c0a09' : '#fafaf9' } /** * Reflect the chosen accent onto the document. A named preset uses the * `[data-accent]` CSS rules; a custom hex sets inline CSS variables (which win). */ export function applyTheme(accent: AccentName, customAccent: string | null) { if (typeof document === 'undefined') return const root = document.documentElement const rgb = customAccent ? hexToRgb(customAccent) : null if (customAccent && rgb) { root.style.setProperty('--accent', customAccent) root.style.setProperty('--accent-soft', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.14)`) root.style.setProperty('--on-accent', onAccentFor(rgb.r, rgb.g, rgb.b)) root.setAttribute('data-accent', 'custom') } else { root.style.removeProperty('--accent') root.style.removeProperty('--accent-soft') root.style.removeProperty('--on-accent') root.setAttribute('data-accent', accent) } }