Seed, asset and reset scripts for Becker - Just In Time e. Kfm. (Wiernsheim): logo cut from the PDF raster, logo-free photos generated with ChatGPT, before/after slider, service pages, contact form, legal pages. Launch configs on ports 8116/8117, Manrope 800 and Montserrat 700/800 fetched locally. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1116 lines
82 KiB
PHP
1116 lines
82 KiB
PHP
<?php
|
||
|
||
/**
|
||
* Baut die Website „Becker – Just In Time e. Kfm.“ (Wiernsheim) als Inhalt dieses CMS auf.
|
||
*
|
||
* Vorlage ist Umuts Entwurf „Becker-Just-In-Time-Version-5.zip“ (ChatGPT Sites, 10.11.2026):
|
||
* Navy-Einstieg, drei Leistungen, Kontaktformular, Impressum. Daten daraus übernommen; Gestaltung
|
||
* neu nach dem Logo (Logo_Becker_original.pdf): Navy #172234, Logo-Gold #F4B43A, helles Grau der
|
||
* Schrift, kräftige Montserrat. Die Fotos sind Umuts Werbemotive, von ChatGPT ohne Logos neu
|
||
* erzeugt (siehe build-becker-assets.php) – deshalb im Impressum als KI-Symbolbilder ausgewiesen.
|
||
*
|
||
* Eigener Senf gegenüber dem Entwurf:
|
||
* - je Leistung eine eigene Seite (Suche, KI-Antworten), dazu Vorher/Nachher mit Regler (Plugin
|
||
* „vergleich“), Ablauf in vier Schritten, Team, Einblicke als Galerie,
|
||
* - die Leistungssymbole aus dem Logo statt Allerweltssymbolen, Gold-Streifen wie auf den Flyern,
|
||
* - die Asbestbilder zeigen Schutzanzug und Atemschutz – Fotos ohne Schutz wären bei einem
|
||
* TRGS-519-Betrieb ein Eigentor.
|
||
*
|
||
* Das Skript schreibt ausschließlich in die Datenbank aus DATABASE_URL und in MEDIA_DIRECTORY,
|
||
* beide müssen auf eine frische, migrierte Installation zeigen (siehe reset-becker.sh).
|
||
* Erstes Argument: der Ordner aus build-becker-assets.php.
|
||
*/
|
||
|
||
require dirname(__DIR__).'/config/bootstrap.php';
|
||
|
||
use App\Content\DocumentValidator;
|
||
use App\Content\FormRepository;
|
||
use App\Content\MediaLibrary;
|
||
use App\Content\MenuRepository;
|
||
use App\Content\PageRepository;
|
||
use App\Content\PartialRepository;
|
||
use App\Content\SiteSettings;
|
||
use App\Content\Theme;
|
||
use App\Infrastructure\ConnectionFactory;
|
||
use App\Plugin\PluginRegistry;
|
||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||
|
||
$root = dirname(__DIR__);
|
||
$assets = $argv[1] ?? null;
|
||
if (null === $assets || !is_dir($assets)) {
|
||
fwrite(STDERR, "Bitte den Ordner mit den Bildern als Argument angeben.\n");
|
||
exit(1);
|
||
}
|
||
$url = str_replace('%kernel.project_dir%', str_replace('\\', '/', $root), (string) $_SERVER['DATABASE_URL']);
|
||
$mediaDirectory = str_replace('%kernel.project_dir%', $root, (string) $_SERVER['MEDIA_DIRECTORY']);
|
||
if (!str_contains($url, 'becker')) {
|
||
fwrite(STDERR, "Sicherung: DATABASE_URL muss auf die Becker-Datenbank zeigen.\n");
|
||
exit(1);
|
||
}
|
||
|
||
$db = ConnectionFactory::create($url);
|
||
if (false !== $db->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);
|
||
$plugins = new PluginRegistry($db, $root);
|
||
$pages = new PageRepository($db, new DocumentValidator(plugins: $plugins), $settings);
|
||
$partials = new PartialRepository($db, new DocumentValidator());
|
||
$menus = new MenuRepository($db, $settings);
|
||
$forms = new FormRepository($db);
|
||
$theme = new Theme($db);
|
||
|
||
// Angaben aus dem Impressum des Entwurfs (Version 5, 10.11.2026).
|
||
$firma = 'Becker – Just In Time e. Kfm.';
|
||
$kurz = 'Becker – Just In Time';
|
||
$inhaber = 'Michael Becker';
|
||
$strasse = 'Clara-Zetkin-Straße 36';
|
||
$plz = '75446';
|
||
$ort = 'Wiernsheim';
|
||
$telefon = '0176 41753750';
|
||
$tel = 'tel:+4917641753750';
|
||
$mail = 'info@becker-justintime.de';
|
||
$SCHEMA = DocumentValidator::SCHEMA_VERSION;
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// 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'];
|
||
$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 = '', string $breite = 'normal'): array {
|
||
$block = ['id' => $id, 'type' => 'section', 'children' => $children, 'style' => $style];
|
||
if (null !== $anchor) {
|
||
$block['anchor'] = $anchor;
|
||
}
|
||
if ('' !== $css) {
|
||
$block['css'] = $css;
|
||
}
|
||
if ('normal' !== $breite) {
|
||
$block['layout'] = ['width' => $breite];
|
||
}
|
||
|
||
return $block;
|
||
};
|
||
|
||
$row = static function (string $id, array $children, array $design = [], array $style = [], string $css = ''): array {
|
||
$block = ['id' => $id, 'type' => 'row', 'children' => $children];
|
||
if ([] !== $design) {
|
||
$block['layout'] = ['design' => $design];
|
||
}
|
||
if ([] !== $style) {
|
||
$block['style'] = $style;
|
||
}
|
||
if ('' !== $css) {
|
||
$block['css'] = $css;
|
||
}
|
||
|
||
return $block;
|
||
};
|
||
|
||
$zweiSpalten = ['desktop' => ['columns' => 'two'], 'tablet' => ['columns' => 'one'], 'mobile' => ['columns' => 'one']];
|
||
|
||
/** Eine Zeile, die nebeneinander stellt statt zu rastern. */
|
||
$zeile = static function (string $id, array $children, string $gap, array $extra = [], array $mobil = [], string $css = '') use ($row): array {
|
||
$style = ['desktop' => ['display' => 'flex', 'alignItems' => 'center', 'gap' => $gap, ...$extra, 'margin' => ['top' => '0px', ...($extra['margin'] ?? [])]]];
|
||
if ([] !== $mobil) {
|
||
$style['mobile'] = $mobil;
|
||
}
|
||
|
||
return $row($id, $children, [], $style, $css);
|
||
};
|
||
|
||
$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;
|
||
};
|
||
|
||
$bild = static function (string $id, array $eintrag, string $alt, array $style = [], ?string $href = null, string $css = '', bool $vergroessern = false): 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';
|
||
} elseif ($vergroessern) {
|
||
$props['click'] = 'vergroessern';
|
||
}
|
||
$block = ['id' => $id, 'type' => 'image', 'props' => $props, 'style' => $style];
|
||
if ('' !== $css) {
|
||
$block['css'] = $css;
|
||
}
|
||
|
||
return $block;
|
||
};
|
||
|
||
$symbol = static fn (string $id, string $name, int $groesse, array $style = []): array => ['id' => $id, 'type' => 'icon', 'props' => ['icon' => $name, 'size' => $groesse, 'decorative' => true, 'label' => ''], 'style' => $style];
|
||
|
||
/**
|
||
* Ein Button: kantig wie das Logo, Pfeil gleitet beim Überfahren, leichtes Anheben.
|
||
*
|
||
* @param string $art gold (Theme), navy (auf hell), hell (Rahmen auf dunkel), rahmen (Navy-Rahmen auf hell/gold)
|
||
*/
|
||
$knopf = static function (string $id, string $inhalt, string $href, string $art = 'gold', array $style = [], string $icon = 'arrow-right'): array {
|
||
$farbe = [
|
||
'gold' => '',
|
||
'navy' => "background:#172234;color:#fff;\n&:hover{background:#0f1724;color:#f4b43a}\n",
|
||
'hell' => "background:transparent;color:#fff;box-shadow:inset 0 0 0 2px rgba(255,255,255,.55);\n&:hover{background:#fff;color:#172234}\n",
|
||
'rahmen' => "background:transparent;color:#172234;box-shadow:inset 0 0 0 2px #172234;\n&:hover{background:#172234;color:#fff}\n",
|
||
][$art];
|
||
$props = ['text' => $inhalt, 'href' => $href, 'icon' => $icon];
|
||
if ('phone' === $icon) {
|
||
$props['iconPosition'] = 'start';
|
||
} elseif ('none' !== $icon) {
|
||
$props['iconPosition'] = 'end';
|
||
$props['iconMotion'] = 'slide';
|
||
}
|
||
|
||
return ['id' => $id, 'type' => 'button', 'props' => $props, 'css' => "min-height:54px;padding:0 26px;margin-top:0;letter-spacing:.02em;transition:transform .2s ease,background-color .2s ease,color .2s ease;\n".$farbe.'&:hover{transform:translateY(-2px)}']
|
||
+ ([] === $style ? [] : ['style' => $style]);
|
||
};
|
||
|
||
/**
|
||
* Setzt die Einblendungen nach einer Liste.
|
||
*
|
||
* @param array<string, array{0: string, 1?: string}> $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;
|
||
};
|
||
|
||
$linkCss = "& a{color:#172234;text-decoration:underline;text-decoration-color:#f4b43a;text-decoration-thickness:2px;text-underline-offset:3px}\n& a:hover{color:#8a5a00}";
|
||
|
||
/** Die Kleinzeile: kurzer Gold-Balken davor, Versalien. Auf hell dunkle Schrift, auf Navy Gold. */
|
||
$kleinzeile = static fn (string $id, string $inhalt, string $farbe = '@navy', string $unten = '18px') => $text($id, $inhalt, ['desktop' => [
|
||
'fontSize' => '13px', 'fontWeight' => '800', 'letterSpacing' => '0.16em', 'lineHeight' => 1.6, 'textTransform' => 'uppercase', 'color' => $farbe, 'margin' => ['top' => '0px', 'bottom' => $unten],
|
||
]], "display:flex;align-items:center;gap:12px\n&::before{content:'';width:32px;height:4px;background:#f4b43a;flex-shrink:0;transform:skewX(-20deg)}");
|
||
|
||
$h2Stil = [
|
||
'desktop' => ['fontFamily' => 'heading', 'fontSize' => '44px', 'fontWeight' => '800', 'lineHeight' => 1.12, 'letterSpacing' => '-0.01em', 'color' => '@navy', 'margin' => ['top' => '0px', 'bottom' => '20px']],
|
||
'tablet' => ['fontSize' => '36px'],
|
||
'mobile' => ['fontSize' => '30px'],
|
||
];
|
||
$h2Hell = [...$h2Stil, 'desktop' => [...$h2Stil['desktop'], 'color' => '@weiss']];
|
||
$fliess = static fn (string $farbe = '@text', string $unten = '0px', string $breite = 'none', string $groesse = '17px') => [
|
||
'desktop' => ['fontSize' => $groesse, 'lineHeight' => 1.75, 'color' => $farbe, 'maxWidth' => $breite, 'margin' => ['top' => '0px', 'bottom' => $unten]],
|
||
'mobile' => ['maxWidth' => 'none', 'fontSize' => '16px'],
|
||
];
|
||
|
||
// Ein Link über die ganze Karte: Die Karte ist positioniert, der Link spannt sein ::after auf.
|
||
$ganzeKarte = "position:relative;\n& a::after{content:'';position:absolute;top:0;right:0;bottom:0;left:0}\n& a:focus-visible{outline:none}\n&:focus-within{outline:3px solid #f4b43a;outline-offset:4px}";
|
||
|
||
/** Ein Haken vor einer Zeile. */
|
||
$haken = static fn (string $id, string $inhalt, string $farbe = '@text', string $hakenfarbe = '@gold-dunkel') => $zeile($id, [
|
||
$symbol($id.'-icon', 'check-circle', 24, ['desktop' => ['color' => $hakenfarbe, 'flexShrink' => 0]]),
|
||
$text($id.'-text', $inhalt, ['desktop' => ['fontSize' => '16px', 'lineHeight' => 1.6, 'color' => $farbe, 'margin' => ['bottom' => '0px']]], '& strong{font-weight:700}'),
|
||
], '12px', ['alignItems' => 'flex-start', 'margin' => ['bottom' => '14px']]);
|
||
|
||
// Gold-Streifen wie auf den Flyern – als Hintergrund eines Pseudoelements.
|
||
// Gold-Streifen wie auf den Flyern – als Inline-SVG, damit die Kanten scharf bleiben (ein
|
||
// CSS-Verlauf mit harten Stopps rendert treppig). Schräge 115° wie die Flyer: Versatz = Höhe × 0,466.
|
||
$streifenSvg = static function (string $id, int $breite, int $hoehe, string $lage, float $deckkraft = 1.0): array {
|
||
$versatz = round($hoehe * 0.466, 1);
|
||
$formen = '';
|
||
for ($x = 0; $x < $breite + $versatz; $x += 34) {
|
||
$formen .= sprintf('M%s 0h16L%s %dh-16Z', $x + $versatz, $x + 16, $hoehe);
|
||
}
|
||
$svg = sprintf('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 %d %d" width="%d" height="%d" aria-hidden="true" focusable="false"><path fill="#f4b43a" fill-opacity="%s" d="%s"/></svg>', $breite, $hoehe, $breite, $hoehe, $deckkraft, $formen);
|
||
|
||
return ['id' => $id, 'type' => 'html', 'props' => ['html' => $svg],
|
||
'css' => "position:absolute;$lage;width:{$breite}px;height:{$hoehe}px;margin:0;line-height:0;pointer-events:none;z-index:0\n& svg{display:block;width:100%;height:100%}"];
|
||
};
|
||
// Abschnitte mit Streifen: der Inhalt liegt über Verlauf und Streifen, die Streifen am Abschnittsrand.
|
||
$ueberStreifen = static fn (string $streifen): string => "& .section-inner{position:static}\n& .section-inner > [id]{position:relative;z-index:1}\n& .section-inner > [id=\"$streifen\"]{position:absolute;z-index:0}";
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// Website, Gestaltung, Medien
|
||
// ---------------------------------------------------------------------------------------------
|
||
|
||
$db->insert('website', ['id' => 'default', 'name' => $kurz, 'default_locale' => 'de', 'theme' => '{}']);
|
||
|
||
$theme->save(
|
||
[
|
||
['key' => 'navy', 'name' => 'Navy (Logo-Grund, Überschriften)', 'value' => '#172234'],
|
||
['key' => 'navy-tief', 'name' => 'Navy tief (Fußleiste)', 'value' => '#0f1724'],
|
||
['key' => 'navy-hell', 'name' => 'Navy hell (Flächen auf Navy)', 'value' => '#22314a'],
|
||
['key' => 'gold', 'name' => 'Logo-Gold (Knöpfe, Akzente)', 'value' => '#f4b43a'],
|
||
['key' => 'gold-hell', 'name' => 'Gold hell (Knöpfe beim Überfahren)', 'value' => '#ffc95e'],
|
||
['key' => 'gold-dunkel', 'name' => 'Gold dunkel (Akzente auf hell, lesbar)', 'value' => '#a56d00'],
|
||
['key' => 'text', 'name' => 'Fließtext', 'value' => '#4a5565'],
|
||
['key' => 'grau', 'name' => 'Hellgrau (Flächen)', 'value' => '#f2f3f5'],
|
||
['key' => 'linie', 'name' => 'Linien', 'value' => '#dfe2e7'],
|
||
['key' => 'auf-dunkel', 'name' => 'Schrift auf Navy', 'value' => '#c3cad6'],
|
||
['key' => 'silber', 'name' => 'Logo-Silber', 'value' => '#e4e6eb'],
|
||
['key' => 'weiss', 'name' => 'Weiß', 'value' => '#ffffff'],
|
||
],
|
||
[
|
||
['key' => 'body', 'name' => 'Text', 'family' => 'Manrope', 'source' => 'google', 'weights' => [400, 500, 600, 700, 800], 'fallback' => 'sans-serif'],
|
||
['key' => 'heading', 'name' => 'Überschriften', 'family' => 'Montserrat', 'source' => 'google', 'weights' => [700, 800], 'fallback' => 'sans-serif'],
|
||
],
|
||
['fontFamily' => 'body', 'headingFontFamily' => 'heading', 'fontSize' => '17px', 'lineHeight' => 1.65, 'color' => '@navy', 'backgroundColor' => '@weiss', 'linkColor' => '@navy', 'containerWidth' => '1240px', 'headingScale' => 1.25, 'linkUnderline' => false, 'imageRadius' => '4px'],
|
||
['shape' => 'square', 'appearance' => 'solid', 'size' => 'normal', 'fontFamily' => 'heading', 'fontSize' => 15, 'fontWeight' => 800, 'letterSpacing' => 0, 'textTransform' => 'none', 'backgroundColor' => '@gold', 'color' => '@navy', 'hoverBackgroundColor' => '@gold-hell', 'hoverColor' => '@navy'],
|
||
);
|
||
echo "Farben, Schriften und Buttons gesetzt\n";
|
||
|
||
$medien = [];
|
||
foreach ([
|
||
'logo' => ['logo.png', $kurz],
|
||
'logo-gross' => ['logo-gross.png', $kurz.' – Gartenbau, Zaunbau, Asbestabriss nach TRGS 519'],
|
||
'favicon' => ['favicon.png', 'Seitensymbol von '.$kurz],
|
||
'symbol-garten' => ['symbol-garten.png', ''],
|
||
'symbol-zaun' => ['symbol-zaun.png', ''],
|
||
'symbol-asbest' => ['symbol-asbest.png', ''],
|
||
'haus' => ['haus.webp', 'Modernes Einfamilienhaus in der Abenddämmerung mit gepflasterter Zufahrt, beleuchteten Mauern und Beet mit Palme'],
|
||
'team' => ['team.webp', 'Das Team von Becker – Just In Time vor einer Baustelle mit Gerüst'],
|
||
'bagger' => ['bagger.webp', 'Minibagger und Radlader bei Erdarbeiten auf einem Grundstück, Geovlies ist ausgelegt'],
|
||
'terrasse' => ['terrasse.webp', 'Neu verlegte helle Terrassenplatten vor einem Haus, Gabionenwand mit Leuchten'],
|
||
'zisterne' => ['zisterne.webp', 'Zwei Betonbehälter einer Zisterne in einer Baugrube mit Rohranschlüssen'],
|
||
'vorher' => ['vorher.webp', 'Schotterfläche vor einem neuen Einfamilienhaus vor dem Pflastern'],
|
||
'nachher' => ['nachher.webp', 'Fertig gepflasterte Hof- und Zufahrtsfläche vor einem weißen Einfamilienhaus'],
|
||
'zaun' => ['zaun.webp', 'Anthrazitfarbener Doppelstabmattenzaun mit grau-weißen Sichtschutzstreifen auf einer Betonmauer'],
|
||
'zauntor' => ['zauntor.webp', 'Doppelstabmattenzaun mit Gartentor und Natursteinpfeiler vor einem Einfamilienhaus'],
|
||
'asbest' => ['asbest.webp', 'Arbeiter in Schutzanzügen mit Atemschutz entfernen Deckenplatten in einem abgeschotteten Raum'],
|
||
'dach' => ['dach.webp', 'Rückbau einer Dachabdichtung auf einem Flachdach, Arbeiter in Schutzanzügen füllen Big Bags'],
|
||
] 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";
|
||
}
|
||
|
||
$plugins->activate('vergleich');
|
||
echo "Plugin „Bildvergleich“ eingeschaltet\n";
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// Formular – zwei Schritte, Zusammenfassung
|
||
// ---------------------------------------------------------------------------------------------
|
||
|
||
$forms->save('anfrage', 'Projektanfrage', [
|
||
['name' => 'schritt_vorhaben', 'type' => 'step', 'label' => 'Ihr Vorhaben', 'help' => 'Ein paar Stichworte genügen – den Rest klären wir persönlich.'],
|
||
['name' => 'bereich', 'type' => 'radio', 'label' => 'Worum geht es?', 'required' => true, 'options' => ['Gartenbau', 'Zaunbau', 'Asbestsanierung', 'Etwas anderes']],
|
||
['name' => 'beschreibung', 'type' => 'textarea', 'label' => 'Was haben Sie vor?', 'required' => true, 'help' => 'Zum Beispiel Fläche, Länge, Zustand, Material – gern mit ungefährem Umfang.'],
|
||
['name' => 'ort', 'type' => 'text', 'label' => 'Ort des Vorhabens', 'required' => false, 'help' => 'PLZ und Ort genügen'],
|
||
['name' => 'zeitraum', 'type' => 'select', 'label' => 'Wann soll es losgehen?', 'required' => false, 'options' => ['So bald wie möglich', 'In den nächsten 3 Monaten', 'Später in diesem Jahr', 'Noch offen']],
|
||
|
||
['name' => 'schritt_kontakt', 'type' => 'step', 'label' => 'Ihre Kontaktdaten', 'help' => 'Damit wir uns bei Ihnen melden können.'],
|
||
['name' => 'name', 'type' => 'text', 'label' => 'Vor- und Nachname', 'required' => true],
|
||
['name' => 'email', 'type' => 'email', 'label' => 'E-Mail', 'required' => true],
|
||
['name' => 'telefon', 'type' => 'phone', 'label' => 'Telefon', 'required' => false, 'help' => 'Freiwillig – wenn wir Sie lieber zurückrufen sollen'],
|
||
['name' => 'datenschutz', 'type' => 'consent', 'label' => 'Ich bin einverstanden, dass meine Angaben zur Bearbeitung der Anfrage gespeichert werden und Sie mich kontaktieren.'],
|
||
], ['recipient' => $mail, 'subject' => 'Neue Projektanfrage über die Website', 'success' => 'Vielen Dank! Ihre Anfrage ist bei uns angekommen. Wir melden uns persönlich bei Ihnen.', 'replyToField' => 'email', 'buttonLabel' => 'Anfrage senden', 'summary' => true], 0);
|
||
echo "Formular angelegt: anfrage\n";
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// Kopfbereich: Navy wie der Logo-Grund, Gold-Kante unten
|
||
// ---------------------------------------------------------------------------------------------
|
||
|
||
$kopf = [
|
||
'schemaVersion' => $SCHEMA, 'title' => 'Kopfbereich', 'description' => 'Erscheint auf jeder Seite über dem Inhalt.',
|
||
'blocks' => [
|
||
$section('kopf', [
|
||
$zeile('kopf-zeile', [
|
||
$bild('kopf-logo', $medien['logo'], $kurz.' – zur Startseite', [
|
||
// Feste Breite: sonst bekommt das Logo seine Breite erst nach dem Laden (CLS).
|
||
'desktop' => ['width' => '250px', 'maxWidth' => '250px', 'flexShrink' => 0, 'margin' => $seiten('0px')],
|
||
'mobile' => ['width' => '190px', 'maxWidth' => '190px'],
|
||
], '/', '& img{display:block;width:100%;height:auto;border-radius:0}'),
|
||
['id' => 'kopf-menue', 'type' => 'menu', 'props' => ['slot' => 'header', 'layout' => 'horizontal', 'collapse' => 'tablet', 'burgerLabel' => 'icon'],
|
||
'style' => ['desktop' => [
|
||
'fontSize' => '15px', 'fontWeight' => '700', 'color' => '@weiss',
|
||
'burgerColor' => '@navy', 'burgerBackgroundColor' => '@gold', 'burgerBorderColor' => '@gold', 'burgerBorderRadius' => '2px',
|
||
]],
|
||
// Gold-Balken, der beim Überfahren wächst; das Klappmenü liegt als Navy-Fläche unter dem Kopf.
|
||
'css' => "& .menu-list{gap:4px 30px;align-items:center}\n"
|
||
."& .menu-list a{position:relative;padding:8px 0;text-decoration:none;color:#fff}\n"
|
||
."& .menu-list a::after{content:'';position:absolute;left:0;bottom:0;width:0;height:3px;background:#f4b43a;transition:width .25s ease}\n"
|
||
."& .menu-list a:hover::after{width:100%}\n"
|
||
."& .menu-list a[aria-current]::after{width:100%}\n"
|
||
."& .menu-list > li:first-child .nav-label > span{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}\n"
|
||
."& .menu-list > li:first-child .nav-label svg{width:24px;height:24px;display:block;stroke-width:2}\n"
|
||
."& .menu-burger-body .menu-list > li:first-child .nav-label{display:flex;align-items:center;gap:10px}\n"
|
||
."& .menu-burger-body .menu-list > li:first-child .nav-label > span{position:static;width:auto;height:auto;overflow:visible;clip:auto}\n"
|
||
."& .menu-list .menu-button{padding:12px 20px;color:#172234}\n"
|
||
."& .menu-list .menu-button::after{display:none}\n"
|
||
."& .menu-burger{margin-left:auto}\n"
|
||
."& .menu-burger summary{width:48px;height:48px;display:flex;align-items:center;justify-content:center;padding:0}\n"
|
||
."& .menu-burger-body{position:absolute;left:0;right:0;top:100%;background:#172234;padding:6px 20px 22px;border-top:3px solid #f4b43a;box-shadow:0 18px 30px rgba(0,0,0,.25);font-size:18px}\n"
|
||
."& .menu-burger-body .menu-list{flex-direction:column;align-items:stretch;gap:0}\n"
|
||
."& .menu-burger-body .menu-list a{display:block;padding:14px 0;border-bottom:1px solid #2c3b55}\n"
|
||
."& .menu-burger-body .menu-list a::after{display:none}\n"
|
||
.'& .menu-burger-body .menu-list .menu-button{margin-top:16px;padding:14px 20px;border:0;text-align:center}'],
|
||
], '28px', ['justifyContent' => 'space-between', 'minHeight' => '86px'], ['minHeight' => '70px', 'gap' => '6px']),
|
||
], [
|
||
'desktop' => ['backgroundColor' => '@navy', 'padding' => ['top' => '0px', 'bottom' => '0px'], 'borderWidth' => $nurUnten('3px'), 'borderStyle' => 'solid', 'borderColor' => '@gold'],
|
||
], null, '', 'wide'),
|
||
],
|
||
];
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// Fußbereich: Navy, gestapeltes Logo, Leistungen, Kontakt
|
||
// ---------------------------------------------------------------------------------------------
|
||
|
||
$fussLink = "& a{color:#c3cad6;text-decoration:none;transition:color .2s}\n& a:hover{color:#f4b43a}";
|
||
$fussTitel = static fn (string $id, string $inhalt) => $heading($id, $inhalt, 2, ['desktop' => ['fontFamily' => 'heading', 'fontSize' => '15px', 'fontWeight' => '800', 'letterSpacing' => '0.12em', 'textTransform' => 'uppercase', 'color' => '@gold', 'margin' => ['top' => '0px', 'bottom' => '16px']]]);
|
||
$fussText = static fn (string $id, string $inhalt) => $text($id, $inhalt, ['desktop' => ['fontSize' => '15px', 'lineHeight' => 2, 'color' => '@auf-dunkel', 'margin' => ['bottom' => '0px']]], $fussLink."\n& a{overflow-wrap:anywhere}");
|
||
|
||
$fuss = [
|
||
'schemaVersion' => $SCHEMA, 'title' => 'Fußbereich', 'description' => 'Erscheint auf jeder Seite unter dem Inhalt.',
|
||
'blocks' => [
|
||
$section('fuss', [
|
||
$streifenSvg('fuss-streifen', 190, 44, 'right:-30px;top:0', 0.9),
|
||
$column('fuss-raster', [
|
||
$column('fuss-marke', [
|
||
$bild('fuss-logo', $medien['logo-gross'], $kurz.' – Gartenbau, Zaunbau, Asbestabriss nach TRGS 519', ['desktop' => ['width' => '250px', 'maxWidth' => '250px', 'margin' => $seiten('0px')]], null, '& img{display:block;width:100%;height:auto;border-radius:0}'),
|
||
]),
|
||
$column('fuss-leistungen', [
|
||
$fussTitel('fuss-leistungen-titel', 'Leistungen'),
|
||
$fussText('fuss-leistungen-liste', '<a href="/gartenbau">Garten- und Landschaftsbau</a><br><a href="/zaunbau">Zaunbau und Sichtschutz</a><br><a href="/asbestsanierung">Asbestsanierung nach TRGS 519</a><br><a href="/kontakt">Projekt anfragen</a>'),
|
||
]),
|
||
$column('fuss-kontakt', [
|
||
$fussTitel('fuss-kontakt-titel', 'Kontakt'),
|
||
$fussText('fuss-kontakt-text', $inhaber.'<br>'.$strasse.'<br>'.$plz.' '.$ort.'<br><a href="'.$tel.'">'.$telefon.'</a><br><a href="mailto:'.$mail.'">'.$mail.'</a>'),
|
||
]),
|
||
$column('fuss-anfrage', [
|
||
$fussTitel('fuss-anfrage-titel', 'Projekt geplant?'),
|
||
$text('fuss-anfrage-text', 'Beschreiben Sie kurz Ihr Vorhaben – wir melden uns persönlich.', ['desktop' => ['fontSize' => '15px', 'lineHeight' => 1.7, 'color' => '@auf-dunkel', 'margin' => ['bottom' => '20px']]]),
|
||
$knopf('fuss-anfrage-knopf', 'Anfrage stellen', '/kontakt'),
|
||
]),
|
||
], [
|
||
'desktop' => ['display' => 'grid', 'gridColumns' => 4, 'gap' => '44px', 'padding' => ['top' => '72px', 'bottom' => '56px']],
|
||
'tablet' => ['gridColumns' => 2],
|
||
'mobile' => ['gridColumns' => 1, 'gap' => '34px', 'padding' => ['top' => '52px', 'bottom' => '40px']],
|
||
]),
|
||
], [
|
||
'desktop' => ['backgroundColor' => '@navy', 'color' => '@weiss', 'padding' => ['top' => '0px', 'bottom' => '0px'], 'borderWidth' => $nurOben('4px'), 'borderStyle' => 'solid', 'borderColor' => '@gold'],
|
||
], null, "position:relative;overflow:hidden\n".$ueberStreifen('fuss-streifen')),
|
||
$section('fuss-leiste', [
|
||
$zeile('fuss-unten', [
|
||
$text('fuss-copyright', '© 2026 '.$firma.', '.$ort, ['desktop' => ['fontSize' => '13px', 'color' => '@auf-dunkel', 'margin' => ['bottom' => '0px']]]),
|
||
$zeile('fuss-links', [
|
||
['id' => 'fuss-rechtliches', 'type' => 'legal', 'props' => ['label' => 'Rechtliches'],
|
||
'style' => ['desktop' => ['fontSize' => '13px', 'gap' => '24px', 'color' => '@auf-dunkel']],
|
||
'css' => "& a{color:#c3cad6;text-decoration:none}\n& a:hover{color:#f4b43a}"],
|
||
$text('fuss-agentur', '<a href="https://kant-it.de">Webdesign by Kant-IT Solutions</a>', ['desktop' => ['fontSize' => '13px', 'margin' => ['bottom' => '0px']]], "& a{color:#c3cad6;text-decoration:none}\n& a:hover{color:#f4b43a}"),
|
||
], '24px', ['flexWrap' => 'wrap'], ['rowGap' => '10px', 'columnGap' => '18px']),
|
||
], '24px', ['justifyContent' => 'space-between', 'flexWrap' => 'wrap', 'rowGap' => '12px', 'padding' => ['top' => '20px', 'bottom' => '22px']],
|
||
['flexDirection' => 'column', 'alignItems' => 'flex-start']),
|
||
], ['desktop' => ['backgroundColor' => '@navy-tief', 'padding' => ['top' => '0px', 'bottom' => '0px']]]),
|
||
],
|
||
];
|
||
|
||
$partials->create('header', 'default', 'Kopfbereich', $kopf);
|
||
$partials->create('footer', 'default', 'Fußbereich', $fuss);
|
||
echo "Kopf- und Fußbereich angelegt\n";
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// Startseite
|
||
// ---------------------------------------------------------------------------------------------
|
||
|
||
/** Ein Logo-Symbol im Navy-Quadrat mit Gold-Ecke. */
|
||
$logoSymbol = static fn (string $id, array $eintrag, string $groesse = '72px') => $column($id, [
|
||
$bild($id.'-bild', $eintrag, '', ['desktop' => ['width' => '68%', 'margin' => $seiten('0px')]], null, '& img{display:block;width:100%;height:auto;border-radius:0}'),
|
||
], ['desktop' => ['display' => 'flex', 'alignItems' => 'center', 'justifyContent' => 'center', 'width' => $groesse, 'height' => $groesse, 'flexShrink' => 0, 'backgroundColor' => '@navy']],
|
||
"box-shadow:6px 6px 0 #f4b43a");
|
||
|
||
/** Eine Leistung als Karte: Foto, Logo-Symbol über der Kante, Titel, Satz, Punkte, Link. */
|
||
$leistung = static function (string $id, array $foto, string $fotoAlt, array $zeichen, string $titel, string $satz, array $punkte, string $href) use ($column, $text, $heading, $bild, $logoSymbol, $seiten, $nurUnten, $ganzeKarte): array {
|
||
return $column($id, [
|
||
$bild($id.'-foto', $foto, $fotoAlt, ['desktop' => ['width' => '100%', 'imageRatio' => '3/2', 'objectFit' => 'cover', 'imageRadius' => ['topLeft' => '0px', 'topRight' => '0px', 'bottomRight' => '0px', 'bottomLeft' => '0px'], 'margin' => $seiten('0px')]], null, '& img{display:block;width:100%}'),
|
||
$column($id.'-inhalt', [
|
||
$logoSymbol($id.'-zeichen', $zeichen),
|
||
$heading($id.'-titel', $titel, 3, ['desktop' => ['fontFamily' => 'heading', 'fontSize' => '24px', 'fontWeight' => '800', 'lineHeight' => 1.2, 'color' => '@navy', 'margin' => ['top' => '22px', 'bottom' => '12px']]]),
|
||
$text($id.'-satz', $satz, ['desktop' => ['fontSize' => '16px', 'lineHeight' => 1.7, 'color' => '@text', 'margin' => ['bottom' => '16px']]]),
|
||
$text($id.'-punkte', '<ul><li>'.implode('</li><li>', $punkte).'</li></ul>', ['desktop' => ['fontSize' => '15px', 'lineHeight' => 1.6, 'color' => '@navy', 'margin' => ['bottom' => '24px']]],
|
||
"& ul{list-style:none;margin:0;padding:0}\n& li{position:relative;padding-left:22px;margin-bottom:7px;font-weight:600}\n& li::before{content:'';position:absolute;left:0;top:.55em;width:10px;height:10px;background:#f4b43a;transform:skewX(-20deg)}"),
|
||
$text($id.'-link', '<a href="'.$href.'">Mehr zu '.$titel.'</a>', ['desktop' => ['fontSize' => '15px', 'fontWeight' => '800', 'margin' => ['top' => 'auto', 'bottom' => '0px']]],
|
||
"& a{color:#172234;text-decoration:none;display:inline-flex;align-items:center;gap:10px}\n& a::before{content:'';order:2;width:20px;height:3px;background:#f4b43a;transition:width .25s ease}"),
|
||
], [
|
||
'desktop' => ['display' => 'flex', 'flexDirection' => 'column', 'flexGrow' => 1, 'padding' => ['top' => '0px', 'right' => '30px', 'bottom' => '32px', 'left' => '30px'], 'margin' => ['top' => '-36px']],
|
||
'mobile' => ['padding' => ['top' => '0px', 'right' => '22px', 'bottom' => '28px', 'left' => '22px']],
|
||
], 'position:relative;z-index:1'),
|
||
], [
|
||
'desktop' => ['display' => 'flex', 'flexDirection' => 'column', 'backgroundColor' => '@weiss', 'shadowY' => '12px', 'shadowBlur' => '34px', 'shadowColor' => '#17223422',
|
||
'borderWidth' => $nurUnten('4px'), 'borderStyle' => 'solid', 'borderColor' => '@weiss'],
|
||
], $ganzeKarte."\noverflow:hidden;transition:transform .3s ease,box-shadow .3s ease,border-color .3s ease;\n& img{transition:transform .6s ease}\n&:hover{transform:translateY(-6px);border-bottom-color:#f4b43a;box-shadow:0 24px 48px rgba(23,34,52,.18)}\n&:hover [id=\"{$id}-foto\"] img{transform:scale(1.05)}\n&:hover a::before{width:34px}");
|
||
};
|
||
|
||
/** Eine Kennzahl im Hero: großer Wert, kleine Beschriftung, Gold-Linie davor. */
|
||
$fakt = static fn (string $id, string $wert, string $beschriftung) => $column($id, [
|
||
$text($id.'-wert', $wert, ['desktop' => ['fontFamily' => 'heading', 'fontSize' => '22px', 'fontWeight' => '800', 'lineHeight' => 1.15, 'color' => '@weiss', 'margin' => ['bottom' => '4px']], 'mobile' => ['fontSize' => '19px']]),
|
||
$text($id.'-text', $beschriftung, ['desktop' => ['fontSize' => '14px', 'lineHeight' => 1.5, 'color' => '#d5dbe4', 'margin' => ['bottom' => '0px']]]),
|
||
], ['desktop' => ['borderWidth' => ['top' => '0px', 'right' => '0px', 'bottom' => '0px', 'left' => '4px'], 'borderStyle' => 'solid', 'borderColor' => '@gold', 'padding' => ['left' => '16px']]]);
|
||
|
||
/** Ein Schritt im Ablauf: Gold-Ziffer, Titel, Satz. */
|
||
$schritt = static fn (int $n, string $titel, string $satz) => $column('ablauf-'.$n, [
|
||
$text('ablauf-'.$n.'-nummer', sprintf('%02d', $n), ['desktop' => ['fontFamily' => 'heading', 'fontSize' => '46px', 'fontWeight' => '800', 'lineHeight' => 1, 'color' => '@gold', 'margin' => ['bottom' => '16px']]]),
|
||
$heading('ablauf-'.$n.'-titel', $titel, 3, ['desktop' => ['fontFamily' => 'heading', 'fontSize' => '20px', 'fontWeight' => '800', 'color' => '@weiss', 'margin' => ['top' => '0px', 'bottom' => '10px']]]),
|
||
$text('ablauf-'.$n.'-satz', $satz, ['desktop' => ['fontSize' => '15px', 'lineHeight' => 1.7, 'color' => '@auf-dunkel', 'margin' => ['bottom' => '0px']]]),
|
||
], ['desktop' => ['backgroundColor' => '@navy-hell', 'padding' => ['top' => '30px', 'right' => '26px', 'bottom' => '30px', 'left' => '26px'], 'borderWidth' => $nurOben('4px'), 'borderStyle' => 'solid', 'borderColor' => '@gold']],
|
||
"transition:transform .3s ease,background-color .3s ease\n&:hover{transform:translateY(-4px);background-color:#283a57}");
|
||
|
||
$startseite = [
|
||
'schemaVersion' => $SCHEMA,
|
||
'title' => 'Gartenbau, Zaunbau und Asbestsanierung aus Wiernsheim',
|
||
'description' => 'Becker – Just In Time e. Kfm. in Wiernsheim (Enzkreis): Garten- und Landschaftsbau, Zaunbau mit Sichtschutz und Asbestsanierung nach TRGS 519 – geplant, ausgeführt und termingerecht übergeben.',
|
||
'blocks' => [
|
||
// 1 – Hero: Haus in der Abenddämmerung, links Navy-Verlauf, Gold-Streifen unten rechts.
|
||
$section('hero', [
|
||
$streifenSvg('hero-streifen', 240, 90, 'right:-20px;bottom:0'),
|
||
$column('hero-text', [
|
||
$kleinzeile('hero-klein', 'Becker – Just In Time · Wiernsheim', '@gold', '24px'),
|
||
$heading('hero-titel', 'Gartenbau, Zaunbau und Asbestsanierung <span>aus Wiernsheim</span>', 1, [
|
||
'desktop' => ['fontFamily' => 'heading', 'fontSize' => '62px', 'fontWeight' => '800', 'lineHeight' => 1.06, 'letterSpacing' => '-0.015em', 'color' => '@weiss', 'margin' => ['top' => '0px', 'bottom' => '24px']],
|
||
'tablet' => ['fontSize' => '50px'],
|
||
'mobile' => ['fontSize' => '36px', 'margin' => ['bottom' => '18px']],
|
||
], "text-shadow:0 2px 24px rgba(10,16,28,.35)\n& span{color:#f4b43a}"),
|
||
$text('hero-satz', 'Von der neuen Hofeinfahrt über den Zaun mit Sichtschutz bis zur fachgerechten Asbestsanierung: Wir planen mit Ihnen, führen sauber aus und übergeben zum vereinbarten Termin – im Enzkreis und Umgebung.', [
|
||
'desktop' => ['fontSize' => '19px', 'lineHeight' => 1.7, 'color' => '#eef1f5', 'maxWidth' => '600px', 'margin' => ['bottom' => '36px']],
|
||
'mobile' => ['fontSize' => '17px', 'margin' => ['bottom' => '28px']],
|
||
]),
|
||
$zeile('hero-knoepfe', [
|
||
$knopf('hero-knopf-anfrage', 'Projekt anfragen', '/kontakt'),
|
||
$knopf('hero-knopf-anrufen', $telefon, $tel, 'hell', [], 'phone'),
|
||
], '14px', ['flexWrap' => 'wrap']),
|
||
$row('hero-fakten', [
|
||
$fakt('hero-fakt-1', 'Drei Gewerke', 'aus einer Hand'),
|
||
$fakt('hero-fakt-2', 'TRGS 519', 'Asbestarbeiten fachgerecht'),
|
||
$fakt('hero-fakt-3', 'Just in time', 'zuverlässig und pünktlich'),
|
||
], ['desktop' => ['columns' => 'three', 'gap' => 'medium'], 'mobile' => ['columns' => 'one', 'gap' => 'small']], [
|
||
'desktop' => ['margin' => ['top' => '60px'], 'maxWidth' => '720px'],
|
||
'mobile' => ['margin' => ['top' => '40px']],
|
||
]),
|
||
], ['desktop' => ['maxWidth' => '780px']]),
|
||
], [
|
||
'desktop' => ['backgroundImage' => $medien['haus']['id'], 'backgroundSize' => 'cover', 'backgroundPosition' => 'right', 'overlayColor' => '#0f1724', 'overlayOpacity' => 0.1,
|
||
'color' => '@weiss', 'minHeight' => '86vh', 'display' => 'flex', 'alignItems' => 'center', 'padding' => ['top' => '100px', 'bottom' => '100px']],
|
||
'mobile' => ['minHeight' => 'auto', 'padding' => ['top' => '60px', 'bottom' => '110px']],
|
||
], null, "position:relative;overflow:hidden\n&::before{content:'';position:absolute;top:0;right:0;bottom:0;left:0;background:linear-gradient(90deg,rgba(23,34,52,.92) 0%,rgba(23,34,52,.72) 42%,rgba(23,34,52,.12) 78%);pointer-events:none}\n".$ueberStreifen('hero-streifen')),
|
||
|
||
// 2 – Leistungen: drei Karten mit Foto und Logo-Symbol.
|
||
$section('leistungen-bereich', [
|
||
$row('leistungen-kopf', [
|
||
$column('leistungen-kopf-titel', [
|
||
$kleinzeile('leistungen-klein', 'Unsere Leistungen'),
|
||
$heading('leistungen-titel', 'Was wir für Sie übernehmen', 2, [...$h2Stil, 'desktop' => [...$h2Stil['desktop'], 'margin' => ['top' => '0px', 'bottom' => '0px']]]),
|
||
]),
|
||
$text('leistungen-satz', 'Außenanlage, Einfriedung, Rückbau: Sie sprechen mit einem Betrieb, der Termine hält und hinterher aufräumt.', [
|
||
'desktop' => ['maxWidth' => '400px', 'fontSize' => '17px', 'lineHeight' => 1.75, 'color' => '@text', 'margin' => ['bottom' => '0px']],
|
||
'mobile' => ['maxWidth' => 'none', 'fontSize' => '16px'],
|
||
]),
|
||
], [], [
|
||
'desktop' => ['display' => 'flex', 'flexWrap' => 'wrap', 'justifyContent' => 'space-between', 'alignItems' => 'flex-end', 'rowGap' => '20px', 'columnGap' => '48px', 'margin' => ['top' => '0px', 'bottom' => '52px']],
|
||
'mobile' => ['margin' => ['bottom' => '32px']],
|
||
]),
|
||
$column('leistungen-raster', [
|
||
$leistung('leistung-garten', $medien['terrasse'], '', $medien['symbol-garten'], 'Gartenbau', 'Pflaster, Platten, Erdarbeiten und Zisternen – wir legen Außenanlagen an, die lange schön bleiben.', ['Hofflächen, Zufahrten, Terrassen', 'Erdarbeiten mit eigenem Gerät', 'Zisternen und Entwässerung'], '/gartenbau'),
|
||
$leistung('leistung-zaun', $medien['zaun'], '', $medien['symbol-zaun'], 'Zaunbau', 'Doppelstabmatten, Sichtschutz und Tore – fachgerecht gesetzt, gerade ausgerichtet, sauber abgeschlossen.', ['Doppelstabmattenzäune', 'Sichtschutzstreifen', 'Tore und Pforten'], '/zaunbau'),
|
||
$leistung('leistung-asbest', $medien['asbest'], '', $medien['symbol-asbest'], 'Asbestsanierung', 'Abbruch-, Sanierungs- und Instandhaltungsarbeiten an asbesthaltigen Materialien nach TRGS 519.', ['Dach- und Fassadenplatten', 'Rückbau in Innenräumen', 'Verpackung und Entsorgung'], '/asbestsanierung'),
|
||
], [
|
||
'desktop' => ['display' => 'grid', 'gridColumns' => 3, 'gap' => '30px'],
|
||
'tablet' => ['gridColumns' => 1, 'gap' => '24px'],
|
||
]),
|
||
], [
|
||
'desktop' => ['backgroundColor' => '@grau', 'padding' => ['top' => '104px', 'bottom' => '108px']],
|
||
'mobile' => ['padding' => ['top' => '60px', 'bottom' => '64px']],
|
||
], 'leistungen'),
|
||
|
||
// 3 – Vorher/Nachher mit Regler.
|
||
$section('vergleich-bereich', [
|
||
$row('vergleich-zeile', [
|
||
$column('vergleich-text', [
|
||
$kleinzeile('vergleich-klein', 'Vorher – Nachher'),
|
||
$heading('vergleich-titel', 'Vom Schotter zur fertigen Hoffläche', 2, $h2Stil),
|
||
$text('vergleich-satz', 'Unterbau verdichten, Randsteine setzen, Pflaster verlegen, Fugen schließen – und am Ende eine Fläche, auf der man gern ankommt. Schieben Sie den Regler und sehen Sie selbst.', $fliess('@text', '28px', '460px')),
|
||
$haken('vergleich-punkt-1', '<strong>Fester Unterbau</strong> – tragfähig verdichtet'),
|
||
$haken('vergleich-punkt-2', '<strong>Saubere Kanten</strong> – Randsteine in der Flucht'),
|
||
$haken('vergleich-punkt-3', '<strong>Termin gehalten</strong> – wie vereinbart übergeben'),
|
||
$knopf('vergleich-knopf', 'Mehr zum Gartenbau', '/gartenbau', 'navy', ['desktop' => ['margin' => ['top' => '14px']]]),
|
||
], ['desktop' => ['display' => 'flex', 'flexDirection' => 'column', 'alignItems' => 'flex-start']]),
|
||
$column('vergleich-bild', [
|
||
['id' => 'vergleich-regler', 'type' => 'vergleich.bildvergleich', 'props' => [
|
||
'before' => $medien['vorher']['id'], 'after' => $medien['nachher']['id'],
|
||
'labelBefore' => 'Vorher', 'labelAfter' => 'Nachher',
|
||
'alt' => 'Hoffläche vor einem Einfamilienhaus: vorher Schotter, nachher fertig gepflastert',
|
||
'position' => 50, 'rounded' => false,
|
||
], 'css' => "box-shadow:18px 18px 0 #f4b43a\n& .bildvergleich-etikett{background:#172234;color:#fff;font-weight:800;border-radius:0}"],
|
||
], ['desktop' => ['padding' => ['right' => '18px', 'bottom' => '18px']]]),
|
||
], $zweiSpalten, [
|
||
'desktop' => ['gap' => '80px', 'alignItems' => 'center', 'margin' => ['top' => '0px'], 'columnWidths' => '2 3'],
|
||
'tablet' => ['gap' => '48px'],
|
||
]),
|
||
], [
|
||
'desktop' => ['padding' => ['top' => '108px', 'bottom' => '108px']],
|
||
'mobile' => ['padding' => ['top' => '64px', 'bottom' => '64px']],
|
||
], 'vorher-nachher'),
|
||
|
||
// 4 – Ablauf auf Navy.
|
||
$section('ablauf-bereich', [
|
||
$streifenSvg('ablauf-streifen', 200, 44, 'left:-30px;top:0', 0.85),
|
||
$row('ablauf-kopf', [
|
||
$column('ablauf-kopf-titel', [
|
||
$kleinzeile('ablauf-klein', 'Ablauf', '@gold'),
|
||
$heading('ablauf-titel', 'So läuft Ihr Projekt ab', 2, [...$h2Hell, 'desktop' => [...$h2Hell['desktop'], 'margin' => ['top' => '0px', 'bottom' => '0px']]]),
|
||
]),
|
||
$text('ablauf-satz', 'Sie wissen vorher, was passiert und wann es fertig ist.', [
|
||
'desktop' => ['maxWidth' => '380px', 'fontSize' => '17px', 'lineHeight' => 1.75, 'color' => '@auf-dunkel', 'margin' => ['bottom' => '0px']],
|
||
'mobile' => ['maxWidth' => 'none', 'fontSize' => '16px'],
|
||
]),
|
||
], [], [
|
||
'desktop' => ['display' => 'flex', 'flexWrap' => 'wrap', 'justifyContent' => 'space-between', 'alignItems' => 'flex-end', 'rowGap' => '20px', 'columnGap' => '48px', 'margin' => ['top' => '0px', 'bottom' => '48px']],
|
||
'mobile' => ['margin' => ['bottom' => '32px']],
|
||
]),
|
||
$column('ablauf-raster', [
|
||
$schritt(1, 'Anfrage', 'Sie schildern kurz Ihr Vorhaben – per Formular, Telefon oder E-Mail.'),
|
||
$schritt(2, 'Besichtigung & Angebot', 'Wir sehen uns die Lage vor Ort an und machen ein klares Angebot.'),
|
||
$schritt(3, 'Ausführung', 'Mit eigenem Team und Gerät, zum vereinbarten Termin.'),
|
||
$schritt(4, 'Übergabe', 'Gemeinsame Abnahme – die Baustelle hinterlassen wir sauber.'),
|
||
], [
|
||
'desktop' => ['display' => 'grid', 'gridColumns' => 4, 'gap' => '20px'],
|
||
'tablet' => ['gridColumns' => 2],
|
||
'mobile' => ['gridColumns' => 1, 'gap' => '16px'],
|
||
]),
|
||
], [
|
||
'desktop' => ['backgroundColor' => '@navy', 'color' => '@weiss', 'padding' => ['top' => '104px', 'bottom' => '108px']],
|
||
'mobile' => ['padding' => ['top' => '60px', 'bottom' => '64px']],
|
||
], 'ablauf', "position:relative;overflow:hidden\n".$ueberStreifen('ablauf-streifen')),
|
||
|
||
// 5 – Einblicke: Galerie mit Bildbetrachter.
|
||
$section('einblicke-bereich', [
|
||
$kleinzeile('einblicke-klein', 'Einblicke'),
|
||
$heading('einblicke-titel', 'Aus unseren Arbeitsbereichen', 2, [...$h2Stil, 'desktop' => [...$h2Stil['desktop'], 'margin' => ['top' => '0px', 'bottom' => '40px']]]),
|
||
['id' => 'einblicke-galerie', 'type' => 'gallery', 'props' => ['lightbox' => true], 'children' => array_map(
|
||
static fn (string $f, int $i): array => $bild('einblicke-bild-'.($i + 1), $medien[$f], (string) $medien[$f]['alt'], [
|
||
'desktop' => ['width' => '100%', 'imageRatio' => '3/2', 'objectFit' => 'cover', 'margin' => $seiten('0px')],
|
||
], null, "overflow:hidden\n& img{display:block;width:100%;height:100%;transition:transform .6s ease}\n&:hover img{transform:scale(1.04)}", true),
|
||
['bagger', 'zisterne', 'zauntor', 'dach', 'haus', 'terrasse'], [0, 1, 2, 3, 4, 5],
|
||
), 'style' => [
|
||
'desktop' => ['gridColumns' => 3, 'gap' => '16px', 'margin' => ['top' => '0px']],
|
||
'tablet' => ['gridColumns' => 2],
|
||
'mobile' => ['gridColumns' => 1, 'gap' => '14px'],
|
||
]],
|
||
], [
|
||
'desktop' => ['padding' => ['top' => '104px', 'bottom' => '100px']],
|
||
'mobile' => ['padding' => ['top' => '60px', 'bottom' => '60px']],
|
||
], 'einblicke'),
|
||
|
||
// 6 – Über uns: Teamfoto mit Gold-Versatz, Text rechts.
|
||
$section('team-bereich', [
|
||
$row('team-zeile', [
|
||
$column('team-bild-spalte', [
|
||
$bild('team-bild', $medien['team'], 'Das Team von Becker – Just In Time vor einer Baustelle mit Gerüst', [
|
||
'desktop' => ['margin' => $seiten('0px'), 'imageHeight' => '500px', 'objectFit' => 'cover'],
|
||
'mobile' => ['imageHeight' => '280px'],
|
||
], null, '& img{display:block;width:100%;box-shadow:-18px 18px 0 #172234}'),
|
||
], ['desktop' => ['padding' => ['left' => '18px', 'bottom' => '18px']]]),
|
||
$column('team-text', [
|
||
$kleinzeile('team-klein', 'Über uns'),
|
||
$heading('team-titel', 'Ihr Ansprechpartner in Wiernsheim', 2, $h2Stil),
|
||
$text('team-satz-1', 'Hinter Becker – Just In Time steht '.$inhaber.' mit seinem Team. Wir sind im Enzkreis zu Hause und arbeiten dort, wo es Hand und Gerät braucht: im Garten, am Grundstücksrand und beim Rückbau belasteter Bauteile.', $fliess('@text', '18px', '540px')),
|
||
$text('team-satz-2', 'Sie haben einen festen Ansprechpartner vom ersten Anruf bis zur Übergabe – und eine Baustelle, die ordentlich aussieht.', $fliess('@text', '30px', '540px')),
|
||
$zeile('team-knoepfe', [
|
||
$knopf('team-knopf', 'Kontakt aufnehmen', '/kontakt', 'navy'),
|
||
$knopf('team-anrufen', $telefon, $tel, 'rahmen', [], 'phone'),
|
||
], '14px', ['flexWrap' => 'wrap']),
|
||
]),
|
||
], $zweiSpalten, [
|
||
'desktop' => ['gap' => '80px', 'alignItems' => 'center', 'margin' => ['top' => '0px'], 'columnWidths' => '1 1'],
|
||
'tablet' => ['gap' => '48px'],
|
||
]),
|
||
], [
|
||
'desktop' => ['backgroundColor' => '@grau', 'padding' => ['top' => '108px', 'bottom' => '108px']],
|
||
'mobile' => ['padding' => ['top' => '64px', 'bottom' => '64px']],
|
||
], 'ueber-uns'),
|
||
|
||
// 7 – Abschluss auf Gold.
|
||
$section('aufruf-bereich', [
|
||
$row('aufruf-zeile', [
|
||
$column('aufruf-text', [
|
||
$heading('aufruf-titel', 'Was haben Sie vor?', 2, [...$h2Stil, 'desktop' => [...$h2Stil['desktop'], 'fontSize' => '46px', 'margin' => ['top' => '0px', 'bottom' => '10px']]]),
|
||
$text('aufruf-satz', 'Beschreiben Sie Ihr Vorhaben in zwei kurzen Schritten – oder sprechen Sie direkt mit '.$inhaber.'.', $fliess('@navy', '0px', '560px')),
|
||
]),
|
||
$zeile('aufruf-wege', [
|
||
$knopf('aufruf-anfrage', 'Projekt anfragen', '/kontakt', 'navy'),
|
||
$knopf('aufruf-anrufen', $telefon, $tel, 'rahmen', [], 'phone'),
|
||
], '14px', ['flexWrap' => 'wrap', 'justifyContent' => 'flex-end'], ['justifyContent' => 'flex-start']),
|
||
], $zweiSpalten, [
|
||
'desktop' => ['gap' => '40px', 'alignItems' => 'center', 'margin' => ['top' => '0px'], 'columnWidths' => '3 2'],
|
||
'tablet' => ['gap' => '28px'],
|
||
]),
|
||
], [
|
||
'desktop' => ['backgroundColor' => '@gold', 'color' => '@navy', 'padding' => ['top' => '72px', 'bottom' => '72px']],
|
||
'mobile' => ['padding' => ['top' => '52px', 'bottom' => '52px']],
|
||
], 'anfrage'),
|
||
],
|
||
];
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// 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;
|
||
};
|
||
|
||
// Hero-Text steigt beim Laden auf; der Abschnittshintergrund ist das LCP-Element und bleibt ruhig.
|
||
$startseite = $einblenden($startseite, [
|
||
'hero-titel' => ['rise', 'short'],
|
||
'hero-satz' => ['rise', 'medium'],
|
||
'hero-knoepfe' => ['rise', 'medium'],
|
||
'hero-fakten' => ['fade', 'long'],
|
||
'leistungen-kopf' => ['rise'],
|
||
'leistung-garten' => ['rise'],
|
||
'leistung-zaun' => ['rise', 'short'],
|
||
'leistung-asbest' => ['rise', 'medium'],
|
||
'vergleich-text' => ['rise'],
|
||
'vergleich-bild' => ['right', 'short'],
|
||
'ablauf-kopf' => ['rise'],
|
||
'ablauf-1' => ['rise'],
|
||
'ablauf-2' => ['rise', 'short'],
|
||
'ablauf-3' => ['rise', 'medium'],
|
||
'ablauf-4' => ['rise', 'long'],
|
||
'einblicke-galerie' => ['fade'],
|
||
'team-bild-spalte' => ['left'],
|
||
'team-text' => ['rise', 'short'],
|
||
'aufruf-zeile' => ['rise'],
|
||
]);
|
||
|
||
$homeId = $anlegen('home', $startseite, 'Becker – Just In Time | Gartenbau, Zaunbau & Asbestsanierung in Wiernsheim', $medien['haus']['id']);
|
||
|
||
/** Der Seitenkopf der Unterseiten: Foto, stark abgedunkelt, Kleinzeile, Titel, Vorspann, Knöpfe. */
|
||
$seitenkopf = static fn (string $praefix, array $foto, string $klein, string $titel, string $vorspann, bool $knoepfe = true) => $section($praefix.'-kopf', [
|
||
$streifenSvg($praefix.'-streifen', 200, 70, 'right:-20px;bottom:0'),
|
||
$kleinzeile($praefix.'-klein', $klein, '@gold'),
|
||
$heading($praefix.'-h1', $titel, 1, [
|
||
'desktop' => ['fontFamily' => 'heading', 'fontSize' => '52px', 'fontWeight' => '800', 'lineHeight' => 1.1, 'color' => '@weiss', 'maxWidth' => '820px', 'margin' => ['top' => '0px', 'bottom' => '18px']],
|
||
'mobile' => ['fontSize' => '34px'],
|
||
]),
|
||
$text($praefix.'-vorspann', $vorspann, [
|
||
'desktop' => ['maxWidth' => '640px', 'fontSize' => '18px', 'lineHeight' => 1.7, 'color' => '#e3e7ee', 'margin' => ['bottom' => $knoepfe ? '32px' : '0px']],
|
||
'mobile' => ['fontSize' => '16px'],
|
||
]),
|
||
...($knoepfe ? [$zeile($praefix.'-knoepfe', [
|
||
$knopf($praefix.'-knopf-anfrage', 'Projekt anfragen', '/kontakt'),
|
||
$knopf($praefix.'-knopf-anrufen', $telefon, $tel, 'hell', [], 'phone'),
|
||
], '14px', ['flexWrap' => 'wrap'])] : []),
|
||
], [
|
||
'desktop' => ['backgroundImage' => $foto['id'], 'backgroundSize' => 'cover', 'backgroundPosition' => 'center', 'overlayColor' => '#172234', 'overlayOpacity' => 0.74,
|
||
'color' => '@weiss', 'padding' => ['top' => '96px', 'bottom' => '88px'], 'borderWidth' => $nurUnten('4px'), 'borderStyle' => 'solid', 'borderColor' => '@gold'],
|
||
'mobile' => ['padding' => ['top' => '56px', 'bottom' => '52px']],
|
||
], null, "position:relative;overflow:hidden\n".$ueberStreifen($praefix.'-streifen'));
|
||
|
||
/** Ein Leistungspunkt als Kachel: Gold-Ecke, Titel, Satz. */
|
||
$kachel = static fn (string $id, string $titel, string $satz) => $column($id, [
|
||
$heading($id.'-titel', $titel, 3, ['desktop' => ['fontFamily' => 'heading', 'fontSize' => '19px', 'fontWeight' => '800', 'lineHeight' => 1.3, 'color' => '@navy', 'margin' => ['top' => '0px', 'bottom' => '8px']]]),
|
||
$text($id.'-satz', $satz, ['desktop' => ['fontSize' => '15px', 'lineHeight' => 1.7, 'color' => '@text', 'margin' => ['bottom' => '0px']]]),
|
||
], ['desktop' => ['backgroundColor' => '@weiss', 'padding' => ['top' => '26px', 'right' => '26px', 'bottom' => '26px', 'left' => '26px'], 'borderWidth' => ['top' => '0px', 'right' => '0px', 'bottom' => '0px', 'left' => '4px'], 'borderStyle' => 'solid', 'borderColor' => '@gold',
|
||
'shadowY' => '6px', 'shadowBlur' => '20px', 'shadowColor' => '#1722341a']], "transition:transform .25s ease\n&:hover{transform:translateY(-4px)}");
|
||
|
||
/**
|
||
* Eine Leistungsseite: Kopf mit Foto, Einleitung mit Bild, Kacheln, zweites Bildpaar, Aufruf.
|
||
*
|
||
* @param list<array{0: string, 1: string}> $kacheln
|
||
* @param list<string> $fotos Schlüssel in $medien für das Bildpaar
|
||
*/
|
||
$leistungsseite = static function (string $p, array $kopfFoto, string $klein, string $titel, string $vorspann, string $einTitel, array $einText, array $einFoto, string $kachelTitel, array $kacheln, array $fotos, string $hinweis = '') use ($seitenkopf, $section, $row, $column, $heading, $text, $bild, $kleinzeile, $kachel, $knopf, $zeile, $h2Stil, $fliess, $zweiSpalten, $seiten, $medien, $telefon, $tel, $linkCss): array {
|
||
$absaetze = [];
|
||
foreach ($einText as $i => $absatz) {
|
||
$absaetze[] = $text($p.'-ein-satz-'.$i, $absatz, $fliess('@text', '18px', '560px'), $linkCss);
|
||
}
|
||
|
||
return [
|
||
$seitenkopf($p, $kopfFoto, $klein, $titel, $vorspann),
|
||
$section($p.'-ein', [
|
||
$row($p.'-ein-zeile', [
|
||
$column($p.'-ein-text', [
|
||
$kleinzeile($p.'-ein-klein', $klein),
|
||
$heading($p.'-ein-titel', $einTitel, 2, $h2Stil),
|
||
...$absaetze,
|
||
]),
|
||
$column($p.'-ein-bild-spalte', [
|
||
$bild($p.'-ein-bild', $einFoto, (string) $einFoto['alt'], [
|
||
'desktop' => ['margin' => $seiten('0px'), 'imageHeight' => '460px', 'objectFit' => 'cover'],
|
||
'mobile' => ['imageHeight' => '260px'],
|
||
], null, '& img{display:block;width:100%;box-shadow:18px 18px 0 #f4b43a}'),
|
||
], ['desktop' => ['padding' => ['right' => '18px', 'bottom' => '18px']]]),
|
||
], $zweiSpalten, [
|
||
'desktop' => ['gap' => '72px', 'alignItems' => 'center', 'margin' => ['top' => '0px'], 'columnWidths' => '1 1'],
|
||
'tablet' => ['gap' => '44px'],
|
||
]),
|
||
], [
|
||
'desktop' => ['padding' => ['top' => '96px', 'bottom' => '96px']],
|
||
'mobile' => ['padding' => ['top' => '56px', 'bottom' => '56px']],
|
||
]),
|
||
$section($p.'-leistungen', [
|
||
$heading($p.'-leistungen-titel', $kachelTitel, 2, [...$h2Stil, 'desktop' => [...$h2Stil['desktop'], 'margin' => ['top' => '0px', 'bottom' => '36px']]]),
|
||
$column($p.'-kacheln', array_map(static fn (array $k, int $i) => $kachel($p.'-kachel-'.($i + 1), $k[0], $k[1]), $kacheln, array_keys($kacheln)), [
|
||
'desktop' => ['display' => 'grid', 'gridColumns' => 3, 'gap' => '20px'],
|
||
'tablet' => ['gridColumns' => 2],
|
||
'mobile' => ['gridColumns' => 1, 'gap' => '14px'],
|
||
]),
|
||
...('' !== $hinweis ? [$text($p.'-hinweis', $hinweis, ['desktop' => ['fontSize' => '15px', 'lineHeight' => 1.7, 'color' => '@text', 'maxWidth' => '820px', 'margin' => ['top' => '32px', 'bottom' => '0px']]], $linkCss)] : []),
|
||
], [
|
||
'desktop' => ['backgroundColor' => '@grau', 'padding' => ['top' => '92px', 'bottom' => '96px']],
|
||
'mobile' => ['padding' => ['top' => '56px', 'bottom' => '60px']],
|
||
]),
|
||
$section($p.'-bilder', [
|
||
['id' => $p.'-galerie', 'type' => 'gallery', 'props' => ['lightbox' => true], 'children' => array_map(
|
||
static fn (string $f, int $i): array => $bild($p.'-bild-'.($i + 1), $medien[$f], (string) $medien[$f]['alt'], [
|
||
'desktop' => ['width' => '100%', 'imageRatio' => '3/2', 'objectFit' => 'cover', 'margin' => $seiten('0px')],
|
||
], null, '', true),
|
||
$fotos, array_keys($fotos),
|
||
), 'style' => [
|
||
'desktop' => ['gridColumns' => 2, 'gap' => '20px', 'margin' => ['top' => '0px']],
|
||
'mobile' => ['gridColumns' => 1, 'gap' => '14px'],
|
||
]],
|
||
], [
|
||
'desktop' => ['padding' => ['top' => '88px', 'bottom' => '88px']],
|
||
'mobile' => ['padding' => ['top' => '52px', 'bottom' => '52px']],
|
||
]),
|
||
$section($p.'-aufruf', [
|
||
$row($p.'-aufruf-zeile', [
|
||
$column($p.'-aufruf-text', [
|
||
$heading($p.'-aufruf-titel', 'Wir sehen uns Ihr Vorhaben vor Ort an', 2, [...$h2Stil, 'desktop' => [...$h2Stil['desktop'], 'fontSize' => '40px', 'margin' => ['top' => '0px', 'bottom' => '10px']]]),
|
||
$text($p.'-aufruf-satz', 'Beschreiben Sie kurz Ihr Vorhaben – wir melden uns und vereinbaren einen Termin vor Ort.', $fliess('@navy', '0px', '560px')),
|
||
]),
|
||
$zeile($p.'-aufruf-wege', [
|
||
$knopf($p.'-aufruf-anfrage', 'Projekt anfragen', '/kontakt', 'navy'),
|
||
$knopf($p.'-aufruf-anrufen', $telefon, $tel, 'rahmen', [], 'phone'),
|
||
], '14px', ['flexWrap' => 'wrap', 'justifyContent' => 'flex-end'], ['justifyContent' => 'flex-start']),
|
||
], $zweiSpalten, [
|
||
'desktop' => ['gap' => '40px', 'alignItems' => 'center', 'margin' => ['top' => '0px'], 'columnWidths' => '3 2'],
|
||
'tablet' => ['gap' => '28px'],
|
||
]),
|
||
], [
|
||
'desktop' => ['backgroundColor' => '@gold', 'color' => '@navy', 'padding' => ['top' => '64px', 'bottom' => '64px']],
|
||
'mobile' => ['padding' => ['top' => '48px', 'bottom' => '48px']],
|
||
]),
|
||
];
|
||
};
|
||
|
||
$unterEinblenden = static fn (string $p, array $dok) => $einblenden($dok, [
|
||
$p.'-h1' => ['rise', 'short'], $p.'-vorspann' => ['rise', 'medium'], $p.'-knoepfe' => ['rise', 'medium'],
|
||
$p.'-ein-text' => ['rise'], $p.'-ein-bild-spalte' => ['right', 'short'],
|
||
$p.'-kachel-1' => ['rise'], $p.'-kachel-2' => ['rise', 'short'], $p.'-kachel-3' => ['rise', 'medium'],
|
||
$p.'-kachel-4' => ['rise'], $p.'-kachel-5' => ['rise', 'short'], $p.'-kachel-6' => ['rise', 'medium'],
|
||
$p.'-galerie' => ['fade'], $p.'-aufruf-zeile' => ['rise'],
|
||
]);
|
||
|
||
$anlegen('gartenbau', $unterEinblenden('garten', [
|
||
'schemaVersion' => $SCHEMA, 'title' => 'Garten- und Landschaftsbau',
|
||
'description' => 'Garten- und Landschaftsbau in Wiernsheim und im Enzkreis: Pflaster- und Plattenarbeiten, Hofflächen, Terrassen, Erdarbeiten, Zisternen und Entwässerung.',
|
||
'blocks' => $leistungsseite('garten', $medien['bagger'], 'Gartenbau', 'Garten- und Landschaftsbau in Wiernsheim und im Enzkreis',
|
||
'Pflaster, Platten, Erdarbeiten und Zisternen – wir bauen Außenanlagen mit festem Unterbau und sauberen Kanten.',
|
||
'Vom ersten Spatenstich bis zur fertigen Fläche',
|
||
[
|
||
'Ob neue Hofeinfahrt, Terrasse oder ein Garten, der nach dem Hausbau erst noch entstehen muss: Wir übernehmen die Erdarbeiten mit eigenem Minibagger und Radlader, bauen den Unterbau tragfähig auf und verlegen Pflaster und Platten in der Flucht.',
|
||
'Regenwasser gehört dabei mitgedacht – Zisternen, Rinnen und Anschlüsse setzen wir gleich mit. Sie bekommen alles aus einer Hand und einen Termin, auf den Verlass ist.',
|
||
],
|
||
$medien['nachher'], 'Was wir im Garten- und Landschaftsbau übernehmen', [
|
||
['Hofflächen und Zufahrten', 'Pflaster mit tragfähigem Unterbau, Randsteinen und Entwässerung.'],
|
||
['Terrassen', 'Keramik-, Beton- oder Natursteinplatten – im Splittbett oder auf Stelzlagern.'],
|
||
['Erdarbeiten', 'Aushub, Geländemodellierung und Abtransport mit eigenem Gerät.'],
|
||
['Zisternen und Entwässerung', 'Einbau von Betonzisternen, Rohranschlüsse, Rinnen und Sickerschächte.'],
|
||
['Mauern und Treppen', 'Blockstufen, Natursteinmauern und Gabionen für Hanglagen.'],
|
||
['Beete und Rasen', 'Flächen vorbereiten, Beete anlegen, Rasen einsäen oder Rollrasen verlegen.'],
|
||
], ['terrasse', 'zisterne', 'haus', 'vorher']),
|
||
]), 'Gartenbau in Wiernsheim – Pflaster, Terrassen, Erdarbeiten | '.$kurz, $medien['bagger']['id']);
|
||
|
||
$anlegen('zaunbau', $unterEinblenden('zaun', [
|
||
'schemaVersion' => $SCHEMA, 'title' => 'Zaunbau und Sichtschutz',
|
||
'description' => 'Zaunbau in Wiernsheim und im Enzkreis: Doppelstabmattenzäune, Sichtschutzstreifen, Tore und Pforten – fachgerecht montiert, langlebig und pflegeleicht.',
|
||
'blocks' => $leistungsseite('zaun', $medien['zaun'], 'Zaunbau', 'Zaunbau und Sichtschutz',
|
||
'Doppelstabmatten, Sichtschutzstreifen und Tore: fachgerecht montiert, langlebig und pflegeleicht.',
|
||
'Zaunmontage mit festen Fundamenten',
|
||
[
|
||
'Ein Zaun ist nur so gut wie seine Pfosten. Wir setzen sie in Fundamente oder auf vorhandene Mauern, richten die Felder sauber in der Flucht aus und passen den Verlauf an Gefälle und Grundstücksgrenze an.',
|
||
'Mit Sichtschutzstreifen in Anthrazit, Grau oder Weiß wird aus dem Zaun eine Rückzugsfläche. Tore und Pforten liefern wir passend dazu – auch alten Zaun bauen wir vorher ab.',
|
||
],
|
||
$medien['zauntor'], 'Was wir im Zaunbau übernehmen', [
|
||
['Doppelstabmattenzäune', 'In verschiedenen Höhen, verzinkt und pulverbeschichtet.'],
|
||
['Sichtschutz', 'Sichtschutzstreifen eingeflochten – blickdicht und windstabil.'],
|
||
['Tore und Pforten', 'Ein- und zweiflügelig, passend zum Zaun.'],
|
||
['Fundamente und Mauern', 'Pfosten einbetoniert oder auf Mauern montiert.'],
|
||
['Hanglagen', 'Zaunverlauf gestuft oder dem Gelände angepasst.'],
|
||
['Rückbau alter Zäune', 'Abbau und Entsorgung vor der Neumontage.'],
|
||
], ['zaun', 'zauntor']),
|
||
]), 'Zaunbau in Wiernsheim – Doppelstabmatten & Sichtschutz | '.$kurz, $medien['zaun']['id']);
|
||
|
||
$anlegen('asbestsanierung', $unterEinblenden('asbest', [
|
||
'schemaVersion' => $SCHEMA, 'title' => 'Asbestsanierung nach TRGS 519',
|
||
'description' => 'Asbestsanierung nach TRGS 519 in Wiernsheim und im Enzkreis: Abbruch-, Sanierungs- und Instandhaltungsarbeiten an asbesthaltigen Materialien, Rückbau, Verpackung und Entsorgung.',
|
||
'blocks' => $leistungsseite('asbest', $medien['dach'], 'Asbestsanierung', 'Asbestsanierung nach TRGS 519',
|
||
'Abbruch-, Sanierungs- und Instandhaltungsarbeiten an asbesthaltigen Materialien – mit Schutzausrüstung, abgeschottetem Arbeitsbereich und geregelter Entsorgung.',
|
||
'Asbesthaltige Bauteile fachgerecht entfernen',
|
||
[
|
||
'Asbest steckt in vielen Gebäuden, die vor 1993 gebaut wurden: in Dach- und Fassadenplatten aus Asbestzement, in Bodenbelägen, Deckenplatten oder Kleber. Solange das Material unbeschädigt ist, ruht die Gefahr – beim Abbruch oder Umbau werden Fasern frei.',
|
||
'Deshalb arbeiten wir nach den Technischen Regeln für Gefahrstoffe, TRGS 519: Arbeitsbereich abschotten, Schutzanzug und Atemschutz tragen, Material staubarm ausbauen, dicht verpacken und über zugelassene Wege entsorgen.',
|
||
],
|
||
$medien['asbest'], 'Was wir bei Asbest übernehmen', [
|
||
['Dach- und Fassadenplatten', 'Rückbau von Asbestzementplatten an Dächern, Garagen und Fassaden.'],
|
||
['Innenräume', 'Ausbau asbesthaltiger Decken-, Wand- und Bodenplatten.'],
|
||
['Abschottung', 'Arbeitsbereich mit Folie und Unterdruck vom Rest des Gebäudes getrennt.'],
|
||
['Schutz', 'Einweg-Schutzanzüge und Atemschutz mit P3-Filter für das ganze Team.'],
|
||
['Verpackung und Entsorgung', 'Staubdicht in Big Bags verpackt und fachgerecht entsorgt.'],
|
||
['Behördliche Anzeige', 'Die vorgeschriebene Anzeige der Arbeiten übernehmen wir.'],
|
||
], ['asbest', 'dach'], 'Sie sind unsicher, ob ein Bauteil Asbest enthält? Bitte nicht selbst anbohren oder abbrechen. <a href="/kontakt">Schreiben Sie uns</a> oder rufen Sie an – gern mit Foto und Baujahr.'),
|
||
]), 'Asbestsanierung nach TRGS 519 im Enzkreis | '.$kurz, $medien['asbest']['id']);
|
||
|
||
// Die Kontaktseite: Formularkarte links, Kontaktkarte rechts.
|
||
$formularCss = '& .block-form{max-width:none}
|
||
& .form-field label{font-size:15px;font-weight:700;color:#172234}
|
||
& .form-field input{background:#f7f8fa;border:1px solid #c9ced8;border-radius:2px;padding:13px 14px;font-size:16px;color:#172234;min-height:50px}
|
||
& .form-field select{background:#f7f8fa;border:1px solid #c9ced8;border-radius:2px;padding:13px 14px;font-size:16px;color:#172234;min-height:50px}
|
||
& .form-field textarea{background:#f7f8fa;border:1px solid #c9ced8;border-radius:2px;padding:13px 14px;font-size:16px;color:#172234;min-height:140px}
|
||
& .form-field input:focus{border-color:#172234;outline:3px solid #f4b43a;outline-offset:0}
|
||
& .form-field select:focus{border-color:#172234;outline:3px solid #f4b43a;outline-offset:0}
|
||
& .form-field textarea:focus{border-color:#172234;outline:3px solid #f4b43a;outline-offset:0}
|
||
& .field-note{font-size:13px;color:#4a5565;opacity:1}
|
||
& .form-step-intro{color:#4a5565;opacity:1}
|
||
& .form-progress-step{color:#4a5565;opacity:1}
|
||
& .form-choice{display:flex;flex-wrap:wrap;gap:10px}
|
||
& .form-choice .check-row{gap:10px;align-items:center;background:#f7f8fa;border:1px solid #c9ced8;border-radius:2px;padding:11px 16px;transition:border-color .2s,background-color .2s}
|
||
& .form-choice .check-row:hover{border-color:#172234;background:#fff}
|
||
& .check-row label{font-weight:600;font-size:15px;color:#172234}
|
||
& .check-row input{accent-color:#172234;min-height:0;width:18px;height:18px;padding:0;margin:0}
|
||
& .cms-button{min-height:54px;font-size:16px;transition:transform .2s ease,background-color .2s ease}
|
||
& .cms-button:hover{transform:translateY(-2px)}';
|
||
|
||
$kontaktZeile = static fn (string $id, string $icon, string $inhalt) => $zeile($id, [
|
||
$symbol($id.'-icon', $icon, 20, ['desktop' => ['color' => '@gold', 'flexShrink' => 0]]),
|
||
$text($id.'-text', $inhalt, ['desktop' => ['fontSize' => '16px', 'lineHeight' => 1.6, 'color' => '@auf-dunkel', 'margin' => ['bottom' => '0px']]], "& a{color:#fff;text-decoration:none;font-weight:700;overflow-wrap:anywhere}\n& a:hover{color:#f4b43a}"),
|
||
], '12px', ['alignItems' => 'flex-start', 'margin' => ['bottom' => '14px']]);
|
||
|
||
$anlegen('kontakt', $einblenden([
|
||
'schemaVersion' => $SCHEMA, 'title' => 'Kontakt',
|
||
'description' => 'So erreichen Sie Becker – Just In Time in Wiernsheim: Telefon '.$telefon.', E-Mail und Anfrageformular für Gartenbau, Zaunbau und Asbestsanierung.',
|
||
'blocks' => [
|
||
$seitenkopf('kontakt', $medien['bagger'], 'Kontakt', 'Was haben Sie vor?', 'Beschreiben Sie uns Ihr Vorhaben in zwei kurzen Schritten – oder sprechen Sie direkt mit '.$inhaber.'.', false),
|
||
$section('kontakt', [
|
||
$row('kontakt-zeile', [
|
||
$column('kontakt-karte', [
|
||
$heading('kontakt-karte-titel', 'Ihre Anfrage', 2, ['desktop' => ['fontFamily' => 'heading', 'fontSize' => '28px', 'fontWeight' => '800', 'color' => '@navy', 'margin' => ['top' => '0px', 'bottom' => '24px']], 'mobile' => ['fontSize' => '24px']]),
|
||
['id' => 'kontakt-formular', 'type' => 'form', 'props' => ['form' => 'anfrage'], 'css' => $formularCss],
|
||
$text('kontakt-hinweis', 'Ihre Angaben verwenden wir nur für Ihre Anfrage. Mehr dazu in der <a href="/datenschutz">Datenschutzerklärung</a>.', ['desktop' => ['fontSize' => '13px', 'lineHeight' => 1.6, 'color' => '@text', 'margin' => ['top' => '18px', 'bottom' => '0px']]], $linkCss),
|
||
], [
|
||
'desktop' => ['backgroundColor' => '@weiss', 'padding' => $seiten('44px'), 'shadowY' => '18px', 'shadowBlur' => '50px', 'shadowColor' => '#1722341f', 'borderWidth' => $nurOben('4px'), 'borderStyle' => 'solid', 'borderColor' => '@gold'],
|
||
'mobile' => ['padding' => ['top' => '26px', 'right' => '20px', 'bottom' => '28px', 'left' => '20px']],
|
||
]),
|
||
$column('kontakt-seite', [
|
||
$column('kontakt-direkt', [
|
||
$streifenSvg('kontakt-direkt-streifen', 120, 44, 'right:-20px;bottom:0'),
|
||
$heading('kontakt-direkt-titel', 'Lieber direkt sprechen?', 2, ['desktop' => ['fontFamily' => 'heading', 'fontSize' => '22px', 'fontWeight' => '800', 'color' => '@weiss', 'margin' => ['top' => '0px', 'bottom' => '20px']]]),
|
||
$kontaktZeile('kontakt-telefon', 'phone', '<a href="'.$tel.'">'.$telefon.'</a>'),
|
||
$kontaktZeile('kontakt-mail', 'mail', '<a href="mailto:'.$mail.'">'.$mail.'</a>'),
|
||
$kontaktZeile('kontakt-ort', 'pin', $inhaber.'<br>'.$strasse.'<br>'.$plz.' '.$ort),
|
||
], ['desktop' => ['backgroundColor' => '@navy', 'padding' => $seiten('32px')]], "position:relative;overflow:hidden\n& > [id]{position:relative;z-index:1}\n& > [id=\"kontakt-direkt-streifen\"]{position:absolute;z-index:0}"),
|
||
$column('kontakt-ablauf', [
|
||
$heading('kontakt-ablauf-titel', 'So geht es weiter', 2, ['desktop' => ['fontFamily' => 'heading', 'fontSize' => '20px', 'fontWeight' => '800', 'color' => '@navy', 'margin' => ['top' => '0px', 'bottom' => '18px']]]),
|
||
$haken('kontakt-ablauf-1', 'Wir melden uns persönlich bei Ihnen.'),
|
||
$haken('kontakt-ablauf-2', 'Wir vereinbaren einen Termin vor Ort.'),
|
||
$haken('kontakt-ablauf-3', 'Sie bekommen ein klares Angebot.'),
|
||
], ['desktop' => ['backgroundColor' => '@weiss', 'padding' => $seiten('30px'), 'borderWidth' => ['top' => '0px', 'right' => '0px', 'bottom' => '0px', 'left' => '4px'], 'borderStyle' => 'solid', 'borderColor' => '@gold']]),
|
||
], ['desktop' => ['display' => 'flex', 'flexDirection' => 'column', 'gap' => '24px']]),
|
||
], $zweiSpalten, [
|
||
'desktop' => ['gap' => '40px', 'alignItems' => 'flex-start', 'margin' => ['top' => '0px'], 'columnWidths' => '2 1'],
|
||
'tablet' => ['gap' => '28px'],
|
||
]),
|
||
], [
|
||
'desktop' => ['backgroundColor' => '@grau', 'padding' => ['top' => '64px', 'bottom' => '100px']],
|
||
'mobile' => ['padding' => ['top' => '28px', 'bottom' => '60px']],
|
||
], 'anfrage-formular'),
|
||
],
|
||
], ['kontakt-h1' => ['rise', 'short'], 'kontakt-vorspann' => ['rise', 'medium'], 'kontakt-karte' => ['rise'], 'kontakt-seite' => ['rise', 'short']]), 'Kontakt | '.$kurz.', Wiernsheim');
|
||
|
||
/** Eine Rechtsseite: Seitenkopf, dann Absätze mit Zwischenüberschriften. */
|
||
$rechtsseite = static function (string $praefix, string $titel, string $vorspann, array $absaetze) use ($seitenkopf, $section, $column, $heading, $text, $linkCss, $medien): array {
|
||
$punkte = [];
|
||
foreach ($absaetze as $i => [$zwischentitel, $inhalt]) {
|
||
if ('' !== $zwischentitel) {
|
||
$punkte[] = $heading($praefix.'-h2-'.$i, $zwischentitel, 2, ['desktop' => ['fontFamily' => 'heading', 'fontSize' => '21px', 'fontWeight' => '800', 'lineHeight' => 1.3, 'color' => '@navy', 'margin' => ['top' => '36px', 'bottom' => '10px']]]);
|
||
}
|
||
$punkte[] = $text($praefix.'-p-'.$i, $inhalt, ['desktop' => ['fontSize' => '17px', 'lineHeight' => 1.75, 'color' => '@text', 'margin' => ['bottom' => '0px']]], $linkCss);
|
||
}
|
||
|
||
return [
|
||
$seitenkopf($praefix, $medien['haus'], 'Rechtliches', $titel, $vorspann, false),
|
||
$section($praefix, [
|
||
$column($praefix.'-inhalt', $punkte, ['desktop' => ['maxWidth' => '820px']]),
|
||
], ['desktop' => ['padding' => ['top' => '32px', 'bottom' => '100px']], 'mobile' => ['padding' => ['top' => '12px', 'bottom' => '64px']]]),
|
||
];
|
||
};
|
||
|
||
$anlegen('impressum', [
|
||
'schemaVersion' => $SCHEMA, 'title' => 'Impressum',
|
||
'description' => 'Anbieterangaben von Becker – Just In Time e. Kfm., Clara-Zetkin-Straße 36, 75446 Wiernsheim.',
|
||
'blocks' => $rechtsseite('impressum', 'Impressum', 'Angaben gemäß § 5 DDG.', [
|
||
['Anbieter', $firma.'<br>Inhaber: '.$inhaber.'<br>'.$strasse.'<br>'.$plz.' '.$ort.'<br>Deutschland'],
|
||
['Kontakt', 'Telefon: <a href="'.$tel.'">'.$telefon.'</a><br>E-Mail: <a href="mailto:'.$mail.'">'.$mail.'</a>'],
|
||
['Zuständige Aufsichtsbehörde', 'Landratsamt Enzkreis<br>Gewerbeaufsicht<br>Zähringerallee 3<br>75177 Pforzheim'],
|
||
['Tätigkeitsbereiche', 'Garten- und Landschaftsbau; Zaunbau; Abbruch-, Sanierungs- und Instandhaltungsarbeiten an asbesthaltigen Materialien gemäß TRGS 519.'],
|
||
['Verbraucherstreitbeilegung', 'Wir sind nicht bereit und nicht verpflichtet, an Streitbeilegungsverfahren vor einer Verbraucherschlichtungsstelle teilzunehmen.'],
|
||
['Haftung für Inhalte', 'Die Inhalte dieser Website wurden mit größtmöglicher Sorgfalt erstellt. Für die Richtigkeit, Vollständigkeit und Aktualität der Inhalte übernehmen wir jedoch keine Gewähr.'],
|
||
['Haftung für Links', 'Diese Website enthält Verknüpfungen zu Websites Dritter. Für deren Inhalte ist der jeweilige Betreiber verantwortlich. Bei Bekanntwerden von Rechtsverstößen entfernen wir solche Links umgehend.'],
|
||
['Urheberrecht', 'Die Inhalte dieser Website unterliegen dem deutschen Urheber- und Leistungsschutzrecht. Jede Verwertung außerhalb der Grenzen des Urheberrechts bedarf der vorherigen schriftlichen Zustimmung.'],
|
||
['Bildnachweis', 'Logo: '.$firma.'. Fotos: Symbolbilder, mit künstlicher Intelligenz erzeugt; sie zeigen typische Arbeiten, keine bestimmten Kundenprojekte.'],
|
||
]),
|
||
], 'Impressum | '.$kurz);
|
||
|
||
$anlegen('datenschutz', [
|
||
'schemaVersion' => $SCHEMA, 'title' => 'Datenschutz',
|
||
'description' => 'Welche Daten beim Besuch der Website von Becker – Just In Time und bei einer Anfrage verarbeitet werden.',
|
||
'blocks' => $rechtsseite('datenschutz', 'Datenschutzerklärung', 'Hier erfahren Sie, welche Daten beim Besuch dieser Website und bei einer Anfrage verarbeitet werden – und welche Rechte Sie haben.', [
|
||
['1. Verantwortlicher', $firma.', Inhaber '.$inhaber.', '.$strasse.', '.$plz.' '.$ort.'. Telefon <a href="'.$tel.'">'.$telefon.'</a>, E-Mail <a href="mailto:'.$mail.'">'.$mail.'</a>.'],
|
||
['2. Besuch dieser Website', 'Beim Aufruf verarbeitet unser Hostingdienstleister 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. Mit dem Hostingdienstleister besteht ein Vertrag über die Auftragsverarbeitung nach Art. 28 DSGVO. Die Serverprotokolle werden nach spätestens sieben Tagen gelöscht oder anonymisiert, sofern sie nicht zur Aufklärung eines Sicherheitsvorfalls länger gebraucht werden.'],
|
||
['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. Anfrageformular', 'Wenn Sie uns über das Formular schreiben, verarbeiten wir Ihre Angaben – Name, E-Mail-Adresse, freiwillig Telefonnummer sowie die Angaben zu Ihrem Vorhaben –, um Ihre Anfrage zu bearbeiten und Ihnen ein Angebot zu machen (Art. 6 Abs. 1 lit. b DSGVO). Die Anfrage wird in unserem System gespeichert und per E-Mail an uns weitergeleitet. Wir löschen sie, wenn sie erledigt ist und keine gesetzliche Aufbewahrungspflicht besteht.'],
|
||
['5. Kontakt per E-Mail oder Telefon', 'Wenn Sie uns schreiben oder anrufen, verarbeiten wir Ihre Angaben, um Ihr Anliegen zu bearbeiten (Art. 6 Abs. 1 lit. b oder f DSGVO).'],
|
||
['6. 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.'],
|
||
['7. 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 <a href="mailto:'.$mail.'">'.$mail.'</a>.'],
|
||
]),
|
||
], 'Datenschutz | '.$kurz);
|
||
|
||
$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'),
|
||
$heading('fehler-titel', 'Diese Seite gibt es leider nicht', 1, [
|
||
'desktop' => ['fontFamily' => 'heading', 'fontSize' => '52px', 'fontWeight' => '800', 'lineHeight' => 1.1, 'color' => '@navy', 'margin' => ['top' => '0px', 'bottom' => '20px']],
|
||
'mobile' => ['fontSize' => '34px'],
|
||
]),
|
||
$text('fehler-text', 'Die Seite gibt es nicht oder sie wurde verschoben. Von der Startseite aus finden Sie alles Wichtige.', $fliess('@text', '32px', '520px')),
|
||
$zeile('fehler-knoepfe', [
|
||
$knopf('fehler-knopf', 'Zur Startseite', '/'),
|
||
$knopf('fehler-kontakt', 'Kontakt', '/kontakt', 'rahmen', [], 'none'),
|
||
], '14px', ['flexWrap' => 'wrap']),
|
||
], [
|
||
'desktop' => ['backgroundColor' => '@grau', 'padding' => ['top' => '112px', 'bottom' => '132px']],
|
||
'mobile' => ['padding' => ['top' => '64px', 'bottom' => '80px']],
|
||
]),
|
||
],
|
||
]);
|
||
$db->executeStatement('UPDATE page SET robots = ? WHERE id = ?', ['noindex', $fehlerId]);
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// Menü, Einstellungen, Konto
|
||
// ---------------------------------------------------------------------------------------------
|
||
|
||
$seiteId = static fn (string $pfad): string => (string) $db->fetchOne('SELECT id FROM page WHERE path = ?', [$pfad]);
|
||
$eintrag = static fn (string $label, string $pfad, array $extra = []): array => ['label' => $label, 'type' => 'page', 'pageId' => $seiteId($pfad), 'children' => [], ...$extra];
|
||
$menus->save('header', 'Hauptnavigation', [
|
||
// Startseite als Haus-Symbol, Entwurf B „house“ (Umut, 10.11.2026); die Beschriftung bleibt für Vorlesesoftware da.
|
||
['label' => 'Startseite', 'type' => 'page', 'pageId' => $homeId, 'anchor' => '', 'icon' => 'house', 'iconPosition' => 'before', 'children' => []],
|
||
$eintrag('Gartenbau', 'gartenbau'),
|
||
$eintrag('Zaunbau', 'zaunbau'),
|
||
$eintrag('Asbestsanierung', 'asbestsanierung'),
|
||
['label' => 'Über uns', 'type' => 'page', 'pageId' => $homeId, 'anchor' => 'ueber-uns', 'children' => []],
|
||
$eintrag('Kontakt', 'kontakt'),
|
||
// Anrufen als Menü-Button (ADR 110); auf dem Telefon im aufgeklappten Menü.
|
||
$eintrag($telefon, 'kontakt', [
|
||
'type' => 'url', 'url' => $tel, 'pageId' => null,
|
||
'icon' => 'phone', 'iconPosition' => 'before',
|
||
'button' => [
|
||
'id' => 'm'.substr(hash('sha256', 'becker-anrufen'), 0, 10), 'shape' => 'square', 'appearance' => 'solid', 'size' => 'normal',
|
||
'fill' => '@gold', 'ink' => '@navy', 'hoverFill' => '@weiss', 'hoverInk' => '@navy',
|
||
'mobile' => 'menu', 'iconMotion' => 'none',
|
||
],
|
||
]),
|
||
], 0);
|
||
echo "Menü angelegt\n";
|
||
|
||
$settings->assignNotFoundPage($fehlerId, '');
|
||
$settings->save([
|
||
'legalName' => $firma,
|
||
'description' => 'Becker – Just In Time e. Kfm. in Wiernsheim: Garten- und Landschaftsbau, Zaunbau und Asbestsanierung nach TRGS 519.',
|
||
'email' => $mail, 'phone' => '+49 176 41753750',
|
||
'street' => $strasse, 'postalCode' => $plz, 'city' => $ort, 'country' => 'DE',
|
||
'areaServed' => 'Enzkreis',
|
||
'localBusiness' => true, 'llmsTxt' => true,
|
||
'faviconMediaId' => $medien['favicon']['id'],
|
||
'stickyHeader' => true, 'headerOverlay' => false,
|
||
'motion' => 'always',
|
||
'homePath' => 'home', 'imprintPath' => 'impressum', 'privacyPath' => 'datenschutz',
|
||
// Die Seite lädt nichts von Dritten; deshalb keine Einwilligungsleiste.
|
||
'consentBanner' => false, 'scrollTop' => true, '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"]']);
|
||
file_put_contents($root.'/var/becker-zugang.txt', "Verwaltung: Benutzer „umut“, Passwort: $passwort\n");
|
||
|
||
echo "\nFertig.\nVerwaltung: Benutzer „umut“, Passwort: $passwort\n";
|