fix(forma): concentric inner radius at `full` (top corners, measured)

Full-bleed lost the inset frame entirely — "¿dónde está el radio
interior?". A 16:10 cover can't be concentric on all 4 corners at full
(its height caps every corner to ~half-height), but its TOP corners CAN
match the card if the bottom stays square — the iOS-player geometry.

CSS can't read the card's dimension-capped radius (computed value is
still 9999px; the cap is a *used* value), so measure it via
ResizeObserver (min(w,h)/2 − gap) and apply to the cover's top corners;
bottom corners square, meeting the metadata. The inset frame is kept on
top/sides so the inner radius reads.

Finite radii unchanged (full 4-corner concentric). Toggle at full shows
"auto: solo top". Docs reconciled in SHAPE_ENGINE_RFC §5 + THEMING §30.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
active-uix
dev 4 months ago
parent 003a9a9e47
commit 9677f573f9

@ -101,15 +101,19 @@ nada → cero rotura):
- **Anidado**: `border-radius: max(0px, calc(var(--_outer-radius) - var(--shape-nest-gap)))` —
convención en `calc()`, sin token de salida nuevo.
> **El anidado concéntrico requiere radios FINITOS.** A `full` (`--radius-full` = 9999px) el radio
> se **recorta a la mitad de la dimensión menor de cada elemento** — un contenedor alto y su hijo
> ancho (p. ej. 16:10) recortan a valores distintos, así que **no pueden ser concéntricos**. `full`
> es para **hojas** (chips · botones · avatares · inputs-píldora), no para contenedores con hijos
> que deban encajar. La regla `[data-shape-nest]` se desactiva ahí. El demo `/temas/forma` lo
> refleja: al elegir `full`, el toggle "radios concéntricos" se inhabilita («n/a a full — geometría
> de hoja») y la carátula **sangra a borde completo sin radio propio** — el `overflow: hidden` + la
> curva real del card la recortan para que su esquina superior coincida exacta con la del card (la
> única forma de "encajar" sin radios finitos: dejar que el padre recorte al hijo, no calcular).
> **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)

@ -2322,10 +2322,12 @@ arco + estático; la continuidad solo existía en Apple, atada a plataforma).
`corner-shape` (Chromium 2025+).
- **Armonía anidada** — `[data-shape-nest]` deriva `border-radius: max(0px,
var(--shape-outer-radius) − var(--shape-nest-gap))`: el hijo queda concéntrico al padre (que
expone su radio en `--shape-outer-radius`). **Requiere radios finitos**: a `full` (9999px) el
radio se recorta a la mitad de la dimensión menor *de cada elemento*, así que padre e hijo con
proporciones distintas no pueden ser concéntricos — `full` es para **hojas** (chips · botones ·
avatares · inputs-píldora), no para contenedores anidados.
expone su radio en `--shape-outer-radius`). **El concéntrico de 4 esquinas requiere radios
finitos**: a `full` (9999px) el radio se recorta a ½ de la dimensión menor *de cada elemento*, así
que un hijo de proporción distinta no puede serlo en las 4. Pero **sí en las superiores**
(`radio_card − gap`) si las inferiores quedan rectas — la geometría del reproductor iOS. El demo
`/temas/forma` lo mide (`ResizeObserver`, porque el cap es valor *usado* no legible en CSS) y lo
aplica al top de la carátula.
- **Eventful (dos momentos)** — la forma en reposo (`data-shape`) + el **morph** al pulsar: la
firma `press-squeeze` cuadra la esquina un instante (`--shape-smoothing` 2→3→2, registrado con
`@property` para que interpole). Cross-modal: un evento mueve escala + sombra + esquina.

@ -40,10 +40,26 @@
let nest = $state(true)
let playing = $state(true)
// `full` (pill/circle) is dimension-capped per element → concentric nesting is impossible there:
// a tall pill can't have a concentric wide-pill child (their radii cap to different values).
// Concentric needs FINITE radii; `full` is for leaf elements (chips · buttons · avatars).
// `full` (pill/circle) caps the radius to half each element's SHORTER side. A 16:10 cover can't
// be concentric on all four corners (its height caps them to ~half-height), but its TOP corners
// can match the card if the bottom stays square. CSS can't read the card's dimension-capped
// radius (computed value is still 9999px), so we measure it and derive the concentric top radius.
const isFull = $derived(radius === 'full');
let cardEl = $state<HTMLDivElement>();
let coverTopRadius = $state(0);
const NEST_GAP = 8; // var(--space-2)
$effect(() => {
const el = cardEl;
if (!el) return;
const measure = () => {
const r = el.getBoundingClientRect();
coverTopRadius = Math.max(0, Math.round(Math.min(r.width, r.height) / 2 - NEST_GAP));
};
measure();
const ro = new ResizeObserver(measure);
ro.observe(el);
return () => ro.disconnect();
});
const RADII = ['sm', 'md', 'lg', 'xl', 'full'] as const
@ -88,6 +104,7 @@
<div class="lab">
<!-- the sample UI -->
<div
bind:this={cardEl}
class="player"
data-shape={family}
style="--sample-radius: var(--radius-{radius}); --shape-outer-radius: var(--radius-{radius}); --shape-smoothing: {smoothing}"
@ -98,6 +115,9 @@
data-shape-nest={nest && !isFull ? '' : undefined}
class:cover-naive={!nest && !isFull}
class:cover-full={isFull}
style={isFull
? `border-start-start-radius:${coverTopRadius}px;border-start-end-radius:${coverTopRadius}px`
: undefined}
>
<button class="player-play" data-shape={family} onclick={() => (playing = !playing)}>
{playing ? '❚❚' : '▶'}
@ -153,7 +173,7 @@
<input type="checkbox" bind:checked={nest} disabled={isFull} />
<span class="ctl-label"
>radios concéntricos {isFull
? '· n/a a full (geometría de hoja)'
? '· auto a full (solo top: la píldora no deja redondear el pie)'
: nest
? ''
: '· off (la carátula desencaja)'}</span
@ -349,11 +369,11 @@
overflow: hidden;
}
.cover-naive { border-radius: var(--radius-xl); }
/* at `full` the card is a pill (radius caps to half its shorter side); a finite-radius cover
floating inside reads as broken, and a concentric cover is impossible (different aspect ratios
cap differently). So at full the cover bleeds edge-to-edge with NO radius of its own — the
card's overflow:hidden + its real corner clip the cover's top to match the card exactly. */
.cover-full { margin: 0; border-radius: 0; }
/* at `full` the card is a pill (radius caps to half its shorter side). The cover can't be
concentric on all 4 corners (its height caps them), but its TOP corners CAN match the card —
set inline from the measured radius (card·½ − gap) — while the bottom stays square and meets
the metadata: the iOS-player geometry. Inset frame kept on top/sides so the inner radius reads. */
.cover-full { margin: var(--space-2) var(--space-2) 0; border-end-start-radius: 0; border-end-end-radius: 0; }
.player-play {
inline-size: 3.25rem;
aspect-ratio: 1;

Loading…
Cancel
Save

Powered by TurnKey Linux.