document.addEventListener('DOMContentLoaded', function() { const textInput = document.getElementById('text'); const clearTextBtn = document.getElementById('clear-text'); const contentModeSelect = document.getElementById('content-mode'); const sectionText = document.getElementById('section-text'); const sectionWifi = document.getElementById('section-wifi'); const sectionEvent = document.getElementById('section-event'); const sectionVcard = document.getElementById('section-vcard'); const wifiSsidInput = document.getElementById('wifi-ssid'); const wifiPasswordInput = document.getElementById('wifi-password'); const eventTitleInput = document.getElementById('event-title'); const eventStartDateInput = document.getElementById('event-start-date'); const eventStartHourInput = document.getElementById('event-start-hour'); const eventStartMinuteInput = document.getElementById('event-start-minute'); const eventEndDateInput = document.getElementById('event-end-date'); const eventEndHourInput = document.getElementById('event-end-hour'); const eventEndMinuteInput = document.getElementById('event-end-minute'); const eventLocationInput = document.getElementById('event-location'); const eventDescriptionInput = document.getElementById('event-description'); const vcardFirstNameInput = document.getElementById('vcard-first-name'); const vcardLastNameInput = document.getElementById('vcard-last-name'); const vcardOrgInput = document.getElementById('vcard-org'); const vcardTitleInput = document.getElementById('vcard-title'); const vcardPhoneInput = document.getElementById('vcard-phone'); const vcardEmailInput = document.getElementById('vcard-email'); const vcardWebsiteInput = document.getElementById('vcard-website'); const vcardAddressInput = document.getElementById('vcard-address'); const vcardNoteInput = document.getElementById('vcard-note'); 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 qrcodeImg = document.getElementById('qrcode-img'); 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'); const shareBtn = document.getElementById('share'); const shareHint = document.getElementById('share-hint'); const eventBerlinPreview = document.getElementById('event-berlin-preview'); const vcardPreview = document.getElementById('vcard-preview'); const MAX_EVENT_TITLE = 2000; const MAX_EVENT_LOCATION = 1000; const MAX_EVENT_DESCRIPTION = 8000; const MAX_VCARD_FIELD = 500; // 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'); } }); function toggleContentSections() { const mode = contentModeSelect.value; sectionText.style.display = mode === 'text' ? 'block' : 'none'; sectionWifi.style.display = mode === 'wifi' ? 'block' : 'none'; sectionEvent.style.display = mode === 'event' ? 'block' : 'none'; sectionVcard.style.display = mode === 'vcard' ? 'block' : 'none'; } contentModeSelect.addEventListener('change', function() { toggleContentSections(); updateBerlinPreview(); generateQRCode(); if (this.value === 'text') { textInput.focus(); } else if (this.value === 'wifi') { wifiSsidInput.focus(); } else if (this.value === 'event') { eventTitleInput.focus(); } else if (this.value === 'vcard') { vcardFirstNameInput.focus(); } }); // Set random compliment as default text and select it textInput.value = 'https://medisoftware.de'; textInput.select(); // Show/hide clear button based on input content function toggleClearButton() { const hasText = textInput.value.length > 0; const hasWifi = wifiSsidInput.value.trim().length > 0 || wifiPasswordInput.value.trim().length > 0; const hasEvent = eventTitleInput.value.trim().length > 0 || eventStartDateInput.value.length > 0 || eventStartHourInput.value !== '' || eventStartMinuteInput.value !== '' || eventEndDateInput.value.length > 0 || eventEndHourInput.value !== '' || eventEndMinuteInput.value !== '' || eventLocationInput.value.trim().length > 0 || eventDescriptionInput.value.trim().length > 0; const hasVCard = vcardFirstNameInput.value.trim().length > 0 || vcardLastNameInput.value.trim().length > 0 || vcardOrgInput.value.trim().length > 0 || vcardTitleInput.value.trim().length > 0 || vcardPhoneInput.value.trim().length > 0 || vcardEmailInput.value.trim().length > 0 || vcardWebsiteInput.value.trim().length > 0 || vcardAddressInput.value.trim().length > 0 || vcardNoteInput.value.trim().length > 0; if (hasText || hasWifi || hasEvent || hasVCard) { 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 = ''; wifiSsidInput.value = ''; wifiPasswordInput.value = ''; eventTitleInput.value = ''; eventStartDateInput.value = ''; eventStartHourInput.value = ''; eventStartMinuteInput.value = ''; eventEndDateInput.value = ''; eventEndHourInput.value = ''; eventEndMinuteInput.value = ''; eventLocationInput.value = ''; eventDescriptionInput.value = ''; vcardFirstNameInput.value = ''; vcardLastNameInput.value = ''; vcardOrgInput.value = ''; vcardTitleInput.value = ''; vcardPhoneInput.value = ''; vcardEmailInput.value = ''; vcardWebsiteInput.value = ''; vcardAddressInput.value = ''; vcardNoteInput.value = ''; contentModeSelect.value = 'text'; toggleContentSections(); updateBerlinPreview(); toggleClearButton(); textInput.focus(); generateQRCode(); }); // Update clear button visibility when text changes textInput.addEventListener('input', function() { toggleClearButton(); if (contentModeSelect.value === 'text') { 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); // Generate QR code when wifi settings change wifiSsidInput.addEventListener('input', function() { if (contentModeSelect.value === 'wifi') { updateTextDisplay(); } generateQRCode(); toggleClearButton(); }); wifiPasswordInput.addEventListener('input', function() { if (contentModeSelect.value === 'wifi') { updateTextDisplay(); } generateQRCode(); toggleClearButton(); }); eventTitleInput.addEventListener('input', function() { updateBerlinPreview(); generateQRCode(); toggleClearButton(); }); function onEventDateTimeInput() { updateBerlinPreview(); generateQRCode(); toggleClearButton(); } eventStartDateInput.addEventListener('input', onEventDateTimeInput); eventStartDateInput.addEventListener('change', onEventDateTimeInput); eventStartHourInput.addEventListener('input', onEventDateTimeInput); eventStartHourInput.addEventListener('change', onEventDateTimeInput); eventStartMinuteInput.addEventListener('input', onEventDateTimeInput); eventStartMinuteInput.addEventListener('change', onEventDateTimeInput); eventEndDateInput.addEventListener('input', onEventDateTimeInput); eventEndDateInput.addEventListener('change', onEventDateTimeInput); eventEndHourInput.addEventListener('input', onEventDateTimeInput); eventEndHourInput.addEventListener('change', onEventDateTimeInput); eventEndMinuteInput.addEventListener('input', onEventDateTimeInput); eventEndMinuteInput.addEventListener('change', onEventDateTimeInput); function clampTimeNumberInput(el, max) { if (el.value === '') { return; } var v = parseInt(el.value, 10); if (isNaN(v)) { return; } if (v < 0) { el.value = '0'; } else if (v > max) { el.value = String(max); } } eventStartHourInput.addEventListener('blur', function() { clampTimeNumberInput(eventStartHourInput, 23); }); eventStartMinuteInput.addEventListener('blur', function() { clampTimeNumberInput(eventStartMinuteInput, 59); }); eventEndHourInput.addEventListener('blur', function() { clampTimeNumberInput(eventEndHourInput, 23); }); eventEndMinuteInput.addEventListener('blur', function() { clampTimeNumberInput(eventEndMinuteInput, 59); }); eventLocationInput.addEventListener('input', function() { generateQRCode(); toggleClearButton(); }); eventDescriptionInput.addEventListener('input', function() { generateQRCode(); toggleClearButton(); }); function onVcardInput() { generateQRCode(); toggleClearButton(); } vcardFirstNameInput.addEventListener('input', onVcardInput); vcardLastNameInput.addEventListener('input', onVcardInput); vcardOrgInput.addEventListener('input', onVcardInput); vcardTitleInput.addEventListener('input', onVcardInput); vcardPhoneInput.addEventListener('input', onVcardInput); vcardEmailInput.addEventListener('input', onVcardInput); vcardWebsiteInput.addEventListener('input', onVcardInput); vcardAddressInput.addEventListener('input', onVcardInput); vcardNoteInput.addEventListener('input', onVcardInput); // Download QR code as image downloadBtn.addEventListener('click', downloadQRCode); function sanitizeUrlString(s, maxLen) { if (typeof s !== 'string') { return ''; } return s.replace(/\0/g, '').slice(0, maxLen); } function isValidDatetimeLocal(s) { return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(s); } function combineDateTimeFromParts(dateStr, hourStr, minuteStr) { if (!dateStr || hourStr === '' || minuteStr === '') { return ''; } if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) { return ''; } var h = parseInt(String(hourStr).trim(), 10); var mi = parseInt(String(minuteStr).trim(), 10); if (isNaN(h) || isNaN(mi)) { return ''; } if (h < 0 || h > 23 || mi < 0 || mi > 59) { return ''; } var pad = function(n) { return String(n).padStart(2, '0'); }; return dateStr + 'T' + pad(h) + ':' + pad(mi); } function applyCombinedToDateTimeInputs(combined, dateInput, hourInput, minuteInput) { if (!isValidDatetimeLocal(combined)) { return; } var splitT = combined.split('T'); var d = splitT[0]; var t = splitT[1]; var tm = /^(\d{2}):(\d{2})$/.exec(t); if (!tm) { return; } dateInput.value = d; hourInput.value = String(parseInt(tm[1], 10)); minuteInput.value = String(parseInt(tm[2], 10)); } // Or check for URL parameters if present const urlParams = new URLSearchParams(window.location.search); let explicitModeFromUrl = false; if (urlParams.has('mode')) { const m = urlParams.get('mode'); if (m === 'text' || m === 'wifi' || m === 'event' || m === 'vcard') { contentModeSelect.value = m; explicitModeFromUrl = true; } } if (urlParams.has('text')) { const safeText = urlParams.get('text'); if (/^[\w\-.:/?&=#%+@!;,]*$/i.test(safeText)) { textInput.value = safeText; } } if (urlParams.has('ssid')) { const safeSsid = urlParams.get('ssid'); if (/^[^<>"'`\\]*$/.test(safeSsid)) { wifiSsidInput.value = safeSsid; } } if (urlParams.has('password')) { const safePassword = urlParams.get('password'); if (/^[^<>"'`\\]*$/.test(safePassword)) { wifiPasswordInput.value = safePassword; } } const hasEventParams = urlParams.has('eventTitle') || urlParams.has('eventStart'); if (!explicitModeFromUrl && hasEventParams) { contentModeSelect.value = 'event'; } if (urlParams.has('eventTitle')) { eventTitleInput.value = sanitizeUrlString(urlParams.get('eventTitle'), MAX_EVENT_TITLE); } if (urlParams.has('eventStart')) { const es = urlParams.get('eventStart'); if (isValidDatetimeLocal(es)) { applyCombinedToDateTimeInputs(es, eventStartDateInput, eventStartHourInput, eventStartMinuteInput); } } if (urlParams.has('eventEnd')) { const ee = urlParams.get('eventEnd'); if (isValidDatetimeLocal(ee)) { applyCombinedToDateTimeInputs(ee, eventEndDateInput, eventEndHourInput, eventEndMinuteInput); } } if (urlParams.has('eventLocation')) { eventLocationInput.value = sanitizeUrlString(urlParams.get('eventLocation'), MAX_EVENT_LOCATION); } if (urlParams.has('eventDescription')) { eventDescriptionInput.value = sanitizeUrlString(urlParams.get('eventDescription'), MAX_EVENT_DESCRIPTION); } const hasVcardParams = urlParams.has('vcardFirstName') || urlParams.has('vcardLastName') || urlParams.has('vcardOrg') || urlParams.has('vcardPhone') || urlParams.has('vcardEmail'); if (!explicitModeFromUrl && !hasEventParams && hasVcardParams) { contentModeSelect.value = 'vcard'; } if (urlParams.has('vcardFirstName')) { vcardFirstNameInput.value = sanitizeUrlString(urlParams.get('vcardFirstName'), MAX_VCARD_FIELD); } if (urlParams.has('vcardLastName')) { vcardLastNameInput.value = sanitizeUrlString(urlParams.get('vcardLastName'), MAX_VCARD_FIELD); } if (urlParams.has('vcardOrg')) { vcardOrgInput.value = sanitizeUrlString(urlParams.get('vcardOrg'), MAX_VCARD_FIELD); } if (urlParams.has('vcardTitle')) { vcardTitleInput.value = sanitizeUrlString(urlParams.get('vcardTitle'), MAX_VCARD_FIELD); } if (urlParams.has('vcardPhone')) { vcardPhoneInput.value = sanitizeUrlString(urlParams.get('vcardPhone'), MAX_VCARD_FIELD); } if (urlParams.has('vcardEmail')) { vcardEmailInput.value = sanitizeUrlString(urlParams.get('vcardEmail'), MAX_VCARD_FIELD); } if (urlParams.has('vcardWebsite')) { vcardWebsiteInput.value = sanitizeUrlString(urlParams.get('vcardWebsite'), MAX_VCARD_FIELD); } if (urlParams.has('vcardAddress')) { vcardAddressInput.value = sanitizeUrlString(urlParams.get('vcardAddress'), MAX_VCARD_FIELD); } if (urlParams.has('vcardNote')) { vcardNoteInput.value = sanitizeUrlString(urlParams.get('vcardNote'), MAX_VCARD_FIELD); } if (urlParams.has('size')) { const sizeValue = urlParams.get('size'); if (["128", "256", "512", "1024"].includes(sizeValue)) { sizeSelect.value = sizeValue; } } if (urlParams.has('errorCorrection')) { const ecValue = urlParams.get('errorCorrection').toUpperCase(); if (["L", "M", "Q", "H"].includes(ecValue)) { errorCorrectionSelect.value = ecValue; } } if (urlParams.has('foreground')) { const fgValue = urlParams.get('foreground'); if (/^#[0-9A-F]{6}$/i.test(fgValue)) { foregroundColor.value = fgValue; } } if (urlParams.has('background')) { const bgValue = urlParams.get('background'); if (/^#[0-9A-F]{6}$/i.test(bgValue)) { backgroundColor.value = bgValue; } } toggleContentSections(); updateBerlinPreview(); generateQRCode(); function daysInMonth(y, mo) { return new Date(y, mo, 0).getDate(); } function parseDatetimeLocalValue(s) { const m = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})$/.exec(s); if (!m) { return null; } return { y: parseInt(m[1], 10), mo: parseInt(m[2], 10), d: parseInt(m[3], 10), h: parseInt(m[4], 10), mi: parseInt(m[5], 10) }; } function addHoursWall(parts, deltaH) { let y = parts.y; let mo = parts.mo; let d = parts.d; let h = parts.h + deltaH; let mi = parts.mi; while (h >= 24) { h -= 24; d += 1; const dim = daysInMonth(y, mo); if (d > dim) { d = 1; mo += 1; if (mo > 12) { mo = 1; y += 1; } } } return { y: y, mo: mo, d: d, h: h, mi: mi }; } function wallTimeToICal(p) { const pad = function(n) { return String(n).padStart(2, '0'); }; return String(p.y) + pad(p.mo) + pad(p.d) + 'T' + pad(p.h) + pad(p.mi) + '00'; } function compareWallTime(a, b) { return (a.y * 1e8 + a.mo * 1e6 + a.d * 1e4 + a.h * 100 + a.mi) - (b.y * 1e8 + b.mo * 1e6 + b.d * 1e4 + b.h * 100 + b.mi); } function formatSlashDateTime24hBerlin(p) { const pad = function(n) { return String(n).padStart(2, '0'); }; return pad(p.d) + '/' + pad(p.mo) + '/' + p.y + ', ' + pad(p.h) + ':' + pad(p.mi); } function updateBerlinPreview() { if (!eventBerlinPreview) { return; } if (contentModeSelect.value !== 'event') { eventBerlinPreview.textContent = ''; return; } const startRaw = combineDateTimeFromParts( eventStartDateInput.value, eventStartHourInput.value, eventStartMinuteInput.value ); const endD = eventEndDateInput.value; const endH = eventEndHourInput.value; const endM = eventEndMinuteInput.value; const endTimeComplete = endH !== '' && endM !== ''; const endTimeAny = endH !== '' || endM !== ''; let endRaw = ''; if (endD && endTimeComplete) { endRaw = combineDateTimeFromParts(endD, endH, endM); } else if (endD && !endTimeComplete) { eventBerlinPreview.textContent = 'Bitte geben Sie für das Ende Datum sowie Stunde (0–23) und Minute (0–59) an, oder lassen Sie das Ende vollständig leer.'; return; } else if (!endD && endTimeAny) { eventBerlinPreview.textContent = 'Bitte geben Sie für das Ende ein Datum ein, oder entfernen Sie Stunde/Minute.'; return; } const sp = parseDatetimeLocalValue(startRaw); if (!sp) { eventBerlinPreview.textContent = ''; return; } let endDisplay; if (endRaw) { const ep = parseDatetimeLocalValue(endRaw); if (!ep) { eventBerlinPreview.textContent = 'Beginn: ' + formatSlashDateTime24hBerlin(sp) + ' (Europe/Berlin)'; return; } endDisplay = ep; } else { endDisplay = addHoursWall(sp, 1); } eventBerlinPreview.textContent = 'Beginn: ' + formatSlashDateTime24hBerlin(sp) + ' · Ende: ' + formatSlashDateTime24hBerlin(endDisplay) + ' (Europe/Berlin)'; } function escapeICalText(str) { return String(str) .replace(/\r\n/g, '\n') .replace(/\r/g, '') .replace(/\\/g, '\\\\') .replace(/;/g, '\\;') .replace(/,/g, '\\,') .replace(/\n/g, '\\n'); } function formatICalUTCStamp() { const d = new Date(); const pad = function(n) { return String(n).padStart(2, '0'); }; return ( d.getUTCFullYear() + pad(d.getUTCMonth() + 1) + pad(d.getUTCDate()) + 'T' + pad(d.getUTCHours()) + pad(d.getUTCMinutes()) + pad(d.getUTCSeconds()) + 'Z' ); } function newEventUid() { if (typeof crypto !== 'undefined' && crypto.randomUUID) { return crypto.randomUUID() + '@qr-generator.local'; } return 'evt-' + String(Date.now()) + '-' + String(Math.random()).slice(2) + '@qr-generator.local'; } function buildICalEvent(opts) { const summary = opts.summary; const dtStart = opts.dtStart; const dtEnd = opts.dtEnd; const location = opts.location; const description = opts.description; const vtimezoneBerlin = [ 'BEGIN:VTIMEZONE', 'TZID:Europe/Berlin', 'BEGIN:DAYLIGHT', 'DTSTART:19810329T020000', 'RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU', 'TZNAME:CEST', 'TZOFFSETFROM:+0100', 'TZOFFSETTO:+0200', 'END:DAYLIGHT', 'BEGIN:STANDARD', 'DTSTART:19961027T030000', 'RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU', 'TZNAME:CET', 'TZOFFSETFROM:+0200', 'TZOFFSETTO:+0100', 'END:STANDARD', 'END:VTIMEZONE' ].join('\r\n'); const lines = [ 'BEGIN:VCALENDAR', 'VERSION:2.0', 'PRODID:-//qr-generator//DE', 'CALSCALE:GREGORIAN', vtimezoneBerlin, 'BEGIN:VEVENT', 'UID:' + opts.uid, 'DTSTAMP:' + opts.dtStamp, 'DTSTART;TZID=Europe/Berlin:' + dtStart, 'DTEND;TZID=Europe/Berlin:' + dtEnd, 'SUMMARY:' + escapeICalText(summary) ]; if (location && location.trim()) { lines.push('LOCATION:' + escapeICalText(location.trim())); } if (description && description.trim()) { lines.push('DESCRIPTION:' + escapeICalText(description.trim())); } lines.push('END:VEVENT', 'END:VCALENDAR'); return lines.join('\r\n'); } function escapeVCardText(str) { return String(str) .replace(/\r\n/g, '\n') .replace(/\r/g, '') .replace(/\\/g, '\\\\') .replace(/;/g, '\\;') .replace(/,/g, '\\,') .replace(/\n/g, '\\n'); } function buildVCard(opts) { const firstName = opts.firstName.trim(); const lastName = opts.lastName.trim(); const org = opts.org.trim(); const title = opts.title.trim(); const phone = opts.phone.trim(); const email = opts.email.trim(); const website = opts.website.trim(); const address = opts.address.trim(); const note = opts.note.trim(); const displayName = [firstName, lastName].filter(Boolean).join(' ').trim() || org; const lines = [ 'BEGIN:VCARD', 'VERSION:3.0', 'N:' + escapeVCardText(lastName) + ';' + escapeVCardText(firstName) + ';;;', 'FN:' + escapeVCardText(displayName) ]; if (org) { lines.push('ORG:' + escapeVCardText(org)); } if (title) { lines.push('TITLE:' + escapeVCardText(title)); } if (phone) { lines.push('TEL;TYPE=CELL:' + escapeVCardText(phone)); } if (email) { lines.push('EMAIL;TYPE=INTERNET:' + escapeVCardText(email)); } if (website) { lines.push('URL:' + escapeVCardText(website)); } if (address) { lines.push('ADR;TYPE=WORK:;;' + escapeVCardText(address) + ';;;;'); } if (note) { lines.push('NOTE:' + escapeVCardText(note)); } lines.push('END:VCARD'); return lines.join('\r\n'); } function updateVCardPreview(text) { if (!vcardPreview) { return; } if (contentModeSelect.value !== 'vcard') { vcardPreview.textContent = ''; return; } if (text && String(text).trim()) { vcardPreview.textContent = String(text); return; } vcardPreview.textContent = 'Bitte mindestens Vorname, Nachname oder Organisation eingeben.'; } function generateQRCode() { const mode = contentModeSelect.value; const text = textInput.value.trim(); const wifiSsid = wifiSsidInput.value.trim(); const wifiPassword = wifiPasswordInput.value.trim(); const size = parseInt(sizeSelect.value, 10); 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'; if (mode !== 'vcard') { updateVCardPreview(''); } let qrText = ''; if (mode === 'event') { const title = eventTitleInput.value.trim(); const startRaw = combineDateTimeFromParts( eventStartDateInput.value, eventStartHourInput.value, eventStartMinuteInput.value ); const endD = eventEndDateInput.value; const endH = eventEndHourInput.value; const endM = eventEndMinuteInput.value; const endTimeComplete = endH !== '' && endM !== ''; const endTimeAny = endH !== '' || endM !== ''; const loc = eventLocationInput.value; const desc = eventDescriptionInput.value; if (!title) { errorMessage.textContent = 'Bitte geben Sie einen Titel für den Termin ein'; qrcodeCanvas.style.display = 'none'; qrcodeImg.style.display = 'none'; return; } if (!startRaw) { errorMessage.textContent = 'Bitte geben Sie Datum sowie Stunde (0–23) und Minute (0–59) für den Beginn ein'; qrcodeCanvas.style.display = 'none'; qrcodeImg.style.display = 'none'; return; } if (endD && !endTimeComplete) { errorMessage.textContent = 'Für das Ende: bitte Stunde und Minute ergänzen (0–23 / 0–59) oder Ende leer lassen'; qrcodeCanvas.style.display = 'none'; qrcodeImg.style.display = 'none'; return; } if (!endD && endTimeAny) { errorMessage.textContent = 'Für das Ende bitte zuerst ein Datum wählen oder Zeitfelder leeren'; qrcodeCanvas.style.display = 'none'; qrcodeImg.style.display = 'none'; return; } const startParts = parseDatetimeLocalValue(startRaw); if (!startParts) { errorMessage.textContent = 'Ungültige Startzeit (Datum und 24h-Uhrzeit prüfen)'; qrcodeCanvas.style.display = 'none'; qrcodeImg.style.display = 'none'; return; } const dtStart = wallTimeToICal(startParts); let endParts; const endRaw = endD && endTimeComplete ? combineDateTimeFromParts(endD, endH, endM) : ''; if (endRaw) { endParts = parseDatetimeLocalValue(endRaw); if (!endParts) { errorMessage.textContent = 'Ungültige Endzeit'; qrcodeCanvas.style.display = 'none'; qrcodeImg.style.display = 'none'; return; } } else { endParts = addHoursWall(startParts, 1); } if (compareWallTime(endParts, startParts) <= 0) { errorMessage.textContent = 'Die Endzeit muss nach dem Beginn liegen (Ortszeit Europe/Berlin)'; qrcodeCanvas.style.display = 'none'; qrcodeImg.style.display = 'none'; return; } const dtEndStr = wallTimeToICal(endParts); qrText = buildICalEvent({ uid: newEventUid(), dtStamp: formatICalUTCStamp(), summary: title.slice(0, MAX_EVENT_TITLE), dtStart: dtStart, dtEnd: dtEndStr, location: loc.slice(0, MAX_EVENT_LOCATION), description: desc.slice(0, MAX_EVENT_DESCRIPTION) }); textInput.value = qrText; toggleClearButton(); } else if (mode === 'vcard') { const firstName = vcardFirstNameInput.value.slice(0, MAX_VCARD_FIELD); const lastName = vcardLastNameInput.value.slice(0, MAX_VCARD_FIELD); const org = vcardOrgInput.value.slice(0, MAX_VCARD_FIELD); const title = vcardTitleInput.value.slice(0, MAX_VCARD_FIELD); const phone = vcardPhoneInput.value.slice(0, MAX_VCARD_FIELD); const email = vcardEmailInput.value.slice(0, MAX_VCARD_FIELD); const website = vcardWebsiteInput.value.slice(0, MAX_VCARD_FIELD); const address = vcardAddressInput.value.slice(0, MAX_VCARD_FIELD); const note = vcardNoteInput.value.slice(0, MAX_VCARD_FIELD); const hasIdentity = firstName.trim() || lastName.trim() || org.trim(); if (!hasIdentity) { updateVCardPreview(''); errorMessage.textContent = 'Bitte geben Sie mindestens Vorname, Nachname oder Organisation an'; qrcodeCanvas.style.display = 'none'; qrcodeImg.style.display = 'none'; return; } qrText = buildVCard({ firstName: firstName, lastName: lastName, org: org, title: title, phone: phone, email: email, website: website, address: address, note: note }); updateVCardPreview(qrText); textInput.value = qrText; toggleClearButton(); } else if (mode === 'wifi') { if (wifiSsid && wifiPassword) { qrText = 'WIFI:S:' + escapeSpecialChars(wifiSsid) + ';T:WPA;P:' + escapeSpecialChars(wifiPassword) + ';;'; } else if (wifiSsid && !wifiPassword) { qrText = 'WIFI:S:' + escapeSpecialChars(wifiSsid) + ';T:nopass;;'; } else if (!wifiSsid && wifiPassword) { errorMessage.textContent = 'Bitte geben Sie eine SSID ein, wenn Sie ein Passwort angeben'; qrcodeCanvas.style.display = 'none'; qrcodeImg.style.display = 'none'; return; } else { qrText = text; } } else { qrText = text; } // Validate input if (qrText === '') { if (mode === 'wifi') { errorMessage.textContent = 'Bitte geben Sie Text/URL oder WLAN-Daten ein'; } else if (mode === 'event') { errorMessage.textContent = 'Termin konnte nicht erzeugt werden'; } else if (mode === 'vcard') { errorMessage.textContent = 'vCard konnte nicht erzeugt werden'; } else { errorMessage.textContent = 'Bitte geben Sie Text oder eine URL ein'; } qrcodeCanvas.style.display = 'none'; qrcodeImg.style.display = 'none'; return; } // Create new QR code on canvas try { if (!qr) { qr = new QRious({ element: qrcodeCanvas, size: size, value: qrText, foreground: fgColor, background: bgColor, level: errorCorrection }); } else { qr.set({ size: size, value: qrText, foreground: fgColor, background: bgColor, level: errorCorrection }); } // Convert canvas to image const dataURL = qrcodeCanvas.toDataURL('image/png'); qrcodeImg.src = dataURL; // Show image (hide canvas) and download button qrcodeCanvas.style.display = 'none'; qrcodeImg.style.display = 'block'; downloadBtn.style.display = 'inline-block'; } catch (err) { errorMessage.textContent = 'Error generating QR code: ' + err.message; qrcodeCanvas.style.display = 'none'; qrcodeImg.style.display = 'none'; } } // Helper function to escape special characters in wifi strings function escapeSpecialChars(str) { return str.replace(/[\\;:,]/g, '\\$&'); } function downloadQRCode() { // Use the image source for download const link = document.createElement('a'); link.download = 'qrcode.png'; link.href = qrcodeImg.src; // 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(function() { downloadBtn.textContent = originalText; }, 2000); } // Function to update the text display with current QR code content (WLAN) function updateTextDisplay() { const wifiSsid = wifiSsidInput.value.trim(); const wifiPassword = wifiPasswordInput.value.trim(); if (wifiSsid && wifiPassword) { textInput.value = 'WIFI:S:' + escapeSpecialChars(wifiSsid) + ';T:WPA;P:' + escapeSpecialChars(wifiPassword) + ';;'; } else if (wifiSsid && !wifiPassword) { textInput.value = 'WIFI:S:' + escapeSpecialChars(wifiSsid) + ';T:nopass;;'; } toggleClearButton(); } function updateShareHint() { const mode = contentModeSelect.value; const wifiPassword = wifiPasswordInput.value.trim(); if (mode === 'wifi' && wifiPassword) { shareHint.style.display = 'block'; } else { shareHint.style.display = 'none'; } } wifiPasswordInput.addEventListener('input', updateShareHint); wifiSsidInput.addEventListener('input', updateShareHint); textInput.addEventListener('input', updateShareHint); contentModeSelect.addEventListener('change', updateShareHint); updateShareHint(); shareBtn.addEventListener('click', function() { const params = new URLSearchParams(); const mode = contentModeSelect.value; const text = textInput.value.trim(); const wifiSsid = wifiSsidInput.value.trim(); const wifiPassword = wifiPasswordInput.value.trim(); const size = sizeSelect.value; const errorCorrection = errorCorrectionSelect.value; const fgColor = foregroundColor.value; const bgColor = backgroundColor.value; params.set('mode', mode); if (mode === 'event') { if (eventTitleInput.value.trim()) { params.set('eventTitle', eventTitleInput.value.trim()); } const shareStart = combineDateTimeFromParts( eventStartDateInput.value, eventStartHourInput.value, eventStartMinuteInput.value ); const shareEnd = combineDateTimeFromParts( eventEndDateInput.value, eventEndHourInput.value, eventEndMinuteInput.value ); if (shareStart) { params.set('eventStart', shareStart); } if (shareEnd) { params.set('eventEnd', shareEnd); } if (eventLocationInput.value.trim()) { params.set('eventLocation', eventLocationInput.value.trim()); } if (eventDescriptionInput.value.trim()) { params.set('eventDescription', eventDescriptionInput.value.trim()); } } else if (mode === 'vcard') { if (vcardFirstNameInput.value.trim()) { params.set('vcardFirstName', vcardFirstNameInput.value.trim()); } if (vcardLastNameInput.value.trim()) { params.set('vcardLastName', vcardLastNameInput.value.trim()); } if (vcardOrgInput.value.trim()) { params.set('vcardOrg', vcardOrgInput.value.trim()); } if (vcardTitleInput.value.trim()) { params.set('vcardTitle', vcardTitleInput.value.trim()); } if (vcardPhoneInput.value.trim()) { params.set('vcardPhone', vcardPhoneInput.value.trim()); } if (vcardEmailInput.value.trim()) { params.set('vcardEmail', vcardEmailInput.value.trim()); } if (vcardWebsiteInput.value.trim()) { params.set('vcardWebsite', vcardWebsiteInput.value.trim()); } if (vcardAddressInput.value.trim()) { params.set('vcardAddress', vcardAddressInput.value.trim()); } if (vcardNoteInput.value.trim()) { params.set('vcardNote', vcardNoteInput.value.trim()); } } else if (mode === 'wifi') { if (wifiSsid) { params.set('ssid', wifiSsid); } if (wifiPassword) { params.set('password', wifiPassword); } } else { if (text) { params.set('text', text); } } if (size !== '256') { params.set('size', size); } if (errorCorrection !== 'M') { params.set('errorCorrection', errorCorrection); } if (fgColor !== '#000000') { params.set('foreground', fgColor); } if (bgColor !== '#ffffff') { params.set('background', bgColor); } const url = window.location.origin + window.location.pathname + '?' + params.toString(); navigator.clipboard.writeText(url).then(function() { const original = shareBtn.textContent; shareBtn.textContent = 'Link kopiert!'; setTimeout(function() { shareBtn.textContent = original; }, 2000); }, function() { alert('Konnte Link nicht kopieren.'); }); }); });