El misterio de la rata — rama hp-exp en academia-arcana (2026-09-26)

~/nef/academia-arcana/ ahora es repo git. Dos experiencias, una por rama; el servidor es el mismo para ambas según el checkout activo:

python3 -m http.server 8743 --directory ~/nef/academia-arcana   # http://localhost:8743
  • main → Academia Arcana (grimorio dominio público, ver 2026-07-19).
  • hp-exp → El misterio de la rata: MVP de una página inspirado en la escena de la Casa de los Gritos (Prisionero de Azkaban). Descubrir que Scabbers = Peter Pettigrew y que Sirius es inocente.

MECÁNICAS (hp-exp)

  • 6 hotspots invisibles sobre ilustración SVG propia (viewBox 1200×800, wrapper con aspect-ratio: 3/2 para que los % de los hotspots coincidan siempre con el dibujo). Pulsan, se iluminan al hover, se apagan al encontrar.
  • Modal-pergamino por pista; contador “Pistas: n/6” con barra.
  • Panel lateral: pistas arrastrables (DnD escritorio, tap-dos-veces móvil); pares con texto propio en CONEXIONES (data.js), resto usa genérico.
  • 6/6 desbloquea “Revelar la verdad”: overlay con transformación rata→Pettigrew (crossfade CSS), línea de tiempo de la traición y “SIRIUS BLACK ES INOCENTE”.
  • Progreso en localStorage clave misterio-rata ({encontradas, conexiones, revelado}). Botón “Reiniciar misterio” (confirm + borrado).
  • Modo pista: 30 s sin actividad → resalta un hotspot pendiente; también hay botón “Pista” manual. El timer se resetea con pointermove/keydown.
  • Ding mágico con WebAudio (3 senos E6/B6/E7 en cascada, sin archivos).

DISEÑO

  • Paleta: noche 0d1220, papel e3d3ae, sepia 4a3a26, oro c9982f, luna f0e6c8. Cinzel + EB Garamond (mismas fuentes que Arcana).
  • Escena: personajes a contraluz (siluetas + rim-light sepia), luna llena con halo, haz de luz, puerta rota con luz cálida, textura de papel con feTurbulence sobre toda la escena. Sin imágenes con copyright: todo SVG.
  • Mobile-first; panel pasa a columna derecha 320px sticky a ≥960px.
  • prefers-reduced-motion respetado (revelación sin animación, estados finales).

RESTILO “MOMENTO” POTTERMORE (2026-09-26, ref: youtube FmBvjQj_qgw = Pottermore Book 6 Ch 9 Moment 1 walkthrough)

  • Escena full-bleed: .escena-marco es fixed inset 0, SVG con preserveAspectRatio="xMidYMid slice" (recorta, nunca letterbox).
  • POSICIÓN DE HOTSPOTS: con slice los % se desalinean según el ratio del viewport. Se calculan en JS: escala = max(w/1200, h/800), desp = centrado, px = vb*escala + desp. Recalcular en resize (posicionarHotspots).
  • PARALLAX: SVG reagrupado en 3 <g class="capa" data-depth="0.3|0.6|1"> (fondo / personajes / luz+polvo). pointermove → objetivo ±18px/±12px, lerp 0.07 en rAF (arranca solo al mover, se duerme al asentarse). La capa se traslada en unidades viewBox = px pantalla / escala. Cada pista tiene profundidad en data.js y su hotspot hereda el offset de su capa (inline transform incluye el -50% de centrado).
  • Pared extendida a 640 y suelo subido a 540: solape de 100px para que el parallax nunca abra grietas entre capas.
  • Ambiente: motas de polvo flotando (CSS, duraciones escalonadas), halo de luna respirando, viñeta radial fija (.escena-marco::after).
  • Chrome mínimo: cabecera flotante con degradado y pointer-events:none (botones con auto), progreso como 6 pips dorados (no barra), panel = cajón lateral (“Tablero”, translateX; hoja inferior translateY en móvil) con velo clicable. Pausa de animaciones también con body.panel-abierto.
  • body overflow hidden + 100dvh: la app es un lienzo, no una página.
  • PENDIENTE: zoom con doble clic (sello Pottermore) quedó fuera a propósito.

