diff --git a/css/style.css b/css/style.css
index 047c7f9..9e47227 100644
--- a/css/style.css
+++ b/css/style.css
@@ -949,6 +949,39 @@ section {
margin-top: 4px;
}
+.date-card.cancelled {
+ opacity: 0.65;
+ background: #f0efec;
+}
+
+.date-card.cancelled .date-number {
+ text-decoration: line-through;
+}
+
+.date-status {
+ color: #a33a2b;
+ font-size: 0.75rem;
+ font-weight: 700;
+ letter-spacing: 0.08em;
+ margin-top: 5px;
+}
+
+.calendar-link {
+ align-items: center;
+ color: var(--secondary-color);
+ display: inline-flex;
+ font-size: 0.8rem;
+ gap: 6px;
+ margin-top: 9px;
+ text-decoration: none;
+}
+
+.calendar-link:hover,
+.calendar-link:focus-visible {
+ color: var(--primary-color);
+ text-decoration: underline;
+}
+
.events-cta {
text-align: center;
}
diff --git a/index.html b/index.html
index e32e09d..221773a 100644
--- a/index.html
+++ b/index.html
@@ -409,7 +409,7 @@
-
+
Donnerstag30. OktoberCANCELLED
@@ -675,6 +675,119 @@
});
}
+ // Create one downloadable iCalendar file for each scheduled event
+ const monthNumbers = {
+ 'Januar': '01',
+ 'Februar': '02',
+ 'März': '03',
+ 'April': '04',
+ 'Mai': '05',
+ 'Juni': '06',
+ 'Juli': '07',
+ 'August': '08',
+ 'September': '09',
+ 'Oktober': '10',
+ 'November': '11',
+ 'Dezember': '12'
+ };
+
+ const escapeIcsText = text => String(text)
+ .replace(/\\/g, '\\\\')
+ .replace(/;/g, '\\;')
+ .replace(/,/g, '\\,')
+ .replace(/\r?\n/g, '\\n');
+
+ const formatIcsDateTime = date => [
+ date.getFullYear(),
+ String(date.getMonth() + 1).padStart(2, '0'),
+ String(date.getDate()).padStart(2, '0')
+ ].join('') + 'T' + [
+ String(date.getHours()).padStart(2, '0'),
+ String(date.getMinutes()).padStart(2, '0'),
+ '00'
+ ].join('');
+
+ const formatIcsUtcDateTime = date => [
+ date.getUTCFullYear(),
+ String(date.getUTCMonth() + 1).padStart(2, '0'),
+ String(date.getUTCDate()).padStart(2, '0')
+ ].join('') + 'T' + [
+ String(date.getUTCHours()).padStart(2, '0'),
+ String(date.getUTCMinutes()).padStart(2, '0'),
+ '00'
+ ].join('');
+
+ document.querySelectorAll('.date-card:not(.cancelled)').forEach(card => {
+ const dateNumber = card.querySelector('.date-number')?.textContent.trim();
+ const yearText = card.closest('[data-year-accordion]')?.querySelector('summary')?.textContent;
+ const dateMatch = dateNumber?.match(/^(\d{1,2})\.\s+(.+)$/);
+ const yearMatch = yearText?.match(/\d{4}/);
+ const month = dateMatch ? monthNumbers[dateMatch[2]] : null;
+
+ if (!dateMatch || !yearMatch || !month) {
+ return;
+ }
+
+ const date = `${yearMatch[0]}-${month}-${dateMatch[1].padStart(2, '0')}`;
+ const timeText = card.querySelector('.date-time')?.textContent.trim();
+ const timeMatch = timeText?.match(/^(\d{1,2}):(\d{2})/);
+ const isWineEvent = card.classList.contains('wine-event');
+ const title = isWineEvent ? 'Weinreise bei Frida & Fred' : 'Käsetasting bei Frida & Fred';
+ const slug = `${isWineEvent ? 'weinreise' : 'kaesetasting'}-${date}`;
+ const calendarLink = document.createElement('a');
+ calendarLink.className = 'calendar-link';
+ calendarLink.href = '#';
+ calendarLink.setAttribute('aria-label', `${title} am ${dateNumber} ${yearMatch[0]} in den Kalender eintragen`);
+ calendarLink.innerHTML = 'In Kalender speichern';
+
+ calendarLink.addEventListener('click', event => {
+ event.preventDefault();
+ let calendarContent;
+
+ if (timeMatch) {
+ const start = new Date(`${date}T${timeMatch[1].padStart(2, '0')}:${timeMatch[2]}:00`);
+ const end = new Date(start.getTime() + 2 * 60 * 60 * 1000);
+ calendarContent = [
+ `DTSTART;TZID=Europe/Berlin:${formatIcsDateTime(start)}`,
+ `DTEND;TZID=Europe/Berlin:${formatIcsDateTime(end)}`
+ ];
+ } else {
+ const start = new Date(`${date}T12:00:00`);
+ const end = new Date(start);
+ end.setDate(end.getDate() + 1);
+ calendarContent = [
+ `DTSTART;VALUE=DATE:${date.replace(/-/g, '')}`,
+ `DTEND;VALUE=DATE:${end.getFullYear()}${String(end.getMonth() + 1).padStart(2, '0')}${String(end.getDate()).padStart(2, '0')}`
+ ];
+ }
+
+ const ics = [
+ 'BEGIN:VCALENDAR',
+ 'VERSION:2.0',
+ 'PRODID:-//Frida & Fred//Events//DE',
+ 'CALSCALE:GREGORIAN',
+ 'BEGIN:VEVENT',
+ `UID:${slug}@frida-fred.com`,
+ `DTSTAMP:${formatIcsUtcDateTime(new Date())}Z`,
+ ...calendarContent,
+ `SUMMARY:${escapeIcsText(title)}`,
+ 'LOCATION:Frida & Fred',
+ `DESCRIPTION:${escapeIcsText('Anmeldung: info@frida-fred.com oder 0151 19426530')}`,
+ 'END:VEVENT',
+ 'END:VCALENDAR'
+ ].join('\r\n');
+ const blob = new Blob([ics], { type: 'text/calendar;charset=utf-8' });
+ const downloadUrl = URL.createObjectURL(blob);
+ const downloadLink = document.createElement('a');
+ downloadLink.href = downloadUrl;
+ downloadLink.download = `${slug}.ics`;
+ downloadLink.click();
+ URL.revokeObjectURL(downloadUrl);
+ });
+
+ card.querySelector('.date-info')?.appendChild(calendarLink);
+ });
+
// Smooth scrolling for internal links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {