Add the Becker Just In Time site build
CMS quality / php (8.2) (push) Waiting to run
CMS quality / php (8.4) (push) Waiting to run
CMS quality / browser (push) Waiting to run
CMS quality / mysql (push) Waiting to run

Seed, asset and reset scripts for Becker - Just In Time e. Kfm.
(Wiernsheim): logo cut from the PDF raster, logo-free photos generated
with ChatGPT, before/after slider, service pages, contact form, legal
pages. Launch configs on ports 8116/8117, Manrope 800 and Montserrat
700/800 fetched locally.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
kantarciandClaude Opus 5.5 committed 2026-11-10 10:03:16 +01:00
1 parent c90c03084b
commit 9e3862093e
12 files changed
+1315 -4

No files matched your search

+20
View File
@@ -229,6 +229,26 @@
],
"port": 8107
},
{
"name": "becker",
"runtimeExecutable": "powershell",
"runtimeArgs": [
"-NoProfile",
"-Command",
"$env:APP_ENV='dev'; $env:DATABASE_URL='sqlite:///C:/Django Project/KantSites/var/becker.db'; $env:MEDIA_DIRECTORY='C:/Django Project/KantSites/var/becker-media'; $env:SITE_ORIGIN='http://127.0.0.1:8116'; & C:/php84/php.exe -d upload_max_filesize=8M -d post_max_size=10M -d memory_limit=256M -S 127.0.0.1:8116 -t public bin/dev-router.php"
],
"port": 8116
},
{
"name": "becker-prod",
"runtimeExecutable": "powershell",
"runtimeArgs": [
"-NoProfile",
"-Command",
"$env:APP_ENV='prod'; $env:APP_DEBUG='0'; $env:DATABASE_URL='sqlite:///C:/Django Project/KantSites/var/becker.db'; $env:MEDIA_DIRECTORY='C:/Django Project/KantSites/var/becker-media'; $env:SITE_ORIGIN='http://127.0.0.1:8117'; & C:/php84/php.exe -d upload_max_filesize=8M -d post_max_size=10M -d memory_limit=256M -S 127.0.0.1:8117 -t public bin/dev-router.php"
],
"port": 8117
},
{
"name": "luximmo",
"runtimeExecutable": "powershell",
+12
View File
@@ -1,5 +1,17 @@
# Projektstatus
## Neue Installation „Becker – Just In Time e. Kfm.“, Wiernsheim (10.11.2026)
**Anlass:** Umut lieferte `Becker-Just-In-Time-Version-5.zip` (ChatGPT-Sites-Entwurf), `Logo_Becker_original.pdf` und sieben Werbemotive mit Logos: „nachbauen, an die CI halten, eigenen Senf dazugeben, Bilder entlogolisieren und von ChatGPT ohne Logos nachbauen lassen“.
**Umgesetzt:** Die Fotos hat ChatGPT in Umuts Konto neu erzeugt (11 Motive, `var/becker-quelle/ki`, Originale in `var/becker-quelle/original`); die Asbestmotive zeigen dabei Schutzanzug und Atemschutz. `scripts/build-becker-assets.php` stellt das Logo aus dem PDF-Rasterbild frei (Kopf quer, Fuß gestapelt, drei Leistungssymbole, Favicon). `sh scripts/reset-becker.sh [var/becker-quelle]` baut alles neu: Navy #172234 + Logo-Gold #F4B43A, Montserrat/Manrope, Startseite mit Hero, Leistungskarten, Vorher/Nachher-Regler (Plugin „vergleich“), Ablauf, Einblicke-Galerie, Team, Seiten `/gartenbau`, `/zaunbau`, `/asbestsanierung`, `/kontakt` (Formular `anfrage` in zwei Schritten), Impressum, Datenschutz, 404, Paket `var/becker-paket.zip`. Port 8116 (dev) / 8117 (prod), Passwort in `var/becker-zugang.txt`.
**Geprüft:** Lighthouse im Produktionsmodus für `/`, `/gartenbau`, `/asbestsanierung`, `/kontakt`: Leistung mobil 97–98, Desktop 99–100; Barrierefreiheit, Best Practices, SEO, Agentic Browsing je 100. Ganzseitige Aufnahmen 1440 und 390 px angesehen. Gold-Streifen als Inline-SVG (vorher CSS-Verlauf, sah treppig aus). Nicht geprüft: `test:browser`, Formularversand, Bedienung des Reglers per Tastatur von Hand.
**Kern:** neues Symbol `house` (geschlossene Hausform, Tür ausgespart) in `public/assets/icons.js`, per `node scripts/build-icons.mjs` in Twig und Validator; Becker nutzt es als Startseiten-Punkt im Menü. `tests/icons.test.mjs` und PHPUnit-Filter „Icon“ (26) grün. Das Becker-Paket braucht deshalb einen Kern mit diesem Symbol (nächstes Release).
**Offen:** Impressum – „e. Kfm.“ verlangt Registergericht und HRA-Nummer (fehlen); die im Entwurf als USt-IdNr. genannte „48116/36926“ ist eine Steuernummer und steht deshalb nicht drin. E-Mail: Entwurf `info@becker-justintime.de`, Logo-PDF `Becker-Justintime@web.de` – verwendet ist die aus dem Entwurf. Fotos sind KI-Symbolbilder (so im Bildnachweis). Domain/Hosting unbekannt.
## Fassung 1.39.1: SVG statt Schriftzeichen auf der Website, Luximmo-Immobilien (09.11.2026)
**Anlass:** Umut: „nur eigene SVGs nutzen“. Bildbetrachter, PDF-Betrachter, Nach-oben-Knopf, Video-Knopf, Menüpfeile, Brotkrumen, Weiterlesen, Blättern und Zitatsterne sind SVG (Masken in `site.css`, sonst eingebettet).
+8 -4
View File
@@ -39,10 +39,6 @@
/* latin */
@font-face{font-family:'DM Sans';font-style:normal;font-weight:400 700;font-display:swap;src:url(/assets/fonts/dm-sans-400-latin.woff2) format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
/* latin-ext */
@font-face{font-family:'Manrope';font-style:normal;font-weight:400 700;font-display:swap;src:url(/assets/fonts/manrope-400-latin-ext.woff2) format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF}
/* latin */
@font-face{font-family:'Manrope';font-style:normal;font-weight:400 700;font-display:swap;src:url(/assets/fonts/manrope-400-latin.woff2) format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
/* latin-ext */
@font-face{font-family:'Lora';font-style:normal;font-weight:500 600;font-display:swap;src:url(/assets/fonts/lora-500-latin-ext.woff2) format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF}
/* latin */
@font-face{font-family:'Lora';font-style:normal;font-weight:500 600;font-display:swap;src:url(/assets/fonts/lora-500-latin.woff2) format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@@ -50,3 +46,11 @@
@font-face{font-family:'Instrument Sans';font-style:normal;font-weight:400 600;font-display:swap;src:url(/assets/fonts/instrument-sans-400-latin-ext.woff2) format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF}
/* latin */
@font-face{font-family:'Instrument Sans';font-style:normal;font-weight:400 600;font-display:swap;src:url(/assets/fonts/instrument-sans-400-latin.woff2) format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
/* latin-ext */
@font-face{font-family:'Manrope';font-style:normal;font-weight:400 800;font-display:swap;src:url(/assets/fonts/manrope-400-latin-ext.woff2) format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF}
/* latin */
@font-face{font-family:'Manrope';font-style:normal;font-weight:400 800;font-display:swap;src:url(/assets/fonts/manrope-400-latin.woff2) format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
/* latin-ext */
@font-face{font-family:'Montserrat';font-style:normal;font-weight:700 800;font-display:swap;src:url(/assets/fonts/montserrat-700-latin-ext.woff2) format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF}
/* latin */
@font-face{font-family:'Montserrat';font-style:normal;font-weight:700 800;font-display:swap;src:url(/assets/fonts/montserrat-700-latin.woff2) format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+129
View File
@@ -0,0 +1,129 @@
<?php
/**
* Bereitet Logo und Fotos von „Becker – Just In Time e. Kfm.“ für die Mediathek des Nachbaus vor.
*
* Quellordner (var/becker-quelle):
* - logo-original.png: das Rasterbild aus Logo_Becker_original.pdf (1536 × 1024, Navy-Grund #172234),
* - ki/*.png: die von ChatGPT ohne Logos neu erzeugten Fotos (10.11.2026, nach Umuts Vorlagen).
*
* Daraus entstehen im Zielordner:
* - logo.png: Bagger-Signet links, „BECKER / JUST IN TIME e. Kfm.“ rechts – für den Kopf,
* - logo-gross.png: das Logo gestapelt mit den drei Leistungssymbolen – für den Fuß,
* - favicon.png: das Bagger-Signet auf Navy, 192 × 192,
* - je Foto eine WebP-Datei (Startbild 1920 px, sonst 1600 px breit).
*
* Der Navy-Grund des Logos wird weich freigestellt: Deckkraft aus dem Farbabstand zum Grund,
* die Kantenfarbe zurückgerechnet. So passt das Logo auf jede dunkle Fläche.
*
* Aufruf: php scripts/build-becker-assets.php <Quellordner> <Zielordner>
*/
$quelle = rtrim($argv[1] ?? '', '/\\');
$ziel = rtrim($argv[2] ?? '', '/\\');
if ('' === $quelle || '' === $ziel || !is_file($quelle.'/logo-original.png')) {
fwrite(STDERR, "Aufruf: php scripts/build-becker-assets.php <Ordner mit logo-original.png und ki/> <Zielordner>\n");
exit(1);
}
if (!is_dir($ziel) && !mkdir($ziel, 0775, true)) {
fwrite(STDERR, "Zielordner lässt sich nicht anlegen: $ziel\n");
exit(1);
}
$logo = imagecreatefrompng($quelle.'/logo-original.png');
$grund = [23, 34, 52];
/** Ein Ausschnitt des Logos mit freigestelltem Navy-Grund. */
$freistellen = static function (int $x, int $y, int $b, int $h) use ($logo, $grund): GdImage {
$bild = imagecreatetruecolor($b, $h);
imagealphablending($bild, false);
imagesavealpha($bild, true);
for ($j = 0; $j < $h; ++$j) {
for ($i = 0; $i < $b; ++$i) {
$c = imagecolorat($logo, $x + $i, $y + $j);
$p = [$c >> 16 & 0xFF, $c >> 8 & 0xFF, $c & 0xFF];
$abstand = max(abs($p[0] - $grund[0]), abs($p[1] - $grund[1]), abs($p[2] - $grund[2]));
$a = min(1.0, max(0.0, ($abstand - 6) / 90));
if ($a <= 0.0) {
imagesetpixel($bild, $i, $j, imagecolorallocatealpha($bild, 0, 0, 0, 127));
continue;
}
$f = array_map(static fn (int $k): int => (int) max(0, min(255, round($grund[$k] + ($p[$k] - $grund[$k]) / $a))), [0, 1, 2]);
imagesetpixel($bild, $i, $j, imagecolorallocatealpha($bild, $f[0], $f[1], $f[2], (int) round(127 * (1 - $a))));
}
}
return $bild;
};
/** Verkleinert mit Durchsichtigkeit. */
$verkleinern = static function (GdImage $bild, int $breite): GdImage {
$hoehe = (int) round(imagesy($bild) * $breite / imagesx($bild));
$neu = imagecreatetruecolor($breite, $hoehe);
imagealphablending($neu, false);
imagesavealpha($neu, true);
imagefill($neu, 0, 0, imagecolorallocatealpha($neu, 0, 0, 0, 127));
imagecopyresampled($neu, $bild, 0, 0, 0, 0, $breite, $hoehe, imagesx($bild), imagesy($bild));
return $neu;
};
// Bänder im Original (gemessen): Signet y 15–455 / x 360–1196, „BECKER“ y 466–636,
// „JUST IN TIME e. Kfm.“ mit Linien y 647–701 / x 162–1382, Leistungssymbole y 722–897.
$signet = $freistellen(352, 8, 852, 456);
$schrift = $freistellen(154, 458, 1236, 252);
// Querformat für den Kopf: Signet links (so hoch wie die Schrift plus etwas), Schrift rechts.
$sh = 330;
$sb = (int) round(852 * $sh / 456);
$quer = imagecreatetruecolor($sb + 36 + 1236, $sh);
imagealphablending($quer, false);
imagesavealpha($quer, true);
imagefill($quer, 0, 0, imagecolorallocatealpha($quer, 0, 0, 0, 127));
imagealphablending($quer, true);
imagecopyresampled($quer, $signet, 0, 0, 0, 0, $sb, $sh, 852, 456);
imagecopy($quer, $schrift, $sb + 36, (int) round(($sh - 252) / 2) + 10, 0, 0, 1236, 252);
imagealphablending($quer, false);
imagepng($verkleinern($quer, 760), $ziel.'/logo.png', 9);
echo 'logo.png: 760 × '.(int) round($sh * 760 / imagesx($quer))."\n";
$gross = $freistellen(100, 8, 1336, 898);
imagepng($verkleinern($gross, 560), $ziel.'/logo-gross.png', 9);
echo "logo-gross.png: 560 px breit\n";
// Die drei Leistungssymbole des Logos (Blätter, Zaun, Dach) einzeln, quadratisch, 128 px.
foreach (['symbol-garten' => [250, 716, 186, 120], 'symbol-zaun' => [679, 716, 176, 124], 'symbol-asbest' => [1071, 716, 226, 120]] as $name => [$x, $y, $b, $h]) {
$teil = $freistellen($x, $y, $b, $h);
$q = max($b, $h);
$quadrat = imagecreatetruecolor(128, 128);
imagealphablending($quadrat, false);
imagesavealpha($quadrat, true);
imagefill($quadrat, 0, 0, imagecolorallocatealpha($quadrat, 0, 0, 0, 127));
$f = 128 / $q;
imagecopyresampled($quadrat, $teil, (int) round((128 - $b * $f) / 2), (int) round((128 - $h * $f) / 2), 0, 0, (int) round($b * $f), (int) round($h * $f), $b, $h);
imagepng($quadrat, $ziel.'/'.$name.'.png', 9);
echo "$name.png: 128 × 128\n";
}
// Seitensymbol: Signet mittig auf Navy.
$fav = imagecreatetruecolor(192, 192);
imagefill($fav, 0, 0, imagecolorallocate($fav, ...$grund));
imagecopyresampled($fav, $logo, 14, 22 + (int) round((148 - 164 * 440 / 836) / 2), 360, 15, 164, (int) round(164 * 440 / 836), 836, 440);
imagepng($fav, $ziel.'/favicon.png', 9);
echo "favicon.png: 192 × 192\n";
// Fotos: ChatGPT liefert PNG; als WebP auf Zielbreite.
foreach (['haus' => 1920, 'team' => 1600, 'zaun' => 1600, 'zauntor' => 1600, 'asbest' => 1600, 'dach' => 1600,
'bagger' => 1600, 'terrasse' => 1600, 'zisterne' => 1600, 'vorher' => 1600, 'nachher' => 1600] as $name => $breite) {
$pfad = $quelle.'/ki/'.$name.'.png';
if (!is_file($pfad)) {
fwrite(STDERR, "Foto fehlt: $pfad\n");
exit(1);
}
$bild = imagecreatefrompng($pfad);
$breite = min($breite, imagesx($bild));
$hoehe = (int) round(imagesy($bild) * $breite / imagesx($bild));
$neu = imagecreatetruecolor($breite, $hoehe);
imagecopyresampled($neu, $bild, 0, 0, 0, 0, $breite, $hoehe, imagesx($bild), imagesy($bild));
imagewebp($neu, $ziel.'/'.$name.'.webp', 80);
echo "$name.webp: $breite × $hoehe\n";
}
+31
View File
@@ -0,0 +1,31 @@
#!/bin/sh
# Wirft die Becker-Installation (Becker – Just In Time e. Kfm., Wiernsheim) weg und baut sie neu auf.
# Nur fuer den Aufbau gedacht.
# Erstes Argument (optional): Quellordner mit logo-original.png (Rasterbild aus Logo_Becker_original.pdf)
# und ki/*.png (die ohne Logos neu erzeugten Fotos). Dann entstehen Logo, Symbole und WebP-Fotos neu.
set -e
root="C:/Django Project/KantSites"
php="${PHP_BINARY:-C:/php84/php.exe}"
export DATABASE_URL="sqlite:///$root/var/becker.db"
export MEDIA_DIRECTORY="$root/var/becker-media"
assets="$root/var/becker-assets"
if [ -n "$1" ]; then
"$php" scripts/build-becker-assets.php "$1" "$assets"
fi
if [ ! -f "$assets/haus.webp" ] || [ ! -f "$assets/logo.png" ]; then
echo "Bilder fehlen: erst mit dem Quellordner (z. B. var/becker-quelle) als Argument aufrufen." >&2
exit 1
fi
rm -f "$root/var/becker.db"
rm -rf "$root/var/becker-media"
"$php" vendor/bin/doctrine-migrations migrate --no-interaction -q
"$php" -d memory_limit=512M scripts/seed-becker.php "$assets"
# Manrope und Montserrat vom eigenen Server; der Abruf ergaenzt fonts.css nur.
if [ -f "$root/var/cacert.pem" ]; then
"$php" -d curl.cainfo="$root/var/cacert.pem" -d openssl.cafile="$root/var/cacert.pem" bin/console cms:fonts:fetch
else
"$php" bin/console cms:fonts:fetch
fi
# Das Inhaltspaket fuer eine Live-Seite (ADR 118): dort unter System -> Inhalte uebertragen importieren.
rm -f "$root/var/becker-paket.zip"
"$php" bin/console cms:paket:export --ziel="$root/var/becker-paket.zip"
File diff suppressed because it is too large. Load diff