Hamed744 commited on
Commit
68f7624
·
verified ·
1 Parent(s): 8020536

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +230 -215
index.html CHANGED
@@ -13,10 +13,10 @@
13
  --panel-bg: #FFFFFF;
14
  --panel-border: #E8EEF3;
15
  --text-primary: #121826;
16
- --text-secondary: #5C677D;
17
- --accent-primary: #3B82F6;
18
  --accent-primary-hover: #2563EB;
19
- --accent-secondary: #10B981;
20
  --accent-secondary-hover: #059669;
21
  --input-bg: #F8FAFC;
22
  --input-border-focus: var(--accent-primary);
@@ -30,8 +30,9 @@
30
  --transition-bounce: all 0.4s cubic-bezier(0.68, -0.55, 0.27, 1.55);
31
 
32
  /* Custom Audio Player Colors */
33
- --waveform-color-active: #3B82F6; /* Matches accent-primary */
34
  --waveform-color-inactive: #D0D9E6; /* Lighter shade for inactive part of waveform */
 
35
  }
36
 
37
  @keyframes fadeInDown {
@@ -292,7 +293,7 @@
292
 
293
 
294
  /* --- مودال گالری گویندگان (اختصاصی) --- */
295
- #speaker-modal .modal-dialog { /* Target specific modal dialog */
296
  max-width: 700px;
297
  max-height: 85vh; overflow-y: auto;
298
  }
@@ -456,7 +457,7 @@
456
  vertical-align: middle;
457
  }
458
 
459
- /* --- Output Section (now a container for status/loading/player) --- */
460
  #output-section {
461
  margin-top: 3rem;
462
  display: flex;
@@ -464,28 +465,23 @@
464
  justify-content: center;
465
  flex-direction: column;
466
  gap: 1.5rem;
467
- min-height: 200px;
468
  position: relative;
469
  box-sizing: border-box;
470
- padding: 1.5rem; /* Default padding for status/loading */
471
- }
472
- /* Style for status message and loading when visible */
473
- #output-section #status-message,
474
- #output-section #loading-animation-wrapper {
475
- padding: 2rem;
476
- background-color: var(--input-bg);
477
  border-radius: var(--radius-card);
478
  border: 2px dashed var(--panel-border);
479
  box-shadow: var(--shadow-subtle) inset;
480
- width: 100%;
481
- box-sizing: border-box;
482
  }
483
- /* When output section has content (the player), remove its own styling */
484
  #output-section.has-content {
485
- background-color: transparent;
486
- border: none;
487
- box-shadow: none;
488
- padding: 0; /* Player wrapper will handle padding */
 
489
  min-height: auto; /* Shrink to fit player content */
490
  }
491
  #output-section.has-content #status-message,
@@ -559,215 +555,206 @@
559
  }
560
 
561
  /* --- Custom Audio Player Styles --- */
562
- #audio-player-wrapper {
 
563
  width: 100%;
564
- background-color: var(--panel-bg);
565
- border-radius: var(--radius-card);
566
- box-shadow: var(--shadow-medium);
567
- border: 1px solid var(--panel-border);
568
- padding: 1.5rem;
569
- display: flex;
570
  flex-direction: column;
571
  gap: 1.2rem;
572
- box-sizing: border-box;
573
  }
574
 
575
- .audio-player-header {
576
  display: flex;
577
  align-items: center;
578
  justify-content: flex-end; /* Align to right for RTL */
579
- gap: 0.8rem;
580
- padding-bottom: 0.8rem;
581
- border-bottom: 1px solid var(--panel-border);
582
- margin-bottom: 0.8rem;
 
 
 
583
  }
584
- .audio-player-header .audio-player-title {
585
- font-size: 1.1em;
586
- font-weight: 600;
587
- color: var(--text-primary);
588
- margin-right: auto; /* Pushes icons to the right */
589
  }
590
- .audio-player-header .audio-player-icons svg {
591
- width: 22px;
592
- height: 22px;
593
- color: var(--text-secondary); /* Match the grey from image */
 
 
594
  vertical-align: middle;
595
- margin-left: 0.5rem; /* Space between icons */
596
- }
597
- .audio-player-header .audio-player-icons svg:last-child {
598
- margin-left: 0;
599
- }
600
-
601
- #custom-audio-player {
602
- background-color: transparent;
603
- border: none;
604
- box-shadow: none;
605
- padding: 0;
606
- border-radius: 0;
607
- display: flex;
608
- flex-direction: column;
609
- gap: 1.2rem;
610
- width: 100%;
611
- box-sizing: border-box;
612
  }
613
 
614
- .audio-waveform-wrapper {
615
  display: flex;
616
  align-items: center;
617
  gap: 0.8rem;
618
  width: 100%;
 
619
  }
620
  .audio-time {
621
  font-size: 0.9em;
622
  color: var(--text-secondary);
623
- min-width: 40px;
624
  text-align: center;
625
- font-variant-numeric: tabular-nums; /* Align numbers */
626
- }
627
- .audio-current-time {
628
- text-align: right; /* Time on the left of waveform for RTL */
629
- }
630
- .audio-total-time {
631
- text-align: left; /* Time on the right of waveform for RTL */
632
  }
