- web/: Vue 3 + Vite + UnoCSS + Pinia, dark tactical theme (amber/#0d1117) - AppNav, ListingCard, SearchView with filters/sort, composables (useSnipeMode, useKonamiCode, useMotion), Pinia search store - Steal shimmer, auction countdown, Snipe Mode easter egg all native in Vue - docker/web/: nginx + multi-stage Dockerfile (node build → nginx serve) - compose.yml: api (8510) + web (8509) services - Dockerfile CMD updated to uvicorn for upcoming FastAPI layer - Clean build: 0 TS errors, 380 modules
30 lines
879 B
TypeScript
30 lines
879 B
TypeScript
import { computed, ref } from 'vue'
|
|
|
|
// Snipe-namespaced localStorage entry
|
|
const LS_MOTION = 'cf-snipe-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(LS_MOTION)
|
|
: null,
|
|
)
|
|
|
|
export function useMotion() {
|
|
// null/missing = default ON; 'false' = explicitly disabled by user
|
|
const rich = computed(() =>
|
|
!OS_REDUCED && _richOverride.value !== 'false',
|
|
)
|
|
|
|
function setRich(enabled: boolean) {
|
|
localStorage.setItem(LS_MOTION, enabled ? 'true' : 'false')
|
|
_richOverride.value = enabled ? 'true' : 'false'
|
|
}
|
|
|
|
return { rich, setRich }
|
|
}
|