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.
235 lines
10 KiB
235 lines
10 KiB
<script lang="ts">
|
|
import { TextScramble } from '$uix/eidos/components/text-scramble';
|
|
import { compileMorfo } from '$uix/morfo';
|
|
import { textScrambleMorfo } from '@/uix/morfo/components/text-scramble';
|
|
|
|
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y';
|
|
let tab = $state<Tab>('live');
|
|
|
|
const compiled = compileMorfo(textScrambleMorfo);
|
|
const partsList = $derived([...compiled.parts.byKebab.values()]);
|
|
|
|
// ── Live state ────────────────────────────────────────────────────────
|
|
let text = $state('Hover to distort this paragraph of monospaced text.');
|
|
let radius = $state(100);
|
|
let duration = $state(1.2);
|
|
let speed = $state(0.5);
|
|
let scrambleChars = $state('.:');
|
|
|
|
const eidosSnippet = $derived(
|
|
[
|
|
"<script lang='ts'>",
|
|
" import { TextScramble } from '$uix/eidos/components/text-scramble';",
|
|
'</' + 'script>',
|
|
'',
|
|
'<TextScramble',
|
|
` text="${text}"`,
|
|
radius !== 100 && ` radius={${radius}}`,
|
|
duration !== 1.2 && ` duration={${duration}}`,
|
|
speed !== 0.5 && ` speed={${speed}}`,
|
|
scrambleChars !== '.:' && ` scrambleChars="${scrambleChars}"`,
|
|
'/>'
|
|
]
|
|
.filter(Boolean)
|
|
.join('\n')
|
|
);
|
|
</script>
|
|
|
|
<div data-uix-canvas-inner>
|
|
<header>
|
|
<div data-uix-eyebrow>Text effects · TextScramble</div>
|
|
<h1 data-uix-page-title>TextScramble</h1>
|
|
<p data-uix-page-lede>
|
|
Los caracteres cercanos al puntero parpadean por un charset de scramble y vuelven al texto
|
|
real. <strong>SR endurecido</strong> (los lectores nunca ven los glifos scrambled) y
|
|
<strong>cache de medición</strong>: centros por carácter cacheados vía
|
|
<code>dom.measure</code> — 1 lectura de layout por pointermove (el seed hacía N). Familia del
|
|
diseño de familia <code>docs/decisions/design-text-effects.md</code>.
|
|
</p>
|
|
<div data-uix-page-meta>
|
|
<span data-uix-meta-pill>
|
|
<span data-uix-meta-key>parts</span>{compiled.parts.order.length}
|
|
</span>
|
|
<span data-uix-meta-pill><span data-uix-meta-key>events</span>0</span>
|
|
<span data-uix-meta-pill><span data-uix-meta-key>scope</span>eidos</span>
|
|
<span data-uix-meta-pill><span data-uix-meta-key>motion</span>rAF flicker</span>
|
|
</div>
|
|
</header>
|
|
|
|
<div data-uix-stage>
|
|
<div data-uix-stage-area style="background: var(--color-surface-default); color: var(--color-neutral-text);">
|
|
<TextScramble {text} {radius} {duration} {speed} {scrambleChars} />
|
|
</div>
|
|
<div data-uix-stage-trace>
|
|
<span data-uix-stage-trace-key>trace</span>
|
|
<span>no semantic events (proximity flicker)</span>
|
|
<span style="margin-inline-start: auto;">
|
|
<span data-uix-stage-trace-key>radius</span>
|
|
{radius}px · <span data-uix-stage-trace-key>chars</span> {scrambleChars}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div data-uix-tabs role="tablist">
|
|
<button data-uix-tab data-active={tab === 'live'} onclick={() => (tab = 'live')}>Live</button>
|
|
<button data-uix-tab data-active={tab === 'api'} onclick={() => (tab = 'api')}>API</button>
|
|
<button data-uix-tab data-active={tab === 'morfo'} onclick={() => (tab = 'morfo')}>
|
|
<span data-uix-layer-badge="morfo">morfo</span>
|
|
<span data-uix-tab-count>{partsList.length}p · 0e</span>
|
|
</button>
|
|
<button data-uix-tab data-active={tab === 'sema'} onclick={() => (tab = 'sema')}>
|
|
<span data-uix-layer-badge="sema">sema</span>
|
|
<span data-uix-tab-count>0</span>
|
|
</button>
|
|
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button>
|
|
<button data-uix-tab data-active={tab === 'a11y'} onclick={() => (tab = 'a11y')}>A11y</button>
|
|
</div>
|
|
|
|
{#if tab === 'live'}
|
|
<section data-uix-section>
|
|
<h2 data-uix-section-title>Controls</h2>
|
|
<p data-uix-section-desc>
|
|
Mueve el cursor sobre el párrafo: los caracteres bajo el radio parpadean y se asientan; la
|
|
duración decae con la distancia al puntero.
|
|
</p>
|
|
|
|
<div data-uix-subsection-head>
|
|
<span data-uix-layer-badge="eidos">eidos</span> props · scramble
|
|
</div>
|
|
<div data-uix-controls>
|
|
<label data-uix-control>
|
|
<span data-uix-control-label>text</span>
|
|
<input type="text" bind:value={text} style="inline-size: 24rem;" />
|
|
</label>
|
|
<label data-uix-control>
|
|
<span data-uix-control-label>radius <span data-uix-control-hint>px</span></span>
|
|
<input type="number" min="20" step="20" bind:value={radius} style="inline-size: 6rem;" />
|
|
</label>
|
|
<label data-uix-control>
|
|
<span data-uix-control-label>duration <span data-uix-control-hint>s en el centro</span></span>
|
|
<input type="number" min="0.2" step="0.2" bind:value={duration} style="inline-size: 6rem;" />
|
|
</label>
|
|
<label data-uix-control>
|
|
<span data-uix-control-label>speed <span data-uix-control-hint>ritmo del flicker</span></span>
|
|
<input type="number" min="0.05" step="0.05" bind:value={speed} style="inline-size: 6rem;" />
|
|
</label>
|
|
<label data-uix-control>
|
|
<span data-uix-control-label>scrambleChars</span>
|
|
<span data-uix-chips role="radiogroup">
|
|
{#each ['.:', '01', '#$%&', '▓▒░'] as const as opt (opt)}
|
|
<button data-uix-chip data-active={scrambleChars === opt} onclick={() => (scrambleChars = opt)}>{opt}</button>
|
|
{/each}
|
|
</span>
|
|
</label>
|
|
</div>
|
|
|
|
<div data-uix-code>
|
|
<div data-uix-code-head>
|
|
<span data-uix-layer-badge="eidos">eidos</span>
|
|
<span>proximity flicker · cached char centers</span>
|
|
<span data-uix-code-lang>svelte</span>
|
|
</div>
|
|
<pre><code>{eidosSnippet}</code></pre>
|
|
</div>
|
|
</section>
|
|
{/if}
|
|
|
|
{#if tab === 'api'}
|
|
<section data-uix-section>
|
|
<h2 data-uix-section-title>API reference</h2>
|
|
<div data-uix-table-wrap>
|
|
<table data-uix-table>
|
|
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Notes</th></tr></thead>
|
|
<tbody>
|
|
<tr><td class="name">text</td><td class="type">string</td><td class="default">—</td><td>El texto (también la superficie SR). String plano por diseño — ver Decisiones del README.</td></tr>
|
|
<tr><td class="name">radius</td><td class="type">number (px)</td><td class="default">100</td><td>Radio de influencia.</td></tr>
|
|
<tr><td class="name">duration</td><td class="type">number (s)</td><td class="default">1.2</td><td>Scramble en el centro; decae con la distancia.</td></tr>
|
|
<tr><td class="name">speed</td><td class="type">number</td><td class="default">0.5</td><td>Ritmo del parpadeo.</td></tr>
|
|
<tr><td class="name">scrambleChars</td><td class="type">string</td><td class="default">'.:'</td><td>Charset del parpadeo.</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div data-uix-subsection-head>Public tokens</div>
|
|
<div data-uix-table-wrap>
|
|
<table data-uix-table>
|
|
<thead><tr><th>Token</th><th>Default</th></tr></thead>
|
|
<tbody>
|
|
<tr><td class="name">--text-scramble-font</td><td class="type">var(--font-mono, ui-monospace, monospace)</td></tr>
|
|
<tr><td class="name">--text-scramble-size</td><td class="type">inherit</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
{/if}
|
|
|
|
{#if tab === 'morfo'}
|
|
<section data-uix-section>
|
|
<h2 data-uix-section-title>Morfo contract</h2>
|
|
<div data-uix-table-wrap>
|
|
<table data-uix-table>
|
|
<thead><tr><th>Field</th><th>Value</th></tr></thead>
|
|
<tbody>
|
|
<tr><td class="name">name</td><td>{textScrambleMorfo.name}</td></tr>
|
|
<tr><td class="name">kebab</td><td><code>{textScrambleMorfo.kebab}</code></td></tr>
|
|
<tr><td class="name">scope</td><td>{textScrambleMorfo.scope.join(', ')}</td></tr>
|
|
<tr><td class="name">parts</td><td>{partsList.length}</td></tr>
|
|
<tr><td class="name">events</td><td>0</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<p data-uix-section-desc style="margin-top: var(--uix-space-4);">
|
|
<code>data-text-scramble-char</code> (parpadeo) y <code>data-text-scramble-sr</code>
|
|
(texto SR) son sub-partes eidos-only.
|
|
</p>
|
|
</section>
|
|
{/if}
|
|
|
|
{#if tab === 'sema'}
|
|
<section data-uix-section>
|
|
<h2 data-uix-section-title><span data-uix-layer-badge="sema">sema</span> · events</h2>
|
|
<p data-uix-section-desc>
|
|
Sin eventos semánticos: parpadeo decorativo por proximidad — el puntero perturba la
|
|
presentación, no compromete nada.
|
|
</p>
|
|
</section>
|
|
{/if}
|
|
|
|
{#if tab === 'recipe'}
|
|
<section data-uix-section>
|
|
<h2 data-uix-section-title>Eidos recipe</h2>
|
|
<p data-uix-section-desc>
|
|
<code>src/uix/eidos/components/text-scramble/text-scramble.css</code> — tipografía por
|
|
tokens (mono por defecto, como el seed) + la superficie SR; el flicker es JS por
|
|
<code>eidos.dom.requestFrame</code>.
|
|
</p>
|
|
<div data-uix-table-wrap>
|
|
<table data-uix-table>
|
|
<thead><tr><th>Selector</th><th>Owner</th><th>Purpose</th></tr></thead>
|
|
<tbody>
|
|
<tr><td class="name"><code>[data-text-scramble]</code></td><td><span data-uix-tag data-kind="morfo">morfo</span></td><td>Contenedor (fuente/tamaño por tokens).</td></tr>
|
|
<tr><td class="name"><code>[data-text-scramble-char]</code></td><td><span data-uix-tag data-kind="eidos">eidos</span></td><td>Carácter parpadeante (<code>white-space: pre</code>).</td></tr>
|
|
<tr><td class="name"><code>[data-text-scramble-sr]</code></td><td><span data-uix-tag data-kind="eidos">eidos</span></td><td>Texto real visually-hidden (clip pattern).</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
{/if}
|
|
|
|
{#if tab === 'a11y'}
|
|
<section data-uix-section>
|
|
<h2 data-uix-section-title>Accessibility</h2>
|
|
<div data-uix-table-wrap>
|
|
<table data-uix-table>
|
|
<thead><tr><th>Concern</th><th>Contract</th></tr></thead>
|
|
<tbody>
|
|
<tr><td class="name">SR surface</td><td>Endurecida (upgrade sobre el seed): el párrafo que parpadea es <code>aria-hidden</code>; el texto real vive en <code>[data-text-scramble-sr]</code> — un lector nunca lee «.:» ni glifos intermedios.</td></tr>
|
|
<tr><td class="name">Reduced motion</td><td>El handler de puntero no hace nada — texto estático.</td></tr>
|
|
<tr><td class="name">Pointer-only</td><td>El efecto es exclusivo de puntero por naturaleza; no hay funcionalidad perdida sin él (el contenido completo siempre está).</td></tr>
|
|
<tr><td class="name">Keyboard / focus</td><td>No focusable — contenido pasivo.</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
{/if}
|
|
</div>
|