Hamed744 commited on
Commit
85f3bf6
·
verified ·
1 Parent(s): 74c0b6f

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +97 -457
index.html CHANGED
@@ -19,20 +19,14 @@
19
  .new-proj-btn { background: #333; color: #ccc; border: 1px solid #444; padding: 8px 15px; border-radius: 12px; font-size: 0.9rem; cursor: pointer; transition: 0.2s; display: flex; align-items: center; gap: 8px; font-family: inherit; }
20
  .new-proj-btn:hover { background: #444; color: #fff; border-color: #666; }
21
 
22
- /* --- مدال تایید خروج --- */
23
  .modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); backdrop-filter: blur(5px); z-index: 3000; display: none; align-items: center; justify-content: center; opacity: 0; transition: opacity 0.3s; }
24
  .modal-overlay.show { opacity: 1; }
25
  .modal-box { background: #222; border: 1px solid #444; width: 90%; max-width: 350px; border-radius: 20px; padding: 25px; text-align: center; transform: scale(0.8); transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); box-shadow: 0 10px 40px rgba(0,0,0,0.5); }
26
  .modal-overlay.show .modal-box { transform: scale(1); }
27
- .modal-icon { font-size: 3rem; color: var(--primary); margin-bottom: 15px; }
28
  .modal-title { font-size: 1.2rem; font-weight: bold; margin-bottom: 10px; color: #fff; }
29
- .modal-desc { font-size: 0.9rem; color: #aaa; margin-bottom: 25px; line-height: 1.5; }
30
- .modal-actions { display: flex; gap: 10px; justify-content: center; }
31
  .modal-btn { flex: 1; padding: 12px; border-radius: 12px; border: none; font-family: inherit; font-weight: bold; cursor: pointer; transition: 0.2s; }
32
  .btn-confirm { background: var(--primary); color: #fff; }
33
- .btn-confirm:active { transform: scale(0.95); }
34
- .btn-cancel { background: #333; color: #fff; }
35
- .btn-cancel:hover { background: #444; }
36
 
37
  /* --- فضای ویدیو --- */
38
  #workspace { width: 100%; display: flex; justify-content: center; background: #000; padding: 20px 0; min-height: 300px; flex-shrink: 0; }
@@ -41,38 +35,20 @@
41
  video { width: 100%; height: 100%; object-fit: contain; display: block; }
42
  #playOverlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; background: rgba(0,0,0,0.2); z-index: 10; transition: opacity 0.2s; }
43
  #playOverlay.playing { opacity: 0; pointer-events: none; }
44
- #playOverlay i { font-size: 4rem; color: rgba(255,255,255,0.8); filter: drop-shadow(0 0 5px rgba(0,0,0,0.8)); pointer-events: none; }
45
- #subtitleLayer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; display: flex; justify-content: center; z-index: 20; }
46
 
47
  #activeText {
48
- position: absolute;
49
- white-space: pre;
50
- pointer-events: auto;
51
- cursor: move;
52
- padding: 10px 20px;
53
- line-height: 1.5;
54
- font-weight: bold;
55
  transition: background-color 0.2s, text-shadow 0.2s, -webkit-text-stroke 0.2s, border-radius 0.2s;
56
- transform-origin: center center;
57
- text-align: center;
58
- left: 50%;
59
- transform: translateX(-50%);
60
  paint-order: stroke fill;
61
  }
62
 
63
- /* --- دکمه هوشمند (Magic Button) --- */
64
- .magic-bar { padding: 15px 20px 5px 20px; background: var(--bg); display: flex; justify-content: center; position: sticky; top: 60px; z-index: 95; }
65
- .btn-ai-magic { background: linear-gradient(90deg, #FF0080 0%, #7928CA 50%, #FF0080 100%); background-size: 200% auto; color: white; border: none; width: 100%; max-width: 600px; padding: 12px 20px; border-radius: 16px; font-weight: 900; font-size: 1.1rem; display: flex; align-items: center; justify-content: center; gap: 12px; cursor: pointer; box-shadow: 0 4px 20px rgba(121, 40, 202, 0.5); transition: all 0.3s; animation: gradientMove 3s linear infinite; font-family: 'Vazirmatn', sans-serif; }
66
- .btn-ai-magic:active { transform: scale(0.98); }
67
- @keyframes gradientMove { to { background-position: 200% center; } }
68
-
69
  /* --- نوار ابزار --- */
70
  .controls-bar { display: flex; justify-content: center; gap: 10px; padding: 10px 10px; background: var(--bg); border-bottom: 1px solid #222; position: sticky; top: 115px; z-index: 90; flex-wrap: wrap; }
71
  .tool-btn { display: flex; flex-direction: column; align-items: center; gap: 5px; background: var(--surface); border: 1px solid #333; color: #aaa; cursor: pointer; transition: 0.2s; padding: 8px 10px; border-radius: 12px; min-width: 70px; flex: 1; max-width: 100px; }
72
- .tool-btn i { font-size: 1.2rem; margin-bottom: 2px; }
73
- .tool-btn span { font-size: 0.75rem; font-weight: bold; }
74
- .tool-btn:hover { color: #fff; background: #2a2a2a; }
75
- .tool-btn.active-tool { color: #fff; background: var(--primary); border-color: var(--primary); }
76
 
77
  #toolsContainer { width: 100%; background: var(--panel-bg); overflow: hidden; min-height: 0; transition: none; border-bottom: 1px solid #333; }
78
  .tool-section { display: none; padding: 20px; animation: fadeIn 0.3s ease; max-width: 800px; margin: 0 auto; }
@@ -80,66 +56,42 @@
80
  @keyframes fadeIn { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } }
81
 
82
  .btn-exp { background: var(--primary); color: #fff; border: none; padding: 10px 25px; border-radius: 12px; font-weight: bold; cursor: pointer; transition: 0.2s; font-family: inherit; }
83
- .btn-magic-action { background: linear-gradient(135deg, #FF4081, #7C4DFF); width: 100%; border: none; padding: 12px; border-radius: 12px; color: white; font-weight: bold; font-family: inherit; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 8px; margin-top: 10px; }
84
 
85
  .btn-save-manual {
86
  width: 100%; background: #00C853; color: white; border: none;
87
  padding: 12px; border-radius: 10px; font-weight: bold; font-family: inherit;
88
  margin-top: 20px; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 8px;
89
  }
90
- .btn-save-manual:hover { background: #00E676; }
91
 
92
  .row { margin-bottom: 20px; }
93
  label { display: block; margin-bottom: 8px; color: #ccc; font-size: 0.9rem; }
94
  input[type=range] { width: 100%; accent-color: var(--primary); height: 6px; cursor: pointer; border-radius:3px; }
95
- textarea { width: 100%; background: #222; color: #fff; border: 1px solid #444; padding: 15px; border-radius: 12px; font-family: inherit; font-size: 1rem; margin-bottom: 15px; resize: vertical; min-height: 80px; }
96
 
97
  .style-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 15px; }
98
  .style-card { background: #333; border: 2px solid transparent; border-radius: 10px; padding: 15px; cursor: pointer; display: flex; flex-direction:column; align-items: center; gap: 8px; justify-content: center; min-height: 110px; position: relative; transition: 0.2s; }
99
- .style-card.selected { border-color: var(--primary); background: #2a2a2a; box-shadow: 0 0 15px rgba(124, 77, 255, 0.3); }
100
- .style-card:hover { transform: translateY(-2px); }
101
 
102
  .delete-style-btn {
103
  position: absolute; top: 5px; left: 5px;
104
  background: rgba(255, 50, 50, 0.2); color: #ff5555;
105
  border: none; border-radius: 5px; width: 25px; height: 25px;
106
- display: flex; align-items: center; justify-content: center;
107
- cursor: pointer; transition: 0.2s; z-index: 5;
108
  }
109
- .delete-style-btn:hover { background: #ff4444; color: white; }
110
-
111
- .preview-box { font-size: 1.2rem; margin-bottom: 5px; padding: 5px 10px; border-radius: 6px; }
112
 
113
  .mode-btn { flex: 1; padding: 12px; border-radius: 8px; background: #333; color: #aaa; border: 2px solid transparent; cursor: pointer; }
114
  .mode-btn.active { background: var(--primary); color: #fff; border-color: var(--primary); }
115
 
116
- .font-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
117
  .font-btn { padding: 15px; background: #333; border-radius: 8px; cursor: pointer; border: 2px solid transparent; position: relative; }
118
  .font-btn.ticked { border-color: var(--primary); }
119
- .font-btn.ticked::after { content: '\f00c'; font-family: "Font Awesome 6 Free"; font-weight: 900; position: absolute; top: 10px; left: 10px; color: var(--primary); }
120
 
121
  /* --- لیست زیرنویس --- */
122
- #subtitleListArea { width: 100%; }
123
- .sub-card { background: #222; border-right: 4px solid #444; border-radius: 8px; margin-bottom: 10px; padding: 15px; display: flex; flex-direction: column; gap: 10px; transition: 0.2s; cursor: pointer; position: relative; overflow: hidden; }
124
- .sub-card-header { display: flex; align-items: center; justify-content: space-between; width: 100%; }
125
- .sub-card.active { background: #2b2535; border-right-color: var(--primary); box-shadow: 0 4px 15px rgba(0,0,0,0.3); transform: scale(1.01); }
126
-
127
- .sub-time { font-size: 0.75rem; color: #666; margin-left: 10px; min-width: 50px; text-align: left; font-family: monospace; }
128
- .sub-text-content { flex: 1; font-size: 1rem; color: #ddd; line-height: 1.6; }
129
  .sub-card.active .sub-text-content { color: #fff; font-weight: bold; }
130
-
131
- .card-actions { display: flex; gap: 8px; opacity: 0.8; transition: 0.2s; }
132
- .mini-btn { width: 36px; height: 36px; border-radius: 10px; border: none; background: rgba(255,255,255,0.1); color: #fff; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: 0.2s; font-size: 0.9rem; }
133
- .mini-btn:hover { background: var(--primary); transform: scale(1.1); }
134
- .mini-btn.play-btn { background: rgba(0, 200, 83, 0.2); color: #00E676; }
135
- .mini-btn.play-btn:hover { background: #00C853; color: white; }
136
-
137
- .inline-editor-container { width: 100%; display: flex; flex-direction: column; gap: 8px; animation: fadeIn 0.2s; margin-top: 5px; cursor: default; }
138
- .inline-textarea { width: 100%; background: #111; color: #fff; border: 1px solid var(--primary); padding: 10px; border-radius: 8px; font-family: inherit; font-size: 1rem; resize: vertical; min-height: 80px; }
139
- .inline-actions { display: flex; gap: 10px; justify-content: flex-end; }
140
- .inline-btn { padding: 5px 15px; border-radius: 6px; border: none; cursor: pointer; font-size: 0.85rem; font-family: inherit; }
141
- .btn-save { background: var(--primary); color: #fff; }
142
- .btn-cancel { background: #444; color: #ccc; }
143
 
144
  #uploadScreen, #loader, #resultScreen { position: fixed; top:0; left:0; width:100%; height:100%; background: var(--bg); z-index: 2000; display: flex; flex-direction: column; justify-content: center; align-items: center; }
145
  #loader, #resultScreen { display: none; }
@@ -151,25 +103,24 @@
151
 
152
  <div id="exitModal" class="modal-overlay">
153
  <div class="modal-box">
154
- <div class="modal-icon"><i class="fa-solid fa-circle-exclamation"></i></div>
155
  <div class="modal-title">شروع پروژه جدید؟</div>
156
- <div class="modal-desc">با شروع پروژه جدید، تمام تغییرات فعلی شما از بین خواهند رفت. آیا مطمئن هستید؟</div>
157
- <div class="modal-actions">
158
- <button class="modal-btn btn-confirm" onclick="confirmExit()">بله، جدید</button>
159
- <button class="modal-btn btn-cancel" onclick="closeExitModal()">انصراف</button>
160
  </div>
161
  </div>
162
  </div>
163
 
164
  <div id="uploadScreen">
165
- <h2 style="margin-bottom: 30px; font-size:1.8rem;">استودیو زیرنویس هوشمند</h2>
166
  <input type="file" id="fileIn" hidden accept="video/*" onchange="startUpload()">
167
  <button class="btn-exp" style="padding: 15px 40px; font-size: 1.1rem; border-radius: 30px;" onclick="document.getElementById('fileIn').click()">
168
  <i class="fa-solid fa-folder-open"></i> &nbsp; انتخاب ویدیو
169
  </button>
170
  </div>
171
 
172
- <div id="loader"><div class="spinner"></div><p style="margin-top:20px; color:#fff; font-size:1.1rem;">در حال پردازش...</p></div>
173
 
174
  <div id="resultScreen">
175
  <h3 style="color:#fff; margin-bottom: 15px;">ویدیو آماده شد!</h3>
@@ -177,16 +128,14 @@
177
  <video id="resultVideo" controls style="max-width:100%; max-height:100%; width:100%; height:100%;"></video>
178
  </div>
179
  <div style="display: flex; gap: 15px;">
180
- <a id="downloadBtn" href="#" download class="btn-exp" style="background: #00C853; text-decoration: none; display: flex; align-items: center; gap: 8px;"><i class="fa-solid fa-download"></i> دانلود</a>
181
- <button class="btn-exp" style="background: #444;" onclick="closeResult()">ویرایش مجدد</button>
182
  </div>
183
  </div>
184
 
185
  <div class="top-bar">
186
- <button class="new-proj-btn" onclick="showExitModal()">
187
- <i class="fa-solid fa-plus-circle"></i> پروژه جدید
188
- </button>
189
- <button class="btn-exp" onclick="render()" style="padding: 8px 20px; font-size: 0.9rem;">خروجی نهایی <i class="fa-solid fa-chevron-left"></i></button>
190
  </div>
191
 
192
  <div id="workspace">
@@ -194,18 +143,11 @@
194
  <div id="videoContainer" onclick="togglePlay()">
195
  <video id="vid" playsinline webkit-playsinline></video>
196
  <div id="playOverlay"><i class="fa-solid fa-play"></i></div>
197
- <div id="subtitleLayer"><div id="activeText" ondblclick="toggleTool('text')"></div></div>
198
  </div>
199
  </div>
200
  </div>
201
 
202
- <div class="magic-bar">
203
- <button class="btn-ai-magic" onclick="toggleTool('magic')" id="btn-magic">
204
- <i class="fa-solid fa-wand-magic-sparkles"></i>
205
- <span>طراحـی هوشمـنـد AI</span>
206
- </button>
207
- </div>
208
-
209
  <div class="controls-bar">
210
  <button class="tool-btn" onclick="toggleTool('style')" id="btn-style"><i class="fa-solid fa-layer-group"></i><span>استایل</span></button>
211
  <button class="tool-btn" onclick="toggleTool('appearance')" id="btn-appearance"><i class="fa-solid fa-palette"></i><span>ظاهر</span></button>
@@ -214,32 +156,17 @@
214
  </div>
215
 
216
  <div id="toolsContainer">
217
-
218
- <div id="section-magic" class="tool-section">
219
- <label>توصیف کنید چه ظاهری می‌خواهید:</label>
220
- <textarea id="magicPrompt" style="min-height: 60px;" placeholder="مثال: تم نئون صورتی با فونت بولد..."></textarea>
221
- <button class="btn-magic-action" onclick="generateAIStyle()">
222
- <i class="fa-solid fa-sparkles"></i> اعمال طراحی
223
- </button>
224
- </div>
225
-
226
  <div id="section-style" class="tool-section">
227
- <div style="margin-bottom: 10px; color: #888; font-size: 0.9rem;">
228
- استایل‌های ساخته شده:
229
- </div>
230
- <div class="style-grid" id="savedStylesGrid">
231
- <!-- استایل‌های ذخیره شده اینجا نمایش داده می‌شوند -->
232
- </div>
233
- <div id="noStylesMsg" style="text-align: center; padding: 30px; color: #666; font-size: 0.9rem;">
234
- هنوز استایلی نساخته‌اید.<br>
235
- از دکمه <b>طراحی هوشمند AI</b> یا دکمه <b>ذخیره این سبک</b> استفاده کنید.
236
  </div>
237
  </div>
238
 
239
  <div id="section-appearance" class="tool-section">
240
  <div class="row"><label>سایز متن</label><input type="range" id="fz" min="10" max="300" value="65" oninput="upd()"></div>
241
  <div class="row"><label>موقعیت عمودی</label><input type="range" id="pos" min="0" max="1500" value="150" oninput="upd()"></div>
242
-
243
  <div class="row" id="radiusRow" style="display:none;"><label>گردی کادر (فقط در پیش‌نمایش)</label><input type="range" id="radius" min="0" max="50" value="12" oninput="upd()"></div>
244
 
245
  <div style="display:flex; gap:15px; margin-bottom:20px;">
@@ -251,26 +178,20 @@
251
  <button class="btn-exp mode-btn" onclick="setMode('transparent')">شیشه‌ای</button>
252
  <button class="btn-exp mode-btn" onclick="setMode('outline')">فقط دورخط</button>
253
  </div>
254
-
255
- <!-- دکمه ذخیره دستی استایل -->
256
- <button class="btn-save-manual" onclick="saveCurrentStyle()">
257
- <i class="fa-solid fa-floppy-disk"></i> ذخیره این سبک
258
- </button>
259
  </div>
260
 
261
  <div id="section-font" class="tool-section">
262
- <div class="font-grid">
263
- <button class="font-btn ticked" style="font-family:Lalezar; font-size:1.2rem;" onclick="setFont('lalezar', this)">لاله زار</button>
264
- <button class="font-btn" style="font-family:Vazirmatn; font-weight:900; font-size:1.1rem;" onclick="setFont('vazir', this)">وزیر ضخیم</button>
265
  <button class="font-btn" style="font-family:Arial;" onclick="setFont('roboto', this)">ساده (Arial)</button>
266
  <button class="font-btn" style="font-family:Impact;" onclick="setFont('bangers', this)">بولد (Impact)</button>
267
  </div>
268
  </div>
269
 
270
  <div id="section-text" class="tool-section">
271
- <div id="subtitleListArea">
272
- <div id="subCardsContainer"></div>
273
- </div>
274
  </div>
275
  </div>
276
 
@@ -279,15 +200,11 @@
279
  id: null, w: 1080, h: 1920, segs: [],
280
  st: { f: 'lalezar', fz: 65, col: '#ffffff', bg: '#000000', type: 'solid', y: 150, x: 0, radius: 12, name: 'default' }
281
  };
282
- let curSeg = -1; let manualOverride = false; let editingIndex = -1;
283
- let stopAtTime = null; // برای توقف در پایان جمله
284
  const v = document.getElementById('vid'); const tEl = document.getElementById('activeText');
285
  const toolsContainer = document.getElementById('toolsContainer');
286
 
287
- window.onload = function() {
288
- renderSavedStyles();
289
- fit();
290
- };
291
 
292
  function hexToRgba(hex, alpha) {
293
  let c = hex.substring(1).split('');
@@ -296,71 +213,36 @@
296
  return 'rgba('+[(c>>16)&255, (c>>8)&255, c&255].join(',')+','+alpha+')';
297
  }
298
 
299
- // --- Local Storage & Styles Logic ---
300
- function getSavedStyles() {
301
- try { return JSON.parse(localStorage.getItem('aiStyles') || '[]'); }
302
- catch { return []; }
303
- }
304
-
305
- function saveStyleToStorage(styleData) {
306
- const styles = getSavedStyles();
307
- styles.unshift(styleData);
308
- localStorage.setItem('aiStyles', JSON.stringify(styles));
309
- renderSavedStyles();
310
- }
311
-
312
- function deleteStyle(e, id) {
313
- e.stopPropagation();
314
- if(!confirm('آیا این استایل حذف شود؟')) return;
315
- const styles = getSavedStyles().filter(s => s.id !== id);
316
- localStorage.setItem('aiStyles', JSON.stringify(styles));
317
- renderSavedStyles();
318
- }
319
-
320
- // تابع ذخیره دستی استایل
321
  function saveCurrentStyle() {
322
- const name = prompt("نام این استایل را وارد کنید:", "استایل شخصی");
323
  if(!name) return;
324
-
325
- const newStyle = {
326
- id: Date.now(),
327
- name: name,
328
- primaryColor: state.st.col,
329
- outlineColor: state.st.bg,
330
- backType: state.st.type,
331
- font: state.st.f,
332
- fontSize: state.st.fz
333
- };
334
- saveStyleToStorage(newStyle);
335
- alert("استایل با موفقیت ذخیره شد.");
336
- toggleTool('style'); // رفتن به تب استایل برای دیدن نتیجه
337
  }
338
 
339
  function applySavedStyle(id) {
340
- const styles = getSavedStyles();
341
- const found = styles.find(s => s.id === id);
342
  if(found) {
343
- state.st.col = found.primaryColor;
344
- state.st.bg = found.outlineColor;
345
- state.st.type = found.backType;
346
- state.st.f = found.font;
347
- state.st.fz = found.fontSize;
348
- state.st.name = 'custom_' + id;
349
-
350
  document.getElementById('col').value = state.st.col;
351
  document.getElementById('bgCol').value = state.st.bg;
352
  document.getElementById('fz').value = state.st.fz;
353
  syncModeButtons();
354
-
355
  document.querySelectorAll('.font-btn').forEach(b => b.classList.remove('ticked'));
356
  let fIdx = 0;
357
- if(state.st.f === 'vazir') fIdx=1;
358
- else if(state.st.f === 'roboto') fIdx=2;
359
- else if(state.st.f === 'bangers') fIdx=3;
360
  document.querySelectorAll('.font-btn')[fIdx].classList.add('ticked');
361
-
362
  upd();
363
- renderSavedStyles();
364
  }
365
  }
366
 
@@ -368,61 +250,32 @@
368
  const grid = document.getElementById('savedStylesGrid');
369
  const msg = document.getElementById('noStylesMsg');
370
  const styles = getSavedStyles();
371
-
372
  grid.innerHTML = '';
373
- if(styles.length === 0) {
374
- msg.style.display = 'block';
375
- return;
376
- }
377
  msg.style.display = 'none';
378
-
379
  styles.forEach(s => {
380
  const el = document.createElement('div');
381
  el.className = 'style-card';
382
- if(state.st.name === 'custom_' + s.id) el.classList.add('selected');
383
-
384
- let previewStyle = `color: ${s.primaryColor}; font-family: ${getFontFamily(s.font)};`;
385
  if(s.backType === 'solid') previewStyle += `background: ${s.outlineColor};`;
386
  else if(s.backType === 'outline') previewStyle += `-webkit-text-stroke: 2px ${s.outlineColor}; background: transparent;`;
387
- else if(s.backType === 'transparent') previewStyle += `background: rgba(0,0,0,0.5);`;
388
-
389
- el.innerHTML = `
390
- <button class="delete-style-btn" onclick="deleteStyle(event, ${s.id})"><i class="fa-solid fa-trash"></i></button>
391
- <div class="preview-box" style="${previewStyle}">نمونه</div>
392
- <div style="font-size:0.8rem; color:#aaa;">${s.name}</div>
393
- `;
394
  el.onclick = () => applySavedStyle(s.id);
395
  grid.appendChild(el);
396
  });
397
  }
398
 
399
- function getFontFamily(fName) {
400
- if(fName === 'lalezar') return 'Lalezar';
401
- if(fName === 'vazir') return 'Vazirmatn';
402
- if(fName === 'bangers') return 'Impact';
403
- return 'Arial';
404
- }
405
-
406
- function showExitModal() {
407
- const m = document.getElementById('exitModal');
408
- m.style.display = 'flex';
409
- setTimeout(() => m.classList.add('show'), 10);
410
- }
411
- function closeExitModal() {
412
- const m = document.getElementById('exitModal');
413
- m.classList.remove('show');
414
- setTimeout(() => m.style.display = 'none', 300);
415
- }
416
  function confirmExit() { window.location.reload(); }
417
 
418
  function fit() {
419
  if(!state.w) return;
420
  const ws = document.getElementById('workspace');
421
- const availableHeight = window.innerHeight * 0.6;
422
- const scale = Math.min((ws.clientWidth - 40) / state.w, availableHeight / state.h);
423
  const c = document.getElementById('videoContainer');
424
- c.style.width = state.w + 'px';
425
- c.style.height = state.h + 'px';
426
  document.getElementById('scaler').style.transform = `scale(${scale})`;
427
  ws.style.height = (state.h * scale + 40) + 'px';
428
  }
@@ -430,73 +283,10 @@
430
 
431
  function toggleTool(toolName) {
432
  document.querySelectorAll('.tool-btn').forEach(b => b.classList.remove('active-tool'));
433
- const btnId = 'btn-' + toolName;
434
- const btn = document.getElementById(btnId);
435
-
436
- const sectionId = 'section-' + toolName;
437
- const section = document.getElementById(sectionId);
438
-
439
- const isAlreadyOpen = toolsContainer.classList.contains('open') && section.style.display === 'block';
440
-
441
- if (isAlreadyOpen) {
442
- toolsContainer.classList.remove('open');
443
- setTimeout(() => { section.style.display = 'none'; }, 200);
444
- } else {
445
- document.querySelectorAll('.tool-section').forEach(s => s.style.display = 'none');
446
- if(btn) btn.classList.add('active-tool');
447
- section.style.display = 'block';
448
- section.classList.add('active-section');
449
- if(toolName === 'text') {
450
- toolsContainer.style.maxHeight = 'none';
451
- toolsContainer.classList.add('open');
452
- } else {
453
- toolsContainer.style.maxHeight = '';
454
- toolsContainer.classList.add('open');
455
- }
456
- }
457
- }
458
-
459
- async function generateAIStyle() {
460
- const desc = document.getElementById('magicPrompt').value;
461
- if(!desc) return;
462
-
463
- const btn = document.querySelector('.btn-magic-action');
464
- const originalText = btn.innerHTML;
465
- btn.innerHTML = '<div class="spinner" style="width:20px;height:20px;border-width:3px;"></div> در حال طراحی...';
466
- btn.disabled = true;
467
-
468
- try {
469
- const r = await fetch('/api/generate-style', {
470
- method: 'POST',
471
- headers: {'Content-Type': 'application/json'},
472
- body: JSON.stringify({description: desc})
473
- });
474
- const styleData = await r.json();
475
-
476
- const newSavedStyle = {
477
- id: Date.now(),
478
- name: desc.length > 20 ? desc.substring(0, 20) + '...' : desc,
479
- primaryColor: styleData.primaryColor,
480
- outlineColor: styleData.outlineColor,
481
- backType: styleData.backType,
482
- font: styleData.font,
483
- fontSize: styleData.fontSize
484
- };
485
-
486
- saveStyleToStorage(newSavedStyle);
487
- applySavedStyle(newSavedStyle.id);
488
-
489
- toolsContainer.classList.remove('open');
490
- alert('طراحی با موفقیت انجام و ذخیره شد!');
491
- toggleTool('style');
492
-
493
- } catch(e) {
494
- console.error(e);
495
- alert('خطا در ارتباط با هوش مصنوعی');
496
- } finally {
497
- btn.innerHTML = originalText;
498
- btn.disabled = false;
499
- }
500
  }
501
 
502
  async function startUpload() {
@@ -516,152 +306,31 @@
516
  }
517
 
518
  function renderSegList() {
519
- const container = document.getElementById('subCardsContainer');
520
- container.innerHTML = '';
521
  state.segs.forEach((s, i) => {
522
- const card = document.createElement('div');
523
- card.className = 'sub-card' + (s.isHidden ? ' is-hidden-seg' : '');
524
- card.id = 'seg-card-' + i;
525
-
526
- if(editingIndex === i) {
527
- card.classList.add('active');
528
- card.style.cursor = 'default';
529
- const editorCont = document.createElement('div');
530
- editorCont.className = 'inline-editor-container';
531
- const ta = document.createElement('textarea');
532
- ta.className = 'inline-textarea';
533
- ta.value = s.text;
534
- const acts = document.createElement('div');
535
- acts.className = 'inline-actions';
536
- const btnSave = document.createElement('button');
537
- btnSave.className = 'inline-btn btn-save';
538
- btnSave.innerText = 'تایید';
539
- btnSave.onclick = (e) => { e.stopPropagation(); saveInlineText(i, ta.value); };
540
- const btnCancel = document.createElement('button');
541
- btnCancel.className = 'inline-btn btn-cancel';
542
- btnCancel.innerText = 'لغو';
543
- btnCancel.onclick = (e) => { e.stopPropagation(); editingIndex = -1; renderSegList(); };
544
- acts.appendChild(btnCancel);
545
- acts.appendChild(btnSave);
546
- editorCont.appendChild(ta);
547
- editorCont.appendChild(acts);
548
- card.appendChild(editorCont);
549
- } else {
550
- const header = document.createElement('div');
551
- header.className = 'sub-card-header';
552
- const timeDiv = document.createElement('div');
553
- timeDiv.className = 'sub-time';
554
- timeDiv.innerText = formatTimeSimple(s.start);
555
- const textDiv = document.createElement('div');
556
- textDiv.className = 'sub-text-content';
557
- textDiv.innerText = s.text;
558
- const actionsDiv = document.createElement('div');
559
- actionsDiv.className = 'card-actions';
560
- const playBtn = document.createElement('button');
561
- playBtn.className = 'mini-btn play-btn';
562
- playBtn.innerHTML = '<i class="fa-solid fa-play"></i>';
563
- playBtn.onclick = (e) => {
564
- e.stopPropagation();
565
- manualOverride = true;
566
- selectSegment(i);
567
- // منطق پخش سگمنت و توقف در پایان
568
- v.currentTime = s.start;
569
- stopAtTime = s.end; // تنظیم زمان توقف
570
- v.play();
571
- togglePlayIcon(true);
572
- };
573
- const editBtn = document.createElement('button');
574
- editBtn.className = 'mini-btn';
575
- editBtn.innerHTML = '<i class="fa-solid fa-pen"></i>';
576
- editBtn.onclick = (e) => { e.stopPropagation(); startInlineEdit(i); };
577
- actionsDiv.appendChild(playBtn);
578
- actionsDiv.appendChild(editBtn);
579
- header.appendChild(timeDiv);
580
- header.appendChild(textDiv);
581
- header.appendChild(actionsDiv);
582
- card.appendChild(header);
583
- card.onclick = () => {
584
- if(editingIndex !== -1) return;
585
- manualOverride = true;
586
- selectSegment(i);
587
- if(!v.paused) {
588
- v.currentTime = s.start + 0.01;
589
- manualOverride = false;
590
- }
591
- else {
592
- v.currentTime = s.start + 0.01;
593
- showTextOverlay(i);
594
- }
595
- };
596
- }
597
  container.appendChild(card);
598
  });
599
  }
 
 
600
 
601
- function startInlineEdit(index) {
602
- editingIndex = index;
603
- manualOverride = true;
604
- selectSegment(index);
605
- v.pause(); togglePlayIcon(false);
606
- stopAtTime = null;
607
- v.currentTime = state.segs[index].start + 0.01;
608
- showTextOverlay(index);
609
- renderSegList();
610
- }
611
-
612
- function saveInlineText(index, newText) {
613
- const seg = state.segs[index];
614
- seg.text = newText;
615
- tEl.innerText = seg.text;
616
- editingIndex = -1;
617
- renderSegList();
618
- }
619
-
620
- function formatTimeSimple(sec) {
621
- let m = Math.floor(sec / 60);
622
- let s = Math.floor(sec % 60);
623
- return `${m}:${s < 10 ? '0'+s : s}`;
624
- }
625
-
626
- function selectSegment(i) {
627
- document.querySelectorAll('.sub-card').forEach(el => el.classList.remove('active'));
628
- const activeItem = document.getElementById('seg-card-' + i);
629
- if(activeItem && editingIndex === -1) { activeItem.classList.add('active'); }
630
- curSeg = i;
631
- }
632
-
633
- function showTextOverlay(index) {
634
- if(index === -1) { tEl.style.opacity = 0; return; }
635
- const seg = state.segs[index];
636
- tEl.innerText = seg.text;
637
- if(seg.isHidden) tEl.style.opacity = 0; else tEl.style.opacity = 1;
638
- }
639
 
640
  v.ontimeupdate = () => {
641
- // منطق توقف در پایان جمله
642
- if(stopAtTime && v.currentTime >= stopAtTime) {
643
- v.pause();
644
- togglePlayIcon(false);
645
- stopAtTime = null; // ریست کردن زمان توقف
646
- return;
647
- }
648
-
649
- if(manualOverride && v.paused) return;
650
- if(!v.paused) manualOverride = false;
651
  const idx = state.segs.findIndex(s => v.currentTime >= s.start && v.currentTime <= s.end);
652
- if(idx !== -1) {
653
- if(curSeg !== idx) { selectSegment(idx); }
654
- const seg = state.segs[idx];
655
- if(seg.isHidden) { tEl.style.opacity = 0; }
656
- else {
657
- tEl.style.opacity = 1;
658
- tEl.innerText = seg.text;
659
- }
660
- } else {
661
- if(!manualOverride && curSeg !== -1 && (v.currentTime < state.segs[curSeg].start || v.currentTime > state.segs[curSeg].end)) {
662
- curSeg = -1; document.querySelectorAll('.sub-card').forEach(el => el.classList.remove('active')); tEl.style.opacity = 0;
663
- }
664
- }
665
  };
666
 
667
  function upd() {
@@ -670,71 +339,42 @@
670
  state.st.col = document.getElementById('col').value;
671
  state.st.bg = document.getElementById('bgCol').value;
672
  state.st.radius = parseInt(document.getElementById('radius').value);
673
-
674
- const radiusRow = document.getElementById('radiusRow');
675
- if (state.st.type === 'solid' || state.st.type === 'transparent') {
676
- radiusRow.style.display = 'flex';
677
- } else {
678
- radiusRow.style.display = 'none';
679
- }
680
 
681
  let font = 'Vazirmatn';
682
  if(state.st.f === 'lalezar') font = 'Lalezar';
683
  if(state.st.f === 'bangers') font = 'Impact';
684
  if(state.st.f === 'roboto') font = 'Arial';
685
  tEl.style.fontFamily = font;
686
-
687
  tEl.style.fontSize = state.st.fz + 'px';
688
  tEl.style.bottom = state.st.y + 'px';
689
- tEl.style.textAlign = 'center';
690
- tEl.style.left = '50%';
691
- tEl.style.transform = `translateX(calc(-50% + ${state.st.x}px))`;
692
  tEl.style.color = state.st.col;
693
 
694
- tEl.style.paintOrder = 'stroke fill';
695
 
696
  if(state.st.type === 'solid') {
697
- tEl.style.backgroundColor = state.st.bg;
698
- tEl.style.webkitTextStroke = '0px';
699
- tEl.style.textShadow = 'none';
700
- tEl.style.borderRadius = state.st.radius + 'px';
701
  } else if (state.st.type === 'transparent') {
702
- tEl.style.backgroundColor = hexToRgba(state.st.bg, 0.5);
703
- tEl.style.webkitTextStroke = '0px';
704
- tEl.style.textShadow = 'none';
705
- tEl.style.borderRadius = state.st.radius + 'px';
706
  } else if (state.st.type === 'outline') {
707
- tEl.style.backgroundColor = 'transparent';
708
  const s = Math.max(1, state.st.fz * 0.04);
709
- tEl.style.webkitTextStroke = `${s}px ${state.st.bg}`;
710
- tEl.style.textShadow = `0 0 ${s}px ${state.st.bg}`;
711
- tEl.style.borderRadius = '0px';
712
- } else {
713
- tEl.style.backgroundColor = 'transparent';
714
- tEl.style.webkitTextStroke = '0px';
715
- tEl.style.textShadow = 'none';
716
- tEl.style.borderRadius = '0px';
717
  }
718
  }
719
 
720
- function syncModeButtons() { document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active')); if (state.st.type !== 'none') { const activeButton = document.querySelector(`.mode-btn[onclick="setMode('${state.st.type}')"]`); if (activeButton) activeButton.classList.add('active'); } }
721
-
722
- function togglePlay() {
723
- if(v.paused) {
724
- stopAtTime = null; // اگر دستی پخش شود، توقف خودکار لغو می‌شود
725
- v.play();
726
- togglePlayIcon(true);
727
- } else {
728
- v.pause();
729
- togglePlayIcon(false);
730
- }
731
- }
732
- function togglePlayIcon(isPlaying) { const overlay = document.getElementById('playOverlay'); overlay.className = isPlaying ? 'playing' : ''; }
733
-
734
  function setFont(f, el) { document.querySelectorAll('.font-btn').forEach(btn => btn.classList.remove('ticked')); el.classList.add('ticked'); state.st.f = f; upd(); }
735
  function setMode(m) { state.st.type = m; syncModeButtons(); upd(); }
736
 
737
  async function render() {
 
 
 
 
 
 
738
  v.pause(); togglePlayIcon(false); document.getElementById('loader').style.display='flex';
739
  const activeSegments = state.segs.filter(s => !s.isHidden);
740
  const pl = { file_id: state.id, segments: activeSegments, video_width: state.w, video_height: state.h, style: { font: state.st.f, fontSize: state.st.fz, primaryColor: state.st.col, outlineColor: state.st.bg, backType: state.st.type, marginV: state.st.y, x: state.st.x, name: state.st.name } };
@@ -751,8 +391,8 @@
751
 
752
  function closeResult() { document.getElementById('resultScreen').style.display='none'; const rv = document.getElementById('resultVideo'); rv.pause(); rv.src = ""; }
753
 
754
- // Touch Logic
755
- let initialY = 0, initialX = 0, initialBottom = 0, initialXState = 0, initialDist = 0, initialFontSize = 0, touchMode = null;
756
  tEl.addEventListener('touchstart', (e) => { if(e.touches.length === 1) { touchMode = 'drag'; initialY = e.touches[0].clientY; initialX = e.touches[0].clientX; initialBottom = state.st.y; initialXState = state.st.x; } else if (e.touches.length === 2) { touchMode = 'pinch'; initialDist = Math.hypot(e.touches[0].clientX - e.touches[1].clientX, e.touches[0].clientY - e.touches[1].clientY); initialFontSize = state.st.fz; } }, {passive: false});
757
  tEl.addEventListener('touchmove', (e) => { if (!touchMode) return; e.preventDefault(); if (touchMode === 'drag' && e.touches.length === 1) { let newBottom = initialBottom + ((initialY - e.touches[0].clientY) * 2); state.st.y = Math.round(newBottom); document.getElementById('pos').value = state.st.y; let diffX = (e.touches[0].clientX - initialX) * 1.5; state.st.x = Math.round(initialXState + diffX); upd(); } else if (touchMode === 'pinch' && e.touches.length === 2) { let dist = Math.hypot(e.touches[0].clientX - e.touches[1].clientX, e.touches[0].clientY - e.touches[1].clientY); let newSize = initialFontSize * (dist / initialDist); state.st.fz = Math.round(Math.max(5, newSize)); document.getElementById('fz').value = state.st.fz; upd(); } }, {passive: false});
758
  tEl.addEventListener('touchend', () => touchMode = null);
 
19
  .new-proj-btn { background: #333; color: #ccc; border: 1px solid #444; padding: 8px 15px; border-radius: 12px; font-size: 0.9rem; cursor: pointer; transition: 0.2s; display: flex; align-items: center; gap: 8px; font-family: inherit; }
20
  .new-proj-btn:hover { background: #444; color: #fff; border-color: #666; }
21
 
22
+ /* --- مدال --- */
23
  .modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); backdrop-filter: blur(5px); z-index: 3000; display: none; align-items: center; justify-content: center; opacity: 0; transition: opacity 0.3s; }
24
  .modal-overlay.show { opacity: 1; }
25
  .modal-box { background: #222; border: 1px solid #444; width: 90%; max-width: 350px; border-radius: 20px; padding: 25px; text-align: center; transform: scale(0.8); transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); box-shadow: 0 10px 40px rgba(0,0,0,0.5); }
26
  .modal-overlay.show .modal-box { transform: scale(1); }
 
27
  .modal-title { font-size: 1.2rem; font-weight: bold; margin-bottom: 10px; color: #fff; }
 
 
28
  .modal-btn { flex: 1; padding: 12px; border-radius: 12px; border: none; font-family: inherit; font-weight: bold; cursor: pointer; transition: 0.2s; }
29
  .btn-confirm { background: var(--primary); color: #fff; }
 
 
 
30
 
31
  /* --- فضای ویدیو --- */
32
  #workspace { width: 100%; display: flex; justify-content: center; background: #000; padding: 20px 0; min-height: 300px; flex-shrink: 0; }
 
35
  video { width: 100%; height: 100%; object-fit: contain; display: block; }
36
  #playOverlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; background: rgba(0,0,0,0.2); z-index: 10; transition: opacity 0.2s; }
37
  #playOverlay.playing { opacity: 0; pointer-events: none; }
38
+ #playOverlay i { font-size: 4rem; color: rgba(255,255,255,0.8); pointer-events: none; }
 
39
 
40
  #activeText {
41
+ position: absolute; white-space: pre; pointer-events: auto; cursor: move;
42
+ padding: 10px 20px; line-height: 1.5; font-weight: bold;
 
 
 
 
 
43
  transition: background-color 0.2s, text-shadow 0.2s, -webkit-text-stroke 0.2s, border-radius 0.2s;
44
+ transform-origin: center center; text-align: center; left: 50%; transform: translateX(-50%);
 
 
 
45
  paint-order: stroke fill;
46
  }
47
 
 
 
 
 
 
 
48
  /* --- نوار ابزار --- */
49
  .controls-bar { display: flex; justify-content: center; gap: 10px; padding: 10px 10px; background: var(--bg); border-bottom: 1px solid #222; position: sticky; top: 115px; z-index: 90; flex-wrap: wrap; }
50
  .tool-btn { display: flex; flex-direction: column; align-items: center; gap: 5px; background: var(--surface); border: 1px solid #333; color: #aaa; cursor: pointer; transition: 0.2s; padding: 8px 10px; border-radius: 12px; min-width: 70px; flex: 1; max-width: 100px; }
51
+ .tool-btn:hover, .tool-btn.active-tool { color: #fff; background: var(--primary); border-color: var(--primary); }
 
 
 
52
 
53
  #toolsContainer { width: 100%; background: var(--panel-bg); overflow: hidden; min-height: 0; transition: none; border-bottom: 1px solid #333; }
54
  .tool-section { display: none; padding: 20px; animation: fadeIn 0.3s ease; max-width: 800px; margin: 0 auto; }
 
56
  @keyframes fadeIn { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } }
57
 
58
  .btn-exp { background: var(--primary); color: #fff; border: none; padding: 10px 25px; border-radius: 12px; font-weight: bold; cursor: pointer; transition: 0.2s; font-family: inherit; }
 
59
 
60
  .btn-save-manual {
61
  width: 100%; background: #00C853; color: white; border: none;
62
  padding: 12px; border-radius: 10px; font-weight: bold; font-family: inherit;
63
  margin-top: 20px; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 8px;
64
  }
 
65
 
66
  .row { margin-bottom: 20px; }
67
  label { display: block; margin-bottom: 8px; color: #ccc; font-size: 0.9rem; }
68
  input[type=range] { width: 100%; accent-color: var(--primary); height: 6px; cursor: pointer; border-radius:3px; }
 
69
 
70
  .style-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 15px; }
71
  .style-card { background: #333; border: 2px solid transparent; border-radius: 10px; padding: 15px; cursor: pointer; display: flex; flex-direction:column; align-items: center; gap: 8px; justify-content: center; min-height: 110px; position: relative; transition: 0.2s; }
72
+ .style-card.selected { border-color: var(--primary); background: #2a2a2a; }
 
73
 
74
  .delete-style-btn {
75
  position: absolute; top: 5px; left: 5px;
76
  background: rgba(255, 50, 50, 0.2); color: #ff5555;
77
  border: none; border-radius: 5px; width: 25px; height: 25px;
78
+ display: flex; align-items: center; justify-content: center; cursor: pointer;
 
79
  }
 
 
 
80
 
81
  .mode-btn { flex: 1; padding: 12px; border-radius: 8px; background: #333; color: #aaa; border: 2px solid transparent; cursor: pointer; }
82
  .mode-btn.active { background: var(--primary); color: #fff; border-color: var(--primary); }
83
 
 
84
  .font-btn { padding: 15px; background: #333; border-radius: 8px; cursor: pointer; border: 2px solid transparent; position: relative; }
85
  .font-btn.ticked { border-color: var(--primary); }
 
86
 
87
  /* --- لیست زیرنویس --- */
88
+ .sub-card { background: #222; border-right: 4px solid #444; border-radius: 8px; margin-bottom: 10px; padding: 15px; display: flex; flex-direction: column; gap: 10px; transition: 0.2s; cursor: pointer; }
89
+ .sub-card.active { background: #2b2535; border-right-color: var(--primary); }
90
+ .sub-time { font-size: 0.75rem; color: #666; font-family: monospace; }
91
+ .sub-text-content { font-size: 1rem; color: #ddd; }
 
 
 
92
  .sub-card.active .sub-text-content { color: #fff; font-weight: bold; }
93
+ .mini-btn { width: 36px; height: 36px; border-radius: 10px; border: none; background: rgba(255,255,255,0.1); color: #fff; cursor: pointer; margin-left: 5px;}
94
+ .mini-btn:hover { background: var(--primary); }
 
 
 
 
 
 
 
 
 
 
 
95
 
96
  #uploadScreen, #loader, #resultScreen { position: fixed; top:0; left:0; width:100%; height:100%; background: var(--bg); z-index: 2000; display: flex; flex-direction: column; justify-content: center; align-items: center; }
97
  #loader, #resultScreen { display: none; }
 
103
 
104
  <div id="exitModal" class="modal-overlay">
105
  <div class="modal-box">
 
106
  <div class="modal-title">شروع پروژه جدید؟</div>
107
+ <div style="margin-bottom:20px; color:#aaa;">تغییرات فعلی از بین می‌روند.</div>
108
+ <div style="display:flex; gap:10px;">
109
+ <button class="modal-btn btn-confirm" onclick="confirmExit()">بله</button>
110
+ <button class="modal-btn" style="background:#333; color:#fff;" onclick="closeExitModal()">لغو</button>
111
  </div>
112
  </div>
113
  </div>
114
 
115
  <div id="uploadScreen">
116
+ <h2 style="margin-bottom: 30px; font-size:1.8rem;">استودیو زیرنویس</h2>
117
  <input type="file" id="fileIn" hidden accept="video/*" onchange="startUpload()">
118
  <button class="btn-exp" style="padding: 15px 40px; font-size: 1.1rem; border-radius: 30px;" onclick="document.getElementById('fileIn').click()">
119
  <i class="fa-solid fa-folder-open"></i> &nbsp; انتخاب ویدیو
120
  </button>
121
  </div>
122
 
123
+ <div id="loader"><div class="spinner"></div><p style="margin-top:20px; color:#fff;">در حال پردازش...</p></div>
124
 
125
  <div id="resultScreen">
126
  <h3 style="color:#fff; margin-bottom: 15px;">ویدیو آماده شد!</h3>
 
128
  <video id="resultVideo" controls style="max-width:100%; max-height:100%; width:100%; height:100%;"></video>
129
  </div>
130
  <div style="display: flex; gap: 15px;">
131
+ <a id="downloadBtn" href="#" download class="btn-exp" style="background: #00C853; text-decoration: none;">دانلود</a>
132
+ <button class="btn-exp" style="background: #444;" onclick="closeResult()">ویرایش</button>
133
  </div>
134
  </div>
135
 
136
  <div class="top-bar">
137
+ <button class="new-proj-btn" onclick="showExitModal()">پروژه جدید</button>
138
+ <button class="btn-exp" onclick="render()" style="padding: 8px 20px;">خروجی نهایی</button>
 
 
139
  </div>
140
 
141
  <div id="workspace">
 
143
  <div id="videoContainer" onclick="togglePlay()">
144
  <video id="vid" playsinline webkit-playsinline></video>
145
  <div id="playOverlay"><i class="fa-solid fa-play"></i></div>
146
+ <div id="subtitleLayer"><div id="activeText"></div></div>
147
  </div>
148
  </div>
149
  </div>
150
 
 
 
 
 
 
 
 
151
  <div class="controls-bar">
152
  <button class="tool-btn" onclick="toggleTool('style')" id="btn-style"><i class="fa-solid fa-layer-group"></i><span>استایل</span></button>
153
  <button class="tool-btn" onclick="toggleTool('appearance')" id="btn-appearance"><i class="fa-solid fa-palette"></i><span>ظاهر</span></button>
 
156
  </div>
157
 
158
  <div id="toolsContainer">
 
 
 
 
 
 
 
 
 
159
  <div id="section-style" class="tool-section">
160
+ <div style="margin-bottom: 10px; color: #888;">استایل‌های ذخیره شده:</div>
161
+ <div class="style-grid" id="savedStylesGrid"></div>
162
+ <div id="noStylesMsg" style="text-align: center; padding: 30px; color: #666;">
163
+ هنوز استایلی ندارید. از دکمه "ذخیره این سبک" در تب ظاهر استفاده کنید.
 
 
 
 
 
164
  </div>
165
  </div>
166
 
167
  <div id="section-appearance" class="tool-section">
168
  <div class="row"><label>سایز متن</label><input type="range" id="fz" min="10" max="300" value="65" oninput="upd()"></div>
169
  <div class="row"><label>موقعیت عمودی</label><input type="range" id="pos" min="0" max="1500" value="150" oninput="upd()"></div>
 
170
  <div class="row" id="radiusRow" style="display:none;"><label>گردی کادر (فقط در پیش‌نمایش)</label><input type="range" id="radius" min="0" max="50" value="12" oninput="upd()"></div>
171
 
172
  <div style="display:flex; gap:15px; margin-bottom:20px;">
 
178
  <button class="btn-exp mode-btn" onclick="setMode('transparent')">شیشه‌ای</button>
179
  <button class="btn-exp mode-btn" onclick="setMode('outline')">فقط دورخط</button>
180
  </div>
181
+ <button class="btn-save-manual" onclick="saveCurrentStyle()"><i class="fa-solid fa-floppy-disk"></i> ذخیره این سبک</button>
 
 
 
 
182
  </div>
183
 
184
  <div id="section-font" class="tool-section">
185
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
186
+ <button class="font-btn ticked" style="font-family:Lalezar;" onclick="setFont('lalezar', this)">لاله زار</button>
187
+ <button class="font-btn" style="font-family:Vazirmatn; font-weight:900;" onclick="setFont('vazir', this)">وزیر ضخیم</button>
188
  <button class="font-btn" style="font-family:Arial;" onclick="setFont('roboto', this)">ساده (Arial)</button>
189
  <button class="font-btn" style="font-family:Impact;" onclick="setFont('bangers', this)">بولد (Impact)</button>
190
  </div>
191
  </div>
192
 
193
  <div id="section-text" class="tool-section">
194
+ <div id="subCardsContainer"></div>
 
 
195
  </div>
196
  </div>
197
 
 
200
  id: null, w: 1080, h: 1920, segs: [],
201
  st: { f: 'lalezar', fz: 65, col: '#ffffff', bg: '#000000', type: 'solid', y: 150, x: 0, radius: 12, name: 'default' }
202
  };
203
+ let curSeg = -1; let manualOverride = false; let stopAtTime = null;
 
204
  const v = document.getElementById('vid'); const tEl = document.getElementById('activeText');
205
  const toolsContainer = document.getElementById('toolsContainer');
206
 
207
+ window.onload = function() { renderSavedStyles(); fit(); };
 
 
 
208
 
209
  function hexToRgba(hex, alpha) {
210
  let c = hex.substring(1).split('');
 
213
  return 'rgba('+[(c>>16)&255, (c>>8)&255, c&255].join(',')+','+alpha+')';
214
  }
215
 
216
+ function getSavedStyles() { try { return JSON.parse(localStorage.getItem('aiStyles') || '[]'); } catch { return []; } }
217
+ function saveStyleToStorage(styleData) { const styles = getSavedStyles(); styles.unshift(styleData); localStorage.setItem('aiStyles', JSON.stringify(styles)); renderSavedStyles(); }
218
+ function deleteStyle(e, id) { e.stopPropagation(); if(confirm('حذف شود؟')) { const styles = getSavedStyles().filter(s => s.id !== id); localStorage.setItem('aiStyles', JSON.stringify(styles)); renderSavedStyles(); } }
219
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  function saveCurrentStyle() {
221
+ const name = prompt("نام استایل:", "استایل شخصی");
222
  if(!name) return;
223
+ saveStyleToStorage({
224
+ id: Date.now(), name: name,
225
+ primaryColor: state.st.col, outlineColor: state.st.bg,
226
+ backType: state.st.type, font: state.st.f, fontSize: state.st.fz
227
+ });
228
+ alert("ذخیره شد.");
 
 
 
 
 
 
 
229
  }
230
 
231
  function applySavedStyle(id) {
232
+ const found = getSavedStyles().find(s => s.id === id);
 
233
  if(found) {
234
+ state.st.col = found.primaryColor; state.st.bg = found.outlineColor;
235
+ state.st.type = found.backType; state.st.f = found.font; state.st.fz = found.fontSize;
236
+ state.st.name = 'custom_' + id;
 
 
 
 
237
  document.getElementById('col').value = state.st.col;
238
  document.getElementById('bgCol').value = state.st.bg;
239
  document.getElementById('fz').value = state.st.fz;
240
  syncModeButtons();
 
241
  document.querySelectorAll('.font-btn').forEach(b => b.classList.remove('ticked'));
242
  let fIdx = 0;
243
+ if(state.st.f === 'vazir') fIdx=1; else if(state.st.f === 'roboto') fIdx=2; else if(state.st.f === 'bangers') fIdx=3;
 
 
244
  document.querySelectorAll('.font-btn')[fIdx].classList.add('ticked');
 
245
  upd();
 
246
  }
247
  }
248
 
 
250
  const grid = document.getElementById('savedStylesGrid');
251
  const msg = document.getElementById('noStylesMsg');
252
  const styles = getSavedStyles();
 
253
  grid.innerHTML = '';
254
+ if(styles.length === 0) { msg.style.display = 'block'; return; }
 
 
 
255
  msg.style.display = 'none';
 
256
  styles.forEach(s => {
257
  const el = document.createElement('div');
258
  el.className = 'style-card';
259
+ let previewStyle = `color: ${s.primaryColor};`;
 
 
260
  if(s.backType === 'solid') previewStyle += `background: ${s.outlineColor};`;
261
  else if(s.backType === 'outline') previewStyle += `-webkit-text-stroke: 2px ${s.outlineColor}; background: transparent;`;
262
+ else previewStyle += `background: rgba(0,0,0,0.5);`;
263
+ el.innerHTML = `<button class="delete-style-btn" onclick="deleteStyle(event, ${s.id})"><i class="fa-solid fa-trash"></i></button><div style="font-size:1.2rem; padding:5px; border-radius:6px; ${previewStyle}">نمونه</div><div style="font-size:0.8rem; color:#aaa;">${s.name}</div>`;
 
 
 
 
 
264
  el.onclick = () => applySavedStyle(s.id);
265
  grid.appendChild(el);
266
  });
267
  }
268
 
269
+ function showExitModal() { document.getElementById('exitModal').style.display = 'flex'; setTimeout(() => document.getElementById('exitModal').classList.add('show'), 10); }
270
+ function closeExitModal() { document.getElementById('exitModal').classList.remove('show'); setTimeout(() => document.getElementById('exitModal').style.display = 'none', 300); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
  function confirmExit() { window.location.reload(); }
272
 
273
  function fit() {
274
  if(!state.w) return;
275
  const ws = document.getElementById('workspace');
276
+ const scale = Math.min((ws.clientWidth - 40) / state.w, (window.innerHeight * 0.6) / state.h);
 
277
  const c = document.getElementById('videoContainer');
278
+ c.style.width = state.w + 'px'; c.style.height = state.h + 'px';
 
279
  document.getElementById('scaler').style.transform = `scale(${scale})`;
280
  ws.style.height = (state.h * scale + 40) + 'px';
281
  }
 
283
 
284
  function toggleTool(toolName) {
285
  document.querySelectorAll('.tool-btn').forEach(b => b.classList.remove('active-tool'));
286
+ document.getElementById('btn-'+toolName)?.classList.add('active-tool');
287
+ document.querySelectorAll('.tool-section').forEach(s => s.style.display = 'none');
288
+ document.getElementById('section-'+toolName).style.display = 'block';
289
+ toolsContainer.classList.add('open');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
  }
291
 
292
  async function startUpload() {
 
306
  }
307
 
308
  function renderSegList() {
309
+ const container = document.getElementById('subCardsContainer'); container.innerHTML = '';
 
310
  state.segs.forEach((s, i) => {
311
+ const card = document.createElement('div'); card.className = 'sub-card'; card.id = 'seg-card-' + i;
312
+ card.innerHTML = `<div style="display:flex; justify-content:space-between; align-items:center;">
313
+ <span class="sub-time">${formatTimeSimple(s.start)}</span>
314
+ <div class="sub-text-content">${s.text}</div>
315
+ <div><button class="mini-btn" onclick="playSeg(event, ${i})"><i class="fa-solid fa-play"></i></button></div>
316
+ </div>`;
317
+ card.onclick = () => { manualOverride = true; selectSegment(i); v.currentTime = s.start + 0.01; if(v.paused) showTextOverlay(i); else v.pause(); };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
318
  container.appendChild(card);
319
  });
320
  }
321
+
322
+ function playSeg(e, i) { e.stopPropagation(); v.currentTime = state.segs[i].start; stopAtTime = state.segs[i].end; v.play(); togglePlayIcon(true); }
323
 
324
+ function formatTimeSimple(sec) { let m = Math.floor(sec/60), s = Math.floor(sec%60); return `${m}:${s<10?'0'+s:s}`; }
325
+ function selectSegment(i) { document.querySelectorAll('.sub-card').forEach(el => el.classList.remove('active')); document.getElementById('seg-card-'+i)?.classList.add('active'); curSeg = i; }
326
+ function showTextOverlay(i) { if(i===-1) tEl.style.opacity=0; else { tEl.innerText=state.segs[i].text; tEl.style.opacity=1; } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
 
328
  v.ontimeupdate = () => {
329
+ if(stopAtTime && v.currentTime >= stopAtTime) { v.pause(); stopAtTime = null; togglePlayIcon(false); return; }
330
+ if(manualOverride && v.paused) return; if(!v.paused) manualOverride=false;
 
 
 
 
 
 
 
 
331
  const idx = state.segs.findIndex(s => v.currentTime >= s.start && v.currentTime <= s.end);
332
+ if(idx !== -1) { if(curSeg!==idx) selectSegment(idx); tEl.style.opacity=1; tEl.innerText=state.segs[idx].text; }
333
+ else { if(!manualOverride) { curSeg=-1; document.querySelectorAll('.sub-card').forEach(el=>el.classList.remove('active')); tEl.style.opacity=0; } }
 
 
 
 
 
 
 
 
 
 
 
334
  };
335
 
336
  function upd() {
 
339
  state.st.col = document.getElementById('col').value;
340
  state.st.bg = document.getElementById('bgCol').value;
341
  state.st.radius = parseInt(document.getElementById('radius').value);
342
+ document.getElementById('radiusRow').style.display = (state.st.type !== 'outline') ? 'flex' : 'none';
 
 
 
 
 
 
343
 
344
  let font = 'Vazirmatn';
345
  if(state.st.f === 'lalezar') font = 'Lalezar';
346
  if(state.st.f === 'bangers') font = 'Impact';
347
  if(state.st.f === 'roboto') font = 'Arial';
348
  tEl.style.fontFamily = font;
 
349
  tEl.style.fontSize = state.st.fz + 'px';
350
  tEl.style.bottom = state.st.y + 'px';
 
 
 
351
  tEl.style.color = state.st.col;
352
 
353
+ tEl.style.webkitTextStroke = '0px'; tEl.style.textShadow = 'none'; tEl.style.backgroundColor = 'transparent'; tEl.style.borderRadius = '0px';
354
 
355
  if(state.st.type === 'solid') {
356
+ tEl.style.backgroundColor = state.st.bg; tEl.style.borderRadius = state.st.radius + 'px';
 
 
 
357
  } else if (state.st.type === 'transparent') {
358
+ tEl.style.backgroundColor = hexToRgba(state.st.bg, 0.5); tEl.style.borderRadius = state.st.radius + 'px';
 
 
 
359
  } else if (state.st.type === 'outline') {
 
360
  const s = Math.max(1, state.st.fz * 0.04);
361
+ tEl.style.webkitTextStroke = `${s}px ${state.st.bg}`; tEl.style.textShadow = `0 0 ${s}px ${state.st.bg}`;
 
 
 
 
 
 
 
362
  }
363
  }
364
 
365
+ function syncModeButtons() { document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active')); if (state.st.type !== 'none') { document.querySelector(`.mode-btn[onclick="setMode('${state.st.type}')"]`)?.classList.add('active'); } }
366
+ function togglePlay() { if(v.paused) { stopAtTime=null; v.play(); togglePlayIcon(true); } else { v.pause(); togglePlayIcon(false); } }
367
+ function togglePlayIcon(isPlaying) { document.getElementById('playOverlay').className = isPlaying ? 'playing' : ''; }
 
 
 
 
 
 
 
 
 
 
 
368
  function setFont(f, el) { document.querySelectorAll('.font-btn').forEach(btn => btn.classList.remove('ticked')); el.classList.add('ticked'); state.st.f = f; upd(); }
369
  function setMode(m) { state.st.type = m; syncModeButtons(); upd(); }
370
 
371
  async function render() {
372
+ // Force sync before render
373
+ state.st.fz = parseInt(document.getElementById('fz').value);
374
+ state.st.y = parseInt(document.getElementById('pos').value);
375
+ state.st.col = document.getElementById('col').value;
376
+ state.st.bg = document.getElementById('bgCol').value;
377
+
378
  v.pause(); togglePlayIcon(false); document.getElementById('loader').style.display='flex';
379
  const activeSegments = state.segs.filter(s => !s.isHidden);
380
  const pl = { file_id: state.id, segments: activeSegments, video_width: state.w, video_height: state.h, style: { font: state.st.f, fontSize: state.st.fz, primaryColor: state.st.col, outlineColor: state.st.bg, backType: state.st.type, marginV: state.st.y, x: state.st.x, name: state.st.name } };
 
391
 
392
  function closeResult() { document.getElementById('resultScreen').style.display='none'; const rv = document.getElementById('resultVideo'); rv.pause(); rv.src = ""; }
393
 
394
+ // Touch Logic (Drag & Pinch)
395
+ let initialY=0, initialX=0, initialBottom=0, initialXState=0, initialDist=0, initialFontSize=0, touchMode=null;
396
  tEl.addEventListener('touchstart', (e) => { if(e.touches.length === 1) { touchMode = 'drag'; initialY = e.touches[0].clientY; initialX = e.touches[0].clientX; initialBottom = state.st.y; initialXState = state.st.x; } else if (e.touches.length === 2) { touchMode = 'pinch'; initialDist = Math.hypot(e.touches[0].clientX - e.touches[1].clientX, e.touches[0].clientY - e.touches[1].clientY); initialFontSize = state.st.fz; } }, {passive: false});
397
  tEl.addEventListener('touchmove', (e) => { if (!touchMode) return; e.preventDefault(); if (touchMode === 'drag' && e.touches.length === 1) { let newBottom = initialBottom + ((initialY - e.touches[0].clientY) * 2); state.st.y = Math.round(newBottom); document.getElementById('pos').value = state.st.y; let diffX = (e.touches[0].clientX - initialX) * 1.5; state.st.x = Math.round(initialXState + diffX); upd(); } else if (touchMode === 'pinch' && e.touches.length === 2) { let dist = Math.hypot(e.touches[0].clientX - e.touches[1].clientX, e.touches[0].clientY - e.touches[1].clientY); let newSize = initialFontSize * (dist / initialDist); state.st.fz = Math.round(Math.max(5, newSize)); document.getElementById('fz').value = state.st.fz; upd(); } }, {passive: false});
398
  tEl.addEventListener('touchend', () => touchMode = null);