diff --git a/script.js b/script.js index 5fbda41..8c180ff 100644 --- a/script.js +++ b/script.js @@ -23,6 +23,7 @@ document.addEventListener('DOMContentLoaded', () => { const fmtRadios = document.getElementsByName('opt-fmt'); let debounceTimer; + let renderSeq = 0; // --- 2. STATE GETTERS --- @@ -136,6 +137,8 @@ document.addEventListener('DOMContentLoaded', () => { // --- 4. CORE RENDERER --- function renderQR() { + const mySeq = ++renderSeq; + // 1. Gather State const textData = getDataString(); const format = getFormat(); @@ -143,35 +146,38 @@ document.addEventListener('DOMContentLoaded', () => { const colorDark = optFg.value; const colorLight = optBg.value; - // 2. Validate Size (Do this EARLY so we can update the label) + // 2. Validate Size & Labels let size = parseInt(optSize.value) || 256; if (size < 64) size = 64; if (size > 4000) size = 4000; - // 3. Update UI Labels (Stateless: Input -> Label) hexFg.textContent = colorDark; hexBg.textContent = colorLight; - // Update Resolution Label immediately const resDisplay = document.getElementById('resolution-display'); if (resDisplay) resDisplay.textContent = `${size} x ${size} px`; - // 4. Reset DOM - qrContainer.innerHTML = ''; + // 3. Reset Error State stickyWrapper.classList.remove('has-error'); - // 5. Handle Empty State (The Early Return) + // 4. Handle Empty State if (!textData || textData.trim() === '') { - downloadBtn.disabled = true; - downloadBtn.textContent = `DOWNLOAD ${format.toUpperCase()}`; - return; // Stop here. Container is clean, labels are updated. + // Only clear if we are the latest render + if (mySeq === renderSeq) { + qrContainer.innerHTML = ''; + downloadBtn.disabled = true; + downloadBtn.textContent = `DOWNLOAD ${format.toUpperCase()}`; + } + return; } downloadBtn.textContent = `DOWNLOAD ${format.toUpperCase()}`; - // 6. Generate + // 5. Generate (Off-screen) try { - const instance = new QRCode(qrContainer, { + const tempContainer = document.createElement('div'); + + const instance = new QRCode(tempContainer, { text: textData, width: size, height: size, @@ -180,29 +186,61 @@ document.addEventListener('DOMContentLoaded', () => { correctLevel : ecc }); - // 7. Post-Process if (format === 'svg') { const modules = instance._oQRCode.modules; - const nodes = qrContainer.childNodes; - for(let i=0; i { + newImg.onload = resolve; + newImg.onerror = reject; + }); + + ready.then(() => { + // [CHECK] If a newer render started while we were decoding, ABORT. + if (mySeq !== renderSeq) return; + + qrContainer.replaceChildren(newImg); + downloadBtn.disabled = false; + }).catch(() => { + // Even on error, respect the sequence + if (mySeq !== renderSeq) return; + // Fallback: Just shove it in + qrContainer.replaceChildren(newImg); + downloadBtn.disabled = false; + }); + } } - downloadBtn.disabled = false; - } catch (e) { - // 8. Error Handling + // Error Handling (Sequence Check Not Strictly Needed here but good practice) + if (mySeq !== renderSeq) return; + const blob = new Blob([textData]); const bytes = blob.size; - const maxCapacityMap = { 'L': 2950, 'M': 2328, 'Q': 1660, 'H': 1270 }; - const maxBytes = maxCapacityMap[optEcc.value] || 1270; + const maxCapacityMap = { 'L': 2953, 'M': 2331, 'Q': 1663, 'H': 1273 }; + const maxBytes = maxCapacityMap[optEcc.value] || 1273; if (bytes >= maxBytes) { renderError("CAPACITY EXCEEDED", "REDUCE TEXT OR LOWER ECC LEVEL"); @@ -252,11 +290,11 @@ document.addEventListener('DOMContentLoaded', () => { document.querySelectorAll('.input-form').forEach(f => f.classList.remove('active')); document.getElementById('form-text').classList.add('active'); - // 5. Update Output (This will clear the QR code since inputs are empty) + // 5. Update Output handleUpdate(true); // 6. Focus the first box - const firstField = document.querySelector('#form-text textarea'); // Textarea for text mode + const firstField = document.querySelector('#form-text textarea'); if (firstField) firstField.focus(); } @@ -328,7 +366,6 @@ document.addEventListener('DOMContentLoaded', () => { const initialForm = document.getElementById(`form-${initialMode}`); if (initialForm) { initialForm.classList.add('active'); - // Auto-focus the restored form const startField = initialForm.querySelector('input, textarea'); if (startField) startField.focus(); } diff --git a/style.css b/style.css index 91203a2..06a8e33 100644 --- a/style.css +++ b/style.css @@ -220,10 +220,22 @@ details .control-group:last-child { background: #fff; } -#qr-container img, #qr-container canvas { +/* Inside .preview-pane or relevant section */ + +#qr-container img, +#qr-container canvas, +#qr-container svg { display: block; - max-width: 100%; - image-rendering: pixelated; /* Crisp edges */ + + /* CHANGE: Force it to fill the container exactly */ + width: 100%; + height: 100%; + + /* CRITICAL: This ensures upscaling looks like squares, not blur */ + image-rendering: pixelated; + + /* Optional: Ensure the SVG scales correctly */ + object-fit: contain; } /* Buttons */