Turnabteilung: Übersicht + 5 Disziplin-Unterseiten im Bento-Layout

- Neue Übersichtsseite (Hub: Hero + Disziplin-Kacheln) sowie Unterseiten für
  Erwachsenenturnen, Eltern-Kind-/Kinderturnen, Step Aerobic/Fitness/Pilates,
  Trampolin und Wettkampfturnen. Inhalte 1:1 von der alten Seite, gepflegt in
  der neuen Single-Source data/turnen.json.
- Neue Komponente group-schedule im einheitlichen Bento-Look wie die
  Mannschaftsseiten: Dashboard mit Trainingszeiten, Kontakt-Kachel (mailto an die
  Turnabteilung) und Trainingsort; gestapelte/gerasterte Gruppen-Karten für
  Mehr-Gruppen-Disziplinen, rote Hinweis-Kachel (z. B. Warteliste).
- Routing (app/routes.php) und Navigations-Dropdown (data/navigation.json) für
  Turnen; zugehörige Styles in components.css.
- Themenbezogene, self-hostete Unsplash-Platzhalterbilder für die Kacheln.

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 10:33:03 +02:00
parent d0ff8922f3
commit ea622244db
30 changed files with 964 additions and 1 deletions

View File

@@ -0,0 +1,183 @@
<?php
declare(strict_types=1);
/**
* Turn-Disziplin im einheitlichen Bento-Look (wie die Mannschaftsseiten). Props:
* $location array{venue, street?, zip?, city?} (optional) — Trainingsort + „Route planen"
* $lead string (optional) — verantwortliche Übungsleitung (Akzent-Kachel)
* $notice string (optional) — hervorgehobener Hinweis (z. B. Wartelisten-Status)
* $groups array von Gruppen:
* { name, level?, intro?, details?:[{heading,text}], recommendation?, note?,
* trainers?:[…], sessions:[{ label?, day, time, note?, trainers?:[…] }] }
* $footnote string (optional) — Erläuterung (z. B. zur Sternchen-Kennzeichnung)
* $groupsTitle string (optional, Default 'Unsere Gruppen')
* $anchor string (optional, Default 'training')
*
* Eine Gruppe → Trainingszeiten landen als Bento-Kachel im Dashboard.
* Mehrere → Dashboard zeigt nur Ort/Verantwortlich, darunter eine Gruppen-Sektion.
* Gruppen mit Beschreibung werden gestapelt, schlichte Gruppen als Raster.
*/
$anchor = $anchor ?? 'training';
$groupsTitle = $groupsTitle ?? 'Unsere Gruppen';
$lead = $lead ?? '';
$notice = $notice ?? '';
$footnote = $footnote ?? '';
$groups = $groups ?? [];
$location = $location ?? [];
if ($groups === []) {
return;
}
$club = json_load('club');
$email = $club['email_turnen'] ?? ($club['email'] ?? '');
$single = count($groups) === 1;
$rich = false;
foreach ($groups as $g) {
if (!empty($g['intro']) || !empty($g['details'])) {
$rich = true;
break;
}
}
$hasVenue = !empty($location['venue']);
$mapsQuery = $hasVenue ? rawurlencode(trim(
($location['venue'] ?? '') . ', '
. ($location['street'] ?? '') . ', '
. ($location['zip'] ?? '') . ' ' . ($location['city'] ?? ''),
', '
)) : '';
/** Eine Trainingszeit (Tag/Zeit + optional Label, Notiz, Übungsleitung) ausgeben. */
$renderSession = static function (array $s): void {
?>
<li class="schedule-row">
<?php if (!empty($s['label'])): ?>
<span class="schedule-row__label"><?= e($s['label']) ?></span>
<?php endif; ?>
<span class="schedule-row__when">
<span class="schedule-row__day"><?= e($s['day']) ?></span>
<span class="schedule-row__time"><?= e($s['time']) ?></span>
</span>
<?php if (!empty($s['note'])): ?>
<span class="schedule-row__note text-muted"><?= e($s['note']) ?></span>
<?php endif; ?>
<?php if (!empty($s['trainers'])): ?>
<span class="schedule-row__trainers"><span class="schedule-row__trainers-label">Übungsleitung:</span> <?= e(implode(', ', $s['trainers'])) ?></span>
<?php endif; ?>
</li>
<?php
};
$soleSessions = $single ? ($groups[0]['sessions'] ?? []) : [];
$soleNote = $single ? ($groups[0]['note'] ?? '') : '';
?>
<section class="section turnen-dash-section" id="<?= e($anchor) ?>">
<div class="container">
<div class="turnen-dash<?= $single ? ' turnen-dash--single' : '' ?><?= $notice !== '' ? ' turnen-dash--notice' : '' ?>">
<?php if ($notice !== ''): ?>
<article class="bento__tile turnen-dash__notice">
<?= icon('info-circle', 'turnen-dash__notice-icon') ?>
<p><?= e($notice) ?></p>
</article>
<?php endif; ?>
<?php if ($single): ?>
<article class="bento__tile turnen-dash__training">
<h2 class="bento__heading"><?= icon('clock', 'bento__hicon') ?>Trainingszeiten</h2>
<ul class="bento__sessions">
<?php foreach ($soleSessions as $s): ?>
<li>
<span class="bento__day"><?= e($s['day']) ?></span>
<span class="bento__time"><?= e($s['time']) ?></span>
</li>
<?php endforeach; ?>
</ul>
<?php if ($soleNote !== ''): ?>
<p class="bento__note text-muted"><?= e($soleNote) ?></p>
<?php endif; ?>
<span class="bento__deco" aria-hidden="true"><?= icon('clock') ?></span>
</article>
<?php endif; ?>
<?php if ($lead !== '' || $email !== ''): ?>
<article class="bento__tile bento__tile--accent turnen-dash__contact">
<span class="bento__stat-label">Kontakt</span>
<?php if ($lead !== ''): ?>
<span class="bento__stat-value"><?= e($lead) ?></span>
<?php endif; ?>
<?php if ($email !== ''): ?>
<a class="turnen-dash__mail" href="mailto:<?= e($email) ?>"><?= icon('envelope', 'turnen-dash__mail-icon') ?>E-Mail schreiben</a>
<?php endif; ?>
</article>
<?php endif; ?>
<?php if ($hasVenue): ?>
<article class="bento__tile turnen-dash__venue">
<div class="turnen-dash__venue-main">
<h2 class="bento__heading"><?= icon('geo-alt', 'bento__hicon') ?>Trainingsort</h2>
<p class="bento__address">
<strong><?= e($location['venue']) ?></strong>
<?php if (!empty($location['street'])): ?> · <?= e($location['street']) ?><?php endif; ?>
<?php if (!empty($location['zip']) || !empty($location['city'])): ?>, <?= e(trim(($location['zip'] ?? '') . ' ' . ($location['city'] ?? ''))) ?><?php endif; ?>
</p>
</div>
<a class="btn btn--outline turnen-dash__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>
<?php if (!$single): ?>
<section class="section group-list-section" id="gruppen">
<div class="container">
<h2 class="group-list__title"><?= e($groupsTitle) ?></h2>
<div class="group-list <?= $rich ? 'group-list--stacked' : 'group-list--grid' ?>">
<?php foreach ($groups as $g): ?>
<article class="group-card">
<div class="group-card__body">
<h3 class="group-card__name"><?= e($g['name']) ?></h3>
<?php if (!empty($g['level'])): ?>
<p class="group-card__level"><?= e($g['level']) ?></p>
<?php endif; ?>
<?php if (!empty($g['intro'])): ?>
<p class="group-card__intro"><?= e($g['intro']) ?></p>
<?php endif; ?>
<?php foreach ($g['details'] ?? [] as $det): ?>
<?php if (!empty($det['heading'])): ?>
<h4 class="group-card__detail-heading"><?= e($det['heading']) ?></h4>
<?php endif; ?>
<?php if (!empty($det['text'])): ?>
<p class="group-card__detail-text"><?= e($det['text']) ?></p>
<?php endif; ?>
<?php endforeach; ?>
<?php if (!empty($g['recommendation'])): ?>
<p class="group-card__recommendation text-muted"><?= e($g['recommendation']) ?></p>
<?php endif; ?>
</div>
<?php if (!empty($g['sessions'])): ?>
<div class="group-card__schedule">
<p class="group-card__schedule-title"><?= icon('clock', 'group-card__schedule-icon') ?>Trainingszeiten</p>
<ul class="schedule-list">
<?php foreach ($g['sessions'] as $s): $renderSession($s); endforeach; ?>
</ul>
<?php if (!empty($g['trainers'])): ?>
<p class="group-card__trainers"><span class="schedule-row__trainers-label">Übungsleitung:</span> <?= e(implode(', ', $g['trainers'])) ?></p>
<?php endif; ?>
<?php if (!empty($g['note'])): ?>
<p class="group-card__note text-muted"><?= e($g['note']) ?></p>
<?php endif; ?>
</div>
<?php endif; ?>
</article>
<?php endforeach; ?>
</div>
<?php if ($footnote !== ''): ?>
<p class="group-list__footnote text-muted"><?= e($footnote) ?></p>
<?php endif; ?>
</div>
</section>
<?php endif; ?>

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
/**
* Turnen → Eltern-Kind- & Kinderturnen. Inhalt: data/turnen.json (disciplines.eltern-kind-turnen).
* Bündelt Eltern-Kind-Gruppen und die nach Alter gestaffelten Kinderturn-Gruppen.
* Struktur: Hero → Gruppen/Trainingszeiten → CTA.
*/
$slug = 'turnen/eltern-kind-turnen';
$data = json_load('turnen');
$d = $data['disciplines']['eltern-kind-turnen'];
$meta = [
'title' => 'Eltern-Kind- & Kinderturnen in Kulmbach',
'description' => $d['hero']['text'],
'og_image' => 'img/hero/hero-1600.jpg',
'schema' => page_schema([
['name' => 'Startseite', 'slug' => ''],
['name' => 'Turnen', 'slug' => 'turnen'],
['name' => 'Eltern-Kind- & Kinderturnen', 'slug' => $slug],
]),
];
component('hero', ['hero' => $d['hero']]);
component('group-schedule', [
'location' => $d['location'] ?? [],
'lead' => $d['lead'] ?? '',
'notice' => $d['notice'] ?? '',
'groups' => $d['groups'] ?? [],
'footnote' => $data['footnote'] ?? '',
]);

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
/**
* Turnen → Erwachsenenturnen. Inhalt: data/turnen.json (disciplines.erwachsenenturnen).
* Struktur: Hero → Gruppen/Trainingszeiten → CTA.
*/
$slug = 'turnen/erwachsenenturnen';
$d = json_load('turnen')['disciplines']['erwachsenenturnen'];
$meta = [
'title' => 'Erwachsenenturnen in Kulmbach',
'description' => $d['hero']['text'],
'og_image' => 'img/hero/hero-1600.jpg',
'schema' => page_schema([
['name' => 'Startseite', 'slug' => ''],
['name' => 'Turnen', 'slug' => 'turnen'],
['name' => 'Erwachsenenturnen', 'slug' => $slug],
]),
];
component('hero', ['hero' => $d['hero']]);
component('group-schedule', [
'location' => $d['location'] ?? [],
'lead' => $d['lead'] ?? '',
'groups' => $d['groups'] ?? [],
]);

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/**
* Turnen → Step Aerobic, Fitness & Pilates. Inhalt: data/turnen.json
* (disciplines.step-aerobic-fitness-pilates). Struktur: Hero → Kurse/Zeiten → CTA.
*/
$slug = 'turnen/step-aerobic-fitness-pilates';
$d = json_load('turnen')['disciplines']['step-aerobic-fitness-pilates'];
$meta = [
'title' => 'Step Aerobic, Fitness & Pilates in Kulmbach',
'description' => $d['hero']['text'],
'og_image' => 'img/hero/hero-1600.jpg',
'schema' => page_schema([
['name' => 'Startseite', 'slug' => ''],
['name' => 'Turnen', 'slug' => 'turnen'],
['name' => 'Step Aerobic, Fitness & Pilates', 'slug' => $slug],
]),
];
component('hero', ['hero' => $d['hero']]);
component('group-schedule', [
'groupsTitle' => 'Unsere Kurse',
'location' => $d['location'] ?? [],
'lead' => $d['lead'] ?? '',
'groups' => $d['groups'] ?? [],
]);

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
/**
* Turnen → Trampolinturnen. Inhalt: data/turnen.json (disciplines.trampolin).
* Struktur: Hero → Gruppen/Trainingszeiten → CTA.
*/
$slug = 'turnen/trampolin';
$d = json_load('turnen')['disciplines']['trampolin'];
$meta = [
'title' => 'Trampolinturnen in Kulmbach',
'description' => $d['hero']['text'],
'og_image' => 'img/hero/hero-1600.jpg',
'schema' => page_schema([
['name' => 'Startseite', 'slug' => ''],
['name' => 'Turnen', 'slug' => 'turnen'],
['name' => 'Trampolinturnen', 'slug' => $slug],
]),
];
component('hero', ['hero' => $d['hero']]);
component('group-schedule', [
'location' => $d['location'] ?? [],
'lead' => $d['lead'] ?? '',
'groups' => $d['groups'] ?? [],
]);

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
/**
* Turnen → Wettkampfturnen. Inhalt: data/turnen.json (disciplines.wettkampfturnen).
* Struktur: Hero → Gruppen/Trainingszeiten → CTA.
*/
$slug = 'turnen/wettkampfturnen';
$d = json_load('turnen')['disciplines']['wettkampfturnen'];
$meta = [
'title' => 'Wettkampfturnen in Kulmbach',
'description' => $d['hero']['text'],
'og_image' => 'img/hero/hero-1600.jpg',
'schema' => page_schema([
['name' => 'Startseite', 'slug' => ''],
['name' => 'Turnen', 'slug' => 'turnen'],
['name' => 'Wettkampfturnen', 'slug' => $slug],
]),
];
component('hero', ['hero' => $d['hero']]);
component('group-schedule', [
'location' => $d['location'] ?? [],
'lead' => $d['lead'] ?? '',
'groups' => $d['groups'] ?? [],
]);

