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

10 KiB

title type audience status source
RFC — Eidos shape engine rfc human + agent implemented — phases 1-5 ✅ 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-typography.md and 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 factor 0..1 (Phase 1) → 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.