Das Cover blieb am iPhone leer, während Chrome am Desktop es zeigte. Ursache war kein fehlendes Bild: das freigestellte Spielerfoto hatte 2584x5093 Pixel und 12,3 MB. iOS Safari verwirft Bilder, deren dekodierte Größe sein Budget übersteigt — hier gut 53 MB, und auf einer Ausgabe liegen 86 Bilder. Dargestellt wird das Foto mit 98cqi Höhe, bei 480px Seitenbreite also etwa 238x470 CSS-Pixel. Es gibt jetzt 320/640/1000 als PNG mit erhaltener Transparenz (0,2 / 0,8 / 1,9 MB); sizes nennt die echte Breite, sonst zöge ein Handy mit DPR 3 die größte Variante. Das Original ist gelöscht. Dafür kann resize_image_variants() nun Alpha erhalten ($alpha = true schreibt PNG statt JPG) und die img-Komponente eine Endung ($image['ext']). Der Default bleibt JPG, weil bestehende Aufrufer PNG-Quellen absichtlich zu JPG verkleinern. bin/stadionzeitung-add.php erzeugt die Varianten künftig selbst. Dort stand "unverändert kopieren, Felix liefert bereits ein web-taugliches Foto" — genau diese Annahme ist gerissen. Außerdem: smtp.password stand in der Produktiv-Config noch auf dem Platzhalter POSTFACH_PASSWORT (von preflight auf dem Server gefunden, einziger harter Fehler). Übernommen wurde das Passwort aus der Dev-Config, mit dem der Versand am 27.07.2026 verifiziert wurde; Host, Postfach und Empfänger sind identisch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
54 lines
2.0 KiB
PHP
54 lines
2.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Responsives Bild aus Varianten-Set ODER Einzeldatei. Props:
|
|
* $image array{base: string, widths: int[], alt: string, ext?: string} — Varianten
|
|
* <base>-<w>.<ext>, Default-Endung jpg. ext: 'png' für freigestellte Motive,
|
|
* deren Transparenz JPG zerstören würde (Spielerfoto auf dem Cover).
|
|
* ODER array{src: string, width: int, height: int, alt: string} — Originaldatei 1:1
|
|
* $sizes string (optional, default '100vw')
|
|
* $eager bool (optional) — above the fold: eager + fetchpriority high
|
|
* $class string (optional)
|
|
*/
|
|
$sizes = $sizes ?? '100vw';
|
|
$eager = $eager ?? false;
|
|
$class = $class ?? '';
|
|
|
|
if (!empty($image['src'])): ?>
|
|
<img<?= $class !== '' ? ' class="' . e($class) . '"' : '' ?>
|
|
src="<?= e(asset($image['src'])) ?>"
|
|
alt="<?= e($image['alt']) ?>"
|
|
width="<?= e($image['width']) ?>" height="<?= e($image['height']) ?>"
|
|
<?= $eager ? 'loading="eager" fetchpriority="high"' : 'loading="lazy" decoding="async"' ?>>
|
|
<?php return; endif;
|
|
|
|
$widths = $image['widths'];
|
|
$largest = max($widths);
|
|
$ext = strtolower((string) ($image['ext'] ?? 'jpg'));
|
|
$srcset = implode(', ', array_map(
|
|
static fn (int $w): string => asset("{$image['base']}-{$w}.{$ext}") . " {$w}w",
|
|
$widths
|
|
));
|
|
|
|
// Maße der größten Variante für width/height (CLS-Vermeidung).
|
|
[$w, $h] = (function () use ($image, $largest, $ext): array {
|
|
static $dims = [];
|
|
$file = PUBLIC_PATH . "/assets/{$image['base']}-{$largest}.{$ext}";
|
|
$key = $file;
|
|
if (!isset($dims[$key])) {
|
|
$size = is_file($file) ? (getimagesize($file) ?: [0, 0]) : [0, 0];
|
|
$dims[$key] = [$size[0], $size[1]];
|
|
}
|
|
return $dims[$key];
|
|
})();
|
|
?>
|
|
<img<?= $class !== '' ? ' class="' . e($class) . '"' : '' ?>
|
|
src="<?= e(asset("{$image['base']}-{$largest}.{$ext}")) ?>"
|
|
srcset="<?= e($srcset) ?>"
|
|
sizes="<?= e($sizes) ?>"
|
|
alt="<?= e($image['alt']) ?>"
|
|
width="<?= e($w) ?>" height="<?= e($h) ?>"
|
|
<?= $eager ? 'loading="eager" fetchpriority="high"' : 'loading="lazy" decoding="async"' ?>>
|