Files
BNI-Vorstellungsrunde/index.html

5881 lines
328 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BNI Chapter Sonne – Vorstellungsrunde</title>
<link rel="icon" href="favicon.svg" type="image/svg+xml">
<link rel="icon" href="favicon.ico" sizes="any">
<link rel="apple-touch-icon" href="apple-touch-icon.png">
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/pdfjs-dist@3.11.174/build/pdf.min.js"></script>
<style>
:root {
--red: #cc0000;
--red-dark: #990000;
--red-light: rgba(204,0,0,0.12);
--navy: #1a2744;
--bg: #f0f2f5;
--card: #ffffff;
--text: #1a1a2e;
--muted: #6b7280;
--border: #e5e7eb;
--success: #16a34a;
--warning: #d97706;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--bg);
overflow: hidden;
}
/* ============================================================
ROUTING
============================================================ */
.view { display: none; width: 100vw; height: 100vh; }
.view.active { display: flex; }
/* ============================================================
DISPLAY VIEW
============================================================ */
#view-display {
background: linear-gradient(160deg, #0f1f45 0%, #1a2744 40%, #0d1b35 100%);
flex-direction: column;
align-items: center;
justify-content: flex-start;
padding-top: clamp(90px, 12vh, 160px);
position: relative;
overflow: hidden;
}
/* Decorative background circles */
#view-display::before {
content: '';
position: absolute;
width: 600px; height: 600px;
background: radial-gradient(circle, rgba(204,0,0,0.15) 0%, transparent 70%);
top: -150px; right: -150px;
pointer-events: none;
}
#view-display::after {
content: '';
position: absolute;
width: 400px; height: 400px;
background: radial-gradient(circle, rgba(255,255,255,0.05) 0%, transparent 70%);
bottom: -100px; left: -100px;
pointer-events: none;
}
#fullscreen-btn {
position: fixed;
bottom: 20px; right: 20px;
z-index: 999;
background: rgba(255,255,255,0.08);
border: 1px solid rgba(255,255,255,0.15);
color: rgba(255,255,255,0.4);
border-radius: 8px;
padding: 6px 10px;
font-size: 1.2rem;
cursor: pointer;
opacity: 0;
transition: opacity 0.3s;
}
#view-display:hover #fullscreen-btn { opacity: 1; }
#fullscreen-btn:hover { background: rgba(255,255,255,0.18); color: white; }
#sound-enable-btn {
position: fixed;
top: 20px; right: 50%;
transform: translateX(50%);
z-index: 1000;
background: var(--red, #cf2030);
color: white;
border: none;
border-radius: 999px;
padding: 10px 20px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
box-shadow: 0 4px 20px rgba(0,0,0,0.35);
display: none;
animation: soundBtnPulse 1.6s ease-in-out infinite;
}
#sound-enable-btn.needed { display: block; }
@keyframes soundBtnPulse {
0%, 100% { transform: translateX(50%) scale(1); }
50% { transform: translateX(50%) scale(1.05); }
}
.display-header {
position: absolute;
top: clamp(14px, 2vh, 32px); left: 40px;
display: flex;
align-items: center;
gap: clamp(10px, 1.5vh, 18px);
z-index: 10;
}
.bni-badge {
height: clamp(44px, 6vh, 80px);
width: auto;
display: block;
object-fit: contain;
}
.chapter-name {
color: rgba(255,255,255,0.7);
font-size: clamp(1rem, 1.8vh, 2rem);
font-weight: 300;
letter-spacing: 1px;
}
#display-header-timer {
position: absolute;
top: clamp(10px, 1.5vh, 24px);
right: clamp(230px, 17vw, 310px);
font-size: clamp(2.5rem, 7vh, 6.5rem);
font-weight: 800;
color: rgba(255,255,255,0.95);
font-variant-numeric: tabular-nums;
letter-spacing: 4px;
z-index: 10;
line-height: 1;
text-shadow: 0 2px 20px rgba(0,0,0,0.6);
display: none;
pointer-events: none;
}
#display-header-timer.warn { color: #fbbf24; }
#display-header-timer.danger { color: #ef4444; }
#display-header-timer.expired { color: #ef4444; animation: timerBlink 0.5s alternate infinite; }
.progress-indicator {
position: absolute;
top: clamp(28px, 4vh, 50px); right: 40px;
color: rgba(255,255,255,0.5);
font-size: clamp(0.85rem, 1.5vh, 1.8rem);
letter-spacing: 1px;
z-index: 10;
}
.round-badge {
position: absolute;
top: 96px; right: 40px;
background: var(--red);
color: white;
font-size: 0.8rem;
font-weight: 600;
padding: 4px 12px;
border-radius: 20px;
letter-spacing: 1px;
text-transform: uppercase;
display: none;
}
.round-badge.visible { display: block; }
.main-card {
display: flex;
flex-direction: row;
align-items: center;
gap: clamp(40px, 5vw, 100px);
background: rgba(255,255,255,0.07);
backdrop-filter: blur(24px);
border: 1px solid rgba(255,255,255,0.12);
border-radius: clamp(20px, 3vh, 36px);
padding: clamp(32px, 5vh, 80px) clamp(40px, 5vw, 100px);
max-width: none;
width: 96vw;
flex: 1;
position: relative;
z-index: 1;
margin-bottom: clamp(60px, 8vh, 90px);
transition: background 0.4s ease, border-color 0.4s ease;
}
.main-card.timer-warn { background: #fbbf24; border-color: rgba(0,0,0,0.15); }
.main-card.timer-danger { background: #cf2030; border-color: rgba(255,255,255,0.25); }
.photo-section {
flex-shrink: 0;
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
}
.member-photo-wrap {
width: 100%;
height: 100%;
border-radius: 50%;
border: 4px solid var(--red);
overflow: hidden;
background: rgba(255,255,255,0.1);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 40px rgba(204,0,0,0.3);
}
.member-photo-wrap img {
width: 100%;
height: 100%;
object-fit: cover;
}
.initials-avatar {
font-size: clamp(4rem, 12vh, 12rem);
font-weight: 700;
color: white;
}
.qr-corner {
position: absolute;
top: clamp(102px, 14vh, 180px);
right: clamp(24px, 3vw, 60px);
z-index: 3;
background: white;
padding: clamp(8px, 1vh, 14px);
border-radius: 14px;
box-shadow: 0 4px 24px rgba(0,0,0,0.35);
}
.qr-label {
color: #1a2744;
font-size: clamp(0.6rem, 1vh, 0.9rem);
text-align: center;
margin-top: 5px;
text-transform: uppercase;
letter-spacing: 1px;
font-weight: 600;
}
.info-section {
flex: 1;
display: flex;
flex-direction: column;
gap: clamp(14px, 2.5vh, 36px);
}
.substitute-label {
color: rgba(255,255,255,0.5);
font-size: clamp(0.75rem, 1.5vh, 1.5rem);
text-transform: uppercase;
letter-spacing: 2px;
border-left: 3px solid var(--red);
padding-left: 10px;
}
.display-name {
font-size: clamp(3rem, max(5vw, 7vh), 10rem);
font-weight: 800;
color: white;
line-height: 1.1;
}
.display-company {
font-size: clamp(1.6rem, max(2.8vw, 4vh), 6rem);
color: rgba(255,255,255,0.75);
font-weight: 400;
}
.display-specialty {
display: inline-block;
background: rgba(0,0,0,0.35);
border: 1px solid rgba(255,255,255,0.35);
color: #fff;
font-size: clamp(1rem, 2.2vh, 2.4rem);
padding: clamp(6px, 1vh, 14px) clamp(12px, 1.8vw, 28px);
border-radius: 30px;
align-self: flex-start;
text-shadow: 0 1px 2px rgba(0,0,0,0.4);
}
.visitor-tag {
display: inline-block;
background: rgba(234,179,8,0.2);
border: 1px solid rgba(234,179,8,0.4);
color: #fcd34d;
font-size: clamp(0.8rem, 1.6vh, 1.6rem);
padding: clamp(4px, 0.8vh, 10px) clamp(10px, 1.5vw, 20px);
border-radius: 20px;
align-self: flex-start;
margin-top: 4px;
}
.presenter-row {
display: flex;
align-items: center;
gap: 12px;
margin-top: 20px;
padding: 12px 16px;
background: rgba(255,255,255,0.07);
border: 1px solid rgba(255,255,255,0.1);
border-radius: 12px;
}
.presenter-avatar {
width: clamp(36px, 4.5vh, 64px); height: clamp(36px, 4.5vh, 64px);
border-radius: 50%;
overflow: hidden;
flex-shrink: 0;
border: 2px solid rgba(255,255,255,0.2);
display: flex; align-items: center; justify-content: center;
font-size: clamp(0.8rem, 1.4vh, 1.4rem); font-weight: 700; color: white;
}
.presenter-avatar img {
width: 100%; height: 100%; object-fit: cover;
}
.presenter-label {
font-size: clamp(0.65rem, 1.2vh, 1.2rem);
text-transform: uppercase;
letter-spacing: 1.5px;
color: rgba(255,255,255,0.4);
margin-bottom: 2px;
}
.presenter-name {
font-size: clamp(0.9rem, 1.8vh, 1.8rem);
font-weight: 600;
color: rgba(255,255,255,0.85);
}
.presenter-company {
font-size: clamp(0.75rem, 1.4vh, 1.4rem);
color: rgba(255,255,255,0.5);
}
.empty-state-display {
color: rgba(255,255,255,0.4);
font-size: 1.5rem;
text-align: center;
}
/* Next Bar */
.next-bar {
position: absolute;
top: 0; left: 0; right: 0;
background: rgba(0,0,0,0.6);
backdrop-filter: blur(12px);
border-bottom: 1px solid rgba(255,255,255,0.1);
padding: clamp(12px, 1.8vh, 24px) 40px;
display: flex;
align-items: center;
gap: clamp(16px, 2.5vh, 36px);
z-index: 5;
}
#next-bar {
padding-left: 320px;
}
.next-bar-divider {
width: 1px;
height: clamp(40px, 7vh, 96px);
background: rgba(255,255,255,0.15);
}
.next-photo-small {
width: clamp(56px, 8.5vh, 120px); height: clamp(56px, 8.5vh, 120px);
border-radius: 50%;
object-fit: cover;
border: 2px solid rgba(255,255,255,0.3);
background: rgba(255,255,255,0.1);
}
.next-info .next-name-text {
color: white;
font-size: clamp(1.5rem, 3.8vh, 4.2rem);
font-weight: 700;
}
.next-info .next-company-text {
color: rgba(255,255,255,0.6);
font-size: clamp(1rem, 2.5vh, 2.8rem);
}
.next-bar-label {
color: rgba(255,255,255,0.5);
font-size: clamp(0.8rem, 1.8vh, 2rem);
text-transform: uppercase;
letter-spacing: 2px;
white-space: nowrap;
}
.next-bar-end-text {
color: rgba(255,255,255,0.4);
font-style: italic;
font-size: clamp(1.2rem, 2.5vh, 2.8rem);
}
/* ============================================================
CONTROL VIEW
============================================================ */
#view-control {
flex-direction: column;
background: var(--bg);
}
.ctrl-header {
background: var(--navy);
color: white;
padding: 14px 24px;
display: flex;
align-items: center;
gap: 16px;
flex-shrink: 0;
box-shadow: 0 2px 12px rgba(0,0,0,0.3);
}
.ctrl-header .bni-badge { height: 38px; border-radius: 6px; }
.ctrl-title {
font-size: 1.1rem;
font-weight: 600;
flex: 1;
}
.btn {
padding: 9px 20px;
border: none;
border-radius: 8px;
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
transition: all 0.15s;
}
.btn:active { transform: scale(0.97); }
.btn-primary {
background: var(--red);
color: white;
}
.btn-primary:hover { background: var(--red-dark); }
.btn-secondary {
background: rgba(255,255,255,0.15);
color: white;
border: 1px solid rgba(255,255,255,0.2);
}
.btn-secondary:hover { background: rgba(255,255,255,0.25); }
.btn-ghost {
background: transparent;
color: var(--muted);
border: 1px solid var(--border);
}
.btn-ghost:hover { background: white; color: var(--text); }
.btn-sm {
padding: 5px 12px;
font-size: 0.82rem;
border-radius: 6px;
}
.btn-danger {
background: transparent;
color: #dc2626;
border: 1px solid #fca5a5;
}
.btn-danger:hover { background: #fef2f2; }
.btn-next {
background: var(--red);
color: white;
font-size: 1rem;
padding: 11px 28px;
display: flex;
align-items: center;
gap: 8px;
}
.btn-next:hover { background: var(--red-dark); }
.btn-next:disabled { background: #9ca3af; cursor: not-allowed; }
.btn-next:disabled:active { transform: none; }
.ctrl-body {
flex: 1;
display: grid;
grid-template-columns: 1fr 320px;
gap: 0;
overflow: hidden;
}
/* Left panel: member queue */
.panel-left {
display: flex;
flex-direction: column;
overflow: hidden;
border-right: 1px solid var(--border);
background: var(--bg);
}
.panel-section-header {
padding: 14px 20px 10px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid var(--border);
background: white;
flex-shrink: 0;
}
.panel-section-title {
font-size: 0.8rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1.5px;
color: var(--muted);
}
.queue-tabs {
display: flex;
gap: 4px;
}
.tab-btn {
padding: 6px 14px;
border-radius: 6px;
font-size: 0.85rem;
font-weight: 600;
cursor: pointer;
border: 1px solid var(--border);
background: white;
color: var(--muted);
transition: all 0.15s;
}
.tab-btn.active {
background: var(--navy);
color: white;
border-color: var(--navy);
}
.member-queue {
flex: 1;
overflow-y: auto;
padding: 12px 16px;
min-height: 80px;
}
#queue-resize-handle {
flex-shrink: 0;
height: 10px;
background: #edf0f3;
border-top: 1px solid var(--border);
border-bottom: 1px solid var(--border);
cursor: row-resize;
user-select: none;
display: none;
align-items: center;
justify-content: center;
gap: 3px;
transition: background 0.15s;
}
#queue-resize-handle::before,
#queue-resize-handle::after {
content: '';
display: block;
width: 28px; height: 2px;
background: #c0c8d0;
border-radius: 2px;
}
#queue-resize-handle:hover,
#queue-resize-handle.is-dragging { background: #dde2e8; }
#absent-members-section {
overflow-y: auto;
min-height: 60px;
flex-shrink: 0;
}
.queue-item {
display: flex;
align-items: center;
gap: 12px;
background: white;
border: 1px solid var(--border);
border-radius: 10px;
padding: 10px 14px;
margin-bottom: 6px;
cursor: grab;
transition: all 0.15s;
position: relative;
}
.queue-item:active { cursor: grabbing; }
.queue-item.current {
border-color: var(--red);
background: #fff5f5;
box-shadow: 0 0 0 2px rgba(204,0,0,0.15);
}
.queue-item.next-up {
border-color: var(--navy);
background: #f0f4ff;
}
.queue-item.done {
opacity: 0.45;
}
.queue-item.sortable-ghost {
opacity: 0.35;
border: 2px dashed var(--red);
}
.queue-badge {
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1px;
padding: 2px 8px;
border-radius: 4px;
position: absolute;
top: 8px; right: 8px;
}
.badge-current { background: var(--red); color: white; }
.badge-next { background: var(--navy); color: white; }
.badge-done { background: #e5e7eb; color: #6b7280; }
.badge-visitor { background: #fef3c7; color: #92400e; }
.badge-substitute { background: #e0e7ff; color: #3730a3; }
.drag-handle {
color: #d1d5db;
cursor: grab;
font-size: 1.2rem;
user-select: none;
flex-shrink: 0;
}
.queue-photo-wrap {
width: 44px; height: 44px;
border-radius: 50%;
overflow: hidden;
background: #e5e7eb;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
}
.queue-photo-wrap img {
width: 100%; height: 100%;
object-fit: cover;
}
.queue-initials {
font-size: 1rem;
font-weight: 700;
color: white;
background: var(--navy);
width: 100%; height: 100%;
display: flex; align-items: center; justify-content: center;
border-radius: 50%;
}
.queue-info {
flex: 1;
min-width: 0;
}
.queue-name {
font-size: 0.95rem;
font-weight: 600;
color: var(--text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.queue-company {
font-size: 0.8rem;
color: var(--muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.queue-actions {
display: flex;
gap: 4px;
flex-shrink: 0;
}
.sub-info {
font-size: 0.75rem;
color: #4f46e5;
margin-top: 2px;
}
/* Absent / Medical status in queue */
.status-tag {
font-size: 0.68rem; font-weight: 700; padding: 2px 7px;
border-radius: 4px; letter-spacing: 0.5px; cursor: pointer;
border: none; white-space: nowrap;
}
.status-tag-absent { background: #fef3c7; color: #92400e; }
.status-tag-medical { background: #ede9fe; color: #5b21b6; }
/* Absent section below the main queue */
.absent-section {
padding: 10px 16px 12px;
background: #fafafa;
overflow-y: auto;
}
.absent-section-title {
font-size: 0.7rem; font-weight: 700; text-transform: uppercase;
letter-spacing: 1px; color: var(--muted); margin-bottom: 6px;
}
.absent-section-title + .absent-section-title { margin-top: 10px; }
/* Notizen-Leiste im Steuerpult: warmer Hinweis-Kasten für Sprecher-Notizen des aktuell
live geschalteten Agenda-Punkts. Nur im Steuerpult, nie im Display gerendert. */
.agenda-notes-panel {
padding: 10px 24px;
background: #fef3c7;
border-bottom: 1px solid #fde68a;
color: #78350f;
font-size: 0.88rem;
line-height: 1.4;
white-space: pre-wrap;
}
.agenda-notes-panel strong {
display: block;
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1px;
color: #92400e;
margin-bottom: 4px;
}
/* Display: Absent summary overlay */
#display-absent-summary {
position: fixed; inset: 0; z-index: 5;
display: flex; flex-direction: column;
align-items: center; justify-content: center;
padding: 56px 80px 48px;
gap: 28px;
overflow: hidden;
}
.absent-summary-heading {
color: white; font-size: 1.6rem; font-weight: 800;
letter-spacing: 3px; text-transform: uppercase;
opacity: 0.9; flex-shrink: 0;
}
.absent-summary-cols {
display: flex; gap: 60px; align-items: flex-start;
width: 100%; justify-content: center;
overflow: hidden;
}
.absent-summary-col {
flex: 1; min-width: 0; max-width: 600px;
}
.absent-summary-col-title {
font-size: 0.72rem; font-weight: 700; text-transform: uppercase;
letter-spacing: 2px; margin-bottom: 14px; flex-shrink: 0;
}
.absent-summary-col-title.medical { color: #a78bfa; }
.absent-summary-col-title.absent { color: #fcd34d; }
.absent-summary-name {
color: white; font-size: var(--absent-name-size, 1.3rem); font-weight: 600;
margin-bottom: 0.4em; opacity: 0.92;
break-inside: avoid; line-height: 1.25;
}
#absent-list-medical, #absent-list-absent {
column-gap: 36px;
}
/* ============================================================
TIMER
============================================================ */
.photo-timer-wrap {
position: relative;
width: min(42vh, 45vmin); height: min(42vh, 45vmin);
flex-shrink: 0;
}
.timer-ring-svg {
position: absolute;
top: -14px; left: -14px;
width: calc(min(42vh, 45vmin) + 28px); height: calc(min(42vh, 45vmin) + 28px);
transform: rotate(-90deg);
pointer-events: none;
display: none;
}
.timer-ring-track {
fill: none;
stroke: rgba(255,255,255,0.12);
stroke-width: 7;
}
.timer-ring-progress {
fill: none;
stroke: rgba(255,255,255,0.75);
stroke-width: 7;
stroke-linecap: round;
transition: stroke-dashoffset 0.95s linear, stroke 0.4s;
}
.timer-ring-progress.warn { stroke: #fbbf24; }
.timer-ring-progress.danger { stroke: #ef4444; }
.timer-ring-progress.expired { stroke: #ef4444; animation: timerBlink 0.5s alternate infinite; }
@keyframes timerBlink { to { opacity: 0.15; } }
/* 8-Augen Timer */
.augen-card {
background: rgba(255,255,255,0.05);
border: 2px solid rgba(255,255,255,0.10);
border-radius: 20px;
padding: clamp(18px,2.5vh,32px) clamp(18px,2.5vw,32px);
display: flex; flex-direction: column;
align-items: center; justify-content: center; gap: clamp(8px,1.4vh,18px);
transition: background 0.25s ease, border-color 0.25s ease, transform 0.25s ease;
min-height: 0;
color: #ffffff;
}
.augen-card .augen-card-time { color: #ffffff; text-shadow: 0 2px 12px rgba(0,0,0,0.5); }
.augen-card .augen-card-label { color: rgba(255,255,255,0.75); }
.augen-card .augen-card-sub { color: rgba(255,255,255,0.65); }
.augen-card.warn { color: #000; }
.augen-card.warn .augen-card-time { color: #000; text-shadow: none; }
.augen-card.warn .augen-card-label { color: rgba(0,0,0,0.75); }
.augen-card.warn .augen-card-sub { color: rgba(0,0,0,0.65); }
.augen-card.danger { color: #fff; }
.augen-card.danger .augen-card-time { color: #fff; text-shadow: 0 2px 12px rgba(0,0,0,0.4); }
.augen-card.danger .augen-card-label { color: rgba(255,255,255,0.9); }
.augen-card.danger .augen-card-sub { color: rgba(255,255,255,0.85); }
.augen-card.wechsel {
background: #1e40af;
border-color: rgba(255,255,255,0.3);
color: #fff;
}
.augen-card.wechsel .augen-card-time { color: #fff; letter-spacing: 4px; font-size: clamp(2rem,7vw,5rem); }
.augen-card.wechsel .augen-card-label { color: rgba(255,255,255,0.85); }
.augen-card.wechsel .augen-card-sub { color: rgba(255,255,255,0.9); font-weight: 700; }
.augen-card.finished {
background: rgba(34,197,94,0.15);
border-color: rgba(34,197,94,0.5);
color: #86efac;
}
.augen-card.finished .augen-card-time { color: #86efac; }
.augen-card.finished .augen-card-label { color: rgba(134,239,172,0.85); }
.augen-card.finished .augen-card-sub { color: rgba(134,239,172,0.75); }
.augen-card.warn { background: #fbbf24; border-color: rgba(0,0,0,0.15); color: #000; }
.augen-card.danger { background: #cf2030; border-color: rgba(255,255,255,0.30); color: #fff; }
.augen-card.pulse { animation: augenPulse 0.9s ease-out; }
@keyframes augenPulse {
0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(207,32,48,0.55); }
40% { transform: scale(1.03); box-shadow: 0 0 0 40px rgba(207,32,48,0); }
100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(207,32,48,0); }
}
.augen-card-label {
font-size: clamp(0.85rem,1.8vw,1.35rem);
font-weight: 700;
letter-spacing: 3px;
text-transform: uppercase;
opacity: 0.65;
}
.augen-card.warn .augen-card-label,
.augen-card.danger .augen-card-label { opacity: 0.85; }
.augen-card-time {
font-size: clamp(3rem,10vw,8rem);
font-weight: 800;
font-variant-numeric: tabular-nums;
letter-spacing: 2px;
line-height: 1;
}
.augen-card-sub {
font-size: clamp(0.75rem,1.4vw,1.05rem);
font-weight: 600;
opacity: 0.55;
}
.augen-card.warn .augen-card-sub,
.augen-card.danger .augen-card-sub { opacity: 0.8; }
.augen-size-btn {
padding: 6px 14px; border-radius: 8px;
border: 1px solid rgba(255,255,255,0.15);
background: rgba(255,255,255,0.05);
color: rgba(255,255,255,0.55);
font-family: inherit; font-size: 0.9rem; font-weight: 600;
cursor: pointer; transition: all 0.15s ease;
}
.augen-size-btn.active {
background: #cf2030; border-color: #cf2030; color: #fff;
}
.augen-size-btn:hover { border-color: rgba(255,255,255,0.35); }
#display-timer-text {
font-size: clamp(2rem, 4vh, 5rem); font-weight: 700;
color: rgba(255,255,255,0.95);
font-variant-numeric: tabular-nums;
letter-spacing: 3px;
text-align: center;
text-shadow: 0 2px 12px rgba(0,0,0,0.5);
line-height: 1;
display: none;
}
#display-timer-text.warn { color: #fbbf24; }
#display-timer-text.danger { color: #ef4444; }
#display-timer-text.expired { color: #ef4444; animation: timerBlink 0.5s alternate infinite; }
/* Kontrast auf farbigem Kartenhintergrund */
.main-card.timer-warn #display-timer-text,
.main-card.timer-warn #display-timer-text.warn,
.main-card.timer-warn #display-timer-text.danger,
.main-card.timer-warn #display-timer-text.expired { color: #000; text-shadow: none; }
.main-card.timer-danger #display-timer-text,
.main-card.timer-danger #display-timer-text.warn,
.main-card.timer-danger #display-timer-text.danger,
.main-card.timer-danger #display-timer-text.expired { color: #fff; text-shadow: 0 2px 8px rgba(0,0,0,0.35); }
.main-card.timer-warn .timer-ring-progress,
.main-card.timer-warn .timer-ring-progress.warn,
.main-card.timer-warn .timer-ring-progress.danger,
.main-card.timer-warn .timer-ring-progress.expired { stroke: #000; animation: none; }
.main-card.timer-danger .timer-ring-progress,
.main-card.timer-danger .timer-ring-progress.warn,
.main-card.timer-danger .timer-ring-progress.danger,
.main-card.timer-danger .timer-ring-progress.expired { stroke: #fff; animation: none; }
.main-card.timer-warn .timer-ring-track { stroke: rgba(0,0,0,0.15); }
.main-card.timer-danger .timer-ring-track { stroke: rgba(255,255,255,0.25); }
.queue-timer-btn {
font-size: 0.75rem; font-weight: 700;
color: var(--muted);
background: var(--bg);
border: 1px solid var(--border);
border-radius: 5px;
padding: 3px 7px;
cursor: pointer;
font-variant-numeric: tabular-nums;
letter-spacing: 0.5px;
line-height: 1;
}
.queue-timer-btn:hover { border-color: var(--red); color: var(--red); }
.queue-timer-btn.custom { background: #fff5f5; border-color: var(--red); color: var(--red); font-weight: 800; }
#ctrl-timer-display {
font-variant-numeric: tabular-nums;
letter-spacing: 1px;
}
#ctrl-timer-display.warn { color: #fbbf24; }
#ctrl-timer-display.danger, #ctrl-timer-display.expired { color: #ef4444; }
/* Right panel */
.panel-right {
display: flex;
flex-direction: column;
overflow: hidden;
background: white;
}
.panel-right-tabs {
display: flex;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
.panel-right-tab {
flex: 1;
padding: 10px 4px;
text-align: center;
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.4px;
color: var(--muted);
cursor: pointer;
border-bottom: 2px solid transparent;
transition: all 0.15s;
}
.panel-right-tab.active {
color: var(--red);
border-bottom-color: var(--red);
}
.panel-right-content {
flex: 1;
overflow-y: auto;
}
.right-panel-section { display: none; }
.right-panel-section.active { display: block; }
/* Visitors panel */
.visitor-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 16px;
border-bottom: 1px solid var(--border);
}
.visitor-item .v-name {
flex: 1;
font-size: 0.9rem;
font-weight: 600;
}
.visitor-item .v-company {
font-size: 0.8rem;
color: var(--muted);
}
.add-form {
padding: 16px;
background: #f9fafb;
border-bottom: 1px solid var(--border);
}
.add-form input, .add-form textarea, .add-form select {
width: 100%;
padding: 8px 10px;
border: 1px solid var(--border);
border-radius: 6px;
font-size: 0.88rem;
font-family: inherit;
margin-bottom: 8px;
color: var(--text);
}
.add-form input:focus, .add-form textarea:focus {
outline: none;
border-color: var(--red);
}
.add-form label {
font-size: 0.78rem;
font-weight: 600;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 1px;
display: block;
margin-bottom: 4px;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
/* Members management panel */
.members-list-manage {
padding: 12px;
}
.member-manage-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 12px;
border: 1px solid var(--border);
border-radius: 8px;
margin-bottom: 6px;
background: #fafafa;
}
.member-manage-item .m-name {
flex: 1;
font-size: 0.88rem;
font-weight: 600;
}
.member-manage-item .m-company {
font-size: 0.78rem;
color: var(--muted);
}
/* Modal */
.modal-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.55);
z-index: 1000;
align-items: center;
justify-content: center;
backdrop-filter: blur(4px);
}
.modal-overlay.open { display: flex; }
.modal {
background: white;
border-radius: 16px;
padding: 28px;
width: min(560px, 95vw);
max-height: 85vh;
overflow-y: auto;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
}
.modal-title {
font-size: 1.1rem;
font-weight: 700;
color: var(--text);
}
.modal-close {
background: none;
border: none;
font-size: 1.4rem;
color: var(--muted);
cursor: pointer;
padding: 4px;
}
.modal input, .modal textarea, .modal select {
width: 100%;
padding: 9px 12px;
border: 1px solid var(--border);
border-radius: 8px;
font-size: 0.9rem;
font-family: inherit;
color: var(--text);
margin-bottom: 12px;
}
.modal input:focus, .modal textarea:focus {
outline: none;
border-color: var(--red);
}
.modal label {
font-size: 0.8rem;
font-weight: 600;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 1px;
display: block;
margin-bottom: 4px;
}
.modal-footer {
display: flex;
gap: 10px;
justify-content: flex-end;
margin-top: 20px;
padding-top: 16px;
border-top: 1px solid var(--border);
}
.btn-save {
background: var(--red);
color: white;
}
.btn-save:hover { background: var(--red-dark); }
/* Profile Extras Section in Modal */
.extras-section-divider {
border: none;
border-top: 1px solid var(--border);
margin: 16px 0 12px;
}
.extras-section-title {
font-size: 0.72rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1.5px;
color: var(--muted);
margin-bottom: 12px;
}
.offline-photo-row {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 12px;
background: var(--bg);
border-radius: 8px;
border: 1px solid var(--border);
margin-bottom: 8px;
}
.offline-photo-thumb {
width: 52px; height: 52px;
border-radius: 50%;
object-fit: cover;
border: 2px solid var(--border);
flex-shrink: 0;
}
.offline-photo-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
align-items: center;
}
.btn-file-label {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 5px 12px;
font-size: 0.82rem;
font-weight: 600;
cursor: pointer;
border-radius: 6px;
border: 1px solid var(--border);
background: white;
color: var(--muted);
transition: all 0.15s;
}
.btn-file-label:hover { background: white; color: var(--text); }
/* Import/Export */
.import-export-panel {
padding: 16px;
}
.import-export-panel textarea {
width: 100%;
height: 200px;
font-family: monospace;
font-size: 0.8rem;
padding: 10px;
border: 1px solid var(--border);
border-radius: 8px;
resize: vertical;
}
/* ============================================================
UTILITY
============================================================ */
.flex-gap-8 { display: flex; align-items: center; gap: 8px; }
.mt-8 { margin-top: 8px; }
.mt-16 { margin-top: 16px; }
.text-muted { color: var(--muted); font-size: 0.85rem; }
.text-red { color: var(--red); }
.w-full { width: 100%; }
.empty-hint {
text-align: center;
color: var(--muted);
padding: 32px 16px;
font-size: 0.9rem;
}
.empty-hint h3 {
font-size: 1rem;
margin-bottom: 8px;
}
.status-dot {
width: 8px; height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
.dot-live { background: #22c55e; }
.dot-idle { background: #9ca3af; }
/* Scrollbar */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #d1d5db; border-radius: 3px; }
/* Prevent photo load errors showing broken image icon */
img { font-size: 0; }
</style>
</head>
<body>
<audio id="bell-audio-src" preload="auto" src="data:audio/mpeg;base64,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VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV+T+p3e+99L+v//ZAgLVIWAAswAoAyMAzAMzAWgIAwKkDRMEgBSDCXwtUx8xq8PxsLXzFURhgxLMJfMCoA0DAMwFswCgMjALALLABRYALLABRYAKMAoAswCwCjAyBbMIEPgzTJWj/+9LEMoPYOQ8GD/o0w00poAH/UwjVREgMKgDMrALCwA5gDgMGAwB+FgB/C4DKY6nvU6LABZgZBUG6tdeYLQBZh8IJHXQJCYQAGRgZgFmAWAWYBQBRgFgtFYBY/fIT/+SklSX4rIJz//w4f///E1/0f6f////1////67C/YkAMlgAXMAWASjASwH0wHwDkMCxBSzBGAkwwecW5MWE/5jj6lgcxVMFKMyRASzCTABcwRkAWMCwAFysBLKwBYrAF/LAAsWABcwBYBKMAXAfTAfAbIw7dAwMSECTDAfQLEwEoBIMCoCwwKwGTA+A+MAYAZMcwBgPkxEx1PJjmAsAsYPocp58+XGIyHKYpVRx16EmmD4D4YJYJZgLgLmAsAsYJYPhWCWMTF1jE/r/xixdxdABURiav/wYX9f//WGUX//1f/gwvrX/qTEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf//bP9Frt/Wrv3HJT6MACAAzAFgBcwB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KSwH0Z/OjpgCgCmGs58cyA1Jg3AlGBSBSYAoA5gCACGBSCUVgU//6v/wwwRz1//gwvq//6qv/+v/8Il9VX/BhfCJf///gwlMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV98vfF8/u7//1//6nAQANGAIgCBYAPSwA1FgCZMAQA8iwDqmA1iVBhzHMGcZ4j1GB5C3Bl3QS6YS6B5GC//vSxB4D2Jmg/A/6lMKdLaAB/06YPATJhMgeGDUB6YHoCBgIAIlYCP+VgImB4AgYTICJn5FEmtyB6YTABxgegImAOAqCAFBUEVI30kUkHzZykaWAEDARDFMBGzIwPQmTA8fWM5gacwEAPDARARKwESsBAwEQECsBH8VX9X+KrisgaMEKvX/+EQnq//8RZX////hEh6qv+DAn///8Ihfof+D//+f//9Cp0p2mMXKBQBKVgFIKAgTAcQBIwM4BZMHTBjDHLGhk+vQXKMOVHLDFygtcwUUEsMCgAoTBZBYMCgCkwEwEi5JcpNpNsEgJAgCkwKQzjFEeMMmEOYEAVp+MlDgCjAMANVLJ1Tsnfz38LlmAkDgZSEAZggAJmCyuUZU4i5gUgUJt+XJBQE5WAn+MT///gMw//8G4f/////+3/wyv3/8G4kxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX3z+gfP7/49//+SvgkezhNkwBgAYMAzAMzAaAE4wJ8CFMEkA8DCTQf0x8Q8gP1EH+zE7xpwwhkKDAgHgWAIUCgZFj/+9LEIQPVLW0AD/oUwyEtnwH/UpgBlApAtAry05aQwGQGTBOCEMk1aozCgnjBpAzMBgBgsgWTAADijUHluHIg+DlVUCjAZBpNBd1gwMwTzCFSCNXULswMwGECXlpQKAymz////xWAH///hdf///jexv+N//t/8Lrff/ww/tm9Tps/81ztz/+DUxAwAPKwAYsAC5gCwCUYCWA+mA+AchgcoL+YL8FAGFai9pkRXz4fr0tRmN3idRl+4NmYQwA+GCMgC5g+ALFYJZWAsVgLlYCxWAsVgLmAsAuYJYJRg+Bymdu8Sa3AjJg+gLFYCwiAMAADZgGgiIEGzAEBtd67GytmMBcBcwSgfDiKr1MOQBYxSztjspBLKwsTAWAWLACxYAXLAPvhw//6//wiBA0IDq//CIr1//+GVXw4f/1N/8GF/W6/+ESyTEFNRTMuMTAwqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq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wKkwFoEgMCBCNzA0BXgwFv+HMn4UrjDTA5wzMQGZMKHAqDBIAFswqQMjBaALMDMAorAyKwC/8sAFFgDMsBoGAW+YYVIf//vSxDID2P2s9A/6VMMttd7BT9oIBgFAZmAUAUNACFYB40A6nzB6jLlwdB8GFgAswCggDD5fNMDIDIwqQgTdXBaMFsAswCgCiwAUWACzAKAz8XeLv/1f/wOr6//wZXq//6gxWr8TX/X/+DK9VX/Blf1//wjeDK/CNRiRKs/b52EQIGBAAZMkBkiYGpUAcCCYC6A+GBcAe5g5oX8Y7ZS1n9cFoBhj5AMZG4GemD1gqRgewFyBh9C4Bg+B+BghBABgAACDAAwYAEGAAAwQgBAwfA+A0jrCA2PBcAwughAwQAAAEAIA8AoDgOBq4lxOBLEuSxKhEAAGAELoGZHPAMACBh9XCB0nE0Bg/ACBgAAAEQABEAIGAAEIMABx+j8Qv9X/8GAh//8DACAH1f/+///2/+BgBAB9//BgAf//8IgA/wiAHBgAVUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/b8SoSsSoTUBhgBqzIHiVGBMgKJggwEMYQuB6mPAAVZ9ugh2YjiNSGCnBS5gOoIMY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yd4MlMIZBWjBCgLsDEKGgDCcDMDBmBgAYGIXX4Ng0DBmE4DNYwoDU+E4AIaIXXFZAeAIBwEw1bCIEvC6wGBkDAHfsQoGE8DIRfAAKLsIgYDDwusF1wuv//q//DDBEDPX/+F1vDTP//DX/////BsG/f/ww2F1/8Gwd/DDg2DuF1gbBoYaF14YaF11TEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/b8RQRURQGDQFIAG1Ggd5AYESAamCCALxhE4C+Y98San7IBE5iNo4IYVIFmmBZgjRgJQESYBCAv/70sQcA9bdrvYKftECwzXegV/aGFGAGAEJgBoAYiqo16nKjRYADQoAvGA6h+BgLwDoFAA1BxWBWNWD4PQIuTBjkuSFwwGAwEIHC4rAGDQEIGDVUgGhML4CgGuFw4i/4q///+AoBr//xFf//+Il//9v/iKff/wuHxFIioiviKBcIIpiL/EUiLCLiKBcNEXC4eIqFw3H72/DhA3GGUBuMDCQoAyCQQM4IgwLcDPMEsBdDCSQzUx4x+iPliMtzFPRZoxbMIbMDmAtjAWQFkwCgAoMATAEy5BcsuQogoiogoiCgFAwXUCIMIwAKAUAplyAyAGzhEBw/QQgC4/B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VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV9//+Tf/8///4NUaUbUaRWKwCAIAazASgAwwIkAhMEEAIDCJwF4x9Q/bP8EBWDEQx1Yw/ALjMDxBLjAdA//vSxB4D1hmu8g/2VMLtNh4BT9oQJ0w1EowgDUwNAwKAao1/qNoqGEIQmgj0mtoahA2IrqnVIIQDar///+o0FQhNQ80CBDMSh1CYZKwgRX9RoKgZ/////EUAVv//wuE///7///b/4iv3/8RQRSIpxFPEVEV8Lhoi4ioXCBcLEWC4QLhYi4igikRb/b8OEDcYNwA3GBklAHBggfDgYFuBnmCiArhhPIYOZCQ4oH/gF/ZizY0QYm+E8mBbAcxgIIDiYAkAgAkATBAAkXLLkJtBww4QNwgYEwgAY5lTAZdQgAFCBaILA3RF3F2MXwCAJAaGmmgwCQGEAiwGxwIIBQJAyuEQJf///+HDCIEv//BuHw4X//hlfhlP/t/8G4Pv/DhYNx+HDhwg4UOEGUDKhlMMpBuHDhhwsG4A4UG4wbgg3CDcIcIG4ExBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf6v8DAIBBgACIhAwAPgMfn0DLi4MCbBNTA9gkYwc0VlMTo7nDhRlJcxOkE0MudAITCKAAEwRUAAMCaAATAXQAArAICsAhMAAAACwAAFgABLAAAYACAAmAAALhgbIgkYIoBNFgAfMAAAAC5CiJcj/Ta///zAAQCEwz0PWMA/AIT/+9DEP4PcbbDsCv6RQye2XUH7RqjAPgqIwsIB9MAAAADAAQAH/MABAACsAB//1//wNAB6v/wYh9f//gwAv//q//AwIH11/8DAAAYBwiBwYAwYBhECDAIGAAgwBgwABgAIRABEBgYACDAMGIQYAhFCBgAIRAAYECDAH////67//JP/ysAFMAEABTABACkwAUAoMAEASzABQJAwCgD7MBvByjBPhO8xkv5yPlpS1TBYiDMzPkNuMKcBYzBPwLQDFoG8DDeEoDCUCkDBSAQGAE4MAKBgoAKBmpJ+Bp6AIFBLAwAgXgGHCIGMMP8DAIAQDg45EDBQAUItuAxrCRCIBgiAXBgBQYAX8Rb+v/8GAFAwUgF6v/wOT1/4Rn62X/wZfq//Bk9bL/4HaEaEb9UIwGSEYByAcgMsDl4RoRoRgRnCM8IwGRVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX5P/vn9D/b33vuXPQKQLLSlYAyVgGQGAnwMCEFYHiYQwBdmPvpjx/vIU+YhkO6mJBhdxghQJKYEKBPGJwnmDIZGDAMlpkC/9AtAosBmBcLMTgzLLdU5U4U5U59Rr///LTn//vSxDCD1ZWw8A/1tMOQth4BftKYd7xAYMwLDBqCJ5ab///GfxmEbjPHX//wLIFj//wLP/4R/9v//v/8AD32//+BYwLYFiBZAsAAd8AD4FgADgFkCzwAOwLYAHwLQFsC3/v/wiAAAYACAQgYBAAfAYC6AugYE0BNAYIqCKgYRQEzAY7atvgfTWUmAYoQL+gYesEjAYD6BcgYBAAuGAIQmAAAGAIAlYAFYAlYQmEAAGEIAmEAQGH4+mexNmLpNGEIfmAYQGAIQGAAQmAIAGEAAmEIAGEAAlYQlYQGH4QmH4fmHw+GLqanFDXmPgQGPq4G15sGLgfmLoAFYfmHwAGEIQmAAQAYFCEQARAgYEABgAIMAAwCDAAMA///wNAB//8GAf/////+//wiB+3//hED/gwCDAHwMCAwMABwiBCIGEQIRAwiBUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX5N6BUaoJPIf/5P8H/5WACFYAKWACgwAUBLMBKAbjAbwNcwLQE+MEdCNTCEBVYxb/tROdrUvDFVgjUy5oCQMI1ABSsD6A0iBAYSgYKQYBAMCj/+9LEKIPWbbDqD9Y1QzW2XUFf1ggUIgUIgTwiBQoU4eXwuGC4ULhgiDRFQwwYcMMGHDDBhwwwYcLxBsGBdcLrfhdf+r/+DBT1//gcnq//8I1X//X/+EZ6qv8IwDlgycGX4HLwjIRkDkA7YMoMkI6B2AyBGYHKDJxFOv/C4UBQNAYNEACBqAklAIRJgBoG0YBoDgmB4iSxiiXGSdykinGA6jbRl3AWaYTOCXGCXAToGnS8Bl4agY1EAChBBgNEVEU8Ig0BQNMvEX/////1f4i2IqFwoGDAYIrEWWIr4inEViKAKBnEUh5OIrEXEWEXEXhcJEV4igeT4ioigigi4i//xFhFPgJBvqq+IoIthcNEWC4aIoIsIqItC4cLhAuEEWhcIIoIsFwgiwi4XCgIEIi0LhhFAYDAuHC4QLhRFwYDBFAuFC4ZTEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/9qn//8////ao1VUwcADFYAkVgGYcBSBwIOYHkAJmEJgiRv/70sQRg9ZZsu4P6hQCfjZdwU/SCD4Svmf5gH5GIJjwRiiQYGYJGCZGBJAUZ06RomZkyRggbVPFWKrhHDVjf//4qv4qv//xWBWRWAO4rHFYFXFXhq2KzgzFWGrIq4q/xVRVCsCse4rArGKwKr/7f/Adff4q/FZDVwqorAaugN8VcVQrOKoVkVgI4M4avDVwauAbA1aA2hqwNWirFWA+AfhqwNXirktje7Fzx/EUC4cRQLhwNAMA2rUDvXjAswJ0wRoEaMJRCZzH1FPQ/NQmdMUTGXjDXwnswF4DwMANAdQENAihCIwRULhIi/iKgwY3FV//4q/4i3///BiH4i3/iKf/4iv4iX8Rf/t/8Lh/v8RXxFMRTiLCK4i4i4XDiKhcOFw4i4ioXDiLiLBEYIqIqIqIsFwoXCgJGhcOERoXDBcMAobC4dVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX5P/qIf+uUVHe//8wEBKwA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Ybg2D1QutC634XW4XXX/+Ec+Gmdf/+gX/oF+mz6BSbJadNjyzPlpU2S0pWx5aQDZpsFpSthNlNhNhApAoryQKNhgCMJsgbLywwWkA2SBfH/IRY/EL43oMAgGBQIBgUUAYEJYRNwGBUiYAIB9FgHvMBvFEDD1vV05aRM8MEcGKDMkAp0wnkEcME+AkTAtQEowG4ApMBLABDAKAAUrABSwAC/hSnXiLf/+Kz/DVkVn6v/4GpCdf/8IheoIhf/9UKCPwYF/1//gYQJ6qvBgXgwKBhAsGBMGBcIhwYEgYQKDAgRCAYQKBhAgRCwiogYQIBhQoRChEIBqAgRUgYQKBhAkDChQNQECKkIhAMIEBgVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf9v4ioXDAKGgFDICmoBUqYESAGGB4AL5g/4NUY9U32n//vSxBAD1nG06gp+kEJxtp2BT9II+NDJRh449UYxoGGmCsgoRgTgE6B37wG0agaFABjBoikRfxFQYNbjf//8Rf////4CkDiLf/iLfEWEXEWEWEU4i4ioi0RXh5/EU8RTEV9v/hcOIt7xFuIp4XDcRQRQRYRWIsFw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VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//vSxAADwAABpAAAACAAADSAAAAETEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU="></audio>
<!-- ============================================================
DISPLAY VIEW (Monitor 2)
============================================================ -->
<div id="view-display" class="view">
<button id="fullscreen-btn" onclick="toggleFullscreen()" title="Vollbild">⛶</button>
<button id="sound-enable-btn" onclick="enableBellSound()" title="Klick, damit der Glockenton am Ende der Redezeit spielt">🔔 Ton aktivieren</button>
<div class="display-header">
<img class="bni-badge" id="display-logo" src="logo.jpeg" alt="BNI">
<div class="chapter-name" id="display-chapter-name">Vorstellungsrunde</div>
<a href="#" onclick="window.location.hash=''" style="margin-left:16px;color:rgba(255,255,255,0.25);font-size:0.78rem;text-decoration:none;letter-spacing:1px;" title="Zur Steuerung">⚙ Steuerung</a>
</div>
<div id="display-header-timer"></div>
<div class="progress-indicator" id="progress-text"></div>
<div class="qr-corner" id="qr-wrap" style="display:none">
<div id="display-qr"></div>
<div class="qr-label">Profil</div>
</div>
<div class="round-badge" id="round-badge">2. Runde – Besucher</div>
<!-- Abwesenheits-Zusammenfassung -->
<div id="display-absent-summary" style="display:none;">
<div class="absent-summary-heading">Abwesenheiten</div>
<div class="absent-summary-cols">
<div id="absent-col-medical" class="absent-summary-col" style="display:none;">
<div class="absent-summary-col-title medical">Medical</div>
<div id="absent-list-medical"></div>
</div>
<div id="absent-col-absent" class="absent-summary-col" style="display:none;">
<div class="absent-summary-col-title absent">Abwesend</div>
<div id="absent-list-absent"></div>
</div>
</div>
</div>
<!-- Mitgliederkoordinator-Bericht -->
<div id="display-coordinator-report" style="display:none;position:fixed;inset:0;padding:clamp(60px,8vh,100px) clamp(30px,5vw,80px) clamp(24px,4vh,50px);flex-direction:column;gap:clamp(10px,1.8vh,20px);overflow:hidden;z-index:20;">
<div style="font-size:clamp(0.9rem,2vw,1.4rem);font-weight:700;color:rgba(255,255,255,0.55);letter-spacing:2px;text-transform:uppercase;">Mitgliederkoordinator-Bericht</div>
<div id="coord-report-grid" style="display:grid;grid-template-columns:repeat(4,1fr);gap:clamp(8px,1.5vw,18px);flex:1;min-height:0;"></div>
<div style="font-size:0.7rem;color:rgba(255,255,255,0.25);text-align:right;" id="coord-report-updated"></div>
</div>
<!-- Agenda-Folie (Bild oder Live-Einbettung) -->
<div id="display-agenda-item" style="display:none;position:fixed;inset:0;z-index:20;background:#000;align-items:center;justify-content:center;">
<img id="agenda-item-image" src="" alt="" style="display:none;width:100%;height:100%;object-fit:contain;">
<iframe id="agenda-item-embed" src="" title="Agenda" allow="fullscreen" style="display:none;width:100%;height:100%;border:0;"></iframe>
<div id="agenda-item-error" style="display:none;color:rgba(255,255,255,0.6);font-size:1.3rem;text-align:center;padding:40px;">⚠ Bild nicht gefunden – bitte im Admin-Panel neu hochladen.</div>
</div>
<!-- Klicker-Ersatz: unauffälliger Klick-Button zum Weiterschalten direkt vom Display aus,
für Klicker, die einen Mausklick statt eines Tastendrucks senden, oder falls der
Tastatur-Fokus in eine eingebettete Folie gerutscht ist. Bewusst nur in der Ecke,
unsichtbar bis Hover/Fokus, damit die Zuschauer-Ansicht nicht gestört wird. -->
<button id="display-advance-btn" onclick="displayAdvanceClick()" title="Weiter"
style="position:fixed;right:0;bottom:0;z-index:30;width:64px;height:64px;border:none;
background:rgba(0,0,0,0.35);color:rgba(255,255,255,0.35);font-size:1.6rem;
line-height:1;cursor:pointer;border-top-left-radius:14px;opacity:0;
transition:opacity 0.15s ease, color 0.15s ease, background 0.15s ease;"
onmouseover="this.style.opacity='1';this.style.background='rgba(0,0,0,0.55)';this.style.color='#fff';"
onmouseout="this.style.opacity='0';this.style.background='rgba(0,0,0,0.35)';this.style.color='rgba(255,255,255,0.35)';">▶</button>
<!-- Startbild: außerhalb der Karte, damit es unabhängig angezeigt werden kann -->
<div id="display-empty" style="position:fixed;inset:0;display:flex;align-items:center;justify-content:center;z-index:10;">
<img id="display-logo-empty" src="logo.jpeg" alt="BNI" style="width:min(520px,72vw);height:auto;border-radius:24px;box-shadow:0 8px 60px rgba(0,0,0,0.5);">
</div>
<div class="main-card" id="main-card">
<div id="display-content" style="display:none; width:100%; display:none; flex-direction:row; align-items:center; gap:56px;">
<div class="photo-section">
<div class="photo-timer-wrap">
<svg class="timer-ring-svg" id="timer-ring-svg" viewBox="0 0 248 248">
<circle class="timer-ring-track" cx="124" cy="124" r="116"/>
<circle class="timer-ring-progress" id="timer-ring-progress" cx="124" cy="124" r="116"
stroke-dasharray="729" stroke-dashoffset="0"/>
</svg>
<div class="member-photo-wrap" id="photo-wrap">
<div class="initials-avatar" id="display-initials">?</div>
</div>
</div>
<div id="display-timer-text"></div>
</div>
<div class="info-section">
<div class="display-name" id="display-name">—</div>
<div class="display-company" id="display-company">—</div>
<div class="display-specialty" id="display-specialty" style="display:none"></div>
<div class="visitor-tag" id="visitor-tag" style="display:none">Besucher</div>
<div class="presenter-row" id="presenter-row" style="display:none">
<div class="presenter-avatar" id="presenter-avatar"></div>
<div class="presenter-text">
<div class="presenter-label" id="presenter-label">Vertreten durch</div>
<div class="presenter-name" id="presenter-name"></div>
<div class="presenter-company" id="presenter-company"></div>
</div>
</div>
</div>
</div>
</div>
<div class="next-bar" id="next-bar" style="display:none">
<span class="next-bar-label">Als nächstes</span>
<div class="next-bar-divider"></div>
<img class="next-photo-small" id="next-photo-img" src="" alt="" onerror="this.style.display='none'">
<div class="next-info">
<div class="next-name-text" id="next-name-text">—</div>
<div class="next-company-text" id="next-company-text"></div>
<div id="next-sub-text" style="display:none;font-size:clamp(0.7rem,1.5vh,1.5rem);color:rgba(255,255,255,0.4);margin-top:2px;"></div>
</div>
</div>
<div class="next-bar" id="next-bar-end" style="display:none">
<span class="next-bar-end-text">Das war die letzte Vorstellung – Danke!</span>
</div>
<div style="position:fixed;bottom:16px;right:150px;display:flex;align-items:center;gap:8px;opacity:0.75;pointer-events:none;">
<span style="color:white;font-size:0.65rem;letter-spacing:1px;text-transform:uppercase;">powered by</span>
<img src="digiseed.png" alt="Digiseed" style="height:32px;width:auto;background:white;border-radius:5px;padding:3px 6px;">
</div>
</div>
<!-- ============================================================
CONTROL VIEW (Monitor 1)
============================================================ -->
<div id="view-control" class="view">
<!-- Header -->
<div class="ctrl-header">
<img class="bni-badge" id="ctrl-logo" src="logo.jpeg" alt="BNI">
<div class="ctrl-title" id="ctrl-chapter-name">Vorstellungsrunde steuern</div>
<div class="flex-gap-8">
<div class="status-dot dot-idle" id="sync-dot" title="Display-Verbindung"></div>
<button class="btn btn-secondary btn-sm" onclick="resetMeeting()">↺ Neu starten</button>
<button class="btn btn-secondary btn-sm" onclick="openPdfImport()">📄 PDF importieren</button>
<div id="ctrl-timer-controls" style="display:flex;gap:4px;">
<button class="btn btn-secondary btn-sm" onclick="editDefaultTimer()" title="Standard-Redezeit anpassen" style="font-variant-numeric:tabular-nums;">⏱ <span id="ctrl-timer-default">1:00</span></button>
<button class="btn btn-secondary btn-sm" id="ctrl-timer-toggle" onclick="toggleTimer()" style="font-variant-numeric:tabular-nums;min-width:80px;">▶ Start</button>
<button class="btn btn-secondary btn-sm" id="ctrl-timer-reset" onclick="resetTimer()" title="Timer zurücksetzen">↺ <span id="ctrl-timer-display" style="min-width:36px;display:inline-block;"></span></button>
</div>
<button class="btn btn-secondary btn-sm" onclick="openMemberSync()">🔄 Mitglieder aktualisieren</button>
<button class="btn btn-secondary btn-sm" onclick="openImportExport()">⬇ Import / Export</button>
<button class="btn btn-secondary btn-sm" onclick="window.open(location.href.split('#')[0] + '#display', '_blank', 'noopener')">🖥 Anzeige öffnen</button>
<button class="btn btn-secondary btn-sm" id="btn-coordinator-report" onclick="showCoordinatorReport()" style="display:none;">📊 Koordinatoren-Bericht</button>
<button class="btn btn-next" id="btn-next" onclick="goNext()" disabled>
Weiter <span id="btn-next-name"></span> →
</button>
</div>
</div>
<!-- Notizen-Leiste: Sprecher-Notizen des aktuell live geschalteten Agenda-Punkts, unabhängig
vom gerade aktiven Reiter (Agenda/Mitglieder/Besucher) links. Wird von renderControl()
über renderAgendaNotesPanel() befüllt/versteckt — NUR im Steuerpult, nie im Display-View. -->
<div id="agenda-notes-panel" style="display:none;"></div>
<!-- Body -->
<div class="ctrl-body">
<!-- LEFT: Queue -->
<div class="panel-left">
<div class="panel-section-header">
<span class="panel-section-title">Reihenfolge (ziehen zum Sortieren)</span>
<div class="queue-tabs">
<div class="tab-btn" id="tab-agenda" onclick="switchTab('agenda')">Agenda</div>
<div class="tab-btn active" id="tab-members" onclick="switchTab('members')">Mitglieder</div>
<div class="tab-btn" id="tab-visitors" onclick="switchTab('visitors')">Besucher</div>
</div>
</div>
<div class="member-queue" id="member-queue">
<!-- Member items rendered here -->
</div>
<div id="queue-resize-handle" title="Ziehen zum Anpassen der Höhe"></div>
<div id="absent-members-section" style="display:none;"></div>
<div class="member-queue" id="visitor-queue" style="display:none">
<!-- Visitor items rendered here -->
</div>
<div class="member-queue" id="agenda-queue" style="display:none">
<!-- Agenda items rendered here -->
</div>
<div id="agenda-hidden-section" style="display:none;"></div>
</div>
<!-- RIGHT: Visitors + Members Management -->
<div class="panel-right">
<div class="panel-right-tabs">
<div class="panel-right-tab active" data-panel="visitors-panel" onclick="switchRightPanel('visitors-panel')">Besucher</div>
<div class="panel-right-tab" data-panel="members-panel" onclick="switchRightPanel('members-panel')">Mitglieder</div>
<div class="panel-right-tab" data-panel="substitutes-panel" onclick="switchRightPanel('substitutes-panel')">Vertretungen</div>
<div class="panel-right-tab" data-panel="admin-panel" onclick="switchRightPanel('admin-panel')">Admin</div>
</div>
<div class="panel-right-content">
<!-- VISITORS -->
<div id="visitors-panel" class="right-panel-section active">
<div class="add-form">
<div style="font-size:0.85rem; font-weight:700; margin-bottom:10px; color:var(--text)">Besucher hinzufügen</div>
<label>Name</label>
<input id="v-name" type="text" placeholder="Max Mustermann">
<div class="form-row">
<div>
<label>Firma</label>
<input id="v-company" type="text" placeholder="Firma GmbH">
</div>
<div>
<label>Fachgebiet</label>
<input id="v-specialty" type="text" placeholder="Steuerberatung">
</div>
</div>
<label>Eingeladen von (optional)</label>
<select id="v-invited-by">
<option value="">— kein Mitglied —</option>
</select>
<label>Foto-URL (optional)</label>
<input id="v-photo" type="text" placeholder="https://...">
<button class="btn btn-primary btn-sm w-full mt-8" onclick="addVisitor()">+ Besucher hinzufügen</button>
</div>
<div id="visitors-list">
<!-- visitor items -->
</div>
</div>
<!-- MEMBERS MANAGEMENT -->
<div id="members-panel" class="right-panel-section">
<div style="padding: 12px 16px 6px; display:flex; align-items:center; justify-content:space-between;">
<span class="text-muted">Alle Mitglieder verwalten</span>
<button class="btn btn-primary btn-sm" onclick="openAddMember()">+ Mitglied</button>
</div>
<div id="members-manage-list" class="members-list-manage">
<!-- member management items -->
</div>
</div>
<!-- SUBSTITUTES -->
<div id="admin-panel" class="right-panel-section">
<!-- rendered by renderAdminPanel() -->
</div>
<div id="substitutes-panel" class="right-panel-section">
<div style="padding: 14px 16px 8px;">
<div style="font-size:0.85rem; color:var(--muted); margin-bottom:12px;">Mitglied als vertreten markieren:</div>
<label style="font-size:0.78rem; font-weight:600; color:var(--muted); text-transform:uppercase; letter-spacing:1px; display:block; margin-bottom:4px;">Mitglied</label>
<select id="sub-member-select" style="width:100%; padding:8px; border:1px solid var(--border); border-radius:6px; font-size:0.88rem; margin-bottom:10px;">
<option value="">— Mitglied auswählen —</option>
</select>
<label style="font-size:0.78rem; font-weight:600; color:var(--muted); text-transform:uppercase; letter-spacing:1px; display:block; margin-bottom:4px;">Vertreter Name</label>
<input id="sub-rep-name" type="text" placeholder="Name der Vertretung" style="width:100%; padding:8px; border:1px solid var(--border); border-radius:6px; font-size:0.88rem; margin-bottom:8px;">
<label style="font-size:0.78rem; font-weight:600; color:var(--muted); text-transform:uppercase; letter-spacing:1px; display:block; margin-bottom:4px;">Firma</label>
<input id="sub-rep-company" type="text" placeholder="Firma der Vertretung" style="width:100%; padding:8px; border:1px solid var(--border); border-radius:6px; font-size:0.88rem; margin-bottom:8px;">
<label style="font-size:0.78rem; font-weight:600; color:var(--muted); text-transform:uppercase; letter-spacing:1px; display:block; margin-bottom:4px;">Fachgebiet</label>
<input id="sub-rep-specialty" type="text" placeholder="Fachgebiet" style="width:100%; padding:8px; border:1px solid var(--border); border-radius:6px; font-size:0.88rem; margin-bottom:8px;">
<label style="font-size:0.78rem; font-weight:600; color:var(--muted); text-transform:uppercase; letter-spacing:1px; display:block; margin-bottom:4px;">Foto-URL (optional)</label>
<input id="sub-rep-photo" type="text" placeholder="https://..." style="width:100%; padding:8px; border:1px solid var(--border); border-radius:6px; font-size:0.88rem; margin-bottom:10px;">
<button class="btn btn-primary btn-sm w-full" onclick="saveSubstitute()">Vertretung speichern</button>
</div>
<div style="padding: 0 16px; border-top:1px solid var(--border); margin-top:8px;">
<div style="padding:12px 0 6px; font-size:0.78rem; font-weight:700; color:var(--muted); text-transform:uppercase; letter-spacing:1px;">Aktive Vertretungen</div>
<div id="substitutes-list"></div>
</div>
<div style="padding:0 16px 12px;border-top:1px solid var(--border);margin-top:4px;">
<div style="padding:12px 0 6px;display:flex;align-items:center;justify-content:space-between;">
<div style="font-size:0.78rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:1px;">Stammvertreter-Vorrat</div>
<button class="btn btn-secondary btn-sm" onclick="openAddSv()">+ Person</button>
</div>
<div id="sv-list-container"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ============================================================
8-AUGEN TIMER OVERLAY (Sibling zu allen Views, überlagert bei Bedarf)
============================================================ -->
<div id="display-augen-timer" style="display:none;position:fixed;inset:0;padding:clamp(30px,4vh,60px) clamp(24px,4vw,60px);flex-direction:column;gap:clamp(14px,2vh,24px);overflow:hidden;z-index:100;background:#0a1220;">
<div style="display:flex;align-items:baseline;justify-content:space-between;gap:20px;flex-wrap:wrap;">
<div style="font-size:clamp(1rem,2.2vw,1.6rem);font-weight:700;color:rgba(255,255,255,0.55);letter-spacing:2px;text-transform:uppercase;">8-Augen-Gespräche</div>
<div id="augen-round-info" style="font-size:clamp(0.85rem,1.6vw,1.15rem);color:rgba(255,255,255,0.45);font-weight:600;"></div>
</div>
<div id="augen-timer-grid" style="display:grid;gap:clamp(16px,2vw,28px);flex:1;min-height:0;"></div>
<div id="augen-controls-bar" style="display:none;background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.08);border-radius:12px;padding:14px 18px;gap:18px;flex-wrap:wrap;align-items:center;">
<div style="display:flex;align-items:center;gap:8px;">
<span style="font-size:0.85rem;color:rgba(255,255,255,0.6);font-weight:600;">Wechsel 4er:</span>
<input id="augen-base-input" type="text" style="width:70px;padding:6px 10px;border-radius:6px;border:1px solid rgba(255,255,255,0.15);background:rgba(0,0,0,0.3);color:#fff;font-family:inherit;font-size:0.95rem;text-align:center;" placeholder="10:00" onkeydown="if(event.key==='Enter'){event.preventDefault();augenApplyBaseInput();}">
<button class="btn btn-secondary btn-sm" onclick="augenApplyBaseInput()">✓</button>
</div>
<div id="augen-size-toggles" style="display:flex;gap:6px;"></div>
<div style="display:flex;gap:8px;margin-left:auto;">
<button class="btn btn-secondary btn-sm" onclick="augenToggleRun()"><span id="augen-run-btn-label">▶ Start</span></button>
<button class="btn btn-secondary btn-sm" onclick="augenReset()">↺ Reset</button>
<button class="btn btn-secondary btn-sm" onclick="augenClose()">✕ Beenden</button>
</div>
</div>
</div>
<!-- ============================================================
ACTIVATION VIEW
============================================================ -->
<div id="view-activation" class="view" style="background:linear-gradient(160deg,#0f1f45 0%,#1a2744 40%,#0d1b35 100%);align-items:center;justify-content:center;flex-direction:column;">
<img id="activation-logo" src="logo.jpeg" alt="BNI" style="height:80px;margin-bottom:32px;border-radius:8px;object-fit:contain;">
<div style="background:rgba(255,255,255,0.08);backdrop-filter:blur(20px);border:1px solid rgba(255,255,255,0.15);border-radius:20px;padding:40px;width:min(420px,90vw);color:white;">
<div style="font-size:1.4rem;font-weight:700;margin-bottom:6px;">BNI Vorstellungsrunde</div>
<div style="font-size:0.88rem;color:rgba(255,255,255,0.6);margin-bottom:24px;">Bitte gib deinen Lizenzcode ein, um dein Chapter freizuschalten.</div>
<input id="activation-code-input" type="text" placeholder="chapter-lizenzcode"
style="width:100%;padding:12px 14px;background:rgba(255,255,255,0.1);border:1px solid rgba(255,255,255,0.25);border-radius:10px;color:white;font-size:1rem;outline:none;margin-bottom:12px;letter-spacing:1px;"
onkeydown="if(event.key==='Enter')activateChapter()"
autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">
<div id="activation-status" style="font-size:0.85rem;min-height:20px;margin-bottom:12px;"></div>
<button id="activation-btn" onclick="activateChapter()"
style="width:100%;padding:12px;background:#cc0000;color:white;border:none;border-radius:10px;font-size:1rem;font-weight:700;cursor:pointer;">
Aktivieren
</button>
</div>
</div>
<!-- ============================================================
MODALS
============================================================ -->
<!-- Add/Edit Member Modal -->
<div class="modal-overlay" id="modal-member">
<div class="modal">
<div class="modal-header">
<div class="modal-title" id="modal-member-title">Mitglied hinzufügen</div>
<button class="modal-close" onclick="closeMemberModal()">✕</button>
</div>
<input type="hidden" id="edit-member-id">
<label>Name *</label>
<input id="m-name" type="text" placeholder="Vorname Nachname">
<div class="form-row">
<div>
<label>Firma *</label>
<input id="m-company" type="text" placeholder="Firma GmbH">
</div>
<div>
<label>Fachgebiet *</label>
<input id="m-specialty" type="text" placeholder="Webentwicklung">
</div>
</div>
<label>Foto-URL (von BNI-Profil kopieren)</label>
<input id="m-photo" type="text" placeholder="https://cdn.bniconnectglobal.com/...">
<label>Profil-URL (von BNI, wird für Sync genutzt)</label>
<input id="m-profile" type="text" placeholder="https://bni-ulm.com/sonne/de/memberdetails?...">
<div id="photo-preview-wrap" style="display:none; text-align:center; margin:8px 0;">
<img id="photo-preview" src="" alt="" style="width:80px; height:80px; border-radius:50%; object-fit:cover; border:2px solid var(--border);">
</div>
<hr class="extras-section-divider">
<div class="extras-section-title">Dauerhaft gespeichert (bleibt nach BNI-Sync erhalten)</div>
<label>Kurzlink (für QR-Code) <span style="font-weight:400;color:var(--muted);">— überschreibt Profil-URL im QR</span></label>
<input id="m-kurzlink" type="text" placeholder="z.B. https://qr.bp.com/kai">
<label>Offline-Foto <span style="font-weight:400;color:var(--muted);">— lokal gespeichert, kein Internet nötig</span></label>
<div class="offline-photo-row" id="offline-photo-row">
<img class="offline-photo-thumb" id="offline-photo-thumb" src="" alt="" style="display:none;">
<div class="offline-photo-actions">
<label class="btn-file-label">
📁 Datei wählen
<input type="file" id="m-photo-file" accept="image/*" style="display:none" onchange="handleOfflinePhotoFile(this)">
</label>
<button class="btn btn-ghost btn-sm" onclick="loadOfflinePhotoFromUrl()">⬇ Von Profil-URL laden</button>
<button class="btn btn-danger btn-sm" id="btn-remove-offline-photo" onclick="removeOfflinePhoto()" style="display:none;">✕ Entfernen</button>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-ghost" onclick="closeMemberModal()">Abbrechen</button>
<button class="btn btn-save btn-primary" onclick="saveMember()">Speichern</button>
</div>
</div>
</div>
<!-- Import/Export Modal -->
<div class="modal-overlay" id="modal-importexport">
<div class="modal">
<div class="modal-header">
<div class="modal-title">Import / Export</div>
<button class="modal-close" onclick="closeModal('modal-importexport')">✕</button>
</div>
<p style="font-size:0.88rem; color:var(--muted); margin-bottom:14px;">
Mitgliederdaten als JSON exportieren oder importieren. Daten werden im Browser gespeichert.
</p>
<textarea id="json-textarea" placeholder="JSON hier einfügen..."></textarea>
<div class="modal-footer">
<button class="btn btn-ghost" onclick="exportJSON()">Exportieren</button>
<button class="btn btn-ghost" onclick="closeModal('modal-importexport')">Abbrechen</button>
<button class="btn btn-save btn-primary" onclick="importJSON()">Importieren</button>
</div>
<hr class="extras-section-divider" style="margin-top:24px;">
<div class="extras-section-title">Profildaten (Kurzlinks + Offline-Fotos)</div>
<p style="font-size:0.85rem; color:var(--muted); margin-bottom:14px;">
Kurzlinks und lokale Fotos werden <strong>getrennt</strong> gespeichert und bleiben nach jedem BNI-Sync erhalten.
Diese Datei im Projektordner ablegen und nach einem Browser-Reset wieder importieren.
</p>
<div style="display:flex; gap:8px; align-items:center;">
<button class="btn btn-ghost" onclick="exportProfildaten()">⬇ profildaten.json speichern</button>
<label class="btn-file-label" style="cursor:pointer;">
⬆ profildaten.json laden
<input type="file" id="profildaten-file-input" accept=".json" style="display:none" onchange="importProfildaten(this)">
</label>
<span id="profildaten-status" style="font-size:0.82rem;color:var(--muted);"></span>
</div>
</div>
</div>
<!-- Stammvertreter Modal -->
<div class="modal-overlay" id="modal-sv">
<div class="modal">
<div class="modal-header">
<div class="modal-title" id="sv-modal-title">Stammvertreter hinzufügen</div>
<button class="modal-close" onclick="closeModal('modal-sv')">✕</button>
</div>
<input type="hidden" id="sv-modal-id">
<label>Name *</label>
<input id="sv-modal-name" type="text" placeholder="Vor- und Nachname">
<div class="form-row">
<div><label>Firma</label><input id="sv-modal-company" type="text" placeholder="Firma GmbH"></div>
<div><label>Fachgebiet</label><input id="sv-modal-specialty" type="text" placeholder="Fachgebiet"></div>
</div>
<label>Foto</label>
<div class="offline-photo-row" id="sv-photo-row">
<img class="offline-photo-thumb" id="sv-photo-thumb" src="" alt="" style="display:none;">
<div class="offline-photo-actions">
<label class="btn-file-label">📁 Datei wählen
<input type="file" id="sv-photo-file" accept="image/*" style="display:none" onchange="handleSvPhotoFile(this)">
</label>
<button class="btn btn-danger btn-sm" id="sv-remove-photo" onclick="removeSvPhoto()" style="display:none;">✕ Entfernen</button>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-danger btn-sm" id="sv-delete-btn" onclick="deleteStammvertreter(document.getElementById('sv-modal-id').value)" style="display:none;">Löschen</button>
<button class="btn btn-ghost" onclick="closeModal('modal-sv')">Abbrechen</button>
<button class="btn btn-primary" onclick="saveStammvertreterModal()">Speichern</button>
</div>
</div>
</div>
<!-- Substitute Quick-Pick Modal -->
<div class="modal-overlay" id="modal-substitute">
<div class="modal">
<div class="modal-header">
<div class="modal-title">Vertretung für <span id="sub-modal-member-name"></span></div>
<button class="modal-close" onclick="closeModal('modal-substitute')">✕</button>
</div>
<input type="hidden" id="sub-modal-member-id">
<div id="sub-sv-picker" style="display:none;margin-bottom:12px;">
<div style="font-size:0.75rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:1px;margin-bottom:8px;">Stammvertreter wählen</div>
<select id="sub-sv-select" onchange="if(this.value) pickStammvertreter(this.value)" style="width:100%;padding:8px;border:1px solid var(--border);border-radius:6px;font-size:0.9rem;"></select>
<hr style="margin:12px 0 0;border:none;border-top:1px solid var(--border);">
</div>
<label>Vertreter Name *</label>
<input id="sub-modal-name" type="text" placeholder="Name">
<div class="form-row">
<div>
<label>Firma</label>
<input id="sub-modal-company" type="text" placeholder="Firma GmbH">
</div>
<div>
<label>Fachgebiet</label>
<input id="sub-modal-specialty" type="text" placeholder="Fachgebiet">
</div>
</div>
<label>Foto-URL (optional)</label>
<input id="sub-modal-photo" type="text" placeholder="https://...">
<label style="display:flex;align-items:center;gap:8px;margin-top:10px;cursor:pointer;font-weight:normal;">
<input type="checkbox" id="sub-modal-skip-round2" style="width:16px;height:16px;cursor:pointer;">
Tritt nicht in der 2. Runde auf <span style="font-size:0.75rem;color:var(--muted);">(z.B. Mitarbeiter, Dauervertreter)</span>
</label>
<div class="modal-footer">
<button class="btn btn-danger btn-sm" onclick="removeSubstitute(document.getElementById('sub-modal-member-id').value)">Vertretung entfernen</button>
<button class="btn btn-ghost" onclick="closeModal('modal-substitute')">Abbrechen</button>
<button class="btn btn-save btn-primary" onclick="saveSubstituteModal()">Speichern</button>
</div>
</div>
</div>
<!-- Besucher bearbeiten Modal -->
<div class="modal-overlay" id="modal-visitor-edit">
<div class="modal">
<div class="modal-header">
<div class="modal-title">Besucher bearbeiten</div>
<button class="modal-close" onclick="closeModal('modal-visitor-edit')">✕</button>
</div>
<input type="hidden" id="ve-id">
<label>Name *</label>
<input id="ve-name" type="text" placeholder="Vor- und Nachname">
<div class="form-row">
<div>
<label>Firma</label>
<input id="ve-company" type="text" placeholder="Firma GmbH">
</div>
<div>
<label>Fachgebiet / Beruf</label>
<input id="ve-specialty" type="text" placeholder="Fachgebiet">
</div>
</div>
<label>Eingeladen von</label>
<select id="ve-invited-by"><option value="">— kein Mitglied —</option></select>
<label>Foto-URL (optional)</label>
<input id="ve-photo" type="text" placeholder="https://...">
<div class="modal-footer">
<button class="btn btn-danger btn-sm" onclick="removeVisitor(document.getElementById('ve-id').value);closeModal('modal-visitor-edit')">Entfernen</button>
<button class="btn btn-ghost" onclick="closeModal('modal-visitor-edit')">Abbrechen</button>
<button class="btn btn-primary" onclick="saveVisitorEdit()">Speichern</button>
</div>
</div>
</div>
<!-- Schnellerfassung Modal (ersetzt PDF-Import) -->
<div class="modal-overlay" id="modal-pdf-import">
<div class="modal" style="width:min(760px,97vw);max-height:90vh;">
<div class="modal-header">
<div class="modal-title">Besucher- &amp; Vertreterliste erfassen</div>
<button class="modal-close" onclick="closeModal('modal-pdf-import')">✕</button>
</div>
<div id="pdf-drop-zone"
onclick="document.getElementById('pdf-file-input').click()"
ondragover="event.preventDefault();this.classList.add('dragging')"
ondragleave="this.classList.remove('dragging')"
ondrop="event.preventDefault();this.classList.remove('dragging');handlePdfImportFile(event.dataTransfer.files[0])"
style="border:2px dashed var(--border);border-radius:10px;padding:14px 20px;text-align:center;cursor:pointer;margin-bottom:10px;transition:border-color .2s,background .2s;background:#fafafa;">
<input type="file" id="pdf-file-input" accept=".pdf" style="display:none"
onchange="handlePdfImportFile(this.files[0]);this.value=''">
<span style="font-size:0.88rem;color:var(--muted);">📄 <strong>Chapterportal-PDF hier ablegen</strong> oder klicken – Einträge werden automatisch erkannt</span>
</div>
<div id="pdf-parse-status" style="display:none;font-size:0.82rem;padding:8px 12px;border-radius:8px;margin-bottom:10px;"></div>
<p style="font-size:0.82rem;color:var(--muted);margin-bottom:14px;">
Einträge können nach dem Import noch angepasst werden. Nur ausgefüllte Zeilen werden importiert.
</p>
<div style="overflow-x:auto;">
<table id="quick-entry-table" style="width:100%;border-collapse:collapse;font-size:0.85rem;">
<thead>
<tr style="background:#f9fafb;font-size:0.75rem;text-transform:uppercase;letter-spacing:1px;color:var(--muted);">
<th style="padding:8px 4px;border-bottom:2px solid var(--border);width:24px;"></th>
<th style="padding:8px 6px;text-align:left;border-bottom:2px solid var(--border);width:120px;">Typ</th>
<th style="padding:8px 6px;text-align:left;border-bottom:2px solid var(--border);">Name</th>
<th style="padding:8px 6px;text-align:left;border-bottom:2px solid var(--border);">Fachgebiet / Beruf</th>
<th style="padding:8px 6px;text-align:left;border-bottom:2px solid var(--border);">Vertreter für / Eingeladen von</th>
</tr>
</thead>
<tbody id="quick-entry-tbody"></tbody>
</table>
</div>
<div class="modal-footer">
<button class="btn btn-ghost" onclick="closeModal('modal-pdf-import')">Abbrechen</button>
<button class="btn btn-primary" onclick="confirmQuickEntry()">Importieren</button>
</div>
</div>
</div>
<style>
.qe-row td { padding: 4px 4px; border-bottom: 1px solid var(--border); vertical-align: middle; }
.qe-row:last-child td { border-bottom: none; }
.qe-num { color: var(--muted); font-size: 0.78rem; text-align: center; width: 20px; }
.qe-row select, .qe-row input {
width: 100%;
padding: 6px 8px;
border: 1px solid var(--border);
border-radius: 6px;
font-size: 0.85rem;
font-family: inherit;
color: var(--text);
background: white;
}
.qe-row select:focus, .qe-row input:focus {
outline: none;
border-color: var(--red);
box-shadow: 0 0 0 2px rgba(204,0,0,0.1);
}
#pdf-drop-zone.dragging { border-color: var(--red); background: var(--red-light); }
</style>
<!-- Mitglieder-Sync Modal -->
<div class="modal-overlay" id="modal-sync">
<div class="modal" style="width:min(600px,95vw)">
<div class="modal-header">
<div class="modal-title">🔄 Mitglieder mit BNI-Website abgleichen</div>
<button class="modal-close" onclick="closeModal('modal-sync')">✕</button>
</div>
<!-- Step 1: Bookmarklet -->
<div style="background:#f0f9f0;border:1px solid #bbf0bb;border-radius:10px;padding:16px;margin-bottom:14px;">
<div style="font-weight:700;font-size:0.9rem;margin-bottom:6px;">Schritt 1 – Bookmarklets einrichten <span style="font-weight:400;color:var(--muted)">(einmalig)</span></div>
<p style="font-size:0.85rem;color:var(--muted);margin-bottom:10px;">
Ziehe beide Buttons in die Lesezeichenleiste:
</p>
<div style="display:flex;gap:10px;flex-wrap:wrap;">
<a id="bookmarklet-link" href="" style="display:inline-block;background:var(--red);color:white;padding:8px 18px;border-radius:8px;font-weight:700;font-size:0.9rem;text-decoration:none;cursor:grab;">
📋 BNI Mitglieder exportieren
</a>
<a id="bookmarklet-diag" href="" style="display:inline-block;background:#555;color:white;padding:8px 18px;border-radius:8px;font-weight:700;font-size:0.9rem;text-decoration:none;cursor:grab;">
🔍 Diagnose (einmalig)
</a>
</div>
<p style="font-size:0.78rem;color:var(--muted);margin-top:8px;">
Zuerst <strong>🔍 Diagnose</strong> auf der Mitgliederliste ausführen und die heruntergeladene Datei hier hochladen – dann wird der Export-Button automatisch angepasst.
</p>
</div>
<!-- Diagnose upload -->
<div id="diag-upload-area" style="background:#fffbe6;border:1px solid #f0d060;border-radius:10px;padding:12px 16px;margin-bottom:14px;display:none;">
<div style="font-weight:700;font-size:0.85rem;margin-bottom:6px;">🔍 Diagnose-Datei auswerten</div>
<input type="file" id="diag-file-input" accept=".json" style="display:none" onchange="handleDiagFile(this.files[0])">
<button class="btn btn-ghost btn-sm" onclick="document.getElementById('diag-file-input').click()">bni-diagnose.json laden</button>
<div id="diag-result" style="margin-top:8px;font-size:0.82rem;color:var(--muted);"></div>
</div>
<!-- Step 2: Use it -->
<div style="background:#f0f4ff;border:1px solid #c0c8f0;border-radius:10px;padding:16px;margin-bottom:14px;">
<div style="font-weight:700;font-size:0.9rem;margin-bottom:6px;">Schritt 2 – Daten exportieren</div>
<ol style="font-size:0.85rem;color:var(--muted);padding-left:18px;line-height:1.8;">
<li>BNI-Mitgliederliste öffnen:
<a id="bni-sync-link" href="https://bni-ulm.com/sonne/de/memberlist?chapterName=6035&regionIds=43671&isChapterwebsite=true" target="_blank" style="color:var(--red);font-weight:600;">bni-ulm.com → Chapter Sonne</a>
</li>
<li>Warten bis alle Mitglieder geladen sind</li>
<li>Bookmarklet <strong>"BNI Mitglieder exportieren"</strong> klicken</li>
<li>JSON-Datei wird automatisch heruntergeladen</li>
</ol>
</div>
<!-- Step 3: Import -->
<div style="background:#fff8f0;border:1px solid #f0d8b0;border-radius:10px;padding:16px;">
<div style="font-weight:700;font-size:0.9rem;margin-bottom:10px;">Schritt 3 – Importieren</div>
<p style="font-size:0.85rem;color:var(--muted);margin-bottom:10px;">
Heruntergeladene JSON-Datei hier einlesen. Bestehende Mitglieder werden aktualisiert (Foto, Fachgebiet), neue werden hinzugefügt. Niemand wird gelöscht.
</p>
<input type="file" id="sync-file-input" accept=".json" style="display:none" onchange="handleSyncFile(this.files[0])">
<button class="btn btn-primary" onclick="document.getElementById('sync-file-input').click()">
📂 JSON-Datei laden &amp; synchronisieren
</button>
<div id="sync-result" style="margin-top:10px;font-size:0.85rem;display:none;"></div>
</div>
<div class="modal-footer">
<button class="btn btn-ghost" onclick="closeModal('modal-sync')">Schließen</button>
</div>
</div>
</div>
<script>
// ============================================================
// CONSTANTS & CHAPTER CONFIG
// ============================================================
const APP_VERSION = '2.1';
const GITEA_RAW = 'https://git.digiseed.cloud/api/v1/repos/cheubaum/BNI-Vorstellungsrunde/raw';
const GITEA_API = 'https://git.digiseed.cloud/api/v1/repos/cheubaum/BNI-Vorstellungsrunde';
function giteaUrl(path) { return `${GITEA_RAW}/${path}?ref=main`; }
function giteaDataUrl(path) { return `${GITEA_RAW}/${path}?ref=data`; }
function giteaAuthUrl(path, token, ref) { return `${GITEA_API}/contents/${path}?token=${encodeURIComponent(token)}${ref ? '&ref=' + ref : ''}`; }
const CHAPTER_CONFIG_KEY = 'bni-active-chapter';
function loadChapterConfig() {
try { const s = localStorage.getItem(CHAPTER_CONFIG_KEY); return s ? JSON.parse(s) : null; } catch(e) { return null; }
}
function saveChapterConfig(cfg) {
localStorage.setItem(CHAPTER_CONFIG_KEY, JSON.stringify(cfg));
}
function clearChapterData() {
if (activeChapter) localStorage.removeItem(`bni-chapter-${activeChapter.code}-v2`);
if (activeChapter) localStorage.removeItem(`bni-extras-${activeChapter.code}`);
localStorage.removeItem(CHAPTER_CONFIG_KEY);
activeChapter = null;
}
let activeChapter = loadChapterConfig();
let STORAGE_KEY = activeChapter ? `bni-chapter-${activeChapter.code}-v2` : 'bni-chapter-default-v2';
// ============================================================
// STATE
// ============================================================
let state = loadState();
let sortableInstance = null;
let sortableVisitorInstance = null;
let sortableAgendaInstance = null;
let qrInstance = null;
let activeTab = 'members'; // 'members' | 'visitors'
function defaultState() {
return {
members: [], // { id, name, company, specialty, photoUrl, profileUrl }
meetingOrder: [], // member IDs in meeting order
currentIndex: -1, // -1 = not started, 0..n-1 = presenting
substitutes: {}, // { memberId: { name, company, specialty, photoUrl } }
absentIds: [], // member IDs absent today (no substitute)
medicalIds: [], // member IDs on medical leave
visitors: [], // { id, name, company, specialty, photoUrl }
visitorsOrder: [], // visitor IDs in order
visitorsCurrentIndex: -1,
mode: 'members', // 'members' | 'absent-summary' | 'visitors' | 'coordinator-report' | 'augen-timer' | 'round-transition' | 'agenda-item'
defaultTimer: 60, // seconds (standard speaking time)
memberTimers: {}, // { memberId: customSeconds }
showTimer: true, // false = timer completely hidden
coordinatorReportData: null,
agendaCatalog: [], // { id, label, type: 'image'|'embed', imagePath?, embedUrl?, notes? } — geladen aus chapters/<code>/agenda.json
// notes: optionaler Freitext, nur im Steuerpult sichtbar (nie im Display/Beamer-View)
agendaOrder: [], // Reihenfolge von IDs (Katalog-Punkte + eingebaute Vorstellungsrunden), aktiv für dieses Meeting (wie meetingOrder)
agendaKnownIds: [], // IDs, die schon einmal automatisch in agendaOrder eingefügt wurden (verhindert, dass bewusst ausgeblendete Punkte zurückkehren)
currentAgendaItemId: null,
marketingActive: false, // true = aktuelle 'members'-Runde ist die Marketing-/Ergebnisrunde (gleiche Reihenfolge, ohne Timer)
augen: { // 8-Augen-Gespräche Multi-Timer
visibleSizes: [3, 4, 5, 6],
baseSeconds: 600, // Wechsel-Intervall am 4er-Tisch (Basis)
startedAt: null,
running: false,
},
};
}
function loadState() {
try {
const s = localStorage.getItem(STORAGE_KEY);
const parsed = s ? JSON.parse(s) : defaultState();
// Ensure all fields exist (for upgrades)
return Object.assign(defaultState(), parsed);
} catch(e) { return defaultState(); }
}
function saveState(broadcast = true) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
if (broadcast) {
try { channel.postMessage({ type: 'STATE_UPDATE', state }); } catch(e) {}
}
}
// ============================================================
// PROFILE EXTRAS (Kurzlinks + Offline-Fotos, persistent)
// ============================================================
let EXTRAS_KEY = activeChapter ? `bni-extras-${activeChapter.code}` : 'bni-profile-extras';
function loadExtras() {
try {
const s = localStorage.getItem(EXTRAS_KEY);
return s ? JSON.parse(s) : {};
} catch(e) { return {}; }
}
function saveExtras(extras) {
localStorage.setItem(EXTRAS_KEY, JSON.stringify(extras));
}
function extrasKey(name) {
return (name || '').toLowerCase().trim();
}
// ============================================================
// STAMMVERTRETER (Profilvorrat für Vertretungen)
// ============================================================
function stammvertreterKey() {
return activeChapter ? `bni-stammvertreter-${activeChapter.code}` : 'bni-stammvertreter';
}
function loadStammvertreter() {
try { return JSON.parse(localStorage.getItem(stammvertreterKey()) || '[]'); } catch(e) { return []; }
}
function saveStammvertreter(list) {
localStorage.setItem(stammvertreterKey(), JSON.stringify(list));
}
let _svEditingPhotoB64 = null;
function openAddSv() {
document.getElementById('sv-modal-id').value = '';
document.getElementById('sv-modal-title').textContent = 'Stammvertreter hinzufügen';
document.getElementById('sv-modal-name').value = '';
document.getElementById('sv-modal-company').value = '';
document.getElementById('sv-modal-specialty').value = '';
document.getElementById('sv-delete-btn').style.display = 'none';
_svEditingPhotoB64 = null;
_updateSvPhotoUI(null);
openModal('modal-sv');
}
function openEditSv(svId) {
const sv = loadStammvertreter().find(s => s.id === svId);
if (!sv) return;
document.getElementById('sv-modal-id').value = sv.id;
document.getElementById('sv-modal-title').textContent = 'Stammvertreter bearbeiten';
document.getElementById('sv-modal-name').value = sv.name;
document.getElementById('sv-modal-company').value = sv.company || '';
document.getElementById('sv-modal-specialty').value = sv.specialty || '';
document.getElementById('sv-delete-btn').style.display = '';
_svEditingPhotoB64 = sv.photoB64 || null;
_updateSvPhotoUI(_svEditingPhotoB64);
openModal('modal-sv');
}
function _updateSvPhotoUI(b64) {
_svEditingPhotoB64 = b64;
const thumb = document.getElementById('sv-photo-thumb');
const btn = document.getElementById('sv-remove-photo');
if (b64) { thumb.src = b64; thumb.style.display = 'block'; btn.style.display = 'inline-flex'; }
else { thumb.src = ''; thumb.style.display = 'none'; btn.style.display = 'none'; }
}
function handleSvPhotoFile(input) {
const file = input.files && input.files[0];
if (!file) return;
resizePhotoToBase64(file).then(b64 => _updateSvPhotoUI(b64)).catch(e => alert(e.message));
input.value = '';
}
function removeSvPhoto() { _updateSvPhotoUI(null); }
function saveStammvertreterModal() {
const name = document.getElementById('sv-modal-name').value.trim();
if (!name) { alert('Bitte Namen eingeben.'); return; }
const list = loadStammvertreter();
const existingId = document.getElementById('sv-modal-id').value;
const sv = {
id: existingId || 'sv-' + Date.now(),
name,
company: document.getElementById('sv-modal-company').value.trim(),
specialty: document.getElementById('sv-modal-specialty').value.trim(),
photoB64: _svEditingPhotoB64 || null,
};
if (existingId) {
const idx = list.findIndex(s => s.id === existingId);
if (idx >= 0) list[idx] = sv; else list.push(sv);
} else {
list.push(sv);
}
saveStammvertreter(list);
closeModal('modal-sv');
renderStammvertreterSection();
}
function deleteStammvertreter(svId) {
if (!confirm('Stammvertreter löschen?')) return;
saveStammvertreter(loadStammvertreter().filter(s => s.id !== svId));
closeModal('modal-sv');
renderStammvertreterSection();
}
function sortSvByName(list) {
return [...list].sort((a, b) => (a.name || '').localeCompare(b.name || '', 'de', { sensitivity: 'base' }));
}
function renderStammvertreterSection() {
const container = document.getElementById('sv-list-container');
if (!container) return;
const list = sortSvByName(loadStammvertreter());
if (list.length === 0) {
container.innerHTML = `<div style="color:var(--muted);font-size:0.82rem;padding:2px 0 8px;">Noch keine gespeichert. → <a href="admin.html" target="_blank" style="color:var(--red);">Admin öffnen</a> zum Importieren aus anderen Chaptern.</div>`;
return;
}
container.innerHTML = '';
list.forEach(sv => {
const photo = sv.photoB64
? `<img src="${sv.photoB64}" style="width:34px;height:34px;border-radius:50%;object-fit:cover;flex-shrink:0;">`
: `<div style="width:34px;height:34px;border-radius:50%;background:${getColor(sv.name)};display:flex;align-items:center;justify-content:center;color:white;font-size:0.68rem;font-weight:700;flex-shrink:0;">${getInitials(sv.name)}</div>`;
const div = document.createElement('div');
div.style.cssText = 'display:flex;align-items:center;gap:8px;padding:5px 0;border-bottom:1px solid var(--border);';
div.innerHTML = `${photo}<div style="flex:1;min-width:0;"><div style="font-size:0.83rem;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${escHtml(sv.name)}</div><div style="font-size:0.72rem;color:var(--muted);">${escHtml(sv.company||'')}${sv.specialty?' · '+escHtml(sv.specialty):''}</div></div><button class="btn btn-ghost btn-sm" style="padding:3px 7px;" onclick="openEditSv('${sv.id}')">✏</button>`;
container.appendChild(div);
});
}
function renderSvPicker() {
const list = sortSvByName(loadStammvertreter());
const picker = document.getElementById('sub-sv-picker');
const select = document.getElementById('sub-sv-select');
if (!picker || !select) return;
if (list.length === 0) { picker.style.display = 'none'; return; }
picker.style.display = '';
select.innerHTML = '<option value="">– Stammvertreter auswählen –</option>' + list.map(sv =>
`<option value="${escHtml(sv.id)}">${escHtml(sv.name)}${sv.company ? ' · ' + escHtml(sv.company) : ''}</option>`
).join('');
}
function pickStammvertreter(svId) {
const sv = loadStammvertreter().find(s => s.id === svId);
if (!sv) return;
document.getElementById('sub-modal-name').value = sv.name;
document.getElementById('sub-modal-company').value = sv.company || '';
document.getElementById('sub-modal-specialty').value = sv.specialty || '';
document.getElementById('sub-modal-photo').value = '';
if (sv.photoB64) {
const extras = loadExtras();
const key = extrasKey(sv.name);
if (!extras[key]) extras[key] = {};
extras[key].photoB64 = sv.photoB64;
saveExtras(extras);
}
document.getElementById('sub-sv-picker').style.display = 'none';
}
// Resize an image File to base64 JPEG (max maxPx on longest side)
function resizePhotoToBase64(file, maxPx = 400) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => {
const img = new Image();
img.onload = () => {
let w = img.width, h = img.height;
if (w > maxPx || h > maxPx) {
if (w > h) { h = Math.round(h * maxPx / w); w = maxPx; }
else { w = Math.round(w * maxPx / h); h = maxPx; }
}
const canvas = document.createElement('canvas');
canvas.width = w; canvas.height = h;
canvas.getContext('2d').drawImage(img, 0, 0, w, h);
resolve(canvas.toDataURL('image/jpeg', 0.82));
};
img.onerror = reject;
img.src = e.target.result;
};
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
// Try to load a remote photo as base64 via canvas (requires CORS)
function loadPhotoFromUrl(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = 'anonymous';
img.onload = () => {
const maxPx = 400;
let w = img.width, h = img.height;
if (w > maxPx || h > maxPx) {
if (w > h) { h = Math.round(h * maxPx / w); w = maxPx; }
else { w = Math.round(w * maxPx / h); h = maxPx; }
}
const canvas = document.createElement('canvas');
canvas.width = w; canvas.height = h;
canvas.getContext('2d').drawImage(img, 0, 0, w, h);
resolve(canvas.toDataURL('image/jpeg', 0.82));
};
img.onerror = () => reject(new Error('Foto konnte nicht geladen werden (CORS oder URL ungültig).'));
img.src = url;
});
}
// Module-level state for currently editing offline photo
let _editingOfflinePhotoB64 = null;
function _updateOfflinePhotoUI(b64) {
_editingOfflinePhotoB64 = b64 || null;
const thumb = document.getElementById('offline-photo-thumb');
const removeBtn = document.getElementById('btn-remove-offline-photo');
if (b64) {
thumb.src = b64;
thumb.style.display = 'block';
removeBtn.style.display = 'inline-flex';
} else {
thumb.src = '';
thumb.style.display = 'none';
removeBtn.style.display = 'none';
}
}
function handleOfflinePhotoFile(input) {
const file = input.files && input.files[0];
if (!file) return;
resizePhotoToBase64(file)
.then(b64 => _updateOfflinePhotoUI(b64))
.catch(e => alert('Fehler beim Laden des Fotos: ' + e.message));
input.value = '';
}
function loadOfflinePhotoFromUrl() {
const url = (document.getElementById('m-photo').value || '').trim();
if (!url) { alert('Bitte zuerst eine Foto-URL eingeben.'); return; }
// Remove any previous hint
const existingHint = document.getElementById('offline-photo-url-hint');
if (existingHint) existingHint.remove();
loadPhotoFromUrl(url)
.then(b64 => _updateOfflinePhotoUI(b64))
.catch(() => {
// Show inline hint with direct link instead of an alert
const hint = document.createElement('div');
hint.id = 'offline-photo-url-hint';
hint.style.cssText = 'font-size:0.82rem;color:var(--muted);margin-top:6px;line-height:1.5;';
hint.innerHTML = `Server erlaubt keinen direkten Import. <a href="${url}" target="_blank" rel="noopener" style="color:var(--red);font-weight:600;">Foto öffnen ↗</a> &rarr; Rechtsklick &rarr; Bild speichern &rarr; dann über <strong>Datei wählen</strong> hochladen.`;
document.getElementById('offline-photo-row').insertAdjacentElement('afterend', hint);
});
}
function removeOfflinePhoto() {
_updateOfflinePhotoUI(null);
}
// Export profildaten.json
function exportProfildaten() {
const extras = loadExtras();
const blob = new Blob([JSON.stringify({ version: 1, extras }, null, 2)], { type: 'application/json' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'profildaten.json';
a.click();
document.getElementById('profildaten-status').textContent = '✓ Gespeichert';
setTimeout(() => { document.getElementById('profildaten-status').textContent = ''; }, 3000);
}
// Import profildaten.json
function importProfildaten(input) {
const file = input.files && input.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = JSON.parse(e.target.result);
const incoming = data.extras || data; // support both formats
const existing = loadExtras();
// Merge: incoming data wins for kurzlink/photoB64 per member
Object.entries(incoming).forEach(([k, v]) => {
existing[k] = Object.assign(existing[k] || {}, v);
});
saveExtras(existing);
const count = Object.keys(incoming).length;
document.getElementById('profildaten-status').textContent = `✓ ${count} Einträge geladen`;
setTimeout(() => { document.getElementById('profildaten-status').textContent = ''; }, 4000);
renderCurrentView();
} catch(err) {
alert('Ungültige Datei: ' + err.message);
}
input.value = '';
};
reader.readAsText(file);
}
// ============================================================
// BROADCAST CHANNEL (cross-tab sync)
// ============================================================
let channel;
let syncDot;
function initChannel() {
try {
channel = new BroadcastChannel('bni-channel-' + (activeChapter ? activeChapter.code : 'default'));
channel.onmessage = (ev) => {
if (ev.data.type === 'STATE_UPDATE') {
state = ev.data.state;
saveState(false);
renderCurrentView();
}
if (ev.data.type === 'PING') {
channel.postMessage({ type: 'PONG' });
if (syncDot) { syncDot.className = 'status-dot dot-live'; }
}
if (ev.data.type === 'PONG') {
if (syncDot) { syncDot.className = 'status-dot dot-live'; }
}
if (ev.data.type === 'TIMER_START') {
_t.total = ev.data.total;
_t.startedAt = ev.data.startedAt;
_t.remaining = Math.max(0, ev.data.total - Math.floor((Date.now() - ev.data.startedAt) / 1000));
_t.running = true;
if (_t.iv) clearInterval(_t.iv);
_t.iv = setInterval(_timerTick, 500);
updateTimerDisplays();
}
if (ev.data.type === 'TIMER_STOP') {
_t.total = 0; _t.remaining = 0; _t.running = false;
if (_t.iv) { clearInterval(_t.iv); _t.iv = null; }
updateTimerDisplays();
}
};
// Ping other tabs every 5s
setInterval(() => {
try { channel.postMessage({ type: 'PING' }); } catch(e) {}
}, 5000);
} catch(e) { /* BroadcastChannel not supported */ }
}
// ============================================================
// ROUTING
// ============================================================
function getView() {
return window.location.hash === '#display' ? 'display' : 'control';
}
function renderCurrentView() {
const hasChapter = !!activeChapter;
const view = getView();
document.getElementById('view-activation').classList.toggle('active', !hasChapter);
document.getElementById('view-display').classList.toggle('active', hasChapter && view === 'display');
document.getElementById('view-control').classList.toggle('active', hasChapter && view === 'control');
if (!hasChapter) return;
if (view === 'display') {
renderDisplay();
} else {
renderControl();
}
_refreshSoundBtn();
// Augen-Timer-Overlay auch in Control-View sichtbar machen
const augenEl = document.getElementById('display-augen-timer');
if (augenEl) {
if (state.mode === 'augen-timer') {
augenEl.style.display = 'flex';
renderAugenTimer();
_augenStartTick();
} else if (view === 'control') {
augenEl.style.display = 'none';
}
}
}
window.addEventListener('hashchange', renderCurrentView);
// ============================================================
// DISPLAY VIEW RENDERING
// ============================================================
function getInitials(name) {
if (!name) return '?';
return name.trim().split(/\s+/).map(w => w[0]).join('').toUpperCase().slice(0, 2);
}
function getColor(name) {
const colors = ['#1a2744','#cc0000','#0f3460','#1e3a5f','#4a1942'];
let h = 0;
for (let c of (name || '')) h = (h * 31 + c.charCodeAt(0)) & 0xffffffff;
return colors[Math.abs(h) % colors.length];
}
function renderDisplay() {
const presenter = getCurrentPresenter();
const next = getNextPresenter();
const emptyEl = document.getElementById('display-empty');
const contentEl = document.getElementById('display-content');
const nextBar = document.getElementById('next-bar');
const nextBarEnd = document.getElementById('next-bar-end');
const progressText = document.getElementById('progress-text');
const roundBadge = document.getElementById('round-badge');
const mainCard = document.getElementById('main-card');
const displayHeader = document.querySelector('#view-display .display-header');
const absentSummaryEl = document.getElementById('display-absent-summary');
const coordReportEl = document.getElementById('display-coordinator-report');
const augenEl = document.getElementById('display-augen-timer');
const agendaItemEl = document.getElementById('display-agenda-item');
// 8-AUGEN TIMER MODE
if (state.mode === 'augen-timer') {
emptyEl.style.display = 'none';
mainCard.style.display = 'none';
contentEl.style.display = 'none';
nextBar.style.display = 'none';
nextBarEnd.style.display = 'none';
roundBadge.classList.remove('visible');
progressText.textContent = '';
if (displayHeader) displayHeader.style.display = 'none';
if (absentSummaryEl) absentSummaryEl.style.display = 'none';
if (coordReportEl) coordReportEl.style.display = 'none';
if (agendaItemEl) agendaItemEl.style.display = 'none';
const qrWrap = document.getElementById('qr-wrap');
if (qrWrap) qrWrap.style.display = 'none';
if (augenEl) {
augenEl.style.display = 'flex';
renderAugenTimer();
}
return;
}
if (augenEl) augenEl.style.display = 'none';
// COORDINATOR REPORT MODE
if (state.mode === 'coordinator-report') {
emptyEl.style.display = 'none';
mainCard.style.display = 'none';
contentEl.style.display = 'none';
nextBar.style.display = 'none';
nextBarEnd.style.display = 'none';
roundBadge.classList.remove('visible');
progressText.textContent = '';
if (displayHeader) displayHeader.style.display = '';
if (absentSummaryEl) absentSummaryEl.style.display = 'none';
if (agendaItemEl) agendaItemEl.style.display = 'none';
const qrWrap = document.getElementById('qr-wrap');
if (qrWrap) qrWrap.style.display = 'none';
if (coordReportEl) {
coordReportEl.style.display = 'flex';
renderCoordinatorReport(state.coordinatorReportData);
}
return;
}
if (coordReportEl) coordReportEl.style.display = 'none';
// AGENDA-FOLIE MODE (Bild oder Live-Einbettung)
if (state.mode === 'agenda-item') {
emptyEl.style.display = 'none';
mainCard.style.display = 'none';
contentEl.style.display = 'none';
nextBar.style.display = 'none';
nextBarEnd.style.display = 'none';
roundBadge.classList.remove('visible');
progressText.textContent = '';
if (displayHeader) displayHeader.style.display = 'none';
if (absentSummaryEl) absentSummaryEl.style.display = 'none';
const qrWrap = document.getElementById('qr-wrap');
if (qrWrap) qrWrap.style.display = 'none';
const it = (state.agendaCatalog || []).find(a => a.id === state.currentAgendaItemId);
const imgEl = document.getElementById('agenda-item-image');
const embedEl = document.getElementById('agenda-item-embed');
const errEl = document.getElementById('agenda-item-error');
if (agendaItemEl && it) {
agendaItemEl.style.display = 'flex';
if (errEl) errEl.style.display = 'none';
if (it.type === 'embed' && it.embedUrl) {
embedEl.src = it.embedUrl;
embedEl.style.display = '';
imgEl.style.display = 'none';
imgEl.src = '';
} else if (it.imagePath) {
const src = it.imagePath.startsWith('http') ? it.imagePath
: giteaDataUrl(`chapters/${activeChapter.code}/${it.imagePath}`);
imgEl.onerror = () => {
imgEl.style.display = 'none';
if (errEl) errEl.style.display = 'block';
};
imgEl.onload = () => { if (errEl) errEl.style.display = 'none'; };
imgEl.src = src;
imgEl.style.display = '';
embedEl.style.display = 'none';
embedEl.src = '';
}
} else if (agendaItemEl) {
agendaItemEl.style.display = 'none';
}
return;
}
if (agendaItemEl) agendaItemEl.style.display = 'none';
// ABSENT SUMMARY MODE
if (state.mode === 'absent-summary') {
emptyEl.style.display = 'none';
mainCard.style.display = 'none';
contentEl.style.display = 'none';
nextBar.style.display = 'none';
roundBadge.classList.remove('visible');
progressText.textContent = '';
if (displayHeader) displayHeader.style.display = '';
absentSummaryEl.style.display = 'flex';
const qrWrap = document.getElementById('qr-wrap');
if (qrWrap) qrWrap.style.display = 'none';
const medicalIds = state.medicalIds || [];
const medicalMembers = medicalIds.map(id => state.members.find(m => m.id === id)).filter(Boolean);
const absentMembers = state.absentIds.map(id => state.members.find(m => m.id === id)).filter(Boolean);
const colMedical = document.getElementById('absent-col-medical');
const colAbsent = document.getElementById('absent-col-absent');
const listMedical = document.getElementById('absent-list-medical');
const listAbsent = document.getElementById('absent-list-absent');
const total = medicalMembers.length + absentMembers.length;
// Scale font size down as list grows
const fontSize = total <= 4 ? '2.2rem'
: total <= 8 ? '1.7rem'
: total <= 14 ? '1.35rem'
: total <= 22 ? '1.1rem'
: total <= 32 ? '0.9rem'
: '0.75rem';
absentSummaryEl.style.setProperty('--absent-name-size', fontSize);
// Auto-columns within each section
const absentCols = absentMembers.length > 20 ? 3
: absentMembers.length > 10 ? 2 : 1;
const medicalCols = medicalMembers.length > 10 ? 2 : 1;
if (medicalMembers.length > 0) {
colMedical.style.display = 'block';
listMedical.style.columns = medicalCols;
listMedical.innerHTML = medicalMembers.map(m =>
`<div class="absent-summary-name">${escHtml(m.name)}</div>`).join('');
} else {
colMedical.style.display = 'none';
}
if (absentMembers.length > 0) {
colAbsent.style.display = 'block';
listAbsent.style.columns = absentCols;
listAbsent.innerHTML = absentMembers.map(m =>
`<div class="absent-summary-name">${escHtml(m.name)}</div>`).join('');
} else {
colAbsent.style.display = 'none';
}
if (state.visitorsOrder.length > 0) {
nextBarEnd.style.display = 'none';
// Show a hint that visitors round is next
nextBar.style.display = 'none'; // keep it simple
} else {
nextBarEnd.style.display = 'flex';
}
return;
}
// Hide absent summary
if (absentSummaryEl) absentSummaryEl.style.display = 'none';
// Logo transition between rounds
if (state.mode === 'round-transition') {
emptyEl.style.display = 'flex';
mainCard.style.display = 'none';
if (displayHeader) displayHeader.style.display = 'none';
contentEl.style.display = 'none';
nextBar.style.display = 'none';
nextBarEnd.style.display = 'none';
progressText.textContent = '';
roundBadge.classList.remove('visible');
return;
}
if (!presenter || (state.currentIndex < 0 && state.visitorsCurrentIndex < 0)) {
emptyEl.style.display = 'flex';
mainCard.style.display = 'none';
if (displayHeader) displayHeader.style.display = 'none';
contentEl.style.display = 'none';
nextBar.style.display = 'none';
nextBarEnd.style.display = 'none';
progressText.textContent = '';
roundBadge.classList.remove('visible');
return;
}
emptyEl.style.display = 'none';
mainCard.style.display = '';
if (displayHeader) displayHeader.style.display = '';
contentEl.style.display = 'flex';
// Round badge
if (state.mode === 'visitors') {
roundBadge.textContent = '2. Runde – Besucher';
roundBadge.classList.add('visible');
} else if (state.mode === 'members' && state.marketingActive) {
roundBadge.textContent = 'Marketing-/Ergebnisrunde';
roundBadge.classList.add('visible');
} else {
roundBadge.classList.remove('visible');
}
// Progress
if (state.mode === 'members') {
const total = state.meetingOrder.length;
progressText.textContent = total > 0 ? `${state.currentIndex + 1} / ${total}` : '';
} else {
const total = state.visitorsOrder.length;
progressText.textContent = total > 0 ? `Besucher ${state.visitorsCurrentIndex + 1} / ${total}` : '';
}
// Get display data
let data = getDisplayData(presenter);
// Photo
const photoWrap = document.getElementById('photo-wrap');
const initialsEl = document.getElementById('display-initials');
// Remove existing img
const existingImg = photoWrap.querySelector('img');
if (existingImg) existingImg.remove();
if (data.photoUrl) {
const img = document.createElement('img');
img.src = data.photoUrl;
img.alt = data.name;
img.style.cssText = 'width:100%;height:100%;object-fit:cover;';
img.onerror = () => {
img.remove();
initialsEl.textContent = getInitials(data.name);
initialsEl.style.background = getColor(data.name);
initialsEl.style.display = 'flex';
};
initialsEl.style.display = 'none';
photoWrap.appendChild(img);
} else {
initialsEl.textContent = getInitials(data.name);
initialsEl.style.background = getColor(data.name);
initialsEl.style.display = 'flex';
}
// Text
document.getElementById('display-name').textContent = data.name || '—';
document.getElementById('display-company').textContent = data.company || '';
const specEl = document.getElementById('display-specialty');
if (data.specialty) {
specEl.textContent = data.specialty;
specEl.style.display = 'inline-block';
} else {
specEl.style.display = 'none';
}
const visitorTag = document.getElementById('visitor-tag');
if (data.isVisitor || data.isSubstituteVisitor) {
visitorTag.style.display = 'inline-block';
visitorTag.textContent = data.isSubstituteVisitor ? 'Vertreter' : 'Besucher';
} else {
visitorTag.style.display = 'none';
}
// Presenter row: substitute (members) or invited-by (visitors)
const presenterRow = document.getElementById('presenter-row');
const rowPerson = data.presentedBy || data.invitedBy || null;
const rowLabel = data.presentedBy ? 'Vertreten durch' : 'Eingeladen von';
if (rowPerson) {
presenterRow.style.display = 'flex';
document.getElementById('presenter-label').textContent = rowLabel;
document.getElementById('presenter-name').textContent = rowPerson.name;
document.getElementById('presenter-company').textContent = rowPerson.company || '';
const avatarEl = document.getElementById('presenter-avatar');
avatarEl.innerHTML = '';
avatarEl.textContent = '';
avatarEl.style.background = '';
if (rowPerson.photoUrl) {
const img = document.createElement('img');
img.src = rowPerson.photoUrl;
img.style.cssText = 'width:100%;height:100%;object-fit:cover;display:block;border-radius:50%;';
img.onerror = () => {
img.remove();
avatarEl.textContent = getInitials(rowPerson.name);
avatarEl.style.background = getColor(rowPerson.name);
};
avatarEl.appendChild(img);
} else {
avatarEl.textContent = getInitials(rowPerson.name);
avatarEl.style.background = getColor(rowPerson.name);
}
} else {
presenterRow.style.display = 'none';
}
// QR Code
const qrWrap = document.getElementById('qr-wrap');
const qrEl = document.getElementById('display-qr');
if (data.profileUrl) {
qrWrap.style.display = 'block';
qrEl.innerHTML = '';
try {
const qrSize = Math.round(Math.min(window.innerHeight * 0.24, window.innerWidth * 0.14, 300));
new QRCode(qrEl, {
text: data.profileUrl,
width: qrSize,
height: qrSize,
colorDark: '#1a2744',
colorLight: '#ffffff',
correctLevel: QRCode.CorrectLevel.L,
});
} catch(e) { qrWrap.style.display = 'none'; }
} else {
qrWrap.style.display = 'none';
}
// Next bar
if (next) {
nextBar.style.display = 'flex';
nextBarEnd.style.display = 'none';
const nextData = getDisplayData(next);
const nextImg = document.getElementById('next-photo-img');
if (nextData.photoUrl) {
nextImg.src = nextData.photoUrl;
nextImg.style.display = 'block';
} else {
nextImg.style.display = 'none';
}
document.getElementById('next-name-text').textContent = nextData.name;
document.getElementById('next-company-text').textContent = nextData.company;
const nextSubEl = document.getElementById('next-sub-text');
if (nextData.presentedBy) {
nextSubEl.textContent = 'Vertreten durch: ' + nextData.presentedBy.name;
nextSubEl.style.display = 'block';
} else {
nextSubEl.style.display = 'none';
}
} else {
nextBar.style.display = 'none';
nextBarEnd.style.display = 'flex';
}
}
// ============================================================
// ABSENT / MEDICAL STATUS
// ============================================================
function toggleMemberStatus(id) {
const isAbsent = state.absentIds.includes(id);
const isMedical = (state.medicalIds || []).includes(id);
if (!isAbsent && !isMedical) {
// Active → Abwesend
state.absentIds.push(id);
state.meetingOrder = state.meetingOrder.filter(mid => mid !== id);
if (state.currentIndex >= state.meetingOrder.length) {
state.currentIndex = state.meetingOrder.length - 1;
}
} else if (isAbsent) {
// Abwesend → Medical
state.absentIds = state.absentIds.filter(aid => aid !== id);
if (!state.medicalIds) state.medicalIds = [];
state.medicalIds.push(id);
} else {
// Medical → Aktiv (add back to end of queue)
state.medicalIds = state.medicalIds.filter(mid => mid !== id);
if (!state.meetingOrder.includes(id)) {
state.meetingOrder.push(id);
}
}
saveState();
renderControl();
updateNextButton();
renderDisplay();
}
function renderAbsentSection() {
const section = document.getElementById('absent-members-section');
if (!section) return;
const medicalIds = state.medicalIds || [];
const medicalMembers = medicalIds.map(id => state.members.find(m => m.id === id)).filter(Boolean);
const absentMembers = state.absentIds.map(id => state.members.find(m => m.id === id)).filter(Boolean);
if (medicalMembers.length === 0 && absentMembers.length === 0) {
section.style.display = 'none';
setQueueResizeVisible(false);
return;
}
setQueueResizeVisible(true);
section.style.display = 'block';
section.className = 'absent-section';
let html = '';
if (medicalMembers.length > 0) {
html += `<div class="absent-section-title">Medical</div>`;
medicalMembers.forEach(m => { html += absentItemHtml(m, 'medical'); });
}
if (absentMembers.length > 0) {
html += `<div class="absent-section-title">Abwesend</div>`;
absentMembers.forEach(m => { html += absentItemHtml(m, 'absent'); });
}
section.innerHTML = html;
}
function absentItemHtml(member, status) {
const photoHtml = member.photoUrl
? `<img src="${escHtml(member.photoUrl)}" alt="" style="width:36px;height:36px;border-radius:50%;object-fit:cover;flex-shrink:0;">`
: `<div class="queue-initials" style="width:36px;height:36px;min-width:36px;font-size:0.8rem;background:${getColor(member.name)}">${getInitials(member.name)}</div>`;
const tagCls = status === 'medical' ? 'status-tag-medical' : 'status-tag-absent';
const label = status === 'medical' ? 'Medical' : 'Abwesend';
return `<div class="queue-item" style="cursor:default;margin-bottom:6px;opacity:0.75;">
<div class="queue-photo-wrap" style="width:36px;height:36px;">${photoHtml}</div>
<div class="queue-info"><div class="queue-name" style="font-size:0.9rem;">${escHtml(member.name)}</div></div>
<div class="queue-actions">
<button class="btn btn-ghost btn-sm status-tag ${tagCls}" onclick="toggleMemberStatus('${member.id}')" title="Status ändern (klicken zum Wechseln)">${label}</button>
</div>
</div>`;
}
function getCurrentPresenter() {
if (state.mode === 'visitors') {
const id = state.visitorsOrder[state.visitorsCurrentIndex];
return state.visitors.find(v => v.id === id) || null;
}
if (state.mode === 'absent-summary') return null;
if (state.currentIndex < 0) return null;
const id = state.meetingOrder[state.currentIndex];
return state.members.find(m => m.id === id) || null;
}
function getNextPresenter() {
if (state.mode === 'visitors') {
const id = state.visitorsOrder[state.visitorsCurrentIndex + 1];
return state.visitors.find(v => v.id === id) || null;
}
if (state.mode === 'absent-summary') return null;
// Try next in member queue
const nextId = state.meetingOrder[state.currentIndex + 1];
if (nextId) return state.members.find(m => m.id === nextId) || null;
// Absent summary comes next if there are absent/medical members
const hasAbsent = state.absentIds.length > 0 || (state.medicalIds || []).length > 0;
if (hasAbsent) return null; // signal: absent-summary is next (not a regular presenter)
// Try first visitor
if (state.visitorsOrder.length > 0) {
const vid = state.visitorsOrder[0];
return state.visitors.find(v => v.id === vid) || null;
}
return null;
}
function getDisplayData(presenter) {
// Visitor entries have no substitutes
if (presenter.isVisitor) {
// For substitute-visitors, always pull fresh data from state.substitutes
// (the visitor entry in state.visitors may have been created before a photo was added)
let name = presenter.name;
let company = presenter.company;
let specialty = presenter.specialty;
let photoUrl = presenter.photoUrl || '';
if (presenter.isSubstituteVisitor && presenter.forMemberId) {
const sub = state.substitutes[presenter.forMemberId];
if (sub) {
name = sub.name || name;
company = sub.company || company;
specialty = sub.specialty || specialty;
photoUrl = sub.photoUrl || photoUrl;
}
}
// Stammvertreter-Foto: photoB64 is stored in extras keyed by name
// (set by pickStammvertreter; see ~line 1948). Use it if nothing else is set.
if (!photoUrl) {
const ex = loadExtras()[extrasKey(name)];
if (ex && ex.photoB64) photoUrl = ex.photoB64;
}
// If still no photo, try matching by name in members list
if (!photoUrl) {
const match = state.members.find(m =>
m.name.toLowerCase().trim() === name.toLowerCase().trim()
);
if (match) photoUrl = match.photoUrl || '';
}
let invitedByMember = null;
if (presenter.invitedBy) {
const m = state.members.find(m => m.id === presenter.invitedBy);
if (m) invitedByMember = { name: m.name, company: m.company, photoUrl: m.photoUrl || '' };
}
return {
name,
company,
specialty,
photoUrl,
profileUrl: presenter.profileUrl || '',
presentedBy: null,
invitedBy: invitedByMember,
isVisitor: true,
isSubstituteVisitor: presenter.isSubstituteVisitor || false,
};
}
const sub = state.substitutes[presenter.id];
let resolvedSub = null;
if (sub && sub.name) {
// Foto-Auflösung Vertretung: explizit → manueller Upload (extras) → Server-Sync (extras) → Mitglieder-Match
let photoUrl = sub.photoUrl || '';
if (!photoUrl) {
const subEx = loadExtras()[extrasKey(sub.name)] || {};
photoUrl = subEx.photoB64 || subEx.offlinePhotoB64 || '';
}
if (!photoUrl) {
const match = state.members.find(m =>
m.name.toLowerCase().trim() === sub.name.toLowerCase().trim()
);
if (match) photoUrl = match.photoUrl || '';
}
resolvedSub = { ...sub, photoUrl };
}
// Merge profile extras (kurzlink + offline photo override)
const ex = loadExtras()[extrasKey(presenter.name)] || {};
return {
// Member stays in foreground
name: presenter.name,
company: presenter.company,
specialty: presenter.specialty,
photoUrl: ex.photoB64 || presenter.photoUrl || '',
profileUrl: ex.kurzlink || presenter.profileUrl || '',
// Substitute shown as secondary presenter
presentedBy: resolvedSub,
isVisitor: false,
};
}
// ============================================================
// CONTROL VIEW RENDERING
// ============================================================
function renderControl() {
renderMemberQueue();
renderVisitorQueue();
renderAgendaQueue();
renderVisitorsList();
renderMembersManage();
renderSubstitutesList();
renderSubstituteMemberSelect();
renderInvitedBySelect();
renderAdminPanel();
renderStammvertreterSection();
renderAgendaNotesPanel();
updateNextButton();
syncDot = document.getElementById('sync-dot');
}
// Zeigt die Sprecher-Notizen des aktuell live geschalteten Agenda-Punkts im Steuerpult an —
// unabhängig vom gerade gewählten linken Reiter (Agenda/Mitglieder/Besucher), damit die
// Moderatorin die Notizen auch sieht, während sie z.B. die Mitglieder-Queue pflegt. Bewusst aus
// renderControl() heraus aufgerufen (nicht nur aus showAgendaItem/closeAgendaItem), damit der
// Zustand bei jeder State-Synchronisierung korrekt bleibt. renderDisplay() kennt dieses Element
// nicht — Notizen erscheinen dadurch garantiert nie auf dem Display/Beamer.
function renderAgendaNotesPanel() {
const panel = document.getElementById('agenda-notes-panel');
if (!panel) return;
const it = state.mode === 'agenda-item' ? getAgendaItem(state.currentAgendaItemId) : null;
const notes = it && it.notes ? it.notes.trim() : '';
if (!notes) {
panel.style.display = 'none';
panel.innerHTML = '';
return;
}
panel.className = 'agenda-notes-panel';
panel.style.display = 'block';
panel.innerHTML = `<strong>Notiz — ${escHtml(it.label || it.id)}</strong>${escHtml(notes)}`;
}
function renderInvitedBySelect() {
const sel = document.getElementById('v-invited-by');
if (!sel) return;
const val = sel.value;
sel.innerHTML = '<option value="">— kein Mitglied —</option>';
state.members.slice().sort((a,b) => a.name.localeCompare(b.name)).forEach(m => {
const opt = document.createElement('option');
opt.value = m.id;
opt.textContent = m.name;
if (m.id === val) opt.selected = true;
sel.appendChild(opt);
});
}
function renderMemberQueue() {
const container = document.getElementById('member-queue');
container.innerHTML = '';
if (state.meetingOrder.length === 0) {
container.innerHTML = `<div class="empty-hint">
<h3>Keine Mitglieder</h3>
<p>Füge Mitglieder unter "Mitglieder" hinzu, dann erscheinen sie hier.</p>
</div>`;
return;
}
state.meetingOrder.forEach((id, idx) => {
const member = state.members.find(m => m.id === id);
if (!member) return;
const isDone = idx < state.currentIndex || (state.mode === 'visitors' && state.currentIndex >= 0);
const isCurrent = idx === state.currentIndex && state.mode === 'members';
const isNext = !isCurrent && (
(state.mode === 'members' && idx === state.currentIndex + 1) ||
(state.mode === 'members' && state.currentIndex < 0 && idx === 0)
);
const sub = state.substitutes[id];
const isAbsent = state.absentIds.includes(id);
const item = document.createElement('div');
item.className = 'queue-item' + (isCurrent ? ' current' : '') + (isNext ? ' next-up' : '') + (isDone ? ' done' : '');
item.dataset.id = id;
let badge = '';
if (isCurrent) badge = `<span class="queue-badge badge-current">Jetzt</span>`;
else if (isNext) badge = `<span class="queue-badge badge-next">Nächster</span>`;
else if (isDone) badge = `<span class="queue-badge badge-done">✓</span>`;
const photoHtml = member.photoUrl
? `<img src="${escHtml(member.photoUrl)}" alt="" style="width:44px;height:44px;border-radius:50%;object-fit:cover;">`
: `<div class="queue-initials" style="background:${getColor(member.name)}">${getInitials(member.name)}</div>`;
let subInfo = '';
if (sub && sub.name) {
const skipTag = sub.skipRound2 ? ` <span style="font-size:0.68rem;background:#fef3c7;color:#92400e;padding:1px 5px;border-radius:3px;">keine eigene Vorstellung</span>` : '';
subInfo = `<div class="sub-info">↩ Vertreten durch: ${escHtml(sub.name)}${skipTag}</div>`;
}
item.innerHTML = `
${badge}
<div class="drag-handle" title="Ziehen zum Sortieren">⠿</div>
<div class="queue-photo-wrap">${photoHtml}</div>
<div class="queue-info">
<div class="queue-name">${escHtml(member.name)}</div>
<div class="queue-company">${escHtml(member.company)}</div>
${subInfo}
</div>
<div class="queue-actions">
${renderTimerBadge(id)}
<button class="btn btn-ghost btn-sm" onclick="openSubstituteModal('${id}')" title="Vertretung eintragen">↩</button>
<button class="btn btn-ghost btn-sm" onclick="toggleMemberStatus('${id}')" title="Als abwesend/medical markieren" style="font-size:1rem;">🚫</button>
</div>
`;
container.appendChild(item);
});
// Render absent/medical members below the queue
renderAbsentSection();
// Init Sortable
if (sortableInstance) sortableInstance.destroy();
sortableInstance = Sortable.create(container, {
animation: 150,
handle: '.drag-handle',
ghostClass: 'sortable-ghost',
filter: '.empty-hint',
onEnd: (evt) => {
const moved = state.meetingOrder.splice(evt.oldIndex, 1)[0];
state.meetingOrder.splice(evt.newIndex, 0, moved);
// Adjust currentIndex if needed
if (state.currentIndex >= 0) {
// Recalculate current: find item that was current
// Simple approach: keep currentIndex clamped
state.currentIndex = Math.min(state.currentIndex, state.meetingOrder.length - 1);
}
saveState();
renderControl();
updateNextButton();
},
});
}
function renderVisitorQueue() {
const container = document.getElementById('visitor-queue');
container.innerHTML = '';
if (state.visitorsOrder.length === 0) {
container.innerHTML = `<div class="empty-hint">
<h3>Keine Besucher</h3>
<p>Füge Besucher im rechten Panel hinzu.</p>
</div>`;
return;
}
state.visitorsOrder.forEach((id, idx) => {
const visitor = state.visitors.find(v => v.id === id);
if (!visitor) return;
const isDone = idx < state.visitorsCurrentIndex;
const isCurrent = idx === state.visitorsCurrentIndex && state.mode === 'visitors';
const isNext = state.mode === 'visitors' && idx === state.visitorsCurrentIndex + 1;
const item = document.createElement('div');
item.className = 'queue-item' + (isCurrent ? ' current' : '') + (isNext ? ' next-up' : '') + (isDone ? ' done' : '');
item.dataset.id = id;
let badge = '';
if (isCurrent) badge = `<span class="queue-badge badge-current">Jetzt</span>`;
else if (isNext) badge = `<span class="queue-badge badge-next">Nächster</span>`;
else if (isDone) badge = `<span class="queue-badge badge-done">✓</span>`;
const visitorDisplay = getDisplayData(visitor);
const photoHtml = visitorDisplay.photoUrl
? `<img src="${escHtml(visitorDisplay.photoUrl)}" alt="" style="width:44px;height:44px;border-radius:50%;object-fit:cover;">`
: `<div class="queue-initials" style="background:${getColor(visitor.name)}">${getInitials(visitor.name)}</div>`;
const subTag = visitor.isSubstituteVisitor
? `<div style="font-size:0.72rem;color:#4f46e5;">Vertretung (stellt sich vor)</div>`
: '';
const removeBtn = visitor.isSubstituteVisitor
? '' // substitutes are managed via substitutes panel
: `<button class="btn btn-danger btn-sm" onclick="removeVisitor('${id}')">✕</button>`;
item.innerHTML = `
${badge}
<div class="drag-handle">⠿</div>
<div class="queue-photo-wrap">${photoHtml}</div>
<div class="queue-info">
<div class="queue-name">${escHtml(visitor.name)}</div>
<div class="queue-company">${escHtml(visitor.company || '')}</div>
${subTag}
</div>
<div class="queue-actions">${renderTimerBadge(id)}${removeBtn}</div>
`;
container.appendChild(item);
});
if (sortableVisitorInstance) sortableVisitorInstance.destroy();
sortableVisitorInstance = Sortable.create(container, {
animation: 150,
handle: '.drag-handle',
ghostClass: 'sortable-ghost',
filter: '.empty-hint',
onEnd: (evt) => {
const moved = state.visitorsOrder.splice(evt.oldIndex, 1)[0];
state.visitorsOrder.splice(evt.newIndex, 0, moved);
saveState();
renderControl();
},
});
}
// ============================================================
// AGENDA-FOLIEN (Online-Meeting-Ablauf: Bild, Live-Einbettung, oder eingebaute Vorstellungsrunden)
// ============================================================
// Die Vorstellungsrunden sind fest eingebaute Agenda-Punkte, damit sie in derselben
// frei sortierbaren Reihenfolge stehen können wie die Bild-/Einbettungs-Folien.
const BUILTIN_AGENDA_ITEMS = [
{ id: '__members_round__', label: 'Mitglieder-Vorstellungsrunde', builtin: 'members' },
{ id: '__visitors_round__', label: 'Besucher-Vorstellungsrunde', builtin: 'visitors' },
{ id: '__marketing_round__', label: 'Marketing-/Ergebnisrunde', builtin: 'marketing' },
];
function getAgendaItem(id) {
return BUILTIN_AGENDA_ITEMS.find(b => b.id === id) || (state.agendaCatalog || []).find(a => a.id === id);
}
// Fügt neu bekannt gewordene Punkte (Katalog + eingebaute Runden) genau einmal automatisch
// in die aktive Reihenfolge ein. Punkte, die die Moderatorin bewusst ausgeblendet hat,
// bleiben dauerhaft ausgeblendet (agendaKnownIds merkt sich, was schon einmal eingefügt wurde).
function ensureAgendaOrderDefaults() {
if (!state.agendaOrder) state.agendaOrder = [];
if (!state.agendaKnownIds) state.agendaKnownIds = [];
const known = new Set(state.agendaKnownIds);
const inOrder = new Set(state.agendaOrder);
const allIds = [...BUILTIN_AGENDA_ITEMS.map(b => b.id), ...(state.agendaCatalog || []).map(a => a.id)];
allIds.forEach(id => {
if (!known.has(id)) {
// Für bereits vorhandene agendaOrder-Einträge (Migration von vor agendaKnownIds)
// nur als "bekannt" nachtragen, nicht erneut einfügen.
if (!inOrder.has(id)) { state.agendaOrder.push(id); inOrder.add(id); }
state.agendaKnownIds.push(id);
known.add(id);
}
});
// Vom Server gelöschte Katalog-Punkte aus der aktiven Reihenfolge entfernen; eingebaute Runden bleiben immer gültig.
// Set-Dedupe als Sicherheitsnetz gegen doppelte Einträge aus früheren Läufen.
const validIds = new Set(allIds);
state.agendaOrder = Array.from(new Set(state.agendaOrder.filter(id => validIds.has(id))));
}
function updateAgendaTabVisibility() {
const tab = document.getElementById('tab-agenda');
if (!tab) return;
tab.style.display = ''; // immer sichtbar – enthält immer mind. die eingebauten Vorstellungsrunden
}
async function fetchChapterAgendaFromGitea() {
if (!activeChapter) return;
if (!state.agendaCatalog) state.agendaCatalog = [];
ensureAgendaOrderDefaults();
try {
const res = await fetch(giteaDataUrl(`chapters/${activeChapter.code}/agenda.json`), { cache: 'no-store' });
const items = res.ok ? (await res.json()).items : null;
if (Array.isArray(items)) {
// Mergen statt Überschreiben: lokale, noch nicht hochgeladene Punkte
// (z.B. gerade erst im Admin-Panel angelegt) dürfen nicht verloren gehen.
const map = new Map(state.agendaCatalog.map(i => [i.id, i]));
items.forEach(it => map.set(it.id, Object.assign({}, map.get(it.id) || {}, it)));
state.agendaCatalog = Array.from(map.values());
}
ensureAgendaOrderDefaults();
saveState(false);
updateAgendaTabVisibility();
renderAgendaQueue();
} catch(e) { /* offline → zuletzt bekannten Katalog behalten */ }
}
function renderAgendaQueue() {
const container = document.getElementById('agenda-queue');
if (!container) return;
updateAgendaTabVisibility();
ensureAgendaOrderDefaults();
container.innerHTML = '';
const order = state.agendaOrder || [];
if (order.length === 0) {
container.innerHTML = `<div class="empty-hint">
<h3>Keine Agenda-Punkte aktiv</h3>
<p>Punkte unten unter "Ausgeblendet" wieder einblenden, oder im Admin-Panel neue Punkte anlegen.</p>
</div>`;
renderAgendaHiddenSection();
return;
}
order.forEach(id => {
const it = getAgendaItem(id);
if (!it) return;
const isCurrent = it.builtin
? state.mode === it.builtin
: (state.mode === 'agenda-item' && state.currentAgendaItemId === id);
const item = document.createElement('div');
item.className = 'queue-item' + (isCurrent ? ' current' : '');
item.dataset.id = id;
const badge = isCurrent ? `<span class="queue-badge badge-current">Live</span>` : '';
const typeTag = it.builtin
? `<div style="font-size:0.72rem;color:#16a34a;">Vorstellungsrunde</div>`
: it.type === 'embed'
? `<div style="font-size:0.72rem;color:#4f46e5;">Live-Einbettung</div>`
: `<div style="font-size:0.72rem;color:var(--muted);">Bild</div>`;
const showCall = it.builtin ? `showAgendaRound('${it.builtin}')` : `showAgendaItem('${it.id}')`;
item.innerHTML = `
${badge}
<div class="drag-handle" title="Ziehen zum Sortieren">⠿</div>
<div class="queue-info">
<div class="queue-name">${escHtml(it.label || it.id)}</div>
${typeTag}
</div>
<div class="queue-actions">
<button class="btn ${isCurrent ? 'btn-primary' : 'btn-ghost'} btn-sm" onclick="${showCall}">${isCurrent ? '✓ Live' : '▶ Anzeigen'}</button>
<button class="btn btn-ghost btn-sm" onclick="hideAgendaItem('${it.id}')" title="Aus der aktiven Reihenfolge ausblenden">🚫</button>
</div>
`;
container.appendChild(item);
});
renderAgendaHiddenSection();
if (sortableAgendaInstance) sortableAgendaInstance.destroy();
sortableAgendaInstance = Sortable.create(container, {
animation: 150,
handle: '.drag-handle',
ghostClass: 'sortable-ghost',
filter: '.empty-hint',
onEnd: (evt) => {
const moved = state.agendaOrder.splice(evt.oldIndex, 1)[0];
state.agendaOrder.splice(evt.newIndex, 0, moved);
saveState();
renderControl();
},
});
}
function renderAgendaHiddenSection() {
const section = document.getElementById('agenda-hidden-section');
if (!section) return;
if (activeTab !== 'agenda') { section.style.display = 'none'; return; }
const order = new Set(state.agendaOrder || []);
const allItems = [...BUILTIN_AGENDA_ITEMS, ...(state.agendaCatalog || [])];
const hidden = allItems.filter(it => !order.has(it.id));
if (hidden.length === 0) { section.style.display = 'none'; return; }
section.style.display = 'block';
section.className = 'absent-section';
let html = `<div class="absent-section-title">Ausgeblendet</div>`;
hidden.forEach(it => {
html += `<div class="queue-item" style="cursor:default;margin-bottom:6px;opacity:0.75;">
<div class="queue-info"><div class="queue-name" style="font-size:0.9rem;">${escHtml(it.label || it.id)}</div></div>
<div class="queue-actions">
<button class="btn btn-ghost btn-sm status-tag" onclick="unhideAgendaItem('${it.id}')" title="Wieder in die aktive Reihenfolge aufnehmen">+ Einblenden</button>
</div>
</div>`;
});
section.innerHTML = html;
}
function hideAgendaItem(id) {
state.agendaOrder = (state.agendaOrder || []).filter(aid => aid !== id);
if (state.mode === 'agenda-item' && state.currentAgendaItemId === id) closeAgendaItem();
saveState();
renderControl();
}
function unhideAgendaItem(id) {
if (!state.agendaOrder) state.agendaOrder = [];
if (!state.agendaOrder.includes(id)) state.agendaOrder.push(id);
saveState();
renderControl();
}
function showAgendaItem(id) {
const it = (state.agendaCatalog || []).find(a => a.id === id);
if (!it) return;
if (state.mode !== 'agenda-item') state._agendaPrevMode = state.mode;
state.mode = 'agenda-item';
state.currentAgendaItemId = id;
saveState();
renderControl();
renderDisplay();
}
function closeAgendaItem() {
state.mode = state._agendaPrevMode || 'members';
delete state._agendaPrevMode;
state.currentAgendaItemId = null;
saveState();
renderControl();
renderDisplay();
}
// Startet/holt eine der eingebauten Vorstellungsrunden auf das Display – wie ein Klick
// auf den jeweiligen Tab, nur direkt aus der Agenda-Reihenfolge heraus nutzbar.
function showAgendaRound(kind) {
if (kind === 'members') {
state.mode = 'members';
state.marketingActive = false;
if (state.currentIndex < 0) state.currentIndex = 0;
switchTab('members');
} else if (kind === 'marketing') {
// Zweiter Durchlauf durch dieselbe Mitgliederliste (Marketing-/Ergebnisrunde) – immer
// von vorne, ohne Redezeit-Timer.
state.mode = 'members';
state.marketingActive = true;
state.currentIndex = 0;
switchTab('members');
stopTimer();
} else {
state.mode = 'visitors';
if (state.visitorsCurrentIndex < 0) state.visitorsCurrentIndex = 0;
switchTab('visitors');
}
saveState();
renderControl();
renderDisplay();
}
// Eine eingebaute Vorstellungsrunde ist beim automatischen Weiterspringen nur "nutzbar", wenn
// es dafür überhaupt Personen gibt (z.B. keine Besucher angemeldet → Runde beim Weiter-Klick überspringen).
// Manuelles Anklicken in der Agenda-Liste ist davon nicht betroffen.
function isAgendaStepUsable(id) {
const it = getAgendaItem(id);
if (!it) return false;
if (it.builtin === 'members' || it.builtin === 'marketing') return state.meetingOrder.length > 0;
if (it.builtin === 'visitors') return state.visitorsOrder.length > 0;
return true;
}
// Findet die ID des nächsten gültigen Punkts nach `currentId` in der aktiven Agenda-Reihenfolge
// (Katalog-Folien + eingebaute Vorstellungsrunden). null = Ende der Agenda erreicht.
function nextAgendaSequenceId(currentId) {
const order = state.agendaOrder || [];
const idx = order.indexOf(currentId);
if (idx === -1) return null;
for (let i = idx + 1; i < order.length; i++) {
if (isAgendaStepUsable(order[i])) return order[i];
}
return null;
}
// Beschriftung für den "Weiter"-Button: zeigt, wohin der nächste Klick tatsächlich führt.
function nextStepButtonLabel(afterId) {
const nextId = nextAgendaSequenceId(afterId);
if (!nextId) return '↩ Beenden';
const it = getAgendaItem(nextId);
if (!it) return '↩ Beenden';
if (it.builtin === 'visitors') return '2. Runde'; // vertrauter Text für den Übergang mit Logo-Zwischenschritt
return it.label;
}
// Springt zum übergebenen Agenda-Schritt (Folie oder eingebaute Runde), oder beendet das
// Meeting (zurück zum Logo), wenn kein weiterer Schritt mehr folgt.
function goToAgendaSequenceStep(id) {
if (!id) { endMeetingFlow(); return; }
const it = getAgendaItem(id);
if (!it) { endMeetingFlow(); return; }
if (it.builtin === 'visitors') {
// Für den Übergang in die Besucherrunde den bestehenden Logo-Zwischenschritt beibehalten.
startRoundTransition();
return;
}
if (it.builtin === 'members' || it.builtin === 'marketing') {
showAgendaRound(it.builtin);
return;
}
showAgendaItem(id);
}
function endMeetingFlow() {
state.currentIndex = -1;
state.visitorsCurrentIndex = -1;
state.mode = 'members';
state.marketingActive = false;
switchTab('members');
saveState();
renderControl();
renderDisplay();
}
function renderVisitorsList() {
const container = document.getElementById('visitors-list');
container.innerHTML = '';
if (state.visitors.length === 0) {
container.innerHTML = `<div class="empty-hint"><p>Noch keine Besucher eingetragen.</p></div>`;
return;
}
state.visitors.forEach(v => {
const div = document.createElement('div');
div.className = 'visitor-item';
const photoHtml = v.photoUrl
? `<img src="${escHtml(v.photoUrl)}" alt="" style="width:36px;height:36px;border-radius:50%;object-fit:cover;">`
: `<div style="width:36px;height:36px;border-radius:50%;background:${getColor(v.name)};display:flex;align-items:center;justify-content:center;color:white;font-weight:700;font-size:0.85rem;">${getInitials(v.name)}</div>`;
div.innerHTML = `
${photoHtml}
<div style="flex:1">
<div class="v-name">${escHtml(v.name)}</div>
<div class="v-company">${escHtml(v.company || '')} ${v.specialty ? '· ' + escHtml(v.specialty) : ''}</div>
</div>
${renderTimerBadge(v.id)}
<button class="btn btn-ghost btn-sm" onclick="openVisitorEdit('${v.id}')" title="Bearbeiten" style="padding:4px 8px;margin-right:4px;">✎</button>
<button class="btn btn-danger btn-sm" onclick="removeVisitor('${v.id}')">✕</button>
`;
container.appendChild(div);
});
}
function renderMembersManage() {
const container = document.getElementById('members-manage-list');
container.innerHTML = '';
if (state.members.length === 0) {
container.innerHTML = `<div class="empty-hint"><p>Noch keine Mitglieder angelegt.</p></div>`;
return;
}
state.members.forEach(m => {
const div = document.createElement('div');
div.className = 'member-manage-item';
const photoHtml = m.photoUrl
? `<img src="${escHtml(m.photoUrl)}" alt="" style="width:36px;height:36px;border-radius:50%;object-fit:cover;">`
: `<div style="width:36px;height:36px;border-radius:50%;background:${getColor(m.name)};display:flex;align-items:center;justify-content:center;color:white;font-weight:700;font-size:0.85rem;">${getInitials(m.name)}</div>`;
div.innerHTML = `
${photoHtml}
<div style="flex:1">
<div class="m-name">${escHtml(m.name)}</div>
<div class="m-company">${escHtml(m.company)} · ${escHtml(m.specialty || '')}</div>
</div>
<button class="btn btn-ghost btn-sm" onclick="editMember('${m.id}')">✎</button>
<button class="btn btn-danger btn-sm" onclick="deleteMember('${m.id}')">✕</button>
`;
container.appendChild(div);
});
}
function renderSubstitutesList() {
const container = document.getElementById('substitutes-list');
container.innerHTML = '';
const subs = Object.entries(state.substitutes).filter(([, v]) => v && v.name);
if (subs.length === 0) {
container.innerHTML = `<div style="color:var(--muted);font-size:0.85rem;padding:8px 0;">Keine aktiven Vertretungen.</div>`;
return;
}
subs.forEach(([memberId, sub]) => {
const member = state.members.find(m => m.id === memberId);
if (!member) return;
const div = document.createElement('div');
div.style.cssText = 'padding:8px 0;border-bottom:1px solid var(--border);font-size:0.88rem;';
div.innerHTML = `
<div style="font-weight:600;">${escHtml(member.name)}</div>
<div style="color:var(--muted);">↩ ${escHtml(sub.name)} (${escHtml(sub.company || '')})${sub.skipRound2 ? ' <span style="font-size:0.72rem;background:#fef3c7;color:#92400e;padding:1px 5px;border-radius:3px;margin-left:4px;">keine eigene Vorstellung</span>' : ''}</div>
<button class="btn btn-danger btn-sm" style="margin-top:4px;" onclick="removeSubstitute('${memberId}')">Entfernen</button>
`;
container.appendChild(div);
});
}
function renderSubstituteMemberSelect() {
const sel = document.getElementById('sub-member-select');
const val = sel.value;
sel.innerHTML = '<option value="">— Mitglied auswählen —</option>';
state.members.forEach(m => {
const opt = document.createElement('option');
opt.value = m.id;
opt.textContent = m.name;
if (m.id === val) opt.selected = true;
sel.appendChild(opt);
});
}
function updateNextButton() {
const btn = document.getElementById('btn-next');
const nameEl = document.getElementById('btn-next-name');
if (!btn) return;
if (state.mode === 'coordinator-report') {
btn.disabled = false;
nameEl.textContent = '↩ Schließen';
return;
}
if (state.mode === 'round-transition') {
btn.disabled = false;
nameEl.textContent = '2. Runde';
return;
}
if (state.mode === 'agenda-item') {
btn.disabled = false;
nameEl.textContent = nextStepButtonLabel(state.currentAgendaItemId);
return;
}
const next = getNextPresenter();
const hasStarted = state.currentIndex >= 0 || state.mode === 'visitors';
if (!hasStarted) {
// Not started yet
const first = state.meetingOrder.length > 0
? state.members.find(m => m.id === state.meetingOrder[0])
: null;
if (first) {
btn.disabled = false;
btn.querySelector('span').textContent = first.name;
} else {
btn.disabled = true;
btn.querySelector('span').textContent = '';
}
return;
}
// Absent-summary mode
if (state.mode === 'absent-summary') {
btn.disabled = false;
nameEl.textContent = nextStepButtonLabel('__members_round__');
return;
}
if (next) {
btn.disabled = false;
const nextData = getDisplayData(next);
nameEl.textContent = nextData.name;
} else {
// Check if we just finished
const membersDone = state.mode === 'members' && state.currentIndex >= state.meetingOrder.length - 1;
const visitorsDone = state.mode === 'visitors' && state.visitorsCurrentIndex >= state.visitorsOrder.length - 1;
const hasAbsent = state.absentIds.length > 0 || (state.medicalIds || []).length > 0;
if (membersDone && hasAbsent && !state.marketingActive) {
btn.disabled = false;
nameEl.textContent = 'Abwesenheiten';
} else if (membersDone && state.marketingActive) {
btn.disabled = false;
nameEl.textContent = nextStepButtonLabel('__marketing_round__');
} else if (membersDone) {
btn.disabled = false;
nameEl.textContent = nextStepButtonLabel('__members_round__');
} else if (visitorsDone) {
btn.disabled = false;
nameEl.textContent = nextStepButtonLabel('__visitors_round__');
} else {
btn.disabled = false;
nameEl.textContent = '';
}
}
}
// ============================================================
// ACTIONS
// ============================================================
function _coordReportClose() {
state.mode = state._coordPrevMode || 'members';
delete state._coordPrevMode;
state.coordinatorReportData = null;
saveState();
renderControl();
renderDisplay();
}
function startRoundTransition() {
state.mode = 'round-transition';
saveState();
renderControl();
renderDisplay();
}
function goNext() {
if (state.mode === 'coordinator-report') {
_coordReportClose();
return;
}
if (state.mode === 'agenda-item') {
// Folgt der Agenda-Reihenfolge zum nächsten Punkt. Bei eingebetteten Live-Folien kann
// "Weiter" nicht innerhalb der Einbettung selbst blättern, sondern nur zum nächsten
// Agenda-Punkt springen.
goToAgendaSequenceStep(nextAgendaSequenceId(state.currentAgendaItemId));
return;
}
if (state.mode === 'round-transition') {
state.mode = 'visitors';
state.visitorsCurrentIndex = 0;
switchTab('visitors');
saveState();
renderControl();
renderDisplay();
return;
}
if (state.mode === 'members' && state.marketingActive) {
// Marketing-/Ergebnisrunde: dieselbe Reihenfolge wie die Vorstellungsrunde, aber ohne
// Timer und ohne Abwesenheiten-Zwischenschritt.
if (state.currentIndex < state.meetingOrder.length - 1) {
state.currentIndex++;
} else {
goToAgendaSequenceStep(nextAgendaSequenceId('__marketing_round__'));
return;
}
} else if (state.mode === 'members') {
if (state.currentIndex < state.meetingOrder.length - 1) {
state.currentIndex++;
} else {
// Ende der Mitgliederrunde: erst Abwesenheiten (falls vorhanden), danach dem
// nächsten Punkt in der Agenda-Reihenfolge nach "Mitglieder-Vorstellungsrunde" folgen.
const hasAbsent = state.absentIds.length > 0 || (state.medicalIds || []).length > 0;
if (hasAbsent) {
state.mode = 'absent-summary';
} else {
goToAgendaSequenceStep(nextAgendaSequenceId('__members_round__'));
return;
}
}
} else if (state.mode === 'absent-summary') {
goToAgendaSequenceStep(nextAgendaSequenceId('__members_round__'));
return;
} else if (state.mode === 'visitors') {
if (state.visitorsCurrentIndex < state.visitorsOrder.length - 1) {
state.visitorsCurrentIndex++;
} else {
// Ende der Besucherrunde: dem nächsten Punkt nach "Besucher-Vorstellungsrunde" folgen.
goToAgendaSequenceStep(nextAgendaSequenceId('__visitors_round__'));
return;
}
}
// Start timer for new presenter (2s delay) – in der Marketing-/Ergebnisrunde bewusst kein Timer.
if ((state.mode === 'members' && !state.marketingActive) || state.mode === 'visitors') {
const nowPresenter = getCurrentPresenter();
if (nowPresenter) scheduleTimer(getPresenterSeconds(nowPresenter));
else stopTimer();
} else {
stopTimer();
}
saveState();
renderControl();
renderDisplay();
}
// Klick-Ersatz für die Display-Seite (siehe #display-advance-btn): delegiert 1:1 an dieselbe
// Weiter-Logik/Disabled-Prüfung wie der "Weiter"-Button im Steuerpult, keine zweite Zustandsquelle.
// Ob "Weiter" gerade sinnvoll etwas tut. Bewusst auf `state` statt auf dem DOM-Element
// #btn-next geprüft: #btn-next existiert zwar in beiden Fenstern (Steuerpult + Display sind
// dasselbe Dokument), aber sein disabled-Attribut wird nur von updateNextButton() gepflegt,
// und das läuft laut renderCurrentView() ausschließlich im Steuerpult-Kontext — im
// Display-Fenster bliebe #btn-next.disabled sonst dauerhaft auf dem statischen HTML-Anfangswert
// stehen. Einziger echter "nichts zu tun"-Fall (siehe updateNextButton() ~Zeile 3599): Meeting
// noch nicht gestartet und keine Mitglieder in der Warteschlange.
function canAdvance() {
const hasStarted = state.currentIndex >= 0 || state.mode === 'visitors' || state.mode !== 'members';
if (hasStarted) return true;
return state.meetingOrder.length > 0;
}
function displayAdvanceClick() {
if (canAdvance()) goNext();
}
function resetMeeting() {
if (!confirm('Vorstellungsrunde neu starten? Der Fortschritt wird zurückgesetzt.')) return;
stopTimer();
state.currentIndex = -1;
state.visitorsCurrentIndex = -1;
state.mode = 'members';
state.marketingActive = false;
switchTab('members');
saveState();
renderControl();
renderDisplay();
}
function switchTab(tab) {
activeTab = tab;
document.getElementById('member-queue').style.display = tab === 'members' ? '' : 'none';
document.getElementById('visitor-queue').style.display = tab === 'visitors' ? '' : 'none';
document.getElementById('agenda-queue').style.display = tab === 'agenda' ? '' : 'none';
const agendaHidden = document.getElementById('agenda-hidden-section');
if (agendaHidden && tab !== 'agenda') agendaHidden.style.display = 'none';
else if (agendaHidden) renderAgendaHiddenSection();
document.getElementById('tab-members').classList.toggle('active', tab === 'members');
document.getElementById('tab-visitors').classList.toggle('active', tab === 'visitors');
document.getElementById('tab-agenda').classList.toggle('active', tab === 'agenda');
}
function switchRightPanel(panelId) {
document.querySelectorAll('.right-panel-section').forEach(el => el.classList.remove('active'));
document.querySelectorAll('.panel-right-tab').forEach(el => el.classList.remove('active'));
document.getElementById(panelId).classList.add('active');
document.querySelector(`[data-panel="${panelId}"]`).classList.add('active');
}
// ============================================================
// MEMBER CRUD
// ============================================================
function openAddMember() {
document.getElementById('edit-member-id').value = '';
document.getElementById('modal-member-title').textContent = 'Mitglied hinzufügen';
document.getElementById('m-name').value = '';
document.getElementById('m-company').value = '';
document.getElementById('m-specialty').value = '';
document.getElementById('m-photo').value = '';
document.getElementById('m-profile').value = '';
document.getElementById('m-kurzlink').value = '';
document.getElementById('photo-preview-wrap').style.display = 'none';
_updateOfflinePhotoUI(null);
openModal('modal-member');
}
function editMember(id) {
const m = state.members.find(m => m.id === id);
if (!m) return;
document.getElementById('edit-member-id').value = id;
document.getElementById('modal-member-title').textContent = 'Mitglied bearbeiten';
document.getElementById('m-name').value = m.name || '';
document.getElementById('m-company').value = m.company || '';
document.getElementById('m-specialty').value = m.specialty || '';
document.getElementById('m-photo').value = m.photoUrl || '';
document.getElementById('m-profile').value = m.profileUrl || '';
if (m.photoUrl) {
document.getElementById('photo-preview').src = m.photoUrl;
document.getElementById('photo-preview-wrap').style.display = 'block';
} else {
document.getElementById('photo-preview-wrap').style.display = 'none';
}
// Load extras for this member
const ex = loadExtras()[extrasKey(m.name)] || {};
document.getElementById('m-kurzlink').value = ex.kurzlink || '';
_updateOfflinePhotoUI(ex.photoB64 || null);
openModal('modal-member');
}
document.getElementById('m-photo').addEventListener('input', (e) => {
const url = e.target.value.trim();
const wrap = document.getElementById('photo-preview-wrap');
const img = document.getElementById('photo-preview');
if (url) {
img.src = url;
wrap.style.display = 'block';
} else {
wrap.style.display = 'none';
}
});
function saveMember() {
const name = document.getElementById('m-name').value.trim();
const company = document.getElementById('m-company').value.trim();
const specialty = document.getElementById('m-specialty').value.trim();
const photoUrl = document.getElementById('m-photo').value.trim();
const profileUrl = document.getElementById('m-profile').value.trim();
const kurzlink = document.getElementById('m-kurzlink').value.trim();
if (!name) { alert('Bitte Name eingeben.'); return; }
const editId = document.getElementById('edit-member-id').value;
if (editId) {
const m = state.members.find(m => m.id === editId);
if (m) Object.assign(m, { name, company, specialty, photoUrl, profileUrl });
} else {
const id = 'member-' + Date.now() + '-' + Math.random().toString(36).slice(2,7);
state.members.push({ id, name, company, specialty, photoUrl, profileUrl });
// Add to meeting order if not already there
if (!state.meetingOrder.includes(id)) {
state.meetingOrder.push(id);
}
}
// Save extras (kurzlink + offline photo) — keyed by name
const extras = loadExtras();
const key = extrasKey(name);
const entry = extras[key] || {};
if (kurzlink) entry.kurzlink = kurzlink;
else delete entry.kurzlink;
if (_editingOfflinePhotoB64) entry.photoB64 = _editingOfflinePhotoB64;
else delete entry.photoB64;
if (Object.keys(entry).length > 0) extras[key] = entry;
else delete extras[key];
saveExtras(extras);
saveState();
closeMemberModal();
renderControl();
}
function deleteMember(id) {
if (!confirm('Mitglied entfernen?')) return;
state.members = state.members.filter(m => m.id !== id);
state.meetingOrder = state.meetingOrder.filter(mid => mid !== id);
delete state.substitutes[id];
state.absentIds = state.absentIds.filter(aid => aid !== id);
if (state.medicalIds) state.medicalIds = state.medicalIds.filter(mid => mid !== id);
if (state.currentIndex >= state.meetingOrder.length) {
state.currentIndex = state.meetingOrder.length - 1;
}
saveState();
renderControl();
}
function closeMemberModal() {
const hint = document.getElementById('offline-photo-url-hint');
if (hint) hint.remove();
closeModal('modal-member');
}
// ============================================================
// VISITOR CRUD
// ============================================================
function addVisitor() {
const name = document.getElementById('v-name').value.trim();
if (!name) { alert('Bitte Name eingeben.'); return; }
const id = 'visitor-' + Date.now() + '-' + Math.random().toString(36).slice(2,7);
const visitor = {
id,
name,
company: document.getElementById('v-company').value.trim(),
specialty: document.getElementById('v-specialty').value.trim(),
photoUrl: document.getElementById('v-photo').value.trim(),
invitedBy: document.getElementById('v-invited-by').value || '',
isVisitor: true,
};
state.visitors.push(visitor);
state.visitorsOrder.push(id);
document.getElementById('v-name').value = '';
document.getElementById('v-company').value = '';
document.getElementById('v-specialty').value = '';
document.getElementById('v-photo').value = '';
document.getElementById('v-invited-by').value = '';
saveState();
renderControl();
}
function removeVisitor(id) {
state.visitors = state.visitors.filter(v => v.id !== id);
state.visitorsOrder = state.visitorsOrder.filter(vid => vid !== id);
if (state.visitorsCurrentIndex >= state.visitorsOrder.length) {
state.visitorsCurrentIndex = state.visitorsOrder.length - 1;
}
saveState();
renderControl();
}
function openVisitorEdit(id) {
const v = state.visitors.find(v => v.id === id);
if (!v) return;
document.getElementById('ve-id').value = v.id;
document.getElementById('ve-name').value = v.name || '';
document.getElementById('ve-company').value = v.company || '';
document.getElementById('ve-specialty').value = v.specialty || '';
const sel = document.getElementById('ve-invited-by');
if (sel) {
sel.innerHTML = '<option value="">— kein Mitglied —</option>';
state.members.slice().sort((a,b) => a.name.localeCompare(b.name)).forEach(m => {
const opt = document.createElement('option');
opt.value = m.id;
opt.textContent = m.name;
if (m.id === v.invitedBy) opt.selected = true;
sel.appendChild(opt);
});
}
document.getElementById('ve-photo').value = v.photoUrl || '';
openModal('modal-visitor-edit');
}
function saveVisitorEdit() {
const id = document.getElementById('ve-id').value;
const v = state.visitors.find(v => v.id === id);
if (!v) return;
const name = document.getElementById('ve-name').value.trim();
if (!name) { alert('Bitte Name eingeben.'); return; }
v.name = name;
v.company = document.getElementById('ve-company').value.trim();
v.specialty = document.getElementById('ve-specialty').value.trim();
v.invitedBy = document.getElementById('ve-invited-by').value || '';
v.photoUrl = document.getElementById('ve-photo').value.trim();
saveState();
renderControl();
closeModal('modal-visitor-edit');
}
// ============================================================
// SUBSTITUTE MANAGEMENT
// ============================================================
function openSubstituteModal(memberId) {
const member = state.members.find(m => m.id === memberId);
if (!member) return;
const sub = state.substitutes[memberId] || {};
document.getElementById('sub-modal-member-id').value = memberId;
document.getElementById('sub-modal-member-name').textContent = member.name;
document.getElementById('sub-modal-name').value = sub.name || '';
document.getElementById('sub-modal-company').value = sub.company || '';
document.getElementById('sub-modal-specialty').value = sub.specialty || '';
document.getElementById('sub-modal-photo').value = sub.photoUrl || '';
document.getElementById('sub-modal-skip-round2').checked = sub.skipRound2 || false;
renderSvPicker();
openModal('modal-substitute');
}
function saveSubstituteModal() {
const memberId = document.getElementById('sub-modal-member-id').value;
const name = document.getElementById('sub-modal-name').value.trim();
if (!name) { alert('Bitte Name der Vertretung eingeben.'); return; }
const subData = {
name,
company: document.getElementById('sub-modal-company').value.trim(),
specialty: document.getElementById('sub-modal-specialty').value.trim(),
photoUrl: document.getElementById('sub-modal-photo').value.trim(),
skipRound2: document.getElementById('sub-modal-skip-round2').checked,
};
state.substitutes[memberId] = subData;
syncSubstituteToVisitors(memberId, subData);
saveState();
closeModal('modal-substitute');
renderControl();
}
function saveSubstitute() {
const memberId = document.getElementById('sub-member-select').value;
const name = document.getElementById('sub-rep-name').value.trim();
if (!memberId) { alert('Bitte Mitglied auswählen.'); return; }
if (!name) { alert('Bitte Name der Vertretung eingeben.'); return; }
const subData = {
name,
company: document.getElementById('sub-rep-company').value.trim(),
specialty: document.getElementById('sub-rep-specialty').value.trim(),
photoUrl: document.getElementById('sub-rep-photo').value.trim(),
};
state.substitutes[memberId] = subData;
syncSubstituteToVisitors(memberId, subData);
document.getElementById('sub-member-select').value = '';
document.getElementById('sub-rep-name').value = '';
document.getElementById('sub-rep-company').value = '';
document.getElementById('sub-rep-specialty').value = '';
document.getElementById('sub-rep-photo').value = '';
saveState();
renderControl();
}
function removeSubstitute(memberId) {
removeSubstituteVisitor(memberId);
delete state.substitutes[memberId];
saveState();
closeModal('modal-substitute');
renderControl();
}
// Automatically add/update substitute as visitor for round 2
function syncSubstituteToVisitors(memberId, subData) {
const visitorId = 'sub-visitor-' + memberId;
// Remove old entry if exists
removeSubstituteVisitor(memberId);
// Skip round 2 if flagged (e.g. same-company employee or recurring substitute)
if (subData.skipRound2) return;
// Add new visitor entry
const visitor = {
id: visitorId,
name: subData.name,
company: subData.company || '',
specialty: subData.specialty || '',
photoUrl: subData.photoUrl || '',
isVisitor: true,
isSubstituteVisitor: true,
forMemberId: memberId,
};
state.visitors.push(visitor);
state.visitorsOrder.push(visitorId);
}
function removeSubstituteVisitor(memberId) {
const visitorId = 'sub-visitor-' + memberId;
state.visitors = state.visitors.filter(v => v.id !== visitorId);
state.visitorsOrder = state.visitorsOrder.filter(id => id !== visitorId);
}
// ============================================================
// IMPORT / EXPORT
// ============================================================
function openImportExport() {
const exportData = {
members: state.members,
meetingOrder: state.meetingOrder,
substitutes: state.substitutes,
visitors: state.visitors,
visitorsOrder: state.visitorsOrder,
};
document.getElementById('json-textarea').value = JSON.stringify(exportData, null, 2);
openModal('modal-importexport');
}
function exportJSON() {
const exportData = {
members: state.members,
meetingOrder: state.meetingOrder,
substitutes: state.substitutes,
visitors: state.visitors,
visitorsOrder: state.visitorsOrder,
};
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'bni-sonne-mitglieder.json';
a.click();
}
function importJSON() {
try {
const data = JSON.parse(document.getElementById('json-textarea').value);
if (data.members) state.members = data.members;
if (data.meetingOrder) state.meetingOrder = data.meetingOrder;
if (data.substitutes) state.substitutes = data.substitutes;
if (data.visitors) state.visitors = data.visitors;
if (data.visitorsOrder) state.visitorsOrder = data.visitorsOrder;
state.currentIndex = -1;
state.visitorsCurrentIndex = -1;
state.mode = 'members';
saveState();
closeModal('modal-importexport');
renderControl();
alert('Import erfolgreich! ' + state.members.length + ' Mitglieder geladen.');
} catch(e) {
alert('Ungültiges JSON: ' + e.message);
}
}
// ============================================================
// MODAL HELPERS
// ============================================================
function openModal(id) {
document.getElementById(id).classList.add('open');
}
function closeModal(id) {
document.getElementById(id).classList.remove('open');
}
// Close on overlay click
document.querySelectorAll('.modal-overlay').forEach(overlay => {
overlay.addEventListener('click', (e) => {
if (e.target === overlay) overlay.classList.remove('open');
});
});
// ============================================================
// UTILITY
// ============================================================
function escHtml(str) {
if (!str) return '';
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
function uid() {
return Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
}
// ============================================================
// KEYBOARD SHORTCUTS
// ============================================================
document.addEventListener('keydown', (e) => {
// Bewusst NICHT mehr auf getView()==='control' beschränkt: ein Präsentations-Klicker kann
// ArrowRight/Space senden, während der OS-Fokus auf dem Display-(Beamer)-Fenster liegt statt
// auf dem Steuerpult. #btn-next existiert als reales DOM-Element unabhängig davon, welche
// View gerade sichtbar ist. Funktioniert weiterhin nur, solange der Fokus auf unserer eigenen
// Seite liegt — sobald in eine eingebettete Zoho-Show-Folie geklickt wurde, verschluckt deren
// eigenes (fremdes) Dokument die Tastendrücke; das lässt sich aus Sicherheitsgründen
// (Cross-Origin-iframe) von hier aus nicht abfangen.
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
if (e.key === 'ArrowRight' || e.key === ' ') {
e.preventDefault();
// canAdvance() statt #btn-next.disabled: dessen disabled-Attribut wird nur im
// Steuerpult-Kontext von updateNextButton() gepflegt (siehe canAdvance()-Kommentar).
if (canAdvance()) goNext();
}
});
// ============================================================
// PDF IMPORT
// ============================================================
function openPdfImport() {
const lib = window['pdfjs-dist/build/pdf'] || window.pdfjsLib;
if (lib) lib.GlobalWorkerOptions.workerSrc =
'https://cdn.jsdelivr.net/npm/pdfjs-dist@3.11.174/build/pdf.worker.min.js';
const status = document.getElementById('pdf-parse-status');
if (status) status.style.display = 'none';
renderQuickEntryTable();
openModal('modal-pdf-import');
}
async function handlePdfImportFile(file) {
if (!file || !file.name.toLowerCase().endsWith('.pdf')) return;
const status = document.getElementById('pdf-parse-status');
status.style.display = '';
status.style.cssText += ';background:#f0f4ff;border:1px solid #c0c8f0;color:#1a2744;';
status.textContent = 'PDF wird analysiert…';
try {
const buf = await file.arrayBuffer();
const rows = await parseBNIListPdf(buf);
if (rows.length === 0) {
status.style.cssText += ';background:#fff3cd;border:1px solid #ffc107;color:#664d03;';
status.textContent = 'Keine Einträge erkannt – ist das die BNI Besucher-/Vertreterliste?';
return;
}
fillTableFromPdfRows(rows);
status.style.cssText += ';background:#d1fae5;border:1px solid #6ee7b7;color:#065f46;';
status.textContent = `✓ ${rows.length} Eintr${rows.length===1?'ag':'äge'} aus PDF erkannt und eingetragen.`;
} catch(err) {
status.style.cssText += ';background:#fee2e2;border:1px solid #fca5a5;color:#7f1d1d;';
status.textContent = 'Fehler beim Lesen der PDF: ' + err.message;
}
}
async function parseBNIListPdf(arrayBuffer) {
const lib = window['pdfjs-dist/build/pdf'] || window.pdfjsLib;
if (!lib) throw new Error('pdf.js nicht geladen');
const pdf = await lib.getDocument({ data: arrayBuffer }).promise;
const rows = [];
// Column X boundaries derived from the BNI Chapterportal PDF layout:
// type (B/V): x < 77
// name: 77 ≤ x < 198
// phone: 198 ≤ x < 275
// email: 275 ≤ x < 395
// specialty: 395 ≤ x < 548
// forMember: 548 ≤ x < 670
function colOf(x) {
if (x < 77) return 'type';
if (x < 198) return 'name';
if (x < 275) return 'phone';
if (x < 395) return 'email';
if (x < 548) return 'specialty';
if (x < 670) return 'forMember';
return null;
}
for (let p = 1; p <= pdf.numPages; p++) {
const page = await pdf.getPage(p);
const tc = await page.getTextContent();
const items = tc.items
.map(i => ({ x: i.transform[4], y: i.transform[5], text: i.str.trim() }))
.filter(i => i.text && i.y < 462 && i.y >= 140) // skip header & footer bands
.sort((a, b) => b.y - a.y || a.x - b.x); // top→bottom, left→right
// Group by Y proximity (≤15 units covers multi-line cells within one row)
const groups = [];
for (const item of items) {
const last = groups[groups.length - 1];
if (last && Math.abs(last.refY - item.y) <= 15) {
last.items.push(item);
} else {
groups.push({ refY: item.y, items: [item] });
}
}
for (const g of groups) {
const cols = {};
for (const item of g.items) {
const c = colOf(item.x);
if (!c) continue;
cols[c] = cols[c] ? cols[c] + ' ' + item.text : item.text;
}
// Rows start with a number: "1. B", "2. V", "3." (empty), etc.
const m = (cols.type || '').match(/^\d+\.?\s*([BV])?/i);
if (!m) continue;
const name = (cols.name || '').trim();
if (!name) continue;
rows.push({
type: (m[1] || '').toUpperCase(),
name,
specialty: (cols.specialty || '').trim(),
forMember: (cols.forMember || '').trim(),
});
}
}
return rows;
}
function fillTableFromPdfRows(rows) {
renderQuickEntryTable(); // reset to 10 blank rows first
const memberOptions = state.members.map(m =>
`<option value="${escHtml(m.id)}">${escHtml(m.name)}</option>`
).join('');
// Ensure enough table rows exist
const tbody = document.getElementById('quick-entry-tbody');
while (tbody.querySelectorAll('.qe-row').length < rows.length) {
const i = tbody.querySelectorAll('.qe-row').length + 1;
const tr = document.createElement('tr');
tr.className = 'qe-row';
tr.dataset.row = i;
tr.innerHTML = `
<td style="width:20px" class="qe-num">${i}</td>
<td style="width:105px"><select class="qe-type">
<option value="">—</option>
<option value="B">B – Besucher</option>
<option value="V">V – Vertreter</option>
</select></td>
<td><input type="text" class="qe-name" placeholder="Vor- und Nachname" autocomplete="off"></td>
<td><input type="text" class="qe-specialty" placeholder="Berufsbezeichnung" autocomplete="off"></td>
<td><select class="qe-for-member">
<option value="">— Mitglied auswählen —</option>${memberOptions}
</select></td>`;
tbody.appendChild(tr);
}
const tableRows = tbody.querySelectorAll('.qe-row');
rows.forEach((row, i) => {
const tr = tableRows[i];
tr.querySelector('.qe-type').value = row.type || '';
tr.querySelector('.qe-name').value = row.name;
tr.querySelector('.qe-specialty').value = row.specialty;
if (row.forMember) {
const sel = tr.querySelector('.qe-for-member');
const needle = row.forMember.toLowerCase();
const match = state.members.find(m => {
const mn = m.name.toLowerCase();
return mn === needle
|| mn.includes(needle)
|| needle.includes(mn)
|| needle.includes(mn.split(' ').pop());
});
if (match) sel.value = match.id;
}
});
}
function renderQuickEntryTable() {
const tbody = document.getElementById('quick-entry-tbody');
tbody.innerHTML = '';
const memberOptions = state.members.map(m =>
`<option value="${escHtml(m.id)}">${escHtml(m.name)}</option>`
).join('');
for (let i = 1; i <= 10; i++) {
const tr = document.createElement('tr');
tr.className = 'qe-row';
tr.dataset.row = i;
tr.innerHTML = `
<td style="width:20px" class="qe-num">${i}</td>
<td style="width:105px">
<select class="qe-type">
<option value="">—</option>
<option value="B">B – Besucher</option>
<option value="V">V – Vertreter</option>
</select>
</td>
<td><input type="text" class="qe-name" placeholder="Vor- und Nachname" autocomplete="off"></td>
<td><input type="text" class="qe-specialty" placeholder="Berufsbezeichnung" autocomplete="off"></td>
<td>
<select class="qe-for-member">
<option value="">— Mitglied auswählen —</option>
${memberOptions}
</select>
</td>
`;
tbody.appendChild(tr);
}
}
function confirmQuickEntry() {
const rows = document.querySelectorAll('#quick-entry-tbody .qe-row');
let importedCount = 0;
for (const row of rows) {
const type = row.querySelector('.qe-type').value;
const name = row.querySelector('.qe-name').value.trim();
const specialty = row.querySelector('.qe-specialty').value.trim();
const memberId = row.querySelector('.qe-for-member').value;
if (!name) continue; // skip empty rows
if (type === 'V' && memberId) {
// Vertreter → als Vertretung eintragen + automatisch Besucher-Runde
const subData = { name, company: '', specialty, photoUrl: '' };
state.substitutes[memberId] = subData;
syncSubstituteToVisitors(memberId, subData);
importedCount++;
} else {
// Besucher (oder Vertreter ohne Mitglied-Zuordnung)
const id = 'visitor-' + Date.now() + '-' + Math.random().toString(36).slice(2, 7);
state.visitors.push({ id, name, company: '', specialty, photoUrl: '', isVisitor: true });
state.visitorsOrder.push(id);
importedCount++;
}
}
saveState();
closeModal('modal-pdf-import');
renderControl();
switchRightPanel('visitors-panel');
switchTab('visitors');
alert(`✓ ${importedCount} Eintr${importedCount === 1 ? 'ag' : 'äge'} importiert.`);
}
// ============================================================
// MEMBER SYNC
// ============================================================
const BOOKMARKLET_CODE = `(async function(){
/* ── helper: load a profile page in a hidden iframe, wait for AJAX to render ── */
function loadProfile(url){
return new Promise(function(resolve){
var iframe = document.createElement('iframe');
iframe.style.cssText='position:fixed;opacity:0;pointer-events:none;top:0;left:0;width:1200px;height:900px;z-index:-1;';
var done=false;
var timer;
function finish(){
if(done)return; done=true; clearTimeout(timer);
try{ document.body.removeChild(iframe); }catch(e){}
resolve(null);
}
function extract(){
if(done)return;
try{
var doc=iframe.contentDocument||iframe.contentWindow.document;
var det=doc.getElementById('memberDetail');
if(!det||det.innerHTML.trim().length<100){ return false; }
/* Photo */
var img=det.querySelector('img[src*="ImageDownload"],img[src*="bniconnect"],img[src*="cdn.bni"]')
||det.querySelector('.photoCol img,[class*="photo"] img,img');
var photoUrl=img?(img.getAttribute('src')||''):'';
if(photoUrl&&!photoUrl.startsWith('http')) photoUrl=location.origin+photoUrl;
/* Texts – collect all leaf text nodes */
var leaves=Array.from(det.querySelectorAll('*'))
.filter(function(el){return el.children.length===0&&el.textContent.trim().length>1;})
.map(function(el){return el.textContent.trim().replace(/\\s+/g,' ');});
/* company & specialty: heuristic – look for known BNI class keywords */
function t(sel){var e=det.querySelector(sel);return e?e.textContent.trim().replace(/\\s+/g,' '):'';}
var company =t('[class*="company"],[class*="firm"],[class*="Firm"],[class*="unternehm"]');
var specialty=t('[class*="classif"],[class*="categ"],[class*="spec"],[class*="beruf"],[class*="wirtschaft"],[class*="branch"]');
if(done)return false; done=true; clearTimeout(timer);
try{document.body.removeChild(iframe);}catch(e){}
resolve({photoUrl:photoUrl,company:company,specialty:specialty,leaves:leaves.slice(0,10)});
return true;
}catch(e){return false;}
}
/* Poll until content appears or timeout */
var polls=0;
var poll=setInterval(function(){
polls++;
if(extract()||polls>30) clearInterval(poll);
},400);
timer=setTimeout(function(){ clearInterval(poll); finish(); },14000);
iframe.onload=function(){ /* polling starts above */ };
document.body.appendChild(iframe);
iframe.src=url;
});
}
/* ──────────────────────────────────────────────────────────────── */
/* Step 1: collect all unique member profile links */
var links = Array.from(document.querySelectorAll('a[href*="memberdetails"]'));
var seen = {}; links = links.filter(function(a){ if(seen[a.href]) return false; return (seen[a.href]=true); });
if(!links.length){ alert('Keine Mitglieder gefunden.\\nBitte warten bis alle Kacheln geladen sind.'); return; }
/* Progress overlay */
var ov = document.createElement('div');
ov.style.cssText='position:fixed;inset:0;background:rgba(0,0,0,.75);z-index:999999;display:flex;align-items:center;justify-content:center;font-family:sans-serif;';
var box = document.createElement('div');
box.style.cssText='background:#fff;border-radius:14px;padding:28px 36px;text-align:center;min-width:320px;box-shadow:0 8px 40px rgba(0,0,0,.4);';
box.innerHTML='<div style="font-weight:700;font-size:1.1rem;margin-bottom:10px;">BNI Mitglieder werden geladen…</div>'
+'<div id="_bni_prog" style="color:#555;font-size:.9rem;">0 / '+links.length+'</div>'
+'<div style="margin-top:12px;background:#eee;border-radius:6px;height:8px;"><div id="_bni_bar" style="background:#cc0000;height:8px;border-radius:6px;width:0%;transition:width .2s;"></div></div>'
+'<div style="margin-top:10px;font-size:.78rem;color:#999;">Bitte nicht weg navigieren</div>';
ov.appendChild(box); document.body.appendChild(ov);
/* Step 2: for each member fetch profile detail via AJAX endpoint */
var members = [];
for(var i=0;i<links.length;i++){
var link = links[i];
var url = new URL(link.href);
var encId = url.searchParams.get('encryptedMemberId') || '';
var rawName = (url.searchParams.get('name')||'').replace(/\\+/g,' ');
var name = decodeURIComponent(rawName);
document.getElementById('_bni_prog').textContent = (i+1)+' / '+links.length+' – '+name;
document.getElementById('_bni_bar').style.width = Math.round((i+1)/links.length*100)+'%';
var member = { name:name, company:'', specialty:'', photoUrl:'', profileUrl:link.href };
var detail = await loadProfile(link.href);
if(detail){
member.photoUrl = detail.photoUrl || '';
member.company = detail.company || '';
member.specialty = detail.specialty || '';
/* fallback: if class-based selectors missed, try leaf texts */
if(!member.company && detail.leaves && detail.leaves.length>1) member.company = detail.leaves[1]||'';
if(!member.specialty && detail.leaves && detail.leaves.length>2) member.specialty = detail.leaves[2]||'';
}
members.push(member);
}
document.body.removeChild(ov);
/* Download JSON */
var blob = new Blob([JSON.stringify({members:members,exportedAt:new Date().toISOString()},null,2)],{type:'application/json'});
var a = document.createElement('a'); a.href=URL.createObjectURL(blob); a.download='bni-mitglieder-export.json';
document.body.appendChild(a); a.click(); setTimeout(function(){ document.body.removeChild(a); },1000);
alert('✓ '+members.length+' Mitglieder exportiert!\\n('+members.filter(function(m){return m.photoUrl;}).length+' mit Foto)');
})();`;
const DIAG_CODE = `(function(){
var out = {url:location.href, cards:[]};
var links = Array.from(document.querySelectorAll('a[href*="memberdetails"]'));
var seen={}; links=links.filter(function(a){if(seen[a.href])return false;return(seen[a.href]=true);});
out.totalLinks = links.length;
/* sample first 3 cards */
links.slice(0,3).forEach(function(link){
var c=link; for(var i=0;i<10;i++){var p=c.parentElement;if(!p)break;var r=p.getBoundingClientRect();if(r.width>100&&r.height>60){c=p;break;}c=p;}
/* collect all img srcs in card */
var imgs=Array.from(c.querySelectorAll('img')).map(function(i){return{src:i.src,style:i.getAttribute('style')||'',cls:i.className};});
/* background images */
var bgImgs=[];
Array.from(c.querySelectorAll('*')).forEach(function(el){var s=window.getComputedStyle(el).backgroundImage;if(s&&s!=='none'&&s.includes('url'))bgImgs.push({cls:el.className,tag:el.tagName,bg:s});});
/* text content of direct children */
var texts=Array.from(c.querySelectorAll('*')).filter(function(el){return el.children.length===0&&el.textContent.trim();}).map(function(el){return{tag:el.tagName,cls:el.className,text:el.textContent.trim().substring(0,80)};});
out.cards.push({linkText:link.textContent.trim(),containerTag:c.tagName,containerClass:c.className,imgs:imgs,bgImgs:bgImgs,texts:texts,html:c.outerHTML.substring(0,1500)});
});
var blob=new Blob([JSON.stringify(out,null,2)],{type:'application/json'});
var a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download='bni-diagnose.json';document.body.appendChild(a);a.click();setTimeout(function(){document.body.removeChild(a);},1000);
alert('Diagnose-Datei heruntergeladen!\\nBitte in der App unter "Mitglieder aktualisieren" hochladen.');
})();`;
function openMemberSync() {
document.getElementById('bookmarklet-link').href = 'javascript:' + encodeURIComponent(BOOKMARKLET_CODE);
document.getElementById('bookmarklet-diag').href = 'javascript:' + encodeURIComponent(DIAG_CODE);
document.getElementById('sync-result').style.display = 'none';
document.getElementById('sync-file-input').value = '';
document.getElementById('diag-upload-area').style.display = 'block';
document.getElementById('diag-result').textContent = '';
openModal('modal-sync');
}
function handleDiagFile(file) {
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
try {
const diag = JSON.parse(e.target.result);
const res = document.getElementById('diag-result');
if (!diag.cards || diag.cards.length === 0) {
res.textContent = 'Keine Karten gefunden – Seite war noch nicht vollständig geladen?';
return;
}
// Analyse: find img selector that gives photos
const card = diag.cards[0];
let photoSelector = '';
let photoFound = '';
// Check real img tags
const photoImg = card.imgs.find(i =>
i.src && (i.src.includes('ImageDownload') || i.src.includes('bniconnect') || i.src.includes('cdn.bni') || i.src.includes('member'))
&& !i.src.includes('logo') && !i.src.includes('icon') && !i.src.includes('gif')
);
if (photoImg) {
photoFound = photoImg.src;
photoSelector = photoImg.cls ? `img.${photoImg.cls.trim().split(' ')[0]}` : 'img[src*="ImageDownload"],img[src*="bniconnect"]';
}
// Check background images
if (!photoFound && card.bgImgs.length) {
const bg = card.bgImgs.find(b => !b.bg.includes('gradient'));
if (bg) {
photoFound = bg.bg;
photoSelector = bg.cls ? `.${bg.cls.trim().split(' ')[0]}` : bg.tag;
}
}
// Find specialty/company text patterns
const texts = card.texts.map(t => `[${t.tag}.${t.cls}] "${t.text}"`).slice(0, 8).join('\n');
res.innerHTML = `<strong>Gefunden:</strong><br>
Foto-URL: ${photoFound ? `<span style="color:green">✓ ${photoFound.substring(0,60)}...</span>` : '<span style="color:red">✗ kein Foto-Img gefunden</span>'}<br>
Container-Klasse: <code>${card.containerClass}</code><br>
Texte in Karte:<br><pre style="font-size:0.75rem;background:#f5f5f5;padding:6px;border-radius:4px;overflow:auto;max-height:120px;">${texts}</pre>`;
// Store analysis for potential selector improvement
window._bniDiag = diag;
res.innerHTML += `<br><button class="btn btn-primary btn-sm" onclick="downloadDiagReport()">📋 Diagnose-Report herunterladen (für Entwickler)</button>`;
} catch(e) { document.getElementById('diag-result').textContent = 'Fehler: ' + e.message; }
};
reader.readAsText(file);
document.getElementById('diag-upload-area').style.display = 'block';
}
function downloadDiagReport() {
if (!window._bniDiag) return;
const blob = new Blob([JSON.stringify(window._bniDiag, null, 2)], { type: 'application/json' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'bni-diagnose-report.json';
document.body.appendChild(a); a.click();
setTimeout(() => document.body.removeChild(a), 1000);
}
function handleSyncFile(file) {
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = JSON.parse(e.target.result);
if (!data.members || !Array.isArray(data.members)) {
throw new Error('Ungültiges Format – "members"-Array fehlt.');
}
const result = syncMembers(data.members);
const el = document.getElementById('sync-result');
el.style.display = '';
el.innerHTML = `<span style="color:var(--success)">✓ Synchronisiert:</span>
${result.added} neu hinzugefügt ·
${result.updated} aktualisiert ·
${result.unchanged} unverändert`;
saveState();
renderControl();
} catch(err) {
alert('Fehler: ' + err.message);
}
};
reader.readAsText(file);
}
function syncMembers(incoming) {
let added = 0, updated = 0, unchanged = 0;
for (const inc of incoming) {
if (!inc.name || inc.name.length < 2) continue;
// Match by name (case-insensitive)
const existing = state.members.find(m =>
m.name.toLowerCase().trim() === inc.name.toLowerCase().trim()
);
if (existing) {
let changed = false;
// Update photo if we now have one and didn't before
if (inc.photoUrl && !existing.photoUrl) { existing.photoUrl = inc.photoUrl; changed = true; }
// Update specialty if empty
if (inc.specialty && !existing.specialty) { existing.specialty = inc.specialty; changed = true; }
// Update company if empty
if (inc.company && !existing.company) { existing.company = inc.company; changed = true; }
// Always update profileUrl if we got one (encryptedMemberId might change)
if (inc.profileUrl && inc.profileUrl !== existing.profileUrl) { existing.profileUrl = inc.profileUrl; changed = true; }
changed ? updated++ : unchanged++;
} else {
// New member – add to list and meeting order
const id = 'member-' + Date.now() + '-' + Math.random().toString(36).slice(2,7);
state.members.push({
id,
name: inc.name,
company: inc.company || '',
specialty: inc.specialty || '',
photoUrl: inc.photoUrl || '',
profileUrl: inc.profileUrl || '',
});
if (!state.meetingOrder.includes(id)) state.meetingOrder.push(id);
added++;
}
}
return { added, updated, unchanged };
}
// ============================================================
// TIMER ENGINE
// ============================================================
const _t = { total: 0, remaining: 0, running: false, startedAt: 0, pending: null, iv: null };
let _bellAudio = null;
let _bellUnlocked = false;
function _getBellAudio() {
if (!_bellAudio) _bellAudio = document.getElementById('bell-audio-src');
return _bellAudio;
}
function _bellAudioReady() { return _bellUnlocked; }
function _refreshSoundBtn() {
const btn = document.getElementById('sound-enable-btn');
if (!btn) return;
const inDisplay = getView() === 'display';
if (inDisplay && !_bellAudioReady()) btn.classList.add('needed');
else btn.classList.remove('needed');
}
function _unlockBellAudio() {
if (_bellUnlocked) return;
try {
const a = _getBellAudio();
a.muted = true;
const p = a.play();
if (p && p.then) {
p.then(() => {
a.pause(); a.currentTime = 0; a.muted = false;
_bellUnlocked = true; _refreshSoundBtn();
}).catch(() => {});
}
} catch(e) {}
}
['click','keydown','touchstart'].forEach(t =>
window.addEventListener(t, _unlockBellAudio, { passive: true })
);
function enableBellSound() {
_bellUnlocked = true;
playBellSound();
_refreshSoundBtn();
}
window.addEventListener('hashchange', _refreshSoundBtn);
function playBellSound() {
try {
const a = _getBellAudio();
a.muted = false;
a.currentTime = 0;
const p = a.play();
if (p && p.catch) p.catch(() => {});
} catch(e) {}
}
function fmtSecs(s) {
if (s === undefined || s === null || isNaN(s)) return '0:00';
const m = Math.floor(s / 60);
return `${m}:${String(Math.floor(s % 60)).padStart(2, '0')}`;
}
function timerCls(rem, total) {
if (!total || rem <= 0) return 'expired';
const r = rem / total;
return r <= 0.15 ? 'danger' : r <= 0.30 ? 'warn' : '';
}
function updateTimerDisplays() {
const timerOn = state.showTimer !== false;
const s = _t.remaining;
const tot = _t.total;
const cls = tot > 0 ? timerCls(s, tot) : '';
const txt = tot > 0 ? fmtSecs(s) : '';
const CIRC = 729; // 2 * PI * 116
// Control timer buttons visibility
const tc = document.getElementById('ctrl-timer-controls');
if (tc) tc.style.display = timerOn ? 'flex' : 'none';
// SVG ring
const ring = document.getElementById('timer-ring-progress');
const ringEl = document.getElementById('timer-ring-svg');
if (ring && ringEl) {
const offset = tot > 0 ? CIRC * (1 - s / tot) : 0;
ring.style.strokeDashoffset = offset;
ring.className = 'timer-ring-progress' + (cls ? ' ' + cls : '');
ringEl.style.display = (timerOn && tot > 0) ? 'block' : 'none';
}
// Display timer text
const dt = document.getElementById('display-timer-text');
if (dt) {
dt.textContent = txt;
dt.className = cls;
dt.style.display = (timerOn && tot > 0) ? 'block' : 'none';
}
// Große Header-Anzeige (zentriert oben) – gut sichtbar auch wenn Personen vor dem Podium stehen
const dht = document.getElementById('display-header-timer');
if (dht) {
dht.textContent = txt;
dht.className = cls;
dht.style.display = (timerOn && tot > 0) ? 'block' : 'none';
}
// Control timer display (live countdown)
const ct = document.getElementById('ctrl-timer-display');
if (ct) { ct.textContent = txt; ct.className = cls; }
// Main-card Hintergrund: gelb ≤15s, BNI-Rot ≤5s
const mc = document.getElementById('main-card');
if (mc) {
mc.classList.remove('timer-warn', 'timer-danger');
if (tot > 0 && !_t.pending) {
if (s <= 5) mc.classList.add('timer-danger');
else if (s <= 15) mc.classList.add('timer-warn');
}
}
// Control default timer label
const cd = document.getElementById('ctrl-timer-default');
if (cd) cd.textContent = fmtSecs(state.defaultTimer || 60);
// Toggle button label
const tb = document.getElementById('ctrl-timer-toggle');
if (tb) tb.textContent = (_t.running || _t.pending) ? '⏸ Stop' : '▶ Start';
}
function _timerTick() {
const elapsed = Math.floor((Date.now() - _t.startedAt) / 1000);
const prev = _t.remaining;
_t.remaining = Math.max(0, _t.total - elapsed);
updateTimerDisplays();
if (_t.remaining <= 0) {
clearInterval(_t.iv); _t.iv = null; _t.running = false;
if (prev > 0 && getView() === 'display') playBellSound();
}
}
function stopTimer() {
if (_t.pending) { clearTimeout(_t.pending); _t.pending = null; }
if (_t.iv) { clearInterval(_t.iv); _t.iv = null; }
_t.running = false; _t.total = 0; _t.remaining = 0;
if (channel) try { channel.postMessage({ type: 'TIMER_STOP' }); } catch(e) {}
updateTimerDisplays();
}
function toggleTimer() {
if (state.showTimer === false) return;
if (_t.running || _t.pending) {
stopTimer();
} else {
const presenter = getCurrentPresenter();
const secs = presenter ? getPresenterSeconds(presenter) : (state.defaultTimer || 60);
scheduleTimer(secs);
}
}
function resetTimer() {
const presenter = getCurrentPresenter();
const secs = presenter ? getPresenterSeconds(presenter) : (state.defaultTimer || 60);
scheduleTimer(secs);
}
function scheduleTimer(seconds) {
if (state.showTimer === false) return;
stopTimer();
_t.total = seconds; _t.remaining = seconds;
updateTimerDisplays();
_t.pending = setTimeout(() => {
_t.pending = null;
_t.startedAt = Date.now();
_t.running = true;
if (channel) try { channel.postMessage({ type: 'TIMER_START', total: seconds, startedAt: _t.startedAt }); } catch(e) {}
_t.iv = setInterval(_timerTick, 500);
updateTimerDisplays();
}, 2000);
}
// ============================================================
// 8-AUGEN-GESPRÄCHE MULTI-TIMER
// ============================================================
const _augen = { iv: null, lastSlot: {}, lastPhase: {}, prevRunning: false };
const AUGEN_WECHSEL_SEC = 3;
function _augenCfg() {
if (!state.augen) {
state.augen = { visibleSizes: [3,4,5,6], baseSeconds: 600, startedAt: null, running: false };
}
return state.augen;
}
function _augenSpeakSecondsFor(size) {
// Sprechzeit pro Redner: gleiche Gesamt-Sprechzeit pro Runde (4er = Basis)
const cfg = _augenCfg();
return (4 * cfg.baseSeconds) / size;
}
function _augenSecondsFor(size) {
// Slot = Sprechzeit + 3s Wechsel (außer letzter Slot — wird separat behandelt)
return _augenSpeakSecondsFor(size) + AUGEN_WECHSEL_SEC;
}
function _augenTableFinishAt(size) {
// (size-1) volle Slots (Sprech + Wechsel) + 1 letzter Slot (nur Sprech)
const speak = _augenSpeakSecondsFor(size);
return (size - 1) * (speak + AUGEN_WECHSEL_SEC) + speak;
}
function _augenParseTime(str) {
const s = String(str || '').trim();
if (!s) return null;
if (s.includes(':')) {
const [m, sec] = s.split(':').map(x => parseInt(x, 10) || 0);
return m * 60 + sec;
}
const n = parseFloat(s.replace(',', '.'));
if (isNaN(n) || n <= 0) return null;
// Nur Ganzzahl → Minuten; sonst als Sekunden interpretieren wenn > 60
return n < 60 ? Math.round(n * 60) : Math.round(n);
}
function _augenFmt(sec) {
const s = Math.max(0, Math.round(sec));
const m = Math.floor(s / 60);
const r = s % 60;
return `${m}:${String(r).padStart(2,'0')}`;
}
function showAugenTimer() {
if (state.mode === 'augen-timer') { augenClose(); return; }
state._prevMode = state.mode;
state.mode = 'augen-timer';
_augenCfg(); // ensure exists
saveState();
renderCurrentView();
_augenStartTick();
}
function augenClose() {
_augenStopTick();
if (state.augen) {
state.augen.running = false;
state.augen.startedAt = null;
}
state.mode = state._prevMode || 'members';
delete state._prevMode;
saveState();
renderCurrentView();
}
function augenToggleRun() {
const cfg = _augenCfg();
if (cfg.running) {
cfg.running = false;
cfg.startedAt = null;
} else {
cfg.running = true;
cfg.startedAt = Date.now();
cfg._finished = false;
_augen.lastSlot = {};
_augen.lastPhase = {};
}
saveState();
renderAugenTimer();
_augenStartTick();
}
function augenReset() {
const cfg = _augenCfg();
cfg.running = false;
cfg.startedAt = null;
cfg._finished = false;
_augen.lastSlot = {};
_augen.lastPhase = {};
saveState();
renderAugenTimer();
}
function augenToggleSize(size) {
const cfg = _augenCfg();
const list = Array.isArray(cfg.visibleSizes) ? cfg.visibleSizes.slice() : [3,4,5,6];
const i = list.indexOf(size);
if (i >= 0) list.splice(i, 1); else list.push(size);
list.sort((a,b) => a - b);
if (list.length === 0) return; // mindestens einer sichtbar
cfg.visibleSizes = list;
saveState();
renderAugenTimer();
}
function augenApplyBaseInput() {
const el = document.getElementById('augen-base-input');
if (!el) return;
const sec = _augenParseTime(el.value);
if (!sec || sec < 10) { alert('Bitte gültige Zeit eingeben (z. B. 10:00 oder 10)'); return; }
const cfg = _augenCfg();
cfg.baseSeconds = sec;
// Wenn läuft: neu starten damit Rechnung sofort greift
if (cfg.running) {
cfg.startedAt = Date.now();
_augen.lastSlot = {};
}
saveState();
renderAugenTimer();
}
function _augenStartTick() {
if (_augen.iv) clearInterval(_augen.iv);
_augen.iv = setInterval(_augenTick, 250);
}
function _augenStopTick() {
if (_augen.iv) { clearInterval(_augen.iv); _augen.iv = null; }
}
function _augenTick() {
if (state.mode !== 'augen-timer') { _augenStopTick(); return; }
const cfg = _augenCfg();
if (!cfg.running || !cfg.startedAt) return;
const elapsed = (Date.now() - cfg.startedAt) / 1000;
const sizes = cfg.visibleSizes || [3,4,5,6];
// Übergangs- und Ende-Erkennung pro Tisch.
// Sprechzeit pro Redner = 4*base/N. Slot = Sprech + 3s Wechsel (letzter Slot: nur Sprech).
let allDone = true;
sizes.forEach(size => {
const speak = _augenSpeakSecondsFor(size);
const T = speak + AUGEN_WECHSEL_SEC;
const finishAt = _augenTableFinishAt(size);
const done = elapsed >= finishAt;
if (!done) allDone = false;
const speakerIdx = Math.min(size, Math.floor(elapsed / T) + 1);
const inSlot = elapsed - (speakerIdx - 1) * T;
const phase = done ? 'done' : (inSlot < speak ? 'speak' : 'wechsel');
const prev = _augen.lastPhase[size];
_augen.lastPhase[size] = phase;
if (prev === 'speak' && phase === 'wechsel') {
playBellSound();
_augenPulse(size);
}
if (prev && prev !== 'done' && phase === 'done') {
// Tisch fertig
playBellSound();
_augenPulse(size);
}
});
if (allDone) {
cfg.running = false;
cfg.startedAt = null;
cfg._finished = true;
saveState();
}
_augenUpdateTimes();
}
function _augenPulse(size) {
const card = document.querySelector(`.augen-card[data-size="${size}"]`);
if (!card) return;
card.classList.remove('pulse');
void card.offsetWidth; // reflow für Neustart der Animation
card.classList.add('pulse');
}
function _augenUpdateTimes() {
const cfg = _augenCfg();
const running = !!(cfg.running && cfg.startedAt);
const elapsed = running ? (Date.now() - cfg.startedAt) / 1000 : 0;
const sizes = cfg.visibleSizes || [3,4,5,6];
const longestFinish = sizes.reduce((m, s) => Math.max(m, _augenTableFinishAt(s)), 0);
const finishedAll = !running && cfg._finished;
sizes.forEach(size => {
const card = document.querySelector(`.augen-card[data-size="${size}"]`);
if (!card) return;
const speak = _augenSpeakSecondsFor(size);
const T = speak + AUGEN_WECHSEL_SEC;
const finishAt = _augenTableFinishAt(size);
const tableDone = running ? elapsed >= finishAt : finishedAll;
const speakerIdx = running ? Math.min(size, Math.floor(elapsed / T) + 1) : 1;
const isLast = speakerIdx >= size;
const inSlot = running ? (elapsed - (speakerIdx - 1) * T) : 0;
const isWechsel = running && !tableDone && !isLast && inSlot >= speak;
const remSpeak = running ? Math.max(0, speak - inSlot) : speak;
const remWechsel = running ? Math.max(0, T - inSlot) : 0;
const nextIdx = Math.min(size, speakerIdx + 1);
const timeEl = card.querySelector('.augen-card-time');
const subEl = card.querySelector('.augen-card-sub');
card.classList.remove('warn','danger','wechsel','finished');
if (tableDone) {
if (timeEl) timeEl.textContent = '✓';
if (subEl) subEl.textContent = `Alle ${size} Redner durch`;
card.classList.add('finished');
} else if (isWechsel) {
if (timeEl) timeEl.textContent = 'Wechsel';
if (subEl) subEl.textContent = `Redner ${nextIdx} / ${size} in ${Math.ceil(remWechsel)}…`;
card.classList.add('wechsel');
} else {
if (timeEl) timeEl.textContent = _augenFmt(remSpeak);
if (subEl) subEl.textContent = `Redner ${speakerIdx} / ${size} · Sprechzeit ${_augenFmt(speak)}`;
if (running) {
if (remSpeak <= 10) card.classList.add('danger');
else if (remSpeak <= 30) card.classList.add('warn');
}
}
});
const info = document.getElementById('augen-round-info');
if (info) {
const totalRound = 4 * cfg.baseSeconds;
if (running) {
const remRound = Math.max(0, longestFinish - elapsed);
info.textContent = `Rundendauer ${_augenFmt(totalRound)} · Ende in ${_augenFmt(remRound)}`;
} else if (finishedAll) {
info.textContent = `Runde beendet · Rundendauer ${_augenFmt(totalRound)}`;
} else {
info.textContent = `Rundendauer ${_augenFmt(totalRound)} · bereit`;
}
}
const label = document.getElementById('augen-run-btn-label');
if (label) label.textContent = running ? '⏸ Stop' : '▶ Start';
}
function renderAugenTimer() {
const cfg = _augenCfg();
const grid = document.getElementById('augen-timer-grid');
if (!grid) return;
const sizes = (cfg.visibleSizes && cfg.visibleSizes.length ? cfg.visibleSizes : [4]).slice().sort((a,b) => a-b);
const n = sizes.length;
// Grid-Layout: 1 → 1 col, 2 → 2 cols, 3 → 3 cols, 4 → 2x2
if (n === 1) grid.style.gridTemplateColumns = '1fr';
else if (n === 2) grid.style.gridTemplateColumns = 'repeat(2, 1fr)';
else if (n === 3) grid.style.gridTemplateColumns = 'repeat(3, 1fr)';
else grid.style.gridTemplateColumns = 'repeat(2, 1fr)';
grid.style.gridAutoRows = '1fr';
grid.innerHTML = sizes.map(size => `
<div class="augen-card" data-size="${size}">
<div class="augen-card-label">${size}er Tisch</div>
<div class="augen-card-time">--:--</div>
<div class="augen-card-sub"></div>
</div>
`).join('');
// Controls-Bar nur in Control-View
const bar = document.getElementById('augen-controls-bar');
if (bar) {
const inControl = getView() !== 'display';
bar.style.display = inControl ? 'flex' : 'none';
if (inControl) {
const inp = document.getElementById('augen-base-input');
if (inp && document.activeElement !== inp) inp.value = _augenFmt(cfg.baseSeconds);
const tog = document.getElementById('augen-size-toggles');
if (tog) {
tog.innerHTML = [3,4,5,6].map(s => {
const active = (cfg.visibleSizes || []).includes(s);
return `<button class="augen-size-btn ${active ? 'active' : ''}" onclick="augenToggleSize(${s})">${s}er</button>`;
}).join('');
}
}
}
_augenUpdateTimes();
}
function getPresenterSeconds(presenter) {
const def = state.defaultTimer > 0 ? state.defaultTimer : 60;
if (!presenter) return def;
return (state.memberTimers && state.memberTimers[presenter.id]) > 0
? state.memberTimers[presenter.id] : def;
}
function renderTimerBadge(memberId) {
if (state.showTimer === false) return '';
const def = state.defaultTimer || 60;
const custom = state.memberTimers && state.memberTimers[memberId];
const secs = custom > 0 ? custom : def;
const isCustom = custom > 0 && custom !== def;
return `<button class="queue-timer-btn${isCustom ? ' custom' : ''}"
onclick="editMemberTimer('${memberId}')"
title="Redezeit anpassen">${fmtSecs(secs)}</button>`;
}
function editMemberTimer(memberId) {
const def = state.defaultTimer || 60;
const current = (state.memberTimers && state.memberTimers[memberId]) || def;
const answer = prompt(
`Individuelle Redezeit (M:SS oder Minuten)\nStandard: ${fmtSecs(def)}\nLeer lassen = Standard wiederherstellen`,
current !== def ? fmtSecs(current) : ''
);
if (answer === null) return;
const trimmed = answer.trim();
if (!trimmed) {
if (state.memberTimers) delete state.memberTimers[memberId];
} else {
const parts = trimmed.split(':');
const secs = parts.length === 2
? parseInt(parts[0]) * 60 + parseInt(parts[1])
: parseInt(parts[0]) * 60;
if (!isNaN(secs) && secs > 0) {
if (!state.memberTimers) state.memberTimers = {};
state.memberTimers[memberId] = secs;
} else {
if (state.memberTimers) delete state.memberTimers[memberId];
}
}
saveState();
renderControl();
}
function toggleFullscreen() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().catch(() => {});
} else {
document.exitFullscreen().catch(() => {});
}
}
document.addEventListener('fullscreenchange', () => {
const btn = document.getElementById('fullscreen-btn');
if (btn) btn.textContent = document.fullscreenElement ? '✕' : '⛶';
});
function editDefaultTimer() {
const current = state.defaultTimer || 60;
const answer = prompt(
`Standard-Redezeit für alle Mitglieder (M:SS oder Minuten)\nAktuell: ${fmtSecs(current)}`,
fmtSecs(current)
);
if (answer === null) return;
const parts = answer.trim().split(':');
const secs = parts.length === 2
? parseInt(parts[0]) * 60 + parseInt(parts[1])
: parseInt(parts[0]) * 60;
if (!isNaN(secs) && secs > 0) {
state.defaultTimer = secs;
saveState();
renderControl();
}
}
// ============================================================
// QUEUE RESIZE HANDLE
// ============================================================
let _queueResizeRatio = parseFloat(localStorage.getItem('bni-queue-ratio') || '0.6');
function setQueueResizeVisible(show) {
const handle = document.getElementById('queue-resize-handle');
const queueEl = document.getElementById('member-queue');
if (!handle) return;
if (show) {
handle.style.display = 'flex';
applyQueueRatio(_queueResizeRatio);
} else {
handle.style.display = 'none';
queueEl.style.flex = '1';
queueEl.style.height = '';
const absentEl = document.getElementById('absent-members-section');
if (absentEl) absentEl.style.height = '';
}
}
function applyQueueRatio(ratio) {
_queueResizeRatio = Math.max(0.15, Math.min(0.85, ratio));
const panelLeft = document.querySelector('.panel-left');
const header = panelLeft.querySelector('.panel-section-header');
const handle = document.getElementById('queue-resize-handle');
const queueEl = document.getElementById('member-queue');
const absentEl = document.getElementById('absent-members-section');
if (!panelLeft || !queueEl || !absentEl) return;
const available = panelLeft.clientHeight
- (header ? header.offsetHeight : 0)
- (handle ? handle.offsetHeight : 0);
const queueH = Math.round(available * _queueResizeRatio);
const absentH = available - queueH;
queueEl.style.flex = 'none';
queueEl.style.height = queueH + 'px';
absentEl.style.flex = 'none';
absentEl.style.height = absentH + 'px';
}
(function initQueueResize() {
const handle = document.getElementById('queue-resize-handle');
if (!handle) return;
let dragging = false, startY = 0, startQH = 0;
handle.addEventListener('mousedown', e => {
dragging = true;
startY = e.clientY;
startQH = document.getElementById('member-queue').getBoundingClientRect().height;
handle.classList.add('is-dragging');
document.body.style.cursor = 'row-resize';
document.body.style.userSelect = 'none';
e.preventDefault();
});
document.addEventListener('mousemove', e => {
if (!dragging) return;
const panelLeft = document.querySelector('.panel-left');
const hdr = panelLeft.querySelector('.panel-section-header');
const hdl = document.getElementById('queue-resize-handle');
const available = panelLeft.clientHeight
- (hdr ? hdr.offsetHeight : 0)
- (hdl ? hdl.offsetHeight : 0);
const newQH = startQH + (e.clientY - startY);
applyQueueRatio(newQH / available);
});
document.addEventListener('mouseup', () => {
if (!dragging) return;
dragging = false;
handle.classList.remove('is-dragging');
document.body.style.cursor = '';
document.body.style.userSelect = '';
localStorage.setItem('bni-queue-ratio', _queueResizeRatio);
});
// Touch support
handle.addEventListener('touchstart', e => {
dragging = true;
startY = e.touches[0].clientY;
startQH = document.getElementById('member-queue').getBoundingClientRect().height;
e.preventDefault();
}, { passive: false });
document.addEventListener('touchmove', e => {
if (!dragging) return;
const panelLeft = document.querySelector('.panel-left');
const hdr = panelLeft.querySelector('.panel-section-header');
const hdl = document.getElementById('queue-resize-handle');
const available = panelLeft.clientHeight
- (hdr ? hdr.offsetHeight : 0)
- (hdl ? hdl.offsetHeight : 0);
const newQH = startQH + (e.touches[0].clientY - startY);
applyQueueRatio(newQH / available);
e.preventDefault();
}, { passive: false });
document.addEventListener('touchend', () => {
if (!dragging) return;
dragging = false;
localStorage.setItem('bni-queue-ratio', _queueResizeRatio);
});
})();
// ============================================================
// CHAPTER ACTIVATION & LICENSE
// ============================================================
async function activateChapter() {
const input = document.getElementById('activation-code-input');
const code = (input ? input.value : '').trim().toLowerCase();
if (!code) return;
const btn = document.getElementById('activation-btn');
const status = document.getElementById('activation-status');
btn.disabled = true;
status.textContent = 'Überprüfe Lizenzcode…';
status.style.color = 'rgba(255,255,255,0.6)';
try {
const res = await fetch(giteaDataUrl(`chapters/${code}/config.json`), { cache: 'no-store' });
if (!res.ok) {
status.textContent = 'Ungültiger oder abgelaufener Lizenzcode.';
status.style.color = '#f87171';
btn.disabled = false;
return;
}
const config = await res.json();
activeChapter = { code, config, activatedAt: new Date().toISOString(), lastChecked: new Date().toISOString() };
saveChapterConfig(activeChapter);
STORAGE_KEY = `bni-chapter-${code}-v2`;
EXTRAS_KEY = `bni-extras-${code}`;
state = loadState();
if (config.defaultTimer && !state.meetingOrder.length) {
state.defaultTimer = config.defaultTimer;
saveState(false);
}
applyChapterUI(config);
status.textContent = `✓ Chapter "${config.chapterName}" aktiviert!`;
status.style.color = '#4ade80';
setTimeout(() => {
renderCurrentView();
fetchChapterMembersFromGitea();
fetchChapterAgendaFromGitea();
}, 800);
} catch(e) {
status.textContent = 'Verbindungsfehler. Bitte Internetverbindung prüfen.';
status.style.color = '#f87171';
btn.disabled = false;
}
}
function applyChapterUI(config) {
if (!config) return;
const name = config.chapterName || 'BNI Chapter';
document.title = `${name} – Vorstellungsrunde`;
const el = document.getElementById('display-chapter-name');
if (el) el.textContent = name;
const ctrlName = document.getElementById('ctrl-chapter-name');
if (ctrlName) ctrlName.textContent = `${name} – Steuerung`;
if (config.logoUrl && activeChapter) {
const src = config.logoUrl.startsWith('http') ? config.logoUrl
: giteaDataUrl(`chapters/${activeChapter.code}/${config.logoUrl}`);
['display-logo','display-logo-empty','ctrl-logo'].forEach(id => {
const img = document.getElementById(id);
if (img) img.src = src;
});
}
const syncLink = document.getElementById('bni-sync-link');
if (syncLink && config.syncUrl) {
syncLink.href = config.syncUrl;
syncLink.textContent = name + ' – Mitgliederliste';
}
const coordBtn = document.getElementById('btn-coordinator-report');
if (coordBtn) coordBtn.style.display = config.memberCoordinatorReport ? '' : 'none';
}
async function checkLicenseAndUpdates() {
if (!activeChapter) return;
try {
const res = await fetch(giteaDataUrl(`chapters/${activeChapter.code}/config.json`), { cache: 'no-store' });
if (!res.ok) {
clearChapterData();
state = defaultState();
alert('Deine Lizenz wurde widerrufen. Bitte gib einen neuen Code ein.');
renderCurrentView();
return;
}
const config = await res.json();
activeChapter.config = config;
activeChapter.lastChecked = new Date().toISOString();
saveChapterConfig(activeChapter);
applyChapterUI(config);
await checkForUpdates();
} catch(e) {
// Offline → continue with cached data
const dot = document.getElementById('sync-dot');
if (dot) dot.title = 'Offline – letzte Prüfung: ' + (activeChapter.lastChecked ? new Date(activeChapter.lastChecked).toLocaleDateString('de') : 'unbekannt');
}
}
async function checkForUpdates() {
try {
const res = await fetch(giteaUrl('version.json'), { cache: 'no-store' });
if (!res.ok) return;
const data = await res.json();
if (data.version && data.version > APP_VERSION) showUpdateBanner(data.version, data.notes);
} catch(e) { /* ignore */ }
}
function showUpdateBanner(version, notes) {
let banner = document.getElementById('update-banner');
if (!banner) {
banner = document.createElement('div');
banner.id = 'update-banner';
banner.style.cssText = 'background:#1a2744;color:white;padding:8px 16px;display:flex;align-items:center;gap:12px;font-size:0.85rem;flex-shrink:0;z-index:100;';
const ctrlView = document.getElementById('view-control');
ctrlView.insertBefore(banner, ctrlView.firstChild);
}
banner.innerHTML = `🆕 Update ${version} verfügbar${notes ? ' – ' + notes : ''} &nbsp;<button onclick="downloadUpdate('${version}')" style="background:#cc0000;color:white;border:none;border-radius:6px;padding:4px 12px;cursor:pointer;font-size:0.82rem;font-weight:600;">Jetzt laden</button> <button onclick="this.parentElement.style.display='none'" style="background:transparent;border:none;color:rgba(255,255,255,0.5);cursor:pointer;margin-left:auto;font-size:1rem;">✕</button>`;
}
async function downloadUpdate(version) {
try {
const licRes = await fetch(giteaDataUrl(`chapters/${activeChapter.code}/config.json`), { cache: 'no-store' });
if (!licRes.ok) {
clearChapterData();
state = defaultState();
alert('Lizenz nicht mehr gültig. Bitte neuen Code eingeben.');
renderCurrentView();
return;
}
} catch(e) { alert('Keine Serververbindung möglich.'); return; }
let html, adminHtml;
try {
const [r1, r2] = await Promise.all([
fetch(giteaUrl('index.html'), { cache: 'no-store' }),
fetch(giteaUrl('admin.html'), { cache: 'no-store' }),
]);
html = await r1.text();
adminHtml = await r2.text();
} catch(e) { alert('Download fehlgeschlagen: ' + e.message); return; }
// admin.html immer als klassischen Download starten (läuft parallel, kein zweiter Dialog nötig)
const adminBlob = new Blob([adminHtml], { type: 'text/html' });
const adminA = document.createElement('a');
adminA.href = URL.createObjectURL(adminBlob);
adminA.download = 'admin.html';
adminA.click();
URL.revokeObjectURL(adminA.href);
if ('showSaveFilePicker' in window) {
try {
const fh = await window.showSaveFilePicker({
suggestedName: 'index.html',
types: [{ description: 'HTML-Datei', accept: { 'text/html': ['.html'] } }],
id: 'bni-update',
startIn: 'documents',
});
const w = await fh.createWritable();
await w.write(html);
await w.close();
alert('index.html wurde gespeichert.\n\nadmin.html liegt im Download-Ordner – bitte in denselben Ordner verschieben und die alte ersetzen.\n\nDanach Seite neu laden.');
location.reload();
return;
} catch(e) {
if (e.name === 'AbortError') return;
// Fall through to classic download
}
}
// Fallback für Browser ohne File System Access API
const blob = new Blob([html], { type: 'text/html' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'index.html';
a.click();
URL.revokeObjectURL(a.href);
alert('index.html und admin.html wurden heruntergeladen.\nBitte beide Dateien in den BNI-Ordner verschieben und die alten ersetzen, dann Seite neu laden.');
}
async function fetchChapterMembersFromGitea() {
if (!activeChapter) return;
try {
const res = await fetch(giteaDataUrl(`chapters/${activeChapter.code}/members.json`), { cache: 'no-store' });
if (!res.ok) return;
const data = await res.json();
if (!data.members || !data.members.length) return;
const extras = loadExtras();
const mergedMap = new Map(state.members.map(m => [m.id, m]));
data.members.forEach(m => {
const ex = mergedMap.get(m.id);
mergedMap.set(m.id, ex ? Object.assign({}, ex, m) : m);
// Embed photoData into extras so existing photo display picks it up
if (m.photoData) {
const key = extrasKey(m.name);
if (!extras[key]) extras[key] = {};
extras[key].offlinePhotoB64 = m.photoData;
}
});
saveExtras(extras);
state.members = Array.from(mergedMap.values());
const knownIds = new Set(state.meetingOrder);
state.members.forEach(m => { if (!knownIds.has(m.id)) state.meetingOrder.push(m.id); });
activeChapter.lastMemberSync = new Date().toISOString();
saveChapterConfig(activeChapter);
saveState(false);
renderCurrentView();
renderAdminPanel();
} catch(e) { /* ignore */ }
}
function buildMembersJson() {
const extras = loadExtras();
const members = state.members.map(m => {
const ex = extras[extrasKey(m.name)] || {};
const entry = { id: m.id, name: m.name, company: m.company, specialty: m.specialty, photoUrl: m.photoUrl || '', profileUrl: m.profileUrl || '' };
if (ex.offlinePhotoB64) entry.photoData = ex.offlinePhotoB64;
return entry;
});
return { lastUpdated: new Date().toISOString().slice(0, 10), members };
}
function saveAdminToken() {
const val = (document.getElementById('admin-token-input').value || '').trim();
if (val) localStorage.setItem('bni-gitea-admin-token', val);
else localStorage.removeItem('bni-gitea-admin-token');
renderAdminPanel();
}
async function uploadMembersToGitea() {
const token = localStorage.getItem('bni-gitea-admin-token');
if (!token) { alert('Bitte zuerst den Admin-Token speichern.'); return; }
const statusEl = document.getElementById('upload-status');
statusEl.textContent = 'Lädt hoch…';
statusEl.style.color = 'var(--muted)';
const data = buildMembersJson();
const jsonStr = JSON.stringify(data, null, 2);
const content = btoa(unescape(encodeURIComponent(jsonStr)));
const path = `chapters/${activeChapter.code}/members.json`;
try {
// Get current SHA
let sha = null;
const shaRes = await fetch(giteaAuthUrl(path, token, 'data'));
if (shaRes.ok) sha = (await shaRes.json()).sha;
const body = { message: `Mitglieder aktualisiert ${new Date().toLocaleDateString('de')}`, content, branch: 'data' };
if (sha) body.sha = sha;
const res = await fetch(giteaAuthUrl(path, token, 'data'), {
method: sha ? 'PUT' : 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
if (res.ok) {
activeChapter.lastMemberUpload = new Date().toISOString();
saveChapterConfig(activeChapter);
statusEl.textContent = `✓ ${data.members.length} Mitglieder hochgeladen`;
statusEl.style.color = 'var(--success)';
} else {
statusEl.textContent = 'Fehler beim Upload. Token prüfen.';
statusEl.style.color = '#dc2626';
}
} catch(e) {
statusEl.textContent = 'Verbindungsfehler.';
statusEl.style.color = '#dc2626';
}
}
function renderAdminPanel() {
const panel = document.getElementById('admin-panel');
if (!panel) return;
const cfg = activeChapter && activeChapter.config;
const code = activeChapter && activeChapter.code;
const lastChecked = activeChapter && activeChapter.lastChecked ? new Date(activeChapter.lastChecked).toLocaleString('de') : '—';
const lastSync = activeChapter && activeChapter.lastMemberSync ? new Date(activeChapter.lastMemberSync).toLocaleString('de') : '—';
const lastUpload = activeChapter && activeChapter.lastMemberUpload ? new Date(activeChapter.lastMemberUpload).toLocaleString('de') : '—';
const hasToken = !!localStorage.getItem('bni-gitea-admin-token');
panel.innerHTML = `<div style="padding:16px;overflow-y:auto;height:100%;">
<div style="font-weight:700;font-size:0.9rem;margin-bottom:14px;">Chapter-Administration</div>
${cfg ? `
<div style="background:#f8f9fa;border:1px solid var(--border);border-radius:8px;padding:12px;margin-bottom:14px;">
<div style="font-size:0.78rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:1px;margin-bottom:6px;">Aktives Chapter</div>
<div style="font-weight:600;margin-bottom:2px;">${cfg.chapterName || '—'}</div>
<div style="font-size:0.82rem;color:var(--muted);">Version: <strong>${APP_VERSION}</strong></div>
<div style="font-size:0.82rem;color:var(--muted);">Lizenzprüfung: ${lastChecked}</div>
</div>
<button class="btn btn-secondary btn-sm" style="width:100%;margin-bottom:6px;" onclick="fetchChapterMembersFromGitea()">🔄 Mitglieder vom Server laden</button>
<div style="font-size:0.78rem;color:var(--muted);margin-bottom:16px;">Letzter Download: ${lastSync}</div>
<a href="admin.html" target="_blank" class="btn btn-primary btn-sm" style="width:100%;display:block;text-align:center;text-decoration:none;margin-bottom:16px;">⚙ Admin-Panel öffnen</a>
` : `<div style="color:var(--muted);font-size:0.88rem;margin-bottom:20px;">Kein Chapter aktiviert.</div>`}
<div style="border-top:1px solid var(--border);padding-top:14px;margin-bottom:14px;">
<div style="font-size:0.78rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:1px;margin-bottom:10px;">Einstellungen</div>
<label style="display:flex;align-items:center;gap:10px;cursor:pointer;font-size:0.88rem;">
<input type="checkbox" id="admin-show-timer" ${state.showTimer !== false ? 'checked' : ''}
onchange="state.showTimer=this.checked;saveState();updateTimerDisplays();renderMemberQueue();renderVisitorQueue();renderVisitorsList();"
style="width:16px;height:16px;cursor:pointer;">
Zeitanzeige aktiv
</label>
</div>
<div style="border-top:1px solid var(--border);padding-top:14px;margin-bottom:14px;">
<div style="font-size:0.78rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:1px;margin-bottom:10px;">8-Augen-Gespräche</div>
<button class="btn ${state.mode === 'augen-timer' ? 'btn-primary' : 'btn-secondary'} btn-sm" style="width:100%;" onclick="showAugenTimer()">
${state.mode === 'augen-timer' ? '✕ 8-Augen Timer beenden' : '👥 8-Augen Timer starten'}
</button>
<div style="font-size:0.75rem;color:var(--muted);margin-top:6px;">Multi-Tisch-Timer für 3er/4er/5er/6er-Tische. Zeit und sichtbare Tische im Vollbild einstellbar.</div>
</div>
<div style="border-top:1px solid var(--border);padding-top:14px;">
<button class="btn btn-ghost btn-sm" style="width:100%;" onclick="confirmSwitchChapter()">↔ Chapter wechseln</button>
</div>
</div>`;
}
function confirmSwitchChapter() {
if (!confirm('Chapter wechseln?\n\nAlle lokalen Daten dieses Chapters werden gelöscht. Fortfahren?')) return;
clearChapterData();
state = defaultState();
renderCurrentView();
}
// ============================================================
// COORDINATOR REPORT
// ============================================================
const _COORD_CUMULATIVE = {
attendance: false, members: false,
newMembers: true, guests: true,
oneOnOne: true, cte: true,
referrals: true, revenueThank: true,
};
function _getBniYear() {
const now = new Date();
const m = now.getMonth();
const y = now.getFullYear();
return m >= 3 ? `${y}-${y + 1}` : `${y - 1}-${y}`;
}
function _monthToIdx(monthStr, y1) {
const [y, m] = monthStr.split('-').map(Number);
return y === y1 ? m - 4 : m + 8; // Apr=0 … Dec=8, Jan=9 … Mar=11
}
const _MON_DE = ['Jan','Feb','Mär','Apr','Mai','Jun','Jul','Aug','Sep','Okt','Nov','Dez'];
function _monthAbbr(monthStr) {
const m = parseInt((monthStr || '').split('-')[1], 10);
return _MON_DE[m - 1] || '';
}
function makeSparklineSvg(entries, annualTarget, yearStr, isCumulative) {
const W = 200, H = 56;
const y1 = parseInt((yearStr || '2025-2026').split('-')[0]);
// Today's month index (0–11 in BNI year)
const now = new Date();
const todayIdx = _monthToIdx(
`${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`, y1
);
const todayX = ((Math.max(0, Math.min(11, todayIdx)) + 0.5) / 12 * W).toFixed(1);
// Filter to entries with `month` field (new format)
const sorted = [...(entries || [])]
.filter(e => e.month)
.sort((a, b) => a.month.localeCompare(b.month));
if (sorted.length === 0) {
return `<svg viewBox="0 0 ${W} ${H}" style="width:100%;height:100%;" preserveAspectRatio="none">
<line x1="${todayX}" y1="0" x2="${todayX}" y2="${H}" stroke="rgba(255,255,255,0.1)" stroke-width="1" stroke-dasharray="3,3"/>
<text x="${W/2}" y="${H/2+4}" text-anchor="middle" fill="rgba(255,255,255,0.2)" font-size="9" font-family="sans-serif">Keine Daten</text></svg>`;
}
const toX = monthStr => ((Math.max(0, Math.min(11, _monthToIdx(monthStr, y1))) + 0.5) / 12 * W);
const vals = sorted.map(e => e.value);
const yMax = Math.max(...vals, annualTarget || 0, 1) * 1.08;
const toY = v => H - (v / yMax) * H * 0.9;
const pts = sorted.map(e => [toX(e.month), toY(e.value)]);
const linePath = pts.map((p, i) => `${i ? 'L' : 'M'}${p[0].toFixed(1)},${p[1].toFixed(1)}`).join(' ');
const areaPath = `${linePath} L${pts[pts.length-1][0].toFixed(1)},${H} L${pts[0][0].toFixed(1)},${H}Z`;
// Dynamic target line: starts from first actual value, ends at annualTarget in March
let targetPath, currentTarget;
if (isCumulative) {
const firstIdx = _monthToIdx(sorted[0].month, y1);
const firstVal = sorted[0].value;
const span = 11 - firstIdx;
const startX = ((firstIdx + 0.5) / 12 * W).toFixed(1);
const endX = ((11.5) / 12 * W).toFixed(1);
targetPath = `M${startX},${toY(firstVal).toFixed(1)} L${endX},${toY(annualTarget).toFixed(1)}`;
const clampedToday = Math.max(firstIdx, Math.min(11, todayIdx));
currentTarget = span > 0
? firstVal + (annualTarget - firstVal) * ((clampedToday - firstIdx) / span)
: annualTarget;
} else {
const ty = toY(annualTarget).toFixed(1);
targetPath = `M0,${ty} L${W},${ty}`;
currentTarget = annualTarget;
}
const latestVal = sorted[sorted.length - 1].value;
const onTrack = currentTarget > 0 ? latestVal >= currentTarget : latestVal > 0;
const col = onTrack ? '#22c55e' : '#ef4444';
const lp = pts[pts.length - 1];
return `<svg viewBox="0 0 ${W} ${H}" style="width:100%;height:100%;" preserveAspectRatio="none">
<path d="${areaPath}" fill="${col}" fill-opacity="0.12"/>
<path d="${targetPath}" stroke="rgba(255,255,255,0.28)" stroke-width="1.5" stroke-dasharray="4,3" fill="none"/>
<line x1="${todayX}" y1="0" x2="${todayX}" y2="${H}" stroke="rgba(255,255,255,0.12)" stroke-width="1" stroke-dasharray="3,3"/>
<path d="${linePath}" stroke="${col}" stroke-width="2.5" fill="none" stroke-linejoin="round" stroke-linecap="round"/>
<circle cx="${lp[0].toFixed(1)}" cy="${lp[1].toFixed(1)}" r="3.5" fill="${col}"/>
</svg>`;
}
function renderCoordinatorReport(data) {
const grid = document.getElementById('coord-report-grid');
const updatedEl = document.getElementById('coord-report-updated');
if (!grid) return;
if (!data || !data.metrics || !data.metrics.length) {
grid.innerHTML = '<div style="color:rgba(255,255,255,0.4);grid-column:1/-1;display:flex;align-items:center;justify-content:center;font-size:1.1rem;">Keine Berichtsdaten vorhanden.</div>';
if (updatedEl) updatedEl.textContent = '';
return;
}
const yearStr = data.year || _getBniYear();
const y1 = parseInt(yearStr.split('-')[0]);
const now = new Date();
const todayMonthIdx = _monthToIdx(
`${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`, y1
);
if (updatedEl) updatedEl.textContent = `BNI-Jahr ${yearStr.replace('-', '/')}${data.updatedAt ? ' · Stand: ' + data.updatedAt : ''}`;
grid.innerHTML = '';
for (const m of data.metrics) {
const isCumulative = _COORD_CUMULATIVE[m.key] !== false;
const annualTarget = m.annualTarget || 0;
const entries = m.entries || [];
const sorted = [...entries].filter(e => e.month).sort((a, b) => a.month.localeCompare(b.month));
const latestEntry = sorted[sorted.length - 1];
const latestVal = latestEntry ? latestEntry.value : null;
// Dynamic target: linear from first actual value to annualTarget by March
let currentTarget, monthlyNeeded = null;
if (isCumulative && sorted.length > 0) {
const firstVal = sorted[0].value;
const firstIdx = _monthToIdx(sorted[0].month, y1);
const span = 11 - firstIdx;
const clampedToday = Math.max(firstIdx, Math.min(11, todayMonthIdx));
currentTarget = span > 0
? firstVal + (annualTarget - firstVal) * ((clampedToday - firstIdx) / span)
: annualTarget;
if (latestVal !== null && annualTarget > 0) {
const latestIdx = _monthToIdx(latestEntry.month, y1);
const monthsLeft = 11 - latestIdx;
if (monthsLeft > 0) monthlyNeeded = (annualTarget - latestVal) / monthsLeft;
}
} else {
currentTarget = annualTarget;
}
const onTrack = latestVal !== null && currentTarget > 0 ? latestVal >= currentTarget : null;
const valColor = onTrack === null ? 'rgba(255,255,255,0.5)' : onTrack ? '#22c55e' : '#ef4444';
const progressPct = annualTarget > 0 && latestVal !== null ? Math.round((latestVal / annualTarget) * 100) : null;
const monthlyHtml = sorted.length > 0
? `<div style="display:flex;flex-wrap:wrap;gap:clamp(4px,0.8vw,12px);font-size:clamp(0.55rem,0.85vw,0.78rem);color:rgba(255,255,255,0.7);justify-content:center;line-height:1.25;">
${sorted.map(e => `<span><span style="color:rgba(255,255,255,0.35);font-weight:600;">${_monthAbbr(e.month)}</span> <span style="font-weight:700;">${e.value}${m.unit || ''}</span></span>`).join('')}
</div>`
: '';
const tile = document.createElement('div');
tile.style.cssText = 'background:rgba(255,255,255,0.07);border-radius:12px;padding:clamp(8px,1.2vw,14px);display:flex;flex-direction:column;gap:clamp(4px,0.6vh,8px);overflow:hidden;';
tile.innerHTML = `
<div style="font-size:clamp(0.5rem,0.8vw,0.7rem);color:rgba(255,255,255,0.45);font-weight:700;text-transform:uppercase;letter-spacing:0.5px;">${escHtml(m.label)}${m.unit ? ' (' + m.unit + ')' : ''}</div>
<div style="flex:1;min-height:0;">${makeSparklineSvg(entries, annualTarget, yearStr, isCumulative)}</div>
${monthlyHtml}
<div style="display:flex;justify-content:space-between;align-items:baseline;gap:4px;">
<div style="font-size:clamp(0.95rem,2vw,1.7rem);font-weight:700;color:${valColor};">${latestVal !== null ? latestVal + (m.unit || '') : '—'}</div>
<div style="font-size:clamp(0.5rem,0.85vw,0.72rem);color:rgba(255,255,255,0.28);text-align:right;line-height:1.3;">
Ziel: ${annualTarget}${m.unit || ''}${progressPct !== null ? `<br><span style="color:${valColor};">${progressPct}%</span>` : ''}${monthlyNeeded !== null ? `<br><span style="color:rgba(255,255,255,0.45);">${monthlyNeeded >= 0 ? '+' : ''}${monthlyNeeded.toFixed(1)}/Mo</span>` : ''}
</div>
</div>`;
grid.appendChild(tile);
}
}
async function showCoordinatorReport() {
if (!activeChapter) return;
if (state.mode === 'coordinator-report') { _coordReportClose(); return; }
const btn = document.getElementById('btn-coordinator-report');
if (btn) { btn.disabled = true; btn.textContent = 'Lade…'; }
// Try current BNI year and the two preceding ones, plus legacy unsuffixed file.
// Pick whichever file has the most recent entry month — protects against
// stale data sitting in a year file the user hasn't updated.
const currentYear = _getBniYear();
const y1 = parseInt(currentYear.split('-')[0]);
const candidates = [
`coordinator-report-${currentYear}.json`,
`coordinator-report-${y1 - 1}-${y1}.json`,
`coordinator-report-${y1 - 2}-${y1 - 1}.json`,
'coordinator-report.json',
];
const datas = await Promise.all(candidates.map(async f => {
try {
const res = await fetch(giteaDataUrl(`chapters/${activeChapter.code}/${f}`), { cache: 'no-store' });
if (!res.ok) return null;
return await res.json();
} catch(e) { return null; }
}));
let reportData = null;
let bestLatestMonth = '';
for (const d of datas) {
if (!d || !Array.isArray(d.metrics)) continue;
let latestMonth = '';
for (const m of d.metrics) {
for (const e of (m.entries || [])) {
if (e.month && e.month > latestMonth) latestMonth = e.month;
}
}
if (latestMonth && latestMonth > bestLatestMonth) {
bestLatestMonth = latestMonth;
reportData = d;
}
}
if (btn) { btn.disabled = false; btn.textContent = '📊 Koordinatoren-Bericht'; }
state._coordPrevMode = state.mode;
state.mode = 'coordinator-report';
state.coordinatorReportData = reportData;
saveState();
renderControl();
renderDisplay();
}
// ============================================================
// INIT
// ============================================================
initChannel();
if (activeChapter) applyChapterUI(activeChapter.config);
if (!activeChapter) {
const urlChapterCode = new URLSearchParams(window.location.search).get('chapter');
if (urlChapterCode) {
document.getElementById('activation-code-input').value = urlChapterCode;
activateChapter();
}
}
renderCurrentView();
updateTimerDisplays();
checkLicenseAndUpdates();
if (activeChapter) fetchChapterAgendaFromGitea();
// Keyboard shortcut hint
if (getView() === 'control') {
console.log('Tastaturkürzel: Leertaste oder → = Weiter');
}
</script>
</body>
</html>