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

56 lines
15 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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.