Update index.html
Browse files- 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; /*
|
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 {
|
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
|
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:
|
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 |
-
|
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:
|
486 |
-
border:
|
487 |
-
|
488 |
-
|
|
|
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-
|
|
|
563 |
width: 100%;
|
564 |
-
|
565 |
-
|
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.
|
580 |
-
padding-bottom: 0.8rem;
|
581 |
-
|
582 |
-
|
|
|
|
|
|
|
583 |
}
|
584 |
-
.audio-player-header
|
585 |
-
|
586 |
-
|
587 |
-
color: var(--text-primary);
|
588 |
-
margin-right: auto; /* Pushes icons to the right */
|
589 |
}
|
590 |
-
.audio-player-header .
|
591 |
-
width:
|
592 |
-
height:
|
593 |
-
|
|
|
|
|
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-
|
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;
|
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 |
-
|
|
|
635 |
flex-grow: 1;
|
636 |
-
height: 60px; /*
|
637 |
position: relative;
|
638 |
-
|
639 |
-
|
640 |
-
/*
|
641 |
-
|
642 |
-
|
643 |
-
|
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 |
-
|
|
|
695 |
display: flex;
|
|
|
696 |
align-items: center;
|
697 |
-
gap:
|
|
|
698 |
}
|
699 |
|
700 |
-
.audio-
|
701 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
702 |
}
|
703 |
|
704 |
-
.audio-
|
705 |
background: none;
|
706 |
border: none;
|
707 |
cursor: pointer;
|
708 |
-
padding:
|
|
|
|
|
709 |
border-radius: 50%;
|
710 |
display: flex;
|
711 |
align-items: center;
|
712 |
justify-content: center;
|
713 |
-
|
714 |
-
|
|
|
715 |
}
|
716 |
-
.audio-
|
717 |
-
background-color: var(--input-bg);
|
718 |
transform: scale(1.05);
|
719 |
}
|
720 |
-
.audio-
|
721 |
transform: scale(0.95);
|
722 |
}
|
723 |
-
|
724 |
-
|
725 |
-
|
726 |
-
height: 24px;
|
727 |
fill: currentColor;
|
728 |
}
|
729 |
|
730 |
-
|
731 |
-
|
732 |
-
|
733 |
-
|
734 |
-
|
735 |
-
|
736 |
-
border-radius: 50%;
|
737 |
-
box-shadow: 0 4px 10px rgba(59, 130, 246, 0.3);
|
738 |
}
|
739 |
-
|
740 |
-
|
741 |
-
|
742 |
-
|
|
|
|
|
|
|
|
|
743 |
}
|
744 |
-
.audio-
|
745 |
-
|
|
|
|
|
|
|
|
|
|
|
746 |
}
|
747 |
-
.audio-
|
748 |
-
|
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(--
|
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:
|
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-
|
841 |
-
<div class="audio-player-header">
|
842 |
-
<
|
843 |
-
|
844 |
-
|
845 |
-
</div>
|
846 |
-
<span class="audio-player-title">فایل صوتی</span>
|
847 |
</div>
|
848 |
-
|
849 |
-
|
850 |
-
|
851 |
-
|
852 |
-
|
853 |
-
|
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
|
985 |
-
const audioCurrentTimeSpan =
|
986 |
-
const audioTotalTimeSpan =
|
987 |
-
const
|
988 |
-
const playPauseBtn =
|
989 |
const playIcon = playPauseBtn.querySelector('.play-icon');
|
990 |
const pauseIcon = playPauseBtn.querySelector('.pause-icon');
|
991 |
-
const skipBackwardBtn =
|
992 |
-
const skipForwardBtn =
|
993 |
-
const volumeBtn =
|
994 |
const volumeHighIcon = volumeBtn.querySelector('.volume-high-icon');
|
995 |
const volumeMuteIcon = volumeBtn.querySelector('.volume-mute-icon');
|
996 |
-
const speedBtn =
|
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
|
1158 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1159 |
} else {
|
1160 |
audioTotalTimeSpan.textContent = '0:00';
|
1161 |
-
|
|
|
|
|
|
|
|
|
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',
|
|
|
|
|
|
|
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 |
-
|
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 |
-
|
1232 |
outputSection.classList.add('has-content'); // Add class to output section parent
|
1233 |
|
1234 |
-
//
|
1235 |
-
|
1236 |
-
hiddenAudioPlayer.play();
|
1237 |
} else {
|
1238 |
statusMessage.textContent = message || 'خطایی رخ داد. لطفاً دوباره تلاش کنید.';
|
1239 |
statusMessage.style.display = 'block';
|
1240 |
-
|
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 |
-
|
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
|