| <!DOCTYPE html> |
| <html lang="fa" dir="rtl"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> |
| <title>آلفا مترجم | Alpha Translator</title> |
| <link href="https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@v33.0.0/Vazirmatn-font-face.css" rel="stylesheet"> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
|
| <style> |
| :root { |
| --sky: #38bdf8; |
| --sky-deep: #0ea5e9; |
| --sky-pale: #e0f2fe; |
| --ocean: #0369a1; |
| --mint: #34d399; |
| --lavender: #818cf8; |
| --peach: #fb923c; |
| --bg: #f0f9ff; |
| --card: #ffffff; |
| --text: #0f172a; |
| --muted: #64748b; |
| --border: #e2e8f0; |
| --shadow: 0 20px 60px -10px rgba(14,165,233,0.15); |
| } |
| |
| * { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; } |
| |
| body { |
| font-family: 'Vazirmatn', sans-serif; |
| background: var(--bg); |
| min-height: 100vh; |
| overflow-x: hidden; |
| color: var(--text); |
| } |
| |
| |
| .bg-scene { |
| position: fixed; inset: 0; z-index: 0; overflow: hidden; pointer-events: none; |
| } |
| .bg-scene::before { |
| content: ''; position: absolute; top: -20%; right: -10%; |
| width: 70vw; height: 70vw; border-radius: 50%; |
| background: radial-gradient(circle, rgba(56,189,248,0.18) 0%, transparent 70%); |
| animation: floatBlob 12s ease-in-out infinite; |
| } |
| .bg-scene::after { |
| content: ''; position: absolute; bottom: -10%; left: -5%; |
| width: 55vw; height: 55vw; border-radius: 50%; |
| background: radial-gradient(circle, rgba(129,140,248,0.14) 0%, transparent 70%); |
| animation: floatBlob 15s ease-in-out infinite reverse; |
| } |
| @keyframes floatBlob { |
| 0%,100% { transform: translate(0,0) scale(1); } |
| 33% { transform: translate(3%,4%) scale(1.04); } |
| 66% { transform: translate(-2%,2%) scale(0.97); } |
| } |
| |
| |
| .app-wrapper { position: relative; z-index: 1; max-width: 720px; margin: 0 auto; padding: 0 16px 40px; } |
| |
| |
| header { |
| padding: 32px 0 20px; text-align: center; |
| animation: headerDrop 0.8s cubic-bezier(0.34,1.56,0.64,1) both; |
| } |
| @keyframes headerDrop { |
| 0% { opacity:0; transform: translateY(-40px) scale(0.9); } |
| 100% { opacity:1; transform: translateY(0) scale(1); } |
| } |
| .logo-wrap { display: inline-flex; align-items: center; justify-content: center; gap: 14px; } |
| |
| |
| .logo-icon-wrap { position: relative; width: 64px; height: 64px; } |
| .logo-icon-wrap svg { width: 64px; height: 64px; filter: drop-shadow(0 4px 18px rgba(14,165,233,0.4)); } |
| .logo-icon-pulse { |
| position: absolute; inset: -6px; border-radius: 50%; |
| border: 2px solid rgba(56,189,248,0.5); |
| animation: pulse-ring 2s ease-out infinite; |
| } |
| .logo-icon-pulse2 { |
| position: absolute; inset: -14px; border-radius: 50%; |
| border: 2px solid rgba(56,189,248,0.25); |
| animation: pulse-ring 2s ease-out 0.6s infinite; |
| } |
| @keyframes pulse-ring { |
| 0% { transform: scale(0.9); opacity: 1; } |
| 100% { transform: scale(1.5); opacity: 0; } |
| } |
| |
| .logo-text { display: flex; flex-direction: column; align-items: flex-start; line-height: 1; gap: 4px; } |
| .logo-title { |
| font-size: 2rem; font-weight: 900; |
| background: linear-gradient(135deg, var(--sky-deep) 0%, var(--lavender) 100%); |
| -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; |
| } |
| .logo-sub { font-size: 0.72rem; color: var(--muted); font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; } |
| |
| |
| .card { |
| background: rgba(255,255,255,0.82); |
| backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px); |
| border: 1px solid rgba(255,255,255,0.9); |
| border-radius: 28px; padding: 28px 24px; |
| box-shadow: var(--shadow), 0 1px 0 rgba(255,255,255,0.9) inset; |
| animation: cardRise 0.7s cubic-bezier(0.34,1.2,0.64,1) 0.2s both; |
| } |
| @keyframes cardRise { |
| 0% { opacity:0; transform: translateY(30px) scale(0.97); } |
| 100% { opacity:1; transform: translateY(0) scale(1); } |
| } |
| |
| |
| .lang-row { display: flex; align-items: flex-end; gap: 10px; margin-bottom: 20px; } |
| .lang-group { flex: 1; } |
| .lang-label { |
| display: flex; align-items: center; gap: 6px; |
| font-size: 0.7rem; font-weight: 700; color: var(--muted); |
| text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 6px; |
| } |
| .lang-label .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--sky-deep); } |
| |
| .select-wrap { position: relative; } |
| .select-wrap select { |
| width: 100%; appearance: none; -webkit-appearance: none; |
| background: rgba(248,250,252,0.9); |
| border: 1.5px solid var(--border); border-radius: 14px; |
| padding: 13px 14px 13px 40px; |
| font-family: 'Vazirmatn', sans-serif; font-size: 0.9rem; font-weight: 600; |
| color: var(--text); cursor: pointer; transition: all 0.25s ease; outline: none; |
| } |
| .select-wrap select:focus { |
| border-color: var(--sky); background: #fff; |
| box-shadow: 0 0 0 4px rgba(56,189,248,0.12); |
| } |
| .select-arrow { |
| position: absolute; left: 14px; top: 50%; |
| transform: translateY(-50%); color: var(--muted); |
| pointer-events: none; font-size: 0.7rem; |
| } |
| |
| .swap-btn { |
| flex-shrink: 0; width: 44px; height: 44px; border-radius: 50%; |
| background: linear-gradient(135deg, var(--sky-pale), #ede9fe); |
| border: 1.5px solid rgba(56,189,248,0.3); |
| display: flex; align-items: center; justify-content: center; |
| cursor: pointer; transition: all 0.35s cubic-bezier(0.34,1.56,0.64,1); |
| color: var(--sky-deep); font-size: 0.85rem; margin-bottom: 2px; |
| } |
| .swap-btn:hover { |
| transform: rotate(180deg) scale(1.12); |
| background: linear-gradient(135deg, #bae6fd, #c7d2fe); |
| box-shadow: 0 4px 16px rgba(56,189,248,0.3); |
| } |
| |
| |
| .textarea-wrap { position: relative; margin-bottom: 28px; } |
| .textarea-wrap textarea { |
| width: 100%; min-height: 140px; resize: none; |
| border: 1.5px solid var(--border); border-radius: 18px; |
| padding: 18px 18px 50px; |
| font-family: 'Vazirmatn', sans-serif; font-size: 1.05rem; line-height: 1.8; |
| color: var(--text); background: rgba(248,250,252,0.8); |
| outline: none; transition: all 0.3s ease; |
| } |
| .textarea-wrap textarea:focus { |
| border-color: var(--sky); background: #fff; |
| box-shadow: 0 0 0 4px rgba(56,189,248,0.1); |
| } |
| .textarea-wrap textarea::placeholder { color: #94a3b8; } |
| .textarea-wrap textarea.shake { |
| animation: shakeField 0.5s cubic-bezier(0.36,0.07,0.19,0.97) both; |
| border-color: #fb923c !important; |
| box-shadow: 0 0 0 4px rgba(251,146,60,0.15) !important; |
| } |
| @keyframes shakeField { |
| 10%,90% { transform: translateX(-4px); } |
| 20%,80% { transform: translateX(6px); } |
| 30%,50%,70% { transform: translateX(-6px); } |
| 40%,60% { transform: translateX(6px); } |
| } |
| .textarea-hint { |
| position: absolute; bottom: -26px; right: 0; |
| font-size: 0.78rem; color: #fb923c; font-weight: 700; |
| opacity: 0; transform: translateY(-4px); transition: all 0.3s ease; |
| display: flex; align-items: center; gap: 5px; |
| } |
| .textarea-hint.show { opacity: 1; transform: translateY(0); } |
| .clear-btn { |
| position: absolute; bottom: 14px; left: 14px; |
| width: 32px; height: 32px; border-radius: 50%; |
| background: white; border: 1px solid var(--border); |
| display: flex; align-items: center; justify-content: center; |
| cursor: pointer; color: var(--muted); font-size: 0.8rem; |
| transition: all 0.2s; box-shadow: 0 2px 8px rgba(0,0,0,0.06); |
| } |
| .clear-btn:hover { color: #ef4444; transform: scale(1.1); } |
| |
| |
| .translate-btn-wrap { margin-top: 4px; } |
| .translate-btn { |
| width: 100%; padding: 17px 24px; border-radius: 18px; border: none; |
| background: linear-gradient(135deg, var(--sky-deep) 0%, #6366f1 100%); |
| color: white; font-family: 'Vazirmatn', sans-serif; |
| font-size: 1.1rem; font-weight: 800; letter-spacing: 0.02em; |
| cursor: pointer; display: flex; align-items: center; justify-content: center; |
| gap: 10px; position: relative; overflow: hidden; |
| transition: all 0.3s cubic-bezier(0.34,1.2,0.64,1); |
| box-shadow: 0 8px 32px rgba(3,105,161,0.35), 0 1px 0 rgba(255,255,255,0.15) inset; |
| } |
| .translate-btn::before { |
| content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; |
| background: linear-gradient(90deg, transparent, rgba(255,255,255,0.18), transparent); |
| transition: left 0.5s ease; |
| } |
| .translate-btn:hover::before { left: 100%; } |
| .translate-btn:hover { transform: translateY(-2px); box-shadow: 0 14px 40px rgba(3,105,161,0.4); } |
| .translate-btn:active { transform: scale(0.98); } |
| .translate-btn:disabled { opacity: 0.85; cursor: not-allowed; transform: none; } |
| .btn-icon-wrap { |
| width: 34px; height: 34px; background: rgba(255,255,255,0.2); |
| border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 1rem; |
| } |
| |
| |
| .spinner { |
| width: 22px; height: 22px; |
| border: 2.5px solid rgba(255,255,255,0.3); border-top-color: white; |
| border-radius: 50%; animation: spin 0.7s linear infinite; |
| } |
| @keyframes spin { to { transform: rotate(360deg); } } |
| |
| |
| .settings-panel { |
| margin-top: 18px; border-radius: 18px; |
| background: rgba(248,250,252,0.7); border: 1.5px solid var(--border); overflow: hidden; |
| } |
| .settings-toggle { |
| display: flex; justify-content: space-between; align-items: center; |
| padding: 14px 18px; cursor: pointer; user-select: none; list-style: none; |
| } |
| .settings-toggle::-webkit-details-marker { display: none; } |
| .settings-label { display: flex; align-items: center; gap: 8px; font-size: 0.85rem; font-weight: 700; color: var(--muted); } |
| .settings-label .badge { |
| background: linear-gradient(135deg, var(--sky-pale), #ede9fe); |
| color: var(--ocean); font-size: 0.65rem; padding: 2px 8px; |
| border-radius: 20px; font-weight: 800; |
| } |
| .settings-chevron { color: var(--muted); font-size: 0.75rem; transition: transform 0.3s ease; } |
| details[open] .settings-chevron { transform: rotate(180deg); } |
| .settings-body { |
| padding: 0 18px 18px; border-top: 1px solid var(--border); |
| display: flex; flex-direction: column; gap: 16px; |
| animation: fadeDown 0.3s ease both; |
| } |
| @keyframes fadeDown { |
| from { opacity: 0; transform: translateY(-8px); } |
| to { opacity: 1; transform: translateY(0); } |
| } |
| .voice-select-wrap { margin-top: 12px; } |
| .settings-row { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 14px; } |
| @media (max-width: 500px) { .settings-row { grid-template-columns: 1fr; } } |
| .slider-group label { |
| display: flex; justify-content: space-between; align-items: center; |
| font-size: 0.73rem; font-weight: 700; color: var(--muted); margin-bottom: 6px; |
| } |
| .slider-group label span { |
| background: linear-gradient(135deg, var(--sky-deep), var(--lavender)); |
| -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-weight: 800; |
| } |
| input[type="range"] { |
| -webkit-appearance: none; width: 100%; height: 4px; |
| border-radius: 4px; background: linear-gradient(to left, var(--sky-deep) 50%, #e2e8f0 50%); |
| outline: none; cursor: pointer; |
| } |
| input[type="range"]::-webkit-slider-thumb { |
| -webkit-appearance: none; width: 18px; height: 18px; border-radius: 50%; |
| background: white; border: 2.5px solid var(--sky-deep); |
| box-shadow: 0 2px 8px rgba(3,105,161,0.25); cursor: pointer; transition: transform 0.15s; |
| } |
| input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.2); } |
| |
| #voiceSelect { |
| width: 100%; appearance: none; -webkit-appearance: none; |
| background: white; border: 1.5px solid var(--border); border-radius: 12px; |
| padding: 11px 14px 11px 38px; |
| font-family: 'Vazirmatn', sans-serif; font-size: 0.85rem; font-weight: 600; |
| color: var(--text); outline: none; transition: all 0.2s; cursor: pointer; |
| } |
| #voiceSelect:focus { border-color: var(--sky); box-shadow: 0 0 0 4px rgba(56,189,248,0.1); } |
| |
| |
| .output-section { |
| margin-top: 20px; display: none; flex-direction: column; gap: 14px; |
| opacity: 0; transform: translateY(16px); |
| transition: opacity 0.45s ease, transform 0.45s cubic-bezier(0.34,1.2,0.64,1); |
| } |
| .output-section.visible { display: flex; opacity: 1; transform: translateY(0); } |
| |
| .output-box { |
| position: relative; |
| background: linear-gradient(135deg, rgba(224,242,254,0.6), rgba(237,233,254,0.4)); |
| border: 1.5px solid rgba(56,189,248,0.25); border-radius: 18px; |
| padding: 22px 18px 52px; min-height: 90px; |
| } |
| .output-chip { |
| position: absolute; top: -12px; right: 18px; |
| background: linear-gradient(135deg, var(--sky-deep), var(--lavender)); |
| color: white; font-size: 0.7rem; font-weight: 800; |
| padding: 4px 14px; border-radius: 20px; letter-spacing: 0.05em; |
| box-shadow: 0 4px 12px rgba(3,105,161,0.3); |
| } |
| .output-text { |
| font-size: 1.05rem; line-height: 1.9; color: var(--text); |
| white-space: pre-wrap; margin-top: 8px; |
| } |
| .copy-btn { |
| position: absolute; bottom: 14px; left: 14px; |
| width: 34px; height: 34px; border-radius: 50%; |
| background: white; border: 1.5px solid rgba(56,189,248,0.3); |
| display: flex; align-items: center; justify-content: center; |
| cursor: pointer; color: var(--sky-deep); font-size: 0.8rem; |
| transition: all 0.2s; box-shadow: 0 2px 10px rgba(3,105,161,0.1); |
| } |
| .copy-btn:hover { background: var(--sky-pale); transform: scale(1.1); } |
| |
| |
| .audio-player-wrap { |
| background: white; border: 1.5px solid var(--border); border-radius: 16px; |
| padding: 16px; box-shadow: 0 4px 16px rgba(0,0,0,0.04); |
| display: flex; flex-direction: column; gap: 14px; |
| } |
| .audio-player-row { |
| display: flex; align-items: center; gap: 8px; width: 100%; |
| } |
| audio { flex: 1; height: 38px; border-radius: 8px; outline: none; min-width: 0; } |
| |
| |
| .download-btn { |
| align-self: center; |
| display: flex; align-items: center; justify-content: center; gap: 8px; |
| padding: 12px 24px; border-radius: 14px; border: none; |
| background: linear-gradient(135deg, #0ea5e9, #6366f1); |
| color: white; font-family: 'Vazirmatn', sans-serif; |
| font-size: 0.85rem; font-weight: 800; cursor: pointer; |
| transition: all 0.25s cubic-bezier(0.34,1.3,0.64,1); |
| box-shadow: 0 4px 14px rgba(14,165,233,0.35); |
| white-space: nowrap; position: relative; overflow: hidden; |
| min-width: 200px; |
| } |
| .download-btn::before { |
| content: ''; position: absolute; inset: 0; |
| background: linear-gradient(135deg, rgba(255,255,255,0.15), transparent); |
| border-radius: 14px; |
| } |
| .download-btn:hover { |
| transform: translateY(-2px) scale(1.04); |
| box-shadow: 0 8px 22px rgba(14,165,233,0.45); |
| } |
| .download-btn:active { transform: scale(0.97); } |
| .download-btn .dl-icon { |
| width: 22px; height: 22px; border-radius: 50%; |
| background: rgba(255,255,255,0.25); |
| display: flex; align-items: center; justify-content: center; |
| font-size: 0.72rem; flex-shrink: 0; |
| } |
| |
| |
| .section-divider { display: flex; align-items: center; gap: 10px; margin: 4px 0; } |
| .section-divider::before, .section-divider::after { |
| content: ''; flex: 1; height: 1px; |
| background: linear-gradient(to right, transparent, var(--border), transparent); |
| } |
| .section-divider span { font-size: 0.65rem; font-weight: 800; color: var(--muted); letter-spacing: 0.1em; text-transform: uppercase; } |
| |
| |
| .toast-container { |
| position: fixed; top: 20px; right: 50%; transform: translateX(50%); |
| z-index: 9999; display: flex; flex-direction: column; gap: 10px; pointer-events: none; |
| } |
| .toast { |
| background: white; border-radius: 14px; padding: 12px 20px; |
| font-size: 0.88rem; font-weight: 700; |
| display: flex; align-items: center; gap: 10px; |
| box-shadow: 0 8px 32px rgba(0,0,0,0.12); border: 1.5px solid var(--border); |
| pointer-events: auto; animation: toastIn 0.4s cubic-bezier(0.34,1.56,0.64,1) both; |
| min-width: 240px; max-width: 90vw; |
| } |
| .toast.leaving { animation: toastOut 0.3s ease both; } |
| @keyframes toastIn { from { opacity:0; transform: translateY(-20px) scale(0.9); } to { opacity:1; transform: translateY(0) scale(1); } } |
| @keyframes toastOut { from { opacity:1; transform: translateY(0) scale(1); } to { opacity:0; transform: translateY(-12px) scale(0.92); } } |
| .toast-icon { width: 30px; height: 30px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 0.85rem; flex-shrink: 0; } |
| .toast.success .toast-icon { background: #dcfce7; color: #16a34a; } |
| .toast.error .toast-icon { background: #fee2e2; color: #dc2626; } |
| .toast.info .toast-icon { background: var(--sky-pale); color: var(--sky-deep); } |
| |
| |
| footer { padding: 16px 0 28px; } |
| |
| |
| ::-webkit-scrollbar { width: 5px; } |
| ::-webkit-scrollbar-track { background: transparent; } |
| ::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 10px; } |
| </style> |
| </head> |
| <body> |
|
|
| <div class="bg-scene"></div> |
| <div class="toast-container" id="toastContainer"></div> |
|
|
| <div class="app-wrapper"> |
|
|
| |
| <header> |
| <div class="logo-wrap"> |
| <div class="logo-icon-wrap"> |
| <div class="logo-icon-pulse"></div> |
| <div class="logo-icon-pulse2"></div> |
| |
| <svg viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg"> |
| <defs> |
| <linearGradient id="lg2" x1="22" y1="24" x2="56" y2="58" gradientUnits="userSpaceOnUse"> |
| <stop offset="0%" stop-color="#0ea5e9"/> |
| <stop offset="100%" stop-color="#6366f1"/> |
| </linearGradient> |
| </defs> |
| <rect x="8" y="12" width="34" height="34" rx="8" fill="#e0f2fe" stroke="#0ea5e9" stroke-width="2.5"/> |
| <text x="25" y="36" fill="#0ea5e9" font-size="22" font-family="Arial, sans-serif" font-weight="900" text-anchor="middle">A</text> |
| <rect x="22" y="24" width="34" height="34" rx="8" fill="url(#lg2)" stroke="#ffffff" stroke-width="3"/> |
| <text x="39" y="48" fill="#ffffff" font-size="22" font-family="Arial, sans-serif" font-weight="bold" text-anchor="middle">文</text> |
| </svg> |
| </div> |
| <div class="logo-text"> |
| <span class="logo-title">آلفا مترجم</span> |
| <span class="logo-sub">Alpha Translator</span> |
| </div> |
| </div> |
| </header> |
|
|
| |
| <div class="card"> |
|
|
| |
| <div class="lang-row"> |
| <div class="lang-group"> |
| <div class="lang-label"><span class="dot"></span> زبان مبدأ</div> |
| <div class="select-wrap"> |
| <select id="sourceLang"> |
| <option value="شناسایی خودکار">🌍 شناسایی خودکار</option> |
| </select> |
| <i class="fa-solid fa-chevron-down select-arrow"></i> |
| </div> |
| </div> |
|
|
| <button class="swap-btn" id="swapBtn" title="تعویض زبانها"> |
| <i class="fa-solid fa-arrows-left-right"></i> |
| </button> |
|
|
| <div class="lang-group"> |
| <div class="lang-label"><span class="dot" style="background:var(--lavender)"></span> زبان مقصد</div> |
| <div class="select-wrap"> |
| <select id="targetLang"></select> |
| <i class="fa-solid fa-chevron-down select-arrow" style="color:var(--lavender)"></i> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="textarea-wrap" id="textareaWrap"> |
| <textarea id="inputText" rows="5" placeholder="متن خود را برای ترجمه اینجا وارد کنید..."></textarea> |
| <button class="clear-btn" id="clearBtn" title="پاک کردن"> |
| <i class="fa-solid fa-eraser"></i> |
| </button> |
| <div class="textarea-hint" id="textHint"> |
| <i class="fa-solid fa-triangle-exclamation"></i> لطفاً متن را وارد کنید |
| </div> |
| </div> |
|
|
| |
| <div class="translate-btn-wrap"> |
| <button class="translate-btn" id="translateBtn"> |
| <div class="btn-icon-wrap"> |
| <i class="fa-solid fa-wand-magic-sparkles"></i> |
| </div> |
| <span id="btnText">ترجمه و پخش صدا</span> |
| </button> |
| </div> |
|
|
| |
| <details class="settings-panel" id="settingsDetails"> |
| <summary class="settings-toggle"> |
| <div class="settings-label"> |
| <i class="fa-solid fa-sliders" style="color:var(--sky-deep)"></i> |
| تنظیمات گوینده و صدا |
| <span class="badge">پیشرفته</span> |
| </div> |
| <i class="fa-solid fa-chevron-down settings-chevron"></i> |
| </summary> |
| <div class="settings-body"> |
| <div class="voice-select-wrap"> |
| <div class="lang-label" style="margin-bottom:8px;"> |
| <i class="fa-solid fa-microphone" style="color:var(--sky-deep)"></i> انتخاب گوینده |
| </div> |
| <div class="select-wrap"> |
| <select id="voiceSelect"></select> |
| <i class="fa-solid fa-chevron-down select-arrow"></i> |
| </div> |
| </div> |
| <div class="section-divider"><span>تنظیمات صدا</span></div> |
| <div class="settings-row"> |
| <div class="slider-group"> |
| <label>سرعت <span id="rateVal">0</span></label> |
| <input type="range" id="rateSlider" min="-50" max="50" value="0"> |
| </div> |
| <div class="slider-group"> |
| <label>گام <span id="pitchVal">0</span></label> |
| <input type="range" id="pitchSlider" min="-50" max="50" value="0"> |
| </div> |
| <div class="slider-group"> |
| <label>حجم <span id="volVal">0</span></label> |
| <input type="range" id="volSlider" min="-50" max="50" value="0"> |
| </div> |
| </div> |
| </div> |
| </details> |
|
|
| |
| <div class="output-section" id="outputSection"> |
| <div class="output-box"> |
| <div class="output-chip">✦ ترجمه</div> |
| <p class="output-text" id="outputText"></p> |
| <button class="copy-btn" id="copyBtn" title="کپی متن"> |
| <i class="fa-regular fa-copy"></i> |
| </button> |
| </div> |
| <div class="audio-player-wrap"> |
| <div class="audio-player-row"> |
| <audio id="audioPlayer" controls controlsList="nodownload"> |
| مرورگر شما از پخش صدا پشتیبانی نمیکند. |
| </audio> |
| </div> |
| <button class="download-btn" id="downloadBtn" title="دانلود صدا"> |
| <div class="dl-icon"><i class="fa-solid fa-arrow-down"></i></div> |
| دانلود صدا |
| </button> |
| </div> |
| </div> |
|
|
| </div> |
|
|
| <footer></footer> |
| </div> |
|
|
| <script> |
| |
| const RTL_LANGS = new Set(['فارسی','عربی','هبری','اردو','پشتو','کردی','سندی','دیوهی']); |
| |
| function isRTL(langName) { |
| return RTL_LANGS.has(langName); |
| } |
| |
| |
| function showToast(msg, type = 'info', duration = 3500) { |
| const icons = { success: 'fa-check', error: 'fa-xmark', info: 'fa-info' }; |
| const container = document.getElementById('toastContainer'); |
| const toast = document.createElement('div'); |
| toast.className = `toast ${type}`; |
| toast.innerHTML = `<div class="toast-icon"><i class="fa-solid ${icons[type]}"></i></div><span>${msg}</span>`; |
| container.appendChild(toast); |
| setTimeout(() => { |
| toast.classList.add('leaving'); |
| setTimeout(() => toast.remove(), 300); |
| }, duration); |
| } |
| |
| |
| function updateSliderTrack(slider) { |
| const pct = ((slider.value - slider.min) / (slider.max - slider.min)) * 100; |
| slider.style.background = `linear-gradient(to left, var(--sky-deep) ${pct}%, #e2e8f0 ${pct}%)`; |
| } |
| ['rate','pitch','vol'].forEach(id => { |
| const slider = document.getElementById(id+'Slider'); |
| const valEl = document.getElementById(id+'Val'); |
| updateSliderTrack(slider); |
| slider.addEventListener('input', () => { valEl.textContent = slider.value; updateSliderTrack(slider); }); |
| }); |
| |
| |
| let languagesData = {}; |
| const sourceLang = document.getElementById('sourceLang'); |
| const targetLang = document.getElementById('targetLang'); |
| const voiceSelect = document.getElementById('voiceSelect'); |
| const inputText = document.getElementById('inputText'); |
| const translateBtn = document.getElementById('translateBtn'); |
| const btnText = document.getElementById('btnText'); |
| const outputSection = document.getElementById('outputSection'); |
| const outputText = document.getElementById('outputText'); |
| const audioPlayer = document.getElementById('audioPlayer'); |
| const clearBtn = document.getElementById('clearBtn'); |
| const copyBtn = document.getElementById('copyBtn'); |
| const downloadBtn = document.getElementById('downloadBtn'); |
| const textHint = document.getElementById('textHint'); |
| |
| |
| function applyInputDir(langName) { |
| const rtl = isRTL(langName) || langName === 'شناسایی خودکار'; |
| inputText.dir = rtl ? 'rtl' : 'ltr'; |
| inputText.style.textAlign = rtl ? 'right' : 'left'; |
| if (langName === 'شناسایی خودکار') { |
| checkDynamicDirection(); |
| } |
| } |
| |
| function checkDynamicDirection() { |
| if (sourceLang.value !== 'شناسایی خودکار') return; |
| const val = inputText.value; |
| const firstCharMatch = val.match(/\S/); |
| if (firstCharMatch) { |
| const rtlRegex = /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/; |
| const isRtl = rtlRegex.test(firstCharMatch[0]); |
| inputText.dir = isRtl ? 'rtl' : 'ltr'; |
| inputText.style.textAlign = isRtl ? 'right' : 'left'; |
| } else { |
| inputText.dir = 'rtl'; |
| inputText.style.textAlign = 'right'; |
| } |
| } |
| |
| inputText.addEventListener('input', checkDynamicDirection); |
| |
| function applyOutputDir(langName) { |
| const rtl = isRTL(langName); |
| outputText.dir = rtl ? 'rtl' : 'ltr'; |
| outputText.style.textAlign = rtl ? 'right' : 'left'; |
| } |
| |
| |
| async function loadConfig() { |
| try { |
| const res = await fetch('/api/config'); |
| const data = await res.json(); |
| languagesData = data.languages; |
| Object.keys(languagesData).forEach(lang => { |
| sourceLang.add(new Option(lang, lang)); |
| const isSelected = lang === 'انگلیسی'; |
| targetLang.add(new Option(lang, lang, isSelected, isSelected)); |
| }); |
| updateVoices('انگلیسی'); |
| applyInputDir('شناسایی خودکار'); |
| applyOutputDir('انگلیسی'); |
| } catch { |
| showToast('خطا در بارگذاری زبانها', 'error'); |
| } |
| } |
| |
| function updateVoices(langKey) { |
| voiceSelect.innerHTML = ''; |
| const voices = languagesData[langKey]?.voices || {}; |
| if (!Object.keys(voices).length) { voiceSelect.add(new Option('بدون پشتیبانی صوتی', '')); return; } |
| Object.keys(voices).forEach(vName => voiceSelect.add(new Option(vName, vName))); |
| } |
| |
| sourceLang.addEventListener('change', e => applyInputDir(e.target.value)); |
| targetLang.addEventListener('change', e => { updateVoices(e.target.value); applyOutputDir(e.target.value); }); |
| |
| |
| document.getElementById('swapBtn').addEventListener('click', () => { |
| const sv = sourceLang.value, tv = targetLang.value; |
| if (sv === 'شناسایی خودکار') { showToast('ابتدا یک زبان مبدأ انتخاب کنید', 'info'); return; } |
| for (let i = 0; i < sourceLang.options.length; i++) if (sourceLang.options[i].value === tv) { sourceLang.selectedIndex = i; break; } |
| for (let i = 0; i < targetLang.options.length; i++) if (targetLang.options[i].value === sv) { targetLang.selectedIndex = i; break; } |
| updateVoices(targetLang.value); |
| applyInputDir(sourceLang.value); |
| applyOutputDir(targetLang.value); |
| const translated = outputText.textContent; |
| if (translated) { inputText.value = translated; outputSection.classList.remove('visible'); setTimeout(() => { outputSection.style.display = 'none'; }, 450); } |
| }); |
| |
| |
| clearBtn.addEventListener('click', () => { |
| inputText.value = ''; |
| inputText.focus(); |
| checkDynamicDirection(); |
| outputSection.classList.remove('visible'); |
| setTimeout(() => { outputSection.style.display = 'none'; }, 450); |
| }); |
| |
| |
| copyBtn.addEventListener('click', () => { |
| navigator.clipboard.writeText(outputText.innerText).then(() => { |
| copyBtn.innerHTML = '<i class="fa-solid fa-check" style="color:#16a34a"></i>'; |
| showToast('متن کپی شد!', 'success', 2000); |
| setTimeout(() => copyBtn.innerHTML = '<i class="fa-regular fa-copy"></i>', 2000); |
| }); |
| }); |
| |
| |
| downloadBtn.addEventListener('click', () => { |
| const src = audioPlayer.src; |
| if (!src) return; |
| |
| |
| if (window.self !== window.top) { |
| |
| window.parent.postMessage({ |
| type: 'INITIATE_DOWNLOAD_FROM_URL', |
| payload: { |
| audioUrl: src |
| } |
| }, '*'); |
| showToast('در حال آمادهسازی فایل صوتی در برنامه...', 'success', 2000); |
| } else { |
| |
| const a = document.createElement('a'); |
| a.href = src; |
| a.download = 'alpha-translator-audio.mp3'; |
| a.click(); |
| showToast('در حال دانلود مستقیم...', 'success', 2000); |
| } |
| }); |
| |
| |
| translateBtn.addEventListener('click', async () => { |
| const text = inputText.value.trim(); |
| if (!text) { |
| inputText.classList.add('shake'); |
| textHint.classList.add('show'); |
| setTimeout(() => inputText.classList.remove('shake'), 500); |
| setTimeout(() => textHint.classList.remove('show'), 3000); |
| return; |
| } |
| |
| textHint.classList.remove('show'); |
| translateBtn.disabled = true; |
| btnText.innerHTML = '<span style="display:inline-flex; align-items:center; gap:8px;"><span class="spinner"></span> در حال پردازش...</span>'; |
| outputSection.classList.remove('visible'); |
| setTimeout(() => { outputSection.style.display = 'none'; }, 450); |
| |
| try { |
| const response = await fetch('/api/translate_and_speak', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ |
| text, |
| source_lang: sourceLang.value, |
| target_lang: targetLang.value, |
| voice_key: voiceSelect.value || '', |
| rate: parseInt(document.getElementById('rateSlider').value), |
| pitch: parseInt(document.getElementById('pitchSlider').value), |
| volume: parseInt(document.getElementById('volSlider').value) |
| }) |
| }); |
| |
| const result = await response.json(); |
| |
| if (result.success) { |
| outputText.innerText = result.translated_text; |
| applyOutputDir(targetLang.value); |
| |
| if (result.audio_url) { |
| audioPlayer.src = result.audio_url + '?t=' + Date.now(); |
| audioPlayer.parentElement.parentElement.style.display = 'flex'; |
| audioPlayer.play().catch(() => {}); |
| } else { |
| audioPlayer.parentElement.parentElement.style.display = 'none'; |
| } |
| |
| outputSection.style.display = 'flex'; |
| requestAnimationFrame(() => requestAnimationFrame(() => outputSection.classList.add('visible'))); |
| showToast('ترجمه با موفقیت انجام شد!', 'success'); |
| } else { |
| showToast(result.error || 'خطایی رخ داد', 'error'); |
| } |
| } catch { |
| showToast('خطا در ارتباط با سرور', 'error'); |
| } finally { |
| translateBtn.disabled = false; |
| btnText.innerHTML = 'ترجمه و پخش صدا'; |
| } |
| }); |
| |
| loadConfig(); |
| </script> |
| </body> |
| </html> |