Opera10 commited on
Commit
011e659
·
verified ·
1 Parent(s): 8e0c332

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +303 -569
index.html CHANGED
@@ -22,7 +22,6 @@
22
  --muted: #64748b;
23
  --border: #e2e8f0;
24
  --shadow: 0 20px 60px -10px rgba(14,165,233,0.15);
25
- --radius: 20px;
26
  }
27
 
28
  * { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; }
@@ -37,48 +36,29 @@
37
 
38
  /* ===== BACKGROUND ===== */
39
  .bg-scene {
40
- position: fixed;
41
- inset: 0;
42
- z-index: 0;
43
- overflow: hidden;
44
- pointer-events: none;
45
  }
46
  .bg-scene::before {
47
- content: '';
48
- position: absolute;
49
- top: -20%;
50
- right: -10%;
51
- width: 70vw;
52
- height: 70vw;
53
- border-radius: 50%;
54
  background: radial-gradient(circle, rgba(56,189,248,0.18) 0%, transparent 70%);
55
  animation: floatBlob 12s ease-in-out infinite;
56
  }
57
  .bg-scene::after {
58
- content: '';
59
- position: absolute;
60
- bottom: -10%;
61
- left: -5%;
62
- width: 55vw;
63
- height: 55vw;
64
- border-radius: 50%;
65
  background: radial-gradient(circle, rgba(129,140,248,0.14) 0%, transparent 70%);
66
  animation: floatBlob 15s ease-in-out infinite reverse;
67
  }
68
  @keyframes floatBlob {
69
- 0%, 100% { transform: translate(0,0) scale(1); }
70
  33% { transform: translate(3%,4%) scale(1.04); }
71
- 66% { transform: translate(-2%, 2%) scale(0.97); }
72
  }
73
 
74
  /* ===== PARTICLES ===== */
75
  .particles { position: fixed; inset: 0; z-index: 0; pointer-events: none; }
76
- .particle {
77
- position: absolute;
78
- border-radius: 50%;
79
- opacity: 0;
80
- animation: particleFly linear infinite;
81
- }
82
  @keyframes particleFly {
83
  0% { transform: translateY(100vh) scale(0); opacity: 0; }
84
  10% { opacity: 0.6; }
@@ -87,53 +67,29 @@
87
  }
88
 
89
  /* ===== LAYOUT ===== */
90
- .app-wrapper {
91
- position: relative;
92
- z-index: 1;
93
- max-width: 720px;
94
- margin: 0 auto;
95
- padding: 0 16px 40px;
96
- }
97
 
98
  /* ===== HEADER ===== */
99
  header {
100
- padding: 32px 0 20px;
101
- text-align: center;
102
  animation: headerDrop 0.8s cubic-bezier(0.34,1.56,0.64,1) both;
103
  }
104
  @keyframes headerDrop {
105
  0% { opacity:0; transform: translateY(-40px) scale(0.9); }
106
  100% { opacity:1; transform: translateY(0) scale(1); }
107
  }
 
108
 
109
- .logo-wrap {
110
- display: inline-flex;
111
- align-items: center;
112
- justify-content: center;
113
- gap: 12px;
114
- }
115
-
116
- .logo-icon-wrap {
117
- position: relative;
118
- width: 60px;
119
- height: 60px;
120
- }
121
- .logo-icon-wrap svg {
122
- width: 60px;
123
- height: 60px;
124
- filter: drop-shadow(0 4px 16px rgba(14,165,233,0.45));
125
- }
126
  .logo-icon-pulse {
127
- position: absolute;
128
- inset: -6px;
129
- border-radius: 50%;
130
  border: 2px solid rgba(56,189,248,0.5);
131
  animation: pulse-ring 2s ease-out infinite;
132
  }
133
  .logo-icon-pulse2 {
134
- position: absolute;
135
- inset: -14px;
136
- border-radius: 50%;
137
  border: 2px solid rgba(56,189,248,0.25);
138
  animation: pulse-ring 2s ease-out 0.6s infinite;
139
  }
@@ -142,37 +98,46 @@
142
  100% { transform: scale(1.5); opacity: 0; }
143
  }
144
 
145
- .logo-text {
146
- display: flex;
147
- flex-direction: column;
148
- align-items: flex-start;
149
- line-height: 1;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
  }
 
 
151
  .logo-title {
152
- font-size: 2rem;
153
- font-weight: 900;
154
  background: linear-gradient(135deg, var(--sky-deep) 0%, var(--lavender) 100%);
155
- -webkit-background-clip: text;
156
- -webkit-text-fill-color: transparent;
157
- background-clip: text;
158
- letter-spacing: -0.5px;
159
- }
160
- .logo-sub {
161
- font-size: 0.75rem;
162
- color: var(--muted);
163
- font-weight: 500;
164
- letter-spacing: 0.1em;
165
- text-transform: uppercase;
166
  }
 
167
 
168
  /* ===== CARD ===== */
169
  .card {
170
  background: rgba(255,255,255,0.82);
171
- backdrop-filter: blur(24px);
172
- -webkit-backdrop-filter: blur(24px);
173
  border: 1px solid rgba(255,255,255,0.9);
174
- border-radius: 28px;
175
- padding: 28px 24px;
176
  box-shadow: var(--shadow), 0 1px 0 rgba(255,255,255,0.9) inset;
177
  animation: cardRise 0.7s cubic-bezier(0.34,1.2,0.64,1) 0.2s both;
178
  }
@@ -181,121 +146,61 @@
181
  100% { opacity:1; transform: translateY(0) scale(1); }
182
  }
183
 
184
- /* ===== LANGUAGE SELECTORS ===== */
185
- .lang-row {
186
- display: flex;
187
- align-items: flex-end;
188
- gap: 10px;
189
- margin-bottom: 20px;
190
- }
191
- .lang-group {
192
- flex: 1;
193
- position: relative;
194
- }
195
  .lang-label {
196
- display: flex;
197
- align-items: center;
198
- gap: 6px;
199
- font-size: 0.7rem;
200
- font-weight: 700;
201
- color: var(--muted);
202
- text-transform: uppercase;
203
- letter-spacing: 0.08em;
204
- margin-bottom: 6px;
205
- }
206
- .lang-label .dot {
207
- width: 6px;
208
- height: 6px;
209
- border-radius: 50%;
210
- background: var(--sky-deep);
211
  }
212
- .lang-group:last-child .lang-label .dot { background: var(--lavender); }
213
 
214
- .select-wrap {
215
- position: relative;
216
- }
217
  .select-wrap select {
218
- width: 100%;
219
- appearance: none;
220
- -webkit-appearance: none;
221
  background: rgba(248,250,252,0.9);
222
- border: 1.5px solid var(--border);
223
- border-radius: 14px;
224
  padding: 13px 14px 13px 40px;
225
- font-family: 'Vazirmatn', sans-serif;
226
- font-size: 0.9rem;
227
- font-weight: 600;
228
- color: var(--text);
229
- cursor: pointer;
230
- transition: all 0.25s ease;
231
- outline: none;
232
  }
233
  .select-wrap select:focus {
234
- border-color: var(--sky);
235
- background: #fff;
236
  box-shadow: 0 0 0 4px rgba(56,189,248,0.12);
237
  }
238
- .lang-group:last-child .select-wrap select:focus {
239
- border-color: var(--lavender);
240
- box-shadow: 0 0 0 4px rgba(129,140,248,0.12);
241
- }
242
  .select-arrow {
243
- position: absolute;
244
- left: 14px;
245
- top: 50%;
246
- transform: translateY(-50%);
247
- color: var(--muted);
248
- pointer-events: none;
249
- font-size: 0.7rem;
250
- transition: transform 0.2s;
251
  }
252
 
