ttup / index.html
Phoe2004's picture
Upload 5 files
bf9831a verified
<!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;
}
/* ── Background grid ── */
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;
}
/* ── Glow blobs ── */
.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 ── */
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 ── */
.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 input row ── */
.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 selector ── */
.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);
}
/* ── Video info preview ── */
#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;}
/* ── Download btn ── */
.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;}
/* ── Progress ── */
#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 ── */
#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;}
/* ── Error ── */
.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 ── */
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">
<!-- URL input -->
<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>
<!-- Info preview -->
<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>
<!-- Quality -->
<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>
<!-- Download button -->
<button class="btn-dl" id="btn-dl" onclick="startDownload()">⬇ DOWNLOAD</button>
<!-- Progress -->
<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>
<!-- Result -->
<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>
<!-- Error -->
<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;
// Show progress
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;
}
// Enter key support
document.getElementById('url-input').addEventListener('keydown', function(e){
if(e.key==='Enter') fetchInfo();
});
</script>
</body>
</html>