| <!DOCTYPE html> |
| <html lang="my"> |
| <head> |
| <meta charset="UTF-8"/> |
| <meta name="viewport" content="width=device-width,initial-scale=1"/> |
| <title>YT Downloader</title> |
| <link href="https://fonts.googleapis.com/css2?family=Rajdhani:wght@400;600;700&family=Noto+Sans+Myanmar:wght@400;700&display=swap" rel="stylesheet"/> |
| <style> |
| *,*::before,*::after{box-sizing:border-box;margin:0;padding:0} |
| |
| :root{ |
| --bg:#0a0a0f; |
| --surface:#13131a; |
| --card:#1a1a25; |
| --border:#2a2a3a; |
| --accent:#ff3b3b; |
| --accent2:#ff7043; |
| --text:#f0f0f0; |
| --muted:#888; |
| --success:#00e676; |
| --radius:12px; |
| } |
| |
| body{ |
| background:var(--bg); |
| color:var(--text); |
| font-family:'Rajdhani',sans-serif; |
| min-height:100vh; |
| overflow-x:hidden; |
| } |
| |
| |
| body::before{ |
| content:''; |
| position:fixed;inset:0; |
| background-image: |
| linear-gradient(rgba(255,59,59,.04) 1px,transparent 1px), |
| linear-gradient(90deg,rgba(255,59,59,.04) 1px,transparent 1px); |
| background-size:40px 40px; |
| pointer-events:none; |
| } |
| |
| |
| .blob{ |
| position:fixed;border-radius:50%;filter:blur(120px); |
| pointer-events:none;z-index:0;opacity:.25; |
| } |
| .blob1{width:500px;height:500px;background:#ff3b3b;top:-200px;right:-150px;} |
| .blob2{width:400px;height:400px;background:#ff7043;bottom:-100px;left:-100px;} |
| |
| .wrap{ |
| position:relative;z-index:1; |
| max-width:640px;margin:0 auto; |
| padding:40px 20px 60px; |
| } |
| |
| |
| header{ |
| text-align:center; |
| margin-bottom:36px; |
| } |
| .logo-row{ |
| display:flex;align-items:center;justify-content:center;gap:12px; |
| margin-bottom:6px; |
| } |
| .logo-icon{ |
| width:44px;height:44px; |
| background:linear-gradient(135deg,var(--accent),var(--accent2)); |
| border-radius:10px; |
| display:grid;place-items:center; |
| font-size:22px; |
| } |
| header h1{ |
| font-size:2rem;font-weight:700;letter-spacing:2px; |
| background:linear-gradient(90deg,#fff 40%,var(--accent)); |
| -webkit-background-clip:text;-webkit-text-fill-color:transparent; |
| } |
| header p{ |
| font-family:'Noto Sans Myanmar',sans-serif; |
| font-size:.85rem;color:var(--muted);margin-top:4px; |
| } |
| |
| |
| .card{ |
| background:var(--card); |
| border:1px solid var(--border); |
| border-radius:var(--radius); |
| padding:28px; |
| margin-bottom:20px; |
| position:relative; |
| overflow:hidden; |
| } |
| .card::before{ |
| content:''; |
| position:absolute;top:0;left:0;right:0;height:2px; |
| background:linear-gradient(90deg,transparent,var(--accent),transparent); |
| } |
| |
| |
| .url-row{ |
| display:flex;gap:10px;align-items:stretch; |
| margin-bottom:18px; |
| } |
| .url-row input{ |
| flex:1; |
| background:var(--surface); |
| border:1.5px solid var(--border); |
| border-radius:8px; |
| color:var(--text); |
| font-family:'Rajdhani',sans-serif; |
| font-size:1rem; |
| padding:12px 16px; |
| outline:none; |
| transition:border-color .2s; |
| } |
| .url-row input:focus{border-color:var(--accent);} |
| .url-row input::placeholder{color:var(--muted);} |
| |
| .btn-fetch{ |
| background:linear-gradient(135deg,var(--accent),var(--accent2)); |
| border:none;border-radius:8px; |
| color:#fff;font-family:'Rajdhani',sans-serif; |
| font-size:.95rem;font-weight:600;letter-spacing:1px; |
| padding:12px 20px;cursor:pointer; |
| white-space:nowrap; |
| transition:opacity .15s,transform .1s; |
| } |
| .btn-fetch:hover{opacity:.88;} |
| .btn-fetch:active{transform:scale(.96);} |
| .btn-fetch:disabled{opacity:.4;cursor:not-allowed;} |
| |
| |
| .quality-label{ |
| font-size:.8rem;letter-spacing:1px;color:var(--muted); |
| text-transform:uppercase;margin-bottom:10px;display:block; |
| } |
| .quality-grid{ |
| display:grid;grid-template-columns:repeat(5,1fr);gap:8px; |
| margin-bottom:20px; |
| } |
| .q-btn{ |
| border:1.5px solid var(--border);border-radius:8px; |
| background:var(--surface);color:var(--muted); |
| font-family:'Rajdhani',sans-serif;font-size:.9rem;font-weight:600; |
| padding:10px 4px;cursor:pointer;text-align:center; |
| transition:all .15s; |
| } |
| .q-btn:hover{border-color:#555;color:var(--text);} |
| .q-btn.active{ |
| border-color:var(--accent);color:var(--text); |
| background:rgba(255,59,59,.15); |
| box-shadow:0 0 12px rgba(255,59,59,.25); |
| } |
| |
| |
| #info-box{display:none;} |
| .info-inner{ |
| display:flex;gap:14px;align-items:flex-start; |
| } |
| .thumb-wrap{ |
| flex-shrink:0;width:120px;height:68px; |
| border-radius:8px;overflow:hidden;background:#000; |
| } |
| .thumb-wrap img{width:100%;height:100%;object-fit:cover;} |
| .info-text{flex:1;min-width:0;} |
| .info-title{ |
| font-size:1rem;font-weight:600;line-height:1.3; |
| white-space:nowrap;overflow:hidden;text-overflow:ellipsis; |
| } |
| .info-meta{font-size:.8rem;color:var(--muted);margin-top:4px;} |
| |
| |
| .btn-dl{ |
| width:100%; |
| margin-top:20px; |
| background:linear-gradient(135deg,var(--accent),var(--accent2)); |
| border:none;border-radius:8px; |
| color:#fff;font-family:'Rajdhani',sans-serif; |
| font-size:1.05rem;font-weight:700;letter-spacing:2px; |
| padding:14px;cursor:pointer; |
| transition:opacity .15s,transform .1s; |
| display:none; |
| } |
| .btn-dl:hover{opacity:.88;} |
| .btn-dl:active{transform:scale(.98);} |
| .btn-dl:disabled{opacity:.4;cursor:not-allowed;} |
| |
| |
| #prog-box{display:none;margin-top:16px;} |
| .prog-msg{ |
| font-family:'Noto Sans Myanmar',sans-serif; |
| font-size:.9rem;color:var(--muted);margin-bottom:8px;text-align:center; |
| } |
| .prog-bar-wrap{ |
| height:6px;background:var(--surface);border-radius:999px;overflow:hidden; |
| } |
| .prog-bar-fill{ |
| height:100%;width:0%; |
| background:linear-gradient(90deg,var(--accent),var(--accent2)); |
| border-radius:999px; |
| transition:width .4s ease; |
| } |
| |
| |
| #result-box{display:none;margin-top:16px;} |
| .result-inner{ |
| background:rgba(0,230,118,.07); |
| border:1.5px solid rgba(0,230,118,.3); |
| border-radius:10px; |
| padding:18px; |
| text-align:center; |
| } |
| .result-inner .icon{font-size:2rem;margin-bottom:6px;} |
| .result-inner .done-title{ |
| font-family:'Noto Sans Myanmar',sans-serif; |
| font-weight:700;font-size:1rem;color:var(--success);margin-bottom:14px; |
| } |
| .btn-save{ |
| display:inline-block; |
| background:var(--success); |
| color:#000;font-family:'Rajdhani',sans-serif; |
| font-size:1rem;font-weight:700;letter-spacing:1px; |
| padding:12px 32px;border-radius:8px; |
| text-decoration:none;cursor:pointer; |
| transition:opacity .15s; |
| } |
| .btn-save:hover{opacity:.85;} |
| |
| |
| .err-box{ |
| background:rgba(255,59,59,.1); |
| border:1px solid rgba(255,59,59,.4); |
| border-radius:8px;padding:14px; |
| font-family:'Noto Sans Myanmar',sans-serif; |
| font-size:.88rem;color:#ff6b6b; |
| margin-top:14px;display:none;text-align:center; |
| } |
| |
| |
| footer{ |
| text-align:center; |
| color:var(--muted);font-size:.78rem; |
| padding-top:24px; |
| } |
| </style> |
| </head> |
| <body> |
| <div class="blob blob1"></div> |
| <div class="blob blob2"></div> |
|
|
| <div class="wrap"> |
| <header> |
| <div class="logo-row"> |
| <div class="logo-icon">▶</div> |
| <h1>YT DOWNLOADER</h1> |
| </div> |
| <p>YouTube · TikTok · Facebook · Instagram</p> |
| </header> |
|
|
| <div class="card"> |
| |
| <div class="url-row"> |
| <input id="url-input" type="url" placeholder="https://youtube.com/watch?v=..."/> |
| <button class="btn-fetch" id="btn-fetch" onclick="fetchInfo()">INFO</button> |
| </div> |
|
|
| |
| <div id="info-box" class="card" style="padding:16px;margin-bottom:0;"> |
| <div class="info-inner"> |
| <div class="thumb-wrap"><img id="info-thumb" src="" alt="thumb"/></div> |
| <div class="info-text"> |
| <div class="info-title" id="info-title"></div> |
| <div class="info-meta" id="info-meta"></div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div style="margin-top:20px;"> |
| <span class="quality-label">Quality ရွေးချယ်ပါ</span> |
| <div class="quality-grid"> |
| <button class="q-btn" data-q="1080" onclick="setQ(this)">1080p</button> |
| <button class="q-btn active" data-q="720" onclick="setQ(this)">720p</button> |
| <button class="q-btn" data-q="480" onclick="setQ(this)">480p</button> |
| <button class="q-btn" data-q="360" onclick="setQ(this)">360p</button> |
| <button class="q-btn" data-q="audio" onclick="setQ(this)">🎵 MP3</button> |
| </div> |
| </div> |
|
|
| |
| <button class="btn-dl" id="btn-dl" onclick="startDownload()">⬇ DOWNLOAD</button> |
|
|
| |
| <div id="prog-box"> |
| <div class="prog-msg" id="prog-msg">⏳ ...</div> |
| <div class="prog-bar-wrap"><div class="prog-bar-fill" id="prog-fill"></div></div> |
| </div> |
|
|
| |
| <div id="result-box"> |
| <div class="result-inner"> |
| <div class="icon">✅</div> |
| <div class="done-title">Download ပြီးပါပြီ!</div> |
| <a class="btn-save" id="btn-save" href="#" download>💾 Save File</a> |
| </div> |
| </div> |
|
|
| |
| <div class="err-box" id="err-box"></div> |
| </div> |
|
|
| <footer>PS Online · yt-dlp powered downloader</footer> |
| </div> |
|
|
| <script> |
| let selectedQ = '720'; |
| let currentTid = null; |
| let sseSource = null; |
| |
| function setQ(el){ |
| document.querySelectorAll('.q-btn').forEach(b=>b.classList.remove('active')); |
| el.classList.add('active'); |
| selectedQ = el.dataset.q; |
| } |
| |
| function showErr(msg){ |
| const b=document.getElementById('err-box'); |
| b.textContent=msg; b.style.display='block'; |
| } |
| function hideErr(){ document.getElementById('err-box').style.display='none'; } |
| |
| async function fetchInfo(){ |
| const url = document.getElementById('url-input').value.trim(); |
| if(!url){ showErr('URL ထည့်ပါ'); return; } |
| hideErr(); |
| document.getElementById('info-box').style.display='none'; |
| document.getElementById('btn-dl').style.display='none'; |
| document.getElementById('result-box').style.display='none'; |
| const btn = document.getElementById('btn-fetch'); |
| btn.disabled=true; btn.textContent='...'; |
| |
| try{ |
| const res = await fetch('/api/info',{ |
| method:'POST', |
| headers:{'Content-Type':'application/json'}, |
| body: JSON.stringify({url}) |
| }); |
| const d = await res.json(); |
| if(!d.ok){ showErr(d.msg||'Error'); return; } |
| |
| document.getElementById('info-thumb').src = d.thumbnail||''; |
| document.getElementById('info-title').textContent = d.title||'Unknown'; |
| const dur = d.duration ? fmtDur(d.duration) : ''; |
| const views = d.view_count ? fmtNum(d.view_count)+' views' : ''; |
| document.getElementById('info-meta').textContent = [d.uploader, dur, views].filter(Boolean).join(' · '); |
| document.getElementById('info-box').style.display='block'; |
| document.getElementById('btn-dl').style.display='block'; |
| }catch(e){ |
| showErr('Network error: '+e.message); |
| }finally{ |
| btn.disabled=false; btn.textContent='INFO'; |
| } |
| } |
| |
| function fmtDur(s){ |
| const h=Math.floor(s/3600), m=Math.floor((s%3600)/60), sec=s%60; |
| if(h>0) return `${h}:${String(m).padStart(2,'0')}:${String(sec).padStart(2,'0')}`; |
| return `${m}:${String(sec).padStart(2,'0')}`; |
| } |
| function fmtNum(n){ |
| if(n>=1e6) return (n/1e6).toFixed(1)+'M'; |
| if(n>=1e3) return (n/1e3).toFixed(1)+'K'; |
| return n; |
| } |
| |
| async function startDownload(){ |
| const url = document.getElementById('url-input').value.trim(); |
| if(!url){ showErr('URL ထည့်ပါ'); return; } |
| hideErr(); |
| document.getElementById('result-box').style.display='none'; |
| document.getElementById('btn-dl').disabled=true; |
| |
| |
| document.getElementById('prog-box').style.display='block'; |
| document.getElementById('prog-msg').textContent='⏳ Download စတင်နေသည်…'; |
| document.getElementById('prog-fill').style.width='5%'; |
| |
| if(sseSource){ sseSource.close(); sseSource=null; } |
| |
| try{ |
| const res = await fetch('/api/download',{ |
| method:'POST', |
| headers:{'Content-Type':'application/json'}, |
| body: JSON.stringify({url, quality: selectedQ}) |
| }); |
| const d = await res.json(); |
| if(!d.ok){ showErr(d.msg||'Error'); resetUI(); return; } |
| |
| currentTid = d.tid; |
| listenProgress(d.tid); |
| }catch(e){ |
| showErr('Network error: '+e.message); |
| resetUI(); |
| } |
| } |
| |
| function listenProgress(tid){ |
| sseSource = new EventSource('/api/progress/'+tid); |
| sseSource.onmessage = function(e){ |
| const d = JSON.parse(e.data); |
| document.getElementById('prog-msg').textContent = d.msg||''; |
| document.getElementById('prog-fill').style.width = (d.pct||0)+'%'; |
| |
| if(d.done){ |
| sseSource.close(); |
| document.getElementById('prog-box').style.display='none'; |
| document.getElementById('result-box').style.display='block'; |
| const sa = document.getElementById('btn-save'); |
| sa.href = d.file_url; |
| sa.setAttribute('download', d.file_name||'download'); |
| document.getElementById('btn-dl').disabled=false; |
| } |
| if(d.error){ |
| sseSource.close(); |
| showErr(d.msg||'Download မအောင်မြင်ပါ'); |
| resetUI(); |
| } |
| }; |
| sseSource.onerror = function(){ |
| sseSource.close(); |
| showErr('Connection error'); |
| resetUI(); |
| }; |
| } |
| |
| function resetUI(){ |
| document.getElementById('prog-box').style.display='none'; |
| document.getElementById('btn-dl').disabled=false; |
| } |
| |
| |
| document.getElementById('url-input').addEventListener('keydown', function(e){ |
| if(e.key==='Enter') fetchInfo(); |
| }); |
| </script> |
| </body> |
| </html> |
|
|