633
 
634
- .audio-waveform-display {
 
635
  flex-grow: 1;
636
- height: 60px; /* Height of the waveform area */
637
  position: relative;
638
- border-radius: 4px;
639
- overflow: hidden; /* To contain the progress fill */
640
- /* Static background to mimic waveform bars */
641
- background-image:
642
- linear-gradient(to top, var(--waveform-color-inactive) 20%, transparent 20%),
643
- linear-gradient(to top, var(--waveform-color-inactive) 60%, transparent 60%),
644
- linear-gradient(to top, var(--waveform-color-inactive) 40%, transparent 40%),
645
- linear-gradient(to top, var(--waveform-color-inactive) 80%, transparent 80%),
646
- linear-gradient(to top, var(--waveform-color-inactive) 30%, transparent 30%),
647
- linear-gradient(to top, var(--waveform-color-inactive) 70%, transparent 70%),
648
- linear-gradient(to top, var(--waveform-color-inactive) 50%, transparent 50%),
649
- linear-gradient(to top, var(--waveform-color-inactive) 90%, transparent 90%),
650
- linear-gradient(to top, var(--waveform-color-inactive) 25%, transparent 25%),
651
- linear-gradient(to top, var(--waveform-color-inactive) 65%, transparent 65%),
652
- linear-gradient(to top, var(--waveform-color-inactive) 45%, transparent 45%),
653
- linear-gradient(to top, var(--waveform-color-inactive) 85%, transparent 85%);
654
- background-size: 4px 100%; /* Width of each bar */
655
- background-repeat: repeat-x;
656
- background-position: 0 0;
657
- border: 1px dashed var(--waveform-color-inactive); /* Dashed line effect */
658
- }
659
-
660
- .audio-progress-fill {
661
  position: absolute;
662
  top: 0;
663
  left: 0;
 
664
  height: 100%;
665
- width: 0%; /* Will be updated by JS */
666
- /* Active background to fill waveform bars as progress */
667
- background-image:
668
- linear-gradient(to top, var(--waveform-color-active) 20%, transparent 20%),
669
- linear-gradient(to top, var(--waveform-color-active) 60%, transparent 60%),
670
- linear-gradient(to top, var(--waveform-color-active) 40%, transparent 40%),
671
- linear-gradient(to top, var(--waveform-color-active) 80%, transparent 80%),
672
- linear-gradient(to top, var(--waveform-color-active) 30%, transparent 30%),
673
- linear-gradient(to top, var(--waveform-color-active) 70%, transparent 70%),
674
- linear-gradient(to top, var(--waveform-color-active) 50%, transparent 50%),
675
- linear-gradient(to top, var(--waveform-color-active) 90%, transparent 90%),
676
- linear-gradient(to top, var(--waveform-color-active) 25%, transparent 25%),
677
- linear-gradient(to top, var(--waveform-color-active) 65%, transparent 65%),
678
- linear-gradient(to top, var(--waveform-color-active) 45%, transparent 45%),
679
- linear-gradient(to top, var(--waveform-color-active) 85%, transparent 85%);
680
- background-size: 4px 100%;
681
- background-repeat: repeat-x;
682
- background-position: 0 0;
683
- transition: width 0.1s linear; /* Smooth progress animation */
684
- }
685
-
686
-
687
- .audio-controls-bottom {
688
  display: flex;
689
- justify-content: space-between;
690
- align-items: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
691
  width: 100%;
 
 
 
 
 
 
692
  }
693
 
694
- .audio-left-group, .audio-center-group, .audio-right-group {
 
695
  display: flex;
 
696
  align-items: center;
697
- gap: 0.5rem;
 
698
  }
699
 
700
- .audio-center-group {
701
- gap: 1.5rem; /* More space for main controls */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
702
  }
703
 
704
- .audio-controls-bottom button {
705
  background: none;
706
  border: none;
707
  cursor: pointer;
708
- padding: 8px;
 
 
709
  border-radius: 50%;
710
  display: flex;
711
  align-items: center;
712
  justify-content: center;
713
- transition: background-color 0.2s, transform 0.2s;
714
- color: var(--text-secondary);
 
715
  }
716
- .audio-controls-bottom button:hover {
717
- background-color: var(--input-bg);
718
  transform: scale(1.05);
719
  }
720
- .audio-controls-bottom button:active {
721
  transform: scale(0.95);
722
  }
723
-
724
- .audio-controls-bottom button svg {
725
- width: 24px;
726
- height: 24px;
727
  fill: currentColor;
728
  }
729
 
730
- /* Specific button styling */
731
- .audio-play-pause-btn {
732
- width: 50px;
733
- height: 50px;
734
- background-color: var(--accent-primary);
735
- color: #fff;
736
- border-radius: 50%;
737
- box-shadow: 0 4px 10px rgba(59, 130, 246, 0.3);
738
  }
