You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/docs/process/PLAN-text-effects.md

15 KiB

PLAN — Efectos de texto (F5 del plan Scene/Ambient, desgajado por D9)

Tipo: plan de ejecución por fases (process — efímero, no fuente de verdad). Fecha: 2026-07-12 · Estado: PLAN COMPLETO — D-T1…D-T4 resueltas · FT0–FT7 EJECUTADAS (2026-07-12). FT7: 6 demos en web/routes/uix/components/{kebab}/ (frame de 6 pestañas de los pasivos shipped — patrón box/format-number; cada prop pública = control vivo; snippet con paridad; sema empty-state justificado; CountUp con demoLocale propio por chips — convención service-demos). Matriz 141/0/0 con Demo ✓ y 0 warns en los 6 · check 0 propios · docs:check 0/0 · contracts 38/38. Verificado en navegador: 6 rutas SSR 200 + hidratadas; CountUp formatea por locale del ecosistema EN VIVO (0,00 es-ES → chip en-US → 0.00); TextGradient resuelve tokens→OKLCH del tema vía var() con background-clip:text + animación activa; TextCircular role=img+33 chars posicionados; TextFocus SIN botones falsos dentro y el auto-avance de timers.interval CORRE (palabra activa cambia — los timers no son rAF); TextBlur segmentos aria-hidden + SR completo + estado inicial sin flash; TextScramble SR surface correcta. Cero errores de consola. Los caminos rAF/IO-dependientes (count visual, flicker, marco) quedan bajo el pendiente visual de pestaña visible ya conocido (pane oculto suspende rAF/IO/measure). Ajuste de tipos post-demo: los 5 visuales aceptan style (fijar tokens por instancia); text-gradient fusiona su style interno con el del consumidor. FT0–FT6: los 6 componentes construidos y verdes en toda la maquinaria — matriz component-audit 141/0/0 (los 6 PASS con README F-1 completo: Baseline/Comparativa/Gaps/Passive justification) · contracts 38/38 (enumeración morfo + scope) · check 0 errores propios. CountUp (servicio patrón FormatNumber: morfo 1-part, uix.format.numbers reactivo cada frame con fallback Intl, spring DHO analítico — el de $motion es element/CSS-bound, anotado —, IO/rAF vía eidos.dom, timers vía eidos.timers; separator del seed NO portado: el locale posee los separadores) · TextGradient (receta CSS + tokens públicos, stops var()-interpolados + preset → --gradient-{name} S9, borde interior #000→--color-surface-default token, keyframes R-4.5 anotados, margin:0 auto del seed fuera) · TextCircular (rAF vía dom, reduce-motion AÑADIDO — el seed no paraba nunca —, currentColor default, nota Aura en morfo/README) · TextBlur (SR hardening: texto real visually-hidden + segmentos aria-hidden; WAAPI directa = primitivo del driver waapi de $motion, migrará al dominio content cuando el RFC aterrice; IO vía dom) · TextFocus (a11y fix: fuera los role="button"+tabindex falsos del seed; timers.interval + dom.measure + observeResize; tokens border/glow; reduce = marco quieto; 1 !important anotado R-4.7) · TextScramble (rediseño de medición: cache de centros por carácter vía dom.measure invalidado por RO — el seed medía N rects POR pointermove; 1 lectura por move; SR hardening; text: string en vez de children — el split solo es honesto con texto plano, declarado). Extensión de vocabulario: MorfoElement + 'p' (primer párrafo del catálogo). Origen: docs/process/PLAN-scene-ambient-pack.md D9 — los 6 efectos de texto de web/routes/demos/animations/text/ NO van al pack Ambient: envuelven contenido real → superficie a11y = superficie de contrato → canon, con destinos heterogéneos. La colección seed queda intacta como referencia comparativa (D5/D7).

Los 6 seeds, analizados

Seed Sustrato Qué hace Interacción A11y del seed Ciudadanía a corregir
count (Count Up) rAF spring analítico Cuenta from→to al entrar en viewport — ✓ texto real (números) Intl.NumberFormat('en-US') hardcoded → debe componer uix.format.numbers (locale-aware); setTimeout→uix.timers; IO→$adom; reduce ✓ ya salta al final
gradient (Gradient Text) CSS puro (background-clip:text + keyframes) Gradiente animado sobre el texto (+ borde opcional) pause-on-hover ✓ texto real intacto Colores crudos→tokens; #000 del borde interior = phantom→token de superficie; @keyframes→R-4.5 (anotación functional / registro motion); reduce ✓ ya
blur (Blur Text) WAAPI por segmento Entrada staggered blur→nítido por palabras/letras al entrar en viewport — ⚠️ trocea en spans (por letras fragmenta lectura SR) SR hardening: aria-label en contenedor + segmentos aria-hidden; WAAPI/stagger vía dominio content de $motion; IO→$adom; reduce ✓ ya
circular (Circular Text) rAF + transforms Caracteres en círculo girando; hover cambia velocidad hover (slowDown/speedUp/pause/goBonkers) ✓ role="img" + aria-label (buena base) rAF→dom.requestFrame; escrituras transform→dom.apply; colores/tamaños crudos→tokens. Nota Aura: texto orbital alrededor de un shape redondo = pieza candidata de la periferia del indicador de agente
focus (True Focus) CSS transitions + JS Palabras difuminadas salvo la activa; marco de esquinas viaja (auto o hover) hover en modo manual ⚠️ role="button" + tabindex=0 en CADA palabra sin acción real = anti-patrón Quitar role=button (o darle semántica real); setInterval→uix.timers; getBoundingClientRect→**dom.measure** (regla dura de layout reads); RO→dom.observe; border/glow crudos→tokens; reduce ✓ ya
scrambled (Scrambled Text) rAF + DOM textContent Scramble de caracteres cerca del puntero pointermove ⚠️ SR puede leer los caracteres scrambled; split destruye el HTML original SR hardening (aria-label + chars aria-hidden); reflow storm: mide getBoundingClientRect POR CARÁCTER en CADA pointermove → rediseño obligado: cache de rects vía dom.measure invalidado por RO; rAF→dom.requestFrame; pointer→dom.listen

Precedente real del canon para servicios: formatNumberMorfo — morfo de 1 part, scope: ['eidos'], sin CSS/eventos («grep-friendly typed wrapper», src/uix/morfo/components/format-number.ts). Confirma que morfo-first aplica incluso a componentes de servicio, con coste mínimo. Los efectos con interacción (circular/focus/scrambled) añaden data-attrs de estado y, si procede, eventos sema.

Decisiones (gate del usuario)

ID Cuestión Opciones analizadas Recomendación
D-T1 Arquitectura por efecto (a) canon completo ×6 (ruta 9 fases con soma+sema cada uno) · (b) familia "text effects" = wrappers eidos con morfo mínimo · (c) mixto: cada uno a su hogar natural — count=service component (patrón FormatNumber); gradient=eidos CSS con morfo mínimo; blur/circular/focus/scrambled=eidos decorativos con morfo (parts+data-attrs de estado) sin soma provider (no hay máquina de estados accesible que justifique soma; la interacción es decorativa) (c) — (a) infla 4 componentes decorativos con capas vacías; (b) niega a count su hogar de servicio
D-T2 count: ¿componente nuevo o prop de FormatNumber? (a) <CountUp> hermano de FormatNumber que compone el MISMO formatter (uix.format.numbers) · (b) prop animate en FormatNumber (a) — FormatNumber es puro/inmediato; animar es otra responsabilidad. Ambos comparten formatter (sin duplicar lógica de locale)
D-T3 gradient: ¿API de colores cruda o sistema de gradientes del theming (S9)? (a) colors: string[] con tokens resolubles (API del seed, theming vía tokens) · (b) integrar el vocabulario de gradientes S9 (presets canónicos) además de colores libres (b) con (a) dentro: acepta stops libres (tokens o hex) Y presets del sistema de gradientes — canon presentado como opción, sin recortar la API del seed
D-T4 Naming de la familia (a) prefijo Text*: TextBlur / TextCircular / TextFocus / TextGradient / TextScramble + CountUp aparte (servicio) · (b) nombres del seed (BlurText, TrueFocus…) · (c) compound Text.Blur (a) — agrupa en el catálogo junto al primitivo Text, evita el genérico "True/Up" del seed; count no es de la familia visual
D-T5 Dónde vive la animación de cada uno blur = dominio content de motion (entrada de contenido: consume uix.motion / WAAPI vía runtime, no keyframes CSS) · gradient = keyframes CSS de receta (loop ambiental, R-4.5) · circular/scrambled = rAF vía dom.requestFrame (estado continuo JS) · focus = CSS transitions + uix.timers · count = spring JS (¿reutilizar el spring de $motion si su API lo expone para valores numéricos? — se verifica en FT1; si no, spring local como el seed) Se deriva de la doctrina motion (perceptual anchoring; animation = channel); no exige gate salvo desacuerdo

Fases (tras el gate)

  • FT0 — Doctrina: asentar D-T* en este plan; verificar precedentes (spring de $motion, sistema de gradientes S9, wrappers eidos-only) leyendo el código real; actualizar docs/README.md si la familia merece fila propia.

  • FT1 — CountUp (servicio): morfo 1-part patrón FormatNumber · compone uix.format.numbers (locale reactivo del ecosistema, NO en-US fijo; el separator del seed se reevalúa contra la API real del formatter) · IO por $adom · uix.timers · spring (D-T5) · reduce = salto al final.

  • FT2 — TextGradient (eidos): receta CSS con tokens + R-4.5 · stops libres + presets S9 (D-T3) · borde con token de superficie · reduce ya en CSS.

  • FT3 — TextBlur (eidos + motion content): split words/letters con SR hardening (contenedor aria-label, segmentos aria-hidden) · stagger/WAAPI vía runtime motion · IO por $adom.

  • FT4 — TextCircular (eidos): rAF vía dom.requestFrame · hover states como data-attrs del morfo · tokens de tipografía/tamaño · conserva role="img". Anotar la conexión Aura (texto orbital del indicador).

  • FT5 — TextFocus (eidos): uix.timers + dom.measure + dom.observe · fix a11y del role=button · border/glow por tokens.

  • FT6 — TextScramble (eidos): cache de rects (dom.measure + invalidación por RO — mata el reflow storm) · SR hardening · pointer por dom.listen · charset/duración/radio como props.

  • FT7 — Cierre: demos con profundidad DEMO_AUTHORING (cada prop = control vivo) · checklist de componente por cada uno · npm run check + vitest + eidos-lint + navegador · memoria + este plan con bloques EJECUTADA.

  • Verify por fase: check 0 errores propios · component:audit verde para los nuevos · demo montada en navegador · SR hardening revisado (árbol de accesibilidad en devtools).

Fuera de alcance

La fase Aura (el plan madre §F6.3) sigue sin construirse aquí; TextCircular solo deja anotada la pieza orbital. Los 4 efectos de imagen (D8) siguen fuera.

Powered by TurnKey Linux.