M-07 · W8b — Motion specs + Tech plan (de la dramaturgia al contrato de ingeniería)
QUÉ ES ESTO: el thinking detrás de la asignación técnica de la landing. W8a fijó la dramaturgia (storyboard, beats 0-5). W8b pone, escena a escena, qué técnica la hace inevitable y a qué coste, y traza el plano Next.js App Router. Planning + specs, no build (eso es M-08). Entregables hermanos:
landing/planning/motion-specs.md+landing/planning/tech-plan.md.FASE: M-07, COMBO W8b. SKU
I-config. Stack OD-004 (Next.js App Router + Framer Motion + GSAP ScrollTrigger + R3F · Vercel). Context7 antes de specs (DC-1). Skill frontend-discipline cargada (Foundation Chain + Premium Floor 7-criteria + Anti-Loop).
El encargo, en una frase
W8a entregó el guion; W8b entrega el reparto técnico y el presupuesto. Por cada pieza animada del storyboard: técnica (R3F / GSAP / Framer / Canvas2D) + budget de performance + degradación móvil/reduced-motion. Y el plano de Next.js: dónde vive cada escena, qué se carga cuándo, cómo se cumple el Premium Floor.
Decisión 0 — Context7 primero, cero API alucinada (DC-1)
Antes de escribir una sola spec de framework, coteja contra docs live: Next.js (next/dynamic { ssr:false }), R3F (Canvas frameloop="demand", dpr adaptativo, forceContextLoss en unmount, Suspense anidado), GSAP (useGSAP + scope + contextSafe, gsap.matchMedia, pinSpacing), Framer (useReducedMotion, LazyMotion features={domAnimation} ~17 KB, useInView { once }). El training data miente en APIs que cambian cada trimestre — esto es lo que separa una spec que se construye de una que se rompe en M-08.
Decisión 1 — el reparto de técnica NO es “R3F para todo lo chulo”
El storyboard ya marcó 3 candidatas R3F (escalera líquida BEAT2, loop BEAT3, grafo BEAT5). La tentación de junior es meter WebGL en las 6. El gate de Rafaella (“¿CSS moderno + SVG + vanilla shippean el 80%? Si sí, no añado three.js”) manda. Resultado del gate, escena por escena:
- BEAT0 sello → SVG + GSAP. Son 4 trazos vectoriales con stroke-draw + un micro-output. SVG lo vende entero, es nítido a cualquier DPR, y es el LCP — no puede depender de WebGL. R3F aquí sería suicidio de performance en la pieza más crítica.
- BEAT1 torrente→embudo → Canvas 2D + GSAP. Miles de fragmentos, 2D plano, masivo. Canvas 2D es ~10× más barato que WebGL para esto. R3F sería sobre-ingeniería.
- BEAT2/3/5 → R3F, y aquí sí: el “cuajar” del líquido (tensión superficial, shader de fluido), la profundidad real del loop con partículas instanciadas, el grafo procedural que se teje en Z. CSS no da el depth ni el “vivo y acumulativo”. Gate superado con argumento, no por gusto.
- BEAT4 mapa → Canvas 2D / SVG + GSAP. Zonas que se iluminan + interacción de veto. Sin profundidad, sin física → no toca WebGL.
Marcador final: 3 R3F · 2 Canvas2D · 1 SVG. Máximo 1 orquestador timeline (GSAP) + 1 engine 3D (R3F). Cero libs “por si acaso”.
Decisión 2 — “Performance es feature”: el budget se diseña, no se audita a posteriori
El error clásico es construir bonito y rezar al Lighthouse después. Aquí el budget es parte de la spec:
- El LCP es el H1 + sello SVG, cero R3F en initial. Las 3 escenas WebGL salen del initial bundle vía
dynamic ssr:false+ IntersectionObserver (mount near-viewport). El peso dethreeno toca el primer chunk → objetivo < 250 KB gzip preservado. - CLS 0 por diseño:
pinSpacing:trueen cada ScrollTrigger + altura del<canvas>reservada conaspect-ratioantes del lazy-mount. El truco fino: el mismo componente estático sirve de<Suspense fallback>Y de versiónreduced-motion. Un solo nodo → cero duplicación, cero deuda, y el fallback ocupa exactamente el box que ocupará el canvas → el mount no desplaza nada. frameloop="demand"+invalidate()por step de scroll: las escenas R3F renderizan cuando el scroll las mueve, no 60 fps en bucle ocioso. Coste de scroll, no de reposo. Única excepción acotada: las barras de error que “respiran” en BEAT3 (unuseFramede amplitud mínima, sólo en viewport) — la firma de honestidad vale ese latido.
Decisión 3 — el riesgo real de las 3 R3F es Safari, no el framerate
Tres canvas WebGL en la misma página = “Too many active WebGL contexts” en Safari si todos viven a la vez. La mitigación no es “esperar a que pase”: IntersectionObserver coordina mount/unmount de modo que nunca haya >2 contextos vivos, y al salir de viewport lejano R3F llama forceContextLoss() + dispose(scene) (libera GPU). Esto es lo que un “demo técnica” ignora y revienta en el navegador del inversor que usa Mac.
Decisión 4 — reduced-motion en tres capas, no un afterthought
El floor inviolable exige que cada efecto degrade. Lo resolví con tres mecanismos cosidos:
- GSAP
matchMedia('(prefers-reduced-motion: reduce)')→ no crea scrub ni pin; estado final, scroll nativo. - Framer
useReducedMotion()→ transform off, opacity sí (un fade suave no marea y sigue siendo accesible). - R3F → si reduce-motion, la escena 3D ni se monta; se sirve el estático. Cero WebGL para quien pidió calma. El storyboard ya especificó el estado reduced-motion de cada beat — mi trabajo fue mapearlo a la capa técnica correcta. Y un inviolable extra: el CTA del BEAT5 es focus-visible y operable por teclado en cualquier estado de motion. La acción nunca depende de que la animación funcione.
Decisión 5 — Next.js App Router y la nota “Astro” del storyboard
El storyboard menciona “OD-004 Astro estático”. OD-004 vigente y el mandato de este COMBO fijan Next.js App Router. No cambio de stack (inviolable). Reconcilio el requisito real que “Astro” cubría — copy SSR indexable + cero hydration mismatch — con la arquitectura App Router: el copy del paper vive en Server Components (SSR, indexable, accesible sin JS); sólo motion/WebGL es client-only (ssr:false). Si M-08 confirma que es 100% estática, output:'export' sobre Vercel sin tocar el stack. Lo dejo anotado explícito en tech-plan.md §1 para que no se lea como contradicción.
Decisión 6 — Foundation Chain: la disciplina antes que el pixel
La skill frontend-discipline obliga el orden brief → tokens → variant → widgets → page. No improviso CSS de página. Propongo recipe VARIANT (un root primario + modificaciones documentadas), no HYBRID — porque el scroll es un solo gesto continuo, no secciones con estéticas distintas; mezclar roots por viewport rompería la continuidad de objeto que es justo nuestra ventaja sobre la Lovable. Root primario candidato: #8 Data Intelligence / Analytical (esto es evidencia auditable, barras de error, grafos) con VARIANT hacia calma institucional para el registro “tranquiliza, no alarma” del BEAT4. Pendiente de ratificación en W8b-fina (paleta + tipo + reference pin Awwwards/FWA + frame Figma de Rafaella). El brief.md y los valores exactos de token se escriben antes de tocar CSS en M-08 — aquí dejo la estructura, no la deuda invisible.
Decisión 7 — Premium Floor mapeado ahora, no descubierto en M-08
Mapeo los 7 criterios contra la landing en tech-plan.md §6. Los que se cierran en specs (5 enrichment real con holgura: 6 piezas de motion; 3 widget variety: ≥7 widgets distintos; 4 reference pin formato) quedan declarados; los que son gate de build (1 token grep, 2 WCAG medición real, 6 human-eye 8-item, 7 visual-critic 3-lens adversarial) quedan declarados como gate de M-08 con su disparador. Honestidad inviolable transversal: los slots ROI [a validar] no se rellenan, las cifras MEDIDAS llevan su id, cero individuo, “no es scoring” visible. El build NO puede “mejorar el copy” rellenando huecos — es contenido de due diligence, no decoración.
Lo que queda vivo (W8b-fina + M-08)
- W8b-fina (arte fino): paleta + tipografía variable + reference pin norte Awwwards/FWA + ratificación root #8 VARIANT + frame Figma de Rafaella vs. wireframe.
- M-08 (build): versiones exactas (
npm viewantes de instalar), brief.md + tokens.css, catálogo/dev/widgets/, las 6 escenas, V3-Visual completo (screenshot 1440 + 375 · Lighthouse · reduced-motion audit · visual-critic 3-lens). 🔴 GATE visual-audit OBLIGATORIO en M-08 (premium-positioning · launch gate).
Cierre
W8b convierte el guion en un contrato de ingeniería falsable: cada animación tiene técnica justificada por el gate, budget de performance explícito, y degradación en tres capas. La vara sigue siendo Awwwards — y Awwwards no se gana con adjetivos, se gana con frames que caben en el budget.
W8b · A-retro + verdict (APPROVE)
Semáforo: 🟢 GREEN. El COMBO W8b cierra limpio.
Qué se entregó (specs, no build). Dos artefactos de planificación: landing/planning/motion-specs.md (las 6 piezas de motion mapeadas beat a beat, cada animación con su técnica, su disparador y su estado reduced-motion) y landing/planning/tech-plan.md (arquitectura Next.js App Router + Framer Motion + GSAP ScrollTrigger + React Three Fiber sobre Vercel, budget de performance explícito, reconciliación de la nota “Astro” del storyboard, mapeo Premium Floor 7-criteria, Foundation Chain VARIANT con root #8 candidato). El thinking completo quedó en este journal (Decisiones 1-7).
Disciplina cumplida. Stack OD-004 respetado sin desvíos (inviolable). Context7 consultado antes de las specs de framework (DC-1). Skill frontend-discipline aplicada: Foundation Chain ordenado (brief→tokens→variant→widgets→page sin improvisar CSS), Premium Floor 7-criteria mapeado, Anti-Loop respetado. Guardarraíles del paper preservados en el copy referenciado: cero invención de cifras, slots ROI [a validar] intactos, las cifras MEDIDAS conservan su id, “no es scoring” visible. Límite NO build respetado — esto es contrato de ingeniería, el build es M-08.
Lo que NO se hizo (y bien). No se construyó la landing (correcto: build = M-08). No se inventó estética de gusto del CEO — sólo decisiones técnicas. No se tocó otro stack que OD-004.
Deuda viva declarada, no oculta. W8b-fina (paleta + tipografía variable + reference pin Awwwards/FWA + ratificación root #8 VARIANT + frame Figma de Rafaella). M-08 (versiones exactas vía npm view, brief.md + tokens.css, catálogo /dev/widgets/, las 6 escenas, V3-Visual completo). 🔴 GATE visual-audit OBLIGATORIO en M-08 (premium-positioning · launch gate).
V-review (id 563f6e53-6148-4309-9afa-5391be9795a7): gaps = [].
Verdict: APPROVE.