Relaunch Phase 1: Hero mit Deutschland-Karte, Kundenportal-Bühne, Leistungen, Preise, Unterseiten im neuen Layout

- Hero: wechselnde Überschrift (ein Durchlauf, Pause-Knopf), Karte zeichnet
  sich, roter Punkt auf Falkenstein
- Kundenportal: Bühne mit Browserfenster und Handy-Rahmen (Desktop),
  Wisch-Reihe (Handy), Schrittzeile statt „So arbeiten wir“
- Leistungen als nummerierte Zeilen, Preise aus src/data/abos.ts
- Unterseiten ohne eigenen Kopfbereich, Wortlaut der Rechtstexte unverändert
- Tests für Hero, Kundenportal, Leistungen/Preise und Layout

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-10-01 15:28:38 +00:00
1 parent 69d006334f
commit 1c8cfa5c1c
54 files changed
+4761 -1166

No files matched your search

+131 -171
View File
@@ -1,23 +1,18 @@
import { useState } from "react";
import { Link } from "react-router-dom";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Label } from "@/components/ui/label";
import { ArrowLeft, Send } from "lucide-react";
import { useToast } from "@/hooks/use-toast";
import { createContactDocument } from "@/lib/appwrite";
import { trackFormSubmitted } from "@/lib/analytics";
import { useRef, useState } from "react";
import { usePageMeta } from "@/hooks/use-page-meta";
import "./unterseiten.css";
type Rueckmeldung = { art: "erfolg" | "fehler"; titel: string; text: string };
const Contact = () => {
usePageMeta(
"Kontakt & Potenzialanalyse",
"Beschreiben Sie Ihr Projekt – WEBklar meldet sich spätestens am nächsten Werktag mit den nächsten Schritten. Unverbindlich und kostenlos."
"Projekt besprechen",
"Beschreiben Sie Ihr Vorhaben in zwei Sätzen. WEBklar meldet sich spätestens am nächsten Werktag."
);
const { toast } = useToast();
const [isSubmitting, setIsSubmitting] = useState(false);
const [rueckmeldung, setRueckmeldung] = useState<Rueckmeldung | null>(null);
const statusRef = useRef<HTMLDivElement>(null);
const [formData, setFormData] = useState({
name: "",
email: "",
@@ -36,190 +31,155 @@ const Contact = () => {
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (isSubmitting) return;
setIsSubmitting(true);
setRueckmeldung(null);
try {
// Das Appwrite-SDK wird erst beim Absenden geladen (Spezifikation 9.7)
const { createContactDocument } = await import("@/lib/appwrite");
await createContactDocument(formData);
trackFormSubmitted("contact");
toast({
title: "Nachricht gesendet!",
description: "Wir melden uns spätestens am nächsten Werktag bei Ihnen.",
setRueckmeldung({
art: "erfolg",
titel: "Nachricht gesendet.",
text: "Wir melden uns spätestens am nächsten Werktag bei Ihnen.",
});
setFormData({ name: "", email: "", company: "", message: "" });
} catch (err) {
const message = err instanceof Error ? err.message : "Speichern fehlgeschlagen.";
toast({
variant: "destructive",
title: "Fehler beim Senden",
description: message,
} catch {
setRueckmeldung({
art: "fehler",
titel: "Die Nachricht konnte nicht gesendet werden.",
text: "Bitte versuchen Sie es noch einmal oder schreiben Sie uns direkt per E-Mail.",
});
} finally {
setIsSubmitting(false);
// Rückmeldung ins Bild holen, falls sie unter dem Bildschirmrand erscheint
requestAnimationFrame(() => statusRef.current?.scrollIntoView?.({ block: "nearest" }));
}
};
return (
<div className="min-h-screen bg-background">
{/* Header */}
<header className="fixed top-0 left-0 right-0 z-50 glass-nav py-4">
<div className="container mx-auto px-6">
<div className="flex items-center justify-between">
<Link to="/" className="flex items-center gap-2 group">
<span className="text-xl font-display font-medium text-foreground tracking-normal">
WEBklar
</span>
</Link>
<Link to="/">
<Button
variant="ghost"
className="text-muted-foreground hover:text-foreground"
>
<ArrowLeft className="w-4 h-4 mr-2" />
Zurück
</Button>
</Link>
</div>
<div className="wk-seite">
<div className="wk-container">
<div className="wk-seite__kopf">
<h1 className="wk-seite__titel wk-seite__titel--gross">Projekt besprechen</h1>
<p className="wk-seite__lead">
Zwei Sätze genügen. Wir melden uns spätestens am nächsten Werktag. Im ersten Gespräch klären wir
in&nbsp;15&nbsp;Minuten, ob und wie wir helfen können – kostenlos und unverbindlich.
</p>
</div>
</header>
{/* Main Content */}
<main className="pt-32 pb-24">
<div className="container mx-auto px-6">
<div className="max-w-2xl mx-auto">
{/* Header */}
<div className="mb-12">
<div className="label-tag mb-4">Kontakt</div>
<h1 className="text-4xl md:text-5xl lg:text-6xl font-display font-medium text-foreground tracking-tight uppercase mb-6">
Kostenlose
<br />
<span className="text-muted-foreground">Potenzialanalyse</span>
</h1>
<p className="text-muted-foreground text-lg">
Kurz Ihr Ziel beschreiben – wir prüfen, wo Website, Automatisierung
oder Vernetzung den größten Hebel haben. Antwort spätestens am nächsten Werktag,
unverbindlich.
</p>
<div className="wk-kontakt-seite__raster">
<form onSubmit={handleSubmit} className="wk-form" aria-labelledby="kontakt-formular-titel">
<h2 id="kontakt-formular-titel" className="wk-visually-hidden">
Kontaktformular
</h2>
<div className="wk-form__paar">
<div className="wk-feld">
<label htmlFor="name">Name</label>
<input
id="name"
name="name"
type="text"
required
autoComplete="name"
value={formData.name}
onChange={handleChange}
placeholder="Ihr Name"
/>
</div>
<div className="wk-feld">
<label htmlFor="email">E-Mail</label>
<input
id="email"
name="email"
type="email"
required
autoComplete="email"
value={formData.email}
onChange={handleChange}
placeholder="ihre@email.de"
/>
</div>
</div>
{/* Contact Form */}
<form onSubmit={handleSubmit} className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<Label htmlFor="name" className="text-sm uppercase tracking-wider text-muted-foreground">
Name *
</Label>
<Input
id="name"
name="name"
type="text"
required
value={formData.name}
onChange={handleChange}
className="bg-card border-border focus:border-foreground transition-colors h-12"
placeholder="Ihr Name"
/>
</div>
<div className="space-y-2">
<Label htmlFor="email" className="text-sm uppercase tracking-wider text-muted-foreground">
E-Mail *
</Label>
<Input
id="email"
name="email"
type="email"
required
value={formData.email}
onChange={handleChange}
className="bg-card border-border focus:border-foreground transition-colors h-12"
placeholder="ihre@email.de"
/>
</div>
</div>
<div className="wk-feld">
<label htmlFor="company">
Unternehmen <span className="wk-feld__optional">(optional)</span>
</label>
<input
id="company"
name="company"
type="text"
autoComplete="organization"
value={formData.company}
onChange={handleChange}
placeholder="Name Ihres Betriebs"
/>
</div>
<div className="space-y-2">
<Label htmlFor="company" className="text-sm uppercase tracking-wider text-muted-foreground">
Unternehmen
</Label>
<Input
id="company"
name="company"
type="text"
value={formData.company}
onChange={handleChange}
className="bg-card border-border focus:border-foreground transition-colors h-12"
placeholder="Ihr Unternehmen (optional)"
/>
</div>
<div className="wk-feld">
<label htmlFor="message">Nachricht</label>
<textarea
id="message"
name="message"
required
value={formData.message}
onChange={handleChange}
placeholder="z. B. neue Website, Online-Shop, Web-App oder Automatisierung …"
/>
</div>
<div className="space-y-2">
<Label htmlFor="message" className="text-sm uppercase tracking-wider text-muted-foreground">
Nachricht *
</Label>
<Textarea
id="message"
name="message"
required
value={formData.message}
onChange={handleChange}
className="bg-card border-border focus:border-foreground transition-colors min-h-[180px] resize-none"
placeholder="z. B. neue Website, CRM-Anbindung, weniger manuelle Abläufe …"
/>
</div>
<div className="wk-form__senden">
<button type="submit" className="wk-btn wk-btn--primary" aria-disabled={isSubmitting}>
{isSubmitting ? "Wird gesendet …" : "Nachricht senden"}
</button>
</div>
<Button
type="submit"
disabled={isSubmitting}
className="btn-minimal rounded-full px-8 py-6 text-base font-medium group w-full md:w-auto"
{/* Rückmeldung für Screenreader: der Bereich steht immer im Dokument, nur der Inhalt wechselt */}
<div ref={statusRef} className="wk-form__status" aria-live="polite" aria-atomic="true" data-art={rueckmeldung?.art}>
{rueckmeldung && (
<>
<strong>{rueckmeldung.titel}</strong>
<span>{rueckmeldung.text}</span>
</>
)}
</div>
<p className="wk-form__datenschutz wk-muted wk-small">
Wir verwenden Ihre Angaben nur, um Ihre Anfrage zu beantworten. Mehr dazu in unserer{" "}
<a
href="https://project.webklar.com/datenschutz.html"
target="_blank"
rel="noopener noreferrer"
>
{isSubmitting ? (
"Wird gesendet..."
) : (
<>
Nachricht senden
<Send className="w-4 h-4 ml-2 group-hover:translate-x-1 transition-transform" />
</>
)}
</Button>
Datenschutzerklärung
</a>
.
</p>
</form>
<p className="text-sm leading-relaxed text-muted-foreground">
Wir verwenden Ihre Angaben nur, um Ihre Anfrage zu beantworten. Mehr dazu in unserer{" "}
<a
href="https://project.webklar.com/datenschutz.html"
target="_blank"
rel="noopener noreferrer"
className="text-foreground underline underline-offset-4 hover:no-underline"
>
Datenschutzerklärung
</a>
.
</p>
</form>
{/* Contact Info */}
<div className="mt-16 pt-12 border-t border-border">
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<div>
<div className="label-tag mb-2">E-Mail</div>
<a
href="mailto:support@webklar.com"
className="text-foreground hover:text-muted-foreground transition-colors"
>
support@webklar.com
</a>
</div>
<div>
<div className="label-tag mb-2">Telefon</div>
<a
href="tel:+4917623726355"
className="text-foreground hover:text-muted-foreground transition-colors"
>
<div className="wk-direkt">
<h2>Direkt erreichen</h2>
<dl>
<div>
<dt>E-Mail</dt>
<dd>
<a href="mailto:support@webklar.com">support@webklar.com</a>
</dd>
</div>
<div>
<dt>Telefon</dt>
<dd>
<a href="tel:+4917623726355" className="wk-num">
0176 23726355
</a>
</div>
</dd>
</div>
</div>
</dl>
</div>
</div>
</main>
</div>
</div>
);
};