Director cuts between palettes; the look panel shows which one is live
One palette for a whole song reads as one colour once five minutes have passed. The track now keeps 2-4 audio-tilted palettes (forced scheme/hue diversity so switches are legible) and the director owns how they are traversed per cue — sequential/pong/kindLed/storyLed/ contrast — with cut or OKLCH blend timed to the cue crossfade. The fine OKLCH paletteArc drift still rides on top of whichever base is active. The look tab renders the full set as a list; the row matching ArcDriver.paletteIndexAt(frame) gets an active border/glow and follows playback/scrub live, with a blend badge while two palettes interpolate. Co-Authored-By: internal-model
This commit is contained in:
co-authored by
internal-model
parent
df609424d5
commit
ef1d24e692
+58
-3
@@ -343,6 +343,14 @@ function renderPanel() {
|
||||
// The track's production design. Scenes that cannot express what it is
|
||||
// built on were never cast — see look/Personality.js.
|
||||
const personality = show.look.personality;
|
||||
const palettes = show.look.palettes || [show.look.palette];
|
||||
const schemes = show.look.paletteSchemes || [show.look.paletteScheme];
|
||||
const plan = show.look.palettePlan;
|
||||
const activeIdx = show.arc ? show.arc.paletteIndexAt(show.timeline.frame) : 0;
|
||||
const blend = show.arc ? show.arc.paletteBlendAt(show.timeline.frame) : null;
|
||||
const planLabel = plan
|
||||
? `${plan.progression}/${plan.transition} · ${palettes.length} palettes`
|
||||
: `${schemes[0] || ''}`;
|
||||
dom.panelBody.innerHTML = `
|
||||
<div class="pp-heading"><span class="pp-name">${show.fileName || 'track'}</span></div>
|
||||
<div class="kv"><span>seed</span><b>${show.look.seed.toString(16)}</b></div>
|
||||
@@ -350,7 +358,7 @@ function renderPanel() {
|
||||
<div class="kv"><span>tempo conf.</span><b>${show.track.tempo.confidence.toFixed(2)}</b></div>
|
||||
<div class="kv"><span>duration</span><b>${formatTime(show.duration)}</b></div>
|
||||
<div class="kv"><span>sections</span><b>${show.track.sections.length}</b></div>
|
||||
<div class="kv"><span>scheme</span><b>${show.look.paletteScheme}</b></div>
|
||||
<div class="kv"><span>director</span><b>${show.look.director}${plan ? ` · ${plan.progression}` : ''}</b></div>
|
||||
<div class="kv"><span>built on</span><b>${personality.signature.join(' + ') || 'nothing'}</b></div>
|
||||
<div class="kv"><span>form</span><b>${personality.shape.sides || 'round'}${
|
||||
personality.shape.sides ? '-sided' : ''}</b></div>
|
||||
@@ -360,8 +368,22 @@ function renderPanel() {
|
||||
? personality.style.symmetry + '-fold · ' : ''}line ${personality.style.lineWeight.toFixed(2)}</b></div>
|
||||
<div class="kv"><span>brightness</span><b>${summary.meanCentroid.toFixed(3)}</b></div>
|
||||
<div class="kv"><span>dynamics</span><b>${summary.dynamicRange.toFixed(3)}</b></div>
|
||||
<div class="swatches">${show.look.palette.map((c) =>
|
||||
`<span class="sw" style="background:${toHex(c)}" title="${toHex(c)}"></span>`).join('')}</div>
|
||||
<div class="pp-sub">palettes · ${planLabel}</div>
|
||||
<div id="palette-list">${palettes.map((pal, i) => {
|
||||
const sc = schemes[i] || schemes[0] || '';
|
||||
const isActive = i === activeIdx;
|
||||
const isBlendSrc = blend && (i === blend.from || i === blend.to);
|
||||
const cls = isActive ? 'active' : (isBlendSrc ? 'blending' : '');
|
||||
const tag = isActive
|
||||
? (blend ? `● blend ${(blend.t * 100).toFixed(0)}%` : '● active')
|
||||
: (isBlendSrc ? '○ blend' : '');
|
||||
return `<div class="pal-row ${cls}" data-pal="${i}">
|
||||
<div class="pal-meta"><span class="pal-idx">#${i + 1}</span><span class="pal-scheme">${sc}</span><span class="pal-tag">${tag}</span></div>
|
||||
<div class="swatches pal-swatches">${pal.map((c) =>
|
||||
`<span class="sw" style="background:${toHex(c)}" title="${toHex(c)}"></span>`).join('')}</div>
|
||||
</div>`;
|
||||
}).join('')}</div>
|
||||
<div id="palette-blend-hint" class="hint"${blend ? '' : ' hidden'}>${blend ? `blending #${blend.from + 1} → #${blend.to + 1} · ${(blend.t * 100).toFixed(0)}% through cut` : ''}</div>
|
||||
<div class="pp-sub">sections</div>
|
||||
${show.look.sections.map((s, i) => `
|
||||
<div class="kv ${i === index ? 'current' : ''}">
|
||||
@@ -631,6 +653,37 @@ window.addEventListener('resize', resize);
|
||||
let lastPanelSection = -1;
|
||||
let lastRenderedFrame = -1;
|
||||
|
||||
function syncPalettesLive() {
|
||||
if (state.tab !== 'look' || !state.show.ready || !state.show.arc) return;
|
||||
const palettes = state.show.look.palettes;
|
||||
if (!palettes || palettes.length <= 1) return;
|
||||
const list = document.getElementById('palette-list');
|
||||
if (!list) return;
|
||||
const frame = state.show.timeline.frame;
|
||||
const active = state.show.arc.paletteIndexAt(frame);
|
||||
const blend = state.show.arc.paletteBlendAt(frame);
|
||||
for (const row of list.querySelectorAll('.pal-row')) {
|
||||
const idx = Number(row.dataset.pal);
|
||||
const isActive = idx === active;
|
||||
const isBlendSrc = !!blend && (idx === blend.from || idx === blend.to) && !isActive;
|
||||
row.classList.toggle('active', isActive);
|
||||
row.classList.toggle('blending', isBlendSrc);
|
||||
const tag = row.querySelector('.pal-tag');
|
||||
if (tag) {
|
||||
if (isActive) tag.textContent = blend ? `● blend ${(blend.t * 100).toFixed(0)}%` : '● active';
|
||||
else if (isBlendSrc) tag.textContent = '○ blend';
|
||||
else tag.textContent = '';
|
||||
}
|
||||
}
|
||||
const hint = document.getElementById('palette-blend-hint');
|
||||
if (hint) {
|
||||
if (blend) {
|
||||
hint.hidden = false;
|
||||
hint.textContent = `blending #${blend.from + 1} → #${blend.to + 1} · ${(blend.t * 100).toFixed(0)}% through cut`;
|
||||
} else hint.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
function frame(now) {
|
||||
requestAnimationFrame(frame);
|
||||
const show = state.show;
|
||||
@@ -672,6 +725,8 @@ function frame(now) {
|
||||
if (state.tab === 'scene' || state.tab === 'look') renderPanel();
|
||||
}
|
||||
|
||||
syncPalettesLive();
|
||||
|
||||
if (state.hudVisible) {
|
||||
const f = show.track.at(show.timeline.frame);
|
||||
dom.hud.innerHTML =
|
||||
|
||||
Reference in New Issue
Block a user