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/web/routes/temas/forma/+page.svelte

480 lines
24 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.

<script lang="ts">
/**
* Shape demo — the Eidos shape channel, made tangible on real UI.
*
* 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+); degrades to the plain arc elsewhere.
*/
import '@/uix/eidos/index.css'
let theme = $state<'light' | 'dark'>('dark')
$effect(() => {
const el = document.documentElement
const prev = el.getAttribute('data-theme')
el.setAttribute('data-theme', theme)
return () => {
if (prev === null) el.removeAttribute('data-theme')
else el.setAttribute('data-theme', prev)
}
})
const FAMILIES = [
{ id: 'rounded', label: 'rounded' },
{ id: 'continuous', label: 'continuous' },
{ id: 'cut', label: 'cut' },
{ id: 'scoop', label: 'scoop' }
] as const
type Family = (typeof FAMILIES)[number]['id']
// ── 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)
// `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
// ── 4 · eventful press ───────────────────────────────────────────────────────
let pressed = $state<string | null>(null)
function firePress(id: string): void {
pressed = null
requestAnimationFrame(() => (pressed = id))
}
</script>
<svelte:head><title>Forma · Eidos</title></svelte:head>
<div class="root" data-theme={theme}>
<header class="masthead">
<div class="mast-row">
<p class="kicker">Eidos · canal de forma</p>
<button class="theme-toggle" onclick={() => (theme = theme === 'light' ? 'dark' : 'light')}>
{theme === 'light' ? '◐ claro' : '◑ oscuro'}
</button>
</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. La 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: tócala y mira
la misma UI cambiar de carácter.
</p>
</header>
<!-- ── 1 · Taller (centerpiece) ─────────────────────────────────────────── -->
<section class="section">
<div class="sec-head">
<h2 class="sec-title">El taller</h2>
<p class="sec-note">
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="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}"
>
<div
class="player-cover"
data-shape={family}
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 ? '❚❚' : '▶'}
</button>
<span class="player-eq" data-on={playing}><i></i><i></i><i></i><i></i></span>
</div>
<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" class:ctl-dim={isFull}>
<input type="checkbox" bind:checked={nest} disabled={isFull} />
<span class="ctl-label"
>radios concéntricos {isFull
? '· auto a full (solo top: la píldora no deja redondear el pie)'
: nest
? ''
: '· off (la carátula desencaja)'}</span
>
</label>
<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>
<!-- ── 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. Material solo tiene <code>cut</code>; Apple solo <code>continuous</code>. Aquí,
la misma etiqueta en las cuatro:
</p>
</div>
<div class="fam-row">
{#each FAMILIES as f}
<figure class="fam">
<span class="fam-chip" data-shape={f.id}>etiqueta</span>
<figcaption>{f.label}</figcaption>
</figure>
{/each}
</div>
</section>
<!-- ── 4 · Ocurre ───────────────────────────────────────────────────────── -->
<section class="section">
<div class="sec-head">
<h2 class="sec-title">La forma <em>ocurre</em></h2>
<p class="sec-note">
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="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>
<!-- ── 5 · Vs el resto ──────────────────────────────────────────────────── -->
<section class="section">
<div class="sec-head">
<h2 class="sec-title">Lo que solo hace Eidos</h2>
<p class="sec-note">
El campo entero (Tailwind · shadcn · Chakra · Mantine · Ant · Radix · Carbon · Fluent ·
Spectrum · Polaris) se queda en la primera columna.
</p>
</div>
<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="vs-sample vs-press"
data-shape="continuous"
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> 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>
<style>
.root {
min-block-size: 100vh;
background:
radial-gradient(130% 90% at 50% -15%, var(--color-surface-raised), transparent 55%),
var(--color-surface-default);
color: var(--color-content-primary);
font-family: var(--font-family-primary);
padding: clamp(1.5rem, 5vw, 5rem) clamp(1.25rem, 5vw, 6rem) 6rem;
--rule: var(--color-border-default);
}
.masthead { max-inline-size: 56rem; margin-block-end: clamp(3rem, 8vw, 6rem); }
.mast-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-block-end: 2.5rem; }
.kicker { margin: 0; font-family: var(--font-family-mono); font-size: var(--font-size-xs); letter-spacing: 0.18em; text-transform: uppercase; color: var(--color-content-muted); }
.theme-toggle { font-family: var(--font-family-mono); font-size: var(--font-size-xs); color: var(--color-content-secondary); background: var(--color-surface-raised); border: 1px solid var(--rule); border-radius: 999px; padding: 0.5rem 1rem; cursor: pointer; }
.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.03em; margin: 0 0 1.25rem; text-wrap: balance; }
.mast-title em { font-style: italic; color: var(--color-primary-text, var(--color-content-primary)); }
.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.5rem, 9vw, 7rem); }
.sec-head { margin-block-end: 2.5rem; }
.sec-title { font-family: var(--font-family-display); font-size: var(--font-size-xl); font-weight: 600; letter-spacing: -0.01em; margin: 0 0 0.5rem; }
.sec-title em { font-style: italic; color: var(--color-primary-text, var(--color-content-primary)); }
.sec-note { margin: 0; max-inline-size: 64ch; font-size: var(--font-size-sm); line-height: 1.55; color: var(--color-content-muted); }
.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.4em; border-radius: 4px; }
/* 1 · Taller */
.lab { display: grid; grid-template-columns: 1fr; gap: clamp(1.5rem, 4vw, 3rem); align-items: start; }
@media (min-width: 52rem) { .lab { grid-template-columns: 22rem 1fr; } }
.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), .cover-naive, or .cover-full — never set
here, or the scoped rule would beat the foundation's nest rule and break the concentric inset */
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); }
/* 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;
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 200ms; }
.player-eq[data-on='true'] { opacity: 0.9; }
.player-eq i { inline-size: 3px; background: #fff; border-radius: 2px; animation: eq 900ms ease-in-out infinite; }
.player-eq i:nth-child(1) { block-size: 40%; animation-delay: -200ms; }
.player-eq i:nth-child(2) { block-size: 90%; animation-delay: -500ms; }
.player-eq i:nth-child(3) { block-size: 60%; animation-delay: -100ms; }
.player-eq i:nth-child(4) { block-size: 100%; animation-delay: -350ms; }
@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.5rem; 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.75rem; 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); }
.panel { display: flex; flex-direction: column; gap: 1.5rem; padding: clamp(1.25rem, 3vw, 2rem); 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.75rem; 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.9rem; border-radius: var(--radius-md); background: var(--color-surface-muted); color: var(--color-content-secondary); word-break: break-all; }
/* 2 · De cerca */
.closeup { display: flex; flex-wrap: wrap; gap: clamp(1.5rem, 5vw, 4rem); justify-content: center; }
.cu { margin: 0; display: flex; flex-direction: column; gap: 1.25rem; align-items: center; }
.cu-box { inline-size: clamp(9rem, 26vw, 14rem); aspect-ratio: 1; border-radius: clamp(2.5rem, 7vw, 4.5rem); background: linear-gradient(145deg, var(--color-primary-solid), #12a594); }
.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(1rem, 4vw, 2.5rem); justify-content: center; }
.fam { margin: 0; display: flex; flex-direction: column; gap: 0.9rem; align-items: center; }
/* filled chips (no border / drop-shadow): a concave `scoop` casts shadow INTO its notches and
a 1px border seams at the concave corners — both read as broken. Flat fills render every
family cleanly and are the right home for cut/scoop anyway (chips/badges). */
.fam-chip { display: grid; place-items: center; min-inline-size: 6.5rem; padding: 0.9rem 1.6rem; border-radius: 16px; background: color-mix(in oklab, var(--color-primary-solid) 26%, var(--color-surface-overlay)); 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; }
.press-btn {
font-family: var(--font-family-primary);
font-size: var(--font-size-md);
font-weight: 600;
padding: 1rem 2.25rem;
border-radius: 26px;
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);
box-shadow: var(--depth-raised-shadow);
cursor: pointer;
will-change: transform, border-radius;
}
.press-ctrl { opacity: 0.85; }
@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%, 8rem), 1fr)); gap: clamp(1rem, 3vw, 2rem); }
.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(145deg, var(--color-primary-solid), #12a594); border: 0; padding: 0; }
.vs-them .vs-sample { background: linear-gradient(145deg, 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(145deg, var(--color-primary-solid), #12a594); }
.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; }
.footnote strong { color: var(--color-content-primary); font-weight: 600; }
</style>

Powered by TurnKey Linux.