Files
website/assets/qrcode/index.html
2025-11-19 06:59:37 -08:00

561 lines
19 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Just a QR Code</title>
<link rel="icon" href="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><rect width='32' height='32' fill='%23ffffff' rx='3' ry='3'/><rect x='8' y='8' width='4' height='4' fill='%23000000'/><rect x='12' y='8' width='4' height='4' fill='%23000000'/><rect x='16' y='8' width='4' height='4' fill='%23000000'/><rect x='8' y='12' width='4' height='4' fill='%23000000'/><rect x='8' y='16' width='4' height='4' fill='%23000000'/><rect x='20' y='8' width='4' height='4' fill='%23000000'/><rect x='16' y='12' width='4' height='4' fill='%23000000'/><rect x='20' y='16' width='4' height='4' fill='%23000000'/><rect x='8' y='20' width='4' height='4' fill='%23000000'/><rect x='12' y='20' width='4' height='4' fill='%23000000'/><rect x='16' y='20' width='4' height='4' fill='%23000000'/><rect x='20' y='20' width='4' height='4' fill='%23000000'/><rect x='20' y='12' width='4' height='4' fill='%23000000'/></svg>">
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrious/4.0.2/qrious.min.js"></script>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
line-height: 1.6;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f7;
color: #333;
}
h1 {
text-align: center;
margin-bottom: 30px;
color: #1d1d1f;
cursor: default;
user-select: none;
}
.container {
background-color: white;
border-radius: 10px;
padding: 30px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
position: relative;
}
.input-section {
margin-bottom: 40px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: 500;
}
input, select {
width: 100%;
padding: 10px;
margin-bottom: 15px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
font-size: 16px;
}
.text-input-wrapper {
position: relative;
margin-bottom: 15px;
}
.text-input-wrapper input {
padding-right: 40px;
margin-bottom: 0;
}
.clear-button {
position: absolute;
right: 0;
top: 0;
height: 100%;
width: 40px;
background: none;
border: none;
color: #666;
font-size: 18px;
cursor: pointer;
display: none;
padding: 0;
}
.clear-button:hover {
color: #333;
background: none;
}
.options-row {
display: flex;
gap: 15px;
}
.options-col {
flex: 1;
}
.color-input-wrapper {
position: relative;
}
input[type="color"] {
width: 100%;
height: 40px;
padding: 0;
border: 1px solid #ccc;
border-radius: 4px;
background-color: transparent;
cursor: pointer;
}
input[type="color"]::-webkit-color-swatch-wrapper {
padding: 0;
}
input[type="color"]::-webkit-color-swatch {
border: none;
border-radius: 3px;
}
.output-section {
text-align: center;
margin-top: 30px;
}
#qrcode {
margin: 0 auto;
padding: 15px;
background-color: white;
border-radius: 4px;
display: block;
}
.download-btn {
background-color: #ff9500;
margin-top: 15px;
display: none;
}
.download-btn:hover {
background-color: #e68600;
}
.error {
color: #e74c3c;
margin-top: 5px;
}
button {
background-color: #0071e3;
color: white;
border: none;
padding: 12px 20px;
border-radius: 6px;
cursor: pointer;
font-size: 16px;
font-weight: 500;
transition: background-color 0.2s;
width: 100%;
}
button:hover {
background-color: #0058b9;
}
.info-button {
position: absolute;
top: 20px;
right: 20px;
width: 30px;
height: 30px;
border-radius: 50%;
background: none;
border: none;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 18px;
color: #999;
transition: color 0.2s ease;
z-index: 10;
}
.info-button:hover {
color: #333;
background: none;
}
.info-panel {
position: fixed;
top: 0;
right: -350px;
width: 330px;
height: 100%;
background-color: white;
box-shadow: -2px 0 10px rgba(0,0,0,0.1);
transition: right 0.3s ease;
z-index: 100;
overflow-y: auto;
padding: 20px;
box-sizing: border-box;
}
.info-panel.open {
right: 0;
}
.info-panel-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.info-panel-header h2 {
margin: 0;
color: #333;
}
.info-close {
background: none;
border: none;
font-size: 22px;
color: #666;
cursor: pointer;
width: auto;
padding: 5px;
}
.info-close:hover {
color: #333;
background: none;
}
.info-content h3 {
font-size: 16px;
margin-top: 20px;
margin-bottom: 10px;
}
.info-content p {
font-size: 14px;
line-height: 1.6;
color: #555;
margin-bottom: 20px;
}
.footer {
margin-top: 30px;
text-align: center;
font-size: 14px;
color: #666;
}
.credit {
margin-top: 5px;
font-size: 12px;
color: #888;
}
.credit a {
text-decoration: none;
}
.credit a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div class="container">
<h1 id="title">Just a QR Code</h1>
<button id="info-button" class="info-button" aria-label="Information">?</button>
<div class="input-section">
<label for="text">Enter text or URL:</label>
<div class="text-input-wrapper">
<input type="text" id="text" placeholder="https://example.com" value="Gabe is cool" autofocus>
<button type="button" id="clear-text" class="clear-button" aria-label="Clear text"></button>
</div>
<div class="options-row">
<div class="options-col">
<label for="size">QR Code Size:</label>
<select id="size">
<option value="128">Small (128×128)</option>
<option value="256" selected>Medium (256×256)</option>
<option value="512">Large (512×512)</option>
<option value="1024">X-Large (1024×1024)</option>
</select>
</div>
<div class="options-col">
<label for="errorCorrection">Error Correction:</label>
<select id="errorCorrection">
<option value="L">Low (7%)</option>
<option value="M" selected>Medium (15%)</option>
<option value="Q">Quartile (25%)</option>
<option value="H">High (30%)</option>
</select>
</div>
</div>
<div class="options-row">
<div class="options-col">
<label for="foreground">Foreground Color:</label>
<div class="color-input-wrapper">
<input type="color" id="foreground" value="#000000">
</div>
</div>
<div class="options-col">
<label for="background">Background Color:</label>
<div class="color-input-wrapper">
<input type="color" id="background" value="#ffffff">
</div>
</div>
</div>
</div>
<div class="output-section">
<canvas id="qrcode"></canvas>
<div id="error-message" class="error"></div>
<button id="download" class="download-btn">Download QR Code</button>
</div>
</div>
<div class="info-panel" id="info-panel">
<div class="info-panel-header">
<h2>About</h2>
<button class="info-close" id="info-close"></button>
</div>
<div class="info-content">
<p>I needed to make a QR code -- again -- and faced with an array of janky free sites, wondered if I could just create a one-page app to do it on my terms. No ads, no trackers, no logging what I type, no upsell ... just a qr code.</p>
<p>What I ended up with is this one-page app that's completely yours to control. You can even download the source to your computer and run it from there. Read through the source if you like. It doesn't send your data to anyone.</p>
<p>If you found this useful, consider creating something similar of your own.</p>
<p>Be excellent to each other.</p>
</div>
</div>
<div class="footer">
This QR code generator works entirely in your browser. No data is sent to any server.
<p class="credit">made with moxie by <a href="https://www.gabe-sky.com/" target="_blank">gabe</a></p>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const textInput = document.getElementById('text');
const clearTextBtn = document.getElementById('clear-text');
const sizeSelect = document.getElementById('size');
const errorCorrectionSelect = document.getElementById('errorCorrection');
const foregroundColor = document.getElementById('foreground');
const backgroundColor = document.getElementById('background');
const downloadBtn = document.getElementById('download');
const qrcodeCanvas = document.getElementById('qrcode');
const errorMessage = document.getElementById('error-message');
const infoButton = document.getElementById('info-button');
const infoPanel = document.getElementById('info-panel');
const infoClose = document.getElementById('info-close');
const titleElement = document.getElementById('title');
// Title Easter egg
let titleClickCount = 0;
let titleTimeoutId = null;
titleElement.addEventListener('click', function() {
titleClickCount++;
clearTimeout(titleTimeoutId);
if (titleClickCount === 1) {
this.textContent = "Just a [bleep]ing QR Code";
titleTimeoutId = setTimeout(() => {
this.textContent = "Just a QR Code";
titleClickCount = 0;
}, 500);
}
});
// Info panel toggle
infoButton.addEventListener('click', function() {
infoPanel.classList.add('open');
});
infoClose.addEventListener('click', function() {
infoPanel.classList.remove('open');
});
// Close info panel when clicking outside
document.addEventListener('click', function(event) {
if (infoPanel.classList.contains('open') &&
!infoPanel.contains(event.target) &&
event.target !== infoButton) {
infoPanel.classList.remove('open');
}
});
// Close info panel with Escape key
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape' && infoPanel.classList.contains('open')) {
infoPanel.classList.remove('open');
}
});
// Set random compliment as default text and select it
textInput.value = getRandomCompliment();
textInput.select();
// Show/hide clear button based on text input content
function toggleClearButton() {
if (textInput.value.length > 0) {
clearTextBtn.style.display = 'block';
} else {
clearTextBtn.style.display = 'none';
}
}
// Initialize clear button visibility
toggleClearButton();
// Clear text when clear button is clicked
clearTextBtn.addEventListener('click', function() {
textInput.value = '';
toggleClearButton();
textInput.focus();
generateQRCode();
});
// Update clear button visibility when text changes
textInput.addEventListener('input', function() {
toggleClearButton();
generateQRCode();
});
let qr = null;
// Generate QR code when settings change
sizeSelect.addEventListener('change', generateQRCode);
errorCorrectionSelect.addEventListener('change', generateQRCode);
foregroundColor.addEventListener('input', generateQRCode);
backgroundColor.addEventListener('input', generateQRCode);
// Download QR code as image
downloadBtn.addEventListener('click', downloadQRCode);
// Generate a QR code on page load with default text
generateQRCode();
// Or check for URL parameters if present
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has('text')) {
textInput.value = urlParams.get('text');
generateQRCode();
}
function getRandomCompliment() {
const compliments = [
"Gabe is cool",
"Gabe is awesome",
"Gabe is neat-o",
"Gabe is the bee's knees",
"Gabe is remarkable",
"Gabe is better than average",
"Gabe rocks",
"Gabe = Genius",
"Gabe is the best",
"10 PRINT GABE RULEZ; 20 GOTO 10",
"Gabe's outie appreciates modern jazz",
"Lindsay spells it right",
"Aaron is amazing"
];
return compliments[Math.floor(Math.random() * compliments.length)];
}
function generateQRCode() {
const text = textInput.value.trim();
const size = parseInt(sizeSelect.value);
const errorCorrection = errorCorrectionSelect.value.toLowerCase();
const fgColor = foregroundColor.value;
const bgColor = backgroundColor.value;
// Update the gabe link color to match foreground color
const gabeLink = document.querySelector('.credit a');
if (gabeLink) {
gabeLink.style.color = fgColor;
}
// Clear previous error
errorMessage.textContent = '';
downloadBtn.style.display = 'none';
// Validate input
if (text === '') {
errorMessage.textContent = 'Please enter text or URL';
// Hide canvas
qrcodeCanvas.style.display = 'none';
return;
}
// Create new QR code
try {
if (!qr) {
qr = new QRious({
element: qrcodeCanvas,
size: size,
value: text,
foreground: fgColor,
background: bgColor,
level: errorCorrection
});
} else {
qr.set({
size: size,
value: text,
foreground: fgColor,
background: bgColor,
level: errorCorrection
});
}
// Show canvas and download button
qrcodeCanvas.style.display = 'block';
downloadBtn.style.display = 'inline-block';
} catch (err) {
errorMessage.textContent = 'Error generating QR code: ' + err.message;
qrcodeCanvas.style.display = 'none';
}
}
function downloadQRCode() {
// Get the canvas data as a data URL
const dataURL = qrcodeCanvas.toDataURL('image/png');
// Create a temporary link element
const link = document.createElement('a');
link.download = 'qrcode.png';
link.href = dataURL;
// Simulate click to trigger download
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// Show success feedback
const originalText = downloadBtn.textContent;
downloadBtn.textContent = 'Downloaded!';
// Reset button after a short delay
setTimeout(() => {
downloadBtn.textContent = originalText;
}, 2000);
}
});
</script>
</body>
</html>