feat: Kalender-Termine als QR (iCal Europe/Berlin) und UX für Datum/Zeit

- Modus Text/WLAN/Termin; VEVENT mit TZID Europe/Berlin und VTIMEZONE
- Eingabe: separates Datum und 24h-Uhrzeit; Vorschau TT/MM/JJJJ
- URL-Parameter und Teilen für Terminfelder
- Docker Compose: Healthcheck für Caddy-Container

Made-with: Cursor
This commit is contained in:
2026-04-16 08:15:29 +02:00
parent 5478d943e5
commit d5cd4427d8
3 changed files with 599 additions and 73 deletions
+87 -15
View File
@@ -1,6 +1,6 @@
<!DOCTYPE html>
<html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<html lang="de"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; object-src 'none';">
@@ -53,7 +53,7 @@
color: #374151;
}
input, select {
input, select, textarea {
width: 100%;
padding: 10px;
margin-bottom: 15px;
@@ -64,7 +64,12 @@
transition: border-color 0.2s ease;
}
input:focus, select:focus {
textarea {
min-height: 80px;
resize: vertical;
}
input:focus, select:focus, textarea:focus {
outline: none;
border-color: #1e3a8a;
box-shadow: 0 0 0 3px rgba(30, 58, 138, 0.1);
@@ -295,6 +300,24 @@
text-decoration: underline;
color: #1e40af;
}
.event-berlin-preview {
font-size: 14px;
color: #4b5563;
margin: -8px 0 16px 0;
line-height: 1.5;
}
.event-berlin-preview:empty {
display: none;
}
.event-datetime-hint {
font-size: 13px;
color: #6b7280;
margin: -4px 0 12px 0;
line-height: 1.4;
}
</style>
</head>
<body>
@@ -303,24 +326,70 @@
<button id="info-button" class="info-button" aria-label="Information">?</button>
<div class="input-section">
<label for="text">Text oder URL:</label>
<div class="text-input-wrapper">
<input type="text" id="text" placeholder="https://qr.medisoftware.org" value="" autofocus="">
<button type="button" id="clear-text" class="clear-button" aria-label="Clear text" style="display: none;"></button>
<label for="content-mode">QR-Inhalt:</label>
<select id="content-mode">
<option value="text">Text oder URL</option>
<option value="wifi">WLAN</option>
<option value="event">Termin (Kalender)</option>
</select>
<div id="section-text" class="content-section">
<label for="text">Text oder URL:</label>
<div class="text-input-wrapper">
<input type="text" id="text" placeholder="https://qr.medisoftware.org" value="" autofocus="">
<button type="button" id="clear-text" class="clear-button" aria-label="Clear text" style="display: none;"></button>
</div>
</div>
<div class="options-row">
<div class="options-col">
<label for="wifi-ssid">WLAN SSID:</label>
<input type="text" id="wifi-ssid" placeholder="">
</div>
<div id="section-wifi" class="content-section" style="display: none;">
<div class="options-row">
<div class="options-col">
<label for="wifi-ssid">WLAN SSID:</label>
<input type="text" id="wifi-ssid" placeholder="">
</div>
<div class="options-col">
<label for="wifi-password">WLAN Passwort:</label>
<input type="password" id="wifi-password" placeholder="">
<div class="options-col">
<label for="wifi-password">WLAN Passwort:</label>
<input type="password" id="wifi-password" placeholder="">
</div>
</div>
</div>
<div id="section-event" class="content-section" style="display: none;" lang="en-GB">
<label for="event-title">Titel:</label>
<input type="text" id="event-title" placeholder="" autocomplete="off">
<p class="event-datetime-hint">Datum im Format <strong>TT/MM/JJJJ</strong> (Kalenderfeld), Uhrzeit im <strong>24-Stunden-Format</strong> (keine AM/PM). Alle Zeiten: Ortszeit <strong>Europe/Berlin</strong>.</p>
<div class="options-row">
<div class="options-col">
<label for="event-start-date">Beginn Datum:</label>
<input type="date" id="event-start-date" autocomplete="off">
</div>
<div class="options-col">
<label for="event-start-time">Beginn Uhrzeit (24h):</label>
<input type="time" id="event-start-time" step="60" autocomplete="off">
</div>
</div>
<div class="options-row">
<div class="options-col">
<label for="event-end-date">Ende (optional) Datum:</label>
<input type="date" id="event-end-date" autocomplete="off">
</div>
<div class="options-col">
<label for="event-end-time">Ende (optional) Uhrzeit (24h):</label>
<input type="time" id="event-end-time" step="60" autocomplete="off">
</div>
</div>
<p class="event-berlin-preview" id="event-berlin-preview" aria-live="polite"></p>
<label for="event-location">Ort (optional):</label>
<input type="text" id="event-location" placeholder="" autocomplete="off">
<label for="event-description">Beschreibung (optional):</label>
<textarea id="event-description" placeholder="" rows="3"></textarea>
</div>
<div class="options-row">
<div class="options-col">
<label for="size">QR Code Gr&ouml;sse:</label>
@@ -393,6 +462,9 @@
<p>This is the web we were trying to build.</p>
<p>Be excellent to each other.</p>
<h3>Modi</h3>
<p>Unter <strong>QR-Inhalt</strong> wählst du, was kodiert wird: freier Text oder URL, WLAN-Zugangsdaten (WIFI-QR) oder einen <strong>Kalendertermin</strong>. Im Modus „Termin“ erzeugt der QR-Code einen Standard-iCalendar-Eintrag (VEVENT), den viele Smartphones beim Scannen in die Kalender-App übernehmen. Datumseingabe als <strong>TT/MM/JJJJ</strong>, Uhrzeit im <strong>24-Stunden-Format</strong>; die Zeitzone ist <strong>Europe/Berlin</strong>. Es wird nichts an einen Server gesendet.</p>
</div>
</div>