diff --git a/index.html b/index.html
index 242124b..3743bfb 100644
--- a/index.html
+++ b/index.html
@@ -127,9 +127,21 @@
+
-
+
+
+
+
diff --git a/script.js b/script.js
index f191aaf..620d68c 100644
--- a/script.js
+++ b/script.js
@@ -2,7 +2,13 @@ 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');
@@ -11,14 +17,14 @@ document.addEventListener('DOMContentLoaded', () => {
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;
- // --- LOGIC ---
+ // --- 1. DATA GATHERING ---
function getFormat() {
for (const r of fmtRadios) {
@@ -27,36 +33,6 @@ document.addEventListener('DOMContentLoaded', () => {
return 'png';
}
- function createQRInstance() {
- // We clean the container but keep the instance variable if we can
- 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;
-
- hexFg.textContent = colorDark;
- hexBg.textContent = colorLight;
-
- // Initialize the library
- // Note: The library always creates a canvas internally.
- // We will just hide it if we are in SVG mode.
- qrcodeObj = new QRCode(qrContainer, {
- width: size,
- height: size,
- colorDark : colorDark,
- colorLight : colorLight,
- correctLevel : ecc
- });
-
- // Force update to draw
- updateQR();
- }
-
function getDataString() {
const mode = modeSelector.value;
if (mode === 'text') {
@@ -65,7 +41,8 @@ document.addEventListener('DOMContentLoaded', () => {
const ssid = document.getElementById('inp-wifi-ssid').value;
const pass = document.getElementById('inp-wifi-pass').value;
const type = document.getElementById('inp-wifi-type').value;
- if (!ssid) return '';
+ // 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};;`;
@@ -79,133 +56,230 @@ document.addEventListener('DOMContentLoaded', () => {
return '';
}
- // The Custom SVG Renderer
- // We read the grid data from the library and build a vector string
+ // --- 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;
-
- // Calculate module (pixel) size
const modSize = size / count;
let pathData = '';
-
- // Loop through the matrix
for (let r = 0; r < count; r++) {
for (let c = 0; c < count; c++) {
if (modules[r][c]) {
- // It's a black dot. Draw a rect.
- // To prevent "hairline cracks" between blocks in some viewers,
- // we can slightly overlap or just use standard math.
- // Standard math is usually fine for SVGs.
const x = c * modSize;
const y = r * modSize;
- // Using 'h' and 'v' in path is more efficient than rects
- pathData += `M${x},${y}h${modSize}v${modSize}h-${modSize}z`;
+ pathData += `M${x},${y}h${modSize}v${modSize}h-${modSize}z`;
}
}
}
- const svg = `
+ return `
`;
-
- return svg;
}
- function updateQR() {
- if (!qrcodeObj) return;
- const data = getDataString();
+ // --- 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();
- if (!data || data.trim() === '') {
- qrcodeObj.clear();
- qrContainer.innerHTML = ''; // Clear SVG leftovers
+ // Handle Empty
+ if (!textData || textData.trim() === '') {
+ if (qrcodeObj) qrcodeObj.clear();
+ qrcodeObj = null;
+ qrContainer.innerHTML = '';
downloadBtn.disabled = true;
downloadBtn.textContent = `DOWNLOAD ${format.toUpperCase()}`;
return;
}
- downloadBtn.disabled = false;
+ // Ensure Instance
+ if (!qrcodeObj) qrcodeObj = createQRInstance();
+ if (!qrcodeObj) return;
+
downloadBtn.textContent = `DOWNLOAD ${format.toUpperCase()}`;
- // 1. Let the library calculate the math
- qrcodeObj.makeCode(data);
+ try {
+ // Generate
+ // qrcodeObj.clear()
+ qrcodeObj.makeCode(textData);
- // 2. Handle Display based on format
- if (format === 'svg') {
- // Hide the canvas image generated by lib
+ // check if anything royally fucked up
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 (imgs.length == 0) {
+ // console.log('==================== YIKES =====================')
+ // qrcodeObj = createQRInstance();
+ // qrcodeObj.makeCode(textData)
+ // }
- // Access internal data to build SVG
- // Safety check: _oQRCode is the internal object of qrcode.js
- if (qrcodeObj._oQRCode && qrcodeObj._oQRCode.modules) {
- const svgString = renderSVG(qrcodeObj._oQRCode.modules);
+ // 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');
- // Remove old SVG if exists
- const oldSvg = qrContainer.querySelector('svg');
- if (oldSvg) oldSvg.remove();
-
- // Inject new SVG
- qrContainer.insertAdjacentHTML('beforeend', svgString);
+ 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());
}
- } else {
- // PNG Mode: Ensure canvas/img is visible
- // The library handles the rest
- const imgs = qrContainer.querySelectorAll('img');
- imgs.forEach(i => i.style.display = 'block');
- // Remove SVG if exists
- const oldSvg = qrContainer.querySelector('svg');
- if (oldSvg) oldSvg.remove();
+ downloadBtn.disabled = false;
+
+ } catch (e) {
+ // Error Handling
+ // console.log(e)
+ qrcodeObj = null;
+ qrContainer.innerHTML = `
+
+ ERROR: CAPACITY EXCEEDED
+
+ REDUCE TEXT OR
LOWER ECC LEVEL
+
+
+ `;
+ downloadBtn.disabled = true;
+ downloadBtn.textContent = "GENERATION FAILED";
}
}
+ // --- 5. THE COORDINATOR ---
+
+ function handleInput() {
+ console.log("Input detected. Text length:", getDataString().length);
+ const text = getDataString();
+ console.log(text)
+
+ // 1. Instant Feedback
+ updateCapacityGauge(text);
+
+ // 2. Delayed Heavy Lifting
+ clearTimeout(debounceTimer);
+ debounceTimer = setTimeout(() => {
+ generateQR(text);
+ }, 300);
+ }
+
+ // Force Immediate Update (for Config changes)
+ function forceUpdate() {
+ const text = getDataString();
+ updateCapacityGauge(text);
+ generateQR(text);
+ }
+
// --- EVENT LISTENERS ---
- // 1. Inputs triggering update
+ // Config Changes (Require Rebuild)
[optEcc, optSize, optFg, optBg].forEach(el => {
el.addEventListener('input', () => {
+ // Debounce the rebuild slightly
clearTimeout(debounceTimer);
- debounceTimer = setTimeout(createQRInstance, 100);
+ debounceTimer = setTimeout(() => {
+ qrcodeObj = null;
+ forceUpdate();
+ }, 100);
});
});
- // Format Change
- fmtRadios.forEach(r => {
- r.addEventListener('change', updateQR);
- });
+ // Format Change (Instant)
+ fmtRadios.forEach(r => r.addEventListener('change', forceUpdate));
- // Mode Switch
+ // Mode Switch (Instant)
modeSelector.addEventListener('change', (e) => {
const newMode = e.target.value;
document.querySelectorAll('.input-form').forEach(f => f.classList.remove('active'));
document.getElementById(`form-${newMode}`).classList.add('active');
- updateQR();
+ forceUpdate();
});
- // Data Entry
+ // DATA ENTRY (The optimized path)
document.querySelectorAll('.input-form').forEach(form => {
- form.addEventListener('input', () => {
- clearTimeout(debounceTimer);
- debounceTimer = setTimeout(updateQR, 300);
- });
+ form.addEventListener('input', handleInput);
});
- // 2. Download Logic
+ // Download
downloadBtn.addEventListener('click', () => {
const format = getFormat();
-
if (format === 'png') {
- // PNG Download
const img = qrContainer.querySelector('img');
if (img && img.src) {
const link = document.createElement('a');
@@ -213,28 +287,25 @@ document.addEventListener('DOMContentLoaded', () => {
link.download = `qr-manifesto-${Date.now()}.png`;
document.body.appendChild(link);
link.click();
- document.body.removeChild(link);
+ link.remove();
}
} else {
- // SVG Download
const svgEl = qrContainer.querySelector('svg');
if (svgEl) {
- // Serialize the SVG DOM to a string
const serializer = new XMLSerializer();
const svgString = serializer.serializeToString(svgEl);
const blob = new Blob([svgString], {type: 'image/svg+xml;charset=utf-8'});
const url = URL.createObjectURL(blob);
-
const link = document.createElement('a');
link.href = url;
link.download = `qr-manifesto-${Date.now()}.svg`;
document.body.appendChild(link);
link.click();
- document.body.removeChild(link);
+ link.remove();
}
}
});
- // Init
- createQRInstance();
+ // Initial Start
+ forceUpdate();
});
diff --git a/style.css b/style.css
index 4ce8b91..67c844e 100644
--- a/style.css
+++ b/style.css
@@ -445,3 +445,26 @@ input[type="radio"]:checked {
input[type="radio"]:focus {
outline: none;
}
+
+/* In-Box Error Message */
+.qr-error-msg {
+ color: red;
+ font-family: var(--font-mono);
+ font-weight: 700;
+ text-align: center;
+ text-transform: uppercase;
+ font-size: 0.9rem;
+ line-height: 1.4;
+
+ /* Brutalist Box inside the Box */
+ border: 2px solid red;
+ padding: 1rem;
+ background-color: #fff;
+ width: 80%; /* Don't touch the edges */
+}
+
+/* Ensure the container keeps its shape even when showing error */
+#qr-container {
+ /* Existing styles... */
+ position: relative; /* Just in case */
+}