diff options
| author | Elizabeth Hunt <me@liz.coffee> | 2025-12-14 17:23:02 -0800 |
|---|---|---|
| committer | Elizabeth Hunt <me@liz.coffee> | 2025-12-14 17:23:02 -0800 |
| commit | ac49e3a48fb18d95f7f3609107bbf05dc9e170ea (patch) | |
| tree | ea0029c8f1208e01ddc01afea7dfa3ff75db58b5 /src/ts/script.ts | |
| parent | 17708f1430fd63e9350af82abe40a7dd78b15b8c (diff) | |
| download | adelie-ac49e3a48fb18d95f7f3609107bbf05dc9e170ea.tar.gz adelie-ac49e3a48fb18d95f7f3609107bbf05dc9e170ea.zip | |
Code cleanup
Diffstat (limited to 'src/ts/script.ts')
| -rw-r--r-- | src/ts/script.ts | 163 |
1 files changed, 97 insertions, 66 deletions
diff --git a/src/ts/script.ts b/src/ts/script.ts index 56c6d63..81c61f7 100644 --- a/src/ts/script.ts +++ b/src/ts/script.ts @@ -1,92 +1,123 @@ import Prism from 'prismjs'; -import 'prismjs/components/prism-javascript'; import 'prismjs/components/prism-css'; +import 'prismjs/components/prism-javascript'; import 'prismjs/components/prism-markup'; import { initOneko } from './oneko'; -// Auto-detect asset base from the bundled script's origin -(() => { - window.ASSET_BASE = ''; - const bundleScript = document.querySelector('script[src*="bundle"]'); - if (bundleScript?.src) { - try { - const url = new URL(bundleScript.src, window.location.href); - window.ASSET_BASE = url.origin; - } catch { - // Fall back to empty string - } - } -})(); +type Theme = 'light' | 'dark'; -(() => { - const toggleButton = document.getElementById('theme-toggle') as HTMLInputElement; - const html = document.documentElement; +const THEME_STORAGE_KEY = 'theme'; +const DARK_THEME_ATTRIBUTE = 'data-theme'; - const sessionTheme = sessionStorage.getItem('theme'); - const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; +function detectAssetBase(): string { + const currentScript = document.currentScript as HTMLScriptElement | null; + const scriptSrc = + currentScript?.src ?? + document.querySelector<HTMLScriptElement>('script[src*="bundle"]')?.src ?? + ''; - const initialTheme = sessionTheme || (systemPrefersDark ? 'dark' : 'light'); + if (!scriptSrc) return ''; - if (initialTheme === 'dark') { - html.setAttribute('data-theme', 'dark'); - toggleButton.checked = true; + try { + return new URL(scriptSrc, window.location.href).origin; + } catch { + return ''; } +} + +function setAssetBase(): void { + window.ASSET_BASE = detectAssetBase(); +} + +function getSystemTheme(): Theme { + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; +} + +function applyTheme(theme: Theme): void { + if (theme === 'dark') { + document.documentElement.setAttribute(DARK_THEME_ATTRIBUTE, 'dark'); + } else { + document.documentElement.removeAttribute(DARK_THEME_ATTRIBUTE); + } +} + +function getStoredTheme(): Theme | null { + const theme = sessionStorage.getItem(THEME_STORAGE_KEY); + return theme === 'dark' || theme === 'light' ? theme : null; +} + +function initThemeToggle(): void { + const toggleButton = document.getElementById('theme-toggle'); + if (!(toggleButton instanceof HTMLInputElement)) return; + + const initialTheme = getStoredTheme() ?? getSystemTheme(); + applyTheme(initialTheme); + toggleButton.checked = initialTheme === 'dark'; toggleButton.addEventListener('change', () => { - const theme = html.getAttribute('data-theme'); - - if (theme === 'dark') { - html.removeAttribute('data-theme'); - sessionStorage.setItem('theme', 'light'); - toggleButton.checked = false; - } else { - html.setAttribute('data-theme', 'dark'); - sessionStorage.setItem('theme', 'dark'); - toggleButton.checked = true; - } + const nextTheme: Theme = toggleButton.checked ? 'dark' : 'light'; + applyTheme(nextTheme); + sessionStorage.setItem(THEME_STORAGE_KEY, nextTheme); }); -})(); +} -(() => { - const colors = ['#ff69b4', '#b19cd9', '#8b6f47', '#ff85c0', '#c4b5fd', '#d4a574']; - const shapes = ['❀', '✿', '✽', '✾', '✻', '❊', '❋', '✼']; +function sample<T>(items: readonly T[]): T { + return items[Math.floor(Math.random() * items.length)]; +} - document.addEventListener('mousemove', (e: MouseEvent) => { - createParticle(e.clientX, e.clientY); - }); +function initFairyDust(): void { + if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; - const createParticle = (x: number, y: number) => { + const colors = ['#ff69b4', '#b19cd9', '#8b6f47', '#ff85c0', '#c4b5fd', '#d4a574'] as const; + const shapes = ['❀', '✿', '✽', '✾', '✻', '❊', '❋', '✼'] as const; + + const particleLifetimeMs = 800; + + function createParticle(x: number, y: number): void { const particle = document.createElement('div'); particle.className = 'fairy-dust'; - const shape = shapes[Math.floor(Math.random() * shapes.length)]; - const size = Math.random() * 8 + 6; - const color = colors[Math.floor(Math.random() * colors.length)]; const offsetX = (Math.random() - 0.5) * 20; const offsetY = (Math.random() - 0.5) * 20; - const rotation = Math.random() * 360; - - particle.textContent = shape; - particle.style.cssText = ` - position: fixed; - left: ${x + offsetX}px; - top: ${y + offsetY}px; - font-size: ${size}px; - color: ${color}; - opacity: 0.4; - pointer-events: none; - z-index: 9001; /* it's over 9000 */ - line-height: 1; - transform: rotate(${rotation}deg); - animation: fairy-float 0.8s ease-out forwards; - `; + const rotationDeg = Math.random() * 360; + const sizePx = Math.random() * 8 + 6; + + particle.textContent = sample(shapes); + particle.style.left = `${x + offsetX}px`; + particle.style.top = `${y + offsetY}px`; + particle.style.fontSize = `${sizePx}px`; + particle.style.color = sample(colors); + particle.style.transform = `rotate(${rotationDeg}deg)`; document.body.appendChild(particle); - setTimeout(() => particle.remove(), 800); - }; -})(); + window.setTimeout(() => particle.remove(), particleLifetimeMs); + } + + // rAF throttle to avoid creating a particle per mouse event. + let latestX = 0; + let latestY = 0; + let scheduled = false; -document.addEventListener('DOMContentLoaded', () => { + document.addEventListener('mousemove', (event: MouseEvent) => { + latestX = event.clientX; + latestY = event.clientY; + + if (scheduled) return; + scheduled = true; + + window.requestAnimationFrame(() => { + scheduled = false; + createParticle(latestX, latestY); + }); + }); +} + +function init(): void { + setAssetBase(); + initThemeToggle(); + initFairyDust(); Prism.highlightAll(); initOneko(); -}); +} + +document.addEventListener('DOMContentLoaded', init); |
