Matchcenter: „Nächste Spiele" als Band statt Autoplay-Slider

Der Bereich wirkte deplatziert: der Track war auf 620px begrenzt und zeigte
eine Folie, wodurch die Karte auf breiten Fenstern in ~840px Totraum schwebte;
dazu derselbe Hintergrund wie der Hero direkt darüber, kein Akzent, und ein
einsames „:" zwischen den Wappen, das wie ein fehlendes Ergebnis aussah.

Neu als abgesetztes Band (.section--sunken, nutzt endlich das dafür
vorgesehene, bisher unbenutzte Token --clr-bg-deep) mit roter Oberkante:
links der Aufmacher (match-feature), rechts die folgenden Termine als
verlinkte match-row-Zeilen ohne Kartenfläche.

Der Aufmacher ist die große Variante der Terminzeile — Heim über Gast,
linksbündig, gleiche Struktur wie die Zeilen daneben. Bewusst nur drei
typografische Rollen (Meta-Zeile · Vereinsnamen in Coolvetica · Anstoßzeile
mit Countdown) statt der acht der ersten Fassung, ohne Badges, Icons oder
Button. Die eigene Mannschaft wird über Helligkeit markiert, nicht über
font-weight — Coolvetica hat nur einen Schnitt, bold wäre synthetisch.

match-row bekommt optionale $href/$meta-Props; Raster und Innenabstand liegen
jetzt im __link-Wrapper, damit als Link die ganze Zeilenfläche klickbar ist.
Neue Helper match_when() (ISO + deutscher Countdown, explizit Europe/Berlin,
weil PHP global auf UTC läuft) und match_competition_label().

match-slider.php und match-card.php entfallen — Letztere wurde
ausschließlich vom Slider genutzt. carousel.js treibt damit nur noch den
Banner-Slider der Startseite und wird im Matchcenter nicht mehr geladen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-27 00:39:57 +02:00
parent bee19a2ddc
commit 66c402d9f7
10 changed files with 461 additions and 401 deletions

View File

@@ -1,47 +0,0 @@
<?php
declare(strict_types=1);
/**
* Einzelnes Spiel (Begegnung) als Karte. Props:
* $match Eintrag aus data/matchcenter.json (next_matches[]/last_results[]/upcoming[])
* $variant '' (Default) | 'hero' (groß, für den Slider)
* Aufbau (kicker-Stil): Datum oben mittig, darunter Heim | Ergebnis/Anstoß | Gast,
* die Zeit mittig unter dem Ergebnis. Die Heimmannschaft steht immer links — daher
* kein zusätzlicher Heim/Auswärts-Hinweis. Die eigene Mannschaft wird hervorgehoben.
*/
$variant = $variant ?? '';
$m = $match;
$hasResult = isset($m['goals_home'], $m['goals_away']);
$isHome = !empty($m['is_home']);
$classes = 'match-card';
if ($variant !== '') {
$classes .= ' match-card--' . preg_replace('/[^a-z0-9-]+/', '', strtolower($variant));
}
?>
<article class="<?= $classes ?>">
<?php if (!empty($m['date_display'])): ?>
<p class="match-card__date"><?= icon('calendar-event', 'match-card__date-icon') ?><?= e($m['date_display']) ?></p>
<?php endif; ?>
<div class="match-card__fixture">
<div class="match-card__side<?= $isHome ? ' is-own' : '' ?>">
<?php component('crest', ['src' => $m['home_crest'] ?? null, 'name' => $m['home'] ?? '']); ?>
<span class="match-card__club"><?= e($m['home'] ?? '') ?></span>
</div>
<div class="match-card__center">
<?php if ($hasResult): ?>
<span class="match-card__score"><?= (int) $m['goals_home'] ?><span class="match-card__colon">:</span><?= (int) $m['goals_away'] ?></span>
<?php else: ?>
<span class="match-card__vs" aria-label="gegen">:</span>
<?php endif; ?>
<?php if (!empty($m['time_display'])): ?>
<span class="match-card__time"><?= e($m['time_display']) ?></span>
<?php endif; ?>
</div>
<div class="match-card__side<?= !$isHome ? ' is-own' : '' ?>">
<?php component('crest', ['src' => $m['away_crest'] ?? null, 'name' => $m['away'] ?? '']); ?>
<span class="match-card__club"><?= e($m['away'] ?? '') ?></span>
</div>
</div>
</article>

