Unterseiten: Fußball-Übersicht, Mannschaften, Jugend, Verein, Rechtliches
Neue Seiten + Routen: fussball, fussball/{erste-mannschaft,zweite-mannschaft,
damen}, jugendfussball, verein, impressum, datenschutz. Dazu die Komponenten
team, team-cards/-roster/-photo/-bento, faq/faq-contact, key-facts, age-groups,
cta-band, training-location und der contact-slider.
Daten in teams/fussball/jugend/verein.json; club.json um Vorstand, Bankdaten,
Datenschutz-Kontakt erweitert. person-grid bekommt $names-Filter, partner-grid
einen Intro-Stil. Seiten setzen $meta['schema'] via team_/page_schema.
Styles für alle neuen Komponenten in components.css.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KDQZ6FuchNUh2bxSW9PeNv
47
app/components/age-groups.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Jugend-Altersklassen als Karten-Grid. Props:
|
||||
* $groups array [{code, name, photo?{base,widths,alt}, training?, note?}, …]
|
||||
* $title string (optional, Default 'Unsere Jugendmannschaften')
|
||||
* $anchor string (optional, Default 'mannschaften')
|
||||
* Fehlt ein Foto, greift ein Platzhalter; fehlt die Trainingszeit, entfällt die Zeile.
|
||||
*/
|
||||
$title = $title ?? 'Unsere Jugendmannschaften';
|
||||
$anchor = $anchor ?? 'mannschaften';
|
||||
$groups = $groups ?? [];
|
||||
if ($groups === []) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<section class="section age-groups" id="<?= e($anchor) ?>">
|
||||
<div class="container">
|
||||
<h2><?= e($title) ?></h2>
|
||||
<ul class="age-groups__grid">
|
||||
<?php foreach ($groups as $g): ?>
|
||||
<li class="age-group">
|
||||
<div class="age-group__media">
|
||||
<?php if (!empty($g['photo']['base'])): ?>
|
||||
<?php component('img', ['image' => $g['photo'], 'sizes' => '(max-width: 600px) 100vw, 320px', 'class' => 'age-group__img']); ?>
|
||||
<?php else: ?>
|
||||
<img class="age-group__img" src="<?= e(asset('img/persons/platzhalter.jpg')) ?>" alt="" width="400" height="300" loading="lazy" decoding="async">
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($g['code'])): ?>
|
||||
<span class="age-group__badge" aria-hidden="true"><?= e($g['code']) ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="age-group__body">
|
||||
<h3 class="age-group__name"><?= e($g['name']) ?></h3>
|
||||
<?php if (!empty($g['training'])): ?>
|
||||
<p class="age-group__training"><?= icon('clock', 'age-group__icon') ?><span><?= e($g['training']) ?></span></p>
|
||||
<?php elseif (!empty($g['note'])): ?>
|
||||
<p class="age-group__training text-muted"><?= e($g['note']) ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
30
app/components/cta-band.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Vollbreites Call-to-Action-Band. Props:
|
||||
* $band array{title, text?, ctas: [{label, slug, style?}]}
|
||||
* $anchor string (optional)
|
||||
* Rendert nichts ohne CTAs. Nutzt das bestehende cta-/btn-Muster.
|
||||
*/
|
||||
$anchor = $anchor ?? null;
|
||||
if (empty($band['ctas'])) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<section class="section section--tinted cta-band"<?= $anchor ? ' id="' . e($anchor) . '"' : '' ?>>
|
||||
<div class="container cta-band__inner">
|
||||
<?php if (!empty($band['title'])): ?>
|
||||
<h2 class="cta-band__title"><?= e($band['title']) ?></h2>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($band['text'])): ?>
|
||||
<p class="cta-band__text"><?= e($band['text']) ?></p>
|
||||
<?php endif; ?>
|
||||
<div class="cta-band__actions">
|
||||
<?php foreach ($band['ctas'] as $cta): ?>
|
||||
<?php component('cta', ['cta' => $cta]); ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
76
app/components/faq-contact.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Kombi-Sektion: FAQ (2/3) + einzelner Ansprechpartner (1/3) nebeneinander.
|
||||
* Sinnvoll, wenn es pro Mannschaft nur einen Kontakt gibt. Props:
|
||||
* $faq array [{q, a}]
|
||||
* $persons array — ein oder mehrere Ansprechpartner (person-card). Leer → FAQ volle Breite.
|
||||
* Bei mehreren: Mini-Slider (ohne JS alle Karten gestapelt sichtbar).
|
||||
* $faqTitle string (optional, Default 'Häufige Fragen')
|
||||
* $faqIntro string (optional) — gedämpfte Subline unter der Überschrift
|
||||
* $contactTitle string (optional, Default 'Dein Ansprechpartner')
|
||||
* $anchor string (optional, Default 'kontakt')
|
||||
* Natives <details>/<summary> (ohne JS bedienbar); FAQPage-Schema liefert die Seite.
|
||||
*/
|
||||
$faqTitle = $faqTitle ?? 'Häufige Fragen';
|
||||
$faqIntro = $faqIntro ?? '';
|
||||
$contactTitle = $contactTitle ?? 'Dein Ansprechpartner';
|
||||
$anchor = $anchor ?? 'kontakt';
|
||||
$faq = array_values(array_filter(
|
||||
$faq ?? [],
|
||||
static fn (array $i): bool => !empty($i['q']) && !empty($i['a'])
|
||||
));
|
||||
$persons = array_values(array_filter($persons ?? [], static fn ($p): bool => !empty($p)));
|
||||
$hasPerson = $persons !== [];
|
||||
$isSlider = count($persons) > 1;
|
||||
if ($faq === [] && !$hasPerson) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<section class="section faq-contact<?= $hasPerson ? '' : ' faq-contact--solo' ?>" id="<?= e($anchor) ?>">
|
||||
<div class="container">
|
||||
<h2 class="faq-contact__title"><?= e($faqTitle) ?></h2>
|
||||
<?php if ($faqIntro !== ''): ?>
|
||||
<p class="faq-contact__intro text-muted"><?= e($faqIntro) ?></p>
|
||||
<?php endif; ?>
|
||||
<div class="faq-contact__inner">
|
||||
<div class="faq-contact__main">
|
||||
<div class="faq__list">
|
||||
<?php foreach ($faq as $item): ?>
|
||||
<details class="faq__item">
|
||||
<summary class="faq__q"><span><?= e($item['q']) ?></span><?= icon('chevron-down', 'faq__chevron') ?></summary>
|
||||
<div class="faq__a"><p><?= e($item['a']) ?></p></div>
|
||||
</details>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php if ($hasPerson): ?>
|
||||
<aside class="faq-contact__aside">
|
||||
<?php if ($contactTitle !== ''): ?>
|
||||
<h2><?= e($contactTitle) ?></h2>
|
||||
<?php endif; ?>
|
||||
<?php if ($isSlider): ?>
|
||||
<div class="contact-slider" data-contact-slider>
|
||||
<ul class="contact-slider__track">
|
||||
<?php foreach ($persons as $p): ?>
|
||||
<li class="contact-slider__slide" data-contact-slide>
|
||||
<?php component('person-card', ['person' => $p]); ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<div class="contact-slider__dots">
|
||||
<?php foreach ($persons as $i => $p): ?>
|
||||
<button type="button" class="contact-slider__dot" data-contact-dot<?= $i === 0 ? ' aria-current="true"' : '' ?> aria-label="Ansprechpartner <?= $i + 1 ?>: <?= e($p['name']) ?>"></button>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php component('person-card', ['person' => $persons[0]]); ?>
|
||||
<?php endif; ?>
|
||||
</aside>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
35
app/components/faq.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* FAQ-Accordion. Props:
|
||||
* $faq array [{q, a}, …]
|
||||
* $title string (optional, Default 'Häufige Fragen')
|
||||
* $anchor string (optional, Default 'faq')
|
||||
* Natives <details>/<summary> → ohne JS bedienbar. Das FAQPage-Schema liefert
|
||||
* die Seite über $meta['schema'] (helpers: faq_schema/team_schema), nicht diese Komponente.
|
||||
*/
|
||||
$title = $title ?? 'Häufige Fragen';
|
||||
$anchor = $anchor ?? 'faq';
|
||||
$faq = array_values(array_filter(
|
||||
$faq ?? [],
|
||||
static fn (array $i): bool => !empty($i['q']) && !empty($i['a'])
|
||||
));
|
||||
if ($faq === []) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<section class="section faq" id="<?= e($anchor) ?>">
|
||||
<div class="container faq__inner">
|
||||
<h2><?= e($title) ?></h2>
|
||||
<div class="faq__list">
|
||||
<?php foreach ($faq as $item): ?>
|
||||
<details class="faq__item">
|
||||
<summary class="faq__q"><span><?= e($item['q']) ?></span><?= icon('chevron-down', 'faq__chevron') ?></summary>
|
||||
<div class="faq__a"><p><?= e($item['a']) ?></p></div>
|
||||
</details>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
41
app/components/key-facts.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Kompakte Faktenleiste (Icon + Label + Wert). Props:
|
||||
* $facts array [{icon, label, value}, …] — Einträge mit leerem 'value' werden übersprungen.
|
||||
* $anchor string (optional) — Sektions-ID.
|
||||
* $tinted bool (optional, Default false) — aufgehelltes Band.
|
||||
* GEO/SEO: kompakte, zitierfähige Fakten. Rendert nichts, wenn keine Werte vorliegen.
|
||||
*/
|
||||
$anchor = $anchor ?? null;
|
||||
$tinted = $tinted ?? false;
|
||||
|
||||
$facts = array_values(array_filter(
|
||||
$facts ?? [],
|
||||
static fn (array $f): bool => isset($f['value']) && $f['value'] !== ''
|
||||
));
|
||||
if ($facts === []) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<section class="section key-facts<?= $tinted ? ' section--tinted' : '' ?>"<?= $anchor ? ' id="' . e($anchor) . '"' : '' ?>>
|
||||
<div class="container">
|
||||
<ul class="key-facts__grid">
|
||||
<?php foreach ($facts as $fact): ?>
|
||||
<li class="key-facts__item">
|
||||
<?php if (!empty($fact['icon'])): ?>
|
||||
<span class="key-facts__icon"><?= icon($fact['icon']) ?></span>
|
||||
<?php endif; ?>
|
||||
<span class="key-facts__text">
|
||||
<?php if (!empty($fact['label'])): ?>
|
||||
<span class="key-facts__label"><?= e($fact['label']) ?></span>
|
||||
<?php endif; ?>
|
||||
<span class="key-facts__value"><?= e($fact['value']) ?></span>
|
||||
</span>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
@@ -12,7 +12,7 @@ $partners = json_load('partners')['partners'] ?? [];
|
||||
<section class="section partners" id="partner">
|
||||
<div class="container">
|
||||
<h2><?= e($intro['title']) ?></h2>
|
||||
<p><?= e($intro['text']) ?></p>
|
||||
<p class="partners__intro text-muted"><?= e($intro['text']) ?></p>
|
||||
<ul class="partners__grid">
|
||||
<?php foreach ($partners as $partner): ?>
|
||||
<li class="partners__item">
|
||||
|
||||
@@ -6,6 +6,7 @@ declare(strict_types=1);
|
||||
* Ansprechpartner-Grid aus data/ansprechpartner.json (Single Source of Truth).
|
||||
* Alle Props optional:
|
||||
* $departments array — nur diese Abteilungen zeigen (z. B. ['Herrenfußball','Damenfußball']).
|
||||
* $names array — zusätzlich auf diese Namen einschränken (z. B. ['Benedikt Höfner']).
|
||||
* $title string — Überschrift überschreiben (Default aus JSON).
|
||||
* $intro string — Intro-Text überschreiben (Default aus JSON; '' = ausblenden).
|
||||
* $anchor string — Sektions-ID (Default 'ansprechpartner').
|
||||
@@ -24,6 +25,12 @@ if (!empty($departments)) {
|
||||
static fn (array $p): bool => in_array($p['department'] ?? '', $departments, true)
|
||||
));
|
||||
}
|
||||
if (!empty($names)) {
|
||||
$persons = array_values(array_filter(
|
||||
$persons,
|
||||
static fn (array $p): bool => in_array($p['name'] ?? '', $names, true)
|
||||
));
|
||||
}
|
||||
|
||||
if ($persons === []) {
|
||||
return;
|
||||
|
||||
82
app/components/team-bento.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Team-Dashboard als Bento-Grid: kombiniert Bild, Einleitung, Trainingszeiten,
|
||||
* Spielklasse und Spielort in einem Kachel-Layout. Props:
|
||||
* $team array — Eintrag aus data/teams.json.
|
||||
* $anchor string (optional, Default 'team-info')
|
||||
* Spielort kommt aus data/club.json (NAP-Single-Source). Deko-Icons sind dekorativ.
|
||||
*/
|
||||
$anchor = $anchor ?? 'team-info';
|
||||
$about = $team['about'] ?? [];
|
||||
$sessions = $team['training']['sessions'] ?? [];
|
||||
$venueOverride = $team['training']['venue'] ?? null; // mannschaftsspezifischer Spielort (z. B. SG-Heimat)
|
||||
$showVenue = !empty($team['training']['venue_from_club']) || !empty($venueOverride);
|
||||
$highlight = !empty($team['league'])
|
||||
? ['label' => 'Spielklasse', 'value' => $team['league']]
|
||||
: ['label' => 'Bereich', 'value' => $team['category'] ?? ''];
|
||||
|
||||
$club = json_load('club');
|
||||
$addr = $club['address'] ?? [];
|
||||
$mapsQuery = !empty($venueOverride['maps_query'])
|
||||
? rawurlencode($venueOverride['maps_query'])
|
||||
: rawurlencode(trim(($addr['venue'] ?? '') . ', ' . ($addr['street'] ?? '') . ', ' . ($addr['zip'] ?? '') . ' ' . ($addr['city'] ?? ''), ', '));
|
||||
?>
|
||||
<section class="section team-bento-section" id="<?= e($anchor) ?>">
|
||||
<div class="container">
|
||||
<div class="team-bento">
|
||||
<article class="bento__tile team-bento__about">
|
||||
<h2><?= e($about['title'] ?? 'Über die Mannschaft') ?></h2>
|
||||
<p><?= e($about['text'] ?? '') ?></p>
|
||||
</article>
|
||||
|
||||
<article class="bento__tile team-bento__training">
|
||||
<h2 class="bento__heading"><?= icon('clock', 'bento__hicon') ?>Trainingszeiten</h2>
|
||||
<?php if ($sessions !== []): ?>
|
||||
<ul class="bento__sessions">
|
||||
<?php foreach ($sessions as $s): ?>
|
||||
<li>
|
||||
<span class="bento__day"><?= e($s['day']) ?></span>
|
||||
<span class="bento__time"><?= e($s['time']) ?></span>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php else: ?>
|
||||
<p class="text-muted">Die genauen Trainingszeiten erfährst du direkt bei unserem Ansprechpartner.</p>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($team['training']['note'])): ?>
|
||||
<p class="bento__note text-muted"><?= e($team['training']['note']) ?></p>
|
||||
<?php endif; ?>
|
||||
<span class="bento__deco" aria-hidden="true"><?= icon('clock') ?></span>
|
||||
</article>
|
||||
|
||||
<?php if (($highlight['value'] ?? '') !== ''): ?>
|
||||
<article class="bento__tile bento__tile--accent team-bento__klasse">
|
||||
<span class="bento__stat-label"><?= e($highlight['label']) ?></span>
|
||||
<span class="bento__stat-value"><?= e($highlight['value']) ?></span>
|
||||
</article>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($showVenue && ($venueOverride || $addr !== [])): ?>
|
||||
<article class="bento__tile team-bento__venue">
|
||||
<div class="team-bento__venue-main">
|
||||
<h2 class="bento__heading"><?= icon('geo-alt', 'bento__hicon') ?>Spielort</h2>
|
||||
<?php if ($venueOverride): ?>
|
||||
<p class="bento__address">
|
||||
<?php if (!empty($venueOverride['name'])): ?><strong><?= e($venueOverride['name']) ?></strong><?php endif; ?><?php if (!empty($venueOverride['locality'])): ?> · <?= e($venueOverride['locality']) ?><?php endif; ?>
|
||||
</p>
|
||||
<?php else: ?>
|
||||
<p class="bento__address">
|
||||
<?php if (!empty($addr['venue'])): ?><strong><?= e($addr['venue']) ?></strong> · <?php endif; ?><?= e($addr['street']) ?>, <?= e($addr['zip']) ?> <?= e($addr['city']) ?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<a class="btn btn--outline team-bento__route" href="https://www.google.com/maps/search/?api=1&query=<?= e($mapsQuery) ?>" target="_blank" rel="noopener">Route planen<span class="visually-hidden"> (öffnet Google Maps in neuem Tab)</span></a>
|
||||
<span class="bento__deco" aria-hidden="true"><?= icon('geo-alt') ?></span>
|
||||
</article>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
55
app/components/team-cards.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Mannschafts-Karten, die auf die Team-Unterseiten verlinken. Props:
|
||||
* $teams array [{name, slug, category, league?, image?{base,widths,alt}}, …]
|
||||
* $title string (optional, Default 'Unsere Mannschaften')
|
||||
* $title_hidden bool (optional) — H2 für SEO/A11y im DOM, aber visuell versteckt
|
||||
* $intro string (optional)
|
||||
* $intro_below bool (optional) — Intro als „Klärtext" UNTER die Kacheln statt darüber
|
||||
* $anchor string (optional, Default 'mannschaften')
|
||||
* Ganze Karte ist ein Link. Ohne Bild greift ein Platzhalter.
|
||||
*/
|
||||
$title = $title ?? 'Unsere Mannschaften';
|
||||
$title_hidden = $title_hidden ?? false;
|
||||
$anchor = $anchor ?? 'mannschaften';
|
||||
$intro = $intro ?? '';
|
||||
$intro_below = $intro_below ?? false;
|
||||
$teams = $teams ?? [];
|
||||
if ($teams === []) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<section class="section team-cards" id="<?= e($anchor) ?>">
|
||||
<div class="container">
|
||||
<h2<?= $title_hidden ? ' class="visually-hidden"' : '' ?>><?= e($title) ?></h2>
|
||||
<?php if ($intro !== '' && !$intro_below): ?>
|
||||
<p class="team-cards__intro text-muted"><?= e($intro) ?></p>
|
||||
<?php endif; ?>
|
||||
<ul class="team-cards__grid">
|
||||
<?php foreach ($teams as $t): ?>
|
||||
<li>
|
||||
<a class="team-card" href="<?= e(url($t['slug'])) ?>">
|
||||
<div class="team-card__media">
|
||||
<?php if (!empty($t['image']['base'])): ?>
|
||||
<?php component('img', ['image' => $t['image'], 'sizes' => '(max-width: 600px) 100vw, 360px', 'class' => 'team-card__img']); ?>
|
||||
<?php else: ?>
|
||||
<img class="team-card__img" src="<?= e(asset('img/persons/platzhalter.jpg')) ?>" alt="" width="400" height="300" loading="lazy" decoding="async">
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="team-card__body">
|
||||
<h3 class="team-card__name"><?= e($t['name']) ?></h3>
|
||||
<p class="team-card__meta"><?= e(trim($t['category'] . (!empty($t['league']) ? ' · ' . $t['league'] : ''))) ?></p>
|
||||
<span class="team-card__cta">Mehr erfahren <?= icon('arrow-right', 'team-card__arrow') ?></span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php if ($intro !== '' && $intro_below): ?>
|
||||
<p class="team-cards__intro team-cards__intro--below text-muted"><?= e($intro) ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
27
app/components/team-photo.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Mannschaftsfoto in voller Breite (Querformat-Gruppenbild). Props:
|
||||
* $photo array{ image: {base,widths,alt} | {src,width,height,alt}, caption? }
|
||||
* $anchor string (optional, Default 'mannschaftsfoto')
|
||||
* Zeigt das Bild im natürlichen Seitenverhältnis, abgerundet; optionale Bildunterschrift.
|
||||
*/
|
||||
$anchor = $anchor ?? 'mannschaftsfoto';
|
||||
$image = $photo['image'] ?? null;
|
||||
if (empty($image)) {
|
||||
return;
|
||||
}
|
||||
$caption = $photo['caption'] ?? '';
|
||||
?>
|
||||
<section class="section team-photo" id="<?= e($anchor) ?>">
|
||||
<div class="container">
|
||||
<figure class="team-photo__figure">
|
||||
<?php component('img', ['image' => $image, 'sizes' => '(max-width: 1100px) 100vw, 1040px', 'class' => 'team-photo__img']); ?>
|
||||
<?php if ($caption !== ''): ?>
|
||||
<figcaption class="team-photo__caption"><?= e($caption) ?></figcaption>
|
||||
<?php endif; ?>
|
||||
</figure>
|
||||
</div>
|
||||
</section>
|
||||
78
app/components/team-roster.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Kader-/Trainerstab-Raster einer Mannschaft. Props:
|
||||
* $roster array{
|
||||
* title?, intro?,
|
||||
* variant? : 'below' (Name unter dem Foto, Default) | 'overlay' (Name im Foto, Coolvetica),
|
||||
* groups? : [{ title?, members:[…] }, …] ODER
|
||||
* members? : [{ name, role?, image? }, …] (eine Gruppe ohne Überschrift)
|
||||
* }
|
||||
* $anchor string (optional, Default 'kader')
|
||||
* Gruppen mit Titel bekommen eine Zwischenüberschrift (h3); leere Gruppen entfallen.
|
||||
* Ohne Foto greift der Platzhalter-Avatar.
|
||||
*/
|
||||
$anchor = $anchor ?? 'kader';
|
||||
|
||||
// Gruppen normalisieren: entweder explizite groups oder flache members als eine Gruppe.
|
||||
$groups = $roster['groups'] ?? null;
|
||||
if ($groups === null) {
|
||||
$flat = $roster['members'] ?? [];
|
||||
$groups = $flat === [] ? [] : [['title' => '', 'members' => $flat]];
|
||||
}
|
||||
// Leere Gruppen rauswerfen.
|
||||
$groups = array_values(array_filter($groups, static fn ($g): bool => !empty($g['members'])));
|
||||
if ($groups === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$variant = ($roster['variant'] ?? 'below') === 'overlay' ? 'overlay' : 'below';
|
||||
$title = $roster['title'] ?? 'Kader';
|
||||
$intro = $roster['intro'] ?? '';
|
||||
$cardMod = $variant === 'overlay' ? ' roster-card--overlay' : '';
|
||||
?>
|
||||
<section class="section team-roster" id="<?= e($anchor) ?>">
|
||||
<div class="container">
|
||||
<h2><?= e($title) ?></h2>
|
||||
<?php if ($intro !== ''): ?>
|
||||
<p class="team-roster__intro text-muted"><?= e($intro) ?></p>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($groups as $group): ?>
|
||||
<?php if (!empty($group['title'])): ?>
|
||||
<h3 class="team-roster__group"><?= e($group['title']) ?></h3>
|
||||
<?php endif; ?>
|
||||
<ul class="team-roster__grid">
|
||||
<?php foreach ($group['members'] as $m): ?>
|
||||
<?php $src = !empty($m['image']) ? $m['image'] : 'img/persons/platzhalter-spieler.jpg'; ?>
|
||||
<?php $alt = !empty($m['image']) ? e($m['name']) : ''; ?>
|
||||
<?php $staffMod = !empty($m['role']) ? ' roster-card--staff' : ''; ?>
|
||||
<li class="roster-card<?= $cardMod . $staffMod ?>">
|
||||
<?php if ($variant === 'overlay'): ?>
|
||||
<img class="roster-card__img" src="<?= e(asset($src)) ?>" alt="<?= $alt ?>"
|
||||
width="480" height="600" loading="lazy" decoding="async">
|
||||
<div class="roster-card__caption">
|
||||
<?php if (!empty($m['role'])): ?>
|
||||
<span class="roster-card__role"><?= e($m['role']) ?></span>
|
||||
<?php endif; ?>
|
||||
<p class="roster-card__name"><?= e($m['name']) ?></p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="roster-card__media">
|
||||
<img src="<?= e(asset($src)) ?>" alt="<?= $alt ?>"
|
||||
width="480" height="600" loading="lazy" decoding="async">
|
||||
</div>
|
||||
<div class="roster-card__body">
|
||||
<?php if (!empty($m['role'])): ?>
|
||||
<span class="roster-card__role"><?= e($m['role']) ?></span>
|
||||
<?php endif; ?>
|
||||
<p class="roster-card__name"><?= e($m['name']) ?></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
46
app/components/team.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Mannschafts-Seite (DRY-Template für 1./2. Herren + Damen). Props:
|
||||
* $team array — Eintrag aus data/teams.json.
|
||||
* $slug string — voller Seiten-Slug (für Anker-Links).
|
||||
* Aufbau: Hero → Team-Bento (Bild, Einleitung, Training, Spielort) → FAQ + Ansprechpartner.
|
||||
* Leere Datenblöcke (Training, FAQ, fehlender Kontakt) blenden sich selbst aus.
|
||||
*/
|
||||
$slug = $slug ?? '';
|
||||
|
||||
// Ansprechpartner aus der Single-Source auflösen (ein oder mehrere, Reihenfolge wie angegeben).
|
||||
$allPersons = json_load('ansprechpartner')['persons'] ?? [];
|
||||
$wantDept = $team['contact_department'] ?? '';
|
||||
$wantNames = $team['contact_names'] ?? (isset($team['contact_name']) ? [$team['contact_name']] : []);
|
||||
$contacts = [];
|
||||
foreach ($wantNames as $wn) {
|
||||
foreach ($allPersons as $p) {
|
||||
if (($p['department'] ?? '') === $wantDept && ($p['name'] ?? '') === $wn) {
|
||||
$contacts[] = $p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component('hero', ['hero' => $team['hero']]);
|
||||
|
||||
if (!empty($team['team_photo']['image'])) {
|
||||
component('team-photo', ['photo' => $team['team_photo'], 'anchor' => 'mannschaftsfoto']);
|
||||
}
|
||||
|
||||
component('team-bento', ['team' => $team, 'anchor' => 'team-info']);
|
||||
|
||||
if (!empty($team['roster']['members']) || !empty($team['roster']['groups'])) {
|
||||
component('team-roster', ['roster' => $team['roster'], 'anchor' => 'kader']);
|
||||
}
|
||||
|
||||
component('faq-contact', [
|
||||
'faq' => $team['faq'] ?? [],
|
||||
'faqIntro' => 'Die wichtigsten Fragen rund um die ' . ($team['name'] ?? 'Mannschaft') . ' – kurz beantwortet.',
|
||||
'persons' => $contacts,
|
||||
'contactTitle' => '',
|
||||
'anchor' => 'kontakt',
|
||||
]);
|
||||
73
app/components/training-location.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Trainings-/Spielort-„Dashboard" als Bento-Grid. Props:
|
||||
* $training array{sessions: [{day, time}], venue_from_club?: bool, note?: string}
|
||||
* $highlight array{label, value} (optional) — kleiner Akzent-Tile (z. B. Spielklasse).
|
||||
* $anchor string (optional, Default 'training')
|
||||
* Spielort kommt aus data/club.json (NAP-Single-Source). Ohne Daten: nichts.
|
||||
*/
|
||||
$anchor = $anchor ?? 'training';
|
||||
$sessions = $training['sessions'] ?? [];
|
||||
$showVenue = !empty($training['venue_from_club']);
|
||||
$highlight = $highlight ?? null;
|
||||
|
||||
if ($sessions === [] && !$showVenue) {
|
||||
return;
|
||||
}
|
||||
|
||||
$club = json_load('club');
|
||||
$addr = $club['address'] ?? [];
|
||||
$mapsQuery = rawurlencode(trim(($addr['venue'] ?? '') . ', ' . ($addr['street'] ?? '') . ', ' . ($addr['zip'] ?? '') . ' ' . ($addr['city'] ?? ''), ', '));
|
||||
?>
|
||||
<section class="section training" id="<?= e($anchor) ?>">
|
||||
<div class="container">
|
||||
<div class="bento">
|
||||
<article class="bento__tile bento__tile--times">
|
||||
<h2 class="bento__heading"><?= icon('clock', 'bento__hicon') ?>Trainingszeiten</h2>
|
||||
<?php if ($sessions !== []): ?>
|
||||
<ul class="bento__sessions">
|
||||
<?php foreach ($sessions as $s): ?>
|
||||
<li>
|
||||
<span class="bento__day"><?= e($s['day']) ?></span>
|
||||
<span class="bento__time"><?= e($s['time']) ?></span>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php else: ?>
|
||||
<p class="text-muted">Die genauen Trainingszeiten erfährst du direkt bei unserem Ansprechpartner.</p>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($training['note'])): ?>
|
||||
<p class="bento__note text-muted"><?= e($training['note']) ?></p>
|
||||
<?php endif; ?>
|
||||
<span class="bento__deco" aria-hidden="true"><?= icon('clock') ?></span>
|
||||
</article>
|
||||
|
||||
<?php if ($showVenue && $addr !== []): ?>
|
||||
<article class="bento__tile bento__tile--venue">
|
||||
<h2 class="bento__heading"><?= icon('geo-alt', 'bento__hicon') ?>Spielort</h2>
|
||||
<p class="bento__address">
|
||||
<?php if (!empty($addr['venue'])): ?><strong><?= e($addr['venue']) ?></strong><br><?php endif; ?>
|
||||
<?= e($addr['street']) ?><br>
|
||||
<?= e($addr['zip']) ?> <?= e($addr['city']) ?>
|
||||
</p>
|
||||
<span class="bento__deco" aria-hidden="true"><?= icon('geo-alt') ?></span>
|
||||
</article>
|
||||
|
||||
<article class="bento__tile bento__tile--route">
|
||||
<span class="bento__route-label">Anfahrt</span>
|
||||
<a class="btn btn--outline" href="https://www.google.com/maps/search/?api=1&query=<?= e($mapsQuery) ?>" target="_blank" rel="noopener">Route planen<span class="visually-hidden"> (öffnet Google Maps in neuem Tab)</span></a>
|
||||
</article>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($highlight !== null && ($highlight['value'] ?? '') !== ''): ?>
|
||||
<article class="bento__tile bento__tile--accent">
|
||||
<span class="bento__stat-label"><?= e($highlight['label'] ?? '') ?></span>
|
||||
<span class="bento__stat-value"><?= e($highlight['value']) ?></span>
|
||||
</article>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
261
app/pages/datenschutz.php
Normal file
@@ -0,0 +1,261 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Datenschutzerklärung — zugeschnitten auf den tatsächlichen Stack:
|
||||
* komplett self-hosted, keine Tracker/Analyse/Werbe-Dienste, keine externen
|
||||
* Fonts/Maps/Embeds. Einzige Verarbeitungen: Server-Logs (Hoster),
|
||||
* Kontaktformular (Versand via Brevo SMTP), server-seitig gecachter
|
||||
* Instagram-Feed (Bilder von unserer Domain). Vereinsdaten aus club.json.
|
||||
*/
|
||||
$club = json_load('club');
|
||||
$addr = $club['address'] ?? [];
|
||||
$dpo = $club['data_protection_contact'] ?? '';
|
||||
|
||||
$meta = [
|
||||
'title' => 'Datenschutzerklärung',
|
||||
'description' => 'Datenschutzerklärung des TSV 1908 Kulmbach e. V. – welche Daten beim Besuch unserer Website verarbeitet werden. Komplett self-hosted, ohne Tracking und ohne Drittanbieter-Dienste im Browser.',
|
||||
'schema' => page_schema([
|
||||
['name' => 'Startseite', 'slug' => ''],
|
||||
['name' => 'Datenschutz', 'slug' => 'datenschutz'],
|
||||
]),
|
||||
];
|
||||
?>
|
||||
<section class="section legal">
|
||||
<div class="container">
|
||||
<div class="legal__head">
|
||||
<p class="legal__kicker">Rechtliches</p>
|
||||
<h1>Datenschutzerklärung</h1>
|
||||
<p class="legal__lead">
|
||||
Der Schutz deiner persönlichen Daten ist uns wichtig. Diese Website ist
|
||||
bewusst datensparsam gebaut: Sie lädt keine externen Schriften, Karten,
|
||||
Skripte oder Tracking-Dienste – dein Browser kontaktiert ausschließlich
|
||||
unseren eigenen Server.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="legal__body">
|
||||
<section>
|
||||
<h2>Verantwortlicher</h2>
|
||||
<p>Verantwortlich für die Datenverarbeitung auf dieser Website ist:</p>
|
||||
<div class="legal__card">
|
||||
<address>
|
||||
<strong><?= e($club['legal_name']) ?></strong><br>
|
||||
<?= e($addr['street']) ?><br>
|
||||
<?= e($addr['zip']) ?> <?= e($addr['city']) ?><br>
|
||||
<br>
|
||||
E-Mail: <a href="mailto:<?= e($club['email']) ?>"><?= e($club['email']) ?></a>
|
||||
</address>
|
||||
</div>
|
||||
<?php if ($dpo !== ''): ?>
|
||||
<p class="legal__muted">
|
||||
Ansprechpartner für Fragen zum Datenschutz: <?= e($dpo) ?>,
|
||||
erreichbar unter <a href="mailto:<?= e($club['email']) ?>"><?= e($club['email']) ?></a>.
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Grundsätzliches</h2>
|
||||
<p>
|
||||
Wir verarbeiten personenbezogene Daten nur im Rahmen der geltenden
|
||||
Datenschutzgesetze, insbesondere der Datenschutz-Grundverordnung (DSGVO)
|
||||
und des Bundesdatenschutzgesetzes (BDSG). „Personenbezogene Daten“ sind
|
||||
alle Informationen, die sich auf eine identifizierte oder identifizierbare
|
||||
Person beziehen. Eine Nutzung unserer Website ist grundsätzlich ohne Angabe
|
||||
personenbezogener Daten möglich.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Aufruf der Website & Server-Logfiles</h2>
|
||||
<p>
|
||||
Unsere Website wird bei folgendem Dienstleister gehostet:
|
||||
</p>
|
||||
<div class="legal__card">
|
||||
<address>
|
||||
<strong>ALL-INKL.COM – Neue Medien Münnich</strong><br>
|
||||
Inhaber: René Münnich<br>
|
||||
Hauptstraße 68<br>
|
||||
02742 Friedersdorf
|
||||
</address>
|
||||
</div>
|
||||
<p>
|
||||
Bei jedem Aufruf unserer Website werden durch unseren Webhoster automatisch
|
||||
Informationen erfasst, die dein Browser übermittelt und die technisch
|
||||
erforderlich sind, um die Seite auszuliefern. Erfasst werden:
|
||||
</p>
|
||||
<ul>
|
||||
<li>die aufgerufene Adresse (URL) und die zuvor besuchte Seite (Referrer)</li>
|
||||
<li>Datum und Uhrzeit des Zugriffs</li>
|
||||
<li>verwendeter Browser und Betriebssystem</li>
|
||||
<li>die IP-Adresse des anfragenden Geräts</li>
|
||||
</ul>
|
||||
<p>
|
||||
Diese Daten dienen ausschließlich dem technischen Betrieb, der Sicherheit
|
||||
und der Stabilität der Website und werden nicht mit anderen Datenquellen
|
||||
zusammengeführt. Rechtsgrundlage ist unser berechtigtes Interesse an einer
|
||||
sicheren und funktionsfähigen Website (Art. 6 Abs. 1 lit. f DSGVO). Die
|
||||
Logdaten werden nach kurzer Zeit automatisch gelöscht.
|
||||
</p>
|
||||
<p class="legal__muted">
|
||||
Unser Webhoster verarbeitet diese Daten in unserem Auftrag auf Grundlage
|
||||
eines Vertrags zur Auftragsverarbeitung (Art. 28 DSGVO).
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Verschlüsselung (SSL/TLS)</h2>
|
||||
<p>
|
||||
Diese Website nutzt aus Sicherheitsgründen eine SSL- bzw. TLS-Verschlüsselung.
|
||||
Eine verschlüsselte Verbindung erkennst du am „https://“ in der Adresszeile
|
||||
deines Browsers. Daten, die du an uns übermittelst, können dadurch nicht ohne
|
||||
Weiteres von Dritten mitgelesen werden.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Cookies</h2>
|
||||
<p>
|
||||
Diese Website setzt <strong>keine Cookies zu Analyse-, Marketing- oder
|
||||
Tracking-Zwecken</strong> und bindet keine Dienste ein, die solche Cookies
|
||||
setzen würden. Aus diesem Grund benötigen wir auch kein Cookie-Banner.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Kontaktformular & Kontaktaufnahme</h2>
|
||||
<p>
|
||||
Wenn du uns über das Kontaktformular oder per E-Mail kontaktierst, verarbeiten
|
||||
wir die von dir angegebenen Daten, um deine Anfrage zu bearbeiten. Über das
|
||||
Formular sind das:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Name (Pflichtangabe)</li>
|
||||
<li>E-Mail-Adresse (Pflichtangabe)</li>
|
||||
<li>Telefonnummer (freiwillig)</li>
|
||||
<li>dein Interessensgebiet, Betreff und deine Nachricht</li>
|
||||
</ul>
|
||||
<p>
|
||||
Rechtsgrundlage ist deine Einwilligung (Art. 6 Abs. 1 lit. a DSGVO), die du
|
||||
mit dem Absenden erteilst, sowie unser berechtigtes Interesse bzw. die
|
||||
Anbahnung eines (Mitgliedschafts-)Verhältnisses an der Beantwortung deiner
|
||||
Anfrage (Art. 6 Abs. 1 lit. f und b DSGVO). Die Daten werden gespeichert, bis
|
||||
deine Anfrage abschließend bearbeitet ist und keine gesetzlichen
|
||||
Aufbewahrungspflichten entgegenstehen.
|
||||
</p>
|
||||
<p>
|
||||
Für den technischen Versand der Formular-Nachrichten als E-Mail nutzen wir den
|
||||
Dienstleister Brevo (Sendinblue GmbH, Köln) als Auftragsverarbeiter. Dabei
|
||||
werden die im Formular angegebenen Daten an die Mailserver von Brevo
|
||||
übermittelt. Die Übertragung erfolgt verschlüsselt.
|
||||
</p>
|
||||
<p class="legal__muted">
|
||||
Hinweis: Zum Schutz vor automatisiertem Spam verwenden wir ein verstecktes
|
||||
Pflichtfeld sowie einen signierten Zeitstempel. Dabei werden keine
|
||||
zusätzlichen personenbezogenen Daten erhoben und keine externen Dienste
|
||||
(z. B. reCAPTCHA) eingebunden.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Keine Analyse-, Tracking- oder Werbedienste</h2>
|
||||
<p>
|
||||
Wir verzichten bewusst vollständig auf Dienste, die dein Verhalten
|
||||
auswerten oder Daten an Dritte weitergeben. Insbesondere setzen wir
|
||||
<strong>nicht</strong> ein:
|
||||
</p>
|
||||
<ul>
|
||||
<li>keine Webanalyse (z. B. Google Analytics, Matomo, Hotjar)</li>
|
||||
<li>keine externen Schriftarten – alle Fonts liegen auf unserem Server</li>
|
||||
<li>keine eingebetteten Karten (z. B. Google Maps) und keine Video-Embeds</li>
|
||||
<li>keine Social-Media-Plugins, „Like“-Buttons oder Tracking-Pixel</li>
|
||||
<li>keine Werbenetzwerke oder Conversion-Tracking</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Instagram-Inhalte auf dieser Website</h2>
|
||||
<p>
|
||||
Auf unserer Website zeigen wir aktuelle Beiträge unseres öffentlichen
|
||||
Instagram-Profils. Diese Inhalte werden <strong>nicht</strong> über ein
|
||||
Instagram-Plugin oder einen externen Einbettungscode geladen. Stattdessen
|
||||
rufen wir die Beiträge in regelmäßigen Abständen serverseitig ab, speichern
|
||||
sie auf unserem eigenen Server und liefern Bilder und Texte ausschließlich von
|
||||
unserer Domain aus. Beim Ansehen unserer Website wird dadurch
|
||||
<strong>keine Verbindung zu Instagram oder Meta hergestellt</strong> und es
|
||||
werden keine Daten an diese Anbieter übertragen. Erst wenn du aktiv einen
|
||||
Link zu Instagram anklickst, verlässt du unsere Website (siehe nächster
|
||||
Abschnitt).
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Externe Links & Social Media</h2>
|
||||
<p>
|
||||
Unsere Website enthält Links zu externen Angeboten, etwa zu unseren Profilen
|
||||
bei Instagram und Facebook oder zu einer Routenplanung über Google Maps. Wenn
|
||||
du einen solchen Link anklickst, verlässt du unsere Website und es gelten die
|
||||
Datenschutzbestimmungen des jeweiligen Anbieters. Auf deren Datenverarbeitung
|
||||
haben wir keinen Einfluss.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Foto- und Videoaufnahmen</h2>
|
||||
<p>
|
||||
Im Rahmen unseres Vereinslebens, von Spielen und Veranstaltungen entstehen
|
||||
Foto- und Videoaufnahmen, die wir zur Darstellung des Vereins auf dieser
|
||||
Website, in unseren Social-Media-Kanälen und in Printmedien verwenden. Die
|
||||
Veröffentlichung erfolgt auf Grundlage einer Einwilligung
|
||||
(Art. 6 Abs. 1 lit. a DSGVO) bzw. unseres berechtigten Interesses an der
|
||||
Vereinsöffentlichkeitsarbeit (Art. 6 Abs. 1 lit. f DSGVO). Wenn du mit der
|
||||
Veröffentlichung einer dich zeigenden Aufnahme nicht einverstanden bist,
|
||||
genügt eine formlose Nachricht an
|
||||
<a href="mailto:<?= e($club['email']) ?>"><?= e($club['email']) ?></a> – wir
|
||||
entfernen die entsprechende Aufnahme dann zeitnah.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Deine Rechte</h2>
|
||||
<p>Dir stehen hinsichtlich deiner personenbezogenen Daten folgende Rechte zu:</p>
|
||||
<ul>
|
||||
<li>Recht auf Auskunft (Art. 15 DSGVO)</li>
|
||||
<li>Recht auf Berichtigung (Art. 16 DSGVO)</li>
|
||||
<li>Recht auf Löschung (Art. 17 DSGVO)</li>
|
||||
<li>Recht auf Einschränkung der Verarbeitung (Art. 18 DSGVO)</li>
|
||||
<li>Recht auf Datenübertragbarkeit (Art. 20 DSGVO)</li>
|
||||
<li>Recht auf Widerspruch gegen die Verarbeitung (Art. 21 DSGVO)</li>
|
||||
</ul>
|
||||
<p>
|
||||
Eine erteilte Einwilligung kannst du jederzeit mit Wirkung für die Zukunft
|
||||
widerrufen. Zur Ausübung deiner Rechte genügt eine Nachricht an die oben
|
||||
genannten Kontaktdaten.
|
||||
</p>
|
||||
<p>
|
||||
Außerdem hast du das Recht, dich bei einer Datenschutz-Aufsichtsbehörde zu
|
||||
beschweren. Die für uns zuständige Behörde ist:
|
||||
</p>
|
||||
<div class="legal__card">
|
||||
<address>
|
||||
<strong>Bayerisches Landesamt für Datenschutzaufsicht (BayLDA)</strong><br>
|
||||
Promenade 18<br>
|
||||
91522 Ansbach
|
||||
</address>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Aktualität dieser Datenschutzerklärung</h2>
|
||||
<p class="legal__muted">
|
||||
Stand: <?= e(date('m/Y')) ?>. Durch die Weiterentwicklung unserer Website
|
||||
oder geänderte gesetzliche Vorgaben kann es nötig werden, diese
|
||||
Datenschutzerklärung anzupassen. Es gilt jeweils die hier veröffentlichte
|
||||
aktuelle Fassung.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
53
app/pages/fussball.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Fußball-Übersichtsseite (Hub). Inhalt: data/fussball.json + data/teams.json.
|
||||
* Struktur: Hero → Mannschafts-Karten → Ansprechpartner → CTA → FAQ.
|
||||
*/
|
||||
$page = json_load('fussball');
|
||||
$teams = json_load('teams');
|
||||
|
||||
// Karten verlinken auf die vier Unterseiten (3 Senior + Jugend).
|
||||
$cards = [
|
||||
['name' => '1. Mannschaft', 'slug' => 'fussball/erste-mannschaft', 'category' => 'Herren', 'league' => 'Kreisklasse', 'image' => $teams['erste-mannschaft']['team_photo']['image'] ?? $teams['erste-mannschaft']['hero']['image']],
|
||||
['name' => '2. Mannschaft', 'slug' => 'fussball/zweite-mannschaft', 'category' => 'Herren', 'image' => $teams['zweite-mannschaft']['hero']['image']],
|
||||
['name' => 'Damenmannschaft', 'slug' => 'fussball/damen', 'category' => 'Damen', 'image' => $teams['damen']['hero']['image']],
|
||||
['name' => 'Jugendfußball', 'slug' => 'jugendfussball', 'category' => 'G- bis C-Jugend', 'image' => [
|
||||
'base' => 'img/pages/jugendfussball-hero', 'widths' => [640, 1000, 1440], 'alt' => 'Jugendfußball des TSV 08 Kulmbach',
|
||||
]],
|
||||
];
|
||||
|
||||
$meta = [
|
||||
'title' => 'Fußball in Kulmbach',
|
||||
'description' => 'Herren-, Damen- und Jugendfußball beim TSV 08 Kulmbach: erste und zweite Mannschaft auf Kreisklasse-Niveau, Damenmannschaft und Nachwuchs – aktiv werden im Katzbachtal.',
|
||||
'og_image' => 'img/pages/fussball-hero-1600.jpg',
|
||||
'schema' => page_schema(
|
||||
[
|
||||
['name' => 'Startseite', 'slug' => ''],
|
||||
['name' => 'Fußball', 'slug' => 'fussball'],
|
||||
],
|
||||
$page['faq'] ?? []
|
||||
),
|
||||
];
|
||||
|
||||
component('hero', ['hero' => $page['hero']]);
|
||||
component('team-cards', [
|
||||
'teams' => $cards,
|
||||
'intro' => $page['mannschaften_intro'] ?? '',
|
||||
'title_hidden' => true,
|
||||
'intro_below' => true,
|
||||
]);
|
||||
|
||||
component('person-grid', [
|
||||
'departments' => ['Herrenfußball', 'Damenfußball'],
|
||||
// Auf dem Hub bewusst nur die beiden Haupt-Ansprechpartner (mannschaftsspezifische
|
||||
// Zusatzkontakte wie Rustem erscheinen auf der jeweiligen Team-Seite).
|
||||
'names' => ['Benedikt Höfner', 'Sam Kern'],
|
||||
'title' => 'Ansprechpartner Fußball',
|
||||
'intro' => 'Fragen zu Training, Probetraining oder einem Wechsel? Sprich direkt unsere Verantwortlichen an.',
|
||||
'anchor' => 'kontakt',
|
||||
]);
|
||||
|
||||
component('faq', ['faq' => $page['faq'] ?? []]);
|
||||
@@ -26,4 +26,5 @@ foreach ($home['sections'] as $section) {
|
||||
|
||||
component('stats', ['stats' => $home['stats']]);
|
||||
component('partner-grid', ['intro' => $home['partners']]);
|
||||
component('person-grid');
|
||||
component('contact-form', ['intro' => $home['contact']]);
|
||||
|
||||
139
app/pages/impressum.php
Normal file
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Impressum — Angaben gemäß § 5 DDG, Vertretungsberechtigter Vorstand,
|
||||
* Bankverbindung, Haftungs- und Urheberrechtshinweise.
|
||||
* Alle Vereinsdaten aus data/club.json (Single Source of Truth).
|
||||
*/
|
||||
$club = json_load('club');
|
||||
$addr = $club['address'] ?? [];
|
||||
$chair = $club['board']['members'][0]['name'] ?? '';
|
||||
|
||||
$meta = [
|
||||
'title' => 'Impressum',
|
||||
'description' => 'Impressum des TSV 1908 Kulmbach e. V. – Anbieterkennzeichnung, vertretungsberechtigter Vorstand und Kontaktdaten.',
|
||||
'schema' => page_schema([
|
||||
['name' => 'Startseite', 'slug' => ''],
|
||||
['name' => 'Impressum', 'slug' => 'impressum'],
|
||||
]),
|
||||
];
|
||||
?>
|
||||
<section class="section legal">
|
||||
<div class="container">
|
||||
<div class="legal__head">
|
||||
<p class="legal__kicker">Rechtliches</p>
|
||||
<h1>Impressum</h1>
|
||||
<p class="legal__lead">Angaben gemäß § 5 Digitale-Dienste-Gesetz (DDG).</p>
|
||||
</div>
|
||||
|
||||
<div class="legal__body">
|
||||
<section>
|
||||
<h2>Anbieter</h2>
|
||||
<div class="legal__card">
|
||||
<address>
|
||||
<strong><?= e($club['legal_name']) ?></strong><br>
|
||||
<?= e($addr['street']) ?><br>
|
||||
<?= e($addr['zip']) ?> <?= e($addr['city']) ?><br>
|
||||
<br>
|
||||
E-Mail: <a href="mailto:<?= e($club['email']) ?>"><?= e($club['email']) ?></a><br>
|
||||
Web: <a href="<?= e(config('base_url')) ?>"><?= e(preg_replace('#^https?://#', '', (string) config('base_url'))) ?></a>
|
||||
</address>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Vertretungsberechtigter Vorstand</h2>
|
||||
<p><?= e($club['board']['note']) ?>:</p>
|
||||
<ul>
|
||||
<?php foreach ($club['board']['members'] as $member): ?>
|
||||
<li><?= e($member['name']) ?><?= $member['role'] !== '' ? ' – ' . e($member['role']) : '' ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Bankverbindung</h2>
|
||||
<div class="legal__cards legal__cards--2">
|
||||
<?php foreach ($club['banking'] as $bank): ?>
|
||||
<div class="legal__card">
|
||||
<p>
|
||||
<strong><?= e($bank['bank']) ?></strong><br>
|
||||
IBAN: <?= e($bank['iban']) ?><br>
|
||||
BIC: <?= e($bank['bic']) ?>
|
||||
</p>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Mitgliedschaften</h2>
|
||||
<ul>
|
||||
<?php foreach ($club['memberships'] as $membership): ?>
|
||||
<li><?= e($membership) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Verantwortlich für den Inhalt</h2>
|
||||
<p>Verantwortlich für den Inhalt nach § 18 Abs. 2 Medienstaatsvertrag (MStV):</p>
|
||||
<div class="legal__card">
|
||||
<address>
|
||||
<?php if ($chair !== ''): ?>
|
||||
<strong><?= e($chair) ?></strong><br>
|
||||
<?php endif; ?>
|
||||
<?= e($club['legal_name']) ?><br>
|
||||
<?= e($addr['street']) ?>, <?= e($addr['zip']) ?> <?= e($addr['city']) ?>
|
||||
</address>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Urheberrecht</h2>
|
||||
<p>
|
||||
Alle Rechte vorbehalten. Sämtliche auf dieser Website veröffentlichten Bilder,
|
||||
Texte und sonstigen Dateien unterliegen dem Urheberrecht. Eine Verwendung,
|
||||
Vervielfältigung oder Weitergabe – auch auszugsweise – ist nur mit ausdrücklicher,
|
||||
vorheriger Zustimmung des TSV 1908 Kulmbach e. V. gestattet. Soweit Inhalte auf
|
||||
dieser Seite nicht vom Betreiber erstellt wurden, werden die Urheberrechte Dritter
|
||||
beachtet und entsprechend gekennzeichnet. Sollte dir dennoch eine
|
||||
Urheberrechtsverletzung auffallen, bitten wir um einen Hinweis – bei Bekanntwerden
|
||||
entfernen wir die betreffenden Inhalte umgehend.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Haftung für Inhalte</h2>
|
||||
<p>
|
||||
Die Inhalte dieser Seiten wurden mit größter Sorgfalt erstellt. Für die Richtigkeit,
|
||||
Vollständigkeit und Aktualität der Inhalte können wir jedoch keine Gewähr übernehmen.
|
||||
Als Diensteanbieter sind wir gemäß § 7 Abs. 1 DDG für eigene Inhalte auf diesen Seiten
|
||||
nach den allgemeinen Gesetzen verantwortlich. Nach §§ 8 bis 10 DDG sind wir als
|
||||
Diensteanbieter jedoch nicht verpflichtet, übermittelte oder gespeicherte fremde
|
||||
Informationen zu überwachen oder nach Umständen zu forschen, die auf eine rechtswidrige
|
||||
Tätigkeit hinweisen.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Haftung für Links</h2>
|
||||
<p>
|
||||
Unser Angebot enthält Links zu externen Websites Dritter, auf deren Inhalte wir keinen
|
||||
Einfluss haben. Für die Gestaltung und die Inhalte der gelinkten Seiten sind
|
||||
ausschließlich deren Betreiber verantwortlich. Zum Zeitpunkt der Verlinkung waren keine
|
||||
rechtswidrigen Inhalte erkennbar. Sollten uns Rechtsverletzungen bekannt werden,
|
||||
entfernen wir die entsprechenden Links umgehend. Hiermit distanzieren wir uns
|
||||
ausdrücklich von allen Inhalten sämtlicher verlinkter Seiten.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Gestaltung & Umsetzung</h2>
|
||||
<p class="legal__muted">Webdesign und Layout: <?= e($club['webdesign']) ?>.</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
80
app/pages/jugendfussball.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Jugendfußball-Übersichtsseite. Inhalt: data/jugend.json.
|
||||
* Komposition: Hero → Intro-Bento (Bild, Mitmachen, Spielort, Altersklassen) →
|
||||
* Altersklassen-Karten → Ansprechpartner → CTA → FAQ.
|
||||
*/
|
||||
$page = json_load('jugend');
|
||||
|
||||
$club = json_load('club');
|
||||
$addr = $club['address'] ?? [];
|
||||
$mapsQuery = rawurlencode(trim(($addr['venue'] ?? '') . ', ' . ($addr['street'] ?? '') . ', ' . ($addr['zip'] ?? '') . ' ' . ($addr['city'] ?? ''), ', '));
|
||||
|
||||
$meta = [
|
||||
'title' => 'Jugendfußball in Kulmbach',
|
||||
'description' => 'Jugendfußball in Kulmbach – entdecke unsere Mannschaften von der G- bis zur C-Jugend, Betreuer und Trainingszeiten. Probiere Fußball in familiärer Atmosphäre aus.',
|
||||
'og_image' => 'img/pages/jugendfussball-hero-1440.jpg',
|
||||
'schema' => page_schema(
|
||||
[
|
||||
['name' => 'Startseite', 'slug' => ''],
|
||||
['name' => 'Fußball', 'slug' => 'fussball'],
|
||||
['name' => 'Jugendfußball', 'slug' => 'jugendfussball'],
|
||||
],
|
||||
$page['faq'] ?? []
|
||||
),
|
||||
];
|
||||
|
||||
component('hero', ['hero' => $page['hero']]);
|
||||
?>
|
||||
<section class="section" id="ueber-uns">
|
||||
<div class="container">
|
||||
<div class="intro-bento">
|
||||
<div class="bento__tile intro-bento__image">
|
||||
<?php component('img', ['image' => $page['hero']['image'], 'sizes' => '(max-width: 900px) 100vw, 40vw', 'class' => 'intro-bento__img']); ?>
|
||||
</div>
|
||||
|
||||
<article class="bento__tile intro-bento__about">
|
||||
<h2><?= e($page['about']['title']) ?></h2>
|
||||
<p><?= e($page['about']['text']) ?></p>
|
||||
</article>
|
||||
|
||||
<article class="bento__tile intro-bento__venue">
|
||||
<h2 class="bento__heading"><?= icon('geo-alt', 'bento__hicon') ?>Spielort</h2>
|
||||
<p class="bento__address">
|
||||
<?php if (!empty($addr['venue'])): ?><strong><?= e($addr['venue']) ?></strong><br><?php endif; ?>
|
||||
<?= e($addr['street']) ?>, <?= e($addr['zip']) ?> <?= e($addr['city']) ?>
|
||||
</p>
|
||||
<a class="btn btn--outline intro-bento__route" href="https://www.google.com/maps/search/?api=1&query=<?= e($mapsQuery) ?>" target="_blank" rel="noopener">Route planen<span class="visually-hidden"> (öffnet Google Maps in neuem Tab)</span></a>
|
||||
<span class="bento__deco" aria-hidden="true"><?= icon('geo-alt') ?></span>
|
||||
</article>
|
||||
|
||||
<article class="bento__tile bento__tile--accent intro-bento__stat">
|
||||
<span class="bento__stat-label">Altersklassen</span>
|
||||
<span class="bento__stat-value">G – C</span>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<?php
|
||||
component('age-groups', ['groups' => $page['groups'] ?? []]);
|
||||
|
||||
component('person-grid', [
|
||||
'departments' => ['Jugendfußball'],
|
||||
'title' => 'Ansprechpartner Jugendfußball',
|
||||
'intro' => 'Fragen zum Schnuppertraining oder zur passenden Altersgruppe? Melde dich direkt.',
|
||||
'anchor' => 'kontakt',
|
||||
]);
|
||||
|
||||
component('cta-band', ['band' => [
|
||||
'title' => 'Neugierig geworden?',
|
||||
'text' => 'Komm mit deinem Kind zum kostenlosen Schnuppertraining – ganz unverbindlich.',
|
||||
'ctas' => [
|
||||
['label' => 'Zum Ansprechpartner', 'slug' => 'jugendfussball#kontakt', 'style' => 'primary'],
|
||||
['label' => 'Mitglied werden', 'slug' => 'mitglied-werden', 'style' => 'outline'],
|
||||
],
|
||||
]]);
|
||||
|
||||
component('faq', ['faq' => $page['faq'] ?? []]);
|
||||
18
app/pages/team-damen.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Damenmannschaft (Damenfußball). Inhalt: data/teams.json → damen.
|
||||
*/
|
||||
$slug = 'fussball/damen';
|
||||
$team = json_load('teams')['damen'];
|
||||
|
||||
$meta = [
|
||||
'title' => 'Damenmannschaft – Damenfußball Kulmbach',
|
||||
'description' => $team['hero']['text'],
|
||||
'og_image' => $team['hero']['image']['base'] . '-1600.jpg',
|
||||
'schema' => team_schema($team, $slug),
|
||||
];
|
||||
|
||||
component('team', ['team' => $team, 'slug' => $slug]);
|
||||
18
app/pages/team-erste.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* 1. Mannschaft (Herrenfußball). Inhalt: data/teams.json → erste-mannschaft.
|
||||
*/
|
||||
$slug = 'fussball/erste-mannschaft';
|
||||
$team = json_load('teams')['erste-mannschaft'];
|
||||
|
||||
$meta = [
|
||||
'title' => '1. Mannschaft – Herrenfußball Kreisklasse Kulmbach',
|
||||
'description' => $team['hero']['text'],
|
||||
'og_image' => $team['hero']['image']['base'] . '-1600.jpg',
|
||||
'schema' => team_schema($team, $slug),
|
||||
];
|
||||
|
||||
component('team', ['team' => $team, 'slug' => $slug]);
|
||||
19
app/pages/team-zwei.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* 2. Mannschaft (Herrenfußball). Inhalt: data/teams.json → zweite-mannschaft.
|
||||
*/
|
||||
$slug = 'fussball/zweite-mannschaft';
|
||||
$team = json_load('teams')['zweite-mannschaft'];
|
||||
|
||||
$meta = [
|
||||
'title' => '2. Mannschaft – Herrenfußball Kulmbach',
|
||||
'description' => $team['hero']['text'],
|
||||
'og_image' => $team['hero']['image']['base'] . '-1600.jpg',
|
||||
'scripts' => (count($team['contact_names'] ?? []) > 1) ? ['contact-slider.js'] : [],
|
||||
'schema' => team_schema($team, $slug),
|
||||
];
|
||||
|
||||
component('team', ['team' => $team, 'slug' => $slug]);
|
||||
84
app/pages/verein.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Vereins-Übersichtsseite (Hub). Inhalt: data/verein.json + data/club.json.
|
||||
* Struktur: Hero → Eckdaten (key-facts) → Leitbild (split) → Abteilungs-Kacheln →
|
||||
* Gelände/Drohnenbild → CTA → FAQ.
|
||||
*/
|
||||
$page = json_load('verein');
|
||||
|
||||
$club = json_load('club');
|
||||
$addr = $club['address'] ?? [];
|
||||
$mapsQuery = rawurlencode(trim(($addr['venue'] ?? '') . ', ' . ($addr['street'] ?? '') . ', ' . ($addr['zip'] ?? '') . ' ' . ($addr['city'] ?? ''), ', '));
|
||||
|
||||
// Zwei Abteilungs-Kacheln. Turnen verlinkt bereits auf /turnen (Seite folgt).
|
||||
$cards = [
|
||||
[
|
||||
'name' => 'Fußball',
|
||||
'slug' => 'fussball',
|
||||
'category' => 'Herren · Damen · Jugend',
|
||||
'image' => ['base' => 'img/pages/fussball-hero', 'widths' => [640, 1000, 1600], 'alt' => 'Fußball beim TSV 08 Kulmbach'],
|
||||
],
|
||||
[
|
||||
'name' => 'Turnabteilung',
|
||||
'slug' => 'turnen',
|
||||
'category' => 'Turnen · Trampolin',
|
||||
'image' => ['base' => 'img/pages/turnen', 'widths' => [640, 1000], 'alt' => 'Turnabteilung des TSV 08 Kulmbach'],
|
||||
],
|
||||
];
|
||||
|
||||
$meta = [
|
||||
'title' => 'Der Verein',
|
||||
'description' => 'Der TSV 1908 Kulmbach e. V. – seit 1908 dein Sportverein im Katzbachtal in Kulmbach. Über 650 Mitglieder, die Abteilungen Fußball und Turnen, zuhause auf der Hans-Rausch-Sportanlage.',
|
||||
'og_image' => 'img/pages/vereinsgelaende-1600.jpg',
|
||||
'schema' => page_schema(
|
||||
[
|
||||
['name' => 'Startseite', 'slug' => ''],
|
||||
['name' => 'Der Verein', 'slug' => 'verein'],
|
||||
],
|
||||
$page['faq'] ?? []
|
||||
),
|
||||
];
|
||||
|
||||
component('hero', ['hero' => $page['hero']]);
|
||||
|
||||
component('key-facts', ['facts' => $page['facts'] ?? [], 'anchor' => 'eckdaten']);
|
||||
|
||||
component('section', ['section' => $page['about']]);
|
||||
|
||||
component('team-cards', [
|
||||
'teams' => $cards,
|
||||
'title' => 'Unsere Abteilungen',
|
||||
'intro' => $page['abteilungen_intro'] ?? '',
|
||||
'intro_below' => true,
|
||||
'anchor' => 'abteilungen',
|
||||
]);
|
||||
?>
|
||||
<section class="section facility" id="gelaende">
|
||||
<div class="container">
|
||||
<h2><?= e($page['gelaende']['title']) ?></h2>
|
||||
<p class="facility__lead"><?= e($page['gelaende']['text']) ?></p>
|
||||
<figure class="facility__figure">
|
||||
<?php component('img', ['image' => $page['gelaende']['image'], 'sizes' => '(max-width: 1200px) 100vw, 1200px', 'class' => 'facility__img']); ?>
|
||||
<?php if (!empty($page['gelaende']['caption'])): ?>
|
||||
<figcaption class="facility__caption"><?= e($page['gelaende']['caption']) ?></figcaption>
|
||||
<?php endif; ?>
|
||||
</figure>
|
||||
<p class="facility__actions">
|
||||
<a class="btn btn--outline" href="https://www.google.com/maps/search/?api=1&query=<?= e($mapsQuery) ?>" target="_blank" rel="noopener">Route planen<span class="visually-hidden"> (öffnet Google Maps in neuem Tab)</span></a>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<?php
|
||||
component('cta-band', ['band' => [
|
||||
'title' => 'Werde Teil des TSV 08',
|
||||
'text' => 'Ob Fußball oder Turnen – komm unverbindlich vorbei oder schreib uns. Wir finden gemeinsam die passende Gruppe für dich.',
|
||||
'ctas' => [
|
||||
['label' => 'Mitglied werden', 'slug' => 'mitglied-werden', 'style' => 'primary'],
|
||||
['label' => 'Kontakt aufnehmen', 'slug' => '#kontakt', 'style' => 'outline'],
|
||||
],
|
||||
]]);
|
||||
|
||||
component('faq', ['faq' => $page['faq'] ?? []]);
|
||||
@@ -12,6 +12,13 @@ declare(strict_types=1);
|
||||
return [
|
||||
'' => ['file' => 'home.php', 'sitemap' => true],
|
||||
'fussball' => ['file' => 'fussball.php', 'sitemap' => true],
|
||||
'fussball/erste-mannschaft' => ['file' => 'team-erste.php', 'sitemap' => true],
|
||||
'fussball/zweite-mannschaft' => ['file' => 'team-zwei.php', 'sitemap' => true],
|
||||
'fussball/damen' => ['file' => 'team-damen.php', 'sitemap' => true],
|
||||
'jugendfussball' => ['file' => 'jugendfussball.php', 'sitemap' => true],
|
||||
'verein' => ['file' => 'verein.php', 'sitemap' => true],
|
||||
'historie' => ['file' => 'historie.php', 'sitemap' => true],
|
||||
'impressum' => ['file' => 'impressum.php', 'sitemap' => true],
|
||||
'datenschutz' => ['file' => 'datenschutz.php', 'sitemap' => true],
|
||||
'404' => ['file' => '404.php', 'sitemap' => false],
|
||||
];
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
"phone": "+49 160 97516404",
|
||||
"image": "img/persons/benedikt-hoefner.jpg"
|
||||
},
|
||||
{
|
||||
"department": "Herrenfußball",
|
||||
"name": "Rustem Hajzeraj",
|
||||
"image": "img/persons/rustem-hajzeraj.jpg"
|
||||
},
|
||||
{
|
||||
"department": "Damenfußball",
|
||||
"name": "Sam Kern",
|
||||
|
||||
@@ -13,6 +13,22 @@
|
||||
},
|
||||
"email": "info@tsv08kulmbach.de",
|
||||
"email_turnen": "turnabteilung.tsv08kulmbach@gmail.com",
|
||||
"board": {
|
||||
"note": "Vertretungsberechtigter Vorstand gemäß § 26 BGB",
|
||||
"members": [
|
||||
{ "name": "Kai van Avondt", "role": "1. Vorsitzender" },
|
||||
{ "name": "Marco Früh", "role": "" },
|
||||
{ "name": "Fabian Früh", "role": "" },
|
||||
{ "name": "Matthias Korzonek", "role": "" },
|
||||
{ "name": "Michael Senar", "role": "" }
|
||||
]
|
||||
},
|
||||
"banking": [
|
||||
{ "bank": "Sparkasse Kulmbach-Kronach", "iban": "DE11 7715 0000 0000 1542 52", "bic": "BYLADEM1KUB" },
|
||||
{ "bank": "VR Bank Oberfranken Mitte", "iban": "DE90 7719 0000 0000 0362 18", "bic": "GENODEF1KU1" }
|
||||
],
|
||||
"data_protection_contact": "Fabian Früh",
|
||||
"webdesign": "Felix Schneider",
|
||||
"memberships": [
|
||||
"Bayerischer Landes-Sportverband",
|
||||
"Bayerischer Fußball-Verband",
|
||||
|
||||
23
data/fussball.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"hero": {
|
||||
"align": "bottom-left",
|
||||
"compact": true,
|
||||
"title": "Fußball",
|
||||
"text": "Zwei Herrenmannschaften (1. Mannschaft in der Kreisklasse), eine Damenmannschaft und Jugendteams von der G- bis zur C-Jugend – alle zuhause auf der Hans-Rausch-Sportanlage im Katzbachtal in Kulmbach.",
|
||||
"image": {
|
||||
"base": "img/pages/fussball-hero",
|
||||
"widths": [640, 1000, 1600],
|
||||
"alt": "Fußballplatz des TSV 08 Kulmbach im Katzbachtal"
|
||||
},
|
||||
"ctas": []
|
||||
},
|
||||
"mannschaften_intro": "Vom Kreisklasse-Fußball der Herren über die Damenmannschaft bis zum Nachwuchs – wähle deine Mannschaft.",
|
||||
"faq": [
|
||||
{ "q": "Wo kann ich in Kulmbach Fußball spielen?", "a": "Beim TSV 08 Kulmbach im Katzbachtal – mit erster und zweiter Herrenmannschaft, einer Damenmannschaft und Jugendmannschaften von der G- bis zur C-Jugend." },
|
||||
{ "q": "Nehmt ihr neue Spieler und Spielerinnen auf?", "a": "Ja, jederzeit. Komm unverbindlich zum Probetraining der passenden Mannschaft – sprich einfach den jeweiligen Ansprechpartner an." },
|
||||
{ "q": "Auf welchem Niveau spielt der Herrenbereich?", "a": "Die erste Herrenmannschaft spielt auf Kreisklasse-Niveau, dazu kommt die zweite Mannschaft für alle, die mit Spaß regelmäßig kicken." },
|
||||
{ "q": "Wann ist Fußballtraining beim TSV 08 Kulmbach?", "a": "Die erste Herrenmannschaft trainiert dienstags und donnerstags um 18:30 Uhr auf der Hans-Rausch-Sportanlage. Die Trainingszeiten der zweiten Mannschaft, der Damen und der Jugend erfährst du direkt beim jeweiligen Ansprechpartner." },
|
||||
{ "q": "Ab welchem Alter kann mein Kind bei euch Fußball spielen?", "a": "Schon die Kleinsten sind willkommen: Unsere Jugendabteilung reicht von der G-Jugend (Bambini) bis zur C-Jugend. Mehr dazu auf unserer Seite zum Jugendfußball." },
|
||||
{ "q": "Wo ist der Sportplatz?", "a": "Auf der Hans-Rausch-Sportanlage in der Thurnauer Str. 51 in Kulmbach, mitten im Katzbachtal." }
|
||||
]
|
||||
}
|
||||
32
data/jugend.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"hero": {
|
||||
"compact": true,
|
||||
"align": "bottom-left",
|
||||
"title": "Jugendfußball in Kulmbach",
|
||||
"tagline": "G- bis C-Jugend im Katzbachtal",
|
||||
"text": "Entdecke unsere Jugendmannschaften, Spielerinnen und Spieler sowie engagierte Betreuer – und lerne den Katzbachtaler Weg im Jugendfußball kennen.",
|
||||
"image": {
|
||||
"base": "img/pages/jugendfussball-hero",
|
||||
"widths": [640, 1000, 1440],
|
||||
"alt": "Kinder beim Jugendfußball-Training des TSV 08 Kulmbach"
|
||||
},
|
||||
"ctas": []
|
||||
},
|
||||
"about": {
|
||||
"title": "Mitmachen leicht gemacht",
|
||||
"text": "Wenn deine Kinder Lust haben, bei uns Fußball auszuprobieren oder mitzuspielen, meldet euch gerne ganz unkompliziert. Wir freuen uns über jedes neue Gesicht und bieten Fußball in familiärer Atmosphäre – vom ersten Ballkontakt bis zum echten Mannschaftsspiel."
|
||||
},
|
||||
"groups": [
|
||||
{ "code": "G", "name": "G-Jugend (Bambini)", "training": "Freitag 16:30 Uhr", "photo": { "base": "img/teams/jugend-g", "widths": [400, 800], "alt": "G-Jugend des TSV 08 Kulmbach" } },
|
||||
{ "code": "F", "name": "F-Jugend", "training": "Freitag 16:30 Uhr", "photo": { "base": "img/teams/jugend-f", "widths": [400, 800], "alt": "F-Jugend des TSV 08 Kulmbach" } },
|
||||
{ "code": "E", "name": "E-Jugend", "note": "Trainingszeit auf Anfrage", "photo": { "base": "img/teams/jugend-e", "widths": [400, 800], "alt": "E-Jugend des TSV 08 Kulmbach" } },
|
||||
{ "code": "D", "name": "D-Jugend", "note": "Trainingszeit auf Anfrage", "photo": { "base": "img/teams/jugend-d", "widths": [400, 800], "alt": "D-Jugend des TSV 08 Kulmbach" } },
|
||||
{ "code": "C", "name": "C-Jugend", "note": "Trainingszeit auf Anfrage", "photo": { "base": "img/teams/jugend-c", "widths": [400, 800], "alt": "C-Jugend des TSV 08 Kulmbach" } }
|
||||
],
|
||||
"faq": [
|
||||
{ "q": "Ab welchem Alter können Kinder mitspielen?", "a": "Vom Bambini-Alter (G-Jugend) bis zur C-Jugend ist bei uns alles dabei. Am besten kommt ihr einfach zum Schnuppertraining der passenden Altersgruppe vorbei." },
|
||||
{ "q": "Wie melde ich mein Kind an?", "a": "Ganz unkompliziert: Ruf unseren Ansprechpartner an oder schreib eine WhatsApp-Nachricht. Wir beantworten alle Fragen rund um den Jugendfußball und sagen euch, wann das nächste Training ist." },
|
||||
{ "q": "Was kostet ein Schnuppertraining?", "a": "Das Schnuppertraining ist kostenlos und unverbindlich – ihr schaut euch in Ruhe an, ob es passt." },
|
||||
{ "q": "Wo trainiert die Jugend?", "a": "Auf der Hans-Rausch-Sportanlage in der Thurnauer Str. 51 in Kulmbach, mitten im Katzbachtal." }
|
||||
]
|
||||
}
|
||||
209
data/teams.json
Normal file
@@ -0,0 +1,209 @@
|
||||
{
|
||||
"erste-mannschaft": {
|
||||
"name": "1. Mannschaft",
|
||||
"department": "Herrenfußball",
|
||||
"category": "Herren",
|
||||
"league": "Kreisklasse",
|
||||
"hero": {
|
||||
"compact": true,
|
||||
"align": "bottom-left",
|
||||
"title": "1. Mannschaft",
|
||||
"tagline": "Herrenfußball · Kreisklasse",
|
||||
"text": "Unsere erste Herrenmannschaft spielt auf Kreisklasse-Niveau im Katzbachtal – ambitioniert, aber mit echtem Vereinsgeist. Komm vorbei und spiel mit.",
|
||||
"image": {
|
||||
"base": "img/teams/herren-1",
|
||||
"widths": [640, 1000, 1600],
|
||||
"alt": "Spielszene der 1. Mannschaft des TSV 08 Kulmbach"
|
||||
},
|
||||
"ctas": []
|
||||
},
|
||||
"facts": [
|
||||
{ "icon": "people-fill", "label": "Bereich", "value": "Herren" },
|
||||
{ "icon": "trophy", "label": "Liga", "value": "Kreisklasse" },
|
||||
{ "icon": "geo-alt", "label": "Heimat", "value": "Katzbachtal, Kulmbach" }
|
||||
],
|
||||
"about": {
|
||||
"title": "Über uns",
|
||||
"text": "Bei der ersten Mannschaft treffen Ehrgeiz und Spaß am Spiel aufeinander. Wir trainieren regelmäßig im Katzbachtal und freuen uns über ambitionierte Hobbyfußballer, die Lust auf Kreisklasse-Fußball in einer eingeschworenen Truppe haben."
|
||||
},
|
||||
"team_photo": {
|
||||
"image": {
|
||||
"base": "img/teams/herren-1-team",
|
||||
"widths": [640, 1000, 1600],
|
||||
"alt": "Mannschaftsfoto der 1. Mannschaft des TSV 08 Kulmbach"
|
||||
},
|
||||
"caption": "Die 1. Mannschaft des TSV 08 Kulmbach"
|
||||
},
|
||||
"roster": {
|
||||
"title": "Mannschaft & Trainerstab",
|
||||
"intro": "Trainerstab und Kader der ersten Herrenmannschaft in der Saison.",
|
||||
"variant": "overlay",
|
||||
"groups": [
|
||||
{
|
||||
"title": "Trainer & Betreuer",
|
||||
"members": [
|
||||
{ "name": "Ali Kilic", "role": "Trainer", "image": "img/persons/ali-kilic.jpg" },
|
||||
{ "name": "Benedikt Höfner", "role": "Sportlicher Leiter", "image": "img/persons/benedikt-hoefner.jpg" },
|
||||
{ "name": "Bastian Werner", "role": "Spielleiter", "image": "img/persons/bastian-werner.jpg" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Torwart",
|
||||
"members": [
|
||||
{ "name": "Janick Martin", "image": "img/persons/janick-martin.jpg" },
|
||||
{ "name": "Sascha Zimmerer", "image": "img/persons/sascha-zimmerer.jpg" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Abwehr",
|
||||
"members": [
|
||||
{ "name": "Ali Kilic", "image": "img/persons/ali-kilic-spieler.jpg" },
|
||||
{ "name": "Alexander Hatter", "image": "img/persons/alexander-hatter.jpg" },
|
||||
{ "name": "Alex Schütz", "image": "img/persons/alex-schuetz.jpg" },
|
||||
{ "name": "Rustem Hajzeraj", "image": "img/persons/rustem-hajzeraj.jpg" },
|
||||
{ "name": "Güray Kosuva" },
|
||||
{ "name": "Maximilian Koenen" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Mittelfeld",
|
||||
"members": [
|
||||
{ "name": "Fabian Früh", "image": "img/persons/fabian-frueh.jpg" },
|
||||
{ "name": "Christian Kästner", "image": "img/persons/christian-kaestner.jpg" },
|
||||
{ "name": "Sebastiano Gross", "image": "img/persons/sebastiano-gross.jpg" },
|
||||
{ "name": "Tim Hofmann", "image": "img/persons/tim-hofmann.jpg" },
|
||||
{ "name": "Felix Schneider", "image": "img/persons/felix-schneider.jpg" },
|
||||
{ "name": "Alexander Vogel", "image": "img/persons/alexander-vogel.jpg" },
|
||||
{ "name": "Hannes Buchmeier" },
|
||||
{ "name": "Hakan Yilmaz", "image": "img/persons/hakan-yilmaz.jpg" },
|
||||
{ "name": "Dominik Schulz", "image": "img/persons/dominik-schulz.jpg" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Angriff",
|
||||
"members": [
|
||||
{ "name": "Christian Ohnemüller", "image": "img/persons/christian-ohnemueller.jpg" },
|
||||
{ "name": "Marek Szabelski", "image": "img/persons/marek-szabelski.jpg" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"training": {
|
||||
"venue_from_club": true,
|
||||
"sessions": [
|
||||
{ "day": "Dienstag", "time": "18:30 Uhr" },
|
||||
{ "day": "Donnerstag", "time": "18:30 Uhr" }
|
||||
]
|
||||
},
|
||||
"contact_department": "Herrenfußball",
|
||||
"contact_name": "Benedikt Höfner",
|
||||
"faq": [
|
||||
{ "q": "Kann ich zum Probetraining kommen?", "a": "Klar – melde dich kurz bei unserem Ansprechpartner und komm einfach zum Training vorbei. Ein Probetraining ist jederzeit unverbindlich möglich." },
|
||||
{ "q": "Auf welchem Niveau spielt die 1. Mannschaft?", "a": "Die erste Herrenmannschaft des TSV 08 Kulmbach spielt auf Kreisklasse-Niveau." },
|
||||
{ "q": "Wann trainiert die 1. Mannschaft?", "a": "Die erste Mannschaft trainiert dienstags und donnerstags um 18:30 Uhr auf der Hans-Rausch-Sportanlage im Katzbachtal." },
|
||||
{ "q": "Wer ist der Trainer der 1. Mannschaft?", "a": "Trainer ist Ali Kilic. Sportlicher Leiter ist Benedikt Höfner, Spielleiter Bastian Werner." },
|
||||
{ "q": "Wo trainiert und spielt die Mannschaft?", "a": "Heimat ist die Hans-Rausch-Sportanlage in der Thurnauer Str. 51 in Kulmbach, mitten im Katzbachtal." }
|
||||
]
|
||||
},
|
||||
"zweite-mannschaft": {
|
||||
"name": "2. Mannschaft",
|
||||
"department": "Herrenfußball",
|
||||
"category": "Herren",
|
||||
"league": null,
|
||||
"hero": {
|
||||
"compact": true,
|
||||
"align": "bottom-left",
|
||||
"title": "2. Mannschaft",
|
||||
"tagline": "Herrenfußball · SG mit TSV Melkendorf",
|
||||
"text": "Unsere zweite Herrenmannschaft spielt in einer Spielgemeinschaft mit dem TSV Melkendorf. Hier steht der Spaß am Fußball im Vordergrund – ideal für alle, die regelmäßig kicken und Teil der TSV-Familie werden wollen.",
|
||||
"image": {
|
||||
"base": "img/teams/herren-2",
|
||||
"widths": [640, 1000, 1600],
|
||||
"alt": "Einlaufen der Mannschaften beim TSV 08 Kulmbach"
|
||||
},
|
||||
"ctas": []
|
||||
},
|
||||
"facts": [
|
||||
{ "icon": "people-fill", "label": "Bereich", "value": "Herren" },
|
||||
{ "icon": "geo-alt", "label": "Heimat", "value": "Katzbachtal, Kulmbach" }
|
||||
],
|
||||
"about": {
|
||||
"title": "Über uns",
|
||||
"text": "Unsere zweite Mannschaft ist die ideale Anlaufstelle für alle, die mit Freude am Spiel regelmäßig auf dem Platz stehen wollen – vom Wiedereinsteiger bis zum ambitionierten Hobbyfußballer. Gemeinsam mit dem TSV Melkendorf bilden wir eine Spielgemeinschaft: Breiter Kader, gute Stimmung, echtes Teamgefühl – über beide Vereine hinweg."
|
||||
},
|
||||
"team_photo": {
|
||||
"image": {
|
||||
"base": "img/teams/herren-2",
|
||||
"widths": [640, 1000, 1600],
|
||||
"alt": "Die 2. Mannschaft des TSV 08 Kulmbach"
|
||||
},
|
||||
"caption": "Die 2. Mannschaft des TSV 08 Kulmbach"
|
||||
},
|
||||
"training": {
|
||||
"venue_from_club": true,
|
||||
"venue": {
|
||||
"name": "Sportgelände TSV Melkendorf",
|
||||
"locality": "Melkendorf · 95326 Kulmbach",
|
||||
"maps_query": "Sportgelände TSV Melkendorf Kulmbach"
|
||||
},
|
||||
"sessions": [],
|
||||
"note": "Als Spielgemeinschaft mit dem TSV Melkendorf finden die Heimspiele meistens in Melkendorf statt; trainiert wird abwechselnd in Melkendorf und bei uns im Katzbachtal."
|
||||
},
|
||||
"contact_department": "Herrenfußball",
|
||||
"contact_names": ["Benedikt Höfner", "Rustem Hajzeraj"],
|
||||
"faq": [
|
||||
{ "q": "Kann ich zum Probetraining kommen?", "a": "Jederzeit – sprich kurz unseren Ansprechpartner an und komm unverbindlich zum Training vorbei." },
|
||||
{ "q": "Für wen ist die 2. Mannschaft geeignet?", "a": "Für alle, die mit Freude am Spiel regelmäßig auf dem Platz stehen wollen – vom Wiedereinsteiger bis zum ambitionierten Hobbyfußballer. Vereinserfahrung ist keine Voraussetzung." },
|
||||
{ "q": "Bildet die 2. Mannschaft eine Spielgemeinschaft?", "a": "Ja, die zweite Mannschaft ist eine Spielgemeinschaft mit dem TSV Melkendorf. Trainiert wird abwechselnd auf der Hans-Rausch-Sportanlage im Katzbachtal und in Melkendorf." },
|
||||
{ "q": "Wer ist Ansprechpartner der 2. Mannschaft?", "a": "Benedikt Höfner – sprich ihn einfach direkt an, wenn du mitspielen oder zum Probetraining kommen möchtest." },
|
||||
{ "q": "Wo trainiert und spielt die 2. Mannschaft?", "a": "Heimspiele und Training finden auf der Hans-Rausch-Sportanlage in der Thurnauer Str. 51 in Kulmbach im Katzbachtal sowie – im Rahmen der Spielgemeinschaft – beim TSV Melkendorf statt." }
|
||||
]
|
||||
},
|
||||
"damen": {
|
||||
"name": "Damenmannschaft",
|
||||
"department": "Damenfußball",
|
||||
"category": "Damen",
|
||||
"league": null,
|
||||
"hero": {
|
||||
"compact": true,
|
||||
"align": "bottom-left",
|
||||
"title": "Damenmannschaft",
|
||||
"tagline": "Damenfußball",
|
||||
"text": "Frauen- und Mädchenfußball hat beim TSV 08 Kulmbach einen festen Platz. Ob erfahren oder Wiedereinsteigerin – bei uns findest du dein Team im Katzbachtal.",
|
||||
"image": {
|
||||
"base": "img/teams/damen",
|
||||
"widths": [640, 1000, 1600],
|
||||
"alt": "Damenmannschaft des TSV 08 Kulmbach"
|
||||
},
|
||||
"ctas": []
|
||||
},
|
||||
"facts": [
|
||||
{ "icon": "people-fill", "label": "Bereich", "value": "Damen" },
|
||||
{ "icon": "geo-alt", "label": "Heimat", "value": "Katzbachtal, Kulmbach" }
|
||||
],
|
||||
"about": {
|
||||
"title": "Über uns",
|
||||
"text": "Unsere Damenmannschaft lebt vom Zusammenhalt und der Freude am Fußball. Wir freuen uns über jede neue Spielerin – egal ob mit Vereinserfahrung oder als Wiedereinsteigerin. Trau dich und komm zum Probetraining."
|
||||
},
|
||||
"team_photo": {
|
||||
"image": {
|
||||
"base": "img/teams/damen",
|
||||
"widths": [640, 1000, 1600],
|
||||
"alt": "Die Damenmannschaft des TSV 08 Kulmbach"
|
||||
},
|
||||
"caption": "Die Damenmannschaft des TSV 08 Kulmbach"
|
||||
},
|
||||
"training": {
|
||||
"venue_from_club": true,
|
||||
"sessions": []
|
||||
},
|
||||
"contact_department": "Damenfußball",
|
||||
"contact_name": "Sam Kern",
|
||||
"faq": [
|
||||
{ "q": "Kann ich als Wiedereinsteigerin mitmachen?", "a": "Unbedingt – bei uns sind Spielerinnen mit und ohne Vereinserfahrung willkommen. Komm einfach zum Probetraining." },
|
||||
{ "q": "Für wen ist die Damenmannschaft?", "a": "Für Frauen und Mädchen, die Fußball spielen wollen – ob ambitioniert oder als Wiedereinsteigerin, mit oder ohne Vereinserfahrung." },
|
||||
{ "q": "Wer ist Ansprechpartner für die Damenmannschaft?", "a": "Sam Kern – melde dich einfach direkt für Fragen oder ein unverbindliches Probetraining." },
|
||||
{ "q": "Wo trainiert und spielt die Damenmannschaft?", "a": "Heimat ist die Hans-Rausch-Sportanlage in der Thurnauer Str. 51 in Kulmbach, mitten im Katzbachtal." }
|
||||
]
|
||||
}
|
||||
}
|
||||
49
data/verein.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"hero": {
|
||||
"align": "bottom-left",
|
||||
"compact": true,
|
||||
"title": "Der Verein",
|
||||
"text": "Seit über 100 Jahren dein Sportverein im Katzbachtal – Fußball und Turnen unter einem Dach, getragen vom Ehrenamt. Made in Kulmbach.",
|
||||
"image": {
|
||||
"base": "img/pages/vereinsgelaende",
|
||||
"widths": [1000, 1600],
|
||||
"alt": "Luftaufnahme der Hans-Rausch-Sportanlage des TSV 08 Kulmbach im Katzbachtal"
|
||||
},
|
||||
"ctas": []
|
||||
},
|
||||
"facts": [
|
||||
{ "icon": "clock", "label": "Gegründet", "value": "1908" },
|
||||
{ "icon": "people-fill", "label": "Mitglieder", "value": "650+" },
|
||||
{ "icon": "trophy", "label": "Mannschaften", "value": "10+" },
|
||||
{ "icon": "geo-alt", "label": "Zuhause", "value": "Katzbachtal" }
|
||||
],
|
||||
"about": {
|
||||
"id": "ueber-uns",
|
||||
"title": "Im Katzbachtal zuhause",
|
||||
"text": "Im Katzbachtal zuhause sind wir dein Verein für Turnen und Fußball. Schwerpunkt ist dabei die kulturelle und sportliche Förderung von Jugendlichen. Bei uns wird das Ehrenamt leidenschaftlich gelebt – so entstehen Jahr für Jahr neue erfolgreiche Sportgeschichten, made in Kulmbach.",
|
||||
"tinted": true,
|
||||
"image": {
|
||||
"base": "img/pages/sportheim",
|
||||
"widths": [640, 1000],
|
||||
"alt": "Sportheim des TSV 08 Kulmbach mit Pokalen und Vereinswappen"
|
||||
}
|
||||
},
|
||||
"abteilungen_intro": "Zwei Abteilungen, ein Verein – wähle deinen Sport.",
|
||||
"gelaende": {
|
||||
"title": "Unser Gelände im Katzbachtal",
|
||||
"text": "Mach dir einen Eindruck von unseren Gegebenheiten: Auf der Hans-Rausch-Sportanlage liegen Rasenplatz, Hartplatz, Turnhalle und Sportheim direkt beieinander – mitten im Katzbachtal in Kulmbach. Wir freuen uns, dich schon bald hier begrüßen zu dürfen.",
|
||||
"caption": "Hans-Rausch-Sportanlage aus der Luft – Rasenplatz, Hartplatz, Turnhalle und Sportheim auf einen Blick.",
|
||||
"image": {
|
||||
"base": "img/pages/verein",
|
||||
"widths": [640, 1000],
|
||||
"alt": "Beschriftete Luftaufnahme des Vereinsgeländes: Rasenplatz, Hartplatz, Turnhalle und Sportheim"
|
||||
}
|
||||
},
|
||||
"faq": [
|
||||
{ "q": "Welche Abteilungen hat der TSV 08 Kulmbach?", "a": "Der TSV 1908 Kulmbach e. V. hat zwei Abteilungen: Fußball (Herren, Damen und Jugend) sowie Turnen, zu dem auch das Trampolinturnen gehört." },
|
||||
{ "q": "Seit wann gibt es den TSV 08 Kulmbach?", "a": "Der Verein wurde 1908 gegründet und ist damit seit über 100 Jahren in Kulmbach aktiv." },
|
||||
{ "q": "Wo ist der TSV 08 Kulmbach zuhause?", "a": "Auf der Hans-Rausch-Sportanlage in der Thurnauer Str. 51 in Kulmbach, mitten im Katzbachtal." },
|
||||
{ "q": "Wie viele Mitglieder hat der Verein?", "a": "Der TSV 08 Kulmbach zählt über 650 Mitglieder in mehr als zehn Mannschaften." },
|
||||
{ "q": "Wie kann ich Mitglied werden?", "a": "Komm unverbindlich vorbei oder nimm über das Kontaktformular Kontakt mit uns auf – wir helfen dir, die passende Abteilung und Gruppe zu finden." }
|
||||
]
|
||||
}
|
||||
BIN
public/assets/img/pages/fussball-hero-1000.jpg
Normal file
|
After Width: | Height: | Size: 124 KiB |
BIN
public/assets/img/pages/fussball-hero-1600.jpg
Normal file
|
After Width: | Height: | Size: 299 KiB |
BIN
public/assets/img/pages/fussball-hero-640.jpg
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
public/assets/img/pages/fussball-mannschaften-1000.jpg
Normal file
|
After Width: | Height: | Size: 135 KiB |
BIN
public/assets/img/pages/fussball-mannschaften-640.jpg
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
public/assets/img/pages/jugendfussball-hero-1000.jpg
Normal file
|
After Width: | Height: | Size: 147 KiB |
BIN
public/assets/img/pages/jugendfussball-hero-1440.jpg
Normal file
|
After Width: | Height: | Size: 267 KiB |
BIN
public/assets/img/pages/jugendfussball-hero-640.jpg
Normal file
|
After Width: | Height: | Size: 69 KiB |
BIN
public/assets/img/pages/turnen-1000.jpg
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
public/assets/img/pages/turnen-640.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/assets/img/persons/alex-schuetz.jpg
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
public/assets/img/persons/alexander-hatter.jpg
Normal file
|
After Width: | Height: | Size: 97 KiB |
BIN
public/assets/img/persons/alexander-vogel.jpg
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
public/assets/img/persons/ali-kilic-spieler.jpg
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
public/assets/img/persons/ali-kilic.jpg
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
public/assets/img/persons/bastian-werner.jpg
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
public/assets/img/persons/christian-kaestner.jpg
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
public/assets/img/persons/christian-ohnemueller.jpg
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
public/assets/img/persons/dominik-schulz.jpg
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
public/assets/img/persons/felix-schneider.jpg
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
public/assets/img/persons/hakan-yilmaz.jpg
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
public/assets/img/persons/janick-martin.jpg
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
public/assets/img/persons/marek-szabelski.jpg
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
public/assets/img/persons/platzhalter-spieler.jpg
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
public/assets/img/persons/rustem-hajzeraj.jpg
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
public/assets/img/persons/sascha-zimmerer.jpg
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
public/assets/img/persons/sebastiano-gross.jpg
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
public/assets/img/persons/tim-hofmann.jpg
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
public/assets/img/teams/damen-1000.jpg
Normal file
|
After Width: | Height: | Size: 141 KiB |
BIN
public/assets/img/teams/damen-1600.jpg
Normal file
|
After Width: | Height: | Size: 313 KiB |
BIN
public/assets/img/teams/damen-640.jpg
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
public/assets/img/teams/herren-1-1000.jpg
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
public/assets/img/teams/herren-1-1600.jpg
Normal file
|
After Width: | Height: | Size: 236 KiB |
BIN
public/assets/img/teams/herren-1-640.jpg
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
public/assets/img/teams/herren-1-team-1000.jpg
Normal file
|
After Width: | Height: | Size: 263 KiB |
BIN
public/assets/img/teams/herren-1-team-1600.jpg
Normal file
|
After Width: | Height: | Size: 561 KiB |
BIN
public/assets/img/teams/herren-1-team-640.jpg
Normal file
|
After Width: | Height: | Size: 124 KiB |
BIN
public/assets/img/teams/herren-2-1000.jpg
Normal file
|
After Width: | Height: | Size: 147 KiB |
BIN
public/assets/img/teams/herren-2-1600.jpg
Normal file
|
After Width: | Height: | Size: 312 KiB |
BIN
public/assets/img/teams/herren-2-640.jpg
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
public/assets/img/teams/jugend-c-400.jpg
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
public/assets/img/teams/jugend-c-800.jpg
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
public/assets/img/teams/jugend-d-400.jpg
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
public/assets/img/teams/jugend-d-800.jpg
Normal file
|
After Width: | Height: | Size: 95 KiB |
BIN
public/assets/img/teams/jugend-e-400.jpg
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
public/assets/img/teams/jugend-e-800.jpg
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
public/assets/img/teams/jugend-f-400.jpg
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
public/assets/img/teams/jugend-f-800.jpg
Normal file
|
After Width: | Height: | Size: 123 KiB |
BIN
public/assets/img/teams/jugend-g-400.jpg
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
public/assets/img/teams/jugend-g-800.jpg
Normal file
|
After Width: | Height: | Size: 175 KiB |
75
public/assets/js/contact-slider.js
Normal file
@@ -0,0 +1,75 @@
|
||||
/* Ansprechpartner-Mini-Slider: zeigt bei mehreren Kontakten nur eine Karte und
|
||||
wechselt automatisch durch. Progressive Enhancement:
|
||||
- ohne JS bleiben alle Karten gestapelt sichtbar (kein Info-Verlust),
|
||||
- bei prefers-reduced-motion findet keine Enhancement/Auto-Rotation statt
|
||||
(alle Karten bleiben sichtbar),
|
||||
- Pause bei Hover/Fokus, Punkte zur manuellen Auswahl. */
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var root = document.querySelector('[data-contact-slider]');
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
|
||||
var slides = Array.prototype.slice.call(root.querySelectorAll('[data-contact-slide]'));
|
||||
if (slides.length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bewegungsreduzierung: nicht enhancen → alle Karten bleiben sichtbar.
|
||||
if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
||||
return;
|
||||
}
|
||||
|
||||
var dots = Array.prototype.slice.call(root.querySelectorAll('[data-contact-dot]'));
|
||||
var INTERVAL = 5000;
|
||||
var current = 0;
|
||||
var timer = null;
|
||||
|
||||
function show(n) {
|
||||
current = (n + slides.length) % slides.length;
|
||||
slides.forEach(function (slide, idx) {
|
||||
var active = idx === current;
|
||||
slide.hidden = !active;
|
||||
slide.classList.toggle('is-active', active);
|
||||
});
|
||||
dots.forEach(function (dot, idx) {
|
||||
if (idx === current) {
|
||||
dot.setAttribute('aria-current', 'true');
|
||||
} else {
|
||||
dot.removeAttribute('aria-current');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function stop() {
|
||||
if (timer) {
|
||||
window.clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function start() {
|
||||
stop();
|
||||
timer = window.setInterval(function () {
|
||||
show(current + 1);
|
||||
}, INTERVAL);
|
||||
}
|
||||
|
||||
dots.forEach(function (dot, idx) {
|
||||
dot.addEventListener('click', function () {
|
||||
show(idx);
|
||||
start();
|
||||
});
|
||||
});
|
||||
|
||||
root.addEventListener('mouseenter', stop);
|
||||
root.addEventListener('mouseleave', start);
|
||||
root.addEventListener('focusin', stop);
|
||||
root.addEventListener('focusout', start);
|
||||
|
||||
root.classList.add('contact-slider--ready');
|
||||
show(0);
|
||||
start();
|
||||
})();
|
||||