739
- .audio-play-pause-btn:hover {
740
- background-color: var(--accent-primary-hover);
741
- transform: scale(1.08);
742
- box-shadow: 0 6px 15px rgba(59, 130, 246, 0.4);
 
 
 
 
743
  }
744
- .audio-play-pause-btn:active {
745
- transform: scale(0.95);
 
 
 
 
 
746
  }
747
- .audio-play-pause-btn svg {
748
- width: 32px;
749
- height: 32px;
750
  }
751
 
752
  .audio-speed-btn {
753
  font-family: var(--app-font);
754
  font-size: 0.9em;
755
  font-weight: 600;
756
- background-color: var(--input-bg);
757
- border: 1px solid var(--panel-border);
758
  padding: 6px 12px;
759
  border-radius: 8px;
760
  min-width: 40px;
761
  text-align: center;
762
  color: var(--text-primary);
 
 
 
763
  }
764
  .audio-speed-btn:hover {
765
- background-color: #EAF0F6;
766
- border-color: var(--accent-primary);
767
  }
768
 
769
  /* Hide the default audio player */
770
- #audio-player { display: none !important; }
771
  </style>
772
  </head>
773
  <body>
@@ -837,44 +824,41 @@
837
  </div>
838
 
839
  <!-- Custom Audio Player Structure -->
840
- <div id="audio-player-wrapper" style="display: none;">
841
- <div class="audio-player-header">
842
- <div class="audio-player-icons">
843
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-music"><path d="M9 18V5l12-2v13"></path><circle cx="6" cy="18" r="3"></circle><circle cx="18" cy="16" r="3"></circle></svg>
844
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-headphones"><path d="M3 18v-6a9 9 0 0 1 18 0v6"></path><path d="M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3zM3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z"></path></svg>
845
- </div>
846
- <span class="audio-player-title">فایل صوتی</span>
847
  </div>
848
- <div id="custom-audio-player">
849
- <div class="audio-waveform-wrapper">
850
- <span class="audio-time audio-current-time">0:00</span>
851
- <div class="audio-waveform-display">
852
- <div class="audio-progress-fill"></div>
853
- </div>
854
- <span class="audio-time audio-total-time">0:00</span>
855
- </div>
856
- <div class="audio-controls-bottom">
857
- <div class="audio-left-group">
858
- <button class="audio-volume-btn">
859
- <svg viewBox="0 0 24 24" class="volume-high-icon"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"></path></svg>
860
- <svg viewBox="0 0 24 24" class="volume-mute-icon" style="display:none;"><path d="M7 9v6h4l5 5V4L11 9H7zM16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zM19 12c0 .94-.23 1.82-.68 2.6L19 14.88c.45-.88.7-1.88.7-2.88 0-4.01-2.99-7.14-7-8.05v2.06c2.89.86 5 3.54 5 6.71zM4.55 4L2 6.55 9.45 14H7v6h4l5 5V14.55l4.05 4.05L22 18 12 8 4.55 4z"></path></svg>
861
- </button>
862
- <button class="audio-speed-btn">1x</button>
863
- </div>
864
- <div class="audio-center-group">
865
- <button class="audio-skip-backward-btn">
866
- <svg viewBox="0 0 24 24"><path d="M11 16V8l-4 4 4 4zm4-12v16l7-8-7-8z"></path></svg>
867
- </button>
868
- <button class="audio-play-pause-btn">
869
- <svg viewBox="0 0 24 24" class="play-icon"><path d="M8 5v14l11-7z"></path></svg>
870
- <svg viewBox="0 0 24 24" class="pause-icon" style="display:none;"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"></path></svg>
871
- </button>
872
- <button class="audio-skip-forward-btn">
873
- <svg viewBox="0 0 24 24"><path d="M13 16V8l4 4-4 4zM9 4v16L2 12l7-8z"></path></svg>
874
- </button>
875
- </div>
876
- <div class="audio-right-group"></div>
877
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
878
  </div>
879
  </div>
880
  <audio id="hidden-audio-player" style="display: none;"></audio>
@@ -946,7 +930,7 @@
946
  { id: "Orus", name: "بردیا (مرد)", desc: "ورزشی و پرهیجان" },
947
  { id: "Aoede", name: "ترانه (زن)", desc: "موزیکال و خوش‌آهنگ" },
948
  { id: "Callirrhoe", name: "نیما (مرد)", desc: "روایتگر و قصه‌گو" },
949
- { id: "Autonoe", name: "هستی (زن)", desc: "طبیعی و خودمانی" },
950
  { id: "Enceladus", name: "کامیار (مرد)", desc: "مصمم �� جدی" },
951
  { id: "Iapetus", name: "ستاره (زن)", desc: "درخشان و گیرا" },
952
  { id: "Puck", name: "پویا (مرد)", desc: "بازیگوش و سرزنده" },
@@ -981,22 +965,23 @@
981
 
982
  // --- Custom Audio Player Elements ---
983
  const hiddenAudioPlayer = document.getElementById('hidden-audio-player');
