Replace the contact form with the new high-fidelity redesign
Rebuilds /kontakt from the design handoff (src/referenz/Kontaktformular Redesign/) using the site's own stack instead of copying the prototype's inline-styled HTML: React + Tailwind arbitrary values bound to a small set of CSS custom properties (.kf-page, index.css) that swap automatically with the site's existing dark-mode class toggle - no separate theme control was added, per the brief. The "Deine Anfrage bezieht sich auf" package box wires into the existing query-param handoff from the package picker (ModelPage's contactUrl: brand/model/package/packageId/total) and only renders when all three are present; the optional-upgrades picker that already existed on this page is preserved and restyled to match. Submission still posts to the real /api/contact endpoint (same payload shape as before), so requests keep landing in the admin inbox. Adds Michroma (heading/price font) alongside the already-loaded Barlow. Michroma has no Euro-sign glyph (renders as tofu), so price displays split the € into a plain-font span rather than showing a broken glyph. Dark-mode background assets (grunge/splatter) come from the handoff; the light-mode car photo reuses an existing site asset (hero-trunk) since none was supplied, masked per the handoff's recipe. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
9de1059042
commit
78d3d4a8e3
11 changed files with 1025 additions and 133 deletions
|
|
@ -8,7 +8,7 @@
|
||||||
Kunden-Referenzdesign) - der Rest der Seite bleibt beim Standard-Font. -->
|
Kunden-Referenzdesign) - der Rest der Seite bleibt beim Standard-Font. -->
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Barlow:wght@400;500;600&family=Barlow+Condensed:wght@500;600&display=swap" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/css2?family=Barlow:wght@400;500;600;700&family=Barlow+Condensed:wght@500;600&family=Michroma&display=swap" rel="stylesheet" />
|
||||||
<title>HifiPlanet | Car-Hifi Umbauten nach Maß</title>
|
<title>HifiPlanet | Car-Hifi Umbauten nach Maß</title>
|
||||||
<meta name="description" content="HifiPlanet – dein Car-Hifi Spezialist. Marke und Modell wählen, passende Sound-Pakete entdecken und unverbindlich anfragen." />
|
<meta name="description" content="HifiPlanet – dein Car-Hifi Spezialist. Marke und Modell wählen, passende Sound-Pakete entdecken und unverbindlich anfragen." />
|
||||||
<link rel="canonical" href="https://hifi-planet.de/" />
|
<link rel="canonical" href="https://hifi-planet.de/" />
|
||||||
|
|
|
||||||
BIN
hifi-src/src/assets/contact/grunge.png
Normal file
BIN
hifi-src/src/assets/contact/grunge.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 MiB |
BIN
hifi-src/src/assets/contact/splatter.png
Normal file
BIN
hifi-src/src/assets/contact/splatter.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 411 KiB |
|
|
@ -165,8 +165,15 @@ export default {
|
||||||
metaTitle: 'Kontakt',
|
metaTitle: 'Kontakt',
|
||||||
metaDescription: 'Kontaktiere HifiPlanet in Amorbach für dein individuelles Car-Hifi Projekt – wir beraten dich gerne unverbindlich.',
|
metaDescription: 'Kontaktiere HifiPlanet in Amorbach für dein individuelles Car-Hifi Projekt – wir beraten dich gerne unverbindlich.',
|
||||||
title: 'Kontakt aufnehmen',
|
title: 'Kontakt aufnehmen',
|
||||||
sentTitle: 'Danke für deine Anfrage!',
|
heroEyebrow: 'Kontakt',
|
||||||
sentText: 'Wir melden uns so schnell wie möglich bei dir.',
|
heroTitlePre: 'Sprich',
|
||||||
|
heroTitleAccent: 'mit',
|
||||||
|
heroTitlePost: 'uns.',
|
||||||
|
heroSubtitle: 'Ob Anlage, Einbau oder Klang-Tuning für dein Fahrzeug — schreib uns, wir melden uns schnellstmöglich zurück.',
|
||||||
|
packageBoxTitle: 'Deine Anfrage bezieht sich auf',
|
||||||
|
formTitle: 'Anfrage senden',
|
||||||
|
sentTitle: 'Anfrage gesendet ✓',
|
||||||
|
sentText: 'Danke! Wir melden uns innerhalb eines Werktags bei dir.',
|
||||||
contextIntro: 'Deine Anfrage bezieht sich auf:',
|
contextIntro: 'Deine Anfrage bezieht sich auf:',
|
||||||
contextBrand: 'Marke',
|
contextBrand: 'Marke',
|
||||||
contextModel: 'Modell',
|
contextModel: 'Modell',
|
||||||
|
|
@ -175,12 +182,18 @@ export default {
|
||||||
packagePrice: 'Paketpreis',
|
packagePrice: 'Paketpreis',
|
||||||
upgrades: 'Upgrades',
|
upgrades: 'Upgrades',
|
||||||
optionalUpgrades: 'Optionale Upgrades',
|
optionalUpgrades: 'Optionale Upgrades',
|
||||||
|
totalWithUpgrades: 'Gesamt inkl. Upgrades',
|
||||||
nameLabel: 'Name *',
|
nameLabel: 'Name *',
|
||||||
|
namePlaceholder: 'Max Mustermann',
|
||||||
emailLabel: 'E-Mail *',
|
emailLabel: 'E-Mail *',
|
||||||
|
emailPlaceholder: 'max@mail.de',
|
||||||
phoneLabel: 'Telefon',
|
phoneLabel: 'Telefon',
|
||||||
|
phonePlaceholder: 'Optional',
|
||||||
vinLabel: 'Fahrgestellnummer (FIN)',
|
vinLabel: 'Fahrgestellnummer (FIN)',
|
||||||
vinPlaceholder: 'Optional – hilft uns bei der genauen Einschätzung deines Fahrzeugs',
|
vinPlaceholder: 'Optional – hilft bei der Einschätzung',
|
||||||
messageLabel: 'Nachricht',
|
messageLabel: 'Nachricht',
|
||||||
|
messagePlaceholder: 'Erzähl uns von deinem Projekt – Fahrzeug, Wunschklang, Budget …',
|
||||||
|
requiredHint: '* Pflichtfelder',
|
||||||
sending: 'Wird gesendet…',
|
sending: 'Wird gesendet…',
|
||||||
submit: 'Anfrage senden',
|
submit: 'Anfrage senden',
|
||||||
cardTitle: 'HifiPlanet Amorbach',
|
cardTitle: 'HifiPlanet Amorbach',
|
||||||
|
|
@ -190,9 +203,22 @@ export default {
|
||||||
hours: 'Öffnungszeiten',
|
hours: 'Öffnungszeiten',
|
||||||
hoursWeek: 'Mo–Fr: 9:00–18:00 Uhr',
|
hoursWeek: 'Mo–Fr: 9:00–18:00 Uhr',
|
||||||
hoursSat: 'Sa: 10:00–13:00 Uhr',
|
hoursSat: 'Sa: 10:00–13:00 Uhr',
|
||||||
|
hoursWeekDays: 'Mo–Fr',
|
||||||
|
hoursWeekTime: '9:00 – 18:00 Uhr',
|
||||||
|
hoursSatDay: 'Sa',
|
||||||
|
hoursSatTime: '10:00 – 13:00 Uhr',
|
||||||
directionsTitle: 'Anfahrt',
|
directionsTitle: 'Anfahrt',
|
||||||
mapEmbedName: 'Die Google-Maps-Karte',
|
mapEmbedName: 'Die Google-Maps-Karte',
|
||||||
|
mapOpenChip: 'In Maps öffnen',
|
||||||
routePlan: 'Route planen',
|
routePlan: 'Route planen',
|
||||||
|
featurePremiumTitle: 'Premium Klang',
|
||||||
|
featurePremiumSub: 'Höchste Qualität',
|
||||||
|
featureGermanyTitle: 'Made in Germany',
|
||||||
|
featureGermanySub: 'Präzise Handarbeit',
|
||||||
|
featureIndividualTitle: 'Individuell',
|
||||||
|
featureIndividualSub: 'Maßgeschneiderte Lösungen',
|
||||||
|
featureExpertsTitle: 'Experten Team',
|
||||||
|
featureExpertsSub: 'Leidenschaft & Know-how',
|
||||||
},
|
},
|
||||||
galleryOverview: {
|
galleryOverview: {
|
||||||
metaTitle: 'Bildergalerie',
|
metaTitle: 'Bildergalerie',
|
||||||
|
|
|
||||||
|
|
@ -165,8 +165,15 @@ export default {
|
||||||
metaTitle: 'Contact',
|
metaTitle: 'Contact',
|
||||||
metaDescription: 'Get in touch with HifiPlanet in Amorbach for your custom car-hifi project – we\'re happy to advise you, no obligation.',
|
metaDescription: 'Get in touch with HifiPlanet in Amorbach for your custom car-hifi project – we\'re happy to advise you, no obligation.',
|
||||||
title: 'Get in touch',
|
title: 'Get in touch',
|
||||||
sentTitle: 'Thanks for your inquiry!',
|
heroEyebrow: 'Contact',
|
||||||
sentText: "We'll get back to you as soon as possible.",
|
heroTitlePre: 'Talk',
|
||||||
|
heroTitleAccent: 'to',
|
||||||
|
heroTitlePost: 'us.',
|
||||||
|
heroSubtitle: 'Whether it\'s a system, installation, or sound tuning for your car — write to us, we\'ll get back to you as soon as possible.',
|
||||||
|
packageBoxTitle: 'Your inquiry relates to',
|
||||||
|
formTitle: 'Send inquiry',
|
||||||
|
sentTitle: 'Inquiry sent ✓',
|
||||||
|
sentText: "Thanks! We'll get back to you within one business day.",
|
||||||
contextIntro: 'Your inquiry relates to:',
|
contextIntro: 'Your inquiry relates to:',
|
||||||
contextBrand: 'Brand',
|
contextBrand: 'Brand',
|
||||||
contextModel: 'Model',
|
contextModel: 'Model',
|
||||||
|
|
@ -175,12 +182,18 @@ export default {
|
||||||
packagePrice: 'Package price',
|
packagePrice: 'Package price',
|
||||||
upgrades: 'Upgrades',
|
upgrades: 'Upgrades',
|
||||||
optionalUpgrades: 'Optional upgrades',
|
optionalUpgrades: 'Optional upgrades',
|
||||||
|
totalWithUpgrades: 'Total incl. upgrades',
|
||||||
nameLabel: 'Name *',
|
nameLabel: 'Name *',
|
||||||
|
namePlaceholder: 'Max Mustermann',
|
||||||
emailLabel: 'Email *',
|
emailLabel: 'Email *',
|
||||||
|
emailPlaceholder: 'max@mail.de',
|
||||||
phoneLabel: 'Phone',
|
phoneLabel: 'Phone',
|
||||||
|
phonePlaceholder: 'Optional',
|
||||||
vinLabel: 'Vehicle identification number (VIN)',
|
vinLabel: 'Vehicle identification number (VIN)',
|
||||||
vinPlaceholder: 'Optional – helps us assess your vehicle accurately',
|
vinPlaceholder: 'Optional – helps us assess your vehicle',
|
||||||
messageLabel: 'Message',
|
messageLabel: 'Message',
|
||||||
|
messagePlaceholder: 'Tell us about your project – vehicle, desired sound, budget …',
|
||||||
|
requiredHint: '* Required fields',
|
||||||
sending: 'Sending…',
|
sending: 'Sending…',
|
||||||
submit: 'Send inquiry',
|
submit: 'Send inquiry',
|
||||||
cardTitle: 'HifiPlanet Amorbach',
|
cardTitle: 'HifiPlanet Amorbach',
|
||||||
|
|
@ -190,9 +203,22 @@ export default {
|
||||||
hours: 'Opening hours',
|
hours: 'Opening hours',
|
||||||
hoursWeek: 'Mon–Fri: 9:00 AM–6:00 PM',
|
hoursWeek: 'Mon–Fri: 9:00 AM–6:00 PM',
|
||||||
hoursSat: 'Sat: 10:00 AM–1:00 PM',
|
hoursSat: 'Sat: 10:00 AM–1:00 PM',
|
||||||
|
hoursWeekDays: 'Mon–Fri',
|
||||||
|
hoursWeekTime: '9:00 AM – 6:00 PM',
|
||||||
|
hoursSatDay: 'Sat',
|
||||||
|
hoursSatTime: '10:00 AM – 1:00 PM',
|
||||||
directionsTitle: 'Directions',
|
directionsTitle: 'Directions',
|
||||||
mapEmbedName: 'The Google Maps map',
|
mapEmbedName: 'The Google Maps map',
|
||||||
|
mapOpenChip: 'Open in Maps',
|
||||||
routePlan: 'Get directions',
|
routePlan: 'Get directions',
|
||||||
|
featurePremiumTitle: 'Premium Sound',
|
||||||
|
featurePremiumSub: 'Highest quality',
|
||||||
|
featureGermanyTitle: 'Made in Germany',
|
||||||
|
featureGermanySub: 'Precision craftsmanship',
|
||||||
|
featureIndividualTitle: 'Custom-built',
|
||||||
|
featureIndividualSub: 'Tailored solutions',
|
||||||
|
featureExpertsTitle: 'Expert Team',
|
||||||
|
featureExpertsSub: 'Passion & know-how',
|
||||||
},
|
},
|
||||||
galleryOverview: {
|
galleryOverview: {
|
||||||
metaTitle: 'Photo gallery',
|
metaTitle: 'Photo gallery',
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,69 @@ html {
|
||||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.7' numOctaves='3' stitchTiles='stitch' result='noise'/%3E%3CfeColorMatrix in='noise' type='matrix' values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.14 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.7' numOctaves='3' stitchTiles='stitch' result='noise'/%3E%3CfeColorMatrix in='noise' type='matrix' values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.14 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ==== Kontaktformular Redesign: Design-Tokens (siehe src/referenz/Kontaktformular
|
||||||
|
Redesign/design_handoff_kontaktformular/README.md) - gescopt auf .kf-page statt
|
||||||
|
global, damit sie den Rest der Seite nicht beeinflussen. Reagiert automatisch auf
|
||||||
|
den bestehenden globalen Dark-Mode-Schalter (.dark-Klasse auf <html>), kein
|
||||||
|
eigener Toggle noetig. */
|
||||||
|
.kf-page {
|
||||||
|
--kf-pagebg: linear-gradient(120deg, #eef0ea 0%, #f6f8f3 45%, #fbfcf9 100%);
|
||||||
|
--kf-card: #ffffff;
|
||||||
|
--kf-cardbrd: #e4e8dc;
|
||||||
|
--kf-text: #23271d;
|
||||||
|
--kf-text2: #2a2e24;
|
||||||
|
--kf-muted: #6c7360;
|
||||||
|
--kf-label: #838a72;
|
||||||
|
--kf-field: #fafcf6;
|
||||||
|
--kf-fieldbrd: #dde2d3;
|
||||||
|
--kf-fieldicon: #a7ad9d;
|
||||||
|
--kf-accent: #a4d417;
|
||||||
|
--kf-accent2: #8ec21a;
|
||||||
|
--kf-accentsoft: #f2f8df;
|
||||||
|
--kf-pill: #ffffff;
|
||||||
|
--kf-pillbrd: #dfe4d6;
|
||||||
|
--kf-shadow: 0 24px 60px rgba(60, 70, 40, 0.08);
|
||||||
|
--kf-shadow2: 0 16px 40px rgba(60, 70, 40, 0.06);
|
||||||
|
--kf-divider: #e8ecdf;
|
||||||
|
--kf-topo: #d7dccd;
|
||||||
|
--kf-pkgbg: #f4fbe4;
|
||||||
|
--kf-pkgbrd: #cfe89a;
|
||||||
|
--kf-btntext: #1f2417;
|
||||||
|
--kf-scrim: linear-gradient(90deg, rgba(244, 246, 241, 0.35) 0%, rgba(244, 246, 241, 0.55) 55%, rgba(246, 248, 243, 1) 100%);
|
||||||
|
}
|
||||||
|
.dark .kf-page {
|
||||||
|
--kf-pagebg: linear-gradient(120deg, #181b0f 0%, #111409 45%, #0a0c06 100%);
|
||||||
|
--kf-card: #14170e;
|
||||||
|
--kf-cardbrd: rgba(168, 225, 12, 0.16);
|
||||||
|
--kf-text: #f3f6ea;
|
||||||
|
--kf-text2: #e8ece0;
|
||||||
|
--kf-muted: #9aa38c;
|
||||||
|
--kf-label: #8f987f;
|
||||||
|
--kf-field: rgba(0, 0, 0, 0.35);
|
||||||
|
--kf-fieldbrd: #2c3323;
|
||||||
|
--kf-fieldicon: #5f6852;
|
||||||
|
--kf-accent: #a8e10c;
|
||||||
|
--kf-accent2: #b6e619;
|
||||||
|
--kf-accentsoft: rgba(168, 225, 12, 0.1);
|
||||||
|
--kf-pill: #14170e;
|
||||||
|
--kf-pillbrd: rgba(168, 225, 12, 0.28);
|
||||||
|
--kf-shadow: 0 24px 60px rgba(0, 0, 0, 0.55);
|
||||||
|
--kf-shadow2: 0 16px 40px rgba(0, 0, 0, 0.45);
|
||||||
|
--kf-divider: rgba(168, 225, 12, 0.15);
|
||||||
|
--kf-pkgbg: rgba(168, 225, 12, 0.08);
|
||||||
|
--kf-pkgbrd: rgba(168, 225, 12, 0.4);
|
||||||
|
--kf-btntext: #0f1208;
|
||||||
|
}
|
||||||
|
.kf-page input::placeholder,
|
||||||
|
.kf-page textarea::placeholder {
|
||||||
|
color: var(--kf-fieldicon);
|
||||||
|
}
|
||||||
|
.kf-page input:focus,
|
||||||
|
.kf-page textarea:focus {
|
||||||
|
border-color: var(--kf-accent) !important;
|
||||||
|
box-shadow: 0 0 0 3px rgba(164, 212, 23, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
.grain-band {
|
.grain-band {
|
||||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.7' numOctaves='3' stitchTiles='stitch' result='noise'/%3E%3CfeColorMatrix in='noise' type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.13 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.7' numOctaves='3' stitchTiles='stitch' result='noise'/%3E%3CfeColorMatrix in='noise' type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.13 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
|
||||||
background-size: 180px 180px;
|
background-size: 180px 180px;
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,92 @@ import usePageMeta from '../../hooks/usePageMeta.js';
|
||||||
import ExternalEmbed from '../../components/ExternalEmbed.jsx';
|
import ExternalEmbed from '../../components/ExternalEmbed.jsx';
|
||||||
import { useSiteSettings } from '../../context/SiteSettingsContext.jsx';
|
import { useSiteSettings } from '../../context/SiteSettingsContext.jsx';
|
||||||
import { useLanguage } from '../../context/LanguageContext.jsx';
|
import { useLanguage } from '../../context/LanguageContext.jsx';
|
||||||
|
import { useCookieConsent } from '../../context/CookieConsentContext.jsx';
|
||||||
|
import { User, Mail, Phone, Car, Package, Send, MapPin, ExternalLink } from 'lucide-react';
|
||||||
|
import grungeBg from '../../assets/contact/grunge.png';
|
||||||
|
import splatterBg from '../../assets/contact/splatter.png';
|
||||||
|
import carPhoto from '../../assets/photos/hero-trunk.webp';
|
||||||
|
|
||||||
const digitsOnly = (value) => (value || '').replace(/[^\d+]/g, '');
|
const digitsOnly = (value) => (value || '').replace(/[^\d+]/g, '');
|
||||||
|
const waHref = (whatsapp) => `https://wa.me/${digitsOnly(whatsapp).replace(/^0/, '49').replace('+', '')}`;
|
||||||
|
|
||||||
const SHOP_ADDRESS_ENCODED = encodeURIComponent('Boxbrunner Str. 20a, 63916 Amorbach');
|
const SHOP_ADDRESS_ENCODED = encodeURIComponent('Boxbrunner Str. 20a, 63916 Amorbach');
|
||||||
|
const DIRECTIONS_URL = `https://www.google.com/maps/dir/?api=1&destination=${SHOP_ADDRESS_ENCODED}`;
|
||||||
|
|
||||||
|
const michroma = { fontFamily: "'Michroma', sans-serif" };
|
||||||
|
|
||||||
|
function WhatsAppIcon({ className }) {
|
||||||
|
return (
|
||||||
|
<svg className={className} viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||||
|
<path d="M12 2a10 10 0 0 0-8.6 15l-1.3 4.8 5-1.3A10 10 0 1 0 12 2zm0 2a8 8 0 1 1-4.2 14.8l-.3-.2-2.6.7.7-2.5-.2-.3A8 8 0 0 1 12 4zm4.3 9.9c-.2-.1-1.4-.7-1.6-.8s-.4-.1-.5.1l-.7.9c-.1.1-.3.2-.5.1a6.5 6.5 0 0 1-3.2-2.8c-.1-.2 0-.4.1-.5l.4-.5.2-.4v-.4l-.7-1.7c-.2-.4-.4-.4-.5-.4h-.5c-.2 0-.4.1-.6.3-.7.7-.9 1.6-.6 2.7.5 1.7 1.6 3.1 3.4 4.2 1.7 1 2.5 1 3.4.9.5-.1 1.4-.6 1.6-1.1.2-.5.2-1 .1-1.1z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Die 4 Feature-Icons aus dem Referenzdesign - kein 1:1-Lucide-Pendant fuer alle,
|
||||||
|
// daher als rohe Pfade uebernommen statt einem angenaeherten Lucide-Icon.
|
||||||
|
function FeatureIcon({ path, className }) {
|
||||||
|
return (
|
||||||
|
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
||||||
|
<path d={path} />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const FEATURE_ICON_PATHS = {
|
||||||
|
premium: 'M4 12v3M8 8v11M12 4v16M16 8v11M20 12v3',
|
||||||
|
germany: 'M12 12m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0 M12 2v2M12 20v2M2 12h2M20 12h2M5 5l1.5 1.5M17.5 17.5 19 19M19 5l-1.5 1.5M6.5 17.5 5 19',
|
||||||
|
individual: 'M6 3h12l4 6-10 12L2 9z M11 3 8 9l4 12 4-12-3-6M2 9h20',
|
||||||
|
experts: 'M20 6 9 17l-5-5',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Michroma hat kein Euro-Zeichen-Glyph (rendert als Tofu/"O"-Ersatzform) - das
|
||||||
|
// Waehrungssymbol bekommt darum immer die System-Schrift, nur Ziffern/Komma bleiben
|
||||||
|
// in Michroma. Robust gegen beide Symbolpositionen (de-DE: "999,00 €", en-US: "€999.00").
|
||||||
|
function PriceMichroma({ formatted, color }) {
|
||||||
|
const [pre, post] = formatted.split('€');
|
||||||
|
return (
|
||||||
|
<span style={{ ...michroma, color }}>
|
||||||
|
{pre}
|
||||||
|
<span style={{ fontFamily: "'Barlow', sans-serif" }}>€</span>
|
||||||
|
{post}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Field({ icon: Icon, label, name, type = 'text', required, value, onChange, placeholder }) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label
|
||||||
|
htmlFor={`kf-${name}`}
|
||||||
|
className="text-xs font-semibold uppercase tracking-[2px]"
|
||||||
|
style={{ color: 'var(--kf-label)' }}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
<div className="relative flex items-center">
|
||||||
|
<Icon className="pointer-events-none absolute left-[14px] h-[17px] w-[17px]" style={{ color: 'var(--kf-fieldicon)' }} />
|
||||||
|
<input
|
||||||
|
id={`kf-${name}`}
|
||||||
|
name={name}
|
||||||
|
type={type}
|
||||||
|
required={required}
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
placeholder={placeholder}
|
||||||
|
className="w-full rounded-lg border py-3.5 pl-[42px] pr-4 text-[15px] outline-none transition-colors"
|
||||||
|
style={{ background: 'var(--kf-field)', borderColor: 'var(--kf-fieldbrd)', color: 'var(--kf-text2)' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function ContactPage() {
|
export default function ContactPage() {
|
||||||
const [params] = useSearchParams();
|
const [params] = useSearchParams();
|
||||||
const { phone, contact_email: contactEmail } = useSiteSettings();
|
const { phone, whatsapp, contact_email: contactEmail } = useSiteSettings();
|
||||||
const { t, language } = useLanguage();
|
const { t, language } = useLanguage();
|
||||||
|
const { consent } = useCookieConsent();
|
||||||
const formatPrice = (value) =>
|
const formatPrice = (value) =>
|
||||||
new Intl.NumberFormat(language === 'de' ? 'de-DE' : 'en-US', { style: 'currency', currency: 'EUR' }).format(value);
|
new Intl.NumberFormat(language === 'de' ? 'de-DE' : 'en-US', { style: 'currency', currency: 'EUR' }).format(value);
|
||||||
const [form, setForm] = useState({ name: '', email: '', phone: '', vin: '', message: '' });
|
const [form, setForm] = useState({ name: '', email: '', phone: '', vin: '', message: '' });
|
||||||
|
|
@ -51,9 +128,8 @@ export default function ContactPage() {
|
||||||
brand: params.get('brand') || '',
|
brand: params.get('brand') || '',
|
||||||
model: params.get('model') || '',
|
model: params.get('model') || '',
|
||||||
package: params.get('package') || '',
|
package: params.get('package') || '',
|
||||||
product: params.get('product') || '',
|
|
||||||
};
|
};
|
||||||
const hasContext = context.brand || context.model || context.package || context.product;
|
const hasPackage = Boolean(context.brand && context.model && context.package);
|
||||||
|
|
||||||
const handleChange = (e) => setForm((f) => ({ ...f, [e.target.name]: e.target.value }));
|
const handleChange = (e) => setForm((f) => ({ ...f, [e.target.name]: e.target.value }));
|
||||||
|
|
||||||
|
|
@ -68,190 +144,455 @@ export default function ContactPage() {
|
||||||
brand_name: context.brand || null,
|
brand_name: context.brand || null,
|
||||||
model_name: context.model || null,
|
model_name: context.model || null,
|
||||||
package_name: context.package || null,
|
package_name: context.package || null,
|
||||||
product_name: context.product || null,
|
product_name: params.get('product') || null,
|
||||||
package_id: packageId || null,
|
package_id: packageId || null,
|
||||||
package_product_id: params.get('productId') || null,
|
package_product_id: params.get('productId') || null,
|
||||||
upgrade_ids: selectedUpgradeIds,
|
upgrade_ids: selectedUpgradeIds,
|
||||||
});
|
});
|
||||||
setStatus('sent');
|
setStatus('sent');
|
||||||
|
setForm({ name: '', email: '', phone: '', vin: '', message: '' });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(e.message);
|
setError(e.message);
|
||||||
setStatus('idle');
|
setStatus('idle');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
const features = [
|
||||||
<div className="mx-auto max-w-5xl px-4 py-12 sm:px-6">
|
{ key: 'premium', title: t('contactPage.featurePremiumTitle'), sub: t('contactPage.featurePremiumSub') },
|
||||||
<h1 className="mb-8 text-2xl font-bold text-neutral-900 dark:text-white sm:text-3xl">{t('contactPage.title')}</h1>
|
{ key: 'germany', title: t('contactPage.featureGermanyTitle'), sub: t('contactPage.featureGermanySub') },
|
||||||
|
{ key: 'individual', title: t('contactPage.featureIndividualTitle'), sub: t('contactPage.featureIndividualSub') },
|
||||||
|
{ key: 'experts', title: t('contactPage.featureExpertsTitle'), sub: t('contactPage.featureExpertsSub') },
|
||||||
|
];
|
||||||
|
|
||||||
<div className="grid gap-10 md:grid-cols-5">
|
return (
|
||||||
<div className="md:col-span-3">
|
<div
|
||||||
{status === 'sent' ? (
|
className="kf-page relative w-full overflow-hidden px-4 pb-10 sm:px-10"
|
||||||
<div className="rounded-xl border border-neutral-200 bg-white p-8 text-center dark:border-neutral-800 dark:bg-neutral-900">
|
style={{ background: 'var(--kf-pagebg)', color: 'var(--kf-text2)' }}
|
||||||
<h2 className="mb-2 text-xl font-bold text-neutral-900 dark:text-white">{t('contactPage.sentTitle')}</h2>
|
>
|
||||||
<p className="text-neutral-600 dark:text-neutral-300">{t('contactPage.sentText')}</p>
|
{/* Dark Mode: Grunge-Textur + Splatter + Bogen (Assets aus dem Handoff) */}
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
className="pointer-events-none absolute inset-0 hidden bg-cover bg-center dark:block"
|
||||||
|
style={{ backgroundImage: `url(${grungeBg})` }}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
className="pointer-events-none absolute -right-[4%] -top-[8%] hidden h-[122%] w-[46%] bg-contain bg-right bg-no-repeat dark:block"
|
||||||
|
style={{ backgroundImage: `url(${splatterBg})`, filter: 'drop-shadow(0 0 22px rgba(164,212,23,0.35))' }}
|
||||||
|
/>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
viewBox="0 0 600 1000"
|
||||||
|
preserveAspectRatio="xMaxYMid slice"
|
||||||
|
className="pointer-events-none absolute right-0 top-0 hidden h-full w-[60%] dark:block"
|
||||||
|
>
|
||||||
|
<path d="M 900 -200 A 720 720 0 0 1 900 1200" fill="none" stroke="rgba(168,225,12,0.5)" strokeWidth="1.5" />
|
||||||
|
<path d="M 860 -160 A 700 700 0 0 1 860 1160" fill="none" stroke="rgba(168,225,12,0.18)" strokeWidth="1" strokeDasharray="2 9" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
{/* Light Mode: Auto-Foto links + gruener Lichtstreifen rechts (ab 981px, s. README) */}
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
className="pointer-events-none absolute left-0 top-0 hidden h-full w-1/5 bg-cover bg-left dark:!hidden min-[981px]:block"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url(${carPhoto})`,
|
||||||
|
WebkitMaskImage: 'linear-gradient(90deg, black 55%, transparent 100%)',
|
||||||
|
maskImage: 'linear-gradient(90deg, black 55%, transparent 100%)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
className="pointer-events-none absolute right-[4%] top-0 hidden h-full w-[26%] rotate-6 dark:!hidden min-[981px]:block"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="absolute right-1/2 top-0 h-full w-[3px] opacity-80 blur-[1px]"
|
||||||
|
style={{ background: 'linear-gradient(180deg, transparent, #b6e619 30%, #cfef5a 55%, transparent)' }}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="absolute right-1/2 top-0 h-full w-[90px] translate-x-1/2 opacity-70 blur-[26px]"
|
||||||
|
style={{ background: 'linear-gradient(180deg, transparent, #cfef5a 40%, transparent)' }}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="absolute right-[40%] top-0 h-full w-[2px] opacity-60 blur-[1px]"
|
||||||
|
style={{ background: 'linear-gradient(180deg, transparent, #b6e619 35%, transparent)' }}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="absolute right-1/2 top-1/3 h-[420px] w-[420px] translate-x-1/2 rounded-full opacity-70 blur-[40px]"
|
||||||
|
style={{ background: 'radial-gradient(circle, rgba(207,239,90,0.35), transparent 70%)' }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
|
||||||
<>
|
<div className="relative z-10 mx-auto max-w-[1280px] pt-10 sm:pt-14">
|
||||||
{hasContext && (
|
{/* Hero */}
|
||||||
<div className="mb-6 rounded-lg border border-brand-200 bg-brand-50 p-4 text-sm text-brand-800 dark:border-brand-900 dark:bg-brand-900/20 dark:text-brand-200">
|
<div
|
||||||
<p className="font-semibold">{t('contactPage.contextIntro')}</p>
|
className="mb-3 text-[13px] font-semibold uppercase tracking-[6px]"
|
||||||
<ul className="mt-1 space-y-0.5">
|
style={{ color: 'var(--kf-accent2)' }}
|
||||||
{context.brand && <li>{t('contactPage.contextBrand')}: {context.brand}</li>}
|
>
|
||||||
{context.model && <li>{t('contactPage.contextModel')}: {context.model}</li>}
|
{t('contactPage.heroEyebrow')}
|
||||||
{context.package && <li>{t('contactPage.contextPackage')}: {context.package}</li>}
|
</div>
|
||||||
{context.product && <li>{t('contactPage.contextProduct')}: {context.product}</li>}
|
<h1
|
||||||
</ul>
|
className="text-[34px] leading-[1.08] sm:text-[46px] lg:text-[60px]"
|
||||||
{packageTotal != null && (
|
style={{ ...michroma, color: 'var(--kf-text)' }}
|
||||||
<p className="mt-2 font-semibold">
|
>
|
||||||
{t('contactPage.packagePrice')}: {formatPrice(packageTotal)}
|
{t('contactPage.heroTitlePre')} <span style={{ color: 'var(--kf-accent2)' }}>{t('contactPage.heroTitleAccent')}</span> {t('contactPage.heroTitlePost')}
|
||||||
{upgradesTotal > 0 && <> + {formatPrice(upgradesTotal)} {t('contactPage.upgrades')} = {formatPrice(packageTotal + upgradesTotal)}</>}
|
</h1>
|
||||||
|
<p className="mb-10 mt-5 max-w-[520px] text-lg leading-[1.55]" style={{ color: 'var(--kf-muted)' }}>
|
||||||
|
{t('contactPage.heroSubtitle')}
|
||||||
</p>
|
</p>
|
||||||
)}
|
|
||||||
|
{/* Hauptraster */}
|
||||||
|
<div className="grid grid-cols-1 items-start gap-10 min-[981px]:grid-cols-[minmax(0,1.35fr)_minmax(320px,1fr)]">
|
||||||
|
{/* Formular-Karte */}
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
className="rounded-2xl border p-[22px] sm:p-10"
|
||||||
|
style={{ background: 'var(--kf-card)', borderColor: 'var(--kf-cardbrd)', boxShadow: 'var(--kf-shadow)' }}
|
||||||
|
>
|
||||||
|
{hasPackage && (
|
||||||
|
<div
|
||||||
|
className="mb-8 rounded-xl border px-6 py-[22px]"
|
||||||
|
style={{ background: 'var(--kf-pkgbg)', borderColor: 'var(--kf-pkgbrd)' }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="mb-3.5 flex items-center gap-2.5 text-xs font-bold uppercase tracking-[2px]"
|
||||||
|
style={{ color: 'var(--kf-accent2)' }}
|
||||||
|
>
|
||||||
|
<Package className="h-4 w-4" />
|
||||||
|
{t('contactPage.packageBoxTitle')}
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-[auto_1fr] gap-x-5 gap-y-2 text-[15px]">
|
||||||
|
<span style={{ color: 'var(--kf-label)' }}>{t('contactPage.contextBrand')}</span>
|
||||||
|
<span className="font-semibold" style={{ color: 'var(--kf-text2)' }}>{context.brand}</span>
|
||||||
|
<span style={{ color: 'var(--kf-label)' }}>{t('contactPage.contextModel')}</span>
|
||||||
|
<span className="font-semibold" style={{ color: 'var(--kf-text2)' }}>{context.model}</span>
|
||||||
|
<span style={{ color: 'var(--kf-label)' }}>{t('contactPage.contextPackage')}</span>
|
||||||
|
<span className="font-semibold" style={{ color: 'var(--kf-text2)' }}>{context.package}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="mt-3.5 flex items-baseline justify-between border-t pt-3.5"
|
||||||
|
style={{ borderColor: 'var(--kf-pkgbrd)' }}
|
||||||
|
>
|
||||||
|
<span className="text-[13px] font-semibold uppercase tracking-wide" style={{ color: 'var(--kf-muted)' }}>
|
||||||
|
{t('contactPage.packagePrice')}
|
||||||
|
</span>
|
||||||
|
<span className="text-xl">
|
||||||
|
{packageTotal != null ? <PriceMichroma formatted={formatPrice(packageTotal)} color="var(--kf-text)" /> : '—'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{upgrades.length > 0 && (
|
{upgrades.length > 0 && (
|
||||||
<div className="mb-6 rounded-lg border border-neutral-200 bg-white p-4 dark:border-neutral-800 dark:bg-neutral-900">
|
<div
|
||||||
<p className="mb-3 text-sm font-semibold text-neutral-900 dark:text-white">{t('contactPage.optionalUpgrades')}</p>
|
className="mb-8 rounded-xl border p-5"
|
||||||
|
style={{ background: 'var(--kf-field)', borderColor: 'var(--kf-fieldbrd)' }}
|
||||||
|
>
|
||||||
|
<div className="mb-3 text-xs font-bold uppercase tracking-[2px]" style={{ color: 'var(--kf-accent2)' }}>
|
||||||
|
{t('contactPage.optionalUpgrades')}
|
||||||
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{upgrades.map((u) => (
|
{upgrades.map((u) => (
|
||||||
<label key={u.id} className="flex cursor-pointer items-start gap-3 rounded-md border border-neutral-200 p-3 text-sm hover:bg-neutral-50 dark:border-neutral-700 dark:hover:bg-neutral-800">
|
<label
|
||||||
|
key={u.id}
|
||||||
|
className="flex cursor-pointer items-start gap-3 rounded-lg border p-3 text-sm"
|
||||||
|
style={{ borderColor: 'var(--kf-fieldbrd)' }}
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={selectedUpgradeIds.includes(u.id)}
|
checked={selectedUpgradeIds.includes(u.id)}
|
||||||
onChange={() => toggleUpgrade(u.id)}
|
onChange={() => toggleUpgrade(u.id)}
|
||||||
className="mt-0.5 h-4 w-4 rounded border-neutral-300 text-brand-600 focus:ring-brand-500"
|
className="mt-0.5 h-4 w-4 rounded"
|
||||||
|
style={{ accentColor: 'var(--kf-accent)' }}
|
||||||
/>
|
/>
|
||||||
<span className="flex-1">
|
<span className="flex-1">
|
||||||
<span className="block font-medium text-neutral-800 dark:text-neutral-100">{u.name}</span>
|
<span className="block font-semibold" style={{ color: 'var(--kf-text2)' }}>{u.name}</span>
|
||||||
{u.description && <span className="block text-neutral-500 dark:text-neutral-400">{u.description}</span>}
|
{u.description && (
|
||||||
|
<span className="block text-xs" style={{ color: 'var(--kf-muted)' }}>{u.description}</span>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span className="font-semibold text-brand-600 dark:text-brand-400">+{formatPrice(u.price)}</span>
|
<span className="font-semibold" style={{ color: 'var(--kf-accent2)' }}>+{formatPrice(u.price)}</span>
|
||||||
</label>
|
</label>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
{selectedUpgradeIds.length > 0 && packageTotal != null && (
|
||||||
|
<div
|
||||||
|
className="mt-3 flex items-baseline justify-between border-t pt-3 text-sm"
|
||||||
|
style={{ borderColor: 'var(--kf-pkgbrd)' }}
|
||||||
|
>
|
||||||
|
<span className="text-xs font-semibold uppercase tracking-wide" style={{ color: 'var(--kf-muted)' }}>
|
||||||
|
{t('contactPage.totalWithUpgrades')}
|
||||||
|
</span>
|
||||||
|
<PriceMichroma formatted={formatPrice(packageTotal + upgradesTotal)} color="var(--kf-text)" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<h2 className="mb-8 text-xl" style={{ ...michroma, color: 'var(--kf-text)' }}>
|
||||||
|
{t('contactPage.formTitle')}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{status === 'sent' && (
|
||||||
|
<div
|
||||||
|
className="mb-6 rounded-[10px] border p-5"
|
||||||
|
style={{ borderColor: 'var(--kf-accent)', background: 'var(--kf-accentsoft)' }}
|
||||||
|
>
|
||||||
|
<div className="text-base font-bold" style={{ color: 'var(--kf-accent2)' }}>
|
||||||
|
{t('contactPage.sentTitle')}
|
||||||
|
</div>
|
||||||
|
<div className="mt-1 text-sm" style={{ color: 'var(--kf-muted)' }}>
|
||||||
|
{t('contactPage.sentText')}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
<form onSubmit={handleSubmit}>
|
||||||
<div>
|
<div className="grid grid-cols-1 gap-6 min-[641px]:grid-cols-2">
|
||||||
<label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">{t('contactPage.nameLabel')}</label>
|
<Field
|
||||||
<input
|
icon={User}
|
||||||
|
label={t('contactPage.nameLabel')}
|
||||||
name="name"
|
name="name"
|
||||||
required
|
required
|
||||||
value={form.name}
|
value={form.name}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm focus:border-brand-500 focus:outline-none dark:border-neutral-700 dark:bg-neutral-900"
|
placeholder={t('contactPage.namePlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
<Field
|
||||||
<div>
|
icon={Mail}
|
||||||
<label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">{t('contactPage.emailLabel')}</label>
|
label={t('contactPage.emailLabel')}
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
name="email"
|
name="email"
|
||||||
|
type="email"
|
||||||
required
|
required
|
||||||
value={form.email}
|
value={form.email}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm focus:border-brand-500 focus:outline-none dark:border-neutral-700 dark:bg-neutral-900"
|
placeholder={t('contactPage.emailPlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
<Field
|
||||||
<div>
|
icon={Phone}
|
||||||
<label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">{t('contactPage.phoneLabel')}</label>
|
label={t('contactPage.phoneLabel')}
|
||||||
<input
|
|
||||||
name="phone"
|
name="phone"
|
||||||
value={form.phone}
|
value={form.phone}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm focus:border-brand-500 focus:outline-none dark:border-neutral-700 dark:bg-neutral-900"
|
placeholder={t('contactPage.phonePlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
<Field
|
||||||
<div>
|
icon={Car}
|
||||||
<label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">
|
label={t('contactPage.vinLabel')}
|
||||||
{t('contactPage.vinLabel')}
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
name="vin"
|
name="vin"
|
||||||
value={form.vin}
|
value={form.vin}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
placeholder={t('contactPage.vinPlaceholder')}
|
placeholder={t('contactPage.vinPlaceholder')}
|
||||||
className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm focus:border-brand-500 focus:outline-none dark:border-neutral-700 dark:bg-neutral-900"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">{t('contactPage.messageLabel')}</label>
|
<div className="mt-6 flex flex-col gap-2">
|
||||||
|
<label
|
||||||
|
htmlFor="kf-message"
|
||||||
|
className="text-xs font-semibold uppercase tracking-[2px]"
|
||||||
|
style={{ color: 'var(--kf-label)' }}
|
||||||
|
>
|
||||||
|
{t('contactPage.messageLabel')}
|
||||||
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
|
id="kf-message"
|
||||||
name="message"
|
name="message"
|
||||||
rows={4}
|
rows={5}
|
||||||
value={form.message}
|
value={form.message}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm focus:border-brand-500 focus:outline-none dark:border-neutral-700 dark:bg-neutral-900"
|
placeholder={t('contactPage.messagePlaceholder')}
|
||||||
|
className="w-full resize-y rounded-lg border px-4 py-3.5 text-[15px] outline-none"
|
||||||
|
style={{ background: 'var(--kf-field)', borderColor: 'var(--kf-fieldbrd)', color: 'var(--kf-text2)' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && <p className="text-sm text-red-600">{error}</p>}
|
{error && <p className="mt-4 text-sm text-red-600 dark:text-red-400">{error}</p>}
|
||||||
|
|
||||||
|
<div className="mt-8 flex flex-wrap items-center gap-5">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={status === 'sending'}
|
disabled={status === 'sending'}
|
||||||
className="w-full rounded-md bg-brand-500 px-4 py-2.5 text-sm font-semibold text-white hover:bg-brand-600 disabled:opacity-60"
|
className="inline-flex items-center gap-3 rounded-lg px-8 py-4 text-sm font-bold uppercase tracking-[3px] transition-[filter,box-shadow] hover:brightness-110 disabled:opacity-60"
|
||||||
|
style={{
|
||||||
|
background: 'var(--kf-accent)',
|
||||||
|
color: 'var(--kf-btntext)',
|
||||||
|
boxShadow: '0 10px 24px rgba(150,200,20,0.32)',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
|
<Send className="h-[18px] w-[18px]" />
|
||||||
{status === 'sending' ? t('contactPage.sending') : t('contactPage.submit')}
|
{status === 'sending' ? t('contactPage.sending') : t('contactPage.submit')}
|
||||||
</button>
|
</button>
|
||||||
|
<div className="text-[13px]" style={{ color: 'var(--kf-label)' }}>
|
||||||
|
{t('contactPage.requiredHint')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Rechte Spalte */}
|
||||||
|
<div className="flex flex-col gap-6">
|
||||||
|
<div
|
||||||
|
className="relative overflow-hidden rounded-2xl border px-[22px] py-[26px] sm:px-8 sm:py-[34px]"
|
||||||
|
style={{ background: 'var(--kf-card)', borderColor: 'var(--kf-cardbrd)', boxShadow: 'var(--kf-shadow)' }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="pointer-events-none absolute -right-[60px] -top-[60px] h-[190px] w-[190px] rounded-full border border-dashed"
|
||||||
|
style={{ borderColor: 'var(--kf-pkgbrd)' }}
|
||||||
|
/>
|
||||||
|
<h2 className="mb-7 text-[17px]" style={{ ...michroma, color: 'var(--kf-text)' }}>
|
||||||
|
{t('contactPage.cardTitle')}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-5">
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div
|
||||||
|
className="flex h-11 w-11 flex-shrink-0 items-center justify-center rounded-full"
|
||||||
|
style={{ background: 'var(--kf-accentsoft)' }}
|
||||||
|
>
|
||||||
|
<Phone className="h-[18px] w-[18px]" style={{ color: 'var(--kf-accent2)' }} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="mb-1 text-[11px] font-semibold uppercase tracking-[3px]" style={{ color: 'var(--kf-accent2)' }}>
|
||||||
|
{t('contactPage.phone')}
|
||||||
|
</div>
|
||||||
|
<a href={`tel:${digitsOnly(phone)}`} className="text-base font-semibold" style={{ color: 'var(--kf-text2)' }}>
|
||||||
|
{phone}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div
|
||||||
|
className="flex h-11 w-11 flex-shrink-0 items-center justify-center rounded-full"
|
||||||
|
style={{ background: 'var(--kf-accentsoft)' }}
|
||||||
|
>
|
||||||
|
<Mail className="h-[18px] w-[18px]" style={{ color: 'var(--kf-accent2)' }} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="mb-1 text-[11px] font-semibold uppercase tracking-[3px]" style={{ color: 'var(--kf-accent2)' }}>
|
||||||
|
{t('contactPage.email')}
|
||||||
|
</div>
|
||||||
|
<a href={`mailto:${contactEmail}`} className="text-base font-semibold" style={{ color: 'var(--kf-text2)' }}>
|
||||||
|
{contactEmail}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div
|
||||||
|
className="flex h-11 w-11 flex-shrink-0 items-center justify-center rounded-full"
|
||||||
|
style={{ background: 'var(--kf-accentsoft)' }}
|
||||||
|
>
|
||||||
|
<MapPin className="h-[18px] w-[18px]" style={{ color: 'var(--kf-accent2)' }} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="mb-1 text-[11px] font-semibold uppercase tracking-[3px]" style={{ color: 'var(--kf-accent2)' }}>
|
||||||
|
{t('contactPage.address')}
|
||||||
|
</div>
|
||||||
|
<div className="text-base font-semibold leading-[1.4]" style={{ color: 'var(--kf-text2)' }}>
|
||||||
|
Boxbrunner Straße 20a<br />63916 Amorbach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{whatsapp && (
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div
|
||||||
|
className="flex h-11 w-11 flex-shrink-0 items-center justify-center rounded-full"
|
||||||
|
style={{ background: 'var(--kf-accentsoft)' }}
|
||||||
|
>
|
||||||
|
<WhatsAppIcon className="h-[18px] w-[18px]" style={{ color: 'var(--kf-accent2)' }} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="mb-1 text-[11px] font-semibold uppercase tracking-[3px]" style={{ color: 'var(--kf-accent2)' }}>
|
||||||
|
{t('nav.whatsapp')}
|
||||||
|
</div>
|
||||||
|
<a href={waHref(whatsapp)} className="text-base font-semibold" style={{ color: 'var(--kf-text2)' }}>
|
||||||
|
{whatsapp}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="md:col-span-2">
|
<div className="mt-7 border-t pt-6" style={{ borderColor: 'var(--kf-divider)' }}>
|
||||||
<div className="rounded-xl border border-neutral-200 bg-white p-6 dark:border-neutral-800 dark:bg-neutral-900">
|
<div className="mb-3 text-[11px] font-semibold uppercase tracking-[3px]" style={{ color: 'var(--kf-accent2)' }}>
|
||||||
<h2 className="mb-4 font-semibold text-neutral-900 dark:text-white">{t('contactPage.cardTitle')}</h2>
|
{t('contactPage.hours')}
|
||||||
<dl className="space-y-3 text-sm text-neutral-600 dark:text-neutral-300">
|
|
||||||
<div>
|
|
||||||
<dt className="font-medium text-neutral-800 dark:text-neutral-100">{t('contactPage.address')}</dt>
|
|
||||||
<dd>Boxbrunner Str. 20a, 63916 Amorbach</dd>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="grid grid-cols-[auto_1fr] gap-x-6 gap-y-2 text-[15px]">
|
||||||
<dt className="font-medium text-neutral-800 dark:text-neutral-100">{t('contactPage.phone')}</dt>
|
<span style={{ color: 'var(--kf-label)' }}>{t('contactPage.hoursWeekDays')}</span>
|
||||||
<dd><a href={`tel:${digitsOnly(phone)}`} className="inline-block py-1 hover:text-brand-500">{phone}</a></dd>
|
<span>{t('contactPage.hoursWeekTime')}</span>
|
||||||
|
<span style={{ color: 'var(--kf-label)' }}>{t('contactPage.hoursSatDay')}</span>
|
||||||
|
<span>{t('contactPage.hoursSatTime')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<dt className="font-medium text-neutral-800 dark:text-neutral-100">{t('contactPage.email')}</dt>
|
|
||||||
<dd><a href={`mailto:${contactEmail}`} className="inline-block py-1 hover:text-brand-500">{contactEmail}</a></dd>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<dt className="font-medium text-neutral-800 dark:text-neutral-100">{t('contactPage.hours')}</dt>
|
|
||||||
<dd>{t('contactPage.hoursWeek')}</dd>
|
|
||||||
<dd>{t('contactPage.hoursSat')}</dd>
|
|
||||||
</div>
|
|
||||||
</dl>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-6 rounded-xl border border-neutral-200 bg-white p-6 dark:border-neutral-800 dark:bg-neutral-900">
|
<div
|
||||||
<h2 className="mb-4 font-semibold text-neutral-900 dark:text-white">{t('contactPage.directionsTitle')}</h2>
|
className="relative overflow-hidden rounded-2xl border"
|
||||||
<div className="overflow-hidden rounded-lg border border-neutral-200 dark:border-neutral-700">
|
style={{ background: 'var(--kf-card)', borderColor: 'var(--kf-cardbrd)', boxShadow: 'var(--kf-shadow)' }}
|
||||||
<ExternalEmbed name={t('contactPage.mapEmbedName')} className="h-[220px] w-full">
|
>
|
||||||
|
{consent.external && (
|
||||||
|
<a
|
||||||
|
href={DIRECTIONS_URL}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="absolute left-3.5 top-3.5 z-10 inline-flex items-center gap-2 rounded-lg border px-3.5 py-2 text-[13px] font-semibold hover:border-[var(--kf-accent)]"
|
||||||
|
style={{ background: 'var(--kf-pill)', borderColor: 'var(--kf-pillbrd)', color: 'var(--kf-text2)', boxShadow: 'var(--kf-shadow2)' }}
|
||||||
|
>
|
||||||
|
{t('contactPage.mapOpenChip')}
|
||||||
|
<ExternalLink className="h-3.5 w-3.5" style={{ color: 'var(--kf-accent2)' }} />
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
<ExternalEmbed name={t('contactPage.mapEmbedName')} className="h-[210px] w-full">
|
||||||
<iframe
|
<iframe
|
||||||
title="HifiPlanet Amorbach – Standort"
|
title="HifiPlanet Amorbach – Standort"
|
||||||
src={`https://www.google.com/maps?q=${SHOP_ADDRESS_ENCODED}&output=embed`}
|
src={`https://www.google.com/maps?q=${SHOP_ADDRESS_ENCODED}&output=embed`}
|
||||||
width="100%"
|
width="100%"
|
||||||
height="220"
|
height="210"
|
||||||
style={{ border: 0 }}
|
style={{ border: 0, display: 'block' }}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
referrerPolicy="no-referrer-when-downgrade"
|
referrerPolicy="no-referrer-when-downgrade"
|
||||||
/>
|
/>
|
||||||
</ExternalEmbed>
|
</ExternalEmbed>
|
||||||
</div>
|
|
||||||
<a
|
<a
|
||||||
href={`https://www.google.com/maps/dir/?api=1&destination=${SHOP_ADDRESS_ENCODED}`}
|
href={DIRECTIONS_URL}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="mt-4 flex w-full items-center justify-center gap-2 rounded-md bg-brand-500 px-4 py-2.5 text-sm font-semibold text-white hover:bg-brand-600"
|
className="flex items-center justify-center gap-2.5 border-t px-4 py-4 text-[13px] font-bold uppercase tracking-[3px] hover:bg-[var(--kf-accentsoft)]"
|
||||||
|
style={{ borderColor: 'var(--kf-divider)', color: 'var(--kf-accent2)' }}
|
||||||
>
|
>
|
||||||
{t('contactPage.routePlan')}
|
{t('contactPage.routePlan')} →
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Feature-Leiste */}
|
||||||
|
<div
|
||||||
|
className="mt-10 grid grid-cols-1 overflow-hidden rounded-2xl border min-[641px]:grid-cols-2 min-[901px]:grid-cols-4"
|
||||||
|
style={{ background: 'var(--kf-card)', borderColor: 'var(--kf-cardbrd)', boxShadow: 'var(--kf-shadow2)' }}
|
||||||
|
>
|
||||||
|
{features.map((f) => (
|
||||||
|
<div
|
||||||
|
key={f.key}
|
||||||
|
className="flex items-center gap-4 border-b p-6 last:border-b-0 min-[641px]:border-b-0 min-[641px]:border-r min-[641px]:last:border-r-0"
|
||||||
|
style={{ borderColor: 'var(--kf-divider)' }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="flex h-[46px] w-[46px] flex-shrink-0 items-center justify-center rounded-full border-[1.5px]"
|
||||||
|
style={{ borderColor: 'var(--kf-pkgbrd)', color: 'var(--kf-accent2)' }}
|
||||||
|
>
|
||||||
|
<FeatureIcon path={FEATURE_ICON_PATHS[f.key]} className="h-[22px] w-[22px]" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-[15px] font-bold" style={{ color: 'var(--kf-text)' }}>{f.title}</div>
|
||||||
|
<div className="mt-0.5 text-[13px]" style={{ color: 'var(--kf-muted)' }}>{f.sub}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,274 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<script src="./support.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<x-dc>
|
||||||
|
<helmet>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Michroma&family=Barlow:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
html, body { margin: 0; padding: 0; background: #f1f2ef; }
|
||||||
|
:root{
|
||||||
|
--pagebg: linear-gradient(120deg,#eceeea 0%,#f2f3f0 45%,#f8f9f6 100%);
|
||||||
|
--card:#ffffff; --cardbrd:#e4e8dc; --text:#23271d; --text2:#2a2e24;
|
||||||
|
--muted:#6c7360; --label:#838a72; --field:#fafcf6; --fieldbrd:#dde2d3;
|
||||||
|
--fieldicon:#a7ad9d; --accent:#a4d417; --accent2:#8ec21a; --accentsoft:#f2f8df;
|
||||||
|
--pill:#ffffff; --pillbrd:#dfe4d6; --shadow:0 24px 60px rgba(60,70,40,0.08);
|
||||||
|
--shadow2:0 16px 40px rgba(60,70,40,0.06); --divider:#e8ecdf; --topo:#d7dccd;
|
||||||
|
--pkgbg:#f4fbe4; --pkgbrd:#cfe89a; --btntext:#1f2417;
|
||||||
|
--scrim: linear-gradient(90deg, rgba(244,246,241,0.35) 0%, rgba(244,246,241,0.55) 55%, rgba(246,248,243,1) 100%);
|
||||||
|
}
|
||||||
|
[data-theme="dark"]{
|
||||||
|
--pagebg: linear-gradient(120deg,#181b0f 0%,#111409 45%,#0a0c06 100%);
|
||||||
|
--card:#14170e; --cardbrd:rgba(168,225,12,0.16); --text:#f3f6ea; --text2:#e8ece0;
|
||||||
|
--muted:#9aa38c; --label:#8f987f; --field:rgba(0,0,0,0.35); --fieldbrd:#2c3323;
|
||||||
|
--fieldicon:#5f6852; --accent:#a8e10c; --accent2:#b6e619; --accentsoft:rgba(168,225,12,0.1);
|
||||||
|
--pill:#14170e; --pillbrd:rgba(168,225,12,0.28); --shadow:0 24px 60px rgba(0,0,0,0.55);
|
||||||
|
--shadow2:0 16px 40px rgba(0,0,0,0.45); --divider:rgba(168,225,12,0.15); --topo:#2c3320;
|
||||||
|
--pkgbg:rgba(168,225,12,0.08); --pkgbrd:rgba(168,225,12,0.4); --btntext:#0f1208;
|
||||||
|
--scrim: linear-gradient(90deg, rgba(15,18,10,0.35) 0%, rgba(15,18,10,0.6) 55%, rgba(10,12,7,1) 100%);
|
||||||
|
}
|
||||||
|
a { color: var(--accent2); text-decoration: none; }
|
||||||
|
a:hover { filter: brightness(1.12); }
|
||||||
|
input::placeholder, textarea::placeholder { color: var(--fieldicon); }
|
||||||
|
@media (max-width: 980px){
|
||||||
|
[data-r="main"]{ grid-template-columns:1fr !important; }
|
||||||
|
[data-r="carbg"]{ display:none !important; }
|
||||||
|
}
|
||||||
|
@media (max-width: 900px){
|
||||||
|
[data-r="features"]{ grid-template-columns:1fr 1fr !important; }
|
||||||
|
}
|
||||||
|
@media (max-width: 640px){
|
||||||
|
[data-r="fields"]{ grid-template-columns:1fr !important; }
|
||||||
|
[data-r="features"]{ grid-template-columns:1fr !important; }
|
||||||
|
[data-r="page"]{ padding-left:16px !important; padding-right:16px !important; }
|
||||||
|
[data-r="formcard"]{ padding:22px !important; }
|
||||||
|
[data-r="contactcard"]{ padding:26px 22px !important; }
|
||||||
|
[data-r="feature"]{ border-right:none !important; border-bottom:1px solid var(--divider); }
|
||||||
|
[data-r="h1"]{ font-size:34px !important; }
|
||||||
|
[data-r="nav"]{ padding-top:22px !important; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="./image-slot.js"></script>
|
||||||
|
</helmet>
|
||||||
|
<div data-theme="{{ theme }}" data-r="page" style="position: relative; min-height: 100vh; background: var(--pagebg); font-family: 'Barlow', sans-serif; color: var(--text2); padding: 0 40px 40px; overflow: hidden; transition: background .3s ease;">
|
||||||
|
|
||||||
|
<!-- ===== DARK MODE: grunge + paint splatter (client theme) ===== -->
|
||||||
|
<sc-if value="{{ isDark }}" hint-placeholder-val="{{ false }}">
|
||||||
|
<div aria-hidden="true" style="position: absolute; inset: 0; background-image: url('./assets/grunge.png'); background-size: cover; background-position: center; pointer-events: none;"></div>
|
||||||
|
<div aria-hidden="true" style="position: absolute; right: -4%; top: -8%; width: 46%; height: 122%; background-image: url('./assets/splatter.png'); background-size: contain; background-repeat: no-repeat; background-position: right center; pointer-events: none; filter: drop-shadow(0 0 22px rgba(164,212,23,0.35));"></div>
|
||||||
|
<svg aria-hidden="true" viewBox="0 0 600 1000" preserveAspectRatio="xMaxYMid slice" style="position: absolute; right: 0; top: 0; width: 60%; height: 100%; pointer-events: none;"><path d="M 900 -200 A 720 720 0 0 1 900 1200" fill="none" stroke="rgba(168,225,12,0.5)" stroke-width="1.5"></path><path d="M 860 -160 A 700 700 0 0 1 860 1160" fill="none" stroke="rgba(168,225,12,0.18)" stroke-width="1" stroke-dasharray="2 9"></path></svg>
|
||||||
|
</sc-if>
|
||||||
|
|
||||||
|
<!-- ===== LIGHT MODE car photo + streaks + topo ===== -->
|
||||||
|
<sc-if value="{{ isLight }}" hint-placeholder-val="{{ true }}">
|
||||||
|
<!-- car photo + wave texture strip (cropped from original mockup), left -->
|
||||||
|
<div data-r="carbg" aria-hidden="true" style="position: absolute; left: 0; top: 0; width: 20%; height: 100%; pointer-events: none; background-image: url('./assets/car-left.png'); background-size: cover; background-position: left center; -webkit-mask-image: linear-gradient(90deg, black 55%, transparent 100%); mask-image: linear-gradient(90deg, black 55%, transparent 100%);"></div>
|
||||||
|
<!-- green light ray (cropped from original mockup), right -->
|
||||||
|
<div aria-hidden="true" style="position: absolute; right: 0; bottom: 0; width: 17%; height: 91%; pointer-events: none; background-image: url('./assets/glow-right.png'); background-size: cover; background-position: right center; -webkit-mask-image: linear-gradient(90deg, transparent 0%, black 45%), linear-gradient(180deg, transparent 0%, black 20%); -webkit-mask-composite: source-in; mask-image: linear-gradient(90deg, transparent 0%, black 45%), linear-gradient(180deg, transparent 0%, black 20%); mask-composite: intersect;"></div>
|
||||||
|
</sc-if>
|
||||||
|
|
||||||
|
<div style="position: relative; z-index: 1; max-width: 1280px; margin: 0 auto;">
|
||||||
|
|
||||||
|
<!-- top nav -->
|
||||||
|
<div data-r="nav" style="display: flex; align-items: center; justify-content: space-between; gap: 20px; padding-top: 34px; padding-bottom: 22px; border-bottom: 1px solid var(--cardbrd); flex-wrap: wrap;">
|
||||||
|
<div style="display: flex; align-items: baseline; gap: 22px; flex-wrap: wrap;">
|
||||||
|
<div style="font-family: 'Michroma', sans-serif; font-size: 24px; letter-spacing: 4px; color: var(--text);">HIFI <span style="color: var(--accent2);">PLANET</span></div>
|
||||||
|
<div style="font-size: 12px; letter-spacing: 6px; color: var(--label); text-transform: uppercase;">Home of Pure Sound</div>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 12px;">
|
||||||
|
<a href="https://wa.me/4915206682940" style="display: flex; align-items: center; gap: 10px; background: var(--pill); border: 1px solid var(--pillbrd); border-radius: 999px; padding: 11px 22px; box-shadow: var(--shadow2); color: var(--text2); font-weight: 600; font-size: 15px;" style-hover="border-color: var(--accent);">
|
||||||
|
<span style="width: 26px; height: 26px; border-radius: 50%; background: var(--accentsoft); display: inline-flex; align-items: center; justify-content: center;"><svg width="15" height="15" viewBox="0 0 24 24" fill="var(--accent2)"><path d="M12 2a10 10 0 0 0-8.6 15l-1.3 4.8 5-1.3A10 10 0 1 0 12 2zm0 2a8 8 0 1 1-4.2 14.8l-.3-.2-2.6.7.7-2.5-.2-.3A8 8 0 0 1 12 4zm4.3 9.9c-.2-.1-1.4-.7-1.6-.8s-.4-.1-.5.1l-.7.9c-.1.1-.3.2-.5.1a6.5 6.5 0 0 1-3.2-2.8c-.1-.2 0-.4.1-.5l.4-.5.2-.4v-.4l-.7-1.7c-.2-.4-.4-.4-.5-.4h-.5c-.2 0-.4.1-.6.3-.7.7-.9 1.6-.6 2.7.5 1.7 1.6 3.1 3.4 4.2 1.7 1 2.5 1 3.4.9.5-.1 1.4-.6 1.6-1.1.2-.5.2-1 .1-1.1z"></path></svg></span>
|
||||||
|
+49 1520 6682940
|
||||||
|
</a>
|
||||||
|
<button onClick="{{ toggleTheme }}" aria-label="Modus wechseln" style="width: 46px; height: 46px; flex-shrink: 0; background: var(--pill); border: 1px solid var(--pillbrd); border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: var(--shadow2); color: var(--text2);" style-hover="border-color: var(--accent);">
|
||||||
|
<sc-if value="{{ isDark }}" hint-placeholder-val="{{ false }}"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--accent2)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"></circle><path d="M12 2v2M12 20v2M2 12h2M20 12h2M5 5l1.4 1.4M17.6 17.6 19 19M19 5l-1.4 1.4M6.4 17.6 5 19"></path></svg></sc-if>
|
||||||
|
<sc-if value="{{ isLight }}" hint-placeholder-val="{{ true }}"><svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg></sc-if>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-r="main" style="display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(320px, 1fr); gap: 40px; align-items: start; margin-top: 46px;">
|
||||||
|
|
||||||
|
<!-- LEFT column -->
|
||||||
|
<div>
|
||||||
|
<div style="font-size: 13px; letter-spacing: 6px; color: var(--accent2); text-transform: uppercase; margin-bottom: 12px; font-weight: 600;">Kontakt</div>
|
||||||
|
<h1 data-r="h1" style="font-family: 'Michroma', sans-serif; font-size: clamp(34px, 5vw, 60px); line-height: 1.08; margin: 0; color: var(--text); text-wrap: balance;">Sprich <span style="color: var(--accent2);">mit</span> uns.</h1>
|
||||||
|
<p style="max-width: 520px; color: var(--muted); font-size: 18px; line-height: 1.55; margin: 20px 0 40px;">Ob Anlage, Einbau oder Klang-Tuning für dein Fahrzeug — schreib uns, wir melden uns schnellstmöglich zurück.</p>
|
||||||
|
|
||||||
|
<!-- form card -->
|
||||||
|
<div data-r="formcard" style="background: var(--card); border: 1px solid var(--cardbrd); border-radius: 14px; padding: 40px; box-shadow: var(--shadow);">
|
||||||
|
|
||||||
|
<!-- package summary -->
|
||||||
|
<sc-if value="{{ hasPackage }}" hint-placeholder-val="{{ true }}">
|
||||||
|
<div style="background: var(--pkgbg); border: 1px solid var(--pkgbrd); border-radius: 12px; padding: 22px 24px; margin-bottom: 30px;">
|
||||||
|
<div style="display: flex; align-items: center; gap: 10px; font-size: 12px; letter-spacing: 2px; text-transform: uppercase; color: var(--accent2); font-weight: 700; margin-bottom: 14px;">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--accent2)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.7l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.7l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path><path d="m3.3 7 8.7 5 8.7-5M12 22V12"></path></svg>
|
||||||
|
Deine Anfrage bezieht sich auf
|
||||||
|
</div>
|
||||||
|
<div style="display: grid; grid-template-columns: auto 1fr; gap: 8px 20px; font-size: 15px;">
|
||||||
|
<span style="color: var(--label);">Marke</span><span style="color: var(--text2); font-weight: 600;">{{ pkgBrand }}</span>
|
||||||
|
<span style="color: var(--label);">Modell</span><span style="color: var(--text2); font-weight: 600;">{{ pkgModel }}</span>
|
||||||
|
<span style="color: var(--label);">Paket</span><span style="color: var(--text2); font-weight: 600;">{{ pkgName }}</span>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--pkgbrd); display: flex; align-items: baseline; justify-content: space-between;">
|
||||||
|
<span style="font-size: 13px; letter-spacing: 1px; text-transform: uppercase; color: var(--muted); font-weight: 600;">Paketpreis</span>
|
||||||
|
<span style="font-family: 'Michroma', sans-serif; font-size: 20px; color: var(--text);">{{ pkgPrice }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</sc-if>
|
||||||
|
|
||||||
|
<h2 style="font-family: 'Michroma', sans-serif; font-size: 20px; margin: 0 0 30px; color: var(--text); letter-spacing: .5px;">Anfrage senden</h2>
|
||||||
|
|
||||||
|
<sc-if value="{{ sent }}" hint-placeholder-val="{{ false }}">
|
||||||
|
<div style="border: 1px solid var(--accent); background: var(--accentsoft); border-radius: 10px; padding: 20px 22px; margin-bottom: 26px;">
|
||||||
|
<div style="font-weight: 700; color: var(--accent2); font-size: 16px;">Anfrage gesendet ✓</div>
|
||||||
|
<div style="color: var(--muted); font-size: 14px; margin-top: 5px;">Danke! Wir melden uns innerhalb eines Werktags bei dir.</div>
|
||||||
|
</div>
|
||||||
|
</sc-if>
|
||||||
|
|
||||||
|
<div data-r="fields" style="display: grid; grid-template-columns: 1fr 1fr; gap: 24px;">
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 9px;">
|
||||||
|
<label style="font-size: 12px; letter-spacing: 2px; text-transform: uppercase; color: var(--label); font-weight: 600;">Name *</label>
|
||||||
|
<div style="position: relative; display: flex; align-items: center;">
|
||||||
|
<svg style="position: absolute; left: 14px;" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="var(--fieldicon)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8" r="4"></circle><path d="M4 21v-1a6 6 0 0 1 6-6h4a6 6 0 0 1 6 6v1"></path></svg>
|
||||||
|
<input value="{{ name }}" onChange="{{ setName }}" style="width: 100%; box-sizing: border-box; background: var(--field); border: 1px solid var(--fieldbrd); border-radius: 8px; padding: 14px 16px 14px 42px; color: var(--text2); font-size: 15px; font-family: 'Barlow', sans-serif; outline: none;" style-focus="border-color: var(--accent); box-shadow: 0 0 0 3px rgba(164,212,23,0.2);" placeholder="Max Mustermann">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 9px;">
|
||||||
|
<label style="font-size: 12px; letter-spacing: 2px; text-transform: uppercase; color: var(--label); font-weight: 600;">E-Mail *</label>
|
||||||
|
<div style="position: relative; display: flex; align-items: center;">
|
||||||
|
<svg style="position: absolute; left: 14px;" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="var(--fieldicon)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"></rect><path d="m22 7-10 6L2 7"></path></svg>
|
||||||
|
<input value="{{ email }}" onChange="{{ setEmail }}" type="email" style="width: 100%; box-sizing: border-box; background: var(--field); border: 1px solid var(--fieldbrd); border-radius: 8px; padding: 14px 16px 14px 42px; color: var(--text2); font-size: 15px; font-family: 'Barlow', sans-serif; outline: none;" style-focus="border-color: var(--accent); box-shadow: 0 0 0 3px rgba(164,212,23,0.2);" placeholder="max@mail.de">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 9px;">
|
||||||
|
<label style="font-size: 12px; letter-spacing: 2px; text-transform: uppercase; color: var(--label); font-weight: 600;">Telefon</label>
|
||||||
|
<div style="position: relative; display: flex; align-items: center;">
|
||||||
|
<svg style="position: absolute; left: 14px;" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="var(--fieldicon)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.36 1.9.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.91.34 1.85.57 2.81.7A2 2 0 0 1 22 16.92z"></path></svg>
|
||||||
|
<input style="width: 100%; box-sizing: border-box; background: var(--field); border: 1px solid var(--fieldbrd); border-radius: 8px; padding: 14px 16px 14px 42px; color: var(--text2); font-size: 15px; font-family: 'Barlow', sans-serif; outline: none;" style-focus="border-color: var(--accent); box-shadow: 0 0 0 3px rgba(164,212,23,0.2);" placeholder="Optional">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<sc-if value="{{ showFin }}" hint-placeholder-val="{{ true }}">
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 9px;">
|
||||||
|
<label style="font-size: 12px; letter-spacing: 2px; text-transform: uppercase; color: var(--label); font-weight: 600;">Fahrgestellnummer (FIN)</label>
|
||||||
|
<div style="position: relative; display: flex; align-items: center;">
|
||||||
|
<svg style="position: absolute; left: 14px;" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="var(--fieldicon)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 17h14M5 17a2 2 0 0 1-2-2v-1l2-5a3 3 0 0 1 2.8-2h8.4A3 3 0 0 1 19 9l2 5v1a2 2 0 0 1-2 2M7 17v2M17 17v2"></path><circle cx="7.5" cy="14" r="1"></circle><circle cx="16.5" cy="14" r="1"></circle></svg>
|
||||||
|
<input style="width: 100%; box-sizing: border-box; background: var(--field); border: 1px solid var(--fieldbrd); border-radius: 8px; padding: 14px 16px 14px 42px; color: var(--text2); font-size: 15px; font-family: 'Barlow', sans-serif; outline: none;" style-focus="border-color: var(--accent); box-shadow: 0 0 0 3px rgba(164,212,23,0.2);" placeholder="Optional – hilft bei der Einschätzung">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</sc-if>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 9px; margin-top: 24px;">
|
||||||
|
<label style="font-size: 12px; letter-spacing: 2px; text-transform: uppercase; color: var(--label); font-weight: 600;">Nachricht</label>
|
||||||
|
<textarea rows="5" style="width: 100%; box-sizing: border-box; background: var(--field); border: 1px solid var(--fieldbrd); border-radius: 8px; padding: 14px 16px; color: var(--text2); font-size: 15px; font-family: 'Barlow', sans-serif; outline: none; resize: vertical;" style-focus="border-color: var(--accent); box-shadow: 0 0 0 3px rgba(164,212,23,0.2);" placeholder="Erzähl uns von deinem Projekt – Fahrzeug, Wunschklang, Budget …"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: flex; align-items: center; gap: 20px; margin-top: 30px; flex-wrap: wrap;">
|
||||||
|
<button onClick="{{ submit }}" style="display: inline-flex; align-items: center; gap: 12px; background: var(--accent); color: var(--btntext); border: none; border-radius: 8px; padding: 16px 34px; font-size: 14px; font-weight: 700; letter-spacing: 3px; text-transform: uppercase; font-family: 'Barlow', sans-serif; cursor: pointer; box-shadow: 0 10px 24px rgba(150,200,20,0.32);" style-hover="filter: brightness(1.08); box-shadow: 0 12px 30px rgba(150,200,20,0.45);"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m22 2-7 20-4-9-9-4 20-7z"></path></svg>Anfrage senden</button>
|
||||||
|
<div style="font-size: 13px; color: var(--label);">* Pflichtfelder</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- RIGHT column -->
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 24px;">
|
||||||
|
<div data-r="contactcard" style="background: var(--card); border: 1px solid var(--cardbrd); border-radius: 14px; padding: 34px 32px; box-shadow: var(--shadow); position: relative; overflow: hidden;">
|
||||||
|
<div style="position: absolute; right: -60px; top: -60px; width: 190px; height: 190px; border: 1px dashed var(--pkgbrd); border-radius: 50%;"></div>
|
||||||
|
<h2 style="font-family: 'Michroma', sans-serif; font-size: 17px; margin: 0 0 28px; color: var(--text); letter-spacing: .5px;">HiFi Planet Amorbach</h2>
|
||||||
|
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 22px;">
|
||||||
|
<div style="display: flex; gap: 16px; align-items: flex-start;">
|
||||||
|
<div style="width: 44px; height: 44px; flex-shrink: 0; background: var(--accentsoft); border-radius: 50%; display: flex; align-items: center; justify-content: center;"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--accent2)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.36 1.9.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.91.34 1.85.57 2.81.7A2 2 0 0 1 22 16.92z"></path></svg></div>
|
||||||
|
<div><div style="font-size: 11px; letter-spacing: 3px; color: var(--accent2); text-transform: uppercase; margin-bottom: 4px; font-weight: 600;">Telefon</div><a href="tel:093732062390" style="font-size: 16px; font-weight: 600; color: var(--text2);">09373 20 62 390</a></div>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; gap: 16px; align-items: flex-start;">
|
||||||
|
<div style="width: 44px; height: 44px; flex-shrink: 0; background: var(--accentsoft); border-radius: 50%; display: flex; align-items: center; justify-content: center;"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--accent2)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"></rect><path d="m22 7-10 6L2 7"></path></svg></div>
|
||||||
|
<div><div style="font-size: 11px; letter-spacing: 3px; color: var(--accent2); text-transform: uppercase; margin-bottom: 4px; font-weight: 600;">E-Mail</div><a href="mailto:info@hifi-planet.de" style="font-size: 16px; font-weight: 600; color: var(--text2);">info@hifi-planet.de</a></div>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; gap: 16px; align-items: flex-start;">
|
||||||
|
<div style="width: 44px; height: 44px; flex-shrink: 0; background: var(--accentsoft); border-radius: 50%; display: flex; align-items: center; justify-content: center;"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--accent2)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0z"></path><circle cx="12" cy="10" r="3"></circle></svg></div>
|
||||||
|
<div><div style="font-size: 11px; letter-spacing: 3px; color: var(--accent2); text-transform: uppercase; margin-bottom: 4px; font-weight: 600;">Adresse</div><div style="font-size: 16px; font-weight: 600; color: var(--text2); line-height: 1.4;">Boxbrunner Straße 20a<br>63916 Amorbach</div></div>
|
||||||
|
</div>
|
||||||
|
<sc-if value="{{ showWhatsapp }}" hint-placeholder-val="{{ true }}">
|
||||||
|
<div style="display: flex; gap: 16px; align-items: flex-start;">
|
||||||
|
<div style="width: 44px; height: 44px; flex-shrink: 0; background: var(--accentsoft); border-radius: 50%; display: flex; align-items: center; justify-content: center;"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--accent2)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path></svg></div>
|
||||||
|
<div><div style="font-size: 11px; letter-spacing: 3px; color: var(--accent2); text-transform: uppercase; margin-bottom: 4px; font-weight: 600;">WhatsApp</div><a href="https://wa.me/4915206682940" style="font-size: 16px; font-weight: 600; color: var(--text2);">+49 1520 6682940</a></div>
|
||||||
|
</div>
|
||||||
|
</sc-if>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top: 28px; padding-top: 24px; border-top: 1px solid var(--divider);">
|
||||||
|
<div style="font-size: 11px; letter-spacing: 3px; color: var(--accent2); text-transform: uppercase; margin-bottom: 12px; font-weight: 600;">Öffnungszeiten</div>
|
||||||
|
<div style="display: grid; grid-template-columns: auto 1fr; gap: 8px 24px; font-size: 15px; color: var(--text2);">
|
||||||
|
<span style="color: var(--label);">Mo–Fr</span><span>9:00 – 18:00 Uhr</span>
|
||||||
|
<span style="color: var(--label);">Sa</span><span>10:00 – 13:00 Uhr</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="background: var(--card); border: 1px solid var(--cardbrd); border-radius: 14px; overflow: hidden; box-shadow: var(--shadow); position: relative;">
|
||||||
|
<a href="https://www.google.com/maps/dir/?api=1&destination=Boxbrunner+Str.+20a,+63916+Amorbach" target="_blank" style="position: absolute; top: 14px; left: 14px; z-index: 2; display: inline-flex; align-items: center; gap: 8px; background: var(--pill); border: 1px solid var(--pillbrd); border-radius: 8px; padding: 8px 14px; font-size: 13px; font-weight: 600; color: var(--text2); box-shadow: var(--shadow2);" style-hover="border-color: var(--accent);">In Maps öffnen <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="var(--accent2)" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6M10 14 21 3M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path></svg></a>
|
||||||
|
<iframe title="Anfahrt HiFi Planet" src="https://www.google.com/maps?q=Boxbrunner+Str.+20a,+63916+Amorbach&output=embed" style="width: 100%; height: 210px; border: 0; display: block;"></iframe>
|
||||||
|
<a href="https://www.google.com/maps/dir/?api=1&destination=Boxbrunner+Str.+20a,+63916+Amorbach" target="_blank" style="display: flex; align-items: center; justify-content: center; gap: 10px; padding: 16px; font-size: 13px; font-weight: 700; letter-spacing: 3px; text-transform: uppercase; color: var(--accent2); border-top: 1px solid var(--divider);" style-hover="background: var(--accentsoft);">Route planen →</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- feature bar -->
|
||||||
|
<div data-r="features" style="margin-top: 40px; background: var(--card); border: 1px solid var(--cardbrd); border-radius: 14px; box-shadow: var(--shadow2); display: grid; grid-template-columns: repeat(4, 1fr); gap: 0; overflow: hidden;">
|
||||||
|
<sc-for list="{{ features }}" as="f" hint-placeholder-count="4">
|
||||||
|
<div data-r="feature" style="display: flex; align-items: center; gap: 16px; padding: 26px 30px; border-right: 1px solid var(--divider);">
|
||||||
|
<div style="width: 46px; height: 46px; flex-shrink: 0; border: 1.5px solid var(--pkgbrd); border-radius: 50%; display: flex; align-items: center; justify-content: center; color: var(--accent2);" dangerouslySetInnerHTML="{{ f.icon }}"></div>
|
||||||
|
<div>
|
||||||
|
<div style="font-size: 15px; font-weight: 700; color: var(--text); letter-spacing: .3px;">{{ f.title }}</div>
|
||||||
|
<div style="font-size: 13px; color: var(--muted); margin-top: 2px;">{{ f.sub }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</sc-for>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</x-dc>
|
||||||
|
<script type="text/x-dc" data-dc-script data-props="{"defaultDark":{"editor":"boolean","default":false,"tsType":"boolean","section":"Darstellung"},"showFin":{"editor":"boolean","default":true,"tsType":"boolean","section":"Formular"},"showWhatsapp":{"editor":"boolean","default":true,"tsType":"boolean","section":"Kontaktdaten"},"pkgBrand":{"editor":"text","default":"","tsType":"string","section":"Paket (leer = ausgeblendet)"},"pkgModel":{"editor":"text","default":"","tsType":"string","section":"Paket (leer = ausgeblendet)"},"pkgName":{"editor":"text","default":"","tsType":"string","section":"Paket (leer = ausgeblendet)"},"pkgPrice":{"editor":"text","default":"","tsType":"string","section":"Paket (leer = ausgeblendet)"}}">
|
||||||
|
class Component extends DCLogic {
|
||||||
|
state = { name: "", email: "", sent: false, dark: null };
|
||||||
|
renderVals() {
|
||||||
|
const icon = (p) => ({ __html: `<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${p}</svg>` });
|
||||||
|
const dark = this.state.dark == null ? (this.props.defaultDark ?? false) : this.state.dark;
|
||||||
|
const brand = this.props.pkgBrand ?? "";
|
||||||
|
return {
|
||||||
|
name: this.state.name,
|
||||||
|
email: this.state.email,
|
||||||
|
sent: this.state.sent,
|
||||||
|
theme: dark ? "dark" : "light",
|
||||||
|
isDark: dark,
|
||||||
|
isLight: !dark,
|
||||||
|
toggleTheme: () => this.setState({ dark: !dark }),
|
||||||
|
setName: (e) => this.setState({ name: e.target.value }),
|
||||||
|
setEmail: (e) => this.setState({ email: e.target.value }),
|
||||||
|
submit: () => this.setState({ sent: true }),
|
||||||
|
showFin: this.props.showFin ?? true,
|
||||||
|
showWhatsapp: this.props.showWhatsapp ?? true,
|
||||||
|
hasPackage: !!brand,
|
||||||
|
pkgBrand: brand,
|
||||||
|
pkgModel: this.props.pkgModel ?? "",
|
||||||
|
pkgName: this.props.pkgName ?? "",
|
||||||
|
pkgPrice: this.props.pkgPrice ?? "",
|
||||||
|
features: [
|
||||||
|
{ title: "Premium Klang", sub: "Höchste Qualität", icon: icon('<path d="M4 12v3M8 8v11M12 4v16M16 8v11M20 12v3"></path>') },
|
||||||
|
{ title: "Made in Germany", sub: "Präzise Handarbeit", icon: icon('<circle cx="12" cy="12" r="3"></circle><path d="M12 2v2M12 20v2M2 12h2M20 12h2M5 5l1.5 1.5M17.5 17.5 19 19M19 5l-1.5 1.5M6.5 17.5 5 19"></path>') },
|
||||||
|
{ title: "Individuell", sub: "Maßgeschneiderte Lösungen", icon: icon('<path d="M6 3h12l4 6-10 12L2 9z"></path><path d="M11 3 8 9l4 12 4-12-3-6M2 9h20"></path>') },
|
||||||
|
{ title: "Experten Team", sub: "Leidenschaft & Know-how", icon: icon('<path d="M20 6 9 17l-5-5"></path>') },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,162 @@
|
||||||
|
# Handoff: Kontaktformular Redesign (HiFi Planet)
|
||||||
|
|
||||||
|
## Aufgabe für Claude Code
|
||||||
|
|
||||||
|
Ersetze das bestehende Kontaktformular der Website durch das neue Design aus `Kontaktformular.dc.html` (Design-Referenz in diesem Ordner). Baue es in der bestehenden Codebase mit deren Framework, Komponenten- und Styling-Patterns nach — die HTML-Datei ist ein **Design-Prototyp als Referenz**, kein Produktionscode zum Kopieren.
|
||||||
|
|
||||||
|
### Wichtige Integrationspunkte (unbedingt beachten!)
|
||||||
|
|
||||||
|
1. **KEIN eigener Dark-Mode-Toggle.** Die Seite hat bereits einen globalen Hell/Dunkel-Umschalter. Den Sonne/Mond-Button aus dem Prototyp (oben rechts in der Nav) **weglassen**. Stattdessen: das Kontaktformular reagiert auf den vorhandenen Theme-Mechanismus der Seite (z.B. `data-theme`-Attribut, CSS-Klasse auf `<html>`/`<body>`, oder Theme-Context — an das anpassen, was die Codebase nutzt). Beide Farbwelten (Light + Dark, siehe Design Tokens unten) müssen umgesetzt werden.
|
||||||
|
|
||||||
|
2. **Paket-Vorauswahl übernehmen.** Auf der Seite gibt es eine Paketauswahl (Marke → Modell → Paket); klickt der Nutzer dort auf „Kontakt aufnehmen", landet er auf dem Kontaktformular und dort muss die Box **„Deine Anfrage bezieht sich auf"** mit Marke, Modell, Paketname und Paketpreis erscheinen (siehe Screens unten). Anbindung an den bestehenden Mechanismus (Query-Params, Router-State, Store o.ä. — prüfen, wie es aktuell gelöst ist). Kommt der Nutzer **ohne** Paketauswahl auf die Seite, wird die Box **nicht** angezeigt. Die Paketdaten sollen zusammen mit dem Formular abgeschickt werden.
|
||||||
|
|
||||||
|
3. **Formular-Versand:** Der Prototyp zeigt nur eine Erfolgsmeldung im UI. Den echten Versand (bestehender Endpoint / Mailer der Seite) anbinden.
|
||||||
|
|
||||||
|
## Fidelity
|
||||||
|
|
||||||
|
**High-fidelity.** Farben, Typografie, Abstände, Radii und Zustände sind final und sollen pixelgenau übernommen werden.
|
||||||
|
|
||||||
|
## Screens / Aufbau
|
||||||
|
|
||||||
|
Eine Seite, zwei Themes (light/dark), responsive. Max. Inhaltsbreite 1280px, zentriert, Seiten-Padding 40px (mobil 16px), unten 40px.
|
||||||
|
|
||||||
|
### Top-Nav
|
||||||
|
- Zeile mit `justify-content: space-between`, unten 1px Border (`--cardbrd`), padding-top 34px, padding-bottom 22px.
|
||||||
|
- Links: Logo-Wortmarke „HIFI PLANET" (Michroma, 24px, letter-spacing 4px; „PLANET" in Akzentgrün) + Tagline „HOME OF PURE SOUND" (12px, letter-spacing 6px, uppercase, gedämpft). **Falls die Seite bereits eine globale Nav hat, entfällt dieser Block — dann nur den Seiteninhalt ab „Kontakt" übernehmen.**
|
||||||
|
- Rechts: WhatsApp-Pille — weiße (dark: Karten-)Pille, `border-radius: 999px`, padding 11px 22px, Icon in grünem Kreis (26px, Hintergrund `--accentsoft`), Nummer „+49 1520 6682940", Link `https://wa.me/4915206682940`. Hover: Border wird Akzentgrün.
|
||||||
|
- (Der Theme-Toggle-Button daneben entfällt, s.o.)
|
||||||
|
|
||||||
|
### Hero / Intro (linke Spalte, oberhalb Formular)
|
||||||
|
- Eyebrow „KONTAKT": 13px, letter-spacing 6px, uppercase, Akzentgrün, semibold, margin-bottom 12px.
|
||||||
|
- H1 „Sprich mit uns.": Michroma, `clamp(34px, 5vw, 60px)`, line-height 1.08, Textfarbe `--text`; das Wort „mit" in Akzentgrün. Mobil 34px.
|
||||||
|
- Untertitel: „Ob Anlage, Einbau oder Klang-Tuning für dein Fahrzeug — schreib uns, wir melden uns schnellstmöglich zurück." — 18px, line-height 1.55, `--muted`, max-width 520px, margin 20px 0 40px.
|
||||||
|
|
||||||
|
### Hauptraster
|
||||||
|
- Grid: `minmax(0, 1.35fr) minmax(320px, 1fr)`, gap 40px, `align-items: start`, margin-top 46px.
|
||||||
|
- **≤980px:** einspaltig.
|
||||||
|
|
||||||
|
### Formular-Karte (links)
|
||||||
|
Karte: Hintergrund `--card`, 1px Border `--cardbrd`, `border-radius: 14px`, padding 40px (mobil 22px), Schatten `--shadow`.
|
||||||
|
|
||||||
|
**Paket-Box (konditional, ganz oben in der Karte):**
|
||||||
|
- Hintergrund `--pkgbg`, 1px Border `--pkgbrd`, `border-radius: 12px`, padding 22px 24px, margin-bottom 30px.
|
||||||
|
- Kopfzeile: Paket-Icon (16px, Outline-Stil) + „DEINE ANFRAGE BEZIEHT SICH AUF" — 12px, letter-spacing 2px, uppercase, Akzentgrün, bold, margin-bottom 14px.
|
||||||
|
- Zeilen als 2-Spalten-Grid (`auto 1fr`, gap 8px 20px, 15px): Label gedämpft (`--label`), Wert semibold (`--text2`):
|
||||||
|
- Marke → z.B. „Audi"
|
||||||
|
- Modell → z.B. „A5 F5 Cabrio"
|
||||||
|
- Paket → z.B. „Base"
|
||||||
|
- Trennlinie (1px, `--pkgbrd`), darunter Zeile „PAKETPREIS" (13px, uppercase, `--muted`) links und Preis rechts (Michroma, 20px, `--text`), z.B. „999,00 €".
|
||||||
|
|
||||||
|
**Überschrift:** „Anfrage senden" — Michroma, 20px, `--text`, margin-bottom 30px.
|
||||||
|
|
||||||
|
**Felder** (Grid 2 Spalten, gap 24px; **≤640px** einspaltig):
|
||||||
|
1. Name * (Pflicht) — Icon: Person
|
||||||
|
2. E-Mail * (Pflicht, type=email) — Icon: Briefumschlag
|
||||||
|
3. Telefon (optional) — Icon: Hörer
|
||||||
|
4. Fahrgestellnummer (FIN) (optional, Placeholder „Optional – hilft bei der Einschätzung") — Icon: Auto
|
||||||
|
|
||||||
|
Feld-Anatomie:
|
||||||
|
- Label: 12px, letter-spacing 2px, uppercase, `--label`, semibold, gap 9px zum Input.
|
||||||
|
- Input: Hintergrund `--field`, 1px Border `--fieldbrd`, `border-radius: 8px`, padding 14px 16px (links 42px wegen Icon), 15px Text, Icon 17px absolut links 14px in Farbe `--fieldicon`.
|
||||||
|
- Focus: Border Akzent, `box-shadow: 0 0 0 3px rgba(164,212,23,0.2)`.
|
||||||
|
- Placeholder-Farbe: `--fieldicon`.
|
||||||
|
|
||||||
|
**Nachricht:** Textarea, 5 Zeilen, gleiche Feld-Optik (ohne Icon), `resize: vertical`, margin-top 24px. Placeholder: „Erzähl uns von deinem Projekt – Fahrzeug, Wunschklang, Budget …".
|
||||||
|
|
||||||
|
**Submit-Zeile** (margin-top 30px, flex, gap 20px):
|
||||||
|
- Button „ANFRAGE SENDEN" mit Papierflieger-Icon (18px): Hintergrund `--accent`, Text `--btntext`, `border-radius: 8px`, padding 16px 34px, 14px, bold, letter-spacing 3px, uppercase, Schatten `0 10px 24px rgba(150,200,20,0.32)`. Hover: leicht heller + stärkerer Schatten.
|
||||||
|
- Hinweis „* Pflichtfelder", 13px, `--label`.
|
||||||
|
|
||||||
|
**Erfolgszustand:** Nach Versand Box über den Feldern — Border `--accent`, Hintergrund `--accentsoft`, `border-radius: 10px`, padding 20px 22px: „Anfrage gesendet ✓" (bold, Akzent) + „Danke! Wir melden uns innerhalb eines Werktags bei dir." (14px, `--muted`).
|
||||||
|
|
||||||
|
### Kontakt-Karte (rechts oben)
|
||||||
|
Gleiche Kartenoptik, padding 34px 32px (mobil 26px 22px). Dekor: gestrichelter Kreis (190px, `--pkgbrd`) rechts oben aus der Karte laufend.
|
||||||
|
- Überschrift „HiFi Planet Amorbach" — Michroma 17px.
|
||||||
|
- 4 Einträge, je: runder Icon-Chip 44px (`--accentsoft`, Icon 18px Akzentgrün) + Mini-Label (11px, letter-spacing 3px, uppercase, Akzentgrün) + Wert (16px, semibold):
|
||||||
|
- TELEFON → „09373 20 62 390" (`tel:093732062390`)
|
||||||
|
- E-MAIL → „info@hifi-planet.de" (`mailto:`)
|
||||||
|
- ADRESSE → „Boxbrunner Straße 20a / 63916 Amorbach" (zweizeilig)
|
||||||
|
- WHATSAPP → „+49 1520 6682940" (`https://wa.me/4915206682940`)
|
||||||
|
- Abschnitt „ÖFFNUNGSZEITEN" nach Trennlinie (`--divider`): Grid `auto 1fr`, gap 8px 24px, 15px — „Mo–Fr / 9:00 – 18:00 Uhr", „Sa / 10:00 – 13:00 Uhr".
|
||||||
|
|
||||||
|
### Karten-/Map-Block (rechts unten)
|
||||||
|
- Karte mit `border-radius: 14px`, overflow hidden.
|
||||||
|
- Google-Maps-Embed (`https://www.google.com/maps?q=Boxbrunner+Str.+20a,+63916+Amorbach&output=embed`), Höhe 210px.
|
||||||
|
- Overlay-Chip oben links: „In Maps öffnen" + Extern-Icon, Pillen-Optik (Hintergrund `--pill`, Border `--pillbrd`, radius 8px, padding 8px 14px, 13px semibold).
|
||||||
|
- Fußzeile: „ROUTE PLANEN →" — 13px, bold, letter-spacing 3px, uppercase, Akzentgrün, padding 16px, obere Trennlinie; Hover: Hintergrund `--accentsoft`. Beide Links: Google-Maps-Directions-URL, `target="_blank"`.
|
||||||
|
|
||||||
|
### Feature-Leiste (unter dem Raster, volle Breite)
|
||||||
|
Karte, margin-top 40px, Grid 4 Spalten (≤900px: 2 Spalten, ≤640px: 1 Spalte), Zellen mit 1px Trenner rechts (mobil unten):
|
||||||
|
Je Zelle: Icon im Kreis 46px (1.5px Border `--pkgbrd`, Icon 22px Akzentgrün) + Titel (15px, bold) + Untertitel (13px, `--muted`):
|
||||||
|
1. „Premium Klang" / „Höchste Qualität" (Equalizer-Icon)
|
||||||
|
2. „Made in Germany" / „Präzise Handarbeit" (Zahnrad/Sonne-Icon)
|
||||||
|
3. „Individuell" / „Maßgeschneiderte Lösungen" (Diamant-Icon)
|
||||||
|
4. „Experten Team" / „Leidenschaft & Know-how" (Häkchen-Icon)
|
||||||
|
|
||||||
|
Alle Icons: Outline-SVGs, `stroke-width: 2`, `stroke-linecap/linejoin: round` (Lucide-Stil — falls die Codebase Lucide o.ä. nutzt, deren Icons verwenden).
|
||||||
|
|
||||||
|
## Hintergründe (Seitenebene, hinter den Karten)
|
||||||
|
|
||||||
|
### Light Mode
|
||||||
|
- Seitenverlauf: `linear-gradient(120deg, #eef0ea 0%, #f6f8f3 45%, #fbfcf9 100%)`.
|
||||||
|
- Links: Auto-Foto-Streifen, 34% Breite, volle Höhe, `object-fit: cover`, darüber Scrim `linear-gradient(90deg, rgba(244,246,241,0.35), rgba(244,246,241,0.55) 55%, rgba(246,248,243,1))`. Foto: eigenes Asset der Seite verwenden. **≤980px ausblenden.**
|
||||||
|
- Rechts: grüne „Lichtstreifen"-Gruppe, 42% Breite, um 6° rotiert, aus vier Ebenen: 3px-Linie (`linear-gradient(180deg, transparent, #b6e619 30%, #cfef5a 55%, transparent)`, blur 1px, opacity .85), 90px weicher Schein (blur 26px), zweite dünne Linie (2px, blur 1px), großer radialer Glow (`rgba(207,239,90,0.35)`, blur 20px).
|
||||||
|
- Oben links: dezente Topo-Wellenlinien (5 geschwungene Pfade, Stroke `#d7dccd`, 1px, opacity .5, ca. 360×280px).
|
||||||
|
|
||||||
|
### Dark Mode
|
||||||
|
- Seitenverlauf: `linear-gradient(120deg, #181b0f 0%, #111409 45%, #0a0c06 100%)`.
|
||||||
|
- Vollflächige Grunge-Textur: `assets/grunge.png` (cover, zentriert) — dunkle Körnung mit Lichtfleck und Vignette.
|
||||||
|
- Rechts: grüner Paint-Splatter `assets/splatter.png` — 46% Breite, 122% Höhe, top -8%, right -4%, `background-size: contain`, rechtsbündig, mit `filter: drop-shadow(0 0 22px rgba(164,212,23,0.35))`.
|
||||||
|
- Rechts zusätzlich: großer Kreisbogen als SVG — Vollton-Bogen `rgba(168,225,12,0.5)` (1.5px) + innerer gestrichelter Bogen `rgba(168,225,12,0.18)` (1px, `stroke-dasharray: 2 9`).
|
||||||
|
- Beide PNGs liegen in `assets/` bei.
|
||||||
|
|
||||||
|
Alle Hintergrund-Ebenen: `position: absolute`, `pointer-events: none`, Inhalt darüber (`z-index: 1`).
|
||||||
|
|
||||||
|
## Design Tokens
|
||||||
|
|
||||||
|
Als CSS Custom Properties umgesetzt (Prototyp nutzt `:root` + `[data-theme="dark"]`):
|
||||||
|
|
||||||
|
| Token | Light | Dark |
|
||||||
|
|---|---|---|
|
||||||
|
| `--card` | `#ffffff` | `#14170e` |
|
||||||
|
| `--cardbrd` | `#e4e8dc` | `rgba(168,225,12,0.16)` |
|
||||||
|
| `--text` (Headings) | `#23271d` | `#f3f6ea` |
|
||||||
|
| `--text2` (Body) | `#2a2e24` | `#e8ece0` |
|
||||||
|
| `--muted` | `#6c7360` | `#9aa38c` |
|
||||||
|
| `--label` | `#838a72` | `#8f987f` |
|
||||||
|
| `--field` | `#fafcf6` | `rgba(0,0,0,0.35)` |
|
||||||
|
| `--fieldbrd` | `#dde2d3` | `#2c3323` |
|
||||||
|
| `--fieldicon` | `#a7ad9d` | `#5f6852` |
|
||||||
|
| `--accent` (Button) | `#a4d417` | `#a8e10c` |
|
||||||
|
| `--accent2` (Grün-Text/Icons) | `#8ec21a` | `#b6e619` |
|
||||||
|
| `--accentsoft` | `#f2f8df` | `rgba(168,225,12,0.1)` |
|
||||||
|
| `--pill` | `#ffffff` | `#14170e` |
|
||||||
|
| `--pillbrd` | `#dfe4d6` | `rgba(168,225,12,0.28)` |
|
||||||
|
| `--shadow` | `0 24px 60px rgba(60,70,40,0.08)` | `0 24px 60px rgba(0,0,0,0.55)` |
|
||||||
|
| `--shadow2` | `0 16px 40px rgba(60,70,40,0.06)` | `0 16px 40px rgba(0,0,0,0.45)` |
|
||||||
|
| `--divider` | `#e8ecdf` | `rgba(168,225,12,0.15)` |
|
||||||
|
| `--pkgbg` (Paket-Box) | `#f4fbe4` | `rgba(168,225,12,0.08)` |
|
||||||
|
| `--pkgbrd` | `#cfe89a` | `rgba(168,225,12,0.4)` |
|
||||||
|
| `--btntext` | `#1f2417` | `#0f1208` |
|
||||||
|
|
||||||
|
**Typografie:** Michroma (Headings/Logo/Preis) + Barlow 400–700 (alles andere), via Google Fonts. Falls die Seite diese Fonts schon lädt, vorhandene Einbindung nutzen.
|
||||||
|
|
||||||
|
**Radii:** Karten 14px, Paket-Box 12px, Erfolgsbox 10px, Inputs/Buttons 8px, Pillen 999px, Icon-Chips 50%.
|
||||||
|
|
||||||
|
## Responsive Breakpoints
|
||||||
|
- **≤980px:** Hauptraster einspaltig; Auto-Foto (light) ausblenden.
|
||||||
|
- **≤900px:** Feature-Leiste 2-spaltig.
|
||||||
|
- **≤640px:** Formularfelder 1-spaltig, Feature-Leiste 1-spaltig (Trenner unten statt rechts), Seiten-Padding 16px, Karten-Padding reduziert (Formular 22px), H1 34px.
|
||||||
|
|
||||||
|
## State Management
|
||||||
|
- `sent: boolean` — Erfolgsbox nach Submit (bzw. an echten Submit-Flow koppeln: loading/success/error).
|
||||||
|
- Paketdaten `{ marke, modell, paket, preis }` — von der Paketauswahl-Seite übergeben (bestehenden Mechanismus verwenden); `null` → Box ausblenden.
|
||||||
|
- Theme — vom globalen Umschalter der Seite, kein lokaler State.
|
||||||
|
|
||||||
|
## Assets (beiliegend)
|
||||||
|
- `assets/grunge.png` — Dark-Mode Grunge-Hintergrund (1600×1400)
|
||||||
|
- `assets/splatter.png` — Dark-Mode Paint-Splatter, transparent (1100×1500)
|
||||||
|
- Auto-Foto für den Light Mode: **nicht enthalten** — vorhandenes Bildmaterial der Seite verwenden.
|
||||||
|
|
||||||
|
## Dateien
|
||||||
|
- `Kontaktformular.dc.html` — Design-Referenz (Prototyp; Template + Inline-Styles; Dark Mode über `data-theme="dark"` toggelbar — im Prototyp per Button, in der Umsetzung über den globalen Theme-Schalter)
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 2.8 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 411 KiB |
Loading…
Reference in a new issue