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/uix/lib/SemaPanel.svelte

142 lines
5.2 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">
/**
* Sema panel — the perceptual layer as a first-class citizen, not a flat
* event table. Per event it shows the declarative shape (family / verb /
* sequence / intent / hold) AND the RESOLVED firma via `resolveSignature`:
* the sound primitives (pitch / gain / contour / envelope) + the haptic
* (kind / intensity / pattern) + the active channels. ▶ play emits the real
* signal on the live stage target, so sound is audible (Semantics → Sound
* must be on in the topbar).
*/
import { signatureFor } from './harness.svelte';
let {
actions,
boundIntent = 'neutral',
uix,
getTarget
}: {
actions: readonly { name: string; semantic: Record<string, unknown> }[];
boundIntent?: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
uix: any;
/**
* Where to land the preview. The ACTION is handed over so a component
* whose events target different parts can answer per event — a single pin
* would stamp every preview on one node, which is the very drift the
* doctrine names («rules aimed at a part the stamp never visits»): a
* `contact` preview pinned to a 16rem panel squeezes the whole panel, and
* a pack rule bound to another part can never match. Optional parameter,
* so the pages with a single-target morfo keep their zero-arg thunk.
*/
getTarget: (action?: { name: string; semantic: Record<string, unknown> }) => HTMLElement | null;
} = $props();
function effectiveIntent(semantic: Record<string, unknown>): string | undefined {
const decl = 'intent' in semantic ? (semantic.intent as unknown) : undefined;
if (typeof decl === 'string') return decl;
if (decl) return boundIntent; // fromProp binding → demo's current value
return undefined;
}
const CONTOUR_GLYPH: Record<string, string> = {
flat: '─',
arc: '⌒',
ascending: '╱',
descending: '╲'
};
function play(action: { name: string; semantic: Record<string, unknown> }) {
const target = getTarget(action);
if (!target) return;
const intent = effectiveIntent(action.semantic);
void uix.events?.emit({
name: action.name,
family: action.semantic.family,
target,
...(intent ? { intent } : {})
});
}
</script>
{#if actions.length === 0}
<p data-uix-section-desc>
This component's morfo declares <strong>no semantic events</strong>. That is only valid once
the component README justifies why it is perceptually passive (see the Morfo/Sema audit).
</p>
{:else}
<div data-uix-firma-grid>
{#each actions as action}
{@const sem = action.semantic}
{@const intent = effectiveIntent(sem)}
{@const sig = signatureFor(sem.family as string, intent)}
<div data-uix-firma-card>
<div data-uix-firma-head>
<span data-uix-firma-name>{action.name}</span>
<span data-uix-firma-tags>
<span data-uix-firma-tag>{sem.family}</span>
{#if sem.verb}<span data-uix-firma-tag>{sem.verb}</span>{/if}
<span data-uix-firma-tag>{(sem.sequence as string) ?? 'pre'}</span>
{#if intent}<span data-uix-firma-tag data-kind="intent">{intent}</span>{/if}
</span>
<button data-uix-play onclick={() => play(action)}>▶ play</button>
</div>
<div data-uix-firma-channels>
<span data-uix-firma-meta>
channels: {sig.activeChannels.length ? sig.activeChannels.join(' · ') : 'none'}
</span>
{#if sig.hold !== undefined}<span data-uix-firma-meta>hold: {sig.hold}ms</span>{/if}
</div>
<div data-uix-firma-signals>
<!-- Sound -->
<div data-uix-firma-signal data-muted={sig.sound ? undefined : 'true'}>
<span data-uix-firma-signal-label>sound</span>
{#if sig.sound}
<div data-uix-firma-rows>
<span>pitch <b>{Math.round(sig.sound.pitch)}Hz</b></span>
<span>
gain
<span data-uix-firma-bar><span style="inline-size: {Math.min(1, sig.sound.gain) * 100}%"></span></span>
</span>
<span>contour <b data-uix-firma-contour>{CONTOUR_GLYPH[sig.sound.contour] ?? sig.sound.contour}</b> {sig.sound.contour}</span>
<span>centroid <b>{Math.round(sig.sound.centroid)}</b></span>
<span>roughness <b>{sig.sound.roughness}</b></span>
<span>env <b>{sig.sound.attack}/{sig.sound.decay}/{sig.sound.duration}ms</b></span>
</div>
{:else}
<span data-uix-firma-none>silent</span>
{/if}
</div>
<!-- Haptic -->
<div data-uix-firma-signal data-muted={sig.haptic ? undefined : 'true'}>
<span data-uix-firma-signal-label>haptic</span>
{#if sig.haptic}
<div data-uix-firma-rows>
<span>kind <b>{sig.haptic.kind}</b></span>
<span>
intensity
<span data-uix-firma-bar><span style="inline-size: {Math.min(1, sig.haptic.intensity ?? 0) * 100}%"></span></span>
</span>
{#if sig.haptic.pattern}
<span data-uix-firma-pattern>
{#each sig.haptic.pattern as ms, i}
<span data-on={i % 2 === 0 ? 'true' : undefined} style="inline-size: {Math.max(4, ms / 6)}px"></span>
{/each}
</span>
{:else if sig.haptic.duration}
<span>duration <b>{sig.haptic.duration}ms</b></span>
{/if}
</div>
{:else}
<span data-uix-firma-none>none</span>
{/if}
</div>
</div>
</div>
{/each}
</div>
{/if}

Powered by TurnKey Linux.