Tree green from the logo everywhere, the 100 emoji inline wherever 100 appears, downloads for the exemption certificates and the PQ certificate, the image video behind consent, Google reviews, a 48-hour quote promise, WhatsApp, two job cards and a page for the alpacas. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1618 lines
130 KiB
PHP
1618 lines
130 KiB
PHP
<?php
|
||
|
||
/**
|
||
* Baut den Entwurf „Garten- und Landschaftsbau Hüfner GmbH“ als Inhalt dieses CMS auf.
|
||
*
|
||
* Vorlage ist Umuts Ordner „huefner-website“ (06.10.2026): eine statische Seite (index.html,
|
||
* 16 Fotos und das Logo) mit Leiste, klebendem Kopf, Hero „Gärten auf Premium-Niveau.“,
|
||
* Einleitung mit Bildcollage, Leistungen als Kachelraster, Referenzen auf Dunkelgrün, Ablauf in
|
||
* vier Schritten, Fuhrpark, Über uns, Karriere, Instagram-Raster, Abschlussband, Kontakt mit
|
||
* Formular und Fuß. Wortlaut, Farben (Grün #067f30, Rot #B71C22, Nacht #142018, Hellgrün
|
||
* #eff8f2) und Schriften (Barlow, Barlow Condensed) sind daraus übernommen. Gebaut ist alles aus
|
||
* gewöhnlichen Bausteinen, Gestaltungswerten und eigenem CSS je Baustein – kein Kerncode, kein
|
||
* Plugin.
|
||
*
|
||
* Bewusste Abweichungen:
|
||
* - Die Platzhalter der Vorlage („[Öffnungszeiten]“, „[Ort] · [Jahr]“, „[XX] Jahre“,
|
||
* „[Kurzer Absatz zur Firmengeschichte]“) erscheinen nicht auf der Seite. An ihrer Stelle stehen
|
||
* Aussagen, die die Vorlage selbst schon macht (Einsatzgebiet, 15-t-Bagger, ein
|
||
* Team). Impressumsangaben aus dem Handelsregister (HRB 742159, Amtsgericht Mannheim,
|
||
* Geschäftsführer Florian Hüfner), Öffnungszeiten und Hoster (IONOS) von Umut.
|
||
* - Das Formular ist ein echtes CMS-Formular mit Versand an info@huefner-gartenbau.de, nicht
|
||
* der Knopf ohne Funktion der Vorlage. „Leistung“ ist eine Auswahl wie dort.
|
||
* - Die Stellen unter „Karriere“ sind Links (E-Mail mit Betreff), weil sie einen Pfeil tragen.
|
||
* - Anrufknöpfe sind echte Knöpfe mit tel: (Kern seit 06.10.2026), der Pfeil im Knopf hat die
|
||
* Schriftfarbe.
|
||
* - Das LKW-Symbol der Leiste ist seit 1.25.0 ein gewöhnliches Symbol („truck“); vorher stand
|
||
* es als „Eigenes HTML“ mit dem Pfad der Vorlage da.
|
||
* - Leiste und Kopf kleben gemeinsam oben (die Einstellung gilt für den ganzen Kopfbereich); in
|
||
* der Vorlage klebt nur der Kopf. Auf dem Telefon ist die Leiste deshalb einzeilig.
|
||
* - Impressum, Datenschutz und 404 sind neu. Die Vorlage verweist nur auf #impressum.
|
||
*
|
||
* Nach dem Kundentermin vom 12.10.2026 (Umuts Notizen):
|
||
* - Grün ist das des Baums im Logo (#067F30); alle Grüntöne sind auf seinen Farbton gedreht.
|
||
* - Das „💯“ von Instagram: Umuts Emoji-Bild überall, wo „100“ steht, im Text (<mark>100</mark>).
|
||
* - Downloads aus Umuts Unterlagen (zweites Argument von reset-huefner.sh): PQ-Bescheinigung
|
||
* unter „Über uns“ und im Fuß, die Freistellungsbescheinigungen (§ 13b UStG, § 48b EStG) bei
|
||
* „Planung & Angebot“ und im Fuß. Dazu Galabau-Signet und PQ-Logo.
|
||
* - Imagevideo von YouTube als „Externer Inhalt“: lädt erst nach Einwilligung, deshalb gibt es
|
||
* jetzt den Einwilligungshinweis.
|
||
* - Google-Bewertungen: drei echte Rezensionen (Google Maps, 12.10.2026), gekürzt mit „…“,
|
||
* Namen abgekürzt, Link zu allen Bewertungen. Kein Bewertungs-Markup (Google wertet eigene
|
||
* Bewertungen auf der eigenen Seite nicht).
|
||
* - Angebot innerhalb von 48 Stunden garantiert (Ablauf und Kontakt), „Leistung“ aus dem
|
||
* Formular, WhatsApp für Kontakt und Bewerbung, 25 Jahre Erfahrung, über 7.000 Projekte,
|
||
* „Bagger bis 15 t“ raus (es gibt größere). Karriere: Facharbeiter und LKW-Fahrer C/CE als zwei
|
||
* schlichte Karten, ein Klick zeigt die Beschreibung.
|
||
* - Eigene Seite „Unsere Alpakas“ mit Umuts Text und Fotos.
|
||
*
|
||
* 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-huefner.sh).
|
||
* Erstes Argument: der Ordner aus build-huefner-assets.php.
|
||
*/
|
||
|
||
require dirname(__DIR__).'/config/bootstrap.php';
|
||
|
||
use App\Content\DocumentLibrary;
|
||
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 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, 'huefner')) {
|
||
fwrite(STDERR, "Sicherung: DATABASE_URL muss auf die Hüfner-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);
|
||
$pages = new PageRepository($db, new DocumentValidator(), $settings);
|
||
$partials = new PartialRepository($db, new DocumentValidator());
|
||
$menus = new MenuRepository($db, $settings);
|
||
$forms = new FormRepository($db);
|
||
$theme = new Theme($db);
|
||
|
||
$firma = 'Garten- und Landschaftsbau Hüfner GmbH';
|
||
$kurz = 'Hüfner Garten- und Landschaftsbau';
|
||
$mail = 'info@huefner-gartenbau.de';
|
||
$telefon = '07229 6992826';
|
||
$tel = 'tel:+4972296992826';
|
||
$strasse = 'Am Forlenspitzen 6';
|
||
$plz = '76473';
|
||
$ort = 'Iffezheim';
|
||
$instagram = 'https://www.instagram.com/galabau_florianhuefner/';
|
||
$facebook = 'https://www.facebook.com/huefnergalabau/';
|
||
// Von Umut am 12.10.2026: Mobilnummer für WhatsApp, für Kontakt und Bewerbung.
|
||
$whatsappNummer = '0176 30387990';
|
||
$whatsapp = 'https://wa.me/4917630387990';
|
||
$whatsappBewerbung = $whatsapp.'?text='.rawurlencode('Hallo Hüfner-Team, ich interessiere mich für eine Stelle bei euch.');
|
||
// Der Google-Eintrag (Maps, Kennung aus der Adresse des Eintrags vom 12.10.2026).
|
||
$google = 'https://maps.google.com/?cid=7006168525111040261';
|
||
$SCHEMA = 24;
|
||
// Öffnungszeiten von Umut (06.10.2026, aus dem Google-Eintrag): Mo–Fr 8–17 Uhr, Sa 8–12 Uhr.
|
||
$zeiten = 'Mo–Fr 8–17 Uhr · Sa 8–12 Uhr';
|
||
$zeitenDaten = [
|
||
...array_map(static fn (string $tag): array => ['day' => $tag, 'opens' => '08:00', 'closes' => '17:00'], ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']),
|
||
['day' => 'Saturday', 'opens' => '08:00', 'closes' => '12:00'],
|
||
];
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// 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 = [], 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;
|
||
};
|
||
|
||
/** Zwei Spalten auf dem Desktop, eine darunter. */
|
||
$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 = [], array $tablet = [], string $css = '') 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, $css);
|
||
};
|
||
|
||
/** Wie $zeile, aber als Spalte: nur eine Spalte trägt eine Fläche (Zeilen sind im Kern durchsichtig). */
|
||
$karte = static function (string $id, array $children, string $gap, array $extra = [], array $mobil = [], array $tablet = [], string $css = '') use (&$zeile): array {
|
||
$block = $zeile($id, $children, $gap, $extra, $mobil, $tablet, $css);
|
||
$block['type'] = 'column';
|
||
|
||
return $block;
|
||
};
|
||
|
||
$column = static function (string $id, array $children, array $style = [], string $css = '', string $sichtbar = 'all'): array {
|
||
$block = ['id' => $id, 'type' => 'column', 'children' => $children];
|
||
if ([] !== $style) {
|
||
$block['style'] = $style;
|
||
}
|
||
if ('' !== $css) {
|
||
$block['css'] = $css;
|
||
}
|
||
if ('all' !== $sichtbar) {
|
||
$block['layout'] = ['visibility' => $sichtbar];
|
||
}
|
||
|
||
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 = '', string $sichtbar = 'all'): array {
|
||
$block = ['id' => $id, 'type' => 'text', 'props' => ['text' => $inhalt]];
|
||
if ([] !== $style) {
|
||
$block['style'] = $style;
|
||
}
|
||
if ('' !== $css) {
|
||
$block['css'] = $css;
|
||
}
|
||
if ('all' !== $sichtbar) {
|
||
$block['layout'] = ['visibility' => $sichtbar];
|
||
}
|
||
|
||
return $block;
|
||
};
|
||
|
||
/** Eine reine Fläche: Striche, Linien. */
|
||
$flaeche = static function (string $id, array $style, string $css = '', string $sichtbar = 'all'): array {
|
||
$block = ['id' => $id, 'type' => 'spacer', 'props' => ['size' => 'small'], 'style' => $style];
|
||
if ('' !== $css) {
|
||
$block['css'] = $css;
|
||
}
|
||
if ('all' !== $sichtbar) {
|
||
$block['layout'] = ['visibility' => $sichtbar];
|
||
}
|
||
|
||
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 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];
|
||
};
|
||
|
||
/**
|
||
* Ein Button. Der Pfeil des Kerns zeigt schräg nach oben; um 45 Grad gedreht zeigt er nach
|
||
* rechts wie in der Vorlage. Alle heben sich beim Überfahren leicht an.
|
||
*
|
||
* @param string $art rot (Theme), glas (durchsichtig auf Bild), weiss, dunkel
|
||
*/
|
||
$knopf = static function (string $id, string $inhalt, string $href, string $art = 'rot', bool $pfeil = true, array $style = [], string $sichtbar = 'all'): array {
|
||
$farbe = [
|
||
'rot' => '',
|
||
'glas' => "background:rgba(255,255,255,.12);color:#fff;box-shadow:inset 0 0 0 1px rgba(255,255,255,.5);\n&:hover{background:rgba(255,255,255,.22);color:#fff}\n",
|
||
'weiss' => "background:#fff;color:#17221b;\n&:hover{background:#e9f5ed;color:#17221b}\n",
|
||
'dunkel' => "background:#17221b;color:#fff;\n&:hover{background:#067f30;color:#fff}\n",
|
||
'linie' => "background:transparent;color:#fff;box-shadow:inset 0 0 0 1px rgba(255,255,255,.35);\n&:hover{background:rgba(255,255,255,.1);color:#fff}\n",
|
||
][$art];
|
||
$css = "gap:12px;min-height:58px;padding:0 30px;margin-top:0;transition:transform .2s ease,box-shadow .2s ease,background-color .2s ease;\n"
|
||
.$farbe
|
||
."&:hover{transform:translateY(-2px);box-shadow:0 10px 24px rgba(0,0,0,.18)}\n"
|
||
.($pfeil ? "& .button-arrow svg{transform:rotate(45deg)}\n& .button-arrow{display:inline-flex}" : '& .button-arrow{display:none}');
|
||
$block = ['id' => $id, 'type' => 'button', 'props' => ['text' => $inhalt, 'href' => $href], 'css' => $css];
|
||
if ([] !== $style) {
|
||
$block['style'] = $style;
|
||
}
|
||
if ('all' !== $sichtbar) {
|
||
$block['layout'] = ['visibility' => $sichtbar];
|
||
}
|
||
|
||
return $block;
|
||
};
|
||
|
||
/**
|
||
* 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;
|
||
};
|
||
|
||
// Wiederkehrende Gestaltungswerte der Vorlage.
|
||
$h2Stil = [
|
||
'desktop' => ['fontSize' => '64px', 'fontWeight' => '800', 'lineHeight' => 0.98, 'letterSpacing' => '0px', 'textTransform' => 'uppercase', 'color' => '@tinte', 'margin' => ['top' => '18px', 'bottom' => '28px']],
|
||
'tablet' => ['fontSize' => '46px'],
|
||
'mobile' => ['fontSize' => '38px', 'margin' => ['top' => '14px', 'bottom' => '22px']],
|
||
];
|
||
$h3Stil = static fn (string $groesse, string $farbe = '@tinte', string $unten = '10px'): array => [
|
||
'desktop' => ['fontSize' => $groesse, 'fontWeight' => '800', 'lineHeight' => 1.0, 'letterSpacing' => '0px', 'textTransform' => 'uppercase', 'color' => $farbe, 'margin' => ['top' => '0px', 'bottom' => $unten]],
|
||
];
|
||
$fliess = static fn (string $groesse = '17px', string $farbe = '@text', string $unten = '18px', float $zeile = 1.7): array => [
|
||
'desktop' => ['fontSize' => $groesse, 'lineHeight' => $zeile, 'color' => $farbe, 'margin' => ['top' => '0px', 'bottom' => $unten]],
|
||
];
|
||
$em = '& em{font-style:normal;color:#067f30}';
|
||
$linkCss = "& a{color:#067f30;text-decoration:underline;text-underline-offset:3px}\n& a:hover{color:#05622a}";
|
||
$emGruen = '& em{font-style:normal;color:#067f30}';
|
||
|
||
/**
|
||
* Die Kleinzeile in Versalien mit dem kurzen Strich davor („— LEISTUNGEN“), auf Wunsch auch
|
||
* dahinter. Seit 12.10.2026 ein einziger Textbaustein, die Striche sind ::before/::after in der
|
||
* Schriftfarbe – vorher eine Zeile mit zwei Abstandhaltern, dreimal so viel HTML und CSS.
|
||
*/
|
||
$kleinzeile = static function (string $id, string $inhalt, string $farbe = '@rot', bool $beidseitig = false, string $unten = '0px') use ($text): array {
|
||
$strich = "content:'';display:inline-block;width:32px;height:2px;background:currentColor;vertical-align:.32em";
|
||
|
||
return $text($id, $inhalt, ['desktop' => [
|
||
'fontFamily' => 'heading', 'fontSize' => '17px', 'fontWeight' => '700', 'letterSpacing' => '0.2em', 'lineHeight' => 1.2,
|
||
'textTransform' => 'uppercase', 'color' => $farbe, 'textAlign' => $beidseitig ? 'center' : 'left', 'margin' => ['top' => '0px', 'bottom' => $unten],
|
||
]], '&::before{'.$strich.';margin-right:12px}'.($beidseitig ? "\n&::after{".$strich.';margin-left:12px}' : ''));
|
||
};
|
||
|
||
/** Ein Symbol im grünen Kreis (Kontaktkarten, Direkter Draht). */
|
||
$kreis = static fn (string $id, string $icon, string $groesse = '48px') => ['id' => $id, 'type' => 'column', 'children' => [
|
||
['id' => $id.'-icon', 'type' => 'icon', 'props' => ['icon' => $icon, 'size' => 20, 'decorative' => true, 'label' => ''], 'style' => ['desktop' => ['color' => '@papier']]],
|
||
], 'style' => ['desktop' => ['display' => 'flex', 'alignItems' => 'center', 'justifyContent' => 'center', 'width' => $groesse, 'height' => $groesse, 'flexShrink' => 0, 'backgroundColor' => '@gruen', 'borderRadius' => ['topLeft' => '50%', 'topRight' => '50%', 'bottomRight' => '50%', 'bottomLeft' => '50%']]]];
|
||
|
||
// Ein Link über die ganze Karte: Die Karte ist positioniert, der Text darin nicht, also spannt
|
||
// das ::after des Links die ganze Karte auf. Ein Link, ein Name, ganze Fläche klickbar.
|
||
$ganzeKarte = "position:relative;\n& a{color:inherit;text-decoration:none}\n& a::after{content:'';position:absolute;top:0;right:0;bottom:0;left:0;border-radius:inherit}\n& a:focus-visible{outline:none}\n&:focus-within{outline:3px solid #067f30;outline-offset:3px}";
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// Website, Gestaltung, Medien
|
||
// ---------------------------------------------------------------------------------------------
|
||
|
||
$db->insert('website', ['id' => 'default', 'name' => $kurz, 'default_locale' => 'de', 'theme' => '{}']);
|
||
|
||
$theme->save(
|
||
[
|
||
['key' => 'gruen', 'name' => 'Grün (Akzent)', 'value' => '#067f30'],
|
||
['key' => 'gruen-dunkel', 'name' => 'Grün dunkel (Links beim Überfahren)', 'value' => '#05622a'],
|
||
['key' => 'hellgruen', 'name' => 'Baumgrün auf Dunkel (nur große Schrift und Symbole)', 'value' => '#067f30'],
|
||
['key' => 'rot', 'name' => 'Rot (Knöpfe, Kleinzeilen)', 'value' => '#b71c22'],
|
||
['key' => 'rot-dunkel', 'name' => 'Rot dunkel (Knöpfe beim Überfahren)', 'value' => '#931419'],
|
||
['key' => 'tinte', 'name' => 'Schrift', 'value' => '#17221b'],
|
||
['key' => 'nacht', 'name' => 'Nachtgrün (Leiste, Referenzen)', 'value' => '#142018'],
|
||
['key' => 'fuss', 'name' => 'Fuß', 'value' => '#0f1812'],
|
||
['key' => 'mint', 'name' => 'Hellgrün (Seitengrund)', 'value' => '#eff8f2'],
|
||
['key' => 'chip', 'name' => 'Hellgrün (Flächen)', 'value' => '#e9f5ed'],
|
||
['key' => 'linie', 'name' => 'Linien', 'value' => '#d4e8db'],
|
||
['key' => 'text', 'name' => 'Fließtext', 'value' => '#55635a'],
|
||
['key' => 'text-stark', 'name' => 'Fließtext kräftig', 'value' => '#3a483f'],
|
||
['key' => 'auf-dunkel', 'name' => 'Schrift auf Dunkel', 'value' => '#b5c7bb'],
|
||
['key' => 'papier', 'name' => 'Weiß', 'value' => '#ffffff'],
|
||
],
|
||
[
|
||
['key' => 'body', 'name' => 'Fließtext', 'family' => 'Barlow', 'source' => 'google', 'weights' => [400, 600, 700], 'fallback' => 'sans-serif'],
|
||
['key' => 'heading', 'name' => 'Überschriften', 'family' => 'Barlow Condensed', 'source' => 'google', 'weights' => [700, 800], 'fallback' => 'sans-serif'],
|
||
],
|
||
['fontFamily' => 'body', 'headingFontFamily' => 'heading', 'fontSize' => '16px', 'lineHeight' => 1.6, 'color' => '@tinte', 'backgroundColor' => '@mint', 'linkColor' => '@gruen', 'containerWidth' => '1280px', 'headingScale' => 1.25, 'linkUnderline' => false, 'imageRadius' => '0px'],
|
||
['shape' => 'pill', 'appearance' => 'solid', 'size' => 'normal', 'fontFamily' => 'heading', 'fontSize' => 19, 'fontWeight' => 700, 'letterSpacing' => 1.5, 'textTransform' => 'uppercase', 'backgroundColor' => '@rot', 'color' => '@papier', 'hoverBackgroundColor' => '@rot-dunkel', 'hoverColor' => '@papier'],
|
||
);
|
||
echo "Farben, Schriften und Buttons gesetzt\n";
|
||
|
||
$medien = [];
|
||
foreach ([
|
||
'logo' => ['logo.png', 'Garten- und Landschaftsbau Hüfner GmbH'],
|
||
'favicon' => ['favicon.png', 'Baum aus dem Hüfner-Logo'],
|
||
'hero' => ['hero-pool-stufen.jpg', 'Pool mit Sitzstufen in Anthrazit und Keramikumrandung'],
|
||
'pool-stufen' => ['pool-stufen.jpg', 'Pool mit Sitzstufen und Keramikumrandung'],
|
||
'plattenweg' => ['plattenweg.jpg', 'Plattenweg im Rasen entlang eines Neubaus'],
|
||
'pool-fachwerk' => ['pool-fachwerk.jpg', 'Pool mit Natursteinterrasse am Fachwerkhaus'],
|
||
'terrasse' => ['terrasse.jpg', 'Keramikterrasse am Pool'],
|
||
'pflaster-verlegt' => ['pflaster-verlegt.jpg', 'Neu verlegtes Pflaster in Grau- und Brauntönen'],
|
||
'naturstein' => ['naturstein.jpg', 'Natursteinmauer mit Trittplatten im Rasen'],
|
||
'rasen' => ['rasen.jpg', 'Frisch verlegter Rollrasen'],
|
||
'pool-garten' => ['pool-garten.jpg', 'Pool mit Hortensien und Gartenanlage'],
|
||
'pool-terrasse' => ['pool-terrasse.jpg', 'Pool mit Natursteinterrasse und Gabionen'],
|
||
'sichtschutz' => ['sichtschutz.jpg', 'Moderner Sichtschutz aus Aluminium'],
|
||
'pflaster' => ['pflaster.jpg', 'Hanggarten mit Natursteinmauer und Treppe'],
|
||
'baustelle' => ['poolbau-baustelle.jpg', 'Poolbau im Rohbau mit Minibagger und Radlader im Hintergrund'],
|
||
'team' => ['team.jpg', 'Das Hüfner-Team in Arbeitsjacken mit Firmenlogo'],
|
||
// Aus Umuts Unterlagen vom 12.10.2026.
|
||
'alpaka-plakat' => ['alpaka-plakat.jpg', 'Plakat „100 % Alpakas live!“: zwei gezeichnete Alpakas mit Sonnenbrille im Hüfner-LKW – „Unsere Alpakas sind da und freuen sich auf Sie! Kommen Sie vorbei und lernen Sie sie kennen!“'],
|
||
'alpaka-stall' => ['alpaka-stall.jpg', 'Die beiden Alpakas, ein braunes und ein helles, im Eingang ihres Stalls'],
|
||
'alpaka-blume' => ['alpaka-blume.jpg', 'Braunes Alpaka mit einem Gänseblümchen im Maul'],
|
||
'alpaka-zaunbau' => ['alpaka-zaunbau.jpg', 'Die Alpakas auf der Wiese, im Hintergrund baut das Team den Holzzaun ihres Geheges'],
|
||
'alpaka-wiese' => ['alpaka-wiese.jpg', 'Ein Alpaka schaut über den Holzzaun auf die Wiese hinaus'],
|
||
'alpaka-rasen' => ['alpaka-rasen.jpg', 'Beide Alpakas auf dem Rasen vor ihrem Stall auf dem Firmengelände'],
|
||
'pq-logo' => ['pq-logo.png', 'PQ-VOB – präqualifiziertes Unternehmen'],
|
||
'galabau-signet' => ['galabau-signet.png', 'Signet „Ihre Experten für Garten & Landschaft“ der Landschaftsgärtner-Verbände'],
|
||
// Das 💯 als Bild, von Umut am 12.10.2026 (aus ChatGPT, transparent, auf 400 px zugeschnitten).
|
||
'hundert' => ['hundert.png', '100'],
|
||
] 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";
|
||
}
|
||
|
||
// Das „💯“ von Hüfners Instagram steht überall, wo „100“ steht – im Text, als <mark>100</mark>:
|
||
// Die Zahl bleibt Text (Vorleser, Suche, KI lesen „100“), sichtbar ist Umuts Emoji-Bild an ihrer
|
||
// Stelle, in Schriftgröße. Ein geschütztes Leerzeichen hält „💯 %“ zusammen. Die Regel steht am
|
||
// Abschnitt bzw. an der Überschrift, nicht an jedem Text.
|
||
$hundertCss = "& mark{display:inline-block;width:1.07em;height:1em;margin-right:.04em;vertical-align:-.12em;background:url(".end($medien['hundert']['variants'])['src'].") center/contain no-repeat;color:inherit;overflow:hidden;white-space:nowrap;text-indent:2em}";
|
||
|
||
// Die PDFs gehen in die Dokumentablage; ihr Titel ist der Dateiname beim Speichern.
|
||
$dokumente = new DocumentLibrary($db, $mediaDirectory);
|
||
$pdf = [];
|
||
foreach ([
|
||
'pq' => ['pq-bescheinigung.pdf', 'PQ-Bescheinigung Garten- und Landschaftsbau Hüfner GmbH'],
|
||
'f13b' => ['freistellung-13b.pdf', 'Freistellungsbescheinigung § 13b UStG Garten- und Landschaftsbau Hüfner GmbH'],
|
||
'f48b' => ['freistellung-48b.pdf', 'Freistellungsbescheinigung § 48b EStG Garten- und Landschaftsbau Hüfner GmbH'],
|
||
] as $schluessel => [$datei, $titel]) {
|
||
$pfad = rtrim($assets, '/\\').'/'.$datei;
|
||
if (!is_file($pfad)) {
|
||
fwrite(STDERR, "PDF fehlt: $pfad\n");
|
||
exit(1);
|
||
}
|
||
$pdf[$schluessel] = $dokumente->upload(new UploadedFile($pfad, $titel.'.pdf', 'application/pdf', null, true))['src'];
|
||
echo "PDF übernommen: $titel\n";
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// Formular
|
||
// ---------------------------------------------------------------------------------------------
|
||
|
||
$forms->save('anfrage', 'Projektanfrage', [
|
||
['name' => 'name', 'type' => 'text', 'label' => 'Name', 'required' => true, 'help' => 'Vor- und Nachname'],
|
||
['name' => 'telefon', 'type' => 'phone', 'label' => 'Telefon', 'required' => false, 'help' => 'Für Rückfragen'],
|
||
['name' => 'email', 'type' => 'email', 'label' => 'E-Mail', 'required' => true],
|
||
['name' => 'vorhaben', 'type' => 'textarea', 'label' => 'Ihr Vorhaben', 'required' => true, 'help' => 'Kurze Beschreibung, Größe der Fläche, Wunschtermin …'],
|
||
['name' => 'datenschutz', 'type' => 'consent', 'label' => 'Ich stimme der Verarbeitung meiner Angaben gemäß Datenschutzhinweisen zu.'],
|
||
], ['recipient' => $mail, 'subject' => 'Neue Projektanfrage über die Website', 'success' => 'Vielen Dank! Ihre Anfrage ist bei uns angekommen. Wir melden uns zeitnah und vereinbaren einen Termin bei Ihnen vor Ort.', 'replyToField' => 'email', 'buttonLabel' => 'Anfrage absenden'], 0);
|
||
echo "Formular angelegt: anfrage\n";
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// Kopfbereich: dunkle Leiste, darunter der weiße Kopf mit Menü, Profilen und Knopf
|
||
// ---------------------------------------------------------------------------------------------
|
||
|
||
$leistenLink = "& a{color:#fff;text-decoration:none;transition:color .2s}\n& a:hover{color:#fff;text-decoration:underline;text-underline-offset:3px}";
|
||
$leistenPunkt = static fn (string $id, string $icon, string $inhalt, string $sichtbar = 'all') => $zeile($id, [
|
||
$symbol($id.'-icon', $icon, 16, ['desktop' => ['color' => '#067f30']]),
|
||
$text($id.'-text', $inhalt, ['desktop' => ['fontSize' => '14px', 'lineHeight' => 1.4, 'color' => '#c5d6cb', 'margin' => ['bottom' => '0px']]], $leistenLink),
|
||
], '8px') + ('all' === $sichtbar ? [] : ['layout' => ['visibility' => $sichtbar]]);
|
||
|
||
$kopf = [
|
||
'schemaVersion' => $SCHEMA, 'title' => 'Kopfbereich', 'description' => 'Erscheint auf jeder Seite über dem Inhalt.',
|
||
'blocks' => [
|
||
$section('kopf-leiste', [
|
||
$zeile('kopf-leiste-zeile', [
|
||
$zeile('kopf-leiste-kontakt', [
|
||
$leistenPunkt('kopf-telefon', 'phone', '<a href="'.$tel.'">'.$telefon.'</a>'),
|
||
$leistenPunkt('kopf-mail', 'mail', '<a href="mailto:'.$mail.'">'.$mail.'</a>', 'hide-mobile'),
|
||
$leistenPunkt('kopf-adresse', 'pin', $strasse.', '.$plz.' '.$ort, 'desktop'),
|
||
], '28px', ['flexWrap' => 'wrap', 'rowGap' => '8px']),
|
||
['id' => 'kopf-leiste-profile', 'type' => 'social', 'props' => ['size' => 16, 'shape' => 'plain', 'layout' => 'horizontal', 'labels' => true, 'networks' => ['instagram', 'facebook']],
|
||
'style' => ['desktop' => ['fontSize' => '15px', 'color' => '#c5d6cb', 'gap' => '24px']],
|
||
'css' => "& a{font-weight:400}\n& a:hover{color:#fff;transform:none}",
|
||
'layout' => ['visibility' => 'hide-mobile']],
|
||
], '28px', ['justifyContent' => 'space-between']),
|
||
], [
|
||
'desktop' => ['backgroundColor' => '@nacht', 'color' => '#c5d6cb', 'padding' => ['top' => '9px', 'bottom' => '9px']],
|
||
'mobile' => ['padding' => ['top' => '7px', 'bottom' => '7px']],
|
||
]),
|
||
$section('kopf-haupt', [
|
||
$zeile('kopf-haupt-zeile', [
|
||
$bild('kopf-logo', $medien['logo'], $kurz.' – zur Startseite', [
|
||
// Feste Breite: sonst bekommt das Logo seine Breite erst nach dem Laden (CLS).
|
||
'desktop' => ['width' => '153px', 'maxWidth' => '153px', 'flexShrink' => 0, 'margin' => $seiten('0px')],
|
||
'mobile' => ['width' => '121px', 'maxWidth' => '121px'],
|
||
], '/', '& img{display:block;width:100%;height:auto;border-radius:0}'),
|
||
$column('kopf-navigation', [
|
||
['id' => 'kopf-menue', 'type' => 'menu', 'props' => ['slot' => 'header', 'layout' => 'horizontal', 'collapse' => 'tablet', 'burgerLabel' => 'icon'],
|
||
'style' => ['desktop' => [
|
||
'fontFamily' => 'heading', 'fontSize' => '18px', 'fontWeight' => '700', 'letterSpacing' => '0.06em', 'textTransform' => 'uppercase', 'color' => '@tinte',
|
||
'burgerColor' => '@papier', 'burgerBackgroundColor' => '@tinte', 'burgerBorderColor' => '@tinte', 'burgerBorderRadius' => '999px',
|
||
]],
|
||
// Unterstrich, der beim Überfahren von links einläuft. Das Klappmenü auf
|
||
// Tablet und Telefon liegt als Fläche unter dem ganzen Kopf.
|
||
'css' => "& .menu-list{gap:4px 34px;justify-content:center;align-items:center}\n"
|
||
."& .menu-list a{position:relative;padding:6px 0;text-decoration:none;color:#17221b}\n"
|
||
."& .menu-list a::after{content:'';position:absolute;left:0;right:100%;bottom:0;height:2px;background:#067f30;transition:right .25s ease}\n"
|
||
."& .menu-list a:hover::after{right:0}\n"
|
||
."& .menu-burger summary{width:46px;height:46px;display:flex;align-items:center;justify-content:center;padding:0}\n"
|
||
."& .menu-burger-body{position:absolute;left:0;right:0;top:100%;background:#fff;padding:4px 20px 20px;border-top:1px solid #d4e8db;box-shadow:0 18px 30px rgba(20,32,26,.12);font-size:22px}\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 #e0efe5}\n"
|
||
.'& .menu-burger-body .menu-list a::after{display:none}'],
|
||
], ['desktop' => ['flexGrow' => 1, 'display' => 'flex', 'justifyContent' => 'center'], 'tablet' => ['justifyContent' => 'flex-end', 'order' => 3, 'flexGrow' => 0], 'mobile' => ['justifyContent' => 'flex-end', 'order' => 3, 'flexGrow' => 0]]),
|
||
$zeile('kopf-aktionen', [
|
||
['id' => 'kopf-profile', 'type' => 'social', 'props' => ['size' => 20, 'shape' => 'plain', 'layout' => 'horizontal', 'labels' => false, 'networks' => ['instagram', 'facebook']],
|
||
'style' => ['desktop' => ['color' => '@tinte', 'gap' => '10px']],
|
||
'css' => "& a{width:46px;height:46px;justify-content:center;border-radius:50%;box-shadow:inset 0 0 0 1px #cae3d3}\n& a:hover{background:#e9f5ed;color:#067f30}",
|
||
'layout' => ['visibility' => 'hide-mobile']],
|
||
$knopf('kopf-knopf', 'Angebot anfragen', '/#kontakt', 'rot', true, [], 'desktop'),
|
||
], '10px', ['flexShrink' => 0], ['margin' => ['left' => 'auto']], ['margin' => ['left' => 'auto']]),
|
||
], '24px', ['justifyContent' => 'space-between', 'minHeight' => '82px'], ['minHeight' => '66px', 'gap' => '12px']),
|
||
], [
|
||
'desktop' => ['backgroundColor' => '#fffffff5', 'padding' => ['top' => '0px', 'bottom' => '0px'], 'borderWidth' => $nurUnten('1px'), 'borderStyle' => 'solid', 'borderColor' => '@linie'],
|
||
], null, 'box-shadow:0 8px 24px rgba(20,32,26,.06)'),
|
||
],
|
||
];
|
||
|
||
// Der rote Knopf im Kopf ist kleiner als die im Inhalt.
|
||
$kleinerKnopf = static fn (array $b): array => [...$b, 'css' => str_replace('min-height:58px;padding:0 30px', 'min-height:50px;padding:0 22px;font-size:16px;white-space:nowrap', $b['css'])];
|
||
$kopf['blocks'][1]['children'][0]['children'][2]['children'][1]['css'] = str_replace('min-height:58px;padding:0 30px', 'min-height:46px;padding:0 24px;font-size:17px', $kopf['blocks'][1]['children'][0]['children'][2]['children'][1]['css']);
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// Fußbereich
|
||
// ---------------------------------------------------------------------------------------------
|
||
|
||
$fussLink = "& a{color:#b5c7bb;text-decoration:none;transition:color .2s}\n& a:hover{color:#fff}";
|
||
$fussTitel = static fn (string $id, string $inhalt) => $text($id, $inhalt, ['desktop' => [
|
||
'fontFamily' => 'heading', 'fontSize' => '17px', 'fontWeight' => '700', 'letterSpacing' => '0.14em', 'textTransform' => 'uppercase', 'color' => '@papier', 'margin' => ['bottom' => '16px'],
|
||
]]);
|
||
$fussListe = static fn (string $id, array $links) => $text($id, implode('<br>', array_map(static fn (array $l): string => '<a href="'.$l[1].'">'.$l[0].'</a>', $links)), [
|
||
'desktop' => ['fontSize' => '15px', 'lineHeight' => 2.1, 'color' => '@auf-dunkel', 'margin' => ['bottom' => '0px']],
|
||
], $fussLink);
|
||
|
||
$fuss = [
|
||
'schemaVersion' => $SCHEMA, 'title' => 'Fußbereich', 'description' => 'Erscheint auf jeder Seite unter dem Inhalt.',
|
||
'blocks' => [
|
||
$section('fuss', [
|
||
$column('fuss-raster', [
|
||
$column('fuss-marke', [
|
||
$column('fuss-logo-flaeche', [
|
||
$bild('fuss-logo', $medien['logo'], $firma, ['desktop' => ['width' => '137px', 'maxWidth' => '137px', 'margin' => $seiten('0px')]], null, '& img{display:block;width:100%;height:auto;border-radius:0}'),
|
||
], ['desktop' => ['display' => 'inline-block', 'backgroundColor' => '@papier', 'borderRadius' => $rund('12px'), 'padding' => ['top' => '12px', 'right' => '16px', 'bottom' => '12px', 'left' => '16px']]]),
|
||
$text('fuss-spruch', 'Garten- und Landschaftsbau auf Premium-Niveau – aus Iffezheim für die Region.', ['desktop' => ['maxWidth' => '300px', 'fontSize' => '15px', 'lineHeight' => 1.65, 'color' => '@auf-dunkel', 'margin' => ['top' => '20px', 'bottom' => '20px']]]),
|
||
['id' => 'fuss-profile', 'type' => 'social', 'props' => ['size' => 20, 'shape' => 'plain', 'layout' => 'horizontal', 'labels' => false, 'networks' => ['instagram', 'facebook']],
|
||
'style' => ['desktop' => ['color' => '@auf-dunkel', 'gap' => '10px']],
|
||
'css' => "& a{width:44px;height:44px;justify-content:center;border-radius:50%;box-shadow:inset 0 0 0 1px #2c3a31}\n& a:hover{color:#fff;box-shadow:inset 0 0 0 1px #067f30}"],
|
||
], ['desktop' => ['display' => 'flex', 'flexDirection' => 'column', 'alignItems' => 'flex-start']]),
|
||
$column('fuss-leistungen', [
|
||
$fussTitel('fuss-leistungen-titel', 'Leistungen'),
|
||
$fussListe('fuss-leistungen-liste', [['Pools', '/#leistungen'], ['Terrassen', '/#leistungen'], ['Pflaster', '/#leistungen'], ['Natursteine', '/#leistungen'], ['Außenanlagen', '/#leistungen']]),
|
||
]),
|
||
$column('fuss-unternehmen', [
|
||
$fussTitel('fuss-unternehmen-titel', 'Unternehmen'),
|
||
$fussListe('fuss-unternehmen-liste', [['Über uns', '/#ueber-uns'], ['Referenzen', '/#projekte'], ['Bewertungen', '/#bewertungen'], ['Karriere', '/#karriere'], ['Unsere Alpakas', '/alpakas'], ['Kontakt', '/#kontakt']]),
|
||
]),
|
||
// Umut, 12.10.2026: PQ-Bescheinigung und die Freistellungsbescheinigungen auch im Fuß.
|
||
$column('fuss-nachweise', [
|
||
$fussTitel('fuss-nachweise-titel', 'Nachweise'),
|
||
$fussListe('fuss-nachweise-liste', [['PQ-Bescheinigung (PDF)', $pdf['pq']], ['Freistellung § 13b UStG (PDF)', $pdf['f13b']], ['Freistellung § 48b EStG (PDF)', $pdf['f48b']]]),
|
||
$karte('fuss-siegel', [
|
||
$bild('fuss-signet', $medien['galabau-signet'], 'Ihre Experten für Garten & Landschaft – Signet der Landschaftsgärtner-Verbände', ['desktop' => ['width' => '46px', 'maxWidth' => '46px', 'flexShrink' => 0, 'margin' => $seiten('0px')]], null, '& img{display:block;width:100%;height:auto;border-radius:0}'),
|
||
$bild('fuss-pq', $medien['pq-logo'], 'PQ-VOB – präqualifiziertes Unternehmen', ['desktop' => ['width' => '52px', 'maxWidth' => '52px', 'flexShrink' => 0, 'margin' => $seiten('0px')]], null, '& img{display:block;width:100%;height:auto;border-radius:0}'),
|
||
], '12px', ['margin' => ['top' => '18px'], 'backgroundColor' => '@papier', 'borderRadius' => $rund('12px'), 'padding' => ['top' => '10px', 'right' => '14px', 'bottom' => '10px', 'left' => '14px']], [], [], 'display:inline-flex'),
|
||
]),
|
||
$column('fuss-kontakt', [
|
||
$fussTitel('fuss-kontakt-titel', 'Kontakt'),
|
||
$text('fuss-kontakt-text', 'Garten- und Landschaftsbau<br>Hüfner GmbH<br>'.$strasse.'<br>'.$plz.' '.$ort.'<br><br><a href="'.$tel.'">'.$telefon.'</a><br><a href="mailto:'.$mail.'">'.$mail.'</a>', [
|
||
'desktop' => ['fontSize' => '15px', 'lineHeight' => 1.7, 'color' => '@auf-dunkel', 'margin' => ['bottom' => '0px']],
|
||
], $fussLink."\n& a{overflow-wrap:anywhere}"),
|
||
]),
|
||
], [
|
||
'desktop' => ['display' => 'grid', 'gridColumns' => 5, 'gap' => '40px', 'padding' => ['top' => '80px', 'bottom' => '48px']],
|
||
'tablet' => ['gridColumns' => 2],
|
||
'mobile' => ['gridColumns' => 1, 'gap' => '36px', 'padding' => ['top' => '56px', 'bottom' => '36px']],
|
||
]),
|
||
$zeile('fuss-unten', [
|
||
$text('fuss-copyright', '© 2026 '.$firma, ['desktop' => ['fontSize' => '14px', 'color' => '@auf-dunkel', 'margin' => ['bottom' => '0px']]]),
|
||
$zeile('fuss-links', [
|
||
['id' => 'fuss-rechtliches', 'type' => 'legal', 'props' => ['label' => 'Rechtliches'],
|
||
'style' => ['desktop' => ['fontSize' => '14px', 'gap' => '24px', 'color' => '@auf-dunkel']],
|
||
'css' => $fussLink],
|
||
$text('fuss-agentur', '<a href="https://kant-it.de">Website by Kant-IT Solutions</a>', ['desktop' => ['fontSize' => '14px', 'margin' => ['bottom' => '0px']]], $fussLink),
|
||
], '24px', ['flexWrap' => 'wrap'], ['rowGap' => '10px', 'columnGap' => '18px']),
|
||
], '24px', ['justifyContent' => 'space-between', 'flexWrap' => 'wrap', 'rowGap' => '12px', 'borderWidth' => $nurOben('1px'), 'borderStyle' => 'solid', 'borderColor' => '#223027', 'padding' => ['top' => '24px', 'bottom' => '36px']],
|
||
['flexDirection' => 'column', 'alignItems' => 'flex-start']),
|
||
], [
|
||
'desktop' => ['backgroundColor' => '@fuss', 'color' => '@auf-dunkel', '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
|
||
// ---------------------------------------------------------------------------------------------
|
||
|
||
/**
|
||
* Eine Bildkachel: Foto über die ganze Fläche, dunkler Verlauf von unten, Text darauf. Beim
|
||
* Überfahren zoomt nur das Foto, die Kachel bekommt Schatten. Mit $href ist das Foto der Link
|
||
* (sein Alternativtext ist dann der Name des Links), der Text darüber lässt Klicks durch.
|
||
*/
|
||
$kachel = static function (string $id, array $foto, string $alt, array $inhalt, string $innen, ?string $href = null, array $hoehe = [], string $verlauf = '', bool $mehr = false) use ($column, $bild, $seiten, $rund): array {
|
||
return $column($id, [
|
||
// Foto, Verlauf und Fokusrahmen stehen einmal im CSS des Abschnitts ($kachelFlaeche),
|
||
// nicht in jeder Kachel: sonst wird die Seite über das HTML-Budget von 150 KB schwer.
|
||
$bild($id.'-foto', $foto, $alt, [], $href),
|
||
...$inhalt,
|
||
], [
|
||
'desktop' => ['position' => 'relative', 'overflow' => 'hidden', 'display' => 'flex', 'flexDirection' => 'column', 'justifyContent' => 'flex-end', 'backgroundColor' => '#2a372f', 'color' => '@papier',
|
||
'borderRadius' => $rund('18px'), 'padding' => $seiten($innen), ...($hoehe['desktop'] ?? [])],
|
||
...array_diff_key($hoehe, ['desktop' => true]),
|
||
], $mehr ? "& .block-row{position:relative;pointer-events:none;transition:gap .25s ease}\n&:hover .block-row{gap:14px}" : '');
|
||
};
|
||
|
||
/** Was alle Kacheln eines Abschnitts teilen; die Kacheln sind dort die einzigen Bilder. */
|
||
$kachelFlaeche = "& .block-column > .block-image{position:absolute;top:0;right:0;bottom:0;left:0;margin:0;z-index:0}\n"
|
||
."& .block-column:hover > .block-image img{transform:scale(1.06)}\n"
|
||
."& .block-column:focus-within > .block-image img{transform:scale(1.06)}\n"
|
||
."& .block-image img{width:100%;height:100%;object-fit:cover;border-radius:0;transition:transform .7s cubic-bezier(.2,.7,.2,1)}\n"
|
||
."& .image-link{height:100%}\n"
|
||
."& .image-link:focus-visible{outline:3px solid #fff;outline-offset:-8px}\n"
|
||
."& .block-image::after{content:'';position:absolute;top:0;right:0;bottom:0;left:0;background:linear-gradient(0deg,rgba(10,18,13,.88) 0%,rgba(10,18,13,0) 58%);pointer-events:none}\n"
|
||
."& .block-image+h3{position:relative;pointer-events:none}\n"
|
||
."& .block-image~.block-text{position:relative;pointer-events:none}\n"
|
||
.'& .block-image~h3{position:relative;pointer-events:none}';
|
||
|
||
// Die kleinen Kacheln (Spalte in Spalte) bekommen Titel, Satz und Kleinzeile vom Abschnitt.
|
||
$kleineKachel = static fn (string $titel, string $text) => "\n& .block-column > .block-column > .block-image~h3{".$titel."}\n& .block-column > .block-column > .block-image~.block-text{".$text.'}';
|
||
$kachelTitel = static fn (string $id, string $titel, string $groesse = '30px') => $heading($id, $titel, 3, [
|
||
'desktop' => ['fontSize' => $groesse, 'fontWeight' => '800', 'lineHeight' => 1.0, 'letterSpacing' => '0px', 'textTransform' => 'uppercase', 'color' => '@papier', 'margin' => ['top' => '4px', 'bottom' => '6px']],
|
||
'mobile' => ['fontSize' => '30px'],
|
||
]);
|
||
$kachelSatz = static fn (string $id, string $satz, string $groesse = '15px', string $unten = '0px', string $breite = 'none') => $text($id, $satz, ['desktop' => ['fontSize' => $groesse, 'lineHeight' => 1.5, 'color' => '#d8e6dd', 'maxWidth' => $breite, 'margin' => ['bottom' => $unten]]]);
|
||
$kachelKlein = static fn (string $id, string $klein, string $groesse = '13px') => $text($id, $klein, ['desktop' => ['fontSize' => $groesse, 'fontWeight' => '600', 'letterSpacing' => '0.06em', 'textTransform' => 'uppercase', 'color' => '@papier', 'margin' => ['bottom' => '2px']]]);
|
||
|
||
/** Ein Vorteil im Band unter dem Hero; die Trennlinien hängen am Platz im Raster. */
|
||
$vorteil = static function (int $n, array $icon, string $titel, string $satz) use ($zeile, $text): array {
|
||
$rahmen = static fn (bool $oben, bool $links): array => ['borderWidth' => ['top' => $oben ? '1px' : '0px', 'right' => '0px', 'bottom' => '0px', 'left' => $links ? '1px' : '0px'], 'borderStyle' => 'solid', 'borderColor' => '#ffffff24'];
|
||
// Desktop: vier nebeneinander, Linie links ab dem zweiten. Darunter zwei mal zwei.
|
||
$zwei = $rahmen($n > 2, 0 === $n % 2);
|
||
$mobil = ['flexDirection' => 'column', 'alignItems' => 'flex-start', 'gap' => '8px', 'padding' => ['top' => '16px', 'right' => '16px', 'bottom' => '16px', 'left' => '16px'], ...$zwei];
|
||
|
||
return $zeile('vorteil-'.$n, [
|
||
$icon,
|
||
$text('vorteil-'.$n.'-text', '<strong>'.$titel.'</strong>'.$satz),
|
||
], '14px', ['padding' => ['top' => '24px', 'right' => '32px', 'bottom' => '24px', 'left' => '32px'], ...$rahmen(false, $n > 1)], $mobil, $zwei);
|
||
};
|
||
|
||
$vorteilIcon = static fn (int $n, string $name) => $symbol('vorteil-'.$n.'-icon', $name, 32);
|
||
// Schrift und Symbol der Vorteile einmal am Raster; 14 px, auf dem Telefon bis 12,5 px.
|
||
$vorteilCss = "& .block-icon{color:#067f30;flex-shrink:0}\n& .block-text{font-size:clamp(12.5px,3.4vw,14px);line-height:1.45;color:#b5c7bb;margin-bottom:0}\n& strong{display:block;margin-bottom:2px;font-size:16px;color:#fff}";
|
||
// Seit 1.25.0 hat der Symbolsatz einen LKW; vorher stand er hier als „Eigenes HTML“.
|
||
$lkw = $vorteilIcon(3, 'truck');
|
||
|
||
// Ab hier tragen wiederkehrende Bausteine (Häkchen, Chips, Schritte, Geräte, Kontaktkarten,
|
||
// Instagram-Bilder) keine eigene Gestaltung mehr: sie steht einmal am Behälter (…Css). Sonst
|
||
// reißt die Startseite das HTML-Budget von 150 KB aus test:browser (12.10.2026: 176 KB).
|
||
$haken = static fn (string $id, string $inhalt) => $row($id, [
|
||
$symbol($id.'-icon', 'check', 24),
|
||
$text($id.'-text', $inhalt),
|
||
]);
|
||
$hakenCss = "& .block-row{display:flex;align-items:center;gap:10px;margin-top:0}\n& .block-icon{color:#067f30;flex-shrink:0}\n& .block-text{font-size:16px;font-weight:600;line-height:1.4;color:#17221b;margin-bottom:0}";
|
||
|
||
// Seit 12.10.2026 führt jeder Chip wie die Kacheln zum Anfrageformular (Umut: „Verlinken zu
|
||
// Kontaktformular“). Der Link spannt den ganzen Chip auf.
|
||
$chip = static fn (string $id, string $inhalt) => $text($id, '<a href="#kontakt">'.$inhalt.'</a>');
|
||
// Der erste Text der Zeile ist „Außerdem“, alle weiteren sind Chips.
|
||
$chipCss = "& .block-text~.block-text{position:relative;font-size:15px;font-weight:600;line-height:1.3;color:#17221b;background-color:#e9f5ed;border-radius:999px;padding:10px 18px;margin-bottom:0;transition:background-color .2s ease,color .2s ease}
|
||
& .block-text~.block-text a{color:inherit;text-decoration:none}
|
||
& .block-text~.block-text a::after{content:'';position:absolute;top:0;right:0;bottom:0;left:0;border-radius:inherit}
|
||
& .block-text~.block-text a:focus-visible{outline:none}
|
||
& .block-text~.block-text:hover{background-color:#067f30;color:#fff}
|
||
& .block-text~.block-text:focus-within{outline:3px solid #067f30;outline-offset:3px;background-color:#067f30;color:#fff}";
|
||
|
||
$schritt = static fn (int $n, string $titel, string $satz, array $extra = []) => $column('schritt-'.$n, [
|
||
$text('schritt-'.$n.'-nummer', sprintf('%02d', $n)),
|
||
$heading('schritt-'.$n.'-titel', $titel, 3),
|
||
$text('schritt-'.$n.'-satz', $satz),
|
||
...$extra,
|
||
]);
|
||
// Nummer im Kreis, der letzte Schritt rot; die Downloads unter Schritt 2 sind der dritte Text.
|
||
$schrittCss = "& > .block-column{text-align:center}
|
||
& > .block-column > .block-text:first-child{position:relative;z-index:1;display:flex;align-items:center;justify-content:center;width:72px;height:72px;margin:0 auto 24px;border-radius:50%;background:#067f30;color:#fff;font-family:var(--kf-heading);font-size:28px;font-weight:800;line-height:1;box-shadow:0 0 0 10px #eff8f2;transition:transform .3s cubic-bezier(.2,.7,.2,1)}
|
||
& > .block-column:last-child > .block-text:first-child{background:#b71c22}
|
||
& > .block-column:hover > .block-text:first-child{transform:scale(1.08)}
|
||
& h3{margin:0 0 10px;font-size:26px;font-weight:800;line-height:1;letter-spacing:0;text-transform:uppercase;color:#17221b}
|
||
& h3+.block-text{max-width:260px;margin:0 auto;font-size:16px;line-height:1.6;color:#55635a}
|
||
& h3+.block-text strong{color:#17221b}";
|
||
|
||
$geraet = static fn (string $id, string $titel, string $satz) => $text($id, '<strong>'.$titel.'</strong>'.$satz);
|
||
$geraetCss = "& .block-text{font-size:14px;line-height:1.5;color:#55635a;background:#eff8f2;border-radius:12px;padding:18px;margin-bottom:0}\n& strong{display:block;font-size:16px;color:#17221b;hyphens:auto;overflow-wrap:break-word}";
|
||
|
||
// Zahl und Satz der Kennzahlen stehen einmal am Raster ($kennzahlCss).
|
||
$kennzahlCss = "column-gap:24px\n& .block-text:first-child{font-family:var(--kf-heading);font-size:clamp(40px,10vw,52px);font-weight:800;line-height:1;color:#067f30;margin-bottom:6px}\n& .block-text+.block-text{font-size:15px;line-height:1.4;color:#55635a;margin-bottom:0}";
|
||
$kennzahl = static fn (string $id, string $zahl, string $satz, bool $linie, string $css = '') => $column($id, [
|
||
$text($id.'-zahl', $zahl, [], $css),
|
||
$text($id.'-satz', $satz),
|
||
], $linie ? ['desktop' => ['borderWidth' => $nurLinks('1px'), 'borderStyle' => 'solid', 'borderColor' => '@linie', 'padding' => ['left' => '24px']], 'mobile' => ['padding' => ['left' => '14px']]] : []);
|
||
|
||
/**
|
||
* Die zwei Stellen als schlichte Karten: eine ruhige Zeichnung, der Titel, ein Klick klappt die
|
||
* Beschreibung auf (details/summary – Tastatur und Vorleser kennen das, kein Skript). Umut,
|
||
* 12.10.2026: „Zwei Bilder … wenn man drauf klickt soll Beschreibung kommen … viel einfacher,
|
||
* leerer.“ Die Zeichnungen sind SVG im eigenen HTML; kommt ein Bild aus ChatGPT, ersetzt es das
|
||
* <svg> in der jeweiligen Karte.
|
||
*/
|
||
$stellenKarte = static function (string $id, string $titel, string $zeile, string $zeichnung, array $punkte, string $betreff) use ($whatsapp, $tel, $telefon): string {
|
||
$nachricht = rawurlencode('Hallo Hüfner-Team, ich bewerbe mich als '.$betreff.'.');
|
||
|
||
return '<details class="stelle" id="'.$id.'"><summary>'
|
||
.'<span class="stelle-bild" aria-hidden="true">'.$zeichnung.'</span>'
|
||
.'<span class="stelle-kopf"><span class="stelle-titel">'.$titel.'</span><span class="stelle-zeile">'.$zeile.'</span></span>'
|
||
.'<span class="stelle-mehr" aria-hidden="true"></span>'
|
||
.'</summary><div class="stelle-inhalt"><ul>'
|
||
.implode('', array_map(static fn (string $p): string => '<li>'.$p.'</li>', $punkte))
|
||
.'</ul><p class="stelle-wege"><a href="'.$whatsapp.'?text='.$nachricht.'">Per WhatsApp bewerben</a> <a href="'.$tel.'">Anrufen: '.$telefon.'</a></p></div></details>';
|
||
};
|
||
$helm = '<svg viewBox="0 0 240 150" width="240" height="150"><circle cx="120" cy="80" r="62" fill="#e9f5ed"/><path d="M68 104a52 52 0 0 1 104 0Z" fill="#067f30"/><rect x="113" y="54" width="14" height="48" rx="5" fill="#fff" fill-opacity=".28"/><rect x="52" y="102" width="136" height="13" rx="6.5" fill="#05622a"/></svg>';
|
||
$lkwBild = '<svg viewBox="0 0 240 150" width="240" height="150"><circle cx="120" cy="80" r="62" fill="#e9f5ed"/><path d="M54 70h92v34H54z" fill="#05622a"/><path d="M148 62h30l18 22v22h-48z" fill="#067f30"/><path d="M158 70h18l12 14h-30z" fill="#e9f5ed"/><rect x="50" y="104" width="150" height="8" rx="3" fill="#17221b"/><circle cx="82" cy="116" r="12" fill="#17221b"/><circle cx="172" cy="116" r="12" fill="#17221b"/></svg>';
|
||
|
||
// Eigenes CSS kennt kein @media: zwei Karten nebeneinander, sobald je 220 px Platz sind. Zu sind
|
||
// beide gleich hoch; eine offene Karte nimmt die ganze Zeile, damit die andere nicht mitwächst.
|
||
$stellenCss = "& .stellen{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px}
|
||
& .stelle[open]{grid-column:1/-1}
|
||
& .stelle{background:#fff;border-radius:18px;color:#17221b;box-shadow:0 14px 30px rgba(10,30,18,.18);overflow:hidden}
|
||
& .stelle summary{display:grid;grid-template-columns:1fr auto;align-items:center;gap:4px 12px;padding:0 20px 18px;cursor:pointer;list-style:none}
|
||
& .stelle summary::-webkit-details-marker{display:none}
|
||
& .stelle summary:focus-visible{outline:3px solid #fff;outline-offset:-3px;border-radius:18px}
|
||
& .stelle-bild{grid-column:1/-1;display:block;margin:0 -20px 6px;background:#f6fbf8}
|
||
& .stelle-bild svg{display:block;width:100%;height:auto;max-height:150px}
|
||
& .stelle-titel{display:block;font-family:var(--kf-heading);font-size:24px;font-weight:800;line-height:1.05;text-transform:uppercase}
|
||
& .stelle-zeile{display:block;margin-top:4px;font-size:14px;color:#55635a}
|
||
& .stelle-mehr{width:36px;height:36px;border-radius:50%;background:#067f30;position:relative;flex-shrink:0}
|
||
& .stelle-mehr::before{content:'';position:absolute;left:11px;right:11px;top:17px;height:2px;background:#fff}
|
||
& .stelle-mehr::after{content:'';position:absolute;top:11px;bottom:11px;left:17px;width:2px;background:#fff;transition:opacity .2s ease}
|
||
& .stelle[open] .stelle-mehr::after{opacity:0}
|
||
& .stelle-inhalt{padding:0 20px 20px;font-size:15px;line-height:1.6;color:#3a483f}
|
||
& .stelle-inhalt ul{margin:0 0 14px;padding-left:20px}
|
||
& .stelle-wege{display:flex;flex-wrap:wrap;gap:8px;margin:0}
|
||
& .stelle-wege a{display:inline-flex;align-items:center;min-height:44px;padding:0 16px;border-radius:999px;background:#067f30;color:#fff;font-weight:700;font-size:14px;text-decoration:none}
|
||
& .stelle-wege a+a{background:#e9f5ed;color:#17221b}
|
||
& .stelle-wege a:hover{background:#05622a;color:#fff}";
|
||
|
||
$kontaktKarte = static fn (string $id, string $icon, string $klein, string $wert, ?string $href) => $column($id, [
|
||
['id' => $id.'-kreis', 'type' => 'column', 'children' => [$symbol($id.'-kreis-icon', $icon, 20)]],
|
||
$text($id.'-text', $klein.'<br><strong>'.(null === $href ? $wert : '<a href="'.$href.'">'.$wert.'</a>').'</strong>'),
|
||
]);
|
||
// Karten mit Link heben sich beim Überfahren; der Link spannt die ganze Karte auf.
|
||
$kontaktCss = "& > .block-column{position:relative;display:flex;align-items:center;gap:16px;margin-top:0;background:#fff;border-radius:14px;padding:16px 18px;box-shadow:0 6px 18px rgba(20,32,24,.05);transition:transform .2s ease,box-shadow .2s ease}
|
||
& > .block-column > .block-column{display:flex;align-items:center;justify-content:center;width:48px;height:48px;flex-shrink:0;margin:0;background:#067f30;border-radius:50%;color:#fff}
|
||
& .block-icon{color:#fff}
|
||
& .block-text{font-size:13px;line-height:1.5;color:#6a766c;min-width:0;margin-bottom:0}
|
||
& strong{display:block;font-size:18px;color:#17221b;overflow-wrap:anywhere}
|
||
& a{color:inherit;text-decoration:none}
|
||
& a::after{content:'';position:absolute;top:0;right:0;bottom:0;left:0;border-radius:inherit}
|
||
& a:focus-visible{outline:none}
|
||
& > .block-column:focus-within{outline:3px solid #067f30;outline-offset:3px}
|
||
& > .block-column:hover{box-shadow:0 14px 30px rgba(20,32,26,.1)}";
|
||
|
||
$instaBild = static fn (string $id, array $foto, string $was) => $bild($id, $foto, $was.' – Beiträge auf Instagram ansehen', [], $instagram);
|
||
$instaCss = "& .block-image{margin:0;border-radius:12px;overflow:hidden}\n& img{width:100%;aspect-ratio:1;object-fit:cover;border-radius:0;transition:transform .5s cubic-bezier(.2,.7,.2,1)}\n& .block-image:hover img{transform:scale(1.05)}\n& .image-link:focus-visible{outline:3px solid #067f30;outline-offset:-6px}";
|
||
|
||
// Formular im Zuschnitt der Vorlage: volle Breite statt der 640 px des Kerns, Name und Telefon
|
||
// nebeneinander, Felder hellgrün auf weiß.
|
||
$formularCss = '& .block-form{max-width:none;grid-template-columns:repeat(2,minmax(0,1fr));column-gap:20px;row-gap:20px}
|
||
& .block-form>.form-field{grid-column:1/-1}
|
||
& .block-form>.cms-button{grid-column:1/-1}
|
||
& .block-form>.form-error-summary{grid-column:1/-1}
|
||
& .block-form>.form-success{grid-column:1/-1}
|
||
& .block-form>.form-missing{grid-column:1/-1}
|
||
& .block-form>[data-label="Name"]{grid-column:span 1}
|
||
& .block-form>[data-label="Telefon"]{grid-column:span 1}
|
||
& .form-field{align-content:start}
|
||
& .form-field label{font-size:14px;font-weight:700;letter-spacing:0.02em;color:#17221b}
|
||
& .form-field input{background:#f6fbf8;border:1px solid #c5e0ce;border-radius:10px;padding:14px 16px;font-size:16px;color:#17221b}
|
||
& .form-field textarea{background:#f6fbf8;border:1px solid #c5e0ce;border-radius:10px;padding:14px 16px;font-size:16px;color:#17221b;min-height:140px}
|
||
& .form-field input:focus{border-color:#067f30}
|
||
& .form-field textarea:focus{border-color:#067f30}
|
||
& .field-note{font-size:13px;color:#55635a;opacity:1}
|
||
& .check-row label{font-weight:400;font-size:14px;line-height:1.6;color:#55635a}
|
||
& .check-row input{accent-color:#067f30}
|
||
& .cms-button{width:100%;justify-content:center;min-height:58px;transition:transform .2s ease,box-shadow .2s ease,background-color .2s ease}
|
||
& .cms-button:hover{transform:translateY(-2px);box-shadow:0 10px 24px rgba(0,0,0,.18)}
|
||
& .cms-button .button-arrow{display:none}';
|
||
|
||
$abschnitt = static fn (string $farbe = '', array $extra = []) => [
|
||
'desktop' => [...('' === $farbe ? [] : ['backgroundColor' => $farbe]), 'padding' => ['top' => '128px', 'bottom' => '128px'], ...($extra['desktop'] ?? [])],
|
||
'tablet' => ['padding' => ['top' => '80px', 'bottom' => '80px'], ...($extra['tablet'] ?? [])],
|
||
'mobile' => ['padding' => ['top' => '64px', 'bottom' => '64px'], ...($extra['mobile'] ?? [])],
|
||
];
|
||
|
||
$startseite = [
|
||
'schemaVersion' => $SCHEMA,
|
||
'title' => 'Gärten auf Premium-Niveau',
|
||
'description' => 'Garten- und Landschaftsbau auf Premium-Niveau aus Iffezheim: Terrassen, Pools, Pflaster, Natursteine und komplette Außenanlagen – geplant, gebaut und übergeben aus einer Hand.',
|
||
'blocks' => [
|
||
// 1 – Hero: Foto über die ganze Fläche, Verlauf von links und von unten, Text unten links,
|
||
// rechts die Karte „Direkter Draht“, darunter das Band mit vier Vorteilen.
|
||
$section('hero', [
|
||
$zeile('hero-zeile', [
|
||
$column('hero-text', [
|
||
$heading('hero-titel', 'Gärten auf<br><em>Premium-</em>Niveau.', 1, [
|
||
'desktop' => ['fontSize' => '104px', 'fontWeight' => '800', 'lineHeight' => 0.92, 'letterSpacing' => '-0.01em', 'textTransform' => 'uppercase', 'color' => '@papier', 'margin' => ['bottom' => '0px']],
|
||
'tablet' => ['fontSize' => '72px'],
|
||
'mobile' => ['fontSize' => '50px'],
|
||
], $em),
|
||
$text('hero-intro', 'Terrassen, Pools, Pflaster, Natursteine und komplette Außenanlagen – geplant, gebaut und übergeben aus einer Hand. Mit <mark>100</mark> % Detailarbeit.', [
|
||
'desktop' => ['maxWidth' => '580px', 'fontSize' => '21px', 'lineHeight' => 1.55, 'color' => '#e0ebe4', 'margin' => ['top' => '28px', 'bottom' => '40px']],
|
||
'mobile' => ['fontSize' => '18px', 'margin' => ['top' => '22px', 'bottom' => '30px']],
|
||
]),
|
||
$zeile('hero-knoepfe', [
|
||
$knopf('hero-beratung', 'Kostenlose Beratung', '#kontakt', 'rot', true, ['mobile' => ['width' => '100%', 'justifyContent' => 'center']]),
|
||
$knopf('hero-referenzen', 'Referenzen ansehen', '#projekte', 'glas', false, ['mobile' => ['width' => '100%', 'justifyContent' => 'center']]),
|
||
], '14px', ['flexWrap' => 'wrap']),
|
||
], ['desktop' => ['flexGrow' => 1, 'flexBasis' => '560px', 'maxWidth' => '760px'], 'mobile' => ['flexBasis' => 'auto']]),
|
||
$column('hero-karte', [
|
||
$text('hero-karte-klein', 'Direkter Draht', ['desktop' => ['fontSize' => '13px', 'fontWeight' => '700', 'letterSpacing' => '0.12em', 'textTransform' => 'uppercase', 'color' => '@gruen', 'margin' => ['bottom' => '8px']]]),
|
||
$text('hero-karte-titel', 'Vor-Ort-Termin vereinbaren', ['desktop' => ['fontFamily' => 'heading', 'fontSize' => '30px', 'fontWeight' => '800', 'lineHeight' => 1.05, 'textTransform' => 'uppercase', 'color' => '@tinte', 'margin' => ['bottom' => '18px']]]),
|
||
$zeile('hero-karte-telefon', [
|
||
$kreis('hero-karte-kreis', 'phone', '44px'),
|
||
$text('hero-karte-nummer', 'Rufen Sie uns an<br><a href="'.$tel.'"><strong>'.$telefon.'</strong></a>', ['desktop' => ['fontSize' => '13px', 'lineHeight' => 1.45, 'color' => '@text', 'margin' => ['bottom' => '0px']]], '& strong{display:block;font-size:20px;color:#17221b}'),
|
||
], '12px', [], [], [], $ganzeKarte),
|
||
$text('hero-karte-hinweis', '<strong>Mo–Fr</strong> 8–17 Uhr · <strong>Sa</strong> 8–12 Uhr', ['desktop' => ['fontSize' => '14px', 'lineHeight' => 1.5, 'color' => '@text', 'borderWidth' => $nurOben('1px'), 'borderStyle' => 'solid', 'borderColor' => '#ddece2', 'padding' => ['top' => '14px'], 'margin' => ['top' => '14px', 'bottom' => '0px']]], '& strong{color:#17221b}'),
|
||
], ['desktop' => ['flexBasis' => '300px', 'flexShrink' => 0, 'backgroundColor' => '#fffffff2', 'borderRadius' => $rund('16px'), 'padding' => $seiten('24px'), 'shadowY' => '24px', 'shadowBlur' => '60px', 'shadowColor' => '#0000004d']], '', 'desktop'),
|
||
], '40px', ['justifyContent' => 'space-between', 'alignItems' => 'flex-end', 'flexWrap' => 'wrap', 'padding' => ['bottom' => '72px']], ['padding' => ['bottom' => '36px']]),
|
||
// Das Band reicht über die ganze Breite; der Abschnitt schneidet ab, was übersteht.
|
||
$column('hero-vorteile', [
|
||
$column('hero-vorteile-raster', [
|
||
$vorteil(1, $vorteilIcon(1, 'home'), 'Alles aus einer Hand', 'Planung bis Übergabe'),
|
||
$vorteil(2, $vorteilIcon(2, 'star'), '<mark>100</mark> % Detailarbeit', 'Saubere Ausführung'),
|
||
$vorteil(3, $lkw, 'Eigener Fuhrpark', 'LKW, Bagger & Maschinen'),
|
||
$vorteil(4, $vorteilIcon(4, 'pin'), 'Regional vor Ort', 'Iffezheim, Rastatt, Baden-Baden'),
|
||
], [
|
||
'desktop' => ['display' => 'grid', 'gridColumns' => 4, 'gap' => '0px', 'maxWidth' => '1280px', 'margin' => ['left' => 'auto', 'right' => 'auto']],
|
||
'tablet' => ['gridColumns' => 2],
|
||
'mobile' => ['gridColumns' => 2],
|
||
], $vorteilCss),
|
||
], ['desktop' => ['backgroundColor' => '#0c140f8c', 'borderWidth' => $nurOben('1px'), 'borderStyle' => 'solid', 'borderColor' => '#ffffff24']],
|
||
'margin-left:calc(50% - 50vw);margin-right:calc(50% - 50vw);backdrop-filter:blur(6px)'),
|
||
], [
|
||
'desktop' => ['position' => 'relative', 'overflow' => 'hidden', 'display' => 'flex', 'flexDirection' => 'column', 'justifyContent' => 'flex-end', 'minHeight' => '800px', 'backgroundColor' => '#2a372f',
|
||
'backgroundImage' => $medien['hero']['id'], 'backgroundSize' => 'cover', 'backgroundPosition' => 'center',
|
||
'gradientFrom' => '#0c140fdb', 'gradientTo' => '#0c140f1a', 'gradientAngle' => 90,
|
||
'padding' => ['top' => '160px', 'bottom' => '0px']],
|
||
'tablet' => ['minHeight' => '680px', 'padding' => ['top' => '120px', 'bottom' => '0px']],
|
||
// Auf dem Telefon steht der Text über der ganzen Breite: dort deckt der Verlauf überall.
|
||
'mobile' => ['minHeight' => '0px', 'gradientFrom' => '#0c140fd9', 'gradientTo' => '#0c140f8c', 'padding' => ['top' => '64px', 'bottom' => '0px']],
|
||
], null, "&::before{content:'';position:absolute;top:0;right:0;bottom:0;left:0;background:linear-gradient(0deg,rgba(12,20,15,.9) 0%,rgba(12,20,15,0) 40%);pointer-events:none}\n& .section-inner{position:relative}\n".$hundertCss),
|
||
|
||
// 2 – Einleitung: Text links, Collage rechts.
|
||
$section('willkommen', [
|
||
$row('willkommen-zeile', [
|
||
$column('willkommen-text', [
|
||
$kleinzeile('willkommen-label', 'Willkommen bei Hüfner'),
|
||
$heading('willkommen-titel', 'Wir bauen Außenanlagen, die bleiben.', 2, $h2Stil),
|
||
$text('willkommen-lead', 'Ob neue Terrasse, Pool oder die komplette Außenanlage am Neubau: Wir begleiten Ihr Projekt vom ersten Gespräch bis zur Übergabe – mit eigenem Team, eigenem Fuhrpark und hochwertigen Materialien.', [
|
||
...$fliess('20px', '@text-stark', '18px', 1.65), 'mobile' => ['fontSize' => '18px'],
|
||
]),
|
||
$text('willkommen-satz', 'Klare Linien, saubere Fugen, stimmige Übergänge. Darauf legen wir Wert – bei jedem Projekt, ob privat oder öffentlich.', $fliess('17px', '@text', '36px')),
|
||
$column('willkommen-haken', [
|
||
$haken('haken-1', 'Persönliche Beratung'),
|
||
$haken('haken-2', 'Transparentes Angebot'),
|
||
$haken('haken-3', 'Geschultes Fachpersonal'),
|
||
$haken('haken-4', 'Saubere Baustelle'),
|
||
], ['desktop' => ['display' => 'grid', 'gridColumns' => 2, 'rowGap' => '14px', 'columnGap' => '24px'], 'mobile' => ['gridColumns' => 1]], $hakenCss),
|
||
]),
|
||
$column('willkommen-collage', [
|
||
$bild('collage-pool', $medien['pool-stufen'], 'Pool mit Sitzstufen und Keramikumrandung', [
|
||
'desktop' => ['position' => 'absolute', 'top' => '0px', 'right' => '0px', 'width' => '78%', 'height' => '78%', 'margin' => $seiten('0px'), 'overflow' => 'hidden', 'borderRadius' => $rund('18px'), 'backgroundColor' => '#2a372f',
|
||
'shadowY' => '30px', 'shadowBlur' => '60px', 'shadowColor' => '#1420182e'],
|
||
], null, "& img{width:100%;height:100%;object-fit:cover;border-radius:0;transition:transform .7s cubic-bezier(.2,.7,.2,1)}\n&:hover img{transform:scale(1.04)}"),
|
||
$bild('collage-weg', $medien['plattenweg'], 'Plattenweg im Rasen entlang eines Neubaus', [
|
||
'desktop' => ['position' => 'absolute', 'left' => '0px', 'bottom' => '0px', 'width' => '52%', 'height' => '56%', 'margin' => $seiten('0px'), 'overflow' => 'hidden', 'borderRadius' => $rund('18px'), 'zIndex' => 1,
|
||
'borderWidth' => $seiten('8px'), 'borderStyle' => 'solid', 'borderColor' => '@mint', 'backgroundColor' => '#2a372f', 'shadowY' => '30px', 'shadowBlur' => '60px', 'shadowColor' => '#14201833'],
|
||
'mobile' => ['borderWidth' => $seiten('6px')],
|
||
], null, "& img{width:100%;height:100%;object-fit:cover;border-radius:0;transition:transform .7s cubic-bezier(.2,.7,.2,1)}\n&:hover img{transform:scale(1.04)}"),
|
||
$column('collage-abzeichen', [
|
||
$text('collage-abzeichen-zahl', '<mark>100</mark> %', ['desktop' => ['fontFamily' => 'heading', 'fontSize' => '48px', 'fontWeight' => '800', 'lineHeight' => 1.0, 'color' => '@tinte', 'margin' => ['bottom' => '6px']], 'mobile' => ['fontSize' => '38px']]),
|
||
$text('collage-abzeichen-text', 'Detailarbeit', ['desktop' => ['fontSize' => '15px', 'fontWeight' => '600', 'color' => '@text-stark', 'margin' => ['bottom' => '0px']]]),
|
||
], [
|
||
'desktop' => ['position' => 'absolute', 'right' => '24px', 'bottom' => '36px', 'zIndex' => 2, 'backgroundColor' => '@papier', 'borderRadius' => $rund('16px'), 'padding' => ['top' => '20px', 'right' => '24px', 'bottom' => '20px', 'left' => '24px'],
|
||
'shadowY' => '20px', 'shadowBlur' => '40px', 'shadowColor' => '#14201840'],
|
||
'mobile' => ['right' => '0px', 'bottom' => '20px', 'padding' => ['top' => '14px', 'right' => '18px', 'bottom' => '14px', 'left' => '18px']],
|
||
]),
|
||
], ['desktop' => ['position' => 'relative', 'height' => '600px'], 'tablet' => ['height' => '520px'], 'mobile' => ['height' => '420px']]),
|
||
], $zweiSpalten, [
|
||
'desktop' => ['gap' => '96px', 'alignItems' => 'center', 'margin' => ['top' => '0px']],
|
||
'tablet' => ['gap' => '48px'],
|
||
]),
|
||
], $abschnitt(), null, $hundertCss),
|
||
|
||
// 3 – Leistungen: Kopf mit Satz rechts, links die große Pool-Kachel, rechts vier kleine,
|
||
// darunter weitere Leistungen als Chips.
|
||
$section('leistungen-bereich', [
|
||
$row('leistungen-kopf', [
|
||
$column('leistungen-kopf-titel', [
|
||
$kleinzeile('leistungen-label', 'Leistungen'),
|
||
$heading('leistungen-titel', 'Alles für Ihren Außenbereich', 2, ['desktop' => [...$h2Stil['desktop'], 'margin' => ['top' => '18px', 'bottom' => '0px']], 'tablet' => $h2Stil['tablet'], 'mobile' => $h2Stil['mobile']]),
|
||
], ['desktop' => ['maxWidth' => '680px']]),
|
||
$text('leistungen-kopf-text', 'Vom Erdbau über Pflaster- und Natursteinarbeiten bis zur fertigen Gartenanlage – alles aus einer Hand.', [
|
||
'desktop' => ['maxWidth' => '420px', 'fontSize' => '18px', 'lineHeight' => 1.65, 'color' => '@text', 'margin' => ['bottom' => '0px']],
|
||
]),
|
||
], [], [
|
||
'desktop' => ['display' => 'flex', 'flexWrap' => 'wrap', 'justifyContent' => 'space-between', 'alignItems' => 'flex-end', 'rowGap' => '24px', 'columnGap' => '64px', 'margin' => ['top' => '0px', 'bottom' => '56px']],
|
||
'mobile' => ['margin' => ['bottom' => '36px']],
|
||
]),
|
||
$row('leistungen-raster', [
|
||
$kachel('leistung-pool', $medien['pool-fachwerk'], 'Pools & Schwimmbecken – Beratung anfragen', [
|
||
$kachelTitel('leistung-pool-titel', 'Pools & Schwimmbecken', '44px'),
|
||
$kachelSatz('leistung-pool-satz', 'Poolanlagen inklusive Umrandung, Technik und stimmiger Gestaltung rundherum – vom Aushub bis zur fertigen Terrasse.', '17px', '18px', '460px'),
|
||
$zeile('leistung-pool-mehr', [
|
||
$text('leistung-pool-mehr-text', 'Beratung anfragen', ['desktop' => ['fontSize' => '15px', 'fontWeight' => '700', 'letterSpacing' => '0.06em', 'textTransform' => 'uppercase', 'color' => '@papier', 'margin' => ['bottom' => '0px']]]),
|
||
$symbol('leistung-pool-mehr-pfeil', 'arrow-right', 16, ['desktop' => ['color' => '@papier']]),
|
||
], '8px'),
|
||
], '36px', '#kontakt', ['desktop' => ['minHeight' => '620px'], 'tablet' => ['minHeight' => '380px'], 'mobile' => ['minHeight' => '440px', 'padding' => $seiten('24px')]], '', true),
|
||
$column('leistungen-klein', [
|
||
$kachel('leistung-terrasse', $medien['terrasse'], 'Terrassen – Beratung anfragen', [
|
||
$heading('leistung-terrasse-titel', 'Terrassen', 3),
|
||
$text('leistung-terrasse-satz', 'Keramik, Naturstein, Holz oder WPC.'),
|
||
], '24px', '#kontakt'),
|
||
$kachel('leistung-pflaster', $medien['pflaster-verlegt'], 'Pflaster – Beratung anfragen', [
|
||
$heading('leistung-pflaster-titel', 'Pflaster', 3),
|
||
$text('leistung-pflaster-satz', 'Einfahrten, Wege und Plätze.'),
|
||
], '24px', '#kontakt'),
|
||
$kachel('leistung-naturstein', $medien['naturstein'], 'Natursteine – Beratung anfragen', [
|
||
$heading('leistung-naturstein-titel', 'Natursteine', 3),
|
||
$text('leistung-naturstein-satz', 'Mauern, Stufen, Einfassungen.'),
|
||
], '24px', '#kontakt'),
|
||
$kachel('leistung-aussen', $medien['rasen'], 'Außenanlagen – Beratung anfragen', [
|
||
$heading('leistung-aussen-titel', 'Außenanlagen', 3),
|
||
$text('leistung-aussen-satz', 'Rasen, Bepflanzung, Gesamtanlage.'),
|
||
], '24px', '#kontakt'),
|
||
], ['desktop' => ['display' => 'grid', 'gridColumns' => 2, 'gap' => '20px'], 'mobile' => ['gridColumns' => 1]], 'grid-auto-rows:300px'),
|
||
], $zweiSpalten, ['desktop' => ['gap' => '20px', 'margin' => ['top' => '0px']]]),
|
||
$zeile('leistungen-chips', [
|
||
$text('leistungen-chips-label', 'Außerdem', ['desktop' => ['fontFamily' => 'heading', 'fontSize' => '16px', 'fontWeight' => '700', 'letterSpacing' => '0.14em', 'textTransform' => 'uppercase', 'color' => '@text', 'margin' => ['right' => '8px', 'bottom' => '0px']]]),
|
||
$chip('chip-erdbau', 'Erdbau'),
|
||
$chip('chip-rollrasen', 'Rollrasen'),
|
||
$chip('chip-sichtschutz', 'Sichtschutz & Zäune'),
|
||
$chip('chip-gabionen', 'Gabionen'),
|
||
$chip('chip-bewaesserung', 'Bewässerung'),
|
||
$chip('chip-entwaesserung', 'Entwässerung & Rinnen'),
|
||
$chip('chip-spielplaetze', 'Spielplätze'),
|
||
], '12px', ['flexWrap' => 'wrap', 'margin' => ['top' => '40px']], [], [], $chipCss),
|
||
], $abschnitt('@papier'), 'leistungen', $kachelFlaeche.$kleineKachel('font-size:30px;font-weight:800;line-height:1;letter-spacing:0;text-transform:uppercase;color:#fff;margin:4px 0 6px', 'font-size:15px;line-height:1.5;color:#d8e6dd;margin:0')),
|
||
|
||
// 4 – Referenzen auf Nachtgrün.
|
||
$section('referenzen-bereich', [
|
||
$row('referenzen-kopf', [
|
||
$column('referenzen-kopf-titel', [
|
||
$kleinzeile('referenzen-label', 'Referenzen', '@papier'),
|
||
$heading('referenzen-titel', 'Der Hüfner-Look', 2, ['desktop' => [...$h2Stil['desktop'], 'color' => '@papier', 'margin' => ['top' => '18px', 'bottom' => '0px']], 'tablet' => $h2Stil['tablet'], 'mobile' => $h2Stil['mobile']]),
|
||
]),
|
||
$kleinerKnopf($knopf('referenzen-instagram', 'Alle Projekte auf Instagram', $instagram, 'linie', true, [])),
|
||
], [], [
|
||
'desktop' => ['display' => 'flex', 'flexWrap' => 'wrap', 'justifyContent' => 'space-between', 'alignItems' => 'flex-end', 'gap' => '24px', 'margin' => ['top' => '0px', 'bottom' => '56px']],
|
||
'mobile' => ['margin' => ['bottom' => '36px']],
|
||
]),
|
||
$row('referenzen-raster', [
|
||
$kachel('referenz-garten', $medien['pool-garten'], 'Pool mit Hortensien und Gartenanlage', [
|
||
$kachelKlein('referenz-garten-klein', 'Pool · Garten · Terrasse', '14px'),
|
||
$kachelTitel('referenz-garten-titel', 'Poolanlage mit Gartengestaltung', '36px'),
|
||
], '32px', null, ['desktop' => ['minHeight' => '620px'], 'tablet' => ['minHeight' => '380px'], 'mobile' => ['minHeight' => '360px', 'padding' => $seiten('24px')]], '45%'),
|
||
$column('referenzen-klein', [
|
||
$kachel('referenz-terrasse', $medien['pool-terrasse'], 'Pool mit Natursteinterrasse und Gabionen', [
|
||
$text('referenz-terrasse-klein', 'Pool · Naturstein'),
|
||
$heading('referenz-terrasse-titel', 'Pool & Terrasse', 3),
|
||
], '24px', null, [], '55%'),
|
||
$kachel('referenz-zaun', $medien['sichtschutz'], 'Moderner Sichtschutz aus Aluminium', [
|
||
$text('referenz-zaun-klein', 'Zaun'),
|
||
$heading('referenz-zaun-titel', 'Sichtschutz', 3),
|
||
], '24px', null, [], '55%'),
|
||
$kachel('referenz-hang', $medien['pflaster'], 'Hanggarten mit Natursteinmauer und Treppe', [
|
||
$text('referenz-hang-klein', 'Hanggarten'),
|
||
$heading('referenz-hang-titel', 'Mauer & Treppe', 3),
|
||
], '24px', null, [], '55%'),
|
||
], ['desktop' => ['display' => 'grid', 'gridColumns' => 2, 'gap' => '20px'], 'mobile' => ['gridColumns' => 1]], "grid-auto-rows:300px\n& [id=\"referenz-terrasse\"]{grid-column:1/-1}"),
|
||
], $zweiSpalten, ['desktop' => ['gap' => '20px', 'margin' => ['top' => '0px']]]),
|
||
$karte('referenzen-kommunen', [
|
||
$text('referenzen-kommunen-titel', 'Auch für Kommunen', ['desktop' => ['fontFamily' => 'heading', 'fontSize' => '22px', 'fontWeight' => '800', 'letterSpacing' => '0.04em', 'textTransform' => 'uppercase', 'color' => '@papier', 'margin' => ['bottom' => '0px']]]),
|
||
$text('referenzen-kommunen-text', 'Zum Beispiel der Spielplatz an der Pagodenburg in Rastatt oder frischer Spielsand für die Spielplätze der Stadt Karlsruhe.', ['desktop' => ['flexGrow' => 1, 'flexBasis' => '300px', 'fontSize' => '16px', 'lineHeight' => 1.6, 'color' => '#c5d6cb', 'margin' => ['bottom' => '0px']], 'mobile' => ['flexBasis' => 'auto']]),
|
||
], '40px', ['flexWrap' => 'wrap', 'rowGap' => '16px', 'margin' => ['top' => '32px'], 'padding' => ['top' => '28px', 'right' => '32px', 'bottom' => '28px', 'left' => '32px']],
|
||
['padding' => $seiten('24px')], [], 'background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.12);border-radius:18px'),
|
||
], $abschnitt('@nacht', ['desktop' => ['color' => '@papier']]), 'projekte', $kachelFlaeche.$kleineKachel('font-size:26px;font-weight:800;line-height:1;letter-spacing:0;text-transform:uppercase;color:#fff;margin:4px 0 6px', 'font-size:13px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:#fff;margin:0 0 2px')),
|
||
|
||
// 4b – Google-Bewertungen (Umut, 12.10.2026: „3–5 Bemerkungen und Button“). Drei echte
|
||
// Rezensionen aus Google Maps vom 12.10.2026, gekürzt mit „…“, Namen abgekürzt. Stand
|
||
// damals: 4,9 von 5 bei 159 Rezensionen – „über 150“, damit der Satz nicht veraltet.
|
||
$section('bewertungen-bereich', [
|
||
$row('bewertungen-kopf', [
|
||
$column('bewertungen-kopf-titel', [
|
||
$kleinzeile('bewertungen-label', 'Google-Bewertungen'),
|
||
$heading('bewertungen-titel', '<mark>100</mark> % Qualität.<br><em><mark>100</mark> % Zufriedenheit.</em>', 2, ['desktop' => [...$h2Stil['desktop'], 'margin' => ['top' => '18px', 'bottom' => '12px']], 'tablet' => $h2Stil['tablet'], 'mobile' => $h2Stil['mobile']], $emGruen."\n".$hundertCss),
|
||
// Umut, 12.10.2026: die Überschrift als Versprechen, die 4,9 als Beleg darunter.
|
||
$text('bewertungen-satz', '<strong>4,9 von 5 Sternen</strong> bei über 150 Bewertungen auf Google. Das sagen unsere Kunden:', $fliess('18px', '@text', '0px', 1.6)),
|
||
]),
|
||
$kleinerKnopf($knopf('bewertungen-google', 'Alle Bewertungen auf Google', $google, 'dunkel', true, [])),
|
||
], [], [
|
||
'desktop' => ['display' => 'flex', 'flexWrap' => 'wrap', 'justifyContent' => 'space-between', 'alignItems' => 'flex-end', 'gap' => '24px', 'margin' => ['top' => '0px', 'bottom' => '48px']],
|
||
'mobile' => ['margin' => ['bottom' => '32px']],
|
||
]),
|
||
$column('bewertungen-raster', [
|
||
['id' => 'bewertung-1', 'type' => 'quote', 'props' => ['rating' => 5, 'author' => 'Jasmin B.', 'role' => '',
|
||
'text' => 'Wir sind wirklich begeistert. Vom ersten Gespräch bis zum letzten Arbeitstag hat alles super funktioniert. … Aus unserem „Erdloch“ ist in kurzer Zeit ein wunderschöner Garten geworden, sauber, ordentlich und genau so, wie wir ihn uns vorgestellt haben. … Wir würden uns jederzeit wieder an die Firma Hüfner wenden.']],
|
||
['id' => 'bewertung-2', 'type' => 'quote', 'props' => ['rating' => 5, 'author' => 'Marc A.', 'role' => '',
|
||
'text' => 'Unsere Vorstellungen wurden perfekt umgesetzt, die Mitarbeiter waren zu jeder Zeit freundlich und gingen auf unsere Wünsche ein, sie unterstützen unser Vorhaben mit wertvollen Tipps. Die Baustelle wurde jeden Abend sauber und aufgeräumt hinterlassen.']],
|
||
['id' => 'bewertung-3', 'type' => 'quote', 'props' => ['rating' => 5, 'author' => 'Heiko K.', 'role' => '',
|
||
'text' => 'Von Angebotserstellung bis zur Ausführung lief alles schnell und professionell. … Hervorzuheben ist darüber hinaus der besondere Einsatz des Ausführungsteams vor Ort und die Schnelligkeit bei der Ausführung. Dabei wurde sauber und ordentlich gearbeitet.']],
|
||
], [
|
||
'desktop' => ['display' => 'grid', 'gridColumns' => 3, 'gap' => '20px'],
|
||
'tablet' => ['gridColumns' => 1],
|
||
'mobile' => ['gridColumns' => 1],
|
||
], "& .block-quote{background:#fff;border:0;border-radius:18px;padding:32px;font-size:17px;color:#3a483f;box-shadow:0 14px 34px rgba(20,32,24,.07);align-content:start}\n"
|
||
."& .quote-rating{color:#e2a400;font-size:22px;letter-spacing:4px}\n"
|
||
."& .block-quote figcaption{font-size:16px;font-weight:700;color:#17221b}"),
|
||
], $abschnitt(), 'bewertungen'),
|
||
|
||
// 5 – Ablauf in vier Schritten mit gestrichelter Linie hinter den Nummern.
|
||
$section('ablauf-bereich', [
|
||
$column('ablauf-kopf', [
|
||
$kleinzeile('ablauf-label', 'So arbeiten wir', '@rot', true),
|
||
$heading('ablauf-titel', 'In vier Schritten zum Gartentraum', 2, ['desktop' => [...$h2Stil['desktop'], 'textAlign' => 'center', 'margin' => ['top' => '18px', 'bottom' => '0px']], 'tablet' => $h2Stil['tablet'], 'mobile' => $h2Stil['mobile']]),
|
||
], ['desktop' => ['maxWidth' => '760px', 'margin' => ['left' => 'auto', 'right' => 'auto', 'bottom' => '72px']], 'mobile' => ['margin' => ['bottom' => '48px']]]),
|
||
$column('ablauf-schritte', [
|
||
$flaeche('ablauf-linie', ['desktop' => ['position' => 'absolute', 'left' => '12.5%', 'right' => '12.5%', 'top' => '36px', 'height' => '2px', 'margin' => $seiten('0px')]],
|
||
"min-height:0;background:repeating-linear-gradient(90deg,#8cc39f 0 10px,transparent 10px 18px)", 'desktop'),
|
||
$column('ablauf-raster', [
|
||
$schritt(1, 'Beratung vor Ort', 'Wir schauen uns Ihr Grundstück an und hören zu, was Sie sich wünschen.'),
|
||
// Umut, 12.10.2026: „Bei Angebot innerhalb 48 h Angebot garantiert“ und die
|
||
// Freistellungsbescheinigungen hier als Download.
|
||
$schritt(2, 'Planung & Angebot', 'Klares Konzept, transparentes Angebot – <strong>innerhalb von 48 Stunden nach dem Termin, garantiert.</strong>', [
|
||
$text('schritt-2-downloads', '<a href="'.$pdf['f13b'].'">Freistellung § 13b UStG (PDF)</a><br><a href="'.$pdf['f48b'].'">Freistellung § 48b EStG (PDF)</a>', [
|
||
'desktop' => ['fontSize' => '14px', 'lineHeight' => 1.9, 'color' => '@text', 'margin' => ['top' => '14px', 'bottom' => '0px']],
|
||
], $linkCss),
|
||
]),
|
||
$schritt(3, 'Ausführung', 'Unser Team baut termintreu, sauber und mit Liebe zum Detail.'),
|
||
$schritt(4, 'Übergabe', 'Gemeinsame Abnahme Ihres fertigen Außenbereichs.'),
|
||
], [
|
||
'desktop' => ['display' => 'grid', 'gridColumns' => 4, 'gap' => '32px'],
|
||
'tablet' => ['gridColumns' => 2, 'rowGap' => '48px'],
|
||
'mobile' => ['gridColumns' => 1, 'rowGap' => '44px'],
|
||
], $schrittCss),
|
||
], ['desktop' => ['position' => 'relative']]),
|
||
], $abschnitt(), 'ablauf'),
|
||
|
||
// 6 – Fuhrpark: weiße Karte mit Baustellenfoto und vier Geräten.
|
||
$section('fuhrpark-bereich', [
|
||
$column('fuhrpark-karte', [
|
||
$row('fuhrpark-zeile', [
|
||
$column('fuhrpark-foto-spalte', [
|
||
$bild('fuhrpark-foto', $medien['baustelle'], 'Poolbau im Rohbau mit Minibagger und Radlader im Hintergrund', ['desktop' => ['height' => '100%', 'margin' => $seiten('0px')]], null,
|
||
'& img{width:100%;height:100%;min-height:360px;object-fit:cover;border-radius:0}'),
|
||
], ['desktop' => ['backgroundColor' => '#2a372f']]),
|
||
$column('fuhrpark-text', [
|
||
$kleinzeile('fuhrpark-label', 'Fuhrpark & Maschinen'),
|
||
$heading('fuhrpark-titel', 'Eigenes Gerät. Kurze Wege.', 2, ['desktop' => [...$h2Stil['desktop'], 'fontSize' => '52px', 'margin' => ['top' => '18px', 'bottom' => '20px']], 'tablet' => ['fontSize' => '44px'], 'mobile' => $h2Stil['mobile']]),
|
||
$text('fuhrpark-satz', 'Weil wir mit eigenem Fuhrpark arbeiten, sind wir unabhängig, flexibel und schnell auf der Baustelle – auch bei großen Flächen.', $fliess('17px', '@text', '28px', 1.65)),
|
||
$column('fuhrpark-geraete', [
|
||
$geraet('geraet-lkw', 'LKW & Kipper', 'für Material & Aushub'),
|
||
// „bis 15 t“ ist raus: Hüfner hat inzwischen größere (Umut, 12.10.2026).
|
||
$geraet('geraet-bagger', 'Bagger & Radlader', 'inkl. Tieflader'),
|
||
$geraet('geraet-pflaster', 'Pflasterverlegemaschine', 'für große Flächen'),
|
||
$geraet('geraet-container', 'Abrollcontainer', 'für Entsorgung'),
|
||
], ['desktop' => ['display' => 'grid', 'gridColumns' => 2, 'gap' => '12px']], $geraetCss),
|
||
], ['desktop' => ['padding' => $seiten('56px')], 'mobile' => ['padding' => ['top' => '32px', 'right' => '24px', 'bottom' => '32px', 'left' => '24px']]]),
|
||
], $zweiSpalten, ['desktop' => ['gap' => '0px', 'margin' => ['top' => '0px'], 'columnWidths' => '11 10', 'alignItems' => 'stretch']]),
|
||
], ['desktop' => ['backgroundColor' => '@papier', 'borderRadius' => $rund('24px'), 'overflow' => 'hidden', 'shadowY' => '30px', 'shadowBlur' => '60px', 'shadowColor' => '#14201814']]),
|
||
], ['desktop' => ['padding' => ['top' => '0px', 'bottom' => '128px']], 'tablet' => ['padding' => ['top' => '0px', 'bottom' => '80px']], 'mobile' => ['padding' => ['top' => '0px', 'bottom' => '64px']]]),
|
||
|
||
// 7 – Über uns.
|
||
$section('ueber-bereich', [
|
||
$row('ueber-zeile', [
|
||
$column('ueber-foto-spalte', [
|
||
$bild('ueber-foto', $medien['team'], 'Das Hüfner-Team in Arbeitsjacken mit Firmenlogo', [
|
||
'desktop' => ['margin' => $seiten('0px'), 'overflow' => 'hidden', 'borderRadius' => $rund('18px'), 'backgroundColor' => '#2a372f', 'imageHeight' => '560px'],
|
||
'mobile' => ['imageHeight' => '320px'],
|
||
], null, '& img{width:100%;object-fit:cover;border-radius:0}'),
|
||
$text('ueber-abzeichen', 'Miteinander. <mark>100</mark> %.', [
|
||
'desktop' => ['position' => 'absolute', 'left' => '-1px', 'bottom' => '32px', 'backgroundColor' => '@tinte', 'color' => '@papier', 'fontFamily' => 'heading', 'fontSize' => '22px', 'fontWeight' => '800', 'letterSpacing' => '0.06em', 'textTransform' => 'uppercase', 'lineHeight' => 1.2,
|
||
'padding' => ['top' => '16px', 'right' => '24px', 'bottom' => '16px', 'left' => '24px'], 'borderRadius' => ['topLeft' => '0px', 'topRight' => '12px', 'bottomRight' => '12px', 'bottomLeft' => '0px'], 'margin' => ['bottom' => '0px']],
|
||
'mobile' => ['fontSize' => '18px', 'bottom' => '20px'],
|
||
]),
|
||
], ['desktop' => ['position' => 'relative']], '', 'all'),
|
||
$column('ueber-text', [
|
||
$kleinzeile('ueber-label', 'Über uns'),
|
||
$heading('ueber-titel', 'Handwerk mit Anspruch.', 2, $h2Stil),
|
||
$text('ueber-lead', 'Hinter Garten- und Landschaftsbau Hüfner steht Florian Hüfner mit seinem Team. Wir bauen Außenanlagen, die lange Freude machen – sauber geplant, präzise ausgeführt und bis ins Detail durchdacht.', [
|
||
...$fliess('20px', '@text-stark', '18px', 1.65), 'mobile' => ['fontSize' => '18px'],
|
||
]),
|
||
$text('ueber-satz', 'Unsere Mitarbeiter bilden sich regelmäßig weiter – etwa in moderner Bewässerungstechnik oder im Einbau und der Verdichtung von Asphalt.', $fliess('17px', '@text', '18px')),
|
||
// Laut Handelsregister (North Data, 06.10.2026): bis April 2022 „Garten- und
|
||
// Landschaftsbau Florian Hüfner e. K.“, seither GmbH mit Sitz in Iffezheim.
|
||
$text('ueber-geschichte', 'Angefangen hat alles als Einzelunternehmen von Florian Hüfner. Seit 2022 sind wir die Garten- und Landschaftsbau Hüfner GmbH – mit Sitz in Iffezheim und Baustellen in der ganzen Region.', $fliess('17px', '@text', '36px')),
|
||
$column('ueber-zahlen', [
|
||
// Von Umut, 12.10.2026: 25 Jahre Erfahrung, über 7.000 Projekte.
|
||
$kennzahl('zahl-jahre', '25', 'Jahre Erfahrung', false),
|
||
$kennzahl('zahl-projekte', '7.000+', 'Projekte umgesetzt', true),
|
||
$kennzahl('zahl-detail', '<mark>100</mark> %', 'Detailarbeit', true),
|
||
], ['desktop' => ['display' => 'grid', 'gridColumns' => 3, 'gap' => '0px', 'borderWidth' => $nurOben('1px'), 'borderStyle' => 'solid', 'borderColor' => '@linie', 'padding' => ['top' => '28px']], 'mobile' => ['gridColumns' => 3]], $kennzahlCss),
|
||
// Umut, 12.10.2026: Galabau-Verband und PQ-Zertifizierung zum Herunterladen.
|
||
$zeile('ueber-nachweise', [
|
||
$bild('nachweis-signet', $medien['galabau-signet'], 'Ihre Experten für Garten & Landschaft – Signet der Landschaftsgärtner-Verbände', ['desktop' => ['width' => '64px', 'maxWidth' => '64px', 'flexShrink' => 0, 'margin' => $seiten('0px')]], null, '& img{display:block;width:100%;height:auto;border-radius:0}'),
|
||
$bild('nachweis-pq', $medien['pq-logo'], 'PQ-VOB – präqualifiziertes Unternehmen', ['desktop' => ['width' => '72px', 'maxWidth' => '72px', 'flexShrink' => 0, 'margin' => $seiten('0px')]], null, '& img{display:block;width:100%;height:auto;border-radius:0}'),
|
||
$text('nachweis-text', '<strong>Mitglied im Fachverband der Landschaftsgärtner</strong> und <strong>präqualifiziert nach PQ-VOB</strong> (Nr. 101.003531) für Erd-, Landschaftsbau- und Pflasterarbeiten.<br><a href="'.$pdf['pq'].'">PQ-Bescheinigung herunterladen (PDF)</a>', [
|
||
'desktop' => ['flexGrow' => 1, 'flexBasis' => '260px', 'fontSize' => '15px', 'lineHeight' => 1.6, 'color' => '@text', 'margin' => ['bottom' => '0px']],
|
||
'mobile' => ['flexBasis' => '100%'],
|
||
], $linkCss."
|
||
& strong{color:#17221b}"),
|
||
], '20px', ['flexWrap' => 'wrap', 'rowGap' => '16px', 'margin' => ['top' => '28px'], 'borderWidth' => $nurOben('1px'), 'borderStyle' => 'solid', 'borderColor' => '@linie', 'padding' => ['top' => '28px']]),
|
||
]),
|
||
], $zweiSpalten, [
|
||
'desktop' => ['gap' => '96px', 'alignItems' => 'center', 'margin' => ['top' => '0px']],
|
||
'tablet' => ['gap' => '48px'],
|
||
]),
|
||
], $abschnitt('@papier'), 'ueber-uns', $hundertCss),
|
||
|
||
// 7b – Imagevideo (Umut, 12.10.2026). YouTube als „Externer Inhalt“ mit youtube-nocookie:
|
||
// nichts von Google wird geladen, bevor jemand einwilligt. Bis dahin steht das Teamfoto
|
||
// als Fläche da, der Knopf des Kerns fragt um Einwilligung und lädt dann das Video.
|
||
$section('video-bereich', [
|
||
$column('video-kopf', [
|
||
$kleinzeile('video-label', 'Imagevideo', '@rot', true),
|
||
$heading('video-titel', 'Hüfner in Bewegung', 2, ['desktop' => [...$h2Stil['desktop'], 'textAlign' => 'center', 'margin' => ['top' => '18px', 'bottom' => '0px']], 'tablet' => $h2Stil['tablet'], 'mobile' => $h2Stil['mobile']]),
|
||
], ['desktop' => ['maxWidth' => '760px', 'margin' => ['left' => 'auto', 'right' => 'auto', 'bottom' => '48px']], 'mobile' => ['margin' => ['bottom' => '32px']]]),
|
||
['id' => 'video-rahmen', 'type' => 'embed', 'props' => [
|
||
'group' => 'external',
|
||
'title' => 'Imagevideo der Garten- und Landschaftsbau Hüfner GmbH',
|
||
'provider' => 'YouTube (Google)',
|
||
'url' => 'https://www.youtube-nocookie.com/embed/uNGB5yCs47o?rel=0',
|
||
'hint' => 'Das Video liegt bei YouTube. Erst wenn Sie es laden, erfährt Google Ihre IP-Adresse und dass Sie diese Seite ansehen.',
|
||
'height' => 720,
|
||
], 'style' => ['desktop' => ['maxWidth' => '1080px', 'margin' => ['left' => 'auto', 'right' => 'auto']]],
|
||
// Knapp gehalten: die Startseite steht am HTML-Budget von 150 KB.
|
||
'css' => "& iframe{display:block;width:100%;height:auto;aspect-ratio:16/9;border:0;border-radius:22px}\n"
|
||
."&.embed-blocked{box-sizing:border-box;width:100%;display:flex;flex-direction:column;justify-content:flex-end;align-items:flex-start;aspect-ratio:16/9;min-height:320px;padding:40px;border:0;border-radius:22px;color:#fff;text-align:left;"
|
||
."background:linear-gradient(0deg,#0a120deb,#0a120d59 70%),url(".end($medien['team']['variants'])['src'].") center/cover #142018}\n"
|
||
."& .embed-title{margin:0 0 8px;font:800 30px/1.05 var(--kf-heading);text-transform:uppercase;color:#fff}\n"
|
||
."& .embed-hint{max-width:560px;margin:0 0 6px;font-size:15px;color:#e0ebe4}\n"
|
||
."& .embed-provider{margin:0 0 18px;font-size:13px;color:#e0ebe4}\n"
|
||
."& .consent-button{min-height:56px;padding:0 28px;border:0;border-radius:999px;background:#b71c22;color:#fff;font:700 18px var(--kf-heading);letter-spacing:.06em;text-transform:uppercase}\n"
|
||
."& .consent-button:hover{background:#931419}"],
|
||
], ['desktop' => ['backgroundColor' => '@papier', 'padding' => ['top' => '0px', 'bottom' => '128px']], 'tablet' => ['padding' => ['top' => '0px', 'bottom' => '80px']], 'mobile' => ['padding' => ['top' => '0px', 'bottom' => '64px']]], 'video'),
|
||
|
||
// 8 – Karriere: grüne Karte.
|
||
$section('karriere-bereich', [
|
||
$karte('karriere-karte', [
|
||
$column('karriere-text', [
|
||
$text('karriere-klein', 'Karriere', ['desktop' => ['fontFamily' => 'heading', 'fontSize' => '17px', 'fontWeight' => '700', 'letterSpacing' => '0.2em', 'textTransform' => 'uppercase', 'color' => '#e6f7ec', 'margin' => ['bottom' => '0px']]]),
|
||
$heading('karriere-titel', 'Werde Teil unseres Teams!', 2, ['desktop' => [...$h2Stil['desktop'], 'fontSize' => '52px', 'color' => '@papier', 'margin' => ['top' => '12px', 'bottom' => '14px']], 'tablet' => ['fontSize' => '44px'], 'mobile' => $h2Stil['mobile']]),
|
||
$text('karriere-satz', 'Du arbeitest gerne draußen, packst mit an und bist zuverlässig im Team? Wir suchen Facharbeiter und LKW-Fahrer – tippe auf eine Stelle für mehr.', $fliess('17px', '#eef9f2', '28px', 1.6)),
|
||
// „Ein Anruf genügt!“ wie auf Hüfners Anzeige – mit WhatsApp daneben.
|
||
$zeile('karriere-anruf', [
|
||
// Weißer Kreis mit grünem Hörer: der grüne Standardkreis verschwände auf der grünen Karte.
|
||
array_replace_recursive($kreis('karriere-anruf-kreis', 'phone', '52px'), ['style' => ['desktop' => ['backgroundColor' => '@papier']], 'children' => [['style' => ['desktop' => ['color' => '@gruen']]]]]),
|
||
$text('karriere-anruf-text', 'Ein Anruf genügt!', ['desktop' => ['fontFamily' => 'heading', 'fontSize' => '30px', 'fontWeight' => '800', 'lineHeight' => 1.0, 'textTransform' => 'uppercase', 'color' => '@papier', 'margin' => ['bottom' => '0px']]]),
|
||
], '14px', ['margin' => ['bottom' => '18px']]),
|
||
$zeile('karriere-knoepfe', [
|
||
$knopf('karriere-anrufen', $telefon, $tel, 'weiss', false, ['mobile' => ['width' => '100%', 'justifyContent' => 'center']]),
|
||
$knopf('karriere-whatsapp', 'Per WhatsApp bewerben', $whatsappBewerbung, 'linie', false, ['mobile' => ['width' => '100%', 'justifyContent' => 'center']]),
|
||
], '10px', ['flexWrap' => 'wrap']),
|
||
], ['desktop' => ['flexGrow' => 1, 'flexBasis' => '340px'], 'mobile' => ['flexBasis' => 'auto']]),
|
||
$column('karriere-stellen', [
|
||
['id' => 'karriere-karten', 'type' => 'html', 'props' => ['html' => '<div class="stellen">'
|
||
.$stellenKarte('stelle-facharbeiter', 'Facharbeiter (m/w/d)', 'Vollzeit · Garten- und Landschaftsbau', $helm, [
|
||
'Pflaster-, Naturstein- und Erdarbeiten, Terrassen und Poolumrandungen',
|
||
'Ausbildung im Garten- und Landschaftsbau oder Erfahrung am Bau',
|
||
'Selbstständiges Arbeiten im Team, Führerschein Klasse B von Vorteil',
|
||
], 'Facharbeiter (m/w/d)')
|
||
// Wie Hüfners Anzeige auf Instagram: „LKW-Fahrer C/CE gesucht (m/w/d)“.
|
||
.$stellenKarte('stelle-lkw', 'LKW-Fahrer C/CE (m/w/d)', 'Vollzeit · Fuhrpark', $lkwBild, [
|
||
'Führerschein Klasse C/CE',
|
||
'Material, Aushub und Maschinen mit unseren LKW, Kippern und dem Tieflader',
|
||
'Zuverlässig, pünktlich und gern im Team unterwegs',
|
||
], 'LKW-Fahrer C/CE (m/w/d)')
|
||
.'</div>'], 'css' => $stellenCss],
|
||
// Aus Hüfners eigener Stellenanzeige auf Instagram („Was dich erwartet“).
|
||
$text('karriere-vorteile', '<strong>Was dich erwartet:</strong> faire Bezahlung · Sonderzahlungen · Spesen · 30 Tage Urlaub', [
|
||
'desktop' => ['fontSize' => '15px', 'lineHeight' => 1.6, 'color' => '#eef9f2', 'margin' => ['top' => '4px', 'bottom' => '0px']],
|
||
], '& strong{color:#fff}'),
|
||
], ['desktop' => ['flexGrow' => 1, 'flexBasis' => '460px', 'display' => 'flex', 'flexDirection' => 'column', 'gap' => '14px'], 'mobile' => ['flexBasis' => 'auto']]),
|
||
], '64px', [
|
||
'flexWrap' => 'wrap', 'rowGap' => '32px', 'justifyContent' => 'space-between', 'backgroundColor' => '@gruen', 'color' => '@papier', 'borderRadius' => $rund('24px'),
|
||
'padding' => ['top' => '56px', 'right' => '64px', 'bottom' => '56px', 'left' => '64px'],
|
||
], ['padding' => ['top' => '32px', 'right' => '24px', 'bottom' => '32px', 'left' => '24px']]),
|
||
], ['desktop' => ['padding' => ['top' => '96px', 'bottom' => '96px']], 'mobile' => ['padding' => ['top' => '64px', 'bottom' => '64px']]], 'karriere'),
|
||
|
||
// 9 – Instagram.
|
||
$section('social-bereich', [
|
||
$row('social-kopf', [
|
||
$column('social-kopf-titel', [
|
||
$kleinzeile('social-label', 'Social Media'),
|
||
$heading('social-titel', 'Live von der Baustelle', 2, ['desktop' => [...$h2Stil['desktop'], 'fontSize' => '48px', 'lineHeight' => 1.0, 'margin' => ['top' => '12px', 'bottom' => '0px']], 'mobile' => ['fontSize' => '36px']]),
|
||
]),
|
||
$zeile('social-knoepfe', [
|
||
['id' => 'social-instagram', 'type' => 'social', 'props' => ['size' => 20, 'shape' => 'plain', 'layout' => 'horizontal', 'labels' => true, 'networks' => ['instagram']],
|
||
'css' => "& a{min-height:50px;padding:0 24px;border-radius:999px;background:#17221b;color:#fff;font-family:var(--kf-heading);font-size:17px;font-weight:700;letter-spacing:0.06em}\n& a:hover{background:#067f30;box-shadow:0 10px 24px rgba(0,0,0,.18)}"],
|
||
['id' => 'social-facebook', 'type' => 'social', 'props' => ['size' => 20, 'shape' => 'plain', 'layout' => 'horizontal', 'labels' => true, 'networks' => ['facebook']],
|
||
'css' => "& a{min-height:50px;padding:0 24px;border-radius:999px;background:#1f4fa0;color:#fff;font-family:var(--kf-heading);font-size:17px;font-weight:700;letter-spacing:0.06em}\n& a:hover{background:#173d7d;box-shadow:0 10px 24px rgba(0,0,0,.18)}"],
|
||
], '10px', ['flexWrap' => 'wrap']),
|
||
], [], [
|
||
'desktop' => ['display' => 'flex', 'flexWrap' => 'wrap', 'justifyContent' => 'space-between', 'alignItems' => 'flex-end', 'gap' => '20px', 'margin' => ['top' => '0px', 'bottom' => '28px']],
|
||
]),
|
||
$column('social-raster', [
|
||
$instaBild('insta-1', $medien['pool-stufen'], 'Pool mit Sitzstufen'),
|
||
$instaBild('insta-2', $medien['naturstein'], 'Natursteinmauer mit Trittplatten'),
|
||
$instaBild('insta-3', $medien['pool-garten'], 'Pool mit Gartenanlage'),
|
||
$instaBild('insta-4', $medien['plattenweg'], 'Plattenweg im Rasen'),
|
||
$instaBild('insta-5', $medien['terrasse'], 'Keramikterrasse am Pool'),
|
||
$instaBild('insta-6', $medien['team'], 'Das Hüfner-Team'),
|
||
], ['desktop' => ['display' => 'grid', 'gridColumns' => 6, 'gap' => '12px'], 'tablet' => ['gridColumns' => 3], 'mobile' => ['gridColumns' => 2]], $instaCss),
|
||
], ['desktop' => ['padding' => ['top' => '0px', 'bottom' => '128px']], 'tablet' => ['padding' => ['top' => '0px', 'bottom' => '80px']], 'mobile' => ['padding' => ['top' => '0px', 'bottom' => '64px']]]),
|
||
|
||
// 10 – Abschlussband auf Foto.
|
||
$section('band', [
|
||
$column('band-inhalt', [
|
||
$heading('band-titel', 'Bereit für Ihren <em>Gartentraum?</em>', 2, [
|
||
'desktop' => ['fontSize' => '72px', 'fontWeight' => '800', 'lineHeight' => 0.95, 'letterSpacing' => '0px', 'textTransform' => 'uppercase', 'color' => '@papier', 'textAlign' => 'center', 'margin' => ['bottom' => '22px']],
|
||
'tablet' => ['fontSize' => '56px'],
|
||
'mobile' => ['fontSize' => '40px'],
|
||
], $em),
|
||
$text('band-satz', 'Erzählen Sie uns von Ihrem Vorhaben. Wir melden uns und vereinbaren einen Termin bei Ihnen vor Ort.', [
|
||
'desktop' => ['maxWidth' => '560px', 'fontSize' => '19px', 'lineHeight' => 1.6, 'color' => '#d8e6dd', 'textAlign' => 'center', 'margin' => ['left' => 'auto', 'right' => 'auto', 'bottom' => '36px']],
|
||
]),
|
||
$zeile('band-knoepfe', [
|
||
$knopf('band-anfragen', 'Projekt anfragen', '#kontakt', 'rot', false),
|
||
$knopf('band-anrufen', $telefon, $tel, 'weiss', false),
|
||
], '14px', ['flexWrap' => 'wrap', 'justifyContent' => 'center']),
|
||
], ['desktop' => ['maxWidth' => '820px', 'margin' => ['left' => 'auto', 'right' => 'auto']]]),
|
||
], [
|
||
'desktop' => ['backgroundColor' => '#2a372f', 'backgroundImage' => $medien['pool-fachwerk']['id'], 'backgroundSize' => 'cover', 'backgroundPosition' => 'center', 'overlayColor' => '#0c140f', 'overlayOpacity' => 0.72,
|
||
'color' => '@papier', 'padding' => ['top' => '120px', 'bottom' => '120px']],
|
||
'mobile' => ['padding' => ['top' => '72px', 'bottom' => '72px']],
|
||
]),
|
||
|
||
// 11 – Kontakt mit Formular.
|
||
$section('kontakt-bereich', [
|
||
$row('kontakt-zeile', [
|
||
$column('kontakt-text', [
|
||
$kleinzeile('kontakt-label', 'Kontakt'),
|
||
$heading('kontakt-titel', 'Ihr Projekt. Unser Handwerk.', 2, ['desktop' => [...$h2Stil['desktop'], 'margin' => ['top' => '18px', 'bottom' => '24px']], 'tablet' => $h2Stil['tablet'], 'mobile' => $h2Stil['mobile']]),
|
||
$text('kontakt-satz', 'Schreiben Sie uns, rufen Sie an oder schicken Sie eine WhatsApp – wir melden uns zeitnah und vereinbaren einen Termin bei Ihnen vor Ort.', $fliess('18px', '@text', '40px', 1.65)),
|
||
$column('kontakt-karten', [
|
||
$kontaktKarte('kontakt-telefon', 'phone', 'Telefon', $telefon, $tel),
|
||
$kontaktKarte('kontakt-whatsapp', 'chat', 'WhatsApp', $whatsappNummer, $whatsapp),
|
||
$kontaktKarte('kontakt-mail', 'mail', 'E-Mail', $mail, 'mailto:'.$mail),
|
||
$kontaktKarte('kontakt-adresse', 'pin', 'Adresse', $strasse.', '.$plz.' '.$ort, null),
|
||
$kontaktKarte('kontakt-zeiten', 'clock', 'Öffnungszeiten', $zeiten, null),
|
||
], ['desktop' => ['display' => 'flex', 'flexDirection' => 'column', 'gap' => '14px']], $kontaktCss),
|
||
]),
|
||
$column('kontakt-formular', [
|
||
$heading('kontakt-formular-titel', 'Anfrage senden', 3, $h3Stil('30px', '@tinte', '16px')),
|
||
// Umut, 12.10.2026: Angebot innerhalb von 48 Stunden garantiert.
|
||
$karte('kontakt-garantie', [
|
||
$symbol('kontakt-garantie-icon', 'check-circle', 24, ['desktop' => ['color' => '@gruen', 'flexShrink' => 0]]),
|
||
$text('kontakt-garantie-text', '<strong>Ihr Angebot innerhalb von 48 Stunden</strong> nach dem Termin vor Ort – garantiert.', [
|
||
'desktop' => ['fontSize' => '15px', 'lineHeight' => 1.5, 'color' => '@text-stark', 'margin' => ['bottom' => '0px']],
|
||
], '& strong{color:#17221b}'),
|
||
], '10px', ['alignItems' => 'flex-start', 'backgroundColor' => '@chip', 'borderRadius' => $rund('12px'), 'padding' => ['top' => '14px', 'right' => '16px', 'bottom' => '14px', 'left' => '16px'], 'margin' => ['bottom' => '24px']]),
|
||
['id' => 'kontakt-formular-block', 'type' => 'form', 'props' => ['form' => 'anfrage'], 'css' => $formularCss],
|
||
$text('kontakt-formular-hinweis', 'Ihre Angaben verwenden wir nur für Ihre Anfrage. Mehr dazu in unseren <a href="/datenschutz">Datenschutzhinweisen</a>.', [
|
||
'desktop' => ['fontSize' => '13px', 'lineHeight' => 1.6, 'color' => '@text', 'margin' => ['top' => '16px', 'bottom' => '0px']],
|
||
], $linkCss),
|
||
], [
|
||
'desktop' => ['backgroundColor' => '@papier', 'borderRadius' => $rund('24px'), 'padding' => $seiten('48px'), 'shadowY' => '30px', 'shadowBlur' => '60px', 'shadowColor' => '#14201814'],
|
||
'mobile' => ['padding' => $seiten('24px')],
|
||
]),
|
||
], $zweiSpalten, [
|
||
'desktop' => ['gap' => '80px', 'alignItems' => 'flex-start', 'margin' => ['top' => '0px'], 'columnWidths' => '9 11'],
|
||
'tablet' => ['gap' => '48px'],
|
||
]),
|
||
], $abschnitt(), 'kontakt'),
|
||
],
|
||
];
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// 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;
|
||
};
|
||
|
||
// Wie die Vorlage: der Hero-Text steigt beim Laden auf, das Foto nicht (LCP). Weiter unten
|
||
// erscheinen Köpfe und Raster beim Scrollen.
|
||
$startseite = $einblenden($startseite, [
|
||
'hero-titel' => ['rise', 'short'],
|
||
'hero-intro' => ['rise', 'medium'],
|
||
'hero-knoepfe' => ['rise', 'medium'],
|
||
'hero-karte' => ['rise', 'medium'],
|
||
'willkommen-text' => ['rise'],
|
||
'willkommen-collage' => ['rise', 'short'],
|
||
'leistungen-kopf' => ['rise'],
|
||
'leistungen-raster' => ['rise', 'short'],
|
||
'leistungen-chips' => ['fade', 'short'],
|
||
'referenzen-kopf' => ['rise'],
|
||
'referenzen-raster' => ['rise', 'short'],
|
||
'bewertungen-kopf' => ['rise'],
|
||
'bewertungen-raster' => ['rise', 'short'],
|
||
'ablauf-kopf' => ['rise'],
|
||
'schritt-1' => ['rise'],
|
||
'schritt-2' => ['rise', 'short'],
|
||
'schritt-3' => ['rise', 'medium'],
|
||
'schritt-4' => ['rise', 'long'],
|
||
'fuhrpark-karte' => ['rise'],
|
||
'ueber-foto-spalte' => ['rise'],
|
||
'ueber-text' => ['rise', 'short'],
|
||
'video-kopf' => ['rise'],
|
||
'video-rahmen' => ['rise', 'short'],
|
||
'karriere-karte' => ['rise'],
|
||
'social-kopf' => ['rise'],
|
||
'social-raster' => ['fade', 'short'],
|
||
'band-inhalt' => ['rise'],
|
||
'kontakt-text' => ['rise'],
|
||
'kontakt-formular' => ['rise', 'short'],
|
||
]);
|
||
|
||
$homeId = $anlegen('home', $startseite, 'Garten- und Landschaftsbau Hüfner GmbH – Terrassen, Pools, Pflaster & Natursteine', $medien['pool-fachwerk']['id']);
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// Unsere Alpakas (Umut, 12.10.2026: „Für Alpakas eigene Seite – Unsere Alpakas und Bilder“).
|
||
// Die ersten drei Absätze sind Umuts Text, der Rest ergänzt, was die Fotos zeigen.
|
||
// ---------------------------------------------------------------------------------------------
|
||
|
||
$alpakaFoto = static fn (string $id, string $schluessel) => $bild($id, $medien[$schluessel], $medien[$schluessel]['alt'], [
|
||
'desktop' => ['margin' => $seiten('0px'), 'overflow' => 'hidden', 'borderRadius' => $rund('16px')],
|
||
], null, "& img{width:100%;aspect-ratio:3/4;object-fit:cover;border-radius:0;transition:transform .5s cubic-bezier(.2,.7,.2,1)}\n&:hover img{transform:scale(1.04)}", true);
|
||
|
||
$alpakaSeite = $einblenden([
|
||
'schemaVersion' => $SCHEMA, 'title' => 'Unsere Alpakas',
|
||
'description' => 'Mona-Lisa und Magnifique: Seit Juli 2026 leben zwei Alpakas auf dem Firmengelände von Hüfner Garten- und Landschaftsbau in Iffezheim. Kommen Sie vorbei und lernen Sie sie kennen.',
|
||
'blocks' => [
|
||
$section('alpaka-kopf', [
|
||
$row('alpaka-kopf-zeile', [
|
||
$column('alpaka-kopf-text', [
|
||
$kleinzeile('alpaka-label', 'Unsere Alpakas'),
|
||
$heading('alpaka-titel', 'Mona-Lisa & Magnifique', 1, [
|
||
'desktop' => ['fontSize' => '84px', 'fontWeight' => '800', 'lineHeight' => 0.95, 'letterSpacing' => '0px', 'textTransform' => 'uppercase', 'color' => '@tinte', 'margin' => ['top' => '18px', 'bottom' => '18px']],
|
||
'tablet' => ['fontSize' => '64px'],
|
||
'mobile' => ['fontSize' => '46px'],
|
||
]),
|
||
$text('alpaka-hundert', '<mark>100</mark> % flauschig.', ['desktop' => ['fontFamily' => 'heading', 'fontSize' => '40px', 'fontWeight' => '800', 'lineHeight' => 1.1, 'textTransform' => 'uppercase', 'color' => '@tinte', 'margin' => ['bottom' => '28px']], 'mobile' => ['fontSize' => '32px']], $hundertCss),
|
||
$text('alpaka-lead', 'Seit Juli 2026 bereichern zwei bezaubernde Alpakas unseren Arbeitsalltag auf dem Firmengelände: Mona-Lisa und Magnifique.', [
|
||
...$fliess('21px', '@text-stark', '18px', 1.6), 'mobile' => ['fontSize' => '18px'],
|
||
]),
|
||
$text('alpaka-geboren', 'Die beiden wurden im Mai 2025 geboren und freuen sich darauf, von Ihnen entdeckt zu werden.', $fliess('18px', '@text', '18px')),
|
||
$text('alpaka-besuch', 'Schauen Sie bei Ihrem nächsten Besuch auf unserem Firmengelände gerne bei den beiden vorbei und lernen Sie unsere flauschigen Kollegen persönlich kennen!', $fliess('18px', '@text', '0px')),
|
||
]),
|
||
$column('alpaka-kopf-bild', [
|
||
$bild('alpaka-kopf-foto', $medien['alpaka-stall'], $medien['alpaka-stall']['alt'], [
|
||
'desktop' => ['margin' => $seiten('0px'), 'overflow' => 'hidden', 'borderRadius' => $rund('22px'), 'backgroundColor' => '#2a372f', 'imageHeight' => '620px', 'shadowY' => '30px', 'shadowBlur' => '60px', 'shadowColor' => '#14201833'],
|
||
'tablet' => ['imageHeight' => '520px'],
|
||
'mobile' => ['imageHeight' => '420px'],
|
||
], null, '& img{width:100%;object-fit:cover;border-radius:0}'),
|
||
]),
|
||
], $zweiSpalten, [
|
||
'desktop' => ['gap' => '80px', 'alignItems' => 'center', 'margin' => ['top' => '0px']],
|
||
'tablet' => ['gap' => '48px'],
|
||
]),
|
||
], [
|
||
'desktop' => ['padding' => ['top' => '96px', 'bottom' => '112px']],
|
||
'tablet' => ['padding' => ['top' => '72px', 'bottom' => '80px']],
|
||
'mobile' => ['padding' => ['top' => '48px', 'bottom' => '64px']],
|
||
]),
|
||
|
||
$section('alpaka-wissen', [
|
||
$row('alpaka-wissen-zeile', [
|
||
$column('alpaka-wissen-1', [
|
||
$heading('alpaka-wissen-1-titel', 'Zu zweit, nie allein', 2, $h3Stil('28px')),
|
||
$text('alpaka-wissen-1-text', 'Alpakas stammen aus den Anden Südamerikas und leben in der Herde. Allein wäre keines von ihnen glücklich – deshalb sind Mona-Lisa und Magnifique immer zu zweit unterwegs.', $fliess('17px', '@text', '0px', 1.65)),
|
||
]),
|
||
$column('alpaka-wissen-2', [
|
||
$heading('alpaka-wissen-2-titel', 'Neugierig und vorsichtig', 2, $h3Stil('28px')),
|
||
$text('alpaka-wissen-2-text', 'Alpakas sind ruhige Tiere. Wer langsam kommt und leise ist, wird erst neugierig beäugt – und mit etwas Geduld auch begrüßt.', $fliess('17px', '@text', '0px', 1.65)),
|
||
]),
|
||
$column('alpaka-wissen-3', [
|
||
$heading('alpaka-wissen-3-titel', 'Selbst gebaut', 2, $h3Stil('28px')),
|
||
$text('alpaka-wissen-3-text', 'Gehege, Holzzaun und Rasen rund um den Stall hat unser Team selbst angelegt – Garten- und Landschaftsbau für die flauschigsten Kollegen, die wir haben.', $fliess('17px', '@text', '0px', 1.65)),
|
||
]),
|
||
], [], [
|
||
'desktop' => ['display' => 'grid', 'gridColumns' => 3, 'gap' => '48px', 'margin' => ['top' => '0px']],
|
||
'tablet' => ['gridColumns' => 1, 'gap' => '32px'],
|
||
'mobile' => ['gridColumns' => 1, 'gap' => '28px'],
|
||
]),
|
||
], $abschnitt('@papier')),
|
||
|
||
$section('alpaka-galerie-bereich', [
|
||
$column('alpaka-galerie-kopf', [
|
||
$kleinzeile('alpaka-galerie-label', 'Bilder', '@rot', true),
|
||
$heading('alpaka-galerie-titel', 'Die beiden im Porträt', 2, ['desktop' => [...$h2Stil['desktop'], 'textAlign' => 'center', 'margin' => ['top' => '18px', 'bottom' => '0px']], 'tablet' => $h2Stil['tablet'], 'mobile' => $h2Stil['mobile']]),
|
||
], ['desktop' => ['maxWidth' => '760px', 'margin' => ['left' => 'auto', 'right' => 'auto', 'bottom' => '48px']], 'mobile' => ['margin' => ['bottom' => '32px']]]),
|
||
['id' => 'alpaka-galerie', 'type' => 'gallery', 'props' => ['lightbox' => true], 'children' => [
|
||
$alpakaFoto('alpaka-galerie-1', 'alpaka-blume'),
|
||
$alpakaFoto('alpaka-galerie-2', 'alpaka-rasen'),
|
||
$alpakaFoto('alpaka-galerie-3', 'alpaka-wiese'),
|
||
$alpakaFoto('alpaka-galerie-4', 'alpaka-zaunbau'),
|
||
], 'style' => ['desktop' => ['gridColumns' => 4, 'gap' => '16px', 'margin' => ['top' => '0px']], 'tablet' => ['gridColumns' => 2], 'mobile' => ['gridColumns' => 2, 'gap' => '10px']]],
|
||
], $abschnitt()),
|
||
|
||
// Das Plakat ist quer und voller Schrift: ganz zeigen statt in die Galerie schneiden.
|
||
$section('alpaka-band', [
|
||
$row('alpaka-band-zeile', [
|
||
$column('alpaka-band-inhalt', [
|
||
$heading('alpaka-band-titel', 'Kommen Sie <em>vorbei!</em>', 2, [
|
||
'desktop' => ['fontSize' => '64px', 'fontWeight' => '800', 'lineHeight' => 0.95, 'letterSpacing' => '0px', 'textTransform' => 'uppercase', 'color' => '@papier', 'margin' => ['bottom' => '20px']],
|
||
'mobile' => ['fontSize' => '40px'],
|
||
], $em),
|
||
$text('alpaka-band-satz', 'Sie finden Mona-Lisa und Magnifique bei uns auf dem Firmengelände, '.$strasse.' in '.$plz.' '.$ort.'.', [
|
||
'desktop' => ['maxWidth' => '480px', 'fontSize' => '19px', 'lineHeight' => 1.6, 'color' => '#d8e6dd', 'margin' => ['bottom' => '32px']],
|
||
]),
|
||
$zeile('alpaka-band-knoepfe', [
|
||
$knopf('alpaka-route', 'Route planen', $google, 'rot', true, ['mobile' => ['width' => '100%', 'justifyContent' => 'center']]),
|
||
$knopf('alpaka-start', 'Zur Startseite', '/', 'linie', false, ['mobile' => ['width' => '100%', 'justifyContent' => 'center']]),
|
||
], '14px', ['flexWrap' => 'wrap']),
|
||
]),
|
||
$column('alpaka-band-plakat', [
|
||
$bild('alpaka-plakat-bild', $medien['alpaka-plakat'], $medien['alpaka-plakat']['alt'], [
|
||
'desktop' => ['margin' => $seiten('0px'), 'overflow' => 'hidden', 'borderRadius' => $rund('18px'), 'shadowY' => '24px', 'shadowBlur' => '50px', 'shadowColor' => '#00000059'],
|
||
], null, '& img{width:100%;height:auto;border-radius:0}', true),
|
||
]),
|
||
], $zweiSpalten, [
|
||
'desktop' => ['gap' => '64px', 'alignItems' => 'center', 'margin' => ['top' => '0px']],
|
||
'tablet' => ['gap' => '40px'],
|
||
]),
|
||
], [
|
||
'desktop' => ['backgroundColor' => '@nacht', 'color' => '@papier', 'padding' => ['top' => '104px', 'bottom' => '104px']],
|
||
'mobile' => ['padding' => ['top' => '64px', 'bottom' => '64px']],
|
||
]),
|
||
],
|
||
], [
|
||
'alpaka-kopf-text' => ['rise', 'short'],
|
||
'alpaka-wissen-zeile' => ['rise'],
|
||
'alpaka-galerie-kopf' => ['rise'],
|
||
'alpaka-galerie' => ['fade', 'short'],
|
||
'alpaka-band-inhalt' => ['rise'],
|
||
'alpaka-band-plakat' => ['rise', 'short'],
|
||
]);
|
||
$alpakaId = $anlegen('alpakas', $alpakaSeite, 'Unsere Alpakas Mona-Lisa & Magnifique | '.$kurz, $medien['alpaka-stall']['id']);
|
||
|
||
/** Eine Rechtsseite: Kleinzeile, Titel und Vorspann auf Nachtgrü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, '@papier'),
|
||
$heading($praefix.'-h1', $titel, 1, [
|
||
'desktop' => ['fontSize' => '72px', 'fontWeight' => '800', 'lineHeight' => 0.95, 'letterSpacing' => '0px', 'textTransform' => 'uppercase', 'color' => '@papier', 'margin' => ['top' => '18px', 'bottom' => '20px']],
|
||
'mobile' => ['fontSize' => '46px'],
|
||
]),
|
||
$text($praefix.'-vorspann', $vorspann, [
|
||
'desktop' => ['maxWidth' => '640px', 'fontSize' => '18px', 'lineHeight' => 1.7, 'color' => '@auf-dunkel', 'margin' => ['bottom' => '0px']],
|
||
'mobile' => ['fontSize' => '16px'],
|
||
]),
|
||
], [
|
||
'desktop' => ['backgroundColor' => '@nacht', 'color' => '@papier', 'padding' => ['top' => '88px', 'bottom' => '72px']],
|
||
'mobile' => ['padding' => ['top' => '56px', 'bottom' => '48px']],
|
||
]);
|
||
|
||
$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' => ['fontSize' => '22px', 'fontWeight' => '800', 'lineHeight' => 1.2, 'letterSpacing' => '0.02em', 'textTransform' => 'uppercase', 'color' => '@tinte', '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' => '@linie', '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' => '96px']], 'mobile' => ['padding' => ['top' => '24px', 'bottom' => '64px']]]);
|
||
|
||
return [$kopf, $inhalt];
|
||
};
|
||
|
||
$anlegen('impressum', [
|
||
'schemaVersion' => $SCHEMA, 'title' => 'Impressum',
|
||
'description' => 'Anbieterangaben der Garten- und Landschaftsbau Hüfner GmbH, Iffezheim.',
|
||
'blocks' => [
|
||
...$rechtsseite('impressum', 'Rechtliche Angaben', 'Impressum', 'Angaben gemäß § 5 DDG und § 18 Abs. 2 MStV.', [
|
||
['Anbieter', $firma.'<br>'.$strasse.'<br>'.$plz.' '.$ort],
|
||
// Von Umut am 06.10.2026: HRB 742159, Mannheim. Laut Netzwerkauszug ist heute Florian
|
||
// Hüfner mit der GmbH verbunden, Simone Hüfner nur früher.
|
||
['Vertreten durch', 'Geschäftsführer: Florian Hüfner'],
|
||
['Registereintrag', 'Eintragung im Handelsregister<br>Registergericht: Amtsgericht Mannheim<br>Registernummer: HRB 742159'],
|
||
['Kontakt', 'Telefon: <a href="'.$tel.'">'.$telefon.'</a><br>E-Mail: <a href="mailto:'.$mail.'">'.$mail.'</a>'],
|
||
// Eine USt-IdNr. gehört nur hinein, wenn es eine gibt; weder Register noch North Data
|
||
// nennen eine (06.10.2026). Falls doch: hier als ['Umsatzsteuer', '… nach § 27a UStG: DE…'].
|
||
['Verantwortlich für den Inhalt', 'Florian Hüfner, 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.'],
|
||
['Haftung für Links', 'Diese Website verweist auf unsere Profile bei Instagram und Facebook, auf WhatsApp, auf unseren Eintrag bei Google Maps und bindet nach Einwilligung ein Video von YouTube ein. Für deren Inhalte ist der jeweilige Anbieter verantwortlich.'],
|
||
['Urheberrecht', 'Die von uns erstellten Inhalte, Fotos 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', 'Alle Fotos sind eigene Aufnahmen der '.$firma.'. Logo: '.$firma.'. Signet „Ihre Experten für Garten & Landschaft“: Bundesverband Garten-, Landschafts- und Sportplatzbau e. V.; PQ-Logo: Verein für die Präqualifikation von Bauunternehmen e. V.'],
|
||
]),
|
||
],
|
||
], 'Impressum | '.$kurz);
|
||
|
||
$anlegen('datenschutz', [
|
||
'schemaVersion' => $SCHEMA, 'title' => 'Datenschutz',
|
||
'description' => 'Welche Daten beim Besuch der Website der Garten- und Landschaftsbau Hüfner GmbH und bei einer Anfrage verarbeitet werden.',
|
||
'blocks' => [
|
||
...$rechtsseite('datenschutz', 'Ihre Daten', 'Datenschutz', 'Hier erfahren Sie, welche Daten beim Besuch dieser Website und bei einer Anfrage verarbeitet werden – und welche Rechte Sie haben.', [
|
||
['1. Verantwortlicher', $firma.', '.$strasse.', '.$plz.' '.$ort.'. Telefon <a href="'.$tel.'">'.$telefon.'</a>, E-Mail <a href="mailto:'.$mail.'">'.$mail.'</a>.'],
|
||
['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.<br><br>Unser Hostinganbieter ist die IONOS SE, Elgendorfer Straße 57, 56410 Montabaur. Mit IONOS 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.<br><br>Einzige Ausnahme ist unser Imagevideo auf der Startseite. Es liegt bei YouTube (Google Ireland Limited, Gordon House, Barrow Street, Dublin 4, Irland) und wird im erweiterten Datenschutzmodus (youtube-nocookie.com) eingebunden – aber erst, wenn Sie externe Inhalte erlauben. Vorher wird nichts von YouTube geladen. Mit dem Laden erfährt Google Ihre IP-Adresse und dass Sie diese Seite aufrufen; dabei können Daten auch in die USA übermittelt werden. Rechtsgrundlage ist Ihre Einwilligung, Art. 6 Abs. 1 lit. a DSGVO und § 25 Abs. 1 TDDDG.<br><br>Ihre Entscheidung speichern wir in einem technisch notwendigen Cookie, damit wir Sie nicht bei jedem Seitenaufruf erneut fragen. Sie können sie jederzeit über „Einwilligung ändern“ unten auf jeder Seite widerrufen.'],
|
||
['4. Anfrageformular', 'Wenn Sie uns über das Formular schreiben, verarbeiten wir Ihren Namen, Ihre E-Mail-Adresse, auf Wunsch Ihre Telefonnummer und Ihre Beschreibung, um Ihre Anfrage zu beantworten und ein Angebot vorzubereiten (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 anrufen oder schreiben, verarbeiten wir Ihre Angaben, um Ihr Anliegen zu bearbeiten (Art. 6 Abs. 1 lit. b oder f DSGVO).'],
|
||
['6. Instagram, Facebook, WhatsApp und Google', 'Auf dieser Seite stehen nur Links zu unseren Profilen bei Instagram und Facebook, zu WhatsApp und zu unserem Eintrag bei Google Maps. Erst wenn Sie einem Link folgen, gelangen Sie zum jeweiligen Anbieter (Meta Platforms Ireland Ltd. bzw. Google Ireland Limited); dort gelten dessen Datenschutzhinweise. Schreiben Sie uns per WhatsApp, verarbeiten wir Ihre Nachricht und Ihre Telefonnummer, um Ihr Anliegen oder Ihre Bewerbung zu bearbeiten (Art. 6 Abs. 1 lit. b DSGVO).'],
|
||
['7. 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.'],
|
||
['8. 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', 'Hier wächst <em>noch nichts.</em>', 1, [
|
||
'desktop' => ['fontSize' => '80px', 'fontWeight' => '800', 'lineHeight' => 0.95, 'letterSpacing' => '0px', 'textTransform' => 'uppercase', 'color' => '@tinte', 'margin' => ['top' => '18px', 'bottom' => '22px']],
|
||
'mobile' => ['fontSize' => '48px'],
|
||
], $emGruen),
|
||
$text('fehler-text', 'Diese Seite gibt es nicht oder sie wurde verschoben. Über die Startseite finden Sie zurück.', [
|
||
'desktop' => ['maxWidth' => '480px', 'fontSize' => '18px', 'lineHeight' => 1.7, 'color' => '@text', 'margin' => ['bottom' => '32px']],
|
||
]),
|
||
$knopf('fehler-knopf', 'Zur Startseite', '/', 'rot', true),
|
||
], [
|
||
'desktop' => ['padding' => ['top' => '120px', 'bottom' => '140px']],
|
||
'mobile' => ['padding' => ['top' => '64px', 'bottom' => '80px']],
|
||
]),
|
||
],
|
||
]);
|
||
$db->executeStatement('UPDATE page SET robots = ? WHERE id = ?', ['noindex', $fehlerId]);
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// Menü, Einstellungen, Konto
|
||
// ---------------------------------------------------------------------------------------------
|
||
|
||
$sprung = static fn (string $label, string $anchor): array => ['label' => $label, 'type' => 'page', 'pageId' => $homeId, 'anchor' => $anchor, 'children' => []];
|
||
$menus->save('header', 'Hauptnavigation', [
|
||
$sprung('Leistungen', 'leistungen'),
|
||
$sprung('Referenzen', 'projekte'),
|
||
$sprung('Über uns', 'ueber-uns'),
|
||
$sprung('Karriere', 'karriere'),
|
||
['label' => 'Alpakas', 'type' => 'page', 'pageId' => $alpakaId, 'anchor' => '', 'children' => []],
|
||
$sprung('Kontakt', 'kontakt'),
|
||
], 0);
|
||
echo "Menü angelegt\n";
|
||
|
||
$settings->assignNotFoundPage($fehlerId, '');
|
||
$settings->save([
|
||
'legalName' => $firma,
|
||
'description' => 'Garten- und Landschaftsbau auf Premium-Niveau aus Iffezheim: Terrassen, Pools, Pflaster, Natursteine und komplette Außenanlagen – geplant, gebaut und übergeben aus einer Hand.',
|
||
'phone' => '+49 7229 6992826', 'email' => $mail,
|
||
'street' => $strasse, 'postalCode' => $plz, 'city' => $ort, 'country' => 'DE',
|
||
'social' => ['instagram' => $instagram, 'facebook' => $facebook],
|
||
'areaServed' => implode("\n", ['Iffezheim', 'Rastatt', 'Baden-Baden', 'Karlsruhe', 'Mittelbaden']),
|
||
'openingHours' => $zeitenDaten,
|
||
'localBusiness' => true, 'llmsTxt' => true,
|
||
'faviconMediaId' => $medien['favicon']['id'],
|
||
'stickyHeader' => true, 'headerOverlay' => false,
|
||
'motion' => 'always',
|
||
'homePath' => 'home', 'imprintPath' => 'impressum', 'privacyPath' => 'datenschutz',
|
||
// Seit 12.10.2026 gibt es das YouTube-Video: Es lädt erst nach Einwilligung, deshalb fragt die
|
||
// Seite jetzt – klein in der Ecke statt als Wand vor dem Inhalt.
|
||
'consentBanner' => true, 'consentGroups' => ['external'], 'consentLayout' => 'ecke', 'consentShape' => 'rund',
|
||
'consentTitle' => 'Video von YouTube?',
|
||
'consentText' => 'Unser Imagevideo liegt bei YouTube. Es wird erst geladen, wenn Sie externe Inhalte erlauben – sonst lädt diese Seite nichts von Dritten.',
|
||
'consentColor' => '@papier', 'consentInk' => '@tinte', 'consentAccent' => '@gruen', 'consentAccentInk' => '@papier',
|
||
'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"]']);
|
||
|
||
echo "\nFertig.\nVerwaltung: Benutzer „umut“, Passwort: $passwort\n";
|