Compare commits
125 Commits
24808e35f6
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| baf236ea40 | |||
| e9a98e7423 | |||
| b6fb0f519b | |||
| 7893110125 | |||
| 7b024ae919 | |||
| d6e6809dd7 | |||
| b0c83b7b0c | |||
|
|
7c3523959e | ||
|
|
2bcf6095ab | ||
| bb9186d759 | |||
| 29f4e91e38 | |||
| ae68d34829 | |||
| 22561af0a4 | |||
| 719bb05610 | |||
| 3ef838a58b | |||
| 475aa65bc0 | |||
| 24eee60cf1 | |||
| bd1575500e | |||
| 4e2dacc50f | |||
| 9d48b427ba | |||
| 294a1be11c | |||
| 958f8e13c7 | |||
| 8aee368200 | |||
| 8c26f26e47 | |||
| b1cd3b1c5f | |||
| 64fb49f4bc | |||
| 6fa3d62d36 | |||
| f993ac91b2 | |||
| be377caf51 | |||
| 9df667e7ee | |||
| b3f4f41bfa | |||
|
|
5d9427c7a6 | ||
|
|
08340b5aec | ||
|
|
8127f87e03 | ||
|
|
95df518900 | ||
|
|
f031e94fd8 | ||
|
|
22997c1add | ||
|
|
f15dba0786 | ||
|
|
7841c4854b | ||
| 3b4c16e85d | |||
| 9906eb03fe | |||
|
|
760a6aadfa | ||
|
|
66134424e2 | ||
|
|
706a91b193 | ||
| ea876fb29d | |||
| 370a3f6cd8 | |||
| 7d96a3b5ad | |||
| ca1902c7bb | |||
| 64a2c5b279 | |||
| de5f560ee7 | |||
| d526b97873 | |||
| 9e00549391 | |||
| 9366fa71af | |||
| 45400cd71e | |||
| c5e5895303 | |||
| 36e001ae97 | |||
| c1a5eaf653 | |||
| 11a97b62f0 | |||
| 8a629399ce | |||
| e2a9423a6a | |||
| a309d32d52 | |||
| 6f1800d9a6 | |||
| 5229c614f4 | |||
|
|
580725f2f7 | ||
| b40f384123 | |||
| 2b23bf8abc | |||
| 8164e2c7bc | |||
| 443eb138ec | |||
| 7b4b6a3f92 | |||
| 7f2ebd6bb4 | |||
| e66612de93 | |||
| 6f70a6bf71 | |||
| 1d53f7b41a | |||
| 2865895a67 | |||
| d05f1396a2 | |||
| 30f0290363 | |||
| a03740c2b4 | |||
| 0debe09f64 | |||
| 1a89a62149 | |||
| 347b682576 | |||
| 9a5a8d1f2b | |||
| b5d62ce51e | |||
| 2cdb378883 | |||
| e719ccbee0 | |||
| 28b0338a15 | |||
| 0651fb4752 | |||
| b11e9afa6a | |||
| 7ad0ab8114 | |||
| c0b9a6cbf8 | |||
| cf94ffd320 | |||
| 8df0d03f8f | |||
| 26882ae897 | |||
| 1796145bb3 | |||
| 7e19ede38d | |||
| f20f67ebc0 | |||
| ad52836ca4 | |||
| d810ca68a0 | |||
| d4aea00b4d | |||
| 677ba40104 | |||
| f9048c5826 | |||
| e208bd2cf1 | |||
| 3548dea926 | |||
| f4dfa39b7d | |||
| d52e28c3d5 | |||
| c9991a4192 | |||
| 88d9fac5da | |||
| 66de72edc0 | |||
| 69670dd58f | |||
| 0d16aecf3a | |||
| 2d8931d140 | |||
| 5a6778de40 | |||
| 4506dbf8f6 | |||
| c21f5605a2 | |||
| a336e55759 | |||
| 9ce7c00b69 | |||
| 462d97b6cf | |||
| c6a682fc62 | |||
| 611373b8d2 | |||
| d4303dce98 | |||
| 6399f714fb | |||
| f802da4a46 | |||
| 2170f3b8a4 | |||
| 62b5fcbfd6 | |||
| 6429b45fff | |||
| 7e7e1bdff0 |
4
.gitignore
vendored
@@ -1,2 +1,6 @@
|
||||
Push
|
||||
.DS_Store
|
||||
screenshots/
|
||||
anleitung.html
|
||||
BNI-Vorstellungsrunde-Anleitung.pdf
|
||||
start-*.html
|
||||
|
||||
354
admin.html
@@ -4,6 +4,9 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>BNI Admin-Panel</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">
|
||||
<style>
|
||||
:root {
|
||||
--red: #cc0000; --red-dark: #990000;
|
||||
@@ -102,6 +105,7 @@
|
||||
<div class="tabs">
|
||||
<div class="tab active" onclick="switchAdminTab('stammvertreter')">Stammvertreter</div>
|
||||
<div class="tab" onclick="switchAdminTab('mitglieder')">Mitglieder</div>
|
||||
<div class="tab" onclick="switchAdminTab('agenda')">Agenda</div>
|
||||
<div class="tab" id="superadmin-tab" onclick="switchAdminTab('superadmin')" style="display:none;">Superadmin</div>
|
||||
<div class="tab" onclick="switchAdminTab('einstellungen')">Einstellungen</div>
|
||||
</div>
|
||||
@@ -146,6 +150,27 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============================================================ AGENDA -->
|
||||
<div id="tab-agenda" class="tab-content">
|
||||
<div class="card">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:14px;">
|
||||
<div class="card-title" style="margin:0;">Agenda-Folien (Online-Meeting)</div>
|
||||
<div style="display:flex;gap:8px;">
|
||||
<button class="btn btn-secondary btn-sm" onclick="fetchAgendaAdmin()">🔄 Vom Server laden</button>
|
||||
<button class="btn btn-primary btn-sm" onclick="uploadAgendaAdmin()">📤 Auf Server hochladen</button>
|
||||
</div>
|
||||
</div>
|
||||
<p style="font-size:0.85rem;color:var(--muted);margin-bottom:12px;">
|
||||
Punkte für den Online-Meeting-Ablauf (z.B. Begrüßung, DSGVO, BNI-Zahlen, Events) – als Bild oder als Live-Einbettung
|
||||
aus Zoho Show. Die Reihenfolge hier ist nur der Katalog; welche Punkte in welcher Reihenfolge live gezeigt werden,
|
||||
stellt die Moderatorin frei im Steuerpult (index.html) ein.
|
||||
</p>
|
||||
<div class="status-msg" id="agenda-status"></div>
|
||||
<div id="admin-agenda-list" style="margin-top:10px;"><div style="color:var(--muted);font-size:0.88rem;">Lade…</div></div>
|
||||
<button class="btn btn-secondary btn-sm w-full" style="margin-top:12px;" onclick="openAddAgendaItem()">+ Agenda-Punkt hinzufügen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============================================================ SUPERADMIN -->
|
||||
<div id="tab-superadmin" class="tab-content">
|
||||
<div class="card">
|
||||
@@ -291,6 +316,44 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Agenda Item Modal -->
|
||||
<div class="modal-overlay" id="modal-agenda-item">
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<div class="modal-title" id="agenda-item-modal-title">Agenda-Punkt</div>
|
||||
<button class="modal-close" onclick="closeAdminModal('modal-agenda-item')">✕</button>
|
||||
</div>
|
||||
<input type="hidden" id="ai-id">
|
||||
<label>Bezeichnung</label>
|
||||
<input type="text" id="ai-label" placeholder="z.B. Begrüßung">
|
||||
<label>Anzeigeart</label>
|
||||
<select id="ai-type" onchange="updateAgendaItemTypeUI()">
|
||||
<option value="image">Bild (Export, wird hochgeladen)</option>
|
||||
<option value="embed">Live-Einbettung (Zoho Show o.ä.)</option>
|
||||
</select>
|
||||
<div id="ai-image-row">
|
||||
<label>Bild</label>
|
||||
<div class="photo-actions">
|
||||
<img id="ai-image-thumb" class="photo-thumb" style="width:80px;height:80px;border-radius:8px;display:none;" src="" alt="">
|
||||
<label class="btn-file-label">📁 Bild wählen<input type="file" id="ai-image-file" accept="image/*" style="display:none" onchange="handleAgendaImageFile(this)"></label>
|
||||
</div>
|
||||
<div id="ai-image-thumb-hint" style="display:none;font-size:0.78rem;color:#d97706;margin-top:6px;"></div>
|
||||
</div>
|
||||
<div id="ai-embed-row" style="display:none;">
|
||||
<label>Einbettungs-URL</label>
|
||||
<input type="text" id="ai-embed-url" placeholder="https://show.zoho.eu/publish/...">
|
||||
<p style="font-size:0.78rem;color:var(--muted);margin-top:4px;">In Zoho Show: Datei → Freigeben/Veröffentlichen → Einbetten, Link hier einfügen.</p>
|
||||
</div>
|
||||
<label>Notizen — nur im Steuerpult sichtbar, nie auf dem Display</label>
|
||||
<textarea id="ai-notes" rows="4" placeholder="z.B. Stichpunkte, was hier gesagt werden soll"></textarea>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-danger btn-sm" id="ai-delete-btn" onclick="deleteAgendaItem()" style="display:none;margin-right:auto;">Löschen</button>
|
||||
<button class="btn btn-ghost" onclick="closeAdminModal('modal-agenda-item')">Abbrechen</button>
|
||||
<button class="btn btn-primary" onclick="saveAgendaItem()">Speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Import from Chapter Modal -->
|
||||
<div class="modal-overlay" id="modal-import-chapter">
|
||||
<div class="modal">
|
||||
@@ -316,11 +379,12 @@
|
||||
// SHARED CONSTANTS (copy from index.html)
|
||||
// ============================================================
|
||||
const APP_VERSION = '1.1.0';
|
||||
const GITEA_RAW = 'http://72.60.134.73:3000/api/v1/repos/cheubaum/BNI-Vorstellungsrunde/raw';
|
||||
const GITEA_API = 'http://72.60.134.73:3000/api/v1/repos/cheubaum/BNI-Vorstellungsrunde';
|
||||
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';
|
||||
const CHAPTER_CONFIG_KEY = 'bni-active-chapter';
|
||||
function giteaUrl(path) { return `${GITEA_RAW}/${path}?ref=main`; }
|
||||
function giteaAuthUrl(path, token) { return `${GITEA_API}/contents/${path}?token=${encodeURIComponent(token)}`; }
|
||||
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 : ''}`; }
|
||||
|
||||
// ============================================================
|
||||
// CHAPTER CONFIG
|
||||
@@ -399,6 +463,7 @@ function switchAdminTab(tab) {
|
||||
document.getElementById(`tab-${tab}`).classList.add('active');
|
||||
if (tab === 'stammvertreter') renderSvList();
|
||||
if (tab === 'mitglieder') renderMembersList();
|
||||
if (tab === 'agenda') renderAgendaAdminList();
|
||||
if (tab === 'superadmin') { loadAllChapters(); loadCurrentVersion(); }
|
||||
if (tab === 'einstellungen') renderSettings();
|
||||
}
|
||||
@@ -409,7 +474,7 @@ function switchAdminTab(tab) {
|
||||
let _svAdminPhotoB64 = null;
|
||||
|
||||
function renderSvList() {
|
||||
const list = loadStammvertreter();
|
||||
const list = loadStammvertreter().sort((a, b) => (a.name || '').localeCompare(b.name || '', 'de', { sensitivity: 'base' }));
|
||||
const container = document.getElementById('admin-sv-list');
|
||||
if (!list.length) {
|
||||
container.innerHTML = `<div style="color:var(--muted);font-size:0.88rem;padding:8px 0;">Noch keine Stammvertreter gespeichert.</div>`;
|
||||
@@ -516,7 +581,7 @@ async function openImportFromChapter() {
|
||||
const listEl = document.getElementById('import-chapter-select-list');
|
||||
listEl.innerHTML = '<div style="color:var(--muted);font-size:0.85rem;">Lade Chapter…</div>';
|
||||
try {
|
||||
const res = await fetch(`${GITEA_API}/contents/chapters?ref=main`);
|
||||
const res = await fetch(`${GITEA_API}/contents/chapters?ref=data`);
|
||||
const items = await res.json();
|
||||
const chapters = Array.isArray(items) ? items.filter(i => i.type === 'dir') : [];
|
||||
if (!chapters.length) { listEl.innerHTML = '<div style="color:var(--muted);">Keine anderen Chapter gefunden.</div>'; return; }
|
||||
@@ -524,7 +589,7 @@ async function openImportFromChapter() {
|
||||
for (const ch of chapters) {
|
||||
if (activeChapter && ch.name === activeChapter.code) continue;
|
||||
try {
|
||||
const cfgRes = await fetch(giteaUrl(`chapters/${ch.name}/config.json`), { cache: 'no-store' });
|
||||
const cfgRes = await fetch(giteaDataUrl(`chapters/${ch.name}/config.json`), { cache: 'no-store' });
|
||||
const cfg = cfgRes.ok ? await cfgRes.json() : {};
|
||||
const btn = document.createElement('div');
|
||||
btn.className = 'chapter-item';
|
||||
@@ -550,7 +615,7 @@ async function loadChapterMembersForImport(code, chapterName) {
|
||||
const listEl = document.getElementById('import-member-list');
|
||||
listEl.innerHTML = '<div style="padding:12px;color:var(--muted);">Lade Mitglieder…</div>';
|
||||
try {
|
||||
const res = await fetch(giteaUrl(`chapters/${code}/members.json`));
|
||||
const res = await fetch(giteaDataUrl(`chapters/${code}/members.json`));
|
||||
if (!res.ok) { listEl.innerHTML = '<div style="padding:12px;color:#dc2626;">Keine Mitglieder gefunden.</div>'; return; }
|
||||
const data = await res.json();
|
||||
const members = data.members || [];
|
||||
@@ -608,11 +673,11 @@ async function uploadSvToGitea() {
|
||||
const path = `chapters/${activeChapter.code}/stammvertreter.json`;
|
||||
try {
|
||||
let sha = null;
|
||||
const shaRes = await fetch(giteaAuthUrl(path, token));
|
||||
const shaRes = await fetch(giteaAuthUrl(path, token, 'data'));
|
||||
if (shaRes.ok) sha = (await shaRes.json()).sha;
|
||||
const body = { message: 'Stammvertreter aktualisiert', content };
|
||||
const body = { message: 'Stammvertreter aktualisiert', content, branch: 'data' };
|
||||
if (sha) body.sha = sha;
|
||||
const res = await fetch(giteaAuthUrl(path, token), { method: sha?'PUT':'POST', headers: { 'Content-Type':'application/json' }, body: JSON.stringify(body) });
|
||||
const res = await fetch(giteaAuthUrl(path, token, 'data'), { method: sha?'PUT':'POST', headers: { 'Content-Type':'application/json' }, body: JSON.stringify(body) });
|
||||
if (res.ok) { statusEl.textContent = `✓ ${list.length} Stammvertreter gesichert`; statusEl.style.color = 'var(--success)'; }
|
||||
else { statusEl.textContent = 'Fehler beim Upload.'; statusEl.style.color = '#dc2626'; }
|
||||
} catch(e) { statusEl.textContent = 'Verbindungsfehler.'; statusEl.style.color = '#dc2626'; }
|
||||
@@ -622,7 +687,7 @@ async function downloadSvFromGitea() {
|
||||
const statusEl = document.getElementById('sv-sync-status');
|
||||
statusEl.textContent = 'Lade…'; statusEl.style.color = 'var(--muted)';
|
||||
try {
|
||||
const res = await fetch(giteaUrl(`chapters/${activeChapter.code}/stammvertreter.json`), { cache: 'no-store' });
|
||||
const res = await fetch(giteaDataUrl(`chapters/${activeChapter.code}/stammvertreter.json`), { cache: 'no-store' });
|
||||
if (!res.ok) { statusEl.textContent = 'Keine Daten auf Server.'; statusEl.style.color = 'var(--muted)'; return; }
|
||||
const data = await res.json();
|
||||
if (data.substitutes) { saveStammvertreter(data.substitutes); renderSvList(); statusEl.textContent = `✓ ${data.substitutes.length} Stammvertreter geladen`; statusEl.style.color = 'var(--success)'; }
|
||||
@@ -719,7 +784,7 @@ async function fetchMembersAdmin() {
|
||||
const statusEl = document.getElementById('members-status');
|
||||
statusEl.textContent = 'Lade vom Server…'; statusEl.style.color = 'var(--muted)';
|
||||
try {
|
||||
const res = await fetch(giteaUrl(`chapters/${activeChapter.code}/members.json`), { cache: 'no-store' });
|
||||
const res = await fetch(giteaDataUrl(`chapters/${activeChapter.code}/members.json`), { cache: 'no-store' });
|
||||
if (!res.ok) { statusEl.textContent = 'Keine Mitglieder auf Server.'; return; }
|
||||
const data = await res.json();
|
||||
if (!data.members || !data.members.length) { statusEl.textContent = 'Liste ist leer.'; return; }
|
||||
@@ -756,14 +821,14 @@ async function uploadMembersAdmin() {
|
||||
const data = { lastUpdated: new Date().toISOString().slice(0,10), members };
|
||||
const content = btoa(unescape(encodeURIComponent(JSON.stringify(data, null, 2))));
|
||||
const path = `chapters/${activeChapter.code}/members.json`;
|
||||
const url = giteaAuthUrl(path, token);
|
||||
const url = giteaAuthUrl(path, token, 'data');
|
||||
console.log('[Upload] URL:', url);
|
||||
statusEl.textContent = 'Prüfe SHA…';
|
||||
let sha = null;
|
||||
const shaRes = await fetch(url);
|
||||
console.log('[Upload] SHA-Status:', shaRes.status);
|
||||
if (shaRes.ok) sha = (await shaRes.json()).sha;
|
||||
const body = { message:`Mitglieder aktualisiert ${new Date().toLocaleDateString('de')}`, content };
|
||||
const body = { message:`Mitglieder aktualisiert ${new Date().toLocaleDateString('de')}`, content, branch: 'data' };
|
||||
if (sha) body.sha = sha;
|
||||
statusEl.textContent = sha ? 'Aktualisiere…' : 'Erstelle…';
|
||||
const res = await fetch(url, { method:sha?'PUT':'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body) });
|
||||
@@ -780,6 +845,245 @@ async function uploadMembersAdmin() {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// AGENDA-FOLIEN (Online-Meeting-Ablauf)
|
||||
// ============================================================
|
||||
let _agendaImageDataUrl = null;
|
||||
let _agendaImageChanged = false;
|
||||
let _agendaImageProcessing = null; // Promise, solange ein gewähltes Bild noch verkleinert wird
|
||||
|
||||
function slugify(s) {
|
||||
const map = { ä:'ae', ö:'oe', ü:'ue', ß:'ss' };
|
||||
return (s || '').toLowerCase().trim()
|
||||
.replace(/[äöüß]/g, c => map[c])
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '') || 'punkt';
|
||||
}
|
||||
|
||||
function renderAgendaAdminList() {
|
||||
const state = loadState();
|
||||
const container = document.getElementById('admin-agenda-list');
|
||||
const items = state.agendaCatalog || [];
|
||||
if (!items.length) {
|
||||
container.innerHTML = `<div style="color:var(--muted);font-size:0.88rem;">Noch keine Agenda-Punkte. Mit "+ Agenda-Punkt hinzufügen" starten.</div>`;
|
||||
return;
|
||||
}
|
||||
container.innerHTML = '';
|
||||
items.forEach(it => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'member-item';
|
||||
div.style.cursor = 'pointer';
|
||||
div.title = 'Bearbeiten';
|
||||
const typeLabel = it.type === 'embed' ? 'Live-Einbettung' : 'Bild';
|
||||
div.innerHTML = `
|
||||
<div style="flex:1;min-width:0;">
|
||||
<div style="font-weight:600;font-size:0.88rem;">${escHtml(it.label || it.id)}</div>
|
||||
<div style="font-size:0.75rem;color:var(--muted);">${typeLabel}</div>
|
||||
</div>
|
||||
<button class="btn btn-ghost btn-sm">✎</button>
|
||||
`;
|
||||
div.onclick = () => editAgendaItem(it.id);
|
||||
container.appendChild(div);
|
||||
});
|
||||
}
|
||||
|
||||
function openAddAgendaItem() {
|
||||
document.getElementById('ai-id').value = '';
|
||||
document.getElementById('agenda-item-modal-title').textContent = 'Agenda-Punkt hinzufügen';
|
||||
document.getElementById('ai-label').value = '';
|
||||
document.getElementById('ai-type').value = 'image';
|
||||
document.getElementById('ai-embed-url').value = '';
|
||||
document.getElementById('ai-notes').value = '';
|
||||
document.getElementById('ai-delete-btn').style.display = 'none';
|
||||
_agendaImageDataUrl = null; _agendaImageChanged = false;
|
||||
document.getElementById('ai-image-thumb').style.display = 'none';
|
||||
document.getElementById('ai-image-thumb-hint').style.display = 'none';
|
||||
updateAgendaItemTypeUI();
|
||||
openAdminModal('modal-agenda-item');
|
||||
}
|
||||
|
||||
function editAgendaItem(id) {
|
||||
const state = loadState();
|
||||
const it = (state.agendaCatalog || []).find(a => a.id === id);
|
||||
if (!it) return;
|
||||
document.getElementById('ai-id').value = it.id;
|
||||
document.getElementById('agenda-item-modal-title').textContent = 'Agenda-Punkt bearbeiten';
|
||||
document.getElementById('ai-label').value = it.label || '';
|
||||
document.getElementById('ai-type').value = it.type || 'image';
|
||||
document.getElementById('ai-embed-url').value = it.embedUrl || '';
|
||||
document.getElementById('ai-notes').value = it.notes || '';
|
||||
document.getElementById('ai-delete-btn').style.display = 'inline-flex';
|
||||
_agendaImageDataUrl = null; _agendaImageChanged = false;
|
||||
const thumb = document.getElementById('ai-image-thumb');
|
||||
const thumbHint = document.getElementById('ai-image-thumb-hint');
|
||||
if (it.imagePath) {
|
||||
thumb.onerror = () => {
|
||||
thumb.style.display = 'none';
|
||||
if (thumbHint) { thumbHint.textContent = '⚠ Bild nicht auf dem Server gefunden – bitte neu hochladen.'; thumbHint.style.display = 'block'; }
|
||||
};
|
||||
thumb.onload = () => { if (thumbHint) thumbHint.style.display = 'none'; };
|
||||
if (thumbHint) thumbHint.style.display = 'none';
|
||||
thumb.src = it.imagePath.startsWith('http') ? it.imagePath : giteaDataUrl(`chapters/${activeChapter.code}/${it.imagePath}`);
|
||||
thumb.style.display = 'block';
|
||||
} else {
|
||||
thumb.style.display = 'none';
|
||||
if (thumbHint) thumbHint.style.display = 'none';
|
||||
}
|
||||
updateAgendaItemTypeUI();
|
||||
openAdminModal('modal-agenda-item');
|
||||
}
|
||||
|
||||
function updateAgendaItemTypeUI() {
|
||||
const type = document.getElementById('ai-type').value;
|
||||
document.getElementById('ai-image-row').style.display = type === 'image' ? 'block' : 'none';
|
||||
document.getElementById('ai-embed-row').style.display = type === 'embed' ? 'block' : 'none';
|
||||
}
|
||||
|
||||
function handleAgendaImageFile(input) {
|
||||
const file = input.files && input.files[0];
|
||||
if (!file) return;
|
||||
const hint = document.getElementById('ai-image-thumb-hint');
|
||||
if (hint) { hint.textContent = 'Bild wird verarbeitet…'; hint.style.color = 'var(--muted)'; hint.style.display = 'block'; }
|
||||
// Auf eine für Vollbild-Anzeige ausreichende Größe verkleinern/komprimieren, damit der
|
||||
// Upload sicher unter dem Größenlimit der Gitea-Contents-API bleibt (typischerweise ~1 MB/Datei).
|
||||
// _agendaImageProcessing merkt sich das laufende Promise, damit saveAgendaItem() nicht zu früh
|
||||
// speichert, während das Bild noch verarbeitet wird (führte sonst zu einem stillen "kein Bild gewählt").
|
||||
_agendaImageProcessing = resizePhotoToBase64(file, 1920).then(b64 => {
|
||||
_agendaImageDataUrl = b64;
|
||||
_agendaImageChanged = true;
|
||||
const thumb = document.getElementById('ai-image-thumb');
|
||||
thumb.src = b64;
|
||||
thumb.style.display = 'block';
|
||||
if (hint) {
|
||||
const kb = Math.round((b64.length * 0.75) / 1024);
|
||||
hint.textContent = `Für den Upload verkleinert auf max. 1920px, ~${kb} KB.`;
|
||||
hint.style.color = 'var(--muted)';
|
||||
hint.style.display = 'block';
|
||||
}
|
||||
}).catch(() => alert('Bild konnte nicht gelesen werden.'))
|
||||
.finally(() => { _agendaImageProcessing = null; });
|
||||
input.value = '';
|
||||
}
|
||||
|
||||
async function uploadAgendaImage(path, dataUrl, token) {
|
||||
const content = dataUrl.split(',')[1];
|
||||
const url = giteaAuthUrl(path, token, 'data');
|
||||
let sha = null;
|
||||
const shaRes = await fetch(url);
|
||||
if (shaRes.ok) sha = (await shaRes.json()).sha;
|
||||
const body = { message: `Agenda-Bild aktualisiert ${new Date().toLocaleDateString('de')}`, content, branch: 'data' };
|
||||
if (sha) body.sha = sha;
|
||||
const res = await fetch(url, { method: sha ? 'PUT' : 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
|
||||
if (!res.ok) { const t = await res.text(); throw new Error(`${res.status}: ${t.slice(0, 80)}`); }
|
||||
}
|
||||
|
||||
async function saveAgendaItem() {
|
||||
const label = document.getElementById('ai-label').value.trim();
|
||||
if (!label) { alert('Bitte eine Bezeichnung eingeben.'); return; }
|
||||
const type = document.getElementById('ai-type').value;
|
||||
|
||||
if (_agendaImageProcessing) {
|
||||
const statusEl = document.getElementById('agenda-status');
|
||||
statusEl.textContent = 'Bild wird noch verarbeitet…'; statusEl.style.color = 'var(--muted)';
|
||||
await _agendaImageProcessing;
|
||||
}
|
||||
|
||||
const state = loadState();
|
||||
if (!state.agendaCatalog) state.agendaCatalog = [];
|
||||
|
||||
let id = document.getElementById('ai-id').value;
|
||||
if (!id) {
|
||||
const base = slugify(label);
|
||||
const ids = new Set(state.agendaCatalog.map(a => a.id));
|
||||
let candidate = base, n = 1;
|
||||
while (ids.has(candidate)) candidate = `${base}-${++n}`;
|
||||
id = candidate;
|
||||
}
|
||||
const existing = state.agendaCatalog.find(a => a.id === id) || {};
|
||||
const notes = document.getElementById('ai-notes').value.trim();
|
||||
const item = { id, label, type };
|
||||
if (notes) item.notes = notes;
|
||||
|
||||
if (type === 'embed') {
|
||||
const url = document.getElementById('ai-embed-url').value.trim();
|
||||
if (!url) { alert('Bitte eine Einbettungs-URL eingeben.'); return; }
|
||||
item.embedUrl = url;
|
||||
} else {
|
||||
item.imagePath = existing.imagePath || `agenda/${id}.jpg`;
|
||||
if (!_agendaImageChanged && !existing.imagePath) { alert('Bitte ein Bild wählen.'); return; }
|
||||
}
|
||||
|
||||
const statusEl = document.getElementById('agenda-status');
|
||||
if (type === 'image' && _agendaImageChanged && _agendaImageDataUrl) {
|
||||
const token = getAdminToken();
|
||||
if (!token) { alert('Bitte zuerst Admin-Token in den Einstellungen speichern.'); return; }
|
||||
statusEl.textContent = 'Lade Bild hoch…'; statusEl.style.color = 'var(--muted)';
|
||||
try {
|
||||
await uploadAgendaImage(`chapters/${activeChapter.code}/${item.imagePath}`, _agendaImageDataUrl, token);
|
||||
} catch(e) {
|
||||
statusEl.textContent = `Fehler beim Bild-Upload: ${e.message}`; statusEl.style.color = '#dc2626';
|
||||
alert(`Bild-Upload fehlgeschlagen: ${e.message}\n\nDer Agenda-Punkt wurde NICHT gespeichert.`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const idx = state.agendaCatalog.findIndex(a => a.id === id);
|
||||
if (idx >= 0) state.agendaCatalog[idx] = item; else state.agendaCatalog.push(item);
|
||||
saveState(state);
|
||||
closeAdminModal('modal-agenda-item');
|
||||
renderAgendaAdminList();
|
||||
statusEl.textContent = '✓ Lokal gespeichert – "Auf Server hochladen" nicht vergessen, damit es im Steuerpult ankommt.';
|
||||
statusEl.style.color = 'var(--success)';
|
||||
}
|
||||
|
||||
function deleteAgendaItem() {
|
||||
const id = document.getElementById('ai-id').value;
|
||||
if (!id) return;
|
||||
if (!confirm('Diesen Agenda-Punkt wirklich löschen?')) return;
|
||||
const state = loadState();
|
||||
state.agendaCatalog = (state.agendaCatalog || []).filter(a => a.id !== id);
|
||||
saveState(state);
|
||||
closeAdminModal('modal-agenda-item');
|
||||
renderAgendaAdminList();
|
||||
}
|
||||
|
||||
async function fetchAgendaAdmin() {
|
||||
const statusEl = document.getElementById('agenda-status');
|
||||
statusEl.textContent = 'Lade vom Server…'; statusEl.style.color = 'var(--muted)';
|
||||
try {
|
||||
const res = await fetch(giteaDataUrl(`chapters/${activeChapter.code}/agenda.json`), { cache: 'no-store' });
|
||||
if (!res.ok) { statusEl.textContent = 'Noch keine Agenda auf dem Server.'; return; }
|
||||
const data = await res.json();
|
||||
const state = loadState();
|
||||
state.agendaCatalog = Array.isArray(data.items) ? data.items : [];
|
||||
saveState(state);
|
||||
statusEl.textContent = `✓ ${state.agendaCatalog.length} Punkte geladen`; statusEl.style.color = 'var(--success)';
|
||||
renderAgendaAdminList();
|
||||
} catch(e) { statusEl.textContent = 'Verbindungsfehler.'; statusEl.style.color = '#dc2626'; }
|
||||
}
|
||||
|
||||
async function uploadAgendaAdmin() {
|
||||
const token = getAdminToken();
|
||||
if (!token) { alert('Bitte zuerst Admin-Token in den Einstellungen speichern.'); return; }
|
||||
const statusEl = document.getElementById('agenda-status');
|
||||
statusEl.textContent = 'Lädt hoch…'; statusEl.style.color = 'var(--muted)';
|
||||
try {
|
||||
const state = loadState();
|
||||
const data = { items: state.agendaCatalog || [] };
|
||||
const content = btoa(unescape(encodeURIComponent(JSON.stringify(data, null, 2))));
|
||||
const path = `chapters/${activeChapter.code}/agenda.json`;
|
||||
const url = giteaAuthUrl(path, token, 'data');
|
||||
let sha = null;
|
||||
const shaRes = await fetch(url);
|
||||
if (shaRes.ok) sha = (await shaRes.json()).sha;
|
||||
const body = { message: `Agenda aktualisiert ${new Date().toLocaleDateString('de')}`, content, branch: 'data' };
|
||||
if (sha) body.sha = sha;
|
||||
const res = await fetch(url, { method: sha ? 'PUT' : 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
|
||||
if (res.ok) { statusEl.textContent = `✓ ${data.items.length} Agenda-Punkte hochgeladen`; statusEl.style.color = 'var(--success)'; }
|
||||
else { const t = await res.text(); statusEl.textContent = `Fehler ${res.status}: ${t.slice(0, 80)}`; statusEl.style.color = '#dc2626'; }
|
||||
} catch(e) { statusEl.textContent = `Fehler: ${e.message}`; statusEl.style.color = '#dc2626'; }
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// SUPERADMIN
|
||||
// ============================================================
|
||||
@@ -789,16 +1093,16 @@ async function loadAllChapters() {
|
||||
const container = document.getElementById('all-chapters-list');
|
||||
container.innerHTML = '<div style="color:var(--muted);font-size:0.85rem;">Lade…</div>';
|
||||
try {
|
||||
const res = await fetch(`${GITEA_API}/contents/chapters?ref=main`);
|
||||
const res = await fetch(`${GITEA_API}/contents/chapters?ref=data`);
|
||||
const items = await res.json();
|
||||
const chapters = Array.isArray(items) ? items.filter(i => i.type === 'dir') : [];
|
||||
_allChaptersData = [];
|
||||
container.innerHTML = '';
|
||||
for (const ch of chapters) {
|
||||
try {
|
||||
const cfgRes = await fetch(giteaUrl(`chapters/${ch.name}/config.json`), { cache: 'no-store' });
|
||||
const cfgRes = await fetch(giteaDataUrl(`chapters/${ch.name}/config.json`), { cache: 'no-store' });
|
||||
const cfg = cfgRes.ok ? await cfgRes.json() : {};
|
||||
const membersRes = await fetch(giteaUrl(`chapters/${ch.name}/members.json`));
|
||||
const membersRes = await fetch(giteaDataUrl(`chapters/${ch.name}/members.json`));
|
||||
const membersData = membersRes.ok ? await membersRes.json() : {};
|
||||
_allChaptersData.push({ code: ch.name, cfg });
|
||||
const isEnabled = !!cfg.memberCoordinatorReport;
|
||||
@@ -927,18 +1231,18 @@ async function toggleCoordinatorReport(code, currentEnabled) {
|
||||
const token = getAdminToken();
|
||||
if (!token) { alert('Bitte Admin-Token in den Einstellungen eintragen.'); return; }
|
||||
try {
|
||||
const cfgRes = await fetch(giteaUrl(`chapters/${code}/config.json`), { cache: 'no-store' });
|
||||
const cfgRes = await fetch(giteaDataUrl(`chapters/${code}/config.json`), { cache: 'no-store' });
|
||||
if (!cfgRes.ok) { alert('Config nicht gefunden.'); return; }
|
||||
const cfg = await cfgRes.json();
|
||||
const shaRes = await fetch(giteaAuthUrl(`chapters/${code}/config.json`, token));
|
||||
const shaRes = await fetch(giteaAuthUrl(`chapters/${code}/config.json`, token, 'data'));
|
||||
if (!shaRes.ok) { alert('SHA-Fetch fehlgeschlagen.'); return; }
|
||||
const sha = (await shaRes.json()).sha;
|
||||
cfg.memberCoordinatorReport = !currentEnabled;
|
||||
const content = btoa(unescape(encodeURIComponent(JSON.stringify(cfg, null, 2))));
|
||||
const res = await fetch(giteaAuthUrl(`chapters/${code}/config.json`, token), {
|
||||
const res = await fetch(giteaAuthUrl(`chapters/${code}/config.json`, token, 'data'), {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ message: `Koordinatoren-Bericht ${cfg.memberCoordinatorReport ? 'aktiviert' : 'deaktiviert'} für ${code}`, content, sha })
|
||||
body: JSON.stringify({ message: `Koordinatoren-Bericht ${cfg.memberCoordinatorReport ? 'aktiviert' : 'deaktiviert'} für ${code}`, content, sha, branch: 'data' })
|
||||
});
|
||||
if (res.ok) { loadAllChapters(); }
|
||||
else { alert('Fehler beim Speichern der Konfiguration.'); }
|
||||
@@ -953,7 +1257,7 @@ async function loadCoordinatorReportData() {
|
||||
form.style.display = 'block';
|
||||
_coordReportData = null;
|
||||
try {
|
||||
const res = await fetch(giteaUrl(`chapters/${code}/coordinator-report-${year}.json`), { cache: 'no-store' });
|
||||
const res = await fetch(giteaDataUrl(`chapters/${code}/coordinator-report-${year}.json`), { cache: 'no-store' });
|
||||
if (res.ok) _coordReportData = await res.json();
|
||||
} catch(e) {}
|
||||
if (!_coordReportData) {
|
||||
@@ -1129,14 +1433,14 @@ async function _saveCoordinatorReportToGitea(code, data) {
|
||||
_coordStatus('Speichere…');
|
||||
let sha = null;
|
||||
try {
|
||||
const shaRes = await fetch(giteaAuthUrl(path, token));
|
||||
const shaRes = await fetch(giteaAuthUrl(path, token, 'data'));
|
||||
if (shaRes.ok) sha = (await shaRes.json()).sha;
|
||||
} catch(e) {}
|
||||
const content = btoa(unescape(encodeURIComponent(JSON.stringify(data, null, 2))));
|
||||
const body = { message: `Update Koordinatoren-Bericht ${year} für ${code}`, content };
|
||||
const body = { message: `Update Koordinatoren-Bericht ${year} für ${code}`, content, branch: 'data' };
|
||||
if (sha) body.sha = sha;
|
||||
try {
|
||||
const res = await fetch(giteaAuthUrl(path, token), {
|
||||
const res = await fetch(giteaAuthUrl(path, token, 'data'), {
|
||||
method: sha ? 'PUT' : 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body)
|
||||
|
||||
598
anleitung.html
@@ -1,598 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>BNI Vorstellungsrunde – Anleitung</title>
|
||||
<style>
|
||||
:root {
|
||||
--red: #cc0000; --navy: #1a2744; --bg: #f0f2f5;
|
||||
--card: #fff; --muted: #6b7280; --border: #e5e7eb;
|
||||
--success: #16a34a;
|
||||
}
|
||||
* { margin:0; padding:0; box-sizing:border-box; }
|
||||
body { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; background:var(--bg); color:#1a1a2e; line-height:1.6; }
|
||||
|
||||
.cover { background:linear-gradient(160deg,#0f1f45,#1a2744,#0d1b35); color:white; padding:60px 40px; text-align:center; }
|
||||
.cover h1 { font-size:2rem; font-weight:800; letter-spacing:-0.5px; }
|
||||
.cover .subtitle { color:rgba(255,255,255,0.6); margin-top:8px; font-size:1rem; }
|
||||
.cover .badge { display:inline-block; margin-top:24px; background:var(--red); color:white; padding:6px 18px; border-radius:20px; font-size:0.82rem; font-weight:600; }
|
||||
|
||||
.toc { background:white; padding:24px 32px; border-bottom:1px solid var(--border); }
|
||||
.toc h2 { font-size:0.78rem; font-weight:700; color:var(--muted); text-transform:uppercase; letter-spacing:1px; margin-bottom:12px; }
|
||||
.toc ol { padding-left:18px; }
|
||||
.toc li { font-size:0.9rem; padding:3px 0; }
|
||||
.toc a { color:var(--navy); text-decoration:none; }
|
||||
.toc a:hover { color:var(--red); }
|
||||
|
||||
.content { max-width:820px; margin:0 auto; padding:32px 24px 64px; }
|
||||
|
||||
.section { margin-bottom:48px; }
|
||||
.section-header { display:flex; align-items:center; gap:14px; margin-bottom:20px; padding-bottom:12px; border-bottom:2px solid var(--red); }
|
||||
.step-num { width:36px; height:36px; background:var(--red); color:white; border-radius:50%; display:flex; align-items:center; justify-content:center; font-weight:800; font-size:1rem; flex-shrink:0; }
|
||||
.section-header h2 { font-size:1.25rem; font-weight:700; color:var(--navy); }
|
||||
|
||||
.card { background:white; border-radius:12px; border:1px solid var(--border); padding:20px; margin-bottom:16px; }
|
||||
.tip { background:#fef3c7; border:1px solid #fcd34d; border-radius:10px; padding:14px 16px; font-size:0.88rem; margin-bottom:16px; }
|
||||
.tip strong { color:#92400e; }
|
||||
.info { background:#eff6ff; border:1px solid #bfdbfe; border-radius:10px; padding:14px 16px; font-size:0.88rem; margin-bottom:16px; }
|
||||
|
||||
.steps { counter-reset:step; }
|
||||
.step { display:flex; gap:14px; margin-bottom:12px; align-items:flex-start; }
|
||||
.step::before { counter-increment:step; content:counter(step); width:24px; height:24px; background:var(--navy); color:white; border-radius:50%; font-size:0.78rem; font-weight:700; display:flex; align-items:center; justify-content:center; flex-shrink:0; margin-top:2px; }
|
||||
.step p { font-size:0.9rem; }
|
||||
.step code { background:#f3f4f6; padding:1px 6px; border-radius:4px; font-size:0.85rem; }
|
||||
|
||||
/* ——— MOCKUPS ——— */
|
||||
.mockup { background:#1a1a2e; border-radius:12px; overflow:hidden; margin:16px 0; box-shadow:0 4px 20px rgba(0,0,0,0.2); }
|
||||
.mockup-bar { background:#0f1420; padding:8px 14px; display:flex; align-items:center; gap:8px; }
|
||||
.mockup-dot { width:10px; height:10px; border-radius:50%; }
|
||||
.mockup-body { padding:16px; }
|
||||
|
||||
.mock-activation { background:linear-gradient(160deg,#0f1f45,#1a2744); padding:32px 20px; text-align:center; border-radius:8px; }
|
||||
.mock-activation h3 { color:white; font-size:1.1rem; margin-bottom:6px; }
|
||||
.mock-activation p { color:rgba(255,255,255,0.5); font-size:0.82rem; margin-bottom:20px; }
|
||||
.mock-input { background:rgba(255,255,255,0.1); border:1px solid rgba(255,255,255,0.2); border-radius:8px; padding:10px 14px; color:white; font-size:0.9rem; text-align:center; margin-bottom:12px; width:100%; max-width:280px; }
|
||||
.mock-btn-red { background:#cc0000; color:white; border:none; border-radius:8px; padding:10px 24px; font-weight:700; cursor:pointer; font-size:0.9rem; }
|
||||
.mock-btn-gray { background:#374151; color:white; border:none; border-radius:8px; padding:8px 16px; font-weight:600; cursor:pointer; font-size:0.82rem; }
|
||||
|
||||
.mock-panel { background:#1e2235; border-radius:10px; padding:12px; }
|
||||
.mock-member { display:flex; align-items:center; gap:10px; padding:8px 10px; background:rgba(255,255,255,0.05); border-radius:8px; margin-bottom:6px; }
|
||||
.mock-avatar { width:36px; height:36px; border-radius:50%; object-fit:cover; flex-shrink:0; display:flex; align-items:center; justify-content:center; font-size:0.75rem; font-weight:700; color:white; }
|
||||
.mock-name { font-size:0.85rem; font-weight:600; color:white; }
|
||||
.mock-role { font-size:0.72rem; color:rgba(255,255,255,0.5); }
|
||||
.mock-badge { padding:2px 8px; border-radius:12px; font-size:0.7rem; font-weight:700; margin-left:auto; }
|
||||
.mock-badge.absent { background:rgba(204,0,0,0.2); color:#f87171; }
|
||||
.mock-badge.present { background:rgba(22,163,74,0.2); color:#4ade80; }
|
||||
|
||||
.mock-timer { text-align:center; padding:20px; }
|
||||
.mock-timer-ring { width:100px; height:100px; border-radius:50%; border:6px solid #cc0000; display:flex; align-items:center; justify-content:center; margin:0 auto 12px; }
|
||||
.mock-timer-num { font-size:2rem; font-weight:800; color:white; }
|
||||
.mock-timer-name { color:white; font-weight:700; font-size:1rem; }
|
||||
.mock-timer-company { color:rgba(255,255,255,0.5); font-size:0.8rem; }
|
||||
|
||||
.mock-tabs { display:flex; border-bottom:1px solid rgba(255,255,255,0.1); margin-bottom:12px; }
|
||||
.mock-tab { padding:8px 14px; font-size:0.78rem; color:rgba(255,255,255,0.4); border-bottom:2px solid transparent; }
|
||||
.mock-tab.active { color:#cc0000; border-bottom-color:#cc0000; }
|
||||
|
||||
.mock-sub-modal { background:#2a2f45; border-radius:12px; padding:16px; }
|
||||
.mock-sub-modal h4 { color:white; font-size:0.9rem; margin-bottom:12px; }
|
||||
.mock-field { background:rgba(255,255,255,0.1); border-radius:6px; padding:8px 10px; color:rgba(255,255,255,0.7); font-size:0.8rem; margin-bottom:8px; }
|
||||
.mock-sv-card { display:flex; align-items:center; gap:8px; background:rgba(255,255,255,0.07); border-radius:8px; padding:8px 10px; margin-bottom:6px; border:1px solid transparent; cursor:pointer; }
|
||||
.mock-sv-card:hover { border-color:rgba(204,0,0,0.5); }
|
||||
.mock-sv-name { color:white; font-size:0.82rem; font-weight:600; }
|
||||
.mock-sv-meta { color:rgba(255,255,255,0.45); font-size:0.72rem; }
|
||||
|
||||
.admin-mockup { background:#f0f2f5; border-radius:12px; overflow:hidden; border:1px solid var(--border); margin:16px 0; }
|
||||
.admin-header { background:#1a2744; color:white; padding:12px 16px; font-size:0.9rem; font-weight:700; }
|
||||
.admin-tabs { background:white; display:flex; border-bottom:1px solid var(--border); padding:0 16px; }
|
||||
.admin-tab { padding:10px 16px; font-size:0.82rem; font-weight:600; color:var(--muted); border-bottom:2px solid transparent; }
|
||||
.admin-tab.active { color:var(--red); border-bottom-color:var(--red); }
|
||||
.admin-body { padding:16px; }
|
||||
.admin-member-row { display:flex; align-items:center; gap:10px; padding:8px 0; border-bottom:1px solid var(--border); cursor:pointer; }
|
||||
.admin-member-row:last-child { border-bottom:none; }
|
||||
.admin-photo-badge { font-size:0.72rem; margin-left:auto; }
|
||||
.admin-photo-badge.ok { color:var(--success); }
|
||||
.admin-photo-badge.missing { color:#d97706; }
|
||||
|
||||
.highlight-box { border-left:3px solid var(--red); padding:10px 16px; background:white; border-radius:0 8px 8px 0; margin:12px 0; font-size:0.88rem; }
|
||||
|
||||
@page { margin: 14mm 14mm 14mm 14mm; }
|
||||
@media print {
|
||||
body { -webkit-print-color-adjust:exact; print-color-adjust:exact; }
|
||||
.cover {
|
||||
-webkit-print-color-adjust:exact; print-color-adjust:exact;
|
||||
min-height: 100vh;
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
page-break-after: always;
|
||||
}
|
||||
.toc { page-break-inside: avoid; }
|
||||
.section { page-break-inside:avoid; }
|
||||
.mockup, .admin-mockup { -webkit-print-color-adjust:exact; print-color-adjust:exact; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="cover">
|
||||
<div style="font-size:2.5rem; margin-bottom:8px;">🌟</div>
|
||||
<h1>BNI Vorstellungsrunde</h1>
|
||||
<div class="subtitle">Benutzeranleitung für Chapter-Admins</div>
|
||||
<div class="badge">Version 1.2 · April 2026</div>
|
||||
</div>
|
||||
|
||||
<div class="toc">
|
||||
<h2>Inhalt</h2>
|
||||
<ol>
|
||||
<li><a href="#start">Tool starten</a></li>
|
||||
<li><a href="#aktivierung">Erstmalige Aktivierung</a></li>
|
||||
<li><a href="#ansichten">Ansichten: Display & Steuerung</a></li>
|
||||
<li><a href="#vorbereitung">Meeting vorbereiten</a></li>
|
||||
<li><a href="#durchfuehrung">Meeting durchführen</a></li>
|
||||
<li><a href="#vertreter">Vertreter eintragen</a></li>
|
||||
<li><a href="#besucher">Besucher eintragen</a></li>
|
||||
<li><a href="#admin">Admin-Panel: Fotos & Stammvertreter</a></li>
|
||||
<li><a href="#offline">Offline-Nutzung</a></li>
|
||||
<li><a href="#update">Updates einspielen</a></li>
|
||||
<li><a href="#timer-schalter">Zeitanzeige deaktivieren</a></li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
|
||||
<!-- ========== 1. START ========== -->
|
||||
<div class="section" id="start">
|
||||
<div class="section-header">
|
||||
<div class="step-num">1</div>
|
||||
<h2>Tool starten</h2>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="steps">
|
||||
<div class="step"><p>Den Ordner <code>BNI Sonne</code> im Finder öffnen.</p></div>
|
||||
<div class="step"><p>Die Datei <strong>start.command</strong> doppelklicken — es öffnet sich kurz ein Terminal-Fenster, dann startet Chrome automatisch mit dem Tool.</p></div>
|
||||
<div class="step"><p>Das Terminal-Fenster im Hintergrund lassen (nicht schließen, solange du das Tool nutzt).</p></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tip">
|
||||
<strong>Hinweis:</strong> Beim ersten Doppelklick erscheint eventuell eine Sicherheitswarnung von macOS. Dann: Rechtsklick auf <code>start.command</code> → „Öffnen" → „Öffnen" bestätigen. Ab dem zweiten Mal klappt der Doppelklick direkt.
|
||||
</div>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-bar">
|
||||
<div class="mockup-dot" style="background:#ff5f57;"></div>
|
||||
<div class="mockup-dot" style="background:#febc2e;"></div>
|
||||
<div class="mockup-dot" style="background:#28c840;"></div>
|
||||
<span style="color:rgba(255,255,255,0.4);font-size:0.78rem;margin-left:8px;">Terminal</span>
|
||||
</div>
|
||||
<div class="mockup-body">
|
||||
<p style="color:#4ade80;font-size:0.82rem;font-family:monospace;">$ python3 -m http.server 8181</p>
|
||||
<p style="color:rgba(255,255,255,0.4);font-size:0.82rem;font-family:monospace;">Serving HTTP on 0.0.0.0 port 8181 …</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 2. AKTIVIERUNG ========== -->
|
||||
<div class="section" id="aktivierung">
|
||||
<div class="section-header">
|
||||
<div class="step-num">2</div>
|
||||
<h2>Erstmalige Aktivierung</h2>
|
||||
</div>
|
||||
|
||||
<div class="info">
|
||||
Dieser Schritt ist nur beim <strong>allerersten Start</strong> auf einem neuen Gerät oder Browser nötig. Danach öffnet das Tool direkt.
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="steps">
|
||||
<div class="step"><p>Den <strong>Lizenzcode</strong> deines Chapters eingeben (erhältst du vom Administrator).</p></div>
|
||||
<div class="step"><p>Auf <strong>„Aktivieren"</strong> klicken. Das Tool lädt einmalig die Chapter-Daten vom Server.</p></div>
|
||||
<div class="step"><p>Nach erfolgreicher Aktivierung öffnet sich direkt die Steuerungsansicht.</p></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-bar">
|
||||
<div class="mockup-dot" style="background:#ff5f57;"></div>
|
||||
<div class="mockup-dot" style="background:#febc2e;"></div>
|
||||
<div class="mockup-dot" style="background:#28c840;"></div>
|
||||
<span style="color:rgba(255,255,255,0.4);font-size:0.78rem;margin-left:8px;">http://localhost:8181</span>
|
||||
</div>
|
||||
<div class="mockup-body">
|
||||
<div class="mock-activation">
|
||||
<h3>BNI Vorstellungsrunde</h3>
|
||||
<p>Bitte Chapter-Code eingeben</p>
|
||||
<div style="margin-bottom:8px;"><input class="mock-input" placeholder="z. B. sonne-7x4m9k" readonly></div>
|
||||
<button class="mock-btn-red">Aktivieren</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 3. ANSICHTEN ========== -->
|
||||
<div class="section" id="ansichten">
|
||||
<div class="section-header">
|
||||
<div class="step-num">3</div>
|
||||
<h2>Ansichten: Display & Steuerung</h2>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<p style="font-size:0.9rem;">Das Tool läuft in <strong>zwei Fenstern gleichzeitig</strong>:</p>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:14px;">
|
||||
<div style="background:#f8f9fa;border-radius:8px;padding:14px;border-left:3px solid var(--navy);">
|
||||
<div style="font-weight:700;font-size:0.88rem;color:var(--navy);margin-bottom:6px;">🖥 Display-Fenster</div>
|
||||
<div style="font-size:0.82rem;color:var(--muted);">Wird auf dem Präsentationsmonitor / Beamer angezeigt. Zeigt das aktuelle Mitglied groß mit Timer.</div>
|
||||
</div>
|
||||
<div style="background:#f8f9fa;border-radius:8px;padding:14px;border-left:3px solid var(--red);">
|
||||
<div style="font-weight:700;font-size:0.88rem;color:var(--red);margin-bottom:6px;">🎛 Steuerungs-Fenster</div>
|
||||
<div style="font-size:0.82rem;color:var(--muted);">Dein Laptop-Bildschirm. Hier steuerst du den Ablauf, trägst Vertreter ein und navigierst.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="highlight-box">
|
||||
Beim Öffnen des Tools wird automatisch ein zweites Tab für das Display geöffnet. Ziehe dieses Tab auf den Beamer-Bildschirm und drücke <strong>F11</strong> für Vollbild.
|
||||
</div>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-bar">
|
||||
<div class="mockup-dot" style="background:#ff5f57;"></div>
|
||||
<div class="mockup-dot" style="background:#febc2e;"></div>
|
||||
<div class="mockup-dot" style="background:#28c840;"></div>
|
||||
<span style="color:rgba(255,255,255,0.4);font-size:0.78rem;margin-left:8px;">Display-Ansicht (Beamer)</span>
|
||||
</div>
|
||||
<div class="mockup-body">
|
||||
<div class="mock-timer">
|
||||
<div style="color:rgba(255,255,255,0.4);font-size:0.75rem;margin-bottom:10px;">JETZT SPRICHT</div>
|
||||
<div class="mock-timer-ring">
|
||||
<span class="mock-timer-num">45</span>
|
||||
</div>
|
||||
<div class="mock-timer-name">Maria Mustermann</div>
|
||||
<div class="mock-timer-company">Muster GmbH · Steuerberatung</div>
|
||||
<div style="color:rgba(255,255,255,0.3);font-size:0.75rem;margin-top:8px;">Nächster: Hans Müller</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 4. VORBEREITUNG ========== -->
|
||||
<div class="section" id="vorbereitung">
|
||||
<div class="section-header">
|
||||
<div class="step-num">4</div>
|
||||
<h2>Meeting vorbereiten</h2>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<p style="font-weight:700;font-size:0.9rem;margin-bottom:10px;">Vor dem Meeting: Abwesenheiten markieren</p>
|
||||
<div class="steps">
|
||||
<div class="step"><p>Im Steuerungs-Fenster: Tab <strong>„Mitglieder"</strong> öffnen.</p></div>
|
||||
<div class="step"><p>Auf den Namen eines abwesenden Mitglieds klicken → kleines Menü öffnet sich.</p></div>
|
||||
<div class="step"><p><strong>„Abwesend"</strong> wählen — das Mitglied wird aus der Vorstellungsrunde ausgeblendet (roter Punkt).</p></div>
|
||||
<div class="step"><p>Optional: Reihenfolge per <strong>Drag & Drop</strong> anpassen.</p></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-bar">
|
||||
<div class="mockup-dot" style="background:#ff5f57;"></div>
|
||||
<div class="mockup-dot" style="background:#febc2e;"></div>
|
||||
<div class="mockup-dot" style="background:#28c840;"></div>
|
||||
<span style="color:rgba(255,255,255,0.4);font-size:0.78rem;margin-left:8px;">Steuerung – Reihenfolge</span>
|
||||
</div>
|
||||
<div class="mockup-body">
|
||||
<div style="font-size:0.72rem;color:rgba(255,255,255,0.35);font-weight:700;letter-spacing:1px;text-transform:uppercase;margin-bottom:10px;">Reihenfolge (Ziehen zum Sortieren)</div>
|
||||
<!-- Nächster -->
|
||||
<div style="display:flex;align-items:center;gap:10px;padding:10px 12px;background:rgba(255,255,255,0.1);border:1px solid rgba(255,255,255,0.2);border-radius:10px;margin-bottom:6px;">
|
||||
<span style="color:rgba(255,255,255,0.25);font-size:1rem;">⠿</span>
|
||||
<div style="width:38px;height:38px;border-radius:50%;background:#1a2744;display:flex;align-items:center;justify-content:center;font-size:0.75rem;font-weight:700;color:white;flex-shrink:0;">AM</div>
|
||||
<div style="flex:1;"><div style="font-size:0.85rem;font-weight:700;color:white;">Anna Müller</div><div style="font-size:0.72rem;color:rgba(255,255,255,0.45);">Steuerberatung</div></div>
|
||||
<span style="background:#374151;color:white;font-size:0.68rem;font-weight:700;padding:2px 8px;border-radius:6px;letter-spacing:0.5px;">NÄCHSTER</span>
|
||||
<button style="background:rgba(255,255,255,0.08);border:none;border-radius:6px;padding:5px 8px;color:rgba(255,255,255,0.6);cursor:pointer;font-size:0.85rem;">↺</button>
|
||||
<button style="background:rgba(204,0,0,0.15);border:none;border-radius:6px;padding:5px 8px;color:rgba(255,100,100,0.7);cursor:pointer;font-size:0.85rem;">🚫</button>
|
||||
</div>
|
||||
<!-- Abwesend -->
|
||||
<div style="display:flex;align-items:center;gap:10px;padding:10px 12px;background:rgba(204,0,0,0.08);border:1px solid rgba(204,0,0,0.2);border-radius:10px;margin-bottom:6px;opacity:0.6;">
|
||||
<span style="color:rgba(255,255,255,0.25);font-size:1rem;">⠿</span>
|
||||
<div style="width:38px;height:38px;border-radius:50%;background:#7f1d1d;display:flex;align-items:center;justify-content:center;font-size:0.75rem;font-weight:700;color:white;flex-shrink:0;">KS</div>
|
||||
<div style="flex:1;"><div style="font-size:0.85rem;font-weight:700;color:rgba(255,255,255,0.5);">Klaus Schmidt</div><div style="font-size:0.72rem;color:rgba(255,255,255,0.3);">IT-Systeme</div></div>
|
||||
<button style="background:rgba(255,255,255,0.08);border:none;border-radius:6px;padding:5px 8px;color:rgba(255,255,255,0.4);cursor:pointer;font-size:0.85rem;">↺</button>
|
||||
<button style="background:rgba(204,0,0,0.3);border:none;border-radius:6px;padding:5px 8px;color:#f87171;cursor:pointer;font-size:0.85rem;">🚫</button>
|
||||
</div>
|
||||
<!-- Normal -->
|
||||
<div style="display:flex;align-items:center;gap:10px;padding:10px 12px;background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.08);border-radius:10px;">
|
||||
<span style="color:rgba(255,255,255,0.25);font-size:1rem;">⠿</span>
|
||||
<div style="width:38px;height:38px;border-radius:50%;background:#1e3a5f;display:flex;align-items:center;justify-content:center;font-size:0.75rem;font-weight:700;color:white;flex-shrink:0;">SL</div>
|
||||
<div style="flex:1;"><div style="font-size:0.85rem;font-weight:700;color:white;">Sandra Lang</div><div style="font-size:0.72rem;color:rgba(255,255,255,0.45);">Marketing</div></div>
|
||||
<button style="background:rgba(255,255,255,0.08);border:none;border-radius:6px;padding:5px 8px;color:rgba(255,255,255,0.6);cursor:pointer;font-size:0.85rem;">↺</button>
|
||||
<button style="background:rgba(255,255,255,0.05);border:none;border-radius:6px;padding:5px 8px;color:rgba(255,255,255,0.4);cursor:pointer;font-size:0.85rem;">🚫</button>
|
||||
</div>
|
||||
<div style="margin-top:10px;font-size:0.75rem;color:rgba(255,255,255,0.35);">↺ = Vertretung eintragen · 🚫 = Als abwesend markieren</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 5. DURCHFÜHRUNG ========== -->
|
||||
<div class="section" id="durchfuehrung">
|
||||
<div class="section-header">
|
||||
<div class="step-num">5</div>
|
||||
<h2>Meeting durchführen</h2>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="steps">
|
||||
<div class="step"><p>Auf <strong>„Start"</strong> klicken — der Timer startet für das erste Mitglied.</p></div>
|
||||
<div class="step"><p>Wenn die Zeit abgelaufen ist (Timer rot): <strong>„Weiter →"</strong> drücken für das nächste Mitglied.</p></div>
|
||||
<div class="step"><p>Mit <strong>„← Zurück"</strong> kannst du jederzeit zum vorherigen Mitglied springen.</p></div>
|
||||
<div class="step"><p>Timer lässt sich jederzeit mit <strong>„Pause / Fortsetzen"</strong> anhalten.</p></div>
|
||||
<div class="step"><p>Am Ende erscheint eine Abschluss-Ansicht — mit <strong>„Neu starten"</strong> zurück zum Anfang.</p></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tip">
|
||||
<strong>Tipp:</strong> Die Tastenkombination <strong>→ Pfeiltaste</strong> auf der Tastatur funktioniert als „Weiter" — praktisch für die Fernbedienung.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 6. VERTRETER ========== -->
|
||||
<div class="section" id="vertreter">
|
||||
<div class="section-header">
|
||||
<div class="step-num">6</div>
|
||||
<h2>Vertreter eintragen</h2>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<p style="font-size:0.9rem;margin-bottom:12px;">Wenn ein Mitglied einen Vertreter schickt, wird dieser in der Vorstellungsrunde an Stelle des Mitglieds angezeigt.</p>
|
||||
<div class="steps">
|
||||
<div class="step"><p>Tab <strong>„Vertretungen"</strong> öffnen.</p></div>
|
||||
<div class="step"><p>Mitglied aus der Liste wählen, das vertreten wird.</p></div>
|
||||
<div class="step"><p>Falls die Person bereits als <strong>Stammvertreter</strong> gespeichert ist: oben aus der Schnellauswahl wählen — alle Felder werden automatisch ausgefüllt.</p></div>
|
||||
<div class="step"><p>Andernfalls: Name, Firma und Fachgebiet des Vertreters eingeben und ggf. ein Foto hochladen.</p></div>
|
||||
<div class="step"><p>Auf <strong>„Speichern"</strong> klicken.</p></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-bar">
|
||||
<div class="mockup-dot" style="background:#ff5f57;"></div>
|
||||
<div class="mockup-dot" style="background:#febc2e;"></div>
|
||||
<div class="mockup-dot" style="background:#28c840;"></div>
|
||||
<span style="color:rgba(255,255,255,0.4);font-size:0.78rem;margin-left:8px;">Steuerung – Vertretung eintragen</span>
|
||||
</div>
|
||||
<div class="mockup-body">
|
||||
<div class="mock-sub-modal">
|
||||
<h4>Vertreter für Klaus Schmidt</h4>
|
||||
<div style="font-size:0.72rem;color:rgba(255,255,255,0.4);margin-bottom:8px;">STAMMVERTRETER WÄHLEN</div>
|
||||
<div class="mock-sv-card">
|
||||
<div class="mock-avatar" style="background:#1a2744;width:32px;height:32px;">MF</div>
|
||||
<div><div class="mock-sv-name">Max Fischer</div><div class="mock-sv-meta">Fischer GmbH · Versicherung</div></div>
|
||||
</div>
|
||||
<div class="mock-sv-card">
|
||||
<div class="mock-avatar" style="background:#4a1942;width:32px;height:32px;">LB</div>
|
||||
<div><div class="mock-sv-name">Lisa Berger</div><div class="mock-sv-meta">Berger & Co · Marketing</div></div>
|
||||
</div>
|
||||
<div style="font-size:0.72rem;color:rgba(255,255,255,0.4);margin:10px 0 6px;">ODER MANUELL EINGEBEN</div>
|
||||
<div class="mock-field">Name des Vertreters …</div>
|
||||
<div class="mock-field">Firma …</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 7. BESUCHER ========== -->
|
||||
<div class="section" id="besucher">
|
||||
<div class="section-header">
|
||||
<div class="step-num">7</div>
|
||||
<h2>Besucher eintragen</h2>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="steps">
|
||||
<div class="step"><p>Tab <strong>„Besucher"</strong> öffnen.</p></div>
|
||||
<div class="step"><p>Auf <strong>„+ Besucher hinzufügen"</strong> klicken.</p></div>
|
||||
<div class="step"><p>Name und optional Firma eingeben und speichern.</p></div>
|
||||
<div class="step"><p>Besucher erscheinen am Ende der Vorstellungsrunde nach allen Mitgliedern.</p></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tip">
|
||||
<strong>Tipp:</strong> Besucher werden nicht dauerhaft gespeichert — sie werden nach dem Meeting automatisch gelöscht, wenn die Runde zurückgesetzt wird.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 8. ADMIN ========== -->
|
||||
<div class="section" id="admin">
|
||||
<div class="section-header">
|
||||
<div class="step-num">8</div>
|
||||
<h2>Admin-Panel: Fotos & Stammvertreter</h2>
|
||||
</div>
|
||||
|
||||
<div class="info">
|
||||
Das Admin-Panel öffnest du über <code>http://localhost:8181/admin.html</code> — am einfachsten über den Link im Steuerungs-Fenster unter dem Tab <strong>„Admin"</strong>.
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<p style="font-weight:700;font-size:0.9rem;margin-bottom:10px;">Mitgliederfotos hinzufügen</p>
|
||||
<div class="steps">
|
||||
<div class="step"><p>Im Admin-Panel Tab <strong>„Mitglieder"</strong> öffnen.</p></div>
|
||||
<div class="step"><p>Auf ein Mitglied mit <strong>„+ Foto"</strong> klicken.</p></div>
|
||||
<div class="step"><p>Foto aus dem Finder wählen (JPG/PNG, beliebige Größe — wird automatisch verkleinert).</p></div>
|
||||
<div class="step"><p>Auf <strong>„Speichern"</strong> klicken — das Foto ist jetzt lokal gespeichert.</p></div>
|
||||
<div class="step"><p>Nach allen Fotos: <strong>„Auf Server hochladen"</strong> — dann haben alle Geräte die Fotos beim nächsten „Vom Server laden".</p></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-mockup">
|
||||
<div class="admin-header">BNI Admin-Panel · BNI Chapter Sonne</div>
|
||||
<div class="admin-tabs">
|
||||
<div class="admin-tab">Stammvertreter</div>
|
||||
<div class="admin-tab active">Mitglieder</div>
|
||||
<div class="admin-tab">Einstellungen</div>
|
||||
</div>
|
||||
<div class="admin-body">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
|
||||
<span style="font-weight:700;font-size:0.9rem;">Mitglieder</span>
|
||||
<div style="display:flex;gap:8px;">
|
||||
<span style="background:#f3f4f6;border:1px solid var(--border);border-radius:6px;padding:5px 10px;font-size:0.78rem;font-weight:600;">🔄 Vom Server laden</span>
|
||||
<span style="background:var(--red);color:white;border-radius:6px;padding:5px 10px;font-size:0.78rem;font-weight:600;">📤 Auf Server hochladen</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-member-row">
|
||||
<div class="mock-avatar" style="background:#1a2744;width:40px;height:40px;font-size:0.8rem;">AM</div>
|
||||
<div style="flex:1;"><div style="font-size:0.88rem;font-weight:600;">Anna Müller</div><div style="font-size:0.75rem;color:var(--muted);">Steuerberatung</div></div>
|
||||
<span class="admin-photo-badge ok">✓ Foto</span>
|
||||
</div>
|
||||
<div class="admin-member-row">
|
||||
<div class="mock-avatar" style="background:#4a1942;width:40px;height:40px;font-size:0.8rem;">KS</div>
|
||||
<div style="flex:1;"><div style="font-size:0.88rem;font-weight:600;">Klaus Schmidt</div><div style="font-size:0.75rem;color:var(--muted);">IT-Systeme</div></div>
|
||||
<span class="admin-photo-badge missing">+ Foto</span>
|
||||
</div>
|
||||
<div class="admin-member-row">
|
||||
<div class="mock-avatar" style="background:#1e3a5f;width:40px;height:40px;font-size:0.8rem;">SL</div>
|
||||
<div style="flex:1;"><div style="font-size:0.88rem;font-weight:600;">Sandra Lang</div><div style="font-size:0.75rem;color:var(--muted);">Marketing</div></div>
|
||||
<span class="admin-photo-badge ok">✓ Foto</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-top:16px;">
|
||||
<p style="font-weight:700;font-size:0.9rem;margin-bottom:10px;">Stammvertreter anlegen</p>
|
||||
<p style="font-size:0.88rem;color:var(--muted);margin-bottom:12px;">Stammvertreter sind Personen, die regelmäßig als Vertreter kommen. Einmal anlegen → bei jeder Vertretung schnell auswählen.</p>
|
||||
<div class="steps">
|
||||
<div class="step"><p>Tab <strong>„Stammvertreter"</strong> öffnen.</p></div>
|
||||
<div class="step"><p>Auf <strong>„+ Hinzufügen"</strong> klicken.</p></div>
|
||||
<div class="step"><p>Name, Firma, Fachgebiet und Foto eingeben.</p></div>
|
||||
<div class="step"><p>Optional: <strong>„Aus anderem Chapter"</strong> — Mitglied aus einem anderen Chapter-Verzeichnis als Stammvertreter übernehmen.</p></div>
|
||||
<div class="step"><p>Auf <strong>„Auf Server sichern"</strong> klicken, damit die Liste nicht verloren geht.</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 9. OFFLINE ========== -->
|
||||
<div class="section" id="offline">
|
||||
<div class="section-header">
|
||||
<div class="step-num">9</div>
|
||||
<h2>Offline-Nutzung</h2>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<p style="font-size:0.9rem;margin-bottom:12px;">Das Tool funktioniert <strong>vollständig ohne Internet</strong>, sobald es einmal online eingerichtet wurde.</p>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
|
||||
<div style="background:#f0fdf4;border-radius:8px;padding:12px;border:1px solid #bbf7d0;">
|
||||
<div style="font-weight:700;color:var(--success);font-size:0.85rem;margin-bottom:6px;">✓ Funktioniert offline</div>
|
||||
<ul style="font-size:0.82rem;color:#166534;padding-left:16px;line-height:1.7;">
|
||||
<li>Meeting starten & steuern</li>
|
||||
<li>Timer, Navigation</li>
|
||||
<li>Abwesenheiten markieren</li>
|
||||
<li>Vertreter & Besucher eintragen</li>
|
||||
<li>Stammvertreter verwenden</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div style="background:#fef2f2;border-radius:8px;padding:12px;border:1px solid #fecaca;">
|
||||
<div style="font-weight:700;color:#dc2626;font-size:0.85rem;margin-bottom:6px;">✗ Braucht Internet</div>
|
||||
<ul style="font-size:0.82rem;color:#991b1b;padding-left:16px;line-height:1.7;">
|
||||
<li>Erstmalige Aktivierung</li>
|
||||
<li>Mitglieder vom Server laden</li>
|
||||
<li>Fotos auf Server hochladen</li>
|
||||
<li>Stammvertreter sichern</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="highlight-box">
|
||||
<strong>Empfehlung:</strong> Vor jedem Meeting einmal kurz „Vom Server laden" klicken — so hast du immer die aktuellste Mitgliederliste & Fotos, auch wenn beim Meeting kein Internet verfügbar ist.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 10. UPDATE ========== -->
|
||||
<div class="section" id="update">
|
||||
<div class="section-header">
|
||||
<div class="step-num">10</div>
|
||||
<h2>Updates einspielen</h2>
|
||||
</div>
|
||||
|
||||
<div class="info">
|
||||
Wenn eine neue Version verfügbar ist, erscheint oben im Steuerungs-Fenster automatisch ein roter Hinweis-Banner.
|
||||
</div>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-bar">
|
||||
<div class="mockup-dot" style="background:#ff5f57;"></div>
|
||||
<div class="mockup-dot" style="background:#febc2e;"></div>
|
||||
<div class="mockup-dot" style="background:#28c840;"></div>
|
||||
<span style="color:rgba(255,255,255,0.4);font-size:0.78rem;margin-left:8px;">Steuerung – Update-Banner</span>
|
||||
</div>
|
||||
<div class="mockup-body">
|
||||
<div style="background:#1a2744;color:white;padding:10px 14px;border-radius:8px;display:flex;align-items:center;gap:12px;font-size:0.85rem;">
|
||||
<span>🆕 Update 1.2.0 verfügbar – Timer-Schalter im Admin-Menü</span>
|
||||
<button class="mock-btn-red" style="padding:4px 12px;font-size:0.82rem;white-space:nowrap;">Jetzt laden</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="steps">
|
||||
<div class="step"><p>Auf <strong>„Jetzt laden"</strong> klicken.</p></div>
|
||||
<div class="step"><p>Es öffnen sich zwei Speichern-Dialoge nacheinander: erst für <code>index.html</code>, dann wird <code>admin.html</code> automatisch heruntergeladen.</p></div>
|
||||
<div class="step"><p>Im Speichern-Dialog zum BNI-Ordner navigieren und die bestehende <code>index.html</code> <strong>überschreiben</strong>.</p></div>
|
||||
<div class="step"><p><code>admin.html</code> aus dem Download-Ordner ebenfalls in den BNI-Ordner verschieben und ersetzen.</p></div>
|
||||
<div class="step"><p>Seite neu laden — das Update ist eingespielt.</p></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tip">
|
||||
<strong>Hinweis:</strong> Alle gespeicherten Daten (Mitglieder, Fotos, Stammvertreter) bleiben beim Update erhalten.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 11. TIMER-SCHALTER ========== -->
|
||||
<div class="section" id="timer-schalter">
|
||||
<div class="section-header">
|
||||
<div class="step-num">11</div>
|
||||
<h2>Zeitanzeige deaktivieren</h2>
|
||||
</div>
|
||||
|
||||
<div class="info">
|
||||
Manche Chapter möchten keine Zeitanzeige verwenden. Diese kann im Admin-Tab der Steuerung dauerhaft ausgeschaltet werden.
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="steps">
|
||||
<div class="step"><p>Im Steuerungs-Fenster den Tab <strong>„Admin"</strong> (rechte Seitenleiste) öffnen.</p></div>
|
||||
<div class="step"><p>Unter <strong>„Einstellungen"</strong> den Haken bei <strong>„Zeitanzeige aktiv"</strong> entfernen.</p></div>
|
||||
<div class="step"><p>Timer-Ring, Countdown und Zeitbadges verschwinden sofort auf allen Ansichten.</p></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-bar">
|
||||
<div class="mockup-dot" style="background:#ff5f57;"></div>
|
||||
<div class="mockup-dot" style="background:#febc2e;"></div>
|
||||
<div class="mockup-dot" style="background:#28c840;"></div>
|
||||
<span style="color:rgba(255,255,255,0.4);font-size:0.78rem;margin-left:8px;">Steuerung – Admin-Tab</span>
|
||||
</div>
|
||||
<div class="mockup-body">
|
||||
<div style="background:#1e2235;border-radius:10px;padding:14px;">
|
||||
<div style="color:rgba(255,255,255,0.4);font-size:0.72rem;font-weight:700;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.85rem;color:white;">
|
||||
<input type="checkbox" checked style="width:15px;height:15px;">
|
||||
Zeitanzeige aktiv
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tip">
|
||||
<strong>Hinweis:</strong> Die Einstellung wird automatisch gespeichert und bleibt nach einem Neustart erhalten.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<div style="text-align:center;padding:32px 0;color:var(--muted);font-size:0.8rem;border-top:1px solid var(--border);margin-top:32px;">
|
||||
BNI Vorstellungsrunde · Interne Anleitung · Bei Fragen an den Administrator wenden
|
||||
</div>
|
||||
|
||||
</div><!-- .content -->
|
||||
</body>
|
||||
</html>
|
||||
BIN
apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
chapters/.DS_Store
vendored
6
chapters/epsilon-847362/config.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"chapterName": "BNI Chapter Epsilon",
|
||||
"syncUrl": "https://bni-ulm.com/epsilon/de/memberlist?chapterName=®ionIds=43671&isChapterwebsite=true",
|
||||
"websiteUrl": "https://bni-ulm.de/epsilon",
|
||||
"defaultTimer": 60
|
||||
}
|
||||
6
chapters/helios-2cbws6/config.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"chapterName": "BNI Chapter Helios",
|
||||
"syncUrl": "https://bni-konstanz.de/helios/de/memberlist?chapterName=40832®ionIds=6023&isChapterwebsite=true",
|
||||
"websiteUrl": "https://bni-konstanz.de/helios",
|
||||
"defaultTimer": 60
|
||||
}
|
||||
259
chapters/helios-2cbws6/members.json
Normal file
BIN
chapters/lumora-567294212/BNI Logo.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
90
chapters/lumora-567294212/agenda.json
Normal file
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": "willkommen",
|
||||
"label": "Willkommen",
|
||||
"type": "embed",
|
||||
"notes": "wie könnte euer Produkt als Weihnachtsgeschenk vermarktet werden?\n\nVertreter:\nAlexander Kaya für Sabine Welp\nSandra Stengel für Nathalie Musto\nStefan Roth für Jennifer Born\nNiko Gäßler für Thomas Stetter\nAlexander Weimer für Jens Berger\n\n\nWert: Lebenslanges Lernen - was habt ihr dieses Jahr gelernt?",
|
||||
"embedUrl": "https://show.zohopublic.eu/publish/cb5g063c60ec44c3347ad841c139671232dff"
|
||||
},
|
||||
{
|
||||
"id": "trainingsbeitrag",
|
||||
"label": "Trainingsbeitrag",
|
||||
"type": "embed",
|
||||
"embedUrl": "https://show.zohopublic.eu/publish/cb5g066608ae9da024f8e87c0e705dbe6852f"
|
||||
},
|
||||
{
|
||||
"id": "visitenkarten",
|
||||
"label": "Visitenkarten",
|
||||
"type": "image",
|
||||
"notes": "07:15",
|
||||
"imagePath": "agenda/visitenkarten.jpg"
|
||||
},
|
||||
{
|
||||
"id": "verleihung-der-lumora",
|
||||
"label": "Verleihung der Lumora",
|
||||
"type": "embed",
|
||||
"embedUrl": "https://show.zohopublic.eu/publish/cb5g0385ba8f3e8a8422ab051f1591bcf598d"
|
||||
},
|
||||
{
|
||||
"id": "bericht-des-mitgliederkoordinators",
|
||||
"label": "Bericht des Mitgliederkoordinators",
|
||||
"type": "embed",
|
||||
"notes": "07:48",
|
||||
"embedUrl": "https://show.zohopublic.eu/publish/cb5g0d27561ab34c141a39f981ab6b36fbd03"
|
||||
},
|
||||
{
|
||||
"id": "break-out",
|
||||
"label": "Break Out",
|
||||
"type": "embed",
|
||||
"notes": "07:51",
|
||||
"embedUrl": "https://show.zohopublic.eu/publish/cb5g0fbb79876ba15437abf469c098a72b789"
|
||||
},
|
||||
{
|
||||
"id": "naechste-hauptpraesentation",
|
||||
"label": "Nächste Hauptpräsentation",
|
||||
"type": "embed",
|
||||
"embedUrl": "https://show.zohopublic.eu/publish/cb5g0ba90bb948a91426d9bb4bbb59bb5bf9d"
|
||||
},
|
||||
{
|
||||
"id": "marketing-ergebnisrunde-erklaerung",
|
||||
"label": "Marketing- Ergebnisrunde Erklärung",
|
||||
"type": "embed",
|
||||
"notes": "08:12",
|
||||
"embedUrl": "https://show.zohopublic.eu/publish/cb5g0848053a6eea54f18bbaaee7d33861386"
|
||||
},
|
||||
{
|
||||
"id": "feedback-gaeste",
|
||||
"label": "Feedback Gäste",
|
||||
"type": "embed",
|
||||
"embedUrl": "https://show.zohopublic.eu/publish/cb5g0da62ecf51cdf4d9285097db2c9b80b26"
|
||||
},
|
||||
{
|
||||
"id": "ziehung-des-tagespreises",
|
||||
"label": "Ziehung des Tagespreises",
|
||||
"type": "embed",
|
||||
"notes": "08:20",
|
||||
"embedUrl": "https://show.zohopublic.eu/publish/cb5g0b231f4b1bdf34850a8a8baca4b2d6972"
|
||||
},
|
||||
{
|
||||
"id": "bericht-des-schatzmeisters",
|
||||
"label": "Bericht des Schatzmeisters",
|
||||
"type": "embed",
|
||||
"notes": "wer am Termin in Dellmensingen nicht da ist, denk daran das Geld davor an Jens zu geben / überweisen - wir bringen ihn sonst in die blöde Lage, dass er privat das Geld auslegen muss",
|
||||
"embedUrl": "https://show.zohopublic.eu/publish/cb5g0249a5f37a97e419db184bef50bcbdb8e"
|
||||
},
|
||||
{
|
||||
"id": "bericht-des-eventkoordinators",
|
||||
"label": "Bericht des Eventkoordinators",
|
||||
"type": "embed",
|
||||
"notes": "08:23",
|
||||
"embedUrl": "https://show.zohopublic.eu/publish/cb5g0ad05a18b97214634818823cd3bd3ebca"
|
||||
},
|
||||
{
|
||||
"id": "zitat",
|
||||
"label": "Zitat",
|
||||
"type": "embed",
|
||||
"embedUrl": "https://show.zohopublic.eu/publish/cb5g03d4fc1ad7a8141c098addbee9dfcbffb"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
chapters/lumora-567294212/agenda/visitenkarten.jpg
Normal file
|
After Width: | Height: | Size: 197 KiB |
7
chapters/lumora-567294212/config.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"chapterName": "BNI Chapter Lumora Hybrid",
|
||||
"logoUrl": "BNI Logo.png",
|
||||
"syncUrl": "https://bni-ulm.com/lumora/de/memberlist?chapterName=44269®ionIds=43671&isChapterwebsite=true",
|
||||
"websiteUrl": "https://bni-ulm.de/lumora",
|
||||
"defaultTimer": 60
|
||||
}
|
||||
184
chapters/lumora-567294212/members.json
Normal file
69
chapters/lumora-567294212/stammvertreter.json
Normal file
41
chapters/sonne-7x4m9k/agenda.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": "bericht-der-schatzmeisterin",
|
||||
"label": "Bericht der Schatzmeisterin",
|
||||
"type": "image",
|
||||
"notes": "Hier sollte was stehen",
|
||||
"imagePath": "agenda/bericht-der-schatzmeisterin.jpg"
|
||||
},
|
||||
{
|
||||
"id": "beispieldatei",
|
||||
"label": "Beispieldatei",
|
||||
"type": "embed",
|
||||
"embedUrl": "https://workdrive.digiseed.de/writer/open/a5u6x594bfa69700846d88739fa2756082ada"
|
||||
},
|
||||
{
|
||||
"id": "willkommen",
|
||||
"label": "Willkommen",
|
||||
"type": "image",
|
||||
"imagePath": "agenda/willkommen.jpg"
|
||||
},
|
||||
{
|
||||
"id": "ende-logo",
|
||||
"label": "Ende - Logo",
|
||||
"type": "image",
|
||||
"imagePath": "agenda/ende-logo.jpg"
|
||||
},
|
||||
{
|
||||
"id": "witz-des-tages",
|
||||
"label": "Witz des Tages",
|
||||
"type": "embed",
|
||||
"embedUrl": "https://digiseed.de"
|
||||
},
|
||||
{
|
||||
"id": "schatzmeister-offline",
|
||||
"label": "Schatzmeister Offline",
|
||||
"type": "image",
|
||||
"imagePath": "agenda/schatzmeister-offline.jpg"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
chapters/sonne-7x4m9k/agenda/bericht-der-schatzmeisterin.jpg
Normal file
|
After Width: | Height: | Size: 196 KiB |
BIN
chapters/sonne-7x4m9k/agenda/ende-logo.jpg
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
chapters/sonne-7x4m9k/agenda/schatzmeister-offline.jpg
Normal file
|
After Width: | Height: | Size: 277 KiB |
BIN
chapters/sonne-7x4m9k/agenda/willkommen.jpg
Normal file
|
After Width: | Height: | Size: 72 KiB |
@@ -3,5 +3,5 @@
|
||||
"syncUrl": "https://bni-ulm.com/sonne/de/memberlist?chapterName=6035®ionIds=43671&isChapterwebsite=true",
|
||||
"websiteUrl": "https://bni-ulm.de/sonne",
|
||||
"defaultTimer": 60,
|
||||
"memberCoordinatorReport": true
|
||||
"memberCoordinatorReport": false
|
||||
}
|
||||
BIN
favicon.ico
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
10
favicon.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<g stroke="#F5D822" stroke-width="3.5" stroke-linecap="round">
|
||||
<path d="M32 2v8M32 54v8M2 32h8M54 32h8M10.8 10.8l5.7 5.7M47.5 47.5l5.7 5.7M10.8 53.2l5.7-5.7M47.5 16.5l5.7-5.7"/>
|
||||
</g>
|
||||
<circle cx="32" cy="32" r="18" fill="#F5D822" stroke="#C70607" stroke-width="3"/>
|
||||
<g fill="none" stroke="#C70607" stroke-width="2.6" stroke-linecap="round">
|
||||
<path d="M23 29.5q3-4 6 0M35 29.5q3-4 6 0"/>
|
||||
<path d="M23.5 36q8.5 8 17 0"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 512 B |
1057
index.html
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"version": "1.5",
|
||||
"notes": "Zeitanzeige oben ergänzt. letzte 15 Sekunden gelber Hintergrund, letzte 5 Sekunden roter Hintergrund"
|
||||
"version": "2.2",
|
||||
"notes": "Neue, verschlüsselte Serveradresse. Bitte jetzt aktualisieren – die alte Version kann ab 1. November keine Daten mehr laden oder speichern."
|
||||
}
|
||||