Matchcenter: Spielplan, Ergebnisse & Tabellen (eigene Seite statt BFV-Widget)
Neue Unterseite /matchcenter ersetzt die alten BFV-Widget-Embeds (verstoßen
gegen CSP) durch eine self-hostete Lösung: Cron-Scraper holt die Daten aus der
öffentlichen BFV-Widget-JSON-API, schreibt data/matchcenter.json und lädt die
Vereinswappen lokal — Besucher kontaktieren nur unsere Domain.
- bin/matchcenter-sync.php: CLI/Cron-Scraper (Muster wie instagram-sync), JSON-API
team/{id}/matches + competition/{compoundId}/table, Wappen via getLogo lokal,
atomarer Write, Fail-safe. Auto-Saison: Tabellen-compoundId wird aus der
Matches-API abgeleitet (team_id ist saison-stabil). Per-Team-Cache-Fallback bei
Fetch-Fehlern, damit Sektionen nie leeren.
- Seite app/pages/matchcenter.php + Route + Nav-Eintrag (unter Fußball).
- Komponenten: match-slider (nächste Spiele), match-card, match-row, league-table
(mit Auf-/Abstiegszonen), matchcenter-section (kicker-Layout: Tabelle 2/3 +
Spiele 1/3), matchcenter-empty, crest (weißes Chip + Initialen-Fallback).
- carousel.js: banner.js zum generischen [data-carousel]-Antrieb verallgemeinert
(Banner- und Match-Slider teilen ihn); crest.js: Logo-Fallback im Browser.
- helpers.php: sportsevent_nodes() für SportsEvent-JSON-LD.
- Icons calendar-event; config.example + .gitignore + CLAUDE.md ergänzt.
- Responsive geprüft (kein Seiten-Overflow 360–1280, Tabelle scrollt in der Karte).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0192vTfGmGpoWeyUse4TuQFj
This commit is contained in:
@@ -11,8 +11,8 @@ declare(strict_types=1);
|
||||
* "cta_secondary" (zweiter Button als Outline), "logo_light" (helle Logo-Platte
|
||||
* für dunkle Firmenlogos), "accent" (CSS-Farbe; überschreibt das Vereins-Rot
|
||||
* der Buttons für diese Slide, z. B. Sponsor-CI).
|
||||
* Auto-Rotation + Fortschritts-Indikatoren übernimmt assets/js/banner.js
|
||||
* (pausiert bei Hover/Fokus, respektiert prefers-reduced-motion).
|
||||
* Auto-Rotation + Fortschritts-Indikatoren übernimmt der generische Antrieb
|
||||
* assets/js/carousel.js (pausiert bei Hover/Fokus, respektiert prefers-reduced-motion).
|
||||
* Ohne JS: nur der erste Banner ist sichtbar (Graceful Degradation).
|
||||
*/
|
||||
$bannerData = json_load('banners');
|
||||
@@ -24,7 +24,7 @@ $heading = $bannerData['heading'] ?? null;
|
||||
$subline = $bannerData['subline'] ?? null;
|
||||
$total = count($banners);
|
||||
?>
|
||||
<section class="section ad-banner" id="anzeigen" aria-roledescription="Karussell" aria-label="Anzeigen unserer Sponsoren und Partner" data-banner>
|
||||
<section class="section ad-banner" id="anzeigen" aria-roledescription="Karussell" aria-label="Anzeigen unserer Sponsoren und Partner" data-carousel>
|
||||
<div class="container">
|
||||
<?php if ($heading !== null): ?>
|
||||
<h2><?= e($heading) ?></h2>
|
||||
@@ -34,7 +34,7 @@ $total = count($banners);
|
||||
<?php if ($subline !== null): ?>
|
||||
<p class="text-muted ad-banner__subline"><?= e($subline) ?></p>
|
||||
<?php endif; ?>
|
||||
<ul class="ad-banner__track" id="ad-banner-track" aria-live="off">
|
||||
<ul class="ad-banner__track" id="ad-banner-track" data-carousel-track aria-live="off">
|
||||
<?php foreach ($banners as $i => $banner): ?>
|
||||
<?php
|
||||
$bg = $banner['background'] ?? null;
|
||||
@@ -76,7 +76,7 @@ $total = count($banners);
|
||||
. '</a>';
|
||||
};
|
||||
?>
|
||||
<li class="<?= e($classes) ?>"<?= $style ?> data-banner-slide role="group" aria-roledescription="Folie" aria-label="<?= ($i + 1) ?> von <?= $total ?>"<?= $i === 0 ? '' : ' hidden' ?>>
|
||||
<li class="<?= e($classes) ?>"<?= $style ?> data-carousel-slide role="group" aria-roledescription="Folie" aria-label="<?= ($i + 1) ?> von <?= $total ?>"<?= $i === 0 ? '' : ' hidden' ?>>
|
||||
<?php if (!empty($banner['logo'])): ?>
|
||||
<div class="ad-banner__logo<?= !empty($banner['logo_light']) ? ' ad-banner__logo--light' : '' ?>">
|
||||
<img src="<?= e(asset($banner['logo'])) ?>" alt="<?= e($banner['company'] ?? $banner['title']) ?>" loading="lazy" decoding="async">
|
||||
@@ -99,18 +99,18 @@ $total = count($banners);
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php if ($total > 1): ?>
|
||||
<?php /* Ohne JS verborgen (Steuerung wäre funktionslos); banner.js blendet sie ein. */ ?>
|
||||
<div class="ad-banner__controls" data-banner-controls hidden>
|
||||
<?php /* Ohne JS verborgen (Steuerung wäre funktionslos); carousel.js blendet sie ein. */ ?>
|
||||
<div class="ad-banner__controls" data-carousel-controls hidden>
|
||||
<?php /* Explizite Pause/Play-Taste — WCAG 2.2.2. Nur bei aktiver Auto-Rotation sichtbar. */ ?>
|
||||
<button class="ad-banner__toggle" type="button" data-banner-toggle aria-pressed="false" aria-controls="ad-banner-track" hidden>
|
||||
<button class="ad-banner__toggle" type="button" data-carousel-toggle aria-pressed="false" aria-controls="ad-banner-track" hidden>
|
||||
<?= icon('pause-fill', 'ad-banner__toggle-icon ad-banner__toggle-icon--pause') ?>
|
||||
<?= icon('play-fill', 'ad-banner__toggle-icon ad-banner__toggle-icon--play') ?>
|
||||
<span class="visually-hidden" data-banner-toggle-label>Automatischen Wechsel pausieren</span>
|
||||
<span class="visually-hidden" data-carousel-toggle-label>Automatischen Wechsel pausieren</span>
|
||||
</button>
|
||||
<div class="ad-banner__indicators" role="group" aria-label="Anzeige wählen">
|
||||
<?php foreach ($banners as $i => $banner): ?>
|
||||
<button class="ad-banner__dot" type="button" data-banner-dot aria-controls="ad-banner-track" aria-label="Anzeige <?= ($i + 1) ?> von <?= $total ?>: <?= e($banner['company'] ?? $banner['title']) ?>">
|
||||
<span class="ad-banner__progress" aria-hidden="true"></span>
|
||||
<button class="ad-banner__dot" type="button" data-carousel-dot aria-controls="ad-banner-track" aria-label="Anzeige <?= ($i + 1) ?> von <?= $total ?>: <?= e($banner['company'] ?? $banner['title']) ?>">
|
||||
<span class="ad-banner__progress" data-carousel-progress aria-hidden="true"></span>
|
||||
</button>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
32
app/components/crest.php
Normal file
32
app/components/crest.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Vereinswappen. Props: $src (lokaler Pfad relativ zu assets/, oder null/leer),
|
||||
* $name (Vereinsname — für Initialen-Fallback). Das Wappen ist dekorativ (alt=""),
|
||||
* weil der Vereinsname direkt daneben als Text steht. Fehlt das Bild, rendert ein
|
||||
* Initialen-Kreis statt eines kaputten <img>; lädt das Bild im Browser nicht
|
||||
* (z. B. 404), ersetzt assets/js/crest.js es ebenfalls durch die Initialen
|
||||
* (data-club liefert den Namen). Das weiße Chip im CSS hält auch helle/weiße
|
||||
* Logos auf dunklem Grund sichtbar.
|
||||
*/
|
||||
$src = $src ?? null;
|
||||
$name = (string) ($name ?? '');
|
||||
|
||||
$initials = '';
|
||||
foreach (preg_split('/[\s.-]+/', trim($name)) ?: [] as $word) {
|
||||
if ($word !== '') {
|
||||
$initials .= mb_substr($word, 0, 1);
|
||||
}
|
||||
if (mb_strlen($initials) >= 2) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$initials = mb_strtoupper($initials !== '' ? $initials : '?');
|
||||
?>
|
||||
<?php if (!empty($src)): ?>
|
||||
<img class="crest" src="<?= e(asset($src)) ?>" alt="" width="40" height="40" loading="lazy" decoding="async" data-club="<?= e($name) ?>">
|
||||
<?php else: ?>
|
||||
<span class="crest crest--initials" aria-hidden="true"><?= e($initials) ?></span>
|
||||
<?php endif; ?>
|
||||
82
app/components/league-table.php
Normal file
82
app/components/league-table.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Liga-Tabelle. Props: $table (data/matchcenter.json → teams[].table[]),
|
||||
* $caption (Beschriftung für Screenreader/Region, z. B. "Tabelle Kreisklasse").
|
||||
* Die eigene Vereinszeile wird hervorgehoben; Auf-/Abstiegszonen über farbige
|
||||
* Randmarkierungen (Legende darunter). Horizontal scrollbar auf schmalen Screens
|
||||
* (fokussierbare Region, WCAG). Spaltenköpfe in Abel (Coolvetica wäre zu klein).
|
||||
*/
|
||||
$table = $table ?? [];
|
||||
$caption = (string) ($caption ?? 'Tabelle');
|
||||
if ($table === []) {
|
||||
return;
|
||||
}
|
||||
$zones = array_column($table, 'zone');
|
||||
$hasPromo = in_array('promotion', $zones, true);
|
||||
$hasPromoPlayoff = in_array('promotion-playoff', $zones, true);
|
||||
$hasRelegPlayoff = in_array('relegation-playoff', $zones, true);
|
||||
$hasReleg = in_array('relegation', $zones, true);
|
||||
?>
|
||||
<div class="table-scroll" role="region" aria-label="<?= e($caption) ?>" tabindex="0">
|
||||
<table class="league-table">
|
||||
<caption class="visually-hidden"><?= e($caption) ?></caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="league-table__rank">#</th>
|
||||
<th scope="col" class="league-table__club">Verein</th>
|
||||
<th scope="col"><abbr title="Spiele">Sp</abbr></th>
|
||||
<th scope="col"><abbr title="Siege">S</abbr></th>
|
||||
<th scope="col"><abbr title="Unentschieden">U</abbr></th>
|
||||
<th scope="col"><abbr title="Niederlagen">N</abbr></th>
|
||||
<th scope="col"><abbr title="Tordifferenz">Diff</abbr></th>
|
||||
<th scope="col"><abbr title="Punkte">Pkt</abbr></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($table as $row): ?>
|
||||
<?php
|
||||
$trClass = [];
|
||||
if (!empty($row['is_own_club'])) {
|
||||
$trClass[] = 'is-own';
|
||||
}
|
||||
if (!empty($row['zone'])) {
|
||||
$trClass[] = 'zone-' . e($row['zone']);
|
||||
}
|
||||
$diff = (int) ($row['goal_diff'] ?? 0);
|
||||
?>
|
||||
<tr<?= $trClass !== [] ? ' class="' . implode(' ', $trClass) . '"' : '' ?>>
|
||||
<td class="league-table__rank"><?= (int) ($row['rank'] ?? 0) ?></td>
|
||||
<th scope="row" class="league-table__club">
|
||||
<?php component('crest', ['src' => $row['crest'] ?? null, 'name' => $row['club'] ?? '']); ?>
|
||||
<span class="league-table__name" title="<?= e($row['club'] ?? '') ?>"><?= e($row['club'] ?? '') ?></span>
|
||||
</th>
|
||||
<td><?= (int) ($row['played'] ?? 0) ?></td>
|
||||
<td><?= (int) ($row['won'] ?? 0) ?></td>
|
||||
<td><?= (int) ($row['drawn'] ?? 0) ?></td>
|
||||
<td><?= (int) ($row['lost'] ?? 0) ?></td>
|
||||
<td><?= ($diff > 0 ? '+' : '') . $diff ?></td>
|
||||
<td class="league-table__pts"><?= (int) ($row['points'] ?? 0) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php if ($hasPromo || $hasPromoPlayoff || $hasRelegPlayoff || $hasReleg): ?>
|
||||
<ul class="league-table__legend">
|
||||
<?php if ($hasPromo): ?>
|
||||
<li class="league-table__legend-item zone-promotion">Aufstieg</li>
|
||||
<?php endif; ?>
|
||||
<?php if ($hasPromoPlayoff): ?>
|
||||
<li class="league-table__legend-item zone-promotion-playoff">Aufstiegsrelegation</li>
|
||||
<?php endif; ?>
|
||||
<?php if ($hasRelegPlayoff): ?>
|
||||
<li class="league-table__legend-item zone-relegation-playoff">Abstiegsrelegation</li>
|
||||
<?php endif; ?>
|
||||
<?php if ($hasReleg): ?>
|
||||
<li class="league-table__legend-item zone-relegation">Abstieg</li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
47
app/components/match-card.php
Normal file
47
app/components/match-card.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?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>
|
||||
38
app/components/match-row.php
Normal file
38
app/components/match-row.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
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).
|
||||
*/
|
||||
$m = $match;
|
||||
$hasResult = isset($m['goals_home'], $m['goals_away']);
|
||||
$isHome = !empty($m['is_home']);
|
||||
?>
|
||||
<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 else: ?>
|
||||
<span class="match-row__time" aria-hidden="true">–</span>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
</li>
|
||||
45
app/components/match-slider.php
Normal file
45
app/components/match-slider.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?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>
|
||||
23
app/components/matchcenter-empty.php
Normal file
23
app/components/matchcenter-empty.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Fallback, wenn data/matchcenter.json (noch) fehlt oder leer ist — z. B. vor dem
|
||||
* ersten erfolgreichen Cron-Lauf. Bricht nie, verweist auf die Mannschaftsseiten.
|
||||
*/
|
||||
?>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="matchcenter-empty">
|
||||
<?= icon('info-circle', 'matchcenter-empty__icon') ?>
|
||||
<h2>Spieldaten in Kürze verfügbar</h2>
|
||||
<p>Die aktuellen Spiele, Ergebnisse und Tabellen werden gerade aktualisiert. Schau gleich noch einmal vorbei – oder besuche direkt die Seiten unserer Mannschaften.</p>
|
||||
<div class="matchcenter-empty__actions">
|
||||
<a class="btn" href="<?= e(url('fussball/erste-mannschaft')) ?>">1. Mannschaft</a>
|
||||
<a class="btn btn--outline" href="<?= e(url('fussball/zweite-mannschaft')) ?>">2. Mannschaft</a>
|
||||
<a class="btn btn--outline" href="<?= e(url('fussball/damen')) ?>">Damen</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
60
app/components/matchcenter-section.php
Normal file
60
app/components/matchcenter-section.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Matchcenter-Sektion einer Mannschaft (kicker-Stil): Kopf (Name + Liga + Link zur
|
||||
* Mannschaftsseite), darunter zweispaltig — Tabelle 2/3 links, anstehende Spiele +
|
||||
* letzte Ergebnisse als kompakte Liste 1/3 rechts. Auf schmalen Screens gestapelt.
|
||||
* Props: $team (data/matchcenter.json → teams[]). Leere Blöcke verstecken sich selbst.
|
||||
*/
|
||||
$team = $team ?? [];
|
||||
$next = $team['next_matches'] ?? [];
|
||||
$results = $team['last_results'] ?? [];
|
||||
$table = $team['table'] ?? [];
|
||||
$league = $team['league'] ?? null;
|
||||
$key = preg_replace('/[^a-z0-9-]+/', '', strtolower((string) ($team['key'] ?? '')));
|
||||
?>
|
||||
<section class="section matchcenter-team" id="team-<?= e($key) ?>">
|
||||
<div class="container">
|
||||
<div class="matchcenter-team__head">
|
||||
<div class="matchcenter-team__heading">
|
||||
<h2><?= e($team['name'] ?? '') ?></h2>
|
||||
<?php if ($league): ?>
|
||||
<p class="matchcenter-team__league"><?= icon('trophy', 'matchcenter-team__league-icon') ?><?= e($league) ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php if (!empty($team['slug'])): ?>
|
||||
<a class="btn btn--outline" href="<?= e(url($team['slug'])) ?>">Zur Mannschaft</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="matchcenter-team__grid">
|
||||
<?php if ($table !== []): ?>
|
||||
<div class="matchcenter-team__main">
|
||||
<?php /* Sichtbare Überschrift bewusst weggelassen; <caption> labelt die Tabelle für Screenreader. */ ?>
|
||||
<?php component('league-table', ['table' => $table, 'caption' => 'Tabelle ' . ($team['name'] ?? '') . ($league ? ' · ' . $league : '')]); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($next !== [] || $results !== []): ?>
|
||||
<aside class="matchcenter-team__aside">
|
||||
<?php if ($next !== []): ?>
|
||||
<h3 class="visually-hidden">Nächste Spiele</h3>
|
||||
<ul class="match-list">
|
||||
<?php foreach ($next as $m): ?>
|
||||
<?php component('match-row', ['match' => $m]); ?>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
<?php if ($results !== []): ?>
|
||||
<h3 class="visually-hidden">Letzte Ergebnisse</h3>
|
||||
<ul class="match-list">
|
||||
<?php foreach ($results as $m): ?>
|
||||
<?php component('match-row', ['match' => $m]); ?>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</aside>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
Reference in New Issue
Block a user