38
app/pages/turnen.php Normal file
View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
/**
* Turn-Übersichtsseite (reiner Hub). Inhalt: data/turnen.json.
* Struktur: Hero → Disziplin-Karten. Keine weiteren Sektionen — die Detailinhalte
* (Beiträge, FAQ, Trainingszeiten) leben auf den Disziplin-Unterseiten.
*/
$page = json_load('turnen')['overview'];
// Heros nutzen das Vereins-Default-Bild; nur die Übersichts-Kacheln zeigen die
// (vorläufigen) themenbezogenen Unsplash-Platzhalter aus public/assets/img/pages/.
$cards = [
['name' => 'Erwachsenenturnen', 'slug' => 'turnen/erwachsenenturnen', 'category' => 'Mäximum Gym', 'image' => ['base' => 'img/pages/turnen-erwachsenenturnen', 'widths' => [640, 1000, 1600], 'alt' => 'Erwachsenenturnen beim TSV 08 Kulmbach']],
['name' => 'Eltern-Kind- & Kinderturnen', 'slug' => 'turnen/eltern-kind-turnen', 'category' => 'ab ca. 2 Jahren', 'image' => ['base' => 'img/pages/turnen-eltern-kind', 'widths' => [640, 1000, 1600], 'alt' => 'Eltern-Kind- und Kinderturnen beim TSV 08 Kulmbach']],
['name' => 'Step Aerobic, Fitness & Pilates', 'slug' => 'turnen/step-aerobic-fitness-pilates', 'category' => 'Max-Hundt-Schule', 'image' => ['base' => 'img/pages/turnen-step-aerobic', 'widths' => [640, 1000, 1600], 'alt' => 'Step Aerobic, Fitness und Pilates beim TSV 08 Kulmbach']],
['name' => 'Trampolinturnen', 'slug' => 'turnen/trampolin', 'category' => 'Carl-von-Linde-Realschule', 'image' => ['base' => 'img/pages/turnen-trampolin', 'widths' => [640, 1000, 1600], 'alt' => 'Trampolinturnen beim TSV 08 Kulmbach']],
['name' => 'Wettkampfturnen', 'slug' => 'turnen/wettkampfturnen', 'category' => 'Leistungssport', 'image' => ['base' => 'img/pages/turnen-wettkampf', 'widths' => [640, 1000, 1600], 'alt' => 'Wettkampfturnen beim TSV 08 Kulmbach']],
];
$meta = [
'title' => 'Turnen in Kulmbach',
'description' => 'Die Turnabteilung des TSV 08 Kulmbach: Eltern-Kind- und Kinderturnen, Erwachsenenturnen, Step Aerobic, Fitness und Pilates, Trampolin- und Wettkampfturnen.',
'og_image' => 'img/hero/hero-1600.jpg',
'schema' => page_schema([
['name' => 'Startseite', 'slug' => ''],
['name' => 'Turnen', 'slug' => 'turnen'],
]),
];
component('hero', ['hero' => $page['hero']]);
component('team-cards', [
'teams' => $cards,
'title' => 'Unsere Turngruppen',
'title_hidden' => true,
'anchor' => 'gruppen',
]);

View File

@@ -16,6 +16,12 @@ return [
'fussball/zweite-mannschaft' => ['file' => 'team-zwei.php', 'sitemap' => true],
'fussball/damen' => ['file' => 'team-damen.php', 'sitemap' => true],
'jugendfussball' => ['file' => 'jugendfussball.php', 'sitemap' => true],
'turnen' => ['file' => 'turnen.php', 'sitemap' => true],
'turnen/erwachsenenturnen' => ['file' => 'turnen-erwachsenenturnen.php', 'sitemap' => true],
'turnen/eltern-kind-turnen' => ['file' => 'turnen-eltern-kind.php', 'sitemap' => true],
'turnen/step-aerobic-fitness-pilates' => ['file' => 'turnen-step-aerobic.php', 'sitemap' => true],
'turnen/trampolin' => ['file' => 'turnen-trampolin.php', 'sitemap' => true],
'turnen/wettkampfturnen' => ['file' => 'turnen-wettkampfturnen.php', 'sitemap' => true],
'verein' => ['file' => 'verein.php', 'sitemap' => true],
'historie' => ['file' => 'historie.php', 'sitemap' => true],
'impressum' => ['file' => 'impressum.php', 'sitemap' => true],