Elias207 commited on
Commit
9cfc4d8
·
verified ·
1 Parent(s): cf8cfdb

Update static/js/editor.js

Browse files
Files changed (1) hide show
  1. static/js/editor.js +206 -317
static/js/editor.js CHANGED
@@ -1,45 +1,25 @@
1
- // منطق اصلی ادیتور (نمایش ویدیو، سینک متن، ابزارها)
2
-
3
- // --- تابع آپدیت گرافیکی نوار اسلایدر ---
4
  function updateRange(input, labelId) {
5
  const label = document.getElementById(labelId);
6
- if (label) {
7
- label.innerText = input.value + '%';
8
- }
9
- const min = parseFloat(input.min);
10
- const max = parseFloat(input.max);
11
- const val = parseFloat(input.value);
12
  const percentage = ((val - min) / (max - min)) * 100;
13
  input.style.backgroundSize = percentage + '% 100%';
14
  }
15
 
16
- // --- هماهنگ‌سازی اسلایدرها با وضعیت (برای تاچ) ---
17
  function syncUIWithState() {
18
- // 1. سایز: تبدیل پیکسل به درصد (با فرض ماکسیمم 150 پیکسل)
19
- // فرمول معکوس: Percent = ((Pixel - 10) / 140) * 100
20
  let fzPercent = ((state.st.fz - 10) / 140) * 100;
21
  const fzInput = document.getElementById('fz');
22
- if(fzInput) {
23
- fzInput.value = Math.round(Math.max(0, Math.min(100, fzPercent)));
24
- updateRange(fzInput, 'lbl-size');
25
- }
26
 
27
- // 2. موقعیت Y
28
  let yPercent = (state.st.y / 1200) * 100;
29
  const posInput = document.getElementById('pos');
30
- if(posInput) {
31
- posInput.value = Math.round(Math.max(0, Math.min(100, yPercent)));
32
- updateRange(posInput, 'lbl-y');
33
- }
34
 
35
- // 3. موقعیت X
36
  let currentX = state.st.x || 0;
37
  let xPercent = (currentX / 500) * 100;
38
  const posXInput = document.getElementById('posX');
39
- if(posXInput) {
40
- posXInput.value = Math.round(Math.max(-100, Math.min(100, xPercent)));
41
- updateRange(posXInput, 'lbl-x');
42
- }
43
  }
44
 