253
- /* swap button */
254
  .swap-btn {
255
- flex-shrink: 0;
256
- width: 44px;
257
- height: 44px;
258
- border-radius: 50%;
259
  background: linear-gradient(135deg, var(--sky-pale), #ede9fe);
260
  border: 1.5px solid rgba(56,189,248,0.3);
261
- display: flex;
262
- align-items: center;
263
- justify-content: center;
264
- cursor: pointer;
265
- transition: all 0.3s cubic-bezier(0.34,1.56,0.64,1);
266
- color: var(--sky-deep);
267
- font-size: 0.85rem;
268
- margin-bottom: 2px;
269
  }
270
  .swap-btn:hover {
271
- transform: rotate(180deg) scale(1.1);
272
  background: linear-gradient(135deg, #bae6fd, #c7d2fe);
273
  box-shadow: 0 4px 16px rgba(56,189,248,0.3);
274
  }
275
 
276
  /* ===== TEXTAREA ===== */
277
- .textarea-wrap {
278
- position: relative;
279
- margin-bottom: 16px;
280
- }
281
  .textarea-wrap textarea {
282
- width: 100%;
283
- min-height: 140px;
284
- resize: none;
285
- border: 1.5px solid var(--border);
286
- border-radius: 18px;
287
  padding: 18px 18px 50px;
288
- font-family: 'Vazirmatn', sans-serif;
289
- font-size: 1.05rem;
290
- line-height: 1.8;
291
- color: var(--text);
292
- background: rgba(248,250,252,0.8);
293
- outline: none;
294
- transition: all 0.3s ease;
295
  }
296
  .textarea-wrap textarea:focus {
297
- border-color: var(--sky);
298
- background: #fff;
299
  box-shadow: 0 0 0 4px rgba(56,189,248,0.1);
300
  }
301
  .textarea-wrap textarea::placeholder { color: #94a3b8; }
@@ -310,431 +215,235 @@
310
  30%,50%,70% { transform: translateX(-6px); }
311
  40%,60% { transform: translateX(6px); }
312
  }
313
-
314
  .textarea-hint {
315
- position: absolute;
316
- bottom: -24px;
317
- right: 0;
318
- font-size: 0.78rem;
319
- color: #fb923c;
320
- font-weight: 600;
321
- opacity: 0;
322
- transform: translateY(-4px);
323
- transition: all 0.3s ease;
324
- display: flex;
325
- align-items: center;
326
- gap: 4px;
327
- }
328
- .textarea-hint.show {
329
- opacity: 1;
330
- transform: translateY(0);
331
  }
332
-
333
  .clear-btn {
334
- position: absolute;
335
- bottom: 14px;
336
- left: 14px;
337
- width: 32px;
338
- height: 32px;
339
- border-radius: 50%;
340
- background: white;
341
- border: 1px solid var(--border);
342
- display: flex;
343
- align-items: center;
344
- justify-content: center;
345
- cursor: pointer;
346
- color: var(--muted);
347
- font-size: 0.8rem;
348
- transition: all 0.2s;
349
- box-shadow: 0 2px 8px rgba(0,0,0,0.06);
350
  }
351
  .clear-btn:hover { color: #ef4444; transform: scale(1.1); }
352
 
353
  /* ===== TRANSLATE BUTTON ===== */
354
- .translate-btn-wrap { margin-top: 28px; }
355
-
356
  .translate-btn {
357
- width: 100%;
358
- padding: 17px 24px;
359
- border-radius: 18px;
360
- border: none;
361
  background: linear-gradient(135deg, var(--sky-deep) 0%, #6366f1 100%);
362
- color: white;
363
- font-family: 'Vazirmatn', sans-serif;
364
- font-size: 1.1rem;
365
- font-weight: 800;
366
- letter-spacing: 0.02em;
367
- cursor: pointer;
368
- display: flex;
369
- align-items: center;
370
- justify-content: center;
371
- gap: 10px;
372
- position: relative;
373
- overflow: hidden;
374
  transition: all 0.3s cubic-bezier(0.34,1.2,0.64,1);
375
  box-shadow: 0 8px 32px rgba(3,105,161,0.35), 0 1px 0 rgba(255,255,255,0.15) inset;
376
  }
377
  .translate-btn::before {
378
- content: '';
379
- position: absolute;
380
- top: 0; left: -100%;
381
- width: 100%;
382
- height: 100%;
383
  background: linear-gradient(90deg, transparent, rgba(255,255,255,0.18), transparent);
384
  transition: left 0.5s ease;
385
  }
386
  .translate-btn:hover::before { left: 100%; }
387
- .translate-btn:hover {
388
- transform: translateY(-2px);
389
- box-shadow: 0 14px 40px rgba(3,105,161,0.4), 0 1px 0 rgba(255,255,255,0.15) inset;
390
- }
391
- .translate-btn:active { transform: scale(0.98) translateY(0); }
392
- .translate-btn:disabled {
393
- opacity: 0.85;
394
- cursor: not-allowed;
395
- transform: none;
396
- }
397
-
398
  .btn-icon-wrap {
399
- width: 34px;
400
- height: 34px;
401
- background: rgba(255,255,255,0.2);
402
- border-radius: 50%;
403
- display: flex;
404
- align-items: center;
405
- justify-content: center;
406
- font-size: 1rem;
407
  }
408
 
409
  /* ===== SPINNER ===== */
410
  .spinner {
411
- width: 22px;
412
- height: 22px;
413
- border: 2.5px solid rgba(255,255,255,0.3);
414
- border-top-color: white;
415
- border-radius: 50%;
416
- animation: spin 0.7s linear infinite;
417
  }
418
  @keyframes spin { to { transform: rotate(360deg); } }
419
 
420
- /* ===== SETTINGS PANEL ===== */
421
  .settings-panel {
422
- margin-top: 18px;
423
- border-radius: 18px;
424
- background: rgba(248,250,252,0.7);
425
- border: 1.5px solid var(--border);
426
- overflow: hidden;
427
- transition: all 0.3s ease;
428
  }
429
  .settings-toggle {
430
- display: flex;
431
- justify-content: space-between;
432
- align-items: center;
433
- padding: 14px 18px;
434
- cursor: pointer;
435
- user-select: none;
436
- list-style: none;
437
  }
438
  .settings-toggle::-webkit-details-marker { display: none; }
439
- .settings-label {
440
- display: flex;
441
- align-items: center;
442
- gap: 8px;
443
- font-size: 0.85rem;
444
- font-weight: 700;
445
- color: var(--muted);
446
- }
447
  .settings-label .badge {
448
  background: linear-gradient(135deg, var(--sky-pale), #ede9fe);
449
- color: var(--ocean);
450
- font-size: 0.65rem;
451
- padding: 2px 8px;
452
- border-radius: 20px;
453
- font-weight: 800;
454
- }
455
- .settings-chevron {
456
- color: var(--muted);
457
- font-size: 0.75rem;
458
- transition: transform 0.3s ease;
459
  }
 
460
  details[open] .settings-chevron { transform: rotate(180deg); }
461
-
462
  .settings-body {
463
- padding: 0 18px 18px;
464
- border-top: 1px solid var(--border);
465
- display: flex;
466
- flex-direction: column;
467
- gap: 16px;
468
  animation: fadeDown 0.3s ease both;
469
  }
470
  @keyframes fadeDown {
471
  from { opacity: 0; transform: translateY(-8px); }
472
  to { opacity: 1; transform: translateY(0); }
473
  }
474
-
475
  .voice-select-wrap { margin-top: 12px; }
476
  .settings-row { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 14px; }
477
  @media (max-width: 500px) { .settings-row { grid-template-columns: 1fr; } }
478
-
479
  .slider-group label {
480
- display: flex;
481
- justify-content: space-between;
482
- align-items: center;
483
- font-size: 0.73rem;
484
- font-weight: 700;
485
- color: var(--muted);
486
- margin-bottom: 6px;
487
  }
488
  .slider-group label span {
489
  background: linear-gradient(135deg, var(--sky-deep), var(--lavender));
490
- -webkit-background-clip: text;
491
- -webkit-text-fill-color: transparent;
492
- font-weight: 800;
493
  }
494
-
495
  input[type="range"] {
496
- -webkit-appearance: none;
497
- width: 100%;
498
- height: 4px;
499
- border-radius: 4px;
500
- background: linear-gradient(to right, var(--sky-deep) 50%, #e2e8f0 50%);
501
- outline: none;
502
- cursor: pointer;
503
  }
504
  input[type="range"]::-webkit-slider-thumb {
505
- -webkit-appearance: none;
506
- width: 18px;
507
- height: 18px;
508
- border-radius: 50%;
509
- background: white;
510
- border: 2.5px solid var(--sky-deep);
511
- box-shadow: 0 2px 8px rgba(3,105,161,0.25);
512
- cursor: pointer;
513
- transition: transform 0.15s;
514
  }
515
  input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.2); }
516
 
 
 
 
 
 
 
 
 
 
517
  /* ===== OUTPUT ===== */
518
  .output-section {
519
- margin-top: 20px;
520
- display: none;
521
- flex-direction: column;
522
- gap: 14px;
523
- opacity: 0;
524
- transform: translateY(16px);
525
  transition: opacity 0.45s ease, transform 0.45s cubic-bezier(0.34,1.2,0.64,1);
526
  }
527
- .output-section.visible {
528
- display: flex;
529
- opacity: 1;
530
- transform: translateY(0);
531
- }
532
 
533
  .output-box {
534
  position: relative;
535
  background: linear-gradient(135deg, rgba(224,242,254,0.6), rgba(237,233,254,0.4));
536
- border: 1.5px solid rgba(56,189,248,0.25);
537
- border-radius: 18px;
538
- padding: 22px 18px 18px;
539
- min-height: 90px;
540
  }
541
  .output-chip {
542
- position: absolute;
543
- top: -12px;
544
- right: 18px;
545
  background: linear-gradient(135deg, var(--sky-deep), var(--lavender));
546
- color: white;
547
- font-size: 0.7rem;
548
- font-weight: 800;
549
- padding: 4px 14px;
550
- border-radius: 20px;
551
- letter-spacing: 0.05em;
552
  box-shadow: 0 4px 12px rgba(3,105,161,0.3);
553
  }
554
  .output-text {
555
- font-size: 1.05rem;
556
- line-height: 1.9;
557
- color: var(--text);
558
- white-space: pre-wrap;
559
- margin-top: 8px;
560
  }
561
  .copy-btn {
562
- position: absolute;
563
- bottom: 14px;
564
- left: 14px;
565
- width: 34px;
566
- height: 34px;
567
- border-radius: 50%;
568
- background: white;
569
- border: 1.5px solid rgba(56,189,248,0.3);
570
- display: flex;
571
- align-items: center;
572
- justify-content: center;
573
- cursor: pointer;
574
- color: var(--sky-deep);
575
- font-size: 0.8rem;
576
- transition: all 0.2s;
577
- box-shadow: 0 2px 10px rgba(3,105,161,0.1);
578
  }
579
  .copy-btn:hover { background: var(--sky-pale); transform: scale(1.1); }
580
 
 
581
  .audio-player-wrap {
582
- background: white;
583
- border: 1.5px solid var(--border);
584
- border-radius: 16px;
585
- padding: 12px 16px;
586
- box-shadow: 0 4px 16px rgba(0,0,0,0.04);
587
- display: flex;
588
- align-items: center;
589
- gap: 10px;
590
  }
591
  .audio-label {
592
- display: flex;
593
- align-items: center;
594
- gap: 6px;
595
- font-size: 0.75rem;
596
- font-weight: 700;
597
- color: var(--muted);
598
- white-space: nowrap;
599
  }
600
  .audio-label i { color: var(--sky-deep); }
601
- audio {
602
- flex: 1;
603
- height: 36px;
604
- border-radius: 8px;
605
- outline: none;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
606
  }
607
 
608
- /* ===== TOAST NOTIFICATIONS ===== */
 
 
 
 
 
 
 
 
609
  .toast-container {
610
- position: fixed;
611
- top: 20px;
612
- right: 50%;
613
- transform: translateX(50%);
614
- z-index: 9999;
615
- display: flex;
616
- flex-direction: column;
617
- gap: 10px;
618
- pointer-events: none;
619
  }
620
  .toast {
621
- background: white;
622
- border-radius: 14px;
623
- padding: 12px 20px;
624
- font-size: 0.88rem;
625
- font-weight: 700;
626
- display: flex;
627
- align-items: center;
628
- gap: 10px;
629
- box-shadow: 0 8px 32px rgba(0,0,0,0.12), 0 1px 0 rgba(255,255,255,0.9) inset;
630
- border: 1.5px solid var(--border);
631
- pointer-events: auto;
632
- animation: toastIn 0.4s cubic-bezier(0.34,1.56,0.64,1) both;
633
- min-width: 240px;
634
- max-width: 90vw;
635
  }
636
  .toast.leaving { animation: toastOut 0.3s ease both; }
637
- @keyframes toastIn {
638
- from { opacity:0; transform: translateY(-20px) scale(0.9); }
639
- to { opacity:1; transform: translateY(0) scale(1); }
640
- }
641
- @keyframes toastOut {
642
- from { opacity:1; transform: translateY(0) scale(1); }
643
- to { opacity:0; transform: translateY(-12px) scale(0.92); }
644
- }
645
- .toast-icon {
646
- width: 30px;
647
- height: 30px;
648
- border-radius: 50%;
649
- display: flex;
650
- align-items: center;
651
- justify-content: center;
652
- font-size: 0.85rem;
653
- flex-shrink: 0;
654
- }
655
  .toast.success .toast-icon { background: #dcfce7; color: #16a34a; }
656
  .toast.error .toast-icon { background: #fee2e2; color: #dc2626; }
657
  .toast.info .toast-icon { background: var(--sky-pale); color: var(--sky-deep); }
658
 
659
  /* ===== FOOTER ===== */
660
- footer {
661
- text-align: center;
662
- padding: 20px 0 30px;
663
- font-size: 0.78rem;
664
- color: var(--muted);
665
- animation: fadeUp 0.6s ease 0.5s both;
666
- }
667
- @keyframes fadeUp {
668
- from { opacity:0; transform: translateY(10px); }
669
- to { opacity:1; transform: translateY(0); }
670
- }
671
- footer .heart { color: #f43f5e; animation: heartBeat 1.5s ease infinite; display: inline-block; }
672
- @keyframes heartBeat {
673
- 0%,100% { transform: scale(1); }
674
- 14% { transform: scale(1.2); }
675
- 28% { transform: scale(1); }
676
- 42% { transform: scale(1.15); }
677
- 70% { transform: scale(1); }
678
- }
679
 
680
  /* ===== SCROLLBAR ===== */
681
  ::-webkit-scrollbar { width: 5px; }
682
  ::-webkit-scrollbar-track { background: transparent; }
683
  ::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 10px; }
684
-
685
- /* ===== DIVIDER ===== */
686
- .section-divider {
687
- display: flex;
688
- align-items: center;
689
- gap: 10px;
690
- margin: 4px 0;
691
- }
692
- .section-divider::before, .section-divider::after {
693
- content: '';
694
- flex: 1;
695
- height: 1px;
696
- background: linear-gradient(to right, transparent, var(--border), transparent);
697
- }
698
- .section-divider span {
699
- font-size: 0.65rem;
700
- font-weight: 800;
701
- color: var(--muted);
702
- letter-spacing: 0.1em;
703
- text-transform: uppercase;
704
- }
705
-
706
- /* ===== SELECT VOICE ===== */
707
- #voiceSelect {
708
- width: 100%;
709
- appearance: none;
710
- -webkit-appearance: none;
711
- background: white;
712
- border: 1.5px solid var(--border);
713
- border-radius: 12px;
714
- padding: 11px 14px 11px 38px;
715
- font-family: 'Vazirmatn', sans-serif;
716
- font-size: 0.85rem;
717
- font-weight: 600;
718
- color: var(--text);
719
- outline: none;
720
- transition: all 0.2s;
721
- cursor: pointer;
722
- }
723
- #voiceSelect:focus {
724
- border-color: var(--sky);
725
- box-shadow: 0 0 0 4px rgba(56,189,248,0.1);
726
- }
727
  </style>
728
  </head>
729
  <body>
730
 
731
- <!-- Background Scene -->
732
  <div class="bg-scene"></div>
733
-
734
- <!-- Floating Particles -->
735
  <div class="particles" id="particles"></div>
736
-
737
- <!-- Toast Container -->
738
  <div class="toast-container" id="toastContainer"></div>
739
 
740
  <div class="app-wrapper">
@@ -745,21 +454,38 @@
745
  <div class="logo-icon-wrap">
746
  <div class="logo-icon-pulse"></div>
747
  <div class="logo-icon-pulse2"></div>
748
- <svg viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
 
 
 
749
  <defs>
750
- <linearGradient id="lg1" x1="0" y1="0" x2="60" y2="60" gradientUnits="userSpaceOnUse">
751
  <stop offset="0%" stop-color="#38bdf8"/>
752
  <stop offset="100%" stop-color="#818cf8"/>
753
  </linearGradient>
754
- <linearGradient id="lg2" x1="0" y1="0" x2="60" y2="60" gradientUnits="userSpaceOnUse">
755
  <stop offset="0%" stop-color="#0ea5e9"/>
756
  <stop offset="100%" stop-color="#6366f1"/>
757
  </linearGradient>
758
  </defs>
759
- <circle cx="30" cy="30" r="28" fill="url(#lg1)" opacity="0.2"/>
760
- <circle cx="30" cy="30" r="22" fill="url(#lg2)"/>
761
- <!-- Letter A stylized -->
762
- <text x="30" y="40" text-anchor="middle" font-family="Georgia,serif" font-weight="900" font-size="26" fill="white">α</text>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
763
  </svg>
764
  </div>
765
  <div class="logo-text">
@@ -873,32 +599,35 @@
873
  <audio id="audioPlayer" controls controlsList="nodownload">
874
  مرورگر شما از پخش صدا پشتیبانی نمی‌کند.
875
  </audio>
 
 
 
 
876
  </div>
877
  </div>
878
 
879
  </div><!-- end .card -->
880
 
881
- <footer>
882
- طراحی شده با <span class="heart">❤️</span> برای ترجمه حرفه‌ای
883
- </footer>
884
  </div>
885
 
886
  <script>
 
 
 
 
 
 
 
887
  // ===== PARTICLES =====
888
- (function spawnParticles() {
889
  const container = document.getElementById('particles');
890
  const colors = ['#38bdf8','#818cf8','#34d399','#fb923c','#a5f3fc'];
891
  for (let i = 0; i < 18; i++) {
892
  const p = document.createElement('div');
893
  p.className = 'particle';
894
  const size = Math.random() * 6 + 3;
895
- p.style.cssText = `
896
- width:${size}px; height:${size}px;
897
- left:${Math.random()*100}%;
898
- background:${colors[Math.floor(Math.random()*colors.length)]};
899
- animation-duration:${8+Math.random()*10}s;
900
- animation-delay:${Math.random()*8}s;
901
- `;
902
  container.appendChild(p);
903
  }
904
  })();
@@ -909,10 +638,7 @@
909
  const container = document.getElementById('toastContainer');
910
  const toast = document.createElement('div');
911
  toast.className = `toast ${type}`;
912
- toast.innerHTML = `
913
- <div class="toast-icon"><i class="fa-solid ${icons[type]}"></i></div>
914
- <span>${msg}</span>
915
- `;
916
  container.appendChild(toast);
917
  setTimeout(() => {
918
  toast.classList.add('leaving');
@@ -920,7 +646,7 @@
920
  }, duration);
921
  }
922
 
923
- // ===== SLIDER TRACK UPDATE =====
924
  function updateSliderTrack(slider) {
925
  const pct = ((slider.value - slider.min) / (slider.max - slider.min)) * 100;
926
  slider.style.background = `linear-gradient(to right, var(--sky-deep) ${pct}%, #e2e8f0 ${pct}%)`;
@@ -929,27 +655,39 @@
929
  const slider = document.getElementById(id+'Slider');
930
  const valEl = document.getElementById(id+'Val');
931
  updateSliderTrack(slider);
932
- slider.addEventListener('input', () => {
933
- valEl.textContent = slider.value;
934
- updateSliderTrack(slider);
935
- });
936
  });
937
 
938
- // ===== DATA =====
939
  let languagesData = {};
940
- const sourceLang = document.getElementById('sourceLang');
941
- const targetLang = document.getElementById('targetLang');
942
- const voiceSelect = document.getElementById('voiceSelect');
943
- const inputText = document.getElementById('inputText');
944
- const translateBtn = document.getElementById('translateBtn');
945
- const btnText = document.getElementById('btnText');
946
  const outputSection = document.getElementById('outputSection');
947
- const outputText = document.getElementById('outputText');
948
- const audioPlayer = document.getElementById('audioPlayer');
949
- const clearBtn = document.getElementById('clearBtn');
950
- const copyBtn = document.getElementById('copyBtn');
951
- const textHint = document.getElementById('textHint');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
952
 
 
953
  async function loadConfig() {
954
  try {
955
  const res = await fetch('/api/config');
@@ -961,7 +699,9 @@
961
  targetLang.add(new Option(lang, lang, isSelected, isSelected));
962
  });
963
  updateVoices('انگلیسی');
964
- } catch (err) {
 
 
965
  showToast('خطا در بارگذاری زبان‌ها', 'error');
966
  }
967
  }
