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:
Maaxxs 2026-07-18 20:00:06 +02:00
parent 9de1059042
commit 78d3d4a8e3
11 changed files with 1025 additions and 133 deletions

View file

@ -8,7 +8,7 @@
Kunden-Referenzdesign) - der Rest der Seite bleibt beim Standard-Font. -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<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>
<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/" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 KiB

View file

@ -165,8 +165,15 @@ export default {
metaTitle: 'Kontakt',
metaDescription: 'Kontaktiere HifiPlanet in Amorbach für dein individuelles Car-Hifi Projekt wir beraten dich gerne unverbindlich.',
title: 'Kontakt aufnehmen',
sentTitle: 'Danke für deine Anfrage!',
sentText: 'Wir melden uns so schnell wie möglich bei dir.',
heroEyebrow: 'Kontakt',
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:',
contextBrand: 'Marke',
contextModel: 'Modell',
@ -175,12 +182,18 @@ export default {
packagePrice: 'Paketpreis',
upgrades: 'Upgrades',
optionalUpgrades: 'Optionale Upgrades',
totalWithUpgrades: 'Gesamt inkl. Upgrades',
nameLabel: 'Name *',
namePlaceholder: 'Max Mustermann',
emailLabel: 'E-Mail *',
emailPlaceholder: 'max@mail.de',
phoneLabel: 'Telefon',
phonePlaceholder: 'Optional',
vinLabel: 'Fahrgestellnummer (FIN)',
vinPlaceholder: 'Optional hilft uns bei der genauen Einschätzung deines Fahrzeugs',
vinPlaceholder: 'Optional hilft bei der Einschätzung',
messageLabel: 'Nachricht',
messagePlaceholder: 'Erzähl uns von deinem Projekt Fahrzeug, Wunschklang, Budget …',
requiredHint: '* Pflichtfelder',
sending: 'Wird gesendet…',
submit: 'Anfrage senden',
cardTitle: 'HifiPlanet Amorbach',
@ -190,9 +203,22 @@ export default {
hours: 'Öffnungszeiten',
hoursWeek: 'MoFr: 9:0018:00 Uhr',
hoursSat: 'Sa: 10:0013:00 Uhr',
hoursWeekDays: 'MoFr',
hoursWeekTime: '9:00 18:00 Uhr',
hoursSatDay: 'Sa',
hoursSatTime: '10:00 13:00 Uhr',
directionsTitle: 'Anfahrt',
mapEmbedName: 'Die Google-Maps-Karte',
mapOpenChip: 'In Maps öffnen',
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: {
metaTitle: 'Bildergalerie',

View file

@ -165,8 +165,15 @@ export default {
metaTitle: 'Contact',
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',
sentTitle: 'Thanks for your inquiry!',
sentText: "We'll get back to you as soon as possible.",
heroEyebrow: 'Contact',
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:',
contextBrand: 'Brand',
contextModel: 'Model',
@ -175,12 +182,18 @@ export default {
packagePrice: 'Package price',
upgrades: 'Upgrades',
optionalUpgrades: 'Optional upgrades',
totalWithUpgrades: 'Total incl. upgrades',
nameLabel: 'Name *',
namePlaceholder: 'Max Mustermann',
emailLabel: 'Email *',
emailPlaceholder: 'max@mail.de',
phoneLabel: 'Phone',
phonePlaceholder: 'Optional',
vinLabel: 'Vehicle identification number (VIN)',
vinPlaceholder: 'Optional helps us assess your vehicle accurately',
vinPlaceholder: 'Optional helps us assess your vehicle',
messageLabel: 'Message',
messagePlaceholder: 'Tell us about your project vehicle, desired sound, budget …',
requiredHint: '* Required fields',
sending: 'Sending…',
submit: 'Send inquiry',
cardTitle: 'HifiPlanet Amorbach',
@ -190,9 +203,22 @@ export default {
hours: 'Opening hours',
hoursWeek: 'MonFri: 9:00 AM6:00 PM',
hoursSat: 'Sat: 10:00 AM1:00 PM',
hoursWeekDays: 'MonFri',
hoursWeekTime: '9:00 AM 6:00 PM',
hoursSatDay: 'Sat',
hoursSatTime: '10:00 AM 1:00 PM',
directionsTitle: 'Directions',
mapEmbedName: 'The Google Maps map',
mapOpenChip: 'Open in Maps',
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: {
metaTitle: 'Photo gallery',

View file

@ -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");
}
/* ==== 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 {
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;

View file

@ -5,15 +5,92 @@ import usePageMeta from '../../hooks/usePageMeta.js';
import ExternalEmbed from '../../components/ExternalEmbed.jsx';
import { useSiteSettings } from '../../context/SiteSettingsContext.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 waHref = (whatsapp) => `https://wa.me/${digitsOnly(whatsapp).replace(/^0/, '49').replace('+', '')}`;
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() {
const [params] = useSearchParams();
const { phone, contact_email: contactEmail } = useSiteSettings();
const { phone, whatsapp, contact_email: contactEmail } = useSiteSettings();
const { t, language } = useLanguage();
const { consent } = useCookieConsent();
const formatPrice = (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: '' });
@ -51,9 +128,8 @@ export default function ContactPage() {
brand: params.get('brand') || '',
model: params.get('model') || '',
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 }));
@ -68,189 +144,454 @@ export default function ContactPage() {
brand_name: context.brand || null,
model_name: context.model || null,
package_name: context.package || null,
product_name: context.product || null,
product_name: params.get('product') || null,
package_id: packageId || null,
package_product_id: params.get('productId') || null,
upgrade_ids: selectedUpgradeIds,
});
setStatus('sent');
setForm({ name: '', email: '', phone: '', vin: '', message: '' });
} catch (e) {
setError(e.message);
setStatus('idle');
}
};
const features = [
{ key: 'premium', title: t('contactPage.featurePremiumTitle'), sub: t('contactPage.featurePremiumSub') },
{ 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') },
];
return (
<div className="mx-auto max-w-5xl px-4 py-12 sm:px-6">
<h1 className="mb-8 text-2xl font-bold text-neutral-900 dark:text-white sm:text-3xl">{t('contactPage.title')}</h1>
<div
className="kf-page relative w-full overflow-hidden px-4 pb-10 sm:px-10"
style={{ background: 'var(--kf-pagebg)', color: 'var(--kf-text2)' }}
>
{/* 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>
<div className="grid gap-10 md:grid-cols-5">
<div className="md:col-span-3">
{status === 'sent' ? (
<div className="rounded-xl border border-neutral-200 bg-white p-8 text-center dark:border-neutral-800 dark:bg-neutral-900">
<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>
</div>
) : (
<>
{hasContext && (
<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">
<p className="font-semibold">{t('contactPage.contextIntro')}</p>
<ul className="mt-1 space-y-0.5">
{context.brand && <li>{t('contactPage.contextBrand')}: {context.brand}</li>}
{context.model && <li>{t('contactPage.contextModel')}: {context.model}</li>}
{context.package && <li>{t('contactPage.contextPackage')}: {context.package}</li>}
{context.product && <li>{t('contactPage.contextProduct')}: {context.product}</li>}
</ul>
{packageTotal != null && (
<p className="mt-2 font-semibold">
{t('contactPage.packagePrice')}: {formatPrice(packageTotal)}
{upgradesTotal > 0 && <> + {formatPrice(upgradesTotal)} {t('contactPage.upgrades')} = {formatPrice(packageTotal + upgradesTotal)}</>}
</p>
)}
</div>
)}
{/* 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>
{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">
<p className="mb-3 text-sm font-semibold text-neutral-900 dark:text-white">{t('contactPage.optionalUpgrades')}</p>
<div className="space-y-2">
{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">
<input
type="checkbox"
checked={selectedUpgradeIds.includes(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"
/>
<span className="flex-1">
<span className="block font-medium text-neutral-800 dark:text-neutral-100">{u.name}</span>
{u.description && <span className="block text-neutral-500 dark:text-neutral-400">{u.description}</span>}
</span>
<span className="font-semibold text-brand-600 dark:text-brand-400">+{formatPrice(u.price)}</span>
</label>
))}
<div className="relative z-10 mx-auto max-w-[1280px] pt-10 sm:pt-14">
{/* Hero */}
<div
className="mb-3 text-[13px] font-semibold uppercase tracking-[6px]"
style={{ color: 'var(--kf-accent2)' }}
>
{t('contactPage.heroEyebrow')}
</div>
<h1
className="text-[34px] leading-[1.08] sm:text-[46px] lg:text-[60px]"
style={{ ...michroma, color: 'var(--kf-text)' }}
>
{t('contactPage.heroTitlePre')} <span style={{ color: 'var(--kf-accent2)' }}>{t('contactPage.heroTitleAccent')}</span> {t('contactPage.heroTitlePost')}
</h1>
<p className="mb-10 mt-5 max-w-[520px] text-lg leading-[1.55]" style={{ color: 'var(--kf-muted)' }}>
{t('contactPage.heroSubtitle')}
</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>
)}
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">{t('contactPage.nameLabel')}</label>
<input
{upgrades.length > 0 && (
<div
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">
{upgrades.map((u) => (
<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
type="checkbox"
checked={selectedUpgradeIds.includes(u.id)}
onChange={() => toggleUpgrade(u.id)}
className="mt-0.5 h-4 w-4 rounded"
style={{ accentColor: 'var(--kf-accent)' }}
/>
<span className="flex-1">
<span className="block font-semibold" style={{ color: 'var(--kf-text2)' }}>{u.name}</span>
{u.description && (
<span className="block text-xs" style={{ color: 'var(--kf-muted)' }}>{u.description}</span>
)}
</span>
<span className="font-semibold" style={{ color: 'var(--kf-accent2)' }}>+{formatPrice(u.price)}</span>
</label>
))}
</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>
)}
<form onSubmit={handleSubmit}>
<div className="grid grid-cols-1 gap-6 min-[641px]:grid-cols-2">
<Field
icon={User}
label={t('contactPage.nameLabel')}
name="name"
required
value={form.name}
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>
<div>
<label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">{t('contactPage.emailLabel')}</label>
<input
type="email"
<Field
icon={Mail}
label={t('contactPage.emailLabel')}
name="email"
type="email"
required
value={form.email}
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>
<div>
<label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">{t('contactPage.phoneLabel')}</label>
<input
<Field
icon={Phone}
label={t('contactPage.phoneLabel')}
name="phone"
value={form.phone}
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>
<div>
<label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">
{t('contactPage.vinLabel')}
</label>
<input
<Field
icon={Car}
label={t('contactPage.vinLabel')}
name="vin"
value={form.vin}
onChange={handleChange}
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>
<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
id="kf-message"
name="message"
rows={4}
rows={5}
value={form.message}
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>
{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>}
<button
type="submit"
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"
>
{status === 'sending' ? t('contactPage.sending') : t('contactPage.submit')}
</button>
<div className="mt-8 flex flex-wrap items-center gap-5">
<button
type="submit"
disabled={status === 'sending'}
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')}
</button>
<div className="text-[13px]" style={{ color: 'var(--kf-label)' }}>
{t('contactPage.requiredHint')}
</div>
</div>
</form>
</>
)}
</div>
<div className="md:col-span-2">
<div className="rounded-xl border border-neutral-200 bg-white p-6 dark:border-neutral-800 dark:bg-neutral-900">
<h2 className="mb-4 font-semibold text-neutral-900 dark:text-white">{t('contactPage.cardTitle')}</h2>
<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>
<dt className="font-medium text-neutral-800 dark:text-neutral-100">{t('contactPage.phone')}</dt>
<dd><a href={`tel:${digitsOnly(phone)}`} className="inline-block py-1 hover:text-brand-500">{phone}</a></dd>
</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>
<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">
<h2 className="mb-4 font-semibold text-neutral-900 dark:text-white">{t('contactPage.directionsTitle')}</h2>
<div className="overflow-hidden rounded-lg border border-neutral-200 dark:border-neutral-700">
<ExternalEmbed name={t('contactPage.mapEmbedName')} className="h-[220px] w-full">
{/* 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 className="mt-7 border-t pt-6" style={{ borderColor: 'var(--kf-divider)' }}>
<div className="mb-3 text-[11px] font-semibold uppercase tracking-[3px]" style={{ color: 'var(--kf-accent2)' }}>
{t('contactPage.hours')}
</div>
<div className="grid grid-cols-[auto_1fr] gap-x-6 gap-y-2 text-[15px]">
<span style={{ color: 'var(--kf-label)' }}>{t('contactPage.hoursWeekDays')}</span>
<span>{t('contactPage.hoursWeekTime')}</span>
<span style={{ color: 'var(--kf-label)' }}>{t('contactPage.hoursSatDay')}</span>
<span>{t('contactPage.hoursSatTime')}</span>
</div>
</div>
</div>
<div
className="relative overflow-hidden rounded-2xl border"
style={{ background: 'var(--kf-card)', borderColor: 'var(--kf-cardbrd)', boxShadow: 'var(--kf-shadow)' }}
>
{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
title="HifiPlanet Amorbach Standort"
src={`https://www.google.com/maps?q=${SHOP_ADDRESS_ENCODED}&output=embed`}
width="100%"
height="220"
style={{ border: 0 }}
height="210"
style={{ border: 0, display: 'block' }}
loading="lazy"
referrerPolicy="no-referrer-when-downgrade"
/>
</ExternalEmbed>
<a
href={DIRECTIONS_URL}
target="_blank"
rel="noopener noreferrer"
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')}
</a>
</div>
<a
href={`https://www.google.com/maps/dir/?api=1&destination=${SHOP_ADDRESS_ENCODED}`}
target="_blank"
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"
>
{t('contactPage.routePlan')}
</a>
</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>
);

View file

@ -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);">MoFr</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="{&quot;defaultDark&quot;:{&quot;editor&quot;:&quot;boolean&quot;,&quot;default&quot;:false,&quot;tsType&quot;:&quot;boolean&quot;,&quot;section&quot;:&quot;Darstellung&quot;},&quot;showFin&quot;:{&quot;editor&quot;:&quot;boolean&quot;,&quot;default&quot;:true,&quot;tsType&quot;:&quot;boolean&quot;,&quot;section&quot;:&quot;Formular&quot;},&quot;showWhatsapp&quot;:{&quot;editor&quot;:&quot;boolean&quot;,&quot;default&quot;:true,&quot;tsType&quot;:&quot;boolean&quot;,&quot;section&quot;:&quot;Kontaktdaten&quot;},&quot;pkgBrand&quot;:{&quot;editor&quot;:&quot;text&quot;,&quot;default&quot;:&quot;&quot;,&quot;tsType&quot;:&quot;string&quot;,&quot;section&quot;:&quot;Paket (leer = ausgeblendet)&quot;},&quot;pkgModel&quot;:{&quot;editor&quot;:&quot;text&quot;,&quot;default&quot;:&quot;&quot;,&quot;tsType&quot;:&quot;string&quot;,&quot;section&quot;:&quot;Paket (leer = ausgeblendet)&quot;},&quot;pkgName&quot;:{&quot;editor&quot;:&quot;text&quot;,&quot;default&quot;:&quot;&quot;,&quot;tsType&quot;:&quot;string&quot;,&quot;section&quot;:&quot;Paket (leer = ausgeblendet)&quot;},&quot;pkgPrice&quot;:{&quot;editor&quot;:&quot;text&quot;,&quot;default&quot;:&quot;&quot;,&quot;tsType&quot;:&quot;string&quot;,&quot;section&quot;:&quot;Paket (leer = ausgeblendet)&quot;}}">
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>

View file

@ -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 — „MoFr / 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 400700 (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