Vereinshistorie-Seite: kompakter Hero, interaktive Museums-Timeline (Scroll-Reveal, Jahres-Sprungnav), Originalbilder unverändert

routes.php enthält auch die fussball-Route (User-WIP, Seite existiert)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 22:50:15 +02:00
parent 1ee08b932d
commit efa47d4f60
13 changed files with 368 additions and 4 deletions

View File

@@ -4,12 +4,14 @@ declare(strict_types=1);
/** /**
* Vollflächiger Hero mit Hintergrundbild/-video, Titel, Intro und CTAs. * Vollflächiger Hero mit Hintergrundbild/-video, Titel, Intro und CTAs.
* Props: $hero (array aus home.json: title, text, image, video?, ctas, align?) * Props: $hero (array aus home.json: title, text, image, video?, ctas, align?, compact?)
* align-Varianten: 'center' (mittig zentriert), 'bottom-left' (Default). * align-Varianten: 'center' (mittig zentriert), 'bottom-left' (Default).
* compact: true → niedrigere Variante für Unterseiten-Header.
*/ */
$align = in_array($hero['align'] ?? '', ['center', 'bottom-left'], true) ? $hero['align'] : 'bottom-left'; $align = in_array($hero['align'] ?? '', ['center', 'bottom-left'], true) ? $hero['align'] : 'bottom-left';
$compact = !empty($hero['compact']);
?> ?>
<section class="hero<?= $align === 'center' ? ' hero--center' : '' ?>"> <section class="hero<?= $align === 'center' ? ' hero--center' : '' ?><?= $compact ? ' hero--compact' : '' ?>">
<div class="hero__media" aria-hidden="true"> <div class="hero__media" aria-hidden="true">
<?php component('img', ['image' => $hero['image'], 'sizes' => '100vw', 'eager' => true, 'class' => 'hero__img']); ?> <?php component('img', ['image' => $hero['image'], 'sizes' => '100vw', 'eager' => true, 'class' => 'hero__img']); ?>
<?php if (!empty($hero['video'])): ?> <?php if (!empty($hero['video'])): ?>
@@ -19,11 +21,15 @@ $align = in_array($hero['align'] ?? '', ['center', 'bottom-left'], true) ? $hero
</div> </div>
<div class="container hero__content"> <div class="container hero__content">
<h1 class="hero__title"><?= e($hero['title']) ?><?php if (!empty($hero['tagline'])): ?> <span class="hero__tagline"><?= e($hero['tagline']) ?></span><?php endif; ?></h1> <h1 class="hero__title"><?= e($hero['title']) ?><?php if (!empty($hero['tagline'])): ?> <span class="hero__tagline"><?= e($hero['tagline']) ?></span><?php endif; ?></h1>
<?php if (!empty($hero['text'])): ?>
<p class="hero__text"><?= e($hero['text']) ?></p> <p class="hero__text"><?= e($hero['text']) ?></p>
<?php endif; ?>
<?php if (!empty($hero['ctas'])): ?>
<div class="hero__actions"> <div class="hero__actions">
<?php foreach ($hero['ctas'] as $cta): ?> <?php foreach ($hero['ctas'] as $cta): ?>
<?php component('cta', ['cta' => $cta]); ?> <?php component('cta', ['cta' => $cta]); ?>
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
<?php endif; ?>
</div> </div>
</section> </section>

View File