@@ -969,40 +709,27 @@
969
  function updateVoices(langKey) {
970
  voiceSelect.innerHTML = '';
971
  const voices = languagesData[langKey]?.voices || {};
972
- if (Object.keys(voices).length === 0) {
973
- voiceSelect.add(new Option('بدون پشتیبانی صوتی', ''));
974
- return;
975
- }
976
  Object.keys(voices).forEach(vName => voiceSelect.add(new Option(vName, vName)));
977
  }
978
 
979
- targetLang.addEventListener('change', e => updateVoices(e.target.value));
 
980
 
981
- // Swap button
982
  document.getElementById('swapBtn').addEventListener('click', () => {
983
- const sv = sourceLang.value;
984
- const tv = targetLang.value;
985
- if (sv === 'شناسایی خودکار') {
986
- showToast('ابتدا یک زبان مبدأ انتخاب کنید', 'info');
987
- return;
988
- }
989
- // Swap values
990
- for (let i = 0; i < sourceLang.options.length; i++) {
991
- if (sourceLang.options[i].value === tv) { sourceLang.selectedIndex = i; break; }
992
- }
993
- for (let i = 0; i < targetLang.options.length; i++) {
994
- if (targetLang.options[i].value === sv) { targetLang.selectedIndex = i; break; }
995
- }
996
  updateVoices(targetLang.value);
997
- // Also swap text
 
998
  const translated = outputText.textContent;
999
- if (translated) {
1000
- inputText.value = translated;
1001
- outputSection.style.display = 'none';
1002
- outputSection.classList.remove('visible');
1003
- }
1004
  });
