5881 lines
328 KiB
HTML
5881 lines
328 KiB
HTML
<!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- & 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®ionIds=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 & 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.2';
|
||
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> → Rechtsklick → Bild speichern → 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, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"');
|
||
}
|
||
|
||
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 : ''} <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>
|