984
- const audioPlayerWrapper = document.getElementById('audio-player-wrapper'); // New wrapper for header + player
985
- const audioCurrentTimeSpan = audioPlayerWrapper.querySelector('.audio-current-time');
986
- const audioTotalTimeSpan = audioPlayerWrapper.querySelector('.audio-total-time');
987
- const audioProgressFill = audioPlayerWrapper.querySelector('.audio-progress-fill');
988
- const playPauseBtn = audioPlayerWrapper.querySelector('.audio-play-pause-btn');
989
  const playIcon = playPauseBtn.querySelector('.play-icon');
990
  const pauseIcon = playPauseBtn.querySelector('.pause-icon');
991
- const skipBackwardBtn = audioPlayerWrapper.querySelector('.audio-skip-backward-btn');
992
- const skipForwardBtn = audioPlayerWrapper.querySelector('.audio-skip-forward-btn');
993
- const volumeBtn = audioPlayerWrapper.querySelector('.audio-volume-btn');
994
  const volumeHighIcon = volumeBtn.querySelector('.volume-high-icon');
995
  const volumeMuteIcon = volumeBtn.querySelector('.volume-mute-icon');
996
- const speedBtn = audioPlayerWrapper.querySelector('.audio-speed-btn');
997
 
998
  let currentPlaybackSpeedIndex = 0;
999
  const playbackSpeeds = [1.0, 1.25, 1.5, 1.75, 2.0, 0.75, 0.5]; // Common speeds
 
1000
 
1001
  // Character Counter
1002
  const charCountSpan = document.getElementById('char-count');
@@ -1129,6 +1114,21 @@
1129
 
1130
  // --- Custom Audio Player Functionality ---
1131
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1132
  // Helper function to format time (e.g., 0:06)
1133
  function formatTime(seconds) {
1134
  if (isNaN(seconds) || seconds < 0) return '0:00';
@@ -1154,11 +1154,24 @@
1154
 
1155
  if (isFinite(duration) && duration > 0) {
1156
  audioTotalTimeSpan.textContent = formatTime(duration);
1157
- const progress = (currentTime / duration) * 100;
1158
- audioProgressFill.style.width = `${progress}%`;
 
 
 
 
 
 
 
 
 
1159
  } else {
1160
  audioTotalTimeSpan.textContent = '0:00';
1161
- audioProgressFill.style.width = '0%';
 
 
 
 
1162
  }
1163
  }
1164
 
@@ -1200,7 +1213,10 @@
1200
 
1201
  // Listen to hidden audio player events to update UI
1202
  hiddenAudioPlayer.addEventListener('timeupdate', updatePlayerUI);
1203
- hiddenAudioPlayer.addEventListener('loadedmetadata', updatePlayerUI);
 
 
 
1204
  hiddenAudioPlayer.addEventListener('play', updatePlayerUI);
1205
  hiddenAudioPlayer.addEventListener('pause', updatePlayerUI);
1206
  hiddenAudioPlayer.addEventListener('ended', () => {
@@ -1210,8 +1226,8 @@
1210
 
1211
  // --- State Management Functions ---
1212
  function showLoadingState() {
1213
- audioPlayerWrapper.style.display = 'none'; // Hide custom player
1214
- outputSection.classList.remove('has-content'); // Remove has-content class
1215
  statusMessage.style.display = 'none';
1216
  loadingAnimationWrapper.style.display = 'flex';
1217
  generateBtn.disabled = true;
@@ -1228,17 +1244,16 @@
1228
  loadingAnimationWrapper.style.display = 'none';
1229
  if (isSuccess) {
1230
  statusMessage.style.display = 'none';
1231
- audioPlayerWrapper.style.display = 'flex'; // Show custom player wrapper
1232
  outputSection.classList.add('has-content'); // Add class to output section parent
1233
 
1234
- // Ensure play/pause button is in correct state, and audio starts playing
1235
- updatePlayerUI();
1236
- hiddenAudioPlayer.play();
1237
  } else {
1238
  statusMessage.textContent = message || 'خطایی رخ داد. لطفاً دوباره تلاش کنید.';
1239
  statusMessage.style.display = 'block';
1240
- audioPlayerWrapper.style.display = 'none'; // Hide custom player wrapper
1241
- outputSection.classList.remove('has-content'); // Remove class
1242
  hiddenAudioPlayer.pause(); // Stop any potential audio
1243
  hiddenAudioPlayer.src = ''; // Clear audio source
1244
  }
@@ -1346,7 +1361,7 @@
1346
  form.addEventListener('submit', generateAudio);
1347
 
1348
  // Hide custom audio player and show status message on load
1349
- audioPlayerWrapper.style.display = 'none';
1350
  statusMessage.style.display = 'block';
1351
  loadingAnimationWrapper.style.display = 'none';
1352
  outputSection.classList.remove('has-content'); // Ensure initial state is clean
 
13
  --panel-bg: #FFFFFF;
14
  --panel-border: #E8EEF3;
15
  --text-primary: #121826;
16
+ --text-secondary: #5C677D; /* Main grey for secondary text and icons */
17
+ --accent-primary: #3B82F6; /* Blue for highlights */
18
  --accent-primary-hover: #2563EB;
19
+ --accent-secondary: #10B981; /* Green for success/other highlights */
20
  --accent-secondary-hover: #059669;
21
  --input-bg: #F8FAFC;
22
  --input-border-focus: var(--accent-primary);
 
30
  --transition-bounce: all 0.4s cubic-bezier(0.68, -0.55, 0.27, 1.55);
31
 
32
  /* Custom Audio Player Colors */
33
+ --waveform-color-active: #3B82F6; /* Blue for active part of waveform */
34
  --waveform-color-inactive: #D0D9E6; /* Lighter shade for inactive part of waveform */
35
+ --waveform-dashed-line-color: #E0E4E9; /* Very light grey for the dashed line */
36
  }
37
 
38
  @keyframes fadeInDown {
 
293
 
294
 
295
  /* --- مودال گالری گویندگان (اختصاصی) --- */
296
+ #speaker-modal .modal-dialog {
297
  max-width: 700px;
298
  max-height: 85vh; overflow-y: auto;
299
  }
 
457
  vertical-align: middle;
458
  }
459
 
460
+ /* --- Output Section (now the card container for status/loading/player) --- */
461
  #output-section {
462
  margin-top: 3rem;
463
  display: flex;
 
465
  justify-content: center;
466
  flex-direction: column;
467
  gap: 1.5rem;
468
+ min-height: 200px; /* Default height for empty/loading state */
469
  position: relative;
470
  box-sizing: border-box;
471
+ padding: 2rem; /* Default padding for status/loading */
472
+ background-color: var(--input-bg); /* Default background for empty/loading */
 
 
 
 
 
473
  border-radius: var(--radius-card);
474
  border: 2px dashed var(--panel-border);
475
  box-shadow: var(--shadow-subtle) inset;
476
+ transition: var(--transition-smooth), border-color 0.5s, box-shadow 0.5s;
 
477
  }
