Stadionzeitung: Ergebnis- und Gegnerseite neu gestaltet

Ergebnisse laufen als Anzeigetafel (Wappen und Name je Seite, Ergebnis gross
in der Mitte) statt als Listenzeilen. Verworfen und im CSS begruendet: Tore
am rechten Blattrand, getoente Karten je Spiel, roter Strich unter der
Mannschaftszeile, graue Verlierer-Ziffern. Vereinsnamen einzeilig, sonst
ausgepunktet.

Die Gegnerseite bekommt ein symmetrisches Duell, eine aus den Tabellenwerten
abgeleitete Aufmacher-Zeile, den Vergleich als Balkenpaar aus der Mitte und
einen Tabellenausschnitt ueber die geteilte league-table-Komponente. Die
Balkenlaengen kommen aus Klassen in 5-Prozent-Stufen, nie aus einem
style-Attribut (die CSP verwirft inline styles stillschweigend).

Das Mitglied-werden-Poster sagt jetzt "Werde Teil unseres Vereins".

Alle geaenderten Seiten auf A5-Hoehe nachgemessen, kein Ueberstand.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hv9SGDu3m3diiPrW9Fmj3v
This commit is contained in:
2026-09-19 10:54:05 +02:00
parent 8f864368f0
commit 0ee75aa93b
5 changed files with 423 additions and 114 deletions

View File

