Clickable previews: every still links to its exact app frame
New shared component src/ui/preview.js (appFrameUrl + previewLink): song + seed + frame + venue/venueSel deep-links. App honors ?frame= (warm seek on bank-song load) and ?venueSel= via shared pinVenueShell helper (src/venues/pin.js), which filmstrip and venue-gallery now also use. Filmstrip figures and venue-gallery cards wrap canvases; gallery cells stay unlinked (isolated scene probes, not reproducible in-app).
This commit is contained in:
@@ -23,6 +23,8 @@
|
||||
.meta { color:#6b7280; font-size:11px; }
|
||||
.meta.err { color:#f87171; }
|
||||
canvas { width:100%; display:block; background:#05070a; border-radius:2px; aspect-ratio:16/9; }
|
||||
a.preview-link canvas { cursor: zoom-in; }
|
||||
a.preview-link:hover canvas { outline: 1px solid #7dd3fc; outline-offset: -1px; }
|
||||
.hint { font-size:10px; color:#565f70; margin-top:4px; }
|
||||
#controls { display:flex; gap:8px; align-items:center; flex-wrap:wrap; margin-top:8px; }
|
||||
.progress { height:2px; background:#1a1e28; margin-top:8px; border-radius:2px; overflow:hidden; display:none; }
|
||||
@@ -59,11 +61,13 @@ import { Show } from '/src/Show.js';
|
||||
import { SONGS, song } from '/src/audio/songbank.js';
|
||||
import { Rng, hashString } from '/src/engine/rng.js';
|
||||
import { generateLook } from '/src/look/LookGenerator.js';
|
||||
import { pinVenueShell } from '/src/venues/pin.js';
|
||||
import { sampleValues } from '/src/params/schema.js';
|
||||
import { subjectIndexOf } from '/src/look/stack.js';
|
||||
import { venues } from '/src/venues/registry.js';
|
||||
import { scenes } from '/src/scenes/registry.js';
|
||||
import { blit } from '/src/checks/blit.js';
|
||||
import { previewLink } from '/src/ui/preview.js';
|
||||
|
||||
const THUMB = { width: 320, height: 180 };
|
||||
const DEFAULT_ROWS = 3;
|
||||
@@ -228,25 +232,12 @@ async function renderCell(show, entry, venueName, vizMod, colIdx) {
|
||||
// Force the selected venue. The VenueSpec population stays the census one
|
||||
// (counts overlap between archetypes), so this is a shell swap for the lab.
|
||||
if (venueName) {
|
||||
const picked = venues.find(v => v.name === venueName);
|
||||
if (!picked) {
|
||||
errorCard(`${entry.name} · col ${colIdx + 1}`, `unknown venue "${venueName}"`);
|
||||
try {
|
||||
pinVenueShell(look, venueName, { seed: seedBase, buildIfMissing: true });
|
||||
} catch (err) {
|
||||
errorCard(`${entry.name} · col ${colIdx + 1}`, err.message || String(err));
|
||||
return;
|
||||
}
|
||||
if (!look.venue) {
|
||||
look.venue = {
|
||||
archetype: picked.archetype, name: picked.name, seed: seedBase,
|
||||
shell: { kind: picked.archetype }, projection: { kind: 'screen' },
|
||||
population: { kind: 'scatter', count: 5, spread: 0.7, jitter: 0.25 },
|
||||
paletteMap: [0, 1, 2, 3],
|
||||
perSection: look.sections.map(() => ({ light: 0.7, fog: 0.35, popMul: 0.65, reachMul: 0.85 })),
|
||||
venue: true, readsDepth: false, module: picked,
|
||||
};
|
||||
} else {
|
||||
look.venue.module = picked;
|
||||
look.venue.name = picked.name;
|
||||
look.venue.archetype = picked.archetype;
|
||||
}
|
||||
}
|
||||
if (vizMod) pinViz(look, vizMod, seedBase);
|
||||
|
||||
@@ -286,6 +277,18 @@ async function renderCell(show, entry, venueName, vizMod, colIdx) {
|
||||
card.appendChild(cv);
|
||||
card.appendChild(meta);
|
||||
out.appendChild(card);
|
||||
// Clicking opens the exact frame in the app — but only when the cell is
|
||||
// reproducible there: a pinned viz re-samples the stacks outside any
|
||||
// look, so those cards stay unlinked (same reason gallery cells aren't).
|
||||
if (!vizMod && look.venue) {
|
||||
previewLink(cv, {
|
||||
song: entry.name,
|
||||
seed: seedBase,
|
||||
frame,
|
||||
venue: true,
|
||||
venueSel: venueName || look.venue.name,
|
||||
});
|
||||
}
|
||||
blit(cv, pix, THUMB.width, THUMB.height);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user