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/rfcs/rfc-shape.md

195 lines
12 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.

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

Powered by TurnKey Linux.