diff --git a/CLAUDE.md b/CLAUDE.md index 51edb02..e78b6b5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -68,7 +68,9 @@ Sichtbare FAQ + FAQPage-Schema aus derselben `data/*.json`-Quelle, damit Inhalt Pfade), `abs_url()` (absolute URLs für Canonical/OG), `asset()` (Versionierung via filemtime), `json_load('name')` (liest `data/name.json`, static cache), `component()`, `config('key')` (Werte aus `config/config.php`), `icon('name', 'klasse', 'label?')` (Inline-SVG aus `public/assets/icons/`, siehe - Abschnitt Icons), `form_token()`/`form_token_valid()` (Time-Trap-Token, siehe Formulare). + Abschnitt Icons), `form_token()`/`form_token_valid()` (Time-Trap-Token, siehe Formulare), + `img_intrinsic_attrs('pfad')` (width/height-Attribute aus der Bilddatei für CLS-freie ``, + wenn die Maße nicht schon statisch bekannt sind). - **CSS:** 6 Dateien als `` in fester Reihenfolge: tokens → reset → base → layout → components → utilities. Kein `@import`, kein Inline-Style. Komponenten-Styles in `components.css` mit Banner-Kommentaren (`/* === hero === */`). diff --git a/app/components/banner-slider.php b/app/components/banner-slider.php index 90043a7..3274e29 100644 --- a/app/components/banner-slider.php +++ b/app/components/banner-slider.php @@ -72,7 +72,7 @@ $total = count($banners);
  • data-carousel-slide role="group" aria-roledescription="Folie" aria-label=" von ">
    - <?= e($banner['company'] ?? $banner['title']) ?> + <?= e($banner['company'] ?? $banner['title']) ?> loading="lazy" decoding="async">
    diff --git a/app/components/partner-grid.php b/app/components/partner-grid.php index 169eba7..2010de8 100644 --- a/app/components/partner-grid.php +++ b/app/components/partner-grid.php @@ -18,10 +18,10 @@ $partners = json_load('partners')['partners'] ?? [];
  • - <?= e($partner['name']) ?> + <?= e($partner['name']) ?> loading="lazy" decoding="async"> - <?= e($partner['name']) ?> + <?= e($partner['name']) ?> loading="lazy" decoding="async">
  • diff --git a/app/helpers.php b/app/helpers.php index b5e0e2e..a923201 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -96,6 +96,35 @@ function json_load(string $name): array 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]; +} + /** * Komponente rendern: component('hero', ['title' => …]). * Props werden als lokale Variablen extrahiert; Komponenten sind dumme Includes. diff --git a/app/pages/ansprechpartner.php b/app/pages/ansprechpartner.php index 0397e1c..8adaa8c 100644 --- a/app/pages/ansprechpartner.php +++ b/app/pages/ansprechpartner.php @@ -31,7 +31,7 @@ $anchor_slug = static function (string $s): string { $meta = [ 'title' => 'Ansprechpartner', - 'description' => 'Ansprechpartner und Kontaktwege beim TSV 1908 Kulmbach – Geschäftsstelle, Turnabteilung und Vorstand auf einen Blick.', + 'description' => 'Ansprechpartner und Kontaktwege beim TSV 1908 Kulmbach – Geschäftsstelle, Turnabteilung und Vorstand mit E-Mail und Telefon auf einen Blick.', 'schema' => page_schema([ ['name' => 'Startseite', 'slug' => ''], ['name' => 'Der Verein', 'slug' => 'verein'], diff --git a/app/pages/datenschutz.php b/app/pages/datenschutz.php index 3e683df..b4b8197 100644 --- a/app/pages/datenschutz.php +++ b/app/pages/datenschutz.php @@ -15,7 +15,7 @@ $dpo = $club['data_protection_contact'] ?? ''; $meta = [ 'title' => 'Datenschutzerklärung', - 'description' => 'Datenschutzerklärung des TSV 1908 Kulmbach e. V. – welche Daten beim Besuch unserer Website verarbeitet werden. Komplett self-hosted, ohne Tracking und ohne Drittanbieter-Dienste im Browser.', + 'description' => 'Datenschutzerklärung des TSV 1908 Kulmbach e. V. – welche Daten beim Besuch der Website verarbeitet werden. Self-hosted, ohne Tracking und Drittanbieter.', 'schema' => page_schema([ ['name' => 'Startseite', 'slug' => ''], ['name' => 'Datenschutz', 'slug' => 'datenschutz'], diff --git a/app/pages/fussball.php b/app/pages/fussball.php index 0d7bad4..06824c5 100644 --- a/app/pages/fussball.php +++ b/app/pages/fussball.php @@ -21,7 +21,7 @@ $cards = [ $meta = [ 'title' => 'Fußball in Kulmbach', - 'description' => 'Herren-, Damen- und Jugendfußball beim TSV 08 Kulmbach: erste und zweite Mannschaft auf Kreisklasse-Niveau, Damenmannschaft und Nachwuchs – aktiv werden im Katzbachtal.', + 'description' => 'Herren-, Damen- und Jugendfußball beim TSV 08 Kulmbach: zwei Herrenmannschaften, Damenmannschaft und Nachwuchs – aktiv werden im Katzbachtal.', 'og_image' => 'img/pages/fussball-hero-1600.jpg', 'schema' => page_schema( [ diff --git a/app/pages/historie.php b/app/pages/historie.php index 60cdb8e..a9f93e3 100644 --- a/app/pages/historie.php +++ b/app/pages/historie.php @@ -12,6 +12,11 @@ $meta = [ 'title' => 'Vereinshistorie – Der Katzbachtaler Weg', 'description' => 'Erlebe die bewegte Geschichte des TSV 08 Kulmbach – von den ersten Spielen an der Bayreuther Straße 1947 bis zur Hans-Rausch-Sportanlage im Katzbachtal.', 'scripts' => ['timeline.js'], + 'schema' => page_schema([ + ['name' => 'Startseite', 'slug' => ''], + ['name' => 'Der Verein', 'slug' => 'verein'], + ['name' => 'Vereinshistorie', 'slug' => 'historie'], + ]), ]; component('hero', ['hero' => $historie['hero']]); diff --git a/app/pages/mitglied-werden.php b/app/pages/mitglied-werden.php index 42e8f70..b76bd15 100644 --- a/app/pages/mitglied-werden.php +++ b/app/pages/mitglied-werden.php @@ -19,7 +19,7 @@ $feeOptions = array_map( $meta = [ 'title' => 'Mitglied werden', - 'description' => 'Mitglied werden beim TSV 08 Kulmbach: Fußball bequem online anmelden oder Turnen per Aufnahmeantrag. Alle Beiträge, Aufnahme und Infos für deinen Vereinsbeitritt in Kulmbach.', + 'description' => 'Mitglied werden beim TSV 08 Kulmbach: Fußball online anmelden oder Turnen per Aufnahmeantrag – alle Beiträge und Infos für deinen Vereinsbeitritt.', 'og_image' => 'img/hero/hero-1600.jpg', 'scripts' => ['form.js'], 'schema' => page_schema( diff --git a/app/pages/mitmachen.php b/app/pages/mitmachen.php index c00e09a..a8f9b56 100644 --- a/app/pages/mitmachen.php +++ b/app/pages/mitmachen.php @@ -26,7 +26,7 @@ $contact_for = static function (array $pos) use ($directory): ?array { $meta = [ 'title' => 'Mitmachen & Ehrenamt', - 'description' => 'Werde ehrenamtlicher Helfer beim TSV 1908 Kulmbach: Jugendtrainer, Betreuer und Unterstützung bei Veranstaltungen. Offene Aufgaben mit direktem Ansprechpartner – ohne Vorerfahrung.', + 'description' => 'Ehrenamt beim TSV 08 Kulmbach: offene Aufgaben als Jugendtrainer, Betreuer oder Helfer bei Veranstaltungen – mit direktem Ansprechpartner, ohne Vorerfahrung.', 'og_image' => 'img/pages/jugendfussball-hero-1440.jpg', 'scripts' => ['position-filter.js'], 'schema' => array_merge( diff --git a/app/pages/partner-werden.php b/app/pages/partner-werden.php index 5bc19e8..bc36414 100644 --- a/app/pages/partner-werden.php +++ b/app/pages/partner-werden.php @@ -27,6 +27,7 @@ component('hero', ['hero' => $page['hero']]); ?>