+ )}
+
+ )
+}
diff --git a/frontend/src/i18n/coverage-check.mjs b/frontend/src/i18n/coverage-check.mjs
index efd7d59..8501e0e 100644
--- a/frontend/src/i18n/coverage-check.mjs
+++ b/frontend/src/i18n/coverage-check.mjs
@@ -34,6 +34,12 @@ const RULES = [
['seance.status.', ['attuning', 'summoning', 'gathering']],
['seance.toast.', ['rate_limited', 'veil_crowded']], // other codes fall back to toast.message
['seance.modes.', ['wire', 'evp', 'radio', 'ouija', 'emf']],
+ ['seance.hints.', ['wire', 'evp', 'radio', 'ouija', 'emf']],
+ ['seance.conditions.moon.', [
+ 'new_moon', 'waxing_crescent', 'first_quarter', 'waxing_gibbous',
+ 'full_moon', 'waning_gibbous', 'last_quarter', 'waning_crescent',
+ ]],
+ ['seance.conditions.veil.', ['thin', 'veiled', 'heavy']],
['seance.views.', ['board', 'matrix', 'graphs']],
['seance.entity.rarity.', RARITY],
['codex.rarity.', RARITY],
diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json
index 50ae8fa..9ae6775 100644
--- a/frontend/src/i18n/en.json
+++ b/frontend/src/i18n/en.json
@@ -127,12 +127,40 @@
"line": "⚡ {{source}} {{frequency}} +{{magnitude}}dB",
"count": "{{count}} anomalies witnessed this session"
},
+ "hints": {
+ "wire": "the wire whispers on its own when you listen passively",
+ "evp": "the veil listens through your microphone — grant it your ear",
+ "radio": "a tuner in the hand hears further than the wire",
+ "ouija": "ask below, or let the board drift",
+ "emf": "hold still; the field remembers movement"
+ },
+ "conditions": {
+ "title": "VEIL CONDITIONS",
+ "lit": "{{percent}}% lit",
+ "veil": {
+ "thin": "the veil runs thin tonight",
+ "veiled": "the veil hangs, neither open nor shut",
+ "heavy": "the veil lies heavy"
+ },
+ "fieldQuiet": "field: quiet",
+ "fieldStorm": "geomagnetic storm",
+ "moon": {
+ "new_moon": "new moon",
+ "waxing_crescent": "waxing crescent",
+ "first_quarter": "first quarter",
+ "waxing_gibbous": "waxing gibbous",
+ "full_moon": "full moon",
+ "waning_gibbous": "waning gibbous",
+ "last_quarter": "last quarter",
+ "waning_crescent": "waning crescent"
+ }
+ },
"evp": {
"start": "open the microphone",
"stop": "close the microphone",
"listening": "listening to the voice band…",
"deniedTitle": "the microphone stays shut",
- "denied": "This vessel refused the microphone. Grant audio permission in your browser to practice EVP — or let the Wire Ghost listen instead; it needs nothing but the network you already fear.",
+ "denied": "This vessel refused the microphone. Grant audio permission in your browser's site settings to practice EVP — or turn to the ouija board or the Wire Ghost instead; the board needs no ear, and the wire only the network you already fear.",
"floor": "room floor",
"band": "voice band 300–3400 Hz",
"scopeLabel": "live audio spectrum from the microphone"
@@ -141,7 +169,7 @@
"start": "sweep the band",
"stop": "rest the needle",
"unsupportedTitle": "this vessel cannot hear the radio dead",
- "unsupported": "Spirit Radio needs Chromium (WebUSB) and an RTL-SDR dongle. The other modes do not — the wire and the microphone still answer.",
+ "unsupported": "Spirit Radio needs Chromium (WebUSB) and an RTL-SDR dongle. The other modes still answer — EVP hears through the microphone you already carry, and the wire needs nothing at all.",
"troubleshoot": "troubleshooting: chromium-based browser · udev rules for 0bda:2838 · the dongle plugged in, not merely believed in",
"insecure": "the radio dead only speak through consecrated ground — open this site via its https address.",
"insecureHint": "localhost counts as hallowed; a bare http://lan-ip does not.",
diff --git a/frontend/src/i18n/es.json b/frontend/src/i18n/es.json
index f8788d8..7020879 100644
--- a/frontend/src/i18n/es.json
+++ b/frontend/src/i18n/es.json
@@ -127,12 +127,40 @@
"line": "⚡ {{source}} {{frequency}} +{{magnitude}}dB",
"count": "{{count}} anomalías presenciadas en esta sesión"
},
+ "hints": {
+ "wire": "el cable susurra por sí solo cuando escuchas en pasivo",
+ "evp": "el velo escucha a través de tu micrófono — préstale tu oído",
+ "radio": "un sintonizador en la mano oye más lejos que el cable",
+ "ouija": "pregunta abajo, o deja que el tablero derive",
+ "emf": "quédate quieto; el campo recuerda el movimiento"
+ },
+ "conditions": {
+ "title": "CONDICIONES DEL VELO",
+ "lit": "{{percent}}% iluminada",
+ "veil": {
+ "thin": "el velo se vuelve fino esta noche",
+ "veiled": "el velo cuelga, ni abierto ni cerrado",
+ "heavy": "el velo pesa"
+ },
+ "fieldQuiet": "campo: en calma",
+ "fieldStorm": "tormenta geomagnética",
+ "moon": {
+ "new_moon": "luna nueva",
+ "waxing_crescent": "luna creciente",
+ "first_quarter": "cuarto creciente",
+ "waxing_gibbous": "gibosa creciente",
+ "full_moon": "luna llena",
+ "waning_gibbous": "gibosa menguante",
+ "last_quarter": "cuarto menguante",
+ "waning_crescent": "luna menguante"
+ }
+ },
"evp": {
"start": "abrir el micrófono",
"stop": "cerrar el micrófono",
"listening": "escuchando la banda de voz…",
"deniedTitle": "el micrófono sigue cerrado",
- "denied": "Este recipiente rechazó el micrófono. Concede el permiso de audio en tu navegador para practicar EVP — o deja que el Fantasma del Cable escuche en tu lugar; no necesita más que la red que ya temes.",
+ "denied": "Este recipiente rechazó el micrófono. Concede el permiso de audio en los ajustes del sitio de tu navegador para practicar EVP — o acude al tablero ouija o al Fantasma del Cable; el tablero no necesita oído, y el cable solo la red que ya temes.",
"floor": "suelo de la sala",
"band": "banda de voz 300–3400 Hz",
"scopeLabel": "espectro de audio en vivo desde el micrófono"
@@ -141,7 +169,7 @@
"start": "barrer la banda",
"stop": "detener la aguja",
"unsupportedTitle": "este recipiente no oye a los muertos de la radio",
- "unsupported": "La Radio Espíritu necesita Chromium (WebUSB) y un dongle RTL-SDR. Los demás modos no — el cable y el micrófono aún responden.",
+ "unsupported": "La Radio Espíritu necesita Chromium (WebUSB) y un dongle RTL-SDR. Los demás modos aún responden — EVP escucha por el micrófono que ya llevas, y el cable no necesita nada.",
"troubleshoot": "solución de problemas: navegador basado en Chromium · reglas udev para 0bda:2838 · el dongle enchufado, no meramente invocado",
"insecure": "los muertos de la radio solo hablan desde suelo consagrado — abre este sitio por su dirección https.",
"insecureHint": "localhost cuenta como sagrado; una http://ip-local desnuda, no.",
diff --git a/frontend/src/pages/SeancePage.tsx b/frontend/src/pages/SeancePage.tsx
index fc7cb51..bec7b5a 100644
--- a/frontend/src/pages/SeancePage.tsx
+++ b/frontend/src/pages/SeancePage.tsx
@@ -26,6 +26,8 @@ import { EntityCard } from '../components/EntityCard'
import { RitualPanel } from '../components/RitualPanel'
import { JudgmentPanel } from '../components/JudgmentPanel'
import { TelemetryReadout } from '../components/TelemetryReadout'
+import { VeilConditions } from '../components/VeilConditions'
+import { ModeHint } from '../components/ModeHint'
import { PlanchetteMachine } from '../lib/planchette'
import type { PlanchetteSnapshot } from '../lib/planchette'
import { EvpListener } from '../lib/evp'
@@ -362,6 +364,7 @@ function SeanceSession({ username }: { username: string }) {
onToggleOpen={() => setDossierOpen((o) => !o)}
/>
+
@@ -477,6 +480,7 @@ function WirePanel() {
})}