- useApiFetch: typed fetch wrapper with network/http error discrimination - useMotion: reactive localStorage override for rich-animation toggle, respects OS prefers-reduced-motion - useHaptics: label/discard/skip/undo vibration patterns, gated on rich mode - useKonamiCode + useHackerMode: 10-key Konami sequence → hacker theme, persisted in localStorage - test-setup.ts: jsdom matchMedia stub so useMotion imports cleanly in Vitest - smoke.test.ts: import smoke tests for all 4 composables (12 tests, all passing)
28 lines
775 B
TypeScript
28 lines
775 B
TypeScript
import { computed, ref } from 'vue'
|
|
|
|
const STORAGE_KEY = 'cf-avocet-rich-motion'
|
|
|
|
// OS-level prefers-reduced-motion — checked once at module load
|
|
const OS_REDUCED = typeof window !== 'undefined'
|
|
? window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
: false
|
|
|
|
// Reactive ref so toggling localStorage triggers re-reads in the same session
|
|
const _richOverride = ref(
|
|
typeof window !== 'undefined'
|
|
? localStorage.getItem(STORAGE_KEY)
|
|
: null
|
|
)
|
|
|
|
export function useMotion() {
|
|
const rich = computed(() =>
|
|
!OS_REDUCED && _richOverride.value !== 'false'
|
|
)
|
|
|
|
function setRich(enabled: boolean) {
|
|
localStorage.setItem(STORAGE_KEY, enabled ? 'true' : 'false')
|
|
_richOverride.value = enabled ? 'true' : 'false'
|
|
}
|
|
|
|
return { rich, setRich }
|
|
}
|