Somos Valientes — canvas del camino (decisión de diseño e interacción)

Fecha: 2026-07-10 Contexto: Rediseño de portafolio. Rescatar la idea “Somos Valientes” (proyecto de Lydia Cacho, educación para la paz) como pieza de muestra. Referencia viva: somosvalientes.mx (quedó en sitio vertical simple). Fuente de verdad del diseño: ~/Desktop/SV/boceto.ai (50 artboards, AI 2018). Entregable: ~/Desktop/SV/site/ — prototipo html/css/js corriendo y deployable.

Idea central

Lienzo 2D navegable en todos sentidos. Las escenas del libro/proyecto viven dispersas en un mundo grande, unidas por un sendero punteado que recorre un avión de papel. El usuario “sigue el camino a ser valiente”. El mapa de México es el landmark (escena “Empoderando”) con pins a ONGs reales: Marabunta, Escuela de niñas ciegas, Melel Xojobal. Esto es literal lo que el boceto proponía y no se logró.

Decisiones técnicas (devsigner: scope down, open source, local-first)

  1. DOM + SVG + GSAP, NO WebGPU en v1. WebGPU = soporte irregular y sobre-ingeniería para una demo. La capa de fondo (doodles/mapa) queda como punto de inserción si algún día se quieren shaders — sin reescribir la capa DOM.
  2. GSAP 3 (Draggable/Inertia/MotionPath) — gratis y open desde 2024. Vendorizado local en vendor/ (137 KB). Mueve avión sobre el path + viaje de cámara entre escenas.
  3. Paneo/inercia/rueda hechos a mano (sin lib). Cámara = punto de mundo en el centro del viewport; translate3d sobre #world.
  4. Sendero derivado, no dibujado: SCENES[] (coords) → Catmull-Rom→bézier genera el path, el minimapa y las fracciones de viaje del avión. Editar una escena = editar el array.
  5. Sin build. Deploy = arrastrar site/ a Netlify.

Swaps antes de público

  • Fuente Geomanist (licencia de pago, tuya) → OFL geométrica (General Sans / Hanken Grotesk). @font-face aislado.
  • Silueta de México estilizada (a mano) → GeoJSON/TopoJSON proyectado si se quiere precisión.
  • Video/formularios/fichas ONG = placeholders.

Escenas (orden del camino)

Portada · Sobre el proyecto · Tráiler · Conociendo a los valientes · La Unión hace la fuerza · ¿Cómo le hacen los valientes? · Soy Valiente · Empoderando (mapa MX) · Contacto y ayuda.

Estado — publicable (nivel alto)

Verificado en preview: intro-gate, viaje avión+cámara, temas claro/oscuro/amarillo/rojo, mapa con silueta real de MX + 5 pins numerados (CDMX/Chiapas/Jalisco/Sinaloa/Yucatán) + ruta punteada, ficha de aliada al tocar pin, minimapa, parallax de doodles, teclado, reduced-motion, touch.

Pase de craft (skill frontend-craft, thetasteskill.dev):

  • Firma: mundo = cuaderno de cuadrícula (ancla escuela/infancia), no crema genérico.
  • Avatar del recorrido: avión rojo del boceto (Nef pidió regresar del intento mochila); flip horizontal según dirección de viaje para no volar de cabeza.
  • Camino claro: dashes grandes rojos + halo blanco.
  • Cajas de cada paso: CTA con .btn--line (pill de contorno que hereda la tinta de la tarjeta). Fix: antes usaba .btn--ghost (círculo fijo 44px) que rompía el texto en vertical.
  • Piso de calidad: focus visible, meta/OG/favicon, responsive móvil, escena activa resaltada.
  • Publicable sin licencia: Hanken Grotesk (OFL) vendorizada como respaldo de Geomanist.
  • Corte “una pieza menos”: doodle ‘star’ eliminado (fuera del brief).

Siguiente (witness > finished)

Subir a Netlify esta semana y enseñárselo a alguien (¿colega de R&D? ¿la propia gente del proyecto?). No dejarlo “en progreso” entre sesiones.

Pivote final (mismo día): dos actos

Nef pidió reestructura: Acto 1 = narrativa vertical con fade-ins (IntersectionObserver, scroll-snap); Acto 2 = “El mapa de los viajes” limpio. Orden del viaje: CDMX → Chiapas → Jalisco → Sinaloa → Yucatán, con el copy real de los 5 episodios. Avión vuela SOBRE la ruta curva exacta (Catmull-Rom; getPointAtLength → getScreenCTM → pantalla) con power2.inOut + inclinación tangente clampeada ±16° + settle al llegar. Bug resuelto: transición CSS de mapact nunca disparaba → opacidad por GSAP inline. Lección de verificación: pestaña oculta congela rAF/ticker GSAP → tweens en progress 0; verificar manejando tween.progress() a mano; posicionamiento de entrada ahora síncrono (offsetWidth flush, no rAF).

Mapa rehecho desde boceto 2.pdf (extracción vectorial real)