478
+ /* When output section has content (the player), remove its own styling and apply new */
479
  #output-section.has-content {
480
+ background-color: var(--panel-bg); /* Solid white background like the image */
481
+ border-style: solid; /* Solid border for active state */
482
+ border-color: var(--panel-border); /* Standard border */
483
+ box-shadow: var(--shadow-medium); /* Nicer shadow for active state */
484
+ padding: 0; /* No padding on the main output-section, inner div handles it */
485
  min-height: auto; /* Shrink to fit player content */
486
  }
487
  #output-section.has-content #status-message,
 
555
  }
556
 
557
  /* --- Custom Audio Player Styles --- */
558
+ #audio-player-content {
559
+ display: none; /* Controlled by JS */
560
  width: 100%;
561
+ padding: 1.5rem; /* Padding applied here, inside the output-section card */
562
+ box-sizing: border-box;
 
 
 
 
563
  flex-direction: column;
564
  gap: 1.2rem;
 
565
  }
566
 
567
+ .audio-player-header-simple {
568
  display: flex;
569
  align-items: center;
570
  justify-content: flex-end; /* Align to right for RTL */
571
+ gap: 0.5rem;
572
+ padding-bottom: 0.8rem; /* No border-bottom as in image */
573
+ margin-right: auto; /* Push content to left */
574
+ width: 100%; /* Take full width */
575
+ color: var(--text-secondary); /* Grey color for header text */
576
+ font-size: 0.95em; /* Slightly smaller text */
577
+ font-weight: 500;
578
  }
579
+ .audio-player-header-simple span {
580
+ order: -1; /* Puts text first in RTL order */
581
+ margin-left: 0.5rem; /* Space between text and icon */
 
 
582
  }
583
+ .audio-player-header-simple .icon {
584
+ width: 18px; /* Smaller icons */
585
+ height: 18px;
586
+ fill: currentColor;
587
+ stroke: currentColor;
588
+ stroke-width: 2;
589
  vertical-align: middle;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
590
  }
591
 
592
+ .audio-waveform-container {
593
  display: flex;
594
  align-items: center;
595
  gap: 0.8rem;
596
  width: 100%;
597
+ margin-bottom: 1rem; /* Space between waveform and controls */
598
  }
599
  .audio-time {
600
  font-size: 0.9em;
601
  color: var(--text-secondary);
602
+ min-width: 40px; /* Ensure consistent width */
603
  text-align: center;
604
+ font-variant-numeric: tabular-nums;
 
 
 
 
 
 
605
  }
606
 
