diff --git a/dist/bookmarklet-payload.js b/dist/bookmarklet-payload.js index 33cae43..663f4b9 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 r=[],i=U(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\":\"1.7.0_prod/2026-01-26\"}}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=\"\"),z()}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.`),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\")}`,r=t.toLocaleString(\"de-AT\",{month:\"long\"}),i=U(t);n[a]||(n[a]={year:a,months:{}}),n[a].months[o]||(n[a].months[o]={name:r,year:a,monthIndex:s,count:0,total:0,weeks:{}}),n[a].months[o].weeks[i]||(n[a].months[o].weeks[i]={label:`KW ${i}`,items:[],count:0,total:0});(e.items||[]).forEach(t=>{const s=parseFloat(t.price||e.total||0);n[a].months[o].weeks[i].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[i].count++,n[a].months[o].weeks[i].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 ${J(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 r)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));const o=new Date(a),i=Date.now()-o.getTime(),l=Math.floor(i/6e4);s=l<1?\"gerade eben\":l<60?`vor ${l} Min.`:`vor ${Math.floor(l/60)} Std.`,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 i=`${n}_${a}`;let l=!1;g.has(i)?(g.delete(i),F(`Flag entfernt f\u00fcr ${s}`,\"success\")):(g.add(i),l=!0,F(`Benachrichtigung aktiviert f\u00fcr ${s}`,\"success\"),\"default\"===Notification.permission&&Notification.requestPermission()),k(),A(),z(),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 i=[];for(const e of o)e.items&&Array.isArray(e.items)&&(i=i.concat(e.items));for(let e of r){if(!e.days)continue;let n=e.days.find(e=>e.date===t);n&&(n.items=i.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(),z())}()}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\"}),M()}}}catch(e){console.error(`Poll error for ${t}:`,e),await new Promise(e=>setTimeout(e,200))}}}(),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)),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 C=\"kantine_menuCache\",D=\"kantine_menuCacheTs\";function O(){try{localStorage.setItem(C,JSON.stringify(r)),localStorage.setItem(D,(new Date).toISOString())}catch(e){console.warn(\"Failed to cache menu data:\",e)}}async function M(){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;r&&r.length>0&&r.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=U(t),a=G(t),s=`${a}-${n}`;y.has(s)||y.set(s,{year:a,weekNumber:n,days:[]});const o=y.get(s),r=t.toLocaleDateString(\"en-US\",{weekday:\"long\"}),i=new Date(e.date);i.setHours(10,0,0,0);const l={date:e.date,weekday:r,orderCutoff:i.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)}r=Array.from(y.values()).sort((e,t)=>e.year!==t.year?e.year-t.year:e.weekNumber-t.weekNumber),r.forEach(e=>{e.days&&e.days.sort((e,t)=>e.date.localeCompare(t.date))}),O(),q((new Date).toISOString()),i=U(new Date),l=(new Date).getFullYear(),v(),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 N=null,T=null;function q(e){const t=document.getElementById(\"last-updated-subtitle\");if(e){N=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=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.`}(n);t.textContent=`Aktualisiert: ${s} ${a} (${o})`}catch(e){t.textContent=\"\"}T||(T=setInterval(()=>{N&&q(N)},6e4))}}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 $(){const e=document.getElementById(\"btn-next-week\");let t=i+1,n=l;t>52&&(t=1,n++);const a=r.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 r=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)&&r++})}),r>0&&(m.innerHTML+=`(${r})`,m.title+=` \u2022 ${r} 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 z(){const t=document.getElementById(\"menu-container\");if(!t)return;t.innerHTML=\"\";let a=i,s=l;\"next-week\"===c&&(a++,a>52&&(a=1,s++));const o=r.flatMap(e=>e.days||[]).filter(e=>{const t=new Date(e.date);return U(t)===a&&G(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 r=!1;if(t.orderCutoff)r=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),r=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+=\"+\"),i.push(e)}}});const l=document.createElement(\"div\");l.className=\"card-header\";const c=o.toLocaleDateString(\"de-DE\",{day:\"2-digit\",month:\"2-digit\"}),m=i.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&&!r?\"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}`),r=u.has(`${t.date}_${s}`);if(o&&!r)return-1;if(!o&&r)return 1}return e.name.localeCompare(n.name)});return S.forEach(a=>{const o=document.createElement(\"div\");o.className=\"menu-item\";const i=a.articleId||parseInt(a.id.split(\"_\")[1]),l=`${t.date}_${i}`,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}_${i}`,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&&!r){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${J(e)}`).join(\"\")}
`}o.innerHTML=`\\n
\\n ${J(a.name)}\\n ${a.price.toFixed(2)} \u20ac\\n
\\n
\\n ${h}\\n ${L}\\n ${I}\\n ${S}\\n
${m}
\\n
\\n ${B}\\n

${J(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)/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;W.includes(a)?s=a.length:W.forEach(e=>{a.includes(e)&&e.length>s&&(s=e.length)}),P.includes(a)?o=a.length:P.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;el.en&&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=e.split(/\\s*\\/\\s*(?![A-Z,]+\\))/);if(s.length>4)return{de:t,en:\"\",raw:t};const o=[],r=[];o.push(s[0].trim());const i=/(?:\\(|(?:\\/|\\s|^))([A-Z,]+)\\)\\s*/;for(let e=1;e0&&(o[o.length-1]=o[o.length-1]+\"(\"+s+\")\"),i&&o.push(i)}else{const e=a(t);r.push(e.enPart),e.nextDe&&o.push(e.nextDe)}}1===s.length&&0===r.length&&r.push(o[0]);if(o.length>r.length)for(let e=r.length;e0&&!l.startsWith(\"\u2022 \")&&(l=\"\u2022 \"+l);let c=r.join(\"\\n\u2022 \");r.length>0&&!c.startsWith(\"\u2022 \")&&(c=\"\u2022 \"+c);return{de:l,en:c,raw:t}}(e);return\"en\"===p?t.en||t.raw:t.de||t.raw}(a.description))}

`;const C=o.querySelector(\".btn-order\");C&&C.addEventListener(\"click\",t=>{t.stopPropagation();const a=t.currentTarget;a.disabled=!0,a.classList.add(\"loading\"),async function(t,a,s,o,r){if(d)try{const i=await fetch(`${e}/auth/user/`,{headers:f(d)});if(!i.ok)return void F(\"Fehler: Benutzerdaten konnten nicht geladen werden\",\"error\");const l=await i.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:r||\"\",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 D=o.querySelector(\".btn-cancel\");D&&D.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 r=o[o.length-1];try{const t=await fetch(`${e}/user/orders/${r}/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 j(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 H(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 K(){const e=\"v1.6.7\",t=\"true\"===localStorage.getItem(\"kantine_dev_mode\");try{const n=await H(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\"})`),!j(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 Q(){if(!d||!m)return void X();const e=new Date,t=e.getDay();if(0===t||6===t)return void X();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 X();const s=new Date;s.setHours(10,0,0,0);const o=s-e;if(o<=0)return void X();const r=Math.floor(o/36e5),i=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: ${r}h ${i}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 X(){const e=document.getElementById(\"order-countdown\");e&&e.remove()}function U(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 G(e){const t=new Date(e.getTime());return t.setDate(t.getDate()+3-(t.getDay()+6)%7),t.getFullYear()}function J(e){const t=document.createElement(\"div\");return t.textContent=e||\"\",t.innerHTML}setInterval(Q,6e4),setTimeout(Q,1e3);const W=[\"apfel\",\"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\"],P=[\"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.7

\\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.7\\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\"),r=document.getElementById(\"btn-login-open\"),i=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\"),C=document.getElementById(\"btn-add-tag\"),D=document.getElementById(\"tag-input\"),O=document.getElementById(\"btn-history\"),N=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\"),z()})}),A&&A.addEventListener(\"click\",()=>{E.classList.remove(\"hidden\")}),x&&x.addEventListener(\"click\",()=>{E.classList.add(\"hidden\")}),O.addEventListener(\"click\",()=>{d?(N.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 r=o.length>0?`${e}/user/orders/?venue=591&ordering=-created&limit=5`:`${e}/user/orders/?venue=591&ordering=-created&limit=50`,i=[],l=0,c=0===o.length,m=!1;try{for(;r&&!m;){const e=await fetch(r,{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}}i.push(e)}if(!m&&c)if(l>0){const e=Math.round(i.length/l*100);a.style.width=`${e}%`,s.textContent=`Lade Bestellung ${i.length} von ${l}...`}else s.textContent=`Lade Bestellung ${i.length}...`;else m||(s.textContent=`${i.length} neue/ge\u00e4nderte Bestellungen gefunden...`);r=m?null:t.next}if(i.length>0){const e=new Map(o.map(e=>[e.id,e]));for(const t of i)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\",()=>{N.classList.add(\"hidden\")}),window.addEventListener(\"click\",e=>{e.target===N&&N.classList.add(\"hidden\"),e.target===E&&E.classList.add(\"hidden\")});const q=document.querySelector(\".version-tag\"),$=document.getElementById(\"version-modal\"),K=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.7\";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 r(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=j(e.tag,a),o=document.createElement(\"li\");o.className=\"version-item\"+(t?\" current\":\"\");let r=\"\";t?r='\u2713 Installiert':s&&(r='\u2b06 Neu!');let i=\"\";t||(i=`Installieren`),o.innerHTML=`\\n
    \\n ${e.tag}\\n ${r}\\n
    \\n ${i}\\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 H(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,r(!1),n.onchange=()=>{localStorage.setItem(\"kantine_dev_mode\",n.checked),localStorage.removeItem(\"kantine_version_cache\"),r(!0)}}()}),K&&K.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\")}),C.addEventListener(\"click\",()=>{(function(e){if(e=e.trim().toLowerCase(),e&&!L.includes(e))return L.push(e),S(),!0;return!1})(D.value)&&(D.value=\"\",B())}),D.addEventListener(\"keypress\",e=>{\"Enter\"===e.key&&C.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\"),z())}),a.addEventListener(\"click\",()=>{a.classList.remove(\"new-week-available\"),\"next-week\"!==c&&(c=\"next-week\",a.classList.add(\"active\"),n.classList.remove(\"active\"),z())}),s.addEventListener(\"click\",()=>{d?M():k.classList.remove(\"hidden\")}),r.addEventListener(\"click\",()=>{k.classList.remove(\"hidden\"),document.getElementById(\"login-error\").classList.add(\"hidden\"),g.reset()}),i.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\"),r=g.querySelector('button[type=\"submit\"]'),i=r.textContent;r.disabled=!0,r.textContent=\"Wird eingeloggt...\";try{const n=`knapp-${a}@bessa.app`,r=await fetch(`${e}/auth/login/`,{method:\"POST\",headers:f(t),body:JSON.stringify({email:n,password:s})}),i=await r.json();if(r.ok){d=i.key,m=a,localStorage.setItem(\"kantine_authToken\",i.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(),M()}else o.textContent=i.non_field_errors?.[0]||i.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{r.disabled=!1,r.textContent=i}}),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(),z()})}(),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(C),t=localStorage.getItem(D);if(console.log(`[Cache] localStorage: key=${!!e} (${e?e.length:0} chars), ts=${t}`),e){r=JSON.parse(e),i=U(new Date),l=(new Date).getFullYear(),console.log(`[Cache] Parsed ${r.length} weeks:`,r.map(e=>`KW${e.weekNumber}/${e.year} (${(e.days||[]).length} days)`)),z(),$(),A(),t&&q(t);try{const e=new Set;r.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(D);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=U(new Date),s=G(new Date),o=r.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\"),M()):console.log(\"Cache fresh & complete \u2013 skipping API refresh\")):M(),d&&I(),K(),setInterval(K,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 r=[],i=U(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\":\"1.7.0_prod/2026-01-26\"}}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=\"\"),z()}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.`),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\")}`,r=t.toLocaleString(\"de-AT\",{month:\"long\"}),i=U(t);n[a]||(n[a]={year:a,months:{}}),n[a].months[o]||(n[a].months[o]={name:r,year:a,monthIndex:s,count:0,total:0,weeks:{}}),n[a].months[o].weeks[i]||(n[a].months[o].weeks[i]={label:`KW ${i}`,items:[],count:0,total:0});(e.items||[]).forEach(t=>{const s=parseFloat(t.price||e.total||0);n[a].months[o].weeks[i].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[i].count++,n[a].months[o].weeks[i].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 ${J(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 r)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));const o=new Date(a),i=Date.now()-o.getTime(),l=Math.floor(i/6e4);s=l<1?\"gerade eben\":l<60?`vor ${l} Min.`:`vor ${Math.floor(l/60)} Std.`,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 i=`${n}_${a}`;let l=!1;g.has(i)?(g.delete(i),F(`Flag entfernt f\u00fcr ${s}`,\"success\")):(g.add(i),l=!0,F(`Benachrichtigung aktiviert f\u00fcr ${s}`,\"success\"),\"default\"===Notification.permission&&Notification.requestPermission()),k(),A(),z(),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 i=[];for(const e of o)e.items&&Array.isArray(e.items)&&(i=i.concat(e.items));for(let e of r){if(!e.days)continue;let n=e.days.find(e=>e.date===t);n&&(n.items=i.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(),z())}()}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\"}),M()}}}catch(e){console.error(`Poll error for ${t}:`,e),await new Promise(e=>setTimeout(e,200))}}}(),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)),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 C=\"kantine_menuCache\",D=\"kantine_menuCacheTs\";function O(){try{localStorage.setItem(C,JSON.stringify(r)),localStorage.setItem(D,(new Date).toISOString())}catch(e){console.warn(\"Failed to cache menu data:\",e)}}async function M(){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;r&&r.length>0&&r.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=U(t),a=G(t),s=`${a}-${n}`;y.has(s)||y.set(s,{year:a,weekNumber:n,days:[]});const o=y.get(s),r=t.toLocaleDateString(\"en-US\",{weekday:\"long\"}),i=new Date(e.date);i.setHours(10,0,0,0);const l={date:e.date,weekday:r,orderCutoff:i.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)}r=Array.from(y.values()).sort((e,t)=>e.year!==t.year?e.year-t.year:e.weekNumber-t.weekNumber),r.forEach(e=>{e.days&&e.days.sort((e,t)=>e.date.localeCompare(t.date))}),O(),q((new Date).toISOString()),i=U(new Date),l=(new Date).getFullYear(),v(),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 N=null,T=null;function q(e){const t=document.getElementById(\"last-updated-subtitle\");if(e){N=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=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.`}(n);t.textContent=`Aktualisiert: ${s} ${a} (${o})`}catch(e){t.textContent=\"\"}T||(T=setInterval(()=>{N&&q(N)},6e4))}}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 $(){const e=document.getElementById(\"btn-next-week\");let t=i+1,n=l;t>52&&(t=1,n++);const a=r.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 r=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)&&r++})}),r>0&&(m.innerHTML+=`(${r})`,m.title+=` \u2022 ${r} 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 z(){const t=document.getElementById(\"menu-container\");if(!t)return;t.innerHTML=\"\";let a=i,s=l;\"next-week\"===c&&(a++,a>52&&(a=1,s++));const o=r.flatMap(e=>e.days||[]).filter(e=>{const t=new Date(e.date);return U(t)===a&&G(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 r=!1;if(t.orderCutoff)r=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),r=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+=\"+\"),i.push(e)}}});const l=document.createElement(\"div\");l.className=\"card-header\";const c=o.toLocaleDateString(\"de-DE\",{day:\"2-digit\",month:\"2-digit\"}),m=i.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&&!r?\"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}`),r=u.has(`${t.date}_${s}`);if(o&&!r)return-1;if(!o&&r)return 1}return e.name.localeCompare(n.name)});return S.forEach(a=>{const o=document.createElement(\"div\");o.className=\"menu-item\";const i=a.articleId||parseInt(a.id.split(\"_\")[1]),l=`${t.date}_${i}`,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}_${i}`,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&&!r){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${J(e)}`).join(\"\")}
    `}o.innerHTML=`\\n
    \\n ${J(a.name)}\\n ${a.price.toFixed(2)} \u20ac\\n
    \\n
    \\n ${h}\\n ${L}\\n ${I}\\n ${S}\\n
    ${m}
    \\n
    \\n ${B}\\n

    ${J(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)/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;W.includes(a)?s=a.length:W.forEach(e=>{a.includes(e)&&e.length>s&&(s=e.length)}),P.includes(a)?o=a.length:P.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;el.en&&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=e.split(/\\s*\\/\\s*(?![A-Z,]+\\))/);if(s.length>4)return{de:t,en:\"\",raw:t};const o=[],r=[];o.push(s[0].trim());const i=/(?:\\(|(?:\\/|\\s|^))([A-Z,]+)\\)\\s*/;for(let e=1;e0&&(o[o.length-1]=o[o.length-1]+\"(\"+s+\")\"),i&&o.push(i)}else{const e=a(t);r.push(e.enPart),e.nextDe&&o.push(e.nextDe)}}1===s.length&&0===r.length&&r.push(o[0]);if(o.length>r.length)for(let e=r.length;e0&&!l.startsWith(\"\u2022 \")&&(l=\"\u2022 \"+l);let c=r.join(\"\\n\u2022 \");r.length>0&&!c.startsWith(\"\u2022 \")&&(c=\"\u2022 \"+c);return{de:l,en:c,raw:t}}(e);return\"en\"===p?t.en||t.raw:t.de||t.raw}(a.description))}

    `;const C=o.querySelector(\".btn-order\");C&&C.addEventListener(\"click\",t=>{t.stopPropagation();const a=t.currentTarget;a.disabled=!0,a.classList.add(\"loading\"),async function(t,a,s,o,r){if(d)try{const i=await fetch(`${e}/auth/user/`,{headers:f(d)});if(!i.ok)return void F(\"Fehler: Benutzerdaten konnten nicht geladen werden\",\"error\");const l=await i.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:r||\"\",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 D=o.querySelector(\".btn-cancel\");D&&D.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 r=o[o.length-1];try{const t=await fetch(`${e}/user/orders/${r}/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 j(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 H(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 K(){const e=\"v1.6.8\",t=\"true\"===localStorage.getItem(\"kantine_dev_mode\");try{const n=await H(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\"})`),!j(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 Q(){if(!d||!m)return void X();const e=new Date,t=e.getDay();if(0===t||6===t)return void X();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 X();const s=new Date;s.setHours(10,0,0,0);const o=s-e;if(o<=0)return void X();const r=Math.floor(o/36e5),i=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: ${r}h ${i}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 X(){const e=document.getElementById(\"order-countdown\");e&&e.remove()}function U(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 G(e){const t=new Date(e.getTime());return t.setDate(t.getDate()+3-(t.getDay()+6)%7),t.getFullYear()}function J(e){const t=document.createElement(\"div\");return t.textContent=e||\"\",t.innerHTML}setInterval(Q,6e4),setTimeout(Q,1e3);const W=[\"apfel\",\"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\"],P=[\"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.8

    \\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.8\\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\"),r=document.getElementById(\"btn-login-open\"),i=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\"),C=document.getElementById(\"btn-add-tag\"),D=document.getElementById(\"tag-input\"),O=document.getElementById(\"btn-history\"),N=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\"),z()})}),A&&A.addEventListener(\"click\",()=>{E.classList.remove(\"hidden\")}),x&&x.addEventListener(\"click\",()=>{E.classList.add(\"hidden\")}),O.addEventListener(\"click\",()=>{d?(N.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 r=o.length>0?`${e}/user/orders/?venue=591&ordering=-created&limit=5`:`${e}/user/orders/?venue=591&ordering=-created&limit=50`,i=[],l=0,c=0===o.length,m=!1;try{for(;r&&!m;){const e=await fetch(r,{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}}i.push(e)}if(!m&&c)if(l>0){const e=Math.round(i.length/l*100);a.style.width=`${e}%`,s.textContent=`Lade Bestellung ${i.length} von ${l}...`}else s.textContent=`Lade Bestellung ${i.length}...`;else m||(s.textContent=`${i.length} neue/ge\u00e4nderte Bestellungen gefunden...`);r=m?null:t.next}if(i.length>0){const e=new Map(o.map(e=>[e.id,e]));for(const t of i)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\",()=>{N.classList.add(\"hidden\")}),window.addEventListener(\"click\",e=>{e.target===N&&N.classList.add(\"hidden\"),e.target===E&&E.classList.add(\"hidden\")});const q=document.querySelector(\".version-tag\"),$=document.getElementById(\"version-modal\"),K=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.8\";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 r(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=j(e.tag,a),o=document.createElement(\"li\");o.className=\"version-item\"+(t?\" current\":\"\");let r=\"\";t?r='\u2713 Installiert':s&&(r='\u2b06 Neu!');let i=\"\";t||(i=`Installieren`),o.innerHTML=`\\n
      \\n ${e.tag}\\n ${r}\\n
      \\n ${i}\\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 H(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,r(!1),n.onchange=()=>{localStorage.setItem(\"kantine_dev_mode\",n.checked),localStorage.removeItem(\"kantine_version_cache\"),r(!0)}}()}),K&&K.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\")}),C.addEventListener(\"click\",()=>{(function(e){if(e=e.trim().toLowerCase(),e&&!L.includes(e))return L.push(e),S(),!0;return!1})(D.value)&&(D.value=\"\",B())}),D.addEventListener(\"keypress\",e=>{\"Enter\"===e.key&&C.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\"),z())}),a.addEventListener(\"click\",()=>{a.classList.remove(\"new-week-available\"),\"next-week\"!==c&&(c=\"next-week\",a.classList.add(\"active\"),n.classList.remove(\"active\"),z())}),s.addEventListener(\"click\",()=>{d?M():k.classList.remove(\"hidden\")}),r.addEventListener(\"click\",()=>{k.classList.remove(\"hidden\"),document.getElementById(\"login-error\").classList.add(\"hidden\"),g.reset()}),i.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\"),r=g.querySelector('button[type=\"submit\"]'),i=r.textContent;r.disabled=!0,r.textContent=\"Wird eingeloggt...\";try{const n=`knapp-${a}@bessa.app`,r=await fetch(`${e}/auth/login/`,{method:\"POST\",headers:f(t),body:JSON.stringify({email:n,password:s})}),i=await r.json();if(r.ok){d=i.key,m=a,localStorage.setItem(\"kantine_authToken\",i.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(),M()}else o.textContent=i.non_field_errors?.[0]||i.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{r.disabled=!1,r.textContent=i}}),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(),z()})}(),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(C),t=localStorage.getItem(D);if(console.log(`[Cache] localStorage: key=${!!e} (${e?e.length:0} chars), ts=${t}`),e){r=JSON.parse(e),i=U(new Date),l=(new Date).getFullYear(),console.log(`[Cache] Parsed ${r.length} weeks:`,r.map(e=>`KW${e.weekNumber}/${e.year} (${(e.days||[]).length} days)`)),z(),$(),A(),t&&q(t);try{const e=new Set;r.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(D);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=U(new Date),s=G(new Date),o=r.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\"),M()):console.log(\"Cache fresh & complete \u2013 skipping API refresh\")):M(),d&&I(),K(),setInterval(K,36e5),console.log(\"Kantine Wrapper loaded \u2705\")}();\n"; document.head.appendChild(sc); })(); diff --git a/dist/bookmarklet.txt b/dist/bookmarklet.txt index 0be2e6e..dba9d67 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 r=[],i=U(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\":\"1.7.0_prod/2026-01-26\"}}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=\"\"),z()}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.`),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\")}`,r=t.toLocaleString(\"de-AT\",{month:\"long\"}),i=U(t);n[a]||(n[a]={year:a,months:{}}),n[a].months[o]||(n[a].months[o]={name:r,year:a,monthIndex:s,count:0,total:0,weeks:{}}),n[a].months[o].weeks[i]||(n[a].months[o].weeks[i]={label:`KW ${i}`,items:[],count:0,total:0});(e.items||[]).forEach(t=>{const s=parseFloat(t.price||e.total||0);n[a].months[o].weeks[i].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[i].count++,n[a].months[o].weeks[i].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 ${J(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 r)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));const o=new Date(a),i=Date.now()-o.getTime(),l=Math.floor(i/6e4);s=l<1?\"gerade eben\":l<60?`vor ${l} Min.`:`vor ${Math.floor(l/60)} Std.`,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 i=`${n}_${a}`;let l=!1;g.has(i)?(g.delete(i),F(`Flag entfernt f\u00fcr ${s}`,\"success\")):(g.add(i),l=!0,F(`Benachrichtigung aktiviert f\u00fcr ${s}`,\"success\"),\"default\"===Notification.permission&&Notification.requestPermission()),k(),A(),z(),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 i=[];for(const e of o)e.items&&Array.isArray(e.items)&&(i=i.concat(e.items));for(let e of r){if(!e.days)continue;let n=e.days.find(e=>e.date===t);n&&(n.items=i.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(),z())}()}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\"}),M()}}}catch(e){console.error(`Poll error for ${t}:`,e),await new Promise(e=>setTimeout(e,200))}}}(),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)),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 C=\"kantine_menuCache\",D=\"kantine_menuCacheTs\";function O(){try{localStorage.setItem(C,JSON.stringify(r)),localStorage.setItem(D,(new Date).toISOString())}catch(e){console.warn(\"Failed to cache menu data:\",e)}}async function M(){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;r&&r.length>0&&r.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=U(t),a=G(t),s=`${a}-${n}`;y.has(s)||y.set(s,{year:a,weekNumber:n,days:[]});const o=y.get(s),r=t.toLocaleDateString(\"en-US\",{weekday:\"long\"}),i=new Date(e.date);i.setHours(10,0,0,0);const l={date:e.date,weekday:r,orderCutoff:i.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)}r=Array.from(y.values()).sort((e,t)=>e.year!==t.year?e.year-t.year:e.weekNumber-t.weekNumber),r.forEach(e=>{e.days&&e.days.sort((e,t)=>e.date.localeCompare(t.date))}),O(),q((new Date).toISOString()),i=U(new Date),l=(new Date).getFullYear(),v(),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 N=null,T=null;function q(e){const t=document.getElementById(\"last-updated-subtitle\");if(e){N=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=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.`}(n);t.textContent=`Aktualisiert: ${s} ${a} (${o})`}catch(e){t.textContent=\"\"}T||(T=setInterval(()=>{N&&q(N)},6e4))}}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 $(){const e=document.getElementById(\"btn-next-week\");let t=i+1,n=l;t>52&&(t=1,n++);const a=r.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 r=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)&&r++})}),r>0&&(m.innerHTML+=`(${r})`,m.title+=` \u2022 ${r} 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 z(){const t=document.getElementById(\"menu-container\");if(!t)return;t.innerHTML=\"\";let a=i,s=l;\"next-week\"===c&&(a++,a>52&&(a=1,s++));const o=r.flatMap(e=>e.days||[]).filter(e=>{const t=new Date(e.date);return U(t)===a&&G(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 r=!1;if(t.orderCutoff)r=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),r=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+=\"+\"),i.push(e)}}});const l=document.createElement(\"div\");l.className=\"card-header\";const c=o.toLocaleDateString(\"de-DE\",{day:\"2-digit\",month:\"2-digit\"}),m=i.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&&!r?\"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}`),r=u.has(`${t.date}_${s}`);if(o&&!r)return-1;if(!o&&r)return 1}return e.name.localeCompare(n.name)});return S.forEach(a=>{const o=document.createElement(\"div\");o.className=\"menu-item\";const i=a.articleId||parseInt(a.id.split(\"_\")[1]),l=`${t.date}_${i}`,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}_${i}`,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&&!r){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${J(e)}`).join(\"\")}
      `}o.innerHTML=`\\n
      \\n ${J(a.name)}\\n ${a.price.toFixed(2)} \u20ac\\n
      \\n
      \\n ${h}\\n ${L}\\n ${I}\\n ${S}\\n
      ${m}
      \\n
      \\n ${B}\\n

      ${J(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)/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;W.includes(a)?s=a.length:W.forEach(e=>{a.includes(e)&&e.length>s&&(s=e.length)}),P.includes(a)?o=a.length:P.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;el.en&&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=e.split(/\\s*\\/\\s*(?![A-Z,]+\\))/);if(s.length>4)return{de:t,en:\"\",raw:t};const o=[],r=[];o.push(s[0].trim());const i=/(?:\\(|(?:\\/|\\s|^))([A-Z,]+)\\)\\s*/;for(let e=1;e0&&(o[o.length-1]=o[o.length-1]+\"(\"+s+\")\"),i&&o.push(i)}else{const e=a(t);r.push(e.enPart),e.nextDe&&o.push(e.nextDe)}}1===s.length&&0===r.length&&r.push(o[0]);if(o.length>r.length)for(let e=r.length;e0&&!l.startsWith(\"\u2022 \")&&(l=\"\u2022 \"+l);let c=r.join(\"\\n\u2022 \");r.length>0&&!c.startsWith(\"\u2022 \")&&(c=\"\u2022 \"+c);return{de:l,en:c,raw:t}}(e);return\"en\"===p?t.en||t.raw:t.de||t.raw}(a.description))}

      `;const C=o.querySelector(\".btn-order\");C&&C.addEventListener(\"click\",t=>{t.stopPropagation();const a=t.currentTarget;a.disabled=!0,a.classList.add(\"loading\"),async function(t,a,s,o,r){if(d)try{const i=await fetch(`${e}/auth/user/`,{headers:f(d)});if(!i.ok)return void F(\"Fehler: Benutzerdaten konnten nicht geladen werden\",\"error\");const l=await i.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:r||\"\",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 D=o.querySelector(\".btn-cancel\");D&&D.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 r=o[o.length-1];try{const t=await fetch(`${e}/user/orders/${r}/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 j(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 H(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 K(){const e=\"v1.6.7\",t=\"true\"===localStorage.getItem(\"kantine_dev_mode\");try{const n=await H(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\"})`),!j(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 Q(){if(!d||!m)return void X();const e=new Date,t=e.getDay();if(0===t||6===t)return void X();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 X();const s=new Date;s.setHours(10,0,0,0);const o=s-e;if(o<=0)return void X();const r=Math.floor(o/36e5),i=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: ${r}h ${i}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 X(){const e=document.getElementById(\"order-countdown\");e&&e.remove()}function U(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 G(e){const t=new Date(e.getTime());return t.setDate(t.getDate()+3-(t.getDay()+6)%7),t.getFullYear()}function J(e){const t=document.createElement(\"div\");return t.textContent=e||\"\",t.innerHTML}setInterval(Q,6e4),setTimeout(Q,1e3);const W=[\"apfel\",\"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\"],P=[\"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.7

      \\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.7\\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\"),r=document.getElementById(\"btn-login-open\"),i=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\"),C=document.getElementById(\"btn-add-tag\"),D=document.getElementById(\"tag-input\"),O=document.getElementById(\"btn-history\"),N=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\"),z()})}),A&&A.addEventListener(\"click\",()=>{E.classList.remove(\"hidden\")}),x&&x.addEventListener(\"click\",()=>{E.classList.add(\"hidden\")}),O.addEventListener(\"click\",()=>{d?(N.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 r=o.length>0?`${e}/user/orders/?venue=591&ordering=-created&limit=5`:`${e}/user/orders/?venue=591&ordering=-created&limit=50`,i=[],l=0,c=0===o.length,m=!1;try{for(;r&&!m;){const e=await fetch(r,{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}}i.push(e)}if(!m&&c)if(l>0){const e=Math.round(i.length/l*100);a.style.width=`${e}%`,s.textContent=`Lade Bestellung ${i.length} von ${l}...`}else s.textContent=`Lade Bestellung ${i.length}...`;else m||(s.textContent=`${i.length} neue/ge\u00e4nderte Bestellungen gefunden...`);r=m?null:t.next}if(i.length>0){const e=new Map(o.map(e=>[e.id,e]));for(const t of i)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\",()=>{N.classList.add(\"hidden\")}),window.addEventListener(\"click\",e=>{e.target===N&&N.classList.add(\"hidden\"),e.target===E&&E.classList.add(\"hidden\")});const q=document.querySelector(\".version-tag\"),$=document.getElementById(\"version-modal\"),K=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.7\";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 r(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=j(e.tag,a),o=document.createElement(\"li\");o.className=\"version-item\"+(t?\" current\":\"\");let r=\"\";t?r='\u2713 Installiert':s&&(r='\u2b06 Neu!');let i=\"\";t||(i=`Installieren`),o.innerHTML=`\\n
        \\n ${e.tag}\\n ${r}\\n
        \\n ${i}\\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 H(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,r(!1),n.onchange=()=>{localStorage.setItem(\"kantine_dev_mode\",n.checked),localStorage.removeItem(\"kantine_version_cache\"),r(!0)}}()}),K&&K.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\")}),C.addEventListener(\"click\",()=>{(function(e){if(e=e.trim().toLowerCase(),e&&!L.includes(e))return L.push(e),S(),!0;return!1})(D.value)&&(D.value=\"\",B())}),D.addEventListener(\"keypress\",e=>{\"Enter\"===e.key&&C.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\"),z())}),a.addEventListener(\"click\",()=>{a.classList.remove(\"new-week-available\"),\"next-week\"!==c&&(c=\"next-week\",a.classList.add(\"active\"),n.classList.remove(\"active\"),z())}),s.addEventListener(\"click\",()=>{d?M():k.classList.remove(\"hidden\")}),r.addEventListener(\"click\",()=>{k.classList.remove(\"hidden\"),document.getElementById(\"login-error\").classList.add(\"hidden\"),g.reset()}),i.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\"),r=g.querySelector('button[type=\"submit\"]'),i=r.textContent;r.disabled=!0,r.textContent=\"Wird eingeloggt...\";try{const n=`knapp-${a}@bessa.app`,r=await fetch(`${e}/auth/login/`,{method:\"POST\",headers:f(t),body:JSON.stringify({email:n,password:s})}),i=await r.json();if(r.ok){d=i.key,m=a,localStorage.setItem(\"kantine_authToken\",i.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(),M()}else o.textContent=i.non_field_errors?.[0]||i.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{r.disabled=!1,r.textContent=i}}),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(),z()})}(),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(C),t=localStorage.getItem(D);if(console.log(`[Cache] localStorage: key=${!!e} (${e?e.length:0} chars), ts=${t}`),e){r=JSON.parse(e),i=U(new Date),l=(new Date).getFullYear(),console.log(`[Cache] Parsed ${r.length} weeks:`,r.map(e=>`KW${e.weekNumber}/${e.year} (${(e.days||[]).length} days)`)),z(),$(),A(),t&&q(t);try{const e=new Set;r.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(D);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=U(new Date),s=G(new Date),o=r.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\"),M()):console.log(\"Cache fresh & complete \u2013 skipping API refresh\")):M(),d&&I(),K(),setInterval(K,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 r=[],i=U(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\":\"1.7.0_prod/2026-01-26\"}}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=\"\"),z()}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.`),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\")}`,r=t.toLocaleString(\"de-AT\",{month:\"long\"}),i=U(t);n[a]||(n[a]={year:a,months:{}}),n[a].months[o]||(n[a].months[o]={name:r,year:a,monthIndex:s,count:0,total:0,weeks:{}}),n[a].months[o].weeks[i]||(n[a].months[o].weeks[i]={label:`KW ${i}`,items:[],count:0,total:0});(e.items||[]).forEach(t=>{const s=parseFloat(t.price||e.total||0);n[a].months[o].weeks[i].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[i].count++,n[a].months[o].weeks[i].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 ${J(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 r)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));const o=new Date(a),i=Date.now()-o.getTime(),l=Math.floor(i/6e4);s=l<1?\"gerade eben\":l<60?`vor ${l} Min.`:`vor ${Math.floor(l/60)} Std.`,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 i=`${n}_${a}`;let l=!1;g.has(i)?(g.delete(i),F(`Flag entfernt f\u00fcr ${s}`,\"success\")):(g.add(i),l=!0,F(`Benachrichtigung aktiviert f\u00fcr ${s}`,\"success\"),\"default\"===Notification.permission&&Notification.requestPermission()),k(),A(),z(),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 i=[];for(const e of o)e.items&&Array.isArray(e.items)&&(i=i.concat(e.items));for(let e of r){if(!e.days)continue;let n=e.days.find(e=>e.date===t);n&&(n.items=i.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(),z())}()}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\"}),M()}}}catch(e){console.error(`Poll error for ${t}:`,e),await new Promise(e=>setTimeout(e,200))}}}(),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)),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 C=\"kantine_menuCache\",D=\"kantine_menuCacheTs\";function O(){try{localStorage.setItem(C,JSON.stringify(r)),localStorage.setItem(D,(new Date).toISOString())}catch(e){console.warn(\"Failed to cache menu data:\",e)}}async function M(){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;r&&r.length>0&&r.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=U(t),a=G(t),s=`${a}-${n}`;y.has(s)||y.set(s,{year:a,weekNumber:n,days:[]});const o=y.get(s),r=t.toLocaleDateString(\"en-US\",{weekday:\"long\"}),i=new Date(e.date);i.setHours(10,0,0,0);const l={date:e.date,weekday:r,orderCutoff:i.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)}r=Array.from(y.values()).sort((e,t)=>e.year!==t.year?e.year-t.year:e.weekNumber-t.weekNumber),r.forEach(e=>{e.days&&e.days.sort((e,t)=>e.date.localeCompare(t.date))}),O(),q((new Date).toISOString()),i=U(new Date),l=(new Date).getFullYear(),v(),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 N=null,T=null;function q(e){const t=document.getElementById(\"last-updated-subtitle\");if(e){N=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=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.`}(n);t.textContent=`Aktualisiert: ${s} ${a} (${o})`}catch(e){t.textContent=\"\"}T||(T=setInterval(()=>{N&&q(N)},6e4))}}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 $(){const e=document.getElementById(\"btn-next-week\");let t=i+1,n=l;t>52&&(t=1,n++);const a=r.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 r=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)&&r++})}),r>0&&(m.innerHTML+=`(${r})`,m.title+=` \u2022 ${r} 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 z(){const t=document.getElementById(\"menu-container\");if(!t)return;t.innerHTML=\"\";let a=i,s=l;\"next-week\"===c&&(a++,a>52&&(a=1,s++));const o=r.flatMap(e=>e.days||[]).filter(e=>{const t=new Date(e.date);return U(t)===a&&G(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 r=!1;if(t.orderCutoff)r=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),r=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+=\"+\"),i.push(e)}}});const l=document.createElement(\"div\");l.className=\"card-header\";const c=o.toLocaleDateString(\"de-DE\",{day:\"2-digit\",month:\"2-digit\"}),m=i.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&&!r?\"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}`),r=u.has(`${t.date}_${s}`);if(o&&!r)return-1;if(!o&&r)return 1}return e.name.localeCompare(n.name)});return S.forEach(a=>{const o=document.createElement(\"div\");o.className=\"menu-item\";const i=a.articleId||parseInt(a.id.split(\"_\")[1]),l=`${t.date}_${i}`,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}_${i}`,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&&!r){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${J(e)}`).join(\"\")}
        `}o.innerHTML=`\\n
        \\n ${J(a.name)}\\n ${a.price.toFixed(2)} \u20ac\\n
        \\n
        \\n ${h}\\n ${L}\\n ${I}\\n ${S}\\n
        ${m}
        \\n
        \\n ${B}\\n

        ${J(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)/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;W.includes(a)?s=a.length:W.forEach(e=>{a.includes(e)&&e.length>s&&(s=e.length)}),P.includes(a)?o=a.length:P.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;el.en&&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=e.split(/\\s*\\/\\s*(?![A-Z,]+\\))/);if(s.length>4)return{de:t,en:\"\",raw:t};const o=[],r=[];o.push(s[0].trim());const i=/(?:\\(|(?:\\/|\\s|^))([A-Z,]+)\\)\\s*/;for(let e=1;e0&&(o[o.length-1]=o[o.length-1]+\"(\"+s+\")\"),i&&o.push(i)}else{const e=a(t);r.push(e.enPart),e.nextDe&&o.push(e.nextDe)}}1===s.length&&0===r.length&&r.push(o[0]);if(o.length>r.length)for(let e=r.length;e0&&!l.startsWith(\"\u2022 \")&&(l=\"\u2022 \"+l);let c=r.join(\"\\n\u2022 \");r.length>0&&!c.startsWith(\"\u2022 \")&&(c=\"\u2022 \"+c);return{de:l,en:c,raw:t}}(e);return\"en\"===p?t.en||t.raw:t.de||t.raw}(a.description))}

        `;const C=o.querySelector(\".btn-order\");C&&C.addEventListener(\"click\",t=>{t.stopPropagation();const a=t.currentTarget;a.disabled=!0,a.classList.add(\"loading\"),async function(t,a,s,o,r){if(d)try{const i=await fetch(`${e}/auth/user/`,{headers:f(d)});if(!i.ok)return void F(\"Fehler: Benutzerdaten konnten nicht geladen werden\",\"error\");const l=await i.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:r||\"\",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 D=o.querySelector(\".btn-cancel\");D&&D.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 r=o[o.length-1];try{const t=await fetch(`${e}/user/orders/${r}/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 j(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 H(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 K(){const e=\"v1.6.8\",t=\"true\"===localStorage.getItem(\"kantine_dev_mode\");try{const n=await H(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\"})`),!j(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 Q(){if(!d||!m)return void X();const e=new Date,t=e.getDay();if(0===t||6===t)return void X();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 X();const s=new Date;s.setHours(10,0,0,0);const o=s-e;if(o<=0)return void X();const r=Math.floor(o/36e5),i=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: ${r}h ${i}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 X(){const e=document.getElementById(\"order-countdown\");e&&e.remove()}function U(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 G(e){const t=new Date(e.getTime());return t.setDate(t.getDate()+3-(t.getDay()+6)%7),t.getFullYear()}function J(e){const t=document.createElement(\"div\");return t.textContent=e||\"\",t.innerHTML}setInterval(Q,6e4),setTimeout(Q,1e3);const W=[\"apfel\",\"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\"],P=[\"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.8

        \\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.8\\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\"),r=document.getElementById(\"btn-login-open\"),i=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\"),C=document.getElementById(\"btn-add-tag\"),D=document.getElementById(\"tag-input\"),O=document.getElementById(\"btn-history\"),N=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\"),z()})}),A&&A.addEventListener(\"click\",()=>{E.classList.remove(\"hidden\")}),x&&x.addEventListener(\"click\",()=>{E.classList.add(\"hidden\")}),O.addEventListener(\"click\",()=>{d?(N.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 r=o.length>0?`${e}/user/orders/?venue=591&ordering=-created&limit=5`:`${e}/user/orders/?venue=591&ordering=-created&limit=50`,i=[],l=0,c=0===o.length,m=!1;try{for(;r&&!m;){const e=await fetch(r,{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}}i.push(e)}if(!m&&c)if(l>0){const e=Math.round(i.length/l*100);a.style.width=`${e}%`,s.textContent=`Lade Bestellung ${i.length} von ${l}...`}else s.textContent=`Lade Bestellung ${i.length}...`;else m||(s.textContent=`${i.length} neue/ge\u00e4nderte Bestellungen gefunden...`);r=m?null:t.next}if(i.length>0){const e=new Map(o.map(e=>[e.id,e]));for(const t of i)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\",()=>{N.classList.add(\"hidden\")}),window.addEventListener(\"click\",e=>{e.target===N&&N.classList.add(\"hidden\"),e.target===E&&E.classList.add(\"hidden\")});const q=document.querySelector(\".version-tag\"),$=document.getElementById(\"version-modal\"),K=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.8\";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 r(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=j(e.tag,a),o=document.createElement(\"li\");o.className=\"version-item\"+(t?\" current\":\"\");let r=\"\";t?r='\u2713 Installiert':s&&(r='\u2b06 Neu!');let i=\"\";t||(i=`Installieren`),o.innerHTML=`\\n
          \\n ${e.tag}\\n ${r}\\n
          \\n ${i}\\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 H(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,r(!1),n.onchange=()=>{localStorage.setItem(\"kantine_dev_mode\",n.checked),localStorage.removeItem(\"kantine_version_cache\"),r(!0)}}()}),K&&K.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\")}),C.addEventListener(\"click\",()=>{(function(e){if(e=e.trim().toLowerCase(),e&&!L.includes(e))return L.push(e),S(),!0;return!1})(D.value)&&(D.value=\"\",B())}),D.addEventListener(\"keypress\",e=>{\"Enter\"===e.key&&C.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\"),z())}),a.addEventListener(\"click\",()=>{a.classList.remove(\"new-week-available\"),\"next-week\"!==c&&(c=\"next-week\",a.classList.add(\"active\"),n.classList.remove(\"active\"),z())}),s.addEventListener(\"click\",()=>{d?M():k.classList.remove(\"hidden\")}),r.addEventListener(\"click\",()=>{k.classList.remove(\"hidden\"),document.getElementById(\"login-error\").classList.add(\"hidden\"),g.reset()}),i.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\"),r=g.querySelector('button[type=\"submit\"]'),i=r.textContent;r.disabled=!0,r.textContent=\"Wird eingeloggt...\";try{const n=`knapp-${a}@bessa.app`,r=await fetch(`${e}/auth/login/`,{method:\"POST\",headers:f(t),body:JSON.stringify({email:n,password:s})}),i=await r.json();if(r.ok){d=i.key,m=a,localStorage.setItem(\"kantine_authToken\",i.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(),M()}else o.textContent=i.non_field_errors?.[0]||i.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{r.disabled=!1,r.textContent=i}}),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(),z()})}(),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(C),t=localStorage.getItem(D);if(console.log(`[Cache] localStorage: key=${!!e} (${e?e.length:0} chars), ts=${t}`),e){r=JSON.parse(e),i=U(new Date),l=(new Date).getFullYear(),console.log(`[Cache] Parsed ${r.length} weeks:`,r.map(e=>`KW${e.weekNumber}/${e.year} (${(e.days||[]).length} days)`)),z(),$(),A(),t&&q(t);try{const e=new Set;r.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(D);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=U(new Date),s=G(new Date),o=r.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\"),M()):console.log(\"Cache fresh & complete \u2013 skipping API refresh\")):M(),d&&I(),K(),setInterval(K,36e5),console.log(\"Kantine Wrapper loaded \u2705\")}();\n"; document.head.appendChild(sc); })(); diff --git a/dist/install.html b/dist/install.html index dbb5566..a835fb6 100755 --- a/dist/install.html +++ b/dist/install.html @@ -2,7 +2,7 @@ - Kantine Wrapper Installer (v1.6.7) + Kantine Wrapper Installer (v1.6.8)