diff --git a/backend/static/js/pages/dog-profile.js b/backend/static/js/pages/dog-profile.js
index 5e40c51..f80b034 100644
--- a/backend/static/js/pages/dog-profile.js
+++ b/backend/static/js/pages/dog-profile.js
@@ -195,9 +195,18 @@ window.Page_dog_profile = (() => {
Hundepass
` : ''}
+ ${!dog.is_guest ? `` : ''}
${!dog.is_guest ? `` : ''}
+ ${!dog.is_guest ? `` : ''}
@@ -264,6 +273,14 @@ window.Page_dog_profile = (() => {
_showPassportModal(dog);
});
+ document.getElementById('dp-vcard-btn')?.addEventListener('click', () => {
+ _showVcardModal(dog);
+ });
+
+ document.getElementById('dp-wrapped-btn')?.addEventListener('click', () => {
+ _showWrappedModal(dog);
+ });
+
// Edit- und Add-Klicks laufen über Event-Delegation in init() — keine direkten Listener nötig.
}
@@ -750,6 +767,138 @@ window.Page_dog_profile = (() => {
// ----------------------------------------------------------
// TEILEN
// ----------------------------------------------------------
+ // ----------------------------------------------------------
+ // HUNDE-VISITENKARTE MIT QR-CODE
+ // ----------------------------------------------------------
+ function _showVcardModal(dog) {
+ const passportUrl = `https://banyaro.app/hund/${dog.id}`;
+ const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=140x140&color=ffffff&bgcolor=1a2035&data=${encodeURIComponent(passportUrl)}`;
+
+ const user = _appState?.user;
+ const ownerName = user?.name || '';
+ const wohnort = user?.wohnort || '';
+
+ // Alter errechnen
+ let alterStr = '';
+ if (dog.geburtstag) {
+ const birth = new Date(dog.geburtstag + 'T00:00:00');
+ const now = new Date();
+ const years = now.getFullYear() - birth.getFullYear()
+ - (now < new Date(now.getFullYear(), birth.getMonth(), birth.getDate()) ? 1 : 0);
+ alterStr = years < 1
+ ? `${Math.max(1, Math.round((now - birth) / (30.5 * 86400000)))} Monate`
+ : years === 1 ? '1 Jahr' : `${years} Jahre`;
+ }
+
+ const metaLine = [dog.rasse, alterStr].filter(Boolean).join(' · ');
+
+ const cardHtml = `
+
+
+
+
+
+
+
+
+ ${dog.foto_url
+ ? `
})
`
+ : `
🐾
`}
+
+
${_esc(dog.name)}
+ ${metaLine ? `
${_esc(metaLine)}
` : ''}
+ ${wohnort ? `
📍 ${_esc(wohnort)}
` : ''}
+
+
+
+
+
+
+
+
+
+ ${ownerName ? `
Besitzer
+
${_esc(ownerName)}
` : ''}
+
banyaro.app
+
+
+
})
+
Profil öffnen
+
+
+
+ `;
+
+ UI.modal.open({
+ title: 'Visitenkarte',
+ body: `
+ ${cardHtml}
+
+ QR-Code auf NFC-Tag oder Anhänger kleben — jeder kann das Profil von ${_esc(dog.name)} sofort öffnen.
+
+ `,
+ footer: `
+
+
+ `,
+ });
+
+ // Link kopieren
+ document.getElementById('dp-vcard-copy-btn')?.addEventListener('click', async () => {
+ try {
+ await navigator.clipboard.writeText(passportUrl);
+ UI.toast.success('Link kopiert!');
+ } catch {
+ const inp = document.createElement('input');
+ inp.value = passportUrl;
+ document.body.appendChild(inp);
+ inp.select();
+ document.execCommand('copy');
+ inp.remove();
+ UI.toast.success('Link kopiert!');
+ }
+ });
+
+ // Native Share API
+ document.getElementById('dp-vcard-share-btn')?.addEventListener('click', async () => {
+ if (navigator.share) {
+ try {
+ await navigator.share({
+ title: `${dog.name} auf Ban Yaro`,
+ text: `Schau dir das Profil von ${dog.name} an!`,
+ url: passportUrl,
+ });
+ } catch {}
+ } else {
+ // Fallback: kopieren
+ try {
+ await navigator.clipboard.writeText(passportUrl);
+ UI.toast.success('Link kopiert!');
+ } catch {
+ UI.toast.error('Teilen nicht verfügbar.');
+ }
+ }
+ });
+ }
+
async function _showShareModal(dog) {
UI.modal.open({
title: `${_esc(dog.name)} teilen`,
diff --git a/backend/static/js/worlds.js b/backend/static/js/worlds.js
index e34d163..daacde0 100644
--- a/backend/static/js/worlds.js
+++ b/backend/static/js/worlds.js
@@ -165,13 +165,24 @@ window.Worlds = (() => {
document.querySelectorAll('.wlabel').forEach((l, i) => l.classList.toggle('active', i === _cur));
}
+ function _fabOptions() {
+ const worldNames = ['jetzt', 'hund', 'welt'];
+ const chips = _chipsForWorld(worldNames[_cur]);
+ const opts = [];
+ for (const chip of chips) {
+ if (chip.fab) for (const o of chip.fab) { if (opts.length < 6) opts.push(o); }
+ }
+ return opts;
+ }
+
function _updateFab() {
const fab = document.getElementById('worlds-fab');
if (!fab) return;
- const icons = ['note-pencil', 'paw-print', 'warning'];
- const titles = ['Schnelleintrag', 'Hund-Eintrag', 'Alarm melden'];
- fab.querySelector('use')?.setAttribute('href', `/icons/phosphor.svg#${icons[_cur]}`);
- fab.title = titles[_cur];
+ const opts = _fabOptions();
+ if (!opts.length) { fab.style.display = 'none'; return; }
+ fab.style.display = '';
+ fab.querySelector('use')?.setAttribute('href', `/icons/phosphor.svg#paw-print`);
+ fab.title = 'Schnellaktion';
}
function _setupButtons() {
@@ -195,21 +206,13 @@ window.Worlds = (() => {
}
function _openFab() {
- const isWelt = _cur === 2;
- const dogName = _state?.user ? null : null; // falls mehrere Hunde: erweiterbar
+ const options = _fabOptions();
+ if (!options.length) return;
- const options = isWelt ? [
- { icon:'skull', color:'#EF4444', label:'Giftköder melden', sub:'Warnung für andere Hundebesitzer', page:'poison' },
- { icon:'dog', color:'#3B82F6', label:'Verlorenen Hund melden', sub:'Hilf beim Wiederfinden', page:'lost' },
- { icon:'map-pin', color:'#10B981', label:'Ort vorschlagen', sub:'Neuen POI auf der Karte', page:'map' },
- ] : [
- { icon:'book-open', color:'#8B5CF6', label:'Tagebucheintrag', sub:'Erlebnis, Foto oder Notiz', page:'diary', action:'openNew' },
- { icon:'target', color:'#F59E0B', label:'Training aufzeichnen',sub:'Übung absolviert', page:'uebungen' },
- { icon:'heartbeat', color:'#EF4444', label:'Tierarztbesuch', sub:'Befund oder Impfung eintragen', page:'health' },
- { icon:'wave-sine', color:'#06B6D4', label:'Gewicht messen', sub:'Aktuelles Gewicht eintragen', page:'health' },
- ];
+ const meldenPages = new Set(['poison','lost','recalls','map']);
+ const meldenCount = options.filter(o => meldenPages.has(o.page)).length;
+ const title = meldenCount > options.length / 2 ? 'Was möchtest du melden?' : 'Was möchtest du eintragen?';
- // Overlay erstellen
const ov = document.createElement('div');
ov.id = 'fab-overlay';
ov.style.cssText = 'position:fixed;inset:0;z-index:300;display:flex;flex-direction:column;justify-content:flex-end';
@@ -219,9 +222,7 @@ window.Worlds = (() => {
padding:20px 16px calc(env(safe-area-inset-bottom,16px) + 16px);
box-shadow:0 -8px 32px rgba(0,0,0,0.2)">
-
- ${isWelt ? 'Was möchtest du melden?' : 'Was möchtest du eintragen?'}
-
+
${title}
${user ? userAvatarHtml : ''}