1005
 
 
1006
  clearBtn.addEventListener('click', () => {
1007
  inputText.value = '';
1008
  inputText.focus();
@@ -1010,6 +737,7 @@
1010
  setTimeout(() => { outputSection.style.display = 'none'; }, 450);
1011
  });
1012
 
 
1013
  copyBtn.addEventListener('click', () => {
1014
  navigator.clipboard.writeText(outputText.innerText).then(() => {
1015
  copyBtn.innerHTML = '<i class="fa-solid fa-check" style="color:#16a34a"></i>';
@@ -1018,18 +746,25 @@
1018
  });
1019
  });
1020
 
 
 
 
 
 
 
 
 
 
 
 
 
1021
  translateBtn.addEventListener('click', async () => {
1022
  const text = inputText.value.trim();
1023
  if (!text) {
1024
- // Shake animation
1025
  inputText.classList.add('shake');
1026
  textHint.classList.add('show');
1027
- setTimeout(() => {
1028
- inputText.classList.remove('shake');
1029
- }, 500);
1030
- setTimeout(() => {
1031
- textHint.classList.remove('show');
1032
- }, 3000);
1033
  return;
1034
  }
1035
 
@@ -1058,6 +793,7 @@
1058
 
1059
  if (result.success) {
1060
  outputText.innerText = result.translated_text;
 
1061
 
1062
  if (result.audio_url) {
1063
  audioPlayer.src = result.audio_url + '?t=' + Date.now();
@@ -1068,9 +804,7 @@
1068
  }
1069
 
1070
  outputSection.style.display = 'flex';
1071
- requestAnimationFrame(() => {
1072
- requestAnimationFrame(() => outputSection.classList.add('visible'));
1073
- });
1074
  showToast('ترجمه با موفقیت انجام شد!', 'success');
1075
  } else {
1076
  showToast(result.error || 'خطایی رخ داد', 'error');
@@ -1086,4 +820,4 @@
1086
  loadConfig();
1087
  </script>
1088
  </body>
1089
- </html>
 
22
  --muted: #64748b;
23
  --border: #e2e8f0;
24
  --shadow: 0 20px 60px -10px rgba(14,165,233,0.15);
 
25
  }
26
 
27
  * { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; }
 
36
 
37
  /* ===== BACKGROUND ===== */
38
  .bg-scene {
39
+ position: fixed; inset: 0; z-index: 0; overflow: hidden; pointer-events: none;
 
 
 
 
40
  }
41
  .bg-scene::before {
42
+ content: ''; position: absolute; top: -20%; right: -10%;
43
+ width: 70vw; height: 70vw; border-radius: 50%;
 
 
 
 
 
44
  background: radial-gradient(circle, rgba(56,189,248,0.18) 0%, transparent 70%);
45
  animation: floatBlob 12s ease-in-out infinite;
46
  }
47
  .bg-scene::after {
48
+ content: ''; position: absolute; bottom: -10%; left: -5%;
49
+ width: 55vw; height: 55vw; border-radius: 50%;
 
 
 
 
 
50
  background: radial-gradient(circle, rgba(129,140,248,0.14) 0%, transparent 70%);
51
  animation: floatBlob 15s ease-in-out infinite reverse;
52
  }
53
  @keyframes floatBlob {
54
+ 0%,100% { transform: translate(0,0) scale(1); }
55
  33% { transform: translate(3%,4%) scale(1.04); }
56
+ 66% { transform: translate(-2%,2%) scale(0.97); }
57
  }
58
 
59
  /* ===== PARTICLES ===== */
60
  .particles { position: fixed; inset: 0; z-index: 0; pointer-events: none; }
61
+ .particle { position: absolute; border-radius: 50%; opacity: 0; animation: particleFly linear infinite; }
 
 
 
 
 
62
  @keyframes particleFly {
63
  0% { transform: translateY(100vh) scale(0); opacity: 0; }
64
  10% { opacity: 0.6; }
 
67
  }
68
 
69
  /* ===== LAYOUT ===== */
70
+ .app-wrapper { position: relative; z-index: 1; max-width: 720px; margin: 0 auto; padding: 0 16px 40px; }
 
 
 
 
 
 
71
 
72
  /* ===== HEADER ===== */
73
  header {
74
+ padding: 32px 0 20px; text-align: center;
 
75
  animation: headerDrop 0.8s cubic-bezier(0.34,1.56,0.64,1) both;
76
  }
77
  @keyframes headerDrop {
78
  0% { opacity:0; transform: translateY(-40px) scale(0.9); }
79
  100% { opacity:1; transform: translateY(0) scale(1); }
80
  }
81
+ .logo-wrap { display: inline-flex; align-items: center; justify-content: center; gap: 14px; }
82
 
83
+ /* ===== LOGO ICON ===== */
84
+ .logo-icon-wrap { position: relative; width: 64px; height: 64px; }
85
+ .logo-icon-wrap svg { width: 64px; height: 64px; filter: drop-shadow(0 4px 18px rgba(14,165,233,0.5)); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  .logo-icon-pulse {
87
+ position: absolute; inset: -6px; border-radius: 50%;
 
 
88
  border: 2px solid rgba(56,189,248,0.5);
89
  animation: pulse-ring 2s ease-out infinite;
90
  }
91
  .logo-icon-pulse2 {
92
+ position: absolute; inset: -14px; border-radius: 50%;
 
 
93
  border: 2px solid rgba(56,189,248,0.25);
94
  animation: pulse-ring 2s ease-out 0.6s infinite;
95
  }
 
98
  100% { transform: scale(1.5); opacity: 0; }
99
  }
100
 
101
+ /* Orbiting dot animation */
102
+ .orbit-dot {
103
+ position: absolute;
104
+ width: 8px; height: 8px;
105
+ border-radius: 50%;
106
+ background: linear-gradient(135deg, #38bdf8, #818cf8);
107
+ top: 50%; left: 50%;
108
+ transform-origin: 0 0;
109
+ animation: orbit 3s linear infinite;
110
+ }
111
+ .orbit-dot2 {
112
+ position: absolute;
113
+ width: 5px; height: 5px;
114
+ border-radius: 50%;
115
+ background: #34d399;
116
+ top: 50%; left: 50%;
117
+ transform-origin: 0 0;
118
+ animation: orbit 2s linear infinite reverse;
119
+ animation-delay: -1s;
120
+ }
121
+ @keyframes orbit {
122
+ 0% { transform: rotate(0deg) translateX(36px) scale(1); }
123
+ 50% { transform: rotate(180deg) translateX(36px) scale(0.7); }
124
+ 100% { transform: rotate(360deg) translateX(36px) scale(1); }
125
  }
126
+
127
+ .logo-text { display: flex; flex-direction: column; align-items: flex-start; line-height: 1; gap: 4px; }
128
  .logo-title {
129
+ font-size: 2rem; font-weight: 900;
 
130
  background: linear-gradient(135deg, var(--sky-deep) 0%, var(--lavender) 100%);
131
+ -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
 
 
 
 
 
 
 
 
 
 
132
  }
133
+ .logo-sub { font-size: 0.72rem; color: var(--muted); font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; }
134
 
135
  /* ===== CARD ===== */
136
  .card {
137
  background: rgba(255,255,255,0.82);
138
+ backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px);
 
139
  border: 1px solid rgba(255,255,255,0.9);
140
+ border-radius: 28px; padding: 28px 24px;
 
141
  box-shadow: var(--shadow), 0 1px 0 rgba(255,255,255,0.9) inset;
142
  animation: cardRise 0.7s cubic-bezier(0.34,1.2,0.64,1) 0.2s both;
143
  }
 
146
  100% { opacity:1; transform: translateY(0) scale(1); }
147
  }
148
 
149
+ /* ===== LANGUAGE ROW ===== */
150
+ .lang-row { display: flex; align-items: flex-end; gap: 10px; margin-bottom: 20px; }
151
+ .lang-group { flex: 1; }
 
 
 
 
 
 
 
 
152
  .lang-label {
153
+ display: flex; align-items: center; gap: 6px;
154
+ font-size: 0.7rem; font-weight: 700; color: var(--muted);
155
+ text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 6px;
 
 
 
 
 
 
 
 
 
 
 
 
156
  }
157
+ .lang-label .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--sky-deep); }
158
 
