diff --git a/.claude/launch.json b/.claude/launch.json index cee3fe1..c49ac5a 100644 --- a/.claude/launch.json +++ b/.claude/launch.json @@ -128,6 +128,26 @@ "$env:APP_ENV='prod'; $env:APP_DEBUG='0'; $env:DATABASE_URL='sqlite:///C:/Django Project/KantSites/var/kantarci-immobilien.db'; $env:MEDIA_DIRECTORY='C:/Django Project/KantSites/var/kantarci-immobilien-media'; $env:SITE_ORIGIN='http://127.0.0.1:8099'; & C:/php84/php.exe -d upload_max_filesize=8M -d post_max_size=10M -d memory_limit=256M -S 127.0.0.1:8099 -t public bin/dev-router.php" ], "port": 8099 + }, + { + "name": "kantarci-markt", + "runtimeExecutable": "powershell", + "runtimeArgs": [ + "-NoProfile", + "-Command", + "$env:APP_ENV='dev'; $env:DATABASE_URL='sqlite:///C:/Django Project/KantSites/var/kantarci-markt.db'; $env:MEDIA_DIRECTORY='C:/Django Project/KantSites/var/kantarci-markt-media'; $env:SITE_ORIGIN='http://127.0.0.1:8100'; & C:/php84/php.exe -d upload_max_filesize=8M -d post_max_size=10M -d memory_limit=256M -S 127.0.0.1:8100 -t public bin/dev-router.php" + ], + "port": 8100 + }, + { + "name": "kantarci-markt-prod", + "runtimeExecutable": "powershell", + "runtimeArgs": [ + "-NoProfile", + "-Command", + "$env:APP_ENV='prod'; $env:APP_DEBUG='0'; $env:DATABASE_URL='sqlite:///C:/Django Project/KantSites/var/kantarci-markt.db'; $env:MEDIA_DIRECTORY='C:/Django Project/KantSites/var/kantarci-markt-media'; $env:SITE_ORIGIN='http://127.0.0.1:8101'; & C:/php84/php.exe -d upload_max_filesize=8M -d post_max_size=10M -d memory_limit=256M -S 127.0.0.1:8101 -t public bin/dev-router.php" + ], + "port": 8101 } ] -} +} \ No newline at end of file diff --git a/README.md b/README.md index a1fa86f..c0a9d82 100644 --- a/README.md +++ b/README.md @@ -385,6 +385,10 @@ Der Redesign-Entwurf von ebsolar.de (Elektro Bernhard Regenerative Energie GmbH, Die Landingpage „KANTARCI Immobilien – Mietanfrage“ (KANTARCI Immobilien GmbH, Baden-Baden) liegt in `var/kantarci-immobilien.db` mit Medien in `var/kantarci-immobilien-media`; die Startkonfiguration `kantarci-immobilien` in `.claude/launch.json` nutzt Port 8098, `kantarci-immobilien-prod` denselben Bestand im Produktionsmodus auf Port 8099 zum Messen. `sh scripts/reset-kantarci-immobilien.sh [Bilderordner]` (Standard `var/kantarci-immobilien-assets` mit `kantarci-banner.jpeg` aus dem gelieferten Paket) schneidet Logo, Hausmotiv und Seitensymbol aus dem Banner (`scripts/build-kantarci-assets.php`), zeichnet die Bildkarten der Mietanfrage (`node scripts/build-kantarci-options.mjs`), wirft die Installation weg und baut mit `scripts/seed-kantarci-immobilien.php` Gestaltung, Kopf- und Fußbereich, Startseite, Impressum, Datenschutz, 404, Einstellungen und das Formular `mietanfrage` (drei Schritte mit Bildkarten und Zusammenfassung) auf. Schriften sind Arial und Georgia, es gibt nichts zu holen. Die bewussten Abweichungen vom Paket stehen im Kopf des Skripts. +### Achte lokale Referenzwebsite: Kantarci Markt (Vorab-Seite zur Neueröffnung) + +Die Seite „Kantarci Markt – Neueröffnung“ (Wilhelm-Drapp-Straße, Baden-Baden) liegt in `var/kantarci-markt.db` mit Medien in `var/kantarci-markt-media`; die Startkonfiguration `kantarci-markt` in `.claude/launch.json` nutzt Port 8100, `kantarci-markt-prod` denselben Bestand im Produktionsmodus auf Port 8101 zum Messen. `sh scripts/reset-kantarci-markt.sh [Bilderordner]` (Standard `var/kantarci-markt-assets` mit `kantarci-markt-logo.png`, `market.jpg` und `vegetables.jpg` aus dem gelieferten Paket) schneidet das Logo zu und rastert Seitensymbol und Stempel (`node scripts/build-kantarci-markt-assets.mjs`), wirft die Installation weg, baut mit `scripts/seed-kantarci-markt.php` Gestaltung, Kopf- und Fußbereich, Startseite, Impressum, Datenschutz, 404 und Einstellungen auf und holt DM Sans auf den eigenen Server (`cms:fonts:fetch`, ersetzt in `fonts.css` nur die Regeln für DM Sans). Gemessen mit `MSYS_NO_PATHCONV=1 CMS_URL=http://127.0.0.1:8101 CMS_PATHS=/,/impressum,/datenschutz CMS_AUDIT_DIR=reports/lighthouse-kantarci-markt node scripts/audit.mjs` (in Git Bash verhindert `MSYS_NO_PATHCONV`, dass `/impressum` zu einem Windows-Pfad wird). Die bewussten Abweichungen vom Paket stehen im Kopf des Skripts. + Zusätzliche Prüfung mit den lokalen Kant-IT-Inhalten: `node scripts/admin-quality-check.mjs` (PHP über `PHP_BINARY` wählbar). Das Skript benötigt `var/kantit.db` und `var/kantit-media`, erstellt eine eigene Datenbankkopie und ein zufälliges Testkonto darin; es ändert die Pilotdatenbank nicht. Screenshots und Bericht landen in `reports/admin-quality/`. Die Livesuche prüft `npm run test:search` mit derselben isolierten Pilotkopie: Eingabe ohne Enter, langsame Antworten, Fehler/Wiederholen, Filter, Browserhistorie, Mediathek, Tastaturfokus, mobile Ansicht und axe. Ergebnisse stehen in `reports/live-search/`. Die allgemeine Verwaltungsprüfung ohne erneuten öffentlichen Bewegungstest startet `npm run test:admin -- --admin-only`. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 830f904..af581a4 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -711,6 +711,8 @@ Umut wollte an jedem Element eigenes CSS **und** eigenes Skript setzen können. Zwei echte Löcher kamen beim Testen heraus. `& .block-text` wurde zu `#id.block-text`, weil das führende Leerzeichen wegtrimmt wurde – aus dem Nachfahren wurde derselbe Knoten, die Regel traf das falsche Element. Und `url(//fremd.example/x.png)` kam durch, weil die Prüfung jedes `/` erlaubte: eine protokollrelative Adresse hätte über das Schema der Seite von Dritten geladen. Beides ist behoben und durch Tests abgedeckt. +**Nachtrag 23.09.2026: `content` mit Text.** Werte durften bisher keine Anführungszeichen enthalten, damit gab es kein `::before` oder `::after` – beim Nachbau von Kantarci Markt fehlten dadurch der Verlauf über einem Foto, ein Goldstrich und ein Buchstabe als Wasserzeichen. Jetzt nimmt `content` einen Text in einfachen oder doppelten Anführungszeichen an, höchstens 40 Zeichen aus Buchstaben, Ziffern, Leerzeichen und `.,:!?·•–-`. Kein `<`, kein Backslash, kein zweites Anführungszeichen: Der Wert kann weder den String noch das `${svg}`); + await seite.screenshot({ path: `${ordner}/${datei}`, omitBackground: true }); + console.log(`${datei} ${groesse} × ${groesse}`); +}; + +// Seitensymbol: das K mit Blatt aus dem Favicon der Vorlage. +await rastern(` + + +`, 256, 'symbol.png'); + +// Stempel: goldene Scheibe, Umschrift auf dem Kreis, Blatt in der Mitte. Doppelte Größe für +// scharfe Kanten auf hochauflösenden Bildschirmen (angezeigt mit 122 px). +await rastern(` + + + +FRISCHE • QUALITÄT • VERTRAUEN • + +`, 244, 'stempel.png'); + +await browser.close(); diff --git a/scripts/reset-kantarci-markt.sh b/scripts/reset-kantarci-markt.sh new file mode 100644 index 0000000..35c3a11 --- /dev/null +++ b/scripts/reset-kantarci-markt.sh @@ -0,0 +1,22 @@ +#!/bin/sh +# Wirft die Kantarci-Markt-Installation weg und baut sie neu auf. Nur fuer den Aufbau gedacht. +set -e +root="C:/Django Project/KantSites" +php="${PHP_BINARY:-C:/php84/php.exe}" +export DATABASE_URL="sqlite:///$root/var/kantarci-markt.db" +export MEDIA_DIRECTORY="$root/var/kantarci-markt-media" +rm -f "$root/var/kantarci-markt.db" +rm -rf "$root/var/kantarci-markt-media" +"$php" vendor/bin/doctrine-migrations migrate --no-interaction -q +assets="${1:-$root/var/kantarci-markt-assets}" +# Logo zuschneiden, Seitensymbol und Stempel rastern (Chromium ueber Playwright). +node scripts/build-kantarci-markt-assets.mjs "$assets" +"$php" -d memory_limit=512M scripts/seed-kantarci-markt.php "$assets" +# DM Sans vom eigenen Server. Der Abruf ersetzt in fonts.css nur die Regeln fuer DM Sans; die +# Schriften der anderen Installationen bleiben stehen. Das PHP 8.4 hier kennt keine +# Stammzertifikate; mit var/cacert.pem (https://curl.se/ca/cacert.pem) klappt der Abruf. +if [ -f "$root/var/cacert.pem" ]; then + "$php" -d curl.cainfo="$root/var/cacert.pem" -d openssl.cafile="$root/var/cacert.pem" bin/console cms:fonts:fetch +else + "$php" bin/console cms:fonts:fetch +fi diff --git a/scripts/seed-kantarci-markt.php b/scripts/seed-kantarci-markt.php new file mode 100644 index 0000000..db4cd55 --- /dev/null +++ b/scripts/seed-kantarci-markt.php @@ -0,0 +1,651 @@ +fetchOne('SELECT id FROM page LIMIT 1')) { + fwrite(STDERR, "Diese Datenbank enthält bereits Seiten. Abbruch, damit nichts überschrieben wird.\n"); + exit(1); +} + +$media = new MediaLibrary($db, $mediaDirectory); +$settings = new SiteSettings($db, $media); +$pages = new PageRepository($db, new DocumentValidator(), $settings); +$partials = new PartialRepository($db, new DocumentValidator()); +$theme = new Theme($db); + +$name = 'Kantarci Markt'; +// Die Domain kantarci-markt.de hat Mailserver (23.09.2026). Ob es das Postfach info@ gibt, ist +// nicht bestätigt – deshalb steht es nur im Impressum und dort als „NOCH ZU PRÜFEN“. +$mail = 'info@kantarci-markt.de'; +$strasse = 'Wilhelm-Drapp-Straße'; +$plz = '76532'; +$ort = 'Baden-Baden'; +$SCHEMA = 24; + +// --------------------------------------------------------------------------------------------- +// Bausteine +// --------------------------------------------------------------------------------------------- + +$seiten = static fn (string $w): array => ['top' => $w, 'right' => $w, 'bottom' => $w, 'left' => $w]; +$nurOben = static fn (string $w): array => ['top' => $w, 'right' => '0px', 'bottom' => '0px', 'left' => '0px']; +$nurLinks = static fn (string $w): array => ['top' => '0px', 'right' => '0px', 'bottom' => '0px', 'left' => $w]; +$nurUnten = static fn (string $w): array => ['top' => '0px', 'right' => '0px', 'bottom' => $w, 'left' => '0px']; +$rund = static fn (string $w): array => ['topLeft' => $w, 'topRight' => $w, 'bottomRight' => $w, 'bottomLeft' => $w]; + +$section = static function (string $id, array $children, array $style = [], ?string $anchor = null, string $css = ''): array { + $block = ['id' => $id, 'type' => 'section', 'children' => $children, 'style' => $style]; + if (null !== $anchor) { + $block['anchor'] = $anchor; + } + if ('' !== $css) { + $block['css'] = $css; + } + + return $block; +}; + +$row = static function (string $id, array $children, array $design = [], array $style = []): array { + $block = ['id' => $id, 'type' => 'row', 'children' => $children]; + if ([] !== $design) { + $block['layout'] = ['design' => $design]; + } + if ([] !== $style) { + $block['style'] = $style; + } + + return $block; +}; + +/** Eine Zeile, die nebeneinander stellt statt zu rastern. */ +$zeile = static function (string $id, array $children, string $gap, array $extra = [], array $mobil = [], array $tablet = []) use ($row): array { + $style = ['desktop' => ['display' => 'flex', 'alignItems' => 'center', 'gap' => $gap, 'margin' => ['top' => '0px'], ...$extra]]; + if ([] !== $tablet) { + $style['tablet'] = $tablet; + } + if ([] !== $mobil) { + $style['mobile'] = $mobil; + } + + return $row($id, $children, [], $style); +}; + +$column = static function (string $id, array $children, array $style = [], string $css = ''): array { + $block = ['id' => $id, 'type' => 'column', 'children' => $children]; + if ([] !== $style) { + $block['style'] = $style; + } + if ('' !== $css) { + $block['css'] = $css; + } + + return $block; +}; + +$heading = static function (string $id, string $text, int $level, array $style = [], string $css = ''): array { + $block = ['id' => $id, 'type' => 'heading', 'props' => ['text' => $text, 'level' => $level]]; + if ([] !== $style) { + $block['style'] = $style; + } + if ('' !== $css) { + $block['css'] = $css; + } + + return $block; +}; + +$text = static function (string $id, string $inhalt, array $style = [], string $css = ''): array { + $block = ['id' => $id, 'type' => 'text', 'props' => ['text' => $inhalt]]; + if ([] !== $style) { + $block['style'] = $style; + } + if ('' !== $css) { + $block['css'] = $css; + } + + return $block; +}; + +/** Eine reine Fläche: der kurze Goldstrich vor der Kleinzeile. */ +$flaeche = static function (string $id, array $style, string $css = ''): array { + $block = ['id' => $id, 'type' => 'spacer', 'props' => ['size' => 'small'], 'style' => $style]; + if ('' !== $css) { + $block['css'] = $css; + } + + return $block; +}; + +/** Ein Button ohne den Pfeil des Bausteins. */ +$button = static function (string $id, string $text, string $href, array $style = []): array { + return ['id' => $id, 'type' => 'button', 'props' => ['text' => $text, 'href' => $href], 'style' => $style, 'css' => '& span{display:none}']; +}; + +$bild = static function (string $id, array $eintrag, string $alt, array $style = [], ?string $href = null, string $css = '', string $bildzeile = ''): array { + $gross = end($eintrag['variants']); + $props = [ + 'src' => $gross['src'], 'alt' => $alt, 'decorative' => '' === $alt, 'width' => $gross['width'], 'height' => $gross['height'], + 'mediaId' => $eintrag['id'], + 'sources' => array_map(static fn (array $v): array => ['src' => $v['src'], 'width' => $v['width']], $eintrag['variants']), + ]; + if (null !== $href) { + $props['href'] = $href; + $props['click'] = 'link'; + } + if ('' !== $bildzeile) { + $props['caption'] = $bildzeile; + } + + $block = ['id' => $id, 'type' => 'image', 'props' => $props, 'style' => $style]; + if ('' !== $css) { + $block['css'] = $css; + } + + return $block; +}; + +$symbol = static function (string $id, string $name, int $groesse, array $style = []): array { + return ['id' => $id, 'type' => 'icon', 'props' => ['icon' => $name, 'size' => $groesse, 'decorative' => true, 'label' => ''], 'style' => $style]; +}; + +/** + * Setzt die Einblendungen nach einer Liste, statt sie über das ganze Dokument zu streuen. + * + * @param array $plan Block-ID => [Art, Verzögerung] + */ +$einblenden = static function (array $dokument, array $plan): array { + $gehen = function (array $bloecke) use (&$gehen, $plan): array { + foreach ($bloecke as $i => $block) { + if (isset($plan[$block['id'] ?? ''])) { + [$art, $verzug] = [...$plan[$block['id']], 'none']; + $bloecke[$i]['layout'] = [...($block['layout'] ?? []), 'effects' => ['entrance' => $art, 'delay' => $verzug]]; + } + if (isset($block['children'])) { + $bloecke[$i]['children'] = $gehen($block['children']); + } + } + + return $bloecke; + }; + $dokument['blocks'] = $gehen($dokument['blocks']); + + return $dokument; +}; + +// Wiederkehrende Gestaltungswerte der Vorlage. +$kleinStil = ['fontSize' => '14px', 'fontWeight' => '700', 'letterSpacing' => '0.2em', 'lineHeight' => 1.2, 'textTransform' => 'uppercase']; +$linkCss = "& a{color:#123b26;text-decoration:underline;text-underline-offset:3px}\n& a:hover{color:#806222}"; + +/** Die Kleinzeile in Versalien mit dem kurzen Goldstrich davor („— NEUERÖFFNUNG“). */ +$kleinzeile = static function (string $id, string $inhalt, string $farbe, string $strichFarbe = '@gold') use ($text, $zeile, $flaeche, $kleinStil): array { + return $zeile($id, [ + $flaeche($id.'-strich', ['desktop' => ['width' => '38px', 'height' => '2px', 'backgroundColor' => $strichFarbe, 'flexShrink' => 0]], 'min-height:0'), + $text($id.'-text', $inhalt, ['desktop' => [...$kleinStil, 'color' => $farbe, 'margin' => ['bottom' => '0px']]]), + ], '12px', ['margin' => ['top' => '0px', 'bottom' => '24px']]); +}; + +// --------------------------------------------------------------------------------------------- +// Website, Gestaltung, Medien +// --------------------------------------------------------------------------------------------- + +$db->insert('website', ['id' => 'default', 'name' => $name, 'default_locale' => 'de', 'theme' => '{}']); + +$theme->save( + [ + ['key' => 'forst', 'name' => 'Waldgrün', 'value' => '#123b26'], + ['key' => 'tief', 'name' => 'Tiefgrün (Fuß)', 'value' => '#0b281a'], + ['key' => 'gold', 'name' => 'Gold (Linien)', 'value' => '#ad893b'], + ['key' => 'gold-hell', 'name' => 'Gold hell (Flächen, auf Grün)', 'value' => '#d2b978'], + ['key' => 'gold-text', 'name' => 'Gold als Schrift auf Creme', 'value' => '#806222'], + ['key' => 'gold-titel', 'name' => 'Gold in großen Überschriften', 'value' => '#8c6b27'], + ['key' => 'creme', 'name' => 'Creme', 'value' => '#fcf9f2'], + ['key' => 'sand', 'name' => 'Sand (Abschluss)', 'value' => '#f4efe3'], + ['key' => 'text', 'name' => 'Fließtext', 'value' => '#58685e'], + ['key' => 'text-gruen', 'name' => 'Fließtext auf Grün', 'value' => '#b7c8c1'], + ['key' => 'klein-gruen', 'name' => 'Kleinzeile auf Grün', 'value' => '#a9bdb4'], + ['key' => 'fuss-text', 'name' => 'Schrift im Fuß', 'value' => '#a9bab2'], + ['key' => 'papier', 'name' => 'Weiß', 'value' => '#ffffff'], + ], + [ + ['key' => 'body', 'name' => 'Fließtext', 'family' => 'DM Sans', 'source' => 'google', 'weights' => [400, 500, 700], 'fallback' => 'sans-serif'], + ['key' => 'heading', 'name' => 'Überschriften', 'family' => 'Georgia', 'source' => 'system', 'weights' => [400], 'fallback' => 'serif'], + ], + ['fontFamily' => 'body', 'headingFontFamily' => 'heading', 'fontSize' => '16px', 'lineHeight' => 1.6, 'color' => '@forst', 'backgroundColor' => '@creme', 'linkColor' => '@forst', 'containerWidth' => '1280px', 'headingScale' => 1.25, 'linkUnderline' => false, 'imageRadius' => '0px'], + ['shape' => 'square', 'appearance' => 'solid', 'size' => 'normal', 'fontFamily' => 'inherit', 'fontSize' => 15, 'fontWeight' => 700, 'letterSpacing' => 0, 'textTransform' => 'none', 'backgroundColor' => '@forst', 'color' => '@papier', 'hoverBackgroundColor' => '#1d5236', 'hoverColor' => '@papier'], +); +echo "Farben, Schriften und Buttons gesetzt\n"; + +$medien = []; +foreach ([ + 'logo' => ['logo.png', 'Kantarci Markt – grünes K mit goldener Waage'], + 'symbol' => ['symbol.png', 'Bildmarke von Kantarci Markt'], + 'stempel' => ['stempel.png', ''], + 'markt' => ['market.jpg', 'Frisches Blattgemüse an einem Marktstand – Beispielbild'], + 'tomaten' => ['vegetables.jpg', 'Frische Strauchtomaten – Beispielbild'], +] as $schluessel => [$datei, $alt]) { + $pfad = rtrim($assets, '/\\').'/'.$datei; + if (!is_file($pfad)) { + fwrite(STDERR, "Bild fehlt: $pfad\n"); + exit(1); + } + $eintrag = $media->upload(new UploadedFile($pfad, basename($datei), null, null, true)); + $media->update($eintrag['id'], ['title' => pathinfo($datei, PATHINFO_FILENAME), 'alt' => $alt, 'archived' => false, 'version' => $eintrag['version']]); + $medien[$schluessel] = $media->find($eintrag['id']); + echo "Bild übernommen: $datei\n"; +} + +// --------------------------------------------------------------------------------------------- +// Kopf- und Fußbereich +// --------------------------------------------------------------------------------------------- + +$kopf = [ + 'schemaVersion' => $SCHEMA, 'title' => 'Kopfbereich', 'description' => 'Erscheint auf jeder Seite über dem Inhalt.', + 'blocks' => [ + $section('kopf', [ + $zeile('kopf-zeile', [ + $bild('kopf-logo', $medien['logo'], 'Kantarci Markt – zur Startseite', [ + // Volle Breite bis 300 px: sonst hängt die Breite in der zentrierten Zeile am + // geladenen Bild, und der Kopf springt beim Laden (CLS). + 'desktop' => ['width' => '100%', 'maxWidth' => '300px', 'margin' => $seiten('0px')], + 'mobile' => ['maxWidth' => '230px'], + ], '/', '& img{display:block;width:100%;height:auto}'), + ], '0px', ['justifyContent' => 'center']), + ], [ + 'desktop' => ['backgroundColor' => '@creme', 'padding' => ['top' => '18px', 'bottom' => '18px'], 'borderWidth' => $nurUnten('1px'), 'borderStyle' => 'solid', 'borderColor' => '@gold'], + 'mobile' => ['padding' => ['top' => '14px', 'bottom' => '14px']], + ]), + ], +]; + +$fussLink = "& a{color:#a9bab2}\n& a:hover{color:#fff;text-decoration:underline}"; +$fuss = [ + 'schemaVersion' => $SCHEMA, 'title' => 'Fußbereich', 'description' => 'Erscheint auf jeder Seite unter dem Inhalt.', + 'blocks' => [ + $section('fuss', [ + $zeile('fuss-zeile', [ + $text('fuss-copyright', '© 2026 '.$name, ['desktop' => ['fontSize' => '14px', 'letterSpacing' => '0.08em', 'margin' => ['bottom' => '0px']]]), + $text('fuss-adresse', $strasse.' · '.$ort, ['desktop' => ['fontSize' => '14px', 'letterSpacing' => '0.08em', 'margin' => ['bottom' => '0px']]]), + $zeile('fuss-links', [ + // Impressum und Datenschutz kommen aus den Einstellungen, nicht aus einem Menü. + ['id' => 'fuss-rechtliches', 'type' => 'legal', 'props' => ['label' => 'Rechtliches'], + 'style' => ['desktop' => ['fontSize' => '14px', 'gap' => '4px', 'color' => '@fuss-text']], + 'css' => $fussLink], + $text('fuss-agentur', 'Website by Kant-IT Solutions', ['desktop' => ['fontSize' => '14px', 'margin' => ['bottom' => '0px']]], $fussLink), + ], '22px', ['flexWrap' => 'wrap'], ['rowGap' => '10px', 'columnGap' => '18px']), + ], '20px', ['justifyContent' => 'space-between', 'flexWrap' => 'wrap'], + ['flexDirection' => 'column', 'alignItems' => 'flex-start', 'gap' => '12px']), + ], [ + 'desktop' => ['backgroundColor' => '@tief', 'color' => '@fuss-text', 'padding' => ['top' => '28px', 'bottom' => '28px']], + ]), + ], +]; + +$partials->create('header', 'default', 'Kopfbereich', $kopf); +$partials->create('footer', 'default', 'Fußbereich', $fuss); +echo "Kopf- und Fußbereich angelegt\n"; + +// --------------------------------------------------------------------------------------------- +// Startseite +// --------------------------------------------------------------------------------------------- + +/** Ein Versprechen: Nummer, Titel, Satz – auf dem Telefon rückt der Satz unter den Titel. */ +$versprechen = static fn (string $n, string $titel, string $satz): array => $zeile('versprechen-'.$n, [ + $text('versprechen-'.$n.'-nummer', $n, ['desktop' => ['fontSize' => '13px', 'fontWeight' => '700', 'color' => '@gold-hell', 'flexBasis' => '50px', 'flexShrink' => 0, 'margin' => ['bottom' => '0px']], 'mobile' => ['flexBasis' => '34px']]), + $zeile('versprechen-'.$n.'-inhalt', [ + $heading('versprechen-'.$n.'-titel', $titel, 3, [ + 'desktop' => ['fontSize' => '34px', 'fontWeight' => '400', 'lineHeight' => 1.2, 'color' => '@papier', 'flexGrow' => 3, 'flexBasis' => '0', 'margin' => ['bottom' => '0px']], + 'tablet' => ['fontSize' => '28px'], + 'mobile' => ['fontSize' => '28px'], + ]), + $text('versprechen-'.$n.'-satz', $satz, ['desktop' => ['fontSize' => '16px', 'lineHeight' => 1.6, 'color' => '@text-gruen', 'maxWidth' => '390px', 'flexGrow' => 5, 'flexBasis' => '0', 'margin' => ['bottom' => '0px']]]), + ], '20px', ['alignItems' => 'baseline', 'flexGrow' => 1, 'flexBasis' => '0', 'minWidth' => '0'], + ['flexDirection' => 'column', 'alignItems' => 'flex-start', 'gap' => '8px']), +], '20px', ['alignItems' => 'baseline', 'padding' => ['top' => '31px', 'bottom' => '31px'], 'borderWidth' => $nurUnten('1px'), 'borderStyle' => 'solid', 'borderColor' => '#cfb57359']); + +$startseite = [ + 'schemaVersion' => $SCHEMA, + 'title' => 'Neueröffnung', + 'description' => 'Kantarci Markt eröffnet bald: Frische, Qualität und faire Preise in der Wilhelm-Drapp-Straße in Baden-Baden.', + 'blocks' => [ + // 1 – Hero: Text links, rechts das Marktfoto mit Bildzeile, die gedrehte Tomatenkarte und + // der Stempel. + $section('hero', [ + $row('hero-zeile', [ + $column('hero-text', [ + $kleinzeile('hero-label', 'Neueröffnung', '@gold-text'), + $heading('hero-titel', 'Frische bekommt
eine neue Adresse.', 1, [ + 'desktop' => ['fontSize' => '80px', 'fontWeight' => '400', 'lineHeight' => 1.04, 'letterSpacing' => '-0.045em', 'color' => '@forst', 'margin' => ['bottom' => '0px']], + 'tablet' => ['fontSize' => '64px'], + 'mobile' => ['fontSize' => '44px', 'lineHeight' => 1.07], + ], '& em{font-style:italic;color:#8c6b27}'), + $text('hero-intro', 'Freuen Sie sich auf sorgfältig ausgewählte Produkte, echte Vielfalt und einen Markt, der Nachbarschaft neu denkt.', [ + 'desktop' => ['maxWidth' => '600px', 'fontSize' => '20px', 'lineHeight' => 1.65, 'color' => '@text', 'margin' => ['top' => '32px', 'bottom' => '38px']], + 'mobile' => ['fontSize' => '17px', 'margin' => ['top' => '24px', 'bottom' => '30px']], + ]), + // Eine Spalte statt einer Zeile: Zeilen sind im Kern immer durchsichtig. + $column('hero-termin', [ + $column('hero-termin-symbol', [ + $symbol('hero-termin-icon', 'shop', 24, ['desktop' => ['color' => '@forst']]), + ], ['desktop' => ['display' => 'flex', 'alignItems' => 'center', 'justifyContent' => 'center', 'width' => '50px', 'height' => '50px', 'flexShrink' => 0, 'backgroundColor' => '@gold-hell', 'borderRadius' => $rund('50%')]]), + $column('hero-termin-text', [ + $text('hero-termin-klein', 'Wir öffnen bald', ['desktop' => ['fontSize' => '14px', 'letterSpacing' => '0.08em', 'textTransform' => 'uppercase', 'lineHeight' => 1.3, 'color' => '#bfd1c9', 'margin' => ['bottom' => '3px']]]), + $text('hero-termin-satz', 'Eröffnungstermin folgt', ['desktop' => ['fontSize' => '16px', 'fontWeight' => '700', 'lineHeight' => 1.2, 'color' => '@papier', 'margin' => ['bottom' => '0px']]]), + ]), + ], [ + 'desktop' => ['display' => 'flex', 'alignItems' => 'center', 'gap' => '17px', 'backgroundColor' => '@forst', 'borderWidth' => $seiten('1px'), 'borderStyle' => 'solid', 'borderColor' => '@gold', + 'borderRadius' => $rund('3px'), 'padding' => ['top' => '15px', 'right' => '24px', 'bottom' => '15px', 'left' => '15px'], + 'shadowY' => '18px', 'shadowBlur' => '50px', 'shadowColor' => '#062f2429'], + 'mobile' => ['gap' => '12px', 'padding' => ['top' => '14px', 'right' => '16px', 'bottom' => '14px', 'left' => '14px']], + ]), + $text('hero-adresse', 'Hier finden Sie uns bald
'.$strasse.'
'.$ort, [ + 'desktop' => ['fontSize' => '16px', 'lineHeight' => 1.55, 'color' => '@text', 'borderWidth' => $nurLinks('3px'), 'borderStyle' => 'solid', 'borderColor' => '@gold', 'padding' => ['left' => '18px'], 'margin' => ['top' => '28px', 'bottom' => '0px']], + ], "& strong{color:#123b26;font-weight:700}"), + ], [ + 'desktop' => ['display' => 'flex', 'flexDirection' => 'column', 'alignItems' => 'flex-start', 'justifyContent' => 'center'], + ]), + $column('hero-visual', [ + $bild('hero-markt', $medien['markt'], 'Frisches Blattgemüse an einem Marktstand – Beispielbild', [ + 'desktop' => ['position' => 'absolute', 'top' => '0px', 'right' => '4%', 'bottom' => '8%', 'left' => '5%', 'margin' => $seiten('0px'), 'overflow' => 'hidden', 'backgroundColor' => '@forst', + 'outlineWidth' => '1px', 'outlineStyle' => 'solid', 'outlineColor' => '@gold', 'outlineOffset' => '8px', + 'shadowY' => '24px', 'shadowBlur' => '60px', 'shadowColor' => '#021d162e'], + 'mobile' => ['right' => '0px', 'bottom' => '10%', 'left' => '0px'], + ], null, + "& img{display:block;width:100%;height:100%;object-fit:cover}\n" + ."&::after{content:'';position:absolute;top:25%;right:0;bottom:0;left:0;background:linear-gradient(transparent,rgba(2,29,22,.85) 65%)}\n" + ."& figcaption{position:absolute;z-index:1;bottom:150px;left:28px;right:28px;max-width:260px;margin:0;color:#fff;font-size:26px;font-weight:700;line-height:1.2;text-shadow:0 2px 14px rgba(2,29,22,.7)}", + 'Vorfreude auf frische Vielfalt.'), + $bild('hero-tomaten', $medien['tomaten'], 'Frische Strauchtomaten – Beispielbild', [ + 'desktop' => ['position' => 'absolute', 'width' => '49%', 'height' => '33%', 'right' => '-3%', 'bottom' => '-1%', 'margin' => $seiten('0px'), 'overflow' => 'hidden', 'rotate' => 4, + 'borderWidth' => $seiten('8px'), 'borderStyle' => 'solid', 'borderColor' => '@creme', 'shadowY' => '24px', 'shadowBlur' => '60px', 'shadowColor' => '#021d162e', 'zIndex' => 2], + 'mobile' => ['width' => '63%', 'height' => '39%', 'right' => '0px', 'borderWidth' => $seiten('7px')], + ], null, '& img{display:block;width:100%;height:100%;object-fit:cover}'), + $bild('hero-stempel', $medien['stempel'], '', [ + 'desktop' => ['position' => 'absolute', 'maxWidth' => '122px', 'width' => '122px', 'left' => '-1%', 'bottom' => '9%', 'margin' => $seiten('0px'), 'rotate' => -12, 'zIndex' => 3, + 'borderRadius' => $rund('50%'), 'shadowY' => '8px', 'shadowBlur' => '24px', 'shadowColor' => '#123b2626'], + 'mobile' => ['maxWidth' => '96px', 'width' => '96px', 'left' => '-2%', 'bottom' => '5%'], + ], null, '& img{display:block;width:100%;height:auto}'), + ], [ + 'desktop' => ['position' => 'relative', 'minHeight' => '600px'], + 'tablet' => ['minHeight' => '520px'], + 'mobile' => ['minHeight' => '390px'], + ]), + ], ['desktop' => ['columns' => 'two'], 'tablet' => ['columns' => 'one'], 'mobile' => ['columns' => 'one']], [ + 'desktop' => ['gap' => '80px', 'alignItems' => 'center', 'margin' => ['top' => '0px'], 'columnWidths' => '51 49'], + 'tablet' => ['gap' => '56px'], + 'mobile' => ['gap' => '40px'], + ]), + ], [ + 'desktop' => ['backgroundColor' => '@creme', 'padding' => ['top' => '72px', 'bottom' => '84px'], 'overflow' => 'hidden'], + 'tablet' => ['padding' => ['top' => '56px', 'bottom' => '72px']], + 'mobile' => ['padding' => ['top' => '40px', 'bottom' => '64px']], + ], 'start'), + + // 2 – Das Versprechen auf Waldgrün. + $section('versprechen', [ + $row('versprechen-zeile', [ + $zeile('versprechen-intro', [ + $text('versprechen-nummer', '01', ['desktop' => ['fontSize' => '13px', 'fontWeight' => '700', 'color' => '@gold-hell', 'flexBasis' => '52px', 'flexShrink' => 0, 'margin' => ['top' => '4px', 'bottom' => '0px']], 'mobile' => ['flexBasis' => '38px']]), + $column('versprechen-titel', [ + $text('versprechen-klein', 'Unser Versprechen', ['desktop' => [...$kleinStil, 'letterSpacing' => '0.18em', 'color' => '@klein-gruen', 'margin' => ['bottom' => '20px']]]), + $heading('versprechen-h2', 'Mehr als
einkaufen.', 2, [ + 'desktop' => ['fontSize' => '80px', 'fontWeight' => '400', 'lineHeight' => 1.04, 'letterSpacing' => '-0.035em', 'color' => '@papier', 'margin' => ['bottom' => '0px']], + 'tablet' => ['fontSize' => '64px'], + 'mobile' => ['fontSize' => '48px'], + ]), + ]), + ], '0px', ['alignItems' => 'flex-start']), + $column('versprechen-liste', [ + $versprechen('01', 'Frisch', 'Ausgewählte Produkte, die jeden Einkauf ein bisschen besser machen.'), + $versprechen('02', 'Qualität', 'Sorgfältig ausgewählt, mit Vertrauen gekauft. Gute Produkte zu fairen Preisen.'), + $versprechen('03', 'Vielfältig', 'Ein Sortiment für den Alltag, besondere Momente und jeden Geschmack.'), + ], ['desktop' => ['borderWidth' => $nurOben('1px'), 'borderStyle' => 'solid', 'borderColor' => '#cfb573a6']]), + ], ['desktop' => ['columns' => 'two'], 'tablet' => ['columns' => 'one'], 'mobile' => ['columns' => 'one']], [ + 'desktop' => ['gap' => '120px', 'alignItems' => 'flex-start', 'margin' => ['top' => '0px'], 'columnWidths' => '3 5'], + 'tablet' => ['gap' => '56px'], + 'mobile' => ['gap' => '48px'], + ]), + ], [ + 'desktop' => ['backgroundColor' => '@forst', 'color' => '@papier', 'padding' => ['top' => '140px', 'bottom' => '140px'], 'borderWidth' => $nurOben('4px'), 'borderStyle' => 'solid', 'borderColor' => '@gold'], + 'tablet' => ['padding' => ['top' => '100px', 'bottom' => '100px']], + 'mobile' => ['padding' => ['top' => '80px', 'bottom' => '80px']], + ], 'unser-versprechen'), + + // 3 – Abschluss: Handschrift links, Gruß rechts, das große K als Wasserzeichen. + $section('abschluss', [ + $row('abschluss-zeile', [ + $text('abschluss-spruch', 'Frische. Qualität.
Faire Preise.', [ + 'desktop' => ['fontFamily' => 'heading', 'fontStyle' => 'italic', 'fontSize' => '84px', 'lineHeight' => 1.12, 'letterSpacing' => '-0.05em', 'color' => '@forst', 'margin' => ['bottom' => '0px']], + 'tablet' => ['fontSize' => '64px'], + 'mobile' => ['fontSize' => '48px'], + ]), + $column('abschluss-gruss', [ + $text('abschluss-gruss-text', 'Wir bereiten gerade alles für Sie vor.', ['desktop' => ['fontSize' => '17px', 'color' => '@text', 'margin' => ['bottom' => '10px']]]), + $text('abschluss-gruss-satz', 'Bis ganz bald im Kantarci Markt.', ['desktop' => ['fontFamily' => 'heading', 'fontSize' => '34px', 'lineHeight' => 1.25, 'color' => '@forst', 'margin' => ['bottom' => '0px']], 'mobile' => ['fontSize' => '26px']]), + ], ['desktop' => ['maxWidth' => '390px', 'alignSelf' => 'center']]), + ], ['desktop' => ['columns' => 'two'], 'tablet' => ['columns' => 'one'], 'mobile' => ['columns' => 'one']], [ + 'desktop' => ['gap' => '40px', 'alignItems' => 'center', 'margin' => ['top' => '0px'], 'position' => 'relative', 'zIndex' => 2], + 'tablet' => ['gap' => '56px'], + ]), + ], [ + 'desktop' => ['backgroundColor' => '@sand', 'position' => 'relative', 'overflow' => 'hidden', 'minHeight' => '450px', 'padding' => ['top' => '130px', 'bottom' => '130px'], 'borderWidth' => $nurUnten('1px'), 'borderStyle' => 'solid', 'borderColor' => '@gold'], + 'tablet' => ['padding' => ['top' => '100px', 'bottom' => '110px']], + 'mobile' => ['padding' => ['top' => '80px', 'bottom' => '96px']], + ], 'bis-bald', "&::before{content:'K';position:absolute;right:3%;top:4%;font-family:Georgia,serif;font-size:min(30vw,480px);line-height:1;color:rgba(6,47,36,.045);pointer-events:none}\n" + ."&::after{content:'';position:absolute;right:7%;bottom:58px;width:180px;height:2px;background:#ad893b}"), + ], +]; + +// --------------------------------------------------------------------------------------------- +// Seiten anlegen und veröffentlichen +// --------------------------------------------------------------------------------------------- + +$anlegen = static function (string $slug, array $dokument, string $seoTitel = '', string $teilbild = '') use ($pages, $db): string { + $id = $pages->createPage($slug, null, 'page', $dokument); + if ('' !== $seoTitel) { + $db->executeStatement('UPDATE page SET seo_title = ? WHERE id = ?', [$seoTitel, $id]); + } + if ('' !== $teilbild) { + $db->executeStatement('UPDATE page SET og_media_id = ? WHERE id = ?', [$teilbild, $id]); + } + $seite = $db->fetchAssociative('SELECT draft_revision_id, version FROM page WHERE id = ?', [$id]); + $pages->publish($id, $seite['draft_revision_id'], (int) $seite['version']); + echo 'Seite angelegt: /'.$db->fetchOne('SELECT path FROM page WHERE id = ?', [$id])."\n"; + + return $id; +}; + +// Der Text des Hero spielt beim Laden; die Fotos bleiben ohne Einblendung (LCP). +$startseite = $einblenden($startseite, [ + 'hero-label' => ['fade'], + 'hero-titel' => ['rise', 'short'], + 'hero-intro' => ['rise', 'medium'], + 'hero-termin' => ['rise', 'long'], + 'hero-adresse' => ['fade', 'long'], + 'versprechen-intro' => ['rise'], + 'versprechen-liste' => ['rise', 'short'], + 'abschluss-spruch' => ['rise'], + 'abschluss-gruss' => ['rise', 'short'], +]); + +$anlegen('home', $startseite, 'Kantarci Markt – Neueröffnung in Baden-Baden', $medien['markt']['id']); + +/** Eine Rechtsseite: Kleinzeile, Titel und Vorspann auf Waldgrün, dann Absätze mit Zwischenüberschriften. */ +$rechtsseite = static function (string $praefix, string $label, string $titel, string $vorspann, array $absaetze) use ($section, $row, $column, $kleinzeile, $heading, $text, $nurOben, $linkCss): array { + $kopf = $section($praefix.'-kopf', [ + $kleinzeile($praefix.'-label', $label, '@gold-hell', '@gold-hell'), + $heading($praefix.'-h1', $titel, 1, [ + 'desktop' => ['fontSize' => '56px', 'fontWeight' => '400', 'lineHeight' => 1.1, 'letterSpacing' => '-0.03em', 'color' => '@papier', 'margin' => ['bottom' => '20px']], + 'mobile' => ['fontSize' => '40px'], + ]), + $text($praefix.'-vorspann', $vorspann, [ + 'desktop' => ['maxWidth' => '640px', 'fontSize' => '18px', 'lineHeight' => 1.7, 'color' => '@text-gruen', 'margin' => ['bottom' => '0px']], + 'mobile' => ['fontSize' => '16px'], + ]), + ], [ + 'desktop' => ['backgroundColor' => '@forst', 'color' => '@papier', 'padding' => ['top' => '76px', 'bottom' => '68px'], 'borderWidth' => $nurOben('4px'), 'borderStyle' => 'solid', 'borderColor' => '@gold'], + 'mobile' => ['padding' => ['top' => '48px', 'bottom' => '44px']], + ]); + + $punkte = []; + foreach ($absaetze as $i => [$zwischentitel, $inhalt]) { + $punkte[] = $row($praefix.'-punkt-'.$i, [ + $column($praefix.'-punkt-'.$i.'-titel', '' === $zwischentitel ? [] : [ + $heading($praefix.'-h2-'.$i, $zwischentitel, 2, [ + 'desktop' => ['fontFamily' => 'body', 'fontSize' => '16px', 'fontWeight' => '700', 'lineHeight' => 1.5, 'letterSpacing' => '0', 'color' => '@forst', 'margin' => ['bottom' => '0px']], + ]), + ], [ + 'desktop' => ['flexBasis' => '260px', 'flexShrink' => 0], + 'tablet' => ['flexBasis' => '200px'], + 'mobile' => ['flexBasis' => 'auto'], + ]), + $column($praefix.'-punkt-'.$i.'-text', [ + $text($praefix.'-p-'.$i, $inhalt, ['desktop' => ['fontSize' => '16px', 'lineHeight' => 1.75, 'color' => '@text', 'margin' => ['bottom' => '0px']]], $linkCss), + ], ['desktop' => ['flexGrow' => 1, 'flexBasis' => '0', 'minWidth' => '0'], 'mobile' => ['flexBasis' => 'auto']]), + ], [], [ + 'desktop' => ['display' => 'flex', 'alignItems' => 'flex-start', 'gap' => '44px', 'margin' => ['top' => '0px'], + 'borderWidth' => $nurOben('1px'), 'borderStyle' => 'solid', 'borderColor' => '#e4dcc8', 'padding' => ['top' => '26px', 'bottom' => '26px']], + 'tablet' => ['gap' => '28px'], + 'mobile' => ['flexDirection' => 'column', 'gap' => '8px', 'padding' => ['top' => '20px', 'bottom' => '20px']], + ]); + } + + $inhalt = $section($praefix, [ + $column($praefix.'-inhalt', $punkte, ['desktop' => ['maxWidth' => '920px']]), + ], ['desktop' => ['padding' => ['top' => '48px', 'bottom' => '90px']], 'mobile' => ['padding' => ['top' => '24px', 'bottom' => '56px']]]); + + return [$kopf, $inhalt]; +}; + +$pruefen = static fn (string $was): string => 'NOCH ZU PRÜFEN: '.$was; + +$anlegen('impressum', [ + 'schemaVersion' => $SCHEMA, 'title' => 'Impressum', + 'description' => 'Anbieterangaben von Kantarci Markt, Baden-Baden.', + 'blocks' => [ + ...$rechtsseite('impressum', 'Rechtliche Angaben', 'Impressum', 'Angaben gemäß § 5 DDG und § 18 Abs. 2 MStV.', [ + ['Anbieter', $name.'
'.$pruefen('Inhaber bzw. Firma mit Rechtsform (etwa „… GmbH“ oder „Inhaber …, e. K.“).').'
'.$strasse.' '.$pruefen('Hausnummer').'
'.$plz.' '.$ort], + ['Vertreten durch', $pruefen('Geschäftsführer bzw. Inhaber.')], + ['Registereintrag', $pruefen('Handelsregisternummer und Registergericht, falls eingetragen.')], + ['Kontakt', "E-Mail: $mail
".$pruefen('Ob das Postfach existiert, und eine Telefonnummer als zweiten schnellen Kontaktweg ergänzen.')], + ['Umsatzsteuer', $pruefen('Umsatzsteuer-Identifikationsnummer nach § 27a UStG, falls vorhanden.')], + ['Verantwortlich für den Inhalt', $pruefen('Name der verantwortlichen Person, Anschrift wie oben.')], + ['Verbraucherstreitbeilegung', 'Wir sind nicht bereit und nicht verpflichtet, an einem Streitbeilegungsverfahren vor einer Verbraucherschlichtungsstelle teilzunehmen.'], + ['Haftung für Inhalte', 'Als Diensteanbieter sind wir für eigene Inhalte auf diesen Seiten nach den allgemeinen Gesetzen verantwortlich. Wir sind jedoch nicht verpflichtet, übermittelte oder gespeicherte fremde Informationen zu überwachen oder nach Umständen zu forschen, die auf eine rechtswidrige Tätigkeit hinweisen. Bei Bekanntwerden von Rechtsverletzungen entfernen wir diese Inhalte umgehend.'], + ['Urheberrecht', 'Die von uns erstellten Inhalte und Werke auf diesen Seiten unterliegen dem deutschen Urheberrecht. Vervielfältigung, Bearbeitung, Verbreitung und jede Art der Verwertung außerhalb der Grenzen des Urheberrechts bedürfen der schriftlichen Zustimmung.'], + ['Bildnachweis', 'Logo: '.$name.'. Die Fotos von Marktstand und Tomaten sind Beispielbilder. '.$pruefen('Quelle und Lizenz der beiden Fotos eintragen.')], + ]), + ], +], 'Impressum | '.$name); + +$anlegen('datenschutz', [ + 'schemaVersion' => $SCHEMA, 'title' => 'Datenschutz', + 'description' => 'Welche Daten beim Besuch der Website von Kantarci Markt verarbeitet werden.', + 'blocks' => [ + ...$rechtsseite('datenschutz', 'Ihre Daten', 'Datenschutzhinweise', 'Hier erfahren Sie, welche Daten beim Besuch dieser Website verarbeitet werden – und welche Rechte Sie haben.', [ + ['1. Verantwortlicher', $name.', '.$strasse.', '.$plz.' '.$ort.". E-Mail $mail.

".$pruefen('Inhaber bzw. Firma wie im Impressum.')], + ['2. Besuch dieser Website', 'Beim Aufruf verarbeitet der Hostingdienst technisch erforderliche Verbindungsdaten: IP-Adresse, Zeitpunkt, angeforderte Adresse, Browserangaben und HTTP-Status. Das dient der sicheren Bereitstellung und der Fehlersuche. Rechtsgrundlage ist unser berechtigtes Interesse an einem störungsfreien Betrieb, Art. 6 Abs. 1 lit. f DSGVO.

'.$pruefen('Hostinganbieter benennen, Auftragsverarbeitungsvertrag abschließen und die Speicherdauer der Serverprotokolle eintragen.')], + ['3. Cookies und externe Dienste', 'Diese Website setzt keine Analyse- oder Werbecookies. Bilder, Schriften und Gestaltung kommen vom eigenen Server; es werden keine Karten, Videos oder Schriften von Dritten nachgeladen. Deshalb gibt es auch keinen Einwilligungsdialog.'], + ['4. Kontakt per E-Mail', 'Wenn Sie uns schreiben, verarbeiten wir Ihre Adresse und Ihre Nachricht, um Ihr Anliegen zu beantworten (Art. 6 Abs. 1 lit. b oder f DSGVO). Wir löschen die Nachricht, wenn sie erledigt ist und keine gesetzliche Aufbewahrungspflicht besteht.'], + ['5. Ihre Rechte', 'Sie haben unter den gesetzlichen Voraussetzungen das Recht auf Auskunft, Berichtigung, Löschung, Einschränkung der Verarbeitung und Datenübertragbarkeit. Einer Verarbeitung auf Grundlage von Art. 6 Abs. 1 lit. f DSGVO können Sie widersprechen.'], + ['6. Beschwerderecht', 'Sie können sich bei einer Datenschutz-Aufsichtsbehörde beschweren, für uns beim Landesbeauftragten für den Datenschutz und die Informationsfreiheit Baden-Württemberg.'], + ['', "Fragen zum Datenschutz beantworten wir gern unter $mail."], + ]), + ], +], 'Datenschutz | '.$name); + +$fehlerId = $anlegen('404', [ + 'schemaVersion' => $SCHEMA, 'title' => 'Seite nicht gefunden', + 'description' => 'Die gesuchte Seite gibt es nicht oder sie wurde verschoben.', + 'blocks' => [ + $section('fehler', [ + $kleinzeile('fehler-label', 'Fehler 404', '@gold-text'), + $heading('fehler-titel', 'Diese Seite
gibt es nicht.', 1, [ + 'desktop' => ['fontSize' => '64px', 'fontWeight' => '400', 'lineHeight' => 1.05, 'letterSpacing' => '-0.04em', 'color' => '@forst', 'margin' => ['bottom' => '22px']], + 'mobile' => ['fontSize' => '42px'], + ], '& em{font-style:italic;color:#8c6b27}'), + $text('fehler-text', 'Vielleicht hat sich ein Tippfehler in die Adresse geschlichen. Über die Startseite finden Sie zurück.', [ + 'desktop' => ['maxWidth' => '460px', 'fontSize' => '18px', 'lineHeight' => 1.7, 'color' => '@text', 'margin' => ['bottom' => '28px']], + ]), + $button('fehler-knopf', 'Zur Startseite', '/', ['desktop' => ['margin' => ['top' => '0px']]]), + ], [ + 'desktop' => ['backgroundColor' => '@creme', 'padding' => ['top' => '110px', 'bottom' => '130px']], + 'mobile' => ['padding' => ['top' => '64px', 'bottom' => '80px']], + ]), + ], +]); +$db->executeStatement('UPDATE page SET robots = ? WHERE id = ?', ['noindex', $fehlerId]); + +// --------------------------------------------------------------------------------------------- +// Einstellungen, Konto +// --------------------------------------------------------------------------------------------- + +$settings->assignNotFoundPage($fehlerId, ''); +$settings->save([ + 'legalName' => $name, + 'description' => 'Kantarci Markt eröffnet bald in der Wilhelm-Drapp-Straße in Baden-Baden: frische Produkte, echte Vielfalt und faire Preise.', + 'email' => '', + 'street' => $strasse, 'postalCode' => $plz, 'city' => $ort, 'country' => 'DE', + // Noch kein Geschäft mit Öffnungszeiten: keine LocalBusiness-Angaben, bis der Markt öffnet. + 'localBusiness' => false, 'llmsTxt' => true, + 'faviconMediaId' => $medien['symbol']['id'], + 'stickyHeader' => false, 'headerOverlay' => false, + 'motion' => 'always', + 'homePath' => 'home', 'imprintPath' => 'impressum', 'privacyPath' => 'datenschutz', + // Die Seite lädt nichts von Dritten; deshalb keine Einwilligungsleiste. + 'consentBanner' => false, 'scrollTop' => false, 'postMetaHeader' => false, +]); +echo "Einstellungen gespeichert\n"; + +$passwort = bin2hex(random_bytes(12)); +$db->insert('cms_user', ['id' => bin2hex(random_bytes(16)), 'username' => 'umut', 'password_hash' => password_hash($passwort, PASSWORD_BCRYPT), 'roles' => '["ROLE_ADMIN"]']); + +echo "\nFertig.\nVerwaltung: Benutzer „umut“, Passwort: $passwort\n"; diff --git a/src/Content/CustomCode.php b/src/Content/CustomCode.php index 9d3db24..c57651a 100644 --- a/src/Content/CustomCode.php +++ b/src/Content/CustomCode.php @@ -239,6 +239,14 @@ final class CustomCode if (1 !== preg_match('/^-{0,2}[a-z][a-z0-9-]{0,40}$/D', $property)) { throw new InvalidDocument(sprintf('„%s“ ist kein CSS-Eigenschaftsname.', mb_substr($property, 0, 40))); } + // `content` braucht einen Text in Anfuehrungszeichen, sonst gibt es kein ::before und + // ::after fuer Striche, Verlaeufe oder ein Wasserzeichen. Erlaubt ist ein kurzer Text + // aus Buchstaben, Ziffern und wenigen Satzzeichen: kein <, kein Backslash, kein + // weiteres Anfuehrungszeichen, also kein Weg aus dem String oder dem