From 9677f573f9117a81452958203a86966914a8238b Mon Sep 17 00:00:00 2001 From: dev Date: Sat, 6 Jun 2026 00:25:59 +0200 Subject: [PATCH] fix(forma): concentric inner radius at `full` (top corners, measured) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- src/uix/eidos/SHAPE_ENGINE_RFC.md | 22 ++++++++++------- src/uix/eidos/THEMING.md | 10 +++++--- web/routes/temas/forma/+page.svelte | 38 ++++++++++++++++++++++------- 3 files changed, 48 insertions(+), 22 deletions(-) diff --git a/src/uix/eidos/SHAPE_ENGINE_RFC.md b/src/uix/eidos/SHAPE_ENGINE_RFC.md index bb01b3f3f..7d0ee647c 100644 --- a/src/uix/eidos/SHAPE_ENGINE_RFC.md +++ b/src/uix/eidos/SHAPE_ENGINE_RFC.md @@ -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) diff --git a/src/uix/eidos/THEMING.md b/src/uix/eidos/THEMING.md index d245d8ca6..175f8de74 100644 --- a/src/uix/eidos/THEMING.md +++ b/src/uix/eidos/THEMING.md @@ -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. diff --git a/web/routes/temas/forma/+page.svelte b/web/routes/temas/forma/+page.svelte index ee4f60c72..00f51fa99 100644 --- a/web/routes/temas/forma/+page.svelte +++ b/web/routes/temas/forma/+page.svelte @@ -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(); + 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 @@