@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
/**
* Interaktive Vereins-Timeline (Museums-Look). Props:
* $events array — Stationen aus data/historie.json (year, text, image{src,width,height,alt})
* Bilder werden bewusst in ORIGINALGRÖSSE eingebunden (keine Varianten) —
* historisches Material bleibt unangetastet.
* timeline.js ergänzt Scroll-Reveal + aktive Jahres-Navigation; ohne JS ist
* alles sichtbar (Initial-Versteckt-Zustand nur unter .js-reveal).
*/
// Anker je Jahr — Duplikate (2x 1972) bekommen einen Suffix, die Jahres-Nav
// führt auf das jeweils erste Vorkommen.
$seen = [];
$prepared = [];
foreach ($events as $event) {
$slug = 'jahr-' . strtolower(preg_replace('/[^a-zA-Z0-9]+/', '-', $event['year']));
$first = !isset($seen[$slug]);
$seen[$slug] = ($seen[$slug] ?? 0) + 1;
$prepared[] = $event + [
'id' => $first ? $slug : $slug . '-' . $seen[$slug],
'nav' => $slug,
];
}
?>
<section class="section timeline-section" data-timeline>
<div class="container">
<nav class="timeline-nav" aria-label="Zeitsprünge">
<ol class="timeline-nav__list">
<?php foreach ($prepared as $event): ?>
<?php if ($event['id'] === $event['nav']): ?>
<li><a href="#<?= e($event['nav']) ?>"><?= e($event['year']) ?></a></li>
<?php endif; ?>
<?php endforeach; ?>
</ol>
</nav>
<ol class="timeline">
<?php foreach ($prepared as $i => $event): ?>
<li class="timeline__event" id="<?= e($event['id']) ?>" data-nav="<?= e($event['nav']) ?>">
<span class="timeline__marker" aria-hidden="true"></span>
<article class="timeline__card">
<h2 class="timeline__year"><?= e($event['year']) ?></h2>
<figure class="timeline__figure">
<img src="<?= e(asset($event['image']['src'])) ?>"
alt="<?= e($event['image']['alt']) ?>"
width="<?= e($event['image']['width']) ?>" height="<?= e($event['image']['height']) ?>"
loading="<?= $i === 0 ? 'eager' : 'lazy' ?>" decoding="async">
<figcaption class="timeline__text"><?= e($event['text']) ?></figcaption>
</figure>
</article>
</li>
<?php endforeach; ?>
</ol>
</div>
</section>

23
app/pages/historie.php Normal file
View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
/**
* Vereinshistorie — schmaler Hero + interaktive Timeline.
* Inhalte: data/historie.json (Single Source of Truth).
*/
$historie = json_load('historie');
$meta = [
'title' => 'Vereinshistorie Der Katzbachtaler Weg',
'description' => 'Erlebe die bewegte Geschichte des TSV 08 Kulmbach von den ersten Spielen an der Bayreuther Straße 1947 bis zur Hans-Rausch-Sportanlage im Katzbachtal.',
'scripts' => ['timeline.js'],
];
component('hero', ['hero' => $historie['hero']]);
?>
<div class="container section--slim">
<p class="timeline-intro"><?= e($historie['intro']) ?></p>
</div>
<?php
component('timeline', ['events' => $historie['events']]);

View File

@@ -10,6 +10,8 @@ declare(strict_types=1);
* Canonical und Sitemap folgen automatisch. * Canonical und Sitemap folgen automatisch.
*/ */
return [ return [
'' => ['file' => 'home.php', 'sitemap' => true], '' => ['file' => 'home.php', 'sitemap' => true],
'404' => ['file' => '404.php', 'sitemap' => false], 'fussball' => ['file' => 'fussball.php', 'sitemap' => true],
'historie' => ['file' => 'historie.php', 'sitemap' => true],
'404' => ['file' => '404.php', 'sitemap' => false],
]; ];

46
data/historie.json Normal file
View File

