const API = 'https://cdn.madebydanny.uk';
// ── Utilities ──────────────────────────────────────────────────────
function formatBytes(b) {
if (!b) return '0 B';
const k = 1024, s = ['B','KB','MB','GB','TB'];
const i = Math.floor(Math.log(b) / Math.log(k));
return (b / Math.pow(k, i)).toFixed(1).replace(/\.0$/, '') + ' ' + s[i];
}
function fmt(n) { return Number(n).toLocaleString(); }
// ── Tabs ───────────────────────────────────────────────────────────
function switchTab(name, btn) {
document.querySelectorAll('.tab-pane').forEach(p => p.classList.remove('active'));
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
document.getElementById('tab-' + name).classList.add('active');
const button = btn || document.getElementById(name);
if (button) button.classList.add('active');
window.location.hash = name;
if (name === 'limits') loadLimits();
}
function handleHashChange() {
const hash = window.location.hash.slice(1);
if (hash && ['upload', 'about', 'how', 'build', 'limits'].includes(hash)) {
switchTab(hash);
}
}
// ── Stats ──────────────────────────────────────────────────────────
async function loadStats() {
try {
const r = await fetch(`${API}/stats`);
const d = await r.json();
if (!d.success) throw new Error();
const cat = d.stats.byCategory || {};
document.getElementById('stat-images').textContent = fmt(cat.image || 0);
document.getElementById('stat-videos').textContent = fmt(cat.video || 0);
document.getElementById('stat-gifs').textContent = fmt(cat.gif || 0);
document.getElementById('stat-documents').textContent = fmt(cat.document || 0);
document.getElementById('stat-storage').textContent = formatBytes(d.stats.totalSize);
document.querySelectorAll('.stat-value').forEach(v => v.classList.remove('loading'));
} catch {
document.querySelectorAll('.stat-value').forEach(v => {
v.textContent = '—';
v.classList.remove('loading');
});
}
}
// ── Limits ─────────────────────────────────────────────────────────
async function loadLimits() {
try {
const r = await fetch(`${API}/limits`);
const d = await r.json();
if (!d.success) throw new Error();
const { file_count, total_size, max_files, max_bytes, max_file } = d.limits;
document.getElementById('limit-max-file').textContent = formatBytes(max_file);
document.getElementById('limit-max-bytes').textContent = formatBytes(max_bytes);
document.getElementById('limit-max-files').textContent = max_files;
const filePct = Math.min((file_count / max_files) * 100, 100);
const bytesPct = Math.min((total_size / max_bytes) * 100, 100);
const filesFill = document.getElementById('usage-files-fill');
const bytesFill = document.getElementById('usage-bytes-fill');
filesFill.style.width = filePct + '%';
bytesFill.style.width = bytesPct + '%';
function fillClass(pct) {
return pct >= 90 ? 'usage-fill danger' : pct >= 70 ? 'usage-fill warn' : 'usage-fill';
}
filesFill.className = fillClass(filePct);
bytesFill.className = fillClass(bytesPct);
document.getElementById('usage-files-label').textContent =
`${fmt(file_count)} / ${fmt(max_files)}`;
document.getElementById('usage-bytes-label').textContent =
`${formatBytes(total_size)} / ${formatBytes(max_bytes)}`;
document.getElementById('usage-loading').style.display = 'none';
document.getElementById('usage-bars').style.display = 'block';
} catch {
document.getElementById('usage-loading').textContent = 'Could not load usage data.';
}
}
// ── File icon helper ───────────────────────────────────────────────
function getFileIcon(type) {
if (!type) return 'fa-solid fa-cloud-arrow-up';
if (type.startsWith('video/')) return 'fa-solid fa-film';
if (type === 'image/gif') return 'fa-solid fa-photo-film';
if (type.startsWith('image/')) return 'fa-regular fa-image';
if (type === 'application/pdf') return 'fa-solid fa-file-pdf';
if (type === 'text/csv') return 'fa-solid fa-file-csv';
if (type === 'text/plain') return 'fa-solid fa-file-lines';
return 'fa-solid fa-file-code';
}
// ── File select / drag-drop ────────────────────────────────────────
const fileInput = document.getElementById('file-input');
const dropZone = document.getElementById('drop-zone');
const fileInfo = document.getElementById('file-info');
function showFile(file) {
document.getElementById('drop-icon').className = getFileIcon(file.type);
document.getElementById('file-name').textContent = file.name;
document.getElementById('detail-name').textContent = file.name;
document.getElementById('detail-size').textContent = formatBytes(file.size);
document.getElementById('detail-type').textContent = file.type || 'unknown';
fileInfo.classList.add('show');
}
fileInput.addEventListener('change', () => { if (fileInput.files[0]) showFile(fileInput.files[0]); });
dropZone.addEventListener('dragover', e => { e.preventDefault(); dropZone.classList.add('drag-over'); });
dropZone.addEventListener('dragleave', e => { e.preventDefault(); dropZone.classList.remove('drag-over'); });
dropZone.addEventListener('drop', e => {
e.preventDefault();
dropZone.classList.remove('drag-over');
const f = e.dataTransfer.files[0];
if (f) { fileInput.files = e.dataTransfer.files; showFile(f); }
});
// ── Upload ─────────────────────────────────────────────────────────
const uploadBtn = document.getElementById('upload-btn');
const progressWrap = document.getElementById('progress-wrap');
const progressFill = document.getElementById('progress-fill');
const progressLabel = document.getElementById('progress-label');
const statusEl = document.getElementById('status');
const resultBox = document.getElementById('result-box');
const resultUrl = document.getElementById('result-url');
function setStatus(msg, color) {
statusEl.textContent = msg;
statusEl.style.color = color || 'var(--accent)';
}
function setProgress(pct) {
progressFill.style.width = pct + '%';
progressLabel.textContent = Math.round(pct) + '%';
}
function resetUploadUI(delay = 0) {
setTimeout(() => {
fileInput.value = '';
document.getElementById('drop-icon').className = 'fa-solid fa-cloud-arrow-up';
document.getElementById('file-name').textContent = 'Click to select or drag a file here';
fileInfo.classList.remove('show');
progressWrap.classList.remove('show');
setProgress(0);
if (window.turnstile) window.turnstile.reset();
}, delay);
}
uploadBtn.addEventListener('click', () => {
if (!fileInput.files[0]) {
setStatus('Please select a file first.', 'var(--red)');
return;
}
const token = window.turnstile?.getResponse?.();
if (!token) {
setStatus('Please complete the verification.', 'var(--red)');
return;
}
const file = fileInput.files[0];
uploadBtn.disabled = true;
uploadBtn.innerHTML = ' Uploading…';
setStatus('');
resultBox.classList.remove('show');
progressWrap.classList.add('show');
setProgress(0);
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', e => {
if (e.lengthComputable) setProgress((e.loaded / e.total) * 100);
});
xhr.addEventListener('load', () => {
setProgress(100);
try {
const data = JSON.parse(xhr.responseText);
if (data.success) {
resultUrl.textContent = data.url;
document.getElementById('open-link').href = data.url;
resultBox.classList.add('show');
setStatus('');
setTimeout(() => loadStats(), 600);
resetUploadUI(3000);
} else {
throw new Error(data.error || 'Upload failed');
}
} catch (err) {
progressWrap.classList.remove('show');
setStatus('Error: ' + err.message, 'var(--red)');
}
uploadBtn.disabled = false;
uploadBtn.innerHTML = ' Upload to MBD CDN';
});
xhr.addEventListener('error', () => {
progressWrap.classList.remove('show');
setStatus('Network error. Please try again.', 'var(--red)');
uploadBtn.disabled = false;
uploadBtn.innerHTML = ' Upload to MBD CDN';
});
const apiUrl = API + (API.includes('?') ? '&' : '?') + 'token=' + encodeURIComponent(token);
xhr.open('POST', apiUrl);
xhr.setRequestHeader('Content-Type', file.type || 'application/octet-stream');
xhr.send(file);
});
// ── Copy URL ─────────────────────────────────────────────────────
function copyUrl() {
navigator.clipboard.writeText(resultUrl.textContent);
const btn = document.getElementById('copy-btn');
btn.classList.add('copied');
btn.innerHTML = ' Copied';
setTimeout(() => {
btn.classList.remove('copied');
btn.innerHTML = ' Copy URL';
}, 2000);
}
// ── Init ───────────────────────────────────────────────────────────
window.addEventListener('hashchange', handleHashChange);
if (window.location.hash) handleHashChange();
loadStats();