Turnen: Tab-Navigation für Gruppen mit vielen Trainingszeiten

group-schedule um eine Tab-Leiste (group-tabs) erweitert: bei Disziplinen mit
vielen Gruppen (z. B. Eltern-Kind-Turnen) werden die Gruppen als Tabs gruppiert
statt endlos gestapelt. JS (group-tabs.js) als progressive enhancement — ohne JS
bleiben alle Panels sichtbar. Begleitend Feinschliff an group-card/-list/schedule-row.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGzc6GhWhmLJt1jC2q1SRZ
This commit is contained in:
2026-06-20 20:48:39 +02:00
parent 1552f974aa
commit b40f36aee6
4 changed files with 208 additions and 37 deletions

View File

@@ -135,14 +135,30 @@ $soleNote = $single ? ($groups[0]['note'] ?? '') : '';
<section class="section group-list-section" id="gruppen"> <section class="section group-list-section" id="gruppen">
<div class="container"> <div class="container">
<h2 class="group-list__title"><?= e($groupsTitle) ?></h2> <h2 class="group-list__title"><?= e($groupsTitle) ?></h2>
<div class="group-list <?= $rich ? 'group-list--stacked' : 'group-list--grid' ?>"> <?php if ($rich): ?>
<?php foreach ($groups as $g): ?> <div class="group-tabs" data-group-tabs>
<article class="group-card"> <div class="group-tabs__bar" role="tablist" aria-label="<?= e($groupsTitle) ?>">
<?php foreach ($groups as $i => $g): ?>
<button class="group-tabs__tab" role="tab" type="button"
id="gtab-<?= $i ?>"
aria-controls="gpanel-<?= $i ?>"
aria-selected="<?= $i === 0 ? 'true' : 'false' ?>"
><?= e($g['name']) ?></button>
<?php endforeach; ?>
</div>
<?php foreach ($groups as $i => $g): ?>
<article class="group-card group-tabs__panel" role="tabpanel"
id="gpanel-<?= $i ?>"
aria-labelledby="gtab-<?= $i ?>"
tabindex="0"
>
<div class="group-card__body"> <div class="group-card__body">
<div class="group-card__header">
<h3 class="group-card__name"><?= e($g['name']) ?></h3> <h3 class="group-card__name"><?= e($g['name']) ?></h3>
<?php if (!empty($g['level'])): ?> <?php if (!empty($g['level'])): ?>
<p class="group-card__level"><?= e($g['level']) ?></p> <p class="group-card__level"><?= e($g['level']) ?></p>
<?php endif; ?> <?php endif; ?>
</div>
<?php if (!empty($g['intro'])): ?> <?php if (!empty($g['intro'])): ?>
<p class="group-card__intro"><?= e($g['intro']) ?></p> <p class="group-card__intro"><?= e($g['intro']) ?></p>
<?php endif; ?> <?php endif; ?>
@@ -175,6 +191,29 @@ $soleNote = $single ? ($groups[0]['note'] ?? '') : '';
</article> </article>
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
<?php else: ?>
<div class="group-list group-list--grid">
<?php foreach ($groups as $g): ?>
<article class="group-card">
<div class="group-card__body">
<div class="group-card__header">
<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; ?>
</div>
</div>
<?php if (!empty($g['sessions'])): ?>
<div class="group-card__schedule">
<ul class="schedule-list">
<?php foreach ($g['sessions'] as $s): $renderSession($s); endforeach; ?>
</ul>
</div>
<?php endif; ?>
</article>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php if ($footnote !== ''): ?> <?php if ($footnote !== ''): ?>
<p class="group-list__footnote text-muted"><?= e($footnote) ?></p> <p class="group-list__footnote text-muted"><?= e($footnote) ?></p>
<?php endif; ?> <?php endif; ?>

View File

