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

272 lines
14 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, the 8th book axis no web DS has elevated.
*
* 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).
*
* Self-contained: eidos foundation (shape tokens + families + firma) + `data-theme`.
* Continuity needs `corner-shape` (Chromium 2025+); it 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')
el.setAttribute('data-theme', theme)
return () => {
if (prev === null) el.removeAttribute('data-theme')
else el.setAttribute('data-theme', prev)
}
})
// 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' }
] as const
// 3 · nesting toggle
let nest = $state(true)
// 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))
}
</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 circular. 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, y también
<i>ocurre</i>.
</p>
</header>
<!-- ── 1 · Continuidad ──────────────────────────────────────────────── -->
<section class="section">
<div class="sec-head">
<h2 class="sec-title">Continuidad</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.
</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>
<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>
</label>
<code class="dial-code">corner-shape: superellipse(var(--shape-smoothing));</code>
</div>
</div>
</section>
<!-- ── 2 · 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.
</p>
</div>
<div class="fams">
{#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>
</figure>
{/each}
</div>
</section>
<!-- ── 3 · Anidado ──────────────────────────────────────────────────── -->
<section class="section">
<div class="sec-head">
<h2 class="sec-title">Armonía anidada</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>
</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>
</section>
<!-- ── 4 · Ocurre (eventful · la firma) ─────────────────────────────── -->
<section class="section">
<div class="sec-head">
<h2 class="sec-title">La forma <em>ocurre</em></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.)
</p>
</div>
<div class="press-row">
<button
class="press-btn"
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>
</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>.
</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: 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 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 · Continuidad */
.cont { display: grid; grid-template-columns: 1fr; gap: clamp(1.5rem, 4vw, 3rem); align-items: center; }
@media (min-width: 46rem) { .cont { grid-template-columns: 1.2fr 1fr; } }
.cont-pair { display: flex; gap: clamp(1.5rem, 4vw, 3rem); justify-content: center; }
.cont-item { margin: 0; display: flex; flex-direction: column; gap: 1rem; align-items: center; }
.cont-box { inline-size: clamp(8rem, 20vw, 11rem); aspect-ratio: 1; border-radius: 38px; background: linear-gradient(145deg, var(--color-primary-solid), color-mix(in oklab, var(--color-primary-solid) 40%, #12a594)); }
.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 1rem; border-radius: var(--radius-md); background: var(--color-surface-muted); color: var(--color-content-secondary); word-break: break-all; }
/* 2 · Familias */
.fams { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 11rem), 1fr)); gap: clamp(1.25rem, 3vw, 2rem); }
.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(145deg, 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); }
/* 3 · Anidado */
.nest-stage { display: flex; flex-direction: column; gap: 1.5rem; align-items: center; }
.nest-outer { inline-size: min(30rem, 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 1fr; gap: var(--space-2); }
.nest-inner { aspect-ratio: 1.6; background: linear-gradient(145deg, 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); }
/* 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 2rem;
border-radius: 28px;
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 { animation: none !important; } }
.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.