diff --git a/src/control.js b/src/control.js
index 133fabb..9f3d4e6 100644
--- a/src/control.js
+++ b/src/control.js
@@ -353,6 +353,30 @@ function defaultCue(time) {
return { time, effectIndex: null, effectName: '', text: '', textStyle: null, image: null, imageSize: 60, imagePos: 'center' };
}
+// Build the list of effect families for a cue's effect picker.
+function cueFamilyOptions(selFam) {
+ let s = ' — nessun effetto — ';
+ EFFECTS.families.forEach((name, i) => {
+ s += '' + name + ' ';
+ });
+ return s;
+}
+// Build the list of presets within a family for a cue's effect picker.
+function cuePresetOptions(fam, selIdx) {
+ if (fam < 0) return ' — ';
+ let s = '';
+ for (let i = 0; i < EFFECTS.list.length; i++) {
+ if (EFFECTS.list[i].family !== fam) continue;
+ s += '' + EFFECTS.list[i].name + ' ';
+ }
+ return s;
+}
+// First preset index of a family (used when a family is chosen).
+function firstPresetOfFamily(fam) {
+ for (let i = 0; i < EFFECTS.list.length; i++) if (EFFECTS.list[i].family === fam) return i;
+ return -1;
+}
+
// Build the per-track scene editor (timeline of cues with text + image styling).
function buildSceneEditor(tr, i, li) {
const cues = tr.cues || (tr.cues = []);
@@ -364,14 +388,26 @@ function buildSceneEditor(tr, i, li) {
const posLabels = { center: 'Centro', top: 'Alto', bottom: 'Basso', left: 'Sinistra', right: 'Destra' };
let html = '
🎬 Scene a tempo — la cue @00:00 parte col brano, le altre scattano al loro tempo
';
cues.forEach((c, ci) => {
+ const ce = (c.effectIndex != null && EFFECTS.list[c.effectIndex]) ? EFFECTS.list[c.effectIndex] : null;
+ const cFam = ce ? ce.family : -1;
html +=
'' +
'
' +
'@ ' +
' ' +
- '🌀 ' + (c.effectName || 'imposta effetto') + ' ' +
(ci > 0 ? '✕ ' : 'base ') +
'
' +
+ '
' +
+ '
🌀 Effetto
' +
+ '
' +
+ '' + cueFamilyOptions(cFam) + ' ' +
+ '' + cuePresetOptions(cFam, c.effectIndex) + ' ' +
+ '
' +
+ '
' +
+ '🎯 Usa corrente ' +
+ '' + (ce ? ce.name : 'nessuno') + ' ' +
+ '
' +
+ '
' +
'
' +
'
🔤 Testo
' +
'
' +
@@ -404,7 +440,17 @@ function buildSceneEditor(tr, i, li) {
const c = cues[ci];
cueEl.querySelector('.cue-text').value = c.text || '';
cueEl.querySelector('.cue-time').addEventListener('change', (e) => { c.time = parseTime(e.target.value); savePlaylistState(); renderPlaylist(); });
- cueEl.querySelector('.cue-eff').addEventListener('click', () => { c.effectIndex = EFFECTS.list.indexOf(currentEffect); c.effectName = currentEffect.name; savePlaylistState(); renderPlaylist(); });
+ cueEl.querySelector('.cue-eff-fam').addEventListener('change', (e) => {
+ const fam = parseInt(e.target.value, 10);
+ if (fam < 0) { c.effectIndex = null; c.effectName = ''; }
+ else { const idx = firstPresetOfFamily(fam); c.effectIndex = idx; c.effectName = idx >= 0 ? EFFECTS.list[idx].name : ''; }
+ savePlaylistState(); renderPlaylist();
+ });
+ cueEl.querySelector('.cue-eff-pre').addEventListener('change', (e) => {
+ const idx = parseInt(e.target.value, 10);
+ if (idx >= 0 && EFFECTS.list[idx]) { c.effectIndex = idx; c.effectName = EFFECTS.list[idx].name; savePlaylistState(); renderPlaylist(); }
+ });
+ cueEl.querySelector('.cue-eff-cur').addEventListener('click', () => { c.effectIndex = EFFECTS.list.indexOf(currentEffect); c.effectName = currentEffect.name; savePlaylistState(); renderPlaylist(); });
cueEl.querySelector('.cue-text').addEventListener('input', (e) => { c.text = e.target.value; li.querySelector('.scene-btn').classList.toggle('active', hasScene(tr)); savePlaylistState(); });
cueEl.querySelector('.cue-tstyle').addEventListener('click', () => { c.textStyle = captureTextStyle(); savePlaylistState(); renderPlaylist(); });
const tsc = cueEl.querySelector('.cue-tstyle-clear');
diff --git a/src/output.js b/src/output.js
index bf7a714..b321af9 100644
--- a/src/output.js
+++ b/src/output.js
@@ -332,10 +332,14 @@ djv.onControl(async (m) => {
case 'playVideoTrack':
try {
audio.onEnded = () => djv.report({ type: 'trackEnded' });
- trackVideo.loop = false; trackVideo.muted = false;
- trackVideo.src = toFileURL(m.path);
- trackVideo.classList.add('show');
audio.attachVideo(trackVideo);
+ trackVideo.loop = false; trackVideo.muted = false;
+ // Force a fresh start even when re-selecting the same file:
+ // reassigning the same src may not reload, so pause + load() resets it.
+ trackVideo.pause();
+ trackVideo.src = toFileURL(m.path);
+ trackVideo.load();
+ trackVideo.classList.add('show');
await trackVideo.play();
hideHint();
djv.report({ type: 'playState', playing: true });
diff --git a/src/style.css b/src/style.css
index 1cfe070..df30f14 100644
--- a/src/style.css
+++ b/src/style.css
@@ -372,8 +372,10 @@ textarea.textarea-lg {
.scene-editor .cue-head { display: flex; align-items: center; gap: 6px; }
.scene-editor .cue-at { opacity: 0.5; flex: 0 0 auto; }
.scene-editor .cue-time { width: 60px; flex: 0 0 auto; text-align: center; padding: 6px 4px; font-variant-numeric: tabular-nums; }
-.scene-editor .cue-eff { flex: 1; min-width: 0; text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.scene-editor .cue-del { flex: 0 0 auto; color: #ff9a9a; }
+.scene-editor .cue-del { flex: 0 0 auto; color: #ff9a9a; margin-left: auto; }
+.scene-editor .cue-eff-fam, .scene-editor .cue-eff-pre { flex: 1; min-width: 0; }
+.scene-editor .cue-eff-cur { flex: 0 0 auto; }
+.scene-editor .cue-eff-name { flex: 1; min-width: 0; font-size: 10px; opacity: 0.72; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.scene-editor .cue-base { flex: 0 0 auto; font-size: 9px; opacity: 0.45; padding: 0 4px; }
.scene-editor .cue-block { background: rgba(255,255,255,0.035); border-radius: 8px; padding: 7px 8px; margin-top: 7px; }