@@ -15,6 +15,7 @@ $meta = [
'title' => 'Eltern-Kind- & Kinderturnen in Kulmbach', 'title' => 'Eltern-Kind- & Kinderturnen in Kulmbach',
'description' => $d['hero']['text'], 'description' => $d['hero']['text'],
'og_image' => 'img/hero/hero-1600.jpg', 'og_image' => 'img/hero/hero-1600.jpg',
'scripts' => ['group-tabs.js'],
'schema' => page_schema([ 'schema' => page_schema([
['name' => 'Startseite', 'slug' => ''], ['name' => 'Startseite', 'slug' => ''],
['name' => 'Turnen', 'slug' => 'turnen'], ['name' => 'Turnen', 'slug' => 'turnen'],

View File

@@ -2074,6 +2074,72 @@
/* === group-list (Turn-Gruppen einer Disziplin) === */ /* === group-list (Turn-Gruppen einer Disziplin) === */
.group-list__title { .group-list__title {
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
text-align: center;
}
/* === group-tabs (Tab-Navigation für rich Gruppen) === */
.group-tabs__bar {
display: flex;
justify-content: center;
overflow-x: auto;
scrollbar-width: none;
margin-bottom: 2rem;
gap: 0;
}
.group-tabs__bar::-webkit-scrollbar { display: none; }
.group-tabs__tab {
flex-shrink: 0;
padding: 0.7rem 1.4rem;
background: none;
border: none;
box-shadow: inset 0 -2px 0 var(--clr-glass-border);
font-family: var(--ff-body);
font-size: var(--fs-500);
color: var(--clr-text-muted);
cursor: pointer;
white-space: nowrap;
transition: color var(--transition), box-shadow var(--transition);
}
.group-tabs__tab:hover {
color: var(--clr-text);
}
.group-tabs__tab[aria-selected="true"] {
color: var(--clr-text);
box-shadow: inset 0 -2px 0 var(--clr-accent);
}
.group-tabs__tab:focus-visible {
outline: 2px solid var(--clr-accent);
outline-offset: -2px;
}
/* Panel-Karte übernimmt das bestehende group-card--stacked-Layout */
.group-tabs__panel {
display: grid;
grid-template-columns: 1.6fr 1fr;
gap: 3rem;
align-items: start;
padding: 2.25rem 2.5rem;
background: var(--clr-glass-bg);
border: 1px solid var(--clr-glass-border);
border-radius: var(--radius-card);
}
/* display:grid überschreibt den UA-Stil [hidden]{display:none} — explizit korrigieren */
.group-tabs__panel[hidden] {
display: none;
}
@media (max-width: 768px) {
.group-tabs__panel {
grid-template-columns: 1fr;
gap: 1.75rem;
padding: 1.6rem 1.75rem;
}
} }
/* Schlichte Gruppen (z. B. Kurse) als Raster, beschreibende Gruppen gestapelt. */ /* Schlichte Gruppen (z. B. Kurse) als Raster, beschreibende Gruppen gestapelt. */
@@ -2086,7 +2152,7 @@
.group-list--stacked { .group-list--stacked {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1.25rem; gap: 2.75rem;
} }
.group-card { .group-card {
@@ -2100,15 +2166,15 @@
.group-list--stacked .group-card { .group-list--stacked .group-card {
display: grid; display: grid;
grid-template-columns: 1.6fr 1fr; grid-template-columns: 1.6fr 1fr;
gap: 2.5rem; gap: 3rem;
align-items: start; align-items: start;
padding: 2rem 2.25rem; padding: 2.25rem 2.5rem;
} }
@media (max-width: 768px) { @media (max-width: 768px) {
.group-list--stacked .group-card { .group-list--stacked .group-card {
grid-template-columns: 1fr; grid-template-columns: 1fr;
gap: 1.5rem; gap: 1.75rem;
padding: 1.6rem 1.75rem 1.75rem; padding: 1.6rem 1.75rem 1.75rem;
} }
} }
@@ -2118,47 +2184,69 @@
max-width: 64ch; max-width: 64ch;
} }
/* Name links, Level-Chip rechts vertikal zentriert */
.group-card__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
flex-wrap: wrap;
}
.group-card__name { .group-card__name {
font-size: clamp(1.9rem, 1.5rem + 1.2vw, 2.3rem); font-size: clamp(2.2rem, 1.7rem + 1.5vw, 2.8rem);
line-height: var(--lh-heading); line-height: var(--lh-heading);
} }
.group-card__level { .group-card__level {
margin: -0.15rem 0 0; flex-shrink: 0;
font-size: var(--fs-500); padding: 0.15rem 0.5rem;
font-size: var(--fs-400);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.06em; letter-spacing: 0.07em;
color: var(--clr-text-muted); color: var(--clr-text-muted);
border: 1px solid var(--clr-glass-border-strong);
border-radius: 999px;
white-space: nowrap;
} }
/* Intro = Lead-Paragraph, deutlich abgesetzt vom Level-Chip */
.group-card__intro { .group-card__intro {
margin: 0.85rem 0 0; margin: 1.25rem 0 0;
font-size: var(--fs-600);
line-height: 1.65; line-height: 1.65;
color: var(--clr-text);
} }
/* Detail-Abschnitte: Akzent-Randstrich links als visueller Anker */
.group-card__detail-heading { .group-card__detail-heading {
margin: 1.6rem 0 0; margin: 2.25rem 0 0;
/* Body-Schrift (Abel) als Eyebrow — Coolvetica wäre in dieser Größe unleserlich. */ padding-left: 0.7rem;
border-left: 2px solid var(--clr-accent);
font-family: var(--ff-body); font-family: var(--ff-body);
font-size: var(--fs-500); font-size: var(--fs-500);
font-weight: 400; font-weight: 400;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.08em; letter-spacing: 0.07em;
color: var(--clr-accent); color: var(--clr-text);
} }
.group-card__detail-text { .group-card__detail-text {
margin: 0.4rem 0 0; margin: 0.6rem 0 0;
line-height: 1.65; padding-left: 0.7rem;
font-size: var(--fs-600);
line-height: 1.72;
color: var(--clr-text);
} }
.group-card__recommendation { .group-card__recommendation {
margin: 1.6rem 0 0; margin: 2rem 0 0;
padding-top: 1.1rem; padding: 1rem 1rem 0;
border-top: 1px solid var(--clr-glass-border); border-top: 1px solid var(--clr-glass-border);
font-size: var(--fs-500); font-size: var(--fs-500);
font-style: italic; font-style: italic;
line-height: 1.6; line-height: 1.6;
color: var(--clr-text-muted);
} }
/* Trainingszeiten-Panel in der Karte. */ /* Trainingszeiten-Panel in der Karte. */
@@ -2166,20 +2254,23 @@
margin-top: 1.25rem; margin-top: 1.25rem;
} }
.group-list--stacked .group-card__schedule { .group-list--stacked .group-card__schedule,
.group-tabs .group-card__schedule {
margin-top: 0; margin-top: 0;
padding: 1.5rem 1.6rem; padding: 1.75rem;
background: var(--clr-bg); background: var(--clr-bg);
border: 1px solid var(--clr-glass-border); border: 1px solid var(--clr-glass-border);
border-radius: var(--radius-card); border-radius: var(--radius-card);
border-top: 2px solid var(--clr-accent);
} }
.group-card__schedule-title { .group-card__schedule-title {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.5rem;
margin: 0 0 0.85rem; margin: 0 0 1.1rem;
font-size: var(--fs-600); font-size: var(--fs-650);
letter-spacing: 0.01em;
} }
.group-card__schedule-icon { .group-card__schedule-icon {
@@ -2188,7 +2279,9 @@
} }
.group-card__trainers { .group-card__trainers {
margin: 0.85rem 0 0; margin: 1rem 0 0;
padding-top: 0.75rem;
border-top: 1px solid var(--clr-glass-border);
font-size: var(--fs-500); font-size: var(--fs-500);
line-height: 1.5; line-height: 1.5;
} }
@@ -2206,7 +2299,7 @@
} }
.schedule-row { .schedule-row {
padding: 0.7rem 0; padding: 0.85rem 0;
border-bottom: 1px solid var(--clr-glass-border); border-bottom: 1px solid var(--clr-glass-border);
} }
@@ -2215,9 +2308,12 @@
.schedule-row__label { .schedule-row__label {
display: block; display: block;
margin-bottom: 0.2rem; margin-bottom: 0.3rem;
font-size: var(--fs-600); font-size: var(--fs-500);
color: var(--clr-text); text-transform: uppercase;
letter-spacing: 0.07em;
color: var(--clr-accent);
font-family: var(--ff-body);
} }
.schedule-row__when { .schedule-row__when {
@@ -2227,28 +2323,32 @@
gap: 1rem; gap: 1rem;
} }
.schedule-row__day {
font-size: var(--fs-600);
color: var(--clr-text);
}
.schedule-row__time { .schedule-row__time {
font-size: var(--fs-600); font-size: var(--fs-600);
text-align: right; color: var(--clr-text);
white-space: nowrap;
} }
.schedule-row__note { .schedule-row__note {
display: block; display: block;
margin-top: 0.15rem; margin-top: 0.2rem;
font-size: var(--fs-500); font-size: var(--fs-500);
color: var(--clr-text-muted);
} }
.schedule-row__trainers { .schedule-row__trainers {
display: block; display: block;
margin-top: 0.25rem; margin-top: 0.3rem;
font-size: var(--fs-500); font-size: var(--fs-500);
line-height: 1.5; line-height: 1.5;
color: var(--clr-text-muted);
} }
.schedule-row__trainers-label { .schedule-row__trainers-label {
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--clr-text-muted); color: var(--clr-text-muted);
} }

View File

@@ -0,0 +1,31 @@
(function () {
'use strict';
function init(el) {
const buttons = [...el.querySelectorAll('[role="tab"]')];
const panels = [...el.querySelectorAll('[role="tabpanel"]')];
// Alle außer dem ersten verstecken
panels.forEach(function (p, i) { if (i > 0) p.hidden = true; });
buttons.forEach(function (btn, i) {
btn.addEventListener('click', function () { activate(i); });
btn.addEventListener('keydown', function (e) {
if (e.key === 'ArrowRight') { e.preventDefault(); activate((i + 1) % buttons.length); }
if (e.key === 'ArrowLeft') { e.preventDefault(); activate((i - 1 + buttons.length) % buttons.length); }
if (e.key === 'Home') { e.preventDefault(); activate(0); }
if (e.key === 'End') { e.preventDefault(); activate(buttons.length - 1); }
});
});
function activate(index) {
buttons.forEach(function (b, i) {
b.setAttribute('aria-selected', i === index ? 'true' : 'false');
});
panels.forEach(function (p, i) { p.hidden = i !== index; });
buttons[index].focus();
}
}
document.querySelectorAll('[data-group-tabs]').forEach(init);
}());