import { createStore } from './createStore.js'; /** * User preferences. Small, synchronous, read on boot. * * - versionGroup: the selected "game" (PokéAPI version-group key) * - pokedex: the active regional dex within that game (null = first one) * - theme: 'system' | 'light' | 'dark' * - spriteStyle: 'default' (pixel) | 'official' | 'home' * - showShiny: default the detail view to shiny sprites * - locale: reserved for localized names (PokéAPI supports many) * - fontScale: 'small' | 'default' | 'large' text size * - reduceMotion: in-app override — off even if the OS doesn't ask for it * - defaultRoute: 'dex' | 'team' | 'search' — landing screen on cold launch * - haptics: short vibration on catch (Vibration API, Android only) */ export const settings = createStore('pdx.settings', { versionGroup: 'all', // 'all' = National Dex, newest data, no generation limits pokedex: null, theme: 'system', // system | light | dark | black | sepia accent: 'red', // red | blue | green | amber | violet | rose spriteStyle: 'official', showShiny: false, showTeamNav: true, locale: 'en', fontScale: 'default', reduceMotion: false, defaultRoute: 'dex', haptics: true, }); const DARKISH = new Set(['dark', 'black']); const FONT_SCALE = { small: 0.9, default: 1, large: 1.15 }; export function applyTheme(state = settings.get()) { const root = document.documentElement; const { theme, accent, fontScale, reduceMotion } = state; if (!theme || theme === 'system') root.removeAttribute('data-theme'); else root.setAttribute('data-theme', theme); if (!accent || accent === 'red') root.removeAttribute('data-accent'); else root.setAttribute('data-accent', accent); root.style.fontSize = `${(FONT_SCALE[fontScale] ?? 1) * 100}%`; if (reduceMotion) root.setAttribute('data-reduce-motion', 'true'); else root.removeAttribute('data-reduce-motion'); const meta = document.querySelector('meta[name="theme-color"]'); if (meta) { const dark = DARKISH.has(theme) || ((!theme || theme === 'system') && window.matchMedia('(prefers-color-scheme: dark)').matches); meta.setAttribute( 'content', dark ? '#0b0b0c' : getComputedStyle(root).getPropertyValue('--accent').trim() || '#b3161a', ); } } /** True if animations should be skipped — OS preference or the in-app override. */ export function prefersReducedMotion() { return ( !!settings.get().reduceMotion || window.matchMedia('(prefers-reduced-motion: reduce)').matches ); }