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();