@@ -18,15 +18,29 @@ $teamName = trim((string) ($info['team_name'] ?? ''));
$fixture = is_array($info['fixture'] ?? null) ? $info['fixture'] : [];
$own = is_array($info['own'] ?? null) ? $info['own'] : null;
$opponent = is_array($info['opponent_stats'] ?? null) ? $info['opponent_stats'] : null;
$opponentName = (string) ($info['opponent'] ?? 'Unser Gegner');
// Gedruckt wird der Anzeigename (Kurzname der Ausgabe, wenn gepflegt) — die
// BFV-Schreibweise „(SG1) TSV Wirsberg I/ FC Kupferberg I" ist ein Suchschlüssel,
// keine Überschrift.
$ausschnitt = is_array($info['table_excerpt'] ?? null) ? $info['table_excerpt'] : [];
$opponentName = (string) ($info['opponent_display'] ?? $info['opponent'] ?? 'Unser Gegner');
// Links steht IMMER unsere Mannschaft (Klasse --own hebt sie hervor), rechts der
// Gegner. Bei einem Heimspiel ist das ohnehin die Reihenfolge Heim/Gast; fährt die
// Mannschaft aber auswärts, trüge die Hervorhebung sonst der gegnerische Verein.
$istHeim = !empty($fixture['is_home']);
$ownName = (string) (($istHeim ? ($fixture['home'] ?? null) : ($fixture['away'] ?? null)) ?? 'TSV 08 Kulmbach');
$ownCrest = ($istHeim ? ($fixture['home_crest'] ?? null) : ($fixture['away_crest'] ?? null)) ?? $own['crest'] ?? null;
$gegnerLabel = $opponentName;
$gegnerCrest = ($istHeim ? ($fixture['away_crest'] ?? null) : ($fixture['home_crest'] ?? null)) ?? $opponent['crest'] ?? null;
$kickoff = (string) ($fixture['kickoff'] ?? '');
$kickoffDate = $kickoff !== '' ? substr($kickoff, 0, 10) : '';
$kickoffTime = $kickoff !== '' ? substr($kickoff, 11, 5) : '';
// Der Tabellenplatz steht NICHT in der Balkenliste: klein ist dort besser,
// ein Balken läge genau falsch herum. Er gehört nach oben neben die Wappen.
// „Spiele“ fällt ebenfalls raus (für beide gleich, steht in der Kopfzeile).
$rows = [
['label' => 'Tabellenplatz', 'key' => 'rank'],
['label' => 'Spiele', 'key' => 'played'],
['label' => 'Siege', 'key' => 'won'],
['label' => 'Unentschieden', 'key' => 'drawn'],
['label' => 'Niederlagen', 'key' => 'lost'],
@@ -41,6 +55,57 @@ $format = static function (?array $row, string $key, bool $signed = false): stri
$value = (int) $row[$key];
return $signed && $value > 0 ? '+' . $value : (string) $value;
};
// Balkenlänge als KLASSE, nie als style-Attribut: die CSP verwirft inline
// styles stillschweigend (siehe CLAUDE.md), der Balken wäre in Produktion
// unsichtbar. Deshalb 5-Prozent-Stufen, die components.css ausdefiniert.
$barStep = static function (?array $row, ?array $other, string $key): int {
if ($row === null) {
return 0;
}
$value = abs((int) ($row[$key] ?? 0));
$scale = max($value, $other === null ? 0 : abs((int) ($other[$key] ?? 0)));
if ($scale <= 0) {
return 0;
}
return (int) round($value / $scale * 20);
};
// Ein Satz statt einer Zahlenkolonne: derselbe Bestand, nur in Sprache. Die
// erste zutreffende Regel gewinnt, damit immer genau eine Aussage dasteht.
$zahlwort = static function (int $n): string {
$woerter = [0 => 'Kein', 1 => 'Ein', 2 => 'Zwei', 3 => 'Drei', 4 => 'Vier', 5 => 'Fünf',
6 => 'Sechs', 7 => 'Sieben', 8 => 'Acht', 9 => 'Neun', 10 => 'Zehn', 11 => 'Elf',
12 => 'Zwölf', 13 => 'Dreizehn', 14 => 'Vierzehn', 15 => 'Fünfzehn'];
return $woerter[$n] ?? (string) $n;
};
$aufmacher = '';
if ($opponent !== null) {
$played = (int) ($opponent['played'] ?? 0);
$won = (int) ($opponent['won'] ?? 0);
$lost = (int) ($opponent['lost'] ?? 0);
$rank = (int) ($opponent['rank'] ?? 0);
$points = (int) ($opponent['points'] ?? 0);
$diff = (int) ($opponent['goal_diff'] ?? 0);
if ($played > 0 && $lost === 0) {
$aufmacher = 'Noch ohne Niederlage';
} elseif ($played > 0 && $won === 0) {
$aufmacher = 'Noch ohne Sieg';
} elseif ($played > 0 && $won / $played >= 0.7) {
$aufmacher = $zahlwort($won) . ' Siege aus ' . mb_strtolower($zahlwort($played)) . ' Spielen';
} elseif ($rank === 1) {
$aufmacher = 'Tabellenführer mit ' . $points . ' Punkten';
} elseif ($played > 0 && $diff <= -8) {
$aufmacher = $zahlwort(abs($diff)) . ' Tore mehr kassiert als geschossen';
} elseif ($played > 0 && $diff >= 8) {
$aufmacher = $zahlwort($diff) . ' Tore mehr geschossen als kassiert';
} elseif ($played > 0) {
$aufmacher = $points . ' Punkte aus ' . $played . ' Spielen';
}
}
$spieltagInfo = $opponent !== null && (int) ($opponent['played'] ?? 0) > 0
? 'Stand nach ' . (int) $opponent['played'] . ' Spieltagen'
: '';
?>
<div class="stadionzeitung-gegner">
<header class="stadionzeitung-page__head">
@@ -53,25 +118,26 @@ $format = static function (?array $row, string $key, bool $signed = false): stri
<span class="stadionzeitung-page__rule" aria-hidden="true"></span>
<div class="stadionzeitung-gegner__duell">
<div class="stadionzeitung-gegner__club stadionzeitung-gegner__club--own">
<?php component('crest', ['src' => $fixture['home_crest'] ?? $own['crest'] ?? null, 'name' => $fixture['home'] ?? 'TSV 08 Kulmbach']); ?>
<strong><?= e((string) ($fixture['home'] ?? 'TSV 08 Kulmbach')) ?></strong>
<div class="stadionzeitung-gegner__club">
<?php component('crest', ['src' => $ownCrest, 'name' => $ownName]); ?>
<span class="stadionzeitung-gegner__club-name"><?= e($ownName) ?></span>
</div>
<div class="stadionzeitung-gegner__match">
<span>gegen</span>
<div class="stadionzeitung-gegner__vs">
<p class="stadionzeitung-gegner__vs-wort"><?= $istHeim ? 'gegen' : 'bei' ?></p>
<?php if ($kickoffDate !== ''): ?>
<strong><?= e(german_date($kickoffDate)) ?></strong>
<?php endif; ?>
<?php if ($kickoffTime !== ''): ?>
<span><?= e($kickoffTime) ?> Uhr</span>
<p class="stadionzeitung-gegner__anstoss"><?= e(german_date($kickoffDate)) ?><?php if ($kickoffTime !== ''): ?><br><?= e($kickoffTime) ?> Uhr<?php endif; ?></p>
<?php endif; ?>
</div>
<div class="stadionzeitung-gegner__club">
<?php component('crest', ['src' => $fixture['away_crest'] ?? $opponent['crest'] ?? null, 'name' => $fixture['away'] ?? $opponentName]); ?>
<strong><?= e((string) ($fixture['away'] ?? $opponentName)) ?></strong>
<?php component('crest', ['src' => $gegnerCrest, 'name' => $gegnerLabel]); ?>
<span class="stadionzeitung-gegner__club-name"><?= e($gegnerLabel) ?></span>
</div>
</div>
<?php if ($aufmacher !== ''): ?>
<p class="stadionzeitung-gegner__aufmacher"><?= e($aufmacher) ?></p>
<?php endif; ?>
<?php if ($own !== null && $opponent !== null): ?>
<div class="stadionzeitung-gegner__vergleich" role="table" aria-label="Tabellenvergleich">
<div class="stadionzeitung-gegner__vergleich-head" role="row">
@@ -79,18 +145,33 @@ $format = static function (?array $row, string $key, bool $signed = false): stri
Markup — das Kürzel des Marktschorgast-Gegners wäre in jeder Ausgabe
mitgedruckt worden. Die Überschrift nennt den Gegner ohnehin. */ ?>
<span role="columnheader">Wir</span>
<span role="columnheader">Vergleich</span>
<span role="columnheader"><?= e($spieltagInfo !== '' ? $spieltagInfo : 'Vergleich') ?></span>
<span role="columnheader">Gegner</span>
</div>
<?php foreach ($rows as $row): ?>
<div class="stadionzeitung-gegner__vergleich-row" role="row">
<strong role="cell"><?= e($format($own, (string) $row['key'], (bool) ($row['signed'] ?? false))) ?></strong>
<span role="rowheader"><?= e((string) $row['label']) ?></span>
<strong role="cell"><?= e($format($opponent, (string) $row['key'], (bool) ($row['signed'] ?? false))) ?></strong>
<strong class="stadionzeitung-gegner__wert" role="cell"><?= e($format($own, (string) $row['key'], (bool) ($row['signed'] ?? false))) ?></strong>
<span class="stadionzeitung-gegner__balken stadionzeitung-gegner__balken--own" aria-hidden="true"><span class="stadionzeitung-gegner__balken-fuellung stadionzeitung-gegner__balken-fuellung--s<?= $barStep($own, $opponent, (string) $row['key']) ?>"></span></span>
<span class="stadionzeitung-gegner__label" role="rowheader"><?= e((string) $row['label']) ?></span>
<span class="stadionzeitung-gegner__balken" aria-hidden="true"><span class="stadionzeitung-gegner__balken-fuellung stadionzeitung-gegner__balken-fuellung--s<?= $barStep($opponent, $own, (string) $row['key']) ?>"></span></span>
<strong class="stadionzeitung-gegner__wert" role="cell"><?= e($format($opponent, (string) $row['key'], (bool) ($row['signed'] ?? false))) ?></strong>
</div>
<?php endforeach; ?>
</div>
<?php else: ?>
<p class="stadionzeitung-gegner__empty">Die aktuellen Vergleichsdaten waren beim letzten Abruf nicht verfügbar.</p>
<?php endif; ?>
<?php /* Der Tabellenplatz stand kurz als zwei lose Ziffern oben im Duell und
wirkte dort unruhig (Felix, 19.09.2026). Hier zeigt ihn der Ausschnitt
im Zusammenhang: wer steht zwischen uns und ihnen. Gerendert mit der
geteilten league-table-Komponente, nie mit einer zweiten Tabelle. */ ?>
<?php if ($ausschnitt !== []): ?>
<div class="stadionzeitung-gegner__ausschnitt">
<p class="stadionzeitung-gegner__ausschnitt-titel">Die Tabelle dazwischen</p>
<?php foreach ($ausschnitt as $block): ?>
<?php component('league-table', ['table' => $block, 'caption' => 'Tabellenausschnitt ' . (string) ($info['league'] ?? '')]); ?>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>