commit 2defc6e3ca49020ec045ce6c0ee4bd7b13a2b433
Author: jens <jens.se@icloud.com>
AuthorDate: Wed Apr 22 12:58:10 2026 +0200
Commit: jens <jens.se@icloud.com>
CommitDate: Wed Apr 22 12:58:10 2026 +0200
update
---
index.html | 72 ++++++++++++++++++++++++++++++++++++++------------------------
1 file changed, 44 insertions(+), 28 deletions(-)
diff --git a/index.html b/index.html
index 179cf74..98357e4 100644
--- a/index.html
+++ b/index.html
@@ -101,11 +101,11 @@
.preview-area { flex: 1; display: flex; gap: 0; overflow: hidden; }
.video-panel {
- flex: 1; display: flex; flex-direction: column; align-items: center;
- justify-content: center; padding: 24px; background: #050508; position: relative;
+ flex: 1; min-height: 0; display: flex; flex-direction: column; align-items: center;
+ justify-content: center; padding: 24px; background: #050508; position: relative; overflow: hidden;
}
- .video-wrapper { position: relative; max-width: 100%; max-height: 100%; }
- video { max-width: 100%; max-height: calc(100vh - 200px); border-radius: var(--radius); display: block; }
+ .video-wrapper { position: relative; max-width: 100%; max-height: 100%; display: flex; align-items: center; justify-content: center; }
+ video { max-width: 100%; max-height: 100%; border-radius: var(--radius); display: block; }
.video-placeholder {
width: 300px; height: 500px; border-radius: var(--radius);
background: var(--surface2); border: 2px dashed var(--border);
@@ -266,6 +266,13 @@
.crop-export-btn { margin-top: 12px; }
.gif-search-section { margin-top: 10px; }
+ /* Panel tabs */
+ .panel-tabs { display:flex; gap:2px; }
+ .panel-tab { background:none; border:none; border-bottom:2px solid transparent; color:var(--muted); cursor:pointer; font-size:12px; font-weight:600; padding:4px 10px; transition:all 0.15s; white-space:nowrap; }
+ .panel-tab:hover { color:var(--text); }
+ .panel-tab.active { color:var(--text); border-bottom-color:var(--accent); }
+ #reaction-panel { flex:1; overflow-y:auto; padding:8px 10px; flex-direction:column; gap:6px; }
+
/* Reaction editor panel */
.re-item { display:grid; grid-template-columns:auto 1fr 1fr auto; gap:6px; align-items:center; background:var(--surface2); border-radius:8px; padding:7px 10px; font-size:12px; }
.re-item-emoji { background:rgba(254,232,0,.1); border-color:rgba(254,232,0,.3); }
@@ -675,10 +682,13 @@
</div>
</div>
- <!-- Segments -->
+ <!-- Segments / Reactions panel -->
<div class="segments-panel">
<div class="segments-header">
- <h3>Transcript <span id="lang-badge"></span></h3>
+ <div class="panel-tabs">
+ <button type="button" class="panel-tab active" id="tab-transcript" onclick="switchPanelTab('transcript')">Transcript <span id="lang-badge"></span></button>
+ <button type="button" class="panel-tab" id="tab-reactions" onclick="switchPanelTab('reactions')" style="display:none">Emoji & GIF</button>
+ </div>
<div style="display:flex;align-items:center;gap:8px;">
<span class="seg-count" id="seg-count">0 segments</span>
<button type="button" class="edit-toggle-btn" id="edit-mode-btn" onclick="toggleEditMode()">Edit</button>
@@ -689,6 +699,13 @@
Transcribe a video or import subtitles
</div>
</div>
+ <div id="reaction-panel" style="display:none;flex:1;overflow-y:auto;padding:8px 10px;display:none;flex-direction:column;gap:6px">
+ <div style="display:flex;gap:6px;padding-bottom:6px;border-bottom:1px solid var(--border);margin-bottom:4px">
+ <button type="button" class="tl-btn" onclick="addEmojiItemManual()">+ Emoji</button>
+ <button type="button" class="tl-btn" onclick="addGifItemManual()">+ GIF</button>
+ </div>
+ <div id="reaction-list"></div>
+ </div>
</div>
</div>
@@ -706,18 +723,6 @@
<canvas id="timeline-canvas"></canvas>
</div>
- <!-- Emoji / GIF item editor (shown in edit mode) -->
- <div id="reaction-editor" style="display:none;border-top:1px solid var(--border);background:var(--surface);max-height:220px;overflow-y:auto">
- <div style="display:flex;align-items:center;justify-content:space-between;padding:6px 14px;border-bottom:1px solid var(--border);position:sticky;top:0;background:var(--surface);z-index:1">
- <span style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:1px">Emoji & GIF — tidslinje</span>
- <div style="display:flex;gap:6px">
- <button type="button" class="tl-btn" onclick="addEmojiItemManual()" title="Lägg till emoji-block">+ Emoji</button>
- <button type="button" class="tl-btn" onclick="addGifItemManual()" title="Lägg till GIF-block (välj GIF i söken först)">+ GIF</button>
- </div>
- </div>
- <div id="reaction-list" style="padding:6px 10px;display:flex;flex-direction:column;gap:6px"></div>
- </div>
-
<!-- Export bar -->
<div class="export-bar" id="export-bar">
<span style="font-size:12px;color:var(--muted);flex:1">Export</span>
@@ -984,7 +989,7 @@ function toggleEditMode() {
btn.textContent = editMode ? 'Done' : 'Edit';
const bar = document.getElementById('timeline-bar');
bar.style.display = editMode ? 'flex' : 'none';
- document.getElementById('reaction-editor').style.display = editMode ? 'block' : 'none';
+ document.getElementById('tab-reactions').style.display = editMode ? '' : 'none';
if (editMode) {
requestAnimationFrame(() => initTimeline());
const vid = document.getElementById('video-player');
@@ -992,10 +997,20 @@ function toggleEditMode() {
renderReactionList();
} else {
stopTlRaf();
+ switchPanelTab('transcript');
}
renderSegmentsList();
}
+function switchPanelTab(tab) {
+ const isTranscript = tab === 'transcript';
+ document.getElementById('segments-list').style.display = isTranscript ? '' : 'none';
+ document.getElementById('reaction-panel').style.display = isTranscript ? 'none' : 'flex';
+ document.getElementById('tab-transcript').classList.toggle('active', isTranscript);
+ document.getElementById('tab-reactions').classList.toggle('active', !isTranscript);
+ if (!isTranscript) renderReactionList();
+}
+
function parseFmt(str) {
const parts = str.trim().split(':');
if (parts.length !== 2) return NaN;
@@ -1235,14 +1250,15 @@ function renderSubtitles(time) {
const emojiSize2 = Math.round(emojiPx * scaleFactor * (canvas.width / 1080));
const elapsed2 = Math.max(0, time - item.start);
const t2 = Math.min(1, elapsed2 / 0.4);
- const effect2 = document.getElementById('emoji-effect').value;
+ const effect2 = item.effect ?? document.getElementById('emoji-effect').value;
let dy2 = 0, alpha2 = 1, sc2 = 1;
if (effect2 === 'fadein') alpha2 = t2;
else if (effect2 === 'bounceup') { dy2 = t2 < 1 ? (1 - (t2 < 0.6 ? t2/0.6 : 1 + Math.sin((t2-0.6)/0.4*Math.PI)*0.25)) * emojiSize2 * 1.5 : 0; alpha2 = Math.min(1, t2*2); }
else if (effect2 === 'zoomin') { sc2 = 0.2 + 0.8*t2; alpha2 = t2; }
else if (effect2 === 'shake') { dy2 = t2 < 1 ? Math.sin(t2 * Math.PI * 5) * emojiSize2 * 0.3 * (1-t2) : 0; }
- const ey2 = canvas.height * s.position - emojiSize2;
- const cx2 = canvas.width * s.positionX;
+ // Use item's own position, not the subtitle position
+ const ey2 = canvas.height * ((item.posY ?? 30) / 100);
+ const cx2 = canvas.width * ((item.posX ?? 50) / 100);
const totalW2 = item.emojis.length * (emojiSize2 * 1.2);
let ex2 = cx2 - totalW2 / 2 + emojiSize2 * 0.6;
ctx.save();
@@ -1825,23 +1841,23 @@ function renderReactionList() {
const posX = item.posX ?? 50;
const posY = item.posY ?? 30;
const extra = type === 'gif'
- ? `<div class="re-field"><label>Storlek %</label><input type="number" min="10" max="100" value="${item.size??50}" onchange="updateReItem('${type}',${item.id},'size',+this.value);"></div>
- <div class="re-field"><label>Opacitet %</label><input type="number" min="10" max="100" value="${item.opacity??90}" onchange="updateReItem('${type}',${item.id},'opacity',+this.value);"></div>`
+ ? `<div class="re-field"><label>Storlek %</label><input type="number" min="10" max="100" value="${item.size??50}" oninput="updateReItem('${type}',${item.id},'size',+this.value)"></div>
+ <div class="re-field"><label>Opacitet %</label><input type="number" min="10" max="100" value="${item.opacity??90}" oninput="updateReItem('${type}',${item.id},'opacity',+this.value)"></div>`
: `<button type="button" class="tl-btn" style="font-size:11px" onclick="openEmojiPickerForItem(${item.id},event.clientX,event.clientY)">Välj emojis</button>`;
return `<div class="re-item re-item-${type}">
<span class="re-badge">${badge}</span>
<div class="re-field">
<label>Start</label>
- <input type="number" step="0.1" value="${item.start.toFixed(1)}" onchange="updateReItem('${type}',${item.id},'start',+this.value);" style="width:60px">
+ <input type="number" step="0.1" value="${item.start.toFixed(1)}" oninput="updateReItem('${type}',${item.id},'start',+this.value)" style="width:60px">
</div>
<div class="re-field">
<label>Slut</label>
- <input type="number" step="0.1" value="${item.end.toFixed(1)}" onchange="updateReItem('${type}',${item.id},'end',+this.value);" style="width:60px">
+ <input type="number" step="0.1" value="${item.end.toFixed(1)}" oninput="updateReItem('${type}',${item.id},'end',+this.value)" style="width:60px">
</div>
<button type="button" class="re-del" onclick="removeReItem('${type}',${item.id})">×</button>
<div class="re-row2">
- <div class="re-field"><label>Pos X %</label><input type="number" min="0" max="100" value="${posX}" onchange="updateReItem('${type}',${item.id},'posX',+this.value);" style="width:52px"></div>
- <div class="re-field"><label>Pos Y %</label><input type="number" min="0" max="100" value="${posY}" onchange="updateReItem('${type}',${item.id},'posY',+this.value);" style="width:52px"></div>
+ <div class="re-field"><label>Pos X %</label><input type="number" min="0" max="100" value="${posX}" oninput="updateReItem('${type}',${item.id},'posX',+this.value)" style="width:52px"></div>
+ <div class="re-field"><label>Pos Y %</label><input type="number" min="0" max="100" value="${posY}" oninput="updateReItem('${type}',${item.id},'posY',+this.value)" style="width:52px"></div>
<div class="re-field"><label>Effekt</label><select onchange="updateReItem('${type}',${item.id},'effect',this.value)">${effOpts}</select></div>
${extra}
</div>