document.addEventListener('DOMContentLoaded', () => { // --- GLOBALS --- const qrContainer = document.getElementById('qr-container'); const downloadBtn = document.getElementById('btn-download'); const stickyWrapper = document.querySelector('.sticky-wrapper'); // Gauge Elements const capMeter = document.getElementById('capacity-meter'); const capBar = document.getElementById('capacity-bar'); const capText = document.getElementById('capacity-text'); // Inputs const modeSelector = document.getElementById('mode-selector'); const optEcc = document.getElementById('opt-ecc'); const optSize = document.getElementById('opt-size'); const optFg = document.getElementById('opt-fg'); const optBg = document.getElementById('opt-bg'); const hexFg = document.getElementById('hex-fg'); const hexBg = document.getElementById('hex-bg'); // Format Radios const fmtRadios = document.getElementsByName('opt-fmt'); let qrcodeObj = null; let debounceTimer; // --- 1. DATA GATHERING --- function getFormat() { for (const r of fmtRadios) { if (r.checked) return r.value; } return 'png'; } function getDataString() { const mode = modeSelector.value; if (mode === 'text') { return document.getElementById('inp-text').value; } else if (mode === 'wifi') { const ssid = document.getElementById('inp-wifi-ssid').value; const pass = document.getElementById('inp-wifi-pass').value; const type = document.getElementById('inp-wifi-type').value; // return `WIFI:S:${ssid};T:${type};P:${pass};;`; if (!ssid) return ''; const cleanSSID = ssid.replace(/([\\;,:])/g, '\\$1'); const cleanPass = pass.replace(/([\\;,:])/g, '\\$1'); return `WIFI:S:${cleanSSID};T:${type};P:${cleanPass};;`; } else if (mode === 'email') { const to = document.getElementById('inp-email-to').value; const sub = document.getElementById('inp-email-sub').value; const body = document.getElementById('inp-email-body').value; if (!to) return ''; return `mailto:${to}?subject=${encodeURIComponent(sub)}&body=${encodeURIComponent(body)}`; } return ''; } // --- 2. INSTANCE MANAGEMENT --- function createQRInstance() { qrContainer.innerHTML = ''; let size = parseInt(optSize.value); if (isNaN(size) || size < 64) size = 64; if (size > 4000) size = 4000; const ecc = QRCode.CorrectLevel[optEcc.value]; const colorDark = optFg.value; const colorLight = optBg.value; // Update Labels (UI) hexFg.textContent = colorDark; hexBg.textContent = colorLight; try { return new QRCode(qrContainer, { width: size, height: size, colorDark : colorDark, colorLight : colorLight, correctLevel : ecc }); } catch (e) { return null; } } // --- 3. RENDERING HELPERS --- function renderSVG(modules) { const size = parseInt(optSize.value) || 256; const fg = optFg.value; const bg = optBg.value; const count = modules.length; const modSize = size / count; let pathData = ''; for (let r = 0; r < count; r++) { for (let c = 0; c < count; c++) { if (modules[r][c]) { const x = c * modSize; const y = r * modSize; pathData += `M${x},${y}h${modSize}v${modSize}h-${modSize}z`; } } } return ` `; } // --- 4. CORE FUNCTIONS --- // A. The Fast One: Updates the bar immediately function updateCapacityGauge(textData) { if (!textData || !capMeter) { if (capMeter) capMeter.style.display = 'none'; return; } const blob = new Blob([textData]); const bytes = blob.size; const maxCapacityMap = { 'L': 2953, 'M': 2331, 'Q': 1663, 'H': 1273 }; const maxBytes = maxCapacityMap[optEcc.value] || 1273; const usage = Math.min(100, Math.round((bytes / maxBytes) * 100)); if (usage > 5) { capMeter.style.display = 'block'; capBar.style.width = `${usage}%`; capText.textContent = `${usage}% (${bytes}B)`; if (usage > 90) { capBar.style.backgroundColor = 'red'; capText.style.color = 'red'; } else { capBar.style.backgroundColor = '#000'; capText.style.color = '#000'; } } else { capMeter.style.display = 'none'; } } // B. The Heavy One: Generates the image (Debounced) function generateQR(textData) { // Reset Error State stickyWrapper.classList.remove('has-error'); const format = getFormat(); // Handle Empty if (!textData || textData.trim() === '') { if (qrcodeObj) qrcodeObj.clear(); qrcodeObj = null; qrContainer.innerHTML = ''; downloadBtn.disabled = true; downloadBtn.textContent = `DOWNLOAD ${format.toUpperCase()}`; return; } // Ensure Instance if (!qrcodeObj) qrcodeObj = createQRInstance(); if (!qrcodeObj) return; downloadBtn.textContent = `DOWNLOAD ${format.toUpperCase()}`; try { // Generate // qrcodeObj.clear() qrcodeObj.makeCode(textData); // check if anything royally fucked up const imgs = qrContainer.querySelectorAll('img'); // if (imgs.length == 0) { // console.log('==================== YIKES =====================') // qrcodeObj = createQRInstance(); // qrcodeObj.makeCode(textData) // } // Render SVG vs PNG if (format === 'svg') { const imgs = qrContainer.querySelectorAll('img'); imgs.forEach(i => i.style.display = 'none'); const canvas = qrContainer.querySelectorAll('canvas'); canvas.forEach(c => c.style.display = 'none'); if (qrcodeObj._oQRCode && qrcodeObj._oQRCode.modules) { const svgString = renderSVG(qrcodeObj._oQRCode.modules); // Clear old SVGs/Errors qrContainer.querySelectorAll('svg, .qr-error-msg').forEach(e => e.remove()); qrContainer.insertAdjacentHTML('beforeend', svgString); } } else { // PNG Mode const imgs = qrContainer.querySelectorAll('img'); imgs.forEach(i => i.style.display = 'block'); qrContainer.querySelectorAll('svg, .qr-error-msg').forEach(e => e.remove()); } downloadBtn.disabled = false; } catch (e) { // Error Handling // console.log(e) qrcodeObj = null; qrContainer.innerHTML = `