.svg). * Standard: dekorativ (aria-hidden). Mit $label wird es als beschriftetes Bild * (role="img") ausgegeben. Größe/Farbe steuert CSS über die Klasse .icon. * Quelle: Bootstrap Icons (MIT) — neue Icons via bin/icons-add.php hinzufügen. */ function icon(string $name, string $class = '', ?string $label = null): string { static $cache = []; if (!array_key_exists($name, $cache)) { $file = PUBLIC_PATH . '/assets/icons/' . basename($name) . '.svg'; $cache[$name] = is_file($file) ? trim((string) file_get_contents($file)) : ''; } if ($cache[$name] === '') { return ''; } $attrs = 'class="icon' . ($class !== '' ? ' ' . e($class) : '') . '"'; $attrs .= $label !== null && $label !== '' ? ' role="img" aria-label="' . e($label) . '"' : ' aria-hidden="true" focusable="false"'; return preg_replace('/.json) mit Request-weitem Cache. * Wirft bei kaputtem JSON — Datenfehler sollen laut scheitern, nicht leise. */ function json_load(string $name): array { static $cache = []; if (!array_key_exists($name, $cache)) { $file = DATA_PATH . '/' . $name . '.json'; if (!is_file($file)) { return []; } $cache[$name] = json_decode((string) file_get_contents($file), true, 512, JSON_THROW_ON_ERROR); } return $cache[$name]; } /** * Intrinsische Bildmaße als ' width="…" height="…"' für CLS-freie -Tags. * $rel = Pfad relativ zu public/assets/. PNG/JPG via getimagesize, SVG via * viewBox (Fallback: width/height-Attribute, Einheiten werden ignoriert — * fürs Seitenverhältnis reicht die Zahl). Liefert '' wenn nicht bestimmbar. */ function img_intrinsic_attrs(string $rel): string { static $cache = []; if (!isset($cache[$rel])) { $file = PUBLIC_PATH . '/assets/' . ltrim($rel, '/'); $w = $h = 0; if (is_file($file)) { if (str_ends_with(strtolower($file), '.svg')) { $svg = (string) file_get_contents($file); if (preg_match('/viewBox="\s*[\d.-]+[\s,]+[\d.-]+[\s,]+([\d.]+)[\s,]+([\d.]+)/', $svg, $m)) { [$w, $h] = [(int) round((float) $m[1]), (int) round((float) $m[2])]; } elseif (preg_match('/]*\bwidth="([\d.]+)[a-z%]*"[^>]*\bheight="([\d.]+)[a-z%]*"/s', $svg, $m)) { [$w, $h] = [(int) round((float) $m[1]), (int) round((float) $m[2])]; } } else { [$w, $h] = (getimagesize($file) ?: [0, 0]); } } $cache[$rel] = ($w > 0 && $h > 0) ? ' width="' . $w . '" height="' . $h . '"' : ''; } return $cache[$rel]; } /** * Anzeigewerte zu einem Anstoß. $kickoff = Zeit ohne Zonenangabe aus * data/matchcenter.json (z. B. 2026-08-01T14:00), gelesen als Europe/Berlin — * PHP läuft hier global auf UTC, deshalb explizit. Liefert: * iso maschinenlesbar mit Zone (für