/* ============================================================ BAN YARO — Hunde-Knigge Seiten-Modul: Begegnungen, Community-Voting, KI-Rat, Haftpflicht. ============================================================ */ window.Page_knigge = (() => { // ---------------------------------------------------------- // MODUL-STATE // ---------------------------------------------------------- let _container = null; let _appState = null; // Voting-State: { szenario_id: { counts: {}, user_answer: null } } const _voteState = {}; // ---------------------------------------------------------- // HARDCODED INHALTE // ---------------------------------------------------------- const BEGEGNUNGEN = [ { icon: '', titel: 'Fremder Hund', tipps: 'Kurze Leine, ruhig bleiben, Hunde schnüffeln lassen wenn beide entspannt. Eskalation: weglenken, Richtung wechseln.', }, { icon: '', titel: 'Kinder', tipps: 'Hund nie unbeaufsichtigt mit Kindern. Kind fragen ob es streicheln darf. Hund dahinter positionieren, nicht zwischen Kind und Weg.', }, { icon: '', titel: 'Radfahrer', tipps: 'Hund an die Seite nehmen. Fahrrad = potentielle Bedrohung für manche Hunde. Frühzeitig weglenken.', }, { icon: '', titel: 'Jogger', tipps: 'Kurze Leine, Abstand halten, Hund nicht anspringen lassen.', }, { icon: '', titel: 'ÖPNV', tipps: 'Maulkorbpflicht gilt im ÖPNV (Deutschland-weit). Kleine Hunde in Transportbox kostenlos, große Hunde brauchen Fahrschein. Regeln je Stadt unterschiedlich.', }, { icon: '', titel: 'Supermarkt / Geschäfte', tipps: 'Grundsätzlich Hausrecht des Betreibers. "Hunde willkommen"-Schild = explizite Einladung. Im Zweifel fragen. Außen anbinden nur kurz und beaufsichtigt.', }, ]; const SZENARIEN = [ { id: 'begegnung_leine', frage: 'Dein Hund ist gut sozialisiert und läuft frei. Ein angeleinten Hund kommt entgegen. Was tust du?', antworten: [ { key: 'a', text: 'Hund weiterlaufen lassen — er ist ja friedlich' }, { key: 'b', text: 'Hund anleinen und Abstand halten' }, { key: 'c', text: 'Besitzer fragen ob ein Treffen ok ist' }, ], richtig: 'b', erklaerung: 'Freilaufende Hunde auf angeleine Hunde zulaufen zu lassen ist unhöflich und kann den angeleinten Hund in Stress versetzen ("Leinenfrust"). Immer erst anleinen und Abstand halten.', }, { id: 'gassi_kot', frage: 'Dein Hund macht sein Geschäft im Park abseits des Weges im Gebüsch. Was machst du?', antworten: [ { key: 'a', text: 'Liegenlassen — im Gebüsch stört es niemanden' }, { key: 'b', text: 'Aufsammeln, auch wenn es versteckt liegt' }, { key: 'c', text: 'Nur aufsammeln wenn jemand zuschaut' }, ], richtig: 'b', erklaerung: 'Kot grundsätzlich immer aufsammeln — auch im Gebüsch. Kinder spielen überall, und Parasiten (z.B. Spulwurm) können für Menschen gefährlich sein.', }, { id: 'restaurant_hund', frage: 'Im Restaurant-Außenbereich sitzt du mit deinem Hund. Ein anderer Gast bittet dich deinen Hund wegzunehmen weil er Angst hat. Was tust du?', antworten: [ { key: 'a', text: 'Ablehnen — Außenbereich ist hundefreundlich' }, { key: 'b', text: 'Hund wegsetzen oder selbst weiter hinten platzieren' }, { key: 'c', text: 'Personal entscheiden lassen' }, ], richtig: 'c', erklaerung: 'Das Personal / der Betreiber entscheidet über das Hausrecht. Gut wäre es, selbst Kompromissbereitschaft zu zeigen und den Hund etwas wegzurücken — das deeskaliert und signalisiert Rücksicht.', }, { id: 'anleine_pflicht', frage: 'Im Park gibt es keine Schilder. Muss dein Hund an die Leine?', antworten: [ { key: 'a', text: 'Nein — kein Schild bedeutet keine Pflicht' }, { key: 'b', text: 'Kommt auf die Gemeindeordnung an' }, { key: 'c', text: 'Ja — immer Leinenpflicht in öffentlichen Parks' }, ], richtig: 'b', erklaerung: 'Leinenpflicht ist Ländersache und variiert stark. Viele Bundesländer haben eine allgemeine Anleinpflicht in Ortschaften oder Parks. Im Zweifel Hund anleinen oder Gemeindewebsite prüfen.', }, ]; // ---------------------------------------------------------- // INIT // ---------------------------------------------------------- async function init(container, appState) { _container = container; _appState = appState; _render(); _loadAllVotes(); } function refresh() { // statische Seite — kein Reload nötig } // ---------------------------------------------------------- // HAUPT-RENDER // ---------------------------------------------------------- function _render() { _container.innerHTML = `
${_renderBegegnungen()} ${_renderVoting()} ${_renderKiRat()} ${_renderHaftpflicht()}
`; _bindAccordion(); _bindVoting(); _bindKiRat(); } // ---------------------------------------------------------- // SECTION 1: BEGEGNUNGEN — Accordion-Karten // ---------------------------------------------------------- function _renderBegegnungen() { const cards = BEGEGNUNGEN.map((b, i) => `
`).join(''); return `

${UI.icon('paw-print')} Begegnungen

${cards}
`; } function _bindAccordion() { _container.querySelectorAll('.knigge-accordion-head').forEach(btn => { btn.addEventListener('click', () => { const i = btn.dataset.acc; const body = document.getElementById(`acc-body-${i}`); const arrow = btn.querySelector('.knigge-accordion-arrow'); const open = !body.hidden; body.hidden = open; btn.setAttribute('aria-expanded', String(!open)); arrow.innerHTML = open ? UI.icon('caret-down') : UI.icon('caret-up'); }); }); } // ---------------------------------------------------------- // SECTION 2: COMMUNITY VOTING // ---------------------------------------------------------- function _renderVoting() { const cards = SZENARIEN.map(s => `

${_esc(s.frage)}

${s.antworten.map(a => ` `).join('')}
`).join(''); return `

${UI.icon('star')} Was wäre richtig?

${cards} `; } function _bindVoting() { _container.querySelectorAll('.knigge-vote-btn').forEach(btn => { btn.addEventListener('click', async () => { const szId = btn.dataset.sz; const key = btn.dataset.key; if (!_appState.user) { UI.toast.warning('Bitte melde dich an um abzustimmen.'); return; } try { const result = await API.knigge.vote(szId, key); _voteState[szId] = { counts: result.counts, user_answer: result.user_answer }; _renderVoteResult(szId); } catch (err) { UI.toast.error(err.message || 'Fehler beim Abstimmen.'); } }); }); } async function _loadAllVotes() { for (const s of SZENARIEN) { try { const result = await API.knigge.votes(s.id); _voteState[s.id] = { counts: result.counts, user_answer: result.user_answer }; if (result.user_answer) { _renderVoteResult(s.id); } } catch { // ignorieren — Votes werden on-demand geladen } } } function _renderVoteResult(szId) { const szenario = SZENARIEN.find(s => s.id === szId); if (!szenario) return; const state = _voteState[szId]; if (!state) return; const optsEl = document.getElementById(`opts-${szId}`); const resEl = document.getElementById(`res-${szId}`); if (!optsEl || !resEl) return; // Optionen ausblenden optsEl.classList.add('hidden'); resEl.classList.remove('hidden'); const counts = state.counts || {}; const userAnswer = state.user_answer; const total = Object.values(counts).reduce((s, c) => s + c, 0) || 1; const isCorrect = userAnswer === szenario.richtig; const bars = szenario.antworten.map(a => { const cnt = counts[a.key] || 0; const pct = Math.round((cnt / total) * 100); const isU = a.key === userAnswer; const isR = a.key === szenario.richtig; const color = isR ? 'var(--c-success, #22c55e)' : (isU && !isR ? 'var(--c-danger, #ef4444)' : 'var(--c-border)'); return `
${isU ? UI.icon('arrow-right') + ' ' : ''}${_esc(a.text)}${isR ? ' ' + UI.icon('check') : ''} ${pct}% (${cnt})
`; }).join(''); const badge = isCorrect ? `${UI.icon('check')} Richtig!` : `${UI.icon('x')} Nicht ganz — `; resEl.innerHTML = `
${bars}
${badge} ${_esc(szenario.erklaerung)}
`; } // ---------------------------------------------------------- // SECTION 3: KI-SITUATIONSBERATER // ---------------------------------------------------------- function _renderKiRat() { return `

${UI.icon('robot')} KI-Situationsberater

`; } function _bindKiRat() { const btn = _container.querySelector('#ki-rat-btn'); const input = _container.querySelector('#ki-situation-input'); const result = _container.querySelector('#ki-rat-result'); btn?.addEventListener('click', async () => { const situation = input?.value?.trim(); if (!situation) { UI.toast.warning('Bitte beschreibe zuerst deine Situation.'); return; } if (!_appState.user) { UI.toast.warning('Bitte melde dich an um den KI-Rat zu nutzen.'); return; } UI.setLoading(btn, true); result.style.display = 'none'; try { const data = await API.knigge.kiRat(situation); result.innerHTML = `
${UI.icon('robot')} KI-Rat
${_esc(data.rat)}
`; result.style.display = 'block'; } catch (err) { const is402 = err.status === 402 || err.status === 503; result.innerHTML = `
${is402 ? 'Für KI-Rat wird Ban Yaro Plus oder ein laufender KI-Server benötigt.' : _esc(err.message || 'Fehler beim KI-Abruf.')}
`; result.style.display = 'block'; } finally { UI.setLoading(btn, false); } }); } // ---------------------------------------------------------- // SECTION 4: HAFTPFLICHT-HINWEISE // ---------------------------------------------------------- function _renderHaftpflicht() { return `

${UI.icon('shield')} Haftpflicht-Hinweise

Dies ist keine Rechtsberatung.

`; } // ---------------------------------------------------------- // HELPER // ---------------------------------------------------------- function _esc(str) { if (!str) return ''; return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } // ---------------------------------------------------------- // PUBLIC // ---------------------------------------------------------- return { init, refresh }; })();