HANDOFF DE ARTE (2026-09-26 — spec acordada para subir fidelidad Pottermore)

  • Lo que más falta para el estilo del walkthrough es la ILUSTRACIÓN real; el SVG actual es placeholder. El código ya hace todo lo demás.
  • Nef entrega en ~/nef/academia-arcana/assets/:
    • escena-fondo.webp → paredes, ventana, luna, puerta rota
    • escena-media.webp → suelo, mesa, 5 personajes + Scabbers
    • escena-frente.webp → haz de luz, polvo/bordes cercanos (opcional)
  • REGLAS: lienzo completo 2400×1600 (2× del viewBox 1200×800) en las 3 capas, transparencia, NO recortar objeto por objeto, pintar clean plate detrás de los personajes (si no, el parallax abre agujeros), NO hornear viñeta / textura de papel / brillos de hotspot (van en código). Paleta actual o pasar swatches nuevos para retintar la UI.
  • Opcionales: brillos pintados por hotspot (crossfade hover, ej. brillo-rata.webp) y loop de ambiente ~30 s.
  • Al llegar el arte: sustituir capas SVG por imágenes apiladas (slice / parallax / hotspots quedan igual), reajustar las 6 coordenadas a la nueva composición y añadir el zoom con doble clic.

ESTADO AL CERRAR SESIÓN (2026-09-26)

  • hp-exp commiteado: 856b24a (restilo + fixes). main intacta: c32ac30.
  • Sirviendo con python3 -m http.server 8743 --directory ~/nef/academia-arcana.
  • Smoke test: 34 asserts, todos en verde (stub en /tmp, se regenera fácil).

GOTCHA DEL ENTORNO DE TEST

  • No hay playwright/jsdom instalados. Smoke test con mini-DOM stub en /var/folders/.../T/opencode/test-misterio-rata.js (24 asserts, todos pasan).
  • En node, dos eval() seguidos NO comparten const (en el navegador dos <script> sí). Concatenar data.js + app.js en un solo eval para testear.

GOTCHA RESUELTO (2026-09-26, modal trabado)

  • backdrop-filter: blur() sobre la escena SVG (que ya usa feTurbulence) re-muestrea todo el fondo en cada frame → el modal se trababa al abrir. Fix: quitar backdrop-filter (fondo rgba .82 basta) + pausar con animation-play-state los hotspots y el botón revelar mientras body.modal-abierto / body.revelando están activos. Regla: nunca backdrop-filter encima de SVG con filtros.

GOTCHA RESUELTO (2026-09-26, modal que no se cierra — EL BUG REAL)

  • .modal-fondo { display: grid } pisaba la regla UA [hidden]{display:none}: el modal estaba visible DESDE LA CARGA (vacío) y hidden=true no lo ocultaba. “Guardar pista” sí ejecutaba cerrarModal() pero nada cambiaba en pantalla. Fix: .modal-fondo[hidden], .revelar-fondo[hidden] { display:none !important }.
  • Lección SPAB: el smoke test pasaba porque el stub verificaba la PROPIEDAD hidden, no el display computado. Añadida guardia estática: el test lee styles.css y exige la regla [hidden] para ambos contenedores (26 asserts).
  • Regla: si un contenedor se oculta con el atributo hidden, no declararle display sin una regla [hidden] explícita que lo proteja.

PENDIENTE SI SE RETOMA

  • Verla en navegador real y ajustar posiciones de hotspots a ojo (las coords están calculadas sobre el viewBox, pero el pulso puede tapar la rata).
  • Fuentes autoalojadas si se quiere 100% local-first (mismo pendiente que Arcana).