Spaces:
Paused
Paused
| <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); | |
| } | |
| /* ===== BACKGROUND ===== */ | |
| .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); } | |
| } | |
| /* ===== LAYOUT ===== */ | |
| .app-wrapper { position: relative; z-index: 1; max-width: 720px; margin: 0 auto; padding: 0 16px 40px; } | |
| /* ===== HEADER ===== */ | |
| 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 ===== */ | |
| .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 ===== */ | |
| .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); } | |
| } | |
| /* ===== LANGUAGE ROW ===== */ | |
| .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 ===== */ | |
| .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 ; | |
| box-shadow: 0 0 0 4px rgba(251,146,60,0.15) ; | |
| } | |
| @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 BUTTON ===== */ | |
| .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 ===== */ | |
| .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 ===== */ | |
| .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 ===== */ | |
| .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 ===== */ | |
| .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 BUTTON ===== */ | |
| .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 ===== */ | |
| .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 ===== */ | |
| .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 ===== */ | |
| footer { padding: 16px 0 28px; } | |
| /* ===== SCROLLBAR ===== */ | |
| ::-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 --> | |
| <header> | |
| <div class="logo-wrap"> | |
| <div class="logo-icon-wrap"> | |
| <div class="logo-icon-pulse"></div> | |
| <div class="logo-icon-pulse2"></div> | |
| <!-- Google Translate style SVG icon --> | |
| <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> | |
| <!-- MAIN CARD --> | |
| <div class="card"> | |
| <!-- Language Row --> | |
| <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> | |
| <!-- Textarea --> | |
| <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> | |
| <!-- Translate Button --> | |
| <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> | |
| <!-- Settings --> | |
| <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> | |
| <!-- Output --> | |
| <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><!-- end .card --> | |
| <footer></footer> | |
| </div> | |
| <script> | |
| // ===== RTL LANGUAGES MAP ===== | |
| const RTL_LANGS = new Set(['فارسی','عربی','هبری','اردو','پشتو','کردی','سندی','دیوهی']); | |
| function isRTL(langName) { | |
| return RTL_LANGS.has(langName); | |
| } | |
| // ===== TOAST ===== | |
| 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); | |
| } | |
| // ===== SLIDERS ===== | |
| 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); }); | |
| }); | |
| // ===== ELEMENTS ===== | |
| 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'); | |
| // ===== TEXT DIRECTION HELPERS ===== | |
| 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'; | |
| } | |
| // ===== LOAD CONFIG ===== | |
| 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); }); | |
| // ===== SWAP ===== | |
| 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); } | |
| }); | |
| // ===== CLEAR ===== | |
| clearBtn.addEventListener('click', () => { | |
| inputText.value = ''; | |
| inputText.focus(); | |
| checkDynamicDirection(); | |
| outputSection.classList.remove('visible'); | |
| setTimeout(() => { outputSection.style.display = 'none'; }, 450); | |
| }); | |
| // ===== COPY ===== | |
| 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); | |
| }); | |
| }); | |
| // ===== DOWNLOAD ===== | |
| 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); | |
| } | |
| }); | |
| // ===== TRANSLATE ===== | |
| 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> |