Add EMF field mode, Armory shop/waitlist, and ambient haunting layer

Three self-contained features, verified complete and cross-wired
end-to-end (audited: backend 54/54 tests, frontend 110/110 tests,
tsc --noEmit clean, i18n coverage script clean):

- EMF mode: DeviceMotion/DeviceOrientation-based field-meter sensing,
  a fifth séance channel alongside Wire/EVP/Radio/Ouija, with its own
  fragment prompt persona and full frontend gauge UI.
- Armory (shop/waitlist): pre-order capture page for the future
  Ultimate Quantum Box hardware line, rate-limited public endpoint,
  explicitly no payment collection.
- Haunting layer: ambient possession effects (dread-bed audio, title
  glitching, idle-paced whispers/manifests), respects
  prefers-reduced-motion, mounted once at the app root.

Plus WebUSB robustness fixes in lib/sdr.ts (Terratec vendor ID,
explicit selectConfiguration, isSecureContext gate).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PphXq1s43DNRj1uWKGXof
This commit is contained in:
Indiana
2026-07-21 02:28:14 +00:00
parent c372427ced
commit 0756e677b9
33 changed files with 3148 additions and 48 deletions

View File

@@ -36,7 +36,7 @@ export type Utterance = {
export type AnomalyEntry = {
id: number
source: 'radio' | 'evp' | 'wire'
source: 'radio' | 'evp' | 'wire' | 'emf'
frequency: number
magnitude: number
at: number
@@ -101,7 +101,7 @@ const TOAST_CAP = 4
export type SeanceAction =
| { type: 'connection'; state: VeilConnectionState }
| { type: 'frame'; frame: ServerFrame; at?: number }
| { type: 'local_anomaly'; source: 'radio' | 'evp' | 'wire'; frequency: number; magnitude: number; at?: number }
| { type: 'local_anomaly'; source: 'radio' | 'evp' | 'wire' | 'emf'; frequency: number; magnitude: number; at?: number }
| { type: 'local_question'; text: string; at?: number }
| { type: 'set_mode'; mode: Mode }
| { type: 'set_language'; language: Language }
@@ -377,7 +377,7 @@ export type SeanceApi = {
setLanguage: (language: Language) => void
summon: () => void
ask: (text: string) => void
sendAnomaly: (source: 'radio' | 'evp' | 'wire', frequency: number, magnitude: number) => void
sendAnomaly: (source: 'radio' | 'evp' | 'wire' | 'emf', frequency: number, magnitude: number) => void
playUtterance: (utteranceId: string) => void
dismissToast: (id: number) => void
}
@@ -450,7 +450,7 @@ export function SeanceProvider({ children }: { children: ReactNode }) {
}, [])
const sendAnomaly = useCallback(
(source: 'radio' | 'evp' | 'wire', frequency: number, magnitude: number) => {
(source: 'radio' | 'evp' | 'wire' | 'emf', frequency: number, magnitude: number) => {
dispatch({ type: 'local_anomaly', source, frequency, magnitude, at: Date.now() })
socketRef.current?.send({ type: 'anomaly', source, frequency, magnitude })
},