@@ -0,0 +1,46 @@
{
"hero": {
"align": "center",
"compact": true,
"title": "Vereinshistorie",
"tagline": "Der Katzbachtaler Weg seit 1908",
"image": {
"base": "img/pages/vereinsgelaende",
"widths": [1000, 1600],
"alt": "Luftaufnahme der Hans-Rausch-Sportanlage im Katzbachtal"
}
},
"intro": "Erlebe die bewegte Geschichte des TSV 08 Kulmbach von den ersten Spielen an der Bayreuther Straße bis zur Hans-Rausch-Sportanlage im Katzbachtal.",
"events": [
{
"year": "1947",
"text": "Zu Beginn unserer Zeit im Fußball spielten wir nicht wie heute im Katzbachtal, sondern an der Bayreuther Straße, wo heute beispielsweise der Schuhmücke zu finden ist.",
"image": { "src": "img/historie/1947.png", "width": 519, "height": 352, "alt": "Historische Aufnahme des Sportgeländes an der Bayreuther Straße" }
},
{
"year": "1948",
"text": "Die Bilder zeigen die ersten Fußballspiele an der Bayreuther Straße im Jahre 1948.",
"image": { "src": "img/historie/1948.jpg", "width": 519, "height": 334, "alt": "Fußballspiel an der Bayreuther Straße im Jahr 1948" }
},
{
"year": "1969",
"text": "Im Jahre 1969 wurde die später nach Hans Rausch benannte Sportanlage eingeweiht. Im Bild zu sehen sind Hans Rausch (v. r.) bei Arbeiten an der Sportstätte.",
"image": { "src": "img/historie/1969.png", "width": 1070, "height": 730, "alt": "Hans Rausch bei Bauarbeiten an der Sportanlage 1969" }
},
{
"year": "1972",
"text": "Im Jahr 1972 wurde das Sportheim im Katzbachtal eingeweiht.",
"image": { "src": "img/historie/1972-sportheim.jpg", "width": 519, "height": 272, "alt": "Einweihung des Sportheims im Katzbachtal 1972" }
},
{
"year": "1972",
"text": "Zu diesem Anlass spielten wir damals vor fast 10.000 Zuschauern gegen den FC Bayern München.",
"image": { "src": "img/historie/1972-fcbayern.png", "width": 519, "height": 322, "alt": "Spiel gegen den FC Bayern München vor fast 10.000 Zuschauern, 1972" }
},
{
"year": "Heute",
"text": "Mitten im schönen Katzbachtal liegt nun seit vielen Jahren die Hans-Rausch-Sportanlage des TSV 1908 Kulmbach. Unsere Sportstätte besteht aus einem Großfeldrasenplatz, einem Kleinfeldrasenplatz und einem Hartplatz, der auch im Winter das Training zulässt. Die komplette Sportanlage ist mit Flutlicht ausgestattet.",
"image": { "src": "img/historie/heute.jpg", "width": 519, "height": 360, "alt": "Die Hans-Rausch-Sportanlage im Katzbachtal heute" }
}
]
}

View File

