Neubau Fundament + Startseite: Designsystem, PHP-Komponenten, Brevo-Formular, Instagram-Sync
- Ordnerstruktur mit public/-Docroot, Deny-.htaccess für app/bin/config/data/storage
- CLAUDE.md mit Leitplanken (self-hosted only, Single Source of Truth, Component-first)
- Design-Tokens aus alter Seite extrahiert (Akzent #e20612, Coolvetica/Abel als woff2)
- Front Controller mit Routen-Register, Sitemap, Canonical, JSON-LD (SportsClub)
- Startseite: Hero, Instagram-Feed (lokaler Cache), Historie/Sportheim, Stats, Partner, Kontakt
- Kontaktformular via PHPMailer/Brevo mit Honeypot + HMAC-Time-Trap (kein reCAPTCHA)
- bin/instagram-sync.php: Scraper mit Strategie-Kette, flock, atomarem Cache, lokalen Bildern
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 21:16:25 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
// php -S Router-Modus: existierende Dateien (Assets) direkt ausliefern.
|
|
|
|
|
if (PHP_SAPI === 'cli-server') {
|
|
|
|
|
$file = __DIR__ . parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
|
|
|
|
if (is_file($file)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
Stadionzeitung, Veranstaltungen, News-Artikel, Lightbox und Anzeigen-Verteilung
Stadionzeitung: Live-Seiten (Tabellen, Ergebnisse, Vorschau, Termine, News,
Kontakt, Historie, Sportheim, Partner, Momente, Impressum), automatisches Cover,
Swipe-Viewer mit Vollbildmodus und die Druckfassung als Falt-Heft. Anzeigen
kommen jetzt aus dem Bestand und werden gleichmäßig verteilt, nie mehr als zwei
hintereinander (vorher vier Blöcke à fünf). Die Doppelseiten-Regel rechnet das
Skript selbst, statt sie von Hand zu prüfen.
Veranstaltungen: Bereich /veranstaltungen mit Detailseite pro Fest, Lebenszyklus
über das Datum (Ankündigung vor dem Fest, Rückblick danach), Galerie mit
Lightbox. veranstaltungen.json ist dritte Termin-Quelle, damit ein Fest nie
doppelt gepflegt wird.
News-Artikel als dritte dynamische Prefix-Route, gemeinsamer detail-head.
Behobene Fehler:
- Wappen-Kontexte setzten Zeilen-Layout auf .crest statt auf die Zeile; das
Wappen wurde zum Grid, Vereinsname und Tore rutschten darunter zusammen
(Ergebnis-Rückblick, Vorschau, Cover).
- --header-h (60px) unterschätzt die Kopfleiste um 32px (Logo 72px + 20px
Versatz): neues abgeleitetes --header-space, sonst klebten Zurück-Links
unter dem Logo.
- base_url in der Produktions-Config ohne www, während .htaccess auf www
umleitet; preflight prüft das jetzt.
- .lightbox brauchte display:none mit (0,2,0), sonst lag das Overlay offen.
Neue Werkzeuge: bin/anzeige-add.php (Anzeigen-Bestand), bin/veranstaltung-bilder.php
(Plakate und Galerien, Alt-Texte folgen der Quelldatei), bin/qr.php (Druck-QR mit
Warnung, wenn base_url nicht auf die echte Domain zeigt).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
2026-07-31 14:55:27 +02:00
|
|
|
// Eigenständige Apps unter public/ mit eigener index.php (z. B. /dateien/, die
|
|
|
|
|
// Dateiablage). Muss VOR der Trailing-Slash-Kanonisierung unten stehen, sonst
|
|
|
|
|
// schaukeln sich unser 301 und der des Dev-Servers gegenseitig auf.
|
|
|
|
|
// Docroot selbst ausschließen: für "/" löst sich $file auf public/ auf, und
|
|
|
|
|
// public/index.php (diese Datei) existiert immer — sonst würde die Startseite
|
|
|
|
|
// permanent an den Dev-Server durchgereicht und leer bleiben.
|
|
|
|
|
if (is_dir($file) && rtrim($file, '/') !== rtrim(__DIR__, '/') && is_file(rtrim($file, '/') . '/index.php')) {
|
|
|
|
|
// Verzeichnisse brauchen den Slash am Ende, sonst lösen die relativen URLs der
|
|
|
|
|
// App eine Ebene zu hoch auf: /dateien + "index.php?action=files" wird zu
|
|
|
|
|
// /index.php?action=files und landet hier im Front Controller — die App bekommt
|
|
|
|
|
// dann unsere Startseite als HTML statt JSON und meldet nur „Fehler".
|
|
|
|
|
// Apache erledigt das per mod_dir (DirectorySlash), der Dev-Server nicht.
|
|
|
|
|
$dirPath = (string) parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
|
|
|
|
if (!str_ends_with($dirPath, '/')) {
|
|
|
|
|
$query = $_SERVER['QUERY_STRING'] ?? '';
|
|
|
|
|
header('Location: ' . $dirPath . '/' . ($query !== '' ? '?' . $query : ''), true, 301);
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
Neubau Fundament + Startseite: Designsystem, PHP-Komponenten, Brevo-Formular, Instagram-Sync
- Ordnerstruktur mit public/-Docroot, Deny-.htaccess für app/bin/config/data/storage
- CLAUDE.md mit Leitplanken (self-hosted only, Single Source of Truth, Component-first)
- Design-Tokens aus alter Seite extrahiert (Akzent #e20612, Coolvetica/Abel als woff2)
- Front Controller mit Routen-Register, Sitemap, Canonical, JSON-LD (SportsClub)
- Startseite: Hero, Instagram-Feed (lokaler Cache), Historie/Sportheim, Stats, Partner, Kontakt
- Kontaktformular via PHPMailer/Brevo mit Honeypot + HMAC-Time-Trap (kein reCAPTCHA)
- bin/instagram-sync.php: Scraper mit Strategie-Kette, flock, atomarem Cache, lokalen Bildern
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 21:16:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
require dirname(__DIR__) . '/app/bootstrap.php';
|
|
|
|
|
|
|
|
|
|
$path = (string) parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
|
|
|
|
|
|
|
|
|
|
// Kanonisch ohne trailing slash (Root ausgenommen).
|
|
|
|
|
if ($path !== '/' && str_ends_with($path, '/')) {
|
|
|
|
|
$query = $_SERVER['QUERY_STRING'] ?? '';
|
|
|
|
|
header('Location: ' . rtrim($path, '/') . ($query !== '' ? '?' . $query : ''), true, 301);
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$slug = trim($path, '/');
|
|
|
|
|
|
|
|
|
|
// Formular-Submits.
|
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $slug === 'kontakt-senden') {
|
|
|
|
|
require APP_PATH . '/actions/contact-submit.php';
|
|
|
|
|
exit;
|
|
|
|
|
}
|
Neue Seiten: Mitglied/Partner werden, Sportheimbuchung & Ansprechpartner
Mitglied werden: Hub-Seite mit Online-Anmeldung (membership-form +
membership-submit-Action, Honeypot + Time-Trap), Abteilungswahl
(department-choice), Beitragstabelle (fee-table) und Turn-Aufnahmeantrag
als PDF-Download. Daten in data/mitglied-werden.json.
Partner werden & Sportheimbuchung: neue Seiten samt Routing und Navigation.
Ansprechpartner: echter Hero (Vereinsgelände), Bereiche aus dem group-Feld
der Single Source (Fußball / Turnen) zu zwei Sektionen gebündelt, Disziplin
als Karten-Überschrift. Fehlende Turn-Disziplin-Leads (Frisque, Lorz,
Kornatz, Trotzke) aus den Turnen-Unterseiten extrahiert und in
data/ansprechpartner.json ergänzt (allgemeine Turnabteilungs-Mail als
Kontakt). WhatsApp für alle Personen mit Telefonnummer. person-grid:
kompaktere Karten, Hairline unter der Bereichsüberschrift, 5 Spalten am
Desktop. Startseite auf die Haupt-Ansprechpartner je Bereich begrenzt.
Dazu: neue Icons (check-circle, download, file-earmark-text, whatsapp),
Anpassungen an Header/Footer/CTA/age-groups sowie tokens/layout/utilities.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RZsKTL5kRSG4KkK8w2LKkE
2026-06-19 11:16:10 +02:00
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $slug === 'mitglied-werden-senden') {
|
|
|
|
|
require APP_PATH . '/actions/membership-submit.php';
|
|
|
|
|
exit;
|
|
|
|
|
}
|
2026-06-20 20:46:44 +02:00
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $slug === 'sportheimbuchung-senden') {
|
|
|
|
|
require APP_PATH . '/actions/sportheimbuchung-senden.php';
|
|
|
|
|
exit;
|
|
|
|
|
}
|
Neubau Fundament + Startseite: Designsystem, PHP-Komponenten, Brevo-Formular, Instagram-Sync
- Ordnerstruktur mit public/-Docroot, Deny-.htaccess für app/bin/config/data/storage
- CLAUDE.md mit Leitplanken (self-hosted only, Single Source of Truth, Component-first)
- Design-Tokens aus alter Seite extrahiert (Akzent #e20612, Coolvetica/Abel als woff2)
- Front Controller mit Routen-Register, Sitemap, Canonical, JSON-LD (SportsClub)
- Startseite: Hero, Instagram-Feed (lokaler Cache), Historie/Sportheim, Stats, Partner, Kontakt
- Kontaktformular via PHPMailer/Brevo mit Honeypot + HMAC-Time-Trap (kein reCAPTCHA)
- bin/instagram-sync.php: Scraper mit Strategie-Kette, flock, atomarem Cache, lokalen Bildern
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 21:16:25 +02:00
|
|
|
|
|
|
|
|
$routes = require APP_PATH . '/routes.php';
|
|
|
|
|
|
|
|
|
|
// Sitemap aus dem Routen-Register generieren.
|
|
|
|
|
if ($slug === 'sitemap.xml') {
|
|
|
|
|
header('Content-Type: application/xml; charset=utf-8');
|
|
|
|
|
echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
|
|
|
|
|
echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
|
|
|
|
|
foreach ($routes as $routeSlug => $route) {
|
|
|
|
|
if (empty($route['sitemap'])) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
$lastmod = date('Y-m-d', (int) filemtime(APP_PATH . '/pages/' . $route['file']));
|
|
|
|
|
echo " <url><loc>" . e(abs_url($routeSlug)) . "</loc><lastmod>{$lastmod}</lastmod></url>\n";
|
|
|
|
|
}
|
Stadionzeitung, Veranstaltungen, News-Artikel, Lightbox und Anzeigen-Verteilung
Stadionzeitung: Live-Seiten (Tabellen, Ergebnisse, Vorschau, Termine, News,
Kontakt, Historie, Sportheim, Partner, Momente, Impressum), automatisches Cover,
Swipe-Viewer mit Vollbildmodus und die Druckfassung als Falt-Heft. Anzeigen
kommen jetzt aus dem Bestand und werden gleichmäßig verteilt, nie mehr als zwei
hintereinander (vorher vier Blöcke à fünf). Die Doppelseiten-Regel rechnet das
Skript selbst, statt sie von Hand zu prüfen.
Veranstaltungen: Bereich /veranstaltungen mit Detailseite pro Fest, Lebenszyklus
über das Datum (Ankündigung vor dem Fest, Rückblick danach), Galerie mit
Lightbox. veranstaltungen.json ist dritte Termin-Quelle, damit ein Fest nie
doppelt gepflegt wird.
News-Artikel als dritte dynamische Prefix-Route, gemeinsamer detail-head.
Behobene Fehler:
- Wappen-Kontexte setzten Zeilen-Layout auf .crest statt auf die Zeile; das
Wappen wurde zum Grid, Vereinsname und Tore rutschten darunter zusammen
(Ergebnis-Rückblick, Vorschau, Cover).
- --header-h (60px) unterschätzt die Kopfleiste um 32px (Logo 72px + 20px
Versatz): neues abgeleitetes --header-space, sonst klebten Zurück-Links
unter dem Logo.
- base_url in der Produktions-Config ohne www, während .htaccess auf www
umleitet; preflight prüft das jetzt.
- .lightbox brauchte display:none mit (0,2,0), sonst lag das Overlay offen.
Neue Werkzeuge: bin/anzeige-add.php (Anzeigen-Bestand), bin/veranstaltung-bilder.php
(Plakate und Galerien, Alt-Texte folgen der Quelldatei), bin/qr.php (Druck-QR mit
Warnung, wenn base_url nicht auf die echte Domain zeigt).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
2026-07-31 14:55:27 +02:00
|
|
|
// Stadionzeitung-Ausgaben sind keine routes.php-Einträge (dynamische Route,
|
|
|
|
|
// siehe unten) — hier separat aus data/stadionzeitung.json ergänzen.
|
|
|
|
|
foreach (json_load('stadionzeitung')['issues'] ?? [] as $issue) {
|
|
|
|
|
// Entwürfe tauchen nicht in der Sitemap auf (status-Feld, noindex).
|
|
|
|
|
if (empty($issue['slug']) || !stadionzeitung_is_published($issue)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
$loc = e(abs_url('stadionzeitung/' . $issue['slug']));
|
|
|
|
|
$lastmod = e((string) ($issue['date'] ?? date('Y-m-d')));
|
|
|
|
|
echo " <url><loc>{$loc}</loc><lastmod>{$lastmod}</lastmod></url>\n";
|
|
|
|
|
}
|
|
|
|
|
// Veranstaltungen: zweite dynamische Route, gleiches Muster.
|
|
|
|
|
// veranstaltungen_load() liefert bereits nur die veröffentlichten —
|
|
|
|
|
// Entwürfe tragen noindex und gehören nicht in die Sitemap.
|
|
|
|
|
//
|
|
|
|
|
// lastmod kommt hier NICHT aus dem Termin (anders als bei der
|
|
|
|
|
// Stadionzeitung, deren Ausgabe nach dem Veröffentlichen fixiert ist): eine
|
|
|
|
|
// Fest-Seite ändert sich gerade NACH ihrem Datum noch, wenn Rückblick und
|
|
|
|
|
// Bilder dazukommen. filemtime der Datenquelle ist damit die einzige
|
|
|
|
|
// Angabe, die nie zu alt ist.
|
|
|
|
|
$veranstaltungenLastmod = date('Y-m-d', (int) filemtime(DATA_PATH . '/veranstaltungen.json'));
|
|
|
|
|
foreach (veranstaltungen_load() as $veranstaltung) {
|
|
|
|
|
if (empty($veranstaltung['slug'])) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
$loc = e(abs_url('veranstaltungen/' . $veranstaltung['slug']));
|
|
|
|
|
echo " <url><loc>{$loc}</loc><lastmod>{$veranstaltungenLastmod}</lastmod></url>\n";
|
|
|
|
|
}
|
|
|
|
|
// News-Artikel: dritte dynamische Route. news_load() liefert nur die
|
|
|
|
|
// veröffentlichten, und ohne artikel[] existiert gar keine Seite.
|
|
|
|
|
//
|
|
|
|
|
// lastmod kommt hier aus dem Datum der Meldung (Stadionzeitungs-Muster,
|
|
|
|
|
// nicht Veranstaltungs-Muster): ein Artikel ist mit dem Erscheinen fertig,
|
|
|
|
|
// er wächst nicht wie eine Fest-Seite nach ihrem Datum weiter. filemtime
|
|
|
|
|
// der Datenquelle hätte hier sogar einen Nachteil — jede neue Meldung würde
|
|
|
|
|
// das lastmod ALLER Artikel zurücksetzen und Crawler auf unveränderte
|
|
|
|
|
// Seiten schicken.
|
|
|
|
|
foreach (news_load() as $item) {
|
|
|
|
|
if (!news_has_article($item)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
$loc = e(abs_url('news/' . $item['slug']));
|
|
|
|
|
$lastmod = e((string) ($item['date'] ?? date('Y-m-d')));
|
|
|
|
|
echo " <url><loc>{$loc}</loc><lastmod>{$lastmod}</lastmod></url>\n";
|
|
|
|
|
}
|
Neubau Fundament + Startseite: Designsystem, PHP-Komponenten, Brevo-Formular, Instagram-Sync
- Ordnerstruktur mit public/-Docroot, Deny-.htaccess für app/bin/config/data/storage
- CLAUDE.md mit Leitplanken (self-hosted only, Single Source of Truth, Component-first)
- Design-Tokens aus alter Seite extrahiert (Akzent #e20612, Coolvetica/Abel als woff2)
- Front Controller mit Routen-Register, Sitemap, Canonical, JSON-LD (SportsClub)
- Startseite: Hero, Instagram-Feed (lokaler Cache), Historie/Sportheim, Stats, Partner, Kontakt
- Kontaktformular via PHPMailer/Brevo mit Honeypot + HMAC-Time-Trap (kein reCAPTCHA)
- bin/instagram-sync.php: Scraper mit Strategie-Kette, flock, atomarem Cache, lokalen Bildern
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 21:16:25 +02:00
|
|
|
echo '</urlset>';
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
Stadionzeitung, Veranstaltungen, News-Artikel, Lightbox und Anzeigen-Verteilung
Stadionzeitung: Live-Seiten (Tabellen, Ergebnisse, Vorschau, Termine, News,
Kontakt, Historie, Sportheim, Partner, Momente, Impressum), automatisches Cover,
Swipe-Viewer mit Vollbildmodus und die Druckfassung als Falt-Heft. Anzeigen
kommen jetzt aus dem Bestand und werden gleichmäßig verteilt, nie mehr als zwei
hintereinander (vorher vier Blöcke à fünf). Die Doppelseiten-Regel rechnet das
Skript selbst, statt sie von Hand zu prüfen.
Veranstaltungen: Bereich /veranstaltungen mit Detailseite pro Fest, Lebenszyklus
über das Datum (Ankündigung vor dem Fest, Rückblick danach), Galerie mit
Lightbox. veranstaltungen.json ist dritte Termin-Quelle, damit ein Fest nie
doppelt gepflegt wird.
News-Artikel als dritte dynamische Prefix-Route, gemeinsamer detail-head.
Behobene Fehler:
- Wappen-Kontexte setzten Zeilen-Layout auf .crest statt auf die Zeile; das
Wappen wurde zum Grid, Vereinsname und Tore rutschten darunter zusammen
(Ergebnis-Rückblick, Vorschau, Cover).
- --header-h (60px) unterschätzt die Kopfleiste um 32px (Logo 72px + 20px
Versatz): neues abgeleitetes --header-space, sonst klebten Zurück-Links
unter dem Logo.
- base_url in der Produktions-Config ohne www, während .htaccess auf www
umleitet; preflight prüft das jetzt.
- .lightbox brauchte display:none mit (0,2,0), sonst lag das Overlay offen.
Neue Werkzeuge: bin/anzeige-add.php (Anzeigen-Bestand), bin/veranstaltung-bilder.php
(Plakate und Galerien, Alt-Texte folgen der Quelldatei), bin/qr.php (Druck-QR mit
Warnung, wenn base_url nicht auf die echte Domain zeigt).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
2026-07-31 14:55:27 +02:00
|
|
|
// Die drei dynamischen Routen der Seite (Stadionzeitungs-Ausgaben,
|
|
|
|
|
// Veranstaltungen, News-Artikel). routes.php kennt nur exakte Slugs, deshalb
|
|
|
|
|
// hier gezielte Prefix-Matches VOR dem normalen Lookup — die Page-Datei
|
|
|
|
|
// ermittelt den Detail-Slug selbst wieder aus der URL und schlägt ihn in ihrer
|
|
|
|
|
// JSON nach. Bewusst drei benannte Präfixe, KEINE generelle Wildcard-Fähigkeit.
|
|
|
|
|
//
|
|
|
|
|
// 'news/' ist kein neues Muster, sondern das dritte Vorkommen desselben:
|
|
|
|
|
// Detailseite zu einem Eintrag in einer data/*.json. Die Alternative wäre ein
|
|
|
|
|
// exakter routes.php-Eintrag pro Meldung — damit stünden redaktionelle Inhalte
|
|
|
|
|
// im Routen-Register und die Single-Source-Regel wäre gebrochen.
|
|
|
|
|
// Der exakte Slug 'news' (Übersicht) beginnt nicht mit 'news/' und bleibt
|
|
|
|
|
// davon unberührt; '/news/' wird oben auf '/news' 301-t.
|
|
|
|
|
$prefixRoutes = [
|
|
|
|
|
'stadionzeitung/' => 'stadionzeitung-ausgabe.php',
|
|
|
|
|
'veranstaltungen/' => 'veranstaltung.php',
|
|
|
|
|
'news/' => 'news-artikel.php',
|
|
|
|
|
];
|
|
|
|
|
foreach ($prefixRoutes as $prefix => $file) {
|
|
|
|
|
if (!str_starts_with($slug, $prefix)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
[$meta, $content] = render_page(APP_PATH . '/pages/' . $file);
|
|
|
|
|
// http_response_code() ohne Argument liest den Code zurück, den die Page-Datei
|
|
|
|
|
// ggf. gesetzt hat (globaler Engine-State, kein $meta-artiges Scope-Problem) —
|
|
|
|
|
// so bekommt meta.php für einen unbekannten Slug dieselbe 404-Behandlung wie
|
|
|
|
|
// die echte 404-Route (noindex, kein Self-Canonical).
|
|
|
|
|
$current = http_response_code() === 404 ? '404' : $slug;
|
|
|
|
|
require APP_PATH . '/layout.php';
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
Neubau Fundament + Startseite: Designsystem, PHP-Komponenten, Brevo-Formular, Instagram-Sync
- Ordnerstruktur mit public/-Docroot, Deny-.htaccess für app/bin/config/data/storage
- CLAUDE.md mit Leitplanken (self-hosted only, Single Source of Truth, Component-first)
- Design-Tokens aus alter Seite extrahiert (Akzent #e20612, Coolvetica/Abel als woff2)
- Front Controller mit Routen-Register, Sitemap, Canonical, JSON-LD (SportsClub)
- Startseite: Hero, Instagram-Feed (lokaler Cache), Historie/Sportheim, Stats, Partner, Kontakt
- Kontaktformular via PHPMailer/Brevo mit Honeypot + HMAC-Time-Trap (kein reCAPTCHA)
- bin/instagram-sync.php: Scraper mit Strategie-Kette, flock, atomarem Cache, lokalen Bildern
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 21:16:25 +02:00
|
|
|
if (isset($routes[$slug]) && $slug !== '404') {
|
|
|
|
|
$current = $slug;
|
|
|
|
|
} else {
|
|
|
|
|
http_response_code(404);
|
|
|
|
|
$current = '404';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[$meta, $content] = render_page(APP_PATH . '/pages/' . $routes[$current]['file']);
|
|
|
|
|
|
|
|
|
|
require APP_PATH . '/layout.php';
|