Files
oikos/web/vendor/src/sprites.js
dtoro 8ff382a50d
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
ci / web (push) Has been cancelled
Desktop App / Build Linux (amd64) (push) Has been cancelled
Desktop App / Attach to Release (push) Has been cancelled
0.28.1 — vendor @joan/procedural-glyph-engine for portable SPA builds (fixes deploy)
The procedural-glyph-engine dep pointed at a non-portable file:/private/tmp/orby-pkg
path, breaking npm ci in Docker and every main deploy since v0.20.0 (the build
cache masked it until it busted ~Aug 5). Vendor Orby v5.0.0 into web/vendor/,
switch the dep to file:../vendor, and use npm install in the web Dockerfile
(file: deps need install, not ci). Cherry-picked from 3cd4cf9.
2026-08-08 21:41:51 +02:00

1767 lines
44 KiB
JavaScript

/**
* Immutable semantic recipes for Orby.
*
* A sprite is intentionally data, not a baked animation. Renderers combine its
* glyph silhouette, field stack, switching operator, transition contract, and
* interaction mappings. This keeps the catalogue portable across Canvas 2D,
* WebGL, SVG exporters, sprite-sheet bakers, and reduced-motion renderers.
*/
const deepFreeze = (value, seen = new WeakSet()) => {
if (
value === null ||
(typeof value !== "object" && typeof value !== "function") ||
seen.has(value)
) {
return value;
}
seen.add(value);
for (const child of Object.values(value)) deepFreeze(child, seen);
return Object.freeze(value);
};
export const FIELD_IDS = Object.freeze([
"fbm",
"ridged",
"domain-warp",
"curl",
"flow",
"worley",
"voronoi",
"plasma",
"interference",
"vortex",
"metaballs",
"caustics",
"strata",
"radar",
"constellation",
"liquid",
"electric",
"ripple",
"kaleidoscope",
]);
const FIELD_ID_SET = new Set(FIELD_IDS);
const PALETTE_SOURCE = {
neutral: {
name: "neutral",
background: "#07090c",
shadow: "#1b222b",
ink: "#eef3f8",
accent: "#93cfff",
},
info: {
name: "info",
background: "#071019",
shadow: "#15344a",
ink: "#edf8ff",
accent: "#4fc3ff",
},
success: {
name: "success",
background: "#07110e",
shadow: "#16392d",
ink: "#f0fff9",
accent: "#54d99b",
},
warning: {
name: "warning",
background: "#151006",
shadow: "#493514",
ink: "#fff9e9",
accent: "#f5b94c",
},
danger: {
name: "danger",
background: "#15090b",
shadow: "#4c1c25",
ink: "#fff2f4",
accent: "#ff647c",
},
celebration: {
name: "celebration",
background: "#100a19",
shadow: "#352052",
ink: "#fff7ff",
accent: "#cf86ff",
},
};
export const PALETTES = deepFreeze(PALETTE_SOURCE);
function layer(field, weight, scale, blend = "add", options = {}) {
if (!FIELD_ID_SET.has(field)) {
throw new TypeError(`Unknown field "${field}" in built-in sprite recipe.`);
}
return {
field,
weight,
scale,
blend,
...(Number.isFinite(options.segments)
? { segments: Math.round(options.segments) }
: {}),
phase: options.phase ?? 0,
speed: options.speed ?? 1,
contrast: options.contrast ?? 1,
warp: options.warp ?? 0,
};
}
const defaultPointer = {
enabled: true,
minSize: 20,
effect: "steer-light",
strength: 0.16,
reducedMotion: false,
};
const defaultPress = {
enabled: true,
effect: "spring-compress",
strength: 0.07,
durationMs: 180,
};
function defineSprite({
id,
category,
meaning,
glyph,
field,
fieldMix,
composition = {},
palette = "neutral",
threshold = 0.5,
density = 0.72,
speed = 1,
pixelShape = "rounded-square",
pixelGap = 0.12,
pixelScale = 0.94,
silhouetteFloor = 0.38,
pixelSwitch = {},
timeline = {},
transition = {},
interactions = {},
reducedMotion = {},
labels,
terminal = false,
urgency = "normal",
}) {
if (!FIELD_ID_SET.has(field)) {
throw new TypeError(`Unknown primary field "${field}" for "${id}".`);
}
if (!labels?.default) {
throw new TypeError(`Sprite "${id}" must define labels.default.`);
}
const mix = fieldMix?.length
? fieldMix
: [layer(field, 1, 1, "replace")];
const totalWeight = mix.reduce((sum, item) => sum + item.weight, 0);
const compositionConfig =
typeof composition === "string"
? { mode: composition }
: composition && typeof composition === "object"
? composition
: {};
const recipe = {
schemaVersion: 1,
id,
semantic: {
category,
meaning,
terminal,
urgency,
announce: labels.live ?? (urgency === "high" ? "assertive" : "polite"),
decorativeCanvas: true,
monochromeSafe: true,
minimumReadableSize: 16,
},
glyph,
field,
composition: {
mode: "glyph",
...compositionConfig,
},
fieldMix: mix.map((item) => ({
...item,
weight: totalWeight > 0 ? item.weight / totalWeight : 0,
})),
palette: PALETTES[palette] ?? PALETTES.neutral,
threshold,
density,
speed,
pixel: {
shape: pixelShape,
gap: pixelGap,
scale: pixelScale,
quantize: "1bit",
silhouetteFloor,
},
pixelSwitch: {
mode: pixelSwitch.mode ?? "threshold-hysteresis",
dither: pixelSwitch.dither ?? "bayer8",
hysteresis: pixelSwitch.hysteresis ?? 0.055,
temporalRate: pixelSwitch.temporalRate ?? speed,
neighborhood: pixelSwitch.neighborhood ?? 0,
direction: pixelSwitch.direction ?? "field",
monotonic: pixelSwitch.monotonic ?? false,
maxContrastFlashesPerSecond:
pixelSwitch.maxContrastFlashesPerSecond ?? 3,
},
timeline: {
mode: timeline.mode ?? "loop",
durationMs: timeline.durationMs ?? Math.round(1000 / Math.max(speed, 0.01)),
macroCycleMs: timeline.macroCycleMs ?? null,
holdMs: timeline.holdMs ?? 0,
replay: timeline.replay ?? "continuous",
next: timeline.next ?? null,
},
transition: {
enter: transition.enter ?? "field-morph",
exit: transition.exit ?? "field-morph",
durationMs: transition.durationMs ?? 360,
exitDurationMs:
transition.exitDurationMs ?? transition.durationMs ?? 360,
preservePhase: transition.preservePhase ?? true,
preserveBuffer: transition.preserveBuffer ?? false,
interruptible: transition.interruptible ?? true,
},
interactions: {
pointer: {
...defaultPointer,
...(interactions.pointer ?? {}),
},
press: {
...defaultPress,
...(interactions.press ?? {}),
},
signals: interactions.signals ?? [],
},
reducedMotion: {
mode: reducedMotion.mode ?? "representative-phase",
phase: reducedMotion.phase ?? 0.25,
speed: 0,
transition: reducedMotion.transition ?? "crossfade",
transitionMs: reducedMotion.transitionMs ?? 120,
pointer: false,
spatialMotion: false,
representation:
reducedMotion.representation ?? `static ${labels.default.toLowerCase()} glyph`,
dataUpdates: reducedMotion.dataUpdates ?? "discrete",
},
labels: {
default: labels.default,
aria: labels.aria ?? labels.default,
live: labels.live ?? (urgency === "high" ? "assertive" : "polite"),
value: labels.value ?? null,
variants: labels.variants ?? null,
},
};
return deepFreeze(recipe);
}
const RECIPES = [
defineSprite({
id: "ai.idle",
category: "presence",
meaning: "ready and available",
glyph: "idle",
field: "fbm",
composition: {
gestaltBoundary: true,
gestaltOpenness: 0.58,
},
fieldMix: [
layer("fbm", 0.72, 1.15, "replace", { speed: 0.2, contrast: 0.9 }),
layer("caustics", 0.28, 1.7, "soft-light", {
speed: -0.09,
phase: 0.19,
}),
],
palette: "neutral",
threshold: 0.51,
density: 0.68,
speed: 0.14,
pixelShape: "rounded-square",
pixelSwitch: {
mode: "ordered-dither",
dither: "bayer8",
hysteresis: 0.045,
temporalRate: 0.1,
direction: "field",
},
timeline: {
durationMs: 7200,
macroCycleMs: 14400,
replay: "continuous",
},
transition: {
enter: "field-morph",
exit: "field-morph",
durationMs: 520,
preservePhase: true,
},
interactions: {
pointer: { effect: "steer-highlight", strength: 0.14 },
press: { effect: "spring-compress", strength: 0.055 },
},
reducedMotion: {
phase: 0.18,
representation: "static side-lit lunar sphere with a persistent rim",
},
labels: { default: "Ready", live: "polite" },
}),
defineSprite({
id: "ai.ambient-idle",
category: "ambient",
meaning: "calm field-only presence",
glyph: "idle",
field: "fbm",
composition: {
mode: "field-orb",
gestaltBoundary: true,
gestaltOpenness: 0.46,
lighting: "crescent",
radius: 0.88,
fieldContrast: 1.26,
fieldDetail: 0.86,
lightOrbit: 0.1,
breathe: 0.045,
rim: 0.055,
},
fieldMix: [
layer("fbm", 0.62, 1.14, "replace", {
speed: 0.18,
contrast: 1.24,
}),
layer("caustics", 0.38, 2.05, "soft-light", {
speed: -0.11,
contrast: 1.08,
phase: 0.31,
}),
],
palette: "neutral",
threshold: 0.49,
density: 0.76,
speed: 0.22,
pixelShape: "rounded-square",
pixelScale: 0.9,
pixelSwitch: {
mode: "ordered-dither",
dither: "bayer8",
hysteresis: 0.045,
temporalRate: 0.18,
direction: "surface-drift",
},
timeline: {
durationMs: 7200,
macroCycleMs: 14400,
replay: "continuous",
},
transition: {
enter: "field-morph",
exit: "field-morph",
durationMs: 520,
preservePhase: true,
},
interactions: {
pointer: { effect: "steer-light", strength: 0.18 },
press: { effect: "surface-ripple", strength: 0.09 },
},
reducedMotion: {
phase: 0.18,
representation: "static side-lit dithered field orb",
},
labels: { default: "Ambient ready", live: "polite" },
}),
defineSprite({
id: "ai.ambient-thinking",
category: "ambient",
meaning: "reasoning expressed only through an animated field",
glyph: "thinking",
field: "domain-warp",
composition: {
mode: "field-orb",
gestaltBoundary: true,
gestaltOpenness: 0.62,
lighting: "sphere",
radius: 0.84,
fieldContrast: 1.48,
fieldDetail: 0.96,
lightOrbit: 0.2,
breathe: 0.09,
rim: 0.045,
},
fieldMix: [
layer("domain-warp", 0.46, 1.08, "replace", {
speed: 0.52,
contrast: 1.34,
warp: 0.7,
}),
layer("curl", 0.32, 1.52, "add", {
speed: -0.38,
contrast: 1.22,
}),
layer("ridged", 0.22, 2.18, "soft-light", {
speed: 0.24,
contrast: 1.36,
}),
],
palette: "info",
threshold: 0.5,
density: 0.8,
speed: 0.72,
pixelShape: "rounded-square",
pixelScale: 0.89,
pixelSwitch: {
mode: "temporal-blue-noise",
dither: "blue-noise64",
hysteresis: 0.048,
temporalRate: 0.68,
direction: "counter-flow",
},
timeline: {
durationMs: 4600,
macroCycleMs: 9200,
replay: "continuous",
},
transition: {
enter: "radial-cascade",
exit: "field-morph",
durationMs: 460,
preservePhase: true,
},
interactions: {
pointer: { effect: "bend-flow-and-light", strength: 0.16 },
press: { effect: "damped-vortex", strength: 0.18, durationMs: 460 },
signals: [
{ name: "reasoning.depth", mapsTo: "shellEnergy", range: [0, 1] },
],
},
reducedMotion: {
phase: 0.34,
representation: "static front-lit dithered reasoning field",
},
labels: { default: "Ambient thinking", live: "polite" },
}),
defineSprite({
id: "ai.ambient-thinking-symmetric",
category: "ambient",
meaning: "reasoning expressed through a clean, symmetrical animated field",
glyph: "thinking",
field: "kaleidoscope",
composition: {
mode: "field-orb",
lighting: "flat",
radius: 0.87,
fieldContrast: 1.32,
fieldDetail: 0.84,
lightOrbit: 0,
breathe: 0.045,
rim: 0.04,
},
fieldMix: [
layer("kaleidoscope", 0.74, 0.82, "replace", {
segments: 8,
speed: 0.2,
contrast: 1.25,
}),
layer("kaleidoscope", 0.26, 1.42, "soft-light", {
segments: 8,
speed: -0.12,
contrast: 1.08,
}),
],
palette: "info",
threshold: 0.5,
density: 0.78,
speed: 0.5,
pixelShape: "square",
pixelGap: 0.14,
pixelScale: 0.9,
pixelSwitch: {
mode: "threshold-hysteresis",
dither: "bayer8",
hysteresis: 0.058,
temporalRate: 0.34,
direction: "radial",
},
timeline: {
durationMs: 6400,
macroCycleMs: 12800,
replay: "continuous",
},
transition: {
enter: "radial-cascade",
exit: "field-morph",
durationMs: 500,
preservePhase: true,
},
interactions: {
pointer: { enabled: false },
press: { enabled: false },
signals: [
{ name: "reasoning.depth", mapsTo: "shellEnergy", range: [0, 1] },
],
},
reducedMotion: {
phase: 0.25,
representation: "static symmetrical reasoning field",
},
labels: { default: "Ambient thinking — symmetric", live: "polite" },
}),
defineSprite({
id: "ai.ambient-speaking",
category: "ambient",
meaning: "voice output expressed only through an animated field",
glyph: "speaking",
field: "interference",
composition: {
mode: "field-orb",
gestaltBoundary: true,
gestaltOpenness: 0.5,
lighting: "gibbous",
radius: 0.87,
fieldContrast: 1.38,
fieldDetail: 0.92,
lightOrbit: 0.13,
breathe: 0.065,
voice: 0.2,
rim: 0.05,
},
fieldMix: [
layer("interference", 0.46, 1.26, "replace", {
speed: 1.05,
contrast: 1.28,
}),
layer("plasma", 0.34, 1.7, "screen", {
speed: -0.72,
contrast: 1.18,
}),
layer("ripple", 0.2, 1.12, "add", {
speed: 0.82,
contrast: 1.24,
}),
],
palette: "info",
threshold: 0.48,
density: 0.82,
speed: 1.02,
pixelShape: "circle",
pixelScale: 0.9,
pixelSwitch: {
mode: "ordered-dither",
dither: "bayer8",
hysteresis: 0.042,
temporalRate: 0.96,
direction: "audio-surface",
},
timeline: {
mode: "signal-reactive",
durationMs: 1900,
macroCycleMs: 5700,
},
transition: {
enter: "radial-cascade",
exit: "field-morph",
durationMs: 300,
preservePhase: true,
},
interactions: {
pointer: { effect: "steer-light", strength: 0.13 },
press: { enabled: false },
signals: [
{
name: "audio.level",
mapsTo: "envelope",
range: [0, 1],
decayMs: 220,
},
{ name: "audio.syllable", mapsTo: "bandEnergy", range: [0, 1] },
],
},
reducedMotion: {
mode: "data-step",
phase: 0.38,
representation: "static gibbous field with discrete audio levels",
dataUpdates: "three-level-step",
},
labels: { default: "Ambient speaking", live: "polite" },
}),
defineSprite({
id: "ai.listening",
category: "activity",
meaning: "capturing voice or input",
glyph: "listening",
field: "interference",
fieldMix: [
layer("interference", 0.46, 1.4, "replace", {
speed: 0.68,
contrast: 1.18,
}),
layer("radar", 0.32, 1.05, "add", { speed: -0.46 }),
layer("liquid", 0.22, 1.8, "soft-light", { speed: 0.3 }),
],
palette: "info",
threshold: 0.48,
density: 0.77,
speed: 0.78,
pixelShape: "circle",
pixelSwitch: {
mode: "radial-cascade",
dither: "bayer8",
hysteresis: 0.05,
direction: "inward",
},
timeline: { durationMs: 2400, macroCycleMs: 4800 },
transition: {
enter: "sdf-wavefront",
exit: "radial-cascade",
durationMs: 340,
preservePhase: true,
},
interactions: {
pointer: { effect: "ripple-bias", strength: 0.12 },
press: { effect: "emit-inbound-ripple", strength: 0.18 },
signals: [
{ name: "audio.level", mapsTo: "energy", range: [0, 1], decayMs: 90 },
{ name: "input.pulse", mapsTo: "ripple", range: [0, 1] },
],
},
reducedMotion: {
phase: 0.36,
representation: "static listening ring with discrete level steps",
dataUpdates: "three-level-step",
},
labels: { default: "Listening", live: "polite" },
}),
defineSprite({
id: "ai.thinking",
category: "activity",
meaning: "reasoning",
glyph: "thinking",
field: "flow",
fieldMix: [
layer("flow", 0.47, 1.35, "replace", {
speed: 0.82,
contrast: 1.08,
}),
layer("vortex", 0.35, 1.05, "add", { speed: -0.62, warp: 0.22 }),
layer("fbm", 0.18, 2.1, "soft-light", { speed: 0.25 }),
],
palette: "info",
threshold: 0.5,
density: 0.74,
speed: 0.92,
pixelShape: "rounded-square",
pixelSwitch: {
mode: "temporal-blue-noise",
dither: "blue-noise64",
hysteresis: 0.062,
temporalRate: 0.82,
},
timeline: { durationMs: 3100, macroCycleMs: 6200 },
transition: {
enter: "field-morph",
exit: "field-morph",
durationMs: 430,
preservePhase: true,
},
interactions: {
pointer: { effect: "bend-flow", strength: 0.13 },
press: { effect: "damped-vortex", strength: 0.2, durationMs: 460 },
},
reducedMotion: {
phase: 0.3,
representation: "static faceted spiral",
},
labels: { default: "Thinking", live: "polite" },
}),
defineSprite({
id: "ai.thinking-deep",
category: "activity",
meaning: "deliberate extended reasoning",
glyph: "thinking-deep",
field: "domain-warp",
fieldMix: [
layer("domain-warp", 0.46, 0.95, "replace", {
speed: 0.34,
warp: 0.48,
}),
layer("ridged", 0.31, 1.8, "multiply", {
speed: 0.18,
contrast: 1.24,
}),
layer("vortex", 0.23, 0.72, "add", { speed: -0.27 }),
],
palette: "info",
threshold: 0.52,
density: 0.79,
speed: 0.38,
pixelShape: "diamond",
pixelGap: 0.1,
pixelSwitch: {
mode: "neighbor-propagation",
dither: "bayer8",
hysteresis: 0.07,
temporalRate: 0.36,
neighborhood: 8,
direction: "shell-to-shell",
},
timeline: {
durationMs: 9600,
macroCycleMs: 19200,
replay: "continuous",
},
transition: {
enter: "field-morph",
exit: "field-morph",
durationMs: 680,
preservePhase: true,
},
interactions: {
pointer: { effect: "separate-shells", strength: 0.09 },
press: { effect: "pinch-core", strength: 0.16, durationMs: 520 },
signals: [
{
name: "reasoning.depth",
mapsTo: "shellEnergy",
range: [0, 1],
},
],
},
reducedMotion: {
phase: 0.42,
representation: "static nested rings with a brighter inner core",
},
labels: { default: "Reasoning deeply", live: "polite" },
}),
defineSprite({
id: "ai.still-working",
category: "activity",
meaning: "work is taking longer than expected",
glyph: "still-working",
field: "strata",
fieldMix: [
layer("strata", 0.44, 1.2, "replace", {
speed: 0.21,
contrast: 1.08,
}),
layer("radar", 0.34, 0.82, "add", { speed: 0.17 }),
layer("flow", 0.22, 1.55, "soft-light", { speed: 0.18 }),
],
palette: "neutral",
threshold: 0.5,
density: 0.71,
speed: 0.24,
pixelShape: "rounded-square",
pixelSwitch: {
mode: "radial-cascade",
dither: "bayer8",
hysteresis: 0.055,
temporalRate: 0.62,
direction: "outward-beacon",
},
timeline: {
durationMs: 3200,
macroCycleMs: 3200,
replay: "continuous",
},
transition: {
enter: "field-morph",
exit: "field-morph",
durationMs: 560,
preservePhase: false,
},
interactions: {
pointer: { effect: "expand-beacon", strength: 0.1 },
press: { effect: "host-action", strength: 0.05 },
signals: [
{ name: "latency.elapsed", mapsTo: "beaconEnergy", range: [0, 1] },
],
},
reducedMotion: {
phase: 0.46,
representation: "upright hourglass with settled sand and flip guides",
},
labels: { default: "Still working", live: "polite" },
}),
defineSprite({
id: "ai.loading",
category: "activity",
meaning: "indeterminate startup or wait",
glyph: "loading",
field: "plasma",
fieldMix: [
layer("plasma", 0.58, 1.15, "replace", {
speed: 1.15,
contrast: 1.16,
}),
layer("flow", 0.42, 1.65, "add", { speed: 0.88 }),
],
palette: "info",
threshold: 0.52,
density: 0.7,
speed: 1.38,
pixelShape: "square",
pixelSwitch: {
mode: "ordered-dither",
dither: "bayer8",
hysteresis: 0.052,
temporalRate: 1.3,
direction: "radial-tail",
},
timeline: { durationMs: 1440, macroCycleMs: 5760 },
transition: {
enter: "radial-cascade",
exit: "field-morph",
durationMs: 280,
preservePhase: true,
},
interactions: {
pointer: { effect: "steer-highlight", strength: 0.08 },
press: { enabled: false },
},
reducedMotion: {
phase: 0.63,
representation: "static segmented loading ring",
},
labels: { default: "Loading", live: "polite" },
}),
defineSprite({
id: "ai.progress",
category: "activity",
meaning: "determinate completion progress",
glyph: "progress",
field: "strata",
fieldMix: [
layer("strata", 0.54, 1.25, "replace", {
speed: 0.25,
contrast: 1.1,
}),
layer("constellation", 0.46, 1.9, "add", {
speed: 0.1,
phase: 0.37,
}),
],
palette: "info",
threshold: 0.47,
density: 0.84,
speed: 0.32,
pixelShape: "circle",
pixelSwitch: {
mode: "temporal-blue-noise",
dither: "blue-noise64",
hysteresis: 0.045,
temporalRate: 0.28,
direction: "contour-forward",
monotonic: true,
},
timeline: { mode: "data-driven", durationMs: 240, replay: "on-update" },
transition: {
enter: "contour-trace",
exit: "path-draw",
durationMs: 320,
preservePhase: true,
},
interactions: {
pointer: { effect: "reveal-value", strength: 0 },
press: { effect: "spring-compress", strength: 0.045 },
signals: [
{
name: "progress",
mapsTo: "progress",
range: [0, 1],
monotonic: true,
},
],
},
reducedMotion: {
mode: "data-step",
phase: 0,
representation: "static progress contour updated from data",
dataUpdates: "discrete-monotonic",
},
labels: {
default: "In progress",
value: "{percent}% complete",
live: "polite",
},
}),
defineSprite({
id: "ai.generating",
category: "activity",
meaning: "producing content",
glyph: "generating",
field: "electric",
fieldMix: [
layer("electric", 0.4, 1.75, "replace", {
speed: 0.84,
contrast: 1.23,
}),
layer("metaballs", 0.34, 1.1, "add", {
speed: 0.54,
warp: 0.18,
}),
layer("constellation", 0.26, 2.05, "screen", {
speed: 0.38,
}),
],
palette: "info",
threshold: 0.5,
density: 0.78,
speed: 0.88,
pixelShape: "diamond",
pixelSwitch: {
mode: "curl-advect",
dither: "blue-noise64",
hysteresis: 0.058,
temporalRate: 0.8,
neighborhood: 4,
direction: "core-to-target",
},
timeline: {
mode: "signal-reactive",
durationMs: 3600,
macroCycleMs: 7200,
},
transition: {
enter: "seeded-dissolve",
exit: "field-morph",
durationMs: 460,
preservePhase: true,
},
interactions: {
pointer: { effect: "attract-unlocked-pixels", strength: 0.11 },
press: { effect: "core-pulse", strength: 0.08 },
signals: [
{ name: "token", mapsTo: "spark", range: [0, 1], bounded: true },
{ name: "chunk", mapsTo: "spark", range: [0, 1], bounded: true },
{ name: "frame", mapsTo: "spark", range: [0, 1], bounded: true },
{ name: "stream.energy", mapsTo: "activity", range: [0, 1] },
],
},
reducedMotion: {
mode: "data-step",
phase: 0.72,
representation: "target glyph filled in non-moving chunks",
dataUpdates: "chunked-fill",
},
labels: { default: "Generating", live: "polite" },
}),
defineSprite({
id: "ai.searching",
category: "activity",
meaning: "searching or retrieving information",
glyph: "searching",
field: "radar",
fieldMix: [
layer("radar", 0.53, 0.95, "replace", {
speed: 0.72,
contrast: 1.18,
}),
layer("worley", 0.29, 1.62, "add", { speed: 0.2 }),
layer("caustics", 0.18, 2.1, "screen", { speed: 0.16 }),
],
palette: "info",
threshold: 0.49,
density: 0.73,
speed: 0.74,
pixelShape: "circle",
pixelSwitch: {
mode: "sdf-wavefront",
dither: "bayer8",
hysteresis: 0.057,
temporalRate: 0.72,
direction: "radar-sweep",
},
timeline: { durationMs: 3300, macroCycleMs: 6600 },
transition: {
enter: "radial-cascade",
exit: "field-morph",
durationMs: 380,
preservePhase: true,
},
interactions: {
pointer: { effect: "bias-decorative-beam", strength: 0.1 },
press: { enabled: false },
signals: [
{
name: "search.hit",
mapsTo: "worleyBloom",
range: [0, 1],
decayMs: 620,
},
{ name: "search.results", mapsTo: "hitCount", range: [0, 8] },
],
},
reducedMotion: {
phase: 0.2,
representation: "static magnifier with discrete result dots",
dataUpdates: "result-dot",
},
labels: { default: "Searching", live: "polite" },
}),
defineSprite({
id: "ai.tool-use",
category: "activity",
meaning: "executing a tool or action",
glyph: "tool-use",
field: "strata",
fieldMix: [
layer("strata", 0.43, 1.3, "replace", {
speed: 0.46,
contrast: 1.12,
}),
layer("electric", 0.34, 1.8, "add", { speed: 0.68 }),
layer("voronoi", 0.23, 1.05, "multiply", { speed: 0.22 }),
],
palette: "neutral",
threshold: 0.52,
density: 0.8,
speed: 0.68,
pixelShape: "square",
pixelSwitch: {
mode: "neighbor-propagation",
dither: "bayer8",
hysteresis: 0.062,
temporalRate: 0.62,
neighborhood: 6,
direction: "gear-step",
},
timeline: {
mode: "signal-reactive",
durationMs: 2400,
macroCycleMs: 7200,
},
transition: {
enter: "axis-flip",
exit: "field-morph",
durationMs: 420,
preservePhase: true,
},
interactions: {
pointer: { effect: "tilt-light", strength: 0.13 },
press: { effect: "mechanism-compress", strength: 0.09 },
signals: [
{ name: "tool.step", mapsTo: "toothPulse", range: [0, 1] },
{ name: "tool.wait", mapsTo: "stepHold", range: [0, 1] },
{ name: "tool.result", mapsTo: "coreState", range: [0, 1] },
],
},
reducedMotion: {
phase: 0.125,
representation: "static gear with data-driven step dots",
dataUpdates: "step-dot",
},
labels: { default: "Using a tool", live: "polite" },
}),
defineSprite({
id: "ai.speaking",
category: "activity",
meaning: "producing voice output",
glyph: "speaking",
field: "interference",
fieldMix: [
layer("interference", 0.44, 1.2, "replace", {
speed: 1.05,
contrast: 1.16,
}),
layer("plasma", 0.33, 1.62, "add", { speed: 0.74 }),
layer("liquid", 0.23, 0.92, "soft-light", {
speed: 0.55,
warp: 0.2,
}),
],
palette: "info",
threshold: 0.47,
density: 0.81,
speed: 1.08,
pixelShape: "circle",
pixelSwitch: {
mode: "radial-cascade",
dither: "bayer8",
hysteresis: 0.05,
temporalRate: 1.02,
direction: "outward-audio",
},
timeline: {
mode: "signal-reactive",
durationMs: 1900,
macroCycleMs: 5700,
},
transition: {
enter: "sdf-wavefront",
exit: "radial-cascade",
durationMs: 260,
preservePhase: true,
},
interactions: {
pointer: { effect: "steer-light", strength: 0.11 },
press: { enabled: false },
signals: [
{
name: "audio.level",
mapsTo: "envelope",
range: [0, 1],
decayMs: 240,
},
{ name: "audio.syllable", mapsTo: "bandEnergy", range: [0, 1] },
],
},
reducedMotion: {
mode: "data-step",
phase: 0.38,
representation: "static speaker with three discrete level states",
dataUpdates: "three-level-step",
},
labels: { default: "Speaking", live: "polite" },
}),
defineSprite({
id: "ai.awaiting-input",
category: "activity",
meaning: "user action is required",
glyph: "awaiting-input",
field: "fbm",
fieldMix: [
layer("fbm", 0.72, 0.92, "replace", {
speed: 0.18,
contrast: 0.94,
}),
layer("caustics", 0.28, 1.55, "soft-light", {
speed: 0.1,
contrast: 0.9,
}),
],
palette: "info",
threshold: 0.48,
density: 0.8,
speed: 0.22,
pixelShape: "rounded-square",
pixelSwitch: {
mode: "threshold-hysteresis",
dither: "bayer8",
hysteresis: 0.052,
temporalRate: 0.08,
direction: "focus",
},
timeline: {
mode: "hold",
durationMs: 1800,
macroCycleMs: 10800,
replay: "two-pulses-then-hold",
},
transition: {
enter: "radial-cascade",
exit: "field-morph",
durationMs: 420,
preservePhase: false,
},
interactions: {
pointer: { effect: "turn-aperture", strength: 0.1 },
press: {
effect: "acknowledge",
strength: 0.06,
durationMs: 160,
emits: "activate",
},
signals: [{ name: "activate", mapsTo: "acknowledgement", range: [0, 1] }],
},
reducedMotion: {
phase: 0.44,
representation: "static question mark inside a firefly focus ring",
},
labels: { default: "Your input is needed", live: "polite" },
}),
defineSprite({
id: "status.success",
category: "status",
meaning: "completed successfully",
glyph: "success",
field: "caustics",
fieldMix: [
layer("caustics", 0.57, 1.3, "replace", {
speed: 0.72,
contrast: 1.15,
}),
layer("constellation", 0.43, 1.75, "screen", { speed: 0.35 }),
],
palette: "success",
threshold: 0.46,
density: 0.9,
speed: 0.82,
pixelShape: "rounded-square",
pixelSwitch: {
mode: "path-draw",
dither: "blue-noise64",
hysteresis: 0.042,
temporalRate: 1,
direction: "tail-to-head",
monotonic: false,
},
timeline: {
mode: "one-shot",
durationMs: 500,
holdMs: 800,
replay: "on-enter",
},
transition: {
enter: "path-draw",
exit: "field-morph",
durationMs: 500,
exitDurationMs: 300,
preservePhase: false,
},
interactions: {
pointer: { effect: "edge-sparkle", strength: 0.04 },
press: { enabled: false },
},
reducedMotion: {
mode: "static",
phase: 1,
representation: "static checkmark",
transitionMs: 120,
},
labels: { default: "Completed", aria: "Completed successfully", live: "polite" },
terminal: true,
}),
defineSprite({
id: "status.warning",
category: "status",
meaning: "attention is needed for a nonfatal issue",
glyph: "warning",
field: "strata",
fieldMix: [
layer("strata", 0.58, 1.18, "replace", {
speed: 0.38,
contrast: 1.22,
}),
layer("interference", 0.42, 1.55, "add", { speed: 0.33 }),
],
palette: "warning",
threshold: 0.49,
density: 0.86,
speed: 0.42,
pixelShape: "diamond",
pixelSwitch: {
mode: "contour-trace",
dither: "bayer8",
hysteresis: 0.065,
temporalRate: 0.38,
direction: "bottom-to-contour",
},
timeline: {
mode: "one-shot-hold",
durationMs: 1100,
holdMs: 1600,
replay: "two-pulses-then-hold",
},
transition: {
enter: "sdf-wavefront",
exit: "field-morph",
durationMs: 430,
preservePhase: false,
},
interactions: {
pointer: { effect: "lean-highlight", strength: 0.08 },
press: { effect: "contained-ripple", strength: 0.08 },
},
reducedMotion: {
mode: "static",
phase: 1,
representation: "static outlined triangle and exclamation",
},
labels: { default: "Warning", live: "polite" },
urgency: "medium",
}),
defineSprite({
id: "status.error",
category: "status",
meaning: "operation failed",
glyph: "error",
field: "electric",
fieldMix: [
layer("electric", 0.61, 1.4, "replace", {
speed: 0.82,
contrast: 1.3,
}),
layer("ridged", 0.39, 1.85, "multiply", {
speed: 0.35,
contrast: 1.22,
}),
],
palette: "danger",
threshold: 0.51,
density: 0.88,
speed: 0.78,
pixelShape: "square",
pixelSwitch: {
mode: "axis-flip",
dither: "bayer8",
hysteresis: 0.055,
temporalRate: 0.9,
direction: "diagonal-fracture",
},
timeline: {
mode: "one-shot-hold",
durationMs: 410,
holdMs: 1200,
replay: "on-enter",
},
transition: {
enter: "axis-flip",
exit: "seeded-dissolve",
durationMs: 410,
preservePhase: false,
},
interactions: {
pointer: { effect: "edge-repel", strength: 0.055 },
press: { enabled: false },
},
reducedMotion: {
mode: "static",
phase: 1,
representation: "static X",
transitionMs: 100,
},
labels: { default: "Error", aria: "Operation failed", live: "assertive" },
terminal: true,
urgency: "high",
}),
defineSprite({
id: "status.paused",
category: "status",
meaning: "work is suspended and can resume",
glyph: "paused",
field: "fbm",
fieldMix: [
layer("fbm", 0.55, 1.1, "replace", { speed: 0 }),
layer("strata", 0.45, 1.4, "multiply", { speed: 0 }),
],
palette: "neutral",
threshold: 0.5,
density: 0.9,
speed: 0,
pixelShape: "rounded-square",
pixelSwitch: {
mode: "axis-flip",
dither: "bayer8",
hysteresis: 0.08,
temporalRate: 0,
direction: "columns",
},
timeline: {
mode: "hold",
durationMs: 1,
replay: "resume-reversible",
},
transition: {
enter: "axis-flip",
exit: "axis-flip",
durationMs: 280,
preservePhase: true,
preserveBuffer: true,
},
interactions: {
pointer: { effect: "steer-light-only", strength: 0.06 },
press: { effect: "bar-squeeze", strength: 0.045 },
signals: [{ name: "resume", mapsTo: "reverseTransition", range: [0, 1] }],
},
reducedMotion: {
mode: "static",
phase: 1,
representation: "static pause bars",
},
labels: { default: "Paused", live: "polite" },
}),
defineSprite({
id: "status.cancelled",
category: "status",
meaning: "operation was stopped",
glyph: "cancelled",
field: "vortex",
fieldMix: [
layer("vortex", 0.57, 0.88, "replace", {
speed: -0.68,
contrast: 1.12,
}),
layer("metaballs", 0.43, 1.25, "add", { speed: -0.42 }),
],
palette: "neutral",
threshold: 0.52,
density: 0.85,
speed: 0.62,
pixelShape: "square",
pixelSwitch: {
mode: "seeded-dissolve",
dither: "blue-noise64",
hysteresis: 0.065,
temporalRate: 0.72,
direction: "reverse-to-center",
},
timeline: {
mode: "one-shot-hold",
durationMs: 360,
holdMs: 900,
replay: "on-enter",
},
transition: {
enter: "seeded-dissolve",
exit: "seeded-dissolve",
durationMs: 360,
preservePhase: false,
},
interactions: {
pointer: { enabled: false },
press: { effect: "spring-compress", strength: 0.035 },
},
reducedMotion: {
mode: "static",
phase: 1,
representation: "static stop square",
},
labels: { default: "Cancelled", live: "polite" },
terminal: true,
}),
defineSprite({
id: "status.offline",
category: "status",
meaning: "disconnected or unavailable",
glyph: "offline",
field: "radar",
fieldMix: [
layer("radar", 0.56, 0.72, "replace", {
speed: 0.16,
contrast: 1.16,
}),
layer("electric", 0.44, 1.55, "add", { speed: 0.11 }),
],
palette: "neutral",
threshold: 0.53,
density: 0.72,
speed: 0.18,
pixelShape: "rounded-square",
pixelSwitch: {
mode: "path-draw",
dither: "bayer8",
hysteresis: 0.075,
temporalRate: 0.16,
direction: "bridge-and-retreat",
},
timeline: {
mode: "signal-reactive",
durationMs: 4200,
macroCycleMs: 8400,
replay: "continuous",
},
transition: {
enter: "contour-trace",
exit: "field-morph",
durationMs: 480,
preservePhase: true,
},
interactions: {
pointer: { effect: "weak-echo", strength: 0.045 },
press: { effect: "host-retry", strength: 0.04 },
signals: [
{ name: "network.retry", mapsTo: "retryEnergy", range: [0, 1] },
{ name: "network.strength", mapsTo: "signalStrength", range: [0, 1] },
],
},
reducedMotion: {
phase: 0.31,
representation: "static broken ring with a diagonal disconnect mark",
dataUpdates: "retry-dot",
},
labels: { default: "Offline", live: "polite" },
urgency: "medium",
}),
defineSprite({
id: "transfer.active",
category: "workflow",
meaning: "uploading, downloading, or synchronizing data",
glyph: "transfer",
field: "flow",
fieldMix: [
layer("flow", 0.46, 1.15, "replace", {
speed: 0.88,
contrast: 1.14,
}),
layer("liquid", 0.31, 1.55, "add", { speed: 0.65 }),
layer("strata", 0.23, 1.9, "multiply", { speed: 0.44 }),
],
palette: "info",
threshold: 0.48,
density: 0.79,
speed: 0.9,
pixelShape: "square",
pixelSwitch: {
mode: "axis-flip",
dither: "blue-noise64",
hysteresis: 0.052,
temporalRate: 0.85,
direction: "data-direction",
monotonic: false,
},
timeline: {
mode: "data-or-loop",
durationMs: 2200,
macroCycleMs: 6600,
},
transition: {
enter: "axis-flip",
exit: "field-morph",
durationMs: 380,
preservePhase: true,
},
interactions: {
pointer: { effect: "lateral-wind", strength: 0.07, minSize: 28 },
press: { effect: "spring-compress", strength: 0.045 },
signals: [
{
name: "progress",
mapsTo: "progress",
range: [0, 1],
monotonic: true,
},
{
name: "direction",
mapsTo: "direction",
values: ["up", "down", "sync"],
},
],
},
reducedMotion: {
mode: "data-step",
phase: 0.5,
representation: "static directional arrow with data-driven fill",
dataUpdates: "discrete-monotonic",
},
labels: {
default: "Transferring",
live: "polite",
value: "{percent}% transferred",
variants: {
up: "Uploading",
down: "Downloading",
sync: "Syncing",
},
},
}),
defineSprite({
id: "workflow.handoff",
category: "workflow",
meaning: "passing work to another agent or person",
glyph: "handoff",
field: "flow",
fieldMix: [
layer("flow", 0.44, 1.02, "replace", {
speed: 0.66,
contrast: 1.1,
}),
layer("constellation", 0.34, 1.6, "screen", { speed: 0.31 }),
layer("metaballs", 0.22, 0.85, "add", { speed: 0.38 }),
],
palette: "info",
threshold: 0.49,
density: 0.77,
speed: 0.68,
pixelShape: "circle",
pixelSwitch: {
mode: "path-draw",
dither: "blue-noise64",
hysteresis: 0.055,
temporalRate: 0.62,
direction: "source-to-destination",
},
timeline: {
mode: "signal-reactive",
durationMs: 3000,
macroCycleMs: 6000,
replay: "loop-until-accepted",
},
transition: {
enter: "field-morph",
exit: "path-draw",
durationMs: 460,
preservePhase: true,
},
interactions: {
pointer: { effect: "bend-path-midpoint", strength: 0.09 },
press: { enabled: false },
signals: [
{
name: "handoff.accepted",
mapsTo: "accepted",
range: [0, 1],
latches: true,
},
],
},
reducedMotion: {
phase: 0.58,
representation: "two static nodes joined by a directional arrow",
dataUpdates: "destination-highlight",
},
labels: { default: "Handing off", live: "polite" },
}),
defineSprite({
id: "status.celebration",
category: "status",
meaning: "a milestone or high-value success",
glyph: "celebration",
field: "constellation",
fieldMix: [
layer("constellation", 0.45, 1.45, "replace", {
speed: 0.92,
contrast: 1.2,
}),
layer("caustics", 0.31, 1.9, "screen", { speed: 0.62 }),
layer("electric", 0.24, 2.2, "add", { speed: 0.74 }),
],
palette: "celebration",
threshold: 0.45,
density: 0.88,
speed: 1,
pixelShape: "diamond",
pixelSwitch: {
mode: "radial-cascade",
dither: "blue-noise64",
hysteresis: 0.045,
temporalRate: 1.1,
direction: "bounded-burst",
},
timeline: {
mode: "one-shot",
durationMs: 700,
holdMs: 900,
replay: "on-enter-once",
next: "status.success",
},
transition: {
enter: "radial-cascade",
exit: "field-morph",
durationMs: 700,
preservePhase: false,
},
interactions: {
pointer: { effect: "push-fragments", strength: 0.06, minSize: 32 },
press: { enabled: false },
},
reducedMotion: {
mode: "static",
phase: 1,
representation: "static star with a central success mark",
transitionMs: 120,
},
labels: {
default: "Milestone completed",
aria: "Milestone completed successfully",
live: "polite",
},
terminal: true,
}),
];
export const SPRITES = deepFreeze(
Object.fromEntries(RECIPES.map((recipe) => [recipe.id, recipe])),
);
export const SPRITE_IDS = Object.freeze(RECIPES.map((recipe) => recipe.id));
const SPRITE_LIST = Object.freeze([...RECIPES]);
export const SPRITE_ALIASES = Object.freeze({
idle: "ai.idle",
ready: "ai.idle",
ambient: "ai.ambient-idle",
"ambient-idle": "ai.ambient-idle",
"field-idle": "ai.ambient-idle",
"ambient-thinking": "ai.ambient-thinking",
"field-thinking": "ai.ambient-thinking",
"ambient-thinking-symmetric": "ai.ambient-thinking-symmetric",
"field-thinking-symmetric": "ai.ambient-thinking-symmetric",
"symmetric-thinking": "ai.ambient-thinking-symmetric",
"ambient-speaking": "ai.ambient-speaking",
"field-speaking": "ai.ambient-speaking",
listening: "ai.listening",
thinking: "ai.thinking",
"thinking-deep": "ai.thinking-deep",
"deep-thinking": "ai.thinking-deep",
"still-working": "ai.still-working",
working: "ai.still-working",
loading: "ai.loading",
progress: "ai.progress",
generating: "ai.generating",
searching: "ai.searching",
"tool-use": "ai.tool-use",
tool: "ai.tool-use",
speaking: "ai.speaking",
"awaiting-input": "ai.awaiting-input",
prompt: "ai.awaiting-input",
success: "status.success",
warning: "status.warning",
error: "status.error",
paused: "status.paused",
cancelled: "status.cancelled",
offline: "status.offline",
transfer: "transfer.active",
upload: "transfer.active",
download: "transfer.active",
sync: "transfer.active",
handoff: "workflow.handoff",
celebration: "status.celebration",
});
export function resolveSpriteId(id) {
const normalized = String(id ?? "").trim().toLowerCase();
if (Object.prototype.hasOwnProperty.call(SPRITES, normalized)) {
return normalized;
}
return SPRITE_ALIASES[normalized];
}
export function getSprite(id) {
const resolved = resolveSpriteId(id);
return resolved ? SPRITES[resolved] : undefined;
}
export function listSprites() {
return SPRITE_LIST;
}
export function hasSprite(id) {
return resolveSpriteId(id) !== undefined;
}
export default SPRITES;