Add the Kantarci Reisen build scripts and local launch configurations
Seed, reset and asset scripts for the tenth installation, plus dev and prod launch entries for it and for the multilingual test copy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
28b2442188
commit
c7705b7083
4 files changed
+1061
No files matched your search
@@ -168,6 +168,46 @@
|
||||
"$env:APP_ENV='prod'; $env:APP_DEBUG='0'; $env:DATABASE_URL='sqlite:///C:/Django Project/KantSites/var/huefner.db'; $env:MEDIA_DIRECTORY='C:/Django Project/KantSites/var/huefner-media'; $env:SITE_ORIGIN='http://127.0.0.1:8103'; & C:/php84/php.exe -d upload_max_filesize=8M -d post_max_size=10M -d memory_limit=256M -S 127.0.0.1:8103 -t public bin/dev-router.php"
|
||||
],
|
||||
"port": 8103
|
||||
},
|
||||
{
|
||||
"name": "kantarci-reisen",
|
||||
"runtimeExecutable": "powershell",
|
||||
"runtimeArgs": [
|
||||
"-NoProfile",
|
||||
"-Command",
|
||||
"$env:APP_ENV='dev'; $env:DATABASE_URL='sqlite:///C:/Django Project/KantSites/var/kantarci-reisen.db'; $env:MEDIA_DIRECTORY='C:/Django Project/KantSites/var/kantarci-reisen-media'; $env:SITE_ORIGIN='http://127.0.0.1:8104'; & C:/php84/php.exe -d upload_max_filesize=8M -d post_max_size=10M -d memory_limit=256M -S 127.0.0.1:8104 -t public bin/dev-router.php"
|
||||
],
|
||||
"port": 8104
|
||||
},
|
||||
{
|
||||
"name": "kantarci-reisen-prod",
|
||||
"runtimeExecutable": "powershell",
|
||||
"runtimeArgs": [
|
||||
"-NoProfile",
|
||||
"-Command",
|
||||
"$env:APP_ENV='prod'; $env:APP_DEBUG='0'; $env:DATABASE_URL='sqlite:///C:/Django Project/KantSites/var/kantarci-reisen.db'; $env:MEDIA_DIRECTORY='C:/Django Project/KantSites/var/kantarci-reisen-media'; $env:SITE_ORIGIN='http://127.0.0.1:8105'; & C:/php84/php.exe -d upload_max_filesize=8M -d post_max_size=10M -d memory_limit=256M -S 127.0.0.1:8105 -t public bin/dev-router.php"
|
||||
],
|
||||
"port": 8105
|
||||
},
|
||||
{
|
||||
"name": "sprachen-test",
|
||||
"runtimeExecutable": "powershell",
|
||||
"runtimeArgs": [
|
||||
"-NoProfile",
|
||||
"-Command",
|
||||
"$env:APP_ENV='dev'; $env:DATABASE_URL='sqlite:///C:/Django Project/KantSites/var/browser-tests/lang/huefner-lang.db'; $env:MEDIA_DIRECTORY='C:/Django Project/KantSites/var/huefner-media'; $env:SITE_ORIGIN='http://127.0.0.1:8111'; & C:/php84/php.exe -d upload_max_filesize=8M -d post_max_size=10M -d memory_limit=256M -S 127.0.0.1:8111 -t public bin/dev-router.php"
|
||||
],
|
||||
"port": 8111
|
||||
},
|
||||
{
|
||||
"name": "sprachen-test-prod",
|
||||
"runtimeExecutable": "powershell",
|
||||
"runtimeArgs": [
|
||||
"-NoProfile",
|
||||
"-Command",
|
||||
"$env:APP_ENV='prod'; $env:APP_DEBUG='0'; $env:DATABASE_URL='sqlite:///C:/Django Project/KantSites/var/browser-tests/lang/huefner-lang.db'; $env:MEDIA_DIRECTORY='C:/Django Project/KantSites/var/huefner-media'; $env:SITE_ORIGIN='http://127.0.0.1:8112'; & C:/php84/php.exe -d upload_max_filesize=8M -d post_max_size=10M -d memory_limit=256M -S 127.0.0.1:8112 -t public bin/dev-router.php"
|
||||
],
|
||||
"port": 8112
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Bereitet die Bilder des Kantarci-Reisen-Entwurfs für die Mediathek vor.
|
||||
*
|
||||
* Aus dem gelieferten Paket (Website/ und Logo/) entstehen:
|
||||
* - logo.png: das Logo ohne den breiten durchsichtigen Rand (im Original rund ein Viertel der Fläche),
|
||||
* - favicon.png: das „K“ mit Sonne und Schwingen als quadratisches Seitensymbol,
|
||||
* - kueste.webp: das Reisefoto der Vorlage, unverändert,
|
||||
* - reisesoft-uebersicht.png: der Screenshot der reiseSoft-Übersicht, falls als zweites Argument
|
||||
* angegeben (aufgenommen aus einer lokalen Instanz mit den fiktiven Demodaten von reiseSoft).
|
||||
*
|
||||
* Aufruf: php scripts/build-kantarci-reisen-assets.php <Paketordner Kantarci-Reisen> <Zielordner> [Screenshot]
|
||||
*/
|
||||
$quelle = rtrim($argv[1] ?? '', '/\\');
|
||||
$ziel = rtrim($argv[2] ?? '', '/\\');
|
||||
$screenshot = $argv[3] ?? '';
|
||||
if ('' === $quelle || '' === $ziel || !is_file($quelle.'/Website/coast.webp')) {
|
||||
fwrite(STDERR, "Aufruf: php scripts/build-kantarci-reisen-assets.php <Ordner Kantarci-Reisen> <Zielordner> [Screenshot]\n");
|
||||
exit(1);
|
||||
}
|
||||
if (!is_dir($ziel) && !mkdir($ziel, 0775, true)) {
|
||||
fwrite(STDERR, "Zielordner lässt sich nicht anlegen: $ziel\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
copy($quelle.'/Website/coast.webp', $ziel.'/kueste.webp');
|
||||
if ('' !== $screenshot) {
|
||||
copy($screenshot, $ziel.'/reisesoft-uebersicht.png');
|
||||
}
|
||||
|
||||
$logo = imagecreatefrompng($quelle.'/Logo/kantarci-reisen-logo-original.png');
|
||||
if (false === $logo) {
|
||||
fwrite(STDERR, "Das Logo lässt sich nicht lesen.\n");
|
||||
exit(1);
|
||||
}
|
||||
$breite = imagesx($logo);
|
||||
$hoehe = imagesy($logo);
|
||||
|
||||
/** Kleinstes Rechteck um alle sichtbaren Pixel (Deckkraft über 10 %) innerhalb eines Bereichs. */
|
||||
$rahmen = static function (int $x0, int $y0, int $x1, int $y1) use ($logo): array {
|
||||
[$links, $oben, $rechts, $unten] = [$x1, $y1, $x0, $y0];
|
||||
for ($y = $y0; $y < $y1; $y += 2) {
|
||||
for ($x = $x0; $x < $x1; $x += 2) {
|
||||
if ((imagecolorat($logo, $x, $y) >> 24 & 0x7F) < 115) {
|
||||
$links = min($links, $x);
|
||||
$rechts = max($rechts, $x);
|
||||
$oben = min($oben, $y);
|
||||
$unten = max($unten, $y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [$links, $oben, $rechts - $links + 1, $unten - $oben + 1];
|
||||
};
|
||||
|
||||
$neu = static function (int $b, int $h) {
|
||||
$bild = imagecreatetruecolor($b, $h);
|
||||
imagesavealpha($bild, true);
|
||||
imagealphablending($bild, false);
|
||||
imagefill($bild, 0, 0, imagecolorallocatealpha($bild, 0, 0, 0, 127));
|
||||
imagealphablending($bild, true);
|
||||
|
||||
return $bild;
|
||||
};
|
||||
|
||||
// Ganzes Logo mit 12 px Luft.
|
||||
[$x, $y, $b, $h] = $rahmen(0, 0, $breite, $hoehe);
|
||||
$rand = 12;
|
||||
$zuschnitt = $neu($b + 2 * $rand, $h + 2 * $rand);
|
||||
imagecopy($zuschnitt, $logo, $rand, $rand, $x, $y, $b, $h);
|
||||
imagepng($zuschnitt, $ziel.'/logo.png');
|
||||
|
||||
// Das Bildzeichen steht links bis etwa 38 % der Breite. Die obere Schwinge reicht dabei über den
|
||||
// Anfang des Schriftzugs; das „K“ von KANTARCI beginnt erst unterhalb von ihr (ab etwa 35,5 % der
|
||||
// Breite und 40 % der Höhe). Diese Ecke wird vor dem Zuschnitt geleert.
|
||||
imagealphablending($logo, false);
|
||||
imagefilledrectangle($logo, (int) ($breite * 0.355), (int) ($hoehe * 0.40), $breite - 1, $hoehe - 1, imagecolorallocatealpha($logo, 0, 0, 0, 127));
|
||||
[$x, $y, $b, $h] = $rahmen(0, 0, (int) ($breite * 0.40), $hoehe);
|
||||
$kante = 512;
|
||||
$symbol = $neu($kante, $kante);
|
||||
$massstab = min(($kante - 40) / $b, ($kante - 40) / $h);
|
||||
$zb = (int) round($b * $massstab);
|
||||
$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 vorbereitet: $ziel\n";
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/bin/sh
|
||||
# Wirft die Kantarci-Reisen-Installation weg und baut sie neu auf. Nur fuer den Aufbau gedacht.
|
||||
# Erstes Argument: der entpackte Ordner „Kantarci-Reisen“ aus dem Komplettpaket (Website/, Logo/).
|
||||
# Zweites Argument: der Screenshot der reiseSoft-Uebersicht (PNG, Beispieldaten).
|
||||
set -e
|
||||
root="C:/Django Project/KantSites"
|
||||
php="${PHP_BINARY:-C:/php84/php.exe}"
|
||||
export DATABASE_URL="sqlite:///$root/var/kantarci-reisen.db"
|
||||
export MEDIA_DIRECTORY="$root/var/kantarci-reisen-media"
|
||||
assets="$root/var/kantarci-reisen-assets"
|
||||
if [ -n "$1" ]; then
|
||||
"$php" scripts/build-kantarci-reisen-assets.php "$1" "$assets" "$2"
|
||||
fi
|
||||
if [ ! -f "$assets/favicon.png" ] || [ ! -f "$assets/reisesoft-uebersicht.png" ]; then
|
||||
echo "Bilder fehlen: erst mit dem Paketordner und dem Screenshot als Argumente aufrufen." >&2
|
||||
exit 1
|
||||
fi
|
||||
rm -f "$root/var/kantarci-reisen.db"
|
||||
rm -rf "$root/var/kantarci-reisen-media"
|
||||
"$php" vendor/bin/doctrine-migrations migrate --no-interaction -q
|
||||
"$php" -d memory_limit=512M scripts/seed-kantarci-reisen.php "$assets"
|
||||
# DM Sans 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
|
||||
@@ -0,0 +1,906 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Baut den Entwurf „Kantarci Reisen“ als Inhalt dieses CMS auf.
|
||||
*
|
||||
* Vorlage ist Umuts Paket „Kantarci-Reisen-Komplettpaket.zip“ (08.10.2026): eine statische Seite
|
||||
* mit weißem Kopf, geteiltem Hero (Petrol links, Küstenfoto rechts), drei Angeboten, „Gute Reisen
|
||||
* beginnen mit einem Gespräch“, Anfrage auf Petrol mit weißer Formularkarte, reiseSoft-Abschnitt
|
||||
* und Fuß. Wortlaut, Farben (Petrol #0A363B, Orange #FF753D) und die kursive Serifenschrift für
|
||||
* die Pointe („wo*anders.*“) sind daraus übernommen. Gebaut ist alles aus gewöhnlichen Bausteinen,
|
||||
* Gestaltungswerten und eigenem CSS je Baustein – kein Kerncode, kein Plugin.
|
||||
*
|
||||
* Bewusste Abweichungen und Ergänzungen:
|
||||
* - Statt Arial die DM Sans vom eigenen Server; die Serifenpointe bleibt Georgia kursiv wie in der
|
||||
* Vorlage (Systemschrift, nichts zu laden).
|
||||
* - Das Formular ist ein echtes CMS-Formular mit Versand an info@kantarci-reisen.de, nicht die
|
||||
* Demo der Vorlage. Dazu Telefon, Reisezeitraum und Personenzahl, alle freiwillig.
|
||||
* - Die reiseSoft-Karte der Vorlage (drei Zeilen als Attrappe) ist ein echter Screenshot der
|
||||
* Übersicht – aufgenommen aus einer lokalen reiseSoft-Instanz mit deren fiktiven Demodaten,
|
||||
* also ohne Kundendaten. Daneben drei Funktionen, die reiseSoft wirklich hat, und ein Knopf zur
|
||||
* Plattform unter reisesoft.kantarci-reisen.de.
|
||||
* - Neu: „So läuft’s“ in drei Schritten, Anschrift im Abschnitt „Über uns“, Impressum,
|
||||
* Datenschutz und 404. „01 / 03“ im Hero (ohne Bedeutung) ist der Ort.
|
||||
* - Inhaber, Anschrift, E-Mail und Telefon von Umut. Eine USt-IdNr. gibt es nicht, Öffnungszeiten
|
||||
* sind nicht bekannt; beides erscheint deshalb nirgends.
|
||||
* - WhatsApp als zweiter Weg zur Anfrage: Knopf unter „Über uns“ und neben dem Formular, Link im
|
||||
* Fuß und eine runde Schaltfläche unten links auf jeder Seite. Gebaut aus dem Baustein
|
||||
* „Soziale Profile“ (Netzwerk WhatsApp aus den Einstellungen), der das Markensymbol mitbringt.
|
||||
*
|
||||
* 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-kantarci-reisen.sh).
|
||||
* Erstes Argument: der Ordner aus build-kantarci-reisen-assets.php.
|
||||
*/
|
||||
|
||||
require dirname(__DIR__).'/config/bootstrap.php';
|
||||
|
||||
use App\Content\DocumentValidator;
|
||||
use App\Content\FormRepository;
|
||||
use App\Content\MediaLibrary;
|
||||
use App\Content\MenuRepository;
|
||||
use App\Content\PageRepository;
|
||||
use App\Content\PartialRepository;
|
||||
use App\Content\SiteSettings;
|
||||
use App\Content\Theme;
|
||||
use App\Infrastructure\ConnectionFactory;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
|
||||
$root = dirname(__DIR__);
|
||||
$assets = $argv[1] ?? null;
|
||||
if (null === $assets || !is_dir($assets)) {
|
||||
fwrite(STDERR, "Bitte den Ordner mit den Bildern als Argument angeben.\n");
|
||||
exit(1);
|
||||
}
|
||||
$url = str_replace('%kernel.project_dir%', str_replace('\\', '/', $root), (string) $_SERVER['DATABASE_URL']);
|
||||
$mediaDirectory = str_replace('%kernel.project_dir%', $root, (string) $_SERVER['MEDIA_DIRECTORY']);
|
||||
if (!str_contains($url, 'kantarci-reisen')) {
|
||||
fwrite(STDERR, "Sicherung: DATABASE_URL muss auf die Kantarci-Reisen-Datenbank zeigen.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$db = ConnectionFactory::create($url);
|
||||
if (false !== $db->fetchOne('SELECT id FROM page LIMIT 1')) {
|
||||
fwrite(STDERR, "Diese Datenbank enthält bereits Seiten. Abbruch, damit nichts überschrieben wird.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$media = new MediaLibrary($db, $mediaDirectory);
|
||||
$settings = new SiteSettings($db, $media);
|
||||
$pages = new PageRepository($db, new DocumentValidator(), $settings);
|
||||
$partials = new PartialRepository($db, new DocumentValidator());
|
||||
$menus = new MenuRepository($db, $settings);
|
||||
$forms = new FormRepository($db);
|
||||
$theme = new Theme($db);
|
||||
|
||||
$firma = 'Kantarci Reisen';
|
||||
$inhaber = 'I. Kantarci';
|
||||
$mail = 'info@kantarci-reisen.de';
|
||||
$strasse = 'Ooser Hauptstraße 51';
|
||||
$plz = '76532';
|
||||
$ort = 'Baden-Baden';
|
||||
$reisesoft = 'https://reisesoft.kantarci-reisen.de';
|
||||
// Telefon und WhatsApp von Umut (08.10.2026), dieselbe Mobilnummer.
|
||||
$telefon = '0170 2945935';
|
||||
$tel = 'tel:+491702945935';
|
||||
$whatsapp = 'https://wa.me/491702945935?text='.rawurlencode('Hallo Kantarci Reisen, ich interessiere mich für eine Reise.');
|
||||
$SCHEMA = DocumentValidator::SCHEMA_VERSION;
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// Bausteine
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
$seiten = static fn (string $w): array => ['top' => $w, 'right' => $w, 'bottom' => $w, 'left' => $w];
|
||||
$nurOben = static fn (string $w): array => ['top' => $w, 'right' => '0px', 'bottom' => '0px', 'left' => '0px'];
|
||||
$nurUnten = static fn (string $w): array => ['top' => '0px', 'right' => '0px', 'bottom' => $w, 'left' => '0px'];
|
||||
$rund = static fn (string $w): array => ['topLeft' => $w, 'topRight' => $w, 'bottomRight' => $w, 'bottomLeft' => $w];
|
||||
|
||||
$section = static function (string $id, array $children, array $style = [], ?string $anchor = null, string $css = '', string $breite = 'normal'): array {
|
||||
$block = ['id' => $id, 'type' => 'section', 'children' => $children, 'style' => $style];
|
||||
if (null !== $anchor) {
|
||||
$block['anchor'] = $anchor;
|
||||
}
|
||||
if ('' !== $css) {
|
||||
$block['css'] = $css;
|
||||
}
|
||||
if ('normal' !== $breite) {
|
||||
$block['layout'] = ['width' => $breite];
|
||||
}
|
||||
|
||||
return $block;
|
||||
};
|
||||
|
||||
$row = static function (string $id, array $children, array $design = [], array $style = [], string $css = ''): array {
|
||||
$block = ['id' => $id, 'type' => 'row', 'children' => $children];
|
||||
if ([] !== $design) {
|
||||
$block['layout'] = ['design' => $design];
|
||||
}
|
||||
if ([] !== $style) {
|
||||
$block['style'] = $style;
|
||||
}
|
||||
if ('' !== $css) {
|
||||
$block['css'] = $css;
|
||||
}
|
||||
|
||||
return $block;
|
||||
};
|
||||
|
||||
$zweiSpalten = ['desktop' => ['columns' => 'two'], 'tablet' => ['columns' => 'one'], 'mobile' => ['columns' => 'one']];
|
||||
|
||||
/** Eine Zeile, die nebeneinander stellt statt zu rastern. */
|
||||
$zeile = static function (string $id, array $children, string $gap, array $extra = [], array $mobil = [], array $tablet = [], string $css = '') use ($row): array {
|
||||
$style = ['desktop' => ['display' => 'flex', 'alignItems' => 'center', 'gap' => $gap, 'margin' => ['top' => '0px'], ...$extra]];
|
||||
if ([] !== $tablet) {
|
||||
$style['tablet'] = $tablet;
|
||||
}
|
||||
if ([] !== $mobil) {
|
||||
$style['mobile'] = $mobil;
|
||||
}
|
||||
|
||||
return $row($id, $children, [], $style, $css);
|
||||
};
|
||||
|
||||
$column = static function (string $id, array $children, array $style = [], string $css = '', string $sichtbar = 'all'): array {
|
||||
$block = ['id' => $id, 'type' => 'column', 'children' => $children];
|
||||
if ([] !== $style) {
|
||||
$block['style'] = $style;
|
||||
}
|
||||
if ('' !== $css) {
|
||||
$block['css'] = $css;
|
||||
}
|
||||
if ('all' !== $sichtbar) {
|
||||
$block['layout'] = ['visibility' => $sichtbar];
|
||||
}
|
||||
|
||||
return $block;
|
||||
};
|
||||
|
||||
$heading = static function (string $id, string $text, int $level, array $style = [], string $css = ''): array {
|
||||
$block = ['id' => $id, 'type' => 'heading', 'props' => ['text' => $text, 'level' => $level]];
|
||||
if ([] !== $style) {
|
||||
$block['style'] = $style;
|
||||
}
|
||||
if ('' !== $css) {
|
||||
$block['css'] = $css;
|
||||
}
|
||||
|
||||
return $block;
|
||||
};
|
||||
|
||||
$text = static function (string $id, string $inhalt, array $style = [], string $css = '', string $sichtbar = 'all'): array {
|
||||
$block = ['id' => $id, 'type' => 'text', 'props' => ['text' => $inhalt]];
|
||||
if ([] !== $style) {
|
||||
$block['style'] = $style;
|
||||
}
|
||||
if ('' !== $css) {
|
||||
$block['css'] = $css;
|
||||
}
|
||||
if ('all' !== $sichtbar) {
|
||||
$block['layout'] = ['visibility' => $sichtbar];
|
||||
}
|
||||
|
||||
return $block;
|
||||
};
|
||||
|
||||
$bild = static function (string $id, array $eintrag, string $alt, array $style = [], ?string $href = null, string $css = ''): array {
|
||||
$gross = end($eintrag['variants']);
|
||||
$props = [
|
||||
'src' => $gross['src'], 'alt' => $alt, 'decorative' => '' === $alt, 'width' => $gross['width'], 'height' => $gross['height'],
|
||||
'mediaId' => $eintrag['id'],
|
||||
'sources' => array_map(static fn (array $v): array => ['src' => $v['src'], 'width' => $v['width']], $eintrag['variants']),
|
||||
];
|
||||
if (null !== $href) {
|
||||
$props['href'] = $href;
|
||||
$props['click'] = 'link';
|
||||
}
|
||||
$block = ['id' => $id, 'type' => 'image', 'props' => $props, 'style' => $style];
|
||||
if ('' !== $css) {
|
||||
$block['css'] = $css;
|
||||
}
|
||||
|
||||
return $block;
|
||||
};
|
||||
|
||||
$symbol = static fn (string $id, string $name, int $groesse, array $style = []): array => ['id' => $id, 'type' => 'icon', 'props' => ['icon' => $name, 'size' => $groesse, 'decorative' => true, 'label' => ''], 'style' => $style];
|
||||
|
||||
/**
|
||||
* Ein Button wie in der Vorlage: ohne Pfeil, hebt sich beim Überfahren leicht an.
|
||||
*
|
||||
* @param string $art orange (Theme), petrol, rahmen (Petrol auf Hell), hell (Rahmen auf Petrol)
|
||||
*/
|
||||
$knopf = static function (string $id, string $inhalt, string $href, string $art = 'orange', array $style = [], string $icon = 'none') : array {
|
||||
$farbe = [
|
||||
'orange' => '',
|
||||
'petrol' => "background:#0a363b;color:#fff;\n&:hover{background:#20545a;color:#fff}\n",
|
||||
'rahmen' => "background:transparent;color:#0a363b;box-shadow:inset 0 0 0 1px #0a363b;\n&:hover{background:#f0f5f4;color:#0a363b}\n",
|
||||
'hell' => "background:transparent;color:#fff;box-shadow:inset 0 0 0 1px rgba(255,255,255,.55);\n&:hover{background:rgba(255,255,255,.1);color:#fff}\n",
|
||||
][$art];
|
||||
$props = ['text' => $inhalt, 'href' => $href, 'icon' => $icon];
|
||||
if ('none' !== $icon) {
|
||||
$props['iconPosition'] = 'end';
|
||||
$props['iconMotion'] = 'slide';
|
||||
}
|
||||
|
||||
return ['id' => $id, 'type' => 'button', 'props' => $props, 'css' => "min-height:50px;padding:0 26px;margin-top:0;transition:transform .2s ease,background-color .2s ease;\n".$farbe.'&:hover{transform:translateY(-2px)}']
|
||||
+ ([] === $style ? [] : ['style' => $style]);
|
||||
};
|
||||
|
||||
/** WhatsApp als Knopf: Symbol und Name aus dem Baustein „Soziale Profile“, in WhatsApp-Grün. */
|
||||
$waKnopf = static fn (string $id, array $style = []): array => ['id' => $id, 'type' => 'social',
|
||||
'props' => ['size' => 20, 'shape' => 'plain', 'layout' => 'horizontal', 'labels' => true, 'networks' => ['whatsapp']],
|
||||
'css' => "& a{min-height:50px;padding:0 24px;gap:10px;border-radius:4px;background:#25d366;color:#073b2f;font-size:15px;font-weight:600;transition:transform .2s ease,background-color .2s ease}\n& a:hover{background:#3ee07a;color:#073b2f;transform:translateY(-2px)}",
|
||||
] + ([] === $style ? [] : ['style' => $style]);
|
||||
|
||||
/**
|
||||
* Setzt die Einblendungen nach einer Liste.
|
||||
*
|
||||
* @param array<string, array{0: string, 1?: string}> $plan Block-ID => [Art, Verzögerung]
|
||||
*/
|
||||
$einblenden = static function (array $dokument, array $plan): array {
|
||||
$gehen = function (array $bloecke) use (&$gehen, $plan): array {
|
||||
foreach ($bloecke as $i => $block) {
|
||||
if (isset($plan[$block['id'] ?? ''])) {
|
||||
[$art, $verzug] = [...$plan[$block['id']], 'none'];
|
||||
$bloecke[$i]['layout'] = [...($block['layout'] ?? []), 'effects' => ['entrance' => $art, 'delay' => $verzug]];
|
||||
}
|
||||
if (isset($block['children'])) {
|
||||
$bloecke[$i]['children'] = $gehen($block['children']);
|
||||
}
|
||||
}
|
||||
|
||||
return $bloecke;
|
||||
};
|
||||
$dokument['blocks'] = $gehen($dokument['blocks']);
|
||||
|
||||
return $dokument;
|
||||
};
|
||||
|
||||
// Die kursive Serifenpointe der Vorlage: auf Petrol Apricot, auf Hell in der Schriftfarbe.
|
||||
$serif = "& em{font-family:Georgia,serif;font-style:italic;font-weight:400;letter-spacing:-.045em}";
|
||||
$serifHell = $serif."\n& em{color:#ffab82}";
|
||||
$linkCss = "& a{color:#0a363b;text-decoration:underline;text-decoration-color:#ff753d;text-underline-offset:4px}\n& a:hover{color:#a43e13}";
|
||||
|
||||
/** Die Kleinzeile in Versalien („WOHIN ZIEHT ES DICH?“). */
|
||||
$kleinzeile = static fn (string $id, string $inhalt, string $farbe = '@text', string $unten = '22px') => $text($id, $inhalt, ['desktop' => [
|
||||
'fontSize' => '12px', 'fontWeight' => '600', 'letterSpacing' => '0.15em', 'lineHeight' => 1.7, 'textTransform' => 'uppercase', 'color' => $farbe, 'margin' => ['top' => '0px', 'bottom' => $unten],
|
||||
]]);
|
||||
|
||||
$h2Stil = [
|
||||
'desktop' => ['fontSize' => '49px', 'fontWeight' => '500', 'lineHeight' => 1.1, 'letterSpacing' => '-0.04em', 'color' => '@tinte', 'margin' => ['top' => '0px', 'bottom' => '0px']],
|
||||
'tablet' => ['fontSize' => '40px'],
|
||||
'mobile' => ['fontSize' => '35px'],
|
||||
];
|
||||
$fliess = static fn (string $farbe = '@text', string $unten = '0px', string $breite = 'none', string $groesse = '16px') => [
|
||||
'desktop' => ['fontSize' => $groesse, 'lineHeight' => 1.8, 'color' => $farbe, 'maxWidth' => $breite, 'margin' => ['top' => '0px', 'bottom' => $unten]],
|
||||
'mobile' => ['maxWidth' => 'none'],
|
||||
];
|
||||
|
||||
// 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::after{content:'';position:absolute;top:0;right:0;bottom:0;left:0}\n& a:focus-visible{outline:none}\n&:focus-within{outline:3px solid #ff753d;outline-offset:4px}";
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// Website, Gestaltung, Medien
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
$db->insert('website', ['id' => 'default', 'name' => $firma, 'default_locale' => 'de', 'theme' => '{}']);
|
||||
|
||||
$theme->save(
|
||||
[
|
||||
['key' => 'tinte', 'name' => 'Petrol (Schrift, dunkle Flächen)', 'value' => '#0a363b'],
|
||||
['key' => 'petrol-hell', 'name' => 'Petrol hell (beim Überfahren)', 'value' => '#20545a'],
|
||||
['key' => 'orange', 'name' => 'Orange (Knöpfe, Linien)', 'value' => '#ff753d'],
|
||||
['key' => 'orange-hell', 'name' => 'Orange hell (Knöpfe beim Überfahren)', 'value' => '#ff8b5a'],
|
||||
['key' => 'apricot', 'name' => 'Apricot (Pointe auf Petrol)', 'value' => '#ffab82'],
|
||||
['key' => 'orange-text', 'name' => 'Orange dunkel (Schrift auf Weiß)', 'value' => '#a43e13'],
|
||||
['key' => 'knopf-schrift', 'name' => 'Schrift auf Orange', 'value' => '#102f30'],
|
||||
['key' => 'text', 'name' => 'Fließtext', 'value' => '#52696b'],
|
||||
['key' => 'linie', 'name' => 'Linien', 'value' => '#dbe3e3'],
|
||||
['key' => 'pale', 'name' => 'Helles Petrol (Flächen)', 'value' => '#f0f5f4'],
|
||||
['key' => 'auf-dunkel', 'name' => 'Schrift auf Petrol', 'value' => '#c0d5d5'],
|
||||
['key' => 'papier', 'name' => 'Weiß', 'value' => '#ffffff'],
|
||||
],
|
||||
[
|
||||
['key' => 'body', 'name' => 'Text und Überschriften', 'family' => 'DM Sans', 'source' => 'google', 'weights' => [400, 500, 600, 700], 'fallback' => 'sans-serif'],
|
||||
],
|
||||
['fontFamily' => 'body', 'headingFontFamily' => 'body', 'fontSize' => '16px', 'lineHeight' => 1.6, 'color' => '@tinte', 'backgroundColor' => '@papier', 'linkColor' => '@tinte', 'containerWidth' => '1200px', 'headingScale' => 1.25, 'linkUnderline' => false, 'imageRadius' => '0px'],
|
||||
['shape' => 'rounded', 'appearance' => 'solid', 'size' => 'normal', 'fontFamily' => 'body', 'fontSize' => 15, 'fontWeight' => 600, 'letterSpacing' => 0, 'textTransform' => 'none', 'backgroundColor' => '@orange', 'color' => '@knopf-schrift', 'hoverBackgroundColor' => '@orange-hell', 'hoverColor' => '@knopf-schrift'],
|
||||
);
|
||||
echo "Farben, Schrift und Buttons gesetzt\n";
|
||||
|
||||
$medien = [];
|
||||
foreach ([
|
||||
'logo' => ['logo.png', 'Kantarci Reisen'],
|
||||
'favicon' => ['favicon.png', 'Bildzeichen von Kantarci Reisen: K mit Sonne und Schwingen'],
|
||||
'kueste' => ['kueste.webp', 'Türkisfarbene Mittelmeerbucht mit sonniger Strandterrasse'],
|
||||
'reisesoft' => ['reisesoft-uebersicht.png', 'reiseSoft-Übersicht mit offenen Posten, Umsatz und den letzten Vorgängen (Beispieldaten)'],
|
||||
] as $schluessel => [$datei, $alt]) {
|
||||
$pfad = rtrim($assets, '/\\').'/'.$datei;
|
||||
if (!is_file($pfad)) {
|
||||
fwrite(STDERR, "Bild fehlt: $pfad\n");
|
||||
exit(1);
|
||||
}
|
||||
$eintrag = $media->upload(new UploadedFile($pfad, basename($datei), null, null, true));
|
||||
$media->update($eintrag['id'], ['title' => pathinfo($datei, PATHINFO_FILENAME), 'alt' => $alt, 'archived' => false, 'version' => $eintrag['version']]);
|
||||
$medien[$schluessel] = $media->find($eintrag['id']);
|
||||
echo "Bild übernommen: $datei\n";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// Formular
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
$forms->save('reiseanfrage', 'Reiseanfrage', [
|
||||
['name' => 'name', 'type' => 'text', 'label' => 'Dein Name', 'required' => true, 'help' => 'Vor- und Nachname'],
|
||||
['name' => 'email', 'type' => 'email', 'label' => 'Deine E-Mail', 'required' => true],
|
||||
['name' => 'telefon', 'type' => 'phone', 'label' => 'Telefon', 'required' => false, 'help' => 'Freiwillig – wenn wir dich lieber anrufen sollen'],
|
||||
['name' => 'anliegen', 'type' => 'select', 'label' => 'Anliegen', 'required' => true, 'options' => ['Türkei-Urlaub', 'Hotels weltweit', 'Flüge in die Türkei', 'reiseSoft für mein Reisebüro', 'Allgemeine Anfrage']],
|
||||
['name' => 'zeitraum', 'type' => 'text', 'label' => 'Reisezeitraum', 'required' => false, 'help' => 'z. B. Pfingstferien oder 2 Wochen im August'],
|
||||
['name' => 'personen', 'type' => 'number', 'label' => 'Personen', 'required' => false, 'min' => '1', 'max' => '30'],
|
||||
['name' => 'wuensche', 'type' => 'textarea', 'label' => 'Deine Wünsche', 'required' => true, 'help' => 'Reiseziel, Abflughafen, Budget, was dir wichtig ist …'],
|
||||
['name' => 'datenschutz', 'type' => 'consent', 'label' => 'Ich stimme der Verarbeitung meiner Angaben gemäß Datenschutzhinweisen zu.'],
|
||||
], ['recipient' => $mail, 'subject' => 'Neue Reiseanfrage über die Website', 'success' => 'Danke! Deine Anfrage ist bei uns angekommen. Wir melden uns persönlich bei dir – meist innerhalb eines Werktags.', 'replyToField' => 'email', 'buttonLabel' => 'Anfrage senden'], 0);
|
||||
echo "Formular angelegt: reiseanfrage\n";
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// Kopfbereich: weiß, Logo, Menü, „Reise anfragen“ als Menü-Button
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
$kopf = [
|
||||
'schemaVersion' => $SCHEMA, 'title' => 'Kopfbereich', 'description' => 'Erscheint auf jeder Seite über dem Inhalt.',
|
||||
'blocks' => [
|
||||
$section('kopf', [
|
||||
$zeile('kopf-zeile', [
|
||||
$bild('kopf-logo', $medien['logo'], $firma.' – zur Startseite', [
|
||||
// Feste Breite: sonst bekommt das Logo seine Breite erst nach dem Laden (CLS).
|
||||
'desktop' => ['width' => '230px', 'maxWidth' => '230px', 'flexShrink' => 0, 'margin' => $seiten('0px')],
|
||||
'mobile' => ['width' => '132px', 'maxWidth' => '132px'],
|
||||
], '/', '& img{display:block;width:100%;height:auto;border-radius:0}'),
|
||||
['id' => 'kopf-menue', 'type' => 'menu', 'props' => ['slot' => 'header', 'layout' => 'horizontal', 'collapse' => 'tablet', 'burgerLabel' => 'icon'],
|
||||
'style' => ['desktop' => [
|
||||
'fontSize' => '15px', 'fontWeight' => '500', 'color' => '@tinte',
|
||||
'burgerColor' => '@tinte', 'burgerBackgroundColor' => '@papier', 'burgerBorderColor' => '@linie', 'burgerBorderRadius' => '4px',
|
||||
]],
|
||||
// Orange Linie, die beim Überfahren von links einläuft; das Klappmenü liegt als
|
||||
// Fläche unter dem ganzen Kopf.
|
||||
'css' => "& .menu-list{gap:4px 36px;align-items:center}\n"
|
||||
."& .menu-list a{position:relative;padding:8px 0;text-decoration:none}\n"
|
||||
."& .menu-list a::after{content:'';position:absolute;left:0;right:100%;bottom:2px;height:2px;background:#ff753d;transition:right .25s ease}\n"
|
||||
."& .menu-list a:hover::after{right:0}\n"
|
||||
// Der Menü-Button ist auch ein Link der Liste: Polster und Linie gehören nicht zu ihm.
|
||||
."& .menu-list .menu-button{padding:12px 22px}\n"
|
||||
."& .menu-list .menu-button::after{display:none}\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 #dbe3e3;box-shadow:0 18px 30px rgba(10,54,59,.1);font-size:18px}\n"
|
||||
."& .menu-burger-body .menu-list{flex-direction:column;align-items:stretch;gap:0}\n"
|
||||
."& .menu-burger-body .menu-list a{display:block;padding:14px 0;border-bottom:1px solid #e8eded}\n"
|
||||
.'& .menu-burger-body .menu-list a::after{display:none}'],
|
||||
], '28px', ['justifyContent' => 'space-between', 'minHeight' => '96px'], ['minHeight' => '72px', 'gap' => '6px']),
|
||||
], [
|
||||
'desktop' => ['backgroundColor' => '#fffffff5', 'padding' => ['top' => '0px', 'bottom' => '0px'], 'borderWidth' => $nurUnten('1px'), 'borderStyle' => 'solid', 'borderColor' => '@linie'],
|
||||
], null, '', 'wide'),
|
||||
],
|
||||
];
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// Fußbereich
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
$fussLink = "& a{color:#52696b;text-decoration:none;transition:color .2s}\n& a:hover{color:#0a363b}";
|
||||
$fussTitel = static fn (string $id, string $inhalt) => $kleinzeile($id, $inhalt, '@tinte', '14px');
|
||||
$fussText = static fn (string $id, string $inhalt) => $text($id, $inhalt, ['desktop' => ['fontSize' => '15px', 'lineHeight' => 1.9, 'color' => '@text', 'margin' => ['bottom' => '0px']]], $fussLink."\n& a{overflow-wrap:anywhere}");
|
||||
|
||||
$fuss = [
|
||||
'schemaVersion' => $SCHEMA, 'title' => 'Fußbereich', 'description' => 'Erscheint auf jeder Seite unter dem Inhalt.',
|
||||
'blocks' => [
|
||||
$section('fuss', [
|
||||
$column('fuss-raster', [
|
||||
$column('fuss-marke', [
|
||||
$bild('fuss-logo', $medien['logo'], $firma, ['desktop' => ['width' => '200px', 'maxWidth' => '200px', 'margin' => $seiten('0px')]], null, '& img{display:block;width:100%;height:auto;border-radius:0}'),
|
||||
$text('fuss-spruch', 'Türkei im Herzen. Die Welt im Blick.', ['desktop' => ['fontSize' => '15px', 'lineHeight' => 1.6, 'color' => '@text', 'margin' => ['top' => '18px', 'bottom' => '0px']]]),
|
||||
]),
|
||||
$column('fuss-reisen', [
|
||||
$fussTitel('fuss-reisen-titel', 'Reisen'),
|
||||
$fussText('fuss-reisen-liste', '<a href="/#reisen">Türkei-Urlaub</a><br><a href="/#reisen">Hotels weltweit</a><br><a href="/#reisen">Flüge in die Türkei</a><br><a href="/#kontakt">Reise anfragen</a>'),
|
||||
]),
|
||||
$column('fuss-business', [
|
||||
$fussTitel('fuss-business-titel', 'Für Reisebüros'),
|
||||
$fussText('fuss-business-liste', '<a href="/#reisesoft">reiseSoft kennenlernen</a><br><a href="'.$reisesoft.'">Zur reiseSoft-Plattform</a>'),
|
||||
]),
|
||||
$column('fuss-kontakt', [
|
||||
$fussTitel('fuss-kontakt-titel', 'Kontakt'),
|
||||
$fussText('fuss-kontakt-text', $firma.'<br>'.$strasse.'<br>'.$plz.' '.$ort.'<br><a href="'.$tel.'">'.$telefon.'</a><br><a href="mailto:'.$mail.'">'.$mail.'</a><br><a href="'.$whatsapp.'">WhatsApp</a>'),
|
||||
]),
|
||||
], [
|
||||
'desktop' => ['display' => 'grid', 'gridColumns' => 4, 'gap' => '40px', 'padding' => ['top' => '64px', 'bottom' => '40px']],
|
||||
'tablet' => ['gridColumns' => 2],
|
||||
'mobile' => ['gridColumns' => 1, 'gap' => '32px', 'padding' => ['top' => '48px', 'bottom' => '32px']],
|
||||
]),
|
||||
// Auf jeder Seite unten links: WhatsApp als runde Schaltfläche (rechts sitzt „nach oben“).
|
||||
['id' => 'fuss-whatsapp', 'type' => 'social', 'props' => ['size' => 32, 'shape' => 'plain', 'layout' => 'horizontal', 'labels' => false, 'networks' => ['whatsapp']],
|
||||
'css' => "position:fixed;left:20px;bottom:20px;z-index:40;margin:0\n& a{width:58px;height:58px;justify-content:center;border-radius:50%;background:#25d366;color:#073b2f;box-shadow:0 10px 28px rgba(10,54,59,.28);transition:transform .2s ease}\n& a:hover{transform:scale(1.07);color:#073b2f}"],
|
||||
$zeile('fuss-unten', [
|
||||
$text('fuss-copyright', '© 2026 '.$firma, ['desktop' => ['fontSize' => '13px', 'color' => '@text', 'margin' => ['bottom' => '0px']]]),
|
||||
$zeile('fuss-links', [
|
||||
['id' => 'fuss-rechtliches', 'type' => 'legal', 'props' => ['label' => 'Rechtliches'],
|
||||
'style' => ['desktop' => ['fontSize' => '13px', 'gap' => '24px', 'color' => '@text']],
|
||||
'css' => $fussLink],
|
||||
$text('fuss-agentur', '<a href="https://kant-it.de">Website by Kant-IT Solutions</a>', ['desktop' => ['fontSize' => '13px', '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' => '@linie', 'padding' => ['top' => '22px', 'bottom' => '32px']],
|
||||
['flexDirection' => 'column', 'alignItems' => 'flex-start']),
|
||||
], [
|
||||
'desktop' => ['backgroundColor' => '@papier', 'padding' => ['top' => '0px', 'bottom' => '0px'], 'borderWidth' => $nurOben('7px'), 'borderStyle' => 'solid', 'borderColor' => '@tinte'],
|
||||
]),
|
||||
],
|
||||
];
|
||||
|
||||
$partials->create('header', 'default', 'Kopfbereich', $kopf);
|
||||
$partials->create('footer', 'default', 'Fußbereich', $fuss);
|
||||
echo "Kopf- und Fußbereich angelegt\n";
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// Startseite
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
/** Ein Angebot: Nummer und Kleinzeile, Titel, Satz, Textlink; die ganze Karte ist der Link. */
|
||||
$angebot = static function (int $n, string $klein, string $icon, string $titel, string $satz, string $link) use ($column, $zeile, $text, $heading, $symbol, $nurOben, $ganzeKarte): array {
|
||||
$id = 'angebot-'.$n;
|
||||
|
||||
return $column($id, [
|
||||
$zeile($id.'-kopf', [
|
||||
$symbol($id.'-icon', $icon, 24, ['desktop' => ['color' => '@orange-text']]),
|
||||
$text($id.'-klein', $klein, ['desktop' => ['fontSize' => '11px', 'fontWeight' => '600', 'letterSpacing' => '0.12em', 'textTransform' => 'uppercase', 'color' => '#687e80', 'margin' => ['bottom' => '0px']]]),
|
||||
], '12px', ['justifyContent' => 'space-between', 'margin' => ['bottom' => '30px']]),
|
||||
$heading($id.'-titel', $titel, 3, [
|
||||
'desktop' => ['fontSize' => '30px', 'fontWeight' => '500', 'lineHeight' => 1.15, 'letterSpacing' => '-0.035em', 'color' => '@tinte', 'margin' => ['top' => '0px', 'bottom' => '18px']],
|
||||
'mobile' => ['fontSize' => '28px'],
|
||||
]),
|
||||
$text($id.'-satz', $satz, ['desktop' => ['fontSize' => '15px', 'lineHeight' => 1.8, 'color' => '@text', 'maxWidth' => '320px', 'margin' => ['bottom' => '26px']], 'mobile' => ['maxWidth' => 'none']]),
|
||||
$text($id.'-link', '<a href="#kontakt">'.$link.'</a>', ['desktop' => ['fontSize' => '14px', 'fontWeight' => '600', 'margin' => ['top' => 'auto', 'bottom' => '0px']]],
|
||||
"& a{display:inline-block;color:#0a363b;text-decoration:none;border-bottom:1px solid #ff753d;padding-bottom:6px;transition:border-color .2s}\n& a:hover{border-bottom-color:#0a363b}"),
|
||||
], [
|
||||
'desktop' => ['display' => 'flex', 'flexDirection' => 'column', 'padding' => ['top' => '30px', 'right' => '30px', 'bottom' => '34px', 'left' => '30px'],
|
||||
'borderWidth' => ['top' => '0px', 'right' => '0px', 'bottom' => '0px', 'left' => $n > 1 ? '1px' : '0px'], 'borderStyle' => 'solid', 'borderColor' => '@linie'],
|
||||
'mobile' => ['padding' => ['top' => '26px', 'right' => '0px', 'bottom' => '28px', 'left' => '0px'], 'borderWidth' => $nurOben($n > 1 ? '1px' : '0px')],
|
||||
], $ganzeKarte."\ntransition:background-color .25s ease;\n&:hover{background-color:#f0f5f4}");
|
||||
};
|
||||
|
||||
/** Ein Schritt: große Ziffer in Orange-Linie, Titel, Satz. */
|
||||
$schritt = static fn (int $n, string $titel, string $satz) => $column('schritt-'.$n, [
|
||||
$text('schritt-'.$n.'-nummer', sprintf('%02d', $n), ['desktop' => ['fontSize' => '13px', 'fontWeight' => '600', 'letterSpacing' => '0.12em', 'color' => '@orange-text', 'borderWidth' => $nurOben('2px'), 'borderStyle' => 'solid', 'borderColor' => '@orange', 'padding' => ['top' => '16px'], 'margin' => ['bottom' => '14px']]]),
|
||||
$heading('schritt-'.$n.'-titel', $titel, 3, ['desktop' => ['fontSize' => '22px', 'fontWeight' => '500', 'lineHeight' => 1.25, 'letterSpacing' => '-0.02em', 'color' => '@tinte', 'margin' => ['top' => '0px', 'bottom' => '10px']]]),
|
||||
$text('schritt-'.$n.'-satz', $satz, ['desktop' => ['fontSize' => '15px', 'lineHeight' => 1.75, 'color' => '@text', 'margin' => ['bottom' => '0px']]]),
|
||||
]);
|
||||
|
||||
/** Eine Funktion von reiseSoft: Nummer im Kästchen, Titel, Satz. */
|
||||
$funktion = static fn (int $n, string $titel, string $satz) => $zeile('funktion-'.$n, [
|
||||
$text('funktion-'.$n.'-nummer', sprintf('%02d', $n), ['desktop' => ['display' => 'flex', 'alignItems' => 'center', 'justifyContent' => 'center', 'width' => '36px', 'height' => '36px', 'flexShrink' => 0, 'backgroundColor' => '@papier', 'borderRadius' => $rund('4px'), 'fontSize' => '12px', 'fontWeight' => '600', 'color' => '@tinte', 'margin' => ['bottom' => '0px']]]),
|
||||
$text('funktion-'.$n.'-text', '<strong>'.$titel.'</strong>'.$satz, ['desktop' => ['fontSize' => '14px', 'lineHeight' => 1.55, 'color' => '@text', 'margin' => ['bottom' => '0px']]], '& strong{display:block;font-size:15px;font-weight:600;color:#0a363b;margin-bottom:2px}'),
|
||||
], '16px', ['alignItems' => 'flex-start', 'borderWidth' => $nurOben('1px'), 'borderStyle' => 'solid', 'borderColor' => '#d5dfde', 'padding' => ['top' => '16px', 'bottom' => '16px']]);
|
||||
|
||||
// Formular im Zuschnitt der Vorlage: Felder hell mit Rahmen, Name/E-Mail, Telefon/Anliegen und
|
||||
// Zeitraum/Personen nebeneinander, Knopf über die ganze Breite.
|
||||
$formularCss = '& .block-form{max-width:none;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));column-gap:18px;row-gap:18px}
|
||||
& .block-form>.form-field{grid-column:1/-1}
|
||||
& .block-form>.cms-button{grid-column:1/-1}
|
||||
& .block-form>.form-error-summary{grid-column:1/-1}
|
||||
& .block-form>.form-success{grid-column:1/-1}
|
||||
& .block-form>.form-missing{grid-column:1/-1}
|
||||
& .block-form>[data-label="Dein Name"]{grid-column:span 1}
|
||||
& .block-form>[data-label="Deine E-Mail"]{grid-column:span 1}
|
||||
& .block-form>[data-label="Telefon"]{grid-column:span 1}
|
||||
& .block-form>[data-label="Anliegen"]{grid-column:span 1}
|
||||
& .block-form>[data-label="Reisezeitraum"]{grid-column:span 1}
|
||||
& .block-form>[data-label="Personen"]{grid-column:span 1}
|
||||
& .form-field{align-content:start}
|
||||
& .form-field label{font-size:14px;font-weight:600;color:#0a363b}
|
||||
& .form-field input{background:#f5f8f7;border:1px solid #b5c6c6;border-radius:4px;padding:13px 14px;font-size:16px;color:#0a363b;min-height:48px}
|
||||
& .form-field select{background:#f5f8f7;border:1px solid #b5c6c6;border-radius:4px;padding:13px 14px;font-size:16px;color:#0a363b;min-height:48px}
|
||||
& .form-field textarea{background:#f5f8f7;border:1px solid #b5c6c6;border-radius:4px;padding:13px 14px;font-size:16px;color:#0a363b;min-height:110px}
|
||||
& .form-field input:focus{border-color:#0a363b;outline:2px solid #ff753d;outline-offset:2px}
|
||||
& .form-field select:focus{border-color:#0a363b;outline:2px solid #ff753d;outline-offset:2px}
|
||||
& .form-field textarea:focus{border-color:#0a363b;outline:2px solid #ff753d;outline-offset:2px}
|
||||
& .field-note{font-size:12px;color:#52696b;opacity:1}
|
||||
& .check-row label{font-weight:400;font-size:13px;line-height:1.6;color:#52696b}
|
||||
& .check-row input{accent-color:#0a363b}
|
||||
& .cms-button{width:100%;justify-content:center;min-height:54px;font-size:16px;transition:transform .2s ease,background-color .2s ease}
|
||||
& .cms-button:hover{transform:translateY(-2px)}
|
||||
& .cms-button .button-arrow{display:none}';
|
||||
|
||||
$startseite = [
|
||||
'schemaVersion' => $SCHEMA,
|
||||
'title' => 'Zeit für woanders.',
|
||||
'description' => 'Türkei im Herzen. Die Welt im Blick. Persönliche Reiseberatung in Baden-Baden: Türkei-Urlaub, Hotels weltweit und Flüge in die Türkei.',
|
||||
'blocks' => [
|
||||
// 1 – Hero: Petrol links mit Text, Foto rechts mit Bildunterschrift.
|
||||
$section('hero', [
|
||||
$row('hero-zeile', [
|
||||
$column('hero-text', [
|
||||
$kleinzeile('hero-klein', 'Türkei im Herzen. Die Welt im Blick.', '@auf-dunkel', '0px'),
|
||||
$heading('hero-titel', 'Zeit für<br>wo<em>anders.</em>', 1, [
|
||||
'desktop' => ['fontSize' => '104px', 'fontWeight' => '500', 'lineHeight' => 1.01, 'letterSpacing' => '-0.058em', 'color' => '@papier', 'margin' => ['top' => '30px', 'bottom' => '30px']],
|
||||
'tablet' => ['fontSize' => '76px'],
|
||||
'mobile' => ['fontSize' => '68px', 'margin' => ['top' => '24px', 'bottom' => '22px']],
|
||||
], $serifHell),
|
||||
$text('hero-satz', 'Salz auf der Haut. Sonne im Gesicht.<br>Und eine Reise, die zu dir passt.', [
|
||||
'desktop' => ['fontSize' => '17px', 'lineHeight' => 1.75, 'color' => '#d9e4e3', 'margin' => ['bottom' => '32px']],
|
||||
'mobile' => ['fontSize' => '16px', 'margin' => ['bottom' => '24px']],
|
||||
]),
|
||||
$knopf('hero-knopf', 'Deine Auszeit planen', '#kontakt'),
|
||||
$zeile('hero-unten', [
|
||||
$text('hero-unten-links', 'Persönlich beraten.<br>Entspannt verreisen.', ['desktop' => ['fontSize' => '11px', 'lineHeight' => 1.6, 'letterSpacing' => '0.14em', 'textTransform' => 'uppercase', 'color' => '#a1bcbc', 'margin' => ['bottom' => '0px']]]),
|
||||
$text('hero-unten-rechts', 'Baden-Baden · Oos', ['desktop' => ['fontSize' => '12px', 'letterSpacing' => '0.08em', 'color' => '#a1bcbc', 'margin' => ['bottom' => '0px']]], 'white-space:nowrap'),
|
||||
], '20px', ['justifyContent' => 'space-between', 'alignItems' => 'flex-end', 'width' => '100%', 'margin' => ['top' => 'auto'], 'padding' => ['top' => '56px']], ['padding' => ['top' => '32px']]),
|
||||
], [
|
||||
'desktop' => ['display' => 'flex', 'flexDirection' => 'column', 'alignItems' => 'flex-start', 'backgroundColor' => '@tinte', 'color' => '@papier',
|
||||
'padding' => ['top' => '68px', 'right' => '11%', 'bottom' => '34px', 'left' => '11%']],
|
||||
'tablet' => ['padding' => ['top' => '48px', 'right' => '8%', 'bottom' => '30px', 'left' => '8%']],
|
||||
'mobile' => ['padding' => ['top' => '40px', 'right' => '18px', 'bottom' => '28px', 'left' => '18px']],
|
||||
]),
|
||||
$column('hero-foto-spalte', [
|
||||
// Das größte Element über der Falz: keine Einblendung (LCP).
|
||||
$bild('hero-foto', $medien['kueste'], 'Türkisfarbene Mittelmeerbucht mit sonniger Strandterrasse', [
|
||||
'desktop' => ['position' => 'absolute', 'top' => '0px', 'right' => '0px', 'bottom' => '0px', 'left' => '0px', 'margin' => $seiten('0px')],
|
||||
], null, '& img{width:100%;height:100%;object-fit:cover;object-position:55% center;border-radius:0}'),
|
||||
$text('hero-etikett', 'Reiseinspiration', ['desktop' => ['position' => 'absolute', 'top' => '24px', 'right' => '22px', 'backgroundColor' => '@papier', 'fontSize' => '10px', 'fontWeight' => '600', 'letterSpacing' => '0.15em', 'textTransform' => 'uppercase', 'color' => '@tinte', 'padding' => ['top' => '9px', 'right' => '11px', 'bottom' => '9px', 'left' => '11px'], 'margin' => ['bottom' => '0px']]]),
|
||||
$text('hero-bildzeile', '<strong>Dein nächster Lieblingsort</strong>Ein bisschen mehr Meer.', [
|
||||
'desktop' => ['position' => 'absolute', 'left' => '32px', 'bottom' => '32px', 'maxWidth' => '82%', 'backgroundColor' => '@papier', 'fontSize' => '23px', 'fontWeight' => '500', 'lineHeight' => 1.2, 'letterSpacing' => '-0.025em', 'color' => '@tinte',
|
||||
'padding' => ['top' => '19px', 'right' => '23px', 'bottom' => '19px', 'left' => '23px'], 'margin' => ['bottom' => '0px']],
|
||||
'mobile' => ['left' => '18px', 'bottom' => '18px', 'fontSize' => '20px', 'padding' => ['top' => '15px', 'right' => '18px', 'bottom' => '15px', 'left' => '18px']],
|
||||
], '& strong{display:block;font-size:10px;font-weight:600;letter-spacing:.17em;text-transform:uppercase;margin-bottom:9px}'),
|
||||
], [
|
||||
'desktop' => ['position' => 'relative', 'overflow' => 'hidden', 'minHeight' => '650px', 'backgroundColor' => '#3e8a94'],
|
||||
'tablet' => ['minHeight' => '440px'],
|
||||
'mobile' => ['minHeight' => '370px'],
|
||||
]),
|
||||
], $zweiSpalten, ['desktop' => ['gap' => '0px', 'margin' => ['top' => '0px'], 'columnWidths' => '9 11', 'alignItems' => 'stretch']]),
|
||||
], ['desktop' => ['padding' => ['top' => '0px', 'bottom' => '0px']], 'mobile' => ['padding' => ['top' => '0px', 'bottom' => '0px']]], null, '& .section-inner{max-width:1600px;margin-inline:auto}', 'full'),
|
||||
|
||||
// 2 – Reisen: Kopf mit Satz rechts, drei Angebote zwischen Linien.
|
||||
$section('reisen-bereich', [
|
||||
$row('reisen-kopf', [
|
||||
$column('reisen-kopf-titel', [
|
||||
$kleinzeile('reisen-klein', 'Wohin zieht es dich?'),
|
||||
$heading('reisen-titel', 'Du hast Fernweh.<br>Wir haben Ideen.', 2, $h2Stil),
|
||||
]),
|
||||
$text('reisen-satz', 'Eine Auszeit am Meer, ein neuer Lieblingsort oder ein Besuch bei der Familie. Wir finden die passende Reise für dich.', [
|
||||
'desktop' => ['maxWidth' => '360px', 'fontSize' => '15px', 'lineHeight' => 1.8, 'color' => '@text', 'margin' => ['bottom' => '0px']],
|
||||
'mobile' => ['maxWidth' => 'none'],
|
||||
]),
|
||||
], [], [
|
||||
'desktop' => ['display' => 'flex', 'flexWrap' => 'wrap', 'justifyContent' => 'space-between', 'alignItems' => 'flex-end', 'rowGap' => '22px', 'columnGap' => '50px', 'margin' => ['top' => '0px', 'bottom' => '46px']],
|
||||
'mobile' => ['margin' => ['bottom' => '30px']],
|
||||
]),
|
||||
$column('reisen-raster', [
|
||||
$angebot(1, 'Unser Schwerpunkt', 'sun', 'Türkei.<br>Ganz nach dir.', 'Von der Türkischen Riviera bis zur Ägäis. Entdecke Urlaub zwischen Gastfreundschaft, Küste und Kultur – mit Tipps aus erster Hand.', 'Türkei-Urlaub anfragen'),
|
||||
$angebot(2, 'Hotels weltweit', 'building', 'Einchecken.<br>Abschalten.', 'Kleines Boutiquehotel oder großzügiges Resort? Wir helfen dir, den richtigen Ort für deine Auszeit zu finden.', 'Hotel anfragen'),
|
||||
$angebot(3, 'Flüge in die Türkei', 'navigation', 'Manchmal ist<br>das Ziel Zuhause.', 'Für Urlaub, Familie oder einfach Sehnsucht. Wir suchen dir die passende Verbindung – auch ab Karlsruhe/Baden-Baden, Stuttgart oder Frankfurt.', 'Türkei-Flug anfragen'),
|
||||
], [
|
||||
'desktop' => ['display' => 'grid', 'gridColumns' => 3, 'gap' => '0px', 'borderWidth' => ['top' => '1px', 'right' => '0px', 'bottom' => '1px', 'left' => '0px'], 'borderStyle' => 'solid', 'borderColor' => '@linie'],
|
||||
'mobile' => ['gridColumns' => 1],
|
||||
], '& [id="angebot-1"]{padding-left:0}'),
|
||||
], [
|
||||
'desktop' => ['padding' => ['top' => '96px', 'bottom' => '88px']],
|
||||
'mobile' => ['padding' => ['top' => '56px', 'bottom' => '48px']],
|
||||
], 'reisen'),
|
||||
|
||||
// 3 – Über uns: „Gute Reisen beginnen mit einem Gespräch“, dazu die Anschrift.
|
||||
$section('beratung-bereich', [
|
||||
$row('beratung-zeile', [
|
||||
$column('beratung-links', [
|
||||
$kleinzeile('beratung-klein', 'Dein Reisebüro.<br>Dein direkter Draht.'),
|
||||
$zeile('beratung-ort', [
|
||||
$symbol('beratung-ort-icon', 'pin', 20, ['desktop' => ['color' => '@orange-text', 'flexShrink' => 0]]),
|
||||
$text('beratung-ort-text', $firma.'<br>'.$strasse.'<br>'.$plz.' '.$ort.'-Oos', ['desktop' => ['fontSize' => '14px', 'lineHeight' => 1.7, 'color' => '@text', 'margin' => ['bottom' => '0px']]]),
|
||||
], '10px', ['alignItems' => 'flex-start', 'margin' => ['top' => '8px']]),
|
||||
$zeile('beratung-mail', [
|
||||
$symbol('beratung-mail-icon', 'mail', 20, ['desktop' => ['color' => '@orange-text', 'flexShrink' => 0]]),
|
||||
$text('beratung-mail-text', '<a href="mailto:'.$mail.'">'.$mail.'</a>', ['desktop' => ['fontSize' => '14px', 'lineHeight' => 1.7, 'color' => '@text', 'margin' => ['bottom' => '0px']]], $linkCss),
|
||||
], '10px', ['margin' => ['top' => '14px']]),
|
||||
$zeile('beratung-telefon', [
|
||||
$symbol('beratung-telefon-icon', 'phone', 20, ['desktop' => ['color' => '@orange-text', 'flexShrink' => 0]]),
|
||||
$text('beratung-telefon-text', '<a href="'.$tel.'">'.$telefon.'</a>', ['desktop' => ['fontSize' => '14px', 'lineHeight' => 1.7, 'color' => '@text', 'margin' => ['bottom' => '0px']]], $linkCss),
|
||||
], '10px', ['margin' => ['top' => '14px']]),
|
||||
]),
|
||||
$column('beratung-rechts', [
|
||||
$heading('beratung-titel', 'Gute Reisen beginnen<br>mit einem <em>Gespräch.</em>', 2, [...$h2Stil, 'desktop' => [...$h2Stil['desktop'], 'fontSize' => '56px']], $serif),
|
||||
$zeile('beratung-unten', [
|
||||
$text('beratung-satz', 'Erzähl uns, was dir wichtig ist. Wir hören zu, denken mit und planen mit dir. Persönlich, unkompliziert und mit besonderer Nähe zur Türkei – ob Strandurlaub, Familienbesuch oder Städtereise nach Istanbul.', [
|
||||
'desktop' => ['flexGrow' => 1, 'flexBasis' => '300px', 'maxWidth' => '430px', 'fontSize' => '15px', 'lineHeight' => 1.8, 'color' => '@text', 'margin' => ['bottom' => '0px']],
|
||||
'mobile' => ['flexBasis' => 'auto', 'maxWidth' => 'none'],
|
||||
]),
|
||||
$zeile('beratung-knoepfe', [
|
||||
$knopf('beratung-knopf', 'Lass uns sprechen', '#kontakt', 'rahmen'),
|
||||
$waKnopf('beratung-whatsapp'),
|
||||
], '12px', ['flexWrap' => 'wrap', 'flexShrink' => 0]),
|
||||
], '40px', ['justifyContent' => 'space-between', 'flexWrap' => 'wrap', 'rowGap' => '22px', 'margin' => ['top' => '30px']]),
|
||||
]),
|
||||
], $zweiSpalten, [
|
||||
'desktop' => ['gap' => '30px', 'margin' => ['top' => '0px'], 'columnWidths' => '1 2'],
|
||||
'tablet' => ['gap' => '24px'],
|
||||
]),
|
||||
// Neu gegenüber der Vorlage: der Weg zur Reise in drei Schritten.
|
||||
$column('schritte', [
|
||||
$schritt(1, 'Wunsch erzählen', 'Per Formular, E-Mail oder direkt bei uns in Oos. Ein grober Plan genügt.'),
|
||||
$schritt(2, 'Vorschläge bekommen', 'Wir vergleichen Hotels, Flüge und Termine und melden uns mit passenden Ideen.'),
|
||||
$schritt(3, 'Entspannt losfliegen', 'Du entscheidest, wir buchen. Unterlagen und Ansprechpartner kommen von uns.'),
|
||||
], [
|
||||
'desktop' => ['display' => 'grid', 'gridColumns' => 3, 'gap' => '40px', 'margin' => ['top' => '72px']],
|
||||
'mobile' => ['gridColumns' => 1, 'gap' => '28px', 'margin' => ['top' => '48px']],
|
||||
]),
|
||||
], [
|
||||
'desktop' => ['padding' => ['top' => '0px', 'bottom' => '96px']],
|
||||
'mobile' => ['padding' => ['top' => '0px', 'bottom' => '56px']],
|
||||
], 'beratung', '& h2 br{display:inline}'),
|
||||
|
||||
// 4 – Anfrage: Petrol mit oranger Oberkante, weiße Formularkarte rechts.
|
||||
$section('kontakt-bereich', [
|
||||
$row('kontakt-zeile', [
|
||||
$column('kontakt-text', [
|
||||
$kleinzeile('kontakt-klein', 'Der erste Schritt zur Auszeit', '@auf-dunkel'),
|
||||
$heading('kontakt-titel', 'Wohin darf<br>es <em>gehen?</em>', 2, [
|
||||
'desktop' => ['fontSize' => '66px', 'fontWeight' => '500', 'lineHeight' => 1.05, 'letterSpacing' => '-0.045em', 'color' => '@papier', 'margin' => ['top' => '0px', 'bottom' => '25px']],
|
||||
'mobile' => ['fontSize' => '54px'],
|
||||
], $serifHell),
|
||||
$text('kontakt-satz', 'Ein Wunsch. Ein grober Plan. Oder schon ein festes Ziel. Erzähl uns davon – wir melden uns persönlich bei dir.', $fliess('#c2d3d3', '32px', '320px')),
|
||||
$text('kontakt-whatsapp-satz', 'Lieber kurz schreiben? Auf WhatsApp antworten wir dir persönlich.', ['desktop' => ['fontSize' => '14px', 'lineHeight' => 1.6, 'color' => '#c2d3d3', 'margin' => ['bottom' => '14px']]]),
|
||||
$waKnopf('kontakt-whatsapp', ['desktop' => ['margin' => ['bottom' => '36px']]]),
|
||||
$text('kontakt-hinweis', 'Türkei-Reisen · Hotels weltweit · Türkei-Flüge<br><a href="'.$tel.'">'.$telefon.'</a> · <a href="mailto:'.$mail.'">'.$mail.'</a>', ['desktop' => ['fontSize' => '13px', 'lineHeight' => 1.9, 'color' => '#a5bebe', 'margin' => ['bottom' => '0px']]],
|
||||
"& a{color:#fff;text-decoration:underline;text-decoration-color:#ff753d;text-underline-offset:4px}\n& a:hover{color:#ffab82}"),
|
||||
]),
|
||||
$column('kontakt-karte', [
|
||||
$kleinzeile('kontakt-karte-klein', 'Persönliche Reiseberatung', '@orange-text', '10px'),
|
||||
$heading('kontakt-karte-titel', 'Deine Reiseanfrage', 3, ['desktop' => ['fontSize' => '30px', 'fontWeight' => '500', 'letterSpacing' => '-0.035em', 'color' => '@tinte', 'margin' => ['top' => '0px', 'bottom' => '10px']], 'mobile' => ['fontSize' => '28px']]),
|
||||
$text('kontakt-karte-satz', 'Trag deine Wünsche ein – hier beginnt deine nächste Reise.', ['desktop' => ['fontSize' => '14px', 'lineHeight' => 1.6, 'color' => '@text', 'margin' => ['bottom' => '26px']]]),
|
||||
['id' => 'kontakt-formular', 'type' => 'form', 'props' => ['form' => 'reiseanfrage'], 'css' => $formularCss],
|
||||
$text('kontakt-karte-hinweis', 'Deine Angaben nutzen wir nur für deine Anfrage. Mehr in den <a href="/datenschutz">Datenschutzhinweisen</a>.', ['desktop' => ['fontSize' => '12px', 'lineHeight' => 1.6, 'color' => '@text', 'margin' => ['top' => '16px', 'bottom' => '0px']]], $linkCss),
|
||||
], [
|
||||
'desktop' => ['backgroundColor' => '@papier', 'color' => '@tinte', 'borderRadius' => $rund('8px'), 'padding' => $seiten('36px'), 'shadowY' => '20px', 'shadowBlur' => '50px', 'shadowColor' => '#001c2030'],
|
||||
'mobile' => ['padding' => ['top' => '25px', 'right' => '20px', 'bottom' => '25px', 'left' => '20px']],
|
||||
]),
|
||||
], $zweiSpalten, [
|
||||
'desktop' => ['gap' => '7%', 'alignItems' => 'center', 'margin' => ['top' => '0px'], 'columnWidths' => '9 11'],
|
||||
'tablet' => ['gap' => '40px'],
|
||||
]),
|
||||
], [
|
||||
'desktop' => ['backgroundColor' => '@tinte', 'color' => '@papier', 'padding' => ['top' => '72px', 'bottom' => '72px'], 'borderWidth' => $nurOben('7px'), 'borderStyle' => 'solid', 'borderColor' => '@orange'],
|
||||
'mobile' => ['padding' => ['top' => '44px', 'bottom' => '40px']],
|
||||
], 'kontakt'),
|
||||
|
||||
// 5 – reiseSoft: Text und Funktionen links, echter Screenshot rechts.
|
||||
$section('reisesoft-bereich', [
|
||||
$row('reisesoft-zeile', [
|
||||
$column('reisesoft-text', [
|
||||
$zeile('reisesoft-marke', [
|
||||
$text('reisesoft-name', 'reise<strong>Soft</strong>', ['desktop' => ['fontSize' => '25px', 'letterSpacing' => '-0.04em', 'color' => '@tinte', 'margin' => ['bottom' => '0px']]], '& strong{font-weight:700}'),
|
||||
$text('reisesoft-etikett', 'Für Reisebüros', ['desktop' => ['fontSize' => '10px', 'fontWeight' => '600', 'letterSpacing' => '0.12em', 'textTransform' => 'uppercase', 'color' => '@tinte', 'backgroundColor' => '@papier', 'padding' => ['top' => '6px', 'right' => '8px', 'bottom' => '6px', 'left' => '8px'], 'margin' => ['bottom' => '0px']]]),
|
||||
], '14px', ['margin' => ['bottom' => '26px']]),
|
||||
$kleinzeile('reisesoft-klein', 'Für die andere Seite des Reisens'),
|
||||
$heading('reisesoft-titel', 'Weniger Verwaltung.<br>Mehr <em>Reisebüro.</em>', 2, [...$h2Stil, 'desktop' => [...$h2Stil['desktop'], 'fontSize' => '42px', 'margin' => ['top' => '0px', 'bottom' => '24px']]], $serif),
|
||||
$text('reisesoft-satz', 'reiseSoft ist unsere Webplattform für Reisebüros – entstanden aus dem eigenen Büroalltag. Buchungen, Kunden, Rechnungen und Zahlungen an einem Ort, damit mehr Zeit für die Beratung bleibt.', $fliess('@text', '24px', '440px', '15px')),
|
||||
$column('reisesoft-funktionen', [
|
||||
$funktion(1, 'Buchung per Strg+V übernehmen', 'Aus Amadeus oder dem Airline-Portal kopieren, einfügen, prüfen – fertig.'),
|
||||
$funktion(2, 'Rechnung und E-Rechnung', 'Fortlaufende Rechnungsnummern, ZUGFeRD und XRechnung, Versand per E-Mail.'),
|
||||
$funktion(3, 'Offene Posten im Blick', 'Zahlungen buchen, Umsatz und Ertrag auswerten – direkt im Browser.'),
|
||||
], ['desktop' => ['margin' => ['bottom' => '30px']]], '& [id="funktion-3"]{border-bottom:1px solid #d5dfde}'),
|
||||
$zeile('reisesoft-knoepfe', [
|
||||
$knopf('reisesoft-plattform', 'Zur reiseSoft-Plattform', $reisesoft, 'petrol', [], 'external'),
|
||||
$knopf('reisesoft-anfragen', 'reiseSoft kennenlernen', '#kontakt', 'rahmen'),
|
||||
], '12px', ['flexWrap' => 'wrap']),
|
||||
]),
|
||||
$column('reisesoft-bild-spalte', [
|
||||
$column('reisesoft-fenster', [
|
||||
$zeile('reisesoft-fenster-leiste', [
|
||||
$text('reisesoft-fenster-adresse', 'reisesoft.kantarci-reisen.de', ['desktop' => ['flexGrow' => 1, 'fontSize' => '11px', 'color' => '@text', 'textAlign' => 'center', 'backgroundColor' => '@pale', 'borderRadius' => $rund('4px'), 'padding' => ['top' => '5px', 'right' => '10px', 'bottom' => '5px', 'left' => '10px'], 'margin' => ['bottom' => '0px']]]),
|
||||
], '16px', ['padding' => ['top' => '10px', 'right' => '14px', 'bottom' => '10px', 'left' => '14px'], 'borderWidth' => $nurUnten('1px'), 'borderStyle' => 'solid', 'borderColor' => '#e8eded'], [], [],
|
||||
// Die drei Punkte eines Browserfensters, als Schatten eines einzigen Punkts.
|
||||
"&::before{content:'';flex-shrink:0;width:9px;height:9px;margin-right:30px;border-radius:50%;background:#ff753d;box-shadow:15px 0 #d5dfde,30px 0 #d5dfde}"),
|
||||
$bild('reisesoft-bild', $medien['reisesoft'], 'reiseSoft-Übersicht mit offenen Posten, Umsatz und den letzten Vorgängen (Beispieldaten) – zur Plattform', ['desktop' => ['margin' => $seiten('0px')]], $reisesoft,
|
||||
"& img{display:block;width:100%;height:auto;border-radius:0}\n& .image-link:focus-visible{outline:3px solid #ff753d;outline-offset:-3px}"),
|
||||
], [
|
||||
'desktop' => ['overflow' => 'hidden', 'backgroundColor' => '@papier', 'borderWidth' => $seiten('1px'), 'borderStyle' => 'solid', 'borderColor' => '#d5dfde', 'borderRadius' => $rund('8px'), 'shadowY' => '24px', 'shadowBlur' => '50px', 'shadowColor' => '#0a363b26'],
|
||||
], "transform:rotate(-2deg);transition:transform .5s cubic-bezier(.2,.7,.2,1)\n&:hover{transform:rotate(0deg) scale(1.01)}"),
|
||||
$text('reisesoft-bildzeile', 'Echte Oberfläche, fiktive Beispieldaten.', ['desktop' => ['fontSize' => '12px', 'color' => '@text', 'textAlign' => 'right', 'margin' => ['top' => '22px', 'bottom' => '0px']]]),
|
||||
]),
|
||||
], $zweiSpalten, [
|
||||
'desktop' => ['gap' => '72px', 'alignItems' => 'center', 'margin' => ['top' => '0px'], 'columnWidths' => '9 11'],
|
||||
'tablet' => ['gap' => '48px'],
|
||||
]),
|
||||
], [
|
||||
'desktop' => ['backgroundColor' => '@pale', 'padding' => ['top' => '88px', 'bottom' => '96px']],
|
||||
'mobile' => ['padding' => ['top' => '48px', 'bottom' => '52px']],
|
||||
], 'reisesoft'),
|
||||
],
|
||||
];
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// Seiten anlegen und veröffentlichen
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
$anlegen = static function (string $slug, array $dokument, string $seoTitel = '', string $teilbild = '') use ($pages, $db): string {
|
||||
$id = $pages->createPage($slug, null, 'page', $dokument);
|
||||
if ('' !== $seoTitel) {
|
||||
$db->executeStatement('UPDATE page SET seo_title = ? WHERE id = ?', [$seoTitel, $id]);
|
||||
}
|
||||
if ('' !== $teilbild) {
|
||||
$db->executeStatement('UPDATE page SET og_media_id = ? WHERE id = ?', [$teilbild, $id]);
|
||||
}
|
||||
$seite = $db->fetchAssociative('SELECT draft_revision_id, version FROM page WHERE id = ?', [$id]);
|
||||
$pages->publish($id, $seite['draft_revision_id'], (int) $seite['version']);
|
||||
echo 'Seite angelegt: /'.$db->fetchOne('SELECT path FROM page WHERE id = ?', [$id])."\n";
|
||||
|
||||
return $id;
|
||||
};
|
||||
|
||||
// Der Hero-Text steigt beim Laden auf, das Foto nicht (LCP). Weiter unten erscheinen Köpfe und
|
||||
// Raster beim Scrollen.
|
||||
$startseite = $einblenden($startseite, [
|
||||
'hero-titel' => ['rise', 'short'],
|
||||
'hero-satz' => ['rise', 'medium'],
|
||||
'hero-knopf' => ['rise', 'medium'],
|
||||
'hero-bildzeile' => ['left', 'long'],
|
||||
'reisen-kopf' => ['rise'],
|
||||
'angebot-1' => ['rise'],
|
||||
'angebot-2' => ['rise', 'short'],
|
||||
'angebot-3' => ['rise', 'medium'],
|
||||
'beratung-zeile' => ['rise'],
|
||||
'schritt-1' => ['rise'],
|
||||
'schritt-2' => ['rise', 'short'],
|
||||
'schritt-3' => ['rise', 'medium'],
|
||||
'kontakt-text' => ['rise'],
|
||||
'kontakt-karte' => ['rise', 'short'],
|
||||
'reisesoft-text' => ['rise'],
|
||||
'reisesoft-bild-spalte' => ['right', 'short'],
|
||||
]);
|
||||
|
||||
$homeId = $anlegen('home', $startseite, 'Kantarci Reisen – Türkei-Urlaub, Hotels & Flüge | Reisebüro Baden-Baden', $medien['kueste']['id']);
|
||||
|
||||
/** Eine Rechtsseite: Kleinzeile, Titel und Vorspann auf Petrol, 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, '@auf-dunkel'),
|
||||
$heading($praefix.'-h1', $titel, 1, [
|
||||
'desktop' => ['fontSize' => '66px', 'fontWeight' => '500', 'lineHeight' => 1.05, 'letterSpacing' => '-0.045em', 'color' => '@papier', 'margin' => ['top' => '0px', 'bottom' => '20px']],
|
||||
'mobile' => ['fontSize' => '46px'],
|
||||
]),
|
||||
$text($praefix.'-vorspann', $vorspann, [
|
||||
'desktop' => ['maxWidth' => '640px', 'fontSize' => '17px', 'lineHeight' => 1.75, 'color' => '@auf-dunkel', 'margin' => ['bottom' => '0px']],
|
||||
'mobile' => ['fontSize' => '16px'],
|
||||
]),
|
||||
], [
|
||||
'desktop' => ['backgroundColor' => '@tinte', 'color' => '@papier', 'padding' => ['top' => '80px', 'bottom' => '68px'], 'borderWidth' => ['top' => '0px', 'right' => '0px', 'bottom' => '7px', 'left' => '0px'], 'borderStyle' => 'solid', 'borderColor' => '@orange'],
|
||||
'mobile' => ['padding' => ['top' => '52px', 'bottom' => '44px']],
|
||||
]);
|
||||
|
||||
$punkte = [];
|
||||
foreach ($absaetze as $i => [$zwischentitel, $inhalt]) {
|
||||
$punkte[] = $row($praefix.'-punkt-'.$i, [
|
||||
$column($praefix.'-punkt-'.$i.'-titel', '' === $zwischentitel ? [] : [
|
||||
$heading($praefix.'-h2-'.$i, $zwischentitel, 2, [
|
||||
'desktop' => ['fontSize' => '19px', 'fontWeight' => '600', 'lineHeight' => 1.3, 'letterSpacing' => '-0.01em', 'color' => '@tinte', 'margin' => ['bottom' => '0px']],
|
||||
]),
|
||||
], [
|
||||
'desktop' => ['flexBasis' => '260px', 'flexShrink' => 0],
|
||||
'tablet' => ['flexBasis' => '200px'],
|
||||
'mobile' => ['flexBasis' => 'auto'],
|
||||
]),
|
||||
$column($praefix.'-punkt-'.$i.'-text', [
|
||||
$text($praefix.'-p-'.$i, $inhalt, ['desktop' => ['fontSize' => '16px', 'lineHeight' => 1.75, 'color' => '@text', 'margin' => ['bottom' => '0px']]], $linkCss),
|
||||
], ['desktop' => ['flexGrow' => 1, 'flexBasis' => '0', 'minWidth' => '0'], 'mobile' => ['flexBasis' => 'auto']]),
|
||||
], [], [
|
||||
'desktop' => ['display' => 'flex', 'alignItems' => 'flex-start', 'gap' => '44px', 'margin' => ['top' => '0px'],
|
||||
'borderWidth' => $nurOben('1px'), 'borderStyle' => 'solid', 'borderColor' => '@linie', 'padding' => ['top' => '26px', 'bottom' => '26px']],
|
||||
'tablet' => ['gap' => '28px'],
|
||||
'mobile' => ['flexDirection' => 'column', 'gap' => '8px', 'padding' => ['top' => '20px', 'bottom' => '20px']],
|
||||
]);
|
||||
}
|
||||
|
||||
$inhalt = $section($praefix, [
|
||||
$column($praefix.'-inhalt', $punkte, ['desktop' => ['maxWidth' => '920px']]),
|
||||
], ['desktop' => ['padding' => ['top' => '48px', 'bottom' => '96px']], 'mobile' => ['padding' => ['top' => '24px', 'bottom' => '64px']]]);
|
||||
|
||||
return [$kopf, $inhalt];
|
||||
};
|
||||
|
||||
$anlegen('impressum', [
|
||||
'schemaVersion' => $SCHEMA, 'title' => 'Impressum',
|
||||
'description' => 'Anbieterangaben von Kantarci Reisen, Inhaber I. Kantarci, Baden-Baden.',
|
||||
'blocks' => [
|
||||
...$rechtsseite('impressum', 'Rechtliche Angaben', 'Impressum', 'Angaben gemäß § 5 DDG und § 18 Abs. 2 MStV.', [
|
||||
['Anbieter', $firma.'<br>Inhaber: '.$inhaber.'<br>'.$strasse.'<br>'.$plz.' '.$ort],
|
||||
['Kontakt', 'Telefon: <a href="'.$tel.'">'.$telefon.'</a><br>E-Mail: <a href="mailto:'.$mail.'">'.$mail.'</a><br>WhatsApp: <a href="'.$whatsapp.'">'.$telefon.'</a>'],
|
||||
['Verantwortlich für den Inhalt', $inhaber.', Anschrift wie oben.'],
|
||||
['Reisevermittlung', 'Kantarci Reisen vermittelt Reiseleistungen wie Pauschalreisen, Hotelaufenthalte und Flüge. Vertragspartner für die jeweilige Leistung ist der Reiseveranstalter, das Hotel oder die Fluggesellschaft; es gelten deren Geschäfts- und Beförderungsbedingungen, die wir vor der Buchung mitgeben.'],
|
||||
['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. Reiseinformationen geben wir nach bestem Wissen weiter; verbindlich sind die Angaben im jeweiligen Angebot. Bei Bekanntwerden von Rechtsverletzungen entfernen wir betroffene Inhalte umgehend.'],
|
||||
['Haftung für Links', 'Diese Website verweist auf die Plattform reiseSoft unter <a href="'.$reisesoft.'">reisesoft.kantarci-reisen.de</a>. Für Inhalte fremder Seiten ist der jeweilige Anbieter verantwortlich.'],
|
||||
['Urheberrecht', 'Die von uns erstellten Inhalte und Werke auf diesen Seiten unterliegen dem deutschen Urheberrecht. Vervielfältigung, Bearbeitung, Verbreitung und jede Art der Verwertung außerhalb der Grenzen des Urheberrechts bedürfen der schriftlichen Zustimmung.'],
|
||||
['Bildnachweis', 'Logo: '.$firma.'. Reisefoto der Startseite: KI-generiert. Screenshot: reiseSoft mit fiktiven Beispieldaten.'],
|
||||
]),
|
||||
],
|
||||
], 'Impressum | '.$firma);
|
||||
|
||||
$anlegen('datenschutz', [
|
||||
'schemaVersion' => $SCHEMA, 'title' => 'Datenschutz',
|
||||
'description' => 'Welche Daten beim Besuch der Website von Kantarci Reisen und bei einer Reiseanfrage verarbeitet werden.',
|
||||
'blocks' => [
|
||||
...$rechtsseite('datenschutz', 'Deine Daten', 'Datenschutz', 'Hier erfährst du, welche Daten beim Besuch dieser Website und bei einer Anfrage verarbeitet werden – und welche Rechte du hast.', [
|
||||
['1. Verantwortlicher', $firma.', Inhaber '.$inhaber.', '.$strasse.', '.$plz.' '.$ort.'. Telefon <a href="'.$tel.'">'.$telefon.'</a>, E-Mail <a href="mailto:'.$mail.'">'.$mail.'</a>.'],
|
||||
['2. Besuch dieser Website', 'Beim Aufruf verarbeitet unser Hostingdienstleister technisch erforderliche Verbindungsdaten: IP-Adresse, Zeitpunkt, angeforderte Adresse, Browserangaben und HTTP-Status. Das dient der sicheren Bereitstellung und der Fehlersuche. Rechtsgrundlage ist unser berechtigtes Interesse an einem störungsfreien Betrieb, Art. 6 Abs. 1 lit. f DSGVO.<br><br>Mit dem Hostingdienstleister besteht ein Vertrag über die Auftragsverarbeitung nach Art. 28 DSGVO. Die Serverprotokolle werden nach spätestens sieben Tagen gelöscht oder anonymisiert, sofern sie nicht zur Aufklärung eines Sicherheitsvorfalls länger gebraucht werden.'],
|
||||
['3. Cookies und externe Dienste', 'Diese Website setzt keine Analyse- oder Werbecookies. Bilder, Schriften und Gestaltung kommen vom eigenen Server; es werden keine Karten, Videos oder Schriften von Dritten nachgeladen. Deshalb gibt es auch keinen Einwilligungsdialog.'],
|
||||
['4. Reiseanfrage', 'Wenn du uns über das Formular schreibst, verarbeiten wir deinen Namen, deine E-Mail-Adresse, auf Wunsch deine Telefonnummer, Reisezeitraum und Personenzahl sowie deine Wünsche, um deine Anfrage zu beantworten und passende Angebote 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. Buchungen', 'Kommt es zu einer Buchung, geben wir die dafür nötigen Angaben (zum Beispiel Namen der Reisenden laut Ausweis und Reisedaten) an den Reiseveranstalter, das Hotel oder die Fluggesellschaft weiter (Art. 6 Abs. 1 lit. b DSGVO). Rechnungsunterlagen bewahren wir so lange auf, wie Handels- und Steuerrecht es verlangen.'],
|
||||
['6. Kontakt per E-Mail oder Telefon', 'Wenn du uns schreibst oder anrufst, verarbeiten wir deine Angaben, um dein Anliegen zu bearbeiten (Art. 6 Abs. 1 lit. b oder f DSGVO).'],
|
||||
['7. WhatsApp', 'Die WhatsApp-Knöpfe sind einfache Links. Erst wenn du einen davon nutzt, öffnet sich WhatsApp, ein Dienst der WhatsApp Ireland Limited (Meta). Dabei verarbeitet WhatsApp deine Telefonnummer, Nachrichten und Nutzungsdaten nach eigenen Datenschutzhinweisen, auch in Ländern außerhalb der EU. Wir nutzen deine Nachricht nur, um dein Anliegen zu beantworten (Art. 6 Abs. 1 lit. b DSGVO). Wenn du das nicht möchtest, erreichst du uns genauso per Formular, E-Mail oder Telefon.'],
|
||||
['8. reiseSoft', 'Der Link zur Plattform reiseSoft führt auf eine eigene Anwendung unter reisesoft.kantarci-reisen.de. Erst wenn du ihm folgst, werden dort Daten verarbeitet; für angemeldete Reisebüros gelten die Hinweise der Plattform.'],
|
||||
['9. Deine Rechte', 'Du hast 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 kannst du widersprechen.'],
|
||||
['10. Beschwerderecht', 'Du kannst dich bei einer Datenschutz-Aufsichtsbehörde beschweren, für uns beim Landesbeauftragten für den Datenschutz und die Informationsfreiheit Baden-Württemberg.'],
|
||||
['', 'Fragen zum Datenschutz beantworten wir gern unter <a href="mailto:'.$mail.'">'.$mail.'</a>.'],
|
||||
]),
|
||||
],
|
||||
], 'Datenschutz | '.$firma);
|
||||
|
||||
$fehlerId = $anlegen('404', [
|
||||
'schemaVersion' => $SCHEMA, 'title' => 'Seite nicht gefunden',
|
||||
'description' => 'Die gesuchte Seite gibt es nicht oder sie wurde verschoben.',
|
||||
'blocks' => [
|
||||
$section('fehler', [
|
||||
$kleinzeile('fehler-label', 'Fehler 404', '@orange-text'),
|
||||
$heading('fehler-titel', 'Hier geht’s<br>leider <em>nicht weiter.</em>', 1, [
|
||||
'desktop' => ['fontSize' => '72px', 'fontWeight' => '500', 'lineHeight' => 1.05, 'letterSpacing' => '-0.05em', 'color' => '@tinte', 'margin' => ['top' => '0px', 'bottom' => '22px']],
|
||||
'mobile' => ['fontSize' => '46px'],
|
||||
], $serif),
|
||||
$text('fehler-text', 'Diese Seite gibt es nicht oder sie wurde verschoben. Von der Startseite aus findest du sicher dein nächstes Ziel.', $fliess('@text', '32px', '480px', '17px')),
|
||||
$knopf('fehler-knopf', 'Zur Startseite', '/'),
|
||||
], [
|
||||
'desktop' => ['padding' => ['top' => '112px', 'bottom' => '132px']],
|
||||
'mobile' => ['padding' => ['top' => '64px', 'bottom' => '80px']],
|
||||
]),
|
||||
],
|
||||
]);
|
||||
$db->executeStatement('UPDATE page SET robots = ? WHERE id = ?', ['noindex', $fehlerId]);
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// Menü, Einstellungen, Konto
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
$sprung = static fn (string $label, string $anchor, array $extra = []): array => ['label' => $label, 'type' => 'page', 'pageId' => $homeId, 'anchor' => $anchor, 'children' => [], ...$extra];
|
||||
$menus->save('header', 'Hauptnavigation', [
|
||||
$sprung('Reisen', 'reisen'),
|
||||
$sprung('Über uns', 'beratung'),
|
||||
$sprung('reiseSoft', 'reisesoft'),
|
||||
$sprung('Kontakt', 'kontakt'),
|
||||
// „Reise anfragen“ als Menü-Button (ADR 110): auf dem Telefon neben dem Menü-Knopf.
|
||||
$sprung('Reise anfragen', 'kontakt', [
|
||||
'icon' => 'arrow-right', 'iconPosition' => 'after',
|
||||
'button' => [
|
||||
'id' => 'm'.substr(hash('sha256', 'kantarci-reisen-anfrage'), 0, 10), 'shape' => 'rounded', 'appearance' => 'solid', 'size' => 'normal',
|
||||
'fill' => '@orange', 'ink' => '@knopf-schrift', 'hoverFill' => '@tinte', 'hoverInk' => '@papier',
|
||||
'mobile' => 'bar', 'iconMotion' => 'slide',
|
||||
],
|
||||
]),
|
||||
], 0);
|
||||
echo "Menü angelegt\n";
|
||||
|
||||
$settings->assignNotFoundPage($fehlerId, '');
|
||||
$settings->save([
|
||||
'legalName' => $firma,
|
||||
'description' => 'Türkei im Herzen. Die Welt im Blick. Persönliche Reiseberatung in Baden-Baden: Türkei-Urlaub, Hotels weltweit und Flüge in die Türkei.',
|
||||
'email' => $mail, 'phone' => '+49 170 2945935',
|
||||
'social' => ['whatsapp' => $whatsapp],
|
||||
'street' => $strasse, 'postalCode' => $plz, 'city' => $ort, 'country' => 'DE',
|
||||
'areaServed' => implode("\n", ['Baden-Baden', 'Rastatt', 'Bühl', 'Gaggenau', 'Karlsruhe']),
|
||||
'localBusiness' => true, 'llmsTxt' => true,
|
||||
'faviconMediaId' => $medien['favicon']['id'],
|
||||
'stickyHeader' => true, 'headerOverlay' => false,
|
||||
'motion' => 'always',
|
||||
'homePath' => 'home', 'imprintPath' => 'impressum', 'privacyPath' => 'datenschutz',
|
||||
// Die Seite lädt nichts von Dritten; deshalb keine Einwilligungsleiste.
|
||||
'consentBanner' => false, 'scrollTop' => true, 'postMetaHeader' => false,
|
||||
]);
|
||||
echo "Einstellungen gespeichert\n";
|
||||
|
||||
$passwort = bin2hex(random_bytes(12));
|
||||
$db->insert('cms_user', ['id' => bin2hex(random_bytes(16)), 'username' => 'umut', 'password_hash' => password_hash($passwort, PASSWORD_BCRYPT), 'roles' => '["ROLE_ADMIN"]']);
|
||||
|
||||
echo "\nFertig.\nVerwaltung: Benutzer „umut“, Passwort: $passwort\n";
|
||||
Reference in new issue
Block a user