import { api } from "./client.svelte.js"; import { player } from "./player.svelte.js"; import { settings } from "./settings.svelte.js"; import { getSwatches, getColor } from "colorthief"; const getLuminance = (rgb: number[]) => { const [red, green, blue] = rgb.map((value) => { const normalizedValue = value / 255; return normalizedValue <= 0.03928 ? normalizedValue / 12.92 : Math.pow((normalizedValue + 0.055) / 1.055, 2.4); }); return 0.2126 * red + 0.7152 * green + 0.0722 * blue; }; const getContrast = (color1: number[], color2: number[]) => { const luminance1 = getLuminance(color1) + 0.05; const luminance2 = getLuminance(color2) + 0.05; return luminance1 > luminance2 ? luminance1 / luminance2 : luminance2 / luminance1; }; const cache = new Map(); const apply = (bg: string, text: string, dark: boolean) => { const style = document.documentElement.style; style.setProperty("--bg", bg); style.setProperty("--text", text); document.documentElement.style.colorScheme = dark ? "dark" : "light"; }; const clear = () => { const style = document.documentElement.style; style.removeProperty("--bg"); style.removeProperty("--text"); document.documentElement.style.colorScheme = ""; }; export const initTheme = () => { $effect.root(() => { $effect(() => { const track = player.track; if (!settings.dynamicColors || !track) return clear(); const id = track.albumId || track.coverArt || track.id; const cached = cache.get(id); if (cached) return apply(cached.bg, cached.text, cached.dark); const img = new Image(); img.crossOrigin = "Anonymous"; img.src = api.art(id, 512); img.onload = async () => { if (!settings.dynamicColors || player.track?.id !== track.id) return; try { const dominant = await getColor(img); const swatches = await getSwatches(img); if (!dominant) return; const bg = dominant.array(); let text: number[] | null = null; for (const role of [ "Vibrant", "Muted", "DarkVibrant", "DarkMuted", "LightVibrant", "LightMuted", ] as const) { const swatch = swatches[role]; if (swatch && getContrast(bg, swatch.color.array()) >= 4.5) { text = swatch.color.array(); break; } } if (!text) text = dominant.contrast.foreground.array(); const theme = { bg: `rgb(${bg[0]},${bg[1]},${bg[2]})`, text: `rgb(${text[0]},${text[1]},${text[2]})`, dark: dominant.isDark, }; cache.set(id, theme); apply(theme.bg, theme.text, theme.dark); } catch (err) { console.error("theme fail:", err); } }; }); }); };