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/presence-group/+page.svelte

245 lines
7.1 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">
/**
* PresenceGroup — práctica.
*
* The motion service's presence coordination (RFC: eidos/MOTION_SERVICE_RFC.md
* §7–§9), shown in isolation. Each card is a coordinated list (an owner
* container + its child items) wired through the real soma `PresenceGroup` +
* `Presence`, one per `when` mode. Discovery is by Svelte context (§7.1); the
* visual stagger is pure CSS; soma only sequences WHEN each surface enters /
* exits / unmounts — never the look. Inherits the `ActiveUix + Soma + Eidos`
* scope from `../+layout.svelte`.
*/
import CoordList from './coord-list.svelte';
type Phase = 'before' | 'after' | 'together';
type Mode = { id: string; enter: Phase; exit: Phase; badge: string; desc: string };
const MODES: Mode[] = [
{
id: 'bracket',
enter: 'before',
exit: 'after',
badge: "enter:'before' · exit:'after'",
desc: 'El contenedor BRACKETEA a sus hijos: entra antes que ellos y sale después (reteniendo su DOM hasta que terminan). El caso natural de un contenedor — justo lo que un when simétrico no podía expresar.'
},
{
id: 'exit-heavy',
enter: 'together',
exit: 'after',
badge: "enter:'together' · exit:'after'",
desc: 'Entran a la vez; al cerrar, los hijos salen en stagger y el contenedor aguanta hasta que su salida agrega. La lista exit-heavy de la RFC §13.'
},
{
id: 'together',
enter: 'together',
exit: 'together',
badge: "enter/exit:'together'",
desc: 'Todo en paralelo; el escalonado visual lo pone solo el CSS (animation-delay × índice). soma agrega el finished del árbol.'
}
];
const open = $state<Record<string, boolean>>({
bracket: false,
'exit-heavy': false,
together: false
});
const toggleAll = () => {
const next = !MODES.every((m) => open[m.id]);
for (const m of MODES) open[m.id] = next;
};
// Reversa demo (RFC §8.3): flip the card, then flip it BACK mid-animation (the
// transitions are ~300ms). The group's generation token must keep the surfaces
// mounted through the reversal — re-opening mid-exit must NOT unmount→remount.
const reversa = (id: string) => {
const back = open[id];
open[id] = !back;
setTimeout(() => (open[id] = back), 150);
};
</script>
<svelte:head>
<title>PresenceGroup · práctica</title>
</svelte:head>
<div class="root">
<header>
<a class="back" href="/temas/animations">← Motion</a>
<h1>PresenceGroup <small>coordinación de presencia</small></h1>
<p class="lede">
La capa de coordinación del servicio de motion, en aislado. Cada tarjeta es una lista
coordinada —un contenedor <strong>owner</strong> y sus ítems <strong>child</strong>— cableada
por el <code>PresenceGroup</code> + <code>Presence</code> reales de soma, uno por modo
<code>when</code>. Los ítems descubren el grupo por <strong>context</strong> (§7.1); el
stagger es CSS puro; soma solo decide <strong>cuándo</strong> entra / sale / se desmonta cada
superficie. Fíjate en <code>after</code>: al cerrar, los ítems salen y el contenedor
<strong>aguanta</strong> hasta que terminan. Y <strong>↻ Reversa</strong> reabre a media
salida (§8.3): el token de generación del grupo no desmonta lo que vuelve —
<strong>sin parpadeo</strong>.
</p>
<div class="actions">
<button class="btn primary" type="button" onclick={toggleAll}>Abrir / cerrar todas</button>
<span class="hint"
>Para la animación fluida, abre esta página enfocada en el browser (en preview de fondo el
rAF se throttlea).</span
>
</div>
</header>
<div class="grid">
{#each MODES as m (m.id)}
<section class="card">
<div class="card-head">
<code class="badge">{m.badge}</code>
<div class="card-actions">
<button
class="btn ghost"
type="button"
aria-label="Reversa"
title="Reversa: flip + flip back mid-animation (§8.3)"
onclick={() => reversa(m.id)}
>
↻
</button>
<button class="btn" type="button" onclick={() => (open[m.id] = !open[m.id])}>
{open[m.id] ? 'Cerrar' : 'Abrir'}
</button>
</div>
</div>
<p class="desc">{m.desc}</p>
<div class="stage">
<CoordList enter={m.enter} exit={m.exit} open={open[m.id]} count={5} />
</div>
</section>
{/each}
</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);
}
.actions {
display: flex;
align-items: center;
gap: 1rem;
margin-block-start: 1.25rem;
flex-wrap: wrap;
}
.hint {
font-size: 0.78rem;
color: var(--color-content-muted, #888);
max-width: 42ch;
}
.grid {
max-width: 880px;
margin-inline: auto;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 1rem;
}
.card {
display: flex;
flex-direction: column;
gap: 0.75rem;
padding: 1.25rem;
border-radius: 16px;
background: var(--color-surface-raised, #f7f7f8);
border: 1px solid var(--color-border-subtle, rgba(0, 0, 0, 0.1));
}
.card-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
flex-wrap: wrap;
}
.card-actions {
display: flex;
gap: 0.4rem;
}
.btn.ghost {
background: transparent;
color: var(--color-content-muted, #666);
font-size: 0.95rem;
line-height: 1;
padding: 0.4rem 0.55rem;
}
.badge {
font-family: var(--font-family-mono, 'Roboto Mono', ui-monospace, monospace);
font-size: 0.78rem;
padding: 0.15em 0.5em;
border-radius: 6px;
background: color-mix(in srgb, var(--color-primary-solid, #4f46e5) 12%, transparent);
color: var(--color-primary-text, var(--color-primary-solid, #4f46e5));
}
.desc {
margin: 0;
font-size: 0.82rem;
line-height: 1.5;
color: var(--color-content-secondary, #555);
}
.stage {
min-height: 220px;
display: flex;
align-items: flex-start;
padding: 0.75rem;
border-radius: 12px;
border: 1px dashed var(--color-border-subtle, rgba(0, 0, 0, 0.12));
background: var(--color-surface-default, #fff);
}
.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;
}
.btn:hover {
border-color: var(--color-primary-solid, #4f46e5);
}
</style>

Powered by TurnKey Linux.