pdftocairo -svg sobre boceto 2 (pág. 9) → extraídos: continente naranja completo (assets/land.svg, 2 paths, sangrado a los 4 bordes), avión detallado del boceto (assets/plane.svg, 43 paths con orden de apilado: hélice/piloto/ruedas/carita) y la forma del pin (PIN_D inline en main.js, tip normalizado al origen). Camino gris de dashes grandes simulado. Espacio de coords = página del boceto (725.5×408). Pins por color de categoría (#BA1B73 CDMX, 2DA44C Chiapas, 316CBD Jalisco, EB1658 Sinaloa, 1A2747 Yucatán) + callout estilo pág. 10 (thumb con monito + “Estado — Org”) que aparece al aterrizar y se voltea cerca del borde. El avión del boceto mira a la izquierda → espejo al volar a la derecha. Portrait usa meet (mapa completo), landscape slice (sangrado). Gotcha: appendChild de nodos de otro documento SVG falla silencioso → document.importNode(p,true); y el browser cachea main.js aunque el server sirva fresco → ?v=N en index.html. Fuentes de extracción conservadas en assets/map9.svg y map10.svg por si Nef quiere más piezas.

Modo oscuro del mapa (cierre)

Mar negro (—night), continente naranja intacto, camino gris oscuro 3A342E, panel/controles en la paleta del acto 1. Lo que Nef veía (“no se ve el fondo”) era el main.js viejo cacheado por su navegador (previo al fix importNode) — resuelto con versionado ?v=N; un hard-refresh (Cmd+Shift+R) la primera vez.

Nivel detalle con zoom (cierre de página)

Botón “Ver el detalle” en el panel → zoom cinematográfico GSAP: overview escala ×5 hacia el pin y se desvanece mientras aparece el nivel detalle = 32 estados de México en línea amarilla (#E9C63F, vector-effect:non-scaling-stroke) sobre negro — la pantalla “Nombre del Valiente” del boceto. Pin del episodio + etiqueta roja (org / estado · episodio) en el centroide del estado. Sin librería de tiles: GeoJSON de estados (angelnmara/geojson) vendorizado → proyectado a assets/states.svg (91 KB) con python (equirectangular + simplificación 1.2px). Zoom = animación de viewBox (AttrPlugin de GSAP core). ”‹ Volver al mapa” / Esc regresan; navegar episodios sale del detalle primero. Verificado ida/vuelta, 0 errores. v=8.

Afinado de pins + interacción de marcador (v9)

Pins reposicionados SOBRE la tierra del arte usando isPointInFill como sonda (Sinaloa 318,190 · Chiapas 548,350 · Yucatán 588,288 — antes caía en el mar). Cuadro de focus eliminado en pins (outline:none + anillo blanco como indicador). Click en pin: otro pin = viajar; pin activo = abrir detalle (dos clicks seguidos = viajar+detalle). Verificado 5/5 LAND.

  • 5 episodios con org REAL (datos extraídos de los minisitios originales): CDMX Marabunta, Chiapas Melel Xojobal, Jalisco Escuela Niñas y Niños Ciegos, Sinaloa Malala Academia, Yucatán Hunab Proyecto de Vida. Ya no hay “aliada por confirmar”.
  • Videos YouTube por episodio: cdmx a4NLwWUXRF4 · chiapas -3N5QrKT_9Y (Nef pasó “Sinaloa” 2 veces; asumí el segundo = Chiapas) · jalisco _6fiX8hDL7M · sinaloa KtOu8p4JPCs · yucatán JAPysVUq8b4.
  • Reglas ver-detalle: overview SIN botón (hint “doble click en el marcador”); dentro del detalle el panel muestra about real + ▶ Reproducir capítulo + link al minisitio original; el marcador lleva MINIATURA del video (i.ytimg.com) que también reproduce. Móvil: solo marcador (panel oculto en detalle).
  • Reproductor modal vestido: youtube-nocookie embed, header con núm+título, X de marca, Esc/click-fuera cierran, iframe se vacía al cerrar.
  • Deep-links capcdmx…#capyucatan (hash sync con replaceState; exitMap lo limpia).
  • Acto 1: tarjeta “Involúcrate — Organizaciones” (5 links originales sv/*) y CTA “Material de apoyo ↗” → sv/apoyo.
  • Bug cazado con instrumentación runtime: un paintEpisode(0) de boot al final del archivo corría DESPUÉS de applyHash() y repintaba el episodio 0 (estado interno quedaba bien — active=3, hash correcto — solo el DOM se revertía). Fix: paintEpisode(active).
  • Verificado en cadena: deep-link → detalle → miniatura → player con video real cargando. 0 errores.

Hotfix v14: [hidden] vs display del autor

El reproductor salía al cargar y bloqueaba el scroll: .player{display:grid} (regla de autor) le gana al [hidden]{display:none} del UA — misma familia de bug que mapact. Fix global: [hidden]{display:none !important} al inicio del CSS. Verificado: arranque limpio, scroll ok, flujo mapa→detalle→player→cerrar completo.