Files
Kirschner-Geb-udeservice/tools/build_pages.py
T
NotPika69andClaude Opus 5.5 09f40da363 Echte Karte auf Unternehmen und Kontakt (MapLibre + OpenFreeMap)
Ersetzt die schematische Karte durch MapLibre GL 6.11 (lokal
eingebunden, ohne React, da die Seite statisch ist). Kacheln vom
OpenFreeMap-Stil "dark", auf die Nachtblau-Palette umgefärbt; Marker
mit Label, lädt beim Heranscrollen und zoomt sanft auf den Standort.
Die schematische Karte bleibt als Vorschau und Fallback ohne WebGL.
Datenschutzerklärung um den Kartenanbieter ergänzt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 22:00:33 +02:00

632 lines
36 KiB
Python

"""Erzeugt die sieben HTML-Seiten aus gemeinsamen Bausteinen (Header, Footer, Leistungen).
Quelle der Wahrheit für Inhalt und Struktur ist diese Datei — die HTML-Dateien nicht von Hand
ändern, sondern hier ändern und neu erzeugen:
python3 tools/build_pages.py (braucht Pillow: pip install pillow)
"""
import json
from datetime import date
from pathlib import Path
from PIL import Image
OUT = Path(__file__).resolve().parent.parent
IMG = OUT / "assets/img"
YEARS = date.today().year - 2003 # motion.js aktualisiert zur Laufzeit
SITE = "https://www.kirschner-service.de"
TEL = "0871 / 14 26 91 01"
TEL_HREF = "tel:+4987114269101"
SOS = "0160 / 836 57 53"
SOS_HREF = "tel:+491608365753"
MAIL = "info@kirschner-service.de"
GEO = (12.1740033, 48.5646225) # Etzstraße 45, Ergolding (OpenStreetMap)
MAPS = "https://www.google.com/maps/search/?api=1&query=Etzstra%C3%9Fe+45%2C+84030+Ergolding"
def mailto(subject, body=""):
from urllib.parse import quote
q = "subject=" + quote(subject)
if body:
q += "&body=" + quote(body)
return f"mailto:{MAIL}?{q}"
ANFRAGE = mailto("Angebotsanfrage", "Guten Tag,\n\nich interessiere mich für Ihre Leistungen.\n\nObjekt / Adresse:\nGewünschte Leistungen:\nTelefon für Rückruf:\n\nViele Grüße\n")
# ---------- Icons ----------
I_ARROW = '<svg class="btn__arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" aria-hidden="true"><path d="M4 12h15M13 6l6 6-6 6"/></svg>'
I_ARROW_S = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="square" aria-hidden="true"><path d="M4 12h15M13 6l6 6-6 6"/></svg>'
I_PHONE = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round" aria-hidden="true"><path d="M5 4h4l2 5-2.5 1.5a11 11 0 0 0 5 5L15 13l5 2v4a2 2 0 0 1-2 2A16 16 0 0 1 3 6a2 2 0 0 1 2-2"/></svg>'
I_DOWN = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" aria-hidden="true"><path d="M12 4v13M6 11l6 6 6-6M5 21h14"/></svg>'
I_PLUS = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" aria-hidden="true"><path d="M12 4v16M4 12h16"/></svg>'
I_DRAG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" aria-hidden="true"><path d="M9 6l-6 6 6 6M15 6l6 6-6 6"/></svg>'
def chev_mark(cls="chev-mark"):
return (f'<svg class="{cls}" viewBox="0 0 44 32" aria-hidden="true">'
'<polygon class="chev" opacity=".55" points="14,0 24,0 10,16 24,32 14,32 0,16"/>'
'<polygon class="chev" points="32,0 42,0 28,16 42,32 32,32 18,16"/></svg>')
HERO_CHEVRONS = ('<svg class="hero__chevrons" viewBox="0 0 600 400" preserveAspectRatio="xMidYMid meet" aria-hidden="true">'
'<path pathLength="1" d="M300 0 L100 200 L300 400"/>'
'<path pathLength="1" d="M430 0 L230 200 L430 400"/>'
'<path pathLength="1" d="M560 0 L360 200 L560 400"/></svg>')
BAND_CHEV = ('<svg class="band__chev" viewBox="0 0 600 400" aria-hidden="true">'
'<path d="M260 0h120L180 200l200 200H260L60 200z"/><path d="M460 0h120L380 200l200 200H460L260 200z"/></svg>')
def pic(name, alt, sizes="100vw", eager=False, cls=""):
w, h = Image.open(IMG / f"{name}-1600.webp").size if (IMG / f"{name}-1600.webp").exists() else Image.open(IMG / f"{name}-1400.webp").size
big = 1600 if (IMG / f"{name}-1600.webp").exists() else 1400
load = 'fetchpriority="high"' if eager else 'loading="lazy" decoding="async"'
c = f' class="{cls}"' if cls else ""
return (f'<picture{c}>'
f'<source type="image/avif" srcset="assets/img/{name}-800.avif 800w, assets/img/{name}-{big}.avif {big}w" sizes="{sizes}">'
f'<source type="image/webp" srcset="assets/img/{name}-800.webp 800w, assets/img/{name}-{big}.webp {big}w" sizes="{sizes}">'
f'<img src="assets/img/{name}-{big}.webp" width="{w}" height="{h}" alt="{alt}" {load}></picture>')
NAV = [("index.html", "Start"), ("leistungen.html", "Leistungen"), ("unternehmen.html", "Unternehmen"),
("karriere.html", "Karriere"), ("kontakt.html", "Kontakt")]
HEAD_SCRIPT = ("(function(r){r.classList.add('js');try{if(r.hasAttribute('data-intro')&&!sessionStorage.getItem('kk-intro')"
"&&matchMedia('(prefers-reduced-motion: no-preference)').matches)r.classList.add('has-intro')}catch(e){}"
"setTimeout(function(){if(!r.classList.contains('motion-ready'))r.classList.remove('js','has-intro')},3000)})(document.documentElement)")
LD = {
"@context": "https://schema.org",
"@type": "LocalBusiness",
"name": "Kirschner Gebäudeservice",
"description": "Hausmeisterservice und Gebäudedienstleistungen in Landshut und Ergolding seit 2003.",
"url": SITE + "/",
"telephone": "+49 871 14269101",
"email": MAIL,
"foundingDate": "2003",
"founder": {"@type": "Person", "name": "Thomas Kirschner"},
"address": {"@type": "PostalAddress", "streetAddress": "Etzstraße 45", "postalCode": "84030",
"addressLocality": "Ergolding", "addressRegion": "Bayern", "addressCountry": "DE"},
"areaServed": ["Landshut", "Ergolding"],
"slogan": "persönlich, zuverlässig, erstklassig",
"image": SITE + "/assets/img/og.jpg",
}
def head(title, desc, file, intro=False):
ld = f'<script type="application/ld+json">{json.dumps(LD, ensure_ascii=False)}</script>' if file == "index.html" else ""
canonical = SITE + "/" + ("" if file == "index.html" else file)
return f'''<!doctype html>
<html lang="de"{' data-intro' if intro else ''}>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{title}</title>
<meta name="description" content="{desc}">
<link rel="canonical" href="{canonical}">
<meta name="theme-color" content="#0A1424">
<meta property="og:type" content="website">
<meta property="og:locale" content="de_DE">
<meta property="og:site_name" content="Kirschner Gebäudeservice">
<meta property="og:title" content="{title}">
<meta property="og:description" content="{desc}">
<meta property="og:url" content="{canonical}">
<meta property="og:image" content="{SITE}/assets/img/og.jpg">
<link rel="icon" href="assets/favicon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="assets/apple-touch-icon.png">
<link rel="preload" href="assets/fonts/archivo-latin-wdth-normal.woff2" as="font" type="font/woff2" crossorigin>
<link rel="stylesheet" href="assets/site.css">
<script>{HEAD_SCRIPT}</script>
<script src="assets/vendor/gsap.min.js" defer></script>
<script src="assets/vendor/ScrollTrigger.min.js" defer></script>
<script src="assets/vendor/SplitText.min.js" defer></script>
<script src="assets/vendor/lenis.min.js" defer></script>
<script src="assets/motion.js" defer></script>
{ld}
</head>'''
def header(active):
def cur(f):
return ' aria-current="page"' if f == active else ""
links = "".join(f'<a class="nav__link" href="{f}"{cur(f)}>{n}</a>' for f, n in NAV if f != "index.html")
mlinks = "".join(f'<li><a class="mobile-nav__link" href="{f}"{cur(f)}>{n}</a></li>' for f, n in NAV)
return f'''
<a class="skip-link" href="#inhalt">Zum Inhalt springen</a>
<header class="site-header">
<div class="wrap site-header__inner">
<a class="brand" href="index.html" aria-label="Kirschner Gebäudeservice — Startseite">
{chev_mark()}
<span class="brand__name"><b>KIRSCHNER</b><span>Gebäudeservice</span></span>
</a>
<nav class="nav" aria-label="Hauptnavigation">{links}</nav>
<div class="header-cta">
<a class="header-phone" href="{TEL_HREF}">{I_PHONE}<span>{TEL}</span></a>
<a class="btn" href="{ANFRAGE}">Angebot anfragen {I_ARROW}</a>
<button class="menu-toggle" type="button" aria-expanded="false" aria-controls="mobile-nav" aria-label="Menü öffnen"><span class="menu-toggle__bars"></span></button>
</div>
</div>
</header>
<div class="mobile-nav" id="mobile-nav">
<nav aria-label="Mobile Navigation"><ul role="list">{mlinks}</ul></nav>
<div class="mobile-nav__meta">
<a href="{TEL_HREF}">Büro {TEL}</a>
<a href="{SOS_HREF}">Notruf 24/7 {SOS}</a>
<a href="mailto:{MAIL}">{MAIL}</a>
</div>
</div>'''
FOOTER = f'''
<footer class="site-footer">
<div class="wrap">
<div class="footer-grid">
<div>
<a class="brand" href="index.html" aria-label="Kirschner Gebäudeservice — Startseite">{chev_mark()}<span class="brand__name"><b>KIRSCHNER</b><span>Gebäudeservice</span></span></a>
</div>
<div>
<h2>Seiten</h2>
<ul role="list">{"".join(f'<li><a href="{f}">{n}</a></li>' for f, n in NAV)}</ul>
</div>
<div>
<h2>Kontakt</h2>
<ul role="list">
<li><a href="{TEL_HREF}">{TEL}</a></li>
<li><a href="{SOS_HREF}">Notruf {SOS}</a></li>
<li><a href="mailto:{MAIL}">{MAIL}</a></li>
</ul>
</div>
<div>
<h2>Adresse</h2>
<address style="font-style:normal">Etzstraße 45<br>84030 Ergolding<br><a href="{MAPS}" rel="noopener" target="_blank">Route planen</a></address>
</div>
</div>
<div class="footer-bottom">
<span>© <span data-year>2026</span> Kirschner Gebäudeservice · Thomas Kirschner</span>
<nav aria-label="Rechtliches"><a href="impressum.html">Impressum</a><a href="datenschutz.html">Datenschutz</a></nav>
</div>
</div>
</footer>
<a class="sos" href="{SOS_HREF}" aria-label="24/7 Notruf anrufen: {SOS}">
<span class="sos__dot" aria-hidden="true"></span>
<span><small>Notruf 24/7</small><b>{SOS}</b></span>
</a>'''
def page(file, title, desc, body, intro=False):
h = head(title, desc, file, intro)
if "data-map" in body:
h = h.replace("</head>", '<script type="module" src="assets/map.js"></script>\n</head>')
html = h + "\n<body>" + header(file) + f'\n<main id="inhalt">\n{body}\n</main>' + FOOTER + "\n</body>\n</html>\n"
(OUT / file).write_text(html, encoding="utf-8")
print("geschrieben:", file, len(html))
def page_hero(h1, lead="", img=None, extra="", small=False):
media = f'<div class="hero__media">{pic(img, "", "100vw", eager=True)}</div>' if img else ""
cls = "page-hero" + (" page-hero--image" if img else "") + (" page-hero--small" if small else "")
lead_html = f'<p class="lead" data-hero>{lead}</p>' if lead else ""
return f'''
<section class="{cls}">
{media}
{HERO_CHEVRONS}
<div class="wrap page-hero__grid">
<h1 class="display" data-split>{h1}</h1>
<div class="stack">
{lead_html}
{extra}
</div>
</div>
</section>'''
# ======================================================================
# Leistungen (eine Quelle für Start und Leistungsseite)
# ======================================================================
SERVICES = [
("reinigung", "Reinigung",
"Treppenhäuser, Innen- und Außenflächen nach festem Plan. Tiefgaragen mit modernen Maschinen.",
"Treppenhaus in einem modernen Wohngebäude"),
("glas", "Glas- & PV-Reinigung",
"Mit eigener Osmoseanlage und reinem Wasser — meist sogar ohne Hebebühne.",
"Fensterreinigung mit Teleskopstange an einem Wohnhaus"),
("garten", "Garten- & Grünpflege",
"Rasen, Hecken, Beete und Grünanlagen, das ganze Jahr über.",
"Heckenschnitt in einer Gartenanlage"),
("technik", "Technische Hausbetreuung",
"Kontrolle und Wartung technischer Anlagen.",
"Rohrleitungen einer Haustechnikanlage"),
("reparatur", "Reparaturen & Renovierungen",
"Handwerk im eigenen Team — vom Servicetechniker bis zum Schreiner.",
"Handwerker bei der Arbeit mit einem Akkuschrauber"),
("winter", "Winterdienst",
"Räumen und Streuen mit eigenen Fahrzeugen.",
"Schneeräumung vor einem Gebäude"),
("bereitschaft", "24/7-Bereitschaft",
"Auf Wunsch rund um die Uhr erreichbar — Notruf 0160 / 836 57 53.",
"Reinigung einer Glasfassade am Abend"),
]
# ======================================================================
# Startseite
# ======================================================================
panels = "".join(f'''
<article class="panel" data-reveal>
<div class="panel__media">{pic(n, alt, "(min-width: 1024px) 38vw, (min-width: 700px) 50vw, 100vw")}<span class="panel__num">{i:02d}</span></div>
<div class="panel__body">
<h3>{t}</h3>
<p>{d}</p>
</div>
</article>''' for i, (n, t, d, alt) in enumerate(SERVICES, 1))
INDEX = f'''
<div class="intro" aria-hidden="true">
<div class="intro__mark">
{chev_mark()}
<div class="intro__word"><span>KIRSCHNER</span><small>Gebäudeservice</small></div>
</div>
</div>
<section class="hero" aria-labelledby="hero-title">
<div class="hero__media">{pic("hero", "", "100vw", eager=True)}</div>
{HERO_CHEVRONS}
<div class="wrap">
<div class="hero__content">
<h1 class="mega" id="hero-title" data-split>Ihr Gebäude.<br> In besten Händen.</h1>
<p class="lead" data-hero>Hausmeisterservice in Landshut und Ergolding — seit 2003.</p>
<div class="btn-row" data-hero>
<a class="btn" href="{ANFRAGE}">Angebot anfragen {I_ARROW}</a>
<a class="btn btn--ghost" href="leistungen.html">Leistungen {I_ARROW}</a>
</div>
</div>
</div>
</section>
<section class="section section--flush-bottom" aria-labelledby="services-title">
<div class="wrap services-head">
<h2 class="display" id="services-title" data-split>Leistungen</h2>
<a class="link-arrow" href="leistungen.html" data-reveal>Alle Leistungen im Detail {I_ARROW_S}</a>
</div>
</section>
<section class="hscroll" aria-label="Leistungsübersicht">
<div class="hscroll__track">{panels}
</div>
<div class="hscroll__progress" aria-hidden="true"><span data-hs-count>01</span><span class="hscroll__bar"><i></i></span><span>{len(SERVICES):02d}</span></div>
</section>
<section class="section theme-white" aria-labelledby="osmose-title">
<div class="wrap feature">
<div class="feature__copy">
<h2 class="display" id="osmose-title" data-split>Glas- &amp; PV-Reinigung mit Osmose.</h2>
<p class="lead" data-reveal>Eigene Osmoseanlage, reines Wasser, keine Rückstände — meist sogar ohne Hebebühne.</p>
<p data-reveal class="mt-2"><a class="btn" href="{mailto('Anfrage Glas- / PV-Reinigung')}">Reinigung anfragen {I_ARROW}</a></p>
</div>
<figure data-reveal>
<div class="compare" tabindex="0" role="slider" aria-label="Vorher-Nachher-Vergleich PV-Reinigung" aria-valuemin="0" aria-valuemax="100" aria-valuenow="50">
{pic("pv-nachher", "Photovoltaik-Modul nach der Reinigung, klar und glänzend", "(min-width: 960px) 55vw, 100vw")}
<div class="compare__before">{pic("pv-vorher", "Photovoltaik-Modul vor der Reinigung, mit Staub und Kalkflecken", "(min-width: 960px) 55vw, 100vw")}</div>
<div class="compare__handle" aria-hidden="true"><span class="compare__knob">{I_DRAG}</span></div>
<span class="compare__tag compare__tag--before" aria-hidden="true">Vorher</span>
<span class="compare__tag compare__tag--after" aria-hidden="true">Nachher</span>
</div>
<figcaption class="figure-note">Symbolbild</figcaption>
</figure>
</div>
</section>
<section class="philosophy philosophy--image" aria-label="Unser Motto">
<div class="philosophy__media">{pic("fassade", "", "100vw")}</div>
<div class="wrap">
<p class="philosophy__words" data-scrub-words>Persönlich<span class="dot">.</span> Zuverlässig<span class="dot">.</span> Erstklassig<span class="dot">.</span></p>
</div>
</section>
<section class="band" aria-labelledby="jobs-title">
{BAND_CHEV}
<div class="wrap band__inner">
<div class="stack">
<h2 class="display" id="jobs-title" data-split>Wir stellen ein.</h2>
<p data-reveal>Hausmeister, Ga-La-Bauer, Glas- und Solarreiniger, Reinigungskräfte.</p>
</div>
<div data-reveal><a class="btn" href="karriere.html">Offene Stellen {I_ARROW}</a></div>
</div>
</section>
'''
page("index.html",
"Kirschner Gebäudeservice — Hausmeisterservice in Landshut & Ergolding seit 2003",
"Hausmeisterservice, Reinigung, Glas- & PV-Reinigung mit Osmose, Grünpflege, Winterdienst und 24/7-Bereitschaft in Landshut und Ergolding. Persönlich, zuverlässig, erstklassig — seit 2003.",
INDEX, intro=True)
# ======================================================================
# Leistungen
# ======================================================================
rows = "".join(f'''
<article class="service-row" id="{n}">
<div class="service-row__media reveal-img">{pic(n, alt, "(min-width: 960px) 52vw, 100vw")}</div>
<div class="service-row__body">
<span class="service-row__num" data-reveal>{i:02d}</span>
<h2 class="title" data-split>{t}</h2>
<p class="muted" data-reveal>{d}</p>
</div>
</article>''' for i, (n, t, d, alt) in enumerate(SERVICES, 1))
LEISTUNGEN = page_hero("Leistungen",
"Für Privat, Eigentümergemeinschaften, Gewerbe und die öffentliche Hand.",
"seil",
f'<div class="btn-row" data-hero><a class="btn" href="{ANFRAGE}">Angebot anfragen {I_ARROW}</a></div>') + f'''
<section class="section" aria-label="Leistungen im Detail">
<div class="wrap service-rows">{rows}
</div>
</section>
<section class="section theme-light" aria-labelledby="dl-title">
<div class="wrap">
<div class="section-head">
<h2 class="display" id="dl-title" data-split>Zum Download</h2>
</div>
<div class="downloads">
<a class="download" href="assets/docs/Leistungsverzeichnis-Wohnanlage.pdf" download data-reveal><span class="download__icon">PDF</span><span><b>Leistungsverzeichnis Wohnanlage</b><span>PDF · 1,4 MB</span></span>{I_DOWN}</a>
<a class="download" href="assets/docs/Leistungsverzeichnis-Tiefgarage.pdf" download data-reveal><span class="download__icon">PDF</span><span><b>Leistungsverzeichnis Tiefgarage</b><span>PDF · 400 KB</span></span>{I_DOWN}</a>
</div>
<p class="muted mt-4" data-reveal>Referenzobjekte in Landshut nennen wir Ihnen gern auf Anfrage.</p>
</div>
</section>
'''
page("leistungen.html",
"Leistungen — Hausmeisterservice, Reinigung, Winterdienst | Kirschner Gebäudeservice",
"Reinigung, Glas- & PV-Reinigung mit Osmoseanlage, Garten- & Grünpflege, technische Hausbetreuung, Reparaturen, Winterdienst und 24/7-Bereitschaft in Landshut und Ergolding.",
LEISTUNGEN)
# ======================================================================
# Unternehmen
# ======================================================================
TIMELINE = [
("2003", "Gründung in Ergolding", "Kirschner Gebäudeservice startet mit Dienstleistungen rund um Immobilien."),
("2013", "10 Jahre", "Zehn Jahre Kirschner Gebäudeservice."),
("2015", "Servicetechnik & Schreinerei", "Herr Listl verstärkt Servicetechnik und Hausmeisterei — mit langjähriger Erfahrung als Schreiner."),
("2018", "15 Jahre", "Jubiläumsfeier mit Geschäftspartnern. Die Spenden des Abends versorgen über 120 Kinder in Nepal ein Jahr lang mit Schulessen."),
("2019", "Das Team wächst", "Herr Gerhart und Herr Ponath kommen ins Team."),
("2020", "FC Ergolding", "Ein neuer Trikotsatz für die U8 des FC Ergolding 1932 e.V."),
]
tl = "".join(f'''
<div class="tl-item" data-reveal><span class="tl-item__year">{y}</span><div><h3>{h}</h3><p>{p}</p></div></div>''' for y, h, p in TIMELINE)
TEAM = [("TK", "Thomas Kirschner", "Geschäftsführung"), ("MP", "Marcel Pilsner", "Stellvertretende Geschäftsführung"),
("CK", "Christine Kirschner", "Assistenz Büro"), ("EM", "Elena Miller", "Qualitätsmanagement"),
("HL", "Harald Listl", "Hausmeister, technische Betreuung, Schreiner")]
team = "".join(f'''
<div class="member" data-reveal>
<div class="member__photo">{chev_mark("")}<span class="member__initials" aria-hidden="true">{ini}</span><small>Foto folgt</small></div>
<div><b>{n}</b><span>{r}</span></div>
</div>''' for ini, n, r in TEAM) + '''
<div class="member member--more" data-reveal>
<div class="member__photo"><span class="member__initials" aria-hidden="true">+</span></div>
<div><b>und viele mehr</b><span>Kolleginnen und Kollegen</span></div>
</div>'''
MAP_SVG = '''<svg viewBox="0 0 800 480" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
<defs><pattern id="g" width="40" height="40" patternUnits="userSpaceOnUse"><path d="M40 0H0v40" fill="none" stroke="rgba(169,183,204,.07)"/></pattern></defs>
<rect width="800" height="480" fill="url(#g)"/>
<path d="M-20 360 C 140 330, 240 400, 380 370 S 640 300, 820 330" fill="none" stroke="rgba(91,138,214,.35)" stroke-width="18" stroke-linecap="round"/>
<g fill="none" stroke="rgba(169,183,204,.28)" stroke-width="3" stroke-linecap="round">
<path d="M0 120 L800 170"/><path d="M120 0 L260 480"/><path d="M430 0 C 440 160, 470 260, 520 480"/><path d="M0 250 C 200 230, 520 250, 800 210"/><path d="M620 0 L700 480"/>
</g>
<g fill="none" stroke="rgba(169,183,204,.14)" stroke-width="1.5"><path d="M0 60 L800 90"/><path d="M300 0 L340 480"/><path d="M0 420 L800 440"/><path d="M540 0 L580 480"/></g>
</svg>'''
UNTERNEHMEN = page_hero("Das Original seit 2003.",
"Persönlich, zuverlässig, erstklassig — aus Ergolding für Landshut und Umgebung.",
"werkzeug") + f'''
<section class="section theme-light" aria-labelledby="tl-title">
<div class="wrap">
<div class="section-head">
<h2 class="display" id="tl-title" data-split>Geschichte</h2>
</div>
<div class="timeline">
<span class="timeline__line" aria-hidden="true"><i></i></span>{tl}
</div>
</div>
</section>
<section class="section theme-white" aria-labelledby="team-title">
<div class="wrap">
<div class="section-head">
<h2 class="display" id="team-title" data-split>Unser Team</h2>
</div>
<div class="team">{team}
</div>
</div>
</section>
<section class="section" aria-labelledby="map-title" id="anfahrt">
<div class="wrap map-card">
<div class="stack">
<h2 class="display" id="map-title" data-split>Anfahrt</h2>
<p class="lead" data-reveal>Etzstraße 45<br>84030 Ergolding</p>
<div class="btn-row" data-reveal>
<a class="btn" href="{MAPS}" target="_blank" rel="noopener">Route planen {I_ARROW}</a>
</div>
</div>
<div class="map reveal-img" data-map data-lng="{GEO[0]}" data-lat="{GEO[1]}" data-label="Kirschner Gebäudeservice" role="region" aria-label="Karte: Kirschner Gebäudeservice, Etzstraße 45, Ergolding">
<div class="map__placeholder" aria-hidden="true">{MAP_SVG}<span class="map__pin"><span>Kirschner Gebäudeservice</span><i></i></span></div>
</div>
</div>
</section>
'''
page("unternehmen.html",
"Unternehmen & Team — Kirschner Gebäudeservice Ergolding",
"Kirschner Gebäudeservice in Ergolding — das Original seit 2003. Geschichte, Team und Anfahrt.",
UNTERNEHMEN)
# ======================================================================
# Karriere
# ======================================================================
JOBS = [
("Handwerker / Hausmeister", ["Teilzeit", "Vollzeit", "auch Ga-La-Bauer, Glas- & Solarreiniger"],
["Erfahrung im jeweiligen Bereich", "Blick für Sauberkeit und Qualität", "Freundlichkeit, Teamfähigkeit & Motivation", "Ambition, Verantwortung zu übernehmen"],
["Sicherer Arbeitsplatz — krisenfest und zukunftssicher", "Weit übertarifliche Bezahlung und klare Aufstiegspläne", "Hochwertige Arbeitskleidung", "Boni: Spritgeld, Urlaubs- & Weihnachtsgeld, Erfolgsbonus", "Starkes Team, das sich gegenseitig unterstützt"],
["Du betreust feste Objekte, pflegst mit Kollegen Gärten und Grünflächen, reinigst Tiefgaragen mit modernen Maschinen und sorgst im Winter mit unseren Fahrzeugen für sichere Wege. Mit handwerklichem Geschick übernimmst Du auch Reparaturen.",
"Die Arbeit ist über unsere eigene App strukturiert — ohne permanente Überstunden, ohne Freitagnachmittag und Samstag."],
"Bewerbung: Handwerker / Hausmeister (TZ/VZ)"),
("Reinigungskraft", ["Minijob", "m/w/d"],
["Pünktlichkeit, Ehrlichkeit, Fleiß und Zuverlässigkeit", "Sauberes, gepflegtes Erscheinungsbild und gute Umgangsformen", "Blick für Sauberkeit", "Gutes Deutsch", "Idealerweise Erfahrung im Reinigungsbereich"],
["Tarifliche Vergütung sowie Gutscheine (z. B. Tanken)", "Flexible Zeiteinteilung", "30 Arbeitstage bezahlter Urlaub", "Lohnfortzahlung im Krankheitsfall", "Feste eigene Objekte statt Springer", "Arbeitszeit per App am Handy erfassen"],
[],
"Bewerbung: Reinigungskraft (Minijob)"),
("Hausmeister", ["Minijob", "m/w/d"],
["Pünktlichkeit, Ehrlichkeit, Fleiß und Zuverlässigkeit", "Sauberes, gepflegtes Erscheinungsbild und gute Umgangsformen", "Blick für Sauberkeit", "Gutes Deutsch", "Idealerweise Erfahrung im Garten oder handwerklich"],
["Übertarifliche Vergütung", "Prämien wie Gutscheine (z. B. Tanken)", "Flexible Zeiteinteilung", "30 Arbeitstage bezahlter Urlaub", "Lohnfortzahlung im Krankheitsfall", "Moderne Maschinen und Fuhrpark", "Arbeitszeit per App erfassen"],
[],
"Bewerbung: Hausmeister (Minijob)"),
]
def job_text(text):
return ('<div class="job__text">' + "".join(f"<p>{p}</p>" for p in text) + "</div>") if text else ""
APPLY_BODY = "Hallo Herr Kirschner,\n\nich interessiere mich für die Stelle und möchte mich kurz vorstellen:\n\nName:\nTelefon:\nErfahrung:\nFrühester Start:\n\nViele Grüße\n"
jobs = "".join(f'''
<details class="job" data-reveal{" open" if i == 0 else ""}>
<summary>
<div><h3>{t}</h3><div class="job__meta">{"".join(f"<span>{m}</span>" for m in meta)}</div></div>
<span class="job__toggle" aria-hidden="true">{I_PLUS}</span>
</summary>
<div class="job__body">
{job_text(text)}
<div><h4>Das bringst Du mit</h4><ul role="list">{"".join(f"<li>{x}</li>" for x in need)}</ul></div>
<div><h4>Das bekommst Du</h4><ul role="list">{"".join(f"<li>{x}</li>" for x in give)}</ul></div>
<div class="job__apply btn-row"><a class="btn" href="{mailto(subj, APPLY_BODY)}">Jetzt bewerben {I_ARROW}</a><a class="btn btn--ghost" href="{TEL_HREF}">Anrufen</a></div>
</div>
</details>''' for i, (t, meta, need, give, text, subj) in enumerate(JOBS))
KARRIERE = page_hero("Wir stellen ein.",
"Hausmeister, Ga-La-Bauer, Glas- und Solarreiniger und Reinigungskräfte für Landshut und Ergolding.",
"rasen",
f'<div class="btn-row" data-hero><a class="btn" href="#stellen">Offene Stellen {I_ARROW}</a></div>') + f'''
<section class="section theme-light" aria-labelledby="stellen-title" id="stellen">
<div class="wrap">
<div class="section-head">
<h2 class="display" id="stellen-title" data-split>Offene Stellen</h2>
</div>
<div class="jobs">{jobs}
</div>
</div>
</section>
'''
page("karriere.html",
"Karriere — Hausmeister, Handwerker & Reinigungskräfte gesucht | Kirschner Gebäudeservice",
"Wir stellen ein: Hausmeister, Handwerker, Ga-La-Bauer, Glas- & Solarreiniger und Reinigungskräfte in Landshut und Ergolding.",
KARRIERE)
# ======================================================================
# Kontakt
# ======================================================================
KONTAKT = page_hero("Kontakt", "", "balkone") + f'''
<section class="section" aria-label="Kontaktwege">
<div class="wrap">
<div class="contact-grid">
<a class="contact-card" href="{TEL_HREF}" data-reveal><small>Büro</small><strong>{TEL}</strong><span class="contact-card__go">Anrufen {I_ARROW_S}</span></a>
<a class="contact-card contact-card--sos" href="{SOS_HREF}" data-reveal><small>Notruf 24/7</small><strong>{SOS}</strong><span class="contact-card__go">Notruf wählen {I_ARROW_S}</span></a>
<a class="contact-card" href="{ANFRAGE}" data-reveal><small>E-Mail</small><strong>{MAIL}</strong><span class="contact-card__go">E-Mail schreiben {I_ARROW_S}</span></a>
</div>
</div>
</section>
<section class="section theme-deep" aria-labelledby="map-title">
<div class="wrap map-card">
<div class="stack">
<h2 class="display" id="map-title" data-split>Anfahrt</h2>
<p class="lead" data-reveal>Kirschner Gebäudeservice<br>Etzstraße 45<br>84030 Ergolding</p>
<div class="btn-row" data-reveal><a class="btn" href="{MAPS}" target="_blank" rel="noopener">Route planen {I_ARROW}</a></div>
</div>
<div class="map reveal-img" data-map data-lng="{GEO[0]}" data-lat="{GEO[1]}" data-label="Kirschner Gebäudeservice" role="region" aria-label="Karte: Kirschner Gebäudeservice, Etzstraße 45, Ergolding">
<div class="map__placeholder" aria-hidden="true">{MAP_SVG}<span class="map__pin"><span>Kirschner Gebäudeservice</span><i></i></span></div>
</div>
</div>
</section>
'''
page("kontakt.html",
"Kontakt — Kirschner Gebäudeservice Ergolding",
"Kirschner Gebäudeservice, Etzstraße 45, 84030 Ergolding. Telefon 0871 / 14 26 91 01, Notruf 0160 / 836 57 53, info@kirschner-service.de.",
KONTAKT)
# ======================================================================
# Impressum & Datenschutz
# ======================================================================
CREDITS = [("Titelbild (Wohngebäude)", "Tobias Wilden"), ("Treppenhaus", "Payam Moin Afshari"), ("Fensterreinigung", "Joshua Bowers"),
("PV-Reinigung", "Florida Solar Fix"), ("Heckenschnitt", "Mustafa Turhan"), ("Haustechnik", "Peter Herrmann"),
("Reparatur", "Theme Photos"), ("Winterdienst", "Bradley Andrews"), ("Glasfassade", "Priscilla Du Preez"),
("Fassadenreinigung am Seil", "Nuno Silva"), ("Fassade", "Luke van Zyl"), ("Werkzeugwand", "Barn Images"),
("Rasenmähen", "Carl Tronders"), ("Balkone", "Waldemar Brandt")]
IMPRESSUM = page_hero("Impressum", "Angaben gemäß § 5 Digitale-Dienste-Gesetz (DDG).", small=True) + f'''
<section class="section theme-light" style="padding-top: var(--s6)">
<div class="wrap legal">
<h2>Anbieter</h2>
<address>Kirschner Gebäudeservice<br>Inhaber: Thomas Kirschner<br>Etzstraße 45<br>84030 Ergolding</address>
<span class="todo">Offen: Rechtsform bestätigen (Einzelunternehmen?). Falls GmbH o. Ä.: Firmierung, Registergericht, Registernummer und Vertretungsberechtigte ergänzen.</span>
<h2>Kontakt</h2>
<p>Telefon: <a href="{TEL_HREF}">{TEL}</a><br>Mobil: <a href="{SOS_HREF}">{SOS}</a><br>E-Mail: <a href="mailto:{MAIL}">{MAIL}</a></p>
<h2>Steuern</h2>
<p>Steuernummer: 132/236/30809</p>
<h2>Verantwortlich für den Inhalt nach § 18 Abs. 2 MStV</h2>
<p>Thomas Kirschner, Anschrift wie oben.</p>
<h2>Verbraucherstreitbeilegung</h2>
<p>Wir sind nicht bereit und nicht verpflichtet, an Streitbeilegungsverfahren vor einer Verbraucherschlichtungsstelle teilzunehmen.</p>
<h2>Haftung für Inhalte und Links</h2>
<p>Die Inhalte dieser Seiten wurden mit größter Sorgfalt erstellt. Für die Richtigkeit, Vollständigkeit und Aktualität können wir jedoch keine Gewähr übernehmen. Als Diensteanbieter sind wir für eigene Inhalte nach den allgemeinen Gesetzen verantwortlich. Für Inhalte externer Links sind ausschließlich deren Betreiber verantwortlich; rechtswidrige Inhalte waren zum Zeitpunkt der Verlinkung nicht erkennbar. Bei Bekanntwerden von Rechtsverletzungen entfernen wir entsprechende Inhalte oder Links umgehend.</p>
<h2>Urheberrecht</h2>
<p>Die durch uns erstellten Inhalte und Werke auf diesen Seiten unterliegen dem deutschen Urheberrecht. Vervielfältigung, Bearbeitung und Verbreitung außerhalb der Grenzen des Urheberrechts bedürfen unserer schriftlichen Zustimmung.</p>
<h2>Bildnachweise</h2>
<p>Fotos von Unsplash (Unsplash-Lizenz):</p>
<ul>{"".join(f"<li>{a}: {b}</li>" for a, b in CREDITS)}</ul>
<p>Schrift: Archivo (SIL Open Font License), lokal eingebunden.</p>
</div>
</section>'''
page("impressum.html", "Impressum — Kirschner Gebäudeservice", "Impressum von Kirschner Gebäudeservice, Etzstraße 45, 84030 Ergolding.", IMPRESSUM)
DATENSCHUTZ = page_hero("Datenschutz", "Diese Website setzt keine Cookies und verwendet kein Tracking.", small=True) + f'''
<section class="section theme-light" style="padding-top: var(--s6)">
<div class="wrap legal">
<h2>1. Verantwortlicher</h2>
<address>Kirschner Gebäudeservice, Thomas Kirschner<br>Etzstraße 45, 84030 Ergolding<br>Telefon: {TEL}<br>E-Mail: <a href="mailto:{MAIL}">{MAIL}</a></address>
<h2>2. Hosting und Server-Logfiles</h2>
<p>Beim Aufruf dieser Website verarbeitet unser Hosting-Anbieter technisch notwendige Daten (IP-Adresse, Datum und Uhrzeit, aufgerufene Seite, Referrer, Browser und Betriebssystem), um die Seite auszuliefern und die Sicherheit des Betriebs zu gewährleisten. Rechtsgrundlage ist Art. 6 Abs. 1 lit. f DSGVO; unser berechtigtes Interesse liegt im sicheren und stabilen Betrieb der Website.</p>
<span class="todo">Offen: Name und Anschrift des Hosting-Anbieters, Abschluss eines Auftragsverarbeitungsvertrags (Art. 28 DSGVO) und Speicherdauer der Logfiles eintragen.</span>
<h2>3. Keine Cookies, kein Tracking</h2>
<p>Diese Website setzt keine Cookies und verwendet keine Analyse- oder Marketingwerkzeuge. Schriften und Skripte liegen auf unserem eigenen Server. Lediglich im Browser-Sitzungsspeicher (sessionStorage) wird vermerkt, dass die Eröffnungsanimation bereits gezeigt wurde — dieser Eintrag enthält keine personenbezogenen Daten und wird beim Schließen des Browsers gelöscht.</p>
<h2>4. Karte (OpenFreeMap) und externe Links</h2>
<p>Auf den Seiten „Unternehmen" und „Kontakt" zeigen wir unseren Standort auf einer Karte. Sobald die Karte in den sichtbaren Bereich gescrollt wird, lädt Ihr Browser Kartenkacheln, Schriften und Symbole von OpenFreeMap (tiles.openfreemap.org). Dabei werden Ihre IP-Adresse sowie technische Angaben (Browser, Zeitpunkt, angeforderte Kartenausschnitte) an den Betreiber übertragen. Kartendaten: © OpenMapTiles, © OpenStreetMap-Mitwirkende.</p>
<p>Rechtsgrundlage ist Art. 6 Abs. 1 lit. f DSGVO; unser berechtigtes Interesse liegt darin, Kunden und Bewerbern den Weg zu uns leicht auffindbar zu machen.</p>
<span class="todo">Offen: Betreiberangaben von OpenFreeMap (Name, Anschrift, Serverstandort, Speicherdauer) prüfen und hier ergänzen; rechtlich prüfen lassen, ob das Laden ohne vorherige Einwilligung zulässig ist.</span>
<p>Der Link „Route planen" führt zu Google Maps (Google Ireland Limited). Erst wenn Sie ihn anklicken, werden Daten an Google übertragen; dafür gilt die Datenschutzerklärung von Google.</p>
<h2>5. Kontakt per E-Mail oder Telefon</h2>
<p>Wenn Sie uns per E-Mail oder Telefon kontaktieren, verarbeiten wir Ihre Angaben zur Bearbeitung der Anfrage und für Anschlussfragen. Rechtsgrundlage ist Art. 6 Abs. 1 lit. b DSGVO, soweit Ihre Anfrage mit einem Vertrag zusammenhängt, ansonsten Art. 6 Abs. 1 lit. f DSGVO. Wir löschen die Daten, sobald sie nicht mehr erforderlich sind und keine gesetzlichen Aufbewahrungspflichten bestehen.</p>
<h2>6. Bewerbungen</h2>
<p>Bewerbungen, die Sie uns per E-Mail senden, verarbeiten wir zur Durchführung des Bewerbungsverfahrens (§ 26 BDSG, Art. 6 Abs. 1 lit. b DSGVO). Kommt kein Arbeitsverhältnis zustande, löschen wir Ihre Unterlagen spätestens sechs Monate nach Abschluss des Verfahrens.</p>
<h2>7. Ihre Rechte</h2>
<p>Sie haben das Recht auf Auskunft, Berichtigung, Löschung, Einschränkung der Verarbeitung, Datenübertragbarkeit sowie Widerspruch gegen Verarbeitungen auf Grundlage von Art. 6 Abs. 1 lit. f DSGVO. Wenden Sie sich dazu einfach an die oben genannten Kontaktdaten.</p>
<p>Sie können sich außerdem bei einer Datenschutz-Aufsichtsbehörde beschweren, für uns zuständig ist das Bayerische Landesamt für Datenschutzaufsicht (BayLDA), Promenade 18, 91522 Ansbach.</p>
<p class="mt-4"><em>Stand: September 2026. Entwurf — vor Veröffentlichung juristisch prüfen lassen.</em></p>
</div>
</section>'''
page("datenschutz.html", "Datenschutz — Kirschner Gebäudeservice", "Datenschutzerklärung von Kirschner Gebäudeservice: keine Cookies, kein Tracking.", DATENSCHUTZ)