159
+ .select-wrap { position: relative; }
 
 
160
  .select-wrap select {
161
+ width: 100%; appearance: none; -webkit-appearance: none;
 
 
162
  background: rgba(248,250,252,0.9);
163
+ border: 1.5px solid var(--border); border-radius: 14px;
 
164
  padding: 13px 14px 13px 40px;
165
+ font-family: 'Vazirmatn', sans-serif; font-size: 0.9rem; font-weight: 600;
166
+ color: var(--text); cursor: pointer; transition: all 0.25s ease; outline: none;
 
 
 
 
 
167
  }
168
  .select-wrap select:focus {
169
+ border-color: var(--sky); background: #fff;
 
170
  box-shadow: 0 0 0 4px rgba(56,189,248,0.12);
171
  }
 
 
 
 
172
  .select-arrow {
173
+ position: absolute; left: 14px; top: 50%;
174
+ transform: translateY(-50%); color: var(--muted);
175
+ pointer-events: none; font-size: 0.7rem;
 
 
 
 
 
176
  }
177
 
 
178
  .swap-btn {
179
+ flex-shrink: 0; width: 44px; height: 44px; border-radius: 50%;
 
 
 
180
  background: linear-gradient(135deg, var(--sky-pale), #ede9fe);
181
  border: 1.5px solid rgba(56,189,248,0.3);
182
+ display: flex; align-items: center; justify-content: center;
183
+ cursor: pointer; transition: all 0.35s cubic-bezier(0.34,1.56,0.64,1);
184
+ color: var(--sky-deep); font-size: 0.85rem; margin-bottom: 2px;
 
 
 
 
 
185
  }
186
  .swap-btn:hover {
187
+ transform: rotate(180deg) scale(1.12);
188
  background: linear-gradient(135deg, #bae6fd, #c7d2fe);
189
  box-shadow: 0 4px 16px rgba(56,189,248,0.3);
190
  }
191
 
192
  /* ===== TEXTAREA ===== */
193
+ .textarea-wrap { position: relative; margin-bottom: 28px; }
 
 
 
194
  .textarea-wrap textarea {
195
+ width: 100%; min-height: 140px; resize: none;
196
+ border: 1.5px solid var(--border); border-radius: 18px;
 
 
 
197
  padding: 18px 18px 50px;
198
+ font-family: 'Vazirmatn', sans-serif; font-size: 1.05rem; line-height: 1.8;
199
+ color: var(--text); background: rgba(248,250,252,0.8);
200
+ outline: none; transition: all 0.3s ease;
 
 
 
 
201
  }
202
  .textarea-wrap textarea:focus {
203
+ border-color: var(--sky); background: #fff;
 
204
  box-shadow: 0 0 0 4px rgba(56,189,248,0.1);
205
  }
206
  .textarea-wrap textarea::placeholder { color: #94a3b8; }
 
215
  30%,50%,70% { transform: translateX(-6px); }
216
  40%,60% { transform: translateX(6px); }
217
  }
 
218
  .textarea-hint {
219
+ position: absolute; bottom: -26px; right: 0;
220
+ font-size: 0.78rem; color: #fb923c; font-weight: 700;
221
+ opacity: 0; transform: translateY(-4px); transition: all 0.3s ease;
222
+ display: flex; align-items: center; gap: 5px;
 
 
 
 
 
 
 
 
 
 
 
 
223
  }
224
+ .textarea-hint.show { opacity: 1; transform: translateY(0); }
225
  .clear-btn {
226
+ position: absolute; bottom: 14px; left: 14px;
227
+ width: 32px; height: 32px; border-radius: 50%;
228
+ background: white; border: 1px solid var(--border);
229
+ display: flex; align-items: center; justify-content: center;
230
+ cursor: pointer; color: var(--muted); font-size: 0.8rem;
231
+ transition: all 0.2s; box-shadow: 0 2px 8px rgba(0,0,0,0.06);
 
 
 
 
 
 
 
 
 
 
232
  }
233
  .clear-btn:hover { color: #ef4444; transform: scale(1.1); }
234
 
235
  /* ===== TRANSLATE BUTTON ===== */
236
+ .translate-btn-wrap { margin-top: 4px; }
 
237
  .translate-btn {
238
+ width: 100%; padding: 17px 24px; border-radius: 18px; border: none;
 
 
 
239
  background: linear-gradient(135deg, var(--sky-deep) 0%, #6366f1 100%);
240
+ color: white; font-family: 'Vazirmatn', sans-serif;
241
+ font-size: 1.1rem; font-weight: 800; letter-spacing: 0.02em;
242
+ cursor: pointer; display: flex; align-items: center; justify-content: center;
243
+ gap: 10px; position: relative; overflow: hidden;
 
 
 
 
 
 
 
 
244
  transition: all 0.3s cubic-bezier(0.34,1.2,0.64,1);
245
  box-shadow: 0 8px 32px rgba(3,105,161,0.35), 0 1px 0 rgba(255,255,255,0.15) inset;
246
  }
247
  .translate-btn::before {
248
+ content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
 
 
 
 
249
  background: linear-gradient(90deg, transparent, rgba(255,255,255,0.18), transparent);
250
  transition: left 0.5s ease;
251
  }
252
  .translate-btn:hover::before { left: 100%; }
253
+ .translate-btn:hover { transform: translateY(-2px); box-shadow: 0 14px 40px rgba(3,105,161,0.4); }
254
+ .translate-btn:active { transform: scale(0.98); }
255
+ .translate-btn:disabled { opacity: 0.85; cursor: not-allowed; transform: none; }
 
 
 
 
 
 
 
 
256
  .btn-icon-wrap {
257
+ width: 34px; height: 34px; background: rgba(255,255,255,0.2);
258
+ border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 1rem;
 
 
 
 
 
 
259
  }
260
 
261
  /* ===== SPINNER ===== */
262
  .spinner {
263
+ width: 22px; height: 22px;
264
+ border: 2.5px solid rgba(255,255,255,0.3); border-top-color: white;
265
+ border-radius: 50%; animation: spin 0.7s linear infinite;
 
 
 
266
  }
267
  @keyframes spin { to { transform: rotate(360deg); } }
268
 
269
+ /* ===== SETTINGS ===== */
270
  .settings-panel {
271
+ margin-top: 18px; border-radius: 18px;
272
+ background: rgba(248,250,252,0.7); border: 1.5px solid var(--border); overflow: hidden;
 
 
 
 
273
  }
274
  .settings-toggle {
275
+ display: flex; justify-content: space-between; align-items: center;
276
+ padding: 14px 18px; cursor: pointer; user-select: none; list-style: none;
 
 
 
 
 
277
  }
278
  .settings-toggle::-webkit-details-marker { display: none; }
279
+ .settings-label { display: flex; align-items: center; gap: 8px; font-size: 0.85rem; font-weight: 700; color: var(--muted); }
 
 
 
 
 
 
 
280
  .settings-label .badge {
281
  background: linear-gradient(135deg, var(--sky-pale), #ede9fe);
282
+ color: var(--ocean); font-size: 0.65rem; padding: 2px 8px;
283
+ border-radius: 20px; font-weight: 800;
 
 
 
 
 
 
 
 
284
  }
285
+ .settings-chevron { color: var(--muted); font-size: 0.75rem; transition: transform 0.3s ease; }
286
  details[open] .settings-chevron { transform: rotate(180deg); }
 
287
  .settings-body {
288
+ padding: 0 18px 18px; border-top: 1px solid var(--border);
289
+ display: flex; flex-direction: column; gap: 16px;
 
 
 
290
  animation: fadeDown 0.3s ease both;
291
  }
292
  @keyframes fadeDown {
293
  from { opacity: 0; transform: translateY(-8px); }
294
  to { opacity: 1; transform: translateY(0); }
295
  }
 
296
  .voice-select-wrap { margin-top: 12px; }
297
  .settings-row { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 14px; }
298
  @media (max-width: 500px) { .settings-row { grid-template-columns: 1fr; } }
 
299
  .slider-group label {
300
+ display: flex; justify-content: space-between; align-items: center;
301
+ font-size: 0.73rem; font-weight: 700; color: var(--muted); margin-bottom: 6px;
 
 
 
 
 
302
  }
303
  .slider-group label span {
304
  background: linear-gradient(135deg, var(--sky-deep), var(--lavender));
305
+ -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-weight: 800;
 
 
306
  }
 
307
  input[type="range"] {
308
+ -webkit-appearance: none; width: 100%; height: 4px;
309
+ border-radius: 4px; background: linear-gradient(to right, var(--sky-deep) 50%, #e2e8f0 50%);
310
+ outline: none; cursor: pointer;
 
 
 
 
311
  }
312
  input[type="range"]::-webkit-slider-thumb {
313
+ -webkit-appearance: none; width: 18px; height: 18px; border-radius: 50%;
314
+ background: white; border: 2.5px solid var(--sky-deep);
315
+ box-shadow: 0 2px 8px rgba(3,105,161,0.25); cursor: pointer; transition: transform 0.15s;
 
 
 
 
 
 
316
  }
317
  input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.2); }
318
 
319
+ #voiceSelect {
320
+ width: 100%; appearance: none; -webkit-appearance: none;
321
+ background: white; border: 1.5px solid var(--border); border-radius: 12px;
322
+ padding: 11px 14px 11px 38px;
323
+ font-family: 'Vazirmatn', sans-serif; font-size: 0.85rem; font-weight: 600;
324
+ color: var(--text); outline: none; transition: all 0.2s; cursor: pointer;
325
+ }
326
+ #voiceSelect:focus { border-color: var(--sky); box-shadow: 0 0 0 4px rgba(56,189,248,0.1); }
327
+
328
  /* ===== OUTPUT ===== */
329
  .output-section {
330
+ margin-top: 20px; display: none; flex-direction: column; gap: 14px;
331
+ opacity: 0; transform: translateY(16px);
 
 
 
 
332
  transition: opacity 0.45s ease, transform 0.45s cubic-bezier(0.34,1.2,0.64,1);
333
  }
334
+ .output-section.visible { display: flex; opacity: 1; transform: translateY(0); }
 
 
 
 
335
 
336
  .output-box {
337
  position: relative;
338
  background: linear-gradient(135deg, rgba(224,242,254,0.6), rgba(237,233,254,0.4));
339
+ border: 1.5px solid rgba(56,189,248,0.25); border-radius: 18px;
340
+ padding: 22px 18px 52px; min-height: 90px;
 
 
341
  }
342
  .output-chip {
343
+ position: absolute; top: -12px; right: 18px;
 
 
344
  background: linear-gradient(135deg, var(--sky-deep), var(--lavender));
345
+ color: white; font-size: 0.7rem; font-weight: 800;
346
+ padding: 4px 14px; border-radius: 20px; letter-spacing: 0.05em;
 
 
 
 
347
  box-shadow: 0 4px 12px rgba(3,105,161,0.3);
348
  }
349
  .output-text {
350
+ font-size: 1.05rem; line-height: 1.9; color: var(--text);
351
+ white-space: pre-wrap; margin-top: 8px;
 
 
 
352
  }
353
  .copy-btn {
354
+ position: absolute; bottom: 14px; left: 14px;
355
+ width: 34px; height: 34px; border-radius: 50%;
356
+ background: white; border: 1.5px solid rgba(56,189,248,0.3);
357
+ display: flex; align-items: center; justify-content: center;
358
+ cursor: pointer; color: var(--sky-deep); font-size: 0.8rem;
359
+ transition: all 0.2s; box-shadow: 0 2px 10px rgba(3,105,161,0.1);
 
 
 
 
 
 
 
 
 
 
360
  }
361
  .copy-btn:hover { background: var(--sky-pale); transform: scale(1.1); }
362
 
363
+ /* ===== AUDIO PLAYER ===== */
364
  .audio-player-wrap {
365
+ background: white; border: 1.5px solid var(--border); border-radius: 16px;
366
+ padding: 10px 14px; box-shadow: 0 4px 16px rgba(0,0,0,0.04);
367
+ display: flex; align-items: center; gap: 8px;
 
 
 
 
 
368
  }
369
  .audio-label {
370
+ display: flex; align-items: center; gap: 5px;
371
+ font-size: 0.72rem; font-weight: 700; color: var(--muted); white-space: nowrap; flex-shrink: 0;
 
 
 
 
 
372
  }
373
  .audio-label i { color: var(--sky-deep); }
374
+ audio { flex: 1; height: 34px; border-radius: 8px; outline: none; min-width: 0; }
375
+
376
+ /* ===== DOWNLOAD BUTTON ===== */
377
+ .download-btn {
378
+ flex-shrink: 0;
379
+ display: flex; align-items: center; gap: 6px;
380
+ padding: 8px 14px; border-radius: 12px; border: none;
381
+ background: linear-gradient(135deg, #0ea5e9, #6366f1);
382
+ color: white; font-family: 'Vazirmatn', sans-serif;
383
+ font-size: 0.78rem; font-weight: 800; cursor: pointer;
384
+ transition: all 0.25s cubic-bezier(0.34,1.3,0.64,1);
385
+ box-shadow: 0 4px 14px rgba(14,165,233,0.35);
386
+ white-space: nowrap; position: relative; overflow: hidden;
387
+ }
388
+ .download-btn::before {
389
+ content: ''; position: absolute; inset: 0;
390
+ background: linear-gradient(135deg, rgba(255,255,255,0.15), transparent);
391
+ border-radius: 12px;
392
+ }
393
+ .download-btn:hover {
394
+ transform: translateY(-2px) scale(1.04);
395
+ box-shadow: 0 8px 22px rgba(14,165,233,0.45);
396
+ }
397
+ .download-btn:active { transform: scale(0.97); }
398
+ .download-btn .dl-icon {
399
+ width: 22px; height: 22px; border-radius: 50%;
400
+ background: rgba(255,255,255,0.25);
401
+ display: flex; align-items: center; justify-content: center;
402
+ font-size: 0.72rem; flex-shrink: 0;
403
  }
404
 
405
+ /* ===== SECTION DIVIDER ===== */
406
+ .section-divider { display: flex; align-items: center; gap: 10px; margin: 4px 0; }
407
+ .section-divider::before, .section-divider::after {
408
+ content: ''; flex: 1; height: 1px;
409
+ background: linear-gradient(to right, transparent, var(--border), transparent);
410
+ }
411
+ .section-divider span { font-size: 0.65rem; font-weight: 800; color: var(--muted); letter-spacing: 0.1em; text-transform: uppercase; }
412
+
413
+ /* ===== TOAST ===== */
414
  .toast-container {
415
+ position: fixed; top: 20px; right: 50%; transform: translateX(50%);
416
+ z-index: 9999; display: flex; flex-direction: column; gap: 10px; pointer-events: none;
 
 
 
 
 
 
 
417
  }
418
  .toast {
419
+ background: white; border-radius: 14px; padding: 12px 20px;
420
+ font-size: 0.88rem; font-weight: 700;
421
+ display: flex; align-items: center; gap: 10px;
422
+ box-shadow: 0 8px 32px rgba(0,0,0,0.12); border: 1.5px solid var(--border);
423
+ pointer-events: auto; animation: toastIn 0.4s cubic-bezier(0.34,1.56,0.64,1) both;
424
+ min-width: 240px; max-width: 90vw;
 
 
 
 
 
 
 
 
425
  }
426
  .toast.leaving { animation: toastOut 0.3s ease both; }
427
+ @keyframes toastIn { from { opacity:0; transform: translateY(-20px) scale(0.9); } to { opacity:1; transform: translateY(0) scale(1); } }
428
+ @keyframes toastOut { from { opacity:1; transform: translateY(0) scale(1); } to { opacity:0; transform: translateY(-12px) scale(0.92); } }
429
+ .toast-icon { width: 30px; height: 30px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 0.85rem; flex-shrink: 0; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
430
  .toast.success .toast-icon { background: #dcfce7; color: #16a34a; }
431
  .toast.error .toast-icon { background: #fee2e2; color: #dc2626; }
432
  .toast.info .toast-icon { background: var(--sky-pale); color: var(--sky-deep); }
433
 
434
  /* ===== FOOTER ===== */
435
+ footer { padding: 16px 0 28px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
436
 
437
  /* ===== SCROLLBAR ===== */
438
  ::-webkit-scrollbar { width: 5px; }
439
  ::-webkit-scrollbar-track { background: transparent; }
440
  ::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 10px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
441
  </style>
442
  </head>
443
  <body>
444
 
 
445
  <div class="bg-scene"></div>
 
 
446
  <div class="particles" id="particles"></div>
 
 
447
  <div class="toast-container" id="toastContainer"></div>
448
 
449
  <div class="app-wrapper">
 
454
  <div class="logo-icon-wrap">
455
  <div class="logo-icon-pulse"></div>
456
  <div class="logo-icon-pulse2"></div>
457
+ <div class="orbit-dot"></div>
458
+ <div class="orbit-dot2"></div>
459
+ <!-- Translator emoji SVG icon -->
460
+ <svg viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
461
  <defs>
462
+ <linearGradient id="lg1" x1="0" y1="0" x2="64" y2="64" gradientUnits="userSpaceOnUse">
463
  <stop offset="0%" stop-color="#38bdf8"/>
464
  <stop offset="100%" stop-color="#818cf8"/>
465
  </linearGradient>
466
+ <linearGradient id="lg2" x1="0" y1="0" x2="64" y2="64" gradientUnits="userSpaceOnUse">
467
  <stop offset="0%" stop-color="#0ea5e9"/>
468
  <stop offset="100%" stop-color="#6366f1"/>
469
  </linearGradient>
470
  </defs>
471
+ <!-- Background circle -->
472
+ <circle cx="32" cy="32" r="30" fill="url(#lg1)" opacity="0.25"/>
473
+ <circle cx="32" cy="32" r="24" fill="url(#lg2)"/>
474
+ <!-- Speech bubble left (source language) -->
475
+ <rect x="8" y="14" width="22" height="14" rx="4" fill="white" opacity="0.9"/>
476
+ <polygon points="14,28 10,33 20,28" fill="white" opacity="0.9"/>
477
+ <!-- Lines inside left bubble -->
478
+ <rect x="11" y="18" width="14" height="2" rx="1" fill="#0ea5e9" opacity="0.7"/>
479
+ <rect x="11" y="22" width="10" height="2" rx="1" fill="#0ea5e9" opacity="0.5"/>
480
+ <!-- Speech bubble right (target language) -->
481
+ <rect x="34" y="28" width="22" height="14" rx="4" fill="white" opacity="0.85"/>
482
+ <polygon points="50,28 40,28 54,23" fill="white" opacity="0.85"/>
483
+ <!-- Lines inside right bubble -->
484
+ <rect x="37" y="32" width="14" height="2" rx="1" fill="#818cf8" opacity="0.7"/>
485
+ <rect x="37" y="36" width="10" height="2" rx="1" fill="#818cf8" opacity="0.5"/>
486
+ <!-- Arrow between bubbles -->
487
+ <path d="M31 24 L33 22 L35 24" stroke="white" stroke-width="1.5" stroke-linecap="round" fill="none" opacity="0.9"/>
488
+ <line x1="33" y1="22" x2="33" y2="30" stroke="white" stroke-width="1.5" stroke-linecap="round" opacity="0.9"/>
489
  </svg>
490
  </div>
491
  <div class="logo-text">
 
599
  <audio id="audioPlayer" controls controlsList="nodownload">
600
  مرورگر شما از پخش صدا پشتیبانی نمی‌کند.
601
  </audio>
602
+ <button class="download-btn" id="downloadBtn" title="دانلود صدا">
603
+ <div class="dl-icon"><i class="fa-solid fa-arrow-down"></i></div>
604
+ دانلود صدا
605
+ </button>
606
  </div>
607
  </div>
608
 
609
  </div><!-- end .card -->
610
 
611
+ <footer></footer>
 
 
612
  </div>
613
 
614
  <script>
615
+ // ===== RTL LANGUAGES MAP =====
616
+ const RTL_LANGS = new Set(['فارسی','عربی','هبری','اردو','پشتو','کردی','سندی','دیوهی']);
617
+
618
+ function isRTL(langName) {
619
+ return RTL_LANGS.has(langName);
620
+ }
621
+
622
  // ===== PARTICLES =====
623
+ (function() {
624
  const container = document.getElementById('particles');
625
  const colors = ['#38bdf8','#818cf8','#34d399','#fb923c','#a5f3fc'];
626
  for (let i = 0; i < 18; i++) {
627
  const p = document.createElement('div');
628
  p.className = 'particle';
629
  const size = Math.random() * 6 + 3;
630
+ p.style.cssText = `width:${size}px;height:${size}px;left:${Math.random()*100}%;background:${colors[Math.floor(Math.random()*colors.length)]};animation-duration:${8+Math.random()*10}s;animation-delay:${Math.random()*8}s;`;
 
 
 
 
 
 
631
  container.appendChild(p);
632
  }
633
  })();
 
638
  const container = document.getElementById('toastContainer');
639
  const toast = document.createElement('div');
640
  toast.className = `toast ${type}`;
641
+ toast.innerHTML = `<div class="toast-icon"><i class="fa-solid ${icons[type]}"></i></div><span>${msg}</span>`;
 
 
 
642
  container.appendChild(toast);
643
  setTimeout(() => {
644
  toast.classList.add('leaving');
 
646
  }, duration);
647
  }
648
 
649
+ // ===== SLIDERS =====
650
  function updateSliderTrack(slider) {
651
  const pct = ((slider.value - slider.min) / (slider.max - slider.min)) * 100;
652
  slider.style.background = `linear-gradient(to right, var(--sky-deep) ${pct}%, #e2e8f0 ${pct}%)`;
 
655
  const slider = document.getElementById(id+'Slider');
656
  const valEl = document.getElementById(id+'Val');
657
  updateSliderTrack(slider);
658
+ slider.addEventListener('input', () => { valEl.textContent = slider.value; updateSliderTrack(slider); });
 
 
 
659
  });
660
 
661
+ // ===== ELEMENTS =====
662
  let languagesData = {};
663
+ const sourceLang = document.getElementById('sourceLang');
664
+ const targetLang = document.getElementById('targetLang');
665
+ const voiceSelect = document.getElementById('voiceSelect');
666
+ const inputText = document.getElementById('inputText');
667
+ const translateBtn = document.getElementById('translateBtn');
668
+ const btnText = document.getElementById('btnText');
669
  const outputSection = document.getElementById('outputSection');
670
+ const outputText = document.getElementById('outputText');
671
+ const audioPlayer = document.getElementById('audioPlayer');
672
+ const clearBtn = document.getElementById('clearBtn');
673
+ const copyBtn = document.getElementById('copyBtn');
674
+ const downloadBtn = document.getElementById('downloadBtn');
675
+ const textHint = document.getElementById('textHint');
676
+
677
+ // ===== TEXT DIRECTION HELPERS =====
678
+ function applyInputDir(langName) {
679
+ const rtl = isRTL(langName) || langName === 'شناسایی خودکار';
680
+ inputText.dir = rtl ? 'rtl' : 'ltr';
681
+ inputText.style.textAlign = rtl ? 'right' : 'left';
682
+ }
683
+
684
+ function applyOutputDir(langName) {
685
+ const rtl = isRTL(langName);
686
+ outputText.dir = rtl ? 'rtl' : 'ltr';
687
+ outputText.style.textAlign = rtl ? 'right' : 'left';
688
+ }
689
 
690
+ // ===== LOAD CONFIG =====
691
  async function loadConfig() {
692
  try {
693
  const res = await fetch('/api/config');
 
699
  targetLang.add(new Option(lang, lang, isSelected, isSelected));
700
  });
701
  updateVoices('انگلیسی');
702
+ applyInputDir('شناسایی خودکار');
703
+ applyOutputDir('انگلیسی');
704
+ } catch {
705
  showToast('خطا در بارگذاری زبان‌ها', 'error');
706
  }
707
  }
 
709
  function updateVoices(langKey) {
710
  voiceSelect.innerHTML = '';
711
  const voices = languagesData[langKey]?.voices || {};
712
+ if (!Object.keys(voices).length) { voiceSelect.add(new Option('بدون پشتیبانی صوتی', '')); return; }
 
 
 
713
  Object.keys(voices).forEach(vName => voiceSelect.add(new Option(vName, vName)));
714
  }
715
 
716
+ sourceLang.addEventListener('change', e => applyInputDir(e.target.value));
717
+ targetLang.addEventListener('change', e => { updateVoices(e.target.value); applyOutputDir(e.target.value); });
718
 
719
+ // ===== SWAP =====
720
  document.getElementById('swapBtn').addEventListener('click', () => {
721
+ const sv = sourceLang.value, tv = targetLang.value;
722
+ if (sv === 'شناسایی خودکار') { showToast('ابتدا یک زبان مبدأ انتخاب کنید', 'info'); return; }
723
+ for (let i = 0; i < sourceLang.options.length; i++) if (sourceLang.options[i].value === tv) { sourceLang.selectedIndex = i; break; }
724
+ for (let i = 0; i < targetLang.options.length; i++) if (targetLang.options[i].value === sv) { targetLang.selectedIndex = i; break; }
 
 
 
 
 
 
 
 
 
725
  updateVoices(targetLang.value);
726
+ applyInputDir(sourceLang.value);
727
+ applyOutputDir(targetLang.value);
728
  const translated = outputText.textContent;
729
+ if (translated) { inputText.value = translated; outputSection.classList.remove('visible'); setTimeout(() => { outputSection.style.display = 'none'; }, 450); }
 
 
 
 
730
  });
731
 
732
+ // ===== CLEAR =====
733
  clearBtn.addEventListener('click', () => {
734
  inputText.value = '';
735
  inputText.focus();
 
737
  setTimeout(() => { outputSection.style.display = 'none'; }, 450);
738
  });
739
 
740
+ // ===== COPY =====
741
  copyBtn.addEventListener('click', () => {
742
  navigator.clipboard.writeText(outputText.innerText).then(() => {
743
  copyBtn.innerHTML = '<i class="fa-solid fa-check" style="color:#16a34a"></i>';
 
746
  });
747
  });
748
 
749
+ // ===== DOWNLOAD =====
750
+ downloadBtn.addEventListener('click', () => {
751
+ const src = audioPlayer.src;
752
+ if (!src) return;
753
+ const a = document.createElement('a');
754
+ a.href = src;
755
+ a.download = 'alpha-translator-audio.mp3';
756
+ a.click();
757
+ showToast('در حال دانلود...', 'success', 2000);
758
+ });
759
+
760
+ // ===== TRANSLATE =====
761
  translateBtn.addEventListener('click', async () => {
762
  const text = inputText.value.trim();
763
  if (!text) {
 
764
  inputText.classList.add('shake');
765
  textHint.classList.add('show');
766
+ setTimeout(() => inputText.classList.remove('shake'), 500);
767
+ setTimeout(() => textHint.classList.remove('show'), 3000);
 
 
 
 
768
  return;
769
  }
770
 
 
793
 
794
  if (result.success) {
795
  outputText.innerText = result.translated_text;
796
+ applyOutputDir(targetLang.value);
797
 
798
  if (result.audio_url) {
799
  audioPlayer.src = result.audio_url + '?t=' + Date.now();
 
804
  }
805
 
806
  outputSection.style.display = 'flex';
807
+ requestAnimationFrame(() => requestAnimationFrame(() => outputSection.classList.add('visible')));
 
 
808
  showToast('ترجمه با موفقیت انجام شد!', 'success');
809
  } else {
810
  showToast(result.error || 'خطایی رخ داد', 'error');
 
820
  loadConfig();
821
  </script>
822
  </body>
823
+ </html>