Tttt / wordpress_export (5).html
Ezmary's picture
Upload wordpress_export (5).html
ade471c verified
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>تولید صدای هوشمند با هوش مصنوعی | AI Sada</title>
<meta name="description" content="با AI Sada، متن فارسی خود را به صدایی طبیعی تبدیل کنید یا صدای خود را به صدای خوانندگان و مشاهیر تغییر دهید.">
<style>
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800;900&display=swap');
:root {
--app-font: 'Vazirmatn', sans-serif; --app-bg: #F8F9FC; --panel-bg: #FFFFFF;
--panel-border: #EAEFF7; --input-bg: #F6F8FB; --input-border: #E1E7EF;
--text-primary: #1A202C; --text-secondary: #626F86; --text-tertiary: #8A94A6;
--accent-primary: #4A6CFA; --accent-primary-hover: #3553D6; --accent-primary-glow: rgba(74, 108, 250, 0.25);
--accent-secondary: #0FD4A8; --accent-secondary-hover: #0DA986; --accent-secondary-glow: rgba(15, 212, 168, 0.2);
--shadow-sm: 0 1px 2px 0 rgba(26, 32, 44, 0.03); --shadow-md: 0 4px 6px -1px rgba(26, 32, 44, 0.05), 0 2px 4px -2px rgba(26, 32, 44, 0.04);
--shadow-lg: 0 10px 15px -3px rgba(26, 32, 44, 0.06), 0 4px 6px -4px rgba(26, 32, 44, 0.05);
--shadow-xl: 0 20px 25px -5px rgba(26, 32, 44, 0.07), 0 8px 10px -6px rgba(26, 32, 44, 0.05);
--radius-card: 24px; --radius-btn: 14px; --radius-input: 12px;
--transition-fast: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
--transition-smooth: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
--glass-bg: rgba(255, 255, 255, 0.75);
--glass-border: rgba(255, 255, 255, 0.5);
--glass-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.1);
}
@keyframes fadeIn { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } }
@keyframes modalZoomIn { from { opacity: 0; transform: scale(0.9) translateY(20px); } to { opacity: 1; transform: scale(1) translateY(0); } }
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@keyframes rotate-loader-orbital { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@keyframes orbit-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@keyframes satellite-pulse-1 { from { transform: scale(0.7) translateX(-50%); opacity: 0.6; } to { transform: scale(1.1) translateX(-50%); opacity: 1; } }
@keyframes satellite-pulse-2 { from { transform: scale(0.7) translateY(-50%); opacity: 0.6; } to { transform: scale(1.1) translateY(-50%); opacity: 1; } }
@keyframes satellite-pulse-3 { from { transform: scale(0.7) translateX(50%); opacity: 0.6; } to { transform: scale(1.1) translateX(50%); opacity: 1; } }
html { scroll-behavior: smooth; }
body {
font-family: var(--app-font); direction: rtl; background-color: var(--app-bg);
color: var(--text-primary); font-size: 16px; line-height: 1.8; margin: 0;
padding: 0; min-height: 100vh; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.page-wrapper { max-width: 820px; width: 92%; margin: 0 auto; padding: 1.5rem 0 5rem 0; }
.app-container { width: 100%; margin: 0 auto; }
/* --- Header & Auth --- */
.app-header { text-align: center; margin-bottom: 1.5rem; animation: fadeIn 0.8s 0.2s ease-out backwards; position: relative; z-index: 10; }
.app-header h1 { font-size: 2.2em; font-weight: 900; margin: 0 0 0.5rem 0; background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; letter-spacing: -1px; }
.app-header p { font-size: 1em; color: var(--text-secondary); margin-top: 0; opacity: 0.9; font-weight: 400; }
.header-actions { margin-top: 1rem; text-align: center; display: flex; justify-content: center; gap: 1rem; }
#user-status-container {
padding: 0.5rem 1rem; background-color: var(--panel-bg); border-radius: 50px;
display: none; align-items: center; justify-content: center; gap: 0.8rem;
border: 1px solid var(--panel-border); box-shadow: var(--shadow-sm);
}
#user-status-details { display: flex; align-items: center; gap: 0.8rem; }
#user-status-container .user-email { font-weight: 700; color: var(--text-primary); font-size: 0.9em; }
#user-status-container .user-sub-status { font-weight: 600; padding: 0.2rem 0.8rem; border-radius: 20px; font-size: 0.8em; white-space: nowrap; }
.status-paid { background-color: rgba(15, 212, 168, 0.15); color: #008f6d; border: 1px solid rgba(15, 212, 168, 0.3); }
.status-free { background-color: #e2e8f0; color: #4a5568; }
#user-status-container .logout-btn { background: none; border: none; color: #e53e3e; cursor: pointer; font-weight: 600; font-size: 0.85em; }
#login-check-btn {
background: var(--input-bg); border: 1px solid var(--panel-border); color: var(--text-primary);
font-size: 0.95em; font-weight: 700; padding: 0.6rem 1.5rem; border-radius: 50px;
cursor: pointer; transition: var(--transition-smooth); display: inline-block;
}
#login-check-btn:hover { background: var(--accent-primary); color: white; transform: translateY(-2px); box-shadow: 0 4px 12px var(--accent-primary-glow); }
/* --- Glass Navigation Menu --- */
.glass-nav-container {
position: sticky; top: 1rem; z-index: 100; margin-bottom: 2rem;
display: flex; justify-content: center;
}
.glass-nav {
background: var(--glass-bg); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
border: 1px solid var(--glass-border); border-radius: 20px; padding: 0.5rem;
display: flex; gap: 0.5rem; box-shadow: var(--glass-shadow);
overflow-x: auto; max-width: 100%; white-space: nowrap; scrollbar-width: none;
}
.glass-nav::-webkit-scrollbar { display: none; }
.nav-item {
background: transparent; border: none; padding: 0.7rem 1.2rem; border-radius: 14px;
color: var(--text-secondary); font-family: var(--app-font); font-weight: 700; font-size: 0.95em;
cursor: pointer; transition: all 0.3s ease; position: relative; display: flex; align-items: center; gap: 6px;
}
.nav-item.active {
background: white; color: var(--accent-primary); box-shadow: 0 4px 12px rgba(0,0,0,0.05);
}
.nav-item:hover:not(.active) { background: rgba(255,255,255,0.5); }
.nav-item .badge {
font-size: 0.65em; background: #FFC107; color: #000; padding: 2px 6px; border-radius: 6px;
position: absolute; top: -2px; left: 0; animation: bounce 2s infinite;
}
@keyframes bounce { 0%, 20%, 50%, 80%, 100% {transform: translateY(0);} 40% {transform: translateY(-5px);} 60% {transform: translateY(-3px);} }
/* --- Content Sections --- */
.content-tab { display: none; animation: fadeIn 0.5s ease-out backwards; }
.content-tab.active { display: block; }
/* --- Common Styles (From User's TTS Code) --- */
.main-content { position:relative; padding: 2.5rem; background-color: var(--panel-bg); border-radius: var(--radius-card); box-shadow: var(--shadow-xl); border: 1px solid var(--panel-border); }
.form-group { margin-bottom: 2rem; }
label { display: block; font-weight: 700; color: var(--text-primary); font-size: 1.05em; margin-bottom: 0.8rem; }
textarea, input[type="text"], input[type="email"] { width: 100%; padding: 1rem 1.2rem; border-radius: var(--radius-input); border: 1px solid var(--input-border); background-color: var(--input-bg); color: var(--text-primary); box-shadow: var(--shadow-sm) inset; font-family: var(--app-font); font-size: 1rem; box-sizing: border-box; transition: var(--transition-smooth); }
textarea:focus, input:focus { outline: none; border-color: var(--accent-primary); box-shadow: 0 0 0 3px var(--accent-primary-glow), var(--shadow-sm) inset; background-color: var(--panel-bg); }
.slider-container { display: flex; align-items: center; gap: 1.5rem; }
input[type="range"] { flex-grow: 1; -webkit-appearance: none; width: 100%; height: 6px; background: var(--input-border); border-radius: 3px; outline: none; cursor: pointer; }
input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; width: 22px; height: 22px; background: #fff; border-radius: 50%; cursor: pointer; border: 4px solid var(--accent-primary); box-shadow: var(--shadow-md); margin-top: -8px; }
.temperature-value { font-weight: 700; background-color: var(--input-bg); padding: 0.5rem 1rem; border-radius: 8px; border: 1px solid var(--input-border); min-width: 45px; text-align: center; color: var(--accent-primary); }
.generate-btn { display: flex; align-items: center; justify-content: center; gap: 10px; width: 100%; padding: 1.1rem 1.5rem; font-size: 1.2em; font-weight: 800; background: linear-gradient(95deg, var(--accent-secondary) 0%, var(--accent-primary) 100%); color: #fff; border: none; border-radius: var(--radius-btn); cursor: pointer; transition: all 0.3s ease; box-shadow: 0 6px 12px -3px var(--accent-primary-glow); position: relative; overflow: hidden; }
.generate-btn:hover:not(:disabled) { transform: translateY(-4px); box-shadow: 0 10px 20px -4px var(--accent-primary-glow); }
.generate-btn:disabled { background: var(--text-tertiary); cursor: not-allowed; box-shadow: none; opacity: 0.7; }
.spinner { width: 20px; height: 20px; border: 3px solid rgba(255, 255, 255, 0.4); border-top-color: #fff; border-radius: 50%; animation: spin 0.8s linear infinite; display: none;}
.output-section { margin-top: 2.5rem; display: flex; align-items: center; justify-content: center; flex-direction: column; min-height: 200px; padding: 2rem; background-color: var(--input-bg); border-radius: var(--radius-card); border: 2px dashed var(--input-border); transition: var(--transition-smooth); }
.output-section.has-content { background-color: var(--panel-bg); border: 1px solid var(--panel-border); box-shadow: var(--shadow-lg); padding: 0; min-height: auto; }
.status-message { font-weight: 500; color: var(--text-secondary); text-align: center; font-size: 1.1em; }
.status-message.error { color: #e53e3e; background-color: #fff5f5; padding: 1rem; border-radius: var(--radius-input); border: 1px solid #fed7d7; width: 100%; }
/* --- TTS Specifics --- */
#selected-speaker-card { display: inline-flex; align-items: center; background: linear-gradient(135deg, var(--input-bg) 0%, var(--panel-bg) 100%); border-radius: 50px; padding: 0.6rem 0.6rem 0.6rem 1.2rem; box-shadow: var(--shadow-md); border: 1px solid var(--panel-border); cursor: pointer; margin-bottom: 1rem; transition: transform 0.2s; width: 100%; max-width: 350px; justify-content: space-between; }
#selected-speaker-card:hover { transform: translateY(-3px); border-color: var(--accent-primary); }
#selected-speaker-card img { width: 50px; height: 50px; border-radius: 50%; object-fit: cover; border: 2px solid white; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
#selected-speaker-info { text-align: right; flex-grow: 1; padding-right: 1rem; }
#selected-speaker-info h3 { margin: 0; font-size: 1.1em; font-weight: 800; color: var(--text-primary); }
#change-speaker-btn { background: var(--accent-primary); color: white; border: none; padding: 0.5rem 1rem; border-radius: 20px; font-size: 0.85em; cursor: pointer; font-weight: 700; }
/* --- Voice Changer Specifics (New) --- */
.vc-models-wrapper { display: grid; grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); gap: 1rem; margin-top: 1rem; max-height: 320px; overflow-y: auto; padding: 5px; }
.vc-model-item { background: var(--panel-bg); border: 2px solid var(--input-border); border-radius: 18px; padding: 0.8rem; text-align: center; cursor: pointer; transition: all 0.2s ease; display: flex; flex-direction: column; align-items: center; justify-content: center; }
.vc-model-item:hover { transform: translateY(-5px); box-shadow: var(--shadow-md); }
.vc-model-item.selected { border-color: var(--accent-primary); background: #f0f7ff; box-shadow: 0 4px 15px var(--accent-primary-glow); }
.vc-model-item img { width: 65px; height: 65px; border-radius: 50%; object-fit: cover; margin-bottom: 0.5rem; border: 3px solid white; box-shadow: var(--shadow-sm); }
.vc-model-item span { font-size: 0.85em; font-weight: 700; color: var(--text-primary); line-height: 1.2; }
.upload-area-vc { border: 2px dashed var(--input-border); border-radius: var(--radius-card); padding: 2.5rem 1.5rem; text-align: center; cursor: pointer; background-color: var(--input-bg); transition: all 0.3s; position: relative; overflow: hidden; }
.upload-area-vc:hover { border-color: var(--accent-primary); background-color: white; }
.upload-area-vc .icon { font-size: 3rem; color: var(--accent-primary); margin-bottom: 1rem; opacity: 0.8; }
.upload-area-vc p { margin: 0; color: var(--text-secondary); font-weight: 500; }
.file-status-bar { display: none; align-items: center; justify-content: space-between; background: #e6fffa; border: 1px solid #b2f5ea; padding: 0.8rem 1rem; border-radius: 12px; margin-top: 1rem; color: #047481; font-weight: 600; animation: fadeIn 0.3s; }
/* --- Podcast (Coming Soon) --- */
.coming-soon-wrapper { text-align: center; padding: 4rem 2rem; background: linear-gradient(135deg, #1A202C, #2D3748); border-radius: var(--radius-card); color: white; position: relative; overflow: hidden; box-shadow: var(--shadow-xl); }
.coming-soon-wrapper::before { content: ''; position: absolute; top: -50%; left: -50%; width: 200%; height: 200%; background: radial-gradient(circle, rgba(255,255,255,0.05) 0%, transparent 60%); animation: spin 15s linear infinite; }
.coming-soon-wrapper h2 { font-size: 2.5em; margin-bottom: 1rem; position: relative; z-index: 1; }
.coming-soon-wrapper p { font-size: 1.1em; opacity: 0.8; position: relative; z-index: 1; }
/* --- FAQ Section --- */
.landing-section { padding: 3rem 0; text-align: center; border-top: 1px solid var(--panel-border); margin-top: 3rem; }
.landing-section h2 { font-size: 1.8em; color: var(--text-primary); margin-bottom: 2rem; }
.faq-accordion { max-width: 700px; margin: 0 auto; text-align: right; }
.faq-item { background-color: var(--panel-bg); border-radius: var(--radius-input); margin-bottom: 1rem; border: 1px solid var(--panel-border); box-shadow: var(--shadow-sm); overflow: hidden; }
.faq-question { width: 100%; padding: 1.2rem 1.5rem; font-family: var(--app-font); font-size: 1.05em; font-weight: 700; background: none; border: none; text-align: right; cursor: pointer; display: flex; justify-content: space-between; align-items: center; color: var(--text-primary); transition: background 0.2s; }
.faq-question:hover { background-color: var(--input-bg); }
.faq-answer { max-height: 0; overflow: hidden; transition: max-height 0.3s ease; color: var(--text-secondary); font-size: 0.95em; line-height: 1.8; background: #fafafa; }
.faq-answer p { padding: 1.5rem; margin: 0; }
.faq-item.active .faq-answer { max-height: 200px; }
/* --- Modals --- */
.modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(18, 24, 38, 0.6); backdrop-filter: blur(8px); display: none; align-items: center; justify-content: center; z-index: 1000; }
.modal-overlay.visible { display: flex; animation: fadeIn 0.2s; }
.modal-dialog { background: var(--panel-bg); padding: 2.5rem; border-radius: var(--radius-card); width: 90%; max-width: 500px; box-shadow: var(--shadow-xl); position: relative; animation: modalZoomIn 0.3s; }
.close-modal-btn { position: absolute; top: 1rem; right: 1.5rem; font-size: 2rem; background: none; border: none; cursor: pointer; color: var(--text-tertiary); }
/* Audio Player Styling */
.simple-player-container { display: flex; flex-direction: column; align-items: center; gap: 1rem; width: 100%; }
.audio-download-btn-new { display: inline-flex; align-items: center; justify-content: center; gap: 8px; width: 100%; max-width: 300px; padding: 0.9rem; background: linear-gradient(95deg, #0FD4A8, #09b890); color: white; text-decoration: none; border-radius: 12px; font-weight: 700; box-shadow: 0 4px 12px rgba(15, 212, 168, 0.3); transition: transform 0.2s; margin-top: 1rem; }
.audio-download-btn-new:hover { transform: translateY(-2px); }
/* Loader Animation */
.orbital-loader { width: 80px; height: 80px; position: relative; animation: rotate-loader-orbital 10s linear infinite; margin: 0 auto 1.5rem auto; }
.orbit { position: absolute; top: 50%; left: 50%; border: 2px dashed rgba(74, 108, 250, 0.3); border-radius: 50%; }
.orbit:nth-child(1) { width: 30px; height: 30px; margin: -15px 0 0 -15px; animation: orbit-spin 3s linear infinite reverse; }
.orbit:nth-child(2) { width: 50px; height: 50px; margin: -25px 0 0 -25px; animation: orbit-spin 4s linear infinite; }
.orbit:nth-child(3) { width: 70px; height: 70px; margin: -35px 0 0 -35px; animation: orbit-spin 5s linear infinite reverse; }
.satellite { position: absolute; width: 8px; height: 8px; border-radius: 50%; background: var(--accent-primary); top: -4px; left: 50%; box-shadow: 0 0 10px var(--accent-primary); }
@media (max-width: 600px) {
.page-wrapper { padding: 1rem 0 4rem 0; }
.app-header h1 { font-size: 1.8em; }
.main-content { padding: 1.5rem; }
.vc-models-wrapper { grid-template-columns: repeat(3, 1fr); }
}
</style>
</head>
<body>
<div class="page-wrapper">
<!-- App Header & User Status -->
<header class="app-header">
<h1>هوش مصنوعی آلفا صدا</h1>
<p>جعبه ابزار صوتی شما: متن به صدا، تغییر صدا و به زودی پادکست</p>
<div class="header-actions">
<div id="user-status-container">
<div id="user-status-details">
<span id="user-email-display" class="user-email"></span>
<span id="user-sub-status-display" class="user-sub-status"></span>
</div>
<button id="logout-btn" class="logout-btn">خروج</button>
</div>
<button id="login-check-btn">ورود / ثبت نام</button>
</div>
</header>
<!-- Glass Navigation Menu -->
<div class="glass-nav-container">
<nav class="glass-nav">
<button class="nav-item active" onclick="switchTab('tts')">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>
تبدیل متن به صدا
</button>
<button class="nav-item" onclick="switchTab('vc')">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"></path><path d="M19 10v2a7 7 0 0 1-14 0v-2"></path><line x1="12" y1="19" x2="12" y2="23"></line><line x1="8" y1="23" x2="16" y2="23"></line></svg>
تغییر صدا (AI)
</button>
<button class="nav-item" onclick="switchTab('podcast')">
<span class="badge">بزودی</span>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polygon points="10 8 16 12 10 16 10 8"></polygon></svg>
ساخت پادکست
</button>
</nav>
</div>
<!-- TAB 1: Text to Speech (Exact Original Code) -->
<div id="tab-content-tts" class="content-tab active">
<main class="main-content">
<form id="standard-tts-form" onsubmit="return false;">
<div class="form-group"><label for="text-input-standard">📝 متن اصلی</label><textarea id="text-input-standard" rows="5" placeholder="متن خود را برای تبدیل به گفتار اینجا وارد کنید..."></textarea><div style="font-size:0.85em;color:#8A94A6;text-align:left;margin-top:0.5rem;"><span id="char-count">0</span> / 50000</div></div>
<div class="form-group"><label for="prompt-input-standard">🗣️ توصیف لحن و احساس (اختیاری)</label><input type="text" id="prompt-input-standard" placeholder="مثال: با لحنی آرام و قصه‌گو"></div>
<div class="form-group">
<label>🎤 گوینده منتخب</label>
<div style="text-align: center;">
<div id="selected-speaker-card" onclick="document.getElementById('speaker-modal').classList.add('visible')">
<img id="selected-speaker-img" src="" alt="Speaker">
<div id="selected-speaker-info">
<h3 id="selected-speaker-name"></h3>
<p id="selected-speaker-desc" style="margin:0;font-size:0.8em;color:#666;"></p>
</div>
<button type="button" id="change-speaker-btn">تغییر</button>
</div>
</div>
</div>
<div class="form-group">
<div style="display:flex;align-items:center;gap:0.5rem;margin-bottom:1rem;"><label style="margin:0">🌡️ خلاقیت صدا</label><div id="temp-info-icon" style="width:20px;height:20px;border-radius:50%;background:#eee;color:#666;display:flex;align-items:center;justify-content:center;font-size:0.8em;cursor:pointer;">!</div></div>
<div class="slider-container"><input type="range" id="temperature-slider-standard" min="0.1" max="1.5" step="0.05" value="0.9"><span id="temperature-value-standard" class="temperature-value">0.9</span></div>
</div>
<p id="credit-status-message" style="text-align:center;color:#626F86;font-weight:600;margin:1rem 0;display:none;"></p>
<button type="submit" id="generate-btn-standard" class="generate-btn"><span class="btn-text">✨ خلق صدا با آلفا</span><div class="spinner"></div></button>
</form>
<div id="output-section-standard" class="output-section">
<div id="status-message-standard" class="status-message">صدای تولید شده در اینجا ظاهر خواهد شد.</div>
<div id="loading-animation-wrapper-standard" style="display:none;flex-direction:column;align-items:center;gap:1rem;"><div class="orbital-loader"><div class="orbit"><div class="satellite"></div></div><div class="orbit"><div class="satellite"></div></div><div class="orbit"><div class="satellite"></div></div></div><p class="loading-text" style="font-weight:700;color:var(--accent-primary);">در حال پردازش هوشمند...</p></div>
<div id="audio-player-content-standard" style="width:100%;display:none;"></div>
</div>
</main>
</div>
<!-- TAB 2: Voice Changer (New & Complete) -->
<div id="tab-content-vc" class="content-tab">
<main class="main-content">
<div style="text-align:center; margin-bottom:2rem;">
<h2 style="font-size:1.5em;margin-bottom:0.5rem;">تغییر صدای جادویی</h2>
<p style="color:var(--text-secondary); font-size:0.95em;">صدای خود را ضبط کنید و آن را با کیفیت استودیویی به صدای خوانندگان یا مشاهیر تبدیل کنید.</p>
</div>
<!-- Step 1: Select Model -->
<div class="form-group">
<label>۱. انتخاب مدل صدا (خواننده/گوینده):</label>
<div id="vc-models-container" class="vc-models-wrapper">
<!-- Models will be injected by JS -->
</div>
</div>
<!-- Step 2: Upload File -->
<div class="form-group">
<label>۲. آپلود صدای خودتان (ورودی):</label>
<p style="font-size:0.85em; color:var(--text-tertiary); margin-top:-5px; margin-bottom:10px;">یک فایل صوتی با کیفیت، بدون نویز و موزیک پس‌زمینه (۳ تا ۱۰ ثانیه) آپلود کنید.</p>
<div class="upload-area-vc" onclick="document.getElementById('vc-file-input').click()">
<div class="icon">🎤</div>
<p>برای انتخاب فایل کلیک کنید یا فایل را اینجا رها کنید</p>
<input type="file" id="vc-file-input" accept="audio/*" style="display:none;" onchange="handleVcFileSelect(this)">
</div>
<div id="vc-file-status" class="file-status-bar">
<span id="vc-filename-display">filename.mp3</span>
<span>✅ آماده</span>
</div>
</div>
<!-- Step 3: Action -->
<button id="vc-generate-btn" class="generate-btn" onclick="startVoiceConversion()">
<span class="btn-text">شروع تغییر صدا</span>
<div class="spinner"></div>
</button>
<!-- Output -->
<div id="vc-output-section" class="output-section">
<div id="vc-status-message" class="status-message">فایل خروجی اینجا نمایش داده می‌شود.</div>
<div id="vc-loader" style="display:none; flex-direction:column; align-items:center; gap:1rem;">
<div class="orbital-loader"><div class="orbit"><div class="satellite"></div></div><div class="orbit"><div class="satellite"></div></div></div>
<p style="font-weight:700; color:var(--accent-primary);">در حال ارسال به سرور ابری و پردازش...</p>
<p style="font-size:0.85em; color:var(--text-tertiary);">ممکن است تا ۱ دقیقه زمان ببرد.</p>
</div>
<div id="vc-result-container" style="width:100%; display:none; text-align:center;">
<p style="color:#047481; font-weight:700; margin-bottom:1rem;">تغییر صدا با موفقیت انجام شد! 🎉</p>
<audio id="vc-audio-player" controls style="width:100%; margin-bottom:1rem;"></audio>
<a id="vc-download-btn" href="#" class="audio-download-btn-new" download>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M19.35 10.04A7.49 7.49 0 0 0 12 4C9.11 4 6.6 5.64 5.35 8.04A5.994 5.994 0 0 0 0 14a6 6 0 0 0 6 6h13a5 5 0 0 0 5-5c0-2.64-2.05-4.78-4.65-4.96zM17 13l-5 5-5-5h3V9h4v4h3z"></path></svg>
دانلود فایل نهایی
</a>
</div>
</div>
<!-- VC FAQ -->
<div class="landing-section">
<h2>سوالات متداول تغییر صدا</h2>
<div class="faq-accordion">
<div class="faq-item">
<button class="faq-question" onclick="toggleFaq(this)">چطور بهترین کیفیت را بگیرم؟</button>
<div class="faq-answer"><p>کیفیت خروجی مستقیماً به کیفیت فایل ورودی شما بستگی دارد. صدای خود را در محیطی ساکت ضبط کنید و از فایل‌های بدون اکو و نویز استفاده کنید.</p></div>
</div>
<div class="faq-item">
<button class="faq-question" onclick="toggleFaq(this)">آیا صدای من ذخیره می‌شود؟</button>
<div class="faq-answer"><p>خیر. فایل‌های شما تنها برای پردازش لحظه‌ای به سرور ارسال می‌شوند و پس از تولید خروجی، به طور خودکار حذف می‌گردند.</p></div>
</div>
</div>
</div>
</main>
</div>
<!-- TAB 3: Podcast (Coming Soon) -->
<div id="tab-content-podcast" class="content-tab">
<div class="coming-soon-wrapper">
<h2>استودیو ساخت پادکست</h2>
<p>به زودی... انقلابی در تولید محتوای صوتی چند نفره</p>
<div style="margin-top:2rem; font-size:4rem;">🎙️</div>
<p style="margin-top:1rem; font-size:0.9em; opacity:0.7;">سناریو بدهید، پادکست کامل با چندین گوینده تحویل بگیرید.</p>
</div>
</div>
<!-- Modals -->
<div id="speaker-modal" class="modal-overlay">
<div class="modal-dialog">
<button class="close-modal-btn" onclick="document.getElementById('speaker-modal').classList.remove('visible')">×</button>
<h2 style="margin-top:0; color:var(--accent-primary);">انتخاب گوینده</h2>
<div id="speaker-grid" style="display:grid; grid-template-columns:repeat(3, 1fr); gap:1rem; max-height:400px; overflow-y:auto; padding:5px;"></div>
</div>
</div>
<div id="email-modal" class="modal-overlay">
<div class="modal-dialog" style="max-width:400px; text-align:center;">
<button class="close-modal-btn" onclick="document.getElementById('email-modal').classList.remove('visible')">×</button>
<h2 style="margin-top:0;">ورود / ثبت نام</h2>
<div id="auth-step-1">
<p style="color:var(--text-secondary); margin-bottom:1.5rem;">برای استفاده از امکانات، ایمیل خود را وارد کنید.</p>
<input type="email" id="auth-email-input" placeholder="example@gmail.com" style="margin-bottom:1rem;">
<button class="generate-btn" onclick="handleSendCode()">ارسال کد تایید</button>
</div>
<div id="auth-step-2" style="display:none;">
<p style="color:var(--text-secondary); margin-bottom:1.5rem;">کد ۶ رقمی ارسال شده به ایمیل را وارد کنید.</p>
<input type="text" id="auth-code-input" placeholder="123456" style="margin-bottom:1rem; text-align:center; letter-spacing:5px; font-weight:700;">
<button class="generate-btn" onclick="handleVerifyCode()">تایید و ورود</button>
<button style="background:none; border:none; color:var(--text-tertiary); margin-top:1rem; cursor:pointer;" onclick="document.getElementById('auth-step-2').style.display='none';document.getElementById('auth-step-1').style.display='block'">بازگشت</button>
</div>
</div>
</div>
<!-- Hidden audio element & storage -->
<audio id="hidden-audio-player" style="display: none;"></audio>
<input type="hidden" id="selected_speaker_id_storage" value="Charon">
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// --- Config & State ---
const PROXY_URL = '/tts/proxy.php';
let currentUser = { email: null, status: 'free', fingerprint: null };
let selectedVcModel = null;
// --- Database of Models (VC & TTS) ---
const ttsSpeakers = [ { id: "Charon", name: "شهاب (مرد)", imgUrl: "https://uploadkon.ir/uploads/a18705_25IMG-۲۰۲۵۰۷۰۵-۱۱۰۵۴۹.jpg" }, { id: "Zephyr", name: "آوا (زن)", imgUrl: "https://uploadkon.ir/uploads/029605_25IMG-۲۰۲۵۰۷۰۵-۱۱۱۲۵۲.jpg" }, { id: "Achird", name: "نوید (مرد)", imgUrl: "https://uploadkon.ir/uploads/697e05_25IMG-۲۰۲۵۰۶۰۹-۰۶۴۶۳۷.jpg" }, { id: "Zubenelgenubi", name: "آرمان (مرد)", imgUrl: "https://uploadkon.ir/uploads/a8a705_25IMG-۲۰۲۵۰۷۰۵-۱۱۱۶۲۹.jpg" }, { id: "Vindemiatrix", name: "مهسا (زن)", imgUrl: "https://uploadkon.ir/uploads/d74d05_25IMG-۲۰۲۵۰۷۰۵-۱۱۱۸۳۸.jpg" } ];
const vcModels = [
{ id: 'shadmehr', name: 'شادمهر عقیلی', img: 'https://app.puzzley.net/uploads/user/Jydo/%D8%AA%D8%BA%DB%8C%D8%B1%20%D8%B5%D8%AF%D8%A7%20%D8%A8%D8%A7%20%D9%87%D9%88%D8%B4%20%D9%85%D8%B5%D9%86%D9%88%D8%B9%DB%8C/1000188203.jpg?_t=1725334498', ref: 'https://uploadkon.ir/uploads/55c918_25شادمهر-قوی-2-.mp3' },
{ id: 'moein', name: 'معین', img: 'https://app.puzzley.net/uploads/user/Jydo/%D8%AA%D8%BA%DB%8C%D8%B1%20%D8%B5%D8%AF%D8%A7%20%D8%A8%D8%A7%20%D9%87%D9%88%D8%B4%20%D9%85%D8%B5%D9%86%D9%88%D8%B9%DB%8C/5dbc55de-d6ab-442f-9a00-da874521cc0b.jpg?_t=1725334795', ref: 'https://uploadkon.ir/uploads/f8bb17_25معین-2-.mp3' },
{ id: 'billie', name: 'بیلی آیلیش', img: 'https://app.puzzley.net/uploads/user/Jydo/%D8%AA%D8%BA%DB%8C%D8%B1%20%D8%B5%D8%AF%D8%A7%20%D8%A8%D8%A7%20%D9%87%D9%88%D8%B4%20%D9%85%D8%B5%D9%86%D9%88%D8%B9%DB%8C/1551c598-f02f-4ced-a037-33d2d7317edd.jpg?_t=1726723022', ref: 'https://uploadkon.ir/uploads/c21018_25بیلی-آیلیش-2-.mp3' },
{ id: 'chavoshi', name: 'محسن چاوشی', img: 'https://app.puzzley.net/uploads/user/Jydo/%D8%AA%D8%BA%DB%8C%D8%B1%20%D8%B5%D8%AF%D8%A7%20%D8%A8%D8%A7%20%D9%87%D9%88%D8%B4%20%D9%85%D8%B5%D9%86%D9%88%D8%B9%DB%8C/c52eefb1-071e-40ea-9bc2-e20a7c29cb81.jpg?_t=1726907812', ref: 'https://uploadkon.ir/uploads/7ca518_25محسن-چاووشی-3-2-.mp3' },
{ id: 'ferdosipour', name: 'عادل فردوسی‌پور', img: 'https://app.puzzley.net/uploads/user/Jydo/%D8%AA%D8%BA%DB%8C%D8%B1%20%D8%B5%D8%AF%D8%A7%20%D8%A8%D8%A7%20%D9%87%D9%88%D8%B4%20%D9%85%D8%B5%D9%86%D9%88%D8%B9%DB%8C/1000188207.jpg?_t=1725334637', ref: 'https://uploadkon.ir/uploads/b5f918_25-عادل-فردوسی-پور-2-.mp3' },
{ id: 'pishro', name: 'رضا پیشرو', img: 'https://uploadkon.ir/uploads/580205_25IMG-۲۰۲۵۰۷۰۵-۱۱۳۳۳۰.jpg', ref: 'https://uploadkon.ir/uploads/659018_25%D8%B3%D9%88%D8%B1%D9%86%D8%A7-2-.mp3' } // Placeholder for example
];
// --- Helper Functions ---
async function getFingerprint() {
// Simple fingerprint for demo
return 'fp_' + Math.floor(Math.random() * 1000000000).toString(16);
}
// --- Initialization ---
async function init() {
currentUser.fingerprint = await getFingerprint();
checkUserStatus(localStorage.getItem('userEmail'));
initTtsSpeakers();
initVcModels();
// Set default TTS speaker
updateSelectedTtsSpeaker('Charon');
}
// --- Tab Switching Logic ---
window.switchTab = (tabId) => {
document.querySelectorAll('.nav-item').forEach(el => el.classList.remove('active'));
document.querySelectorAll('.content-tab').forEach(el => el.classList.remove('active'));
// Find the button that called this and make active
const btns = document.querySelectorAll('.nav-item');
if(tabId === 'tts') btns[0].classList.add('active');
if(tabId === 'vc') btns[1].classList.add('active');
if(tabId === 'podcast') btns[2].classList.add('active');
document.getElementById(`tab-content-${tabId}`).classList.add('active');
};
// --- Auth Logic ---
async function checkUserStatus(email) {
if (!email) { updateUserUI(null, 'free'); return; }
try {
const res = await fetch('/tts/check_status.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({email})
});
const data = await res.json();
currentUser.email = email;
currentUser.status = data.status;
updateUserUI(email, data.status);
} catch(e) { console.error(e); updateUserUI(null, 'free'); }
}
function updateUserUI(email, status) {
const container = document.getElementById('user-status-container');
const loginBtn = document.getElementById('login-check-btn');
if(email) {
container.style.display = 'flex';
loginBtn.style.display = 'none';
document.getElementById('user-email-display').textContent = email;
const badge = document.getElementById('user-sub-status-display');
badge.textContent = status === 'paid' ? 'اشتراک ویژه' : 'رایگان';
badge.className = `user-sub-status ${status === 'paid' ? 'status-paid' : 'status-free'}`;
} else {
container.style.display = 'none';
loginBtn.style.display = 'block';
}
}
window.showEmailModal = () => document.getElementById('email-modal').classList.add('visible');
window.handleSendCode = async () => {
const email = document.getElementById('auth-email-input').value;
if(!email) return alert('لطفا ایمیل را وارد کنید');
// Call send_code.php
try {
const res = await fetch('/tts/send_code.php', { method: 'POST', body: JSON.stringify({email}) });
const d = await res.json();
if(d.status === 'success') {
document.getElementById('auth-step-1').style.display='none';
document.getElementById('auth-step-2').style.display='block';
} else alert(d.message);
} catch(e) { alert('خطا در ارسال کد'); }
};
window.handleVerifyCode = async () => {
const email = document.getElementById('auth-email-input').value;
const code = document.getElementById('auth-code-input').value;
try {
const res = await fetch('/tts/verify_code.php', { method: 'POST', body: JSON.stringify({email, code}) });
const d = await res.json();
if(d.status === 'success') {
localStorage.setItem('userEmail', email);
checkUserStatus(email);
document.getElementById('email-modal').classList.remove('visible');
} else alert(d.message);
} catch(e) { alert('خطا در تایید'); }
};
document.getElementById('logout-btn').addEventListener('click', () => {
localStorage.removeItem('userEmail');
fetch('/tts/logout.php');
location.reload();
});
// --- TTS Logic (Keeping User's Logic) ---
function initTtsSpeakers() {
const grid = document.getElementById('speaker-grid');
ttsSpeakers.forEach(s => {
const div = document.createElement('div');
div.className = 'vc-model-item'; // Reuse styles
div.innerHTML = `<img src="${s.imgUrl}"><span>${s.name}</span>`;
div.onclick = () => { updateSelectedTtsSpeaker(s.id); document.getElementById('speaker-modal').classList.remove('visible'); };
grid.appendChild(div);
});
}
function updateSelectedTtsSpeaker(id) {
const s = ttsSpeakers.find(x => x.id === id) || ttsSpeakers[0];
document.getElementById('selected-speaker-img').src = s.imgUrl;
document.getElementById('selected-speaker-name').textContent = s.name;
document.getElementById('selected_speaker_id_storage').value = s.id;
}
// Reuse TTS Generate Logic from user's code, but adapted to new fetch wrapper if needed.
// For exactness, I'm pasting the core logic:
const ttsForm = document.getElementById('standard-tts-form');
ttsForm.addEventListener('submit', async () => {
const text = document.getElementById('text-input-standard').value;
const btn = document.getElementById('generate-btn-standard');
const output = document.getElementById('output-section-standard');
const loader = document.getElementById('loading-animation-wrapper-standard');
const player = document.getElementById('audio-player-content-standard');
if(!currentUser.email) { window.showEmailModal(); return; }
if(!text) return alert('متن خالی است');
btn.disabled = true;
btn.querySelector('.spinner').style.display='inline-block';
btn.querySelector('.btn-text').textContent = 'در حال پردازش...';
output.classList.remove('has-content');
document.getElementById('status-message-standard').style.display='none';
loader.style.display='flex';
player.innerHTML = '';
try {
// Split text, loop chunks, merge... (Simplified for this single file to direct call)
// Using the PROXY:
const res = await fetch(`${PROXY_URL}?endpoint=generate`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
text: text,
speaker: document.getElementById('selected_speaker_id_storage').value,
temperature: parseFloat(document.getElementById('temperature-slider-standard').value),
email: currentUser.email,
fingerprint: currentUser.fingerprint
})
});
if(res.status === 429) throw new Error('محدودیت اعتبار روزانه.');
if(!res.ok) throw new Error('خطا در تولید صدا');
const blob = await res.blob();
const url = URL.createObjectURL(blob);
// Create Audio Player
player.style.display = 'block';
player.innerHTML = `
<div class="simple-player-container">
<audio controls src="${url}" style="width:100%"></audio>
<a href="${url}" class="audio-download-btn-new" download="aisada-tts.wav">دانلود فایل صوتی</a>
</div>
`;
output.classList.add('has-content');
loader.style.display='none';
} catch(e) {
alert(e.message);
document.getElementById('status-message-standard').style.display='block';
document.getElementById('status-message-standard').textContent = e.message;
loader.style.display='none';
} finally {
btn.disabled = false;
btn.querySelector('.spinner').style.display='none';
btn.querySelector('.btn-text').textContent = '✨ خلق صدا با آلفا';
}
});
// --- Voice Changer Logic (New & Full) ---
function initVcModels() {
const container = document.getElementById('vc-models-container');
vcModels.forEach(m => {
const div = document.createElement('div');
div.className = 'vc-model-item';
div.innerHTML = `<img src="${m.img}"><span>${m.name}</span>`;
div.onclick = () => {
document.querySelectorAll('.vc-model-item').forEach(el => el.classList.remove('selected'));
div.classList.add('selected');
selectedVcModel = m;
};
container.appendChild(div);
});
}
window.handleVcFileSelect = (input) => {
if(input.files && input.files[0]) {
document.getElementById('vc-filename-display').textContent = input.files[0].name;
document.getElementById('vc-file-status').style.display = 'flex';
}
};
window.startVoiceConversion = async () => {
if(!currentUser.email) { window.showEmailModal(); return; }
if(!selectedVcModel) return alert('لطفا یک مدل را انتخاب کنید.');
const fileInput = document.getElementById('vc-file-input');
if(!fileInput.files[0]) return alert('لطفا فایل صدای خود را آپلود کنید.');
const btn = document.getElementById('vc-generate-btn');
const loader = document.getElementById('vc-loader');
const resultContainer = document.getElementById('vc-result-container');
const statusMsg = document.getElementById('vc-status-message');
const outputSection = document.getElementById('vc-output-section');
btn.disabled = true;
btn.querySelector('.spinner').style.display='inline-block';
statusMsg.style.display = 'none';
resultContainer.style.display = 'none';
loader.style.display = 'flex';
outputSection.classList.remove('has-content');
try {
// 1. Fetch Reference File (Server-side simulation via client fetch)
// Note: In production, server should handle this. Here we fetch the ref file
// to send it to the proxy which expects 'ref_audio'.
const refBlob = await fetch(selectedVcModel.ref).then(r => r.blob());
const sourceFile = fileInput.files[0];
const formData = new FormData();
formData.append('email', currentUser.email);
formData.append('fingerprint', currentUser.fingerprint);
formData.append('source_audio', sourceFile);
formData.append('ref_audio', refBlob, 'ref.mp3');
// 2. Upload
const uploadRes = await fetch(`${PROXY_URL}?endpoint=vc-upload`, {
method: 'POST',
body: formData
});
if(!uploadRes.ok) {
const err = await uploadRes.json();
throw new Error(err.message || 'خطا در آپلود');
}
const uploadData = await uploadRes.json();
const jobId = uploadData.job_id;
// 3. Poll Status
const checkInterval = setInterval(async () => {
try {
const statusRes = await fetch(`${PROXY_URL}?endpoint=vc-status`, {
method: 'POST',
body: JSON.stringify({ job_id: jobId })
});
const statusData = await statusRes.json();
if (statusData.status === 'completed') {
clearInterval(checkInterval);
// Construct download URL (using direct HF link for speed, or proxy if needed)
// Using direct link but presented nicely
const finalUrl = `https://ezmary-sada.hf.space/download/${statusData.filename}`;
loader.style.display = 'none';
resultContainer.style.display = 'block';
document.getElementById('vc-audio-player').src = finalUrl;
document.getElementById('vc-download-btn').href = finalUrl;
outputSection.classList.add('has-content');
btn.disabled = false;
btn.querySelector('.spinner').style.display='none';
} else if (statusData.status === 'failed') {
clearInterval(checkInterval);
throw new Error('پردازش با خطا مواجه شد.');
}
} catch(e) {
clearInterval(checkInterval);
alert(e.message);
loader.style.display = 'none';
statusMsg.style.display = 'block';
btn.disabled = false;
btn.querySelector('.spinner').style.display='none';
}
}, 4000);
} catch(e) {
alert(e.message);
loader.style.display = 'none';
statusMsg.style.display = 'block';
btn.disabled = false;
btn.querySelector('.spinner').style.display='none';
}
};
// --- Helpers ---
window.toggleFaq = (btn) => {
const item = btn.parentElement;
item.classList.toggle('active');
};
init();
});
</script>
</body>
</html>