@ -1,20 +1,20 @@
< script lang = "ts" >
/**
* Shape demo — the Eidos shape channel, the 8th book axis no web DS has elevated .
* Shape demo — the Eidos shape channel, made tangible on real UI .
*
* 1. Continuidad — round vs squircle, with a live `--shape-smoothing` dial.
* 2. Familias — rounded · continuous · cut · scoop (one `corner-shape` mechanism).
* 3. Anidado — concentric radii (data-shape-nest): inner = outer − gap, corners parallel.
* 4. Ocurre — press → the corner FIRMS (the press-squeeze firma, cross-modal).
* 1. Taller — a real "now playing" card + a control panel (family · smoothing · radius ·
* concentric) that retunes the WHOLE card live (the jaula-abierta centerpiece).
* 2. De cerca — round vs squircle, big, so the seam difference reads.
* 3. Familias — the same chip in all 4 families + each on a fitting component.
* 4. Ocurre — press real buttons → the corner firms (the press-squeeze firma).
* 5. Vs el resto— the field is arc+static; the strip shows what only Eidos does.
*
* Self-contained: eidos foundation (shape tokens + families + firma) + `data-theme`.
* Continuity needs `corner-shape` (Chromium 2025+); it degrades to the plain arc elsewhere.
* Continuity needs `corner-shape` (Chromium 2025+); degrades to the plain arc elsewhere.
*/
import '@/uix/eidos/index.css'
let theme = $state< 'light' | 'dark'>('dark')
// Mirror the theme onto < html > so the foundation tokens resolve at :root (faithful real-app).
$effect(() => {
const el = document.documentElement
const prev = el.getAttribute('data-theme')
@ -25,25 +25,28 @@
}
})
// 1 · live continuity dial (1 = circular arc · 2 = squircle · 3+ = squarer)
let smoothing = $state(2)
// 2 · families
const FAMILIES = [
{ id : 'rounded' , label : 'rounded' , note : 'arco circular — el default de todos' } ,
{ id : 'continuous' , label : 'continuous' , note : 'squircle — iOS, ahora token web' } ,
{ id : 'cut' , label : 'cut' , note : 'bisel — ángulo' } ,
{ id : 'scoop' , label : 'scoop' , note : 'cóncavo — entrante' }
{ id : 'rounded' , label : 'rounded' } ,
{ id : 'continuous' , label : 'continuous' } ,
{ id : 'cut' , label : 'cut' } ,
{ id : 'scoop' , label : 'scoop' }
] as const
type Family = (typeof FAMILIES)[number]['id']
// 3 · nesting toggle
// ── 1 · Taller: the live control panel drives the whole sample card ──────────
let family = $state< Family > ('continuous')
let smoothing = $state(2)
let radius = $state('xl')
let nest = $state(true)
let playing = $state(true)
const RADII = ['sm', 'md', 'lg', 'xl'] as const
// 4 · eventful press — stamp the contact event so the press-squeeze firma fires
let pressed = $state(false)
function firePress(): void {
pressed = false
requestAnimationFrame(() => (pressed = true ))
// ── 4 · eventful press ───────────────────────────────────────────────────────
let pressed = $state< string | null > (null )
function firePress(id: string ): void {
pressed = null
requestAnimationFrame(() => (pressed = id ))
}
< / script >
@ -59,126 +62,219 @@
< / div >
< h1 class = "mast-title" > La esquina, < em > repensada< / em > < / h1 >
< p class = "mast-lede" >
Toda la web reduce la forma a un número: < code > border-radius< / code > , un arco circular. L a
esquina tiene más ejes — < strong > continuidad< / strong > , < strong > familia< / strong > ,
< strong > armonía< / strong > y < strong > tensión< / strong > . Aquí la forma es un canal, y también
< i > ocurre< / i > .
Toda la web reduce la forma a un número: < code > border-radius< / code > , un arco. La esquin a
tiene más ejes — < strong > continuidad< / strong > , < strong > familia< / strong > ,
< strong > armonía< / strong > y < strong > tensión< / strong > . Aquí la forma es un canal: tócala y mira
la misma UI cambiar de carácter .
< / p >
< / header >
<!-- ── 1 · Continuidad ───── ─────────────────────────────────────────── -->
<!-- ── 1 · Taller (centerpiece) ─────────────────────────────────────────── -->
< section class = "section" >
< div class = "sec-head" >
< h2 class = "sec-title" > Continuidad < / h2 >
< h2 class = "sec-title" > El taller < / h2 >
< p class = "sec-note" >
El arco circular tiene un quiebre donde el lado encuentra la curva. El < em > squircle< / em >
(superelipse) fluye sin costura — lo que hace premium a iOS. En Eidos es un token:
< code > --shape-smoothing< / code > . Muévelo.
Una tarjeta real. Cambia familia, suavizado, magnitud o el anidado concéntrico y
< em > todo< / em > se reentona a la vez — un solo sistema, no estilos sueltos. (El avatar es un
círculo de verdad: las familias no lo deforman, por diseño.)
< / p >
< / div >
< div class = "cont" >
< div class = "cont-pair" >
< figure class = "cont-item" >
< div class = "cont-box" data-shape = "rounded" > < / div >
< figcaption > < b > rounded< / b > · arco · superellipse(1)< / figcaption >
< / figure >
< figure class = "cont-item" >
< div class = "cont-box cont-live" data-shape = "continuous" style = "--shape-smoothing: { smoothing } " ></ div >
< figcaption >< b > continuous</ b > · superellipse({ smoothing . toFixed ( 2 )} )</ figcaption >
< / figure >
< div class = "lab" >
<!-- the sample UI -->
< div
class="player"
data-shape={ family }
style="--sample-radius: var(--radius-{ radius } ); --shape-outer-radius: var(--radius-{ radius } ); --shape-smoothing: { smoothing } "
>
< div
class="player-cover"
data-shape={ family }
data-shape-nest={ nest ? '' : undefined }
class:cover-naive={ ! nest }
>
< button class = "player-play" data-shape = { family } onclick= {() => ( playing = ! playing )} >
{ playing ? '❚❚' : '▶' }
< / button >
< span class = "player-eq" data-on = { playing } > <i ></ i >< i ></ i >< i ></ i >< i ></ i ></ span >
< / div >
< div class = "dial" >
< label class = "dial-row" >
< span class = "dial-label" > suavizado< / span >
< input type = "range" min = "1" max = "3.2" step = "0.05" bind:value = { smoothing } / >
< span class = "dial-val" > { smoothing . toFixed ( 2 )} </ span >
< div class = "player-body" >
< div class = "player-head" >
< span class = "player-avatar" > N< / span >
< div class = "player-meta" >
< span class = "player-title" > Continuum< / span >
< span class = "player-artist" > Nocturne Ensemble< / span >
< / div >
< span class = "player-tag" data-shape = { family } > FLAC</span >
< / div >
< div class = "player-track" > < span class = "player-fill" > < / span > < / div >
< div class = "player-row" >
< input class = "player-input" data-shape = { family } placeholder="añade a una lista …" />
< button class = "player-add" data-shape = { family } > + </button>
< / div >
< / div >
< / div >
<!-- the control panel -->
< div class = "panel" >
< div class = "ctl" >
< span class = "ctl-label" > familia< / span >
< div class = "chips" >
{ #each FAMILIES as f }
< button class = "chip" data-active = { family === f . id } onclick= {() => ( family = f . id )} >
{ f . label }
< / button >
{ /each }
< / div >
< / div >
< div class = "ctl" class:ctl-dim = { family !== 'continuous' } >
< span class = "ctl-label" > suavizado< / span >
< input type = "range" min = "1" max = "3.2" step = "0.05" bind:value = { smoothing } disabled= { family !== 'continuous' } />
< span class = "ctl-val" > { smoothing . toFixed ( 2 )} </ span >
< / div >
< div class = "ctl" >
< span class = "ctl-label" > magnitud< / span >
< div class = "chips" >
{ #each RADII as r }
< button class = "chip" data-active = { radius === r } onclick= {() => ( radius = r )} > { r } </ button >
{ /each }
< / div >
< / div >
< label class = "ctl ctl-check" >
< input type = "checkbox" bind:checked = { nest } / >
< span class = "ctl-label" > radios concéntricos { nest ? '' : '· off (la carátula desencaja)' } </ span >
< / label >
< code class = "dial-code" > corner-shape: superellipse(var(--shape-smoothing));< / code >
< code class = "panel-code"
>data-shape="{ family } "{ family === 'continuous' ? ` · --shape-smoothing:$ { smoothing . toFixed ( 2 )} ` : '' } < /code
>
< / div >
< / div >
< / section >
<!-- ── 2 · De cerca ─────────────────────────────────────────────────────── -->
< section class = "section" >
< div class = "sec-head" >
< h2 class = "sec-title" > De cerca< / h2 >
< p class = "sec-note" >
El arco circular tiene un < em > quiebre< / em > donde el lado encuentra la curva. El squircle
(superelipse) fluye sin costura — lo que hace premium a iOS. Misma magnitud, distinta
continuidad.
< / p >
< / div >
< div class = "closeup" >
< figure class = "cu" >
< div class = "cu-box" data-shape = "rounded" > < / div >
< figcaption > < b > rounded< / b > · < code > superellipse(1)< / code > — arco< / figcaption >
< / figure >
< figure class = "cu" >
< div class = "cu-box" data-shape = "continuous" > < / div >
< figcaption > < b > continuous< / b > · < code > superellipse(2)< / code > — squircle< / figcaption >
< / figure >
< / div >
< / section >
<!-- ── 2 · Familias ─────────────────────────────────────────────────── -->
<!-- ── 3 · Familias ──── ─────────────────────────────────────────────────── -->
< section class = "section" >
< div class = "sec-head" >
< h2 class = "sec-title" > Familias< / h2 >
< p class = "sec-note" >
Una sola mecánica (< code > corner-shape< / code > vía < code > data-shape< / code > ) cubre cuatro
familias perceptuales. Material solo tiene < code > cut< / code > ; Apple solo
< code > continuous< / code > . Aquí, las cuatro.
familias. Material solo tiene < code > cut< / code > ; Apple solo < code > continuous< / code > . Aquí,
la misma etiqueta en las cuatro:
< / p >
< / div >
< div class = "fams" >
< div class = "fam -row ">
{ #each FAMILIES as f }
< figure class = "fam" >
< div class = "fam-box" data-shape = { f . id } > </div >
< figcaption >< b > { f . label } </ b >< span > { f . note } </ span ></ figcaption >
< span class = "fam-chip" data-shape = { f . id } > etiqueta</span >
< figcaption > { f . label } </figcaption >
< / figure >
{ /each }
< / div >
< / section >
<!-- ── 3 · Anidado ──────────────────────────────────────────────────── -->
<!-- ── 4 · Ocurre ───── ──────────────────────────────────────────────────── -->
< section class = "section" >
< div class = "sec-head" >
< h2 class = "sec-title" > Armonía anidada< / h2 >
< h2 class = "sec-title" > La forma < em > ocurre< / em > < / h2 >
< p class = "sec-note" >
Radios concéntricos: el interior = exterior − gap, así las esquinas quedan
< em > paralelas< / em > . Sin esto, una tarjeta dentro de otra desencaja. Solo SwiftUI lo tiene;
aquí es < code > data-shape-nest< / code > . Apágalo y míralo romperse.
Púlsalos: la esquina < em > se afirma< / em > bajo el dedo (la continuidad se cuadra un instante)
a la vez que baja de escala y se aplana la sombra — un evento, tres canales: la firma
cross-modal. El botón < code > rounded< / code > es el control: no morfea .
< / p >
< / div >
< div class = "nest-stage" >
< div class = "nest-outer" data-shape = "continuous" style = "--shape-outer-radius: var(--radius-xl)" >
< div class = "nest-row" >
{ #if nest }
< div class = "nest-inner" data-shape = "continuous" data-shape-nest > < / div >
< div class = "nest-inner" data-shape = "continuous" data-shape-nest > < / div >
{ : else }
< div class = "nest-inner nest-naive" data-shape = "continuous" > < / div >
< div class = "nest-inner nest-naive" data-shape = "continuous" > < / div >
{ /if }
< / div >
< / div >
< label class = "nest-toggle" >
< input type = "checkbox" bind:checked = { nest } / >
< span > radios concéntricos { nest ? '· on' : '· off (radio fijo, desencaja)' } </ span >
< / label >
< div class = "press-row" >
< button
class="press-btn"
data-shape="continuous"
data-event-family={ pressed === 'a' ? 'contact' : undefined }
data-event-phase={ pressed === 'a' ? 'active' : undefined }
onanimationend={() => ( pressed = null )}
onpointerdown={() => firePress ( 'a' )}
>continuous< / button >
< button class = "press-btn press-ctrl" data-shape = "rounded" > rounded< / button >
< / div >
< / section >
<!-- ── 4 · Ocurre (eventful · la firma) ─────────────────────────────── -->
<!-- ── 5 · Vs el resto ──────────────────────────────────────────────────── -->
< section class = "section" >
< div class = "sec-head" >
< h2 class = "sec-title" > La forma < em > ocurre< / em > < / h2 >
< h2 class = "sec-title" > Lo que solo hace Eidos < / h2 >
< p class = "sec-note" >
Púlsalo: la esquina < em > se afirma< / em > bajo el dedo (la continuidad se cuadra un instante)
— a la vez que baja de escala y se aplana la sombra. Un solo evento, tres canales: la
< em > firma cross-modal< / em > . (Solo el botón < code > continuous< / code > morfea; el otro es el
control.)
El campo entero (Tailwind · shadcn · Chakra · Mantine · Ant · Radix · Carbon · Fluent ·
Spectrum · Polaris) se queda en la primera columna.
< / p >
< / div >
< div class = "press-row" >
< div class = "vs" >
< div class = "vs-col vs-them" >
< span class = "vs-h" > todos los demás< / span >
< div class = "vs-sample" data-shape = "rounded" > < / div >
< span class = "vs-cap" > escala + arco + estático< / span >
< / div >
< div class = "vs-col" >
< span class = "vs-h" > + continuidad< / span >
< div class = "vs-sample" data-shape = "continuous" > < / div >
< span class = "vs-cap" > squircle tokenizado< / span >
< / div >
< div class = "vs-col" >
< span class = "vs-h" > + familias< / span >
< div class = "vs-sample" data-shape = "scoop" > < / div >
< span class = "vs-cap" > cut · scoop · …< / span >
< / div >
< div class = "vs-col" >
< span class = "vs-h" > + anidado< / span >
< div class = "vs-sample vs-nest" data-shape = "continuous" style = "--shape-outer-radius: 28px" >
< span data-shape = "continuous" data-shape-nest > < / span >
< / div >
< span class = "vs-cap" > concéntrico< / span >
< / div >
< div class = "vs-col" >
< span class = "vs-h" > + eventful< / span >
< button
class="press-btn"
class="vs-sample vs- press"
data-shape="continuous"
data-event-family={ pressed ? 'contact' : undefined }
data-event-phase={ pressed ? 'active' : undefined }
onanimationend={() => ( pressed = false )}
onpointerdown={ firePress }
>continuous — pulsa< / button >
< button class = "press-btn press-ctrl" data-shape = "rounded" > rounded — sin morph< / button >
data-event-family={ pressed === 'v' ? 'contact' : undefined }
data-event-phase={ pressed === 'v' ? 'active' : undefined }
onanimationend={() => ( pressed = null )}
onpointerdown={() => firePress ( 'v' )}
aria-label="pulsa"
>< / button >
< span class = "vs-cap" > pulsa · morfea< / span >
< / div >
< / div >
< / section >
< footer class = "footnote" >
< p >
< strong > El primer squircle-como-token de la web.< / strong > Todo el campo (Tailwind · shadcn ·
Chakra · Mantine · Ant · Radix · Carbon · Fluent · Spectrum · Polaris) es escala de magnitud
+ arco + estático. La continuidad solo existía en Apple (plataforma); el morph solo en
Material (ad-hoc). Eidos reúne continuidad + familias + anidado + eventful como tokens
abiertos — magnitud intacta en < code > --radius-*< / code > , degradando al arco donde no hay
< code > corner-shape< / code > . Runtime: < code > applyShape()< / code > .
< strong > El primer squircle-como-token de la web.< / strong > Continuidad + familias + anidado +
eventful como tokens abiertos — magnitud intacta en < code > --radius-*< / code > , degradando al
arco donde no hay < code > corner-shape< / code > . Config-driven + runtime
< code > applyShape()< / code > . La continuidad solo existía en Apple (plataforma); el morph solo
en Material (ad-hoc). Aquí se reúnen, y son tuyos.
< / p >
< / footer >
< / div >
@ -202,8 +298,7 @@
.theme-toggle:hover { color : var ( -- color - content - primary ); }
.mast-title { font - family : var ( -- font - family - display ); font - size : var ( -- font - size - xxxl ); font - weight : 600 ; line - height : 1 ; letter - spacing : - 0.03 em ; margin : 0 0 1.25 rem ; text - wrap : balance ; }
.mast-title em { font - style : italic ; color : var ( -- color - primary - text , var ( -- color - content - primary )); }
.mast-lede { max - inline - size : 50ch ; font - size : var ( -- font - size - lg ); line - height : 1.5 ; color : var ( -- color - content - secondary ); margin : 0 ; text - wrap : pretty ; }
.mast-lede i { font - style : italic ; }
.mast-lede { max - inline - size : 52ch ; font - size : var ( -- font - size - lg ); line - height : 1.5 ; color : var ( -- color - content - secondary ); margin : 0 ; text - wrap : pretty ; }
.mast-lede strong { color : var ( -- color - content - primary ); font - weight : 600 ; }
.section { margin - block - end : clamp ( 3.5 rem , 9 vw , 7 rem ); }
@ -214,38 +309,99 @@
.sec-note em { color : var ( -- color - content - primary ); font - style : normal ; font - weight : 600 ; }
code { font - family : var ( -- font - family - mono ); font - size : 0.9em ; color : var ( -- color - content - secondary ); background : var ( -- color - surface - muted ); padding : 0.1em 0.4 em ; border - radius : 4px ; }
/* 1 · Continuidad */
.cont { display : grid ; grid - template - columns : 1fr ; gap : clamp ( 1.5 rem , 4 vw , 3 rem ); align - items : center ; }
@media (min-width: 46rem) { . cont { grid - template - columns : 1.2fr 1 fr ; } }
.cont-pair { display : flex ; gap : clamp ( 1.5 rem , 4 vw , 3 rem ); justify - content : center ; }
.cont-item { margin : 0 ; display : flex ; flex - direction : column ; gap : 1rem ; align - items : center ; }
.cont-box { inline - size : clamp ( 8 rem , 20 vw , 11 rem ); aspect - ratio : 1 ; border - radius : 38px ; background : linear - gradient ( 145 deg , var ( -- color - primary - solid ), color - mix ( in oklab , var ( -- color - primary - solid ) 40 % , # 12 a594 )); }
.cont-item figcaption { font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - xs ); color : var ( -- color - content - muted ); }
.cont-item figcaption b { color : var ( -- color - content - primary ); font - weight : 600 ; }
.dial { display : flex ; flex - direction : column ; gap : 1rem ; }
.dial-row { display : flex ; align - items : center ; gap : 1rem ; }
.dial-label { font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - xs ); text - transform : uppercase ; letter - spacing : 0.08em ; color : var ( -- color - content - muted ); inline - size : 5rem ; }
.dial-row input { flex : 1 ; accent - color : var ( -- color - primary - solid ); }
.dial-val { font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - sm ); color : var ( -- color - content - primary ); inline - size : 3rem ; text - align : end ; }
.dial-code { display : block ; font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - xs ); padding : 0.85rem 1 rem ; border - radius : var ( -- radius - md ); background : var ( -- color - surface - muted ); color : var ( -- color - content - secondary ); word - break : break - all ; }
/* 1 · Taller */
.lab { display : grid ; grid - template - columns : 1fr ; gap : clamp ( 1.5 rem , 4 vw , 3 rem ); align - items : start ; }
@media (min-width: 52rem) { . lab { grid - template - columns : 22rem 1 fr ; } }
.player {
inline-size: 100%;
max-inline-size: 22rem;
border-radius: var(--sample-radius);
background: var(--color-surface-raised);
border: 1px solid var(--rule);
box-shadow: var(--depth-overlay-shadow), var(--depth-overlay-halo);
overflow: hidden;
}
.player-cover {
position: relative;
aspect-ratio: 16 / 10;
margin: var(--space-2);
/* radius comes from [data-shape-nest] (concentric) or .cover-naive — never set here, or the
scoped rule would beat the foundation's nest rule and the inset would stop being concentric */
background:
radial-gradient(60% 80% at 30% 20%, color-mix(in oklab, var(--color-primary-solid) 90%, white), transparent 70%),
radial-gradient(70% 90% at 80% 90%, #12a594, transparent 70%),
linear-gradient(145deg, var(--color-primary-solid), #2a1a4a);
display: grid;
place-items: center;
overflow: hidden;
}
.cover-naive { border - radius : var ( -- radius - xl ); }
.player-play {
inline-size: 3.25rem;
aspect-ratio: 1;
border-radius: var(--sample-radius);
border: 0;
background: color-mix(in srgb, #000 28%, transparent);
backdrop-filter: blur(6px);
color: #fff;
font-size: var(--font-size-sm);
cursor: pointer;
display: grid;
place-items: center;
}
.player-play:hover { background : color - mix ( in srgb , # 000 40 % , transparent ); }
.player-eq { position : absolute ; inset - block - end : 0.6rem ; inset - inline - start : 0.7rem ; display : flex ; gap : 3px ; align - items : flex - end ; block - size : 1rem ; opacity : 0 ; transition : opacity 200 ms ; }
.player-eq[data-on='true'] { opacity : 0.9 ; }
.player-eq i { inline - size : 3px ; background : # fff ; border - radius : 2px ; animation : eq 900 ms ease - in - out infinite ; }
.player-eq i:nth-child(1) { block - size : 40 % ; animation - delay : - 200 ms ; }
.player-eq i:nth-child(2) { block - size : 90 % ; animation - delay : - 500 ms ; }
.player-eq i:nth-child(3) { block - size : 60 % ; animation - delay : - 100 ms ; }
.player-eq i:nth-child(4) { block - size : 100 % ; animation - delay : - 350 ms ; }
@keyframes eq { 0 % , 100 % { block - size : 25 % ; } 50 % { block - size : 100 % ; } }
@media (prefers-reduced-motion: reduce) { . player - eq i { animation : none ; } }
.player-body { padding : var ( -- space - 1 ) var ( -- space - 3 ) var ( -- space - 3 ); display : flex ; flex - direction : column ; gap : var ( -- space - 2 - 5 ); }
.player-head { display : flex ; align - items : center ; gap : 0.75rem ; }
.player-avatar { inline - size : 2.25rem ; aspect - ratio : 1 ; border - radius : 50 % ; display : grid ; place - items : center ; background : var ( -- color - primary - solid ); color : var ( -- color - primary - on - solid , # fff ); font - weight : 600 ; font - size : var ( -- font - size - sm ); flex : none ; }
.player-meta { display : flex ; flex - direction : column ; min - inline - size : 0 ; flex : 1 ; }
.player-title { font - family : var ( -- font - family - display ); font - weight : 600 ; color : var ( -- color - content - primary ); }
.player-artist { font - size : var ( -- font - size - xs ); color : var ( -- color - content - muted ); }
.player-tag { font - family : var ( -- font - family - mono ); font - size : 0.62rem ; letter - spacing : 0.06em ; padding : 0.2rem 0.5 rem ; border - radius : var ( -- radius - sm ); background : var ( -- color - surface - overlay ); color : var ( -- color - content - secondary ); border : 1px solid var ( -- rule ); }
.player-track { block - size : 5px ; border - radius : 999px ; background : var ( -- color - surface - muted ); overflow : hidden ; }
.player-fill { display : block ; inline - size : 42 % ; block - size : 100 % ; background : var ( -- color - primary - solid ); }
.player-row { display : flex ; gap : 0.5rem ; }
.player-input { flex : 1 ; min - inline - size : 0 ; padding : 0.5rem 0.75 rem ; border - radius : var ( -- sample - radius ); border : 1px solid var ( -- rule ); background : var ( -- color - surface - default ); color : var ( -- color - content - primary ); font : inherit ; font - size : var ( -- font - size - xs ); outline : none ; }
.player-input:focus { border - color : color - mix ( in srgb , var ( -- color - primary - solid ) 55 % , var ( -- rule )); }
.player-add { inline - size : 2.1rem ; border - radius : var ( -- sample - radius ); border : 1px solid var ( -- rule ); background : var ( -- color - surface - overlay ); color : var ( -- color - content - primary ); cursor : pointer ; font - size : var ( -- font - size - md ); }
.player-add:hover { background : var ( -- color - surface - muted ); }
/* 2 · Familias */
.fams { display : grid ; grid - template - columns : repeat ( auto - fit , minmax ( min ( 100 % , 11 rem ), 1 fr )); gap : clamp ( 1.25 rem , 3 vw , 2 rem ); }
.fam { margin : 0 ; display : flex ; flex - direction : column ; gap : 1rem ; align - items : center ; }
.fam-box { inline - size : 100 % ; aspect - ratio : 1.4 ; border - radius : 32px ; background : linear - gradient ( 145 deg , var ( -- color - surface - raised ), var ( -- color - surface - overlay )); border : 1px solid var ( -- rule ); box - shadow : var ( -- depth - raised - shadow ); }
.fam figcaption { display : flex ; flex - direction : column ; gap : 0.2rem ; text - align : center ; }
.fam figcaption b { font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - sm ); color : var ( -- color - content - primary ); }
.fam figcaption span { font - size : var ( -- font - size - xs ); color : var ( -- color - content - muted ); }
.panel { display : flex ; flex - direction : column ; gap : 1.5rem ; padding : clamp ( 1.25 rem , 3 vw , 2 rem ); border - radius : var ( -- radius - lg ); background : var ( -- color - surface - default ); border : 1px solid var ( -- rule ); }
.ctl { display : flex ; flex - direction : column ; gap : 0.65rem ; }
.ctl-dim { opacity : 0.45 ; }
.ctl-check { flex - direction : row ; align - items : center ; gap : 0.6rem ; cursor : pointer ; }
.ctl-check input { accent - color : var ( -- color - primary - solid ); }
.ctl-label { font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - xs ); text - transform : uppercase ; letter - spacing : 0.08em ; color : var ( -- color - content - muted ); }
.ctl input[type='range'] { accent - color : var ( -- color - primary - solid ); }
.ctl-val { font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - xs ); color : var ( -- color - content - primary ); }
.chips { display : flex ; flex - wrap : wrap ; gap : 0.4rem ; }
.chip { font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - xs ); padding : 0.35rem 0.75 rem ; border - radius : 999px ; cursor : pointer ; background : var ( -- color - surface - raised ); color : var ( -- color - content - secondary ); border : 1px solid var ( -- rule ); }
.chip:hover { color : var ( -- color - content - primary ); }
.chip[data-active='true'] { background : var ( -- color - primary - solid ); color : var ( -- color - primary - on - solid , # fff ); border - color : var ( -- color - primary - solid ); }
.panel-code { display : block ; font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - xs ); padding : 0.7rem 0.9 rem ; border - radius : var ( -- radius - md ); background : var ( -- color - surface - muted ); color : var ( -- color - content - secondary ); word - break : break - all ; }
/* 3 · Anidado */
.nest-stage { display : flex ; flex - direction : column ; gap : 1.5rem ; align - items : center ; }
.nest-outer { inline - size : min ( 30 rem , 100 % ); padding : var ( -- space - 2 ); border - radius : var ( -- radius - xl ); background : var ( -- color - surface - overlay ); border : 1px solid var ( -- rule ); box - shadow : var ( -- depth - overlay - shadow ); }
.nest-row { display : grid ; grid - template - columns : 1fr 1 fr ; gap : var ( -- space - 2 ); }
.nest-inner { aspect - ratio : 1.6 ; background : linear - gradient ( 145 deg , var ( -- color - primary - solid ), color - mix ( in oklab , var ( -- color - primary - solid ) 55 % , transparent )); }
/* naive = fixed inner radius that ignores the outer/gap → corners visibly desync */
.nest-naive { border - radius : var ( -- radius - xl ); }
.nest-toggle { display : flex ; align - items : center ; gap : 0.6rem ; font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - xs ); color : var ( -- color - content - muted ); cursor : pointer ; }
.nest-toggle input { accent - color : var ( -- color - primary - solid ); }
/* 2 · De cerca */
.closeup { display : flex ; flex - wrap : wrap ; gap : clamp ( 1.5 rem , 5 vw , 4 rem ); justify - content : center ; }
.cu { margin : 0 ; display : flex ; flex - direction : column ; gap : 1.25rem ; align - items : center ; }
.cu-box { inline - size : clamp ( 9 rem , 26 vw , 14 rem ); aspect - ratio : 1 ; border - radius : clamp ( 2.5 rem , 7 vw , 4.5 rem ); background : linear - gradient ( 145 deg , var ( -- color - primary - solid ), # 12 a594 ); }
.cu figcaption { font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - xs ); color : var ( -- color - content - muted ); }
.cu figcaption b { color : var ( -- color - content - primary ); }
/* 3 · Familias */
.fam-row { display : flex ; flex - wrap : wrap ; gap : clamp ( 1 rem , 4 vw , 2.5 rem ); justify - content : center ; }
.fam { margin : 0 ; display : flex ; flex - direction : column ; gap : 0.9rem ; align - items : center ; }
.fam-chip { display : grid ; place - items : center ; min - inline - size : 6.5rem ; padding : 1.1rem 1.5 rem ; border - radius : 18px ; background : linear - gradient ( 145 deg , var ( -- color - surface - raised ), var ( -- color - surface - overlay )); border : 1px solid var ( -- rule ); box - shadow : var ( -- depth - raised - shadow ); font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - sm ); color : var ( -- color - content - primary ); }
.fam figcaption { font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - xs ); color : var ( -- color - content - muted ); }
/* 4 · Ocurre */
.press-row { display : flex ; flex - wrap : wrap ; gap : 1.5rem ; }
@ -253,8 +409,8 @@
font-family: var(--font-family-primary);
font-size: var(--font-size-md);
font-weight: 600;
padding: 1rem 2rem;
border-radius: 28 px;
padding: 1rem 2.25 rem;
border-radius: 26 px;
border: 1px solid color-mix(in srgb, var(--color-primary-solid) 50%, var(--rule));
background: linear-gradient(145deg, var(--color-surface-raised), var(--color-surface-overlay));
color: var(--color-content-primary);
@ -263,7 +419,19 @@
will-change: transform, border-radius;
}
.press-ctrl { opacity : 0.85 ; }
@media (prefers-reduced-motion: reduce) { . press - btn { animation : none ! important ; } }
@media (prefers-reduced-motion: reduce) { . press - btn , . vs - press { animation : none ! important ; } }
/* 5 · Vs el resto */
.vs { display : grid ; grid - template - columns : repeat ( auto - fit , minmax ( min ( 100 % , 8 rem ), 1 fr )); gap : clamp ( 1 rem , 3 vw , 2 rem ); }
.vs-col { display : flex ; flex - direction : column ; gap : 0.75rem ; align - items : center ; text - align : center ; }
.vs-h { font - family : var ( -- font - family - mono ); font - size : var ( -- font - size - xs ); color : var ( -- color - content - primary ); font - weight : 600 ; }
.vs-them .vs-h { color : var ( -- color - content - muted ); }
.vs-sample { inline - size : 100 % ; aspect - ratio : 1 ; border - radius : 28px ; background : linear - gradient ( 145 deg , var ( -- color - primary - solid ), # 12 a594 ); border : 0 ; padding : 0 ; }
.vs-them .vs-sample { background : linear - gradient ( 145 deg , var ( -- color - surface - overlay ), var ( -- color - surface - muted )); }
.vs-nest { background : var ( -- color - surface - overlay ); display : grid ; place - items : center ; }
.vs-nest span { inline - size : 64 % ; aspect - ratio : 1 ; background : linear - gradient ( 145 deg , var ( -- color - primary - solid ), # 12 a594 ); }
.vs-press { cursor : pointer ; will - change : transform , border - radius ; }
.vs-cap { font - size : var ( -- font - size - xs ); color : var ( -- color - content - muted ); }
.footnote { max - inline - size : 60rem ; padding - block - start : 2rem ; border - block - start : 1px solid var ( -- rule ); }
.footnote p { margin : 0 ; font - size : var ( -- font - size - sm ); line - height : 1.6 ; color : var ( -- color - content - muted ); text - wrap : pretty ; }