View File

@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
/**
* Aufmacher des „Nächste Spiele"-Bands: das zeitlich nächste Spiel — strukturgleich
* mit match-row (Heim über Gast, linksbündig), nur groß. Ein Bauprinzip für das
* ganze Band; die Wirkung kommt aus der Größe, nicht aus Badges oder Icons.
* Drei Schriftrollen: Meta-Zeile (Abel, gesperrt) · Vereinsnamen (Coolvetica) ·
* Anstoßzeile (Abel). Props:
* $match Eintrag aus data/matchcenter.json → upcoming[]
* $team_name Name der eigenen Mannschaft („1. Mannschaft")
* $league Liganame der Mannschaft (für die Meta-Zeile)
* $href Sprungmarke zur Mannschafts-Sektion (optional; macht den
* Mannschaftsnamen in der Meta-Zeile zum Link)
*/
$m = $match;
$isHome = !empty($m['is_home']);
$teamName = (string) ($team_name ?? '');
$league = (string) ($league ?? '');
$href = $href ?? null;
$when = match_when((string) ($m['kickoff'] ?? ''));
// Meta-Zeile: Mannschaft · Wettbewerb · Heim/Auswärts (leere Teile fallen weg).
$competition = match_competition_label((string) ($m['competition'] ?? ''), $league);
$meta = array_values(array_filter([
$teamName,
$competition,
$isHome ? 'Heimspiel' : 'Auswärtsspiel',
], static fn (string $part): bool => $part !== ''));
$datetime = trim(($m['date_display'] ?? '') . ' · ' . ($m['time_display'] ?? ''), ' ·');
?>
<article class="match-feature">
<?php if ($meta !== []): ?>
<p class="match-feature__meta">
<?php foreach ($meta as $i => $part): ?>
<?php if ($i > 0): ?>
<span class="match-feature__sep" aria-hidden="true">·</span>
<?php endif; ?>
<?php if ($i === 0 && $href !== null): ?>
<a href="<?= e($href) ?>"><?= e($part) ?></a>
<?php else: ?>
<span><?= e($part) ?></span>
<?php endif; ?>
<?php endforeach; ?>
</p>
<?php endif; ?>
<div class="match-feature__fixture">
<span class="match-feature__side<?= $isHome ? ' is-own' : '' ?>">
<?php component('crest', ['src' => $m['home_crest'] ?? null, 'name' => $m['home'] ?? '']); ?>
<span class="match-feature__club"><?= e($m['home'] ?? '') ?></span>
</span>
<span class="match-feature__side<?= !$isHome ? ' is-own' : '' ?>">
<?php component('crest', ['src' => $m['away_crest'] ?? null, 'name' => $m['away'] ?? '']); ?>
<span class="match-feature__club"><?= e($m['away'] ?? '') ?></span>
</span>
</div>
<p class="match-feature__when">
<?php if ($when['iso'] !== ''): ?>
<time datetime="<?= e($when['iso']) ?>"><?= e($datetime) ?></time>
<?php else: ?>
<span><?= e($datetime) ?></span>
<?php endif; ?>
<?php if ($when['countdown'] !== ''): ?>
<span class="match-feature__sep" aria-hidden="true">·</span>
<span class="match-feature__countdown" data-countdown="<?= e($when['iso']) ?>"><?= e($when['countdown']) ?></span>
<?php endif; ?>
</p>
</article>

View File

