Files

1028 lines
68 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function(){
"use strict";
/* ===================== BILDER (echte Fotos von feuerwehr-melle.de) ===================== */
const PIC = {
grund: {card:"assets/img/grundausbildung.jpg", thumb:"assets/img/grundausbildung-thumb.jpg", hero:"assets/img/grundausbildung.jpg"},
ehrung: {card:"assets/img/verbandstag25.jpg", thumb:"assets/img/verbandstag25-thumb.jpg"},
versammlung:{card:"assets/img/verbandstag26.jpg", thumb:"assets/img/verbandstag26-thumb.jpg"},
atemschutz: {card:"assets/img/atemschutz.jpg", thumb:"assets/img/atemschutz-thumb.jpg", hero:"assets/img/atemschutz.jpg"},
gefahrgut: {card:"assets/img/gefahrgut.jpg", thumb:"assets/img/gefahrgut-thumb.jpg", hero:"assets/img/gefahrgut.jpg"},
lehrgang: {card:"assets/img/ausbildung-celle.jpg",thumb:"assets/img/ausbildung-celle-thumb.jpg"},
kinder: {card:"assets/img/jugendflamme.jpg", thumb:"assets/img/jugendflamme-thumb.jpg"},
jugend: {card:"assets/img/leistungsspange.jpg", thumb:"assets/img/leistungsspange-thumb.jpg"},
fahrzeug: {card:"assets/img/gruppenbild.jpg", thumb:"assets/img/gruppenbild-thumb.jpg", hero:"assets/img/gruppenbild.jpg"},
auszeichnung:{card:"assets/img/altenmelle.jpg", thumb:"assets/img/altenmelle-thumb.jpg"},
fuehrung: {card:"assets/img/riemsloh.jpg", thumb:"assets/img/riemsloh-thumb.jpg"},
foerder: {card:"assets/img/pavillon.jpg", thumb:"assets/img/pavillon-thumb.jpg"},
partner: {card:"assets/img/partner.jpg", thumb:"assets/img/partner-thumb.jpg"},
technik: {card:"assets/img/defi.jpg", thumb:"assets/img/defi-thumb.jpg"},
verein: {card:"assets/img/entenrennen.jpg", thumb:"assets/img/entenrennen-thumb.jpg"},
/* Originalbanner der Ortsfeuerwehr Bakum von feuerwehr-melle.de */
bakum: {card:"assets/img/bakum-banner.jpg", thumb:"assets/img/bakum-banner-thumb.jpg", hero:"assets/img/bakum-banner.jpg"},
fztsfw: {card:"assets/img/fz-tsfw.jpg", thumb:"assets/img/fz-tsfw-thumb.jpg"},
fzelw: {card:"assets/img/fz-elw.jpg", thumb:"assets/img/fz-elw-thumb.jpg"},
fzfwa: {card:"assets/img/fz-fwa.jpg", thumb:"assets/img/fz-fwa-thumb.jpg"}
};
function src(key, size){
const p = PIC[key] || PIC.grund;
return p[size] || p.card;
}
function shot(key, size, cls, style){
return '<div class="ratio '+(cls||'')+'"'+(style?' style="'+style+'"':'')+'><img src="'+src(key,size)+'" alt="" loading="lazy"></div>';
}
/* ===================== ICONS ===================== */
const I = {
siren:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 6.5a4 4 0 0 1 4 4V14H8v-3.5a4 4 0 0 1 4-4Z"/><path d="M5.5 14h13v2.5a1.5 1.5 0 0 1-1.5 1.5H7a1.5 1.5 0 0 1-1.5-1.5Z"/><path d="M12 4V2.5M6.2 6.2 5 5M17.8 6.2 19 5M4 10H2.5M20 10h1.5"/><path d="M5 21h14"/></svg>',
doc:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 3h8l4 4v14H6Z"/><path d="M14 3v4h4M9 13h6M9 17h6"/></svg>',
/* Gefuellte Varianten fuer die Sektions-Ueberschriften (wie Vorlage) */
sirenF:'<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">'+
'<path fill-rule="evenodd" d="M12 5.3a5.4 5.4 0 0 1 5.4 5.4v3.2H6.6v-3.2A5.4 5.4 0 0 1 12 5.3Zm0 3.3a1.85 1.85 0 1 0 0 3.7 1.85 1.85 0 0 0 0-3.7Z"/>'+
'<rect x="4.1" y="14.9" width="15.8" height="3.6" rx="1.3"/>'+
'<rect x="0.7" y="9.8" width="3.3" height="1.9" rx=".95"/><rect x="20" y="9.8" width="3.3" height="1.9" rx=".95"/>'+
'<rect x="11.05" y="0.8" width="1.9" height="3.2" rx=".95"/>'+
'<path d="M5.1 4 7.1 6" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"/>'+
'<path d="M18.9 4 16.9 6" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"/></svg>',
star:'<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.4 14.65 8.35 21.13 9.03 16.29 13.39 17.64 19.77 12 16.51 6.36 19.77 7.71 13.39 2.87 9.03 9.35 8.35Z"/></svg>',
docF:'<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="'+
'M6.6 2.3h7.3l5.4 5.4V20a1.8 1.8 0 0 1-1.8 1.8H6.6A1.8 1.8 0 0 1 4.8 20V4.1A1.8 1.8 0 0 1 6.6 2.3Z'+
'M13.5 3.2v3.2a1.1 1.1 0 0 0 1.1 1.1h3.2Z'+
'M7.9 8.7h3.5v1.8H7.9Z M7.9 12.1h8.2v1.8H7.9Z M7.9 15.5h8.2v1.8H7.9Z"/></svg>',
calF:'<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">'+
'<path fill-rule="evenodd" d="M3.6 5.6h16.8A1.6 1.6 0 0 1 22 7.2v12.4a1.6 1.6 0 0 1-1.6 1.6H3.6A1.6 1.6 0 0 1 2 19.6V7.2a1.6 1.6 0 0 1 1.6-1.6Z M4.6 10.5h14.8v9.1H4.6Z"/>'+
'<rect x="6.5" y="2.8" width="2" height="4.4" rx="1"/><rect x="15.5" y="2.8" width="2" height="4.4" rx="1"/>'+
'<path d="M7.9 15.4 10.2 17.7 14.4 13.3" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"/></svg>',
cal:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3.5" y="5" width="17" height="15.5" rx="2.5"/><path d="M8 3v4M16 3v4M3.5 10h17"/><path d="M8 14h4"/></svg>',
calSmall:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" aria-hidden="true"><rect x="3.4" y="5.4" width="17.2" height="15.2" rx="2.4"/><path d="M8 3.2v4M16 3.2v4M3.4 10.2h17.2"/><path d="M8.7 15.3 10.6 17.2 14 13.6" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>',
pin:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 21s-7-6.1-7-11.5A7 7 0 0 1 19 9.5C19 14.9 12 21 12 21Z"/><circle cx="12" cy="9.5" r="2.4"/></svg>',
pinFill:'<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M12 22.2s7.7-7.2 7.7-12.6a7.7 7.7 0 1 0-15.4 0c0 5.4 7.7 12.6 7.7 12.6Zm0-9.3a3.3 3.3 0 1 0 0-6.6 3.3 3.3 0 0 0 0 6.6Z"/></svg>',
map:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 4 3 6.5v14L9 18l6 2.5 6-2.5v-14L15 6.5 9 4Zm0 0v14m6-11.5v14"/></svg>',
chevR:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m9 5 7 7-7 7"/></svg>',
chevL:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m15 5-7 7 7 7"/></svg>',
chevD:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>',
arrowR:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12h15M13 6l6 6-6 6"/></svg>',
home:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 11 12 4l8 7"/><path d="M6.5 9.6V20h11V9.6"/></svg>',
flame:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12.963 2.286a.75.75 0 0 0-1.071-.136 9.742 9.742 0 0 0-3.539 6.176 7.547 7.547 0 0 1-1.705-1.715.75.75 0 0 0-1.152-.082A9 9 0 1 0 15.68 4.534a7.46 7.46 0 0 1-2.717-2.248ZM15.75 14.25a3.75 3.75 0 1 1-7.313-1.172c.628.465 1.35.81 2.133 1a5.99 5.99 0 0 1 1.925-3.546 3.75 3.75 0 0 1 3.255 3.718Z"/></svg>',
news:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3.5" y="4.5" width="17" height="15" rx="2"/><path d="M7 9h10M7 13h10M7 17h6"/></svg>',
users:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="9" cy="8" r="3.2"/><path d="M2.5 20c.6-3.6 3.2-5.6 6.5-5.6s5.9 2 6.5 5.6"/><circle cx="17.5" cy="8.5" r="2.6"/><path d="M15.5 14.8c2.6.2 4.6 2 5.1 5.2"/></svg>',
helmet:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3.5 16a8.5 8.5 0 0 1 17 0Z"/><path d="M2.5 16h19M9 16V9.5a3 3 0 0 1 6 0V16"/></svg>',
heart:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20s-7.3-4.6-9.7-9.1C.7 7.6 2.2 4 5.7 3.4 8 3 10 4.1 12 6.6 14 4.1 16 3 18.3 3.4c3.5.6 5 4.2 3.4 7.5C19.3 15.4 12 20 12 20Z"/></svg>',
file:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 3h8l4 4v14H6Z"/><path d="M14 3v4h4M9 13h6M9 17h6"/></svg>',
phone:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 5h4l2 5-2.5 1.5a11 11 0 0 0 5 5L13 14l5 2v4a2 2 0 0 1-2 2A16 16 0 0 1 3 7a2 2 0 0 1 0-2Z"/></svg>',
mail:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="5.5" width="18" height="13" rx="2"/><path d="m3.5 7 8.5 6 8.5-6"/></svg>',
search:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.6-3.6"/></svg>',
clock:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M12 7v5.2l3.4 2"/></svg>',
cam:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" aria-hidden="true"><path d="M3.6 8.4h3.1l1.6-2.2h7.4l1.6 2.2h3.1a1.6 1.6 0 0 1 1.6 1.6v7.8a1.6 1.6 0 0 1-1.6 1.6H3.6A1.6 1.6 0 0 1 2 18v-7.8a1.6 1.6 0 0 1 1.6-1.6Z"/><circle cx="12" cy="13.6" r="3.5"/></svg>',
info:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M12 11v6M12 7.6v.1"/></svg>',
check:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12.5 9.5 17 19 7.5"/></svg>'
};
const MAP_BG = '<svg viewBox="0 0 300 200" preserveAspectRatio="xMidYMid slice" width="100%" height="100%" aria-hidden="true">'+
'<g stroke="currentColor" stroke-width="7" fill="none"><path d="M-10 40 320 20"/><path d="M-10 118 320 140"/><path d="M40 -10 70 210"/><path d="M180 -10 210 210"/><path d="M255 -10 268 210"/></g>'+
'<g fill="currentColor" opacity=".45"><rect x="80" y="45" width="60" height="45" rx="4"/><rect x="215" y="150" width="45" height="40" rx="4"/></g></svg>';
/* ===================== DEMO-DATEN ===================== */
const EINSAETZE = [
{id:'e1', time:'02:51', date:'16.09.2026', type:'Brandeinsatz', title:'Auslösung Brandmeldeanlage',
ort:'Westlandstraße, Westerhausen', zust:'Ortsfeuerwehr Oldendorf', alarm:'Funkmeldeempfänger', pic:'atemschutz',
einheiten:[{name:'OrtsFw Oldendorf'}, {name:'WerkFw Westland/Assmann'}],
bilder:['atemschutz','fahrzeug','bakum']},
{id:'e2', time:'22:58', date:'15.09.2026', type:'Technische Hilfeleistung', title:'Unterstützung Rettungsdienst',
ort:'Poggenort, Eicken-Bruche', zust:'Ortsfeuerwehr Bakum', alarm:'Funkmeldeempfänger', pic:'gefahrgut',
einheiten:[{name:'OrtsFw Bakum', fz:['ELW','TSF-W']}, {name:'DRK Rettungswache Melle', fz:['RTW']}],
bilder:['gefahrgut','fzelw','atemschutz','fztsfw']},
{id:'e3', time:'21:56', date:'15.09.2026', type:'Brandeinsatz', title:'Auslösung Brandmeldeanlage',
ort:'Westlandstraße, Westerhausen', zust:'Ortsfeuerwehr Oldendorf', alarm:'Funkmeldeempfänger', pic:'atemschutz',
einheiten:[{name:'OrtsFw Oldendorf'}, {name:'WerkFw Westland/Assmann'}]},
{id:'e4', time:'18:24', date:'14.09.2026', type:'Technische Hilfeleistung', title:'Baum auf Fahrbahn',
ort:'Bahnhofstraße, Buer', zust:'Ortsfeuerwehr Buer', alarm:'Funkmeldeempfänger', pic:'fahrzeug',
einheiten:[{name:'OrtsFw Buer', fz:['LF 10','MTW']}]},
{id:'e5', time:'09:12', date:'13.09.2026', type:'Brandeinsatz', title:'Kleinbrand Mülltonne',
ort:'Grönenbergstraße, Melle-Mitte', zust:'Ortsfeuerwehr Melle-Mitte', alarm:'Funkmeldeempfänger', pic:'atemschutz',
einheiten:[{name:'OrtsFw Melle-Mitte', fz:['LF 20']}]},
{id:'e6', time:'03:47', date:'12.09.2026', type:'Technische Hilfeleistung', title:'Türöffnung – Person in Notlage',
ort:'Ravensberger Straße, Gesmold', zust:'Ortsfeuerwehr Gesmold', alarm:'Funkmeldeempfänger', pic:'gefahrgut',
einheiten:[{name:'OrtsFw Gesmold', fz:['TSF-W']}, {name:'DRK Rettungswache Melle', fz:['RTW']}]},
{id:'e7', time:'14:03', date:'10.09.2026', type:'Brandeinsatz', title:'Brandmeldeanlage ausgelöst',
ort:'Industriestraße, Neuenkirchen', zust:'Ortsfeuerwehr Neuenkirchen', alarm:'Funkmeldeempfänger', pic:'fahrzeug',
einheiten:[{name:'OrtsFw Neuenkirchen', fz:['LF 10']}]},
{id:'e8', time:'20:15', date:'08.09.2026', type:'Technische Hilfeleistung', title:'Ölspur beseitigt',
ort:'Osnabrücker Straße, Wellingholzhausen', zust:'Ortsfeuerwehr Wellingholzhausen', alarm:'Funkmeldeempfänger', pic:'fahrzeug',
einheiten:[{name:'OrtsFw Wellingholzhausen', fz:['LF 16/12']}, {name:'OrtsFw Altenmelle', fz:['GW-L']}]},
{id:'e9', time:'17:42', date:'06.09.2026', type:'Technische Hilfeleistung', title:'Tragehilfe für den Rettungsdienst',
ort:'Bakumer Straße, Bakum', zust:'Ortsfeuerwehr Bakum', alarm:'Funkmeldeempfänger', pic:'gefahrgut',
einheiten:[{name:'OrtsFw Bakum', fz:['TSF-W']}, {name:'DRK Rettungswache Melle', fz:['RTW']}]},
{id:'e10', time:'11:20', date:'02.09.2026', type:'Brandeinsatz', title:'Rauchentwicklung in Gebäude',
ort:'Drantumer Weg, Drantum', zust:'Ortsfeuerwehr Bakum', alarm:'Sirene und Funkmeldeempfänger', pic:'atemschutz',
einheiten:[{name:'OrtsFw Bakum', fz:['TSF-W','ELW']}, {name:'OrtsFw Melle-Mitte', fz:['LF 20']}],
bilder:['atemschutz','fztsfw','bakum','fahrzeug']},
{id:'e11', time:'06:15', date:'27.08.2026', type:'Technische Hilfeleistung', title:'Ölspur nach Verkehrsunfall',
ort:'Eicken-Bruche', zust:'Ortsfeuerwehr Bakum', alarm:'Funkmeldeempfänger', pic:'fzelw',
einheiten:[{name:'OrtsFw Bakum', fz:['TSF-W']}]}
];
const NEWS = [
{id:'n1', cat:'Ausbildung', pic:'ehrung', date:'16.09.2026',
title:'Ehrung und Abschluss der 60. Grundausbildung in Melle',
exc:'21 neue Einsatzkräfte haben ihre Grundausbildung erfolgreich abgeschlossen. Bei einer Feierstunde wurden sie für ihr Engagement ausgezeichnet.',
body:['21 neue Einsatzkräfte haben in Melle ihre Grundausbildung erfolgreich abgeschlossen und verstärken ab sofort die Einsatzabteilungen der Ortsfeuerwehren.',
'Über mehrere Monate hinweg durchliefen die Teilnehmerinnen und Teilnehmer die theoretische und praktische Ausbildung — von Löscheinsatz und technischer Hilfeleistung bis zu Erster Hilfe und Gerätekunde.',
'Bei einer Feierstunde wurden die Absolventinnen und Absolventen für ihr Engagement geehrt.']},
{id:'n2', cat:'Auszeichnung', pic:'auszeichnung', date:'12.09.2026',
title:'Feuerwehr Melle ehrt langjährige Mitglieder',
exc:'Für langjährige, treue Dienstzeiten wurden mehrere Mitglieder der Ortsfeuerwehren ausgezeichnet.',
body:['Für ihre langjährige, treue Dienstzeit wurden mehrere Mitglieder der Ortsfeuerwehren im Rahmen einer Feierstunde ausgezeichnet.',
'Die Ehrungen würdigen ehrenamtlichen Einsatz, der oft über Jahrzehnte hinweg geleistet wird — bei Einsätzen ebenso wie in Ausbildung, Jugendarbeit und Vereinsleben.']},
{id:'n3', cat:'Ausbildung', pic:'gefahrgut', date:'09.09.2026',
title:'Realistische Einsatzübungen für die Einsatzkräfte',
exc:'In einer groß angelegten Übung trainierten mehrere Ortsfeuerwehren den Ernstfall unter realistischen Bedingungen.',
body:['In einer groß angelegten gemeinsamen Übung trainierten mehrere Ortsfeuerwehren den Ernstfall unter möglichst realistischen Bedingungen.',
'Im Fokus standen Menschenrettung, Löschangriff und die Zusammenarbeit mit Rettungsdienst und Führungsdienst.']},
{id:'n4', cat:'Fahrzeuge & Technik', pic:'fahrzeug', date:'01.09.2026',
title:'Neues Löschfahrzeug für die Ortsfeuerwehren',
exc:'Ein neues Löschfahrzeug wurde offiziell in Dienst gestellt und verstärkt ab sofort den Fuhrpark.',
body:['Ein neues Löschfahrzeug wurde offiziell in Dienst gestellt und verstärkt ab sofort den Fuhrpark der Ortsfeuerwehren.',
'Das Fahrzeug verfügt über moderne Beladung für Brandbekämpfung und technische Hilfeleistung und ersetzt ein älteres Modell im Bestand.']},
{id:'n5', cat:'Jugendfeuerwehr', pic:'kinder', date:'05.09.2026',
title:'Erfolgreicher Berufsfeuerwehrtag der Jugendfeuerwehr Melle',
exc:'Mitglieder der Jugendfeuerwehren erhielten spannende Einblicke in den Alltag einer Feuerwache.',
body:['Mitglieder der Jugendfeuerwehren erhielten spannende Einblicke in den Alltag einer Feuerwache.',
'Vom Fahrzeugcheck bis zur Übung konnten die Jugendlichen selbst Hand anlegen und viele Fragen stellen.']},
{id:'n6', cat:'Einsätze', pic:'atemschutz', date:'02.09.2026',
title:'Brand in Garage – schnelle Hilfe durch die Feuerwehr',
exc:'Ein Garagenbrand konnte durch den schnellen Einsatz der Ortsfeuerwehr rasch unter Kontrolle gebracht werden.',
body:['Ein Garagenbrand konnte durch den schnellen Einsatz der zuständigen Ortsfeuerwehr rasch unter Kontrolle gebracht werden.',
'Verletzt wurde niemand. Die Einsatzstelle wurde nach Abschluss der Löscharbeiten an die Polizei übergeben.']},
{id:'n7', cat:'Ortsfeuerwehren', pic:'fuehrung', date:'28.08.2026',
title:'Neue Führungskräfte in den Ortsfeuerwehren',
exc:'Mehrere Ortsfeuerwehren haben neue Führungskräfte gewählt und in ihre Ämter eingeführt.',
body:['Mehrere Ortsfeuerwehren haben im Rahmen ihrer Jahreshauptversammlungen neue Führungskräfte gewählt.',
'Die gewählten Kameradinnen und Kameraden wurden feierlich in ihre Ämter eingeführt.']},
{id:'n8', cat:'Ausbildung', pic:'lehrgang', date:'25.08.2026',
title:'Drohne unterstützt bei Waldbrandübung',
exc:'Bei einer Übung zur Waldbrandbekämpfung kam die Drohnenstaffel unterstützend zum Einsatz.',
body:['Bei einer Übung zur Waldbrandbekämpfung kam die Drohnenstaffel unterstützend zum Einsatz.',
'Aus der Luft lassen sich Glutnester und Ausbreitungsrichtungen deutlich schneller erkennen.']},
{id:'n9', cat:'Jugendfeuerwehr', pic:'jugend', date:'20.08.2026',
title:'Jugendfeuerwehren erhalten die Leistungsspange',
exc:'Zahlreiche Jugendliche stellten ihr Können unter Beweis und erhielten die Leistungsspange.',
body:['Zahlreiche Jugendliche der Meller Jugendfeuerwehren stellten ihr Können unter Beweis und erhielten die Leistungsspange.',
'Geprüft werden unter anderem Schnelligkeit, Ausdauer und feuerwehrtechnisches Können im Team.']},
{id:'n10', cat:'Förderverein', pic:'foerder', date:'14.08.2026',
title:'Förderverein unterstützt die Jugendarbeit',
exc:'Eine Spende kommt direkt der Arbeit der Kinder- und Jugendfeuerwehren zugute.',
body:['Eine Spende kommt direkt der Arbeit der Kinder- und Jugendfeuerwehren zugute.',
'Angeschafft werden davon Ausrüstung und Material für die regelmäßigen Dienste.']},
{id:'n11', cat:'Ortsfeuerwehren', pic:'versammlung', date:'07.08.2026',
title:'Jahreshauptversammlung der Feuerwehr Melle',
exc:'Rückblick auf das vergangene Jahr, Ehrungen und ein Ausblick auf kommende Vorhaben.',
body:['Rückblick auf das vergangene Jahr, Ehrungen und ein Ausblick auf kommende Vorhaben standen im Mittelpunkt der Jahreshauptversammlung.',
'Im Berichtsjahr wurden zahlreiche Einsätze abgearbeitet und viele Stunden in Aus- und Fortbildung investiert.']},
{id:'n12', cat:'Ortsfeuerwehren', pic:'technik', date:'30.07.2026',
title:'Defibrillator für die Ortsfeuerwehr',
exc:'Ein neuer Defibrillator steht ab sofort für den Ernstfall bereit.',
body:['Ein neuer Defibrillator steht ab sofort im Gerätehaus für den Ernstfall bereit.',
'Die Einsatzkräfte wurden in die Handhabung des Geräts eingewiesen.']}
];
/* Autoren — Platzhalter fuer die Demo. Echte Namen und Portraitfotos
lassen sich hier direkt eintragen (foto: 'assets/img/…' ergaenzen). */
const AUTOREN = {
presse: {name:'Presseteam Feuerwehr Melle', rolle:'Öffentlichkeitsarbeit'},
jugend: {name:'Redaktion Jugendfeuerwehr', rolle:'Stadtjugendfeuerwehr Melle'},
mitte: {name:'Pressewart Melle-Mitte', rolle:'Ortsfeuerwehr Melle-Mitte'}
};
const AUTOR_ZU = {n5:'jugend', n9:'jugend', n10:'jugend', n6:'mitte', n12:'mitte'};
NEWS.forEach(n => { n.autor = AUTOREN[AUTOR_ZU[n.id] || 'presse']; });
const TERMINE = [
{d:'20', m:'SEP', title:'Tag der offenen Tür', ort:'Ortsfeuerwehr Melle', zeit:'10:00–16:00 Uhr'},
{d:'25', m:'SEP', title:'Atemschutz-Übungsstrecke', ort:'Feuerwehrhaus Melle', zeit:'19:00 Uhr'},
{d:'03', m:'OKT', title:'Jugendfeuerwehr Dienst', ort:'Gerätehaus Melle', zeit:'17:30 Uhr'},
{d:'10', m:'OKT', title:'Kreisfeuerwehrtag', ort:'Landkreis Osnabrück', zeit:'09:00 Uhr'},
{d:'17', m:'OKT', title:'Gemeinschaftsdienst Ortsfeuerwehren', ort:'Gerätehaus Gesmold', zeit:'18:30 Uhr'},
{d:'01', m:'NOV', title:'Übung Wasserförderung über lange Strecke', ort:'Else, Buer', zeit:'09:00 Uhr'}
];
const slugify = s => s.toLowerCase().replace(/ä/g,'ae').replace(/ö/g,'oe').replace(/ü/g,'ue').replace(/ß/g,'ss').replace(/[^a-z0-9]+/g,'-').replace(/^-|-$/g,'');
const PICS_ROT = ['fahrzeug','auszeichnung','fuehrung','kinder','grund','technik','versammlung','jugend','atemschutz','gefahrgut','lehrgang','foerder','partner','verein','ehrung','fahrzeug'];
const ORTE = ['Altenmelle','Bakum','Bruchmühlen','Buer','Gesmold','Groß Aschen','Hoyel','Markendorf','Melle-Mitte','Neuenkirchen','Niederholsten','Oldendorf','Riemsloh','Sankt Annen','Tittingdorf','Wellingholzhausen']
.map((name,i)=>({name, pic:PICS_ROT[i], slug:slugify(name)}));
/* Ausgebaute Beispielseite — Inhalte von feuerwehr-melle.de/feuerwehren/bakum */
ORTE.find(o => o.slug === 'bakum').pic = 'bakum';
ORTE.find(o => o.slug === 'bakum').detail = {
lead:'Die Geschichte der Ortsfeuerwehr Bakum reicht bis in den Sommer 1906 zurück. Angefangen mit einer Handdruckspritze, sind heute ein TSF-W und ein ELW in Bakum stationiert.',
art:'Feuerwehr mit Grundausstattung',
gegruendet:'1906',
gebiet:['Bakum','Eicken-Bruche','Drantum','Teile von Laer','angrenzende Bereiche zu Melle-Mitte'],
schwerpunkte:['Mehrere Industrieparks','Ein Hotel','Teile der Eisenbahnlinie Osnabrück–Hannover','GrönegauBad Melle',
'Altenheim mit Kurzzeitpflege','Mehrere Schulen','Ausgedehnte Waldgebiete inkl. der Meller Berge'],
fahrzeuge:[
{k:'TSF-W', t:'Tragkraftspritzenfahrzeug-Wasser', sub:'MAN TGL 8.180 · Baujahr 2017 · Besatzung 1/5', pic:'fztsfw'},
{k:'ELW', t:'Einsatzleitwagen', sub:'Volkswagen T5', pic:'fzelw'},
{k:'FwA', t:'Feuerwehranhänger', sub:'mit Notstromaggregat', pic:'fzfwa'}
],
technik:['Hochleistungslüfter','Wasserstaubsauger','Wärmebildkamera'],
haus:'Das Feuerwehrhaus besitzt zwei Stellplätze für die Einsatzfahrzeuge, eine Garage mit Feuerwehranhänger und Notstromaggregat, einen Gruppenraum für Aus- und Weiterbildung, eine Teeküche sowie sanitäre Anlagen.',
alarm:'Ab dem Alarmstichwort „Feuer 3“ wird die Alarmeinheit aus Altenmelle, Bakum und Melle-Mitte über Sirene alarmiert.',
adresse:'Bakumer Straße 98, 49324 Melle',
tel:'05422 43329',
mail:'ortsfeuerwehr.bakum@feuerwehr-melle.de',
leitung:[{n:'Mirco Brinkmann', r:'Ortsbrandmeister', m:'ortsbm.bakum@feuerwehr-melle.de'},
{n:'Sebastian Krämer', r:'Stellv. Ortsbrandmeister', m:'stellv.ortsbm.bakum@feuerwehr-melle.de'}]
};
/* Sieben Jugendfeuerwehren + Kinderfeuerwehr (Stand feuerwehr-melle.de) */
const JF_PICS = ['kinder','jugend','foerder','kinder','jugend','foerder','jugend','kinder'];
const JUGEND = ['Buer','Elsetal','Gesmold','Melle-Mitte','Neuenkirchen','Niederholsten/Oldendorf','Wellingholzhausen']
.map((name,i)=>({name, slug:slugify(name), pic:JF_PICS[i]}));
JUGEND.unshift({name:'Kinderfeuerwehr Melle-Mitte', slug:'kinderfeuerwehr-melle-mitte', pic:'kinder', kinder:true});
/* Welche Ortsfeuerwehr gehoert zu welcher Jugendfeuerwehr (Stand feuerwehr-melle.de) */
const JF_VON_ORT = {
'bakum':'melle-mitte', 'melle-mitte':'melle-mitte',
'riemsloh':'elsetal', 'bruchmuehlen':'elsetal', 'gross-aschen':'elsetal', 'hoyel':'elsetal',
'buer':'buer', 'gesmold':'gesmold', 'neuenkirchen':'neuenkirchen',
'niederholsten':'niederholsten-oldendorf', 'oldendorf':'niederholsten-oldendorf',
'wellingholzhausen':'wellingholzhausen'
};
const ORTE_VON_JF = {};
Object.keys(JF_VON_ORT).forEach(ort => {
const jf = JF_VON_ORT[ort];
(ORTE_VON_JF[jf] = ORTE_VON_JF[jf] || []).push(ort);
});
ORTE_VON_JF['kinderfeuerwehr-melle-mitte'] = ['melle-mitte'];
const jfBySlug = s => JUGEND.find(j => j.slug === s);
const ortBySlug = s => ORTE.find(o => o.slug === s);
/* Kurzprofile der uebrigen Jugendfeuerwehren (Inhalte von feuerwehr-melle.de) */
jfBySlug('kinderfeuerwehr-melle-mitte').detail = {
lead:'Möchtest du Spiel, Spaß und Aktion? Und bist du zwischen 6 und 10 Jahre alt? Dann bist du bei den Meller Feuerflöhen genau richtig.',
text:['Wir treffen uns vierzehntägig am Montagnachmittag von 16:15 Uhr bis 17:45 Uhr und erleben zusammen viele tolle Sachen. In spielerischer Form behandeln wir Feuerwehrthemen wie Erste Hilfe, Brandschutz, Schlauchkunde und das Absetzen eines Notrufs.'],
alter:'6–10 Jahre', dienst:'14-tägig montags, 16:15–17:45 Uhr',
themen:['Erste Hilfe','Brandschutz','Schlauchkunde','Notruf absetzen']
};
jfBySlug('melle-mitte').detail = {
lead:'Seit knapp vier Jahrzehnten setzt die Ortsfeuerwehr Melle-Mitte auf Jugendarbeit zur Nachwuchsgewinnung – mit Erfolg.',
text:['Etwa zwei Drittel aller derzeit in der Einsatzabteilung aktiven Kameradinnen und Kameraden sind „durch die Jugendfeuerwehr gegangen“. Neben dem Thema Feuerwehr nimmt auch die allgemeine Jugendarbeit einen großen Raum ein.'],
alter:'10–18 Jahre', dienst:'Regelmäßige Dienstabende'
};
jfBySlug('neuenkirchen').detail = {
lead:'Die Jugendfeuerwehr Neuenkirchen wurde im Jahr 1971 gegründet und ist damit eine der ältesten Jugendfeuerwehren im Stadtgebiet.',
text:['Sie bestand viele Jahre nur aus männlichen Mitgliedern – erst 1995 trat das erste Mädchen ein. Mit 18 Jahren wechseln die Mitglieder in die Einsatzabteilung.'],
gegruendet:'1971', alter:'10–17 Jahre', dienst:'Regelmäßige Dienstabende'
};
jfBySlug('gesmold').detail = {
lead:'Du bist zwischen zehn und 18 Jahre alt und interessiert? Dann mach mit bei der Jugendfeuerwehr Gesmold.',
text:['Wir treffen uns alle 14 Tage dienstags um 17:30 Uhr. Die genauen Termine stehen im Dienstplan.'],
alter:'10–18 Jahre', dienst:'14-tägig dienstags, 17:30 Uhr'
};
jfBySlug('wellingholzhausen').detail = {
lead:'Herzlich willkommen bei der Jugendfeuerwehr Wellingholzhausen.',
text:['Wir treffen uns jeden zweiten Freitag von 17:00 Uhr bis 19:00 Uhr im neuen Feuerwehrhaus Wellingholzhausen.'],
alter:'10–18 Jahre', dienst:'14-tägig freitags, 17:00–19:00 Uhr'
};
jfBySlug('buer').detail = {
lead:'Die Jugendfeuerwehr Buer ist ein wichtiger Baustein unserer Ortsfeuerwehr.',
text:['Unsere Mitglieder sind zwischen 10 und 18 Jahren alt. Bei der Jugendfeuerwehr erleben die Jugendlichen, was es heißt, Kameradschaft zu pflegen und mit technischem Wissen und Geschick zu helfen.'],
alter:'10–18 Jahre', dienst:'Regelmäßige Dienstabende'
};
jfBySlug('niederholsten-oldendorf').detail = {
lead:'Mit der Gründung der Jugendfeuerwehr Niederholsten/Oldendorf besitzt die Feuerwehr Melle ein flächendeckendes Netz an Jugendfeuerwehren.',
text:['Sie ist die jüngste der sieben Meller Jugendfeuerwehren — der letzte weiße Fleck auf der Landkarte ist damit verschwunden.'],
alter:'10–18 Jahre', dienst:'Regelmäßige Dienstabende'
};
/* Ausgebaute Beispielseite — Inhalte von feuerwehr-melle.de/kinder-jugendfeuerwehren/jugendfeuerwehr-elsetal */
JUGEND.find(j => j.slug === 'elsetal').detail = {
lead:'Schon seit 2002 setzt sich die Jugendfeuerwehr Elsetal aus den Ortsfeuerwehren Riemsloh, Bruchmühlen, Groß Aschen und Hoyel zusammen. Gemeinsam sind wir ein Team aus zehn- bis 18-jährigen Jugendlichen und natürlich den Jugendwarten und Betreuern.',
text:['Neben der feuerwehrtechnischen Ausbildung an unseren Dienstabenden legen wir auch viel Wert auf Spiel, Spaß und allgemeine Jugendarbeit. Wir werden gemeinsam kreativ, testen und erweitern unser Wissen und bilden eine starke Truppe. Das WIR zählt bei uns!',
'Du willst auch ein Teil vom WIR sein und bist mindestens zehn Jahre alt? Dann melde dich gerne bei unseren Ansprechpartnern.'],
claim:'WIR sind die Zukunft der Feuerwehr!',
gegruendet:'2002',
alter:'10–18 Jahre',
dienst:'14-tägig donnerstags, 18:00 Uhr',
ortsfw:['Riemsloh','Bruchmühlen','Groß Aschen','Hoyel'],
leitung:[{n:'Lars Fluchtmann', r:'Jugendfeuerwehrwart'},{n:'Marvin Landwehr', r:'Stellv. Jugendfeuerwehrwart'},
{n:'Frank Beer', r:'Stellv. Jugendfeuerwehrwart'},{n:'Michael Wötzel', r:'Stellv. Jugendfeuerwehrwart'}]
};
/* ===================== HELFER ===================== */
const $ = (s,el)=> (el||document).querySelector(s);
const $$ = (s,el)=> Array.from((el||document).querySelectorAll(s));
const main = $('#main');
const esc = s => String(s).replace(/[&<>"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));
const crumbs = items => '<div class="crumbs"><a href="#/" data-nav>Startseite</a>'+
items.map(it => I.chevR + (it.href ? '<a href="'+it.href+'" data-nav>'+esc(it.label)+'</a>' : '<span>'+esc(it.label)+'</span>')).join('') + '</div>';
const dateMeta = d => '<span class="meta">'+I.calSmall+esc(d)+'</span>';
function evRow(e){
return '<a class="ev" href="#/einsaetze/'+e.id+'" data-nav>'+
'<span class="txt">'+
'<span class="l1"><span class="time">'+e.date+', '+e.time+' Uhr</span><span class="pill">'+esc(e.type)+'</span>'+
(e.bilder && e.bilder.length > 1
? '<span class="photo-badge" title="'+e.bilder.length+' Fotos zum Einsatz">'+I.cam+e.bilder.length+'</span>' : '')+
'</span>'+
'<span class="ttl">'+esc(e.title)+'</span>'+
'<span class="meta">'+I.pinFill+esc(e.ort)+'</span>'+
'<span class="meta">'+I.helmet+'zuständig: '+esc(e.zust)+'</span>'+
'</span>'+
'<span class="chev">'+I.chevR+'</span></a>';
}
function miniRow(n){
return '<a class="mini" href="#/aktuelles/'+n.id+'" data-nav>'+shot(n.pic,'thumb','shot')+
'<span class="txt"><span class="cat">'+esc(n.cat)+'</span>'+
'<div class="ttl">'+esc(n.title)+'</div>'+dateMeta(n.date)+'</span></a>';
}
function wmRow(n){
return '<a class="wm" href="#/aktuelles/'+n.id+'" data-nav>'+shot(n.pic,'thumb','shot')+
'<span class="txt"><span class="cat">'+esc(n.cat)+'</span>'+
'<div class="ttl">'+esc(n.title)+'</div>'+dateMeta(n.date)+'</span></a>';
}
function tmRow(t){
return '<div class="tm"><div class="dt"><div class="d">'+t.d+'</div><div class="m">'+t.m+'</div></div>'+
'<div class="rule"></div><div class="txt"><div class="ttl">'+esc(t.title)+'</div>'+
'<div class="sub">'+esc(t.ort)+'<br>'+esc(t.zeit)+'</div></div></div>';
}
function newsCard(n){
return '<a class="card newscard" href="#/aktuelles/'+n.id+'" data-nav>'+shot(n.pic,'card','shot')+
'<div class="body"><span class="pill">'+esc(n.cat)+'</span><h3>'+esc(n.title)+'</h3>'+
'<p class="exc">'+esc(n.exc)+'</p>'+dateMeta(n.date)+'</div></a>';
}
/* ===================== SEITEN ===================== */
const P = {};
let heroIdx = 0;
const HERO_SLIDES = [
{pic:'grund', cat:'Aktuell', date:'16.09.2026', id:'n1',
title:'21 neue Einsatzkräfte starten in den aktiven Feuerwehrdienst',
desc:'In Melle haben 21 Teilnehmerinnen und Teilnehmer erfolgreich die Grundausbildung abgeschlossen und verstärken ab sofort die Einsatzabteilungen der Ortsfeuerwehren.'},
{pic:'gefahrgut', cat:'Ausbildung', date:'09.09.2026', id:'n3',
title:'Realistische Einsatzübungen für die Einsatzkräfte',
desc:'Mehrere Ortsfeuerwehren trainierten gemeinsam den Ernstfall — von der Menschenrettung bis zur Zusammenarbeit mit dem Rettungsdienst.'},
{pic:'fahrzeug', cat:'Fahrzeuge & Technik', date:'01.09.2026', id:'n4',
title:'Neues Löschfahrzeug für die Ortsfeuerwehren',
desc:'Das Fahrzeug verfügt über moderne Beladung für Brandbekämpfung und technische Hilfeleistung und ersetzt ein älteres Modell im Bestand.'}
];
function heroHTML(){
const s = HERO_SLIDES[heroIdx];
return '<a class="hero" href="#/aktuelles/'+s.id+'" data-nav>'+
'<div class="pic"><img src="'+src(s.pic,'hero')+'" alt=""></div><div class="veil"></div>'+
'<div class="body"><span class="pill">'+esc(s.cat)+'</span>'+
'<h2>'+esc(s.title)+'</h2><p class="desc">'+esc(s.desc)+'</p>'+
'<span class="meta">'+I.calSmall+s.date+'</span></div></a>'+
'<button class="hero-nav prev" data-step="-1" aria-label="Vorherige Meldung">'+I.chevL+'</button>'+
'<button class="hero-nav next" data-step="1" aria-label="Nächste Meldung">'+I.chevR+'</button>'+
'<div class="dots">'+HERO_SLIDES.map((x,i)=>'<button data-dot="'+i+'" class="'+(i===heroIdx?'is-active':'')+'" aria-label="Meldung '+(i+1)+' anzeigen"></button>').join('')+'</div>';
}
P['/'] = () => `
<div class="top-row">
<div id="heroWrap">${heroHTML()}</div>
<div class="card einsatz-card">
<div class="sec-head">
<span class="sec-title upper">${I.sirenF}<span class="desk">Wir im Einsatz</span><span class="mob">Einsätze</span></span>
<a class="more" href="#/einsaetze" data-nav>Alle Einsätze ${I.arrowR}</a>
</div>
<div class="ev-list">${EINSAETZE.slice(0,3).map(evRow).join('')}</div>
</div>
</div>
<div class="cols">
<div class="card panel">
<div class="sec-head">
<span class="sec-title">${I.star}Top-News</span>
<a class="more" href="#/aktuelles" data-nav>Alle Meldungen ${I.arrowR}</a>
</div>
<a class="feature" href="#/aktuelles/n1" data-nav>
${shot(NEWS[0].pic,'card','shot')}
<span class="pill">${esc(NEWS[0].cat)}</span>
<h3>${esc(NEWS[0].title)}</h3>
<p class="exc">${esc(NEWS[0].exc)}</p>
${dateMeta(NEWS[0].date)}
</a>
<div class="mini-list">${[NEWS[1],NEWS[2],NEWS[3]].map(miniRow).join('')}</div>
</div>
<div class="card panel">
<div class="sec-head">
<span class="sec-title">${I.docF}Weitere Meldungen</span>
<a class="more" href="#/aktuelles" data-nav>Alle Meldungen ${I.arrowR}</a>
</div>
${[NEWS[4],NEWS[5],NEWS[6],NEWS[7]].map(wmRow).join('')}
</div>
<div class="col-3">
<div class="card panel">
<div class="sec-head">
<span class="sec-title">${I.calF}Termine</span>
<a class="more" href="#/termine" data-nav>Alle Termine ${I.arrowR}</a>
</div>
${TERMINE.slice(0,3).map(tmRow).join('')}
</div>
<div class="mapcard">
<div class="bg">${MAP_BG}</div>
<div class="inner">
<span class="sec-title" style="font-size:18px">${I.pinFill}Einsatzkarte</span>
<p class="txt">Alle aktuellen Einsätze auf einen Blick.</p>
<a class="btn-red" href="#/einsatzkarte" data-nav>Zur Einsatzkarte ${I.arrowR}</a>
</div>
</div>
</div>
</div>`;
P['/einsaetze'] = () => `
<div class="pagehead">${crumbs([{label:'Einsätze'}])}<h1>Einsätze</h1>
<p class="lead">Eine Übersicht der jüngsten Einsätze der Ortsfeuerwehren Melle.</p></div>
<div class="hint">${I.info}<span>Demo-Ansicht: Im Livebetrieb würde diese Liste automatisch aus dem Einsatzleitsystem befüllt.</span></div>
<div class="card einsatz-card" style="padding-top:6px"><div class="ev-list">${EINSAETZE.map(evRow).join('')}</div></div>`;
/* Bildergalerie: bei mehreren Fotos Hauptbild + Vorschauleiste, sonst ein Bild */
let galIdx = 0;
function galerie(e){
const pics = e.bilder && e.bilder.length > 1 ? e.bilder : null;
if (!pics) return shot(e.pic, 'hero', 'detail-hero');
if (galIdx >= pics.length) galIdx = 0;
return '<div class="gallery">' +
shot(pics[galIdx], 'hero', 'detail-hero') +
'<button class="hero-nav prev" data-gal="-1" aria-label="Vorheriges Bild">'+I.chevL+'</button>' +
'<button class="hero-nav next" data-gal="1" aria-label="Nächstes Bild">'+I.chevR+'</button>' +
'<span class="gal-count">'+(galIdx+1)+' / '+pics.length+'</span>' +
'</div>' +
'<div class="gal-thumbs">' + pics.map((p,i) =>
'<button class="gal-thumb'+(i === galIdx ? ' is-active' : '')+'" data-galset="'+i+'" aria-label="Bild '+(i+1)+' anzeigen">' +
'<img src="'+src(p,'thumb')+'" alt=""></button>').join('') + '</div>';
}
P['/einsaetze/:id'] = id => {
const e = EINSAETZE.find(x => x.id === id) || EINSAETZE[0];
return `<a class="back" href="#/einsaetze" data-nav>${I.chevL}Zurück zu Einsätze</a>
<div id="galWrap">${galerie(e)}</div>
${detailHead(e.type, e.title, '')}
<div class="split">
<div>
${block(I.info, 'Einsatzverlauf', `<div class="prose">
<p>Am ${e.date} um ${e.time} Uhr wurde die ${zustSlug(e) ? ortLink(zustSlug(e), e.zust) : '<strong>'+esc(e.zust)+'</strong>'} zu einem Einsatz in <strong>${esc(e.ort)}</strong> alarmiert.</p>
<p>Vor Ort wurde die Lage durch den Einsatzleiter erkundet und die notwendigen Maßnahmen wurden eingeleitet. Die Einsatzstelle konnte im weiteren Verlauf gesichert und übergeben werden.</p>
<p class="note">Demo-Eintrag zur Veranschaulichung einer Einsatzmeldung.</p></div>`)}
${block(I.sirenF, 'Im Einsatz', '<div class="units">' + e.einheiten.map(u => {
const s = einheitSlug(u.name);
return '<div class="unit">' +
(s ? '<a class="nm" href="#/ortsfeuerwehren/'+s+'" data-nav>'+esc(u.name)+'</a>' : '<span class="nm">'+esc(u.name)+'</span>') +
(u.fz && u.fz.length ? '<span class="fz">'+u.fz.map(f => '<span>'+esc(f)+'</span>').join('')+'</span>' : '') +
'</div>';
}).join('') + '</div>')}
</div>
<div class="side">
${sideCard('Einsatzdetails',
`<div class="row">${I.clock}<span>${e.date} · ${e.time} Uhr</span></div>
<div class="row">${I.pin}<span>${esc(e.ort)}</span></div>
<div class="row">${I.siren}<span>${esc(e.type)}</span></div>
<div class="row">${I.helmet}<span>Zuständig:<br>${zustSlug(e) ? ortLink(zustSlug(e), e.zust) : esc(e.zust)}</span></div>
<div class="row">${I.info}<span>Alarmierung: ${esc(e.alarm)}</span></div>`)}
</div>
</div>`;
};
P['/aktuelles'] = () => `
<div class="pagehead">${crumbs([{label:'Aktuelles'}])}<h1>Aktuelles</h1>
<p class="lead">Neuigkeiten aus den Ortsfeuerwehren, der Jugendfeuerwehr und dem Vereinsleben.</p></div>
<div class="grid3">${NEWS.map(newsCard).join('')}</div>`;
P['/aktuelles/:id'] = id => {
const n = NEWS.find(x => x.id === id) || NEWS[0];
return `<a class="back" href="#/aktuelles" data-nav>${I.chevL}Zurück zu Aktuelles</a>
${shot(n.pic,'hero','detail-hero')}
<div class="detail-head">
<span class="eyebrow">${esc(n.cat)}</span>
<h1>${esc(n.title)}</h1>
<div style="margin-top:12px">${dateMeta(n.date)}</div>
</div>
<div class="split">
<div>
<section class="block"><div class="prose">${n.body.map(p => '<p>'+esc(p)+'</p>').join('')}</div></section>
<div class="author">
<span class="ava">${n.autor.foto ? '<img src="'+n.autor.foto+'" alt="">' : I.flame}</span>
<span>
<span class="lbl">Ein Artikel von</span>
<span class="nm">${esc(n.autor.name)}</span>
<span class="role">${esc(n.autor.rolle)} ${I.calSmall} ${n.date}</span>
</span>
</div>
</div>
<div class="side">
${sideCard('Weitere Meldungen', NEWS.filter(x => x.id !== n.id).slice(0,4).map(x =>
'<a class="row" href="#/aktuelles/'+x.id+'" data-nav style="flex-direction:column;gap:4px">'+
'<strong style="font-size:13.5px;font-weight:600;line-height:1.4">'+esc(x.title)+'</strong>'+
'<span style="font-size:11.5px;color:var(--ink-faint)">'+x.date+'</span></a>').join(''))}
</div>
</div>`;
};
P['/termine'] = () => `
<div class="pagehead">${crumbs([{label:'Termine'}])}<h1>Termine</h1>
<p class="lead">Übungsdienste, Veranstaltungen und Termine der Ortsfeuerwehren Melle.</p></div>
<div class="card panel" style="max-width:640px">${TERMINE.map(tmRow).join('')}</div>`;
P['/ortsfeuerwehren'] = () => `
<div class="pagehead">${crumbs([{label:'Ortsfeuerwehren'}])}<h1>Ortsfeuerwehren</h1>
<p class="lead">Die Feuerwehr Melle gliedert sich in ${ORTE.length} Ortsfeuerwehren, die gemeinsam den Brand- und Katastrophenschutz im Stadtgebiet sicherstellen.</p></div>
<div class="grid3">${ORTE.map(o => `<a class="card station" href="#/ortsfeuerwehren/${o.slug}" data-nav>
${shot(o.pic,'thumb','shot')}
<div class="body"><h3>Ortsfeuerwehr ${esc(o.name)}</h3>
<p class="desc">Freiwillige Einsatzabteilung im Ortsteil ${esc(o.name)}.</p></div></a>`).join('')}</div>`;
/* Bausteine der Detailseiten */
const initials = n => n.split(/\s+/).filter(w => /^[A-ZÄÖÜ]/.test(w)).map(w => w[0]).join('').slice(0,2);
const personCard = p => '<div class="card person"><span class="ava">'+esc(initials(p.n))+'</span><span>'+
'<span class="nm">'+esc(p.n)+'</span><span class="role">'+esc(p.r)+'</span>'+
(p.m ? '<span class="mail">'+esc(p.m)+'</span>' : '')+'</span></div>';
const chipList = a => '<div class="chips">'+a.map(x => '<span>'+esc(x)+'</span>').join('')+'</div>';
const chipLinks = a => '<div class="chips">'+a.map(x =>
'<a href="'+x.href+'" data-nav>'+esc(x.label)+'</a>').join('')+'</div>';
const tickList = (a, two) => '<ul class="ticks'+(two?' two':'')+'">'+a.map(x => '<li>'+I.check+'<span>'+esc(x)+'</span></li>').join('')+'</ul>';
const subSec = (icon, title, body) => '<div class="sub-sec"><span class="sec-title">'+icon+esc(title)+'</span>'+body+'</div>';
/* Bausteine des einheitlichen Detailseiten-Rasters */
const detailHead = (eyebrow, title, lead) => '<div class="detail-head"><span class="eyebrow">'+esc(eyebrow)+'</span>'+
'<h1>'+esc(title)+'</h1>'+(lead ? '<p class="lead">'+esc(lead)+'</p>' : '')+'</div>';
const block = (icon, title, body) => '<section class="block"><h2><span class="ic">'+icon+'</span>'+esc(title)+'</h2>'+body+'</section>';
const factTile = (icon, k, v) => '<div class="fact"><div class="k">'+icon+esc(k)+'</div><div class="v">'+esc(v)+'</div></div>';
const factRow = a => '<div class="facts">'+a.join('')+'</div>';
const sideCard = (title, body) => '<div class="card info"><h4>'+esc(title)+'</h4>'+body+'</div>';
/* Einheit bzw. Zuständigkeit auf die passende Ortsfeuerwehr-Seite abbilden */
const ortSlugAus = (text, prefix) => {
const m = new RegExp('^' + prefix + '\\s+(.+)$').exec(text || '');
if (!m) return null;
const s = slugify(m[1]);
return ortBySlug(s) ? s : null;
};
const einheitSlug = name => ortSlugAus(name, 'OrtsFw');
const zustSlug = e => ortSlugAus(e.zust, 'Ortsfeuerwehr');
const ortLink = (slug, label) => '<a class="lnk" href="#/ortsfeuerwehren/'+slug+'" data-nav>'+esc(label)+'</a>';
/* Einsaetze, an denen eine Ortsfeuerwehr beteiligt war */
const einsaetzeVonOrt = slug => EINSAETZE.filter(e =>
zustSlug(e) === slug || (e.einheiten || []).some(u => einheitSlug(u.name) === slug));
const letzteEinsaetzeCard = slug => {
const list = einsaetzeVonOrt(slug).slice(0, 4);
if (!list.length) return '';
return sideCard('Letzte Einsätze',
'<div class="side-items">' + list.map(e =>
'<a class="side-item" href="#/einsaetze/'+e.id+'" data-nav>'+
shot((e.bilder && e.bilder[0]) || e.pic, 'thumb', 'shot')+
'<span><span class="cat">'+esc(e.type)+'</span>'+
'<span class="ttl">'+esc(e.title)+'</span>'+
'<span class="dt">'+e.date+' · '+e.time+' Uhr</span></span>'+
'</a>').join('') + '</div>' +
'<a class="side-more" href="#/einsaetze" data-nav>Alle Einsätze '+I.arrowR+'</a>');
};
/* Abschnitt „Jugendfeuerwehr“ auf einer Ortsfeuerwehr-Seite */
const jfBlock = o => {
const slug = JF_VON_ORT[o.slug];
if (!slug) return block(I.helmet, 'Jugendfeuerwehr', '<div class="prose"><p>Den Nachwuchs fördern die ' +
'<a href="#/jugendfeuerwehr" data-nav style="color:var(--red);font-weight:600">Kinder- und Jugendfeuerwehren</a> der Stadt Melle.</p></div>');
const j = jfBySlug(slug), jd = j.detail || {};
return block(I.helmet, 'Jugendfeuerwehr',
'<div class="prose"><p>Der Nachwuchs der Ortsfeuerwehr '+esc(o.name)+' ist in der <strong>Jugendfeuerwehr '+esc(j.name)+
'</strong> organisiert'+(jd.alter ? ' — Alter: '+esc(jd.alter) : '')+'.'+
(jd.dienst && jd.dienst.indexOf('Regelmäßige') !== 0 ? ' Die Dienste finden '+esc(jd.dienst)+' statt.' : '')+'</p></div>'+
'<a class="more" style="margin-top:12px" href="#/jugendfeuerwehr/'+j.slug+'" data-nav>Zur Jugendfeuerwehr '+esc(j.name)+' '+I.arrowR+'</a>');
};
P['/ortsfeuerwehren/:slug'] = slug => {
const o = ORTE.find(x => x.slug === slug) || ORTE[0];
const d = o.detail;
const main = d ? [
factRow([
factTile(I.flame, 'Gegründet', d.gegruendet),
factTile(I.sirenF, 'Fahrzeuge', d.fahrzeuge.length + ' im Dienst'),
factTile(I.pinFill, 'Einsatzgebiet', d.gebiet.length + ' Bereiche'),
factTile(I.helmet, 'Einstufung', 'Grundausstattung')
]),
block(I.pinFill, 'Einsatzgebiet', chipList(d.gebiet)),
block(I.info, 'Gefahrenschwerpunkte', tickList(d.schwerpunkte, true)),
block(I.sirenF, 'Fahrzeuge', '<div class="vehicles">' + d.fahrzeuge.map(f =>
'<div class="veh">' + shot(f.pic, 'card', 'ratio') +
'<div class="body"><span class="k">'+esc(f.k)+'</span><span class="t">'+esc(f.t)+'</span>'+
(f.sub ? '<span class="sub">'+esc(f.sub)+'</span>' : '')+'</div></div>').join('') + '</div>'),
jfBlock(o),
block(I.helmet, 'Ausstattung & Feuerwehrhaus',
chipList(d.technik) + '<div class="prose" style="margin-top:15px"><p>'+esc(d.haus)+'</p></div>'),
block(I.siren, 'Alarmierung', '<div class="prose"><p>'+esc(d.alarm)+'</p></div>')
].join('') : [
block(I.helmet, 'Die Ortsfeuerwehr ' + o.name, '<div class="prose">'+
'<p>Die Ortsfeuerwehr '+esc(o.name)+' ist eine von '+ORTE.length+' Einsatzabteilungen der Freiwilligen Feuerwehr Melle und stellt gemeinsam mit den übrigen Ortsfeuerwehren die Gefahrenabwehr im Stadtgebiet sicher.</p>'+
'<p>Zu den Aufgaben zählen Brandbekämpfung, technische Hilfeleistung sowie die Mitwirkung im Katastrophenschutz. Regelmäßige Übungsdienste sichern die Einsatzbereitschaft.</p>'+
'<p class="note">Demo-Inhalt — vollständig ausgebaut ist beispielhaft die <a href="#/ortsfeuerwehren/bakum" data-nav style="color:var(--red);font-weight:600">Ortsfeuerwehr Bakum</a>.</p></div>'),
jfBlock(o)
].join('');
const side = d
? sideCard('Kontakt',
`<div class="row">${I.helmet}<span>${esc(d.art)}</span></div>
<div class="row">${I.pin}<span>${esc(d.adresse)}</span></div>
<div class="row">${I.phone}<span>${esc(d.tel)}</span></div>
<div class="row">${I.mail}<span style="overflow-wrap:anywhere">${esc(d.mail)}</span></div>`) +
sideCard('Ansprechpartner', '<div class="people">'+d.leitung.map(personCard).join('')+'</div>')
: sideCard('Auf einen Blick',
`<div class="row">${I.pin}<span>Gerätehaus ${esc(o.name)}</span></div>
<div class="row">${I.users}<span>Freiwillige Einsatzabteilung</span></div>
<div class="row">${I.phone}<span>Notruf: 112</span></div>`);
const sideAll = side + letzteEinsaetzeCard(o.slug);
return `<a class="back" href="#/ortsfeuerwehren" data-nav>${I.chevL}Zurück zu Ortsfeuerwehren</a>
${shot(o.pic,'hero','detail-hero')}
${detailHead('Ortsfeuerwehr', o.name, d ? d.lead : '')}
<div class="split"><div>${main}</div><div class="side">${sideAll}</div></div>`;
};
const JF_LEITUNG = [
{n:'Jan Petering', r:'Stadtjugendfeuerwehrwart'},
{n:'Malte von Bachmann', r:'Stellv. Stadtjugendfeuerwehrwart'},
{n:'Mattis Kappelmann', r:'Stellv. Stadtjugendfeuerwehrwart'}
];
P['/jugendfeuerwehr'] = () => {
const kf = JUGEND.find(j => j.kinder), kd = kf.detail;
const jfs = JUGEND.filter(j => !j.kinder);
return `
<div class="pagehead">${crumbs([{label:'Kinder- und Jugendfeuerwehren'}])}<h1>Kinder- und Jugendfeuerwehren</h1>
<p class="lead">In der Stadt Melle gibt es sieben Jugendfeuerwehren und eine Kinderfeuerwehr, die den Nachwuchs der ${ORTE.length} Ortsfeuerwehren fördern. Seit der Gründung der jüngsten Jugendfeuerwehr Niederholsten/Oldendorf sind die Gruppen flächendeckend über das Stadtgebiet verteilt — so können sich Kinder und Jugendliche ohne weite Anfahrtswege bei der Feuerwehr engagieren.</p></div>
${factRow([
factTile(I.helmet, 'Kinderfeuerwehr', '1 Gruppe'),
factTile(I.users, 'Jugendfeuerwehren', '7 Gruppen'),
factTile(I.flame, 'Einstieg', 'ab 6 Jahren'),
factTile(I.pinFill, 'Verteilt auf', ORTE.length + ' Ortsfeuerwehren')
])}
<div class="sub-sec" style="margin-top:0">
<span class="sec-title">${I.helmet}Kinderfeuerwehr — die Meller Feuerflöhe</span>
<a class="card feature-card" href="#/jugendfeuerwehr/${kf.slug}" data-nav>
${shot(kf.pic,'card','ratio')}
<div class="body">
<span class="eyebrow">Kinderfeuerwehr Melle-Mitte</span>
<h3>Für Kinder von 6 bis 10 Jahren</h3>
<p class="txt">${esc(kd.lead)}</p>
${chipList([kd.alter, kd.dienst])}
<span class="more" style="margin-top:16px">Zur Kinderfeuerwehr ${I.arrowR}</span>
</div>
</a>
</div>
<div class="sub-sec">
<span class="sec-title">${I.users}Die sieben Jugendfeuerwehren</span>
<div class="grid3">${jfs.map(j => `<a class="card station" href="#/jugendfeuerwehr/${j.slug}" data-nav>
${shot(j.pic,'thumb','shot')}
<div class="body"><h3>Jugendfeuerwehr ${esc(j.name)}</h3>
<p class="desc">${j.detail && j.detail.dienst && j.detail.dienst.indexOf('Regelmäßige') !== 0
? esc(j.detail.dienst) : 'Für Jugendliche von 10 bis 18 Jahren'}</p></div></a>`).join('')}</div>
</div>
<div class="split" style="margin-top:28px">
<div>
${block(I.news, 'Aus der Jugendarbeit',
'<div class="grid2">'+NEWS.filter(n => n.cat === 'Jugendfeuerwehr' || n.cat === 'Förderverein').slice(0,2).map(newsCard).join('')+'</div>')}
</div>
<div class="side">
${sideCard('Ansprechpartner', '<div class="people">'+JF_LEITUNG.map(personCard).join('')+'</div>')}
${sideCard('Mitmachen',
`<div class="row">${I.users}<span>Jugendfeuerwehr ab 10 Jahren</span></div>
<div class="row">${I.helmet}<span>Kinderfeuerwehr ab 6 Jahren</span></div>
<a class="btn-red" href="#/kontakt" data-nav style="margin-top:16px">Kontakt aufnehmen ${I.arrowR}</a>`)}
</div>
</div>`;
};
P['/jugendfeuerwehr/:slug'] = slug => {
const j = JUGEND.find(x => x.slug === slug) || JUGEND[0];
const d = j.detail;
const titel = j.kinder ? j.name : 'Jugendfeuerwehr ' + j.name;
const main = d ? [
factRow([
d.gegruendet ? factTile(I.flame, 'Gegründet', d.gegruendet) : '',
factTile(I.users, 'Alter', d.alter),
factTile(I.cal, 'Dienst', d.dienst),
(ORTE_VON_JF[j.slug] || []).length
? factTile(I.helmet, j.kinder ? 'Getragen von' : 'Ortsfeuerwehren', String(ORTE_VON_JF[j.slug].length)) : ''
].filter(Boolean)),
block(I.helmet, 'Über uns', '<div class="prose"><p>'+esc(d.lead)+'</p>'+
(d.text || []).map(p => '<p>'+esc(p)+'</p>').join('')+'</div>'+
(d.claim ? '<p class="pullquote">'+esc(d.claim)+'</p>' : '')),
d.themen ? block(I.check, 'Das machen wir', chipList(d.themen)) : '',
(ORTE_VON_JF[j.slug] || []).length
? block(I.users, j.kinder ? 'Getragen von' : 'Beteiligte Ortsfeuerwehren',
chipLinks(ORTE_VON_JF[j.slug].map(s => ({label:'Ortsfeuerwehr ' + ortBySlug(s).name, href:'#/ortsfeuerwehren/'+s}))))
: '',
block(I.cal, 'Dienstbetrieb', '<div class="prose"><p>Die Dienste finden '+esc(d.dienst)+' statt. Alle Termine stehen im '+
'<a href="#/termine" data-nav style="color:var(--red);font-weight:600">Dienstplan</a>.</p></div>')
].join('') : [
block(I.helmet, titel, '<div class="prose">'+
'<p>Die '+esc(titel)+' gehört zu den sieben Jugendfeuerwehren der Stadt Melle. Neben der feuerwehrtechnischen Ausbildung stehen Teamgeist, gemeinsame Aktionen und Jugendarbeit im Mittelpunkt.</p>'+
'<p>Mit dem Übertritt in die Einsatzabteilung steht der Weg in den aktiven Feuerwehrdienst offen.</p>'+
'<p class="note">Demo-Inhalt — vollständig ausgebaut ist beispielhaft die <a href="#/jugendfeuerwehr/elsetal" data-nav style="color:var(--red);font-weight:600">Jugendfeuerwehr Elsetal</a>.</p></div>')
].join('');
const side = sideCard('Auf einen Blick',
`<div class="row">${I.users}<span>${d ? esc(d.alter) : (j.kinder ? 'Ab 6 Jahren' : '10–18 Jahre')}</span></div>
<div class="row">${I.cal}<span>${d ? esc(d.dienst) : 'Regelmäßige Dienstabende'}</span></div>
${d ? `<div class="row">${I.flame}<span>Gegründet ${esc(d.gegruendet)}</span></div>` : ''}
<a class="btn-red" href="#/kontakt" data-nav style="margin-top:16px">Mitmachen ${I.arrowR}</a>`) +
sideCard('Ansprechpartner', '<div class="people">' +
(d && d.leitung ? d.leitung : JF_LEITUNG.slice(0,1)).map(personCard).join('') + '</div>');
return `<a class="back" href="#/jugendfeuerwehr" data-nav>${I.chevL}Zurück zur Übersicht</a>
${shot(j.pic,'hero','detail-hero')}
${detailHead(j.kinder ? 'Kinderfeuerwehr' : 'Jugendfeuerwehr', j.kinder ? 'Melle-Mitte' : j.name, d ? '' : '')}
<div class="split"><div>${main}</div><div class="side">${side}</div></div>`;
};
P['/foerderverein'] = () => `
<div class="pagehead">${crumbs([{label:'Förderverein'}])}<h1>Förderverein</h1>
<p class="lead">Der Förderverein unterstützt die Arbeit der Feuerwehr Melle ideell und finanziell.</p></div>
${shot('foerder','hero','detail-hero')}
<div class="prose">
<p>Der Förderverein setzt sich für die Belange der Feuerwehr Melle ein — von zusätzlicher Ausrüstung über die Unterstützung der Jugendarbeit bis zur Pflege der Gemeinschaft im Verein.</p>
<p>Mitglied werden kann jede und jeder, die oder der die Arbeit der Feuerwehr Melle unterstützen möchte — auch ohne selbst aktive Einsatzkraft zu sein.</p>
<p class="note">Demo-Inhalt — im Livebetrieb stünden hier Satzung, Beitrittsformular und Ansprechpartner.</p>
</div>`;
P['/service'] = () => {
const items = [
{i:I.file, t:'Formulare & Downloads', x:'Anträge, Merkblätter und Formulare rund um die Feuerwehr Melle.'},
{i:I.helmet, t:'Brandschutzerziehung', x:'Angebote für Kindergärten, Schulen und Betriebe.'},
{i:I.info, t:'Brandsicherheitswache', x:'Anfrage einer Sicherheitswache für Veranstaltungen.'},
{i:I.users, t:'Mitmachen', x:'Interesse am aktiven Dienst? Hier geht es zum Kontakt.'}
];
return `<div class="pagehead">${crumbs([{label:'Service'}])}<h1>Service &amp; Formulare</h1>
<p class="lead">Nützliche Informationen, Formulare und Anlaufstellen rund um die Feuerwehr Melle.</p></div>
<div class="grid2">${items.map(it => `<div class="card" style="padding:20px">
<div style="width:40px;height:40px;border-radius:9px;background:#fdf0f2;color:var(--red);display:flex;align-items:center;justify-content:center;margin-bottom:14px"><span style="width:20px;height:20px;display:block">${it.i}</span></div>
<h3 style="font-size:16px;margin-bottom:7px">${it.t}</h3>
<p style="font-size:13.5px;color:var(--ink-soft);line-height:1.55">${it.x}</p></div>`).join('')}</div>`;
};
P['/ueber-uns'] = () => `
<div class="pagehead">${crumbs([{label:'Über uns'}])}<h1>Über uns</h1>
<p class="lead">Die Feuerwehr Melle sorgt seit Generationen für Sicherheit im Stadtgebiet — ehrenamtlich, engagiert und rund um die Uhr.</p></div>
${shot('grund','hero','detail-hero')}
<div class="prose">
<p>Mit ${ORTE.length} Ortsfeuerwehren, sieben Jugendfeuerwehren und einer Kinderfeuerwehr ist die Feuerwehr Melle flächendeckend im gesamten Stadtgebiet präsent.</p>
<p>Die ehrenamtlichen Einsatzkräfte stehen rund um die Uhr bereit — für Brandbekämpfung, technische Hilfeleistung und den Katastrophenschutz. Regelmäßige Aus- und Fortbildung sichert die Einsatzbereitschaft auf hohem Niveau.</p>
<p class="note">Demo-Inhalt — im Livebetrieb stünden hier Geschichte, Leitung, Satzung und Struktur der Feuerwehr Melle.</p>
</div>`;
P['/kontakt'] = () => `
<div class="pagehead">${crumbs([{label:'Kontakt'}])}<h1>Kontakt</h1>
<p class="lead">Fragen, Anregungen oder Interesse am Mitmachen? Wir freuen uns von Ihnen zu hören.</p></div>
<div class="split">
<form class="form" id="kForm">
<div class="row2">
<div class="field"><label for="k-name">Name</label><input id="k-name" required placeholder="Ihr Name"></div>
<div class="field"><label for="k-mail">E-Mail</label><input id="k-mail" type="email" required placeholder="ihre@email.de"></div>
</div>
<div class="field"><label for="k-sub">Betreff</label><input id="k-sub" placeholder="Worum geht es?"></div>
<div class="field"><label for="k-msg">Nachricht</label><textarea id="k-msg" required placeholder="Ihre Nachricht an uns"></textarea></div>
<button class="btn-red" type="submit" style="align-self:flex-start">Nachricht senden</button>
<div class="ok" id="kOk">${I.check}Danke! Dies ist eine Demo — es wurde keine Nachricht versendet.</div>
</form>
<div class="card info">
<h4>Feuerwehr Melle</h4>
<div class="row">${I.pin}<span>Hermannstraße 29<br>49324 Melle</span></div>
<div class="row">${I.mail}<span>info@feuerwehr-melle.de</span></div>
<div class="row">${I.phone}<span>112 – Notruf<br><span style="color:var(--ink-faint);font-size:12.5px">nur im Notfall</span></span></div>
</div>
</div>`;
P['/suche'] = () => `
<div class="pagehead">${crumbs([{label:'Suche'}])}<h1>Suche</h1></div>
<div class="searchbox">${I.search}<input id="sIn" type="search" placeholder="Meldungen und Einsätze durchsuchen …"></div>
<div id="sOut"></div>`;
P['/einsatzkarte'] = () => `
<div class="pagehead">${crumbs([{label:'Einsatzkarte'}])}<h1>Einsatzkarte</h1>
<p class="lead">Alle aktuellen Einsätze auf einen Blick — Demo-Ansicht.</p></div>
<div class="mapdemo" style="position:relative;color:#c9ccd4">${MAP_BG}
<div style="position:absolute;inset:0">
<span style="position:absolute;left:28%;top:34%;width:30px;height:30px;color:var(--red)">${I.pinFill}</span>
<span style="position:absolute;left:58%;top:52%;width:30px;height:30px;color:#2f6fb0">${I.pinFill}</span>
<span style="position:absolute;left:72%;top:26%;width:30px;height:30px;color:var(--red)">${I.pinFill}</span>
</div></div>
<div class="legend">
<span><span class="dot" style="background:var(--red)"></span>Brandeinsatz</span>
<span><span class="dot" style="background:#2f6fb0"></span>Technische Hilfeleistung</span>
</div>`;
P['/impressum'] = () => `<div class="pagehead">${crumbs([{label:'Impressum'}])}<h1>Impressum</h1></div>
<div class="prose"><p>Feuerwehr Melle · Hermannstraße 29 · 49324 Melle</p>
<p class="note">Dies ist eine Demo-Webseite. Alle Angaben dienen ausschließlich Anschauungszwecken.</p></div>`;
P['/datenschutz'] = () => `<div class="pagehead">${crumbs([{label:'Datenschutz'}])}<h1>Datenschutz</h1></div>
<div class="prose"><p>Diese Demo-Webseite verarbeitet keine echten Nutzerdaten. Formulareingaben werden weder übertragen noch gespeichert.</p></div>`;
/* ===================== MENÜ ===================== */
/* Gleiche Menuepunkte wie die Desktop-Navigation im Header */
const MENU = [
{label:'Einsätze', href:'#/einsaetze', icon:I.siren, sub:[
{label:'Alle Einsätze', href:'#/einsaetze'},
{label:'Einsatzkarte', href:'#/einsatzkarte'}]},
{label:'Aktuelles', href:'#/aktuelles', icon:I.news},
{label:'Ortsfeuerwehren', href:'#/ortsfeuerwehren', icon:I.pin},
{label:'Kinder- und Jugendfeuerwehren', href:'#/jugendfeuerwehr', icon:I.helmet, sub:[
{label:'Übersicht', href:'#/jugendfeuerwehr'},
{label:'Mitmachen', href:'#/kontakt'}]},
{label:'Termine', href:'#/termine', icon:I.cal},
{label:'Über uns', href:'#/ueber-uns', icon:I.users, sub:[
{label:'Die Feuerwehr Melle', href:'#/ueber-uns'},
{label:'Förderverein', href:'#/foerderverein'},
{label:'Service & Formulare', href:'#/service'},
{label:'Kontakt', href:'#/kontakt'}]}
];
$('#menuList').innerHTML = MENU.map((m,i) => {
const hasSub = !!(m.sub && m.sub.length);
return '<li class="menu-item" data-i="'+i+'">'+
'<a class="menu-row" href="'+(hasSub ? '#' : m.href)+'" '+(hasSub ? 'data-toggle' : 'data-nav')+' data-href="'+m.href+'">'+
'<span class="ico" style="width:21px;height:21px;display:block;flex:none">'+m.icon+'</span><span>'+esc(m.label)+'</span>'+
(hasSub ? '<span class="chev">'+I.chevD+'</span>' : '')+'</a>'+
(hasSub ? '<div class="submenu"><div>'+m.sub.map(s => '<a href="'+s.href+'" data-nav>'+esc(s.label)+'</a>').join('')+'</div></div>' : '')+
'</li>';
}).join('');
/* ===================== ROUTER ===================== */
function match(path){
if (P[path]) return {fn:P[path], arg:null};
for (const key in P){
if (key.indexOf(':') === -1) continue;
const a = key.split('/'), b = path.split('/');
if (a.length !== b.length) continue;
let ok = true, arg = null;
for (let i = 0; i < a.length; i++){
if (a[i][0] === ':') arg = decodeURIComponent(b[i]);
else if (a[i] !== b[i]) { ok = false; break; }
}
if (ok) return {fn:P[key], arg};
}
return null;
}
const path = () => {
let h = location.hash.replace(/^#/, '') || '/';
if (h.length > 1 && h.endsWith('/')) h = h.slice(0, -1);
return h;
};
/* Unterseiten den sechs Hauptpunkten zuordnen (fuer den Aktiv-Zustand) */
const SECTIONS = [
['/einsatzkarte', '/einsaetze'], ['/einsaetze', '/einsaetze'],
['/aktuelles', '/aktuelles'], ['/ortsfeuerwehren', '/ortsfeuerwehren'],
['/jugendfeuerwehr', '/jugendfeuerwehr'], ['/termine', '/termine'],
['/ueber-uns', '/ueber-uns'], ['/foerderverein', '/ueber-uns'], ['/service', '/ueber-uns']
];
const section = p => {
if (p === '/') return '/';
for (const [prefix, sec] of SECTIONS) if (p.indexOf(prefix) === 0) return sec;
return p;
};
function render(){
const p = path(), m = match(p);
galIdx = 0;
main.innerHTML = m ? m.fn(m.arg) : P['/']();
window.scrollTo(0, 0);
const sec = section(p);
$$('nav.primary a[data-nav]').forEach(a => a.classList.toggle('is-active', a.getAttribute('href') === '#' + sec));
$$('.menu-item').forEach(li => li.classList.toggle('is-active', $('.menu-row', li).getAttribute('data-href') === '#' + sec));
$$('.tabbar [data-tab]').forEach(b => b.classList.toggle('is-active', b.getAttribute('data-tab') === sec));
wire(p);
closeMenu();
}
function wire(p){
if (p === '/'){
const wrap = $('#heroWrap');
if (wrap){
const show = i => {
heroIdx = (i + HERO_SLIDES.length) % HERO_SLIDES.length;
wrap.innerHTML = heroHTML();
};
wrap.addEventListener('click', ev => {
const dot = ev.target.closest('[data-dot]'), step = ev.target.closest('[data-step]');
if (!dot && !step) return;
ev.preventDefault(); ev.stopPropagation();
show(dot ? +dot.getAttribute('data-dot') : heroIdx + Number(step.getAttribute('data-step')));
});
/* Wischen am Handy */
let tx = null;
wrap.addEventListener('touchstart', ev => { tx = ev.touches[0].clientX; }, {passive:true});
wrap.addEventListener('touchend', ev => {
if (tx === null) return;
const dx = ev.changedTouches[0].clientX - tx;
tx = null;
if (Math.abs(dx) > 45) show(heroIdx + (dx < 0 ? 1 : -1));
});
}
}
if (p.indexOf('/einsaetze/') === 0){
const gw = $('#galWrap'), e = EINSAETZE.find(x => x.id === p.split('/')[2]);
if (gw && e && e.bilder && e.bilder.length > 1){
const show = i => { galIdx = (i + e.bilder.length) % e.bilder.length; gw.innerHTML = galerie(e); };
gw.addEventListener('click', ev => {
const step = ev.target.closest('[data-gal]'), set = ev.target.closest('[data-galset]');
if (!step && !set) return;
ev.preventDefault();
show(set ? +set.getAttribute('data-galset') : galIdx + Number(step.getAttribute('data-gal')));
});
let tx = null;
gw.addEventListener('touchstart', ev => { tx = ev.touches[0].clientX; }, {passive:true});
gw.addEventListener('touchend', ev => {
if (tx === null) return;
const dx = ev.changedTouches[0].clientX - tx;
tx = null;
if (Math.abs(dx) > 45) show(galIdx + (dx < 0 ? 1 : -1));
});
}
}
if (p === '/kontakt'){
const f = $('#kForm');
if (f) f.addEventListener('submit', ev => {
ev.preventDefault();
$('#kOk').classList.add('is-on');
$$('input,textarea', f).forEach(i => i.value = '');
});
}
if (p === '/suche'){
const inp = $('#sIn'), out = $('#sOut');
const run = () => {
const q = (inp.value || '').trim().toLowerCase();
if (!q){ out.innerHTML = '<div class="empty">Geben Sie einen Suchbegriff ein — zum Beispiel „Brand“, „Jugendfeuerwehr“ oder „Ausbildung“.</div>'; return; }
const nh = NEWS.filter(n => (n.title + n.cat + n.exc).toLowerCase().includes(q));
const eh = EINSAETZE.filter(e => (e.title + e.type + e.ort).toLowerCase().includes(q));
if (!nh.length && !eh.length){ out.innerHTML = '<div class="empty">Keine Ergebnisse für „' + esc(inp.value) + '“.</div>'; return; }
let h = '';
if (nh.length) h += '<div class="sec-head"><span class="sec-title">' + I.news + 'Meldungen</span></div><div class="grid3" style="margin-bottom:26px">' + nh.map(newsCard).join('') + '</div>';
if (eh.length) h += '<div class="sec-head"><span class="sec-title">' + I.sirenF + 'Einsätze</span></div><div class="card einsatz-card" style="padding-top:6px"><div class="ev-list">' + eh.map(evRow).join('') + '</div></div>';
out.innerHTML = h;
};
inp.addEventListener('input', run);
run();
inp.focus();
}
}
/* ===================== MENÜ-VERHALTEN ===================== */
const scrim = $('#menuScrim'), panel = $('#menuPanel'), burger = $('#burgerBtn');
function openMenu(){
scrim.classList.add('is-open'); panel.classList.add('is-open');
burger.setAttribute('aria-expanded', 'true');
document.body.style.overflow = 'hidden';
}
function closeMenu(){
scrim.classList.remove('is-open'); panel.classList.remove('is-open');
burger.setAttribute('aria-expanded', 'false');
document.body.style.overflow = '';
}
burger.addEventListener('click', openMenu);
$('#menuCloseBtn').addEventListener('click', closeMenu);
$('#tabMenuBtn').addEventListener('click', openMenu);
scrim.addEventListener('click', closeMenu);
document.addEventListener('keydown', e => { if (e.key === 'Escape') closeMenu(); });
$('#menuList').addEventListener('click', ev => {
const row = ev.target.closest('[data-toggle]');
if (!row) return;
ev.preventDefault();
const li = row.closest('.menu-item'), was = li.classList.contains('is-expanded');
$$('.menu-item').forEach(x => x.classList.remove('is-expanded'));
if (!was) li.classList.add('is-expanded');
});
$('#tabbar').addEventListener('click', ev => {
const b = ev.target.closest('button[data-tab]');
if (b) location.hash = '#' + b.getAttribute('data-tab');
});
document.addEventListener('click', ev => {
const a = ev.target.closest('[data-nav]');
if (!a) return;
const href = a.getAttribute('href');
if (!href || href === '#') return;
ev.preventDefault();
if (location.hash === href) render(); else location.hash = href;
});
window.addEventListener('hashchange', render);
render();
})();