@@ -289,6 +289,184 @@
color: var(--clr-text-muted); color: var(--clr-text-muted);
} }
/* === timeline (Vereinshistorie, Museums-Look) === */
.timeline-intro {
max-width: 60ch;
margin-inline: auto;
padding-block: 2.5rem 0;
text-align: center;
color: var(--clr-text-muted);
}
/* Sprung-Navigation: Jahres-Pills, klebt beim Scrollen oben */
.timeline-nav {
position: sticky;
top: 1rem;
z-index: 5;
display: flex;
justify-content: center;
margin-bottom: 3rem;
}
.timeline-nav__list {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.4rem;
padding: 0.4rem;
background: rgb(10 10 10 / 0.82);
border: 1px solid var(--clr-glass-border);
border-radius: 999px;
backdrop-filter: blur(6px);
}
.timeline-nav a {
display: inline-block;
padding: 0.35em 1em;
font-family: var(--ff-heading);
font-size: var(--fs-500);
letter-spacing: 0.06em;
text-transform: uppercase;
text-decoration: none;
color: var(--clr-text-muted);
border-radius: 999px;
transition: color var(--transition), background var(--transition);
}
.timeline-nav a:hover {
color: var(--clr-text);
}
.timeline-nav a.is-active {
color: var(--clr-text);
background: var(--clr-accent);
}
/* Mittellinie + Stationen */
.timeline {
position: relative;
display: grid;
gap: 4.5rem;
padding-block: 1rem 2rem;
}
.timeline::before {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: 50%;
width: 2px;
transform: translateX(-50%);
background: linear-gradient(180deg,
transparent,
rgb(var(--clr-accent-rgb) / 0.55) 6%,
rgb(var(--clr-accent-rgb) / 0.55) 94%,
transparent);
}
.timeline__event {
position: relative;
width: calc(50% - 3.5rem);
}
.timeline__event:nth-child(even) {
margin-left: auto;
}
.timeline__marker {
position: absolute;
top: 0.6rem;
left: calc(100% + 3.5rem);
width: 16px;
height: 16px;
transform: translateX(-50%);
background: var(--clr-accent);
border-radius: 50%;
box-shadow: 0 0 0 4px rgb(var(--clr-accent-rgb) / 0.25), 0 0 18px rgb(var(--clr-accent-rgb) / 0.5);
}
.timeline__event:nth-child(even) .timeline__marker {
left: -3.5rem;
}
.timeline__year {
font-size: var(--fs-900);
color: var(--clr-accent);
text-shadow: 0 2px 18px rgb(0 0 0 / 0.5);
margin-bottom: 1rem;
}
.timeline__event:nth-child(odd) .timeline__year {
text-align: right;
}
/* Museums-Rahmen: Foto wie ein aufgezogener Print, leicht gedreht */
.timeline__figure {
margin: 0;
padding: 0.9rem 0.9rem 1.1rem;
background: #f3efe4;
border-radius: 3px;
box-shadow: 0 12px 35px rgb(0 0 0 / 0.5);
transform: rotate(-1.2deg);
transition: transform var(--transition);
}
.timeline__event:nth-child(even) .timeline__figure {
transform: rotate(1.2deg);
}
.timeline__event:hover .timeline__figure {
transform: rotate(0deg) scale(1.02);
}
.timeline__figure img {
width: 100%;
height: auto;
display: block;
}
.timeline__text {
padding-top: 0.9rem;
font-size: var(--fs-500);
line-height: 1.45;
color: #3a372f;
}
/* Scroll-Reveal nur mit JS (js-reveal), sonst alles sichtbar */
.js-reveal .timeline__event {
opacity: 0;
translate: 0 28px;
transition: opacity 600ms ease, translate 600ms ease;
}
.js-reveal .timeline__event.is-visible {
opacity: 1;
translate: 0 0;
}
@media (max-width: 768px) {
.timeline::before {
left: 8px;
}
.timeline__event,
.timeline__event:nth-child(even) {
width: auto;
margin-left: 0;
padding-left: 2.5rem;
}
.timeline__marker,
.timeline__event:nth-child(even) .timeline__marker {
left: 8px;
}
.timeline__event:nth-child(odd) .timeline__year {
text-align: left;
}
}
/* === partners === */ /* === partners === */
.partners > .container { .partners > .container {
text-align: center; text-align: center;

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -0,0 +1,52 @@
/* Timeline: Scroll-Reveal der Stationen + aktive Jahres-Navigation.
Progressive Enhancement — ohne JS ist alles sichtbar (js-reveal-Scope). */
(function () {
'use strict';
var section = document.querySelector('[data-timeline]');
if (!section) {
return;
}
var events = section.querySelectorAll('.timeline__event');
var navLinks = section.querySelectorAll('.timeline-nav a');
if (!('IntersectionObserver' in window)) {
return;
}
section.classList.add('js-reveal');
var reveal = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
reveal.unobserve(entry.target);
}
});
}, { threshold: 0.15, rootMargin: '0px 0px -8% 0px' });
// Aktives Jahr in der Sprung-Navigation markieren.
var spy = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (!entry.isIntersecting) {
return;
}
var target = '#' + entry.target.getAttribute('data-nav');
navLinks.forEach(function (link) {
var active = link.getAttribute('href') === target;
link.classList.toggle('is-active', active);
if (active) {
link.setAttribute('aria-current', 'true');
} else {
link.removeAttribute('aria-current');
}
});
});
}, { rootMargin: '-35% 0px -55% 0px' });
events.forEach(function (event) {
reveal.observe(event);
spy.observe(event);
});
})();