@@ -3,36 +3,60 @@
declare(strict_types=1);
/**
* Kompakte Spielzeile für die Matchcenter-Seitenspalte (kicker-Stil): Datum oben,
* darunter Heim über Gast (Wappen + Name, lange Namen werden mit … gekürzt), rechts
* das Ergebnis bzw. — bei anstehenden Spielen — die Anstoßzeit. Heim steht immer
* oben/links. Props: $match (Eintrag aus data/matchcenter.json).
* Kompakte Spielzeile (kicker-Stil): Datum oben, darunter Heim über Gast (Wappen +
* Name, lange Namen werden mit … gekürzt), rechts das Ergebnis bzw. — bei
* anstehenden Spielen — die Anstoßzeit. Heim steht immer oben. Props:
* $match Eintrag aus data/matchcenter.json (next_matches[]/last_results[]/upcoming[])
* $meta optionale Angabe neben dem Datum (im „Nächste Spiele"-Band: die Mannschaft)
* $href optionales Ziel — dann ist die ganze Zeile ein Link
* Der innere __link-Wrapper trägt Raster und Innenabstand, damit als Link die
* komplette Zeilenfläche klickbar ist (und ohne Link nichts anders aussieht).
*/
$m = $match;
$hasResult = isset($m['goals_home'], $m['goals_away']);
$isHome = !empty($m['is_home']);
$meta = (string) ($meta ?? '');
$href = $href ?? null;
?>
<li class="match-row">
<?php if (!empty($m['date_display'])): ?>
<span class="match-row__date"><?= e($m['date_display']) ?></span>
<?php endif; ?>
<div class="match-row__teams">
<span class="match-row__team<?= $isHome ? ' is-own' : '' ?>">
<?php component('crest', ['src' => $m['home_crest'] ?? null, 'name' => $m['home'] ?? '']); ?>
<span class="match-row__name" title="<?= e($m['home'] ?? '') ?>"><?= e($m['home'] ?? '') ?></span>
</span>
<span class="match-row__team<?= !$isHome ? ' is-own' : '' ?>">
<?php component('crest', ['src' => $m['away_crest'] ?? null, 'name' => $m['away'] ?? '']); ?>
<span class="match-row__name" title="<?= e($m['away'] ?? '') ?>"><?= e($m['away'] ?? '') ?></span>
</span>
</div>
<span class="match-row__outcome">
<?php if ($hasResult): ?>
<span class="match-row__score"><?= (int) $m['goals_home'] ?>:<?= (int) $m['goals_away'] ?></span>
<?php elseif (!empty($m['time_display'])): ?>
<span class="match-row__time"><?= e($m['time_display']) ?></span>
<?php if ($href !== null): ?>
<a class="match-row__link" href="<?= e($href) ?>">
<?php else: ?>
<span class="match-row__time" aria-hidden="true"></span>
<div class="match-row__link">
<?php endif; ?>
<?php if (!empty($m['date_display']) || $meta !== ''): ?>
<span class="match-row__head">
<?php if (!empty($m['date_display'])): ?>
<span class="match-row__date"><?= e($m['date_display']) ?></span>
<?php endif; ?>
<?php if ($meta !== ''): ?>
<span class="match-row__meta"><?= e($meta) ?></span>
<?php endif; ?>
</span>
<?php endif; ?>
<span class="match-row__teams">
<span class="match-row__team<?= $isHome ? ' is-own' : '' ?>">
<?php component('crest', ['src' => $m['home_crest'] ?? null, 'name' => $m['home'] ?? '']); ?>
<span class="match-row__name" title="<?= e($m['home'] ?? '') ?>"><?= e($m['home'] ?? '') ?></span>
</span>
<span class="match-row__team<?= !$isHome ? ' is-own' : '' ?>">
<?php component('crest', ['src' => $m['away_crest'] ?? null, 'name' => $m['away'] ?? '']); ?>
<span class="match-row__name" title="<?= e($m['away'] ?? '') ?>"><?= e($m['away'] ?? '') ?></span>
</span>
</span>
<span class="match-row__outcome">
<?php if ($hasResult): ?>
<span class="match-row__score"><?= (int) $m['goals_home'] ?>:<?= (int) $m['goals_away'] ?></span>
<?php elseif (!empty($m['time_display'])): ?>
<span class="match-row__time"><?= e($m['time_display']) ?></span>
<?php else: ?>
<span class="match-row__time" aria-hidden="true"></span>
<?php endif; ?>
</span>
<?php if ($href !== null): ?>
<span class="visually-hidden"> zur Mannschaft</span>
</a>
<?php else: ?>
</div>
<?php endif; ?>
</span>
</li>

View File