45
  function openProject(projectId) {
@@ -51,60 +31,45 @@ function openProject(projectId) {
51
  req.onsuccess = (e) => {
52
  const p = e.target.result;
53
  if (!p) { alert('پروژه یافت نشد'); loadHome(); return; }
54
-
55
  state = p.state;
56
  document.getElementById('currentProjectTitle').innerText = p.name;
57
  const videoURL = URL.createObjectURL(p.videoBlob);
58
  v.src = videoURL;
59
 
60
- document.getElementById('col').value = state.st.col;
61
- document.getElementById('bgCol').value = state.st.bg;
62
-
63
- // تنظیم اولیه اسلایدرها
64
- syncUIWithState();
65
 
66
  document.querySelectorAll('.font-btn').forEach(btn => btn.classList.remove('ticked'));
67
  if(state.st.f === 'vazir') document.querySelectorAll('.font-btn')[1].classList.add('ticked');
68
  else if(state.st.f === 'lalezar') document.querySelectorAll('.font-btn')[0].classList.add('ticked');
69
 
 
70
  document.querySelectorAll('.style-card').forEach(c => c.classList.remove('selected'));
71
  if(state.st.name === 'karaoke_static') {
72
- const staticCard = Array.from(document.querySelectorAll('.style-card')).find(c => c.textContent.includes("هوشمند (ثابت)"));
73
  if(staticCard) staticCard.classList.add('selected');
74
- document.getElementById('staticColorPicker').value = state.st.col;
 
 
 
 
75
  }
76
 
77
  document.getElementById('homeScreen').style.display = 'none';
78
  document.getElementById('editorScreen').style.display = 'flex';
79
-
80
  renderSegList();
81
  fit();
82
  upd();
83
-
84
- v.onloadeddata = () => {
85
- fit();
86
- if(!p.thumbnail && v.duration > 1) {
87
- v.currentTime = 1.0;
88
- let captured = false;
89
- v.addEventListener('seeked', function cap() {
90
- if(captured) return; captured = true; saveProjectToDB(); v.currentTime = 0;
91
- }, {once:true});
92
- } else if (!p.thumbnail) { saveProjectToDB(); }
93
- };
94
  };
95
  }
96
 
97
  function goHome() { v.pause(); saveProjectToDB(); loadHome(); }
98
-
99
  function fit() {
100
  if(!state.w) return;
101
  const ws = document.getElementById('workspace');
102
- const availableHeight = window.innerHeight * 0.6;
103
- const scale = Math.min((ws.clientWidth - 40) / state.w, availableHeight / state.h);
104
- const c = document.getElementById('videoContainer');
105
- c.style.width = state.w + 'px'; c.style.height = state.h + 'px';
106
- document.getElementById('scaler').style.transform = `scale(${scale})`;
107
- ws.style.height = (state.h * scale + 40) + 'px';
108
  }
109
 
110
  function activateCustomStyle() {
@@ -116,264 +81,207 @@ function activateCustomStyle() {
116
  }
117
  }
118
 
119
- function renderSegList() {
120
- saveProjectToDB();
121
- const timeline = document.getElementById('timelineScroll');
122
- const spacers = timeline.querySelectorAll('.spacer');
123
- timeline.innerHTML = '';
124
- timeline.appendChild(spacers[0]);
125
-
126
- state.segs.forEach((seg, sIdx) => {
127
- if (!seg.words || seg.words.length === 0) {
128
- const wordsArr = seg.text.trim().split(/\s+/).filter(w => w.length > 0);
129
- const duration = seg.end - seg.start;
130
- const timePerWord = duration / Math.max(1, wordsArr.length);
131
- let wStart = seg.start;
132
- seg.words = wordsArr.map((wStr, i) => {
133
- let wEnd = wStart + timePerWord;
134
- if (i === wordsArr.length - 1) wEnd = seg.end;
135
- let obj = { word: wStr, start: parseFloat(wStart.toFixed(2)), end: parseFloat(wEnd.toFixed(2)) };
136
- wStart = wEnd;
137
- return obj;
138
- });
139
- }
140
-
141
- seg.words.forEach((w, wIdx) => {
142
- const el = document.createElement('div');
143
- el.className = 'word-chip';
144
- el.innerText = w.word;
145
- const uid = `${sIdx}-${wIdx}`;
146
- el.id = `w-${uid}`;
147
-
148
- if (activeWordId === uid) el.classList.add('active');
149
-
150
- el.onclick = (e) => {
151
- e.stopPropagation();
152
- highlightWord(sIdx, wIdx, true);
153
- };
154
- timeline.appendChild(el);
155
- });
156
 
157
- if (sIdx < state.segs.length - 1) {
158
- const nl = document.createElement('div');
159
- nl.className = 'newline-indicator';
160
- nl.innerHTML = '<i class="fa-solid fa-arrow-turn-down" style="transform: rotate(90deg) scaleX(-1);"></i>';
161
- timeline.appendChild(nl);
162
- }
163
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
 
165
- timeline.appendChild(spacers[1]);
166
- updateSplitButton();
 
167
  }
168
 
169
- function highlightWord(sIdx, wIdx, showToolbar) {
170
- activeWordId = `${sIdx}-${wIdx}`;
171
- v.pause();
172
- togglePlayIcon(false);
173
- const seg = state.segs[sIdx];
174
- const word = seg.words[wIdx];
175
-
176
- v.currentTime = word.start;
177
- manualOverride = true;
178
-
179
- updateOverlayContent(v.currentTime);
180
 
181
- document.querySelectorAll('.word-chip').forEach(c => c.classList.remove('active'));
182
- const el = document.getElementById(`w-${activeWordId}`);
183
- if(el) {
184
- el.classList.add('active');
185
- el.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' });
186
  }
187
 
188
- if(showToolbar) document.getElementById('toolbar').classList.add('show');
189
- updateSplitButton();
190
  }
191
 
192
- function playPreviewWord() {
193
- if (!activeWordId) return;
194
- const [sIdx, wIdx] = activeWordId.split('-').map(Number);
195
- const word = state.segs[sIdx].words[wIdx];
196
-
197
- if(previewInterval) clearInterval(previewInterval);
198
- v.pause();
199
- v.currentTime = word.start;
200
-
201
- const icon = document.getElementById('btnPreviewPlay').querySelector('i');
202
- icon.className = "fa-solid fa-pause";
203
- v.play();
204
-
205
- previewInterval = setInterval(() => {
206
- if(v.currentTime >= word.end) {
207
- v.pause();
208
- clearInterval(previewInterval);
209
- icon.className = "fa-solid fa-play";
210
- if (v.currentTime >= word.end) v.currentTime = word.end - 0.01;
211
- }
212
- }, 20);
213
  }
214
 
215
- function saveText() {
216
- if (!activeWordId) return;
217
- const [sIdx, wIdx] = activeWordId.split('-').map(Number);
218
- const val = document.getElementById('textInput').value.trim();
219
- if (val) {
220
- state.segs[sIdx].words[wIdx].word = val;
221
- state.segs[sIdx].text = state.segs[sIdx].words.map(w => w.word).join(' ');
222
- renderSegList();
223
- highlightWord(sIdx, wIdx, true);
224
- }
225
- closeAllSheets();
226
  }
227
-
228
- function adjustTime(type, am) {
229
- if(type === 'start') {
230
- let v = parseFloat((tempStartTime + am).toFixed(2));
231
- if(v < 0) v = 0;
232
- if(v >= tempEndTime) v = parseFloat((tempEndTime - 0.1).toFixed(2));
233
- tempStartTime = v;
234
- document.getElementById('valStart').innerText = fmt(tempStartTime);
235
- } else {
236
- let v = parseFloat((tempEndTime + am).toFixed(2));
237
- if(v <= tempStartTime) v = parseFloat((tempStartTime + 0.1).toFixed(2));
238
- tempEndTime = v;
239
- document.getElementById('valEnd').innerText = fmt(tempEndTime);
 
 
240
  }
241
  }
242
-
243
- function confirmTimeChanges() {
244
- if (!activeWordId) return;
245
- const [sIdx, wIdx] = activeWordId.split('-').map(Number);
246
- const seg = state.segs[sIdx];
247
- seg.words[wIdx].start = tempStartTime;
248
- seg.words[wIdx].end = tempEndTime;
249
- if (wIdx === 0 && tempStartTime < seg.start) seg.start = tempStartTime;
250
- if (wIdx === seg.words.length - 1 && tempEndTime > seg.end) seg.end = tempEndTime;
251
- closeAllSheets();
252
- renderSegList();
253
- highlightWord(sIdx, wIdx, true);
254
  }
255
- function cancelTimeChanges() { closeAllSheets(); }
256
-
257
- function confirmDelete() {
258
- if (!activeWordId) return;
259
- const [sIdx, wIdx] = activeWordId.split('-').map(Number);
260
- const seg = state.segs[sIdx];
261
-
262
- seg.words.splice(wIdx, 1);
263
- if (seg.words.length === 0) {
264
- state.segs.splice(sIdx, 1);
265
- activeWordId = null;
266
- document.getElementById('toolbar').classList.remove('show');
267
- } else {
268
- seg.text = seg.words.map(w => w.word).join(' ');
269
- activeWordId = null;
270
- document.getElementById('toolbar').classList.remove('show');
271
- }
272
-
273
- closeAllSheets();
274
- renderSegList();
275
  }
276
 
277
- function toggleSplit() {
278
- if (!activeWordId) return;
279
- const [sIdx, wIdx] = activeWordId.split('-').map(Number);
 
280
 
281
- if (wIdx === 0) {
282
- if (sIdx > 0) {
283
- const prevSeg = state.segs[sIdx - 1];
284
- const currSeg = state.segs[sIdx];
285
- prevSeg.words = prevSeg.words.concat(currSeg.words);
286
- prevSeg.end = currSeg.end;
287
- prevSeg.text = prevSeg.words.map(w => w.word).join(' ');
288
- state.segs.splice(sIdx, 1);
289
- const newWIdx = prevSeg.words.length - currSeg.words.length;
290
- renderSegList();
291
- highlightWord(sIdx - 1, newWIdx, true);
292
- }
293
- } else {
294
- const seg = state.segs[sIdx];
295
- const wordsFirstHalf = seg.words.slice(0, wIdx);
296
- const wordsSecondHalf = seg.words.slice(wIdx);
297
- seg.words = wordsFirstHalf;
298
- seg.text = seg.words.map(w => w.word).join(' ');
299
- seg.end = wordsFirstHalf[wordsFirstHalf.length-1].end;
300
- const newSeg = {
301
- text: wordsSecondHalf.map(w => w.word).join(' '),
302
- start: wordsSecondHalf[0].start,
303
- end: wordsSecondHalf[wordsSecondHalf.length-1].end,
304
- words: wordsSecondHalf,
305
- isHidden: false
306
- };
307
- state.segs.splice(sIdx + 1, 0, newSeg);
308
- renderSegList();
309
- highlightWord(sIdx + 1, 0, true);
310
  }
311
  }
312
 
313
- function updateSplitButton() {
314
- const btn = document.getElementById('btnSplit');
315
- if(!activeWordId) { btn.classList.remove('active-state'); return; }
316
- const [sIdx, wIdx] = activeWordId.split('-').map(Number);
317
- if (wIdx === 0 && sIdx > 0) {
318
- btn.classList.add('active-state');
319
- btn.style.transform = "rotate(180deg)";
320
- } else {
321
- btn.classList.remove('active-state');
322
- btn.style.transform = "";
323
- }
324
  }
325
-
326
- function togglePlay() { if(v.paused) { v.play(); togglePlayIcon(true); } else { v.pause(); togglePlayIcon(false); } }
327
- function togglePlayIcon(isPlaying) { const overlay = document.getElementById('playOverlay'); overlay.className = isPlaying ? 'playing' : ''; }
328
-
329
- function updateOverlayContent(currentTime) {
330
- const idx = state.segs.findIndex(s => currentTime >= s.start && currentTime <= s.end);
331
- if(idx !== -1) {
332
- const seg = state.segs[idx];
333
- if(seg.isHidden) { tEl.style.opacity = 0; }
334
- else {
335
- tEl.style.opacity = 1;
336
- if(state.st.name === 'auto_director' && seg.words) {
337
- let html = ""; seg.words.forEach((w, i) => {
338
- let isActive = (currentTime >= w.start && currentTime <= w.end);
339
- let boxColor = (i % 2 === 0) ? '#00D7FF' : '#FF0080';
340
- if(isActive) html += `<span style="background-color: ${boxColor}; color: #ffffff !important; box-shadow: 0 0 15px ${boxColor}; transform: scale(1.1); display:inline-block; border-radius: 6px; padding: 0 6px; text-shadow:none; font-family: inherit;">${w.word}</span> `;
341
- else html += `<span style="color: #ffffff !important; text-shadow:none; font-family: inherit;">${w.word}</span> `;
342
- }); tEl.innerHTML = html;
343
- }
344
- else if(state.st.name === 'karaoke_static' && seg.words) {
345
- let html = ""; seg.words.forEach(w => {
346
- let isActive = (currentTime >= w.start && currentTime <= w.end); let cls = isActive ? "word-active" : "";
347
- let styleAttr = ""; if(isActive) { let boxColor = state.st.col; styleAttr = `style="background-color: ${boxColor} !important; color: #fff !important; box-shadow: 0 2px 8px ${boxColor};"`; }
348
- html += `<span class="${cls}" ${styleAttr}>${w.word}</span> `;
349
- }); tEl.innerHTML = html;
350
- }
351
- else if (state.st.name === 'progressive_write' && seg.words) {
352
- let html = ""; seg.words.forEach(w => { if(currentTime >= w.start) html += `<span style="opacity:1">${w.word}</span> `; else html += `<span style="opacity:0">${w.word}</span> `; }); tEl.innerHTML = html.trim();
353
- }
354
- else { tEl.innerText = seg.text; }
355
- }
356
- } else { tEl.style.opacity = 0; }
 
 
 
 
 
357
  }
358
 
 
 
 
359
  function upd() {
360
  saveProjectToDB();
361
-
362
- // خواندن مقادیر از اسلایدرهای درصدی
363
- const uiFz = parseFloat(document.getElementById('fz').value); // 0 to 100
364
- const uiY = parseFloat(document.getElementById('pos').value); // 0 to 100
365
- const uiX = parseFloat(document.getElementById('posX').value); // -100 to 100
366
-
367
- // تبدیل ریاضی به پیکسل
368
- // فرمول جدید: ماکسیمم 150 پیکسل (نصف 300) برای جلوگیری از بزرگ شدن بیش از حد
369
  state.st.fz = Math.round(10 + (uiFz / 100) * 140);
370
-
371
  state.st.y = Math.round((uiY / 100) * 1200);
372
- state.st.x = Math.round((uiX / 100) * 500);
 
 
 
 
373
 
374
- if (state.st.name !== 'karaoke_static') state.st.col = document.getElementById('col').value;
375
- state.st.bg = document.getElementById('bgCol').value;
376
-
377
  let font = 'Vazirmatn';
378
  if(state.st.f === 'lalezar') font = 'Lalezar'; if(state.st.f === 'bangers') font = 'Impact'; if(state.st.f === 'roboto') font = 'Arial';
379
 
@@ -382,66 +290,47 @@ function upd() {
382
  tEl.style.bottom = state.st.y + 'px';
383
  tEl.style.textAlign = 'center';
384
  tEl.style.left = '50%';
385
-
386
- // اعمال جابجایی افقی (X)
387
  tEl.style.transform = `translateX(calc(-50% + ${state.st.x}px))`;
388
-
389
- tEl.style.paintOrder = 'normal'; tEl.style.webkitPaintOrder = 'normal'; tEl.style.borderRadius = '0px';
390
 
391
  if(state.st.name === 'karaoke_static' || state.st.name === 'auto_director') {
392
  tEl.style.backgroundColor = 'transparent'; tEl.style.color = '#FFFFFF'; tEl.style.webkitTextStroke = '0px'; tEl.style.textShadow = 'none';
393
  } else if (state.st.name === 'plain_white') {
394
  tEl.style.color = '#FFFFFF'; tEl.style.backgroundColor = 'transparent'; tEl.style.webkitTextStroke = '0px'; tEl.style.textShadow = 'none';
395
  } else if (state.st.name === 'white_outline') {
396
- tEl.style.color = '#FFFFFF'; tEl.style.backgroundColor = 'transparent'; const s = Math.max(3, state.st.fz / 4.5); tEl.style.webkitTextStroke = `${s}px #000000`; tEl.style.paintOrder = 'stroke fill'; tEl.style.webkitPaintOrder = 'stroke fill'; tEl.style.textShadow = 'none';
397
  } else {
398
  tEl.style.color = state.st.col;
399
  if(state.st.type === 'solid') { tEl.style.backgroundColor = state.st.bg; tEl.style.webkitTextStroke = '0px'; tEl.style.textShadow = 'none'; tEl.style.borderRadius = '12px'; }
400
  else if (state.st.type === 'transparent') {
401
- let c = state.st.bg.replace('#', ''); let r = parseInt(c.substring(0, 2), 16); let g = parseInt(c.substring(2, 4), 16); let b = parseInt(c.substring(4, 6), 16);
402
- tEl.style.backgroundColor = `rgba(${r},${g},${b},0.6)`; tEl.style.webkitTextStroke = '0px'; tEl.style.textShadow = 'none'; tEl.style.borderRadius = '12px';
 
 
 
 
403
  } else if (state.st.type === 'outline') {
404
- tEl.style.backgroundColor = 'transparent'; tEl.style.color = state.st.col; const s = Math.max(3, state.st.fz / 4.5); tEl.style.webkitTextStroke = `${s}px ${state.st.bg}`; tEl.style.paintOrder = 'stroke fill'; tEl.style.webkitPaintOrder = 'stroke fill'; tEl.style.textShadow = 'none';
405
  } else { tEl.style.backgroundColor = 'transparent'; tEl.style.webkitTextStroke = '0px'; tEl.style.textShadow = 'none'; }
406
  }
407
  }
408
 
409
- function openStaticColorPicker(event) { event.stopPropagation(); document.getElementById('staticColorPicker').click(); }
410
- function updateStaticColor(val) { document.documentElement.style.setProperty('--static-color', val); state.st.col = val; document.getElementById('col').value = val; upd(); }
411
-
412
  function setStylePreset(name, el, skipModeSet = false) {
413
  state.st.name = name;
414
  document.querySelectorAll('.style-card').forEach(c => c.classList.remove('selected'));
415
  if(el) el.classList.add('selected');
416
-
417
  if(name === 'karaoke_static' && !skipModeSet) {
418
- const defaultPurple = '#A020F0';
419
- state.st.col = defaultPurple;
420
- document.documentElement.style.setProperty('--static-color', defaultPurple);
421
- document.getElementById('staticColorPicker').value = defaultPurple;
422
- document.getElementById('col').value = defaultPurple;
423
  }
424
-
425
- if(name === 'classic' && !skipModeSet) {
426
- setMode('solid');
427
- } else if (name === 'progressive_write') {
428
- setMode('none');
429
- } else if (name === 'plain_white' || name === 'white_outline') {
430
- state.st.col = '#FFFFFF';
431
- state.st.bg = '#000000';
432
- document.getElementById('col').value = '#FFFFFF';
433
- document.getElementById('bgCol').value = '#000000';
434
- setMode('outline');
435
  }
436
  upd();
437
  }
438
 
439
- function handleClassicDoubleClick(element) {
440
- event.stopPropagation();
441
- activateCustomStyle();
442
- setStylePreset('classic', element, true);
443
- setMode('none');
444
- }
445
-
446
  function setFont(f, el) { document.querySelectorAll('.font-btn').forEach(btn => btn.classList.remove('ticked')); el.classList.add('ticked'); state.st.f = f; upd(); }
447
  function setMode(m) { state.st.type = m; syncModeButtons(); upd(); }
 
1
+ // --- تابع آپدیت گرافیکی نوار اسلایدر ظاهر ---
 
 
2
  function updateRange(input, labelId) {
3
  const label = document.getElementById(labelId);
4
+ if (label) label.innerText = input.value + '%';
5
+ const min = parseFloat(input.min), max = parseFloat(input.max), val = parseFloat(input.value);
 
 
 
 
6
  const percentage = ((val - min) / (max - min)) * 100;
7
  input.style.backgroundSize = percentage + '% 100%';
8
  }
9
 
 
10
  function syncUIWithState() {
 
 
11
  let fzPercent = ((state.st.fz - 10) / 140) * 100;
12
  const fzInput = document.getElementById('fz');
13
+ if(fzInput) { fzInput.value = Math.round(Math.max(0, Math.min(100, fzPercent))); updateRange(fzInput, 'lbl-size'); }
 
 
 
14
 
 
15
  let yPercent = (state.st.y / 1200) * 100;
16
  const posInput = document.getElementById('pos');
17
+ if(posInput) { posInput.value = Math.round(Math.max(0, Math.min(100, yPercent))); updateRange(posInput, 'lbl-y'); }
 
 
 
18
 
 
19
  let currentX = state.st.x || 0;
20
  let xPercent = (currentX / 500) * 100;
21
  const posXInput = document.getElementById('posX');
22
+ if(posXInput) { posXInput.value = Math.round(Math.max(-100, Math.min(100, xPercent))); updateRange(posXInput, 'lbl-x'); }
 
 
 
23
  }
24
 
25
  function openProject(projectId) {
 
31
  req.onsuccess = (e) => {
32
  const p = e.target.result;
33
  if (!p) { alert('پروژه یافت نشد'); loadHome(); return; }
 
34
  state = p.state;
35
  document.getElementById('currentProjectTitle').innerText = p.name;
36
  const videoURL = URL.createObjectURL(p.videoBlob);
37
  v.src = videoURL;
38
 
39
+ syncUIWithState(); // اسلایدرها
 
 
 
 
40
 
41
  document.querySelectorAll('.font-btn').forEach(btn => btn.classList.remove('ticked'));
42
  if(state.st.f === 'vazir') document.querySelectorAll('.font-btn')[1].classList.add('ticked');
43
  else if(state.st.f === 'lalezar') document.querySelectorAll('.font-btn')[0].classList.add('ticked');
44
 
45
+ // استایل کارت
46
  document.querySelectorAll('.style-card').forEach(c => c.classList.remove('selected'));
47
  if(state.st.name === 'karaoke_static') {
48
+ const staticCard = Array.from(document.querySelectorAll('.style-card')).find(c => c.innerHTML.includes("ثابت"));
49
  if(staticCard) staticCard.classList.add('selected');
50
+ } else if (state.st.name === 'classic' || state.st.name === 'progressive_write') {
51
+ // پیدا کردن کارت مناسب در پنل کاستوم (اینجا پیش‌فرض را کلاسیک در نظر می‌گیریم اگر تطبیقی نبود)
52
+ const cards = document.querySelectorAll('.custom-style-container .style-card');
53
+ if(state.st.name === 'classic' && cards[0]) cards[0].classList.add('selected');
54
+ if(state.st.name === 'progressive_write' && cards[1]) cards[1].classList.add('selected');
55
  }
56
 
57
  document.getElementById('homeScreen').style.display = 'none';
58
  document.getElementById('editorScreen').style.display = 'flex';
 
59
  renderSegList();
60
  fit();
61
  upd();
62
+ v.onloadeddata = () => { fit(); if(!p.thumbnail) saveProjectToDB(); };
 
 
 
 
 
 
 
 
 
 
63
  };
64
  }
65
 
66
  function goHome() { v.pause(); saveProjectToDB(); loadHome(); }
 
67
  function fit() {
68
  if(!state.w) return;
69
  const ws = document.getElementById('workspace');
70
+ const scale = Math.min((ws.clientWidth - 40) / state.w, (window.innerHeight * 0.6) / state.h);
71
+ const c = document.getElementById('videoContainer'); c.style.width = state.w + 'px'; c.style.height = state.h + 'px';
72
+ document.getElementById('scaler').style.transform = `scale(${scale})`; ws.style.height = (state.h * scale + 40) + 'px';
 
 
 
73
  }
74
 
75
  function activateCustomStyle() {
 
81
  }
82
  }
83
 
84
+ /* =========================================
85
+ COLOR PICKER LOGIC (IMPORTED & ADAPTED)
86
+ ========================================= */
87
+ let pickerState = { h: 0, s: 0, v: 100, a: 100 };
88
+ let currentTarget = null;
89
+ let savedColors = [];
90
+ let isDragging = false;
91
+ const spectrumArea = document.getElementById('spectrumArea');
92
+ const spectrumHandle = document.getElementById('spectrumHandle');
93
+
94
+ // تبدیل‌ها
95
+ function rgbToHex(r,g,b) {
96
+ return "#" + ((1<<24)+(r<<16)+(g<<8)+b).toString(16).slice(1).toUpperCase();
97
+ }
98
+ function hexToRgb(hex) {
99
+ hex = (hex||'').replace('#','').trim();
100
+ if(hex.length === 3) hex = hex.split('').map(ch=>ch+ch).join('');
101
+ if(hex.length!==6) return {r:255,g:255,b:255};
102
+ return { r: parseInt(hex.substring(0,2),16), g: parseInt(hex.substring(2,4),16), b: parseInt(hex.substring(4,6),16) };
103
+ }
104
+ function hsvToRgb(h, s, v) {
105
+ s/=100; v/=100; let c=v*s, x=c*(1-Math.abs(((h/60)%2)-1)), m=v-c, r=0,g=0,b=0;
106
+ if(0<=h&&h<60){r=c;g=x;b=0}else if(60<=h&&h<120){r=x;g=c;b=0}else if(120<=h&&h<180){r=0;g=c;b=x}
107
+ else if(180<=h&&h<240){r=0;g=x;b=c}else if(240<=h&&h<300){r=x;g=0;b=c}else if(300<=h&&h<360){r=c;g=0;b=x}
108
+ return { r:Math.round((r+m)*255), g:Math.round((g+m)*255), b:Math.round((b+m)*255) };
109
+ }
110
+ function rgbToHsv(r, g, b) {
111
+ r/=255; g/=255; b/=255; let max=Math.max(r,g,b), min=Math.min(r,g,b), d=max-min, h=0, s=(max===0?0:d/max), v=max;
112
+ if(d!==0){ switch(max){ case r:h=(g-b)/d+(g<b?6:0);break; case g:h=(b-r)/d+2;break; case b:h=(r-g)/d+4;break; } h*=60; }
113
+ return {h, s:s*100, v:v*100};
114
+ }
 
 
 
 
 
 
115
 
116
+ // Picker Actions
117
+ function openColorPicker(target, ev) {
118
+ if(ev) ev.stopPropagation();
119
+ currentTarget = target;
120
+ document.getElementById('pickerBackdrop').classList.add('active');
121
+ document.getElementById('colorPickerModal').classList.add('active');
122
+
123
+ // Read initial color from state
124
+ let hex = '#FFFFFF';
125
+ if(target === 'main') hex = state.st.col || '#FFFFFF';
126
+ if(target === 'bg') hex = state.st.bg || '#000000';
127
+ if(target === 'static') hex = state.st.col || '#A020F0';
128
+
129
+ const rgb = hexToRgb(hex);
130
+ const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b);
131
+ pickerState = { h: hsv.h, s: hsv.s, v: hsv.v, a: 100 }; // فعلاً آلفا روی ۱۰۰ (یا اگه بخواید از استیت بخونید)
132
+
133
+ initSavedColors();
134
+ generateGrid();
135
+ switchTab('spectrum');
136
+ }
137
 
138
+ function closePicker() {
139
+ document.getElementById('colorPickerModal').classList.remove('active');
140
+ document.getElementById('pickerBackdrop').classList.remove('active');
141
  }
142
 
143
+ function saveAndClosePicker() {
144
+ const rgb = hsvToRgb(pickerState.h, pickerState.s, pickerState.v);
145
+ const hex = rgbToHex(rgb.r, rgb.g, rgb.b);
146
+ // برای سادگی فعلاً آلفا را در کد hex اعمال نمی‌کنیم اما اگه بخواید RGBA باشه باید لاجیک `upd` عوض شه
147
+ // سیستم شما بر اساس hex رنگ کار می‌کند:
 
 
 
 
 
 
148
 
149
+ if (currentTarget === 'main') state.st.col = hex;
150
+ else if (currentTarget === 'bg') state.st.bg = hex;
151
+ else if (currentTarget === 'static') {
152
+ state.st.col = hex;
153
+ document.documentElement.style.setProperty('--static-color', hex);
154
  }
155
 
156
+ upd(); // Apply to preview
157
+ closePicker();
158
  }
159
 
160
+ function switchTab(name) {
161
+ document.querySelectorAll('.picker-tab').forEach(t=>t.classList.remove('active'));
162
+ document.getElementById('tab-'+name).classList.add('active');
163
+ document.querySelectorAll('.view-section').forEach(v=>v.classList.remove('active-view'));
164
+ document.getElementById('view-'+name).classList.add('active-view');
165
+ syncAllUI();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  }
167
 
168
+ function handleSpectrum(e) {
169
+ const rect = spectrumArea.getBoundingClientRect();
170
+ let x = Math.max(0, Math.min((e.clientX||e.touches[0].clientX) - rect.left, rect.width));
171
+ let y = Math.max(0, Math.min((e.clientY||e.touches[0].clientY) - rect.top, rect.height));
172
+ spectrumHandle.style.left = x+'px'; spectrumHandle.style.top = y+'px';
173
+ pickerState.h = (x/rect.width)*360; pickerState.s = 100 - ((y/rect.height)*100);
174
+ syncAllUI();
 
 
 
 
175
  }
176
+ spectrumArea.addEventListener('mousedown', e => { isDragging=true; handleSpectrum(e); });
177
+ window.addEventListener('mousemove', e => { if(isDragging) handleSpectrum(e); });
178
+ window.addEventListener('mouseup', () => isDragging=false);
179
+ spectrumArea.addEventListener('touchstart', e => { isDragging=true; handleSpectrum(e.touches[0]); },{passive:false});
180
+ window.addEventListener('touchmove', e => { if(isDragging){e.preventDefault(); handleSpectrum(e.touches[0]);} },{passive:false});
181
+ window.addEventListener('touchend', () => isDragging=false);
182
+
183
+ function updateBrightness() { pickerState.v = parseInt(document.getElementById('brightness-slider').value); syncAllUI(); }
184
+ function updateAlpha() { pickerState.a = parseInt(document.getElementById('alpha-slider').value); syncAllUI(); }
185
+ function updateFromHexInput() {
186
+ let val = document.getElementById('input-hex').value.replace('#','');
187
+ if(/^[0-9A-Fa-f]{6}$/.test(val)) {
188
+ let rgb = hexToRgb(val); let hsv = rgbToHsv(rgb.r,rgb.g,rgb.b);
189
+ pickerState.h=hsv.h; pickerState.s=hsv.s; pickerState.v=hsv.v;
190
+ syncAllUI();
191
  }
192
  }
193
+ function updateRGBFromInputs() {
194
+ let r=parseInt(document.getElementById('input-r').value)||0, g=parseInt(document.getElementById('input-g').value)||0, b=parseInt(document.getElementById('input-b').value)||0;
195
+ let hsv=rgbToHsv(r,g,b); pickerState.h=hsv.h; pickerState.s=hsv.s; pickerState.v=hsv.v; syncAllUI();
 
 
 
 
 
 
 
 
 
196
  }
197
+ function updateRGBFromSliders() {
198
+ let r=parseInt(document.getElementById('slider-r').value), g=parseInt(document.getElementById('slider-g').value), b=parseInt(document.getElementById('slider-b').value);
199
+ let hsv=rgbToHsv(r,g,b); pickerState.h=hsv.h; pickerState.s=hsv.s; pickerState.v=hsv.v; syncAllUI();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
  }
201
 
202
+ function syncAllUI() {
203
+ let rgb = hsvToRgb(pickerState.h, pickerState.s, pickerState.v);
204
+ let hex = rgbToHex(rgb.r, rgb.g, rgb.b);
205
+ let bg = `rgb(${rgb.r},${rgb.g},${rgb.b})`;
206
 
207
+ // Sliders
208
+ document.getElementById('slider-r').value = document.getElementById('input-r').value = rgb.r;
209
+ document.getElementById('slider-g').value = document.getElementById('input-g').value = rgb.g;
210
+ document.getElementById('slider-b').value = document.getElementById('input-b').value = rgb.b;
211
+ document.getElementById('input-hex').value = hex.replace('#','');
212
+ document.getElementById('largeColorPreview').style.backgroundColor = bg;
213
+ document.getElementById('brightness-slider').value = Math.round(pickerState.v);
214
+ document.getElementById('alpha-slider').value = Math.round(pickerState.a);
215
+ document.getElementById('disp-brightness').innerText = Math.round(pickerState.v);
216
+ document.getElementById('disp-alpha').innerText = Math.round(pickerState.a);
217
+
218
+ // Spectrum Handle
219
+ if(!isDragging) {
220
+ spectrumHandle.style.left = (pickerState.h/360*100)+'%';
221
+ spectrumHandle.style.top = (100-pickerState.s)+'%';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
  }
223
  }
224
 
225
+ // Saved Colors & Grid
226
+ function initSavedColors() {
227
+ try { savedColors = JSON.parse(localStorage.getItem('mySavedColors')) || []; } catch{ savedColors=[]; }
228
+ renderSavedColors();
 
 
 
 
 
 
 
229
  }
230
+ function saveCurrentColor() {
231
+ let rgb = hsvToRgb(pickerState.h, pickerState.s, pickerState.v);
232
+ savedColors.push(rgbToHex(rgb.r, rgb.g, rgb.b));
233
+ localStorage.setItem('mySavedColors', JSON.stringify(savedColors));
234
+ renderSavedColors();
235
+ document.getElementById('toastNotification').classList.add('show');
236
+ setTimeout(()=>document.getElementById('toastNotification').classList.remove('show'), 2000);
237
+ }
238
+ function renderSavedColors() {
239
+ const c = document.getElementById('savedColorsContainer');
240
+ // keep only save button
241
+ while(c.children.length > 1) c.removeChild(c.lastChild);
242
+ savedColors.forEach((hex, i) => {
243
+ let d = document.createElement('div'); d.className='saved-color-wrapper';
244
+ d.innerHTML = `<div class="saved-circle" style="background:${hex}" onclick="loadColor('${hex}')"></div><div class="mini-delete-btn" onclick="deleteColor(${i})"><i class="fa-solid fa-xmark"></i></div>`;
245
+ c.appendChild(d);
246
+ });
247
+ }
248
+ function deleteColor(i) {
249
+ savedColors.splice(i, 1); localStorage.setItem('mySavedColors', JSON.stringify(savedColors)); renderSavedColors();
250
+ }
251
+ function loadColor(hex) {
252
+ let rgb = hexToRgb(hex); let hsv = rgbToHsv(rgb.r,rgb.g,rgb.b);
253
+ pickerState.h=hsv.h; pickerState.s=hsv.s; pickerState.v=hsv.v; syncAllUI();
254
+ }
255
+ function generateGrid() {
256
+ const cont = document.getElementById('gridContainer'); cont.innerHTML='';
257
+ const colors = ['#FFFFFF','#000000','#FF3B30','#FF9500','#FFCC00','#34C759','#00C7BE','#32ADE6','#007AFF','#5856D6','#AF52DE','#FF2D55'];
258
+ // Generate palette
259
+ for(let h=0; h<360; h+=15) {
260
+ let rgb = hsvToRgb(h, 80, 90);
261
+ colors.push(rgbToHex(rgb.r, rgb.g, rgb.b));
262
+ }
263
+ colors.forEach(hex => {
264
+ let d = document.createElement('div'); d.className='grid-item'; d.style.background=hex;
265
+ d.onclick=()=>{loadColor(hex)}; cont.appendChild(d);
266
+ });
267
  }
268
 
269
+ // ------------------------------------
270
+ // Original UPD Logic (Synced)
271
+ // ------------------------------------
272
  function upd() {
273
  saveProjectToDB();
274
+ const uiFz = parseFloat(document.getElementById('fz').value);
275
+ const uiY = parseFloat(document.getElementById('pos').value);
276
+ const uiX = parseFloat(document.getElementById('posX').value);
 
 
 
 
 
277
  state.st.fz = Math.round(10 + (uiFz / 100) * 140);
 
278
  state.st.y = Math.round((uiY / 100) * 1200);
279
+ state.st.x = Math.round((uiX / 100) * 500);
280
+
281
+ // Apply color button previews
282
+ document.getElementById('preview-main-btn').style.backgroundColor = state.st.col || '#fff';
283
+ document.getElementById('preview-bg-btn').style.backgroundColor = state.st.bg || '#000';
284
 
 
 
 
285
  let font = 'Vazirmatn';
286
  if(state.st.f === 'lalezar') font = 'Lalezar'; if(state.st.f === 'bangers') font = 'Impact'; if(state.st.f === 'roboto') font = 'Arial';
287
 
 
290
  tEl.style.bottom = state.st.y + 'px';
291
  tEl.style.textAlign = 'center';
292
  tEl.style.left = '50%';
 
 
293
  tEl.style.transform = `translateX(calc(-50% + ${state.st.x}px))`;
294
+ tEl.style.borderRadius = '0px';
 
295
 
296
  if(state.st.name === 'karaoke_static' || state.st.name === 'auto_director') {
297
  tEl.style.backgroundColor = 'transparent'; tEl.style.color = '#FFFFFF'; tEl.style.webkitTextStroke = '0px'; tEl.style.textShadow = 'none';
298
  } else if (state.st.name === 'plain_white') {
299
  tEl.style.color = '#FFFFFF'; tEl.style.backgroundColor = 'transparent'; tEl.style.webkitTextStroke = '0px'; tEl.style.textShadow = 'none';
300
  } else if (state.st.name === 'white_outline') {
301
+ tEl.style.color = '#FFFFFF'; tEl.style.backgroundColor = 'transparent'; const s = Math.max(3, state.st.fz / 4.5); tEl.style.webkitTextStroke = `${s}px #000000`; tEl.style.paintOrder = 'stroke fill'; tEl.style.webkitTextStroke = `${s}px #000000`; tEl.style.textShadow = 'none';
302
  } else {
303
  tEl.style.color = state.st.col;
304
  if(state.st.type === 'solid') { tEl.style.backgroundColor = state.st.bg; tEl.style.webkitTextStroke = '0px'; tEl.style.textShadow = 'none'; tEl.style.borderRadius = '12px'; }
305
  else if (state.st.type === 'transparent') {
306
+ let c = state.st.bg.replace('#', '');
307
+ if(c.length===6) {
308
+ let r = parseInt(c.substring(0, 2), 16); let g = parseInt(c.substring(2, 4), 16); let b = parseInt(c.substring(4, 6), 16);
309
+ tEl.style.backgroundColor = `rgba(${r},${g},${b},0.6)`;
310
+ } else { tEl.style.backgroundColor = state.st.bg; } // fallback
311
+ tEl.style.webkitTextStroke = '0px'; tEl.style.textShadow = 'none'; tEl.style.borderRadius = '12px';
312
  } else if (state.st.type === 'outline') {
313
+ tEl.style.backgroundColor = 'transparent'; tEl.style.color = state.st.col; const s = Math.max(3, state.st.fz / 4.5); tEl.style.webkitTextStroke = `${s}px ${state.st.bg}`; tEl.style.paintOrder = 'stroke fill'; tEl.style.textShadow = 'none';
314
  } else { tEl.style.backgroundColor = 'transparent'; tEl.style.webkitTextStroke = '0px'; tEl.style.textShadow = 'none'; }
315
  }
316
  }
317
 
 
 
 
318
  function setStylePreset(name, el, skipModeSet = false) {
319
  state.st.name = name;
320
  document.querySelectorAll('.style-card').forEach(c => c.classList.remove('selected'));
321
  if(el) el.classList.add('selected');
 
322
  if(name === 'karaoke_static' && !skipModeSet) {
323
+ state.st.col = '#A020F0';
324
+ document.documentElement.style.setProperty('--static-color', '#A020F0');
 
 
 
325
  }
326
+ if(name === 'classic' && !skipModeSet) setMode('solid');
327
+ else if (name === 'progressive_write') setMode('none');
328
+ else if (name === 'plain_white' || name === 'white_outline') {
329
+ state.st.col = '#FFFFFF'; state.st.bg = '#000000'; setMode('outline');
 
 
 
 
 
 
 
330
  }
331
  upd();
332
  }
333
 
334
+ function handleClassicDoubleClick(element) { event.stopPropagation(); activateCustomStyle(); setStylePreset('classic', element, true); setMode('none'); }
 
 
 
 
 
 
335
  function setFont(f, el) { document.querySelectorAll('.font-btn').forEach(btn => btn.classList.remove('ticked')); el.classList.add('ticked'); state.st.f = f; upd(); }
336
  function setMode(m) { state.st.type = m; syncModeButtons(); upd(); }