# Eidos TextGradient `` pinta un gradiente animado A TRAVÉS del texto (`background-clip: text`), con borde opcional enmarcado en el mismo gradiente. El texto queda intacto en el DOM — efecto solo de pintura, los lectores de pantalla no notan nada. Familia text-effects (`TextBlur` / `TextCircular` / `TextFocus` / `TextScramble`) — diseño de familia: `docs/decisions/design-text-effects.md`. ## Superficie ```svelte Shiny words Lista de stops del tema Gradiente canónico con nombre (S9) Con marco ``` `colors` **discrimina por forma**: un `string[]` es una lista de stops; un `string` a secas nombra un gradiente canónico del theming (`var(--gradient-{name})`). ## Props clave | Prop | Tipo | Default | Notas | | --- | --- | --- | --- | | `children` | `Snippet` | — | El texto real (queda en el DOM). | | `colors` | `string \| string[]` | naranjas del seed | **`string[]`** = lista de stops (tokens `--color-*` → `var()` o cualquier color CSS). **`string`** = nombre de gradiente canónico (`var(--gradient-{name})`). | | `animationSpeed` | `number` (s) | `8` | Duración del ciclo de pan. | | `showBorder` | `boolean` | `false` | Marco con el mismo gradiente. | | `direction` | `'horizontal' \| 'vertical' \| 'diagonal'` | `'horizontal'` | Eje del pan. | | `pauseOnHover` | `boolean` | `false` | Pausa al hover. | | `yoyo` | `boolean` | `true` | Alterna la dirección (alternate) o loop continuo. | ## Talla y tema Contrato en `lib/recipes/base.ts > text-gradient` (7 claves, entrada NUEVA). Alcance del censo: **100 %** (era 67 %). **Guard R-5.4** (`npm run theming:sentinel -- text-gradient `): **7/7** tokens mueven un computed en vivo, sin una sola excepción. Corrido 2026-08-22. ``` --text-gradient-radius var(--radius-lg) --text-gradient-font-weight var(--font-weight-medium) --text-gradient-blur var(--blur-sm) --text-gradient-border-padding-block var(--space-1) --text-gradient-border-padding-inline var(--space-2) --text-gradient-border-width var(--border-width) --text-gradient-border-inner-bg var(--color-surface-default) ``` Sin eje `size`: el efecto toma la tipografía de su contexto. **El marco es su propio trazo.** El hueco del borde era `calc(100% - 2px)` — dos trazos del gradiente asomando alrededor—, así que el knob es el TRAZO (`border-width`) y el hueco se deriva de él. Un tema que quiera marco grueso escribe un número, no dos restas. **`blur` va a la escala, no a un literal.** El `blur(4px)` de partida era ciego al zoom global: `blur` es familia MÉTRICA del eje `scaling` (theming §23), y `--blur-sm` (4px × scaling) es el mismo píxel a scaling 100 y el correcto al 90 y al 110. ### Lo que NO es contrato `--text-gradient-duration` y `--_text-gradient-play-direction` los escribe el wrapper INLINE desde `animationSpeed` y `yoyo`. **Ningún token gana a un estilo inline**, así que declararlos sería acuñar dos nombres que mienten — misma clase que los cuatro ausentes de `knob`, el `item-gap` de `carousel` y el `preview-z` de `drag-drop`. Se escriben por prop, que es su canal. ## Baseline Port limpio del seed `web/routes/demos/animations/text/gradient/gradient.svelte` (colección intacta como referencia — provenance en `docs/architecture/packs.md`). El seed ya era CSS puro con reduced-motion; sus déficits: colores solo crudos, `#000` fijo en el interior del borde, radius/weight/padding mágicos, y `margin: 0 auto` (opinión de layout). ## Comparativa | Capacidad | Eidos | Tailwind bg-clip | Framer Motion text | animate-gradient libs | | --- | --- | --- | --- | --- | | Stops = tokens del tema (re-tint al cambiar modo) | Sí | Manual | No | No | | Gradientes con NOMBRE del sistema (S9) | Sí | No | No | No | | CSS puro (cero JS por frame) | Sí | Sí | No (JS) | Depende | | Borde en el mismo gradiente | Sí | Manual | No | No | | Reduced motion | Sí | Manual | Sí | No | Referencias externas: MDN background-clip (https://developer.mozilla.org/docs/Web/CSS/background-clip) · Tailwind gradient utilities (https://tailwindcss.com/docs/background-image) · react-bits GradientText (https://reactbits.dev/text-animations/gradient-text). ## Decisiones - **Stops por `var()` interpolado** — un token `--color-primary-solid` va al `linear-gradient` como `var(--color-primary-solid)`: CSS nativo, reactivo al tema sin `resolveToken` JS. - **`colors` discrimina nombre vs stops (D-T3)** — un `string` resuelve `var(--gradient-{name})` (el set canónico de `EidosConfig.gradients` / `applyGradients`, jaula abierta → el tipo se queda en `string`, no una unión cerrada); un `string[]` son stops libres. Una sola prop en vez de `colors` + `preset`: el vocabulario canónico S9 y los stops crudos son la MISMA entrada, discriminada por forma (sin alias de prop). - **El interior del borde ya no es `#000`**: token público `--text-gradient-border-inner-bg` con default `--color-surface-default` (el seed asumía fondo negro; aquí sigue al tema — fija el token para recuperar el look del seed). - **`margin: 0 auto` del seed NO se porta** — centrar es del consumidor. - **Keyframes locales anotados `/* functional: */`** (R-4.5): pan ambiental decorativo, no firma perceptual de evento. - **No emite eventos sema** (pausa por hover = estado CSS, sin percepción). ## Gaps - Ángulos arbitrarios del pan (más allá de 3 ejes): **diferir** — el eje canónico de ángulos existe (`--gradient-angle-*`); si un consumidor lo pide, `direction` acepta entonces esas keys. - Pan del gradiente con nombre respetando SU ángulo interno: **descartar** — el pan mueve `background-position`; el ángulo del nombre es parte del gradiente y se respeta tal cual. - **Nombres MESH** (`colors="aurora"` role-mesh): **no soportado por naturaleza** — un mesh serializa a radiales + un color base, y ese color base no es un `background-image` válido (sí un `background` shorthand), así que no pinta a través de `background-clip: text`. `colors` con nombre exige un gradiente de un solo `` (linear / radial / conic). - Texto multilínea con clip por línea: **implementar cuando haya consumidor** — `background-clip: text` ya cubre el caso común. ## Passive justification Pasivo por diseño: tratamiento de pintura sobre contenido real. Sin estados interactivos propios (el pause-on-hover es `animation-play-state` CSS), sin foco, sin teclado, sin ARIA extra — el contenido ES el contrato de accesibilidad. ## Referencias - Seed portado: `web/routes/demos/animations/text/gradient/gradient.svelte`. - Gradientes con nombre: `EidosConfig.gradients` → `--gradient-{name}` (`src/uix/eidos/lib/render-css.ts`).