@@ -1,45 +0,0 @@
<?php
declare(strict_types=1);
/**
* „Nächste Spiele"-Slider (teamübergreifend). Props: $matches (data/matchcenter.json
* → upcoming[]). Spiegelt die Mechanik des banner-slider (Crossfade, Pause/Play,
* prefers-reduced-motion) über den generischen Antrieb assets/js/carousel.js.
* Ohne JS ist nur die erste Folie sichtbar. Leer → nichts rendern (Off-Season).
*/
$matches = $matches ?? [];
if ($matches === []) {
return;
}
$total = count($matches);
?>
<section class="section match-slider" id="naechste-spiele" aria-roledescription="Karussell" aria-label="Nächste Spiele unserer Mannschaften" data-carousel>
<div class="container">
<h2>Nächste Spiele</h2>
<ul class="match-slider__track" id="match-slider-track" data-carousel-track aria-live="off">
<?php foreach ($matches as $i => $m): ?>
<li class="match-slider__slide" data-carousel-slide role="group" aria-roledescription="Folie" aria-label="<?= ($i + 1) ?> von <?= $total ?>"<?= $i === 0 ? '' : ' hidden' ?>>
<p class="match-slider__team"><?= e($m['team_name'] ?? '') ?></p>
<?php component('match-card', ['match' => $m, 'variant' => 'hero']); ?>
</li>
<?php endforeach; ?>
</ul>
<?php if ($total > 1): ?>
<div class="match-slider__controls" data-carousel-controls hidden>
<button class="match-slider__toggle" type="button" data-carousel-toggle aria-pressed="false" aria-controls="match-slider-track" hidden>
<?= icon('pause-fill', 'match-slider__toggle-icon match-slider__toggle-icon--pause') ?>
<?= icon('play-fill', 'match-slider__toggle-icon match-slider__toggle-icon--play') ?>
<span class="visually-hidden" data-carousel-toggle-label>Automatischen Wechsel pausieren</span>
</button>
<div class="match-slider__dots" role="group" aria-label="Spiel wählen">
<?php foreach ($matches as $i => $m): ?>
<button class="match-slider__dot" type="button" data-carousel-dot aria-label="Spiel <?= ($i + 1) ?> von <?= $total ?>: <?= e(($m['home'] ?? '') . ' gegen ' . ($m['away'] ?? '')) ?>">
<span class="match-slider__progress" data-carousel-progress aria-hidden="true"></span>
</button>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
</section>

View File

@@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
/**
* „Nächste Spiele"-Band (teamübergreifend): abgesetzte dunklere Fläche mit roter
* Oberkante, links der Aufmacher (das zeitlich nächste Spiel aller Mannschaften),
* rechts die folgenden Termine als kompakte, anklickbare Zeilen. Props:
* $matches data/matchcenter.json → upcoming[] (chronologisch)
* $teams data/matchcenter.json → teams[] (liefert Liganame + Sprungmarke)
* Leer → nichts rendern.
*/
$matches = $matches ?? [];
$teams = $teams ?? [];
if ($matches === []) {
return;
}
// Liganame und Sprungmarke je Mannschaft vorbereiten, damit die Karten nicht
// selbst in den Teams suchen müssen. Anker wie in matchcenter-section.php.
$leagues = [];
$anchors = [];
foreach ($teams as $t) {
$key = (string) ($t['key'] ?? '');
if ($key === '') {
continue;
}
$leagues[$key] = (string) ($t['league'] ?? '');
$anchors[$key] = '#team-' . preg_replace('/[^a-z0-9-]+/', '', strtolower($key));
}
$feature = array_shift($matches);
$featureKey = (string) ($feature['team_key'] ?? '');
?>
<section class="section section--sunken next-matches" id="naechste-spiele">
<div class="container">
<h2 class="next-matches__title">Nächste Spiele</h2>
<div class="next-matches__grid">
<?php component('match-feature', [
'match' => $feature,
'team_name' => $feature['team_name'] ?? '',
'league' => $leagues[$featureKey] ?? '',
'href' => $anchors[$featureKey] ?? null,
]); ?>
<?php if ($matches !== []): ?>
<div class="next-matches__list">
<h3 class="next-matches__list-title">Danach</h3>
<ul class="match-list match-list--plain">
<?php foreach ($matches as $m): ?>
<?php component('match-row', [
'match' => $m,
'meta' => $m['team_name'] ?? '',
'href' => $anchors[(string) ($m['team_key'] ?? '')] ?? null,
]); ?>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
</div>
</div>
</section>