From 12fe75997080fdaddba25abc7fa80d3957a8c101 Mon Sep 17 00:00:00 2001 From: Kantine Wrapper Date: Mon, 9 Mar 2026 14:41:49 +0100 Subject: [PATCH] refactor: Separate timestamp updates for the main header and notification checks, and update the version to v1.6.11. --- REQUIREMENTS.md | 4 ++-- changelog.md | 4 ++++ dist/bookmarklet-payload.js | 2 +- dist/bookmarklet.txt | 2 +- dist/install.html | 17 +++++++++++------ dist/kantine-standalone.html | 22 ++++++++++++---------- kantine.js | 14 ++++++++------ version.txt | 2 +- 8 files changed, 40 insertions(+), 27 deletions(-) diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index 0d18872..7a34b7a 100755 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -59,11 +59,11 @@ Das System umfasst die Darstellung von Menüplänen in einer Wochenübersicht, d | FR-082 | Das System muss beim erstmaligen Laden die Betriebssystem-Präferenz für das Farbschema berücksichtigen. | Niedrig | v1.0.1 | | **Header UI & Navigation** | | | | | FR-090 | Die Hauptnavigation (Wochen-Toggles) muss linksbündig neben dem App-Titel positioniert sein. | Niedrig | v1.5.0 | -| FR-091 | Ein dynamisches Alarm-Icon im Header muss den Überwachungsstatus geflaggter Menüs anzeigen (Gelb=Überwachung aktiv aber kein Menü verfügbar, Grün=Mindestens ein Menü verfügbar, Versteckt=keine Flags). Der Tooltip muss den Zeitpunkt der letzten Prüfung als relativen String (z.B. "vor 4 Min.") enthalten. | Mittel | v1.6.9 (Update v1.5.0) | +| FR-091 | Ein dynamisches Alarm-Icon im Header muss den Überwachungsstatus geflaggter Menüs anzeigen (Gelb=Überwachung aktiv aber kein Menü verfügbar, Grün=Mindestens ein Menü verfügbar, Versteckt=keine Flags). Der Tooltip muss den Zeitpunkt der letzten Prüfung als relativen String (z.B. "vor 4 Min.") enthalten. | Mittel | v1.6.11 (Update v1.5.0) | | FR-092 | Solange Menüdaten für die Nächste Woche verfügbar sind, aber noch keine Bestellungen getätigt wurden, muss der entsprechende Navigation-Button animiert und farblich (Gelb) hervorgehoben werden. Nach der ersten Bestellung muss die Hervorhebung automatisch erlöschen. Zusätzlich muss beim erstmaligen Erscheinen der Daten ein einmaliger Toast-Hinweis angezeigt werden. | Mittel | v1.6.0 (Update v1.4.21) | | **Sprachfilter** | | | | | FR-120 | Das System muss zweisprachige Menübeschreibungen (Deutsch/Englisch) erkennen und dem Benutzer erlauben, via UI-Toggle zwischen DE, EN und ALL (beide Sprachen) zu wechseln. Die Sprachpräferenz muss persistent gespeichert werden. Allergen-Codes müssen in allen Modi angezeigt werden. | Mittel | v1.6.0 | -| FR-121 | Das System muss bei fehlenden Übersetzungen in zweisprachigen Menüs robust reagieren. Wenn ein Gang nur in einer Sprache vorliegt, muss dieser Teil für beide Sprachansichten herangezogen werden, um die Konsistenz der Ganganzahl zu gewährleisten. | Mittel | v1.7.0 | +| FR-121 | Das System muss bei fehlenden Übersetzungen in zweisprachigen Menüs robust reagieren. Wenn ein Gang nur in einer Sprache vorliegt, muss dieser Teil für beide Sprachansichten herangezogen werden, um die Konsistenz der Ganganzahl zu gewährleisten. | Mittel | v1.6.10 | | **Benutzer-Feedback** | | | | | FR-095 | Alle benutzerrelevanten Aktionen (Bestellung, Stornierung, Fehler) müssen durch nicht-blockierende Benachrichtigungen (Toasts) bestätigt werden. | Mittel | v1.0.1 | | FR-096 | Bei einem Verbindungsfehler muss ein Fehlerdialog mit Fallback-Link zur Originalseite angezeigt werden. | Mittel | v1.0.1 | diff --git a/changelog.md b/changelog.md index 8f49add..c80f4b9 100755 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,7 @@ +## v1.6.11 (2026-03-09) +- 🔄 **Refactor**: Trennung der Zeitstempel für die Hauptaktualisierung (Header) und die Benachrichtigungsprüfung (Bell-Icon). Das Polling aktualisiert nun nicht mehr fälschlicherweise die "Aktualisiert am"-Zeit im Header. +- 🏷️ **Metadata**: Version auf v1.6.11 angehoben. + ## v1.6.10 (2026-03-09) - **Feature**: Robuste Kurs-Erkennung in zweisprachigen Menüs ([FR-121](REQUIREMENTS.md#FR-121)). - **Fix**: Verhindert das Verschieben von Gängen bei fehlenden englischen Übersetzungen. diff --git a/dist/bookmarklet-payload.js b/dist/bookmarklet-payload.js index f6ef92e..7ba3baf 100755 --- a/dist/bookmarklet-payload.js +++ b/dist/bookmarklet-payload.js @@ -3,6 +3,6 @@ if(window.__KANTINE_LOADED){alert('Kantine Wrapper already loaded!');return;} var s=document.createElement('style');s.textContent=':root { /* Premium Slate/Gray-Blue Palette - Light Mode */ --bg-body: #f1f5f9; /* Slate 100 */ --bg-card: #ffffff; --text-primary: #334155; /* Slate 700 */ --text-secondary: #64748b; --accent-color: #0f172a; /* Slate 900 (High contrast) */ --border-color: #cbd5e1; /* Slate 300 */ --banner-bg: #e2e8f0; --banner-text: #1e293b; --success-color: #059669; --error-color: #dc2626; --card-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.05), 0 2px 4px -2px rgb(0 0 0 / 0.05); --header-bg: rgba(255, 255, 255, 0.9); --header-border: 1px solid rgba(203, 213, 225, 0.6); } [data-theme="dark"] { /* Premium Slate/Gray-Blue Palette - Dark Mode */ --bg-body: #1e293b; /* Deep Slate Gray (Requested) */ --bg-card: #334155; /* Slate 700 */ --text-primary: #f8fafc; /* Slate 50 */ --text-secondary: #cbd5e1; /* Slate 300 */ --accent-color: #60a5fa; /* Blue 400 */ --border-color: #475569; /* Slate 600 */ --banner-bg: #475569; --banner-text: #e2e8f0; --header-bg: rgba(30, 41, 59, 0.9); --header-border: 1px solid rgba(71, 85, 105, 0.6); --card-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.4); } * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: \'Inter\', system-ui, -apple-system, sans-serif; background-color: var(--bg-body); color: var(--text-primary); transition: background-color 0.3s ease, color 0.3s ease; line-height: 1.5; -webkit-font-smoothing: antialiased; } /* Fix scrolling bug: Reset html/body styles from host page */ /* IMPORTANT: html must NOT have overflow set, or it creates a scroll container that breaks position: sticky */ html { height: auto !important; min-height: 100% !important; overflow: visible !important; position: static !important; margin: 0 !important; padding: 0 !important; } body { height: auto !important; min-height: 100% !important; overflow-x: clip !important; /* clip prevents horizontal overflow without breaking sticky */ overflow-y: visible !important; position: static !important; margin: 0 !important; padding: 0 !important; } /* Header */ .app-header { flex-shrink: 0; z-index: 100; backdrop-filter: blur(12px); background-color: var(--header-bg); border-bottom: var(--header-border); padding: 1rem 0; } .header-content { width: 100%; /* Full width */ padding: 0 2rem; /* Comfortable padding */ display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 1rem; } .brand { display: flex; align-items: center; gap: 0.75rem; } .brand-text { display: flex; flex-direction: column; } .brand h1 { font-size: 1.25rem; font-weight: 700; letter-spacing: -0.025em; margin-bottom: 0; } .subtitle { font-size: 0.85rem; color: var(--text-secondary); font-weight: 400; margin-left: 2px; } .logo-icon { font-size: 1.5rem; color: var(--accent-color); } /* Controls */ .controls { display: flex; align-items: center; gap: 1.5rem; justify-self: end; } /* Header Week Info (centered) */ .header-week-info { text-align: center; line-height: 1.3; } .header-center-wrapper { display: flex; flex-direction: row; align-items: center; gap: 1.5rem; justify-content: center; } .header-week-title { font-size: 1.1rem; font-weight: 600; color: var(--text-primary); } .header-week-subtitle { font-size: 0.85rem; color: var(--text-secondary); } /* Language Toggle (FR-100) */ .lang-toggle { display: inline-flex; gap: 0; border-radius: 6px; overflow: hidden; border: 1px solid var(--border-color); background: var(--bg-card); } .lang-btn { padding: 3px 10px; font-size: 0.7rem; font-weight: 600; letter-spacing: 0.03em; background: transparent; color: var(--text-secondary); border: none; cursor: pointer; transition: all 0.2s; } .lang-btn:hover { color: var(--text-primary); background: rgba(100, 116, 139, 0.1); } .lang-btn.active { background: var(--accent-color); color: white; } .nav-group { display: flex; background-color: var(--bg-card); border: 1px solid var(--border-color); padding: 0.25rem; border-radius: 8px; } .nav-btn { background: none; border: none; padding: 0.5rem 1rem; font-size: 0.875rem; font-weight: 500; color: var(--text-secondary); cursor: pointer; border-radius: 6px; transition: all 0.2s; display: flex; align-items: center; gap: 0.5rem; } .nav-btn:hover { color: var(--text-primary); background-color: rgba(100, 116, 139, 0.1); } .nav-btn.active { background-color: var(--accent-color); color: white; } /* Notification state for Next Week */ .nav-btn.new-week-available { animation: goldPulse 2s infinite; border-color: #f59e0b; color: var(--accent-color); } .nav-btn.new-week-available.active { color: white; } @keyframes goldPulse { 0% { box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.7); } 70% { box-shadow: 0 0 0 10px rgba(245, 158, 11, 0); } 100% { box-shadow: 0 0 0 0 rgba(245, 158, 11, 0); } } /* Badge for nav buttons (day count indicator) */ .nav-badge { background-color: var(--error-color); color: white; font-size: 0.75rem; font-weight: 600; padding: 0 6px; border-radius: 10px; min-width: 18px; height: 18px; display: inline-flex; align-items: center; justify-content: center; margin-left: 8px; gap: 3px; line-height: 1; } .nav-badge .orderable { color: #fff; font-weight: 800; } .nav-badge .separator { opacity: 0.6; font-weight: 400; } .nav-badge .total { opacity: 0.8; font-weight: 400; } .nav-btn.active .nav-badge { background: rgba(255, 255, 255, 0.3); } /* Primary style for Login Button to match header */ #btn-login-open { background-color: var(--accent-color); color: white; padding: 0.5rem 1.25rem; border-radius: 8px; font-weight: 600; letter-spacing: 0.025em; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } #btn-login-open:hover { background-color: #334155; /* Slightly lighter than slate-900 */ transform: translateY(-1px); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } /* User Badge Button (Login) */ .user-badge-btn { display: flex; align-items: center; gap: 8px; padding: 6px 12px; background: var(--bg-card); border: 1px solid var(--border-color); border-radius: 20px; font-size: 0.9rem; font-weight: 500; color: var(--text-primary); cursor: pointer; transition: all 0.2s; } .user-badge-btn:hover { background: rgba(100, 116, 139, 0.1); border-color: var(--accent-color); } .user-badge-btn .material-icons-round { font-size: 1.25rem; color: var(--accent-color); } .icon-btn { background: none; border: none; color: var(--text-primary); cursor: pointer; padding: 0.5rem; border-radius: 50%; transition: background-color 0.2s; display: flex; align-items: center; justify-content: center; } .icon-btn:hover { background-color: rgba(100, 116, 139, 0.1); } /* Refresh button animation */ #btn-refresh.refreshing .material-icons-round { animation: rotate 1s linear infinite; } @keyframes rotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } /* Progress Modal */ .progress-container { margin-bottom: 1.5rem; } .progress-bar { width: 100%; height: 8px; background-color: var(--border-color); border-radius: 4px; overflow: hidden; margin-bottom: 0.75rem; } .progress-fill { height: 100%; background: linear-gradient(90deg, var(--accent-color) 0%, #60a5fa 100%); width: 0%; transition: width 0.3s ease; border-radius: 4px; } .progress-percent { text-align: center; font-size: 1.5rem; font-weight: 700; color: var(--text-primary); margin-bottom: 0.5rem; } .progress-message { text-align: center; color: var(--text-secondary); font-size: 0.9rem; font-weight: 500; } .weekly-cost { background-color: rgba(59, 130, 246, 0.1); /* Blue tint */ color: var(--accent-color); padding: 0.4rem 0.8rem; border-radius: 8px; font-weight: 600; font-size: 0.9rem; display: flex; align-items: center; gap: 0.5rem; border: 1px solid rgba(59, 130, 246, 0.2); } .weekly-cost .material-icons-round { font-size: 18px; } /* Container - flex column, full width so child scrollbar is at edge */ .container { flex: 1; width: 100%; overflow: hidden; padding: 0 0 0 0; /* Only top padding, no horizontal so child fills width */ display: flex; flex-direction: column; } /* Add horizontal padding to direct children of container to maintain layout */ .container>*:not(.menu-grid) { padding-left: 2rem; padding-right: 2rem; } /* Banner */ .banner { background-color: var(--banner-bg); color: var(--banner-text); padding: 0.75rem 1rem; border-radius: 8px; display: flex; align-items: center; gap: 0.5rem; margin-bottom: 2rem; font-size: 0.875rem; font-weight: 500; border: 1px solid var(--border-color); max-width: fit-content; } /* User Badge */ .user-badge { display: flex; align-items: center; gap: 8px; padding: 6px 12px; background: var(--bg-card); /* Changed from --surface */ border: 1px solid var(--border-color); /* Changed from --border */ border-radius: 20px; font-size: 0.9rem; font-weight: 500; } .icon-btn-small { background: none; border: none; padding: 4px; cursor: pointer; color: var(--text-secondary); /* Changed from --text-muted */ display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: all 0.2s; } .icon-btn-small:hover { color: var(--error-color); /* Changed from --danger */ background: rgba(239, 68, 68, 0.1); } /* Modal */ .modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; z-index: 1000; transition: all 0.3s; } .modal.hidden { opacity: 0; pointer-events: none; } .modal-content { background: var(--bg-card); width: 90%; max-width: 400px; border-radius: 16px; box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); overflow: hidden; animation: modalSlide 0.3s ease-out; } /* History Modal specific */ .history-modal-content { max-width: 600px; max-height: 85vh; display: flex; flex-direction: column; } .history-modal-content .modal-body { overflow-y: auto; padding: 0; /* Padding is handled by inner elements */ } /* History Styles */ .history-year-group { margin-bottom: 16px; } .history-year-header { background: var(--bg-card); padding: 12px 20px; margin: 0; font-size: 1.2rem; font-weight: 700; color: var(--text-primary); border-bottom: 2px solid var(--border-color); position: sticky; top: 0; z-index: 12; } .history-month-group { border-bottom: 1px solid var(--border-color); } .history-month-header { display: flex; justify-content: space-between; align-items: center; padding: 14px 20px; margin: 0; font-size: 1.05rem; font-weight: 600; color: var(--text-primary); background: var(--bg-body); cursor: pointer; transition: background 0.2s; } .history-month-header:hover { background: var(--border-color); /* Slight hover effect */ } .history-month-summary { display: flex; align-items: center; gap: 12px; font-size: 0.95rem; color: var(--text-secondary); } .history-month-content { display: none; /* Collapsed by default */ background: var(--bg-card); } .history-month-group.open .history-month-content { display: block; /* Expanded when open class is present */ } .history-month-group.open .history-month-header .material-icons-round { transform: rotate(180deg); } .history-month-header .material-icons-round { transition: transform 0.3s; font-size: 20px; } .history-week-group { padding: 12px 20px; border-bottom: 1px dashed var(--border-color); } .history-week-group:last-child { border-bottom: none; } .history-week-header { display: flex; justify-content: space-between; align-items: center; font-size: 0.9rem; font-weight: 600; color: var(--text-secondary); margin-bottom: 10px; } .history-week-summary { font-size: 0.85rem; font-weight: 500; background: rgba(100, 116, 139, 0.1); padding: 4px 10px; border-radius: 12px; } .history-items { display: flex; flex-direction: column; gap: 8px; } .history-item { display: grid; grid-template-columns: 50px 1fr auto; align-items: center; gap: 12px; padding: 10px 12px; background: var(--bg-body); border-radius: 8px; border: 1px solid var(--border-color); } .history-item-date { font-size: 0.85rem; color: var(--text-secondary); font-weight: 500; } .history-item-details { display: flex; flex-direction: column; gap: 4px; } .history-item-name { font-size: 0.95rem; font-weight: 500; color: var(--text-primary); } .history-item-price { font-weight: 600; color: var(--text-primary); } .history-item-status { font-size: 0.8rem; font-weight: 600; color: var(--text-primary); text-transform: uppercase; letter-spacing: 0.5px; } .history-item-cancelled { opacity: 0.5; filter: grayscale(1); } .history-item-price-cancelled { text-decoration: line-through; color: var(--text-secondary); } @keyframes modalSlide { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } .modal-header { display: flex; align-items: center; justify-content: space-between; padding: 20px; border-bottom: 1px solid var(--border-color); } .modal-header h2 { margin: 0; font-size: 1.25rem; } .modal-body { padding: 20px; } #login-form { padding: 20px; } .form-group { margin-bottom: 20px; } .form-group label { display: block; margin-bottom: 6px; font-weight: 500; font-size: 0.9rem; } .form-group input { width: 100%; padding: 10px 12px; border: 1px solid var(--border-color); /* Changed from --border */ border-radius: 8px; background: var(--bg-body); /* Changed from --bg */ color: var(--text-primary); /* Changed from --text */ font-family: inherit; transition: border-color 0.2s; } .form-group input:focus { outline: none; border-color: var(--accent-color); /* Changed from --primary */ } .help-text { display: block; margin-top: 4px; color: var(--text-secondary); /* Changed from --text-muted */ font-size: 0.75rem; } .error-msg { margin-bottom: 16px; padding: 10px; background: rgba(239, 68, 68, 0.1); color: var(--error-color); /* Changed from --danger */ border-radius: 8px; font-size: 0.85rem; text-align: center; } .modal-actions { margin-top: 24px; } .btn-primary.wide { width: 100%; justify-content: center; } .hidden { display: none !important; } /* Menu Grid Container */ .menu-grid { display: flex; flex-direction: column; flex: 1; overflow: hidden; gap: 1rem; } .week-section { margin-bottom: 2rem; } .week-header { margin-bottom: 1.5rem; border-bottom: 1px solid var(--border-color); padding-bottom: 1rem; text-align: center; } .week-title { font-size: 1.75rem; font-weight: 700; color: var(--text-primary); } .week-range { color: var(--text-secondary); font-size: 0.9rem; margin-top: 0.25rem; } /* Full-viewport layout: header + scrollable content + footer */ #kantine-wrapper { display: flex; flex-direction: column; height: 100vh; height: 100dvh; /* Dynamic viewport height for mobile browsers */ overflow: hidden; } .days-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 0.75rem; flex: 1; overflow-y: auto; /* This is the scroll container at the window edge */ align-content: start; padding: 0 2rem 2rem 2rem; } /* Card */ .menu-card { background-color: var(--bg-card); border-radius: 12px; border: 1px solid var(--border-color); box-shadow: var(--card-shadow); overflow: clip; /* Clips scrolling content behind sticky header */ transition: box-shadow 0.2s ease; display: flex; flex-direction: column; } /* Past Day Styling - Target specific elements so ordered items can remain visible AND preserve sticky context */ /* We MUST apply filter/opacity to children, not the parent .menu-card, or else position: sticky breaks */ /* Header keeps fully opaque background to hide scrolling items, only grayscales */ .menu-card.past-day .card-header { filter: grayscale(0.8); transition: filter 0.3s; } /* Items become semi-transparent */ .menu-card.past-day .menu-item:not(.ordered) { opacity: 0.6; filter: grayscale(0.8); transition: opacity 0.3s, filter 0.3s; } .menu-card.past-day:hover .card-header { filter: grayscale(0.4); } .menu-card.past-day:hover .menu-item:not(.ordered) { opacity: 0.8; filter: grayscale(0.4); } /* Past ordered items get no special frame or shadow, but remain visually distinct by staying fully opaque (via the :not(.ordered) selector above) */ .menu-item.today-ordered { border: 2px solid #8b5cf6; box-shadow: 0 0 30px rgba(139, 92, 246, 0.6); border-radius: 8px; padding: 1rem; margin: 0 -1rem 1.5rem -1rem; background: var(--bg-card); position: relative; z-index: 5; animation: pulse-glow-strong 3s infinite; } @keyframes pulse-glow-strong { 0% { box-shadow: 0 0 20px rgba(139, 92, 246, 0.4); } 50% { box-shadow: 0 0 40px rgba(139, 92, 246, 0.8); } 100% { box-shadow: 0 0 20px rgba(139, 92, 246, 0.4); } } .menu-card:hover { box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); } .card-header { padding: 1rem 1.25rem; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: baseline; background-color: var(--bg-card); /* Removed border-radius: 12px 12px 0 0; .menu-card\'s overflow: clip will round the corners initially. When sticky at the top, it will be square and perfectly hide scrolling content! */ /* Sticky within .container scroll area */ position: sticky; top: 0; z-index: 90; } .card-body { padding: 1.25rem; display: grid; grid-template-rows: auto; align-content: start; } .day-name { font-size: 1.125rem; font-weight: 600; } .day-date { font-size: 0.875rem; color: var(--text-secondary); } .empty-state { color: var(--text-secondary); font-style: italic; text-align: center; padding: 1rem; } /* Menu Items */ .menu-item { margin-bottom: 1.5rem; padding-bottom: 1.5rem; border-bottom: 1px solid var(--border-color); } .menu-item:last-child { margin-bottom: 0; padding-bottom: 0; border-bottom: none; } .item-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 0.5rem; gap: 1rem; } .item-name { font-weight: 600; color: var(--text-primary); font-size: 1rem; } .item-price { font-weight: 700; color: var(--accent-color); white-space: nowrap; } .item-desc { font-size: 0.875rem; color: var(--text-secondary); line-height: 1.6; margin-bottom: 0.75rem; white-space: pre-wrap; } .badges { display: flex; gap: 0.5rem; margin-left: auto; } .item-status-row { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.75rem; } .badge { display: inline-flex; align-items: center; justify-content: center; height: 24px; font-size: 0.75rem; padding: 0 10px; border-radius: 4px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; line-height: normal; white-space: nowrap; } .badge.available { background-color: rgba(16, 185, 129, 0.1); /* Emerald 500 / 10% */ color: var(--success-color); border: 1px solid rgba(16, 185, 129, 0.2); } .badge.sold-out { background-color: rgba(239, 68, 68, 0.1); /* Red 500 / 10% */ color: var(--error-color); border: 1px solid rgba(239, 68, 68, 0.2); } .badge.ordered { background-color: rgba(139, 92, 246, 0.1); /* Violet 500 / 10% */ color: #8b5cf6; border: 1px solid rgba(139, 92, 246, 0.2); gap: 4px; } .badge.ordered .material-icons-round { font-size: 1rem; } /* Loading */ .loading-state { text-align: center; padding: 4rem; color: var(--text-secondary); } .spinner { width: 40px; height: 40px; border: 3px solid var(--border-color); border-top-color: var(--accent-color); border-radius: 50%; margin: 0 auto 1rem; animation: spin 1s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } /* Footer */ .app-footer { flex-shrink: 0; text-align: center; padding: 0.4rem 2rem; color: var(--text-secondary); font-size: 0.8rem; border-top: 1px solid var(--border-color); } /* === Order / Cancel Buttons (inline in status row) === */ .btn-order { display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px; border: none; border-radius: 6px; background: var(--success-color); color: white; font-size: 0.75rem; font-weight: 600; cursor: pointer; transition: all 0.2s ease; font-family: inherit; } .btn-order .material-icons-round { font-size: 16px; } .btn-order:hover:not(:disabled) { filter: brightness(1.15); transform: translateY(-1px); } .btn-order:disabled { opacity: 0.5; cursor: not-allowed; } .btn-order.loading { pointer-events: none; opacity: 0.6; } .btn-order-compact { padding: 2px 4px; gap: 0; } .btn-order-compact .material-icons-round { font-size: 16px; } .btn-cancel { display: inline-flex; align-items: center; justify-content: center; padding: 4px 6px; border: none; border-radius: 6px; background: var(--error-color); color: white; font-size: 0.75rem; cursor: pointer; transition: all 0.2s ease; font-family: inherit; } .btn-cancel .material-icons-round { font-size: 16px; } .btn-cancel:hover:not(:disabled) { filter: brightness(1.15); transform: translateY(-1px); } .btn-cancel:disabled { opacity: 0.5; cursor: not-allowed; } /* Past days: hide action buttons */ .past-day .item-actions { display: none; } /* Order count badge (for multi-orders) */ .order-count-badge { display: inline-flex; align-items: center; justify-content: center; background: rgba(255, 255, 255, 0.3); color: white; font-size: 0.65rem; font-weight: 700; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 8px; margin-left: 4px; line-height: 1; } /* === Toast Notifications === */ #toast-container { position: fixed; bottom: 20px; right: 20px; z-index: 10000; display: flex; flex-direction: column; gap: 8px; pointer-events: none; } .toast { display: flex; align-items: center; gap: 8px; padding: 10px 16px; border-radius: 8px; font-size: 0.85rem; font-weight: 500; font-family: \'Inter\', sans-serif; color: white; backdrop-filter: blur(10px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); pointer-events: auto; transform: translateX(120%); opacity: 0; transition: transform 0.3s ease, opacity 0.3s ease; } .toast.show { transform: translateX(0); opacity: 1; } .toast .material-icons-round { font-size: 18px; } .toast-success { background: rgba(5, 150, 105, 0.95); } .toast-error { background: rgba(220, 38, 38, 0.95); } .toast-info { background: rgba(59, 130, 246, 0.95); } /* === Mobile Responsiveness === */ @media (max-width: 600px) { .header-content { flex-direction: column; gap: 1rem; padding: 0.75rem; } .week-nav { width: 100%; justify-content: center; } .nav-pills { width: 100%; justify-content: space-between; } .nav-btn { flex: 1; justify-content: center; padding: 0.5rem; font-size: 0.85rem; } .days-grid { grid-template-columns: 1fr; /* Force single column */ } .main-content { padding: 1rem; } .week-title { font-size: 1.5rem; } /* Adjust toast position for mobile */ .toast-container { bottom: 1rem; right: 1rem; left: 1rem; /* Center on mobile */ width: auto; } .menu-card { margin-bottom: 1rem; } } /* === Flagging & Notification Styles === */ .btn-flag { display: inline-flex; align-items: center; justify-content: center; background: transparent; border: 1px solid var(--text-secondary); color: var(--text-secondary); border-radius: 6px; padding: 4px; cursor: pointer; transition: all 0.2s; margin-right: 0.5rem; width: 28px; height: 28px; } .btn-flag:hover { background: rgba(234, 179, 8, 0.1); /* Yellow-500 / 10% */ color: #eab308; border-color: #eab308; } .btn-flag.active { background: rgba(234, 179, 8, 0.1); color: #eab308; border-color: #eab308; } .btn-flag .material-icons-round { font-size: 1.1rem; } /* Flagged & Sold Out (Yellow Glow) */ .menu-item.flagged-sold-out { border: 1px solid #eab308; box-shadow: 0 0 10px rgba(234, 179, 8, 0.2); border-radius: 8px; padding: 1rem; margin: 0 -1rem 1.5rem -1rem; background: var(--bg-card); position: relative; z-index: 5; animation: yellow-pulse 3s infinite; } @keyframes yellow-pulse { 0% { box-shadow: 0 0 8px rgba(234, 179, 8, 0.2); } 50% { box-shadow: 0 0 16px rgba(234, 179, 8, 0.5); } 100% { box-shadow: 0 0 8px rgba(234, 179, 8, 0.2); } } /* Flagged & Available (Green Glow) */ .menu-item.flagged-available { border: 2px solid var(--success-color); box-shadow: 0 0 15px rgba(16, 185, 129, 0.3); border-radius: 8px; padding: 1rem; margin: 0 -1rem 1.5rem -1rem; background: var(--bg-card); position: relative; z-index: 5; animation: green-pulse 3s infinite; } @keyframes green-pulse { 0% { box-shadow: 0 0 10px rgba(16, 185, 129, 0.3); } 50% { box-shadow: 0 0 20px rgba(16, 185, 129, 0.6); } 100% { box-shadow: 0 0 10px rgba(16, 185, 129, 0.3); } } /* Day Header Badges */ .day-header-left { display: flex; align-items: center; gap: 0.75rem; } .menu-code-badge { font-size: 0.75rem; font-weight: 700; color: #8b5cf6; /* Violet 500 */ background-color: rgba(139, 92, 246, 0.15); border: 1px solid rgba(139, 92, 246, 0.3); padding: 2px 6px; border-radius: 6px; line-height: normal; display: inline-block; } /* Detailed Badge Colors */ .nav-badge.badge-violet { background-color: #8b5cf6; } .nav-badge.badge-green { background-color: var(--success-color); } .nav-badge.badge-red { background-color: var(--error-color); } .nav-badge.badge-blue { background-color: var(--accent-color); } /* Day Header Status Colors (User Request) */ .card-header.header-violet { background-color: var(--bg-card); background-image: linear-gradient(rgba(139, 92, 246, 0.15), rgba(139, 92, 246, 0.15)); border-bottom: 2px solid #8b5cf6; } .card-header.header-green { background-color: var(--bg-card); background-image: linear-gradient(rgba(16, 185, 129, 0.15), rgba(16, 185, 129, 0.15)); border-bottom: 2px solid var(--success-color); } .card-header.header-red { background-color: var(--bg-card); background-image: linear-gradient(rgba(239, 68, 68, 0.15), rgba(239, 68, 68, 0.15)); border-bottom: 2px solid var(--error-color); } .card-header.header-violet .day-name, .card-header.header-green .day-name, .card-header.header-red .day-name { font-weight: 700; color: var(--text-primary); /* Ensure text remains standard color */ } /* Update Icon */ .update-icon { display: inline-flex; align-items: center; justify-content: center; margin-left: 8px; background-color: rgba(16, 185, 129, 0.2); /* Green tint */ color: var(--success-color); border-radius: 50%; width: 24px; height: 24px; cursor: pointer; font-size: 14px; transition: all 0.2s; text-decoration: none; animation: pulse 2s infinite; } .update-icon:hover { background-color: var(--success-color); color: white; transform: scale(1.1); } @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.4); } 70% { box-shadow: 0 0 0 6px rgba(16, 185, 129, 0); } 100% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); } } /* Order Countdown */ #order-countdown { background: rgba(255, 255, 255, 0.1); padding: 0.25rem 0.75rem; border-radius: 99px; font-size: 0.85rem; display: flex; align-items: center; gap: 0.5rem; white-space: nowrap; border: 1px solid var(--border-color); } #order-countdown span { opacity: 0.7; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.5px; } #order-countdown.urgent { background: rgba(239, 68, 68, 0.2); border-color: rgba(239, 68, 68, 0.5); color: #ef4444; animation: pulse-red 2s infinite; } @keyframes pulse-red { 0% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4); } 70% { box-shadow: 0 0 0 6px rgba(239, 68, 68, 0); } 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); } } /* Smart Highlights (Blue Glow - matches today-ordered/flagged pattern) */ .menu-item.highlight-glow { border: 2px solid rgba(59, 130, 246, 0.7); box-shadow: 0 0 20px rgba(59, 130, 246, 0.4); border-radius: 8px; padding: 1rem; margin: 0 -1rem 1.5rem -1rem; background: var(--bg-card); position: relative; z-index: 5; animation: blue-pulse 3s infinite; } @keyframes blue-pulse { 0% { box-shadow: 0 0 15px rgba(59, 130, 246, 0.3); } 50% { box-shadow: 0 0 25px rgba(59, 130, 246, 0.6); } 100% { box-shadow: 0 0 15px rgba(59, 130, 246, 0.3); } } /* Nav Badge with Count */ .nav-badge.has-highlights { background-color: var(--bg-card); /* Neutral background */ color: var(--text-primary); border: 1px solid var(--border-color); padding: 2px 6px; } .nav-badge .highlight-count { color: #3b82f6; /* Blue 500 */ font-weight: 700; margin-left: 4px; } /* Tag Management Modal */ #tags-list { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 1rem; min-height: 50px; } /* Tag badges styled consistently with .badge (verfügbar/ausverkauft) */ .tag-badge { display: inline-flex; align-items: center; justify-content: center; height: 24px; font-size: 0.75rem; padding: 0 10px; border-radius: 4px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; line-height: normal; white-space: nowrap; background-color: rgba(59, 130, 246, 0.1); color: #3b82f6; border: 1px solid rgba(59, 130, 246, 0.2); gap: 4px; } .tag-remove { cursor: pointer; opacity: 0.7; font-size: 1.1em; line-height: 1; transition: all 0.2s; } .tag-remove:hover { opacity: 1; color: #ef4444; } .input-group { display: flex; gap: 0.5rem; } .input-group input { flex: 1; padding: 0.75rem; background: var(--bg-body); border: 1px solid var(--border-color); color: var(--text-primary); border-radius: 8px; font-family: inherit; } /* Add tag button - styled like .btn-order with nav-btn.active color */ #btn-add-tag { display: inline-flex; align-items: center; gap: 4px; padding: 0.5rem 1rem; border: none; border-radius: 6px; background: var(--accent-color); color: white; font-size: 0.8rem; font-weight: 600; cursor: pointer; transition: all 0.2s ease; font-family: inherit; white-space: nowrap; } #btn-add-tag:hover { filter: brightness(1.15); transform: translateY(-1px); } .matched-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; /* Space between tags and title */ margin-top: -5px; /* Pull closer to header */ } .tag-badge-small { display: inline-flex; align-items: center; font-size: 0.7rem; padding: 2px 8px; border-radius: 4px; background: rgba(59, 130, 246, 0.15); color: #60a5fa; border: 1px solid rgba(59, 130, 246, 0.3); font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; } [data-theme="light"] .tag-badge-small { background: rgba(37, 99, 235, 0.1); color: #2563eb; border: 1px solid rgba(37, 99, 235, 0.2); } /* Installer Changelog */ .changelog-container ul { padding-left: 1.5rem; margin: 0.5rem 0; } .changelog-container li { margin-bottom: 0.4rem; line-height: 1.5; } .changelog-container h3 { margin-top: 1.5rem; margin-bottom: 0.5rem; font-size: 1.1em; color: var(--accent-color); } /* === Version Menu === */ .version-tag { cursor: pointer; transition: opacity 0.2s ease, text-decoration 0.2s ease; } .version-tag:hover { opacity: 1 !important; text-decoration: underline; } .version-list { list-style: none; padding: 0; margin: 0; } .version-item { display: flex; justify-content: space-between; align-items: center; padding: 10px 14px; border-radius: 8px; margin-bottom: 4px; transition: background 0.2s; } .version-item:hover { background: rgba(100, 116, 139, 0.08); } .version-item.current { background: rgba(2, 154, 168, 0.1); border: 1px solid rgba(2, 154, 168, 0.25); } [data-theme="dark"] .version-item:hover { background: rgba(255, 255, 255, 0.05); } [data-theme="dark"] .version-item.current { background: rgba(96, 165, 250, 0.12); border: 1px solid rgba(96, 165, 250, 0.25); } .version-info { display: flex; align-items: center; gap: 10px; } .badge-current { font-size: 0.75rem; font-weight: 600; color: var(--success-color); padding: 2px 8px; border-radius: 4px; background: rgba(5, 150, 105, 0.1); } .badge-new { font-size: 0.75rem; font-weight: 600; color: #029aa8; padding: 2px 8px; border-radius: 4px; background: rgba(2, 154, 168, 0.1); } [data-theme="dark"] .badge-new { color: #60a5fa; background: rgba(96, 165, 250, 0.12); } .install-link { font-size: 0.8rem; font-weight: 500; padding: 4px 12px; border-radius: 6px; background: rgba(2, 154, 168, 0.1); color: #029aa8; text-decoration: none; border: 1px solid rgba(2, 154, 168, 0.25); transition: all 0.2s; white-space: nowrap; } .install-link:hover { background: rgba(2, 154, 168, 0.2); border-color: rgba(2, 154, 168, 0.4); } [data-theme="dark"] .install-link { color: #60a5fa; background: rgba(96, 165, 250, 0.12); border: 1px solid rgba(96, 165, 250, 0.25); } [data-theme="dark"] .install-link:hover { background: rgba(96, 165, 250, 0.2); border-color: rgba(96, 165, 250, 0.4); } .dev-toggle { padding: 10px 14px; border-radius: 8px; background: rgba(100, 116, 139, 0.05); border: 1px solid var(--border-color); } .dev-toggle input[type="checkbox"] { accent-color: #029aa8; width: 16px; height: 16px; } [data-theme="dark"] .dev-toggle input[type="checkbox"] { accent-color: #60a5fa; } ';document.head.appendChild(s); // Inject JS logic var sc=document.createElement('script'); -sc.textContent="function showErrorModal(e,t,n,a){const s=\"error-modal\";let o=document.getElementById(s);o&&o.remove(),o=document.createElement(\"div\"),o.id=s,o.className=\"modal hidden\",o.innerHTML=`\\n
\\n
\\n

\\n signal_wifi_off\\n ${e}\\n

\\n
\\n
\\n

${t}

\\n
\\n \\n
\\n
\\n
\\n `,document.body.appendChild(o),document.getElementById(\"btn-error-redirect\").addEventListener(\"click\",()=>{window.location.href=a}),requestAnimationFrame(()=>{o.classList.remove(\"hidden\")})}!function(){\"use strict\";if(window.__KANTINE_LOADED)return;window.__KANTINE_LOADED=!0;const e=\"https://api.bessa.app/v1\",t=\"c3418725e95a9f90e3645cbc846b4d67c7c66131\",n=591,a=\"TauNeutrino/kantine-overview\",s=`https://api.github.com/repos/${a}`,o=`https://htmlpreview.github.io/?https://github.com/${a}/blob`;let i=[],r=G(new Date),l=(new Date).getFullYear(),c=\"this-week\",d=localStorage.getItem(\"kantine_authToken\"),m=localStorage.getItem(\"kantine_currentUser\"),u=new Map,g=new Set(JSON.parse(localStorage.getItem(\"kantine_flags\")||\"[]\")),h=null,p=localStorage.getItem(\"kantine_lang\")||\"de\";function f(e){return{Authorization:`Token ${e||t}`,Accept:\"application/json\",\"Content-Type\":\"application/json\",\"X-Client-Version\":\"v1.6.10\"}}function v(){if(!d)try{const e=localStorage.getItem(\"AkitaStores\");if(e){const t=JSON.parse(e);t.auth&&t.auth.token&&(console.log(\"Found existing Bessa session!\"),d=t.auth.token,localStorage.setItem(\"kantine_authToken\",d),t.auth.user&&(m=t.auth.user.id||\"unknown\",localStorage.setItem(\"kantine_currentUser\",m),t.auth.user.firstName&&localStorage.setItem(\"kantine_firstName\",t.auth.user.firstName),t.auth.user.lastName&&localStorage.setItem(\"kantine_lastName\",t.auth.user.lastName)))}}catch(e){console.warn(\"Failed to parse AkitaStores:\",e)}d=localStorage.getItem(\"kantine_authToken\"),m=localStorage.getItem(\"kantine_currentUser\");const e=localStorage.getItem(\"kantine_firstName\"),t=document.getElementById(\"btn-login-open\"),n=document.getElementById(\"user-info\"),a=document.getElementById(\"user-id-display\");d?(t.classList.add(\"hidden\"),n.classList.remove(\"hidden\"),a.textContent=e||(m?`User ${m}`:\"Angemeldet\"),y()):(t.classList.remove(\"hidden\"),n.classList.add(\"hidden\"),a.textContent=\"\"),j()}async function y(){if(d)try{const t=await fetch(`${e}/user/orders/?venue=591&ordering=-created&limit=50`,{headers:f(d)}),n=await t.json();if(t.ok){u=new Map;const e=n.results||[];for(const t of e){if(9===t.order_state)continue;const e=t.date.split(\"T\")[0];for(const n of t.items||[]){const a=`${e}_${n.article}`;u.has(a)||u.set(a,[]),u.get(a).push(t.id)}}console.log(`Fetched ${e.length} orders, mapped active ones.`),j(),z()}}catch(e){console.error(\"Error fetching orders:\",e)}}let b=null;function w(e){const t=document.getElementById(\"history-content\");if(!e||0===e.length)return void(t.innerHTML='

Keine Bestellungen gefunden.

');const n={};e.forEach(e=>{const t=new Date(e.date),a=t.getFullYear(),s=t.getMonth(),o=`${a}-${s.toString().padStart(2,\"0\")}`,i=t.toLocaleString(\"de-AT\",{month:\"long\"}),r=G(t);n[a]||(n[a]={year:a,months:{}}),n[a].months[o]||(n[a].months[o]={name:i,year:a,monthIndex:s,count:0,total:0,weeks:{}}),n[a].months[o].weeks[r]||(n[a].months[o].weeks[r]={label:`KW ${r}`,items:[],count:0,total:0});(e.items||[]).forEach(t=>{const s=parseFloat(t.price||e.total||0);n[a].months[o].weeks[r].items.push({date:e.date,name:t.name||\"Men\u00fc\",price:s,state:e.order_state}),9!==e.order_state&&(n[a].months[o].weeks[r].count++,n[a].months[o].weeks[r].total+=s,n[a].months[o].count++,n[a].months[o].total+=s)})});const a=Object.keys(n).sort((e,t)=>t-e);let s=\"\";a.forEach(e=>{const t=n[e];s+=`
\\n

${t.year}

`;Object.keys(t.months).sort((e,t)=>t.localeCompare(e)).forEach(e=>{const n=t.months[e];s+=`
\\n
\\n
\\n ${n.name}\\n
\\n ${n.count} Bestellungen • \u20ac${n.total.toFixed(2)}\\n
\\n
\\n expand_more\\n
\\n
`;Object.keys(n.weeks).sort((e,t)=>parseInt(t)-parseInt(e)).forEach(e=>{const t=n.weeks[e];s+=`
\\n
\\n ${t.label}\\n ${t.count} Bestellungen • \u20ac${t.total.toFixed(2)}\\n
`,t.items.forEach(e=>{const t=new Date(e.date).toLocaleDateString(\"de-AT\",{weekday:\"short\",day:\"2-digit\",month:\"2-digit\"});let n=\"\";n=9===e.state?'Storniert':8===e.state?'Abgeschlossen':'\u00dcbertragen',s+=`\\n
\\n
${t}
\\n
\\n ${W(e.name)}\\n
${n}
\\n
\\n
\u20ac${e.price.toFixed(2)}
\\n
`}),s+=\"
\"}),s+=\"
\"}),s+=\"
\"}),t.innerHTML=s;t.querySelectorAll(\".history-month-header\").forEach(e=>{e.addEventListener(\"click\",()=>{const t=e.parentElement;t.classList.contains(\"open\")?(t.classList.remove(\"open\"),e.setAttribute(\"aria-expanded\",\"false\")):(t.classList.add(\"open\"),e.setAttribute(\"aria-expanded\",\"true\"))})})}function k(){localStorage.setItem(\"kantine_flags\",JSON.stringify([...g]))}function A(){const e=document.getElementById(\"alarm-bell\"),t=document.getElementById(\"alarm-bell-icon\");if(!e||!t)return;if(0===g.size)return e.classList.add(\"hidden\"),e.style.display=\"none\",t.style.color=\"var(--text-secondary)\",void(t.style.textShadow=\"none\");e.classList.remove(\"hidden\"),e.style.display=\"inline-flex\";let n=!1;for(const e of i)if(e.days){for(const t of e.days)if(t.items){for(const e of t.items)if(e.available&&g.has(e.id)){n=!0;break}if(n)break}if(n)break}let a=localStorage.getItem(\"kantine_last_updated\"),s=\"gerade eben\";a||(a=(new Date).toISOString(),localStorage.setItem(\"kantine_last_updated\",a));s=$(new Date(a)),e.title=`Zuletzt gepr\u00fcft: ${s}`,n?(t.style.color=\"#10b981\",t.style.textShadow=\"0 0 10px rgba(16, 185, 129, 0.4)\"):(t.style.color=\"#f59e0b\",t.style.textShadow=\"0 0 10px rgba(245, 158, 11, 0.4)\")}function E(n,a,s,o){const r=`${n}_${a}`;let l=!1;g.has(r)?(g.delete(r),F(`Flag entfernt f\u00fcr ${s}`,\"success\")):(g.add(r),l=!0,F(`Benachrichtigung aktiviert f\u00fcr ${s}`,\"success\"),\"default\"===Notification.permission&&Notification.requestPermission()),k(),A(),j(),l&&async function(){if(0===g.size)return;const n=d||t,a=new Set;for(const e of g){const[t]=e.split(\"_\");a.add(t)}let s=!1;for(const t of a)try{const a=await fetch(`${e}/venues/591/menu/7/${t}/`,{headers:f(n)});if(!a.ok)continue;const o=(await a.json()).results||[];let r=[];for(const e of o)e.items&&Array.isArray(e.items)&&(r=r.concat(e.items));for(let e of i){if(!e.days)continue;let n=e.days.find(e=>e.date===t);n&&(n.items=r.map(e=>{const n=!1===e.amount_tracking,a=parseInt(e.available_amount)>0;return{id:`${t}_${e.id}`,articleId:e.id,name:e.name||\"Unknown\",description:e.description||\"\",price:parseFloat(e.price)||0,available:n||a,availableAmount:parseInt(e.available_amount)||0,amountTracking:!1!==e.amount_tracking}}),s=!0)}}catch(e){console.error(\"Error refreshing flag date\",t,e)}s&&(O(),q((new Date).toISOString()),A(),j())}()}function I(){h||d&&(h=setInterval(()=>async function(){if(0===g.size||!d)return;console.log(`Polling ${g.size} flagged items...`);for(const t of g){const[n,a]=t.split(\"_\"),s=parseInt(a);try{const t=await fetch(`${e}/venues/591/menu/7/${n}/`,{headers:f(d)});if(!t.ok)continue;const a=(await t.json()).results||[];let o=null;for(const e of a)if(e.items&&(o=e.items.find(e=>e.id===s||e.article===s),o))break;if(o){if(!1===o.amount_tracking||parseInt(o.available_amount)>0){const e=o.name||\"Unbekannt\";F(`${e} ist jetzt verf\u00fcgbar!`,\"success\"),\"granted\"===Notification.permission&&new Notification(\"Kantine Wrapper\",{body:`${e} ist jetzt verf\u00fcgbar!`,icon:\"\ud83c\udf7d\ufe0f\"}),N()}}}catch(e){console.error(`Poll error for ${t}:`,e),await new Promise(e=>setTimeout(e,200))}}q((new Date).toISOString())}(),3e5),console.log(\"Polling started (every 5 min)\"))}let L=JSON.parse(localStorage.getItem(\"kantine_highlightTags\")||\"[]\");function S(){localStorage.setItem(\"kantine_highlightTags\",JSON.stringify(L)),j(),z()}function B(){const e=document.getElementById(\"tags-list\");e.innerHTML=\"\",L.forEach(t=>{const n=document.createElement(\"span\");n.className=\"tag-badge\",n.innerHTML=`${t} ×`,e.appendChild(n)}),e.querySelectorAll(\".tag-remove\").forEach(e=>{e.addEventListener(\"click\",e=>{var t;t=e.target.dataset.tag,L=L.filter(e=>e!==t),S(),B()})})}function x(e){return e?(e=e.toLowerCase(),L.filter(t=>e.includes(t))):[]}const D=\"kantine_menuCache\",C=\"kantine_menuCacheTs\";function O(){try{localStorage.setItem(D,JSON.stringify(i)),localStorage.setItem(C,(new Date).toISOString())}catch(e){console.warn(\"Failed to cache menu data:\",e)}}async function N(){const n=document.getElementById(\"loading\"),a=document.getElementById(\"progress-modal\"),s=document.getElementById(\"progress-fill\"),o=document.getElementById(\"progress-percent\"),c=document.getElementById(\"progress-message\");n.classList.remove(\"hidden\");const m=d||t;try{a.classList.remove(\"hidden\"),c.textContent=\"Hole verf\u00fcgbare Daten...\",s.style.width=\"0%\",o.textContent=\"0%\";const t=await fetch(`${e}/venues/591/menu/dates/`,{headers:f(m)});if(!t.ok)throw new Error(`Failed to fetch dates: ${t.status}`);let n=(await t.json()).results||[];const d=new Date;d.setDate(d.getDate()-7);const u=d.toISOString().split(\"T\")[0];n=n.filter(e=>e.date>=u).sort((e,t)=>e.date.localeCompare(t.date)).slice(0,30);const g=n.length;c.textContent=`${g} Tage gefunden. Lade Details...`;const h=[];let p=0;for(const t of n){const n=t.date,a=Math.round((p+1)/g*100);s.style.width=`${a}%`,o.textContent=`${a}%`,c.textContent=`Lade Men\u00fc f\u00fcr ${n}...`;try{const a=await fetch(`${e}/venues/591/menu/7/${n}/`,{headers:f(m)});if(a.ok){const e=await a.json();0===p&&console.log(\"[Kantine Debug] Raw API response for\",n,\":\",JSON.stringify(e).substring(0,2e3));const s=e.results||[];let o=[];for(const e of s)e.items&&Array.isArray(e.items)&&(o=o.concat(e.items));o.length>0&&(0===p&&(console.log(\"[Kantine Debug] First item keys:\",Object.keys(o[0])),console.log(\"[Kantine Debug] First item:\",JSON.stringify(o[0]).substring(0,500))),h.push({date:n,menu_items:o,orders:t.orders||[]}))}}catch(e){console.error(`Failed to fetch details for ${n}:`,e)}p++,await new Promise(e=>setTimeout(e,100))}const y=new Map;i&&i.length>0&&i.forEach(e=>{const t=`${e.year}-${e.weekNumber}`;try{y.set(t,{year:e.year,weekNumber:e.weekNumber,days:e.days?e.days.map(e=>({...e,items:e.items?[...e.items]:[]})):[]})}catch(e){console.warn(\"Error hydrating week:\",e)}});for(const e of h){const t=new Date(e.date),n=G(t),a=J(t),s=`${a}-${n}`;y.has(s)||y.set(s,{year:a,weekNumber:n,days:[]});const o=y.get(s),i=t.toLocaleDateString(\"en-US\",{weekday:\"long\"}),r=new Date(e.date);r.setHours(10,0,0,0);const l={date:e.date,weekday:i,orderCutoff:r.toISOString(),items:e.menu_items.map(t=>{const n=!1===t.amount_tracking,a=parseInt(t.available_amount)>0;return{id:`${e.date}_${t.id}`,articleId:t.id,name:t.name||\"Unknown\",description:t.description||\"\",price:parseFloat(t.price)||0,available:n||a,availableAmount:parseInt(t.available_amount)||0,amountTracking:!1!==t.amount_tracking}})},c=o.days.findIndex(t=>t.date===e.date);c>=0?o.days[c]=l:o.days.push(l)}i=Array.from(y.values()).sort((e,t)=>e.year!==t.year?e.year-t.year:e.weekNumber-t.weekNumber),i.forEach(e=>{e.days&&e.days.sort((e,t)=>e.date.localeCompare(t.date))}),O(),q((new Date).toISOString()),r=G(new Date),l=(new Date).getFullYear(),v(),j(),z(),A(),c.textContent=\"Fertig!\",setTimeout(()=>a.classList.add(\"hidden\"),500)}catch(e){console.error(\"Error fetching menu:\",e),a.classList.add(\"hidden\"),showErrorModal(\"Keine Verbindung\",`Die Men\u00fcdaten konnten nicht geladen werden. M\u00f6glicherweise besteht keine Verbindung zur API oder zur Bessa-Webseite.

${e.message}`,\"Zur Original-Seite\",\"https://web.bessa.app/knapp-kantine\")}finally{n.classList.add(\"hidden\")}}let M=null,T=null;function q(e){const t=document.getElementById(\"last-updated-subtitle\");if(e){M=e,localStorage.setItem(\"kantine_last_updated\",e);try{const n=new Date(e),a=n.toLocaleTimeString(\"de-DE\",{hour:\"2-digit\",minute:\"2-digit\"}),s=n.toLocaleDateString(\"de-DE\",{day:\"2-digit\",month:\"2-digit\"}),o=$(n);t.textContent=`Aktualisiert: ${s} ${a} (${o})`}catch(e){t.textContent=\"\"}T||(T=setInterval(()=>{M&&(q(M),A())},6e4))}}function $(e){const t=Date.now()-e.getTime(),n=Math.floor(t/6e4);if(n<1)return\"gerade eben\";if(1===n)return\"vor 1 min.\";if(n<60)return`vor ${n} min.`;const a=Math.floor(n/60);return 1===a?\"vor 1 Std.\":`vor ${a} Std.`}function F(e,t=\"info\"){let n=document.getElementById(\"toast-container\");n||(n=document.createElement(\"div\"),n.id=\"toast-container\",document.body.appendChild(n));const a=document.createElement(\"div\");a.className=`toast toast-${t}`;const s=\"success\"===t?\"check_circle\":\"error\"===t?\"error\":\"info\";a.innerHTML=`${s}${e}`,n.appendChild(a),requestAnimationFrame(()=>a.classList.add(\"show\")),setTimeout(()=>{a.classList.remove(\"show\"),setTimeout(()=>a.remove(),300)},3e3)}function z(){const e=document.getElementById(\"btn-next-week\");let t=r+1,n=l;t>52&&(t=1,n++);const a=i.find(e=>e.weekNumber===t&&e.year===n);let s=0,o=0,c=0,d=0;a&&a.days&&a.days.forEach(e=>{if(e.items&&e.items.length>0){s++;const t=e.items.some(e=>e.available);t&&o++;let n=!1;e.items.forEach(t=>{const a=t.articleId||parseInt(t.id.split(\"_\")[1]),s=`${e.date}_${a}`;u.has(s)&&u.get(s).length>0&&(n=!0)}),n&&c++,t&&!n&&d++}});let m=e.querySelector(\".nav-badge\");if(s>0){m||(m=document.createElement(\"span\"),m.className=\"nav-badge\",e.appendChild(m)),m.title=`${c} bestellt / ${o} bestellbar / ${s} gesamt`,m.innerHTML=`${c}/${o}/${s}`,m.classList.remove(\"badge-violet\",\"badge-green\",\"badge-red\",\"badge-blue\"),c>0&&0===d?m.classList.add(\"badge-violet\"):d>0?m.classList.add(\"badge-green\"):0===o?m.classList.add(\"badge-red\"):m.classList.add(\"badge-blue\");let i=0;if(a&&a.days&&a.days.forEach(e=>{e.items.forEach(e=>{const t=x(e.name),n=x(e.description);(t.length>0||n.length>0)&&i++})}),i>0&&(m.innerHTML+=`(${i})`,m.title+=` \u2022 ${i} Highlights gefunden`,m.classList.add(\"has-highlights\")),0===c){e.classList.add(\"new-week-available\");const a=`kantine_notified_nextweek_${n}_${t}`;localStorage.getItem(a)||(localStorage.setItem(a,\"true\"),F(\"Neue Men\u00fcdaten f\u00fcr n\u00e4chste Woche verf\u00fcgbar!\",\"info\"))}else e.classList.remove(\"new-week-available\")}else m&&m.remove()}function j(){const t=document.getElementById(\"menu-container\");if(!t)return;t.innerHTML=\"\";let a=r,s=l;\"next-week\"===c&&(a++,a>52&&(a=1,s++));const o=i.flatMap(e=>e.days||[]).filter(e=>{const t=new Date(e.date);return G(t)===a&&J(t)===s});if(0===o.length)return t.innerHTML=`\\n
\\n

Keine Men\u00fcdaten f\u00fcr KW ${a} (${s}) verf\u00fcgbar.

\\n Versuchen Sie eine andere Woche oder schauen Sie sp\u00e4ter vorbei.\\n
`,void document.getElementById(\"weekly-cost-display\").classList.add(\"hidden\");!function(e){let t=0;e&&e.length>0&&e.forEach(e=>{e.items&&e.items.forEach(n=>{const a=n.articleId||parseInt(n.id.split(\"_\")[1]),s=`${e.date}_${a}`,o=u.get(s)||[];o.length>0&&(t+=n.price*o.length)})});const n=document.getElementById(\"weekly-cost-display\");t>0?(n.innerHTML=`shopping_bag Gesamt: ${t.toFixed(2).replace(\".\",\",\")} \u20ac`,n.classList.remove(\"hidden\")):n.classList.add(\"hidden\")}(o);const m=document.getElementById(\"header-week-info\"),h=\"this-week\"===c?\"Diese Woche\":\"N\u00e4chste Woche\";m.innerHTML=`\\n
${h}
\\n
Week ${a} \u2022 ${s}
`;const v=document.createElement(\"div\");v.className=\"days-grid\",o.sort((e,t)=>e.date.localeCompare(t.date));o.filter(e=>{const t=new Date(e.date).getDay();return 0!==t&&6!==t}).forEach(t=>{const a=function(t){if(!t.items||0===t.items.length)return null;const a=document.createElement(\"div\");a.className=\"menu-card\";const s=new Date,o=new Date(t.date);let i=!1;if(t.orderCutoff)i=s>=new Date(t.orderCutoff);else{const e=new Date;e.setHours(0,0,0,0);const n=new Date(t.date);n.setHours(0,0,0,0),i=n{const n=e.articleId||parseInt(e.id.split(\"_\")[1]),a=`${t.date}_${n}`,s=(u.get(a)||[]).length;if(s>0){const t=e.name.match(/([M][1-9][Ff]?)/);if(t){let e=t[1];s>1&&(e+=\"+\"),r.push(e)}}});const l=document.createElement(\"div\");l.className=\"card-header\";const c=o.toLocaleDateString(\"de-DE\",{day:\"2-digit\",month:\"2-digit\"}),m=r.map(e=>`${e}`).join(\"\");let h=\"\";const v=t.items&&t.items.some(e=>{const n=e.articleId||parseInt(e.id.split(\"_\")[1]),a=`${t.date}_${n}`;return u.has(a)&&u.get(a).length>0}),w=t.items&&t.items.some(e=>e.available);h=v?\"header-violet\":w&&!i?\"header-green\":\"header-red\";h&&l.classList.add(h);l.innerHTML=`\\n
\\n ${k=t.weekday,{Monday:\"Montag\",Tuesday:\"Dienstag\",Wednesday:\"Mittwoch\",Thursday:\"Donnerstag\",Friday:\"Freitag\",Saturday:\"Samstag\",Sunday:\"Sonntag\"}[k]||k}\\n
${m}
\\n
\\n ${c}`,a.appendChild(l);var k;const A=document.createElement(\"div\");A.className=\"card-body\";const I=(new Date).toISOString().split(\"T\")[0],L=t.date===I,S=[...t.items].sort((e,n)=>{if(L){const a=e.articleId||parseInt(e.id.split(\"_\")[1]),s=n.articleId||parseInt(n.id.split(\"_\")[1]),o=u.has(`${t.date}_${a}`),i=u.has(`${t.date}_${s}`);if(o&&!i)return-1;if(!o&&i)return 1}return e.name.localeCompare(n.name)});return S.forEach(a=>{const o=document.createElement(\"div\");o.className=\"menu-item\";const r=a.articleId||parseInt(a.id.split(\"_\")[1]),l=`${t.date}_${r}`,c=(u.get(l)||[]).length;let m=\"\";m=a.available?a.amountTracking?`Verf\u00fcgbar (${a.availableAmount})`:'Verf\u00fcgbar':'Ausverkauft';let h=\"\";if(c>0){h=`check_circle Bestellt${c>1?`${c}`:\"\"}`,o.classList.add(\"ordered\"),new Date(t.date).toDateString()===s.toDateString()&&o.classList.add(\"today-ordered\")}const v=`${t.date}_${r}`,w=g.has(v);w&&o.classList.add(a.available?\"flagged-available\":\"flagged-sold-out\");const k=[...new Set([...x(a.name),...x(a.description)])];k.length>0&&o.classList.add(\"highlight-glow\");let I=\"\",L=\"\",S=\"\";if(d&&!i){const e=w?\"notifications_active\":\"notifications_none\",n=w?\"btn-flag active\":\"btn-flag\",s=w?\"Benachrichtigung deaktivieren\":\"Benachrichtigen wenn verf\u00fcgbar\";if(a.available&&!w||(S=``),a.available&&(I=c>0?``:``),c>0){const e=1===c?\"close\":\"remove\",n=1===c?\"Bestellung stornieren\":\"Eine Bestellung stornieren\";L=``}}let B=\"\";if(k.length>0){B=`
${k.map(e=>`star${W(e)}`).join(\"\")}
`}o.innerHTML=`\\n
\\n ${W(a.name)}\\n ${a.price.toFixed(2)} \u20ac\\n
\\n
\\n ${h}\\n ${L}\\n ${I}\\n ${S}\\n
${m}
\\n
\\n ${B}\\n

${W(function(e){if(\"all\"===p)return e||\"\";const t=function(e){if(!e)return{de:\"\",en:\"\",raw:\"\"};let t=e.replace(/(?:\\(|(?:\\/|\\s|^))([A-Z,]+)\\)\\s*(?=\\S)(?!\\s*\\/)/g,\"($1)\\n\u2022 \");t.startsWith(\"\u2022 \")||(t=\"\u2022 \"+t);function n(e){let t=0,n=0;return e.forEach(e=>{const a=e.toLowerCase().replace(/[^a-z\u00e4\u00f6\u00fc\u00df]/g,\"\");if(a){let s=0,o=0;P.includes(a)?s=a.length:P.forEach(e=>{a.includes(e)&&e.length>s&&(s=e.length)}),V.includes(a)?o=a.length:V.forEach(e=>{a.includes(e)&&e.length>o&&(o=e.length)}),s>0&&(t+=s/a.length),o>0&&(n+=o/a.length),/^[A-Z\u00c4\u00d6\u00dc]/.test(e)&&(t+=.5)}}),{de:t,en:n}}function a(e){const t=e.trim().split(/\\s+/);if(t.length<2)return{enPart:e,nextDe:\"\"};let a=-1,s=-9999;for(let e=1;er.de||r.en>0,g=l.de+d>l.en;u&&g&&m>s&&(s=m,a=e)}return-1!==a?{enPart:t.slice(0,a).join(\" \"),nextDe:t.slice(a).join(\" \")}:{enPart:e,nextDe:\"\"}}const s=/(.*?)(?:\\(|(?:\\/|\\s|^))([A-Z,]+)\\)\\s*(?!\\s*[/])/g;let o;const i=[];let r=0;for(;null!==(o=s.exec(e));)o.index>r&&i.push(e.substring(r,o.index).trim()),i.push(o[0].trim()),r=s.lastIndex;r=2){const e=i[0].trim();let t=i.slice(1).join(\" / \").trim();const n=a(t);if(n.nextDe){l.push(e+s),c.push(n.enPart+s);const t=n.nextDe+s;l.push(t),c.push(t)}else{const n=t+s,a=e.includes(s.trim())?e:e+s;l.push(a),c.push(n)}}else{const e=a(n);e.nextDe?(c.push(e.enPart+s),l.push(e.nextDe+s)):(l.push(n+s),c.push(n+s))}}let d=l.join(\"\\n\u2022 \");l.length>0&&!d.startsWith(\"\u2022 \")&&(d=\"\u2022 \"+d);let m=c.join(\"\\n\u2022 \");c.length>0&&!m.startsWith(\"\u2022 \")&&(m=\"\u2022 \"+m);return{de:d,en:m,raw:t}}(e);return\"en\"===p?t.en||t.raw:t.de||t.raw}(a.description))}

`;const D=o.querySelector(\".btn-order\");D&&D.addEventListener(\"click\",t=>{t.stopPropagation();const a=t.currentTarget;a.disabled=!0,a.classList.add(\"loading\"),async function(t,a,s,o,i){if(d)try{const r=await fetch(`${e}/auth/user/`,{headers:f(d)});if(!r.ok)return void F(\"Fehler: Benutzerdaten konnten nicht geladen werden\",\"error\");const l=await r.json(),c=(new Date).toISOString(),m={uuid:crypto.randomUUID(),created:c,updated:c,order_type:7,items:[{article:a,course_group:null,modifiers:[],uuid:crypto.randomUUID(),name:s,description:i||\"\",price:String(parseFloat(o)),amount:1,vat:\"10.00\",comment:\"\"}],table:null,total:parseFloat(o),tip:0,currency:\"EUR\",venue:n,states:[],order_state:1,date:`${t}T10:30:00Z`,payment_method:\"payroll\",customer:{first_name:l.first_name,last_name:l.last_name,email:l.email,newsletter:!1},preorder:!0,delivery_fee:0,cash_box_table_name:null,take_away:!1},u=await fetch(`${e}/user/orders/`,{method:\"POST\",headers:f(d),body:JSON.stringify(m)});if(u.ok||201===u.status)F(`Bestellt: ${s}`,\"success\"),b=null,await y();else{const e=await u.json();F(`Fehler: ${e.detail||e.non_field_errors?.[0]||\"Bestellung fehlgeschlagen\"}`,\"error\")}}catch(e){console.error(\"Order error:\",e),F(\"Netzwerkfehler bei Bestellung\",\"error\")}}(a.dataset.date,parseInt(a.dataset.article),a.dataset.name,parseFloat(a.dataset.price),a.dataset.desc||\"\").finally(()=>{a.disabled=!1,a.classList.remove(\"loading\")})});const C=o.querySelector(\".btn-cancel\");C&&C.addEventListener(\"click\",t=>{t.stopPropagation();const n=t.currentTarget;n.disabled=!0,async function(t,n,a){if(!d)return;const s=`${t}_${n}`,o=u.get(s);if(!o||0===o.length)return;const i=o[o.length-1];try{const t=await fetch(`${e}/user/orders/${i}/cancel/`,{method:\"PATCH\",headers:f(d),body:JSON.stringify({})});t.ok?(F(`Storniert: ${a}`,\"success\"),b=null,await y()):F(`Fehler: ${(await t.json()).detail||\"Stornierung fehlgeschlagen\"}`,\"error\")}catch(e){console.error(\"Cancel error:\",e),F(\"Netzwerkfehler bei Stornierung\",\"error\")}}(n.dataset.date,parseInt(n.dataset.article),n.dataset.name).finally(()=>{n.disabled=!1})});const O=o.querySelector(\".btn-flag\");O&&O.addEventListener(\"click\",e=>{e.stopPropagation();const t=e.currentTarget;E(t.dataset.date,parseInt(t.dataset.article),t.dataset.name,t.dataset.cutoff)}),A.appendChild(o)}),a.appendChild(A),a}(t);a&&v.appendChild(a)}),t.appendChild(v),setTimeout(()=>function(e){const t=e.querySelectorAll(\".menu-card\");if(0===t.length)return;let n=0;t.forEach(e=>{n=Math.max(n,e.querySelectorAll(\".menu-item\").length)});for(let e=0;e{const s=t.querySelectorAll(\".menu-item\");s[e]&&(s[e].style.height=\"auto\",n=Math.max(n,s[e].offsetHeight),a.push(s[e]))}),a.forEach(e=>{e.style.height=`${n}px`})}}(v),0)}function H(e,t){if(!e||!t)return!1;const n=e.replace(/^v/,\"\").split(\".\").map(Number),a=t.replace(/^v/,\"\").split(\".\").map(Number);for(let e=0;e(a[e]||0))return!0;if((n[e]||0)<(a[e]||0))return!1}return!1}async function K(e){const t=e?`${s}/tags?per_page=20`:`${s}/releases?per_page=20`,n=await fetch(t,{headers:{Accept:\"application/vnd.github.v3+json\"}});if(!n.ok){if(403===n.status)throw new Error(\"API Rate Limit erreicht (403). Bitte sp\u00e4ter erneut versuchen.\");throw new Error(`GitHub API ${n.status}`)}return(await n.json()).map(t=>{const n=e?t.name:t.tag_name;return{tag:n,name:e?n:t.name||n,url:`${o}/${n}/dist/install.html`,body:t.body||\"\"}})}async function Q(){const e=\"v1.6.10\",t=\"true\"===localStorage.getItem(\"kantine_dev_mode\");try{const n=await K(t);if(!n.length)return;localStorage.setItem(\"kantine_version_cache\",JSON.stringify({timestamp:Date.now(),devMode:t,versions:n}));const a=n[0].tag;if(console.log(`[Kantine] Version Check: Local [${e}] vs Latest [${a}] (${t?\"dev\":\"stable\"})`),!H(a,e))return;console.log(`[Kantine] Update verf\u00fcgbar: ${a}`);const s=document.querySelector(\".header-left h1\");if(s&&!s.querySelector(\".update-icon\")){const e=document.createElement(\"a\");e.className=\"update-icon\",e.href=n[0].url,e.target=\"_blank\",e.innerHTML=\"\ud83c\udd95\",e.title=`Update: ${a} \u2014 Klick zum Installieren`,e.style.cssText=\"margin-left:8px;font-size:1em;text-decoration:none;cursor:pointer;vertical-align:middle;\",s.appendChild(e)}}catch(e){console.warn(\"[Kantine] Version check failed:\",e)}}function X(){if(!d||!m)return void U();const e=new Date,t=e.getDay();if(0===t||6===t)return void U();const n=e.toISOString().split(\"T\")[0];let a=!1;for(const e of u.keys())if(e.startsWith(n)){a=!0;break}if(a)return void U();const s=new Date;s.setHours(10,0,0,0);const o=s-e;if(o<=0)return void U();const i=Math.floor(o/36e5),r=Math.floor(o%36e5/6e4),l=document.querySelector(\".header-center-wrapper\");if(!l)return;let c=document.getElementById(\"order-countdown\");if(c||(c=document.createElement(\"div\"),c.id=\"order-countdown\",l.insertBefore(c,l.firstChild)),c.innerHTML=`Bestellschluss: ${i}h ${r}m`,o<36e5){c.classList.add(\"urgent\");const e=`kantine_notified_${n}`;localStorage.getItem(e)||(\"granted\"===Notification.permission?new Notification(\"Kantine: Bestellschluss naht!\",{body:\"Du hast heute noch nichts bestellt. Nur noch 1 Stunde!\",icon:\"\u23f3\"}):\"default\"===Notification.permission&&Notification.requestPermission(),localStorage.setItem(e,\"true\"))}else c.classList.remove(\"urgent\")}function U(){const e=document.getElementById(\"order-countdown\");e&&e.remove()}function G(e){const t=new Date(Date.UTC(e.getFullYear(),e.getMonth(),e.getDate())),n=t.getUTCDay()||7;t.setUTCDate(t.getUTCDate()+4-n);const a=new Date(Date.UTC(t.getUTCFullYear(),0,1));return Math.ceil(((t-a)/864e5+1)/7)}function J(e){const t=new Date(e.getTime());return t.setDate(t.getDate()+3-(t.getDay()+6)%7),t.getFullYear()}function W(e){const t=document.createElement(\"div\");return t.textContent=e||\"\",t.innerHTML}setInterval(X,6e4),setTimeout(X,1e3);const P=[\"apfel\",\"achtung\",\"aubergine\",\"auflauf\",\"beere\",\"blumenkohl\",\"bohne\",\"braten\",\"brokkoli\",\"brot\",\"brust\",\"br\u00f6tchen\",\"butter\",\"chili\",\"dessert\",\"dip\",\"eier\",\"eintopf\",\"eis\",\"erbse\",\"erdbeer\",\"essig\",\"filet\",\"fisch\",\"fisole\",\"fleckerl\",\"fleisch\",\"fl\u00fcgel\",\"frucht\",\"f\u00fcr\",\"gebraten\",\"gem\u00fcse\",\"gew\u00fcrz\",\"gratin\",\"grie\u00df\",\"gulasch\",\"gurke\",\"himbeer\",\"honig\",\"huhn\",\"h\u00e4hnchen\",\"jambalaya\",\"joghurt\",\"karotte\",\"kartoffel\",\"keule\",\"kirsch\",\"knacker\",\"knoblauch\",\"kn\u00f6del\",\"kompott\",\"kraut\",\"kr\u00e4uter\",\"kuchen\",\"k\u00e4se\",\"k\u00fcrbis\",\"lauch\",\"mandel\",\"milch\",\"mild\",\"mit\",\"mohn\",\"most\",\"m\u00f6hre\",\"natur\",\"nockerl\",\"nudel\",\"nuss\",\"nu\u00df\",\"obst\",\"oder\",\"olive\",\"paprika\",\"pfanne\",\"pfannkuchen\",\"pfeffer\",\"pikant\",\"pilz\",\"plunder\",\"p\u00fcree\",\"ragout\",\"rahm\",\"reis\",\"rind\",\"sahne\",\"salami\",\"salat\",\"salz\",\"sauer\",\"scharf\",\"schinken\",\"schnitte\",\"schnitzel\",\"schoko\",\"schupf\",\"schwein\",\"sellerie\",\"senf\",\"sosse\",\"so\u00dfe\",\"spargel\",\"sp\u00e4tzle\",\"speck\",\"spie\u00df\",\"spinat\",\"steak\",\"suppe\",\"s\u00fc\u00df\",\"tofu\",\"tomate\",\"topfen\",\"torte\",\"tr\u00fcffel\",\"und\",\"vanille\",\"vogerl\",\"vom\",\"wien\",\"wurst\",\"zucchini\",\"zum\",\"zur\",\"zwiebel\",\"\u00f6l\"],V=[\"almond\",\"and\",\"apple\",\"asparagus\",\"bacon\",\"baked\",\"ball\",\"bean\",\"beef\",\"berry\",\"bread\",\"breast\",\"broccoli\",\"bun\",\"butter\",\"cabbage\",\"cake\",\"caper\",\"carrot\",\"casserole\",\"cauliflower\",\"celery\",\"cheese\",\"cherry\",\"chicken\",\"chili\",\"choco\",\"chocolate\",\"cider\",\"cilantro\",\"coffee\",\"compote\",\"cream\",\"cucumber\",\"curd\",\"danish\",\"dessert\",\"dip\",\"dumpling\",\"egg\",\"eggplant\",\"filet\",\"fish\",\"for\",\"fried\",\"from\",\"fruit\",\"garlic\",\"goulash\",\"gratin\",\"ham\",\"herb\",\"honey\",\"hot\",\"ice\",\"jambalaya\",\"leek\",\"leg\",\"mash\",\"meat\",\"mexican\",\"mild\",\"milk\",\"mint\",\"mushroom\",\"mustard\",\"noodle\",\"nut\",\"oat\",\"oil\",\"olive\",\"onion\",\"or\",\"oven\",\"pan\",\"pancake\",\"pea\",\"pepper\",\"plain\",\"plate\",\"poppy\",\"pork\",\"potato\",\"pumpkin\",\"radish\",\"ragout\",\"raspberry\",\"rice\",\"roast\",\"roll\",\"salad\",\"salami\",\"salt\",\"sauce\",\"sausage\",\"shrimp\",\"skewer\",\"slice\",\"soup\",\"sour\",\"spice\",\"spicy\",\"spinach\",\"steak\",\"stew\",\"strawberr\",\"strawberry\",\"strudel\",\"sweet\",\"tart\",\"thyme\",\"to\",\"tofu\",\"tomat\",\"tomato\",\"truffle\",\"trukey\",\"turkey\",\"vanilla\",\"vegan\",\"vegetable\",\"vinegar\",\"wedge\",\"wing\",\"with\",\"wok\",\"yogurt\",\"zucchini\"];!function(){document.title=\"Kantine Weekly Menu\",document.querySelectorAll&&document.querySelectorAll('link[rel*=\"icon\"]').forEach(e=>e.remove());const e=document.createElement(\"link\");if(e.rel=\"icon\",e.type=\"image/png\",e.href=\"data:image/png;base64,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\",document.head.appendChild(e),!document.querySelector('link[href*=\"fonts.googleapis.com/css2?family=Inter\"]')){const e=document.createElement(\"link\");e.rel=\"stylesheet\",e.href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap\",document.head.appendChild(e)}if(!document.querySelector('link[href*=\"Material+Icons+Round\"]')){const e=document.createElement(\"link\");e.rel=\"stylesheet\",e.href=\"https://fonts.googleapis.com/icon?family=Material+Icons+Round\",document.head.appendChild(e)}document.body.innerHTML=`\\n
\\n
\\n
\\n
\\n \"Logo\"\\n
\\n

Kantinen \u00dcbersicht v1.6.10

\\n
\\n
\\n
\\n \\n \\n
\\n \\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n \\n \\n
\\n person\\n \\n \\n
\\n
\\n
\\n
\\n\\n
\\n
\\n
\\n

Login

\\n \\n
\\n
\\n
\\n \\n \\n Deine offizielle Knapp Mitarbeiternummer.\\n
\\n
\\n \\n \\n Das Passwort f\u00fcr deinen Bessa Account.\\n
\\n
\\n
\\n \\n
\\n
\\n
\\n
\\n\\n
\\n
\\n
\\n

Men\u00fcdaten aktualisieren

\\n
\\n
\\n
\\n
\\n
\\n
\\n
0%
\\n
\\n

Initialisierung...

\\n
\\n
\\n
\\n\\n
\\n
\\n
\\n

Meine Highlights

\\n \\n
\\n
\\n

\\n Markiere Men\u00fcs automatisch, wenn sie diese Schlagw\u00f6rter enthalten.\\n

\\n
\\n \\n \\n
\\n
\\n
\\n
\\n
\\n\\n
\\n
\\n
\\n

Bestellhistorie

\\n \\n
\\n
\\n
\\n

Lade Historie...

\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n \\x3c!-- Dynamically populated --\\x3e\\n
\\n
\\n
\\n
\\n\\n
\\n
\\n
\\n

\ud83d\udce6 Versionen

\\n \\n
\\n
\\n
\\n Aktuell: v1.6.10\\n
\\n
\\n \\n
\\n
\\n

Lade Versionen...

\\n
\\n
\\n \\n bug_report Fehler melden\\n \\n \\n lightbulb Feature vorschlagen\\n \\n \\n
\\n
\\n
\\n
\\n\\n
\\n
\\n update\\n Gerade aktualisiert\\n
\\n
\\n
\\n

Lade Men\u00fcdaten...

\\n
\\n
\\n
\\n\\n
\\n

Jetzt Bessa Einfach! • Knapp-Kantine Wrapper • ${(new Date).getFullYear()} by Kaufi \ud83d\ude03\ud83d\udc4d mit Hilfe von KI \ud83e\udd16

\\n
\\n
`}(),function(){const n=document.getElementById(\"btn-this-week\"),a=document.getElementById(\"btn-next-week\"),s=document.getElementById(\"btn-refresh\"),o=document.getElementById(\"theme-toggle\"),i=document.getElementById(\"btn-login-open\"),r=document.getElementById(\"btn-login-close\"),l=document.getElementById(\"btn-logout\"),g=document.getElementById(\"login-form\"),k=document.getElementById(\"login-modal\"),A=document.getElementById(\"btn-highlights\"),E=document.getElementById(\"highlights-modal\"),x=document.getElementById(\"btn-highlights-close\"),D=document.getElementById(\"btn-add-tag\"),C=document.getElementById(\"tag-input\"),O=document.getElementById(\"btn-history\"),M=document.getElementById(\"history-modal\"),T=document.getElementById(\"btn-history-close\");document.querySelectorAll(\".lang-btn\").forEach(e=>{e.addEventListener(\"click\",()=>{p=e.dataset.lang,localStorage.setItem(\"kantine_lang\",p),document.querySelectorAll(\".lang-btn\").forEach(e=>e.classList.remove(\"active\")),e.classList.add(\"active\"),j()})}),A&&A.addEventListener(\"click\",()=>{E.classList.remove(\"hidden\")}),x&&x.addEventListener(\"click\",()=>{E.classList.add(\"hidden\")}),O.addEventListener(\"click\",()=>{d?(M.classList.remove(\"hidden\"),async function(){const t=document.getElementById(\"history-loading\"),n=document.getElementById(\"history-content\"),a=document.getElementById(\"history-progress-fill\"),s=document.getElementById(\"history-progress-text\");let o=[];if(b)o=b;else{const e=localStorage.getItem(\"kantine_history_cache\");if(e)try{o=JSON.parse(e),b=o}catch(e){console.warn(\"History cache parse error\",e)}}o.length>0&&w(o);if(!d)return;0===o.length&&(n.innerHTML=\"\",t.classList.remove(\"hidden\"));a.style.width=\"0%\",s.textContent=o.length>0?\"Suche nach neuen Bestellungen...\":\"Lade Bestellhistorie...\",o.length>0&&t.classList.remove(\"hidden\");let i=o.length>0?`${e}/user/orders/?venue=591&ordering=-created&limit=5`:`${e}/user/orders/?venue=591&ordering=-created&limit=50`,r=[],l=0,c=0===o.length,m=!1;try{for(;i&&!m;){const e=await fetch(i,{headers:f(d)});if(!e.ok)throw new Error(`Fetch failed: ${e.status}`);const t=await e.json();t.count&&0===l&&(l=t.count);const n=t.results||[];for(const e of n){const t=o.findIndex(t=>t.id===e.id);if(!c&&-1!==t){const n=o[t];if(n.updated===e.updated&&n.order_state===e.order_state){m=!0;break}}r.push(e)}if(!m&&c)if(l>0){const e=Math.round(r.length/l*100);a.style.width=`${e}%`,s.textContent=`Lade Bestellung ${r.length} von ${l}...`}else s.textContent=`Lade Bestellung ${r.length}...`;else m||(s.textContent=`${r.length} neue/ge\u00e4nderte Bestellungen gefunden...`);i=m?null:t.next}if(r.length>0){const e=new Map(o.map(e=>[e.id,e]));for(const t of r)e.set(t.id,t);const t=Array.from(e.values());t.sort((e,t)=>new Date(t.created)-new Date(e.created)),b=t;try{localStorage.setItem(\"kantine_history_cache\",JSON.stringify(t))}catch(e){console.warn(\"History cache write error\",e)}w(b)}}catch(e){console.error(\"Error in history sync:\",e),0===o.length?n.innerHTML='

Fehler beim Laden der Historie.

':F(\"Hintergrund-Synchronisation fehlgeschlagen\",\"error\")}finally{t.classList.add(\"hidden\")}}()):k.classList.remove(\"hidden\")}),T.addEventListener(\"click\",()=>{M.classList.add(\"hidden\")}),window.addEventListener(\"click\",e=>{e.target===M&&M.classList.add(\"hidden\"),e.target===E&&E.classList.add(\"hidden\")});const q=document.querySelector(\".version-tag\"),$=document.getElementById(\"version-modal\"),z=document.getElementById(\"btn-version-close\");q&&q.addEventListener(\"click\",e=>{e.preventDefault(),e.stopPropagation(),function(){const e=document.getElementById(\"version-modal\"),t=document.getElementById(\"version-list-container\"),n=document.getElementById(\"dev-mode-toggle\"),a=\"v1.6.10\";if(!e)return;e.classList.remove(\"hidden\");const s=document.getElementById(\"version-current\");s&&(s.textContent=a);const o=\"true\"===localStorage.getItem(\"kantine_dev_mode\");async function i(e){const s=n.checked;function o(e){if(!e||!e.length)return void(t.innerHTML='

Keine Versionen gefunden.

');t.innerHTML='
    ';const n=t.querySelector(\".version-list\");e.forEach(e=>{const t=e.tag===a,s=H(e.tag,a),o=document.createElement(\"li\");o.className=\"version-item\"+(t?\" current\":\"\");let i=\"\";t?i='\u2713 Installiert':s&&(i='\u2b06 Neu!');let r=\"\";t||(r=`Installieren`),o.innerHTML=`\\n
    \\n ${e.tag}\\n ${i}\\n
    \\n ${r}\\n `,n.appendChild(o)})}t.innerHTML='

    Lade Versionen...

    ';try{const e=localStorage.getItem(\"kantine_version_cache\");let t=null;if(e)try{t=JSON.parse(e)}catch(e){}t&&t.devMode===s&&t.versions&&o(t.versions);const n=await K(s),a=JSON.stringify(n);a!==(t?JSON.stringify(t.versions):\"\")&&(localStorage.setItem(\"kantine_version_cache\",JSON.stringify({timestamp:Date.now(),devMode:s,versions:n})),o(n))}catch(e){t.innerHTML=`

    Fehler: ${e.message}

    `}}n.checked=o,i(!1),n.onchange=()=>{localStorage.setItem(\"kantine_dev_mode\",n.checked),localStorage.removeItem(\"kantine_version_cache\"),i(!0)}}()}),z&&z.addEventListener(\"click\",()=>{$.classList.add(\"hidden\")});const Q=document.getElementById(\"btn-clear-cache\");Q&&Q.addEventListener(\"click\",()=>{confirm(\"M\u00f6chtest du wirklich alle lokalen Daten (inkl. Login-Session, Cache und Einstellungen) l\u00f6schen? Die Seite wird danach neu geladen.\")&&(Object.keys(localStorage).forEach(e=>{e.startsWith(\"kantine_\")&&localStorage.removeItem(e)}),window.location.reload())}),window.addEventListener(\"click\",e=>{e.target===$&&$.classList.add(\"hidden\")}),D.addEventListener(\"click\",()=>{(function(e){if(e=e.trim().toLowerCase(),e&&!L.includes(e))return L.push(e),S(),!0;return!1})(C.value)&&(C.value=\"\",B())}),C.addEventListener(\"keypress\",e=>{\"Enter\"===e.key&&D.click()});const X=localStorage.getItem(\"theme\"),U=window.matchMedia(\"(prefers-color-scheme: dark)\").matches,G=o.querySelector(\".theme-icon\");\"dark\"===X||!X&&U?(document.documentElement.setAttribute(\"data-theme\",\"dark\"),G.textContent=\"dark_mode\"):(document.documentElement.setAttribute(\"data-theme\",\"light\"),G.textContent=\"light_mode\"),o.addEventListener(\"click\",()=>{const e=\"dark\"===document.documentElement.getAttribute(\"data-theme\")?\"light\":\"dark\";document.documentElement.setAttribute(\"data-theme\",e),localStorage.setItem(\"theme\",e),G.textContent=\"dark\"===e?\"dark_mode\":\"light_mode\"}),n.addEventListener(\"click\",()=>{\"this-week\"!==c&&(c=\"this-week\",n.classList.add(\"active\"),a.classList.remove(\"active\"),j())}),a.addEventListener(\"click\",()=>{a.classList.remove(\"new-week-available\"),\"next-week\"!==c&&(c=\"next-week\",a.classList.add(\"active\"),n.classList.remove(\"active\"),j())}),s.addEventListener(\"click\",()=>{d?N():k.classList.remove(\"hidden\")}),i.addEventListener(\"click\",()=>{k.classList.remove(\"hidden\"),document.getElementById(\"login-error\").classList.add(\"hidden\"),g.reset()}),r.addEventListener(\"click\",()=>{k.classList.add(\"hidden\")}),window.addEventListener(\"click\",e=>{e.target===k&&k.classList.add(\"hidden\")}),g.addEventListener(\"submit\",async n=>{n.preventDefault();const a=document.getElementById(\"employee-id\").value.trim(),s=document.getElementById(\"password\").value,o=document.getElementById(\"login-error\"),i=g.querySelector('button[type=\"submit\"]'),r=i.textContent;i.disabled=!0,i.textContent=\"Wird eingeloggt...\";try{const n=`knapp-${a}@bessa.app`,i=await fetch(`${e}/auth/login/`,{method:\"POST\",headers:f(t),body:JSON.stringify({email:n,password:s})}),r=await i.json();if(i.ok){d=r.key,m=a,localStorage.setItem(\"kantine_authToken\",r.key),localStorage.setItem(\"kantine_currentUser\",a);try{const t=await fetch(`${e}/auth/user/`,{headers:f(d)});if(t.ok){const e=await t.json();e.first_name&&localStorage.setItem(\"kantine_firstName\",e.first_name),e.last_name&&localStorage.setItem(\"kantine_lastName\",e.last_name)}}catch(e){console.error(\"Failed to fetch user info:\",e)}v(),k.classList.add(\"hidden\"),y(),g.reset(),I(),N()}else o.textContent=r.non_field_errors?.[0]||r.error||\"Login fehlgeschlagen\",o.classList.remove(\"hidden\")}catch(e){console.error(\"Login error:\",e),o.textContent=\"Ein Fehler ist aufgetreten\",o.classList.remove(\"hidden\")}finally{i.disabled=!1,i.textContent=r}}),l.addEventListener(\"click\",()=>{localStorage.removeItem(\"kantine_authToken\"),localStorage.removeItem(\"kantine_currentUser\"),localStorage.removeItem(\"kantine_firstName\"),localStorage.removeItem(\"kantine_lastName\"),d=null,m=null,u=new Map,h&&(clearInterval(h),h=null,console.log(\"Polling stopped\")),v(),j()})}(),v(),function(){const e=new Date,t=e.toISOString().split(\"T\")[0];let n=!1;for(const a of[...g]){const[s]=a.split(\"_\");let o=!1;if(s=t&&(o=!0)}o&&(g.delete(a),n=!0)}n&&k()}();(function(){try{const e=localStorage.getItem(D),t=localStorage.getItem(C);if(console.log(`[Cache] localStorage: key=${!!e} (${e?e.length:0} chars), ts=${t}`),e){i=JSON.parse(e),r=G(new Date),l=(new Date).getFullYear(),console.log(`[Cache] Parsed ${i.length} weeks:`,i.map(e=>`KW${e.weekNumber}/${e.year} (${(e.days||[]).length} days)`)),j(),z(),A(),t&&q(t);try{const e=new Set;i.forEach(t=>{(t.days||[]).forEach(t=>{(t.items||[]).forEach(t=>{let n=(t.description||\"\").replace(/\\s+/g,\" \").trim();n&&n.includes(\" / \")&&e.add(n)})})});const t=Array.from(e).join(\"\\n\\n\");console.log(\"=== GEFUNDENE MEN\u00dc-TEXTE (\"+e.size+\") ===\"),console.log(t)}catch(e){}return console.log(\"Loaded menu from cache\"),!0}}catch(e){console.warn(\"Failed to load cached menu:\",e)}return!1})()?(document.getElementById(\"loading\").classList.add(\"hidden\"),!function(){const e=localStorage.getItem(C);if(!e)return console.log(\"[Cache] No timestamp found\"),!1;const t=Date.now()-new Date(e).getTime(),n=Math.round(t/6e4);if(t>36e5)return console.log(`[Cache] Stale: ${n}min old (max 60)`),!1;const a=G(new Date),s=J(new Date),o=i.some(e=>e.weekNumber===a&&e.year===s&&e.days&&e.days.length>0);return console.log(`[Cache] Age: ${n}min, looking for KW${a}/${s}, found: ${o}`),o}()?(console.log(\"Cache stale or incomplete \u2013 refreshing from API\"),N()):console.log(\"Cache fresh & complete \u2013 skipping API refresh\")):N(),d&&I(),Q(),setInterval(Q,36e5),console.log(\"Kantine Wrapper loaded \u2705\")}();\n"; +sc.textContent="function showErrorModal(e,t,n,a){const s=\"error-modal\";let o=document.getElementById(s);o&&o.remove(),o=document.createElement(\"div\"),o.id=s,o.className=\"modal hidden\",o.innerHTML=`\\n
    \\n
    \\n

    \\n signal_wifi_off\\n ${e}\\n

    \\n
    \\n
    \\n

    ${t}

    \\n
    \\n \\n
    \\n
    \\n
    \\n `,document.body.appendChild(o),document.getElementById(\"btn-error-redirect\").addEventListener(\"click\",()=>{window.location.href=a}),requestAnimationFrame(()=>{o.classList.remove(\"hidden\")})}!function(){\"use strict\";if(window.__KANTINE_LOADED)return;window.__KANTINE_LOADED=!0;const e=\"https://api.bessa.app/v1\",t=\"c3418725e95a9f90e3645cbc846b4d67c7c66131\",n=591,a=\"TauNeutrino/kantine-overview\",s=`https://api.github.com/repos/${a}`,o=`https://htmlpreview.github.io/?https://github.com/${a}/blob`;let i=[],r=G(new Date),l=(new Date).getFullYear(),c=\"this-week\",d=localStorage.getItem(\"kantine_authToken\"),m=localStorage.getItem(\"kantine_currentUser\"),u=new Map,g=new Set(JSON.parse(localStorage.getItem(\"kantine_flags\")||\"[]\")),h=null,p=localStorage.getItem(\"kantine_lang\")||\"de\";function f(e){return{Authorization:`Token ${e||t}`,Accept:\"application/json\",\"Content-Type\":\"application/json\",\"X-Client-Version\":\"v1.6.11\"}}function v(){if(!d)try{const e=localStorage.getItem(\"AkitaStores\");if(e){const t=JSON.parse(e);t.auth&&t.auth.token&&(console.log(\"Found existing Bessa session!\"),d=t.auth.token,localStorage.setItem(\"kantine_authToken\",d),t.auth.user&&(m=t.auth.user.id||\"unknown\",localStorage.setItem(\"kantine_currentUser\",m),t.auth.user.firstName&&localStorage.setItem(\"kantine_firstName\",t.auth.user.firstName),t.auth.user.lastName&&localStorage.setItem(\"kantine_lastName\",t.auth.user.lastName)))}}catch(e){console.warn(\"Failed to parse AkitaStores:\",e)}d=localStorage.getItem(\"kantine_authToken\"),m=localStorage.getItem(\"kantine_currentUser\");const e=localStorage.getItem(\"kantine_firstName\"),t=document.getElementById(\"btn-login-open\"),n=document.getElementById(\"user-info\"),a=document.getElementById(\"user-id-display\");d?(t.classList.add(\"hidden\"),n.classList.remove(\"hidden\"),a.textContent=e||(m?`User ${m}`:\"Angemeldet\"),y()):(t.classList.remove(\"hidden\"),n.classList.add(\"hidden\"),a.textContent=\"\"),j()}async function y(){if(d)try{const t=await fetch(`${e}/user/orders/?venue=591&ordering=-created&limit=50`,{headers:f(d)}),n=await t.json();if(t.ok){u=new Map;const e=n.results||[];for(const t of e){if(9===t.order_state)continue;const e=t.date.split(\"T\")[0];for(const n of t.items||[]){const a=`${e}_${n.article}`;u.has(a)||u.set(a,[]),u.get(a).push(t.id)}}console.log(`Fetched ${e.length} orders, mapped active ones.`),j(),z()}}catch(e){console.error(\"Error fetching orders:\",e)}}let b=null;function w(e){const t=document.getElementById(\"history-content\");if(!e||0===e.length)return void(t.innerHTML='

    Keine Bestellungen gefunden.

    ');const n={};e.forEach(e=>{const t=new Date(e.date),a=t.getFullYear(),s=t.getMonth(),o=`${a}-${s.toString().padStart(2,\"0\")}`,i=t.toLocaleString(\"de-AT\",{month:\"long\"}),r=G(t);n[a]||(n[a]={year:a,months:{}}),n[a].months[o]||(n[a].months[o]={name:i,year:a,monthIndex:s,count:0,total:0,weeks:{}}),n[a].months[o].weeks[r]||(n[a].months[o].weeks[r]={label:`KW ${r}`,items:[],count:0,total:0});(e.items||[]).forEach(t=>{const s=parseFloat(t.price||e.total||0);n[a].months[o].weeks[r].items.push({date:e.date,name:t.name||\"Men\u00fc\",price:s,state:e.order_state}),9!==e.order_state&&(n[a].months[o].weeks[r].count++,n[a].months[o].weeks[r].total+=s,n[a].months[o].count++,n[a].months[o].total+=s)})});const a=Object.keys(n).sort((e,t)=>t-e);let s=\"\";a.forEach(e=>{const t=n[e];s+=`
    \\n

    ${t.year}

    `;Object.keys(t.months).sort((e,t)=>t.localeCompare(e)).forEach(e=>{const n=t.months[e];s+=`
    \\n
    \\n
    \\n ${n.name}\\n
    \\n ${n.count} Bestellungen • \u20ac${n.total.toFixed(2)}\\n
    \\n
    \\n expand_more\\n
    \\n
    `;Object.keys(n.weeks).sort((e,t)=>parseInt(t)-parseInt(e)).forEach(e=>{const t=n.weeks[e];s+=`
    \\n
    \\n ${t.label}\\n ${t.count} Bestellungen • \u20ac${t.total.toFixed(2)}\\n
    `,t.items.forEach(e=>{const t=new Date(e.date).toLocaleDateString(\"de-AT\",{weekday:\"short\",day:\"2-digit\",month:\"2-digit\"});let n=\"\";n=9===e.state?'Storniert':8===e.state?'Abgeschlossen':'\u00dcbertragen',s+=`\\n
    \\n
    ${t}
    \\n
    \\n ${W(e.name)}\\n
    ${n}
    \\n
    \\n
    \u20ac${e.price.toFixed(2)}
    \\n
    `}),s+=\"
    \"}),s+=\"
    \"}),s+=\"
    \"}),t.innerHTML=s;t.querySelectorAll(\".history-month-header\").forEach(e=>{e.addEventListener(\"click\",()=>{const t=e.parentElement;t.classList.contains(\"open\")?(t.classList.remove(\"open\"),e.setAttribute(\"aria-expanded\",\"false\")):(t.classList.add(\"open\"),e.setAttribute(\"aria-expanded\",\"true\"))})})}function k(){localStorage.setItem(\"kantine_flags\",JSON.stringify([...g]))}function A(){const e=document.getElementById(\"alarm-bell\"),t=document.getElementById(\"alarm-bell-icon\");if(!e||!t)return;if(0===g.size)return e.classList.add(\"hidden\"),e.style.display=\"none\",t.style.color=\"var(--text-secondary)\",void(t.style.textShadow=\"none\");e.classList.remove(\"hidden\"),e.style.display=\"inline-flex\";let n=!1;for(const e of i)if(e.days){for(const t of e.days)if(t.items){for(const e of t.items)if(e.available&&g.has(e.id)){n=!0;break}if(n)break}if(n)break}let a=localStorage.getItem(\"kantine_last_checked\"),s=\"gerade eben\";a||(a=(new Date).toISOString(),localStorage.setItem(\"kantine_last_checked\",a));s=$(new Date(a)),e.title=`Zuletzt gepr\u00fcft: ${s}`,n?(t.style.color=\"#10b981\",t.style.textShadow=\"0 0 10px rgba(16, 185, 129, 0.4)\"):(t.style.color=\"#f59e0b\",t.style.textShadow=\"0 0 10px rgba(245, 158, 11, 0.4)\")}function E(n,a,s,o){const r=`${n}_${a}`;let l=!1;g.has(r)?(g.delete(r),F(`Flag entfernt f\u00fcr ${s}`,\"success\")):(g.add(r),l=!0,F(`Benachrichtigung aktiviert f\u00fcr ${s}`,\"success\"),\"default\"===Notification.permission&&Notification.requestPermission()),k(),A(),j(),l&&async function(){if(0===g.size)return;const n=d||t,a=new Set;for(const e of g){const[t]=e.split(\"_\");a.add(t)}let s=!1;for(const t of a)try{const a=await fetch(`${e}/venues/591/menu/7/${t}/`,{headers:f(n)});if(!a.ok)continue;const o=(await a.json()).results||[];let r=[];for(const e of o)e.items&&Array.isArray(e.items)&&(r=r.concat(e.items));for(let e of i){if(!e.days)continue;let n=e.days.find(e=>e.date===t);n&&(n.items=r.map(e=>{const n=!1===e.amount_tracking,a=parseInt(e.available_amount)>0;return{id:`${t}_${e.id}`,articleId:e.id,name:e.name||\"Unknown\",description:e.description||\"\",price:parseFloat(e.price)||0,available:n||a,availableAmount:parseInt(e.available_amount)||0,amountTracking:!1!==e.amount_tracking}}),s=!0)}}catch(e){console.error(\"Error refreshing flag date\",t,e)}s&&(O(),q((new Date).toISOString()),A(),j())}()}function I(){h||d&&(h=setInterval(()=>async function(){if(0===g.size||!d)return;console.log(`Polling ${g.size} flagged items...`);for(const t of g){const[n,a]=t.split(\"_\"),s=parseInt(a);try{const t=await fetch(`${e}/venues/591/menu/7/${n}/`,{headers:f(d)});if(!t.ok)continue;const a=(await t.json()).results||[];let o=null;for(const e of a)if(e.items&&(o=e.items.find(e=>e.id===s||e.article===s),o))break;if(o){if(!1===o.amount_tracking||parseInt(o.available_amount)>0){const e=o.name||\"Unbekannt\";F(`${e} ist jetzt verf\u00fcgbar!`,\"success\"),\"granted\"===Notification.permission&&new Notification(\"Kantine Wrapper\",{body:`${e} ist jetzt verf\u00fcgbar!`,icon:\"\ud83c\udf7d\ufe0f\"}),N()}}}catch(e){console.error(`Poll error for ${t}:`,e),await new Promise(e=>setTimeout(e,200))}}localStorage.setItem(\"kantine_last_checked\",(new Date).toISOString()),A()}(),3e5),console.log(\"Polling started (every 5 min)\"))}let L=JSON.parse(localStorage.getItem(\"kantine_highlightTags\")||\"[]\");function S(){localStorage.setItem(\"kantine_highlightTags\",JSON.stringify(L)),j(),z()}function B(){const e=document.getElementById(\"tags-list\");e.innerHTML=\"\",L.forEach(t=>{const n=document.createElement(\"span\");n.className=\"tag-badge\",n.innerHTML=`${t} ×`,e.appendChild(n)}),e.querySelectorAll(\".tag-remove\").forEach(e=>{e.addEventListener(\"click\",e=>{var t;t=e.target.dataset.tag,L=L.filter(e=>e!==t),S(),B()})})}function x(e){return e?(e=e.toLowerCase(),L.filter(t=>e.includes(t))):[]}const D=\"kantine_menuCache\",C=\"kantine_menuCacheTs\";function O(){try{localStorage.setItem(D,JSON.stringify(i)),localStorage.setItem(C,(new Date).toISOString())}catch(e){console.warn(\"Failed to cache menu data:\",e)}}async function N(){const n=document.getElementById(\"loading\"),a=document.getElementById(\"progress-modal\"),s=document.getElementById(\"progress-fill\"),o=document.getElementById(\"progress-percent\"),c=document.getElementById(\"progress-message\");n.classList.remove(\"hidden\");const m=d||t;try{a.classList.remove(\"hidden\"),c.textContent=\"Hole verf\u00fcgbare Daten...\",s.style.width=\"0%\",o.textContent=\"0%\";const t=await fetch(`${e}/venues/591/menu/dates/`,{headers:f(m)});if(!t.ok)throw new Error(`Failed to fetch dates: ${t.status}`);let n=(await t.json()).results||[];const d=new Date;d.setDate(d.getDate()-7);const u=d.toISOString().split(\"T\")[0];n=n.filter(e=>e.date>=u).sort((e,t)=>e.date.localeCompare(t.date)).slice(0,30);const g=n.length;c.textContent=`${g} Tage gefunden. Lade Details...`;const h=[];let p=0;for(const t of n){const n=t.date,a=Math.round((p+1)/g*100);s.style.width=`${a}%`,o.textContent=`${a}%`,c.textContent=`Lade Men\u00fc f\u00fcr ${n}...`;try{const a=await fetch(`${e}/venues/591/menu/7/${n}/`,{headers:f(m)});if(a.ok){const e=await a.json();0===p&&console.log(\"[Kantine Debug] Raw API response for\",n,\":\",JSON.stringify(e).substring(0,2e3));const s=e.results||[];let o=[];for(const e of s)e.items&&Array.isArray(e.items)&&(o=o.concat(e.items));o.length>0&&(0===p&&(console.log(\"[Kantine Debug] First item keys:\",Object.keys(o[0])),console.log(\"[Kantine Debug] First item:\",JSON.stringify(o[0]).substring(0,500))),h.push({date:n,menu_items:o,orders:t.orders||[]}))}}catch(e){console.error(`Failed to fetch details for ${n}:`,e)}p++,await new Promise(e=>setTimeout(e,100))}const y=new Map;i&&i.length>0&&i.forEach(e=>{const t=`${e.year}-${e.weekNumber}`;try{y.set(t,{year:e.year,weekNumber:e.weekNumber,days:e.days?e.days.map(e=>({...e,items:e.items?[...e.items]:[]})):[]})}catch(e){console.warn(\"Error hydrating week:\",e)}});for(const e of h){const t=new Date(e.date),n=G(t),a=J(t),s=`${a}-${n}`;y.has(s)||y.set(s,{year:a,weekNumber:n,days:[]});const o=y.get(s),i=t.toLocaleDateString(\"en-US\",{weekday:\"long\"}),r=new Date(e.date);r.setHours(10,0,0,0);const l={date:e.date,weekday:i,orderCutoff:r.toISOString(),items:e.menu_items.map(t=>{const n=!1===t.amount_tracking,a=parseInt(t.available_amount)>0;return{id:`${e.date}_${t.id}`,articleId:t.id,name:t.name||\"Unknown\",description:t.description||\"\",price:parseFloat(t.price)||0,available:n||a,availableAmount:parseInt(t.available_amount)||0,amountTracking:!1!==t.amount_tracking}})},c=o.days.findIndex(t=>t.date===e.date);c>=0?o.days[c]=l:o.days.push(l)}i=Array.from(y.values()).sort((e,t)=>e.year!==t.year?e.year-t.year:e.weekNumber-t.weekNumber),i.forEach(e=>{e.days&&e.days.sort((e,t)=>e.date.localeCompare(t.date))}),O(),q((new Date).toISOString()),r=G(new Date),l=(new Date).getFullYear(),v(),j(),z(),A(),c.textContent=\"Fertig!\",setTimeout(()=>a.classList.add(\"hidden\"),500)}catch(e){console.error(\"Error fetching menu:\",e),a.classList.add(\"hidden\"),showErrorModal(\"Keine Verbindung\",`Die Men\u00fcdaten konnten nicht geladen werden. M\u00f6glicherweise besteht keine Verbindung zur API oder zur Bessa-Webseite.

    ${e.message}`,\"Zur Original-Seite\",\"https://web.bessa.app/knapp-kantine\")}finally{n.classList.add(\"hidden\")}}let M=null,T=null;function q(e){const t=document.getElementById(\"last-updated-subtitle\");if(e){M=e,localStorage.setItem(\"kantine_last_updated\",e),localStorage.setItem(\"kantine_last_checked\",e);try{const n=new Date(e),a=n.toLocaleTimeString(\"de-DE\",{hour:\"2-digit\",minute:\"2-digit\"}),s=n.toLocaleDateString(\"de-DE\",{day:\"2-digit\",month:\"2-digit\"}),o=$(n);t.textContent=`Aktualisiert: ${s} ${a} (${o})`}catch(e){t.textContent=\"\"}T||(T=setInterval(()=>{M&&(q(M),A())},6e4))}}function $(e){const t=Date.now()-e.getTime(),n=Math.floor(t/6e4);if(n<1)return\"gerade eben\";if(1===n)return\"vor 1 min.\";if(n<60)return`vor ${n} min.`;const a=Math.floor(n/60);return 1===a?\"vor 1 Std.\":`vor ${a} Std.`}function F(e,t=\"info\"){let n=document.getElementById(\"toast-container\");n||(n=document.createElement(\"div\"),n.id=\"toast-container\",document.body.appendChild(n));const a=document.createElement(\"div\");a.className=`toast toast-${t}`;const s=\"success\"===t?\"check_circle\":\"error\"===t?\"error\":\"info\";a.innerHTML=`${s}${e}`,n.appendChild(a),requestAnimationFrame(()=>a.classList.add(\"show\")),setTimeout(()=>{a.classList.remove(\"show\"),setTimeout(()=>a.remove(),300)},3e3)}function z(){const e=document.getElementById(\"btn-next-week\");let t=r+1,n=l;t>52&&(t=1,n++);const a=i.find(e=>e.weekNumber===t&&e.year===n);let s=0,o=0,c=0,d=0;a&&a.days&&a.days.forEach(e=>{if(e.items&&e.items.length>0){s++;const t=e.items.some(e=>e.available);t&&o++;let n=!1;e.items.forEach(t=>{const a=t.articleId||parseInt(t.id.split(\"_\")[1]),s=`${e.date}_${a}`;u.has(s)&&u.get(s).length>0&&(n=!0)}),n&&c++,t&&!n&&d++}});let m=e.querySelector(\".nav-badge\");if(s>0){m||(m=document.createElement(\"span\"),m.className=\"nav-badge\",e.appendChild(m)),m.title=`${c} bestellt / ${o} bestellbar / ${s} gesamt`,m.innerHTML=`${c}/${o}/${s}`,m.classList.remove(\"badge-violet\",\"badge-green\",\"badge-red\",\"badge-blue\"),c>0&&0===d?m.classList.add(\"badge-violet\"):d>0?m.classList.add(\"badge-green\"):0===o?m.classList.add(\"badge-red\"):m.classList.add(\"badge-blue\");let i=0;if(a&&a.days&&a.days.forEach(e=>{e.items.forEach(e=>{const t=x(e.name),n=x(e.description);(t.length>0||n.length>0)&&i++})}),i>0&&(m.innerHTML+=`(${i})`,m.title+=` \u2022 ${i} Highlights gefunden`,m.classList.add(\"has-highlights\")),0===c){e.classList.add(\"new-week-available\");const a=`kantine_notified_nextweek_${n}_${t}`;localStorage.getItem(a)||(localStorage.setItem(a,\"true\"),F(\"Neue Men\u00fcdaten f\u00fcr n\u00e4chste Woche verf\u00fcgbar!\",\"info\"))}else e.classList.remove(\"new-week-available\")}else m&&m.remove()}function j(){const t=document.getElementById(\"menu-container\");if(!t)return;t.innerHTML=\"\";let a=r,s=l;\"next-week\"===c&&(a++,a>52&&(a=1,s++));const o=i.flatMap(e=>e.days||[]).filter(e=>{const t=new Date(e.date);return G(t)===a&&J(t)===s});if(0===o.length)return t.innerHTML=`\\n
    \\n

    Keine Men\u00fcdaten f\u00fcr KW ${a} (${s}) verf\u00fcgbar.

    \\n Versuchen Sie eine andere Woche oder schauen Sie sp\u00e4ter vorbei.\\n
    `,void document.getElementById(\"weekly-cost-display\").classList.add(\"hidden\");!function(e){let t=0;e&&e.length>0&&e.forEach(e=>{e.items&&e.items.forEach(n=>{const a=n.articleId||parseInt(n.id.split(\"_\")[1]),s=`${e.date}_${a}`,o=u.get(s)||[];o.length>0&&(t+=n.price*o.length)})});const n=document.getElementById(\"weekly-cost-display\");t>0?(n.innerHTML=`shopping_bag Gesamt: ${t.toFixed(2).replace(\".\",\",\")} \u20ac`,n.classList.remove(\"hidden\")):n.classList.add(\"hidden\")}(o);const m=document.getElementById(\"header-week-info\"),h=\"this-week\"===c?\"Diese Woche\":\"N\u00e4chste Woche\";m.innerHTML=`\\n
    ${h}
    \\n
    Week ${a} \u2022 ${s}
    `;const v=document.createElement(\"div\");v.className=\"days-grid\",o.sort((e,t)=>e.date.localeCompare(t.date));o.filter(e=>{const t=new Date(e.date).getDay();return 0!==t&&6!==t}).forEach(t=>{const a=function(t){if(!t.items||0===t.items.length)return null;const a=document.createElement(\"div\");a.className=\"menu-card\";const s=new Date,o=new Date(t.date);let i=!1;if(t.orderCutoff)i=s>=new Date(t.orderCutoff);else{const e=new Date;e.setHours(0,0,0,0);const n=new Date(t.date);n.setHours(0,0,0,0),i=n{const n=e.articleId||parseInt(e.id.split(\"_\")[1]),a=`${t.date}_${n}`,s=(u.get(a)||[]).length;if(s>0){const t=e.name.match(/([M][1-9][Ff]?)/);if(t){let e=t[1];s>1&&(e+=\"+\"),r.push(e)}}});const l=document.createElement(\"div\");l.className=\"card-header\";const c=o.toLocaleDateString(\"de-DE\",{day:\"2-digit\",month:\"2-digit\"}),m=r.map(e=>`${e}`).join(\"\");let h=\"\";const v=t.items&&t.items.some(e=>{const n=e.articleId||parseInt(e.id.split(\"_\")[1]),a=`${t.date}_${n}`;return u.has(a)&&u.get(a).length>0}),w=t.items&&t.items.some(e=>e.available);h=v?\"header-violet\":w&&!i?\"header-green\":\"header-red\";h&&l.classList.add(h);l.innerHTML=`\\n
    \\n ${k=t.weekday,{Monday:\"Montag\",Tuesday:\"Dienstag\",Wednesday:\"Mittwoch\",Thursday:\"Donnerstag\",Friday:\"Freitag\",Saturday:\"Samstag\",Sunday:\"Sonntag\"}[k]||k}\\n
    ${m}
    \\n
    \\n ${c}`,a.appendChild(l);var k;const A=document.createElement(\"div\");A.className=\"card-body\";const I=(new Date).toISOString().split(\"T\")[0],L=t.date===I,S=[...t.items].sort((e,n)=>{if(L){const a=e.articleId||parseInt(e.id.split(\"_\")[1]),s=n.articleId||parseInt(n.id.split(\"_\")[1]),o=u.has(`${t.date}_${a}`),i=u.has(`${t.date}_${s}`);if(o&&!i)return-1;if(!o&&i)return 1}return e.name.localeCompare(n.name)});return S.forEach(a=>{const o=document.createElement(\"div\");o.className=\"menu-item\";const r=a.articleId||parseInt(a.id.split(\"_\")[1]),l=`${t.date}_${r}`,c=(u.get(l)||[]).length;let m=\"\";m=a.available?a.amountTracking?`Verf\u00fcgbar (${a.availableAmount})`:'Verf\u00fcgbar':'Ausverkauft';let h=\"\";if(c>0){h=`check_circle Bestellt${c>1?`${c}`:\"\"}`,o.classList.add(\"ordered\"),new Date(t.date).toDateString()===s.toDateString()&&o.classList.add(\"today-ordered\")}const v=`${t.date}_${r}`,w=g.has(v);w&&o.classList.add(a.available?\"flagged-available\":\"flagged-sold-out\");const k=[...new Set([...x(a.name),...x(a.description)])];k.length>0&&o.classList.add(\"highlight-glow\");let I=\"\",L=\"\",S=\"\";if(d&&!i){const e=w?\"notifications_active\":\"notifications_none\",n=w?\"btn-flag active\":\"btn-flag\",s=w?\"Benachrichtigung deaktivieren\":\"Benachrichtigen wenn verf\u00fcgbar\";if(a.available&&!w||(S=``),a.available&&(I=c>0?``:``),c>0){const e=1===c?\"close\":\"remove\",n=1===c?\"Bestellung stornieren\":\"Eine Bestellung stornieren\";L=``}}let B=\"\";if(k.length>0){B=`
    ${k.map(e=>`star${W(e)}`).join(\"\")}
    `}o.innerHTML=`\\n
    \\n ${W(a.name)}\\n ${a.price.toFixed(2)} \u20ac\\n
    \\n
    \\n ${h}\\n ${L}\\n ${I}\\n ${S}\\n
    ${m}
    \\n
    \\n ${B}\\n

    ${W(function(e){if(\"all\"===p)return e||\"\";const t=function(e){if(!e)return{de:\"\",en:\"\",raw:\"\"};let t=e.replace(/(?:\\(|(?:\\/|\\s|^))([A-Z,]+)\\)\\s*(?=\\S)(?!\\s*\\/)/g,\"($1)\\n\u2022 \");t.startsWith(\"\u2022 \")||(t=\"\u2022 \"+t);function n(e){let t=0,n=0;return e.forEach(e=>{const a=e.toLowerCase().replace(/[^a-z\u00e4\u00f6\u00fc\u00df]/g,\"\");if(a){let s=0,o=0;P.includes(a)?s=a.length:P.forEach(e=>{a.includes(e)&&e.length>s&&(s=e.length)}),V.includes(a)?o=a.length:V.forEach(e=>{a.includes(e)&&e.length>o&&(o=e.length)}),s>0&&(t+=s/a.length),o>0&&(n+=o/a.length),/^[A-Z\u00c4\u00d6\u00dc]/.test(e)&&(t+=.5)}}),{de:t,en:n}}function a(e){const t=e.trim().split(/\\s+/);if(t.length<2)return{enPart:e,nextDe:\"\"};let a=-1,s=-9999;for(let e=1;er.de||r.en>0,g=l.de+d>l.en;u&&g&&m>s&&(s=m,a=e)}return-1!==a?{enPart:t.slice(0,a).join(\" \"),nextDe:t.slice(a).join(\" \")}:{enPart:e,nextDe:\"\"}}const s=/(.*?)(?:\\(|(?:\\/|\\s|^))([A-Z,]+)\\)\\s*(?!\\s*[/])/g;let o;const i=[];let r=0;for(;null!==(o=s.exec(e));)o.index>r&&i.push(e.substring(r,o.index).trim()),i.push(o[0].trim()),r=s.lastIndex;r=2){const e=i[0].trim();let t=i.slice(1).join(\" / \").trim();const n=a(t);if(n.nextDe){l.push(e+s),c.push(n.enPart+s);const t=n.nextDe+s;l.push(t),c.push(t)}else{const n=t+s,a=e.includes(s.trim())?e:e+s;l.push(a),c.push(n)}}else{const e=a(n);e.nextDe?(c.push(e.enPart+s),l.push(e.nextDe+s)):(l.push(n+s),c.push(n+s))}}let d=l.join(\"\\n\u2022 \");l.length>0&&!d.startsWith(\"\u2022 \")&&(d=\"\u2022 \"+d);let m=c.join(\"\\n\u2022 \");c.length>0&&!m.startsWith(\"\u2022 \")&&(m=\"\u2022 \"+m);return{de:d,en:m,raw:t}}(e);return\"en\"===p?t.en||t.raw:t.de||t.raw}(a.description))}

    `;const D=o.querySelector(\".btn-order\");D&&D.addEventListener(\"click\",t=>{t.stopPropagation();const a=t.currentTarget;a.disabled=!0,a.classList.add(\"loading\"),async function(t,a,s,o,i){if(d)try{const r=await fetch(`${e}/auth/user/`,{headers:f(d)});if(!r.ok)return void F(\"Fehler: Benutzerdaten konnten nicht geladen werden\",\"error\");const l=await r.json(),c=(new Date).toISOString(),m={uuid:crypto.randomUUID(),created:c,updated:c,order_type:7,items:[{article:a,course_group:null,modifiers:[],uuid:crypto.randomUUID(),name:s,description:i||\"\",price:String(parseFloat(o)),amount:1,vat:\"10.00\",comment:\"\"}],table:null,total:parseFloat(o),tip:0,currency:\"EUR\",venue:n,states:[],order_state:1,date:`${t}T10:30:00Z`,payment_method:\"payroll\",customer:{first_name:l.first_name,last_name:l.last_name,email:l.email,newsletter:!1},preorder:!0,delivery_fee:0,cash_box_table_name:null,take_away:!1},u=await fetch(`${e}/user/orders/`,{method:\"POST\",headers:f(d),body:JSON.stringify(m)});if(u.ok||201===u.status)F(`Bestellt: ${s}`,\"success\"),b=null,await y();else{const e=await u.json();F(`Fehler: ${e.detail||e.non_field_errors?.[0]||\"Bestellung fehlgeschlagen\"}`,\"error\")}}catch(e){console.error(\"Order error:\",e),F(\"Netzwerkfehler bei Bestellung\",\"error\")}}(a.dataset.date,parseInt(a.dataset.article),a.dataset.name,parseFloat(a.dataset.price),a.dataset.desc||\"\").finally(()=>{a.disabled=!1,a.classList.remove(\"loading\")})});const C=o.querySelector(\".btn-cancel\");C&&C.addEventListener(\"click\",t=>{t.stopPropagation();const n=t.currentTarget;n.disabled=!0,async function(t,n,a){if(!d)return;const s=`${t}_${n}`,o=u.get(s);if(!o||0===o.length)return;const i=o[o.length-1];try{const t=await fetch(`${e}/user/orders/${i}/cancel/`,{method:\"PATCH\",headers:f(d),body:JSON.stringify({})});t.ok?(F(`Storniert: ${a}`,\"success\"),b=null,await y()):F(`Fehler: ${(await t.json()).detail||\"Stornierung fehlgeschlagen\"}`,\"error\")}catch(e){console.error(\"Cancel error:\",e),F(\"Netzwerkfehler bei Stornierung\",\"error\")}}(n.dataset.date,parseInt(n.dataset.article),n.dataset.name).finally(()=>{n.disabled=!1})});const O=o.querySelector(\".btn-flag\");O&&O.addEventListener(\"click\",e=>{e.stopPropagation();const t=e.currentTarget;E(t.dataset.date,parseInt(t.dataset.article),t.dataset.name,t.dataset.cutoff)}),A.appendChild(o)}),a.appendChild(A),a}(t);a&&v.appendChild(a)}),t.appendChild(v),setTimeout(()=>function(e){const t=e.querySelectorAll(\".menu-card\");if(0===t.length)return;let n=0;t.forEach(e=>{n=Math.max(n,e.querySelectorAll(\".menu-item\").length)});for(let e=0;e{const s=t.querySelectorAll(\".menu-item\");s[e]&&(s[e].style.height=\"auto\",n=Math.max(n,s[e].offsetHeight),a.push(s[e]))}),a.forEach(e=>{e.style.height=`${n}px`})}}(v),0)}function H(e,t){if(!e||!t)return!1;const n=e.replace(/^v/,\"\").split(\".\").map(Number),a=t.replace(/^v/,\"\").split(\".\").map(Number);for(let e=0;e(a[e]||0))return!0;if((n[e]||0)<(a[e]||0))return!1}return!1}async function K(e){const t=e?`${s}/tags?per_page=20`:`${s}/releases?per_page=20`,n=await fetch(t,{headers:{Accept:\"application/vnd.github.v3+json\"}});if(!n.ok){if(403===n.status)throw new Error(\"API Rate Limit erreicht (403). Bitte sp\u00e4ter erneut versuchen.\");throw new Error(`GitHub API ${n.status}`)}return(await n.json()).map(t=>{const n=e?t.name:t.tag_name;return{tag:n,name:e?n:t.name||n,url:`${o}/${n}/dist/install.html`,body:t.body||\"\"}})}async function Q(){const e=\"v1.6.11\",t=\"true\"===localStorage.getItem(\"kantine_dev_mode\");try{const n=await K(t);if(!n.length)return;localStorage.setItem(\"kantine_version_cache\",JSON.stringify({timestamp:Date.now(),devMode:t,versions:n}));const a=n[0].tag;if(console.log(`[Kantine] Version Check: Local [${e}] vs Latest [${a}] (${t?\"dev\":\"stable\"})`),!H(a,e))return;console.log(`[Kantine] Update verf\u00fcgbar: ${a}`);const s=document.querySelector(\".header-left h1\");if(s&&!s.querySelector(\".update-icon\")){const e=document.createElement(\"a\");e.className=\"update-icon\",e.href=n[0].url,e.target=\"_blank\",e.innerHTML=\"\ud83c\udd95\",e.title=`Update: ${a} \u2014 Klick zum Installieren`,e.style.cssText=\"margin-left:8px;font-size:1em;text-decoration:none;cursor:pointer;vertical-align:middle;\",s.appendChild(e)}}catch(e){console.warn(\"[Kantine] Version check failed:\",e)}}function X(){if(!d||!m)return void U();const e=new Date,t=e.getDay();if(0===t||6===t)return void U();const n=e.toISOString().split(\"T\")[0];let a=!1;for(const e of u.keys())if(e.startsWith(n)){a=!0;break}if(a)return void U();const s=new Date;s.setHours(10,0,0,0);const o=s-e;if(o<=0)return void U();const i=Math.floor(o/36e5),r=Math.floor(o%36e5/6e4),l=document.querySelector(\".header-center-wrapper\");if(!l)return;let c=document.getElementById(\"order-countdown\");if(c||(c=document.createElement(\"div\"),c.id=\"order-countdown\",l.insertBefore(c,l.firstChild)),c.innerHTML=`Bestellschluss: ${i}h ${r}m`,o<36e5){c.classList.add(\"urgent\");const e=`kantine_notified_${n}`;localStorage.getItem(e)||(\"granted\"===Notification.permission?new Notification(\"Kantine: Bestellschluss naht!\",{body:\"Du hast heute noch nichts bestellt. Nur noch 1 Stunde!\",icon:\"\u23f3\"}):\"default\"===Notification.permission&&Notification.requestPermission(),localStorage.setItem(e,\"true\"))}else c.classList.remove(\"urgent\")}function U(){const e=document.getElementById(\"order-countdown\");e&&e.remove()}function G(e){const t=new Date(Date.UTC(e.getFullYear(),e.getMonth(),e.getDate())),n=t.getUTCDay()||7;t.setUTCDate(t.getUTCDate()+4-n);const a=new Date(Date.UTC(t.getUTCFullYear(),0,1));return Math.ceil(((t-a)/864e5+1)/7)}function J(e){const t=new Date(e.getTime());return t.setDate(t.getDate()+3-(t.getDay()+6)%7),t.getFullYear()}function W(e){const t=document.createElement(\"div\");return t.textContent=e||\"\",t.innerHTML}setInterval(X,6e4),setTimeout(X,1e3);const P=[\"apfel\",\"achtung\",\"aubergine\",\"auflauf\",\"beere\",\"blumenkohl\",\"bohne\",\"braten\",\"brokkoli\",\"brot\",\"brust\",\"br\u00f6tchen\",\"butter\",\"chili\",\"dessert\",\"dip\",\"eier\",\"eintopf\",\"eis\",\"erbse\",\"erdbeer\",\"essig\",\"filet\",\"fisch\",\"fisole\",\"fleckerl\",\"fleisch\",\"fl\u00fcgel\",\"frucht\",\"f\u00fcr\",\"gebraten\",\"gem\u00fcse\",\"gew\u00fcrz\",\"gratin\",\"grie\u00df\",\"gulasch\",\"gurke\",\"himbeer\",\"honig\",\"huhn\",\"h\u00e4hnchen\",\"jambalaya\",\"joghurt\",\"karotte\",\"kartoffel\",\"keule\",\"kirsch\",\"knacker\",\"knoblauch\",\"kn\u00f6del\",\"kompott\",\"kraut\",\"kr\u00e4uter\",\"kuchen\",\"k\u00e4se\",\"k\u00fcrbis\",\"lauch\",\"mandel\",\"milch\",\"mild\",\"mit\",\"mohn\",\"most\",\"m\u00f6hre\",\"natur\",\"nockerl\",\"nudel\",\"nuss\",\"nu\u00df\",\"obst\",\"oder\",\"olive\",\"paprika\",\"pfanne\",\"pfannkuchen\",\"pfeffer\",\"pikant\",\"pilz\",\"plunder\",\"p\u00fcree\",\"ragout\",\"rahm\",\"reis\",\"rind\",\"sahne\",\"salami\",\"salat\",\"salz\",\"sauer\",\"scharf\",\"schinken\",\"schnitte\",\"schnitzel\",\"schoko\",\"schupf\",\"schwein\",\"sellerie\",\"senf\",\"sosse\",\"so\u00dfe\",\"spargel\",\"sp\u00e4tzle\",\"speck\",\"spie\u00df\",\"spinat\",\"steak\",\"suppe\",\"s\u00fc\u00df\",\"tofu\",\"tomate\",\"topfen\",\"torte\",\"tr\u00fcffel\",\"und\",\"vanille\",\"vogerl\",\"vom\",\"wien\",\"wurst\",\"zucchini\",\"zum\",\"zur\",\"zwiebel\",\"\u00f6l\"],V=[\"almond\",\"and\",\"apple\",\"asparagus\",\"bacon\",\"baked\",\"ball\",\"bean\",\"beef\",\"berry\",\"bread\",\"breast\",\"broccoli\",\"bun\",\"butter\",\"cabbage\",\"cake\",\"caper\",\"carrot\",\"casserole\",\"cauliflower\",\"celery\",\"cheese\",\"cherry\",\"chicken\",\"chili\",\"choco\",\"chocolate\",\"cider\",\"cilantro\",\"coffee\",\"compote\",\"cream\",\"cucumber\",\"curd\",\"danish\",\"dessert\",\"dip\",\"dumpling\",\"egg\",\"eggplant\",\"filet\",\"fish\",\"for\",\"fried\",\"from\",\"fruit\",\"garlic\",\"goulash\",\"gratin\",\"ham\",\"herb\",\"honey\",\"hot\",\"ice\",\"jambalaya\",\"leek\",\"leg\",\"mash\",\"meat\",\"mexican\",\"mild\",\"milk\",\"mint\",\"mushroom\",\"mustard\",\"noodle\",\"nut\",\"oat\",\"oil\",\"olive\",\"onion\",\"or\",\"oven\",\"pan\",\"pancake\",\"pea\",\"pepper\",\"plain\",\"plate\",\"poppy\",\"pork\",\"potato\",\"pumpkin\",\"radish\",\"ragout\",\"raspberry\",\"rice\",\"roast\",\"roll\",\"salad\",\"salami\",\"salt\",\"sauce\",\"sausage\",\"shrimp\",\"skewer\",\"slice\",\"soup\",\"sour\",\"spice\",\"spicy\",\"spinach\",\"steak\",\"stew\",\"strawberr\",\"strawberry\",\"strudel\",\"sweet\",\"tart\",\"thyme\",\"to\",\"tofu\",\"tomat\",\"tomato\",\"truffle\",\"trukey\",\"turkey\",\"vanilla\",\"vegan\",\"vegetable\",\"vinegar\",\"wedge\",\"wing\",\"with\",\"wok\",\"yogurt\",\"zucchini\"];!function(){document.title=\"Kantine Weekly Menu\",document.querySelectorAll&&document.querySelectorAll('link[rel*=\"icon\"]').forEach(e=>e.remove());const e=document.createElement(\"link\");if(e.rel=\"icon\",e.type=\"image/png\",e.href=\"data:image/png;base64,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\",document.head.appendChild(e),!document.querySelector('link[href*=\"fonts.googleapis.com/css2?family=Inter\"]')){const e=document.createElement(\"link\");e.rel=\"stylesheet\",e.href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap\",document.head.appendChild(e)}if(!document.querySelector('link[href*=\"Material+Icons+Round\"]')){const e=document.createElement(\"link\");e.rel=\"stylesheet\",e.href=\"https://fonts.googleapis.com/icon?family=Material+Icons+Round\",document.head.appendChild(e)}document.body.innerHTML=`\\n
    \\n
    \\n
    \\n
    \\n \"Logo\"\\n
    \\n

    Kantinen \u00dcbersicht v1.6.11

    \\n
    \\n
    \\n
    \\n \\n \\n
    \\n \\n
    \\n
    \\n
    \\n \\n \\n \\n
    \\n
    \\n
    \\n
    \\n
    \\n \\n \\n \\n \\n \\n
    \\n person\\n \\n \\n
    \\n
    \\n
    \\n
    \\n\\n
    \\n
    \\n
    \\n

    Login

    \\n \\n
    \\n
    \\n
    \\n \\n \\n Deine offizielle Knapp Mitarbeiternummer.\\n
    \\n
    \\n \\n \\n Das Passwort f\u00fcr deinen Bessa Account.\\n
    \\n
    \\n
    \\n \\n
    \\n
    \\n
    \\n
    \\n\\n
    \\n
    \\n
    \\n

    Men\u00fcdaten aktualisieren

    \\n
    \\n
    \\n
    \\n
    \\n
    \\n
    \\n
    0%
    \\n
    \\n

    Initialisierung...

    \\n
    \\n
    \\n
    \\n\\n
    \\n
    \\n
    \\n

    Meine Highlights

    \\n \\n
    \\n
    \\n

    \\n Markiere Men\u00fcs automatisch, wenn sie diese Schlagw\u00f6rter enthalten.\\n

    \\n
    \\n \\n \\n
    \\n
    \\n
    \\n
    \\n
    \\n\\n
    \\n
    \\n
    \\n

    Bestellhistorie

    \\n \\n
    \\n
    \\n
    \\n

    Lade Historie...

    \\n
    \\n
    \\n
    \\n
    \\n
    \\n
    \\n
    \\n \\x3c!-- Dynamically populated --\\x3e\\n
    \\n
    \\n
    \\n
    \\n\\n
    \\n
    \\n
    \\n

    \ud83d\udce6 Versionen

    \\n \\n
    \\n
    \\n
    \\n Aktuell: v1.6.11\\n
    \\n
    \\n \\n
    \\n
    \\n

    Lade Versionen...

    \\n
    \\n
    \\n \\n bug_report Fehler melden\\n \\n \\n lightbulb Feature vorschlagen\\n \\n \\n
    \\n
    \\n
    \\n
    \\n\\n
    \\n
    \\n update\\n Gerade aktualisiert\\n
    \\n
    \\n
    \\n

    Lade Men\u00fcdaten...

    \\n
    \\n
    \\n
    \\n\\n
    \\n

    Jetzt Bessa Einfach! • Knapp-Kantine Wrapper • ${(new Date).getFullYear()} by Kaufi \ud83d\ude03\ud83d\udc4d mit Hilfe von KI \ud83e\udd16

    \\n
    \\n
    `}(),function(){const n=document.getElementById(\"btn-this-week\"),a=document.getElementById(\"btn-next-week\"),s=document.getElementById(\"btn-refresh\"),o=document.getElementById(\"theme-toggle\"),i=document.getElementById(\"btn-login-open\"),r=document.getElementById(\"btn-login-close\"),l=document.getElementById(\"btn-logout\"),g=document.getElementById(\"login-form\"),k=document.getElementById(\"login-modal\"),A=document.getElementById(\"btn-highlights\"),E=document.getElementById(\"highlights-modal\"),x=document.getElementById(\"btn-highlights-close\"),D=document.getElementById(\"btn-add-tag\"),C=document.getElementById(\"tag-input\"),O=document.getElementById(\"btn-history\"),M=document.getElementById(\"history-modal\"),T=document.getElementById(\"btn-history-close\");document.querySelectorAll(\".lang-btn\").forEach(e=>{e.addEventListener(\"click\",()=>{p=e.dataset.lang,localStorage.setItem(\"kantine_lang\",p),document.querySelectorAll(\".lang-btn\").forEach(e=>e.classList.remove(\"active\")),e.classList.add(\"active\"),j()})}),A&&A.addEventListener(\"click\",()=>{E.classList.remove(\"hidden\")}),x&&x.addEventListener(\"click\",()=>{E.classList.add(\"hidden\")}),O.addEventListener(\"click\",()=>{d?(M.classList.remove(\"hidden\"),async function(){const t=document.getElementById(\"history-loading\"),n=document.getElementById(\"history-content\"),a=document.getElementById(\"history-progress-fill\"),s=document.getElementById(\"history-progress-text\");let o=[];if(b)o=b;else{const e=localStorage.getItem(\"kantine_history_cache\");if(e)try{o=JSON.parse(e),b=o}catch(e){console.warn(\"History cache parse error\",e)}}o.length>0&&w(o);if(!d)return;0===o.length&&(n.innerHTML=\"\",t.classList.remove(\"hidden\"));a.style.width=\"0%\",s.textContent=o.length>0?\"Suche nach neuen Bestellungen...\":\"Lade Bestellhistorie...\",o.length>0&&t.classList.remove(\"hidden\");let i=o.length>0?`${e}/user/orders/?venue=591&ordering=-created&limit=5`:`${e}/user/orders/?venue=591&ordering=-created&limit=50`,r=[],l=0,c=0===o.length,m=!1;try{for(;i&&!m;){const e=await fetch(i,{headers:f(d)});if(!e.ok)throw new Error(`Fetch failed: ${e.status}`);const t=await e.json();t.count&&0===l&&(l=t.count);const n=t.results||[];for(const e of n){const t=o.findIndex(t=>t.id===e.id);if(!c&&-1!==t){const n=o[t];if(n.updated===e.updated&&n.order_state===e.order_state){m=!0;break}}r.push(e)}if(!m&&c)if(l>0){const e=Math.round(r.length/l*100);a.style.width=`${e}%`,s.textContent=`Lade Bestellung ${r.length} von ${l}...`}else s.textContent=`Lade Bestellung ${r.length}...`;else m||(s.textContent=`${r.length} neue/ge\u00e4nderte Bestellungen gefunden...`);i=m?null:t.next}if(r.length>0){const e=new Map(o.map(e=>[e.id,e]));for(const t of r)e.set(t.id,t);const t=Array.from(e.values());t.sort((e,t)=>new Date(t.created)-new Date(e.created)),b=t;try{localStorage.setItem(\"kantine_history_cache\",JSON.stringify(t))}catch(e){console.warn(\"History cache write error\",e)}w(b)}}catch(e){console.error(\"Error in history sync:\",e),0===o.length?n.innerHTML='

    Fehler beim Laden der Historie.

    ':F(\"Hintergrund-Synchronisation fehlgeschlagen\",\"error\")}finally{t.classList.add(\"hidden\")}}()):k.classList.remove(\"hidden\")}),T.addEventListener(\"click\",()=>{M.classList.add(\"hidden\")}),window.addEventListener(\"click\",e=>{e.target===M&&M.classList.add(\"hidden\"),e.target===E&&E.classList.add(\"hidden\")});const q=document.querySelector(\".version-tag\"),$=document.getElementById(\"version-modal\"),z=document.getElementById(\"btn-version-close\");q&&q.addEventListener(\"click\",e=>{e.preventDefault(),e.stopPropagation(),function(){const e=document.getElementById(\"version-modal\"),t=document.getElementById(\"version-list-container\"),n=document.getElementById(\"dev-mode-toggle\"),a=\"v1.6.11\";if(!e)return;e.classList.remove(\"hidden\");const s=document.getElementById(\"version-current\");s&&(s.textContent=a);const o=\"true\"===localStorage.getItem(\"kantine_dev_mode\");async function i(e){const s=n.checked;function o(e){if(!e||!e.length)return void(t.innerHTML='

    Keine Versionen gefunden.

    ');t.innerHTML='
      ';const n=t.querySelector(\".version-list\");e.forEach(e=>{const t=e.tag===a,s=H(e.tag,a),o=document.createElement(\"li\");o.className=\"version-item\"+(t?\" current\":\"\");let i=\"\";t?i='\u2713 Installiert':s&&(i='\u2b06 Neu!');let r=\"\";t||(r=`Installieren`),o.innerHTML=`\\n
      \\n ${e.tag}\\n ${i}\\n
      \\n ${r}\\n `,n.appendChild(o)})}t.innerHTML='

      Lade Versionen...

      ';try{const e=localStorage.getItem(\"kantine_version_cache\");let t=null;if(e)try{t=JSON.parse(e)}catch(e){}t&&t.devMode===s&&t.versions&&o(t.versions);const n=await K(s),a=JSON.stringify(n);a!==(t?JSON.stringify(t.versions):\"\")&&(localStorage.setItem(\"kantine_version_cache\",JSON.stringify({timestamp:Date.now(),devMode:s,versions:n})),o(n))}catch(e){t.innerHTML=`

      Fehler: ${e.message}

      `}}n.checked=o,i(!1),n.onchange=()=>{localStorage.setItem(\"kantine_dev_mode\",n.checked),localStorage.removeItem(\"kantine_version_cache\"),i(!0)}}()}),z&&z.addEventListener(\"click\",()=>{$.classList.add(\"hidden\")});const Q=document.getElementById(\"btn-clear-cache\");Q&&Q.addEventListener(\"click\",()=>{confirm(\"M\u00f6chtest du wirklich alle lokalen Daten (inkl. Login-Session, Cache und Einstellungen) l\u00f6schen? Die Seite wird danach neu geladen.\")&&(Object.keys(localStorage).forEach(e=>{e.startsWith(\"kantine_\")&&localStorage.removeItem(e)}),window.location.reload())}),window.addEventListener(\"click\",e=>{e.target===$&&$.classList.add(\"hidden\")}),D.addEventListener(\"click\",()=>{(function(e){if(e=e.trim().toLowerCase(),e&&!L.includes(e))return L.push(e),S(),!0;return!1})(C.value)&&(C.value=\"\",B())}),C.addEventListener(\"keypress\",e=>{\"Enter\"===e.key&&D.click()});const X=localStorage.getItem(\"theme\"),U=window.matchMedia(\"(prefers-color-scheme: dark)\").matches,G=o.querySelector(\".theme-icon\");\"dark\"===X||!X&&U?(document.documentElement.setAttribute(\"data-theme\",\"dark\"),G.textContent=\"dark_mode\"):(document.documentElement.setAttribute(\"data-theme\",\"light\"),G.textContent=\"light_mode\"),o.addEventListener(\"click\",()=>{const e=\"dark\"===document.documentElement.getAttribute(\"data-theme\")?\"light\":\"dark\";document.documentElement.setAttribute(\"data-theme\",e),localStorage.setItem(\"theme\",e),G.textContent=\"dark\"===e?\"dark_mode\":\"light_mode\"}),n.addEventListener(\"click\",()=>{\"this-week\"!==c&&(c=\"this-week\",n.classList.add(\"active\"),a.classList.remove(\"active\"),j())}),a.addEventListener(\"click\",()=>{a.classList.remove(\"new-week-available\"),\"next-week\"!==c&&(c=\"next-week\",a.classList.add(\"active\"),n.classList.remove(\"active\"),j())}),s.addEventListener(\"click\",()=>{d?N():k.classList.remove(\"hidden\")}),i.addEventListener(\"click\",()=>{k.classList.remove(\"hidden\"),document.getElementById(\"login-error\").classList.add(\"hidden\"),g.reset()}),r.addEventListener(\"click\",()=>{k.classList.add(\"hidden\")}),window.addEventListener(\"click\",e=>{e.target===k&&k.classList.add(\"hidden\")}),g.addEventListener(\"submit\",async n=>{n.preventDefault();const a=document.getElementById(\"employee-id\").value.trim(),s=document.getElementById(\"password\").value,o=document.getElementById(\"login-error\"),i=g.querySelector('button[type=\"submit\"]'),r=i.textContent;i.disabled=!0,i.textContent=\"Wird eingeloggt...\";try{const n=`knapp-${a}@bessa.app`,i=await fetch(`${e}/auth/login/`,{method:\"POST\",headers:f(t),body:JSON.stringify({email:n,password:s})}),r=await i.json();if(i.ok){d=r.key,m=a,localStorage.setItem(\"kantine_authToken\",r.key),localStorage.setItem(\"kantine_currentUser\",a);try{const t=await fetch(`${e}/auth/user/`,{headers:f(d)});if(t.ok){const e=await t.json();e.first_name&&localStorage.setItem(\"kantine_firstName\",e.first_name),e.last_name&&localStorage.setItem(\"kantine_lastName\",e.last_name)}}catch(e){console.error(\"Failed to fetch user info:\",e)}v(),k.classList.add(\"hidden\"),y(),g.reset(),I(),N()}else o.textContent=r.non_field_errors?.[0]||r.error||\"Login fehlgeschlagen\",o.classList.remove(\"hidden\")}catch(e){console.error(\"Login error:\",e),o.textContent=\"Ein Fehler ist aufgetreten\",o.classList.remove(\"hidden\")}finally{i.disabled=!1,i.textContent=r}}),l.addEventListener(\"click\",()=>{localStorage.removeItem(\"kantine_authToken\"),localStorage.removeItem(\"kantine_currentUser\"),localStorage.removeItem(\"kantine_firstName\"),localStorage.removeItem(\"kantine_lastName\"),d=null,m=null,u=new Map,h&&(clearInterval(h),h=null,console.log(\"Polling stopped\")),v(),j()})}(),v(),function(){const e=new Date,t=e.toISOString().split(\"T\")[0];let n=!1;for(const a of[...g]){const[s]=a.split(\"_\");let o=!1;if(s=t&&(o=!0)}o&&(g.delete(a),n=!0)}n&&k()}();(function(){try{const e=localStorage.getItem(D),t=localStorage.getItem(C);if(console.log(`[Cache] localStorage: key=${!!e} (${e?e.length:0} chars), ts=${t}`),e){i=JSON.parse(e),r=G(new Date),l=(new Date).getFullYear(),console.log(`[Cache] Parsed ${i.length} weeks:`,i.map(e=>`KW${e.weekNumber}/${e.year} (${(e.days||[]).length} days)`)),j(),z(),A(),t&&q(t);try{const e=new Set;i.forEach(t=>{(t.days||[]).forEach(t=>{(t.items||[]).forEach(t=>{let n=(t.description||\"\").replace(/\\s+/g,\" \").trim();n&&n.includes(\" / \")&&e.add(n)})})});const t=Array.from(e).join(\"\\n\\n\");console.log(\"=== GEFUNDENE MEN\u00dc-TEXTE (\"+e.size+\") ===\"),console.log(t)}catch(e){}return console.log(\"Loaded menu from cache\"),!0}}catch(e){console.warn(\"Failed to load cached menu:\",e)}return!1})()?(document.getElementById(\"loading\").classList.add(\"hidden\"),!function(){const e=localStorage.getItem(C);if(!e)return console.log(\"[Cache] No timestamp found\"),!1;const t=Date.now()-new Date(e).getTime(),n=Math.round(t/6e4);if(t>36e5)return console.log(`[Cache] Stale: ${n}min old (max 60)`),!1;const a=G(new Date),s=J(new Date),o=i.some(e=>e.weekNumber===a&&e.year===s&&e.days&&e.days.length>0);return console.log(`[Cache] Age: ${n}min, looking for KW${a}/${s}, found: ${o}`),o}()?(console.log(\"Cache stale or incomplete \u2013 refreshing from API\"),N()):console.log(\"Cache fresh & complete \u2013 skipping API refresh\")):N(),d&&I(),Q(),setInterval(Q,36e5),console.log(\"Kantine Wrapper loaded \u2705\")}();\n"; document.head.appendChild(sc); })(); diff --git a/dist/bookmarklet.txt b/dist/bookmarklet.txt index 12a9346..64079cb 100755 --- a/dist/bookmarklet.txt +++ b/dist/bookmarklet.txt @@ -1 +1 @@ -javascript:javascript:(function(){ if(window.__KANTINE_LOADED){alert('Kantine Wrapper already loaded!');return;} var s=document.createElement('style');s.textContent=':root { /* Premium Slate/Gray-Blue Palette - Light Mode */ --bg-body: #f1f5f9; /* Slate 100 */ --bg-card: #ffffff; --text-primary: #334155; /* Slate 700 */ --text-secondary: #64748b; --accent-color: #0f172a; /* Slate 900 (High contrast) */ --border-color: #cbd5e1; /* Slate 300 */ --banner-bg: #e2e8f0; --banner-text: #1e293b; --success-color: #059669; --error-color: #dc2626; --card-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.05), 0 2px 4px -2px rgb(0 0 0 / 0.05); --header-bg: rgba(255, 255, 255, 0.9); --header-border: 1px solid rgba(203, 213, 225, 0.6); } [data-theme="dark"] { /* Premium Slate/Gray-Blue Palette - Dark Mode */ --bg-body: #1e293b; /* Deep Slate Gray (Requested) */ --bg-card: #334155; /* Slate 700 */ --text-primary: #f8fafc; /* Slate 50 */ --text-secondary: #cbd5e1; /* Slate 300 */ --accent-color: #60a5fa; /* Blue 400 */ --border-color: #475569; /* Slate 600 */ --banner-bg: #475569; --banner-text: #e2e8f0; --header-bg: rgba(30, 41, 59, 0.9); --header-border: 1px solid rgba(71, 85, 105, 0.6); --card-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.4); } * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: \'Inter\', system-ui, -apple-system, sans-serif; background-color: var(--bg-body); color: var(--text-primary); transition: background-color 0.3s ease, color 0.3s ease; line-height: 1.5; -webkit-font-smoothing: antialiased; } /* Fix scrolling bug: Reset html/body styles from host page */ /* IMPORTANT: html must NOT have overflow set, or it creates a scroll container that breaks position: sticky */ html { height: auto !important; min-height: 100% !important; overflow: visible !important; position: static !important; margin: 0 !important; padding: 0 !important; } body { height: auto !important; min-height: 100% !important; overflow-x: clip !important; /* clip prevents horizontal overflow without breaking sticky */ overflow-y: visible !important; position: static !important; margin: 0 !important; padding: 0 !important; } /* Header */ .app-header { flex-shrink: 0; z-index: 100; backdrop-filter: blur(12px); background-color: var(--header-bg); border-bottom: var(--header-border); padding: 1rem 0; } .header-content { width: 100%; /* Full width */ padding: 0 2rem; /* Comfortable padding */ display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 1rem; } .brand { display: flex; align-items: center; gap: 0.75rem; } .brand-text { display: flex; flex-direction: column; } .brand h1 { font-size: 1.25rem; font-weight: 700; letter-spacing: -0.025em; margin-bottom: 0; } .subtitle { font-size: 0.85rem; color: var(--text-secondary); font-weight: 400; margin-left: 2px; } .logo-icon { font-size: 1.5rem; color: var(--accent-color); } /* Controls */ .controls { display: flex; align-items: center; gap: 1.5rem; justify-self: end; } /* Header Week Info (centered) */ .header-week-info { text-align: center; line-height: 1.3; } .header-center-wrapper { display: flex; flex-direction: row; align-items: center; gap: 1.5rem; justify-content: center; } .header-week-title { font-size: 1.1rem; font-weight: 600; color: var(--text-primary); } .header-week-subtitle { font-size: 0.85rem; color: var(--text-secondary); } /* Language Toggle (FR-100) */ .lang-toggle { display: inline-flex; gap: 0; border-radius: 6px; overflow: hidden; border: 1px solid var(--border-color); background: var(--bg-card); } .lang-btn { padding: 3px 10px; font-size: 0.7rem; font-weight: 600; letter-spacing: 0.03em; background: transparent; color: var(--text-secondary); border: none; cursor: pointer; transition: all 0.2s; } .lang-btn:hover { color: var(--text-primary); background: rgba(100, 116, 139, 0.1); } .lang-btn.active { background: var(--accent-color); color: white; } .nav-group { display: flex; background-color: var(--bg-card); border: 1px solid var(--border-color); padding: 0.25rem; border-radius: 8px; } .nav-btn { background: none; border: none; padding: 0.5rem 1rem; font-size: 0.875rem; font-weight: 500; color: var(--text-secondary); cursor: pointer; border-radius: 6px; transition: all 0.2s; display: flex; align-items: center; gap: 0.5rem; } .nav-btn:hover { color: var(--text-primary); background-color: rgba(100, 116, 139, 0.1); } .nav-btn.active { background-color: var(--accent-color); color: white; } /* Notification state for Next Week */ .nav-btn.new-week-available { animation: goldPulse 2s infinite; border-color: #f59e0b; color: var(--accent-color); } .nav-btn.new-week-available.active { color: white; } @keyframes goldPulse { 0% { box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.7); } 70% { box-shadow: 0 0 0 10px rgba(245, 158, 11, 0); } 100% { box-shadow: 0 0 0 0 rgba(245, 158, 11, 0); } } /* Badge for nav buttons (day count indicator) */ .nav-badge { background-color: var(--error-color); color: white; font-size: 0.75rem; font-weight: 600; padding: 0 6px; border-radius: 10px; min-width: 18px; height: 18px; display: inline-flex; align-items: center; justify-content: center; margin-left: 8px; gap: 3px; line-height: 1; } .nav-badge .orderable { color: #fff; font-weight: 800; } .nav-badge .separator { opacity: 0.6; font-weight: 400; } .nav-badge .total { opacity: 0.8; font-weight: 400; } .nav-btn.active .nav-badge { background: rgba(255, 255, 255, 0.3); } /* Primary style for Login Button to match header */ #btn-login-open { background-color: var(--accent-color); color: white; padding: 0.5rem 1.25rem; border-radius: 8px; font-weight: 600; letter-spacing: 0.025em; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } #btn-login-open:hover { background-color: #334155; /* Slightly lighter than slate-900 */ transform: translateY(-1px); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } /* User Badge Button (Login) */ .user-badge-btn { display: flex; align-items: center; gap: 8px; padding: 6px 12px; background: var(--bg-card); border: 1px solid var(--border-color); border-radius: 20px; font-size: 0.9rem; font-weight: 500; color: var(--text-primary); cursor: pointer; transition: all 0.2s; } .user-badge-btn:hover { background: rgba(100, 116, 139, 0.1); border-color: var(--accent-color); } .user-badge-btn .material-icons-round { font-size: 1.25rem; color: var(--accent-color); } .icon-btn { background: none; border: none; color: var(--text-primary); cursor: pointer; padding: 0.5rem; border-radius: 50%; transition: background-color 0.2s; display: flex; align-items: center; justify-content: center; } .icon-btn:hover { background-color: rgba(100, 116, 139, 0.1); } /* Refresh button animation */ #btn-refresh.refreshing .material-icons-round { animation: rotate 1s linear infinite; } @keyframes rotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } /* Progress Modal */ .progress-container { margin-bottom: 1.5rem; } .progress-bar { width: 100%; height: 8px; background-color: var(--border-color); border-radius: 4px; overflow: hidden; margin-bottom: 0.75rem; } .progress-fill { height: 100%; background: linear-gradient(90deg, var(--accent-color) 0%, #60a5fa 100%); width: 0%; transition: width 0.3s ease; border-radius: 4px; } .progress-percent { text-align: center; font-size: 1.5rem; font-weight: 700; color: var(--text-primary); margin-bottom: 0.5rem; } .progress-message { text-align: center; color: var(--text-secondary); font-size: 0.9rem; font-weight: 500; } .weekly-cost { background-color: rgba(59, 130, 246, 0.1); /* Blue tint */ color: var(--accent-color); padding: 0.4rem 0.8rem; border-radius: 8px; font-weight: 600; font-size: 0.9rem; display: flex; align-items: center; gap: 0.5rem; border: 1px solid rgba(59, 130, 246, 0.2); } .weekly-cost .material-icons-round { font-size: 18px; } /* Container - flex column, full width so child scrollbar is at edge */ .container { flex: 1; width: 100%; overflow: hidden; padding: 0 0 0 0; /* Only top padding, no horizontal so child fills width */ display: flex; flex-direction: column; } /* Add horizontal padding to direct children of container to maintain layout */ .container>*:not(.menu-grid) { padding-left: 2rem; padding-right: 2rem; } /* Banner */ .banner { background-color: var(--banner-bg); color: var(--banner-text); padding: 0.75rem 1rem; border-radius: 8px; display: flex; align-items: center; gap: 0.5rem; margin-bottom: 2rem; font-size: 0.875rem; font-weight: 500; border: 1px solid var(--border-color); max-width: fit-content; } /* User Badge */ .user-badge { display: flex; align-items: center; gap: 8px; padding: 6px 12px; background: var(--bg-card); /* Changed from --surface */ border: 1px solid var(--border-color); /* Changed from --border */ border-radius: 20px; font-size: 0.9rem; font-weight: 500; } .icon-btn-small { background: none; border: none; padding: 4px; cursor: pointer; color: var(--text-secondary); /* Changed from --text-muted */ display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: all 0.2s; } .icon-btn-small:hover { color: var(--error-color); /* Changed from --danger */ background: rgba(239, 68, 68, 0.1); } /* Modal */ .modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; z-index: 1000; transition: all 0.3s; } .modal.hidden { opacity: 0; pointer-events: none; } .modal-content { background: var(--bg-card); width: 90%; max-width: 400px; border-radius: 16px; box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); overflow: hidden; animation: modalSlide 0.3s ease-out; } /* History Modal specific */ .history-modal-content { max-width: 600px; max-height: 85vh; display: flex; flex-direction: column; } .history-modal-content .modal-body { overflow-y: auto; padding: 0; /* Padding is handled by inner elements */ } /* History Styles */ .history-year-group { margin-bottom: 16px; } .history-year-header { background: var(--bg-card); padding: 12px 20px; margin: 0; font-size: 1.2rem; font-weight: 700; color: var(--text-primary); border-bottom: 2px solid var(--border-color); position: sticky; top: 0; z-index: 12; } .history-month-group { border-bottom: 1px solid var(--border-color); } .history-month-header { display: flex; justify-content: space-between; align-items: center; padding: 14px 20px; margin: 0; font-size: 1.05rem; font-weight: 600; color: var(--text-primary); background: var(--bg-body); cursor: pointer; transition: background 0.2s; } .history-month-header:hover { background: var(--border-color); /* Slight hover effect */ } .history-month-summary { display: flex; align-items: center; gap: 12px; font-size: 0.95rem; color: var(--text-secondary); } .history-month-content { display: none; /* Collapsed by default */ background: var(--bg-card); } .history-month-group.open .history-month-content { display: block; /* Expanded when open class is present */ } .history-month-group.open .history-month-header .material-icons-round { transform: rotate(180deg); } .history-month-header .material-icons-round { transition: transform 0.3s; font-size: 20px; } .history-week-group { padding: 12px 20px; border-bottom: 1px dashed var(--border-color); } .history-week-group:last-child { border-bottom: none; } .history-week-header { display: flex; justify-content: space-between; align-items: center; font-size: 0.9rem; font-weight: 600; color: var(--text-secondary); margin-bottom: 10px; } .history-week-summary { font-size: 0.85rem; font-weight: 500; background: rgba(100, 116, 139, 0.1); padding: 4px 10px; border-radius: 12px; } .history-items { display: flex; flex-direction: column; gap: 8px; } .history-item { display: grid; grid-template-columns: 50px 1fr auto; align-items: center; gap: 12px; padding: 10px 12px; background: var(--bg-body); border-radius: 8px; border: 1px solid var(--border-color); } .history-item-date { font-size: 0.85rem; color: var(--text-secondary); font-weight: 500; } .history-item-details { display: flex; flex-direction: column; gap: 4px; } .history-item-name { font-size: 0.95rem; font-weight: 500; color: var(--text-primary); } .history-item-price { font-weight: 600; color: var(--text-primary); } .history-item-status { font-size: 0.8rem; font-weight: 600; color: var(--text-primary); text-transform: uppercase; letter-spacing: 0.5px; } .history-item-cancelled { opacity: 0.5; filter: grayscale(1); } .history-item-price-cancelled { text-decoration: line-through; color: var(--text-secondary); } @keyframes modalSlide { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } .modal-header { display: flex; align-items: center; justify-content: space-between; padding: 20px; border-bottom: 1px solid var(--border-color); } .modal-header h2 { margin: 0; font-size: 1.25rem; } .modal-body { padding: 20px; } #login-form { padding: 20px; } .form-group { margin-bottom: 20px; } .form-group label { display: block; margin-bottom: 6px; font-weight: 500; font-size: 0.9rem; } .form-group input { width: 100%; padding: 10px 12px; border: 1px solid var(--border-color); /* Changed from --border */ border-radius: 8px; background: var(--bg-body); /* Changed from --bg */ color: var(--text-primary); /* Changed from --text */ font-family: inherit; transition: border-color 0.2s; } .form-group input:focus { outline: none; border-color: var(--accent-color); /* Changed from --primary */ } .help-text { display: block; margin-top: 4px; color: var(--text-secondary); /* Changed from --text-muted */ font-size: 0.75rem; } .error-msg { margin-bottom: 16px; padding: 10px; background: rgba(239, 68, 68, 0.1); color: var(--error-color); /* Changed from --danger */ border-radius: 8px; font-size: 0.85rem; text-align: center; } .modal-actions { margin-top: 24px; } .btn-primary.wide { width: 100%; justify-content: center; } .hidden { display: none !important; } /* Menu Grid Container */ .menu-grid { display: flex; flex-direction: column; flex: 1; overflow: hidden; gap: 1rem; } .week-section { margin-bottom: 2rem; } .week-header { margin-bottom: 1.5rem; border-bottom: 1px solid var(--border-color); padding-bottom: 1rem; text-align: center; } .week-title { font-size: 1.75rem; font-weight: 700; color: var(--text-primary); } .week-range { color: var(--text-secondary); font-size: 0.9rem; margin-top: 0.25rem; } /* Full-viewport layout: header + scrollable content + footer */ #kantine-wrapper { display: flex; flex-direction: column; height: 100vh; height: 100dvh; /* Dynamic viewport height for mobile browsers */ overflow: hidden; } .days-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 0.75rem; flex: 1; overflow-y: auto; /* This is the scroll container at the window edge */ align-content: start; padding: 0 2rem 2rem 2rem; } /* Card */ .menu-card { background-color: var(--bg-card); border-radius: 12px; border: 1px solid var(--border-color); box-shadow: var(--card-shadow); overflow: clip; /* Clips scrolling content behind sticky header */ transition: box-shadow 0.2s ease; display: flex; flex-direction: column; } /* Past Day Styling - Target specific elements so ordered items can remain visible AND preserve sticky context */ /* We MUST apply filter/opacity to children, not the parent .menu-card, or else position: sticky breaks */ /* Header keeps fully opaque background to hide scrolling items, only grayscales */ .menu-card.past-day .card-header { filter: grayscale(0.8); transition: filter 0.3s; } /* Items become semi-transparent */ .menu-card.past-day .menu-item:not(.ordered) { opacity: 0.6; filter: grayscale(0.8); transition: opacity 0.3s, filter 0.3s; } .menu-card.past-day:hover .card-header { filter: grayscale(0.4); } .menu-card.past-day:hover .menu-item:not(.ordered) { opacity: 0.8; filter: grayscale(0.4); } /* Past ordered items get no special frame or shadow, but remain visually distinct by staying fully opaque (via the :not(.ordered) selector above) */ .menu-item.today-ordered { border: 2px solid #8b5cf6; box-shadow: 0 0 30px rgba(139, 92, 246, 0.6); border-radius: 8px; padding: 1rem; margin: 0 -1rem 1.5rem -1rem; background: var(--bg-card); position: relative; z-index: 5; animation: pulse-glow-strong 3s infinite; } @keyframes pulse-glow-strong { 0% { box-shadow: 0 0 20px rgba(139, 92, 246, 0.4); } 50% { box-shadow: 0 0 40px rgba(139, 92, 246, 0.8); } 100% { box-shadow: 0 0 20px rgba(139, 92, 246, 0.4); } } .menu-card:hover { box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); } .card-header { padding: 1rem 1.25rem; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: baseline; background-color: var(--bg-card); /* Removed border-radius: 12px 12px 0 0; .menu-card\'s overflow: clip will round the corners initially. When sticky at the top, it will be square and perfectly hide scrolling content! */ /* Sticky within .container scroll area */ position: sticky; top: 0; z-index: 90; } .card-body { padding: 1.25rem; display: grid; grid-template-rows: auto; align-content: start; } .day-name { font-size: 1.125rem; font-weight: 600; } .day-date { font-size: 0.875rem; color: var(--text-secondary); } .empty-state { color: var(--text-secondary); font-style: italic; text-align: center; padding: 1rem; } /* Menu Items */ .menu-item { margin-bottom: 1.5rem; padding-bottom: 1.5rem; border-bottom: 1px solid var(--border-color); } .menu-item:last-child { margin-bottom: 0; padding-bottom: 0; border-bottom: none; } .item-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 0.5rem; gap: 1rem; } .item-name { font-weight: 600; color: var(--text-primary); font-size: 1rem; } .item-price { font-weight: 700; color: var(--accent-color); white-space: nowrap; } .item-desc { font-size: 0.875rem; color: var(--text-secondary); line-height: 1.6; margin-bottom: 0.75rem; white-space: pre-wrap; } .badges { display: flex; gap: 0.5rem; margin-left: auto; } .item-status-row { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.75rem; } .badge { display: inline-flex; align-items: center; justify-content: center; height: 24px; font-size: 0.75rem; padding: 0 10px; border-radius: 4px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; line-height: normal; white-space: nowrap; } .badge.available { background-color: rgba(16, 185, 129, 0.1); /* Emerald 500 / 10% */ color: var(--success-color); border: 1px solid rgba(16, 185, 129, 0.2); } .badge.sold-out { background-color: rgba(239, 68, 68, 0.1); /* Red 500 / 10% */ color: var(--error-color); border: 1px solid rgba(239, 68, 68, 0.2); } .badge.ordered { background-color: rgba(139, 92, 246, 0.1); /* Violet 500 / 10% */ color: #8b5cf6; border: 1px solid rgba(139, 92, 246, 0.2); gap: 4px; } .badge.ordered .material-icons-round { font-size: 1rem; } /* Loading */ .loading-state { text-align: center; padding: 4rem; color: var(--text-secondary); } .spinner { width: 40px; height: 40px; border: 3px solid var(--border-color); border-top-color: var(--accent-color); border-radius: 50%; margin: 0 auto 1rem; animation: spin 1s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } /* Footer */ .app-footer { flex-shrink: 0; text-align: center; padding: 0.4rem 2rem; color: var(--text-secondary); font-size: 0.8rem; border-top: 1px solid var(--border-color); } /* === Order / Cancel Buttons (inline in status row) === */ .btn-order { display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px; border: none; border-radius: 6px; background: var(--success-color); color: white; font-size: 0.75rem; font-weight: 600; cursor: pointer; transition: all 0.2s ease; font-family: inherit; } .btn-order .material-icons-round { font-size: 16px; } .btn-order:hover:not(:disabled) { filter: brightness(1.15); transform: translateY(-1px); } .btn-order:disabled { opacity: 0.5; cursor: not-allowed; } .btn-order.loading { pointer-events: none; opacity: 0.6; } .btn-order-compact { padding: 2px 4px; gap: 0; } .btn-order-compact .material-icons-round { font-size: 16px; } .btn-cancel { display: inline-flex; align-items: center; justify-content: center; padding: 4px 6px; border: none; border-radius: 6px; background: var(--error-color); color: white; font-size: 0.75rem; cursor: pointer; transition: all 0.2s ease; font-family: inherit; } .btn-cancel .material-icons-round { font-size: 16px; } .btn-cancel:hover:not(:disabled) { filter: brightness(1.15); transform: translateY(-1px); } .btn-cancel:disabled { opacity: 0.5; cursor: not-allowed; } /* Past days: hide action buttons */ .past-day .item-actions { display: none; } /* Order count badge (for multi-orders) */ .order-count-badge { display: inline-flex; align-items: center; justify-content: center; background: rgba(255, 255, 255, 0.3); color: white; font-size: 0.65rem; font-weight: 700; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 8px; margin-left: 4px; line-height: 1; } /* === Toast Notifications === */ #toast-container { position: fixed; bottom: 20px; right: 20px; z-index: 10000; display: flex; flex-direction: column; gap: 8px; pointer-events: none; } .toast { display: flex; align-items: center; gap: 8px; padding: 10px 16px; border-radius: 8px; font-size: 0.85rem; font-weight: 500; font-family: \'Inter\', sans-serif; color: white; backdrop-filter: blur(10px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); pointer-events: auto; transform: translateX(120%); opacity: 0; transition: transform 0.3s ease, opacity 0.3s ease; } .toast.show { transform: translateX(0); opacity: 1; } .toast .material-icons-round { font-size: 18px; } .toast-success { background: rgba(5, 150, 105, 0.95); } .toast-error { background: rgba(220, 38, 38, 0.95); } .toast-info { background: rgba(59, 130, 246, 0.95); } /* === Mobile Responsiveness === */ @media (max-width: 600px) { .header-content { flex-direction: column; gap: 1rem; padding: 0.75rem; } .week-nav { width: 100%; justify-content: center; } .nav-pills { width: 100%; justify-content: space-between; } .nav-btn { flex: 1; justify-content: center; padding: 0.5rem; font-size: 0.85rem; } .days-grid { grid-template-columns: 1fr; /* Force single column */ } .main-content { padding: 1rem; } .week-title { font-size: 1.5rem; } /* Adjust toast position for mobile */ .toast-container { bottom: 1rem; right: 1rem; left: 1rem; /* Center on mobile */ width: auto; } .menu-card { margin-bottom: 1rem; } } /* === Flagging & Notification Styles === */ .btn-flag { display: inline-flex; align-items: center; justify-content: center; background: transparent; border: 1px solid var(--text-secondary); color: var(--text-secondary); border-radius: 6px; padding: 4px; cursor: pointer; transition: all 0.2s; margin-right: 0.5rem; width: 28px; height: 28px; } .btn-flag:hover { background: rgba(234, 179, 8, 0.1); /* Yellow-500 / 10% */ color: #eab308; border-color: #eab308; } .btn-flag.active { background: rgba(234, 179, 8, 0.1); color: #eab308; border-color: #eab308; } .btn-flag .material-icons-round { font-size: 1.1rem; } /* Flagged & Sold Out (Yellow Glow) */ .menu-item.flagged-sold-out { border: 1px solid #eab308; box-shadow: 0 0 10px rgba(234, 179, 8, 0.2); border-radius: 8px; padding: 1rem; margin: 0 -1rem 1.5rem -1rem; background: var(--bg-card); position: relative; z-index: 5; animation: yellow-pulse 3s infinite; } @keyframes yellow-pulse { 0% { box-shadow: 0 0 8px rgba(234, 179, 8, 0.2); } 50% { box-shadow: 0 0 16px rgba(234, 179, 8, 0.5); } 100% { box-shadow: 0 0 8px rgba(234, 179, 8, 0.2); } } /* Flagged & Available (Green Glow) */ .menu-item.flagged-available { border: 2px solid var(--success-color); box-shadow: 0 0 15px rgba(16, 185, 129, 0.3); border-radius: 8px; padding: 1rem; margin: 0 -1rem 1.5rem -1rem; background: var(--bg-card); position: relative; z-index: 5; animation: green-pulse 3s infinite; } @keyframes green-pulse { 0% { box-shadow: 0 0 10px rgba(16, 185, 129, 0.3); } 50% { box-shadow: 0 0 20px rgba(16, 185, 129, 0.6); } 100% { box-shadow: 0 0 10px rgba(16, 185, 129, 0.3); } } /* Day Header Badges */ .day-header-left { display: flex; align-items: center; gap: 0.75rem; } .menu-code-badge { font-size: 0.75rem; font-weight: 700; color: #8b5cf6; /* Violet 500 */ background-color: rgba(139, 92, 246, 0.15); border: 1px solid rgba(139, 92, 246, 0.3); padding: 2px 6px; border-radius: 6px; line-height: normal; display: inline-block; } /* Detailed Badge Colors */ .nav-badge.badge-violet { background-color: #8b5cf6; } .nav-badge.badge-green { background-color: var(--success-color); } .nav-badge.badge-red { background-color: var(--error-color); } .nav-badge.badge-blue { background-color: var(--accent-color); } /* Day Header Status Colors (User Request) */ .card-header.header-violet { background-color: var(--bg-card); background-image: linear-gradient(rgba(139, 92, 246, 0.15), rgba(139, 92, 246, 0.15)); border-bottom: 2px solid #8b5cf6; } .card-header.header-green { background-color: var(--bg-card); background-image: linear-gradient(rgba(16, 185, 129, 0.15), rgba(16, 185, 129, 0.15)); border-bottom: 2px solid var(--success-color); } .card-header.header-red { background-color: var(--bg-card); background-image: linear-gradient(rgba(239, 68, 68, 0.15), rgba(239, 68, 68, 0.15)); border-bottom: 2px solid var(--error-color); } .card-header.header-violet .day-name, .card-header.header-green .day-name, .card-header.header-red .day-name { font-weight: 700; color: var(--text-primary); /* Ensure text remains standard color */ } /* Update Icon */ .update-icon { display: inline-flex; align-items: center; justify-content: center; margin-left: 8px; background-color: rgba(16, 185, 129, 0.2); /* Green tint */ color: var(--success-color); border-radius: 50%; width: 24px; height: 24px; cursor: pointer; font-size: 14px; transition: all 0.2s; text-decoration: none; animation: pulse 2s infinite; } .update-icon:hover { background-color: var(--success-color); color: white; transform: scale(1.1); } @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.4); } 70% { box-shadow: 0 0 0 6px rgba(16, 185, 129, 0); } 100% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); } } /* Order Countdown */ #order-countdown { background: rgba(255, 255, 255, 0.1); padding: 0.25rem 0.75rem; border-radius: 99px; font-size: 0.85rem; display: flex; align-items: center; gap: 0.5rem; white-space: nowrap; border: 1px solid var(--border-color); } #order-countdown span { opacity: 0.7; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.5px; } #order-countdown.urgent { background: rgba(239, 68, 68, 0.2); border-color: rgba(239, 68, 68, 0.5); color: #ef4444; animation: pulse-red 2s infinite; } @keyframes pulse-red { 0% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4); } 70% { box-shadow: 0 0 0 6px rgba(239, 68, 68, 0); } 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); } } /* Smart Highlights (Blue Glow - matches today-ordered/flagged pattern) */ .menu-item.highlight-glow { border: 2px solid rgba(59, 130, 246, 0.7); box-shadow: 0 0 20px rgba(59, 130, 246, 0.4); border-radius: 8px; padding: 1rem; margin: 0 -1rem 1.5rem -1rem; background: var(--bg-card); position: relative; z-index: 5; animation: blue-pulse 3s infinite; } @keyframes blue-pulse { 0% { box-shadow: 0 0 15px rgba(59, 130, 246, 0.3); } 50% { box-shadow: 0 0 25px rgba(59, 130, 246, 0.6); } 100% { box-shadow: 0 0 15px rgba(59, 130, 246, 0.3); } } /* Nav Badge with Count */ .nav-badge.has-highlights { background-color: var(--bg-card); /* Neutral background */ color: var(--text-primary); border: 1px solid var(--border-color); padding: 2px 6px; } .nav-badge .highlight-count { color: #3b82f6; /* Blue 500 */ font-weight: 700; margin-left: 4px; } /* Tag Management Modal */ #tags-list { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 1rem; min-height: 50px; } /* Tag badges styled consistently with .badge (verfügbar/ausverkauft) */ .tag-badge { display: inline-flex; align-items: center; justify-content: center; height: 24px; font-size: 0.75rem; padding: 0 10px; border-radius: 4px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; line-height: normal; white-space: nowrap; background-color: rgba(59, 130, 246, 0.1); color: #3b82f6; border: 1px solid rgba(59, 130, 246, 0.2); gap: 4px; } .tag-remove { cursor: pointer; opacity: 0.7; font-size: 1.1em; line-height: 1; transition: all 0.2s; } .tag-remove:hover { opacity: 1; color: #ef4444; } .input-group { display: flex; gap: 0.5rem; } .input-group input { flex: 1; padding: 0.75rem; background: var(--bg-body); border: 1px solid var(--border-color); color: var(--text-primary); border-radius: 8px; font-family: inherit; } /* Add tag button - styled like .btn-order with nav-btn.active color */ #btn-add-tag { display: inline-flex; align-items: center; gap: 4px; padding: 0.5rem 1rem; border: none; border-radius: 6px; background: var(--accent-color); color: white; font-size: 0.8rem; font-weight: 600; cursor: pointer; transition: all 0.2s ease; font-family: inherit; white-space: nowrap; } #btn-add-tag:hover { filter: brightness(1.15); transform: translateY(-1px); } .matched-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; /* Space between tags and title */ margin-top: -5px; /* Pull closer to header */ } .tag-badge-small { display: inline-flex; align-items: center; font-size: 0.7rem; padding: 2px 8px; border-radius: 4px; background: rgba(59, 130, 246, 0.15); color: #60a5fa; border: 1px solid rgba(59, 130, 246, 0.3); font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; } [data-theme="light"] .tag-badge-small { background: rgba(37, 99, 235, 0.1); color: #2563eb; border: 1px solid rgba(37, 99, 235, 0.2); } /* Installer Changelog */ .changelog-container ul { padding-left: 1.5rem; margin: 0.5rem 0; } .changelog-container li { margin-bottom: 0.4rem; line-height: 1.5; } .changelog-container h3 { margin-top: 1.5rem; margin-bottom: 0.5rem; font-size: 1.1em; color: var(--accent-color); } /* === Version Menu === */ .version-tag { cursor: pointer; transition: opacity 0.2s ease, text-decoration 0.2s ease; } .version-tag:hover { opacity: 1 !important; text-decoration: underline; } .version-list { list-style: none; padding: 0; margin: 0; } .version-item { display: flex; justify-content: space-between; align-items: center; padding: 10px 14px; border-radius: 8px; margin-bottom: 4px; transition: background 0.2s; } .version-item:hover { background: rgba(100, 116, 139, 0.08); } .version-item.current { background: rgba(2, 154, 168, 0.1); border: 1px solid rgba(2, 154, 168, 0.25); } [data-theme="dark"] .version-item:hover { background: rgba(255, 255, 255, 0.05); } [data-theme="dark"] .version-item.current { background: rgba(96, 165, 250, 0.12); border: 1px solid rgba(96, 165, 250, 0.25); } .version-info { display: flex; align-items: center; gap: 10px; } .badge-current { font-size: 0.75rem; font-weight: 600; color: var(--success-color); padding: 2px 8px; border-radius: 4px; background: rgba(5, 150, 105, 0.1); } .badge-new { font-size: 0.75rem; font-weight: 600; color: #029aa8; padding: 2px 8px; border-radius: 4px; background: rgba(2, 154, 168, 0.1); } [data-theme="dark"] .badge-new { color: #60a5fa; background: rgba(96, 165, 250, 0.12); } .install-link { font-size: 0.8rem; font-weight: 500; padding: 4px 12px; border-radius: 6px; background: rgba(2, 154, 168, 0.1); color: #029aa8; text-decoration: none; border: 1px solid rgba(2, 154, 168, 0.25); transition: all 0.2s; white-space: nowrap; } .install-link:hover { background: rgba(2, 154, 168, 0.2); border-color: rgba(2, 154, 168, 0.4); } [data-theme="dark"] .install-link { color: #60a5fa; background: rgba(96, 165, 250, 0.12); border: 1px solid rgba(96, 165, 250, 0.25); } [data-theme="dark"] .install-link:hover { background: rgba(96, 165, 250, 0.2); border-color: rgba(96, 165, 250, 0.4); } .dev-toggle { padding: 10px 14px; border-radius: 8px; background: rgba(100, 116, 139, 0.05); border: 1px solid var(--border-color); } .dev-toggle input[type="checkbox"] { accent-color: #029aa8; width: 16px; height: 16px; } [data-theme="dark"] .dev-toggle input[type="checkbox"] { accent-color: #60a5fa; } ';document.head.appendChild(s); // Inject JS logic var sc=document.createElement('script'); sc.textContent="function showErrorModal(e,t,n,a){const s=\"error-modal\";let o=document.getElementById(s);o&&o.remove(),o=document.createElement(\"div\"),o.id=s,o.className=\"modal hidden\",o.innerHTML=`\\n
      \\n
      \\n

      \\n signal_wifi_off\\n ${e}\\n

      \\n
      \\n
      \\n

      ${t}

      \\n
      \\n \\n
      \\n
      \\n
      \\n `,document.body.appendChild(o),document.getElementById(\"btn-error-redirect\").addEventListener(\"click\",()=>{window.location.href=a}),requestAnimationFrame(()=>{o.classList.remove(\"hidden\")})}!function(){\"use strict\";if(window.__KANTINE_LOADED)return;window.__KANTINE_LOADED=!0;const e=\"https://api.bessa.app/v1\",t=\"c3418725e95a9f90e3645cbc846b4d67c7c66131\",n=591,a=\"TauNeutrino/kantine-overview\",s=`https://api.github.com/repos/${a}`,o=`https://htmlpreview.github.io/?https://github.com/${a}/blob`;let i=[],r=G(new Date),l=(new Date).getFullYear(),c=\"this-week\",d=localStorage.getItem(\"kantine_authToken\"),m=localStorage.getItem(\"kantine_currentUser\"),u=new Map,g=new Set(JSON.parse(localStorage.getItem(\"kantine_flags\")||\"[]\")),h=null,p=localStorage.getItem(\"kantine_lang\")||\"de\";function f(e){return{Authorization:`Token ${e||t}`,Accept:\"application/json\",\"Content-Type\":\"application/json\",\"X-Client-Version\":\"v1.6.10\"}}function v(){if(!d)try{const e=localStorage.getItem(\"AkitaStores\");if(e){const t=JSON.parse(e);t.auth&&t.auth.token&&(console.log(\"Found existing Bessa session!\"),d=t.auth.token,localStorage.setItem(\"kantine_authToken\",d),t.auth.user&&(m=t.auth.user.id||\"unknown\",localStorage.setItem(\"kantine_currentUser\",m),t.auth.user.firstName&&localStorage.setItem(\"kantine_firstName\",t.auth.user.firstName),t.auth.user.lastName&&localStorage.setItem(\"kantine_lastName\",t.auth.user.lastName)))}}catch(e){console.warn(\"Failed to parse AkitaStores:\",e)}d=localStorage.getItem(\"kantine_authToken\"),m=localStorage.getItem(\"kantine_currentUser\");const e=localStorage.getItem(\"kantine_firstName\"),t=document.getElementById(\"btn-login-open\"),n=document.getElementById(\"user-info\"),a=document.getElementById(\"user-id-display\");d?(t.classList.add(\"hidden\"),n.classList.remove(\"hidden\"),a.textContent=e||(m?`User ${m}`:\"Angemeldet\"),y()):(t.classList.remove(\"hidden\"),n.classList.add(\"hidden\"),a.textContent=\"\"),j()}async function y(){if(d)try{const t=await fetch(`${e}/user/orders/?venue=591&ordering=-created&limit=50`,{headers:f(d)}),n=await t.json();if(t.ok){u=new Map;const e=n.results||[];for(const t of e){if(9===t.order_state)continue;const e=t.date.split(\"T\")[0];for(const n of t.items||[]){const a=`${e}_${n.article}`;u.has(a)||u.set(a,[]),u.get(a).push(t.id)}}console.log(`Fetched ${e.length} orders, mapped active ones.`),j(),z()}}catch(e){console.error(\"Error fetching orders:\",e)}}let b=null;function w(e){const t=document.getElementById(\"history-content\");if(!e||0===e.length)return void(t.innerHTML='

      Keine Bestellungen gefunden.

      ');const n={};e.forEach(e=>{const t=new Date(e.date),a=t.getFullYear(),s=t.getMonth(),o=`${a}-${s.toString().padStart(2,\"0\")}`,i=t.toLocaleString(\"de-AT\",{month:\"long\"}),r=G(t);n[a]||(n[a]={year:a,months:{}}),n[a].months[o]||(n[a].months[o]={name:i,year:a,monthIndex:s,count:0,total:0,weeks:{}}),n[a].months[o].weeks[r]||(n[a].months[o].weeks[r]={label:`KW ${r}`,items:[],count:0,total:0});(e.items||[]).forEach(t=>{const s=parseFloat(t.price||e.total||0);n[a].months[o].weeks[r].items.push({date:e.date,name:t.name||\"Men\u00fc\",price:s,state:e.order_state}),9!==e.order_state&&(n[a].months[o].weeks[r].count++,n[a].months[o].weeks[r].total+=s,n[a].months[o].count++,n[a].months[o].total+=s)})});const a=Object.keys(n).sort((e,t)=>t-e);let s=\"\";a.forEach(e=>{const t=n[e];s+=`
      \\n

      ${t.year}

      `;Object.keys(t.months).sort((e,t)=>t.localeCompare(e)).forEach(e=>{const n=t.months[e];s+=`
      \\n
      \\n
      \\n ${n.name}\\n
      \\n ${n.count} Bestellungen • \u20ac${n.total.toFixed(2)}\\n
      \\n
      \\n expand_more\\n
      \\n
      `;Object.keys(n.weeks).sort((e,t)=>parseInt(t)-parseInt(e)).forEach(e=>{const t=n.weeks[e];s+=`
      \\n
      \\n ${t.label}\\n ${t.count} Bestellungen • \u20ac${t.total.toFixed(2)}\\n
      `,t.items.forEach(e=>{const t=new Date(e.date).toLocaleDateString(\"de-AT\",{weekday:\"short\",day:\"2-digit\",month:\"2-digit\"});let n=\"\";n=9===e.state?'Storniert':8===e.state?'Abgeschlossen':'\u00dcbertragen',s+=`\\n
      \\n
      ${t}
      \\n
      \\n ${W(e.name)}\\n
      ${n}
      \\n
      \\n
      \u20ac${e.price.toFixed(2)}
      \\n
      `}),s+=\"
      \"}),s+=\"
      \"}),s+=\"
      \"}),t.innerHTML=s;t.querySelectorAll(\".history-month-header\").forEach(e=>{e.addEventListener(\"click\",()=>{const t=e.parentElement;t.classList.contains(\"open\")?(t.classList.remove(\"open\"),e.setAttribute(\"aria-expanded\",\"false\")):(t.classList.add(\"open\"),e.setAttribute(\"aria-expanded\",\"true\"))})})}function k(){localStorage.setItem(\"kantine_flags\",JSON.stringify([...g]))}function A(){const e=document.getElementById(\"alarm-bell\"),t=document.getElementById(\"alarm-bell-icon\");if(!e||!t)return;if(0===g.size)return e.classList.add(\"hidden\"),e.style.display=\"none\",t.style.color=\"var(--text-secondary)\",void(t.style.textShadow=\"none\");e.classList.remove(\"hidden\"),e.style.display=\"inline-flex\";let n=!1;for(const e of i)if(e.days){for(const t of e.days)if(t.items){for(const e of t.items)if(e.available&&g.has(e.id)){n=!0;break}if(n)break}if(n)break}let a=localStorage.getItem(\"kantine_last_updated\"),s=\"gerade eben\";a||(a=(new Date).toISOString(),localStorage.setItem(\"kantine_last_updated\",a));s=$(new Date(a)),e.title=`Zuletzt gepr\u00fcft: ${s}`,n?(t.style.color=\"#10b981\",t.style.textShadow=\"0 0 10px rgba(16, 185, 129, 0.4)\"):(t.style.color=\"#f59e0b\",t.style.textShadow=\"0 0 10px rgba(245, 158, 11, 0.4)\")}function E(n,a,s,o){const r=`${n}_${a}`;let l=!1;g.has(r)?(g.delete(r),F(`Flag entfernt f\u00fcr ${s}`,\"success\")):(g.add(r),l=!0,F(`Benachrichtigung aktiviert f\u00fcr ${s}`,\"success\"),\"default\"===Notification.permission&&Notification.requestPermission()),k(),A(),j(),l&&async function(){if(0===g.size)return;const n=d||t,a=new Set;for(const e of g){const[t]=e.split(\"_\");a.add(t)}let s=!1;for(const t of a)try{const a=await fetch(`${e}/venues/591/menu/7/${t}/`,{headers:f(n)});if(!a.ok)continue;const o=(await a.json()).results||[];let r=[];for(const e of o)e.items&&Array.isArray(e.items)&&(r=r.concat(e.items));for(let e of i){if(!e.days)continue;let n=e.days.find(e=>e.date===t);n&&(n.items=r.map(e=>{const n=!1===e.amount_tracking,a=parseInt(e.available_amount)>0;return{id:`${t}_${e.id}`,articleId:e.id,name:e.name||\"Unknown\",description:e.description||\"\",price:parseFloat(e.price)||0,available:n||a,availableAmount:parseInt(e.available_amount)||0,amountTracking:!1!==e.amount_tracking}}),s=!0)}}catch(e){console.error(\"Error refreshing flag date\",t,e)}s&&(O(),q((new Date).toISOString()),A(),j())}()}function I(){h||d&&(h=setInterval(()=>async function(){if(0===g.size||!d)return;console.log(`Polling ${g.size} flagged items...`);for(const t of g){const[n,a]=t.split(\"_\"),s=parseInt(a);try{const t=await fetch(`${e}/venues/591/menu/7/${n}/`,{headers:f(d)});if(!t.ok)continue;const a=(await t.json()).results||[];let o=null;for(const e of a)if(e.items&&(o=e.items.find(e=>e.id===s||e.article===s),o))break;if(o){if(!1===o.amount_tracking||parseInt(o.available_amount)>0){const e=o.name||\"Unbekannt\";F(`${e} ist jetzt verf\u00fcgbar!`,\"success\"),\"granted\"===Notification.permission&&new Notification(\"Kantine Wrapper\",{body:`${e} ist jetzt verf\u00fcgbar!`,icon:\"\ud83c\udf7d\ufe0f\"}),N()}}}catch(e){console.error(`Poll error for ${t}:`,e),await new Promise(e=>setTimeout(e,200))}}q((new Date).toISOString())}(),3e5),console.log(\"Polling started (every 5 min)\"))}let L=JSON.parse(localStorage.getItem(\"kantine_highlightTags\")||\"[]\");function S(){localStorage.setItem(\"kantine_highlightTags\",JSON.stringify(L)),j(),z()}function B(){const e=document.getElementById(\"tags-list\");e.innerHTML=\"\",L.forEach(t=>{const n=document.createElement(\"span\");n.className=\"tag-badge\",n.innerHTML=`${t} ×`,e.appendChild(n)}),e.querySelectorAll(\".tag-remove\").forEach(e=>{e.addEventListener(\"click\",e=>{var t;t=e.target.dataset.tag,L=L.filter(e=>e!==t),S(),B()})})}function x(e){return e?(e=e.toLowerCase(),L.filter(t=>e.includes(t))):[]}const D=\"kantine_menuCache\",C=\"kantine_menuCacheTs\";function O(){try{localStorage.setItem(D,JSON.stringify(i)),localStorage.setItem(C,(new Date).toISOString())}catch(e){console.warn(\"Failed to cache menu data:\",e)}}async function N(){const n=document.getElementById(\"loading\"),a=document.getElementById(\"progress-modal\"),s=document.getElementById(\"progress-fill\"),o=document.getElementById(\"progress-percent\"),c=document.getElementById(\"progress-message\");n.classList.remove(\"hidden\");const m=d||t;try{a.classList.remove(\"hidden\"),c.textContent=\"Hole verf\u00fcgbare Daten...\",s.style.width=\"0%\",o.textContent=\"0%\";const t=await fetch(`${e}/venues/591/menu/dates/`,{headers:f(m)});if(!t.ok)throw new Error(`Failed to fetch dates: ${t.status}`);let n=(await t.json()).results||[];const d=new Date;d.setDate(d.getDate()-7);const u=d.toISOString().split(\"T\")[0];n=n.filter(e=>e.date>=u).sort((e,t)=>e.date.localeCompare(t.date)).slice(0,30);const g=n.length;c.textContent=`${g} Tage gefunden. Lade Details...`;const h=[];let p=0;for(const t of n){const n=t.date,a=Math.round((p+1)/g*100);s.style.width=`${a}%`,o.textContent=`${a}%`,c.textContent=`Lade Men\u00fc f\u00fcr ${n}...`;try{const a=await fetch(`${e}/venues/591/menu/7/${n}/`,{headers:f(m)});if(a.ok){const e=await a.json();0===p&&console.log(\"[Kantine Debug] Raw API response for\",n,\":\",JSON.stringify(e).substring(0,2e3));const s=e.results||[];let o=[];for(const e of s)e.items&&Array.isArray(e.items)&&(o=o.concat(e.items));o.length>0&&(0===p&&(console.log(\"[Kantine Debug] First item keys:\",Object.keys(o[0])),console.log(\"[Kantine Debug] First item:\",JSON.stringify(o[0]).substring(0,500))),h.push({date:n,menu_items:o,orders:t.orders||[]}))}}catch(e){console.error(`Failed to fetch details for ${n}:`,e)}p++,await new Promise(e=>setTimeout(e,100))}const y=new Map;i&&i.length>0&&i.forEach(e=>{const t=`${e.year}-${e.weekNumber}`;try{y.set(t,{year:e.year,weekNumber:e.weekNumber,days:e.days?e.days.map(e=>({...e,items:e.items?[...e.items]:[]})):[]})}catch(e){console.warn(\"Error hydrating week:\",e)}});for(const e of h){const t=new Date(e.date),n=G(t),a=J(t),s=`${a}-${n}`;y.has(s)||y.set(s,{year:a,weekNumber:n,days:[]});const o=y.get(s),i=t.toLocaleDateString(\"en-US\",{weekday:\"long\"}),r=new Date(e.date);r.setHours(10,0,0,0);const l={date:e.date,weekday:i,orderCutoff:r.toISOString(),items:e.menu_items.map(t=>{const n=!1===t.amount_tracking,a=parseInt(t.available_amount)>0;return{id:`${e.date}_${t.id}`,articleId:t.id,name:t.name||\"Unknown\",description:t.description||\"\",price:parseFloat(t.price)||0,available:n||a,availableAmount:parseInt(t.available_amount)||0,amountTracking:!1!==t.amount_tracking}})},c=o.days.findIndex(t=>t.date===e.date);c>=0?o.days[c]=l:o.days.push(l)}i=Array.from(y.values()).sort((e,t)=>e.year!==t.year?e.year-t.year:e.weekNumber-t.weekNumber),i.forEach(e=>{e.days&&e.days.sort((e,t)=>e.date.localeCompare(t.date))}),O(),q((new Date).toISOString()),r=G(new Date),l=(new Date).getFullYear(),v(),j(),z(),A(),c.textContent=\"Fertig!\",setTimeout(()=>a.classList.add(\"hidden\"),500)}catch(e){console.error(\"Error fetching menu:\",e),a.classList.add(\"hidden\"),showErrorModal(\"Keine Verbindung\",`Die Men\u00fcdaten konnten nicht geladen werden. M\u00f6glicherweise besteht keine Verbindung zur API oder zur Bessa-Webseite.

      ${e.message}`,\"Zur Original-Seite\",\"https://web.bessa.app/knapp-kantine\")}finally{n.classList.add(\"hidden\")}}let M=null,T=null;function q(e){const t=document.getElementById(\"last-updated-subtitle\");if(e){M=e,localStorage.setItem(\"kantine_last_updated\",e);try{const n=new Date(e),a=n.toLocaleTimeString(\"de-DE\",{hour:\"2-digit\",minute:\"2-digit\"}),s=n.toLocaleDateString(\"de-DE\",{day:\"2-digit\",month:\"2-digit\"}),o=$(n);t.textContent=`Aktualisiert: ${s} ${a} (${o})`}catch(e){t.textContent=\"\"}T||(T=setInterval(()=>{M&&(q(M),A())},6e4))}}function $(e){const t=Date.now()-e.getTime(),n=Math.floor(t/6e4);if(n<1)return\"gerade eben\";if(1===n)return\"vor 1 min.\";if(n<60)return`vor ${n} min.`;const a=Math.floor(n/60);return 1===a?\"vor 1 Std.\":`vor ${a} Std.`}function F(e,t=\"info\"){let n=document.getElementById(\"toast-container\");n||(n=document.createElement(\"div\"),n.id=\"toast-container\",document.body.appendChild(n));const a=document.createElement(\"div\");a.className=`toast toast-${t}`;const s=\"success\"===t?\"check_circle\":\"error\"===t?\"error\":\"info\";a.innerHTML=`${s}${e}`,n.appendChild(a),requestAnimationFrame(()=>a.classList.add(\"show\")),setTimeout(()=>{a.classList.remove(\"show\"),setTimeout(()=>a.remove(),300)},3e3)}function z(){const e=document.getElementById(\"btn-next-week\");let t=r+1,n=l;t>52&&(t=1,n++);const a=i.find(e=>e.weekNumber===t&&e.year===n);let s=0,o=0,c=0,d=0;a&&a.days&&a.days.forEach(e=>{if(e.items&&e.items.length>0){s++;const t=e.items.some(e=>e.available);t&&o++;let n=!1;e.items.forEach(t=>{const a=t.articleId||parseInt(t.id.split(\"_\")[1]),s=`${e.date}_${a}`;u.has(s)&&u.get(s).length>0&&(n=!0)}),n&&c++,t&&!n&&d++}});let m=e.querySelector(\".nav-badge\");if(s>0){m||(m=document.createElement(\"span\"),m.className=\"nav-badge\",e.appendChild(m)),m.title=`${c} bestellt / ${o} bestellbar / ${s} gesamt`,m.innerHTML=`${c}/${o}/${s}`,m.classList.remove(\"badge-violet\",\"badge-green\",\"badge-red\",\"badge-blue\"),c>0&&0===d?m.classList.add(\"badge-violet\"):d>0?m.classList.add(\"badge-green\"):0===o?m.classList.add(\"badge-red\"):m.classList.add(\"badge-blue\");let i=0;if(a&&a.days&&a.days.forEach(e=>{e.items.forEach(e=>{const t=x(e.name),n=x(e.description);(t.length>0||n.length>0)&&i++})}),i>0&&(m.innerHTML+=`(${i})`,m.title+=` \u2022 ${i} Highlights gefunden`,m.classList.add(\"has-highlights\")),0===c){e.classList.add(\"new-week-available\");const a=`kantine_notified_nextweek_${n}_${t}`;localStorage.getItem(a)||(localStorage.setItem(a,\"true\"),F(\"Neue Men\u00fcdaten f\u00fcr n\u00e4chste Woche verf\u00fcgbar!\",\"info\"))}else e.classList.remove(\"new-week-available\")}else m&&m.remove()}function j(){const t=document.getElementById(\"menu-container\");if(!t)return;t.innerHTML=\"\";let a=r,s=l;\"next-week\"===c&&(a++,a>52&&(a=1,s++));const o=i.flatMap(e=>e.days||[]).filter(e=>{const t=new Date(e.date);return G(t)===a&&J(t)===s});if(0===o.length)return t.innerHTML=`\\n
      \\n

      Keine Men\u00fcdaten f\u00fcr KW ${a} (${s}) verf\u00fcgbar.

      \\n Versuchen Sie eine andere Woche oder schauen Sie sp\u00e4ter vorbei.\\n
      `,void document.getElementById(\"weekly-cost-display\").classList.add(\"hidden\");!function(e){let t=0;e&&e.length>0&&e.forEach(e=>{e.items&&e.items.forEach(n=>{const a=n.articleId||parseInt(n.id.split(\"_\")[1]),s=`${e.date}_${a}`,o=u.get(s)||[];o.length>0&&(t+=n.price*o.length)})});const n=document.getElementById(\"weekly-cost-display\");t>0?(n.innerHTML=`shopping_bag Gesamt: ${t.toFixed(2).replace(\".\",\",\")} \u20ac`,n.classList.remove(\"hidden\")):n.classList.add(\"hidden\")}(o);const m=document.getElementById(\"header-week-info\"),h=\"this-week\"===c?\"Diese Woche\":\"N\u00e4chste Woche\";m.innerHTML=`\\n
      ${h}
      \\n
      Week ${a} \u2022 ${s}
      `;const v=document.createElement(\"div\");v.className=\"days-grid\",o.sort((e,t)=>e.date.localeCompare(t.date));o.filter(e=>{const t=new Date(e.date).getDay();return 0!==t&&6!==t}).forEach(t=>{const a=function(t){if(!t.items||0===t.items.length)return null;const a=document.createElement(\"div\");a.className=\"menu-card\";const s=new Date,o=new Date(t.date);let i=!1;if(t.orderCutoff)i=s>=new Date(t.orderCutoff);else{const e=new Date;e.setHours(0,0,0,0);const n=new Date(t.date);n.setHours(0,0,0,0),i=n{const n=e.articleId||parseInt(e.id.split(\"_\")[1]),a=`${t.date}_${n}`,s=(u.get(a)||[]).length;if(s>0){const t=e.name.match(/([M][1-9][Ff]?)/);if(t){let e=t[1];s>1&&(e+=\"+\"),r.push(e)}}});const l=document.createElement(\"div\");l.className=\"card-header\";const c=o.toLocaleDateString(\"de-DE\",{day:\"2-digit\",month:\"2-digit\"}),m=r.map(e=>`${e}`).join(\"\");let h=\"\";const v=t.items&&t.items.some(e=>{const n=e.articleId||parseInt(e.id.split(\"_\")[1]),a=`${t.date}_${n}`;return u.has(a)&&u.get(a).length>0}),w=t.items&&t.items.some(e=>e.available);h=v?\"header-violet\":w&&!i?\"header-green\":\"header-red\";h&&l.classList.add(h);l.innerHTML=`\\n
      \\n ${k=t.weekday,{Monday:\"Montag\",Tuesday:\"Dienstag\",Wednesday:\"Mittwoch\",Thursday:\"Donnerstag\",Friday:\"Freitag\",Saturday:\"Samstag\",Sunday:\"Sonntag\"}[k]||k}\\n
      ${m}
      \\n
      \\n ${c}`,a.appendChild(l);var k;const A=document.createElement(\"div\");A.className=\"card-body\";const I=(new Date).toISOString().split(\"T\")[0],L=t.date===I,S=[...t.items].sort((e,n)=>{if(L){const a=e.articleId||parseInt(e.id.split(\"_\")[1]),s=n.articleId||parseInt(n.id.split(\"_\")[1]),o=u.has(`${t.date}_${a}`),i=u.has(`${t.date}_${s}`);if(o&&!i)return-1;if(!o&&i)return 1}return e.name.localeCompare(n.name)});return S.forEach(a=>{const o=document.createElement(\"div\");o.className=\"menu-item\";const r=a.articleId||parseInt(a.id.split(\"_\")[1]),l=`${t.date}_${r}`,c=(u.get(l)||[]).length;let m=\"\";m=a.available?a.amountTracking?`Verf\u00fcgbar (${a.availableAmount})`:'Verf\u00fcgbar':'Ausverkauft';let h=\"\";if(c>0){h=`check_circle Bestellt${c>1?`${c}`:\"\"}`,o.classList.add(\"ordered\"),new Date(t.date).toDateString()===s.toDateString()&&o.classList.add(\"today-ordered\")}const v=`${t.date}_${r}`,w=g.has(v);w&&o.classList.add(a.available?\"flagged-available\":\"flagged-sold-out\");const k=[...new Set([...x(a.name),...x(a.description)])];k.length>0&&o.classList.add(\"highlight-glow\");let I=\"\",L=\"\",S=\"\";if(d&&!i){const e=w?\"notifications_active\":\"notifications_none\",n=w?\"btn-flag active\":\"btn-flag\",s=w?\"Benachrichtigung deaktivieren\":\"Benachrichtigen wenn verf\u00fcgbar\";if(a.available&&!w||(S=``),a.available&&(I=c>0?``:``),c>0){const e=1===c?\"close\":\"remove\",n=1===c?\"Bestellung stornieren\":\"Eine Bestellung stornieren\";L=``}}let B=\"\";if(k.length>0){B=`
      ${k.map(e=>`star${W(e)}`).join(\"\")}
      `}o.innerHTML=`\\n
      \\n ${W(a.name)}\\n ${a.price.toFixed(2)} \u20ac\\n
      \\n
      \\n ${h}\\n ${L}\\n ${I}\\n ${S}\\n
      ${m}
      \\n
      \\n ${B}\\n

      ${W(function(e){if(\"all\"===p)return e||\"\";const t=function(e){if(!e)return{de:\"\",en:\"\",raw:\"\"};let t=e.replace(/(?:\\(|(?:\\/|\\s|^))([A-Z,]+)\\)\\s*(?=\\S)(?!\\s*\\/)/g,\"($1)\\n\u2022 \");t.startsWith(\"\u2022 \")||(t=\"\u2022 \"+t);function n(e){let t=0,n=0;return e.forEach(e=>{const a=e.toLowerCase().replace(/[^a-z\u00e4\u00f6\u00fc\u00df]/g,\"\");if(a){let s=0,o=0;P.includes(a)?s=a.length:P.forEach(e=>{a.includes(e)&&e.length>s&&(s=e.length)}),V.includes(a)?o=a.length:V.forEach(e=>{a.includes(e)&&e.length>o&&(o=e.length)}),s>0&&(t+=s/a.length),o>0&&(n+=o/a.length),/^[A-Z\u00c4\u00d6\u00dc]/.test(e)&&(t+=.5)}}),{de:t,en:n}}function a(e){const t=e.trim().split(/\\s+/);if(t.length<2)return{enPart:e,nextDe:\"\"};let a=-1,s=-9999;for(let e=1;er.de||r.en>0,g=l.de+d>l.en;u&&g&&m>s&&(s=m,a=e)}return-1!==a?{enPart:t.slice(0,a).join(\" \"),nextDe:t.slice(a).join(\" \")}:{enPart:e,nextDe:\"\"}}const s=/(.*?)(?:\\(|(?:\\/|\\s|^))([A-Z,]+)\\)\\s*(?!\\s*[/])/g;let o;const i=[];let r=0;for(;null!==(o=s.exec(e));)o.index>r&&i.push(e.substring(r,o.index).trim()),i.push(o[0].trim()),r=s.lastIndex;r=2){const e=i[0].trim();let t=i.slice(1).join(\" / \").trim();const n=a(t);if(n.nextDe){l.push(e+s),c.push(n.enPart+s);const t=n.nextDe+s;l.push(t),c.push(t)}else{const n=t+s,a=e.includes(s.trim())?e:e+s;l.push(a),c.push(n)}}else{const e=a(n);e.nextDe?(c.push(e.enPart+s),l.push(e.nextDe+s)):(l.push(n+s),c.push(n+s))}}let d=l.join(\"\\n\u2022 \");l.length>0&&!d.startsWith(\"\u2022 \")&&(d=\"\u2022 \"+d);let m=c.join(\"\\n\u2022 \");c.length>0&&!m.startsWith(\"\u2022 \")&&(m=\"\u2022 \"+m);return{de:d,en:m,raw:t}}(e);return\"en\"===p?t.en||t.raw:t.de||t.raw}(a.description))}

      `;const D=o.querySelector(\".btn-order\");D&&D.addEventListener(\"click\",t=>{t.stopPropagation();const a=t.currentTarget;a.disabled=!0,a.classList.add(\"loading\"),async function(t,a,s,o,i){if(d)try{const r=await fetch(`${e}/auth/user/`,{headers:f(d)});if(!r.ok)return void F(\"Fehler: Benutzerdaten konnten nicht geladen werden\",\"error\");const l=await r.json(),c=(new Date).toISOString(),m={uuid:crypto.randomUUID(),created:c,updated:c,order_type:7,items:[{article:a,course_group:null,modifiers:[],uuid:crypto.randomUUID(),name:s,description:i||\"\",price:String(parseFloat(o)),amount:1,vat:\"10.00\",comment:\"\"}],table:null,total:parseFloat(o),tip:0,currency:\"EUR\",venue:n,states:[],order_state:1,date:`${t}T10:30:00Z`,payment_method:\"payroll\",customer:{first_name:l.first_name,last_name:l.last_name,email:l.email,newsletter:!1},preorder:!0,delivery_fee:0,cash_box_table_name:null,take_away:!1},u=await fetch(`${e}/user/orders/`,{method:\"POST\",headers:f(d),body:JSON.stringify(m)});if(u.ok||201===u.status)F(`Bestellt: ${s}`,\"success\"),b=null,await y();else{const e=await u.json();F(`Fehler: ${e.detail||e.non_field_errors?.[0]||\"Bestellung fehlgeschlagen\"}`,\"error\")}}catch(e){console.error(\"Order error:\",e),F(\"Netzwerkfehler bei Bestellung\",\"error\")}}(a.dataset.date,parseInt(a.dataset.article),a.dataset.name,parseFloat(a.dataset.price),a.dataset.desc||\"\").finally(()=>{a.disabled=!1,a.classList.remove(\"loading\")})});const C=o.querySelector(\".btn-cancel\");C&&C.addEventListener(\"click\",t=>{t.stopPropagation();const n=t.currentTarget;n.disabled=!0,async function(t,n,a){if(!d)return;const s=`${t}_${n}`,o=u.get(s);if(!o||0===o.length)return;const i=o[o.length-1];try{const t=await fetch(`${e}/user/orders/${i}/cancel/`,{method:\"PATCH\",headers:f(d),body:JSON.stringify({})});t.ok?(F(`Storniert: ${a}`,\"success\"),b=null,await y()):F(`Fehler: ${(await t.json()).detail||\"Stornierung fehlgeschlagen\"}`,\"error\")}catch(e){console.error(\"Cancel error:\",e),F(\"Netzwerkfehler bei Stornierung\",\"error\")}}(n.dataset.date,parseInt(n.dataset.article),n.dataset.name).finally(()=>{n.disabled=!1})});const O=o.querySelector(\".btn-flag\");O&&O.addEventListener(\"click\",e=>{e.stopPropagation();const t=e.currentTarget;E(t.dataset.date,parseInt(t.dataset.article),t.dataset.name,t.dataset.cutoff)}),A.appendChild(o)}),a.appendChild(A),a}(t);a&&v.appendChild(a)}),t.appendChild(v),setTimeout(()=>function(e){const t=e.querySelectorAll(\".menu-card\");if(0===t.length)return;let n=0;t.forEach(e=>{n=Math.max(n,e.querySelectorAll(\".menu-item\").length)});for(let e=0;e{const s=t.querySelectorAll(\".menu-item\");s[e]&&(s[e].style.height=\"auto\",n=Math.max(n,s[e].offsetHeight),a.push(s[e]))}),a.forEach(e=>{e.style.height=`${n}px`})}}(v),0)}function H(e,t){if(!e||!t)return!1;const n=e.replace(/^v/,\"\").split(\".\").map(Number),a=t.replace(/^v/,\"\").split(\".\").map(Number);for(let e=0;e(a[e]||0))return!0;if((n[e]||0)<(a[e]||0))return!1}return!1}async function K(e){const t=e?`${s}/tags?per_page=20`:`${s}/releases?per_page=20`,n=await fetch(t,{headers:{Accept:\"application/vnd.github.v3+json\"}});if(!n.ok){if(403===n.status)throw new Error(\"API Rate Limit erreicht (403). Bitte sp\u00e4ter erneut versuchen.\");throw new Error(`GitHub API ${n.status}`)}return(await n.json()).map(t=>{const n=e?t.name:t.tag_name;return{tag:n,name:e?n:t.name||n,url:`${o}/${n}/dist/install.html`,body:t.body||\"\"}})}async function Q(){const e=\"v1.6.10\",t=\"true\"===localStorage.getItem(\"kantine_dev_mode\");try{const n=await K(t);if(!n.length)return;localStorage.setItem(\"kantine_version_cache\",JSON.stringify({timestamp:Date.now(),devMode:t,versions:n}));const a=n[0].tag;if(console.log(`[Kantine] Version Check: Local [${e}] vs Latest [${a}] (${t?\"dev\":\"stable\"})`),!H(a,e))return;console.log(`[Kantine] Update verf\u00fcgbar: ${a}`);const s=document.querySelector(\".header-left h1\");if(s&&!s.querySelector(\".update-icon\")){const e=document.createElement(\"a\");e.className=\"update-icon\",e.href=n[0].url,e.target=\"_blank\",e.innerHTML=\"\ud83c\udd95\",e.title=`Update: ${a} \u2014 Klick zum Installieren`,e.style.cssText=\"margin-left:8px;font-size:1em;text-decoration:none;cursor:pointer;vertical-align:middle;\",s.appendChild(e)}}catch(e){console.warn(\"[Kantine] Version check failed:\",e)}}function X(){if(!d||!m)return void U();const e=new Date,t=e.getDay();if(0===t||6===t)return void U();const n=e.toISOString().split(\"T\")[0];let a=!1;for(const e of u.keys())if(e.startsWith(n)){a=!0;break}if(a)return void U();const s=new Date;s.setHours(10,0,0,0);const o=s-e;if(o<=0)return void U();const i=Math.floor(o/36e5),r=Math.floor(o%36e5/6e4),l=document.querySelector(\".header-center-wrapper\");if(!l)return;let c=document.getElementById(\"order-countdown\");if(c||(c=document.createElement(\"div\"),c.id=\"order-countdown\",l.insertBefore(c,l.firstChild)),c.innerHTML=`Bestellschluss: ${i}h ${r}m`,o<36e5){c.classList.add(\"urgent\");const e=`kantine_notified_${n}`;localStorage.getItem(e)||(\"granted\"===Notification.permission?new Notification(\"Kantine: Bestellschluss naht!\",{body:\"Du hast heute noch nichts bestellt. Nur noch 1 Stunde!\",icon:\"\u23f3\"}):\"default\"===Notification.permission&&Notification.requestPermission(),localStorage.setItem(e,\"true\"))}else c.classList.remove(\"urgent\")}function U(){const e=document.getElementById(\"order-countdown\");e&&e.remove()}function G(e){const t=new Date(Date.UTC(e.getFullYear(),e.getMonth(),e.getDate())),n=t.getUTCDay()||7;t.setUTCDate(t.getUTCDate()+4-n);const a=new Date(Date.UTC(t.getUTCFullYear(),0,1));return Math.ceil(((t-a)/864e5+1)/7)}function J(e){const t=new Date(e.getTime());return t.setDate(t.getDate()+3-(t.getDay()+6)%7),t.getFullYear()}function W(e){const t=document.createElement(\"div\");return t.textContent=e||\"\",t.innerHTML}setInterval(X,6e4),setTimeout(X,1e3);const P=[\"apfel\",\"achtung\",\"aubergine\",\"auflauf\",\"beere\",\"blumenkohl\",\"bohne\",\"braten\",\"brokkoli\",\"brot\",\"brust\",\"br\u00f6tchen\",\"butter\",\"chili\",\"dessert\",\"dip\",\"eier\",\"eintopf\",\"eis\",\"erbse\",\"erdbeer\",\"essig\",\"filet\",\"fisch\",\"fisole\",\"fleckerl\",\"fleisch\",\"fl\u00fcgel\",\"frucht\",\"f\u00fcr\",\"gebraten\",\"gem\u00fcse\",\"gew\u00fcrz\",\"gratin\",\"grie\u00df\",\"gulasch\",\"gurke\",\"himbeer\",\"honig\",\"huhn\",\"h\u00e4hnchen\",\"jambalaya\",\"joghurt\",\"karotte\",\"kartoffel\",\"keule\",\"kirsch\",\"knacker\",\"knoblauch\",\"kn\u00f6del\",\"kompott\",\"kraut\",\"kr\u00e4uter\",\"kuchen\",\"k\u00e4se\",\"k\u00fcrbis\",\"lauch\",\"mandel\",\"milch\",\"mild\",\"mit\",\"mohn\",\"most\",\"m\u00f6hre\",\"natur\",\"nockerl\",\"nudel\",\"nuss\",\"nu\u00df\",\"obst\",\"oder\",\"olive\",\"paprika\",\"pfanne\",\"pfannkuchen\",\"pfeffer\",\"pikant\",\"pilz\",\"plunder\",\"p\u00fcree\",\"ragout\",\"rahm\",\"reis\",\"rind\",\"sahne\",\"salami\",\"salat\",\"salz\",\"sauer\",\"scharf\",\"schinken\",\"schnitte\",\"schnitzel\",\"schoko\",\"schupf\",\"schwein\",\"sellerie\",\"senf\",\"sosse\",\"so\u00dfe\",\"spargel\",\"sp\u00e4tzle\",\"speck\",\"spie\u00df\",\"spinat\",\"steak\",\"suppe\",\"s\u00fc\u00df\",\"tofu\",\"tomate\",\"topfen\",\"torte\",\"tr\u00fcffel\",\"und\",\"vanille\",\"vogerl\",\"vom\",\"wien\",\"wurst\",\"zucchini\",\"zum\",\"zur\",\"zwiebel\",\"\u00f6l\"],V=[\"almond\",\"and\",\"apple\",\"asparagus\",\"bacon\",\"baked\",\"ball\",\"bean\",\"beef\",\"berry\",\"bread\",\"breast\",\"broccoli\",\"bun\",\"butter\",\"cabbage\",\"cake\",\"caper\",\"carrot\",\"casserole\",\"cauliflower\",\"celery\",\"cheese\",\"cherry\",\"chicken\",\"chili\",\"choco\",\"chocolate\",\"cider\",\"cilantro\",\"coffee\",\"compote\",\"cream\",\"cucumber\",\"curd\",\"danish\",\"dessert\",\"dip\",\"dumpling\",\"egg\",\"eggplant\",\"filet\",\"fish\",\"for\",\"fried\",\"from\",\"fruit\",\"garlic\",\"goulash\",\"gratin\",\"ham\",\"herb\",\"honey\",\"hot\",\"ice\",\"jambalaya\",\"leek\",\"leg\",\"mash\",\"meat\",\"mexican\",\"mild\",\"milk\",\"mint\",\"mushroom\",\"mustard\",\"noodle\",\"nut\",\"oat\",\"oil\",\"olive\",\"onion\",\"or\",\"oven\",\"pan\",\"pancake\",\"pea\",\"pepper\",\"plain\",\"plate\",\"poppy\",\"pork\",\"potato\",\"pumpkin\",\"radish\",\"ragout\",\"raspberry\",\"rice\",\"roast\",\"roll\",\"salad\",\"salami\",\"salt\",\"sauce\",\"sausage\",\"shrimp\",\"skewer\",\"slice\",\"soup\",\"sour\",\"spice\",\"spicy\",\"spinach\",\"steak\",\"stew\",\"strawberr\",\"strawberry\",\"strudel\",\"sweet\",\"tart\",\"thyme\",\"to\",\"tofu\",\"tomat\",\"tomato\",\"truffle\",\"trukey\",\"turkey\",\"vanilla\",\"vegan\",\"vegetable\",\"vinegar\",\"wedge\",\"wing\",\"with\",\"wok\",\"yogurt\",\"zucchini\"];!function(){document.title=\"Kantine Weekly Menu\",document.querySelectorAll&&document.querySelectorAll('link[rel*=\"icon\"]').forEach(e=>e.remove());const e=document.createElement(\"link\");if(e.rel=\"icon\",e.type=\"image/png\",e.href=\"data:image/png;base64,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\",document.head.appendChild(e),!document.querySelector('link[href*=\"fonts.googleapis.com/css2?family=Inter\"]')){const e=document.createElement(\"link\");e.rel=\"stylesheet\",e.href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap\",document.head.appendChild(e)}if(!document.querySelector('link[href*=\"Material+Icons+Round\"]')){const e=document.createElement(\"link\");e.rel=\"stylesheet\",e.href=\"https://fonts.googleapis.com/icon?family=Material+Icons+Round\",document.head.appendChild(e)}document.body.innerHTML=`\\n
      \\n
      \\n
      \\n
      \\n \"Logo\"\\n
      \\n

      Kantinen \u00dcbersicht v1.6.10

      \\n
      \\n
      \\n
      \\n \\n \\n
      \\n \\n
      \\n
      \\n
      \\n \\n \\n \\n
      \\n
      \\n
      \\n
      \\n
      \\n \\n \\n \\n \\n \\n
      \\n person\\n \\n \\n
      \\n
      \\n
      \\n
      \\n\\n
      \\n
      \\n
      \\n

      Login

      \\n \\n
      \\n
      \\n
      \\n \\n \\n Deine offizielle Knapp Mitarbeiternummer.\\n
      \\n
      \\n \\n \\n Das Passwort f\u00fcr deinen Bessa Account.\\n
      \\n
      \\n
      \\n \\n
      \\n
      \\n
      \\n
      \\n\\n
      \\n
      \\n
      \\n

      Men\u00fcdaten aktualisieren

      \\n
      \\n
      \\n
      \\n
      \\n
      \\n
      \\n
      0%
      \\n
      \\n

      Initialisierung...

      \\n
      \\n
      \\n
      \\n\\n
      \\n
      \\n
      \\n

      Meine Highlights

      \\n \\n
      \\n
      \\n

      \\n Markiere Men\u00fcs automatisch, wenn sie diese Schlagw\u00f6rter enthalten.\\n

      \\n
      \\n \\n \\n
      \\n
      \\n
      \\n
      \\n
      \\n\\n
      \\n
      \\n
      \\n

      Bestellhistorie

      \\n \\n
      \\n
      \\n
      \\n

      Lade Historie...

      \\n
      \\n
      \\n
      \\n
      \\n
      \\n
      \\n
      \\n \\x3c!-- Dynamically populated --\\x3e\\n
      \\n
      \\n
      \\n
      \\n\\n
      \\n
      \\n
      \\n

      \ud83d\udce6 Versionen

      \\n \\n
      \\n
      \\n
      \\n Aktuell: v1.6.10\\n
      \\n
      \\n \\n
      \\n
      \\n

      Lade Versionen...

      \\n
      \\n
      \\n \\n bug_report Fehler melden\\n \\n \\n lightbulb Feature vorschlagen\\n \\n \\n
      \\n
      \\n
      \\n
      \\n\\n
      \\n
      \\n update\\n Gerade aktualisiert\\n
      \\n
      \\n
      \\n

      Lade Men\u00fcdaten...

      \\n
      \\n
      \\n
      \\n\\n
      \\n

      Jetzt Bessa Einfach! • Knapp-Kantine Wrapper • ${(new Date).getFullYear()} by Kaufi \ud83d\ude03\ud83d\udc4d mit Hilfe von KI \ud83e\udd16

      \\n
      \\n
      `}(),function(){const n=document.getElementById(\"btn-this-week\"),a=document.getElementById(\"btn-next-week\"),s=document.getElementById(\"btn-refresh\"),o=document.getElementById(\"theme-toggle\"),i=document.getElementById(\"btn-login-open\"),r=document.getElementById(\"btn-login-close\"),l=document.getElementById(\"btn-logout\"),g=document.getElementById(\"login-form\"),k=document.getElementById(\"login-modal\"),A=document.getElementById(\"btn-highlights\"),E=document.getElementById(\"highlights-modal\"),x=document.getElementById(\"btn-highlights-close\"),D=document.getElementById(\"btn-add-tag\"),C=document.getElementById(\"tag-input\"),O=document.getElementById(\"btn-history\"),M=document.getElementById(\"history-modal\"),T=document.getElementById(\"btn-history-close\");document.querySelectorAll(\".lang-btn\").forEach(e=>{e.addEventListener(\"click\",()=>{p=e.dataset.lang,localStorage.setItem(\"kantine_lang\",p),document.querySelectorAll(\".lang-btn\").forEach(e=>e.classList.remove(\"active\")),e.classList.add(\"active\"),j()})}),A&&A.addEventListener(\"click\",()=>{E.classList.remove(\"hidden\")}),x&&x.addEventListener(\"click\",()=>{E.classList.add(\"hidden\")}),O.addEventListener(\"click\",()=>{d?(M.classList.remove(\"hidden\"),async function(){const t=document.getElementById(\"history-loading\"),n=document.getElementById(\"history-content\"),a=document.getElementById(\"history-progress-fill\"),s=document.getElementById(\"history-progress-text\");let o=[];if(b)o=b;else{const e=localStorage.getItem(\"kantine_history_cache\");if(e)try{o=JSON.parse(e),b=o}catch(e){console.warn(\"History cache parse error\",e)}}o.length>0&&w(o);if(!d)return;0===o.length&&(n.innerHTML=\"\",t.classList.remove(\"hidden\"));a.style.width=\"0%\",s.textContent=o.length>0?\"Suche nach neuen Bestellungen...\":\"Lade Bestellhistorie...\",o.length>0&&t.classList.remove(\"hidden\");let i=o.length>0?`${e}/user/orders/?venue=591&ordering=-created&limit=5`:`${e}/user/orders/?venue=591&ordering=-created&limit=50`,r=[],l=0,c=0===o.length,m=!1;try{for(;i&&!m;){const e=await fetch(i,{headers:f(d)});if(!e.ok)throw new Error(`Fetch failed: ${e.status}`);const t=await e.json();t.count&&0===l&&(l=t.count);const n=t.results||[];for(const e of n){const t=o.findIndex(t=>t.id===e.id);if(!c&&-1!==t){const n=o[t];if(n.updated===e.updated&&n.order_state===e.order_state){m=!0;break}}r.push(e)}if(!m&&c)if(l>0){const e=Math.round(r.length/l*100);a.style.width=`${e}%`,s.textContent=`Lade Bestellung ${r.length} von ${l}...`}else s.textContent=`Lade Bestellung ${r.length}...`;else m||(s.textContent=`${r.length} neue/ge\u00e4nderte Bestellungen gefunden...`);i=m?null:t.next}if(r.length>0){const e=new Map(o.map(e=>[e.id,e]));for(const t of r)e.set(t.id,t);const t=Array.from(e.values());t.sort((e,t)=>new Date(t.created)-new Date(e.created)),b=t;try{localStorage.setItem(\"kantine_history_cache\",JSON.stringify(t))}catch(e){console.warn(\"History cache write error\",e)}w(b)}}catch(e){console.error(\"Error in history sync:\",e),0===o.length?n.innerHTML='

      Fehler beim Laden der Historie.

      ':F(\"Hintergrund-Synchronisation fehlgeschlagen\",\"error\")}finally{t.classList.add(\"hidden\")}}()):k.classList.remove(\"hidden\")}),T.addEventListener(\"click\",()=>{M.classList.add(\"hidden\")}),window.addEventListener(\"click\",e=>{e.target===M&&M.classList.add(\"hidden\"),e.target===E&&E.classList.add(\"hidden\")});const q=document.querySelector(\".version-tag\"),$=document.getElementById(\"version-modal\"),z=document.getElementById(\"btn-version-close\");q&&q.addEventListener(\"click\",e=>{e.preventDefault(),e.stopPropagation(),function(){const e=document.getElementById(\"version-modal\"),t=document.getElementById(\"version-list-container\"),n=document.getElementById(\"dev-mode-toggle\"),a=\"v1.6.10\";if(!e)return;e.classList.remove(\"hidden\");const s=document.getElementById(\"version-current\");s&&(s.textContent=a);const o=\"true\"===localStorage.getItem(\"kantine_dev_mode\");async function i(e){const s=n.checked;function o(e){if(!e||!e.length)return void(t.innerHTML='

      Keine Versionen gefunden.

      ');t.innerHTML='
        ';const n=t.querySelector(\".version-list\");e.forEach(e=>{const t=e.tag===a,s=H(e.tag,a),o=document.createElement(\"li\");o.className=\"version-item\"+(t?\" current\":\"\");let i=\"\";t?i='\u2713 Installiert':s&&(i='\u2b06 Neu!');let r=\"\";t||(r=`Installieren`),o.innerHTML=`\\n
        \\n ${e.tag}\\n ${i}\\n
        \\n ${r}\\n `,n.appendChild(o)})}t.innerHTML='

        Lade Versionen...

        ';try{const e=localStorage.getItem(\"kantine_version_cache\");let t=null;if(e)try{t=JSON.parse(e)}catch(e){}t&&t.devMode===s&&t.versions&&o(t.versions);const n=await K(s),a=JSON.stringify(n);a!==(t?JSON.stringify(t.versions):\"\")&&(localStorage.setItem(\"kantine_version_cache\",JSON.stringify({timestamp:Date.now(),devMode:s,versions:n})),o(n))}catch(e){t.innerHTML=`

        Fehler: ${e.message}

        `}}n.checked=o,i(!1),n.onchange=()=>{localStorage.setItem(\"kantine_dev_mode\",n.checked),localStorage.removeItem(\"kantine_version_cache\"),i(!0)}}()}),z&&z.addEventListener(\"click\",()=>{$.classList.add(\"hidden\")});const Q=document.getElementById(\"btn-clear-cache\");Q&&Q.addEventListener(\"click\",()=>{confirm(\"M\u00f6chtest du wirklich alle lokalen Daten (inkl. Login-Session, Cache und Einstellungen) l\u00f6schen? Die Seite wird danach neu geladen.\")&&(Object.keys(localStorage).forEach(e=>{e.startsWith(\"kantine_\")&&localStorage.removeItem(e)}),window.location.reload())}),window.addEventListener(\"click\",e=>{e.target===$&&$.classList.add(\"hidden\")}),D.addEventListener(\"click\",()=>{(function(e){if(e=e.trim().toLowerCase(),e&&!L.includes(e))return L.push(e),S(),!0;return!1})(C.value)&&(C.value=\"\",B())}),C.addEventListener(\"keypress\",e=>{\"Enter\"===e.key&&D.click()});const X=localStorage.getItem(\"theme\"),U=window.matchMedia(\"(prefers-color-scheme: dark)\").matches,G=o.querySelector(\".theme-icon\");\"dark\"===X||!X&&U?(document.documentElement.setAttribute(\"data-theme\",\"dark\"),G.textContent=\"dark_mode\"):(document.documentElement.setAttribute(\"data-theme\",\"light\"),G.textContent=\"light_mode\"),o.addEventListener(\"click\",()=>{const e=\"dark\"===document.documentElement.getAttribute(\"data-theme\")?\"light\":\"dark\";document.documentElement.setAttribute(\"data-theme\",e),localStorage.setItem(\"theme\",e),G.textContent=\"dark\"===e?\"dark_mode\":\"light_mode\"}),n.addEventListener(\"click\",()=>{\"this-week\"!==c&&(c=\"this-week\",n.classList.add(\"active\"),a.classList.remove(\"active\"),j())}),a.addEventListener(\"click\",()=>{a.classList.remove(\"new-week-available\"),\"next-week\"!==c&&(c=\"next-week\",a.classList.add(\"active\"),n.classList.remove(\"active\"),j())}),s.addEventListener(\"click\",()=>{d?N():k.classList.remove(\"hidden\")}),i.addEventListener(\"click\",()=>{k.classList.remove(\"hidden\"),document.getElementById(\"login-error\").classList.add(\"hidden\"),g.reset()}),r.addEventListener(\"click\",()=>{k.classList.add(\"hidden\")}),window.addEventListener(\"click\",e=>{e.target===k&&k.classList.add(\"hidden\")}),g.addEventListener(\"submit\",async n=>{n.preventDefault();const a=document.getElementById(\"employee-id\").value.trim(),s=document.getElementById(\"password\").value,o=document.getElementById(\"login-error\"),i=g.querySelector('button[type=\"submit\"]'),r=i.textContent;i.disabled=!0,i.textContent=\"Wird eingeloggt...\";try{const n=`knapp-${a}@bessa.app`,i=await fetch(`${e}/auth/login/`,{method:\"POST\",headers:f(t),body:JSON.stringify({email:n,password:s})}),r=await i.json();if(i.ok){d=r.key,m=a,localStorage.setItem(\"kantine_authToken\",r.key),localStorage.setItem(\"kantine_currentUser\",a);try{const t=await fetch(`${e}/auth/user/`,{headers:f(d)});if(t.ok){const e=await t.json();e.first_name&&localStorage.setItem(\"kantine_firstName\",e.first_name),e.last_name&&localStorage.setItem(\"kantine_lastName\",e.last_name)}}catch(e){console.error(\"Failed to fetch user info:\",e)}v(),k.classList.add(\"hidden\"),y(),g.reset(),I(),N()}else o.textContent=r.non_field_errors?.[0]||r.error||\"Login fehlgeschlagen\",o.classList.remove(\"hidden\")}catch(e){console.error(\"Login error:\",e),o.textContent=\"Ein Fehler ist aufgetreten\",o.classList.remove(\"hidden\")}finally{i.disabled=!1,i.textContent=r}}),l.addEventListener(\"click\",()=>{localStorage.removeItem(\"kantine_authToken\"),localStorage.removeItem(\"kantine_currentUser\"),localStorage.removeItem(\"kantine_firstName\"),localStorage.removeItem(\"kantine_lastName\"),d=null,m=null,u=new Map,h&&(clearInterval(h),h=null,console.log(\"Polling stopped\")),v(),j()})}(),v(),function(){const e=new Date,t=e.toISOString().split(\"T\")[0];let n=!1;for(const a of[...g]){const[s]=a.split(\"_\");let o=!1;if(s=t&&(o=!0)}o&&(g.delete(a),n=!0)}n&&k()}();(function(){try{const e=localStorage.getItem(D),t=localStorage.getItem(C);if(console.log(`[Cache] localStorage: key=${!!e} (${e?e.length:0} chars), ts=${t}`),e){i=JSON.parse(e),r=G(new Date),l=(new Date).getFullYear(),console.log(`[Cache] Parsed ${i.length} weeks:`,i.map(e=>`KW${e.weekNumber}/${e.year} (${(e.days||[]).length} days)`)),j(),z(),A(),t&&q(t);try{const e=new Set;i.forEach(t=>{(t.days||[]).forEach(t=>{(t.items||[]).forEach(t=>{let n=(t.description||\"\").replace(/\\s+/g,\" \").trim();n&&n.includes(\" / \")&&e.add(n)})})});const t=Array.from(e).join(\"\\n\\n\");console.log(\"=== GEFUNDENE MEN\u00dc-TEXTE (\"+e.size+\") ===\"),console.log(t)}catch(e){}return console.log(\"Loaded menu from cache\"),!0}}catch(e){console.warn(\"Failed to load cached menu:\",e)}return!1})()?(document.getElementById(\"loading\").classList.add(\"hidden\"),!function(){const e=localStorage.getItem(C);if(!e)return console.log(\"[Cache] No timestamp found\"),!1;const t=Date.now()-new Date(e).getTime(),n=Math.round(t/6e4);if(t>36e5)return console.log(`[Cache] Stale: ${n}min old (max 60)`),!1;const a=G(new Date),s=J(new Date),o=i.some(e=>e.weekNumber===a&&e.year===s&&e.days&&e.days.length>0);return console.log(`[Cache] Age: ${n}min, looking for KW${a}/${s}, found: ${o}`),o}()?(console.log(\"Cache stale or incomplete \u2013 refreshing from API\"),N()):console.log(\"Cache fresh & complete \u2013 skipping API refresh\")):N(),d&&I(),Q(),setInterval(Q,36e5),console.log(\"Kantine Wrapper loaded \u2705\")}();\n"; document.head.appendChild(sc); })(); +javascript:javascript:(function(){ if(window.__KANTINE_LOADED){alert('Kantine Wrapper already loaded!');return;} var s=document.createElement('style');s.textContent=':root { /* Premium Slate/Gray-Blue Palette - Light Mode */ --bg-body: #f1f5f9; /* Slate 100 */ --bg-card: #ffffff; --text-primary: #334155; /* Slate 700 */ --text-secondary: #64748b; --accent-color: #0f172a; /* Slate 900 (High contrast) */ --border-color: #cbd5e1; /* Slate 300 */ --banner-bg: #e2e8f0; --banner-text: #1e293b; --success-color: #059669; --error-color: #dc2626; --card-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.05), 0 2px 4px -2px rgb(0 0 0 / 0.05); --header-bg: rgba(255, 255, 255, 0.9); --header-border: 1px solid rgba(203, 213, 225, 0.6); } [data-theme="dark"] { /* Premium Slate/Gray-Blue Palette - Dark Mode */ --bg-body: #1e293b; /* Deep Slate Gray (Requested) */ --bg-card: #334155; /* Slate 700 */ --text-primary: #f8fafc; /* Slate 50 */ --text-secondary: #cbd5e1; /* Slate 300 */ --accent-color: #60a5fa; /* Blue 400 */ --border-color: #475569; /* Slate 600 */ --banner-bg: #475569; --banner-text: #e2e8f0; --header-bg: rgba(30, 41, 59, 0.9); --header-border: 1px solid rgba(71, 85, 105, 0.6); --card-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.4); } * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: \'Inter\', system-ui, -apple-system, sans-serif; background-color: var(--bg-body); color: var(--text-primary); transition: background-color 0.3s ease, color 0.3s ease; line-height: 1.5; -webkit-font-smoothing: antialiased; } /* Fix scrolling bug: Reset html/body styles from host page */ /* IMPORTANT: html must NOT have overflow set, or it creates a scroll container that breaks position: sticky */ html { height: auto !important; min-height: 100% !important; overflow: visible !important; position: static !important; margin: 0 !important; padding: 0 !important; } body { height: auto !important; min-height: 100% !important; overflow-x: clip !important; /* clip prevents horizontal overflow without breaking sticky */ overflow-y: visible !important; position: static !important; margin: 0 !important; padding: 0 !important; } /* Header */ .app-header { flex-shrink: 0; z-index: 100; backdrop-filter: blur(12px); background-color: var(--header-bg); border-bottom: var(--header-border); padding: 1rem 0; } .header-content { width: 100%; /* Full width */ padding: 0 2rem; /* Comfortable padding */ display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 1rem; } .brand { display: flex; align-items: center; gap: 0.75rem; } .brand-text { display: flex; flex-direction: column; } .brand h1 { font-size: 1.25rem; font-weight: 700; letter-spacing: -0.025em; margin-bottom: 0; } .subtitle { font-size: 0.85rem; color: var(--text-secondary); font-weight: 400; margin-left: 2px; } .logo-icon { font-size: 1.5rem; color: var(--accent-color); } /* Controls */ .controls { display: flex; align-items: center; gap: 1.5rem; justify-self: end; } /* Header Week Info (centered) */ .header-week-info { text-align: center; line-height: 1.3; } .header-center-wrapper { display: flex; flex-direction: row; align-items: center; gap: 1.5rem; justify-content: center; } .header-week-title { font-size: 1.1rem; font-weight: 600; color: var(--text-primary); } .header-week-subtitle { font-size: 0.85rem; color: var(--text-secondary); } /* Language Toggle (FR-100) */ .lang-toggle { display: inline-flex; gap: 0; border-radius: 6px; overflow: hidden; border: 1px solid var(--border-color); background: var(--bg-card); } .lang-btn { padding: 3px 10px; font-size: 0.7rem; font-weight: 600; letter-spacing: 0.03em; background: transparent; color: var(--text-secondary); border: none; cursor: pointer; transition: all 0.2s; } .lang-btn:hover { color: var(--text-primary); background: rgba(100, 116, 139, 0.1); } .lang-btn.active { background: var(--accent-color); color: white; } .nav-group { display: flex; background-color: var(--bg-card); border: 1px solid var(--border-color); padding: 0.25rem; border-radius: 8px; } .nav-btn { background: none; border: none; padding: 0.5rem 1rem; font-size: 0.875rem; font-weight: 500; color: var(--text-secondary); cursor: pointer; border-radius: 6px; transition: all 0.2s; display: flex; align-items: center; gap: 0.5rem; } .nav-btn:hover { color: var(--text-primary); background-color: rgba(100, 116, 139, 0.1); } .nav-btn.active { background-color: var(--accent-color); color: white; } /* Notification state for Next Week */ .nav-btn.new-week-available { animation: goldPulse 2s infinite; border-color: #f59e0b; color: var(--accent-color); } .nav-btn.new-week-available.active { color: white; } @keyframes goldPulse { 0% { box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.7); } 70% { box-shadow: 0 0 0 10px rgba(245, 158, 11, 0); } 100% { box-shadow: 0 0 0 0 rgba(245, 158, 11, 0); } } /* Badge for nav buttons (day count indicator) */ .nav-badge { background-color: var(--error-color); color: white; font-size: 0.75rem; font-weight: 600; padding: 0 6px; border-radius: 10px; min-width: 18px; height: 18px; display: inline-flex; align-items: center; justify-content: center; margin-left: 8px; gap: 3px; line-height: 1; } .nav-badge .orderable { color: #fff; font-weight: 800; } .nav-badge .separator { opacity: 0.6; font-weight: 400; } .nav-badge .total { opacity: 0.8; font-weight: 400; } .nav-btn.active .nav-badge { background: rgba(255, 255, 255, 0.3); } /* Primary style for Login Button to match header */ #btn-login-open { background-color: var(--accent-color); color: white; padding: 0.5rem 1.25rem; border-radius: 8px; font-weight: 600; letter-spacing: 0.025em; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } #btn-login-open:hover { background-color: #334155; /* Slightly lighter than slate-900 */ transform: translateY(-1px); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } /* User Badge Button (Login) */ .user-badge-btn { display: flex; align-items: center; gap: 8px; padding: 6px 12px; background: var(--bg-card); border: 1px solid var(--border-color); border-radius: 20px; font-size: 0.9rem; font-weight: 500; color: var(--text-primary); cursor: pointer; transition: all 0.2s; } .user-badge-btn:hover { background: rgba(100, 116, 139, 0.1); border-color: var(--accent-color); } .user-badge-btn .material-icons-round { font-size: 1.25rem; color: var(--accent-color); } .icon-btn { background: none; border: none; color: var(--text-primary); cursor: pointer; padding: 0.5rem; border-radius: 50%; transition: background-color 0.2s; display: flex; align-items: center; justify-content: center; } .icon-btn:hover { background-color: rgba(100, 116, 139, 0.1); } /* Refresh button animation */ #btn-refresh.refreshing .material-icons-round { animation: rotate 1s linear infinite; } @keyframes rotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } /* Progress Modal */ .progress-container { margin-bottom: 1.5rem; } .progress-bar { width: 100%; height: 8px; background-color: var(--border-color); border-radius: 4px; overflow: hidden; margin-bottom: 0.75rem; } .progress-fill { height: 100%; background: linear-gradient(90deg, var(--accent-color) 0%, #60a5fa 100%); width: 0%; transition: width 0.3s ease; border-radius: 4px; } .progress-percent { text-align: center; font-size: 1.5rem; font-weight: 700; color: var(--text-primary); margin-bottom: 0.5rem; } .progress-message { text-align: center; color: var(--text-secondary); font-size: 0.9rem; font-weight: 500; } .weekly-cost { background-color: rgba(59, 130, 246, 0.1); /* Blue tint */ color: var(--accent-color); padding: 0.4rem 0.8rem; border-radius: 8px; font-weight: 600; font-size: 0.9rem; display: flex; align-items: center; gap: 0.5rem; border: 1px solid rgba(59, 130, 246, 0.2); } .weekly-cost .material-icons-round { font-size: 18px; } /* Container - flex column, full width so child scrollbar is at edge */ .container { flex: 1; width: 100%; overflow: hidden; padding: 0 0 0 0; /* Only top padding, no horizontal so child fills width */ display: flex; flex-direction: column; } /* Add horizontal padding to direct children of container to maintain layout */ .container>*:not(.menu-grid) { padding-left: 2rem; padding-right: 2rem; } /* Banner */ .banner { background-color: var(--banner-bg); color: var(--banner-text); padding: 0.75rem 1rem; border-radius: 8px; display: flex; align-items: center; gap: 0.5rem; margin-bottom: 2rem; font-size: 0.875rem; font-weight: 500; border: 1px solid var(--border-color); max-width: fit-content; } /* User Badge */ .user-badge { display: flex; align-items: center; gap: 8px; padding: 6px 12px; background: var(--bg-card); /* Changed from --surface */ border: 1px solid var(--border-color); /* Changed from --border */ border-radius: 20px; font-size: 0.9rem; font-weight: 500; } .icon-btn-small { background: none; border: none; padding: 4px; cursor: pointer; color: var(--text-secondary); /* Changed from --text-muted */ display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: all 0.2s; } .icon-btn-small:hover { color: var(--error-color); /* Changed from --danger */ background: rgba(239, 68, 68, 0.1); } /* Modal */ .modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; z-index: 1000; transition: all 0.3s; } .modal.hidden { opacity: 0; pointer-events: none; } .modal-content { background: var(--bg-card); width: 90%; max-width: 400px; border-radius: 16px; box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); overflow: hidden; animation: modalSlide 0.3s ease-out; } /* History Modal specific */ .history-modal-content { max-width: 600px; max-height: 85vh; display: flex; flex-direction: column; } .history-modal-content .modal-body { overflow-y: auto; padding: 0; /* Padding is handled by inner elements */ } /* History Styles */ .history-year-group { margin-bottom: 16px; } .history-year-header { background: var(--bg-card); padding: 12px 20px; margin: 0; font-size: 1.2rem; font-weight: 700; color: var(--text-primary); border-bottom: 2px solid var(--border-color); position: sticky; top: 0; z-index: 12; } .history-month-group { border-bottom: 1px solid var(--border-color); } .history-month-header { display: flex; justify-content: space-between; align-items: center; padding: 14px 20px; margin: 0; font-size: 1.05rem; font-weight: 600; color: var(--text-primary); background: var(--bg-body); cursor: pointer; transition: background 0.2s; } .history-month-header:hover { background: var(--border-color); /* Slight hover effect */ } .history-month-summary { display: flex; align-items: center; gap: 12px; font-size: 0.95rem; color: var(--text-secondary); } .history-month-content { display: none; /* Collapsed by default */ background: var(--bg-card); } .history-month-group.open .history-month-content { display: block; /* Expanded when open class is present */ } .history-month-group.open .history-month-header .material-icons-round { transform: rotate(180deg); } .history-month-header .material-icons-round { transition: transform 0.3s; font-size: 20px; } .history-week-group { padding: 12px 20px; border-bottom: 1px dashed var(--border-color); } .history-week-group:last-child { border-bottom: none; } .history-week-header { display: flex; justify-content: space-between; align-items: center; font-size: 0.9rem; font-weight: 600; color: var(--text-secondary); margin-bottom: 10px; } .history-week-summary { font-size: 0.85rem; font-weight: 500; background: rgba(100, 116, 139, 0.1); padding: 4px 10px; border-radius: 12px; } .history-items { display: flex; flex-direction: column; gap: 8px; } .history-item { display: grid; grid-template-columns: 50px 1fr auto; align-items: center; gap: 12px; padding: 10px 12px; background: var(--bg-body); border-radius: 8px; border: 1px solid var(--border-color); } .history-item-date { font-size: 0.85rem; color: var(--text-secondary); font-weight: 500; } .history-item-details { display: flex; flex-direction: column; gap: 4px; } .history-item-name { font-size: 0.95rem; font-weight: 500; color: var(--text-primary); } .history-item-price { font-weight: 600; color: var(--text-primary); } .history-item-status { font-size: 0.8rem; font-weight: 600; color: var(--text-primary); text-transform: uppercase; letter-spacing: 0.5px; } .history-item-cancelled { opacity: 0.5; filter: grayscale(1); } .history-item-price-cancelled { text-decoration: line-through; color: var(--text-secondary); } @keyframes modalSlide { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } .modal-header { display: flex; align-items: center; justify-content: space-between; padding: 20px; border-bottom: 1px solid var(--border-color); } .modal-header h2 { margin: 0; font-size: 1.25rem; } .modal-body { padding: 20px; } #login-form { padding: 20px; } .form-group { margin-bottom: 20px; } .form-group label { display: block; margin-bottom: 6px; font-weight: 500; font-size: 0.9rem; } .form-group input { width: 100%; padding: 10px 12px; border: 1px solid var(--border-color); /* Changed from --border */ border-radius: 8px; background: var(--bg-body); /* Changed from --bg */ color: var(--text-primary); /* Changed from --text */ font-family: inherit; transition: border-color 0.2s; } .form-group input:focus { outline: none; border-color: var(--accent-color); /* Changed from --primary */ } .help-text { display: block; margin-top: 4px; color: var(--text-secondary); /* Changed from --text-muted */ font-size: 0.75rem; } .error-msg { margin-bottom: 16px; padding: 10px; background: rgba(239, 68, 68, 0.1); color: var(--error-color); /* Changed from --danger */ border-radius: 8px; font-size: 0.85rem; text-align: center; } .modal-actions { margin-top: 24px; } .btn-primary.wide { width: 100%; justify-content: center; } .hidden { display: none !important; } /* Menu Grid Container */ .menu-grid { display: flex; flex-direction: column; flex: 1; overflow: hidden; gap: 1rem; } .week-section { margin-bottom: 2rem; } .week-header { margin-bottom: 1.5rem; border-bottom: 1px solid var(--border-color); padding-bottom: 1rem; text-align: center; } .week-title { font-size: 1.75rem; font-weight: 700; color: var(--text-primary); } .week-range { color: var(--text-secondary); font-size: 0.9rem; margin-top: 0.25rem; } /* Full-viewport layout: header + scrollable content + footer */ #kantine-wrapper { display: flex; flex-direction: column; height: 100vh; height: 100dvh; /* Dynamic viewport height for mobile browsers */ overflow: hidden; } .days-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 0.75rem; flex: 1; overflow-y: auto; /* This is the scroll container at the window edge */ align-content: start; padding: 0 2rem 2rem 2rem; } /* Card */ .menu-card { background-color: var(--bg-card); border-radius: 12px; border: 1px solid var(--border-color); box-shadow: var(--card-shadow); overflow: clip; /* Clips scrolling content behind sticky header */ transition: box-shadow 0.2s ease; display: flex; flex-direction: column; } /* Past Day Styling - Target specific elements so ordered items can remain visible AND preserve sticky context */ /* We MUST apply filter/opacity to children, not the parent .menu-card, or else position: sticky breaks */ /* Header keeps fully opaque background to hide scrolling items, only grayscales */ .menu-card.past-day .card-header { filter: grayscale(0.8); transition: filter 0.3s; } /* Items become semi-transparent */ .menu-card.past-day .menu-item:not(.ordered) { opacity: 0.6; filter: grayscale(0.8); transition: opacity 0.3s, filter 0.3s; } .menu-card.past-day:hover .card-header { filter: grayscale(0.4); } .menu-card.past-day:hover .menu-item:not(.ordered) { opacity: 0.8; filter: grayscale(0.4); } /* Past ordered items get no special frame or shadow, but remain visually distinct by staying fully opaque (via the :not(.ordered) selector above) */ .menu-item.today-ordered { border: 2px solid #8b5cf6; box-shadow: 0 0 30px rgba(139, 92, 246, 0.6); border-radius: 8px; padding: 1rem; margin: 0 -1rem 1.5rem -1rem; background: var(--bg-card); position: relative; z-index: 5; animation: pulse-glow-strong 3s infinite; } @keyframes pulse-glow-strong { 0% { box-shadow: 0 0 20px rgba(139, 92, 246, 0.4); } 50% { box-shadow: 0 0 40px rgba(139, 92, 246, 0.8); } 100% { box-shadow: 0 0 20px rgba(139, 92, 246, 0.4); } } .menu-card:hover { box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); } .card-header { padding: 1rem 1.25rem; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: baseline; background-color: var(--bg-card); /* Removed border-radius: 12px 12px 0 0; .menu-card\'s overflow: clip will round the corners initially. When sticky at the top, it will be square and perfectly hide scrolling content! */ /* Sticky within .container scroll area */ position: sticky; top: 0; z-index: 90; } .card-body { padding: 1.25rem; display: grid; grid-template-rows: auto; align-content: start; } .day-name { font-size: 1.125rem; font-weight: 600; } .day-date { font-size: 0.875rem; color: var(--text-secondary); } .empty-state { color: var(--text-secondary); font-style: italic; text-align: center; padding: 1rem; } /* Menu Items */ .menu-item { margin-bottom: 1.5rem; padding-bottom: 1.5rem; border-bottom: 1px solid var(--border-color); } .menu-item:last-child { margin-bottom: 0; padding-bottom: 0; border-bottom: none; } .item-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 0.5rem; gap: 1rem; } .item-name { font-weight: 600; color: var(--text-primary); font-size: 1rem; } .item-price { font-weight: 700; color: var(--accent-color); white-space: nowrap; } .item-desc { font-size: 0.875rem; color: var(--text-secondary); line-height: 1.6; margin-bottom: 0.75rem; white-space: pre-wrap; } .badges { display: flex; gap: 0.5rem; margin-left: auto; } .item-status-row { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.75rem; } .badge { display: inline-flex; align-items: center; justify-content: center; height: 24px; font-size: 0.75rem; padding: 0 10px; border-radius: 4px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; line-height: normal; white-space: nowrap; } .badge.available { background-color: rgba(16, 185, 129, 0.1); /* Emerald 500 / 10% */ color: var(--success-color); border: 1px solid rgba(16, 185, 129, 0.2); } .badge.sold-out { background-color: rgba(239, 68, 68, 0.1); /* Red 500 / 10% */ color: var(--error-color); border: 1px solid rgba(239, 68, 68, 0.2); } .badge.ordered { background-color: rgba(139, 92, 246, 0.1); /* Violet 500 / 10% */ color: #8b5cf6; border: 1px solid rgba(139, 92, 246, 0.2); gap: 4px; } .badge.ordered .material-icons-round { font-size: 1rem; } /* Loading */ .loading-state { text-align: center; padding: 4rem; color: var(--text-secondary); } .spinner { width: 40px; height: 40px; border: 3px solid var(--border-color); border-top-color: var(--accent-color); border-radius: 50%; margin: 0 auto 1rem; animation: spin 1s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } /* Footer */ .app-footer { flex-shrink: 0; text-align: center; padding: 0.4rem 2rem; color: var(--text-secondary); font-size: 0.8rem; border-top: 1px solid var(--border-color); } /* === Order / Cancel Buttons (inline in status row) === */ .btn-order { display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px; border: none; border-radius: 6px; background: var(--success-color); color: white; font-size: 0.75rem; font-weight: 600; cursor: pointer; transition: all 0.2s ease; font-family: inherit; } .btn-order .material-icons-round { font-size: 16px; } .btn-order:hover:not(:disabled) { filter: brightness(1.15); transform: translateY(-1px); } .btn-order:disabled { opacity: 0.5; cursor: not-allowed; } .btn-order.loading { pointer-events: none; opacity: 0.6; } .btn-order-compact { padding: 2px 4px; gap: 0; } .btn-order-compact .material-icons-round { font-size: 16px; } .btn-cancel { display: inline-flex; align-items: center; justify-content: center; padding: 4px 6px; border: none; border-radius: 6px; background: var(--error-color); color: white; font-size: 0.75rem; cursor: pointer; transition: all 0.2s ease; font-family: inherit; } .btn-cancel .material-icons-round { font-size: 16px; } .btn-cancel:hover:not(:disabled) { filter: brightness(1.15); transform: translateY(-1px); } .btn-cancel:disabled { opacity: 0.5; cursor: not-allowed; } /* Past days: hide action buttons */ .past-day .item-actions { display: none; } /* Order count badge (for multi-orders) */ .order-count-badge { display: inline-flex; align-items: center; justify-content: center; background: rgba(255, 255, 255, 0.3); color: white; font-size: 0.65rem; font-weight: 700; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 8px; margin-left: 4px; line-height: 1; } /* === Toast Notifications === */ #toast-container { position: fixed; bottom: 20px; right: 20px; z-index: 10000; display: flex; flex-direction: column; gap: 8px; pointer-events: none; } .toast { display: flex; align-items: center; gap: 8px; padding: 10px 16px; border-radius: 8px; font-size: 0.85rem; font-weight: 500; font-family: \'Inter\', sans-serif; color: white; backdrop-filter: blur(10px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); pointer-events: auto; transform: translateX(120%); opacity: 0; transition: transform 0.3s ease, opacity 0.3s ease; } .toast.show { transform: translateX(0); opacity: 1; } .toast .material-icons-round { font-size: 18px; } .toast-success { background: rgba(5, 150, 105, 0.95); } .toast-error { background: rgba(220, 38, 38, 0.95); } .toast-info { background: rgba(59, 130, 246, 0.95); } /* === Mobile Responsiveness === */ @media (max-width: 600px) { .header-content { flex-direction: column; gap: 1rem; padding: 0.75rem; } .week-nav { width: 100%; justify-content: center; } .nav-pills { width: 100%; justify-content: space-between; } .nav-btn { flex: 1; justify-content: center; padding: 0.5rem; font-size: 0.85rem; } .days-grid { grid-template-columns: 1fr; /* Force single column */ } .main-content { padding: 1rem; } .week-title { font-size: 1.5rem; } /* Adjust toast position for mobile */ .toast-container { bottom: 1rem; right: 1rem; left: 1rem; /* Center on mobile */ width: auto; } .menu-card { margin-bottom: 1rem; } } /* === Flagging & Notification Styles === */ .btn-flag { display: inline-flex; align-items: center; justify-content: center; background: transparent; border: 1px solid var(--text-secondary); color: var(--text-secondary); border-radius: 6px; padding: 4px; cursor: pointer; transition: all 0.2s; margin-right: 0.5rem; width: 28px; height: 28px; } .btn-flag:hover { background: rgba(234, 179, 8, 0.1); /* Yellow-500 / 10% */ color: #eab308; border-color: #eab308; } .btn-flag.active { background: rgba(234, 179, 8, 0.1); color: #eab308; border-color: #eab308; } .btn-flag .material-icons-round { font-size: 1.1rem; } /* Flagged & Sold Out (Yellow Glow) */ .menu-item.flagged-sold-out { border: 1px solid #eab308; box-shadow: 0 0 10px rgba(234, 179, 8, 0.2); border-radius: 8px; padding: 1rem; margin: 0 -1rem 1.5rem -1rem; background: var(--bg-card); position: relative; z-index: 5; animation: yellow-pulse 3s infinite; } @keyframes yellow-pulse { 0% { box-shadow: 0 0 8px rgba(234, 179, 8, 0.2); } 50% { box-shadow: 0 0 16px rgba(234, 179, 8, 0.5); } 100% { box-shadow: 0 0 8px rgba(234, 179, 8, 0.2); } } /* Flagged & Available (Green Glow) */ .menu-item.flagged-available { border: 2px solid var(--success-color); box-shadow: 0 0 15px rgba(16, 185, 129, 0.3); border-radius: 8px; padding: 1rem; margin: 0 -1rem 1.5rem -1rem; background: var(--bg-card); position: relative; z-index: 5; animation: green-pulse 3s infinite; } @keyframes green-pulse { 0% { box-shadow: 0 0 10px rgba(16, 185, 129, 0.3); } 50% { box-shadow: 0 0 20px rgba(16, 185, 129, 0.6); } 100% { box-shadow: 0 0 10px rgba(16, 185, 129, 0.3); } } /* Day Header Badges */ .day-header-left { display: flex; align-items: center; gap: 0.75rem; } .menu-code-badge { font-size: 0.75rem; font-weight: 700; color: #8b5cf6; /* Violet 500 */ background-color: rgba(139, 92, 246, 0.15); border: 1px solid rgba(139, 92, 246, 0.3); padding: 2px 6px; border-radius: 6px; line-height: normal; display: inline-block; } /* Detailed Badge Colors */ .nav-badge.badge-violet { background-color: #8b5cf6; } .nav-badge.badge-green { background-color: var(--success-color); } .nav-badge.badge-red { background-color: var(--error-color); } .nav-badge.badge-blue { background-color: var(--accent-color); } /* Day Header Status Colors (User Request) */ .card-header.header-violet { background-color: var(--bg-card); background-image: linear-gradient(rgba(139, 92, 246, 0.15), rgba(139, 92, 246, 0.15)); border-bottom: 2px solid #8b5cf6; } .card-header.header-green { background-color: var(--bg-card); background-image: linear-gradient(rgba(16, 185, 129, 0.15), rgba(16, 185, 129, 0.15)); border-bottom: 2px solid var(--success-color); } .card-header.header-red { background-color: var(--bg-card); background-image: linear-gradient(rgba(239, 68, 68, 0.15), rgba(239, 68, 68, 0.15)); border-bottom: 2px solid var(--error-color); } .card-header.header-violet .day-name, .card-header.header-green .day-name, .card-header.header-red .day-name { font-weight: 700; color: var(--text-primary); /* Ensure text remains standard color */ } /* Update Icon */ .update-icon { display: inline-flex; align-items: center; justify-content: center; margin-left: 8px; background-color: rgba(16, 185, 129, 0.2); /* Green tint */ color: var(--success-color); border-radius: 50%; width: 24px; height: 24px; cursor: pointer; font-size: 14px; transition: all 0.2s; text-decoration: none; animation: pulse 2s infinite; } .update-icon:hover { background-color: var(--success-color); color: white; transform: scale(1.1); } @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.4); } 70% { box-shadow: 0 0 0 6px rgba(16, 185, 129, 0); } 100% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); } } /* Order Countdown */ #order-countdown { background: rgba(255, 255, 255, 0.1); padding: 0.25rem 0.75rem; border-radius: 99px; font-size: 0.85rem; display: flex; align-items: center; gap: 0.5rem; white-space: nowrap; border: 1px solid var(--border-color); } #order-countdown span { opacity: 0.7; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.5px; } #order-countdown.urgent { background: rgba(239, 68, 68, 0.2); border-color: rgba(239, 68, 68, 0.5); color: #ef4444; animation: pulse-red 2s infinite; } @keyframes pulse-red { 0% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4); } 70% { box-shadow: 0 0 0 6px rgba(239, 68, 68, 0); } 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); } } /* Smart Highlights (Blue Glow - matches today-ordered/flagged pattern) */ .menu-item.highlight-glow { border: 2px solid rgba(59, 130, 246, 0.7); box-shadow: 0 0 20px rgba(59, 130, 246, 0.4); border-radius: 8px; padding: 1rem; margin: 0 -1rem 1.5rem -1rem; background: var(--bg-card); position: relative; z-index: 5; animation: blue-pulse 3s infinite; } @keyframes blue-pulse { 0% { box-shadow: 0 0 15px rgba(59, 130, 246, 0.3); } 50% { box-shadow: 0 0 25px rgba(59, 130, 246, 0.6); } 100% { box-shadow: 0 0 15px rgba(59, 130, 246, 0.3); } } /* Nav Badge with Count */ .nav-badge.has-highlights { background-color: var(--bg-card); /* Neutral background */ color: var(--text-primary); border: 1px solid var(--border-color); padding: 2px 6px; } .nav-badge .highlight-count { color: #3b82f6; /* Blue 500 */ font-weight: 700; margin-left: 4px; } /* Tag Management Modal */ #tags-list { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 1rem; min-height: 50px; } /* Tag badges styled consistently with .badge (verfügbar/ausverkauft) */ .tag-badge { display: inline-flex; align-items: center; justify-content: center; height: 24px; font-size: 0.75rem; padding: 0 10px; border-radius: 4px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; line-height: normal; white-space: nowrap; background-color: rgba(59, 130, 246, 0.1); color: #3b82f6; border: 1px solid rgba(59, 130, 246, 0.2); gap: 4px; } .tag-remove { cursor: pointer; opacity: 0.7; font-size: 1.1em; line-height: 1; transition: all 0.2s; } .tag-remove:hover { opacity: 1; color: #ef4444; } .input-group { display: flex; gap: 0.5rem; } .input-group input { flex: 1; padding: 0.75rem; background: var(--bg-body); border: 1px solid var(--border-color); color: var(--text-primary); border-radius: 8px; font-family: inherit; } /* Add tag button - styled like .btn-order with nav-btn.active color */ #btn-add-tag { display: inline-flex; align-items: center; gap: 4px; padding: 0.5rem 1rem; border: none; border-radius: 6px; background: var(--accent-color); color: white; font-size: 0.8rem; font-weight: 600; cursor: pointer; transition: all 0.2s ease; font-family: inherit; white-space: nowrap; } #btn-add-tag:hover { filter: brightness(1.15); transform: translateY(-1px); } .matched-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; /* Space between tags and title */ margin-top: -5px; /* Pull closer to header */ } .tag-badge-small { display: inline-flex; align-items: center; font-size: 0.7rem; padding: 2px 8px; border-radius: 4px; background: rgba(59, 130, 246, 0.15); color: #60a5fa; border: 1px solid rgba(59, 130, 246, 0.3); font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; } [data-theme="light"] .tag-badge-small { background: rgba(37, 99, 235, 0.1); color: #2563eb; border: 1px solid rgba(37, 99, 235, 0.2); } /* Installer Changelog */ .changelog-container ul { padding-left: 1.5rem; margin: 0.5rem 0; } .changelog-container li { margin-bottom: 0.4rem; line-height: 1.5; } .changelog-container h3 { margin-top: 1.5rem; margin-bottom: 0.5rem; font-size: 1.1em; color: var(--accent-color); } /* === Version Menu === */ .version-tag { cursor: pointer; transition: opacity 0.2s ease, text-decoration 0.2s ease; } .version-tag:hover { opacity: 1 !important; text-decoration: underline; } .version-list { list-style: none; padding: 0; margin: 0; } .version-item { display: flex; justify-content: space-between; align-items: center; padding: 10px 14px; border-radius: 8px; margin-bottom: 4px; transition: background 0.2s; } .version-item:hover { background: rgba(100, 116, 139, 0.08); } .version-item.current { background: rgba(2, 154, 168, 0.1); border: 1px solid rgba(2, 154, 168, 0.25); } [data-theme="dark"] .version-item:hover { background: rgba(255, 255, 255, 0.05); } [data-theme="dark"] .version-item.current { background: rgba(96, 165, 250, 0.12); border: 1px solid rgba(96, 165, 250, 0.25); } .version-info { display: flex; align-items: center; gap: 10px; } .badge-current { font-size: 0.75rem; font-weight: 600; color: var(--success-color); padding: 2px 8px; border-radius: 4px; background: rgba(5, 150, 105, 0.1); } .badge-new { font-size: 0.75rem; font-weight: 600; color: #029aa8; padding: 2px 8px; border-radius: 4px; background: rgba(2, 154, 168, 0.1); } [data-theme="dark"] .badge-new { color: #60a5fa; background: rgba(96, 165, 250, 0.12); } .install-link { font-size: 0.8rem; font-weight: 500; padding: 4px 12px; border-radius: 6px; background: rgba(2, 154, 168, 0.1); color: #029aa8; text-decoration: none; border: 1px solid rgba(2, 154, 168, 0.25); transition: all 0.2s; white-space: nowrap; } .install-link:hover { background: rgba(2, 154, 168, 0.2); border-color: rgba(2, 154, 168, 0.4); } [data-theme="dark"] .install-link { color: #60a5fa; background: rgba(96, 165, 250, 0.12); border: 1px solid rgba(96, 165, 250, 0.25); } [data-theme="dark"] .install-link:hover { background: rgba(96, 165, 250, 0.2); border-color: rgba(96, 165, 250, 0.4); } .dev-toggle { padding: 10px 14px; border-radius: 8px; background: rgba(100, 116, 139, 0.05); border: 1px solid var(--border-color); } .dev-toggle input[type="checkbox"] { accent-color: #029aa8; width: 16px; height: 16px; } [data-theme="dark"] .dev-toggle input[type="checkbox"] { accent-color: #60a5fa; } ';document.head.appendChild(s); // Inject JS logic var sc=document.createElement('script'); sc.textContent="function showErrorModal(e,t,n,a){const s=\"error-modal\";let o=document.getElementById(s);o&&o.remove(),o=document.createElement(\"div\"),o.id=s,o.className=\"modal hidden\",o.innerHTML=`\\n
        \\n
        \\n

        \\n signal_wifi_off\\n ${e}\\n

        \\n
        \\n
        \\n

        ${t}

        \\n
        \\n \\n
        \\n
        \\n
        \\n `,document.body.appendChild(o),document.getElementById(\"btn-error-redirect\").addEventListener(\"click\",()=>{window.location.href=a}),requestAnimationFrame(()=>{o.classList.remove(\"hidden\")})}!function(){\"use strict\";if(window.__KANTINE_LOADED)return;window.__KANTINE_LOADED=!0;const e=\"https://api.bessa.app/v1\",t=\"c3418725e95a9f90e3645cbc846b4d67c7c66131\",n=591,a=\"TauNeutrino/kantine-overview\",s=`https://api.github.com/repos/${a}`,o=`https://htmlpreview.github.io/?https://github.com/${a}/blob`;let i=[],r=G(new Date),l=(new Date).getFullYear(),c=\"this-week\",d=localStorage.getItem(\"kantine_authToken\"),m=localStorage.getItem(\"kantine_currentUser\"),u=new Map,g=new Set(JSON.parse(localStorage.getItem(\"kantine_flags\")||\"[]\")),h=null,p=localStorage.getItem(\"kantine_lang\")||\"de\";function f(e){return{Authorization:`Token ${e||t}`,Accept:\"application/json\",\"Content-Type\":\"application/json\",\"X-Client-Version\":\"v1.6.11\"}}function v(){if(!d)try{const e=localStorage.getItem(\"AkitaStores\");if(e){const t=JSON.parse(e);t.auth&&t.auth.token&&(console.log(\"Found existing Bessa session!\"),d=t.auth.token,localStorage.setItem(\"kantine_authToken\",d),t.auth.user&&(m=t.auth.user.id||\"unknown\",localStorage.setItem(\"kantine_currentUser\",m),t.auth.user.firstName&&localStorage.setItem(\"kantine_firstName\",t.auth.user.firstName),t.auth.user.lastName&&localStorage.setItem(\"kantine_lastName\",t.auth.user.lastName)))}}catch(e){console.warn(\"Failed to parse AkitaStores:\",e)}d=localStorage.getItem(\"kantine_authToken\"),m=localStorage.getItem(\"kantine_currentUser\");const e=localStorage.getItem(\"kantine_firstName\"),t=document.getElementById(\"btn-login-open\"),n=document.getElementById(\"user-info\"),a=document.getElementById(\"user-id-display\");d?(t.classList.add(\"hidden\"),n.classList.remove(\"hidden\"),a.textContent=e||(m?`User ${m}`:\"Angemeldet\"),y()):(t.classList.remove(\"hidden\"),n.classList.add(\"hidden\"),a.textContent=\"\"),j()}async function y(){if(d)try{const t=await fetch(`${e}/user/orders/?venue=591&ordering=-created&limit=50`,{headers:f(d)}),n=await t.json();if(t.ok){u=new Map;const e=n.results||[];for(const t of e){if(9===t.order_state)continue;const e=t.date.split(\"T\")[0];for(const n of t.items||[]){const a=`${e}_${n.article}`;u.has(a)||u.set(a,[]),u.get(a).push(t.id)}}console.log(`Fetched ${e.length} orders, mapped active ones.`),j(),z()}}catch(e){console.error(\"Error fetching orders:\",e)}}let b=null;function w(e){const t=document.getElementById(\"history-content\");if(!e||0===e.length)return void(t.innerHTML='

        Keine Bestellungen gefunden.

        ');const n={};e.forEach(e=>{const t=new Date(e.date),a=t.getFullYear(),s=t.getMonth(),o=`${a}-${s.toString().padStart(2,\"0\")}`,i=t.toLocaleString(\"de-AT\",{month:\"long\"}),r=G(t);n[a]||(n[a]={year:a,months:{}}),n[a].months[o]||(n[a].months[o]={name:i,year:a,monthIndex:s,count:0,total:0,weeks:{}}),n[a].months[o].weeks[r]||(n[a].months[o].weeks[r]={label:`KW ${r}`,items:[],count:0,total:0});(e.items||[]).forEach(t=>{const s=parseFloat(t.price||e.total||0);n[a].months[o].weeks[r].items.push({date:e.date,name:t.name||\"Men\u00fc\",price:s,state:e.order_state}),9!==e.order_state&&(n[a].months[o].weeks[r].count++,n[a].months[o].weeks[r].total+=s,n[a].months[o].count++,n[a].months[o].total+=s)})});const a=Object.keys(n).sort((e,t)=>t-e);let s=\"\";a.forEach(e=>{const t=n[e];s+=`
        \\n

        ${t.year}

        `;Object.keys(t.months).sort((e,t)=>t.localeCompare(e)).forEach(e=>{const n=t.months[e];s+=`
        \\n
        \\n
        \\n ${n.name}\\n
        \\n ${n.count} Bestellungen • \u20ac${n.total.toFixed(2)}\\n
        \\n
        \\n expand_more\\n
        \\n
        `;Object.keys(n.weeks).sort((e,t)=>parseInt(t)-parseInt(e)).forEach(e=>{const t=n.weeks[e];s+=`
        \\n
        \\n ${t.label}\\n ${t.count} Bestellungen • \u20ac${t.total.toFixed(2)}\\n
        `,t.items.forEach(e=>{const t=new Date(e.date).toLocaleDateString(\"de-AT\",{weekday:\"short\",day:\"2-digit\",month:\"2-digit\"});let n=\"\";n=9===e.state?'Storniert':8===e.state?'Abgeschlossen':'\u00dcbertragen',s+=`\\n
        \\n
        ${t}
        \\n
        \\n ${W(e.name)}\\n
        ${n}
        \\n
        \\n
        \u20ac${e.price.toFixed(2)}
        \\n
        `}),s+=\"
        \"}),s+=\"
        \"}),s+=\"
        \"}),t.innerHTML=s;t.querySelectorAll(\".history-month-header\").forEach(e=>{e.addEventListener(\"click\",()=>{const t=e.parentElement;t.classList.contains(\"open\")?(t.classList.remove(\"open\"),e.setAttribute(\"aria-expanded\",\"false\")):(t.classList.add(\"open\"),e.setAttribute(\"aria-expanded\",\"true\"))})})}function k(){localStorage.setItem(\"kantine_flags\",JSON.stringify([...g]))}function A(){const e=document.getElementById(\"alarm-bell\"),t=document.getElementById(\"alarm-bell-icon\");if(!e||!t)return;if(0===g.size)return e.classList.add(\"hidden\"),e.style.display=\"none\",t.style.color=\"var(--text-secondary)\",void(t.style.textShadow=\"none\");e.classList.remove(\"hidden\"),e.style.display=\"inline-flex\";let n=!1;for(const e of i)if(e.days){for(const t of e.days)if(t.items){for(const e of t.items)if(e.available&&g.has(e.id)){n=!0;break}if(n)break}if(n)break}let a=localStorage.getItem(\"kantine_last_checked\"),s=\"gerade eben\";a||(a=(new Date).toISOString(),localStorage.setItem(\"kantine_last_checked\",a));s=$(new Date(a)),e.title=`Zuletzt gepr\u00fcft: ${s}`,n?(t.style.color=\"#10b981\",t.style.textShadow=\"0 0 10px rgba(16, 185, 129, 0.4)\"):(t.style.color=\"#f59e0b\",t.style.textShadow=\"0 0 10px rgba(245, 158, 11, 0.4)\")}function E(n,a,s,o){const r=`${n}_${a}`;let l=!1;g.has(r)?(g.delete(r),F(`Flag entfernt f\u00fcr ${s}`,\"success\")):(g.add(r),l=!0,F(`Benachrichtigung aktiviert f\u00fcr ${s}`,\"success\"),\"default\"===Notification.permission&&Notification.requestPermission()),k(),A(),j(),l&&async function(){if(0===g.size)return;const n=d||t,a=new Set;for(const e of g){const[t]=e.split(\"_\");a.add(t)}let s=!1;for(const t of a)try{const a=await fetch(`${e}/venues/591/menu/7/${t}/`,{headers:f(n)});if(!a.ok)continue;const o=(await a.json()).results||[];let r=[];for(const e of o)e.items&&Array.isArray(e.items)&&(r=r.concat(e.items));for(let e of i){if(!e.days)continue;let n=e.days.find(e=>e.date===t);n&&(n.items=r.map(e=>{const n=!1===e.amount_tracking,a=parseInt(e.available_amount)>0;return{id:`${t}_${e.id}`,articleId:e.id,name:e.name||\"Unknown\",description:e.description||\"\",price:parseFloat(e.price)||0,available:n||a,availableAmount:parseInt(e.available_amount)||0,amountTracking:!1!==e.amount_tracking}}),s=!0)}}catch(e){console.error(\"Error refreshing flag date\",t,e)}s&&(O(),q((new Date).toISOString()),A(),j())}()}function I(){h||d&&(h=setInterval(()=>async function(){if(0===g.size||!d)return;console.log(`Polling ${g.size} flagged items...`);for(const t of g){const[n,a]=t.split(\"_\"),s=parseInt(a);try{const t=await fetch(`${e}/venues/591/menu/7/${n}/`,{headers:f(d)});if(!t.ok)continue;const a=(await t.json()).results||[];let o=null;for(const e of a)if(e.items&&(o=e.items.find(e=>e.id===s||e.article===s),o))break;if(o){if(!1===o.amount_tracking||parseInt(o.available_amount)>0){const e=o.name||\"Unbekannt\";F(`${e} ist jetzt verf\u00fcgbar!`,\"success\"),\"granted\"===Notification.permission&&new Notification(\"Kantine Wrapper\",{body:`${e} ist jetzt verf\u00fcgbar!`,icon:\"\ud83c\udf7d\ufe0f\"}),N()}}}catch(e){console.error(`Poll error for ${t}:`,e),await new Promise(e=>setTimeout(e,200))}}localStorage.setItem(\"kantine_last_checked\",(new Date).toISOString()),A()}(),3e5),console.log(\"Polling started (every 5 min)\"))}let L=JSON.parse(localStorage.getItem(\"kantine_highlightTags\")||\"[]\");function S(){localStorage.setItem(\"kantine_highlightTags\",JSON.stringify(L)),j(),z()}function B(){const e=document.getElementById(\"tags-list\");e.innerHTML=\"\",L.forEach(t=>{const n=document.createElement(\"span\");n.className=\"tag-badge\",n.innerHTML=`${t} ×`,e.appendChild(n)}),e.querySelectorAll(\".tag-remove\").forEach(e=>{e.addEventListener(\"click\",e=>{var t;t=e.target.dataset.tag,L=L.filter(e=>e!==t),S(),B()})})}function x(e){return e?(e=e.toLowerCase(),L.filter(t=>e.includes(t))):[]}const D=\"kantine_menuCache\",C=\"kantine_menuCacheTs\";function O(){try{localStorage.setItem(D,JSON.stringify(i)),localStorage.setItem(C,(new Date).toISOString())}catch(e){console.warn(\"Failed to cache menu data:\",e)}}async function N(){const n=document.getElementById(\"loading\"),a=document.getElementById(\"progress-modal\"),s=document.getElementById(\"progress-fill\"),o=document.getElementById(\"progress-percent\"),c=document.getElementById(\"progress-message\");n.classList.remove(\"hidden\");const m=d||t;try{a.classList.remove(\"hidden\"),c.textContent=\"Hole verf\u00fcgbare Daten...\",s.style.width=\"0%\",o.textContent=\"0%\";const t=await fetch(`${e}/venues/591/menu/dates/`,{headers:f(m)});if(!t.ok)throw new Error(`Failed to fetch dates: ${t.status}`);let n=(await t.json()).results||[];const d=new Date;d.setDate(d.getDate()-7);const u=d.toISOString().split(\"T\")[0];n=n.filter(e=>e.date>=u).sort((e,t)=>e.date.localeCompare(t.date)).slice(0,30);const g=n.length;c.textContent=`${g} Tage gefunden. Lade Details...`;const h=[];let p=0;for(const t of n){const n=t.date,a=Math.round((p+1)/g*100);s.style.width=`${a}%`,o.textContent=`${a}%`,c.textContent=`Lade Men\u00fc f\u00fcr ${n}...`;try{const a=await fetch(`${e}/venues/591/menu/7/${n}/`,{headers:f(m)});if(a.ok){const e=await a.json();0===p&&console.log(\"[Kantine Debug] Raw API response for\",n,\":\",JSON.stringify(e).substring(0,2e3));const s=e.results||[];let o=[];for(const e of s)e.items&&Array.isArray(e.items)&&(o=o.concat(e.items));o.length>0&&(0===p&&(console.log(\"[Kantine Debug] First item keys:\",Object.keys(o[0])),console.log(\"[Kantine Debug] First item:\",JSON.stringify(o[0]).substring(0,500))),h.push({date:n,menu_items:o,orders:t.orders||[]}))}}catch(e){console.error(`Failed to fetch details for ${n}:`,e)}p++,await new Promise(e=>setTimeout(e,100))}const y=new Map;i&&i.length>0&&i.forEach(e=>{const t=`${e.year}-${e.weekNumber}`;try{y.set(t,{year:e.year,weekNumber:e.weekNumber,days:e.days?e.days.map(e=>({...e,items:e.items?[...e.items]:[]})):[]})}catch(e){console.warn(\"Error hydrating week:\",e)}});for(const e of h){const t=new Date(e.date),n=G(t),a=J(t),s=`${a}-${n}`;y.has(s)||y.set(s,{year:a,weekNumber:n,days:[]});const o=y.get(s),i=t.toLocaleDateString(\"en-US\",{weekday:\"long\"}),r=new Date(e.date);r.setHours(10,0,0,0);const l={date:e.date,weekday:i,orderCutoff:r.toISOString(),items:e.menu_items.map(t=>{const n=!1===t.amount_tracking,a=parseInt(t.available_amount)>0;return{id:`${e.date}_${t.id}`,articleId:t.id,name:t.name||\"Unknown\",description:t.description||\"\",price:parseFloat(t.price)||0,available:n||a,availableAmount:parseInt(t.available_amount)||0,amountTracking:!1!==t.amount_tracking}})},c=o.days.findIndex(t=>t.date===e.date);c>=0?o.days[c]=l:o.days.push(l)}i=Array.from(y.values()).sort((e,t)=>e.year!==t.year?e.year-t.year:e.weekNumber-t.weekNumber),i.forEach(e=>{e.days&&e.days.sort((e,t)=>e.date.localeCompare(t.date))}),O(),q((new Date).toISOString()),r=G(new Date),l=(new Date).getFullYear(),v(),j(),z(),A(),c.textContent=\"Fertig!\",setTimeout(()=>a.classList.add(\"hidden\"),500)}catch(e){console.error(\"Error fetching menu:\",e),a.classList.add(\"hidden\"),showErrorModal(\"Keine Verbindung\",`Die Men\u00fcdaten konnten nicht geladen werden. M\u00f6glicherweise besteht keine Verbindung zur API oder zur Bessa-Webseite.

        ${e.message}`,\"Zur Original-Seite\",\"https://web.bessa.app/knapp-kantine\")}finally{n.classList.add(\"hidden\")}}let M=null,T=null;function q(e){const t=document.getElementById(\"last-updated-subtitle\");if(e){M=e,localStorage.setItem(\"kantine_last_updated\",e),localStorage.setItem(\"kantine_last_checked\",e);try{const n=new Date(e),a=n.toLocaleTimeString(\"de-DE\",{hour:\"2-digit\",minute:\"2-digit\"}),s=n.toLocaleDateString(\"de-DE\",{day:\"2-digit\",month:\"2-digit\"}),o=$(n);t.textContent=`Aktualisiert: ${s} ${a} (${o})`}catch(e){t.textContent=\"\"}T||(T=setInterval(()=>{M&&(q(M),A())},6e4))}}function $(e){const t=Date.now()-e.getTime(),n=Math.floor(t/6e4);if(n<1)return\"gerade eben\";if(1===n)return\"vor 1 min.\";if(n<60)return`vor ${n} min.`;const a=Math.floor(n/60);return 1===a?\"vor 1 Std.\":`vor ${a} Std.`}function F(e,t=\"info\"){let n=document.getElementById(\"toast-container\");n||(n=document.createElement(\"div\"),n.id=\"toast-container\",document.body.appendChild(n));const a=document.createElement(\"div\");a.className=`toast toast-${t}`;const s=\"success\"===t?\"check_circle\":\"error\"===t?\"error\":\"info\";a.innerHTML=`${s}${e}`,n.appendChild(a),requestAnimationFrame(()=>a.classList.add(\"show\")),setTimeout(()=>{a.classList.remove(\"show\"),setTimeout(()=>a.remove(),300)},3e3)}function z(){const e=document.getElementById(\"btn-next-week\");let t=r+1,n=l;t>52&&(t=1,n++);const a=i.find(e=>e.weekNumber===t&&e.year===n);let s=0,o=0,c=0,d=0;a&&a.days&&a.days.forEach(e=>{if(e.items&&e.items.length>0){s++;const t=e.items.some(e=>e.available);t&&o++;let n=!1;e.items.forEach(t=>{const a=t.articleId||parseInt(t.id.split(\"_\")[1]),s=`${e.date}_${a}`;u.has(s)&&u.get(s).length>0&&(n=!0)}),n&&c++,t&&!n&&d++}});let m=e.querySelector(\".nav-badge\");if(s>0){m||(m=document.createElement(\"span\"),m.className=\"nav-badge\",e.appendChild(m)),m.title=`${c} bestellt / ${o} bestellbar / ${s} gesamt`,m.innerHTML=`${c}/${o}/${s}`,m.classList.remove(\"badge-violet\",\"badge-green\",\"badge-red\",\"badge-blue\"),c>0&&0===d?m.classList.add(\"badge-violet\"):d>0?m.classList.add(\"badge-green\"):0===o?m.classList.add(\"badge-red\"):m.classList.add(\"badge-blue\");let i=0;if(a&&a.days&&a.days.forEach(e=>{e.items.forEach(e=>{const t=x(e.name),n=x(e.description);(t.length>0||n.length>0)&&i++})}),i>0&&(m.innerHTML+=`(${i})`,m.title+=` \u2022 ${i} Highlights gefunden`,m.classList.add(\"has-highlights\")),0===c){e.classList.add(\"new-week-available\");const a=`kantine_notified_nextweek_${n}_${t}`;localStorage.getItem(a)||(localStorage.setItem(a,\"true\"),F(\"Neue Men\u00fcdaten f\u00fcr n\u00e4chste Woche verf\u00fcgbar!\",\"info\"))}else e.classList.remove(\"new-week-available\")}else m&&m.remove()}function j(){const t=document.getElementById(\"menu-container\");if(!t)return;t.innerHTML=\"\";let a=r,s=l;\"next-week\"===c&&(a++,a>52&&(a=1,s++));const o=i.flatMap(e=>e.days||[]).filter(e=>{const t=new Date(e.date);return G(t)===a&&J(t)===s});if(0===o.length)return t.innerHTML=`\\n
        \\n

        Keine Men\u00fcdaten f\u00fcr KW ${a} (${s}) verf\u00fcgbar.

        \\n Versuchen Sie eine andere Woche oder schauen Sie sp\u00e4ter vorbei.\\n
        `,void document.getElementById(\"weekly-cost-display\").classList.add(\"hidden\");!function(e){let t=0;e&&e.length>0&&e.forEach(e=>{e.items&&e.items.forEach(n=>{const a=n.articleId||parseInt(n.id.split(\"_\")[1]),s=`${e.date}_${a}`,o=u.get(s)||[];o.length>0&&(t+=n.price*o.length)})});const n=document.getElementById(\"weekly-cost-display\");t>0?(n.innerHTML=`shopping_bag Gesamt: ${t.toFixed(2).replace(\".\",\",\")} \u20ac`,n.classList.remove(\"hidden\")):n.classList.add(\"hidden\")}(o);const m=document.getElementById(\"header-week-info\"),h=\"this-week\"===c?\"Diese Woche\":\"N\u00e4chste Woche\";m.innerHTML=`\\n
        ${h}
        \\n
        Week ${a} \u2022 ${s}
        `;const v=document.createElement(\"div\");v.className=\"days-grid\",o.sort((e,t)=>e.date.localeCompare(t.date));o.filter(e=>{const t=new Date(e.date).getDay();return 0!==t&&6!==t}).forEach(t=>{const a=function(t){if(!t.items||0===t.items.length)return null;const a=document.createElement(\"div\");a.className=\"menu-card\";const s=new Date,o=new Date(t.date);let i=!1;if(t.orderCutoff)i=s>=new Date(t.orderCutoff);else{const e=new Date;e.setHours(0,0,0,0);const n=new Date(t.date);n.setHours(0,0,0,0),i=n{const n=e.articleId||parseInt(e.id.split(\"_\")[1]),a=`${t.date}_${n}`,s=(u.get(a)||[]).length;if(s>0){const t=e.name.match(/([M][1-9][Ff]?)/);if(t){let e=t[1];s>1&&(e+=\"+\"),r.push(e)}}});const l=document.createElement(\"div\");l.className=\"card-header\";const c=o.toLocaleDateString(\"de-DE\",{day:\"2-digit\",month:\"2-digit\"}),m=r.map(e=>`${e}`).join(\"\");let h=\"\";const v=t.items&&t.items.some(e=>{const n=e.articleId||parseInt(e.id.split(\"_\")[1]),a=`${t.date}_${n}`;return u.has(a)&&u.get(a).length>0}),w=t.items&&t.items.some(e=>e.available);h=v?\"header-violet\":w&&!i?\"header-green\":\"header-red\";h&&l.classList.add(h);l.innerHTML=`\\n
        \\n ${k=t.weekday,{Monday:\"Montag\",Tuesday:\"Dienstag\",Wednesday:\"Mittwoch\",Thursday:\"Donnerstag\",Friday:\"Freitag\",Saturday:\"Samstag\",Sunday:\"Sonntag\"}[k]||k}\\n
        ${m}
        \\n
        \\n ${c}`,a.appendChild(l);var k;const A=document.createElement(\"div\");A.className=\"card-body\";const I=(new Date).toISOString().split(\"T\")[0],L=t.date===I,S=[...t.items].sort((e,n)=>{if(L){const a=e.articleId||parseInt(e.id.split(\"_\")[1]),s=n.articleId||parseInt(n.id.split(\"_\")[1]),o=u.has(`${t.date}_${a}`),i=u.has(`${t.date}_${s}`);if(o&&!i)return-1;if(!o&&i)return 1}return e.name.localeCompare(n.name)});return S.forEach(a=>{const o=document.createElement(\"div\");o.className=\"menu-item\";const r=a.articleId||parseInt(a.id.split(\"_\")[1]),l=`${t.date}_${r}`,c=(u.get(l)||[]).length;let m=\"\";m=a.available?a.amountTracking?`Verf\u00fcgbar (${a.availableAmount})`:'Verf\u00fcgbar':'Ausverkauft';let h=\"\";if(c>0){h=`check_circle Bestellt${c>1?`${c}`:\"\"}`,o.classList.add(\"ordered\"),new Date(t.date).toDateString()===s.toDateString()&&o.classList.add(\"today-ordered\")}const v=`${t.date}_${r}`,w=g.has(v);w&&o.classList.add(a.available?\"flagged-available\":\"flagged-sold-out\");const k=[...new Set([...x(a.name),...x(a.description)])];k.length>0&&o.classList.add(\"highlight-glow\");let I=\"\",L=\"\",S=\"\";if(d&&!i){const e=w?\"notifications_active\":\"notifications_none\",n=w?\"btn-flag active\":\"btn-flag\",s=w?\"Benachrichtigung deaktivieren\":\"Benachrichtigen wenn verf\u00fcgbar\";if(a.available&&!w||(S=``),a.available&&(I=c>0?``:``),c>0){const e=1===c?\"close\":\"remove\",n=1===c?\"Bestellung stornieren\":\"Eine Bestellung stornieren\";L=``}}let B=\"\";if(k.length>0){B=`
        ${k.map(e=>`star${W(e)}`).join(\"\")}
        `}o.innerHTML=`\\n
        \\n ${W(a.name)}\\n ${a.price.toFixed(2)} \u20ac\\n
        \\n
        \\n ${h}\\n ${L}\\n ${I}\\n ${S}\\n
        ${m}
        \\n
        \\n ${B}\\n

        ${W(function(e){if(\"all\"===p)return e||\"\";const t=function(e){if(!e)return{de:\"\",en:\"\",raw:\"\"};let t=e.replace(/(?:\\(|(?:\\/|\\s|^))([A-Z,]+)\\)\\s*(?=\\S)(?!\\s*\\/)/g,\"($1)\\n\u2022 \");t.startsWith(\"\u2022 \")||(t=\"\u2022 \"+t);function n(e){let t=0,n=0;return e.forEach(e=>{const a=e.toLowerCase().replace(/[^a-z\u00e4\u00f6\u00fc\u00df]/g,\"\");if(a){let s=0,o=0;P.includes(a)?s=a.length:P.forEach(e=>{a.includes(e)&&e.length>s&&(s=e.length)}),V.includes(a)?o=a.length:V.forEach(e=>{a.includes(e)&&e.length>o&&(o=e.length)}),s>0&&(t+=s/a.length),o>0&&(n+=o/a.length),/^[A-Z\u00c4\u00d6\u00dc]/.test(e)&&(t+=.5)}}),{de:t,en:n}}function a(e){const t=e.trim().split(/\\s+/);if(t.length<2)return{enPart:e,nextDe:\"\"};let a=-1,s=-9999;for(let e=1;er.de||r.en>0,g=l.de+d>l.en;u&&g&&m>s&&(s=m,a=e)}return-1!==a?{enPart:t.slice(0,a).join(\" \"),nextDe:t.slice(a).join(\" \")}:{enPart:e,nextDe:\"\"}}const s=/(.*?)(?:\\(|(?:\\/|\\s|^))([A-Z,]+)\\)\\s*(?!\\s*[/])/g;let o;const i=[];let r=0;for(;null!==(o=s.exec(e));)o.index>r&&i.push(e.substring(r,o.index).trim()),i.push(o[0].trim()),r=s.lastIndex;r=2){const e=i[0].trim();let t=i.slice(1).join(\" / \").trim();const n=a(t);if(n.nextDe){l.push(e+s),c.push(n.enPart+s);const t=n.nextDe+s;l.push(t),c.push(t)}else{const n=t+s,a=e.includes(s.trim())?e:e+s;l.push(a),c.push(n)}}else{const e=a(n);e.nextDe?(c.push(e.enPart+s),l.push(e.nextDe+s)):(l.push(n+s),c.push(n+s))}}let d=l.join(\"\\n\u2022 \");l.length>0&&!d.startsWith(\"\u2022 \")&&(d=\"\u2022 \"+d);let m=c.join(\"\\n\u2022 \");c.length>0&&!m.startsWith(\"\u2022 \")&&(m=\"\u2022 \"+m);return{de:d,en:m,raw:t}}(e);return\"en\"===p?t.en||t.raw:t.de||t.raw}(a.description))}

        `;const D=o.querySelector(\".btn-order\");D&&D.addEventListener(\"click\",t=>{t.stopPropagation();const a=t.currentTarget;a.disabled=!0,a.classList.add(\"loading\"),async function(t,a,s,o,i){if(d)try{const r=await fetch(`${e}/auth/user/`,{headers:f(d)});if(!r.ok)return void F(\"Fehler: Benutzerdaten konnten nicht geladen werden\",\"error\");const l=await r.json(),c=(new Date).toISOString(),m={uuid:crypto.randomUUID(),created:c,updated:c,order_type:7,items:[{article:a,course_group:null,modifiers:[],uuid:crypto.randomUUID(),name:s,description:i||\"\",price:String(parseFloat(o)),amount:1,vat:\"10.00\",comment:\"\"}],table:null,total:parseFloat(o),tip:0,currency:\"EUR\",venue:n,states:[],order_state:1,date:`${t}T10:30:00Z`,payment_method:\"payroll\",customer:{first_name:l.first_name,last_name:l.last_name,email:l.email,newsletter:!1},preorder:!0,delivery_fee:0,cash_box_table_name:null,take_away:!1},u=await fetch(`${e}/user/orders/`,{method:\"POST\",headers:f(d),body:JSON.stringify(m)});if(u.ok||201===u.status)F(`Bestellt: ${s}`,\"success\"),b=null,await y();else{const e=await u.json();F(`Fehler: ${e.detail||e.non_field_errors?.[0]||\"Bestellung fehlgeschlagen\"}`,\"error\")}}catch(e){console.error(\"Order error:\",e),F(\"Netzwerkfehler bei Bestellung\",\"error\")}}(a.dataset.date,parseInt(a.dataset.article),a.dataset.name,parseFloat(a.dataset.price),a.dataset.desc||\"\").finally(()=>{a.disabled=!1,a.classList.remove(\"loading\")})});const C=o.querySelector(\".btn-cancel\");C&&C.addEventListener(\"click\",t=>{t.stopPropagation();const n=t.currentTarget;n.disabled=!0,async function(t,n,a){if(!d)return;const s=`${t}_${n}`,o=u.get(s);if(!o||0===o.length)return;const i=o[o.length-1];try{const t=await fetch(`${e}/user/orders/${i}/cancel/`,{method:\"PATCH\",headers:f(d),body:JSON.stringify({})});t.ok?(F(`Storniert: ${a}`,\"success\"),b=null,await y()):F(`Fehler: ${(await t.json()).detail||\"Stornierung fehlgeschlagen\"}`,\"error\")}catch(e){console.error(\"Cancel error:\",e),F(\"Netzwerkfehler bei Stornierung\",\"error\")}}(n.dataset.date,parseInt(n.dataset.article),n.dataset.name).finally(()=>{n.disabled=!1})});const O=o.querySelector(\".btn-flag\");O&&O.addEventListener(\"click\",e=>{e.stopPropagation();const t=e.currentTarget;E(t.dataset.date,parseInt(t.dataset.article),t.dataset.name,t.dataset.cutoff)}),A.appendChild(o)}),a.appendChild(A),a}(t);a&&v.appendChild(a)}),t.appendChild(v),setTimeout(()=>function(e){const t=e.querySelectorAll(\".menu-card\");if(0===t.length)return;let n=0;t.forEach(e=>{n=Math.max(n,e.querySelectorAll(\".menu-item\").length)});for(let e=0;e{const s=t.querySelectorAll(\".menu-item\");s[e]&&(s[e].style.height=\"auto\",n=Math.max(n,s[e].offsetHeight),a.push(s[e]))}),a.forEach(e=>{e.style.height=`${n}px`})}}(v),0)}function H(e,t){if(!e||!t)return!1;const n=e.replace(/^v/,\"\").split(\".\").map(Number),a=t.replace(/^v/,\"\").split(\".\").map(Number);for(let e=0;e(a[e]||0))return!0;if((n[e]||0)<(a[e]||0))return!1}return!1}async function K(e){const t=e?`${s}/tags?per_page=20`:`${s}/releases?per_page=20`,n=await fetch(t,{headers:{Accept:\"application/vnd.github.v3+json\"}});if(!n.ok){if(403===n.status)throw new Error(\"API Rate Limit erreicht (403). Bitte sp\u00e4ter erneut versuchen.\");throw new Error(`GitHub API ${n.status}`)}return(await n.json()).map(t=>{const n=e?t.name:t.tag_name;return{tag:n,name:e?n:t.name||n,url:`${o}/${n}/dist/install.html`,body:t.body||\"\"}})}async function Q(){const e=\"v1.6.11\",t=\"true\"===localStorage.getItem(\"kantine_dev_mode\");try{const n=await K(t);if(!n.length)return;localStorage.setItem(\"kantine_version_cache\",JSON.stringify({timestamp:Date.now(),devMode:t,versions:n}));const a=n[0].tag;if(console.log(`[Kantine] Version Check: Local [${e}] vs Latest [${a}] (${t?\"dev\":\"stable\"})`),!H(a,e))return;console.log(`[Kantine] Update verf\u00fcgbar: ${a}`);const s=document.querySelector(\".header-left h1\");if(s&&!s.querySelector(\".update-icon\")){const e=document.createElement(\"a\");e.className=\"update-icon\",e.href=n[0].url,e.target=\"_blank\",e.innerHTML=\"\ud83c\udd95\",e.title=`Update: ${a} \u2014 Klick zum Installieren`,e.style.cssText=\"margin-left:8px;font-size:1em;text-decoration:none;cursor:pointer;vertical-align:middle;\",s.appendChild(e)}}catch(e){console.warn(\"[Kantine] Version check failed:\",e)}}function X(){if(!d||!m)return void U();const e=new Date,t=e.getDay();if(0===t||6===t)return void U();const n=e.toISOString().split(\"T\")[0];let a=!1;for(const e of u.keys())if(e.startsWith(n)){a=!0;break}if(a)return void U();const s=new Date;s.setHours(10,0,0,0);const o=s-e;if(o<=0)return void U();const i=Math.floor(o/36e5),r=Math.floor(o%36e5/6e4),l=document.querySelector(\".header-center-wrapper\");if(!l)return;let c=document.getElementById(\"order-countdown\");if(c||(c=document.createElement(\"div\"),c.id=\"order-countdown\",l.insertBefore(c,l.firstChild)),c.innerHTML=`Bestellschluss: ${i}h ${r}m`,o<36e5){c.classList.add(\"urgent\");const e=`kantine_notified_${n}`;localStorage.getItem(e)||(\"granted\"===Notification.permission?new Notification(\"Kantine: Bestellschluss naht!\",{body:\"Du hast heute noch nichts bestellt. Nur noch 1 Stunde!\",icon:\"\u23f3\"}):\"default\"===Notification.permission&&Notification.requestPermission(),localStorage.setItem(e,\"true\"))}else c.classList.remove(\"urgent\")}function U(){const e=document.getElementById(\"order-countdown\");e&&e.remove()}function G(e){const t=new Date(Date.UTC(e.getFullYear(),e.getMonth(),e.getDate())),n=t.getUTCDay()||7;t.setUTCDate(t.getUTCDate()+4-n);const a=new Date(Date.UTC(t.getUTCFullYear(),0,1));return Math.ceil(((t-a)/864e5+1)/7)}function J(e){const t=new Date(e.getTime());return t.setDate(t.getDate()+3-(t.getDay()+6)%7),t.getFullYear()}function W(e){const t=document.createElement(\"div\");return t.textContent=e||\"\",t.innerHTML}setInterval(X,6e4),setTimeout(X,1e3);const P=[\"apfel\",\"achtung\",\"aubergine\",\"auflauf\",\"beere\",\"blumenkohl\",\"bohne\",\"braten\",\"brokkoli\",\"brot\",\"brust\",\"br\u00f6tchen\",\"butter\",\"chili\",\"dessert\",\"dip\",\"eier\",\"eintopf\",\"eis\",\"erbse\",\"erdbeer\",\"essig\",\"filet\",\"fisch\",\"fisole\",\"fleckerl\",\"fleisch\",\"fl\u00fcgel\",\"frucht\",\"f\u00fcr\",\"gebraten\",\"gem\u00fcse\",\"gew\u00fcrz\",\"gratin\",\"grie\u00df\",\"gulasch\",\"gurke\",\"himbeer\",\"honig\",\"huhn\",\"h\u00e4hnchen\",\"jambalaya\",\"joghurt\",\"karotte\",\"kartoffel\",\"keule\",\"kirsch\",\"knacker\",\"knoblauch\",\"kn\u00f6del\",\"kompott\",\"kraut\",\"kr\u00e4uter\",\"kuchen\",\"k\u00e4se\",\"k\u00fcrbis\",\"lauch\",\"mandel\",\"milch\",\"mild\",\"mit\",\"mohn\",\"most\",\"m\u00f6hre\",\"natur\",\"nockerl\",\"nudel\",\"nuss\",\"nu\u00df\",\"obst\",\"oder\",\"olive\",\"paprika\",\"pfanne\",\"pfannkuchen\",\"pfeffer\",\"pikant\",\"pilz\",\"plunder\",\"p\u00fcree\",\"ragout\",\"rahm\",\"reis\",\"rind\",\"sahne\",\"salami\",\"salat\",\"salz\",\"sauer\",\"scharf\",\"schinken\",\"schnitte\",\"schnitzel\",\"schoko\",\"schupf\",\"schwein\",\"sellerie\",\"senf\",\"sosse\",\"so\u00dfe\",\"spargel\",\"sp\u00e4tzle\",\"speck\",\"spie\u00df\",\"spinat\",\"steak\",\"suppe\",\"s\u00fc\u00df\",\"tofu\",\"tomate\",\"topfen\",\"torte\",\"tr\u00fcffel\",\"und\",\"vanille\",\"vogerl\",\"vom\",\"wien\",\"wurst\",\"zucchini\",\"zum\",\"zur\",\"zwiebel\",\"\u00f6l\"],V=[\"almond\",\"and\",\"apple\",\"asparagus\",\"bacon\",\"baked\",\"ball\",\"bean\",\"beef\",\"berry\",\"bread\",\"breast\",\"broccoli\",\"bun\",\"butter\",\"cabbage\",\"cake\",\"caper\",\"carrot\",\"casserole\",\"cauliflower\",\"celery\",\"cheese\",\"cherry\",\"chicken\",\"chili\",\"choco\",\"chocolate\",\"cider\",\"cilantro\",\"coffee\",\"compote\",\"cream\",\"cucumber\",\"curd\",\"danish\",\"dessert\",\"dip\",\"dumpling\",\"egg\",\"eggplant\",\"filet\",\"fish\",\"for\",\"fried\",\"from\",\"fruit\",\"garlic\",\"goulash\",\"gratin\",\"ham\",\"herb\",\"honey\",\"hot\",\"ice\",\"jambalaya\",\"leek\",\"leg\",\"mash\",\"meat\",\"mexican\",\"mild\",\"milk\",\"mint\",\"mushroom\",\"mustard\",\"noodle\",\"nut\",\"oat\",\"oil\",\"olive\",\"onion\",\"or\",\"oven\",\"pan\",\"pancake\",\"pea\",\"pepper\",\"plain\",\"plate\",\"poppy\",\"pork\",\"potato\",\"pumpkin\",\"radish\",\"ragout\",\"raspberry\",\"rice\",\"roast\",\"roll\",\"salad\",\"salami\",\"salt\",\"sauce\",\"sausage\",\"shrimp\",\"skewer\",\"slice\",\"soup\",\"sour\",\"spice\",\"spicy\",\"spinach\",\"steak\",\"stew\",\"strawberr\",\"strawberry\",\"strudel\",\"sweet\",\"tart\",\"thyme\",\"to\",\"tofu\",\"tomat\",\"tomato\",\"truffle\",\"trukey\",\"turkey\",\"vanilla\",\"vegan\",\"vegetable\",\"vinegar\",\"wedge\",\"wing\",\"with\",\"wok\",\"yogurt\",\"zucchini\"];!function(){document.title=\"Kantine Weekly Menu\",document.querySelectorAll&&document.querySelectorAll('link[rel*=\"icon\"]').forEach(e=>e.remove());const e=document.createElement(\"link\");if(e.rel=\"icon\",e.type=\"image/png\",e.href=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAOUElEQVR4nNWYaXRVRbbH//tMd0xITAISyASBAGGSOYJP6fdEhAAiMjiAAxDoVsCWtpu0jdcrrUQFGYI2CQg8RIYwCQiCtjIIChImISASSJgTSYiZ7niqdn+4AQEbaIcP7+21zqqzzqmq86tdtXf96wD/x41+gz4UANylS5dE5mDU3r0H8uueyas1XC6l7tntLTWVgZXAkJXiN2ADAKhEhIg7IpaGhYWdZGYCoOIXDJ6uua6Y9mvhAIjOnTu3y8/Pf0RKqSckJDwD4L26d5IAbrtofs9LJOJVnxcCZGeGBcRWgKwsySpIWAXDQlAsDLZrBLVdzB3PfjpoxPe/FhCqpuLIkSPTwsPD9fDwcFlSUvLapEmT1mRlZVXi3ntV3r5dsCKp2uud57NadcUfBLTQbBOHhsFQwWAQQutClxI+gT8D/+m6uAkbAJHaNjXd4/H8T2bmJLFq1UoZCAQaLFy4cDIRSWzfznC56JsRGZ8319WOVr//ogwEGLW1fng8Jtdd8NSa8HhNeDxB8vpMGQjUBj21gZ8LSDfcMzMbxwuOvxnbKJbHjh1LnTt3Ufv37ydLS0uf7devXysAEm434HJp+54Zd7iFrvax6XoZGxYLGAoAjcGaCdYAaGBoADQCVNht+LmAXBeNV9rJpKSk3/v9/pavv/Z3GR5eT5FS0syZs9hqtRpbt259W9M0BkBwu024XNrep5872FzVHrABhawqBGYmEFQoodETIdSAYL/mQ7fBYgoVTHC7Je69VwMgMzMzY86cOTO5Y6cOcvjwJxUhBIQQSEpKUidOnCiqq6sfaNOmVT8AAoBaB2nsG/WHAw6FtsEwCAQJuiHciUBgGfSr8vaALpcCIr5r3rzk6AXvnmm28N1h2L7dJFXlhQsXTpZSRs2aOUsSEQkhQASYpolJkyZR47jGfPTo8beY2VLnfU1xuwMNc2e/Xk40Cj6/hKKEogPMVyiJADCkrvpuA1jnsYy8vHr7R406yVLsv2BYliW8P/+Z6Y2aNSwpKRn38MMDZffuPVQigmEY0DQdmqbB6XQqWVOzpN/vT0lISpgAIklut9lwXvYr5aqWKb0+wcwKJMAAsaJSKIx/zIQOOELAN4Uj4r4ffBC5q6r0lFXXZpaPGu+ul5v9vveOek/EnP9+evLy1W1yli7pFR+XIE+eLFRKS0ohhED9BvWRmJAIh8PB/Qf05w0bPqKcO++s75r68lM/SH5LeDwmARqYmUmBBpACeAKaaleDQSFVTdVZVt0TE5e8eciQS/8+DxJxv6VLG3z82GOlkTmz3qtyhr8SmTvLWpExbrj1HzMqizk48Ymc7EvVlyvQ7eFHyOP3w2qxAAT4fX4IIdC6TWtu2769svuTT9e/MemPf6wQ8q/S4zPBUgWIWVGkZrOqMVKOjrPY9x7y1mz1a3okCcEMljK0dm/YSeo8l5eXZ4y9dPZwZG72moqMcWOj5s7kmpjoScac6Vpx+86nXvPW8t83rI85mNwc8xctohbNmsFqs13t5vjx41i6YgVdPn2aB29co7xbcORFq8/PBKiAAkmQutWqNhBy/OmMcfPPAOg8f26v4/B/7FGNaAoEgvVQiZ8CEjFcLmXw4MHBF3JmT6kIc8yOzM3G5YxxY6NyZhkluvqnzKoKzOmUhsYR9fiFf24hpV44lrRMwf5du+DxeRHXuDGSU1rA/fLLtOpcMZ7ctD5dV1RIliCAhQKpWS1qAynHn84Yl80ul4b7gL09x+Z3zp1z/1GWnwnAHl8v3v9TQABwuyUBiuJ2ZzeeO8N6KTLyzbB/zKKLGePH9lmx5J7cQ/vbC0XhB5OaKobNjvUH98Ol62hy8CgulpfjYmkpurVOha33/Rjx4WroNrskVVXq5geaYVHrm3L86bETQnButwk3AJdL25vx3MGU+dkPlAtlWX5ZmQ1A7c0VRygtmHE5s6act1n/1pnp6y8GDE51HzvsmLLtc8CwwGrRETBNNI2IxHdPZlxturjgEJ7ashG6ZoAolEYlgdnQzcTI6LEnhzy+8CrctZaXp2LIEDF02bJEr81Wvv6hh6pvJYkILpequt1m7PJFr56tqJj8fGob9E9qigfXroKiKmBm+KRAQ7sdB4eOQITFhhWF32Lkp5tD6UGhkAAQUioOm9Lph9plF17MXFasqRvYFNdrxh8do8Dtvvr8VomaXa+8IgURHv54qzkqsWlw5hfbZa/VeSCFAMnQiEBSItbuRH1nOHws4f5yJ4LBIAxdD6kVyTCFUBb+d29e3LvvgHOGsb5Du/ZtANRpmRvM7ZbXbKm3FJUqABEZHd3H0NSNhceOyb8c2qe8u3c3oGqhllJC03UYRPhrlzS81KU7jpZdQt8P81BcXQOHocFjBjGlbQf8pWt3aHYHp6Wl0Z49e7bput4zEAioqEsnN7ObefCKWtErysreTE5Ols6ISH6tXUe4O3RC99hYpEREIL1pM8TYbPBIE3/buQ0Ttn2CVtEx2DJoGOLDHKitqsSygUPQ4lwJchYvBjNT9+53CwD3paQkP4Yr+/QvAFQAyObNk8cQUWqTpCbMzOrq1WuQXHQeO4eOQP6wEdgwYDDWpg9EpGbAolswe18+Ht+0Ds0jo/DRQ0OQ22cAhsY3gaNBA5w9fRpEhORmzYiZuaj4zFsTJkyIAOoEzM8AVADwo48+Gn3+wkUXM0u7w05EBI/XgxqWkMxwGlb4hYmudzbCugGPQFcAm8WCpceO4MG1y9E8Mgqj23WEKSXqhYUhGAyGemco9evHSCFk7KJFi14lInkLR/3bF0REctOmTS6FlOgnhj/OpaWlCgBER0ehrKwMChFqzCB6rVmBiTv+iXsaxWN1+iBACtgtVmw+dRI9VizG6apKaIqC0rIyOB0OMDNOnTqJoUOHKBmjR4rKyso/9OzZswNuMdU3AqoAZHp6emplZeWYJ4Y/Ll/660vKkcOHAQAdO3bGt0cOw2MG0f/DldhRfApv5+/BS19uQ6+EJKzqNwjCNOGwWJFfWoLfLV+MMz4PThw8iJatW4OIcPjIETRp2pRee30qHA6HumfPnuy6k+B/ZGqdXPrY4XTw+fPnTCEEx8fH8d59+czMvPPLnfy7lUsY06Zw+DvT2TlnGuOtV3nCtk+YmfnDwm9ZmzGVHXOmszJjKjd+7x1evP0zNr1e/qGqihvHNeZ9+0N9vT1jugmAU1KSn7nGQTf1oApAtG7duk9NTU3vF198UcTGNlIVRUHvB/tgissFE8DU8lJ8XlyEMJsdJjMkh/RmjNUGU0oMaJqC9x9Ih8fvhV03UFJdjQlHv8FuXy22rFqFiDsi0eGujggGgxg/fgK1b99OFhYWZblcrmiEAua6WaVrSmJm1TCMgw0b3tny2LFjbLXaFBBwuqgYWz7Zgs0JsVh36ADCwsMhZAjMKwVye6djVErqdSNfXVSIR9asgNNmQyAQhGax4IXGcRjZrBXimqdACBOGbmD37q9EWtrdalRM1LyK8ooMKeV1ufGGQ1DC2GAw2CorK0va7Q7FFAIEQlyTJHzePBHrjnwDZ71wmCwBlvAIE5NSWiLNE0DJ99+jrLwMBQUFmDF7Fs4uy8P8B/qgpqYGuq5BmkFknS7CRzIAVVGgajqCZhDduqWpo0aNEuWXykf16NEjDTcEDF2BzMzMjHrjjTeOde3aJfLLL79CwAwquqrBLwUGrF+FT747jrDwcJhCAmB4g0HM7Z2O3qqBN7Oz4fN6AWZYLBakpKRgwMCBSIxPwIqzpzBs5XI4LFYwS3i8Hvw57R680aMnhBRgBqoqK0VKSopaU1Oz3+v1dqkTGBJA6D8KEYmoqKg5ZeVlz369Z4/ZsVNnjYVAkICBG1Zjc+EJOO0OCCFBxPAEg3C17YDJ3e+FarXeNOL+d8kSHNr5BRo98yT+9MU2OKxWKESorq3BU+3uwvz7+wCSoaoqFixYIEaOHKkmJiZOKC4unn0lJggA9e3bN3Xjxo0Hhj06TFm2dBmxlORniYEb1mBz4XdwOuwQpgQR4KmqQs7Dg6F+uhXuadPRo3t3dOvWFQmJidBUDRcunMfevfnYtWsnQITxzz+PMU8/jaz8r5C57XM4nE6oRKiqrUXfZs2xvO9AOHUDADjt7jTe/dXu6jFjxqTm5ORcAECk6xoMw7Le6XSmF54sNJ0Op1oT8CuDN63D5sLvEO5wQAiBoBAiIAUtHDAITzVJAQCcOHEC69Z9iP37D+Dy5cuQLBEeFo4WLVugb5++SEtLu86jc747jHEb1sNmWFhXFbXKU4u0uASs7vsQGjrD+ey5c8HWqakGES2rqal5TAihUnx8/ONnzpxZEhsbi65du+L+nvfhWMe2nL19K4XXi0BQmCCG1Jx2ZXLrdihbsgKHi4qhqQrsNjscDgdM00QgEAAzwzAM6LoOv9+PyqpKCFNA0zQIKdGpVQsogwbg9QP7oAZNqSuKUlldhf9q0ZInWsJoUe487NixA+Xl5UhKSupfVFS0QfP7/cPtdntBxQ8/8Nq1a9G0VQvrZxcik1WrRZhSgBWiILMyslnKx6ULlzSYNjfHWpevfrZt/OgjyrJagsN63uP7oOBIV0gh7Dab/Pr7Uv2A03dx7dq15RarFXannbw+7xP5+fmbr+Q+AQIURYXP69XvzM3eUWmzdIPXC9UwECPly8Ujn5sCANZbBMXtjAH4fT7oRIiaOzO3ymoZzULC6vN7erVskbruwYHFXq+XDMPgQCCgEpG8cQ9UAMge773X5AR7p5ng2AjGgqLR43JlXp7KgwdLIvpF3rsKyUxEBBXguPnvTPAbets7/GJBwejf70KdDr1tB6ireTVbXiPBf6XRDeWPNz8Khuuc9pNjJ9WdjRmAcLsZeXkKhgz5rX5o83VlXp7KBQWhH6shXXhtnf8f9i8ccK5KeMWwRQAAAABJRU5ErkJggg==\",document.head.appendChild(e),!document.querySelector('link[href*=\"fonts.googleapis.com/css2?family=Inter\"]')){const e=document.createElement(\"link\");e.rel=\"stylesheet\",e.href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap\",document.head.appendChild(e)}if(!document.querySelector('link[href*=\"Material+Icons+Round\"]')){const e=document.createElement(\"link\");e.rel=\"stylesheet\",e.href=\"https://fonts.googleapis.com/icon?family=Material+Icons+Round\",document.head.appendChild(e)}document.body.innerHTML=`\\n
        \\n
        \\n
        \\n
        \\n \"Logo\"\\n
        \\n

        Kantinen \u00dcbersicht v1.6.11

        \\n
        \\n
        \\n
        \\n \\n \\n
        \\n \\n
        \\n
        \\n
        \\n \\n \\n \\n
        \\n
        \\n
        \\n
        \\n
        \\n \\n \\n \\n \\n \\n
        \\n person\\n \\n \\n
        \\n
        \\n
        \\n
        \\n\\n
        \\n
        \\n
        \\n

        Login

        \\n \\n
        \\n
        \\n
        \\n \\n \\n Deine offizielle Knapp Mitarbeiternummer.\\n
        \\n
        \\n \\n \\n Das Passwort f\u00fcr deinen Bessa Account.\\n
        \\n
        \\n
        \\n \\n
        \\n
        \\n
        \\n
        \\n\\n
        \\n
        \\n
        \\n

        Men\u00fcdaten aktualisieren

        \\n
        \\n
        \\n
        \\n
        \\n
        \\n
        \\n
        0%
        \\n
        \\n

        Initialisierung...

        \\n
        \\n
        \\n
        \\n\\n
        \\n
        \\n
        \\n

        Meine Highlights

        \\n \\n
        \\n
        \\n

        \\n Markiere Men\u00fcs automatisch, wenn sie diese Schlagw\u00f6rter enthalten.\\n

        \\n
        \\n \\n \\n
        \\n
        \\n
        \\n
        \\n
        \\n\\n
        \\n
        \\n
        \\n

        Bestellhistorie

        \\n \\n
        \\n
        \\n
        \\n

        Lade Historie...

        \\n
        \\n
        \\n
        \\n
        \\n
        \\n
        \\n
        \\n \\x3c!-- Dynamically populated --\\x3e\\n
        \\n
        \\n
        \\n
        \\n\\n
        \\n
        \\n
        \\n

        \ud83d\udce6 Versionen

        \\n \\n
        \\n
        \\n
        \\n Aktuell: v1.6.11\\n
        \\n
        \\n \\n
        \\n
        \\n

        Lade Versionen...

        \\n
        \\n
        \\n \\n bug_report Fehler melden\\n \\n \\n lightbulb Feature vorschlagen\\n \\n \\n
        \\n
        \\n
        \\n
        \\n\\n
        \\n
        \\n update\\n Gerade aktualisiert\\n
        \\n
        \\n
        \\n

        Lade Men\u00fcdaten...

        \\n
        \\n
        \\n
        \\n\\n
        \\n

        Jetzt Bessa Einfach! • Knapp-Kantine Wrapper • ${(new Date).getFullYear()} by Kaufi \ud83d\ude03\ud83d\udc4d mit Hilfe von KI \ud83e\udd16

        \\n
        \\n
        `}(),function(){const n=document.getElementById(\"btn-this-week\"),a=document.getElementById(\"btn-next-week\"),s=document.getElementById(\"btn-refresh\"),o=document.getElementById(\"theme-toggle\"),i=document.getElementById(\"btn-login-open\"),r=document.getElementById(\"btn-login-close\"),l=document.getElementById(\"btn-logout\"),g=document.getElementById(\"login-form\"),k=document.getElementById(\"login-modal\"),A=document.getElementById(\"btn-highlights\"),E=document.getElementById(\"highlights-modal\"),x=document.getElementById(\"btn-highlights-close\"),D=document.getElementById(\"btn-add-tag\"),C=document.getElementById(\"tag-input\"),O=document.getElementById(\"btn-history\"),M=document.getElementById(\"history-modal\"),T=document.getElementById(\"btn-history-close\");document.querySelectorAll(\".lang-btn\").forEach(e=>{e.addEventListener(\"click\",()=>{p=e.dataset.lang,localStorage.setItem(\"kantine_lang\",p),document.querySelectorAll(\".lang-btn\").forEach(e=>e.classList.remove(\"active\")),e.classList.add(\"active\"),j()})}),A&&A.addEventListener(\"click\",()=>{E.classList.remove(\"hidden\")}),x&&x.addEventListener(\"click\",()=>{E.classList.add(\"hidden\")}),O.addEventListener(\"click\",()=>{d?(M.classList.remove(\"hidden\"),async function(){const t=document.getElementById(\"history-loading\"),n=document.getElementById(\"history-content\"),a=document.getElementById(\"history-progress-fill\"),s=document.getElementById(\"history-progress-text\");let o=[];if(b)o=b;else{const e=localStorage.getItem(\"kantine_history_cache\");if(e)try{o=JSON.parse(e),b=o}catch(e){console.warn(\"History cache parse error\",e)}}o.length>0&&w(o);if(!d)return;0===o.length&&(n.innerHTML=\"\",t.classList.remove(\"hidden\"));a.style.width=\"0%\",s.textContent=o.length>0?\"Suche nach neuen Bestellungen...\":\"Lade Bestellhistorie...\",o.length>0&&t.classList.remove(\"hidden\");let i=o.length>0?`${e}/user/orders/?venue=591&ordering=-created&limit=5`:`${e}/user/orders/?venue=591&ordering=-created&limit=50`,r=[],l=0,c=0===o.length,m=!1;try{for(;i&&!m;){const e=await fetch(i,{headers:f(d)});if(!e.ok)throw new Error(`Fetch failed: ${e.status}`);const t=await e.json();t.count&&0===l&&(l=t.count);const n=t.results||[];for(const e of n){const t=o.findIndex(t=>t.id===e.id);if(!c&&-1!==t){const n=o[t];if(n.updated===e.updated&&n.order_state===e.order_state){m=!0;break}}r.push(e)}if(!m&&c)if(l>0){const e=Math.round(r.length/l*100);a.style.width=`${e}%`,s.textContent=`Lade Bestellung ${r.length} von ${l}...`}else s.textContent=`Lade Bestellung ${r.length}...`;else m||(s.textContent=`${r.length} neue/ge\u00e4nderte Bestellungen gefunden...`);i=m?null:t.next}if(r.length>0){const e=new Map(o.map(e=>[e.id,e]));for(const t of r)e.set(t.id,t);const t=Array.from(e.values());t.sort((e,t)=>new Date(t.created)-new Date(e.created)),b=t;try{localStorage.setItem(\"kantine_history_cache\",JSON.stringify(t))}catch(e){console.warn(\"History cache write error\",e)}w(b)}}catch(e){console.error(\"Error in history sync:\",e),0===o.length?n.innerHTML='

        Fehler beim Laden der Historie.

        ':F(\"Hintergrund-Synchronisation fehlgeschlagen\",\"error\")}finally{t.classList.add(\"hidden\")}}()):k.classList.remove(\"hidden\")}),T.addEventListener(\"click\",()=>{M.classList.add(\"hidden\")}),window.addEventListener(\"click\",e=>{e.target===M&&M.classList.add(\"hidden\"),e.target===E&&E.classList.add(\"hidden\")});const q=document.querySelector(\".version-tag\"),$=document.getElementById(\"version-modal\"),z=document.getElementById(\"btn-version-close\");q&&q.addEventListener(\"click\",e=>{e.preventDefault(),e.stopPropagation(),function(){const e=document.getElementById(\"version-modal\"),t=document.getElementById(\"version-list-container\"),n=document.getElementById(\"dev-mode-toggle\"),a=\"v1.6.11\";if(!e)return;e.classList.remove(\"hidden\");const s=document.getElementById(\"version-current\");s&&(s.textContent=a);const o=\"true\"===localStorage.getItem(\"kantine_dev_mode\");async function i(e){const s=n.checked;function o(e){if(!e||!e.length)return void(t.innerHTML='

        Keine Versionen gefunden.

        ');t.innerHTML='
          ';const n=t.querySelector(\".version-list\");e.forEach(e=>{const t=e.tag===a,s=H(e.tag,a),o=document.createElement(\"li\");o.className=\"version-item\"+(t?\" current\":\"\");let i=\"\";t?i='\u2713 Installiert':s&&(i='\u2b06 Neu!');let r=\"\";t||(r=`Installieren`),o.innerHTML=`\\n
          \\n ${e.tag}\\n ${i}\\n
          \\n ${r}\\n `,n.appendChild(o)})}t.innerHTML='

          Lade Versionen...

          ';try{const e=localStorage.getItem(\"kantine_version_cache\");let t=null;if(e)try{t=JSON.parse(e)}catch(e){}t&&t.devMode===s&&t.versions&&o(t.versions);const n=await K(s),a=JSON.stringify(n);a!==(t?JSON.stringify(t.versions):\"\")&&(localStorage.setItem(\"kantine_version_cache\",JSON.stringify({timestamp:Date.now(),devMode:s,versions:n})),o(n))}catch(e){t.innerHTML=`

          Fehler: ${e.message}

          `}}n.checked=o,i(!1),n.onchange=()=>{localStorage.setItem(\"kantine_dev_mode\",n.checked),localStorage.removeItem(\"kantine_version_cache\"),i(!0)}}()}),z&&z.addEventListener(\"click\",()=>{$.classList.add(\"hidden\")});const Q=document.getElementById(\"btn-clear-cache\");Q&&Q.addEventListener(\"click\",()=>{confirm(\"M\u00f6chtest du wirklich alle lokalen Daten (inkl. Login-Session, Cache und Einstellungen) l\u00f6schen? Die Seite wird danach neu geladen.\")&&(Object.keys(localStorage).forEach(e=>{e.startsWith(\"kantine_\")&&localStorage.removeItem(e)}),window.location.reload())}),window.addEventListener(\"click\",e=>{e.target===$&&$.classList.add(\"hidden\")}),D.addEventListener(\"click\",()=>{(function(e){if(e=e.trim().toLowerCase(),e&&!L.includes(e))return L.push(e),S(),!0;return!1})(C.value)&&(C.value=\"\",B())}),C.addEventListener(\"keypress\",e=>{\"Enter\"===e.key&&D.click()});const X=localStorage.getItem(\"theme\"),U=window.matchMedia(\"(prefers-color-scheme: dark)\").matches,G=o.querySelector(\".theme-icon\");\"dark\"===X||!X&&U?(document.documentElement.setAttribute(\"data-theme\",\"dark\"),G.textContent=\"dark_mode\"):(document.documentElement.setAttribute(\"data-theme\",\"light\"),G.textContent=\"light_mode\"),o.addEventListener(\"click\",()=>{const e=\"dark\"===document.documentElement.getAttribute(\"data-theme\")?\"light\":\"dark\";document.documentElement.setAttribute(\"data-theme\",e),localStorage.setItem(\"theme\",e),G.textContent=\"dark\"===e?\"dark_mode\":\"light_mode\"}),n.addEventListener(\"click\",()=>{\"this-week\"!==c&&(c=\"this-week\",n.classList.add(\"active\"),a.classList.remove(\"active\"),j())}),a.addEventListener(\"click\",()=>{a.classList.remove(\"new-week-available\"),\"next-week\"!==c&&(c=\"next-week\",a.classList.add(\"active\"),n.classList.remove(\"active\"),j())}),s.addEventListener(\"click\",()=>{d?N():k.classList.remove(\"hidden\")}),i.addEventListener(\"click\",()=>{k.classList.remove(\"hidden\"),document.getElementById(\"login-error\").classList.add(\"hidden\"),g.reset()}),r.addEventListener(\"click\",()=>{k.classList.add(\"hidden\")}),window.addEventListener(\"click\",e=>{e.target===k&&k.classList.add(\"hidden\")}),g.addEventListener(\"submit\",async n=>{n.preventDefault();const a=document.getElementById(\"employee-id\").value.trim(),s=document.getElementById(\"password\").value,o=document.getElementById(\"login-error\"),i=g.querySelector('button[type=\"submit\"]'),r=i.textContent;i.disabled=!0,i.textContent=\"Wird eingeloggt...\";try{const n=`knapp-${a}@bessa.app`,i=await fetch(`${e}/auth/login/`,{method:\"POST\",headers:f(t),body:JSON.stringify({email:n,password:s})}),r=await i.json();if(i.ok){d=r.key,m=a,localStorage.setItem(\"kantine_authToken\",r.key),localStorage.setItem(\"kantine_currentUser\",a);try{const t=await fetch(`${e}/auth/user/`,{headers:f(d)});if(t.ok){const e=await t.json();e.first_name&&localStorage.setItem(\"kantine_firstName\",e.first_name),e.last_name&&localStorage.setItem(\"kantine_lastName\",e.last_name)}}catch(e){console.error(\"Failed to fetch user info:\",e)}v(),k.classList.add(\"hidden\"),y(),g.reset(),I(),N()}else o.textContent=r.non_field_errors?.[0]||r.error||\"Login fehlgeschlagen\",o.classList.remove(\"hidden\")}catch(e){console.error(\"Login error:\",e),o.textContent=\"Ein Fehler ist aufgetreten\",o.classList.remove(\"hidden\")}finally{i.disabled=!1,i.textContent=r}}),l.addEventListener(\"click\",()=>{localStorage.removeItem(\"kantine_authToken\"),localStorage.removeItem(\"kantine_currentUser\"),localStorage.removeItem(\"kantine_firstName\"),localStorage.removeItem(\"kantine_lastName\"),d=null,m=null,u=new Map,h&&(clearInterval(h),h=null,console.log(\"Polling stopped\")),v(),j()})}(),v(),function(){const e=new Date,t=e.toISOString().split(\"T\")[0];let n=!1;for(const a of[...g]){const[s]=a.split(\"_\");let o=!1;if(s=t&&(o=!0)}o&&(g.delete(a),n=!0)}n&&k()}();(function(){try{const e=localStorage.getItem(D),t=localStorage.getItem(C);if(console.log(`[Cache] localStorage: key=${!!e} (${e?e.length:0} chars), ts=${t}`),e){i=JSON.parse(e),r=G(new Date),l=(new Date).getFullYear(),console.log(`[Cache] Parsed ${i.length} weeks:`,i.map(e=>`KW${e.weekNumber}/${e.year} (${(e.days||[]).length} days)`)),j(),z(),A(),t&&q(t);try{const e=new Set;i.forEach(t=>{(t.days||[]).forEach(t=>{(t.items||[]).forEach(t=>{let n=(t.description||\"\").replace(/\\s+/g,\" \").trim();n&&n.includes(\" / \")&&e.add(n)})})});const t=Array.from(e).join(\"\\n\\n\");console.log(\"=== GEFUNDENE MEN\u00dc-TEXTE (\"+e.size+\") ===\"),console.log(t)}catch(e){}return console.log(\"Loaded menu from cache\"),!0}}catch(e){console.warn(\"Failed to load cached menu:\",e)}return!1})()?(document.getElementById(\"loading\").classList.add(\"hidden\"),!function(){const e=localStorage.getItem(C);if(!e)return console.log(\"[Cache] No timestamp found\"),!1;const t=Date.now()-new Date(e).getTime(),n=Math.round(t/6e4);if(t>36e5)return console.log(`[Cache] Stale: ${n}min old (max 60)`),!1;const a=G(new Date),s=J(new Date),o=i.some(e=>e.weekNumber===a&&e.year===s&&e.days&&e.days.length>0);return console.log(`[Cache] Age: ${n}min, looking for KW${a}/${s}, found: ${o}`),o}()?(console.log(\"Cache stale or incomplete \u2013 refreshing from API\"),N()):console.log(\"Cache fresh & complete \u2013 skipping API refresh\")):N(),d&&I(),Q(),setInterval(Q,36e5),console.log(\"Kantine Wrapper loaded \u2705\")}();\n"; document.head.appendChild(sc); })(); diff --git a/dist/install.html b/dist/install.html index d3c9625..82a2e6d 100755 --- a/dist/install.html +++ b/dist/install.html @@ -2,7 +2,7 @@ - Kantine Wrapper Installer (v1.6.10) + Kantine Wrapper Installer (v1.6.11)