- Rewrite landing hero subtitle with narrative opener ("Seen a listing
that looks almost too good to pass up?") — more universal than the
feature-list version, avoids category assumptions
- Update eBay cancellation callout CTA to "Search above to score
listings before you commit" — direct action vs. passive reminder
- Tile descriptions rewritten with concrete examples: "does this seller
actually know what they're selling?", "40% below median", quoted
"scratch and dent" pattern for instant recognition
- Sign-in strip: add specific page count ("up to 5 pages") and
"community-maintained" attribution for blocklist credibility
- Fix useTheme restore() to re-read from localStorage instead of using
cached module-level ref — fixes test isolation failure where previous
test's setMode('dark') leaked into the restore() system test
- Add 32-test Vitest suite: useTheme (7), searchStore (7),
ListingView component (18) — all green
35 lines
878 B
TypeScript
35 lines
878 B
TypeScript
import { ref, watchEffect } from 'vue'
|
|
|
|
const LS_KEY = 'snipe:theme'
|
|
type ThemeMode = 'system' | 'dark' | 'light'
|
|
|
|
// Module-level — shared across all callers
|
|
const mode = ref<ThemeMode>((localStorage.getItem(LS_KEY) as ThemeMode) ?? 'system')
|
|
|
|
function _apply(m: ThemeMode) {
|
|
const el = document.documentElement
|
|
if (m === 'dark') {
|
|
el.dataset.theme = 'dark'
|
|
} else if (m === 'light') {
|
|
el.dataset.theme = 'light'
|
|
} else {
|
|
delete el.dataset.theme
|
|
}
|
|
}
|
|
|
|
export function useTheme() {
|
|
function setMode(m: ThemeMode) {
|
|
mode.value = m
|
|
localStorage.setItem(LS_KEY, m)
|
|
_apply(m)
|
|
}
|
|
|
|
/** Re-apply from localStorage on hard reload (call from App.vue onMounted). */
|
|
function restore() {
|
|
const saved = (localStorage.getItem(LS_KEY) as ThemeMode) ?? 'system'
|
|
mode.value = saved
|
|
_apply(saved)
|
|
}
|
|
|
|
return { mode, setMode, restore }
|
|
}
|