Files
tsv08kulmbach-website/app/pages/mitmachen.php
fs 819bdf6173 SEO/GEO-Basics: Descriptions & Titles normalisiert, Outline-Fixes, CLS-Maße
Audit aller 23 indexierbaren Routen (lokaler Crawl) und Fixes:
- Descriptions auf ~130-160 Zeichen; Turnen-Disziplinseiten hatten den
  kurzen Hero-Teaser als Description, jetzt eigene SERP-Texte
- Titles: team-erste gekürzt, sportheimbuchung ohne doppelten Brand
- h1->h3-Sprünge behoben: visually-hidden h2 aus JSON-Section-Titeln
  (sportheimbuchung, partner-werden), sichtbarer choice.title auf
  mitglied-werden; historie bekommt Breadcrumb-Schema
- Neuer Helper img_intrinsic_attrs() liefert width/height aus der
  Bilddatei (PNG via getimagesize, SVG via viewBox) für Partner-Grid-
  und Banner-Slider-Logos — kein Layout-Shift mehr

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 00:27:19 +02:00

92 lines
4.2 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
/**
* Mitmachen & Ehrenamt — „Stellenbörse" für ehrenamtliche Helfer.
* Struktur: Hero → Stellen (position-card je Stelle) → CTA-Band → FAQ.
* Inhalte: data/mitmachen.json. Ansprechpartner: data/ansprechpartner.json (Single Source).
* Je Stelle ein JobPosting-Knoten (helpers: job_posting_schema) im $meta['schema']
* → GEO/semantisches SEO, verknüpft via #club mit dem Organization-Knoten.
*/
$page = json_load('mitmachen');
// Ansprechpartner nach Namen indexieren (Single Source: ansprechpartner.json).
$directory = [];
foreach (json_load('ansprechpartner')['persons'] ?? [] as $p) {
$directory[$p['name']] = $p;
}
// Pro Stelle: inline contact bevorzugt, sonst Lookup über contact_name.
$contact_for = static function (array $pos) use ($directory): ?array {
if (!empty($pos['contact']) && is_array($pos['contact'])) {
return $pos['contact'];
}
return $directory[$pos['contact_name'] ?? ''] ?? null;
};
$meta = [
'title' => 'Mitmachen & Ehrenamt',
'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(
page_schema(
[
['name' => 'Startseite', 'slug' => ''],
['name' => 'Mitmachen', 'slug' => 'mitmachen'],
],
$page['faq'] ?? []
),
array_values(array_filter(array_map(
static fn (array $job): array => job_posting_schema($job, 'mitmachen'),
$page['positions'] ?? []
)))
),
];
component('hero', ['hero' => $page['hero']]);
?>
<section class="section positions" id="stellen" aria-labelledby="positions-heading">
<div class="container">
<h2 id="positions-heading" class="visually-hidden">Offene Stellen</h2>
<?php if (!empty($page['filters'])): ?>
<div class="filter-bar" role="group" aria-label="Stellen nach Bereich filtern" data-position-filter>
<?php foreach ($page['filters'] as $i => $f): ?>
<button type="button" class="filter-bar__tab" data-filter="<?= e($f['value']) ?>" aria-pressed="<?= $i === 0 ? 'true' : 'false' ?>"><?= e($f['label']) ?></button>
<?php endforeach; ?>
</div>
<?php endif; ?>
<div class="positions__list" data-position-list>
<?php foreach ($page['positions'] as $pos): ?>
<?php component('position-card', ['position' => $pos, 'contact' => $contact_for($pos)]); ?>
<?php endforeach; ?>
</div>
<p class="positions__empty" data-position-empty hidden>In diesem Bereich sind aktuell keine Stellen ausgeschrieben. Schau bald wieder vorbei oder sprich uns einfach <a href="<?= e(url('') . '#kontakt') ?>">direkt an</a>, wir finden gemeinsam eine passende Aufgabe.</p>
</div>
</section>
<?php if (!empty($page['arguments']['items'])): ?>
<section class="section sponsor-arguments" id="warum-mithelfen" aria-labelledby="warum-heading">
<div class="container">
<h2 id="warum-heading"><?= e($page['arguments']['title']) ?></h2>
<?php if (!empty($page['arguments']['intro'])): ?>
<p class="sponsor-arguments__intro"><?= e($page['arguments']['intro']) ?></p>
<?php endif; ?>
<ul class="sponsor-arguments__grid" role="list">
<?php foreach ($page['arguments']['items'] as $arg): ?>
<li class="sponsor-arguments__item">
<div class="sponsor-arguments__icon" aria-hidden="true"><?= icon($arg['icon']) ?></div>
<div class="sponsor-arguments__body">
<h3 class="sponsor-arguments__title"><?= e($arg['title']) ?></h3>
<p class="sponsor-arguments__text"><?= e($arg['text']) ?></p>
</div>
</li>
<?php endforeach; ?>
</ul>
</div>
</section>
<?php endif; ?>
<?php
component('cta-band', ['band' => $page['cta'], 'anchor' => 'kontakt-mitmachen']);
component('faq', ['faq' => $page['faq'] ?? []]);