# RFC — Motor de forma (shape) de Eidos > Hermano de `COLOR_ENGINE_RFC.md`, `TYPOGRAPHY_ENGINE_RFC.md` y `DEPTH_ENGINE_RFC.md`. > Lleva el canal de **forma** (`shape` del libro *Diseñando lo que ocurre*) a reference-grade > **rompiendo** el modelo de los referentes — con la **jaula abierta**. Es el **8º y último > canal de expresión** del libro sin elevar (tiempo · movimiento · presencia · profundidad · > **forma** · color · sonido · háptica). ## 0. Tesis > **La forma no es un número de redondeo que un elemento _tiene_; es una cualidad perceptual > — continuidad, familia, tensión — que el sistema _compone_ y que, en su momento, _ocurre_.** Todos reducen la forma a UN número: `border-radius`. Pero la esquina tiene más ejes que su magnitud: **cuán continua** es (arco circular vs superelipse), a **qué familia** pertenece (redondeada / continua / cortada / píldora), cómo se **relaciona** con las formas anidadas, y **qué le ocurre** cuando el elemento es presionado o confirma. Eidos trata la forma como un **canal de primera clase**: unificado (magnitud + continuidad + familia + armonía), y **eventful** (la forma puede tensarse/relajarse), bajo la **jaula abierta**. ## 1. El estudio — cómo lo hacen los referentes y dónde topan | Framework | Modelo | Límite estructural | |---|---|---| | **Tailwind / Bootstrap** | escala `rounded-sm…full`, arco circular | sin continuidad, sin armonía anidada, sin familia, estática; la esquina es un arco y punto | | **Material 3** | shape scale + mapping por componente + (M3 Expressive) *morph* + familia `cut` | arco/cut; el morph es reciente y acotado; sistema **cerrado** (eliges de la escala) | | **Apple / SwiftUI** | esquinas **continuas** (superelipse) `.continuous` | atado a plataforma, no es token portable; no eventful; anidado manual | | **CSS hoy** | `border-radius` = arcos elípticos | sin superelipse nativa universal (`corner-shape` emergente); squircles vía SVG/clip-path hacks | **Límite común**: forma = **un número de radio de arco**, propiedad **estática** que asignas, **desligada** de los elementos vecinos/anidados, sin **continuidad** real, y **desconectada de lo que ocurre**. SwiftUI es el único con continuidad — pero cerrada y por plataforma. Material es el único con familia + morph — pero arco y cerrado. Nadie los **unifica** ni los hace **abiertos**. ## 2. Dónde está Eidos hoy (coherente, pero no novedoso) - **Escala de radius** en `--radius-{none·sm·md·lg·xl·xxl·full}` (4/6/10/16/20px + pill). ✓ - Asignada **por componente** vía el size primitive (`radius: 'md'`). ✓ - **PERO** el modelo es exactamente "un número de **arco circular**": sin continuidad (squircle), sin **armonía** entre radios anidados, sin **familia** perceptual, y **estática** (nunca ocurre). Estás ~a la par de Tailwind; por debajo de SwiftUI (continuidad) y de M3 (familia + morph). ## 3. El modelo novel — 4 piezas 1. **Continuidad (superelipse)** — un eje nuevo `--shape-smoothing` (0 = arco circular … 1 = superelipse tipo iOS). **Output progresivo** (estrategia idéntica al oklch del color): se emite `border-radius` (universal, el fallback) **y** `corner-shape: superellipse(…)` donde se soporta. Resuelve de raíz la esquina-de-arco que todos arrastran, sin romper a nadie. 2. **Armonía de radios anidados** — radio interior **derivado** del exterior: `interior = max(0, exterior − gap)` (concéntrico), computado en `calc()`. Una convención `--shape-nest` para que tarjeta-dentro-de-tarjeta (o input-dentro-de-panel) nunca desencaje. 3. **Familia de forma** — `rounded · continuous · cut · pill` (+ `sharp`). Eje **perceptual**, no de magnitud. Canon del eidos (como roles/variants): valores themeables, set canon. 4. **Forma _eventful_ (lo genuinamente nuevo)** — la forma se **tensa/relaja** al ocurrir algo: `contact`→aprieta esquinas (squeeze), `commit`→las redondea, vía la **misma firma** que ya dirige motion/depth. Forma-como-suceso, coordinada desde **un solo evento sema**. ## 3.bis Los dos momentos de la forma La forma respeta el **modelo de dos momentos** del framework (motion F1) — no lo reinventa: | Momento | Atributo (eje) | Para shape | Fase | |---|---|---|---| | **estado** | `data-state` (persistente) | la **forma en reposo** — magnitud (`--radius-*`) + familia + continuidad: cómo *es* el elemento | 1 | | **evento** | `data-event-*` (transitorio, durante el `hold`) | el **morph** — apretar/redondear al `contact`/`commit`: lo que *ocurre*. Ordenado por `sequence`, junto a las firmas de motion/depth | 3 | Un control **tiene** una forma (estado) **y la tensa** al pulsarse (evento) — los dos momentos, nunca colapsados. Mismo rigor del libro (evento ≠ estado) llevado a la forma. ## 4. Doctrina — _default fuerte, jaula abierta_ | Pieza | Default fuerte | Puerta abierta | |---|---|---| | **escala** | `--radius-*` canónica | config-driven (`EidosConfig` size/radius); el `border-radius` crudo siempre a un paso | | **continuidad** | `--shape-smoothing` por defecto (sutil) | override global/por-componente/por-elemento; `0` vuelve al arco puro | | **familia** | set canónico (rounded/continuous/cut/pill/sharp) | el set es **canon** pero themeable; un wrapper compone una propia sin inventar familia | | **anidado** | convención `--shape-nest` (concéntrico) | opt-in; el cálculo es transparente (`calc()`), anulable | | **eventful** | `contact`↓apretar / `commit`↑redondear | **opt-in/opt-out** (como sound/haptic/depth); registro abierto de sema; **degrada con `prefers-reduced-motion`**; la forma estática funciona sin el canal | | **sistema entero** | tema canónico | **`applyShape(seed)`** runtime (hermano de `applyColorScheme`/`applyTypeScale`/`applyDepth`) | Es exactamente cómo ya operan color/tipografía/profundidad: canon retintable + builders runtime + primitivos siempre accesibles. Forma hereda el mismo contrato de apertura. ## 5. Contrato de tokens (añadidos, congelados) Bare-prefixed por sistema (`--shape-…`). La **magnitud** sigue en `--radius-*` (no se renombra nada → cero rotura): ``` --radius-{key} magnitud (escala existente — se mantiene) --shape-smoothing factor de superelipse 0..1 (Fase 1) → corner-shape: superellipse(k) --shape-nest-gap gap por defecto del radio anidado (Fase 2) ``` - **Continuidad**: `[style] { border-radius: var(--radius-md) }` (universal) + donde se soporte `corner-shape: superellipse(var(--shape-smoothing-k))` (progresivo, degrada al arco). - **Familia**: atributo `data-shape='cut'|'continuous'|…` o recipe del componente (no un token global) — selecciona el `corner-shape` / clip apropiado. - **Anidado**: `border-radius: max(0px, calc(var(--_outer-radius) - var(--shape-nest-gap)))` — convención en `calc()`, sin token de salida nuevo. > **El anidado de 4 esquinas requiere radios FINITOS — a `full` es solo el _top_.** A `full` > (`--radius-full` = 9999px) el radio se **recorta a la mitad de la dimensión menor de cada > elemento**. Un hijo ancho (p. ej. 16:10) **no puede** ser concéntrico en sus 4 esquinas: su altura > cap­ea las cuatro a ~½-altura, distinto del padre. Pero sus **esquinas superiores SÍ pueden** valer > `radio_card − gap` si las **inferiores se quedan rectas** (la restricción de altura deja de morder). > Esa es la geometría del reproductor iOS: carátula con top redondeado concéntrico + base recta que > encuentra los metadatos. > > La regla CSS `[data-shape-nest]` no sirve aquí porque el valor *computado* del radio del padre > sigue siendo `9999px` (el cap es un valor *usado*, no legible en CSS). El demo `/temas/forma` lo > **mide** (`min(w,h)/2 − gap` vía `ResizeObserver`) y lo aplica a las esquinas superiores de la > carátula; las inferiores quedan a `0`. El toggle "radios concéntricos" se inhabilita a `full` > («auto: solo top») porque ahí el anidado es automático y parcial por geometría, no opcional. ## 6. Fases (la flexibilidad va horneada en cada una) 1. ✅ **Continuidad + escala + familias** — `--shape-smoothing` + `data-shape` → `corner-shape` con las 4 familias (`rounded`/`continuous`/`cut`/`scoop`). Emisión progresiva: `border-radius` universal (la magnitud, `--radius-*` intactos) + `corner-shape` donde se soporte (degrada al arco). Config-driven + validación + test + regen. (Las familias se adelantaron aquí — son la misma mecánica `corner-shape`.) 2. ✅ **Armonía anidada** — `--shape-nest-gap` + regla `[data-shape-nest]`: `border-radius: max(0, var(--shape-outer-radius) − gap)` (concéntrico). 3. ✅ **Forma _eventful_ (momento-evento)** — el morph de esquina **plegado en la firma `press-squeeze`** (cross-modal: un press = escala + sombra + esquina) + `@property --shape-smoothing` para que interpole. Sobre el sistema de `signatures` existente; degrada con reduced-motion. No un sistema paralelo. 4. ✅ **Builder runtime `applyShape(seed)`** — dial de `smoothing`/`nestGap` + override de familias, bloque gestionado (hermano de `applyColorScheme`/`applyTypeScale`/`applyDepth`). 5. ✅ **Showcase + docs** — `/temas/forma` (continuidad con dial + familias + anidado + eventful en vivo) + THEMING §30. ## 7. Composición con lo existente - **Escala `--radius-*`** → la magnitud de cada forma (se reusa, no se duplica). - **Sistema de `signatures`** (motion/depth) → el morph eventful (Fase 3), no un motor nuevo. - **Dos momentos de motion** → el morph es un movimiento de la forma, no un salto. - **Bus de eventos sema** → el disparo eventful (Fase 3), como sound/haptic/depth. - **Espaciado** → el gap del radio anidado (Fase 2). No se reinventa nada: se **unifica + se hace continuo, armónico y eventful** lo que hoy es un número suelto, bajo la doctrina del libro. ## 8. Doctrina (paralela a color / tipografía / profundidad) - **Escala + familias = canon del eidos** (como roles/variants y planos): valores themeables, pero el *set* es canon. - **Continuidad = cualidad por defecto**, retuneable; `0` = arco puro (jaula abierta). - **Forma eventful = capacidad del motor**, opt-in con degradación (como sound/haptic/depth). - **Theme = retintar/retunear lo perceptualmente fijo**: cambia CUÁNTO redondea `md` o cuánta continuidad lleva, no QUÉ significa `cut`. - **Jaula abierta**: opinión fuerte que nunca atrapa — `border-radius` crudo siempre accesible. ## 9. Fuera de alcance - Blobs / formas orgánicas aleatorias y morphing de `path` arbitrario (la forma es perceptual, no un motor de geometría libre). - Recortes complejos por geometría (clip-path a medida por componente queda en cada recipe). - Esquinas por-lado asimétricas como sistema (el `border-radius` por-esquina crudo ya las cubre).