607
+ /* Waveform Display */
608
+ .audio-waveform {
609
  flex-grow: 1;
610
+ height: 60px; /* Fixed height for the waveform area */
611
  position: relative;
612
+ overflow: hidden; /* To clip bars */
613
+ border-radius: 4px; /* Slight radius on the waveform area */
614
+ background-color: transparent; /* No background color here */
615
+ }
616
+
617
+ .audio-waveform-bars-wrapper {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
618
  position: absolute;
619
  top: 0;
620
  left: 0;
621
+ width: 100%;
622
  height: 100%;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
623
  display: flex;
624
+ justify-content: space-between; /* Distribute bars */
625
+ align-items: center; /* Center bars vertically */
626
+ z-index: 0; /* Under the dashed line */
627
+ }
628
+
629
+ .waveform-bar {
630
+ width: 4px; /* Width of each bar */
631
+ min-width: 2px; /* Ensure small bars are still visible */
632
+ background-color: var(--waveform-color-inactive); /* Default grey color */
633
+ border-radius: 2px; /* Rounded ends for bars */
634
+ transition: background-color 0.1s linear; /* Smooth color change */
635
+ flex-shrink: 0; /* Don't shrink bars */
636
+ margin: 0 1px; /* Small gap between bars */
637
+ transform-origin: center; /* For scaling/positioning */
638
+ }
639
+
640
+ .audio-waveform-dashed-line {
641
+ position: absolute;
642
+ top: 50%;
643
+ left: 0;
644
  width: 100%;
645
+ height: 1px; /* Height of the dashed line itself */
646
+ background-image: linear-gradient(to right, var(--waveform-dashed-line-color) 33%, transparent 0%);
647
+ background-position: center;
648
+ background-size: 10px 1px; /* Dash length + gap */
649
+ transform: translateY(-50%);
650
+ z-index: 1; /* Above the bars */
651
  }
652
 
653
+ /* Audio Controls */
654
+ .audio-controls-group {
655
  display: flex;
656
+ justify-content: center; /* Center the main controls */
657
  align-items: center;
658
+ gap: 1.5rem; /* Space between buttons */
659
+ margin-bottom: 1rem; /* Space between main controls and bottom controls */
660
  }
661
 
662
+ .audio-skip-btn {
663
+ background: none;
664
+ border: none;
665
+ cursor: pointer;
666
+ padding: 8px;
667
+ color: var(--text-secondary); /* Grey color */
668
+ transition: transform 0.2s;
669
+ }
670
+ .audio-skip-btn svg {
671
+ width: 24px;
672
+ height: 24px;
673
+ fill: currentColor;
674
+ }
675
+ .audio-skip-btn:hover {
676
+ transform: scale(1.1);
677
+ }
678
+ .audio-skip-btn:active {
679
+ transform: scale(0.9);
680
  }
681
 
682
+ .audio-play-pause-btn-large {
683
  background: none;
684
  border: none;
685
  cursor: pointer;
686
+ padding: 0; /* No padding inside for icon sizing */
687
+ width: 60px; /* Larger circular area */
688
+ height: 60px;
689
  border-radius: 50%;
690
  display: flex;
691
  align-items: center;
692
  justify-content: center;
693
+ color: var(--text-secondary); /* Grey color */
694
+ transition: transform 0.2s;
695
+ /* No background effect on hover as per image */
696
  }
697
+ .audio-play-pause-btn-large:hover {
 
698
  transform: scale(1.05);
699
  }
700
+ .audio-play-pause-btn-large:active {
701
  transform: scale(0.95);
702
  }
703
+ .audio-play-pause-btn-large svg {
704
+ width: 36px; /* Icon size */
705
+ height: 36px;
 
706
  fill: currentColor;
707
  }
708
 
709
+ .audio-bottom-controls {
710
+ display: flex;
711
+ align-items: center;
712
+ justify-content: flex-start; /* Volume/Speed on the left, nothing on the right */
713
+ width: 100%;
714
+ gap: 1rem; /* Space between volume and speed button */
 
 
715
  }
716
+
717
+ .audio-volume-btn {
718
+ background: none;
719
+ border: none;
720
+ cursor: pointer;
721
+ padding: 8px;
722
+ color: var(--text-secondary);
723
+ transition: transform 0.2s;
724
  }
725
+ .audio-volume-btn svg {
726
+ width: 24px;
727
+ height: 24px;
728
+ fill: currentColor;
729
+ }
730
+ .audio-volume-btn:hover {
731
+ transform: scale(1.1);
732
  }
733
+ .audio-volume-btn:active {
734
+ transform: scale(0.9);
 
735
  }
736
 
737
  .audio-speed-btn {
738
  font-family: var(--app-font);
739
  font-size: 0.9em;
740
  font-weight: 600;
741
+ background-color: var(--panel-bg); /* Match the image's "1x" button background */
742
+ border: 1px solid var(--panel-border); /* Match the image's border */
743
  padding: 6px 12px;
744
  border-radius: 8px;
745
  min-width: 40px;
746
  text-align: center;
747
  color: var(--text-primary);
748
+ cursor: pointer;
749
+ transition: background-color 0.2s, border-color 0.2s;
750
+ box-shadow: var(--shadow-subtle); /* Add subtle shadow */
751
  }
752
  .audio-speed-btn:hover {
753
+ background-color: var(--input-bg); /* Slightly darker on hover */
 
754
  }
755
 
756
  /* Hide the default audio player */
