Metzgerei Decker – Preisaushang /* ── FONTS ────────────────────────────────────────────────────── */ @font-face { font-family: 'Brandon'; src: url('Brandon_bld.otf') format('opentype'); font-weight: 700; font-style: normal; } @font-face { font-family: 'Brandon'; src: url('Brandon_reg.otf') format('opentype'); font-weight: 400; font-style: normal; } @font-face { font-family: 'GoodlifeScript'; src: url('GoodlifeScript.otf') format('opentype'); font-weight: 400; font-style: normal; } /* ── RESET & VARIABLES ───────────────────────────────────────── */ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } :root { --charcoal: #2B2B2B; --charcoal2: #1E1E1E; --charcoal3: #3A3A3A; --red: #C13333; --red-dark: #A02828; --cream: #F7F2EB; --gold: #B8964E; --white: #FFFFFF; --dim: #999999; --cell-bg: #2E2E2E; --sep: rgba(255,255,255,0.10); --nav-h: 56px; --safe-top: env(safe-area-inset-top, 0px); --safe-bottom: env(safe-area-inset-bottom,0px); } html, body { height: 100%; width: 100%; background: var(--charcoal2); color: var(--white); font-family: 'Brandon', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Arial, sans-serif; font-weight: 400; -webkit-font-smoothing: antialiased; overflow: hidden; user-select: none; -webkit-user-select: none; } /* ── SCREENS ─────────────────────────────────────────────────── */ .screen { position: fixed; inset: 0; display: flex; flex-direction: column; background: var(--charcoal2); transition: transform .32s cubic-bezier(.4,0,.2,1), opacity .32s ease; z-index: 10; } .screen.hidden { transform: translateX(100%); pointer-events: none; } .screen.hidden-back { transform: translateX(-30%); opacity: .5; pointer-events: none; } .screen.active { transform: translateX(0); opacity: 1; } /* ── NAV BAR ─────────────────────────────────────────────────── */ .nav-bar { flex-shrink: 0; height: calc(var(--nav-h) + var(--safe-top)); padding-top: var(--safe-top); background: rgba(30,30,30,.88); backdrop-filter: blur(20px) saturate(180%); -webkit-backdrop-filter: blur(20px) saturate(180%); border-bottom: 1px solid var(--sep); display: flex; align-items: flex-end; padding-bottom: 8px; padding-left: 16px; padding-right: 16px; position: relative; z-index: 2; } .nav-back { color: var(--red); font-family: 'Brandon', sans-serif; font-size: 16px; font-weight: 400; background: none; border: none; cursor: pointer; display: flex; align-items: center; gap: 4px; padding: 4px 8px 4px 0; min-width: 80px; } .nav-back svg { width: 10px; height: 16px; } .nav-title { position: absolute; left: 50%; bottom: 8px; transform: translateX(-50%); font-family: 'Brandon', sans-serif; font-weight: 700; font-size: 17px; letter-spacing: .04em; text-transform: uppercase; color: var(--white); white-space: nowrap; } .nav-action { margin-left: auto; color: var(--red); font-family: 'Brandon', sans-serif; font-size: 16px; font-weight: 700; background: none; border: none; cursor: pointer; padding: 4px 0 4px 8px; } /* ── SCROLL ──────────────────────────────────────────────────── */ .scroll-content { flex: 1; overflow-y: auto; overflow-x: hidden; -webkit-overflow-scrolling: touch; } .scroll-content::-webkit-scrollbar { display: none; } /* ───────────────────────────────────────────────────────────── LOGIN SCREEN ───────────────────────────────────────────────────────────── */ #screen-login { justify-content: center; align-items: center; padding: 40px 32px calc(40px + var(--safe-bottom)); } .login-logo-wrap { display: flex; flex-direction: column; align-items: center; margin-bottom: 44px; } .login-logo-name { font-family: 'Brandon', sans-serif; font-weight: 700; font-size: 28px; letter-spacing: .18em; text-transform: uppercase; color: var(--white); line-height: 1; } .login-logo-name span { color: var(--red); } .login-logo-script { font-family: 'GoodlifeScript', cursive; font-size: 22px; color: var(--gold); margin-top: 6px; } .login-logo-since { font-size: 11px; letter-spacing: .25em; text-transform: uppercase; color: var(--dim); margin-top: 8px; } .login-line { width: 40px; height: 3px; background: var(--red); margin-top: 18px; border-radius: 2px; } .login-card { width: 100%; max-width: 380px; background: var(--charcoal3); border-radius: 18px; overflow: hidden; box-shadow: 0 8px 40px rgba(0,0,0,.5); } .login-card-head { background: var(--charcoal); padding: 20px 24px 16px; border-bottom: 1px solid var(--sep); } .login-card-head h2 { font-family: 'Brandon', sans-serif; font-weight: 700; font-size: 18px; letter-spacing: .06em; text-transform: uppercase; } .login-card-head p { font-size: 13px; color: var(--dim); margin-top: 4px; } .login-card-body { padding: 24px; } .login-label { display: block; font-size: 11px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: var(--dim); margin-bottom: 10px; } .login-input { width: 100%; background: var(--charcoal2); border: 1.5px solid rgba(255,255,255,.12); border-radius: 10px; color: var(--white); font-family: 'Brandon', sans-serif; font-size: 18px; font-weight: 700; letter-spacing: .12em; padding: 14px 16px; outline: none; transition: border-color .2s; text-align: center; } .login-input:focus { border-color: var(--red); } .login-btn { margin-top: 20px; width: 100%; background: var(--red); color: var(--white); font-family: 'Brandon', sans-serif; font-weight: 700; font-size: 16px; letter-spacing: .08em; text-transform: uppercase; border: none; border-radius: 12px; padding: 15px; cursor: pointer; transition: background .15s, transform .1s; -webkit-tap-highlight-color: transparent; } .login-btn:active { background: var(--red-dark); transform: scale(.98); } .login-error { margin-top: 12px; text-align: center; color: #FF6B6B; font-size: 14px; min-height: 20px; } /* ───────────────────────────────────────────────────────────── HOME SCREEN ───────────────────────────────────────────────────────────── */ .home-header { flex-shrink: 0; padding: calc(var(--safe-top) + 16px) 20px 16px; } .home-brand { display: flex; align-items: baseline; gap: 10px; margin-bottom: 2px; } .home-brand-name { font-family: 'Brandon', sans-serif; font-weight: 700; font-size: 22px; letter-spacing: .14em; text-transform: uppercase; } .home-brand-name span { color: var(--red); } .home-brand-script { font-family: 'GoodlifeScript', cursive; font-size: 18px; color: var(--gold); } .home-sub { font-size: 12px; letter-spacing: .12em; text-transform: uppercase; color: var(--dim); } .import-bar { flex-shrink: 0; display: flex; align-items: center; justify-content: space-between; padding: 7px 16px; background: rgba(255,255,255,.04); border-bottom: 1px solid var(--sep); gap: 10px; } .import-bar-left { min-width: 0; flex: 1; } .import-status { font-size: 11px; color: var(--dim); font-weight: 400; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block; } .import-status.ok { color: #5CB85C; } .import-status.err { color: #FF6B6B; } .import-btn { flex-shrink: 0; background: var(--charcoal3); color: var(--white); font-family: 'Brandon', sans-serif; font-weight: 700; font-size: 12px; letter-spacing: .06em; text-transform: uppercase; border: 1px solid rgba(255,255,255,.14); border-radius: 8px; padding: 7px 12px; cursor: pointer; white-space: nowrap; display: flex; align-items: center; gap: 5px; -webkit-tap-highlight-color: transparent; transition: background .15s; } .import-btn:active { background: #555; } .home-section-title { font-size: 11px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; color: var(--dim); padding: 20px 20px 10px; } /* Category grid */ .cat-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; padding: 0 16px; } .cat-card { background: var(--charcoal3); border-radius: 16px; padding: 20px 16px 18px; cursor: pointer; position: relative; overflow: hidden; border: 1px solid rgba(255,255,255,.06); -webkit-tap-highlight-color: transparent; transition: transform .12s, background .12s; } .cat-card:active { transform: scale(.96); background: #444; } .cat-card-accent { position: absolute; top: 0; left: 0; right: 0; height: 3px; background: var(--red); border-radius: 16px 16px 0 0; } .cat-card-icon { font-size: 32px; line-height: 1; margin-bottom: 10px; } .cat-card-name { font-family: 'Brandon', sans-serif; font-weight: 700; font-size: 15px; letter-spacing: .04em; line-height: 1.2; } .cat-card-count { margin-top: 6px; font-size: 12px; color: var(--dim); } .cat-card-badge { position: absolute; top: 12px; right: 12px; background: var(--red); color: white; font-size: 11px; font-weight: 700; border-radius: 10px; padding: 2px 7px; display: none; } .cat-card-badge.visible { display: block; } /* Bottom bar */ .home-bottom-bar { flex-shrink: 0; padding: 12px 16px calc(12px + var(--safe-bottom)); background: rgba(30,30,30,.92); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); border-top: 1px solid var(--sep); } .home-bottom-inner { width: 100%; } .home-selected-count { font-size: 12px; color: var(--dim); text-align: center; margin-bottom: 4px; } .home-btn-preview { width: 100%; background: var(--red); color: white; font-family: 'Brandon', sans-serif; font-weight: 700; font-size: 15px; letter-spacing: .08em; text-transform: uppercase; border: none; border-radius: 12px; padding: 14px; cursor: pointer; -webkit-tap-highlight-color: transparent; transition: background .15s, transform .1s; display: flex; align-items: center; justify-content: center; gap: 6px; } .home-btn-preview:active { background: var(--red-dark); transform: scale(.98); } .home-btn-preview:disabled { background: #555; color: #888; cursor: default; transform: none; } /* ───────────────────────────────────────────────────────────── CATEGORY / ARTICLE SCREEN ───────────────────────────────────────────────────────────── */ .cat-search-wrap { padding: 10px 16px 4px; flex-shrink: 0; } .cat-search { width: 100%; background: rgba(255,255,255,.08); border: none; border-radius: 10px; color: var(--white); font-family: 'Brandon', sans-serif; font-size: 15px; padding: 9px 14px 9px 36px; outline: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='M21 21l-4.35-4.35'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: 12px center; background-size: 16px; } .cat-search::placeholder { color: var(--dim); } .select-all-row { display: flex; align-items: center; justify-content: space-between; padding: 6px 20px 2px; flex-shrink: 0; } .select-all-btn { font-size: 14px; color: var(--red); font-family: 'Brandon', sans-serif; font-weight: 700; background: none; border: none; cursor: pointer; padding: 4px 0; } .select-count-label { font-size: 12px; color: var(--dim); } /* Article items */ .article-list { padding: 0 16px 24px; } .article-item { background: var(--cell-bg); border-radius: 12px; margin-bottom: 8px; border: 1.5px solid rgba(255,255,255,.05); overflow: hidden; -webkit-tap-highlight-color: transparent; transition: border-color .2s, background .15s; } .article-item.selected { background: rgba(193,51,51,.12); border-color: rgba(193,51,51,.4); } /* Main row — tap to toggle */ .article-main-row { display: flex; align-items: center; gap: 14px; padding: 14px 16px; cursor: pointer; } .article-check { width: 24px; height: 24px; flex-shrink: 0; border-radius: 50%; border: 2px solid rgba(255,255,255,.25); display: flex; align-items: center; justify-content: center; transition: background .15s, border-color .15s; } .article-item.selected .article-check { background: var(--red); border-color: var(--red); } .article-check svg { display: none; } .article-item.selected .article-check svg { display: block; } .article-info { flex: 1; min-width: 0; } .article-name { font-family: 'Brandon', sans-serif; font-weight: 700; font-size: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .article-meta { font-size: 11px; color: var(--dim); margin-top: 3px; line-height: 1.4; } .article-meta-zs { color: #B8964E; } .article-meta-al { color: #7FB3D3; } /* ── PRICE ROW — animiertes Aufklappen ──── */ .article-price-row { max-height: 0; overflow: hidden; transition: max-height .3s cubic-bezier(.4,0,.2,1), opacity .25s ease, padding .3s ease; opacity: 0; border-top: 0px solid rgba(193,51,51,.25); } .article-item.selected .article-price-row { max-height: 72px; opacity: 1; border-top-width: 1px; } .article-price-inner { display: flex; align-items: center; gap: 10px; padding: 10px 16px 12px; } .price-label { font-size: 11px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--dim); white-space: nowrap; } .price-field { flex: 1; background: rgba(255,255,255,.08); border: 1.5px solid rgba(255,255,255,.15); border-radius: 9px; color: var(--white); font-family: 'Brandon', sans-serif; font-size: 18px; font-weight: 700; letter-spacing: .02em; padding: 8px 10px; outline: none; text-align: right; transition: border-color .2s; min-width: 0; } .price-field:focus { border-color: var(--red); } .price-field::placeholder { color: rgba(255,255,255,.25); font-weight: 400; font-size: 15px; } .unit-select { background: rgba(255,255,255,.08); border: 1.5px solid rgba(255,255,255,.15); border-radius: 9px; color: var(--white); font-family: 'Brandon', sans-serif; font-size: 12px; font-weight: 700; letter-spacing: .02em; padding: 8px 6px; outline: none; cursor: pointer; -webkit-appearance: none; text-align: center; width: 72px; flex-shrink: 0; } .unit-select:focus { border-color: var(--red); } /* ───────────────────────────────────────────────────────────── PREVIEW SCREEN ───────────────────────────────────────────────────────────── */ .preview-mode-bar { flex-shrink: 0; padding: 10px 16px 8px; border-bottom: 1px solid var(--sep); display: flex; align-items: center; gap: 10px; } .mode-toggle { flex: 1; display: flex; background: rgba(255,255,255,.08); border-radius: 10px; padding: 3px; gap: 0; } .mode-btn { flex: 1; background: transparent; border: none; border-radius: 8px; color: var(--dim); font-family: 'Brandon', sans-serif; font-weight: 700; font-size: 13px; letter-spacing: .06em; text-transform: uppercase; padding: 8px 0; cursor: pointer; transition: background .2s, color .2s; -webkit-tap-highlight-color: transparent; } .mode-btn.active { background: var(--red); color: var(--white); } .preview-print-btn { background: var(--charcoal3); color: white; font-family: 'Brandon', sans-serif; font-weight: 700; font-size: 13px; letter-spacing: .05em; text-transform: uppercase; border: 1px solid rgba(255,255,255,.12); border-radius: 9px; padding: 9px 14px; cursor: pointer; white-space: nowrap; display: flex; align-items: center; gap: 5px; -webkit-tap-highlight-color: transparent; transition: background .15s; } .preview-print-btn:active { background: #555; } .preview-content { flex: 1; overflow-y: auto; background: #606060; padding: 14px; display: flex; flex-direction: column; gap: 14px; -webkit-overflow-scrolling: touch; } .preview-content::-webkit-scrollbar { display: none; } /* ───────────────────────────────────────────────────────────── A4 POSTER — Querformat 297 × 210 ───────────────────────────────────────────────────────────── */ .poster-wrap { display: flex; justify-content: center; } .poster { background: var(--cream); width: 100%; max-width: 680px; aspect-ratio: 297 / 210; position: relative; overflow: hidden; display: flex; flex-direction: column; box-shadow: 0 4px 24px rgba(0,0,0,.45); border-radius: 2px; } /* header */ .poster-header { flex-shrink: 0; background: var(--charcoal); display: flex; align-items: center; justify-content: space-between; padding: 0 4.5%; height: 19%; border-bottom: 3px solid var(--red); } .poster-metzgerei { font-family: 'Brandon', sans-serif; font-weight: 400; font-size: clamp(6px,1.6vw,10px); letter-spacing: .22em; text-transform: uppercase; color: rgba(255,255,255,.55); line-height: 1; } .poster-decker { font-family: 'Brandon', sans-serif; font-weight: 700; font-size: clamp(13px,3.6vw,24px); letter-spacing: .14em; text-transform: uppercase; color: var(--white); line-height: 1; margin-top: 1px; } .poster-seit-script { font-family: 'GoodlifeScript', cursive; font-size: clamp(9px,2.4vw,16px); color: var(--gold); line-height: 1; text-align: right; } .poster-seit-sub { display: block; font-family: 'Brandon', sans-serif; font-size: clamp(5px,1.2vw,8px); letter-spacing: .18em; text-transform: uppercase; color: rgba(255,255,255,.35); margin-top: 2px; text-align: right; } /* main content */ .poster-main { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 1% 5% 1%; background: var(--cream); gap: 0; } .poster-cat-label { font-family: 'Brandon', sans-serif; font-weight: 400; font-size: clamp(5px,1.3vw,9px); letter-spacing: .28em; text-transform: uppercase; color: var(--red); margin-bottom: 2%; } .poster-name { font-family: 'Brandon', sans-serif; font-weight: 700; font-size: clamp(20px,8vw,56px); letter-spacing: -.01em; text-transform: uppercase; color: var(--charcoal); text-align: center; line-height: .95; } .poster-name.sz-lg { font-size: clamp(15px,5.8vw,20px); } .poster-name.sz-xl { font-size: clamp(12px,4.2vw,18px); } .poster-vb { font-family: 'GoodlifeScript', cursive; font-size: clamp(10px,2.8vw,19px); colo: var(--gold); margin-top: 1%; line-height: 1; } .poster-price-row { display: flex; align-items: baseline; gap: 4px; margin-top: 2.5%; } .poster-price { font-family: 'Brandon', sans-serif; font-weight: 700; font-size: clamp(18px,6vw,42px); color: var(--red); line-height: 1; } .poster-price-unit { font-family: 'Brandon', sans-serif; font-weight: 400; font-size: clamp(7px,1.6vw,11px); color: var(--charcoal); opacity: .65; } /* footer */ .poster-footer { flex-shrink: 0; background: var(--charcoal); padding: 1.8% 4%; border-top: 2px solid var(--red); } .poster-footer-cols { display: flex; gap: 16px; align-items: flex-start; justify-content: space-between; } .poster-footer-col { flex: 1; } .poster-footer-lbl { font-family: 'Brandon', sans-serif; font-weight: 700; font-size: clamp(4px,1vw,6.5px); letter-spacing: .16em; text-transform: uppercase; color: rgba(255,255,255,.38); margin-bottom: 2px; } .poster-footer-val { font-family: 'Brandon', sans-serif; font-weight: 400; font-size: clamp(4px,1.1vw,7.5px); color: rgba(255,255,255,.75); line-height: 1.55; } .poster-footer-none { font-size: clamp(4px,1vw,6.5px); color: rgba(255,255,255,.28); font-style: italic; } .poster-legal { font-family: 'Brandon', sans-serif; font-weight: 400; font-size: clamp(3px,.85vw,5.5px); color: rgba(255,255,255,.22); margin-top: 4px; line-height: 1.4; } /* ───────────────────────────────────────────────────────────── SPEISEKARTE — Hochformat 210 × 297 ───────────────────────────────────────────────────────────── */ .sk-wrap { display: flex; justify-content: center; } .sk-page { background: var(--cream); width: 100%; max-width: 480px; /* DIN A4 portrait aspect ratio */ aspect-ratio: 210 / 297; position: relative; overflow: hidden; display: flex; flex-direction: column; box-shadow: 0 4px 24px rgba(0,0,0,.45); border-radius: 2px; font-family: 'Brandon', sans-serif; } .sk-header { flex-shrink: 0; background: var(--charcoal); padding: 3.5% 5% 3%; border-bottom: 3px solid var(--red); } .sk-header-top { display: flex; justify-content: space-between; align-items: flex-end; } .sk-brand { font-family: 'Brandon', sans-serif; font-weight: 700; font-size: clamp(10px,3.5vw,18px); letter-spacing: .12em; text-transform: uppercase; color: var(--white); } .sk-since { font-family: 'GoodlifeScript', cursive; font-size: clamp(9px,3vw,15px); color: var(--gold); } .sk-sub { font-size: clamp(6px,1.8vw,9px); letter-spacing: .18em; text-transform: uppercase; color: rgba(255,255,255,.38); margin-top: 3px; } .sk-title-bar { background: var(--red); padding: 1.8% 5%; font-family: 'Brandon', sans-serif; font-weight: 700; font-size: clamp(8px,2.8vw,14px); letter-spacing: .18em; text-transform: uppercase; color: var(--white); text-align: center; } /* table area — flex grows, hides overflow */ .sk-table-area { flex: 1; overflow: hidden; } .sk-table { width: 100%; border-collapse: collapse; } .sk-table th { background: #E8E1D8; font-weight: 700; font-size: clamp(5px,1.5vw,8px); letter-spacing: .14em; text-transform: uppercase; color: var(--charcoal); padding: 1.5% 2%; border-bottom: 1px solid #CCC5BB; text-align: left; } .sk-table th.right { text-align: right; } .sk-table td { padding: 1.2% 2%; border-bottom: 1px solid rgba(0,0,0,.06); font-size: clamp(5px,1.6vw,9px); color: var(--charcoal); vertical-align: top; } .sk-table td.right { text-align: right; font-weight: 700; color: var(--red); white-space: nowrap; } .sk-table tr:nth-child(even) td { background: rgba(0,0,0,.025); } .sk-cat-row td { background: var(--charcoal) !important; color: var(--white) !important; font-weight: 700; font-size: clamp(5px,1.4vw,8px); letter-spacing: .14em; text-transform: uppercase; padding: 1.2% 2%; border: none; } .sk-name { font-weight: 700; display: block; } .sk-vb { font-size: clamp(4px,1.2vw,7px); color: #888; display: block; } .sk-zs { color: #B8964E; } .sk-al { color: #5588AA; } .sk-legend { flex-shrink: 0; background: #EDE7DC; padding: 2% 4%; font-size: clamp(4px,1.2vw,7px); line-height: 1.7; color: #666; border-top: 1px solid #CCC5BB; } .sk-legend strong { color: var(--charcoal); } .sk-footer { flex-shrink: 0; background: var(--charcoal); padding: 1.8% 4%; border-top: 1px solid rgba(255,255,255,.08); display: flex; justify-content: space-between; font-size: clamp(4px,1.1vw,7px); color: rgba(255,255,255,.35); } /* empty state */ .empty-state { text-align: center; padding: 60px 30px; color: var(--dim); flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; } .empty-state .icon { font-size: 48px; margin-bottom: 14px; } .empty-state h3 { font-size: 17px; font-weight: 700; color: var(--white); margin-bottom: 8px; } .empty-state p { font-size: 14px; } /* ───────────────────────────────────────────────────────────── PRINT — dynamic @page injected by JS ───────────────────────────────────────────────────────────── */ @media print { body { background: white !important; overflow: visible !important; } .screen { position: static !important; transform: none !important; } #screen-login, #screen-home, #screen-cat { display: none !important; } #screen-preview { display: block !important; position: static !important; } .nav-bar, .preview-mode-bar { display: none !important; } .preview-content { background: white !important; padding: 0 !important; display: block !important; overflow: visible !important; } .poster-wrap, .sk-wrap { page-break-after: always; margin: 0 !important; } .poster, .sk-page { width: 100% !important; max-width: 100% !important; box-shadow: none !important; } /* aspect-ratios work in Chrome print — height is set by @page size */ }
/* ── SOFT-HYPHEN HELPER ─────────────────────────────────── */ const SHY = 'u00AD'; // weiches Trennzeichen /* ── DATA ─────────────────────────────────────────────────── */ // Decker's eigenes ZS-Nummern-System const ZS_L = { 1:'Nitritpökelsalz', 2:'Antioxidationsmittel', 3:'Geschmacksverstärker', 4:'Phosphat', 5:'mit Farbstoff', 6:'koffeinhaltig', 7:'Süßungsmittel', 8:'geschwefelt', 9:'geschwärzt', 10:'gewachst', 11:'enthält Phenylalaninquelle', 12:'chininhaltig', 13:'mit Taurin' }; const AL_L = { G:'Gluten', M:'Milch/Laktose', Ei:'Eier', So:'Soja', Sel:'Sellerie', Sen:'Senf', SES:'Sesam' }; let CATS = [ { name:'Würste & Aufschnitt', icon:'🥩' }, { name:'Grillspezialitäten', icon:'🔥' }, { name:'Snacks & Burger', icon:'🍔' }, { name:'Gerichte & Beilagen', icon:'🍲' }, { name:'Pizza', icon:'🍕' }, { name:'Käse', icon:'🧀' }, { name:'Vegane Produkte', icon:'🌱' }, { name:'Brot & Gebäck', icon:'🥐' }, { name:'Saucen & Dips', icon:'🫙' }, ]; // Standard-Artikelliste (wird durch Excel-Import øberschrieben) const PRODUCTS_DEFAULT = [ // ── Würste & Aufschnitt ────────────────────────────────────────────────── { name:'Angeräucherte Bratwurst', vb:'', cat:'Würste & Aufschnitt', zs:[1,2,3,4], al:['Sel'], preis:'', einheit:'€/Stück' }, { name:'Bauern'+SHY+'wurst geräuchert', vb:'', cat:'Würste & Aufschnitt', zs:[1,2,3], al:['Sel'], preis:'', einheit:'€/Stück' }, { name:'Bier'+SHY+'schinken', vb:'', cat:'Würste & Aufschnitt', zs:[1,2,3,4], al:['Sel','M'], preis:'', einheit:'€/100g' }, { name:'Blut & Leber'+SHY+'würstle', vb:'', cat:'Würste & Aufschnitt', zs:[1,3], al:['Sel','M'], preis:'', einheit:'€/Stück' }, { name:'Bock'+SHY+'wurst / Rote', vb:'', cat:'Würste & Aufschnitt', zs:[1,2,3,4], al:['Sel'], preis:'', einheit:'€/Stück' }, { name:'Brat'+SHY+'wurst Frische-Grobe', vb:'', cat:'Würste & Aufschnitt', zs:[1,2,3,4], al:['Sel'], preis:'', einheit:'€/Stück' }, { name:'Fleisch'+SHY+'salat', vb:'', cat:'Würste & Aufschnitt', zs:[1,2,3,4], al:['Sel','Ei'], preis:'', einheit:'€/100g' }, { name:'Haus'+SHY+'salami', vb:'', cat:'Würste & Aufschnitt', zs:[1,2,3], al:['Sel'], preis:'', einheit:'€/100g' }, { name:'Hexen'+SHY+'würste', vb:'', cat:'Würste & Aufschnitt', zs:[1,2,3,4], al:['Sel'], preis:'', einheit:'€/Stück' }, { name:'Käse'+SHY+'knacker', vb:'', cat:'Würste & Aufschnitt', zs:[1,2,3,4], al:['Sel','M'], preis:'', einheit:'€/Stück' }, { name:'Leber'+SHY+'wurst', vb:'', cat:'Würste & Aufschnitt', zs:[1,2,3], al:['Sel'], preis:'', einheit:'€/100g' }, { name:'Lyoner', vb:'', cat:'Würste & Aufschnitt', zs:[1,2,3,4], al:['Sel'], preis:'', einheit:'€/100g' }, { name:'Merguez', vb:'', cat:'Würste & Aufschnitt', zs:[1,2,3,4], al:['Sel'], preis:'', einheit:'€/Stück' }, { name:'Rauch'+SHY+'lyoner', vb:'', cat:'Würste & Aufschnitt', zs:[1,2,3,4], al:['Sel','Sen'], preis:'', einheit:'€/100g' }, { name:'Rost'+SHY+'brat'+SHY+'wurst', vb:'', cat:'Würste & Aufschnitt', zs:[3,4], al:['Sel'], preis:'', einheit:'€/Stück' }, { name:'Schwarz'+SHY+'wälder Schinken', vb:'', cat:'Würste & Aufschnitt', zs:[1,2,3], al:['Sel'], preis:'', einheit:'€/100g' }, { name:'Schwarz'+SHY+'wurst', vb:'', cat:'Würste & Aufschnitt', zs:[1,2,3], al:['Sel'], preis:'', einheit:'€/100g' }, { name:'Schweine'+SHY+'bauch gepökelt', vb:'', cat:'Würste & Aufschnitt', zs:[1,2,3,4], al:[], preis:'', einheit:'€/100g' }, { name:'Schweine'+SHY+'hals gepökelt', vb:'', cat:'Würste & Aufschnitt', zs:[1,2,3,4], al:[], preis:'', einheit:'€/100g' }, { name:'Weiße Brat'+SHY+'wurst', vb:'', cat:'Würste & Aufschnitt', zs:[3,4], al:['Sel'], preis:'', einheit:'€/Stück' }, { name:'Weiß'+SHY+'wurst', vb:'', cat:'Würste & Aufschnitt', zs:[3,4], al:['Sel','M'], preis:'', einheit:'€/Stück' }, { name:'Wienerle', vb:'', cat:'Würste & Aufschnitt', zs:[1,2,3,4], al:['Sel'], preis:'', einheit:'€/Paar' }, { name:'Wurst'+SHY+'salat', vb:'', cat:'Würste & Aufschnitt', zs:[1,2,3,4], al:['Sel'], preis:'', einheit:'€/Portion' }, // ── Grillspezialitäten ─────────────────────────────────────────────────── { name:'Grill'+SHY+'bauch', vb:'', cat:'Grillspezialitäten', zs:[], al:[], preis:'', einheit:'€/Stück' }, { name:'Grill'+SHY+'gemüse', vb:'', cat:'Grillspezialitäten', zs:[], al:[], preis:'', einheit:'€/Portion' }, { name:'Grillhaxen / Schweine'+SHY+'haxen', vb:'', cat:'Grillspezialitäten', zs:[1,2,3,4], al:['Sel'], preis:'', einheit:'€/Stück' }, { name:'Grobe Brat'+SHY+'wurst mit Zwiebeln', vb:'', cat:'Grillspezialitäten', zs:[3,4], al:['Sel','G'], preis:'', einheit:'€/Stück' }, { name:'Schweine'+SHY+'steak', vb:'', cat:'Grillspezialitäten', zs:[], al:[], preis:'', einheit:'€/Stück' }, { name:'Schweine'+SHY+'hals'+SHY+'steak', vb:'', cat:'Grillspezialitäten', zs:[], al:[], preis:'', einheit:'€/Stück' }, { name:'Schweine'+SHY+'ge'+SHY+'schnetzeltes Gyros', vb:'', cat:'Grillspezialitäten', zs:[], al:[], preis:'', einheit:'€/Portion' }, // ── Snacks & Burger ────────────────────────────────────────────────────── { name:'Bacon'+SHY+'burger', vb:'mit Speck & Käse', cat:'Snacks & Burger', zs:[5], al:['G','Sen','Sel','Ei','M'], preis:'', einheit:'€/Stück' }, { name:'Cheese'+SHY+'burger', vb:'mit Käse', cat:'Snacks & Burger', zs:[5], al:['G','Sen','Ei','M'], preis:'', einheit:'€/Stück' }, { name:'Chicken'+SHY+'burger', vb:'', cat:'Snacks & Burger', zs:[], al:['G'], preis:'', einheit:'€/Stück' }, { name:'Chicken Wrap', vb:'', cat:'Snacks & Burger', zs:[], al:['G','Sen','Sel','M'], preis:'', einheit:'€/Stück' }, { name:'Chicken'+SHY+'schnitzel', vb:'', cat:'Snacks & Burger', zs:[], al:['G'], preis:'', einheit:'€/Stück' }, { name:'Chicken'+SHY+'schnitzel im Weck', vb:'', cat:'Snacks & Burger', zs:[], al:['G'], preis:'', einheit:'€/Stück' }, { name:'Curry'+SHY+'wurst mit Weck', vb:'', cat:'Snacks & Burger', zs:[1,2,3,4,7], al:['Sel','G'], preis:'', einheit:'€/Stück' }, { name:'Fleisch'+SHY+'käse im Weck', vb:'', cat:'Snacks & Burger', zs:[1,2,3,4], al:['G','Sel','M'], preis:'', einheit:'€/Stück' }, { name:'Fri'+SHY+'ka'+SHY+'delle im Weck', vb:'', cat:'Snacks & Burger', zs:[3,4], al:['G','Sel','M'], preis:'', einheit:'€/Stück' }, { name:'Grill'+SHY+'bauch im Weck', vb:'', cat:'Snacks & Burger', zs:[], al:['G'], preis:'', einheit:'€/Stück' }, { name:'Gyros im Fladenbrot', vb:'', cat:'Snacks & Burger', zs:[], al:['G','M','Ei','Sen','SES'], preis:'', einheit:'€/Stück' }, { name:'Ham'+SHY+'burger', vb:'', cat:'Snacks & Burger', zs:[], al:['G','Sen','Ei','M'], preis:'', einheit:'€/Stück' }, { name:'Merguez im Weck', vb:'', cat:'Snacks & Burger', zs:[1,2,3,4], al:['Sel','G'], preis:'', einheit:'€/Stück' }, { name:'Portion Chicken'+SHY+'nuggets', vb:'', cat:'Snacks & Burger', zs:[], al:['G'], preis:'', einheit:'€/Portion' }, { name:'Puten'+SHY+'steak im Weck', vb:'', cat:'Snacks & Burger', zs:[], al:['G'], preis:'', einheit:'€/Stück' }, { name:'Rost'+SHY+'brat'+SHY+'wurst mit Weck', vb:'', cat:'Snacks & Burger', zs:[3,4], al:['Sel','G'], preis:'', einheit:'€/Stück' }, { name:'Rote im Weck', vb:'', cat:'Snacks & Burger', zs:[1,2,3,4], al:['Sel','G'], preis:'', einheit:'€/Stück' }, { name:'Salami Baguette', vb:'', cat:'Snacks & Burger', zs:[], al:['G','M'], preis:'', einheit:'€/Stück' }, { name:'Schinken-Käse Baguette', vb:'', cat:'Snacks & Burger', zs:[], al:['G','M'], preis:'', einheit:'€/Stück' }, { name:'Schnitzel im Weck', vb:'', cat:'Snacks & Burger', zs:[], al:['G'], preis:'', einheit:'€/Stück' }, { name:'Schweine'+SHY+'hals'+SHY+'steak im Weck', vb:'', cat:'Snacks & Burger', zs:[], al:['G'], preis:'', einheit:'€/Stück' }, { name:'Weiße Brat'+SHY+'wurst im Weck', vb:'', cat:'Snacks & Burger', zs:[3,4], al:['Sel','G'], preis:'', einheit:'€/Stück' }, { name:'Weiß'+SHY+'wurst mit Weck', vb:'', cat:'Snacks & Burger', zs:[3,4], al:['Sel','M','G'], preis:'', einheit:'€/Stück' }, { name:'Wienerle mit Weck', vb:'', cat:'Snacks & Burger', zs:[1,2,3,4], al:['Sel','G'], preis:'', einheit:'€/Stück' }, // ── Gerichte & Beilagen ────────────────────────────────────────────────── { name:'Cham'+SHY+'pig'+SHY+'nons in Rahm'+SHY+'soße', vb:'', cat:'Gerichte & Beilagen', zs:[], al:['M'], preis:'', einheit:'€/Portion' }, { name:'Chili con Carne mit Weck', vb:'', cat:'Gerichte & Beilagen', zs:[], al:['G'], preis:'', einheit:'€/Portion' }, { name:'Chili sin Carne mit Weck', vb:'', cat:'Gerichte & Beilagen', zs:[], al:['G'], preis:'', einheit:'€/Portion' }, { name:'Fleisch'+SHY+'käse', vb:'', cat:'Gerichte & Beilagen', zs:[], al:['Sel','M'], preis:'', einheit:'€/Portion' }, { name:'Fri'+SHY+'ka'+SHY+'delle', vb:'', cat:'Gerichte & Beilagen', zs:[3,4], al:['G','Sel','M'], preis:'', einheit:'€/Stück' }, { name:'Gulasch'+SHY+'suppe', vb:'', cat:'Gerichte & Beilagen', zs:[], al:['G','So'], preis:'', einheit:'€/Portion' }, { name:'Gulasch'+SHY+'suppe mit Weck', vb:'', cat:'Gerichte & Beilagen', zs:[], al:['G'], preis:'', einheit:'€/Portion' }, { name:'Hähnchen'+SHY+'schlegel', vb:'', cat:'Gerichte & Beilagen', zs:[], al:[], preis:'', einheit:'€/Stück' }, { name:'Kar'+SHY+'tof'+SHY+'fel mit Quark', vb:'', cat:'Gerichte & Beilagen', zs:[], al:['M'], preis:'', einheit:'€/Portion' }, { name:'Kar'+SHY+'tof'+SHY+'fel'+SHY+'creme', vb:'', cat:'Gerichte & Beilagen', zs:[], al:['M','Sen','Ei'], preis:'', einheit:'€/Portion' }, { name:'Kar'+SHY+'tof'+SHY+'fel'+SHY+'gratin', vb:'', cat:'Gerichte & Beilagen', zs:[], al:['M'], preis:'', einheit:'€/Portion' }, { name:'Kar'+SHY+'tof'+SHY+'fel'+SHY+'pürree', vb:'', cat:'Gerichte & Beilagen', zs:[], al:['M','G'], preis:'', einheit:'€/Portion' }, { name:'Kar'+SHY+'tof'+SHY+'fel'+SHY+'salat', vb:'', cat:'Gerichte & Beilagen', zs:[], al:[], preis:'', einheit:'€/Portion' }, { name:'Kar'+SHY+'tof'+SHY+'fel'+SHY+'würfel', vb:'', cat:'Gerichte & Beilagen', zs:[], al:[], preis:'', einheit:'€/Portion' }, { name:'Pommes Frites', vb:'', cat:'Gerichte & Beilagen', zs:[], al:[], preis:'', einheit:'€/Portion' }, { name:'Schnitzel paniert', vb:'', cat:'Gerichte & Beilagen', zs:[], al:['G'], preis:'', einheit:'€/Stück' }, { name:'Schupf'+SHY+'nudeln', vb:'', cat:'Gerichte & Beilagen', zs:[], al:['G'], preis:'', einheit:'€/Portion' }, { name:'Schweine'+SHY+'rücken'+SHY+'schnitzel mit Pommes', vb:'mit Soße', cat:'Gerichte & Beilagen', zs:[], al:['G'], preis:'', einheit:'€/Portion' }, { name:'Spaghetti', vb:'', cat:'Gerichte & Beilagen', zs:[], al:['G'], preis:'', einheit:'€/Portion' }, { name:'Spät'+SHY+'zle', vb:'', cat:'Gerichte & Beilagen', zs:[], al:['G','Ei'], preis:'', einheit:'€/Portion' }, { name:'Sauer'+SHY+'kraut', vb:'', cat:'Gerichte & Beilagen', zs:[], al:[], preis:'', einheit:'€/Portion' }, { name:'Wedges mit Sour Creme', vb:'', cat:'Gerichte & Beilagen', zs:[], al:['M','Ei','Sen'], preis:'', einheit:'€/Portion' }, // ── Pizza ──────────────────────────────────────────────────────────────── { name:'Pizza Margherita', vb:'', cat:'Pizza', zs:[], al:['G','M'], preis:'', einheit:'€/Stück' }, { name:'Pizza Salami', vb:'', cat:'Pizza', zs:[1,2,3,4], al:['G','M'], preis:'', einheit:'€/Stück' }, { name:'Pizza Schinken', vb:'', cat:'Pizza', zs:[1,2,3,4], al:['G','M'], preis:'', einheit:'€/Stück' }, // ── Käse ───────────────────────────────────────────────────────────────── { name:'Gouda in Scheiben', vb:'48 % Fett i.Tr.', cat:'Käse', zs:[], al:['M'], preis:'', einheit:'€/100g' }, { name:'Grønländer', vb:'48 % Fett i.Tr.', cat:'Käse', zs:[], al:['M'], preis:'', einheit:'€/100g' }, { name:'Reibe'+SHY+'käse', vb:'', cat:'Käse', zs:[], al:['M'], preis:'', einheit:'€/100g' }, // ── Vegane Produkte ────────────────────────────────────────────────────── { name:'Gemüse'+SHY+'bolo'+SHY+'gnese', vb:'', cat:'Vegane Produkte', zs:[], al:[], preis:'', einheit:'€/Portion' }, { name:'Gemüse'+SHY+'fri'+SHY+'ka'+SHY+'dellen', vb:'', cat:'Vegane Produkte', zs:[], al:['G','Ei','Sel','M'], preis:'', einheit:'€/Stück' }, { name:'Mault'+SHY+'aschen vegan', vb:'', cat:'Vegane Produkte', zs:[], al:['G'], preis:'', einheit:'€/Portion' }, { name:'Reib'+SHY+'käse vegan', vb:'', cat:'Vegane Produkte', zs:[], al:[], preis:'', einheit:'€/100g' }, { name:'Vegane Schnitzel', vb:'', cat:'Vegane Produkte', zs:[], al:['G','So'], preis:'', einheit:'€/Stück' }, { name:'Veganes Curry', vb:'', cat:'Vegane Produkte', zs:[], al:[], preis:'', einheit:'€/Portion' }, // ── Brot & Gebäck ──────────────────────────────────────────────────────── { name:'Baguette'+SHY+'wecken', vb:'', cat:'Brot & Gebäck', zs:[], al:['G'], preis:'', einheit:'€/Stück' }, { name:'Spitz'+SHY+'wecken', vb:'', cat:'Brot & Gebäck', zs:[], al:['G'], preis:'', einheit:'€/Stück' }, // ── Saucen & Dips ──────────────────────────────────────────────────────── { name:'Braten'+SHY+'sauce', vb:'', cat:'Saucen & Dips', zs:[], al:['Sel'], preis:'', einheit:'€/Portion' }, { name:'Curry'+SHY+'ketchup', vb:'', cat:'Saucen & Dips', zs:[7], al:[], preis:'', einheit:'€/Portion' }, { name:'Ketchup', vb:'', cat:'Saucen & Dips', zs:[7], al:[], preis:'', einheit:'€/Portion' }, { name:'Mayo'+SHY+'nnaise', vb:'', cat:'Saucen & Dips', zs:[], al:['Ei'], preis:'', einheit:'€/Portion' }, { name:'Rahm'+SHY+'sauce', vb:'', cat:'Saucen & Dips', zs:[], al:['Sel','M'], preis:'', einheit:'€/Portion' }, { name:'Scha'+SHY+'schlik'+SHY+'soße', vb:'', cat:'Saucen & Dips', zs:[7], al:[], preis:'', einheit:'€/Portion' }, { name:'Senf', vb:'', cat:'Saucen & Dips', zs:[], al:['Sen'], preis:'', einheit:'€/Portion' }, { name:'Tomaten'+SHY+'sauce', vb:'', cat:'Saucen & Dips', zs:[], al:[], preis:'', einheit:'€/Portion' }, ]; let PRODUCTS = [...PRODUCTS_DEFAULT]; /* ── STATE ────────────────────────────────────────────────── */ let currentCat = null; let previewMode = 'aushang'; const sel = {}; function initSel() { // sel für alle Produkte anlegen; preis/einheit aus Produktdaten vorbelegen PRODUCTS.forEach(p => { if (!sel[p.name]) { sel[p.name] = { checked: false, price: p.preis || '', unit: p.einheit || '€/kg' }; } }); } initSel(); /* ── EXCEL-IMPORT ─────────────────────────────────────────── */ function importExcel(input) { const file = input.files[0]; if (!file) return; const statusEl = document.getElementById('importStatus'); statusEl.className = 'import-status'; statusEl.textContent = 'Wird geladen …'; const reader = new FileReader(); reader.onload = function(e) { try { const wb = XLSX.read(e.target.result, { type: 'array' }); // Erstes Tabellenblatt "Artikelliste" oder erstes Sheet nehmen const sheetName = wb.SheetNames.includes('Artikelliste') ? 'Artikelliste' : wb.SheetNames[0]; const ws = wb.Sheets[sheetName]; const rows = XLSY.utils.sheet_to_json(ws, { header:1, defval:'' }); // Kopfzeilen überspringen (Zeilen 1–3 sind Banner/Hints/Header) // Datenzeilen beginnen bei Index 3 (Zeile 4 in Excel) const newProducts = []; const catsSeen = new Set(); for (let i = 3; i parseInt(x.trim())).filter(n => !isNaN(n)) : []; // AL parsen: "G, M, Se" → ['G','M','Se'] const al = alRaw ? alRaw.split(/[,;]+/).map(x => x.trim()).filter(x => x.length > 0) : []; newProducts.push({ name, vb, cat, zs, al, preis: pr, einheit: ei }); catsSeen.add(cat); } if (newProducts.length === 0) { statusEl.className = 'import-status err'; statusEl.textContent = '⚠ Keine Artikel gefunden – Spaltenstruktur prüfen'; return; } // Produkte und Kategorien aktualisieren PRODUCTS = newProducts; // Kategorien aus Daten ableiten (Reihenfolge beibehalten) const catOrder = []; newProducts.forEach(p => { if (!catOrder.includes(p.cat)) catOrder.push(p.cat); }); CATS.length = 0; const CAT_ICONS = { 'Würste & Aufschnitt':'🥩','Grillspezialitäten':'🔥', 'Snacks & Burger':'🍔','Gerichte & Beilagen':'🍲', 'Pizza':'🍕','Käse':'🧀','Vegane Produkte':'🌱', 'Brot & Gebäck':'🥐','Saucen & Dips':'🫙', // ältere Namen (Rückwärtskompatibilität) 'Fleisch & Wurst':'🥩','Fertiggericht':'🍲','Beilage':'🥗', 'Sauce':'🫙','Backware':'🥐','Getränke':'🥤', }; catOrder.forEach(c => { CATS.push({ name: c, icon: CAT_ICONS[c] || '🍽️' }); }); // sel neu aufbauen (bestehende Preise/Auswahl behalten wenn Artikel gleich) PRODUCTS.forEach(p => { if (!sel[p.name]) { sel[p.name] = { checked: false, price: p.preis || '', unit: p.einheit || '€/kg' }; } else { // Einheit aus Excel übernehmen falls sel noch leer if (!sel[p.name].unit) sel[p.name].unit = p.einheit || '€/kg'; if (!sel[p.name].price && p.preis) sel[p.name].price = p.preis; } }); statusEl.className = 'import-status ok'; statusEl.textContent = `✓ ${file.name} · ${newProducts.length} Artikel aus ${catOrder.length} Warengruppen`; // Kategorie-Grid neu rendern renderCatGrid(); updateHomeCount(); } catch(err) { statusEl.className = 'import-status err'; statusEl.textContent = '⚠ Fehler: ' + err.message; } }; reader.readAsArrayBuffer(file); // Input zurücksetzen damit dieselbe Datei nochmal geladen werden kann input.value = ''; } /* ── HELPERS ──────────────────────────────────────────────── */ const escKey = s => s.replace(/'/g,"\'"); function selectedCount() { return PRODUCTS.filter(p => sel[p.name].checked).length; } function catSelectedCount(cat) { return PRODUCTS.filter(p => p.cat===cat && sel[p.name].checked).length; } function splitName(name) { // Soft-Hyphens (u00AD) für den Browser als ­ ausgeben const htmlName = name.replace(/u00AD/g, '­'); // Namensanzeige: Leerzeichen = Zeilenbruch an optimalster Stelle const words = name.replace(/u00AD/g, '').split(/s+/); if (words.length === 1) { // Kein Leerzeichen: Soft-Hyphens reichen für Browser-Umbruch return htmlName; } // Mehrere Wörter: besten Zeilenbruch-Punkt suchen (ausgewogene Zeilen) let best=1, bestDiff=Infinity; for (let i=1; i<words.length; i++) { const diff = Math.abs(words.slice(0,i).join(' ').length - words.slice(i).join(' ').length); if (diff < bestDiff) { bestDiff=diff; best=i; } } const part1 = words.slice(0,best).join(' '); const part2 = words.slice(best).join(' '); // Soft-Hyphens in den Wortteilen wiederherstellen const re1 = new RegExp(part1.split('').join('u00AD?'), 'u'); return part1 + '
' + part2; } function fmtPrice(price, unit) { if (!price) return ''; const n = parseFloat(price.replace(',','.')); return isNaN(n) ? '' : n.toFixed(2).replace('.',',') + ' ' + unit; } function nameClass(name) { const l = name.replace(/s/g,'').length; if (l > 18) return 'sz-xl'; if (l > 12) return 'sz-lg'; return ''; } /* ── SCREENS ──────────────────────────────────────────────── */ function showScreen(to, from, dir) { const f = document.getElementById(from), t = document.getElementById(to); if (dir === 'fwd') { f.classList.replace('active','hidden-back'); t.classList.remove('hidden','hidden-back'); t.classList.add('active'); } else { f.classList.replace('active','hidden'); t.classList.remove('hidden-back','hidden'); t.classList.add('active'); } } function goBack(from, to) { showScreen(to, from, 'back'); updateHomeCount(); } /* ── LOGIN ────────────────────────────────────────────────── */ function doLogin() { const pw = document.getElementById('pwInput').value; if (pw === 'decker1680') { showScreen('screen-home','screen-login','fwd'); renderCatGrid(); } else { const el = document.getElementById('loginError'); el.textContent = 'Falsches Passwort. Bitte erneut versuchen.'; document.getElementById('pwInput').value = ''; setTimeout(()=>{ el.textContent=''; }, 3000); } } /* ── HOME ─────────────────────────────────────────────────── */ function renderCatGrid() { document.getElementById('catGrid').innerHTML = CATS.map(cat => { const total = PRODUCTS.filter(p => p.cat===cat.name).length; const chosen = catSelectedCount(cat.name); const bid = 'badge-' + cat.name.replace(/W/g,'_'); return `
${chosen}
${cat.icon}
${cat.name}
${total} Artikel
`; }).join(''); } function updateHomeCount() { const n = selectedCount(); document.getElementById('homeSelCount').textContent = n===0 ? 'Keine Artikel ausgewählt' : n===1 ? '1 Artikel ausgewählt' : `${n} Artikel ausgewählt`; document.getElementById('homePreviewBtn').disabled = (n===0); CATS.forEach(cat => { const b = document.getElementById('badge-'+cat.name.replace(/W/g,'_')); if (!b) return; const c = catSelectedCount(cat.name); b.textContent = c; b.classList.toggle('visible', c>0); }); } /* ── CATEGORY ─────────────────────────────────────────────── */ function openCat(catName) { currentCat = catName; document.getElementById('catNavTitle').textContent = catName; document.getElementById('catSearch').value = ''; renderArticleList(); showScreen('screen-cat','screen-home','fwd'); } function renderArticleList() { const q = (document.getElementById('catSearch').value||'').toLowerCase(); const list = PRODUCTS .filter(p => p.cat===currentCat) .filter(p => !q || p.name.toLowerCase().includes(q)); const chosen = list.filter(p => sel[p.name].checked).length; document.getElementById('catSelCount').textContent = chosen ? `${chosen} ausgewählt` : ''; document.getElementById('articleList').innerHTML = list.map(p => { const s = sel[p.name]; const sid = 'ai-' + p.name.replace(/W/g,'_'); const zsHtml = p.zs.length ? p.zs.map(z=>`(${z})`).join(' ') : '–'; const alHtml = p.al.length ? p.al.join(', ') : '–'; const ALL_UNITS = ['€/kg','€/100g','€/Stück','€/Portion','€/Paar']; const unitOpts = ALL_UNITS .map(u=>`${u}`).join(''); return `
Preis ${unitOpts}
`; }).join('') || `
🔍

Keine Artikel gefunden.

`; } function toggleArticle(name) { sel[name].checked = !sel[name].checked; const el = document.getElementById('ai-' + name.replace(/W/g,'_')); if (el) { el.classList.toggle('selected', sel[name].checked); // Focus price field after short delay (after CSS transition starts) if (sel[name].checked) { setTimeout(() => { const inp = el.querySelector('.price-field'); if (inp) inp.focus(); }, 200); } } // update counter const chosen = PRODUCTS.filter(p => p.cat===currentCat && sel[p.name].checked).length; const cl = document.getElementById('catSelCount'); if (cl) cl.textContent = chosen ? `${chosen} ausgewählt` : ''; updateHomeCount(); } function updatePrice(name, val) { sel[name].price = val.trim(); } function updateUnit(name, val) { sel[name].unit = val; } function toggleAllVisible() { const q = (document.getElementById('catSearch').value||'').toLowerCase(); const list = PRODUCTS .filter(p => p.cat===currentCat) .filter(p => !q || p.name.toLowerCase().includes(q)); const allOn = list.every(p => sel[p.name].checked); list.forEach(p => { sel[p.name].checked = !allOn; }); renderArticleList(); updateHomeCount(); } /* ── PREVIEW ──────────────────────────────────────────────── */ function goPreview() { renderPreview(); showScreen('screen-preview','screen-home','fwd'); } function setMode(mode) { previewMode = mode; document.getElementById('btnAushang').classList.toggle('active', mode==='aushang'); document.getElementById('btnSpeisekarte').classList.toggle('active', mode==='speisekarte'); renderPreview(); } function renderPreview() { const selected = PRODUCTS.filter(p => sel[p.name].checked); const c = document.getElementById('previewContent'); if (!selected.length) { c.innerHTML = `
🛒

Keine Artikel ausgewählt

Gehe zurück und wähle Artikel aus.

`; return; } c.innerHTML = previewMode==='aushang' ? selected.map(renderPoster).join('') : renderSpeisekarte(selected); } /* ── POSTER ───────────────────────────────────────────────── */ function renderPoster(p) { const s = sel[p.name]; const ps = fmtPrice(s.price, s.unit); const zsStr = p.zs.length ? p.zs.map(z=>`(${z})u2009${ZS_L[z]||''}`).join(' · ') : null; const alStr = p.al.length ? p.al.map(a=>`${a}u2009${AL_L[a]||a}`).join(' · ') : null; const vbFirst = p.vb ? p.vb.split(',')[0].trim() : ''; const priceNum = ps ? ps.split(' ')[0] : ''; const priceUnit = ps ? ps.slice(ps.indexOf(' ')+1) : ''; return `
Metzgerei
DECKER
seit 1680
Hausach · Schwarzwald
${p.cat}
${splitName(p.name)}
${vbFirst}
${priceNum ? `
${priceNum}
${priceUnit}
` : ''}
`; } /* ── SPEISEKARTE ──────────────────────────────────────────── */ function renderSpeisekarte(selected) { const today = new Date().toLocaleDateString('de-DE',{day:'2-digit',month:'2-digit',year:'numeric'}); const byCat = {}; selected.forEach(p => { if(!byCat[p.cat]) byCat[p.cat]=[]; byCat[p.cat].push(p); }); const allZS = new Set(), allAL = new Set(); selected.forEach(p => { p.zs.forEach(z=>allZS.add(z)); p.al.forEach(a=>allAL.add(a)); }); const rows = Object.entries(byCat).map(([cat,prods]) => { const cr = `${cat}`; const pr = prods.map(p => { const s = sel[p.name]; const ps = fmtPrice(s.price, s.unit) || '–'; return ` ${p.name} ${p.vb} ${p.zs.length ? p.zs.map(z=>`(${z})`).join(' ') : '–'} ${p.al.length ? p.al.join(', ') : '–'} ${ps} `; }).join(''); return cr + pr; }).join(''); const zsLeg = allZS.size ? [...allZS].sort((a,b)=>a-b).map(z=>`(${z})u2009${ZS_L[z]||''}`).join('   ') : 'Keine'; const alLeg = allAL.size ? [...allAL].sort().map(a=>`${a}u2009${AL_L[a]||a}`).join('   ') : 'Keine'; return `
METZGEREI DECKER
seit 1680
Gartenstraße 2 · 77756 Hausach · Tel. 07831/7138
SPEISEKARTE
${rows}
Bezeichnung ZS Allergene Preis
Zusatzstoffe: ${zsLeg}
Allergene: ${alLeg}
Alle Speisen werden lose abgegeben. Nährwertangaben entfallen gem. LMIV Anhang V Nr. 19. Detaillierte Angaben auf Anfrage.
`; } /* ── PRINT — setzt @page je nach Modus ────────────────────── */ function doPrint() { // Dynamic @page orientation const size = previewMode === 'aushang' ? 'A4 landscape' : 'A4 portrait'; let dynStyle = document.getElementById('_dynPage'); if (!dynStyle) { dynStyle = document.createElement('style'); dynStyle.id = '_dynPage'; document.head.appendChild(dynStyle); } dynStyle.textContent = `@media print { @page { size: ${size}; margin: 0; } }`; window.print(); }