Files
oikos/web/vendor/src/preview-thumbnails.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

83 lines
2.2 KiB
JavaScript

const DEFAULT_PREVIEW_PROGRESS = 0.48;
function clamp01(value) {
return Math.min(1, Math.max(0, Number(value) || 0));
}
function representativeTime(recipe) {
const phase = clamp01(recipe?.reducedMotion?.phase ?? 0.25);
const cycleMs =
Number(recipe?.timeline?.macroCycleMs) ||
Number(recipe?.timeline?.durationMs) ||
4000;
return (Math.max(1, cycleMs) / 1000) * phase;
}
/**
* One-shot recipes may advance to a different state while their card is active.
* Atlas previews retain the requested recipe so their identity and resting frame
* always match the card label.
*/
export function createThumbnailRecipe(recipe) {
if (!recipe?.timeline?.next) return recipe;
return {
...recipe,
timeline: {
...recipe.timeline,
next: null,
},
};
}
export function shouldAnimateThumbnail(
id,
{
selectedId = null,
mainRunning = false,
hoveredIds = new Set(),
focusedIds = new Set(),
} = {},
) {
return (
(id === selectedId && mainRunning) ||
hoveredIds.has(id) ||
focusedIds.has(id)
);
}
/**
* Render a deterministic, authored representative phase without leaving the
* preview in reduced-motion mode. JoanGlyphEngine maps time zero to each
* recipe's reducedMotion.phase while reduced motion is enabled.
*/
export function renderThumbnailRestFrame(
preview,
{ progress = DEFAULT_PREVIEW_PROGRESS } = {},
) {
if (!preview || preview.destroyed) return null;
const nextProgress = clamp01(progress);
if (Math.abs(preview.progress - nextProgress) > 1e-6) {
preview.setProgress(nextProgress);
}
// Neighbor-propagation previews otherwise retain their previous gate buffer,
// making the supposedly static pose depend on how long the card was hovered.
preview.stateElapsed = representativeTime(preview.currentRecipe);
preview.gates?.fill(0);
preview.neighborBuffer?.fill(0);
preview.dwell?.fill(0);
const previousReducedMotion = preview.reducedMotion;
preview.reducedMotion = true;
let stats;
try {
stats = preview.renderOnce(0);
} finally {
preview.reducedMotion = previousReducedMotion;
}
if (preview.canvas?.dataset) preview.canvas.dataset.previewState = "ready";
return stats;
}