757
+ #hidden-audio-player { display: none !important; }
758
  </style>
759
  </head>
760
  <body>
 
824
  </div>
825
 
826
  <!-- Custom Audio Player Structure -->
827
+ <div id="audio-player-content">
828
+ <div class="audio-player-header-simple">
829
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18V5l12-2v13"></path><circle cx="6" cy="18" r="3"></circle><circle cx="18" cy="16" r="3"></circle></svg>
830
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 18v-6a9 9 0 0 1 18 0v6"></path><path d="M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3zM3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z"></path></svg>
831
+ <span>فایل صوتی</span>
 
 
832
  </div>
833
+
834
+ <div class="audio-waveform-container">
835
+ <span class="audio-time audio-current-time">0:00</span>
836
+ <div class="audio-waveform">
837
+ <div class="audio-waveform-bars-wrapper"></div> <!-- Bars generated here by JS -->
838
+ <div class="audio-waveform-dashed-line"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
839
  </div>
840
+ <span class="audio-time audio-total-time">0:00</span>
841
+ </div>
842
+
843
+ <div class="audio-controls-group">
844
+ <button class="audio-skip-btn backward">
845
+ <svg viewBox="0 0 24 24"><path d="M11 16V8l-4 4 4 4zm4-12v16l7-8-7-8z"></path></svg>
846
+ </button>
847
+ <button class="audio-play-pause-btn-large">
848
+ <svg viewBox="0 0 24 24" class="play-icon"><path d="M8 5v14l11-7z"></path></svg>
849
+ <svg viewBox="0 0 24 24" class="pause-icon" style="display:none;"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"></path></svg>
850
+ </button>
851
+ <button class="audio-skip-btn forward">
852
+ <svg viewBox="0 0 24 24"><path d="M13 16V8l4 4-4 4zM9 4v16L2 12l7-8z"></path></svg>
853
+ </button>
854
+ </div>
855
+
856
+ <div class="audio-bottom-controls">
857
+ <button class="audio-volume-btn">
858
+ <svg viewBox="0 0 24 24" class="volume-high-icon"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"></path></svg>
859
+ <svg viewBox="0 0 24 24" class="volume-mute-icon" style="display:none;"><path d="M7 9v6h4l5 5V4L11 9H7zM16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zM19 12c0 .94-.23 1.82-.68 2.6L19 14.88c.45-.88.7-1.88.7-2.88 0-4.01-2.99-7.14-7-8.05v2.06c2.89.86 5 3.54 5 6.71zM4.55 4L2 6.55 9.45 14H7v6h4l5 5V14.55l4.05 4.05L22 18 12 8 4.55 4z"></path></svg>
860
+ </button>
861
+ <button class="audio-speed-btn">1x</button>
862
  </div>
863
  </div>
864
  <audio id="hidden-audio-player" style="display: none;"></audio>
 
930
  { id: "Orus", name: "بردیا (مرد)", desc: "ورزشی و پرهیجان" },
931
  { id: "Aoede", name: "ترانه (زن)", desc: "موزیکال و خوش‌آهنگ" },
932
  { id: "Callirrhoe", name: "نیما (مرد)", desc: "روایتگر و قصه‌گو" },
933
+ { id: "Autonoe", name: "هستی (زن)", "desc": "طبیعی و خودمانی" },
934
  { id: "Enceladus", name: "کامیار (مرد)", desc: "مصمم �� جدی" },
935
  { id: "Iapetus", name: "ستاره (زن)", desc: "درخشان و گیرا" },
936
  { id: "Puck", name: "پویا (مرد)", desc: "بازیگوش و سرزنده" },
 
965
 
966
  // --- Custom Audio Player Elements ---
967
  const hiddenAudioPlayer = document.getElementById('hidden-audio-player');
968
+ const audioPlayerContent = document.getElementById('audio-player-content'); // Main player content wrapper
969
+ const audioCurrentTimeSpan = audioPlayerContent.querySelector('.audio-current-time');
970
+ const audioTotalTimeSpan = audioPlayerContent.querySelector('.audio-total-time');
971
+ const audioWaveformBarsWrapper = audioPlayerContent.querySelector('.audio-waveform-bars-wrapper');
972
+ const playPauseBtn = audioPlayerContent.querySelector('.audio-play-pause-btn-large');
973
  const playIcon = playPauseBtn.querySelector('.play-icon');
974
  const pauseIcon = playPauseBtn.querySelector('.pause-icon');
975
+ const skipBackwardBtn = audioPlayerContent.querySelector('.audio-skip-btn.backward');
976
+ const skipForwardBtn = audioPlayerContent.querySelector('.audio-skip-btn.forward');
977
+ const volumeBtn = audioPlayerContent.querySelector('.audio-volume-btn');
978
  const volumeHighIcon = volumeBtn.querySelector('.volume-high-icon');
979
  const volumeMuteIcon = volumeBtn.querySelector('.volume-mute-icon');
980
+ const speedBtn = audioPlayerContent.querySelector('.audio-speed-btn');
981
 
