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
This commit is contained in:
2026-06-19 08:13:04 +02:00
parent a933b76c4f
commit 3b6f630a72
81 changed files with 2513 additions and 78 deletions

View 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>

View 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>

View 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
View 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>

View 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>

View File

@@ -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">

View File

@@ -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;

View 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&amp;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>

View 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>

View 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>

View 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
View 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',
]);

View 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&amp;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>