--- title: RFC — Eidos shape engine type: rfc audience: human + agent status: implemented — phases 1-5 ✅ source: migrated from src/uix/eidos/SHAPE_ENGINE_RFC.md (2026-07-02, docs-book F7.4) --- # RFC — Eidos shape engine > Sibling of [`rfc-color-engine.md`](./rfc-color-engine.md), > [`rfc-typography.md`](./rfc-typography.md) and [`rfc-depth.md`](./rfc-depth.md). > Takes the **shape** channel (`shape` from the book _Diseñando lo que ocurre_) > to reference-grade by **breaking** the references' model — with the **open > cage**. It is the book's **8th and last expression channel** left to elevate > (time · motion · presence · depth · **shape** · color · sound · haptic). ## 0. Thesis > **Shape is not a rounding number an element _has_; it is a perceptual quality > — continuity, family, tension — that the system _composes_ and that, in its > moment, _happens_.** Everybody reduces shape to ONE number: `border-radius`. But the corner has more axes than its magnitude: **how continuous** it is (circular arc vs superellipse), which **family** it belongs to (rounded / continuous / cut / pill), how it **relates** to nested shapes, and **what happens to it** when the element is pressed or commits. Eidos treats shape as a **first-class channel**: unified (magnitude + continuity + family + harmony), and **eventful** (shape can tense/relax), under the **open cage**. ## 1. The survey — how the references do it and where they stop | Framework | Model | Structural limit | | ------------------------ | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | | **Tailwind / Bootstrap** | `rounded-sm…full` scale, circular arc | no continuity, no nested harmony, no family, static; the corner is an arc, period | | **Material 3** | shape scale + per-component mapping + (M3 Expressive) _morph_ + `cut` family | arc/cut; the morph is recent and narrow; a **closed** system (you pick from the scale) | | **Apple / SwiftUI** | **continuous** corners (superellipse) `.continuous` | platform-bound, not a portable token; not eventful; manual nesting | | **CSS today** | `border-radius` = elliptical arcs | no universal native superellipse (`corner-shape` emerging); squircles via SVG/clip-path hacks | **Common limit**: shape = **one arc-radius number**, a **static** property you assign, **detached** from neighboring/nested elements, without real **continuity**, and **disconnected from what happens**. SwiftUI is the only one with continuity — but closed and per-platform. Material is the only one with family + morph — but arc-based and closed. Nobody **unifies** them or makes them **open**. ## 2. Where Eidos stands today (coherent, but not novel) - **Radius scale** in `--radius-{none·sm·md·lg·xl·xxl·full}` (4/6/10/16/20px + pill). ✓ - Assigned **per component** via the size primitive (`radius: 'md'`). ✓ - **BUT** the model is exactly "one **circular arc** number": no continuity (squircle), no **harmony** between nested radii, no perceptual **family**, and **static** (it never happens). You are ~on par with Tailwind; below SwiftUI (continuity) and M3 (family + morph). ## 3. The novel model — 4 pieces 1. **Continuity (superellipse)** — a new axis `--shape-smoothing` (0 = circular arc … 1 = iOS-style superellipse). **Progressive output** (the exact strategy of color's oklch): `border-radius` is emitted (universal, the fallback) **and** `corner-shape: superellipse(…)` where supported. Solves at the root the arc-corner everyone drags along, breaking nobody. 2. **Nested-radius harmony** — inner radius **derived** from the outer: `inner = max(0, outer − gap)` (concentric), computed in `calc()`. A `--shape-nest` convention so card-inside-card (or input-inside-panel) never misaligns. 3. **Shape family** — `rounded · continuous · cut · pill` (+ `sharp`). A **perceptual** axis, not one of magnitude. Eidos canon (like roles/variants): themeable values, canonical set. 4. **_Eventful_ shape (the genuinely new part)** — shape **tenses/relaxes** when something happens: `contact`→squeezes corners, `commit`→rounds them, via the **same signature** that already drives motion/depth. Shape-as-happening, coordinated from **one single sema event**. ## 3.bis The two moments of shape Shape respects the framework's **two-moments model** (motion F1) — it does not reinvent it: | Moment | Attribute (axis) | For shape | Phase | | --------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ----- | | **state** | `data-state` (persistent) | the **resting shape** — magnitude (`--radius-*`) + family + continuity: how the element _is_ | 1 | | **event** | `data-event-*` (transient, during the `hold`) | the **morph** — squeeze/round on `contact`/`commit`: what _happens_. Ordered by `sequence`, next to the motion/depth signatures | 3 | A control **has** a shape (state) **and tenses it** when pressed (event) — the two moments, never collapsed. The book's same rigor (event ≠ state) carried to shape. ## 4. Doctrine — _strong default, open cage_ | Piece | Strong default | Open door | | ---------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | **scale** | canonical `--radius-*` | config-driven (`EidosConfig` size/radius); raw `border-radius` always one step away | | **continuity** | `--shape-smoothing` on by default (subtle) | global/per-component/per-element override; `0` returns to the pure arc | | **family** | canonical set (rounded/continuous/cut/pill/sharp) | the set is **canon** but themeable; a wrapper composes its own without inventing a family | | **nesting** | `--shape-nest` convention (concentric) | opt-in; the computation is transparent (`calc()`), overridable | | **eventful** | `contact`↓squeeze / `commit`↑round | **opt-in/opt-out** (like sound/haptic/depth); sema's open registry; **degrades with `prefers-reduced-motion`**; static shape works without the channel | | **whole system** | canonical theme | runtime **`applyShape(seed)`** (sibling of `applyColorScheme`/`applyTypeScale`/`applyDepth`) | It is exactly how color/typography/depth already operate: retintable canon + runtime builders + primitives always reachable. Shape inherits the same openness contract. ## 5. Token contract (additions, frozen) Bare-prefixed by system (`--shape-…`). The **magnitude** stays in `--radius-*` (nothing renamed → zero breakage): ``` --radius-{key} magnitude (existing scale — kept) --shape-smoothing superellipse EXPONENT (1 = circular arc, 2 ≈ the iOS squircle — the shipped semantics, `STATIC_SHAPE`; this RFC's earlier "factor 0..1" draft did not land) → corner-shape: superellipse(k) --shape-nest-gap default gap for the nested radius (Phase 2) ``` - **Continuity**: `[style] { border-radius: var(--radius-md) }` (universal) + where supported `corner-shape: superellipse(var(--shape-smoothing-k))` (progressive, degrades to the arc). - **Family**: `data-shape='cut'|'continuous'|…` attribute or the component's recipe (not a global token) — selects the appropriate `corner-shape` / clip. - **Nesting**: `border-radius: max(0px, calc(var(--_outer-radius) - var(--shape-nest-gap)))` — a `calc()` convention, no new output token. > **4-corner nesting requires FINITE radii — at `full` only the _top_ nests.** > At `full` (`--radius-full` = 9999px) the radius is **clipped to half the > smaller dimension of each element**. A wide child (e.g. 16:10) **cannot** be > concentric on all 4 corners: its height caps all four at ~½-height, different > from the parent. But its **top corners CAN** be `card_radius − gap` if the > **bottom ones stay square** (the height constraint stops biting). That is the > iOS player geometry: artwork with a concentric rounded top + a straight base > meeting the metadata. > > The `[data-shape-nest]` CSS rule does not work here because the parent's > _computed_ radius value is still `9999px` (the cap is a _used_ value, not > readable from CSS). The `/temas/forma` demo **measures** it > (`min(w,h)/2 − gap` via `ResizeObserver`) and applies it to the artwork's top > corners; the bottom ones stay at `0`. The "concentric radii" toggle is > disabled at `full` ("auto: top only") because there the nesting is automatic > and partial by geometry, not optional. ## 6. Phases (the flexibility is baked into each) 1. ✅ **Continuity + scale + families** — `--shape-smoothing` + `data-shape` → `corner-shape` with the 4 families (`rounded`/`continuous`/`cut`/`scoop`). Progressive emission: universal `border-radius` (the magnitude, `--radius-*` intact) + `corner-shape` where supported (degrades to the arc). Config-driven - validation + test + regen. (Families were brought forward here — they are the same `corner-shape` mechanic.) 2. ✅ **Nested harmony** — `--shape-nest-gap` + `[data-shape-nest]` rule: `border-radius: max(0, var(--shape-outer-radius) − gap)` (concentric). 3. ✅ **_Eventful_ shape (event moment)** — the corner morph **folded into the `press-squeeze` signature** (cross-modal: one press = scale + shadow + corner) + `@property --shape-smoothing` so it interpolates. On the existing `signatures` system; degrades with reduced-motion. Not a parallel system. 4. ✅ **Runtime builder `applyShape(seed)`** — `smoothing`/`nestGap` dial + family override, managed block (sibling of `applyColorScheme`/`applyTypeScale`/`applyDepth`). 5. ✅ **Showcase + docs** — `/temas/forma` (continuity with a dial + families + nesting + eventful, live) + theming reference §30. ## 7. Composition with the existing - **`--radius-*` scale** → each shape's magnitude (reused, not duplicated). - **`signatures` system** (motion/depth) → the eventful morph (Phase 3), not a new engine. - **Motion's two moments** → the morph is a movement of shape, not a jump. - **Sema event bus** → the eventful trigger (Phase 3), like sound/haptic/depth. - **Spacing** → the nested-radius gap (Phase 2). Nothing is reinvented: what is today a loose number gets **unified + made continuous, harmonic and eventful**, under the book's doctrine. ## 8. Doctrine (parallel to color / typography / depth) - **Scale + families = eidos canon** (like roles/variants and planes): themeable values, but the _set_ is canon. - **Continuity = a default quality**, retunable; `0` = pure arc (open cage). - **Eventful shape = an engine capability**, opt-in with degradation (like sound/haptic/depth). - **Theme = retint/retune the perceptually fixed**: change HOW MUCH `md` rounds or how much continuity it carries, not WHAT `cut` means. - **Open cage**: a strong opinion that never traps — raw `border-radius` always reachable. ## 9. Out of scope - Blobs / random organic shapes and arbitrary `path` morphing (shape is perceptual, not a free-geometry engine). - Complex geometry clipping (bespoke per-component clip-path stays in each recipe). - Asymmetric per-side corners as a system (raw per-corner `border-radius` already covers them).