982
  let currentPlaybackSpeedIndex = 0;
983
  const playbackSpeeds = [1.0, 1.25, 1.5, 1.75, 2.0, 0.75, 0.5]; // Common speeds
984
+ const NUM_WAVEFORM_BARS = 60; // Number of bars to display for the waveform
985
 
986
  // Character Counter
987
  const charCountSpan = document.getElementById('char-count');
 
1114
 
1115
  // --- Custom Audio Player Functionality ---
1116
 
1117
+ // Function to generate waveform bars
1118
+ function generateWaveformBars() {
1119
+ audioWaveformBarsWrapper.innerHTML = ''; // Clear existing bars
1120
+ const maxBarHeight = 100; // Percentage of total waveform height
1121
+ const minBarHeight = 10;
1122
+ for (let i = 0; i < NUM_WAVEFORM_BARS; i++) {
1123
+ const bar = document.createElement('div');
1124
+ bar.classList.add('waveform-bar');
1125
+ // Simple randomized heights for visual variety
1126
+ const height = Math.random() * (maxBarHeight - minBarHeight) + minBarHeight;
1127
+ bar.style.height = `${height}%`;
1128
+ audioWaveformBarsWrapper.appendChild(bar);
1129
+ }
1130
+ }
1131
+
1132
  // Helper function to format time (e.g., 0:06)
1133
  function formatTime(seconds) {
1134
  if (isNaN(seconds) || seconds < 0) return '0:00';
 
1154
 
1155
  if (isFinite(duration) && duration > 0) {
1156
  audioTotalTimeSpan.textContent = formatTime(duration);
1157
+ const progressRatio = currentTime / duration;
1158
+ const barsToColor = Math.floor(progressRatio * NUM_WAVEFORM_BARS);
1159
+
1160
+ const bars = audioWaveformBarsWrapper.children;
1161
+ for (let i = 0; i < bars.length; i++) {
1162
+ if (i < barsToColor) {
1163
+ bars[i].style.backgroundColor = 'var(--waveform-color-active)';
1164
+ } else {
1165
+ bars[i].style.backgroundColor = 'var(--waveform-color-inactive)';
1166
+ }
1167
+ }
1168
  } else {
1169
  audioTotalTimeSpan.textContent = '0:00';
1170
+ // Reset all bars to inactive color
1171
+ const bars = audioWaveformBarsWrapper.children;
1172
+ for (let i = 0; i < bars.length; i++) {
1173
+ bars[i].style.backgroundColor = 'var(--waveform-color-inactive)';
1174
+ }
1175
  }
1176
  }
1177
 
 
1213
 
1214
  // Listen to hidden audio player events to update UI
1215
  hiddenAudioPlayer.addEventListener('timeupdate', updatePlayerUI);
1216
+ hiddenAudioPlayer.addEventListener('loadedmetadata', () => {
1217
+ generateWaveformBars(); // Generate bars once metadata is loaded
1218
+ updatePlayerUI(); // Update UI with total duration and initial state
1219
+ });
1220
  hiddenAudioPlayer.addEventListener('play', updatePlayerUI);
1221
  hiddenAudioPlayer.addEventListener('pause', updatePlayerUI);
1222
  hiddenAudioPlayer.addEventListener('ended', () => {
 
1226
 
1227
  // --- State Management Functions ---
1228
  function showLoadingState() {
1229
+ audioPlayerContent.style.display = 'none'; // Hide custom player
1230
+ outputSection.classList.remove('has-content'); // Remove has-content class to revert output-section styling
1231
  statusMessage.style.display = 'none';
1232
  loadingAnimationWrapper.style.display = 'flex';
1233
  generateBtn.disabled = true;
 
1244
  loadingAnimationWrapper.style.display = 'none';
1245
  if (isSuccess) {
1246
  statusMessage.style.display = 'none';
1247
+ audioPlayerContent.style.display = 'flex'; // Show custom player content
1248
  outputSection.classList.add('has-content'); // Add class to output section parent
1249
 
1250
+ updatePlayerUI(); // Update UI with initial playback state (paused, time 0)
1251
+ hiddenAudioPlayer.play(); // Auto-play the audio
 
1252
  } else {
1253
  statusMessage.textContent = message || 'خطایی رخ داد. لطفاً دوباره تلاش کنید.';
1254
  statusMessage.style.display = 'block';
1255
+ audioPlayerContent.style.display = 'none'; // Hide custom player content
1256
+ outputSection.classList.remove('has-content'); // Remove class to revert output-section styling
1257
  hiddenAudioPlayer.pause(); // Stop any potential audio
1258
  hiddenAudioPlayer.src = ''; // Clear audio source
1259
  }
 
1361
  form.addEventListener('submit', generateAudio);
1362
 
1363
  // Hide custom audio player and show status message on load
1364
+ audioPlayerContent.style.display = 'none';
1365
  statusMessage.style.display = 'block';
1366
  loadingAnimationWrapper.style.display = 'none';
1367
  outputSection.classList.remove('has-content'); // Ensure initial state is clean