/** * katdrop - minimal, smooth, and interactive */ (function () { 'use strict'; // DOM Elements const dropZone = document.getElementById('dropZone'); const fileInput = document.getElementById('fileInput'); const uploadBtn = document.getElementById('uploadBtn'); const fileSelection = document.getElementById('fileSelection'); const progressWrap = document.getElementById('progressWrap'); const progressBar = document.getElementById('progressBar'); const progressText = document.getElementById('progressText'); const fileList = document.getElementById('fileList'); const fileCount = document.getElementById('fileCount'); const backendStatus = document.getElementById('backendStatus'); const statusText = document.getElementById('statusText'); const toastEl = document.getElementById('toast'); let selectedFiles = []; // API & Key Configuration (configurable via URL param ?api=...&key=... or localStorage) const DEFAULT_REMOTE_API = 'https://feof-signals-fix-chevy.trycloudflare.com'; const urlParams = new URLSearchParams(window.location.search); if (urlParams.has('api')) { const customApi = urlParams.get('api').replace(/\/+$/, ''); localStorage.setItem('katdrop_api', customApi); } if (urlParams.has('key')) { localStorage.setItem('katdrop_key', urlParams.get('key')); } const isLocalOrigin = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1' || window.location.hostname.startsWith('192.168.'); const API_BASE = window.KATDROP_API || localStorage.getItem('katdrop_api') || (isLocalOrigin ? '' : DEFAULT_REMOTE_API); const API_KEY = window.KATDROP_KEY || localStorage.getItem('katdrop_key') || 'YOUR_API_KEY_HERE'; // Authenticated fetch helper function apiFetch(url, options = {}) { options.headers = options.headers || {}; if (API_KEY && API_KEY !== 'YOUR_API_KEY_HERE') { options.headers['x-katdrop-key'] = API_KEY; } return fetch(url, options); } // Toast let toastTimeout; function showToast(msg) { clearTimeout(toastTimeout); toastEl.textContent = `// ${msg}`; toastEl.classList.add('show'); toastTimeout = setTimeout(() => { toastEl.classList.remove('show'); }, 2200); } // Utility: Format bytes function formatBytes(bytes) { if (!bytes || bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; } // Backend Status Check async function checkBackendStatus() { if (!backendStatus || !statusText) return; backendStatus.className = 'backend-status checking'; statusText.textContent = 'checking...'; try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 4000); const res = await apiFetch(`${API_BASE}/health`, { signal: controller.signal }); clearTimeout(timeoutId); if (res.ok) { backendStatus.className = 'backend-status online'; statusText.textContent = 'online'; } else { throw new Error('Non-200'); } } catch (err) { backendStatus.className = 'backend-status offline'; statusText.textContent = 'offline'; } } // Interactive Particle Background function initInteractiveParticles() { const canvas = document.getElementById('particles'); if (!canvas) return; const ctx = canvas.getContext('2d'); let width = (canvas.width = window.innerWidth); let height = (canvas.height = window.innerHeight); window.addEventListener('resize', () => { width = canvas.width = window.innerWidth; height = canvas.height = window.innerHeight; }); // Mouse tracking for interactivity const mouse = { x: null, y: null, radius: 140 }; window.addEventListener('mousemove', e => { mouse.x = e.clientX; mouse.y = e.clientY; }); window.addEventListener('mouseleave', () => { mouse.x = null; mouse.y = null; }); // Click to push particles slightly window.addEventListener('click', e => { for (const p of particles) { const dx = p.x - e.clientX; const dy = p.y - e.clientY; const dist = Math.sqrt(dx * dx + dy * dy); if (dist < 180 && dist > 0) { const force = (180 - dist) / 180; p.vx += (dx / dist) * force * 3; p.vy += (dy / dist) * force * 3; } } }); const particles = []; const count = Math.min(50, Math.floor(width / 26)); for (let i = 0; i < count; i++) { particles.push({ x: Math.random() * width, y: Math.random() * height, vx: (Math.random() - 0.5) * 0.45, vy: (Math.random() - 0.5) * 0.45, radius: Math.random() * 1.5 + 1 }); } function animate() { ctx.clearRect(0, 0, width, height); // Connect particles to each other for (let i = 0; i < particles.length; i++) { for (let j = i + 1; j < particles.length; j++) { const dx = particles[i].x - particles[j].x; const dy = particles[i].y - particles[j].y; const dist = Math.sqrt(dx * dx + dy * dy); if (dist < 115) { ctx.strokeStyle = `rgba(255, 255, 255, ${0.06 * (1 - dist / 115)})`; ctx.lineWidth = 0.8; ctx.beginPath(); ctx.moveTo(particles[i].x, particles[i].y); ctx.lineTo(particles[j].x, particles[j].y); ctx.stroke(); } } } // Update & draw particles + connect to mouse for (const p of particles) { p.x += p.vx; p.y += p.vy; // Dampen velocity back to normal p.vx *= 0.985; p.vy *= 0.985; // Boundary bounce if (p.x < 0) { p.x = 0; p.vx *= -1; } if (p.x > width) { p.x = width; p.vx *= -1; } if (p.y < 0) { p.y = 0; p.vy *= -1; } if (p.y > height) { p.y = height; p.vy *= -1; } // Interaction with mouse cursor if (mouse.x !== null && mouse.y !== null) { const mdx = mouse.x - p.x; const mdy = mouse.y - p.y; const mDist = Math.sqrt(mdx * mdx + mdy * mdy); if (mDist < mouse.radius) { // Connect line to mouse ctx.strokeStyle = `rgba(77, 184, 255, ${0.18 * (1 - mDist / mouse.radius)})`; ctx.lineWidth = 0.9; ctx.beginPath(); ctx.moveTo(p.x, p.y); ctx.lineTo(mouse.x, mouse.y); ctx.stroke(); // Repel gently if very close if (mDist < 70 && mDist > 0) { const repel = (70 - mDist) / 70; p.x -= (mdx / mDist) * repel * 1.5; p.y -= (mdy / mDist) * repel * 1.5; } } } // Draw particle dot ctx.fillStyle = 'rgba(255, 255, 255, 0.28)'; ctx.beginPath(); ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2); ctx.fill(); } requestAnimationFrame(animate); } animate(); } // Safe HTML helper function escapeHtml(str) { return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } // Countdown Timer Management let countdownTimer = null; function formatTimeLeft(seconds) { if (seconds <= 0) return '00:00'; const m = Math.floor(seconds / 60); const s = seconds % 60; return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`; } function tickCountdowns() { const timerEls = document.querySelectorAll('.file-timer'); if (timerEls.length === 0) return; const now = Date.now(); let hasExpired = false; timerEls.forEach(el => { const expires = parseInt(el.dataset.expiresMs, 10); if (isNaN(expires)) return; const remainingSec = Math.max(0, Math.ceil((expires - now) / 1000)); el.textContent = formatTimeLeft(remainingSec); if (remainingSec <= 30) { el.className = 'file-timer critical'; } else if (remainingSec <= 120) { el.className = 'file-timer warning'; } else { el.className = 'file-timer'; } if (remainingSec === 0) { hasExpired = true; const card = el.closest('li.file-card'); if (card) card.classList.add('deleting'); } }); if (hasExpired) { setTimeout(fetchFiles, 400); } } function startCountdownLoop() { if (countdownTimer) clearInterval(countdownTimer); tickCountdowns(); countdownTimer = setInterval(tickCountdowns, 1000); } // Fetch and display files async function fetchFiles() { try { const res = await apiFetch(`${API_BASE}/files`); const files = await res.json(); fileList.innerHTML = ''; if (!files || files.length === 0) { fileList.innerHTML = '