Dauerhaft gespeichert (bleibt nach BNI-Sync erhalten)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Profildaten (Kurzlinks + Offline-Fotos)
+
+ Kurzlinks und lokale Fotos werden getrennt gespeichert und bleiben nach jedem BNI-Sync erhalten.
+ Diese Datei im Projektordner ablegen und nach einem Browser-Reset wieder importieren.
+
+
+
+
+
+
@@ -1595,6 +1682,161 @@ function saveState(broadcast = true) {
}
}
+// ============================================================
+// PROFILE EXTRAS (Kurzlinks + Offline-Fotos, persistent)
+// ============================================================
+const EXTRAS_KEY = '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();
+}
+
+// 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. Foto öffnen ↗ → Rechtsklick → Bild speichern → dann über Datei wählen 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)
// ============================================================
@@ -1889,14 +2131,14 @@ function renderDisplay() {
qrWrap.style.display = 'block';
qrEl.innerHTML = '';
try {
- const qrSize = Math.round(Math.min(window.innerHeight * 0.16, window.innerWidth * 0.09, 200));
+ 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.M,
+ correctLevel: QRCode.CorrectLevel.L,
});
} catch(e) { qrWrap.style.display = 'none'; }
} else {
@@ -2097,13 +2339,15 @@ function getDisplayData(presenter) {
}
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: presenter.photoUrl || '',
- profileUrl: presenter.profileUrl || '',
+ photoUrl: ex.photoB64 || presenter.photoUrl || '',
+ profileUrl: ex.kurzlink || presenter.profileUrl || '',
// Substitute shown as secondary presenter
presentedBy: resolvedSub,
isVisitor: false,
@@ -2572,7 +2816,9 @@ function openAddMember() {
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');
}
@@ -2593,6 +2839,12 @@ function editMember(id) {
} 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');
}
@@ -2614,6 +2866,7 @@ function saveMember() {
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; }
@@ -2631,6 +2884,18 @@ function saveMember() {
}
}
+ // 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();
@@ -2651,6 +2916,8 @@ function deleteMember(id) {
}
function closeMemberModal() {
+ const hint = document.getElementById('offline-photo-url-hint');
+ if (hint) hint.remove();
closeModal('modal-member');
}
diff --git a/profildaten.json b/profildaten.json
new file mode 100644
index 0000000..d9312e3
--- /dev/null
+++ b/profildaten.json
@@ -0,0 +1,4 @@
+{
+ "version": 1,
+ "extras": {}
+}
diff --git a/profile-image-type-person-1367611.JPG b/profile-image-type-person-1367611.JPG
new file mode 100644
index 0000000..92ddf47
Binary files /dev/null and b/profile-image-type-person-1367611.JPG differ
diff --git a/profile-image-type-person-1368964.jpg b/profile-image-type-person-1368964.jpg
new file mode 100644
index 0000000..73bb049
Binary files /dev/null and b/profile-image-type-person-1368964.jpg differ
diff --git a/profile-image-type-person-1612262.jpeg b/profile-image-type-person-1612262.jpeg
new file mode 100644
index 0000000..80fff8b
Binary files /dev/null and b/profile-image-type-person-1612262.jpeg differ
diff --git a/profile-image-type-person-1861412.jpg b/profile-image-type-person-1861412.jpg
new file mode 100644
index 0000000..56d5b72
Binary files /dev/null and b/profile-image-type-person-1861412.jpg differ
diff --git a/profile-image-type-person-1889624.jpg b/profile-image-type-person-1889624.jpg
new file mode 100644
index 0000000..82c69cc
Binary files /dev/null and b/profile-image-type-person-1889624.jpg differ
diff --git a/profile-image-type-person-2132885.jpg b/profile-image-type-person-2132885.jpg
new file mode 100644
index 0000000..de44234
Binary files /dev/null and b/profile-image-type-person-2132885.jpg differ
diff --git a/profile-image-type-person-2155257.jpg b/profile-image-type-person-2155257.jpg
new file mode 100644
index 0000000..788471c
Binary files /dev/null and b/profile-image-type-person-2155257.jpg differ
diff --git a/profile-image-type-person-2249834.jpg b/profile-image-type-person-2249834.jpg
new file mode 100644
index 0000000..a1f0bce
Binary files /dev/null and b/profile-image-type-person-2249834.jpg differ
diff --git a/profile-image-type-person-2290957.jpg b/profile-image-type-person-2290957.jpg
new file mode 100644
index 0000000..55a8de2
Binary files /dev/null and b/profile-image-type-person-2290957.jpg differ
diff --git a/profile-image-type-person-2326664.jpg b/profile-image-type-person-2326664.jpg
new file mode 100644
index 0000000..f5312ee
Binary files /dev/null and b/profile-image-type-person-2326664.jpg differ
diff --git a/profile-image-type-person-317636.jpg b/profile-image-type-person-317636.jpg
new file mode 100644
index 0000000..3455a9d
Binary files /dev/null and b/profile-image-type-person-317636.jpg differ