1// Tiny shared helpers used across modules.
2
3/** Query one element. Pass the element type when you need more than `Element`. */
4export const $ = <T extends Element = Element>(
5 selector: string,
6 root: ParentNode = document,
7): T | null => root.querySelector<T>(selector);
8
9/** Query all elements as an array. */
10export const $$ = <T extends Element = Element>(
11 selector: string,
12 root: ParentNode = document,
13): T[] => [...root.querySelectorAll<T>(selector)];
14
15/**
16 * Event targets arrive typed as `EventTarget | null`, which has no DOM API.
17 * Narrow once here instead of casting at every listener.
18 */
19export const asElement = (target: EventTarget | null): Element | null =>
20 target instanceof Element ? target : null;
21
22/**
23 * Storage that no-ops when unavailable (private mode, blocked storage).
24 * Resolves once at load; callers use optional chaining: `storage?.getItem(...)`.
25 */
26const safeStorage = (backing: Storage): Storage | null => {
27 try {
28 const key = "__storage-test";
29 backing.setItem(key, "1");
30 backing.removeItem(key);
31 return backing;
32 } catch (_) {
33 return null;
34 }
35};
36export const storage = safeStorage(localStorage);
37export const sessionStore = safeStorage(sessionStorage);
38
39const _rmq = window.matchMedia("(prefers-reduced-motion: reduce)");
40export const prefersReducedMotion = (): boolean => _rmq.matches;