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/animations/rail/+page.svelte

253 lines
7.3 KiB

<script lang="ts">
/**
* Rail — the SECOND consumer of the coordinated-motion helper
* (`soma/layers/coordination.ts`). Same coordination as `Reveal`, DIFFERENT shape:
* the root IS the owner surface, the relation is `together` (all parallel, CSS
* stagger), it is PARENT-CONTROLLED via `open` (no trigger), and has NO sema events.
* Proves the helper is reusable — building this was a handful of lines.
* Inherits the ActiveUix + Soma + Eidos scope from `../+layout.svelte`.
*/
import * as Rail from '$soma/components/rail';
const ITEMS = ['Inicio', 'Buscar', 'Mensajes', 'Perfil', 'Ajustes'];
const STYLES = [
{ value: 'cascade-slide', label: 'slide' },
{ value: 'cascade-fade', label: 'fade' },
{ value: 'cascade-scale', label: 'scale' }
];
let open = $state(false);
let animStyle = $state('cascade-slide');
let duration = $state(300); // ms — per-surface transition speed (exposed as a CSS var)
let stagger = $state(45); // ms — the rhythm between items (`--motion-stagger-each`)
</script>
<svelte:head>
<title>Rail · 2º consumidor coordinado</title>
</svelte:head>
<div class="root">
<header>
<a class="back" href="/temas/animations">← Motion</a>
<h1>Rail <small>2º consumidor del helper coordinado</small></h1>
<p class="lede">
Misma coordinación que <a href="/temas/animations/reveal">Reveal</a>, otra forma: la raíz
<strong>ES</strong> la superficie owner, la relación es <code>together</code> (todo en
paralelo, el escalonado lo pone el CSS), es <strong>controlada por el padre</strong>
(<code>bind:open</code>, sin trigger) y <strong>no tiene eventos sema</strong>. Todo el wiring
—PresenceGroup + routing + auto-stagger— sale del helper compartido
<code>soma/layers/coordination.ts</code>; construir este componente fueron cuatro líneas.
</p>
<div class="actions">
<button class="btn primary" type="button" onclick={() => (open = !open)}>
{open ? 'Ocultar' : 'Mostrar'} rail
</button>
<div class="picker" role="group" aria-label="Estilo de animación">
{#each STYLES as s (s.value)}
<button
class="chip-btn"
class:on={animStyle === s.value}
type="button"
onclick={() => (animStyle = s.value)}
>
{s.label}
</button>
{/each}
</div>
</div>
<div class="sliders">
<label>
duración
<input type="range" min="80" max="700" step="20" bind:value={duration} />
<span class="val">{duration}ms</span>
</label>
<label>
stagger
<input type="range" min="0" max="120" step="5" bind:value={stagger} />
<span class="val">{stagger}ms</span>
</label>
</div>
</header>
<div
class="stage"
style="--cascade-duration: {duration}ms; --motion-stagger-each: {stagger}ms; --motion-stagger-count: {ITEMS.length}"
>
<Rail.Provider bind:open animation={animStyle}>
{#each ITEMS as label (label)}
<Rail.Item class="chip">{label}</Rail.Item>
{/each}
</Rail.Provider>
</div>
</div>
<style>
.root {
min-height: 100dvh;
background: var(--color-surface-default, #fff);
color: var(--color-content-primary, #111);
font-family: var(--font-family-primary, 'Inter', system-ui, sans-serif);
padding: 2.5rem 1.5rem 4rem;
}
header {
max-width: 880px;
margin-inline: auto;
margin-block-end: 2rem;
}
.back {
font-size: 0.85rem;
color: var(--color-content-muted, #666);
text-decoration: none;
}
.back:hover {
text-decoration: underline;
}
h1 {
margin: 0.5rem 0 0.75rem;
font-size: 1.9rem;
font-weight: 700;
}
h1 small {
font-size: 0.95rem;
font-weight: 500;
color: var(--color-content-muted, #666);
}
.lede {
margin: 0;
max-width: 64ch;
line-height: 1.6;
color: var(--color-content-secondary, #444);
}
.lede a {
color: var(--color-primary-solid, #4f46e5);
}
.actions {
display: flex;
align-items: center;
gap: 1rem;
margin-block-start: 1.25rem;
flex-wrap: wrap;
}
.btn {
appearance: none;
cursor: pointer;
font: inherit;
font-size: 0.82rem;
font-weight: 600;
padding: 0.4rem 0.9rem;
border-radius: 8px;
border: 1px solid var(--color-border-default, rgba(0, 0, 0, 0.15));
background: var(--color-surface-default, #fff);
color: var(--color-content-primary, #111);
}
.btn.primary {
background: var(--color-primary-solid, #4f46e5);
color: var(--color-primary-contrast, #fff);
border-color: transparent;
}
.picker {
display: inline-flex;
gap: 0.25rem;
padding: 0.2rem;
border-radius: 10px;
background: var(--color-surface-muted, rgba(0, 0, 0, 0.05));
}
.chip-btn {
appearance: none;
cursor: pointer;
font: inherit;
font-size: 0.78rem;
padding: 0.3rem 0.7rem;
border-radius: 7px;
border: none;
background: transparent;
color: var(--color-content-secondary, #555);
}
.chip-btn.on {
background: var(--color-primary-solid, #4f46e5);
color: var(--color-primary-contrast, #fff);
}
.stage {
max-width: 880px;
margin-inline: auto;
padding: 1.5rem;
border-radius: 16px;
border: 1px dashed var(--color-border-subtle, rgba(0, 0, 0, 0.12));
background: var(--color-surface-raised, #f7f7f8);
min-height: 140px;
display: flex;
align-items: center;
}
.sliders {
display: flex;
gap: 1.5rem;
margin-block-start: 1rem;
flex-wrap: wrap;
}
.sliders label {
display: inline-flex;
align-items: center;
gap: 0.5rem;
font-size: 0.78rem;
color: var(--color-content-secondary, #555);
}
.sliders input[type='range'] {
accent-color: var(--color-primary-solid, #4f46e5);
}
.sliders .val {
min-width: 3.5ch;
font-variant-numeric: tabular-nums;
color: var(--color-content-muted, #888);
}
/* The Rail surfaces — global (rendered by the soma component). HORIZONTAL row;
soma stamps data-starting/ending-style, the CSS only reacts. */
:global([data-rail]) {
display: flex;
flex-direction: row;
gap: 0.5rem;
padding: 0.5rem;
border-radius: 12px;
background: var(--color-surface-overlay, rgba(99, 102, 241, 0.08));
border: 1px solid var(--color-border-subtle, rgba(0, 0, 0, 0.12));
transition:
opacity var(--cascade-duration, 0.3s) ease,
transform var(--cascade-duration, 0.3s) ease;
}
:global([data-rail-item]) {
padding: 0.45rem 0.85rem;
border-radius: 999px;
background: var(--color-surface-default, #fff);
border: 1px solid var(--color-border-subtle, rgba(0, 0, 0, 0.1));
font-size: 0.85rem;
white-space: nowrap;
transition:
opacity var(--cascade-duration, 0.3s) ease,
transform var(--cascade-duration, 0.3s) ease;
transition-delay: calc(var(--motion-stagger-index, 0) * var(--motion-stagger-each, 0ms));
}
/* Close = open reversed (last chip leaves first). */
:global([data-rail-item][data-ending-style]) {
transition-delay: calc(
(var(--motion-stagger-count, 1) - 1 - var(--motion-stagger-index, 0)) *
var(--motion-stagger-each, 0ms)
);
}
/* `cascade-*` (non-eidos-preset names) — react to the Presence attrs, not data-state. */
:global([data-animation-style='cascade-slide'][data-starting-style]),
:global([data-animation-style='cascade-slide'][data-ending-style]) {
opacity: 0;
transform: translateX(-12px);
}
:global([data-animation-style='cascade-fade'][data-starting-style]),
:global([data-animation-style='cascade-fade'][data-ending-style]) {
opacity: 0;
}
:global([data-animation-style='cascade-scale'][data-starting-style]),
:global([data-animation-style='cascade-scale'][data-ending-style]) {
opacity: 0;
transform: scale(0.85);
}
</style>

Powered by TurnKey Linux.