From 6390c0087612f3492e893341818d4da98f523685 Mon Sep 17 00:00:00 2001 From: Umut Kantarci Date: Mon, 12 Oct 2026 13:56:16 +0200 Subject: [PATCH] =?UTF-8?q?Rework=20the=20H=C3=BCfner=20site=20after=20the?= =?UTF-8?q?=20customer=20meeting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- README.md | 2 +- scripts/build-huefner-assets.php | 38 +- scripts/reset-huefner.sh | 7 +- scripts/seed-huefner.php | 733 +++++++++++++++++++++++-------- 4 files changed, 592 insertions(+), 188 deletions(-) diff --git a/README.md b/README.md index 29885d4..6e458dc 100644 --- a/README.md +++ b/README.md @@ -397,7 +397,7 @@ Die Seite „Kantarci Markt – Neueröffnung“ (Wilhelm-Drapp-Straße, Baden-B ### Neunte lokale Referenzwebsite: Hüfner Gartenbau -Die Website der Garten- und Landschaftsbau Hüfner GmbH (Iffezheim) liegt in `var/huefner.db` mit Medien in `var/huefner-media`; die Startkonfiguration `huefner` in `.claude/launch.json` nutzt Port 8102, `huefner-prod` denselben Bestand im Produktionsmodus auf Port 8103 zum Messen. `sh scripts/reset-huefner.sh [Entwurfsordner]` kopiert die Fotos aus dem gelieferten Entwurf nach `var/huefner-assets` und schneidet den Baum aus dem Logo als Seitensymbol (`scripts/build-huefner-assets.php`; ohne Argument nimmt es die schon vorbereiteten Bilder), wirft die Installation weg, baut mit `scripts/seed-huefner.php` Gestaltung, Kopf- und Fußbereich, Startseite, Impressum, Datenschutz, 404, Menü, Einstellungen und das Formular `anfrage` auf und holt Barlow und Barlow Condensed auf den eigenen Server. Gemessen mit `MSYS_NO_PATHCONV=1 CMS_URL=http://127.0.0.1:8103 CMS_PATHS=/,/impressum,/datenschutz npm run audit`. Die bewussten Abweichungen vom Entwurf stehen im Kopf des Skripts. +Die Website der Garten- und Landschaftsbau Hüfner GmbH (Iffezheim) liegt in `var/huefner.db` mit Medien in `var/huefner-media`; die Startkonfiguration `huefner` in `.claude/launch.json` nutzt Port 8102, `huefner-prod` denselben Bestand im Produktionsmodus auf Port 8103 zum Messen. `sh scripts/reset-huefner.sh [Entwurfsordner Unterlagenordner]` kopiert die Fotos aus dem gelieferten Entwurf und Umuts Unterlagen (Alpaka-Fotos, PQ-Logo, Galabau-Signet, PQ-Bescheinigung und Freistellungsbescheinigungen als PDF, unter festen Namen) nach `var/huefner-assets` und schneidet den Baum aus dem Logo als Seitensymbol (`scripts/build-huefner-assets.php`; ohne Argumente nimmt es die schon vorbereiteten Dateien), wirft die Installation weg, baut mit `scripts/seed-huefner.php` Gestaltung, Kopf- und Fußbereich, Startseite, Alpakaseite, Impressum, Datenschutz, 404, Menü, Einstellungen und das Formular `anfrage` auf und holt Barlow und Barlow Condensed auf den eigenen Server. Gemessen mit `MSYS_NO_PATHCONV=1 CMS_URL=http://127.0.0.1:8103 CMS_PATHS=/,/alpakas CMS_AUDIT_DIR=reports/lighthouse-huefner-termin node scripts/audit.mjs`. Die Startseite liegt knapp unter dem HTML-Budget von 150 KB aus `test:browser`: wiederkehrende Bausteine tragen ihre Gestaltung deshalb am Behälter. Die bewussten Abweichungen vom Entwurf stehen im Kopf des Skripts. Zusätzliche Prüfung mit den lokalen Kant-IT-Inhalten: `node scripts/admin-quality-check.mjs` (PHP über `PHP_BINARY` wählbar). Das Skript benötigt `var/kantit.db` und `var/kantit-media`, erstellt eine eigene Datenbankkopie und ein zufälliges Testkonto darin; es ändert die Pilotdatenbank nicht. Screenshots und Bericht landen in `reports/admin-quality/`. diff --git a/scripts/build-huefner-assets.php b/scripts/build-huefner-assets.php index 780c192..deffab3 100644 --- a/scripts/build-huefner-assets.php +++ b/scripts/build-huefner-assets.php @@ -7,7 +7,12 @@ * den Baum mit den Steinen als quadratisches Seitensymbol (favicon.png) heraus. Der Baum ist im * Logo links, der Schriftzug rechts; ein Favicon mit Schrift wäre in 32 px unlesbar. * - * Aufruf: php scripts/build-huefner-assets.php + * Aufruf: php scripts/build-huefner-assets.php [] + * + * Das dritte Argument ist Umuts Ordner „unterlagenfrwebsite“ (12.10.2026): Alpaka-Fotos, PQ-Logo, + * Galabau-Signet und die drei PDFs. Sie bekommen hier feste, einfache Namen, damit das Seed-Skript + * nicht an „Freistellungsbescheinigung §48 … bis 11.05.2029.pdf“ hängt. Kommt eine neue + * Bescheinigung, genügt es, sie nach dem gleichen Muster benannt in den Ordner zu legen. */ $quelle = rtrim($argv[1] ?? '', '/\\'); $ziel = rtrim($argv[2] ?? '', '/\\'); @@ -43,3 +48,34 @@ $zh = (int) round($h * $massstab); imagecopyresampled($symbol, $logo, (int) (($kante - $zb) / 2), (int) (($kante - $zh) / 2), $x, $y, $zb, $zh, $b, $h); imagepng($symbol, $ziel.'/favicon.png'); echo "Bilder kopiert, Seitensymbol geschnitten: $ziel\n"; + +$unterlagen = rtrim($argv[3] ?? '', '/\\'); +if ('' === $unterlagen) { + exit(0); +} +// Muster im Ordner => fester Name. Bei mehreren Treffern gewinnt der letzte in Namensreihenfolge. +$feste = [ + 'Alpaka (1).jpeg' => 'alpaka-plakat.jpg', + 'Alpaka (2).jpeg' => 'alpaka-wiese.jpg', + 'Alpaka (3).jpeg' => 'alpaka-stall.jpg', + 'Alpaka (4).jpeg' => 'alpaka-blume.jpg', + 'Alpaka (6).jpeg' => 'alpaka-zaunbau.jpg', + 'Alpaka (9).jpeg' => 'alpaka-rasen.jpg', + 'PQ-Logo*.png' => 'pq-logo.png', + 'Signum*.png' => 'galabau-signet.png', + '100-emoji.png' => 'hundert.png', + 'PQ-Bescheinigung*.pdf' => 'pq-bescheinigung.pdf', + 'Freistellungsbescheinigung §13*.pdf' => 'freistellung-13b.pdf', + 'Freistellungsbescheinigung §48*.pdf' => 'freistellung-48b.pdf', +]; +$dateien = scandir($unterlagen) ?: []; +foreach ($feste as $muster => $name) { + $treffer = array_values(array_filter($dateien, static fn (string $d): bool => fnmatch($muster, $d, FNM_NOESCAPE))); + if ([] === $treffer) { + fwrite(STDERR, "Fehlt in den Unterlagen: $muster\n"); + exit(1); + } + sort($treffer); + copy($unterlagen.'/'.end($treffer), $ziel.'/'.$name); +} +echo 'Unterlagen übernommen: '.count($feste)." Dateien\n"; diff --git a/scripts/reset-huefner.sh b/scripts/reset-huefner.sh index 0b9838a..8664d31 100644 --- a/scripts/reset-huefner.sh +++ b/scripts/reset-huefner.sh @@ -1,6 +1,7 @@ #!/bin/sh # Wirft die Hüfner-Installation weg und baut sie neu auf. Nur fuer den Aufbau gedacht. # Erstes Argument: der entpackte Entwurf (Ordner mit index.html und images/). +# Zweites Argument: Umuts Unterlagen (Alpakas, PQ, Freistellungsbescheinigungen, Signet). set -e root="C:/Django Project/KantSites" php="${PHP_BINARY:-C:/php84/php.exe}" @@ -8,10 +9,10 @@ export DATABASE_URL="sqlite:///$root/var/huefner.db" export MEDIA_DIRECTORY="$root/var/huefner-media" assets="$root/var/huefner-assets" if [ -n "$1" ]; then - "$php" scripts/build-huefner-assets.php "$1" "$assets" + "$php" scripts/build-huefner-assets.php "$1" "$assets" "$2" fi -if [ ! -f "$assets/favicon.png" ]; then - echo "Bilder fehlen: erst mit dem Ordner des Entwurfs als Argument aufrufen." >&2 +if [ ! -f "$assets/favicon.png" ] || [ ! -f "$assets/pq-bescheinigung.pdf" ]; then + echo "Bilder fehlen: erst mit dem Ordner des Entwurfs und den Unterlagen als Argumente aufrufen." >&2 exit 1 fi rm -f "$root/var/huefner.db" diff --git a/scripts/seed-huefner.php b/scripts/seed-huefner.php index 0cf002b..8cdb6fb 100644 --- a/scripts/seed-huefner.php +++ b/scripts/seed-huefner.php @@ -7,8 +7,8 @@ * 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 #2F6B22, Rot #B71C22, Nacht #14201A, Hellgrün - * #F3F8EF) und Schriften (Barlow, Barlow Condensed) sind daraus übernommen. Gebaut ist alles aus + * 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. * @@ -29,6 +29,23 @@ * 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 (100). + * - 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. @@ -36,6 +53,7 @@ require dirname(__DIR__).'/config/bootstrap.php'; +use App\Content\DocumentLibrary; use App\Content\DocumentValidator; use App\Content\FormRepository; use App\Content\MediaLibrary; @@ -84,6 +102,12 @@ $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'; @@ -208,7 +232,7 @@ $flaeche = static function (string $id, array $style, string $css = '', string $ return $block; }; -$bild = static function (string $id, array $eintrag, string $alt, array $style = [], ?string $href = null, string $css = ''): array { +$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'], @@ -218,6 +242,8 @@ $bild = static function (string $id, array $eintrag, string $alt, array $style = 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) { @@ -241,8 +267,8 @@ $knopf = static function (string $id, string $inhalt, string $href, string $art $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:#17221a;\n&:hover{background:#eef5e9;color:#17221a}\n", - 'dunkel' => "background:#17221a;color:#fff;\n&:hover{background:#2f6b22;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" @@ -296,22 +322,22 @@ $h3Stil = static fn (string $groesse, string $farbe = '@tinte', string $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:#8fd16f}'; -$linkCss = "& a{color:#2f6b22;text-decoration:underline;text-underline-offset:3px}\n& a:hover{color:#1f4a16}"; -$emGruen = '& em{font-style:normal;color:#2f6b22}'; +$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. */ -$kleinzeile = static function (string $id, string $inhalt, string $farbe = '@rot', bool $beidseitig = false, string $unten = '0px') use ($text, $zeile, $flaeche): array { - $strich = static fn (string $sid): array => $flaeche($sid, ['desktop' => ['width' => '32px', 'height' => '2px', 'backgroundColor' => $farbe, 'flexShrink' => 0, 'margin' => ['top' => '0px', 'right' => '0px', 'bottom' => '0px', 'left' => '0px']]], 'min-height:0'); - $kinder = [$strich($id.'-strich'), $text($id.'-text', $inhalt, ['desktop' => [ +/** + * 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, 'margin' => ['top' => '0px', 'bottom' => '0px'], - ]])]; - if ($beidseitig) { - $kinder[] = $strich($id.'-strich-2'); - } - - return $zeile($id, $kinder, '12px', ['justifyContent' => $beidseitig ? 'center' : 'flex-start', 'margin' => ['top' => '0px', 'bottom' => $unten]]); + '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). */ @@ -321,7 +347,7 @@ $kreis = static fn (string $id, string $icon, string $groesse = '48px') => ['id' // 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 #2f6b22;outline-offset:3px}"; +$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 @@ -331,20 +357,20 @@ $db->insert('website', ['id' => 'default', 'name' => $kurz, 'default_locale' => $theme->save( [ - ['key' => 'gruen', 'name' => 'Grün (Akzent)', 'value' => '#2f6b22'], - ['key' => 'gruen-dunkel', 'name' => 'Grün dunkel (Links beim Überfahren)', 'value' => '#1f4a16'], - ['key' => 'hellgruen', 'name' => 'Hellgrün (auf Dunkel)', 'value' => '#8fd16f'], + ['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' => '#17221a'], - ['key' => 'nacht', 'name' => 'Nachtgrün (Leiste, Referenzen)', 'value' => '#14201a'], - ['key' => 'fuss', 'name' => 'Fuß', 'value' => '#0f1813'], - ['key' => 'mint', 'name' => 'Hellgrün (Seitengrund)', 'value' => '#f3f8ef'], - ['key' => 'chip', 'name' => 'Hellgrün (Flächen)', 'value' => '#eef5e9'], - ['key' => 'linie', 'name' => 'Linien', 'value' => '#dce8d4'], + ['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' => '#3a483d'], - ['key' => 'auf-dunkel', 'name' => 'Schrift auf Dunkel', 'value' => '#b9c7b5'], + ['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'], ], [ @@ -374,6 +400,17 @@ foreach ([ '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)) { @@ -386,6 +423,29 @@ foreach ([ echo "Bild übernommen: $datei\n"; } +// Das „💯“ von Hüfners Instagram steht überall, wo „100“ steht – im Text, als 100: +// 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 // --------------------------------------------------------------------------------------------- @@ -394,7 +454,6 @@ $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' => 'leistung', 'type' => 'select', 'label' => 'Leistung', 'required' => true, 'options' => ['Pool', 'Terrasse', 'Pflaster', 'Natursteine', 'Komplette Außenanlage', 'Sonstiges']], ['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); @@ -404,10 +463,10 @@ 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:#8fd16f}"; +$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' => '#8fcb7c']]), - $text($id.'-text', $inhalt, ['desktop' => ['fontSize' => '14px', 'lineHeight' => 1.4, 'color' => '#c9d6c5', 'margin' => ['bottom' => '0px']]], $leistenLink), + $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 = [ @@ -421,12 +480,12 @@ $kopf = [ $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' => '#c9d6c5', 'gap' => '24px']], + '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' => '#c9d6c5', 'padding' => ['top' => '9px', 'bottom' => '9px']], + 'desktop' => ['backgroundColor' => '@nacht', 'color' => '#c5d6cb', 'padding' => ['top' => '9px', 'bottom' => '9px']], 'mobile' => ['padding' => ['top' => '7px', 'bottom' => '7px']], ]), $section('kopf-haupt', [ @@ -445,19 +504,19 @@ $kopf = [ // 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:#17221a}\n" - ."& .menu-list a::after{content:'';position:absolute;left:0;right:100%;bottom:0;height:2px;background:#2f6b22;transition:right .25s ease}\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 #dce8d4;box-shadow:0 18px 30px rgba(20,32,26,.12);font-size:22px}\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 #e6efe0}\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 #d3e3ca}\n& a:hover{background:#eef5e9;color:#2f6b22}", + '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']]), @@ -476,7 +535,7 @@ $kopf['blocks'][1]['children'][0]['children'][2]['children'][1]['css'] = str_rep // Fußbereich // --------------------------------------------------------------------------------------------- -$fussLink = "& a{color:#b9c7b5;text-decoration:none;transition:color .2s}\n& a:hover{color:#fff}"; +$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'], ]]); @@ -496,7 +555,7 @@ $fuss = [ $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 #2c3a2f}\n& a:hover{color:#fff;box-shadow:inset 0 0 0 1px #8fd16f}"], + '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'), @@ -504,7 +563,16 @@ $fuss = [ ]), $column('fuss-unternehmen', [ $fussTitel('fuss-unternehmen-titel', 'Unternehmen'), - $fussListe('fuss-unternehmen-liste', [['Über uns', '/#ueber-uns'], ['Referenzen', '/#projekte'], ['Ablauf', '/#ablauf'], ['Karriere', '/#karriere'], ['Kontakt', '/#kontakt']]), + $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'), @@ -513,7 +581,7 @@ $fuss = [ ], $fussLink."\n& a{overflow-wrap:anywhere}"), ]), ], [ - 'desktop' => ['display' => 'grid', 'gridColumns' => 4, 'gap' => '48px', 'padding' => ['top' => '80px', 'bottom' => '48px']], + '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']], ]), @@ -525,7 +593,7 @@ $fuss = [ 'css' => $fussLink], $text('fuss-agentur', 'Website by Kant-IT Solutions', ['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' => '#22302a', 'padding' => ['top' => '24px', 'bottom' => '36px']], + ], '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']], @@ -550,30 +618,35 @@ $kachel = static function (string $id, array $foto, string $alt, array $inhalt, 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, ['desktop' => ['position' => 'absolute', 'top' => '0px', 'right' => '0px', 'bottom' => '0px', 'left' => '0px', 'margin' => $seiten('0px'), 'zIndex' => 0]], $href), + $bild($id.'-foto', $foto, $alt, [], $href), ...$inhalt, ], [ - 'desktop' => ['position' => 'relative', 'overflow' => 'hidden', 'display' => 'flex', 'flexDirection' => 'column', 'justifyContent' => 'flex-end', 'backgroundColor' => '#2a372c', 'color' => '@papier', + '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]), - ], "transition:box-shadow .3s ease;\n&:hover{box-shadow:0 18px 40px rgba(20,30,22,.22)}\n&:hover img{transform:scale(1.06)}\n&:focus-within img{transform:scale(1.06)}".($mehr ? "\n& .block-row{position:relative;pointer-events:none;transition:gap .25s ease}\n&:hover .block-row{gap:14px}" : '')); + ], $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-image img{width:100%;height:100%;object-fit:cover;border-radius:0;transition:transform .7s cubic-bezier(.2,.7,.2,1)}\n" +$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 #8fd16f;outline-offset:-8px}\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' => '#dce6d8', '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' => '@hellgruen', 'margin' => ['bottom' => '2px']]]); +$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 { @@ -584,66 +657,123 @@ $vorteil = static function (int $n, array $icon, string $titel, string $satz) us return $zeile('vorteil-'.$n, [ $icon, - $text('vorteil-'.$n.'-text', ''.$titel.''.$satz, [ - 'desktop' => ['fontSize' => '14px', 'lineHeight' => 1.45, 'color' => '@auf-dunkel', 'margin' => ['bottom' => '0px']], - 'mobile' => ['fontSize' => '12.5px'], - ], "& strong{display:block;margin-bottom:2px;font-size:16px;color:#fff}"), + $text('vorteil-'.$n.'-text', ''.$titel.''.$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, ['desktop' => ['color' => '@hellgruen', 'flexShrink' => 0]]); +$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'); -$haken = static fn (string $id, string $inhalt) => $zeile($id, [ - $symbol($id.'-icon', 'check', 24, ['desktop' => ['color' => '@gruen', 'flexShrink' => 0]]), - $text($id.'-text', $inhalt, ['desktop' => ['fontSize' => '16px', 'fontWeight' => '600', 'lineHeight' => 1.4, 'color' => '@tinte', 'margin' => ['bottom' => '0px']]]), -], '10px'); +// 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}"; -$chip = static fn (string $id, string $inhalt) => $text($id, $inhalt, [ - 'desktop' => ['fontSize' => '15px', 'fontWeight' => '600', 'lineHeight' => 1.3, 'color' => '@tinte', 'backgroundColor' => '@chip', 'borderRadius' => $rund('999px'), 'padding' => ['top' => '10px', 'right' => '18px', 'bottom' => '10px', 'left' => '18px'], 'margin' => ['bottom' => '0px']], -], "transition:background-color .2s ease,color .2s ease;\n&:hover{background-color:#2f6b22;color:#fff}"); +// 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, ''.$inhalt.''); +// 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, string $farbe = '@gruen') => $column('schritt-'.$n, [ - $text('schritt-'.$n.'-nummer', sprintf('%02d', $n), [ - 'desktop' => ['display' => 'flex', 'alignItems' => 'center', 'justifyContent' => 'center', 'width' => '72px', 'height' => '72px', 'margin' => ['top' => '0px', 'right' => 'auto', 'bottom' => '24px', 'left' => 'auto'], - 'borderRadius' => $rund('50%'), 'backgroundColor' => $farbe, 'color' => '@papier', 'fontFamily' => 'heading', 'fontSize' => '28px', 'fontWeight' => '800', 'lineHeight' => 1.0], - ], 'position:relative;z-index:1;box-shadow:0 0 0 10px #f3f8ef;transition:transform .3s cubic-bezier(.2,.7,.2,1)'), - $heading('schritt-'.$n.'-titel', $titel, 3, $h3Stil('26px')), - $text('schritt-'.$n.'-satz', $satz, ['desktop' => ['maxWidth' => '260px', 'fontSize' => '16px', 'lineHeight' => 1.6, 'color' => '@text', 'margin' => ['top' => '0px', 'right' => 'auto', 'bottom' => '0px', 'left' => 'auto']]]), -], ['desktop' => ['textAlign' => 'center']], '&:hover [id="schritt-'.$n.'-nummer"]{transform:scale(1.08)}'); +$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, ''.$titel.''.$satz, [ - 'desktop' => ['fontSize' => '14px', 'lineHeight' => 1.5, 'color' => '@text', 'backgroundColor' => '@mint', 'borderRadius' => $rund('12px'), 'padding' => $seiten('18px'), 'margin' => ['bottom' => '0px']], -], "& strong{display:block;font-size:16px;color:#17221a;hyphens:auto;overflow-wrap:break-word}"); +$geraet = static fn (string $id, string $titel, string $satz) => $text($id, ''.$titel.''.$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}"; -$kennzahl = static fn (string $id, string $zahl, string $satz, bool $linie) => $column($id, [ - $text($id.'-zahl', $zahl, ['desktop' => ['fontFamily' => 'heading', 'fontSize' => '52px', 'fontWeight' => '800', 'lineHeight' => 1.0, 'color' => '@gruen', 'margin' => ['bottom' => '6px']], 'mobile' => ['fontSize' => '40px']]), - $text($id.'-satz', $satz, ['desktop' => ['fontSize' => '15px', 'lineHeight' => 1.4, 'color' => '@text', 'margin' => ['bottom' => '0px']]]), +// 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']]] : []); -$stelle = static fn (string $id, string $titel, string $satz, string $betreff) => $karte($id, [ - $text($id.'-text', ''.$titel.'
'.$satz, [ - 'desktop' => ['fontSize' => '14px', 'lineHeight' => 1.5, 'color' => '#cdebbe', 'margin' => ['bottom' => '0px']], - ], '& strong{font-size:18px;color:#fff}'), - $symbol($id.'-pfeil', 'arrow-right', 20, ['desktop' => ['color' => '@papier', 'flexShrink' => 0]]), -], '16px', [ - 'justifyContent' => 'space-between', 'backgroundColor' => '#ffffff1f', 'borderWidth' => $seiten('1px'), 'borderStyle' => 'solid', 'borderColor' => '#ffffff33', 'borderRadius' => $rund('14px'), - 'padding' => ['top' => '18px', 'right' => '22px', 'bottom' => '18px', 'left' => '22px'], -], [], [], $ganzeKarte."\ntransition:background-color .2s ease;\n&:hover{background-color:rgba(255,255,255,.2)}\n& .block-icon{transition:transform .25s ease}\n&:hover .block-icon{transform:translateX(4px)}"); +/** + * 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 + * 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.'.'); -$kontaktKarte = static fn (string $id, string $icon, string $klein, string $wert, ?string $href) => $karte($id, [ - $kreis($id.'-kreis', $icon), - $text($id.'-text', $klein.'
'.(null === $href ? $wert : ''.$wert.'').'', [ - 'desktop' => ['fontSize' => '13px', 'lineHeight' => 1.5, 'color' => '#6a766c', 'minWidth' => '0', 'margin' => ['bottom' => '0px']], - ], '& strong{display:block;font-size:18px;color:#17221a;overflow-wrap:anywhere}'), -], '16px', [ - 'backgroundColor' => '@papier', 'borderRadius' => $rund('14px'), 'padding' => ['top' => '16px', 'right' => '18px', 'bottom' => '16px', 'left' => '18px'], - 'shadowY' => '6px', 'shadowBlur' => '18px', 'shadowColor' => '#14201a0d', -], [], [], null === $href ? '' : $ganzeKarte."\ntransition:transform .2s ease,box-shadow .2s ease;\n&:hover{transform:translateY(-2px);box-shadow:0 14px 30px rgba(20,32,26,.1)}"); + return '
' + .'' + .''.$titel.''.$zeile.'' + .'' + .'
    ' + .implode('', array_map(static fn (string $p): string => '
  • '.$p.'
  • ', $punkte)) + .'

Per WhatsApp bewerben Anrufen: '.$telefon.'

'; +}; +$helm = ''; +$lkwBild = ''; -$instaBild = static fn (string $id, array $foto, string $was) => $bild($id, $foto, $was.' – Beiträge auf Instagram ansehen', ['desktop' => ['margin' => $seiten('0px'), 'borderRadius' => $rund('12px'), 'overflow' => 'hidden']], $instagram, - "& img{width:100%;aspect-ratio:1;object-fit:cover;border-radius:0;transition:transform .5s cubic-bezier(.2,.7,.2,1)}\n&:hover img{transform:scale(1.05)}\n& .image-link:focus-visible{outline:3px solid #2f6b22;outline-offset:-6px}"); +// 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.'
'.(null === $href ? $wert : ''.$wert.'').''), +]); +// 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ß. @@ -656,15 +786,14 @@ $formularCss = '& .block-form{max-width:none;grid-template-columns:repeat(2,minm & .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:#17221a} -& .form-field input{background:#f8fbf6;border:1px solid #cfe0c5;border-radius:10px;padding:14px 16px;font-size:16px;color:#17221a} -& .form-field select{background:#f8fbf6;border:1px solid #cfe0c5;border-radius:10px;padding:14px 16px;font-size:16px;color:#17221a} -& .form-field textarea{background:#f8fbf6;border:1px solid #cfe0c5;border-radius:10px;padding:14px 16px;font-size:16px;color:#17221a;min-height:140px} -& .form-field input:focus{border-color:#2f6b22} -& .form-field textarea:focus{border-color:#2f6b22} +& .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:#2f6b22} +& .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}'; @@ -690,8 +819,8 @@ $startseite = [ '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 100 % Detailarbeit.', [ - 'desktop' => ['maxWidth' => '580px', 'fontSize' => '21px', 'lineHeight' => 1.55, 'color' => '#e3ebe0', 'margin' => ['top' => '28px', 'bottom' => '40px']], + $text('hero-intro', 'Terrassen, Pools, Pflaster, Natursteine und komplette Außenanlagen – geplant, gebaut und übergeben aus einer Hand. Mit 100 % 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', [ @@ -704,34 +833,34 @@ $startseite = [ $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
'.$telefon.'', ['desktop' => ['fontSize' => '13px', 'lineHeight' => 1.45, 'color' => '@text', 'margin' => ['bottom' => '0px']]], '& strong{display:block;font-size:20px;color:#17221a}'), + $text('hero-karte-nummer', 'Rufen Sie uns an
'.$telefon.'', ['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', 'Mo–Fr 8–17 Uhr · Sa 8–12 Uhr', ['desktop' => ['fontSize' => '14px', 'lineHeight' => 1.5, 'color' => '@text', 'borderWidth' => $nurOben('1px'), 'borderStyle' => 'solid', 'borderColor' => '#e3ecdd', 'padding' => ['top' => '14px'], 'margin' => ['top' => '14px', 'bottom' => '0px']]], '& strong{color:#17221a}'), + $text('hero-karte-hinweis', 'Mo–Fr 8–17 Uhr · Sa 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'), '100 % Detailarbeit', 'Saubere Ausführung'), + $vorteil(2, $vorteilIcon(2, 'star'), '100 % 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' => '#2a372c', + '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}"), + ], 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', [ @@ -748,24 +877,24 @@ $startseite = [ $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]]), + ], ['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' => '#2a372c', - 'shadowY' => '30px', 'shadowBlur' => '60px', 'shadowColor' => '#14201a2e'], + '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' => '#2a372c', 'shadowY' => '30px', 'shadowBlur' => '60px', 'shadowColor' => '#14201a33'], + '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', '100 %', ['desktop' => ['fontFamily' => 'heading', 'fontSize' => '48px', 'fontWeight' => '800', 'lineHeight' => 1.0, 'color' => '@papier', 'margin' => ['bottom' => '2px']], 'mobile' => ['fontSize' => '38px']]), - $text('collage-abzeichen-text', 'Detailarbeit', ['desktop' => ['fontSize' => '15px', 'fontWeight' => '600', 'color' => '#dcefd3', 'margin' => ['bottom' => '0px']]]), + $text('collage-abzeichen-zahl', '100 %', ['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' => '@gruen', 'borderRadius' => $rund('16px'), 'padding' => ['top' => '20px', 'right' => '24px', 'bottom' => '20px', 'left' => '24px'], - 'shadowY' => '20px', 'shadowBlur' => '40px', 'shadowColor' => '#14201a40'], + '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']]), @@ -773,7 +902,7 @@ $startseite = [ 'desktop' => ['gap' => '96px', 'alignItems' => 'center', 'margin' => ['top' => '0px']], 'tablet' => ['gap' => '48px'], ]), - ], $abschnitt()), + ], $abschnitt(), null, $hundertCss), // 3 – Leistungen: Kopf mit Satz rechts, links die große Pool-Kachel, rechts vier kleine, // darunter weitere Leistungen als Chips. @@ -801,20 +930,20 @@ $startseite = [ ], '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', [ - $kachelTitel('leistung-terrasse-titel', 'Terrassen'), - $kachelSatz('leistung-terrasse-satz', 'Keramik, Naturstein, Holz oder WPC.'), + $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', [ - $kachelTitel('leistung-pflaster-titel', 'Pflaster'), - $kachelSatz('leistung-pflaster-satz', 'Einfahrten, Wege und Plätze.'), + $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', [ - $kachelTitel('leistung-naturstein-titel', 'Natursteine'), - $kachelSatz('leistung-naturstein-satz', 'Mauern, Stufen, Einfassungen.'), + $heading('leistung-naturstein-titel', 'Natursteine', 3), + $text('leistung-naturstein-satz', 'Mauern, Stufen, Einfassungen.'), ], '24px', '#kontakt'), $kachel('leistung-aussen', $medien['rasen'], 'Außenanlagen – Beratung anfragen', [ - $kachelTitel('leistung-aussen-titel', 'Außenanlagen'), - $kachelSatz('leistung-aussen-satz', 'Rasen, Bepflanzung, Gesamtanlage.'), + $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']]]), @@ -827,14 +956,14 @@ $startseite = [ $chip('chip-bewaesserung', 'Bewässerung'), $chip('chip-entwaesserung', 'Entwässerung & Rinnen'), $chip('chip-spielplaetze', 'Spielplätze'), - ], '12px', ['flexWrap' => 'wrap', 'margin' => ['top' => '40px']]), - ], $abschnitt('@papier'), 'leistungen', $kachelFlaeche), + ], '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', '@hellgruen'), + $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, [])), @@ -849,25 +978,57 @@ $startseite = [ ], '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', [ - $kachelKlein('referenz-terrasse-klein', 'Pool · Naturstein'), - $kachelTitel('referenz-terrasse-titel', 'Pool & Terrasse', '26px'), + $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', [ - $kachelKlein('referenz-zaun-klein', 'Zaun'), - $kachelTitel('referenz-zaun-titel', 'Sichtschutz', '26px'), + $text('referenz-zaun-klein', 'Zaun'), + $heading('referenz-zaun-titel', 'Sichtschutz', 3), ], '24px', null, [], '55%'), $kachel('referenz-hang', $medien['pflaster'], 'Hanggarten mit Natursteinmauer und Treppe', [ - $kachelKlein('referenz-hang-klein', 'Hanggarten'), - $kachelTitel('referenz-hang-titel', 'Mauer & Treppe', '26px'), + $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' => '@hellgruen', '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' => '#c9d6c5', 'margin' => ['bottom' => '0px']], 'mobile' => ['flexBasis' => 'auto']]), + $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), + ], $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', '100 % Qualität.
100 % Zufriedenheit.', 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', '4,9 von 5 Sternen 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', [ @@ -877,17 +1038,23 @@ $startseite = [ ], ['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,#9dc38c 0 10px,transparent 10px 18px)", 'desktop'), + "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.'), - $schritt(2, 'Planung & Angebot', 'Sie erhalten ein klares Konzept und ein transparentes Angebot.'), + // 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 – innerhalb von 48 Stunden nach dem Termin, garantiert.', [ + $text('schritt-2-downloads', 'Freistellung § 13b UStG (PDF)
Freistellung § 48b EStG (PDF)', [ + '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.', '@rot'), + $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'), @@ -898,20 +1065,21 @@ $startseite = [ $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' => '#2a372c']]), + ], ['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'), - $geraet('geraet-bagger', 'Bagger bis 15 t', 'inkl. Tieflader'), + // „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']]), + ], ['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' => '#14201a14']]), + ], ['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. @@ -919,11 +1087,11 @@ $startseite = [ $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' => '#2a372c', 'imageHeight' => '560px'], + '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. 100 %.', [ - 'desktop' => ['position' => 'absolute', 'left' => '-1px', 'bottom' => '32px', 'backgroundColor' => '@rot', 'color' => '@papier', 'fontFamily' => 'heading', 'fontSize' => '22px', 'fontWeight' => '800', 'letterSpacing' => '0.06em', 'textTransform' => 'uppercase', 'lineHeight' => 1.2, + $text('ueber-abzeichen', 'Miteinander. 100 %.', [ + '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'], ]), @@ -939,30 +1107,92 @@ $startseite = [ // 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', [ - $kennzahl('zahl-detail', '100 %', 'Detailarbeit', false), - $kennzahl('zahl-bagger', '15 t', 'Bagger im eigenen Fuhrpark', true), - $kennzahl('zahl-team', '1', 'Team von Planung bis Übergabe', true), - ], ['desktop' => ['display' => 'grid', 'gridColumns' => 3, 'gap' => '0px', 'borderWidth' => $nurOben('1px'), 'borderStyle' => 'solid', 'borderColor' => '@linie', 'padding' => ['top' => '28px']], 'mobile' => ['gridColumns' => 3]], 'column-gap:24px'), + // 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', '100 %', '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', 'Mitglied im Fachverband der Landschaftsgärtner und präqualifiziert nach PQ-VOB (Nr. 101.003531) für Erd-, Landschaftsbau- und Pflasterarbeiten.
PQ-Bescheinigung herunterladen (PDF)', [ + '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'), + ], $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' => '#cdebbe', 'margin' => ['bottom' => '0px']]]), + $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? Dann melde dich bei uns – ein Anruf genügt.', $fliess('17px', '#e1f0da', '0px', 1.6)), + $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', [ - $stelle('stelle-helfer', 'Mitarbeiter & Helfer (m/w/d)', 'Vollzeit · Garten- und Landschaftsbau', 'Mitarbeiter und Helfer (m/w/d)'), - $stelle('stelle-lkw', 'LKW-Fahrer C/CE (m/w/d)', 'Vollzeit · Fuhrpark', 'LKW-Fahrer C/CE (m/w/d)'), - $knopf('karriere-anrufen', 'Jetzt anrufen: '.$telefon, $tel, 'weiss', false, ['desktop' => ['alignSelf' => 'flex-start', 'margin' => ['top' => '6px']], 'mobile' => ['fontSize' => '16px', 'width' => '100%', 'justifyContent' => 'center']]), - ], ['desktop' => ['flexGrow' => 1, 'flexBasis' => '300px', 'display' => 'flex', 'flexDirection' => 'column', 'gap' => '12px'], 'mobile' => ['flexBasis' => 'auto']]), + ['id' => 'karriere-karten', 'type' => 'html', 'props' => ['html' => '
' + .$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)') + .'
'], 'css' => $stellenCss], + // Aus Hüfners eigener Stellenanzeige auf Instagram („Was dich erwartet“). + $text('karriere-vorteile', 'Was dich erwartet: 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'], @@ -978,7 +1208,7 @@ $startseite = [ ]), $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:#17221a;color:#fff;font-family:var(--kf-heading);font-size:17px;font-weight:700;letter-spacing:0.06em}\n& a:hover{background:#2f6b22;box-shadow:0 10px 24px rgba(0,0,0,.18)}"], + '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']), @@ -992,7 +1222,7 @@ $startseite = [ $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]]), + ], ['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. @@ -1004,7 +1234,7 @@ $startseite = [ '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' => '#dce6d8', 'textAlign' => 'center', 'margin' => ['left' => 'auto', 'right' => 'auto', 'bottom' => '36px']], + '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), @@ -1012,7 +1242,7 @@ $startseite = [ ], '14px', ['flexWrap' => 'wrap', 'justifyContent' => 'center']), ], ['desktop' => ['maxWidth' => '820px', 'margin' => ['left' => 'auto', 'right' => 'auto']]]), ], [ - 'desktop' => ['backgroundColor' => '#2a372c', 'backgroundImage' => $medien['pool-fachwerk']['id'], 'backgroundSize' => 'cover', 'backgroundPosition' => 'center', 'overlayColor' => '#0c140f', 'overlayOpacity' => 0.72, + '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']], ]), @@ -1023,22 +1253,30 @@ $startseite = [ $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 oder rufen Sie direkt an – wir melden uns zeitnah und vereinbaren einen Termin bei Ihnen vor Ort.', $fliess('18px', '@text', '40px', 1.65)), + $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']]), + ], ['desktop' => ['display' => 'flex', 'flexDirection' => 'column', 'gap' => '14px']], $kontaktCss), ]), $column('kontakt-formular', [ - $heading('kontakt-formular-titel', 'Anfrage senden', 3, $h3Stil('30px', '@tinte', '20px')), + $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', 'Ihr Angebot innerhalb von 48 Stunden 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 Datenschutzhinweisen.', [ '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' => '#14201a14'], + 'desktop' => ['backgroundColor' => '@papier', 'borderRadius' => $rund('24px'), 'padding' => $seiten('48px'), 'shadowY' => '30px', 'shadowBlur' => '60px', 'shadowColor' => '#14201814'], 'mobile' => ['padding' => $seiten('24px')], ]), ], $zweiSpalten, [ @@ -1082,6 +1320,8 @@ $startseite = $einblenden($startseite, [ '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'], @@ -1090,6 +1330,8 @@ $startseite = $einblenden($startseite, [ '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'], @@ -1100,10 +1342,130 @@ $startseite = $einblenden($startseite, [ $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', '100 % 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 vorbei!', 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, '@hellgruen'), + $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'], @@ -1163,9 +1525,9 @@ $anlegen('impressum', [ ['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. Für deren Inhalte ist der jeweilige Anbieter verantwortlich.'], + ['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.'.'], + ['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); @@ -1177,10 +1539,10 @@ $anlegen('datenschutz', [ ...$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 '.$telefon.', E-Mail '.$mail.'.'], ['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.

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; es werden keine Karten, Videos oder Schriften von Dritten nachgeladen. Deshalb gibt es auch keinen Einwilligungsdialog.'], - ['4. Anfrageformular', 'Wenn Sie uns über das Formular schreiben, verarbeiten wir Ihren Namen, Ihre E-Mail-Adresse, auf Wunsch Ihre Telefonnummer, die gewählte Leistung 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.'], + ['3. Cookies und externe Dienste', 'Diese Website setzt keine Analyse- oder Werbecookies. Bilder, Schriften und Gestaltung kommen vom eigenen Server.

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.

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 und Facebook', 'Auf dieser Seite stehen nur Links zu unseren Profilen. Erst wenn Sie einem Link folgen, gelangen Sie zum jeweiligen Anbieter (Meta Platforms Ireland Ltd.); dort gelten dessen Datenschutzhinweise.'], + ['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 '.$mail.'.'], @@ -1218,9 +1580,9 @@ $sprung = static fn (string $label, string $anchor): array => ['label' => $label $menus->save('header', 'Hauptnavigation', [ $sprung('Leistungen', 'leistungen'), $sprung('Referenzen', 'projekte'), - $sprung('Ablauf', 'ablauf'), $sprung('Über uns', 'ueber-uns'), $sprung('Karriere', 'karriere'), + ['label' => 'Alpakas', 'type' => 'page', 'pageId' => $alpakaId, 'anchor' => '', 'children' => []], $sprung('Kontakt', 'kontakt'), ], 0); echo "Menü angelegt\n"; @@ -1239,8 +1601,13 @@ $settings->save([ 'stickyHeader' => true, 'headerOverlay' => false, 'motion' => 'always', 'homePath' => 'home', 'imprintPath' => 'impressum', 'privacyPath' => 'datenschutz', - // Die Seite lädt nichts von Dritten; deshalb keine Einwilligungsleiste. - 'consentBanner' => false, 'scrollTop' => true, 'postMetaHeader' => false, + // 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";