Professionalize landing page design

This commit is contained in:
2026-05-05 22:20:09 +02:00
parent add89b0f92
commit 117839058b
13 changed files with 628 additions and 576 deletions

14
backlog/config.yml Normal file
View File

@@ -0,0 +1,14 @@
project_name: "Dev Landing"
default_status: "To Do"
statuses: ["To Do", "In Progress", "Done"]
labels: []
date_format: yyyy-mm-dd
max_column_width: 20
auto_open_browser: true
default_port: 6420
remote_operations: false
auto_commit: false
bypass_git_hooks: false
check_active_branches: false
active_branch_days: 30
task_prefix: "task"

View File

@@ -0,0 +1,42 @@
---
id: TASK-1
title: Professionalize landing page design
status: In Progress
assignee: []
created_date: '2026-05-05 19:55'
updated_date: '2026-05-05 20:13'
labels: []
dependencies: []
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Refresh the existing landing page so it feels more professional while preserving the current regional KMU positioning and current content structure.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Visual hierarchy is clearer across the first viewport and content sections
- [x] #2 Styling feels cohesive and professional without generic AI visual patterns
- [x] #3 Responsive layout remains usable on mobile and desktop
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Audit current structure and design context
2. Tighten theme tokens and page rhythm
3. Refresh hero/trust/features/pricing/contact styling
4. Build and visual-check responsive behavior
5. Mark acceptance criteria verified, leave task In Progress for user confirmation
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Backlog was missing in the repo, so I initialized it with CLI defaults and disabled remote branch checks after sandbox git fetch failed. Design context exists in .impeccable.md: regional KMU, calm/direct/human, warm light theme.
Implemented the professionalization pass: warm OKLCH theme, full-width page shell, redesigned hero with real workspace image, calmer trust/features/stats sections, clearer pricing tabs, refined FAQ/about/contact/footer. Verified with pnpm build, git diff --check, and in-app browser checks for hero visibility and pricing tab interaction. Dev-server hydration errors in browser logs were from the initial Vite dependency optimization at 20:10:49 and did not recur after reload.
<!-- SECTION:NOTES:END -->

View File

@@ -7,32 +7,48 @@ interface About19Props {
const About19 = ({ className }: About19Props) => { const About19 = ({ className }: About19Props) => {
return ( return (
<section className={cn("py-32", className)}> <section className={cn("px-4 py-20 sm:px-6 lg:px-8 lg:py-28", className)}>
<div className="container"> <div className="mx-auto max-w-6xl">
<div className="grid grid-cols-1 gap-15 lg:grid-cols-7 lg:gap-1"> <div className="grid gap-10 lg:grid-cols-[minmax(0,1fr)_minmax(0,0.88fr)] lg:items-start lg:gap-14">
<div className="col-span-4 h-120"> <div className="overflow-hidden rounded-lg border border-border bg-card">
<img <img
src="/about.jpg" src="/about.jpg"
alt="" alt="Matthias Meister bei der Webentwicklung"
className="h-full w-full object-cover rounded-xl shadow-md" className="h-[20rem] w-full object-cover sm:h-[28rem] lg:h-[34rem]"
/> />
</div> </div>
<div className="col-span-3 ml-auto max-w-4xl space-y-15 lg:pl-15"> <div className="max-w-xl">
<h1 className="text-2xl font-medium tracking-tight"> <p className="text-xs font-semibold uppercase tracking-[0.16em] text-primary">
Hallo, ich bin Matthias. Über die Zusammenarbeit
</h1>
<p className="text-base text-foreground/40 lg:text-lg">
Ich bin in der Region aufgewachsen, war durch die Bundeswehr viele Jahre weg und bin jetzt zurück. Und ich plane zu bleiben.
</p> </p>
<p className="text-base text-foreground/40 lg:text-lg"> <h2 className="mt-4 text-3xl font-semibold tracking-tight text-balance sm:text-4xl">
Seit über 15 Jahren beschäftige ich mich mit Webentwicklung und Software. Einen Großteil davon intern für die Bundeswehr Projekte die ich Ihnen leider nicht zeigen kann. Was ich Ihnen zeigen kann: Wie ich arbeite. Zuverlässig, präzise und ohne unnötigen Schnickschnack. Hallo, ich bin Matthias. Zurück in der Region und hier, um zu
</p> bleiben.
<p className="text-base text-foreground/40 lg:text-lg"> </h2>
Neben Websites für regionale Unternehmen entwickle ich eigene Software und Apps. Das bedeutet: Wenn Ihre Anforderungen irgendwann über eine einfache Website hinausgehen, bin ich noch immer der richtige Ansprechpartner. <div className="mt-8 space-y-5 text-base leading-8 text-muted-foreground">
</p> <p>
<p className="text-base text-foreground/40 lg:text-lg"> Ich bin in der Region aufgewachsen, war durch die Bundeswehr
Mein Ziel ist es, Unternehmen aus der Region Handwerker, Friseure, Ärzte mit dem auszustatten, was Großstadtagenturen ihren Kunden für viel mehr Geld verkaufen. Eine Website die funktioniert, gefunden wird und Ihnen keine Kopfschmerzen macht. viele Jahre weg und bin jetzt zurück.
</p>
<p>
Seit über 15 Jahren beschäftige ich mich mit Webentwicklung und
Software. Einen Großteil davon intern für die Bundeswehr:
Projekte, die ich Ihnen leider nicht zeigen kann. Was ich Ihnen
zeigen kann: wie ich arbeite. Zuverlässig, präzise und ohne
unnötigen Schnickschnack.
</p>
<p>
Neben Websites für regionale Unternehmen entwickle ich eigene
Software und Apps. Wenn Ihre Anforderungen irgendwann über eine
einfache Website hinausgehen, bleibt der Ansprechpartner also
derselbe.
</p>
</div>
<p className="mt-8 rounded-lg border border-border bg-card p-5 text-base font-medium leading-7 text-foreground">
Mein Ziel: Unternehmen aus der Region mit einer Website
ausstatten, die funktioniert, gefunden wird und Ihnen keine
Kopfschmerzen macht.
</p> </p>
</div> </div>
</div> </div>

View File

@@ -21,7 +21,7 @@ const contactFormSchema = z.object({
email: z email: z
.string() .string()
.min(1, "Bitte geben Sie Ihre E-Mail ein") .min(1, "Bitte geben Sie Ihre E-Mail ein")
.email("Bitte geben Sie eine gueltige E-Mail ein"), .email("Bitte geben Sie eine gültige E-Mail ein"),
message: z.string().min(1, "Bitte beschreiben Sie kurz Ihr Anliegen"), message: z.string().min(1, "Bitte beschreiben Sie kurz Ihr Anliegen"),
}); });
@@ -68,19 +68,29 @@ const Contact21 = ({ className, onSubmit }: Contact21Props) => {
}; };
return ( return (
<section id="kontakt" className={cn("py-32", className)}> <section
<div className="container"> id="kontakt"
<div className="mt-20 flex flex-col justify-between gap-15 md:gap-10 lg:flex-row"> className={cn("px-4 py-20 sm:px-6 lg:px-8 lg:py-28", className)}
>
<div className="mx-auto max-w-6xl">
<div className="grid gap-10 rounded-lg border border-border bg-card p-5 sm:p-8 lg:grid-cols-[minmax(0,0.82fr)_minmax(0,1.18fr)] lg:gap-14 lg:p-10">
<div className="flex w-full max-w-lg flex-col justify-between gap-10"> <div className="flex w-full max-w-lg flex-col justify-between gap-10">
<p className="indent-[22%] text-3xl font-medium tracking-tight text-muted-foreground/50 lg:text-4xl"> <div>
Erzählen Sie mir kurz von Ihrem Unternehmen ich melde mich innerhalb von 24 Stunden mit einem unverbindlichen Angebot. <p className="text-xs font-semibold uppercase tracking-[0.16em] text-primary">
</p> Kontakt
<div className="mt-5 flex items-center gap-4 lg:mt-20"> </p>
<img <h2 className="mt-4 text-3xl font-semibold tracking-tight text-balance sm:text-4xl">
src="https://deifkwefumgah.cloudfront.net/shadcnblocks/block/guri3/avatar3.png" Erzählen Sie kurz, worum es geht.
className="size-12" </h2>
alt="Matthias Meister" <p className="mt-5 text-base leading-7 text-muted-foreground">
/> Ich melde mich innerhalb von 24 Stunden mit einer ersten
Einschätzung und dem passenden nächsten Schritt.
</p>
</div>
<div className="flex items-center gap-4">
<div className="flex size-12 items-center justify-center rounded-full bg-primary text-sm font-semibold text-primary-foreground">
MM
</div>
<div> <div>
<h3 className="text-lg font-medium tracking-tight"> <h3 className="text-lg font-medium tracking-tight">
Matthias Meister Matthias Meister
@@ -91,26 +101,22 @@ const Contact21 = ({ className, onSubmit }: Contact21Props) => {
</div> </div>
</div> </div>
</div> </div>
<div className="col-span-4 flex w-full flex-col gap-2 lg:pl-10"> <div className="w-full">
<h2 className="mb-7 text-6xl font-semibold tracking-tight lg:text-5xl"> {isSubmitted && (
Jetzt Website anfordern <div
</h2> className={cn(
"mb-4 rounded-lg border border-primary/20 bg-primary/10 p-4 text-center transition-opacity duration-500",
{isSubmitted && ( showSuccess ? "opacity-100" : "opacity-0",
<div
className={cn(
"mb-4 rounded-lg border border-green-500/20 bg-green-500/10 p-4 text-center transition-opacity duration-500",
showSuccess ? "opacity-100" : "opacity-0",
)}
>
<p className="text-sm font-medium text-green-600 dark:text-green-400">
Vielen Dank! Ich melde mich in Kürze bei Ihnen.
</p>
</div>
)} )}
>
<p className="text-sm font-medium text-primary">
Vielen Dank! Ich melde mich in Kürze bei Ihnen.
</p>
</div>
)}
<form onSubmit={form.handleSubmit(handleFormSubmit)}> <form onSubmit={form.handleSubmit(handleFormSubmit)}>
<FieldGroup className="gap-0"> <FieldGroup className="gap-0">
<Controller <Controller
control={form.control} control={form.control}
name="name" name="name"
@@ -124,7 +130,7 @@ const Contact21 = ({ className, onSubmit }: Contact21Props) => {
id={field.name} id={field.name}
aria-invalid={fieldState.invalid} aria-invalid={fieldState.invalid}
placeholder="Ihr Name*" placeholder="Ihr Name*"
className="h-15 rounded-none border-0 border-b border-b-foreground/25 bg-transparent! shadow-none placeholder:text-foreground/20 focus-visible:ring-0 lg:text-base" className="h-14 rounded-none border-0 border-b border-b-border bg-transparent! px-0 shadow-none placeholder:text-muted-foreground/65 focus-visible:border-b-primary focus-visible:ring-0 lg:text-base"
/> />
{fieldState.invalid && ( {fieldState.invalid && (
<FieldError errors={[fieldState.error]} /> <FieldError errors={[fieldState.error]} />
@@ -147,7 +153,7 @@ const Contact21 = ({ className, onSubmit }: Contact21Props) => {
type="email" type="email"
aria-invalid={fieldState.invalid} aria-invalid={fieldState.invalid}
placeholder="Ihre E-Mail*" placeholder="Ihre E-Mail*"
className="h-15 rounded-none border-0 border-b border-b-foreground/25 bg-transparent! shadow-none placeholder:text-foreground/20 focus-visible:ring-0 lg:text-base" className="h-14 rounded-none border-0 border-b border-b-border bg-transparent! px-0 shadow-none placeholder:text-muted-foreground/65 focus-visible:border-b-primary focus-visible:ring-0 lg:text-base"
/> />
{fieldState.invalid && ( {fieldState.invalid && (
<FieldError errors={[fieldState.error]} /> <FieldError errors={[fieldState.error]} />
@@ -170,7 +176,7 @@ const Contact21 = ({ className, onSubmit }: Contact21Props) => {
aria-invalid={fieldState.invalid} aria-invalid={fieldState.invalid}
placeholder="Nachricht: Worum geht es bei Ihrem Projekt?" placeholder="Nachricht: Worum geht es bei Ihrem Projekt?"
rows={4} rows={4}
className="min-h-32 w-full rounded-none border-0 border-b border-b-foreground/25 bg-transparent px-0 py-3 text-base text-foreground shadow-none outline-none placeholder:text-foreground/20 focus-visible:border-ring focus-visible:ring-0 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive lg:text-base" className="min-h-36 w-full rounded-none border-0 border-b border-b-border bg-transparent px-0 py-4 text-base text-foreground shadow-none outline-none placeholder:text-muted-foreground/65 focus-visible:border-b-primary focus-visible:ring-0 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive lg:text-base"
/> />
{fieldState.invalid && ( {fieldState.invalid && (
<FieldError errors={[fieldState.error]} /> <FieldError errors={[fieldState.error]} />
@@ -186,7 +192,7 @@ const Contact21 = ({ className, onSubmit }: Contact21Props) => {
)} )}
<Button <Button
className="mt-15 flex items-center justify-start gap-2 rounded-none px-8! lg:h-12 lg:text-base" className="mt-8 flex h-11 w-full items-center justify-center gap-2 rounded-md px-6 lg:w-fit lg:text-base"
disabled={form.formState.isSubmitting} disabled={form.formState.isSubmitting}
> >
{form.formState.isSubmitting ? ( {form.formState.isSubmitting ? (

View File

@@ -23,19 +23,18 @@ const trustAnchors = [
export default function CTASection() { export default function CTASection() {
return ( return (
<section className="px-4 pb-16 pt-4 sm:px-6 lg:px-8 lg:pb-24"> <section className="px-4 pb-14 sm:px-6 lg:px-8 lg:pb-20">
<div className="mx-auto max-w-6xl border-y border-border/80 py-8 lg:grid lg:grid-cols-[minmax(0,0.95fr)_minmax(0,1.45fr)] lg:gap-12 lg:py-10"> <div className="mx-auto max-w-6xl border-y border-border/80 py-9 lg:grid lg:grid-cols-[minmax(0,0.82fr)_minmax(0,1.55fr)] lg:gap-14 lg:py-11">
<div className="max-w-md space-y-4"> <div className="max-w-md space-y-4 lg:pt-1">
<p className="text-xs font-medium uppercase tracking-[0.18em] text-muted-foreground"> <p className="text-xs font-medium uppercase tracking-[0.18em] text-muted-foreground">
Vertrauensanker Vor dem Angebot
</p> </p>
<h2 className="text-2xl font-semibold tracking-tight text-balance lg:text-3xl"> <h2 className="text-2xl font-semibold tracking-tight text-balance lg:text-3xl">
Ein gemeinsamer Startpunkt statt leerer Versprechen. Erst verstehen, dann bauen.
</h2> </h2>
<p className="text-base leading-7 text-muted-foreground"> <p className="text-base leading-7 text-muted-foreground">
Noch bevor es um Pakete oder Features geht, soll direkt klar sein, Die Zusammenarbeit ist bewusst direkt gehalten: ein Gespräch, eine
warum diese Zusammenarbeit für regionale Unternehmen greifbar und klare Empfehlung und ein Vorschlag, der zu Ihrem Betrieb passt.
verlaesslich wirkt.
</p> </p>
</div> </div>
<dl className="mt-8 grid gap-6 sm:grid-cols-3 lg:mt-0 lg:gap-0"> <dl className="mt-8 grid gap-6 sm:grid-cols-3 lg:mt-0 lg:gap-0">
@@ -46,14 +45,14 @@ export default function CTASection() {
"space-y-3", "space-y-3",
index === 0 index === 0
? "" ? ""
: "border-t border-border/70 pt-4 sm:border-t-0 sm:border-l sm:pl-6 sm:pt-0 lg:pl-8", : "border-t border-border/70 pt-5 sm:border-t-0 sm:border-l sm:pl-6 sm:pt-0 lg:pl-8",
)} )}
> >
<dt className="text-sm font-medium text-foreground"> <dt className="text-sm font-semibold text-foreground">
{item.title} {item.title}
</dt> </dt>
<dd className="space-y-2"> <dd className="space-y-2">
<p className="text-lg font-semibold leading-7 text-balance text-foreground"> <p className="text-base font-medium leading-7 text-balance text-foreground lg:text-lg">
{item.description} {item.description}
</p> </p>
<p className="text-sm leading-6 text-muted-foreground"> <p className="text-sm leading-6 text-muted-foreground">

View File

@@ -41,35 +41,39 @@ interface Faq7Props {
const Faq7 = ({ className }: Faq7Props) => { const Faq7 = ({ className }: Faq7Props) => {
return ( return (
<section className={cn("py-32", className)}> <section className={cn("px-4 py-20 sm:px-6 lg:px-8 lg:py-28", className)}>
<div className="container"> <div className="mx-auto max-w-6xl">
<div className="mx-auto grid max-w-7xl gap-10 md:grid-cols-2"> <div className="grid gap-10 border-t border-border/80 pt-10 md:grid-cols-[minmax(0,0.85fr)_minmax(0,1.15fr)] lg:gap-16">
<div className="flex flex-col gap-6"> <div className="flex max-w-md flex-col gap-6">
<h2 className="text-4xl font-semibold"> <p className="text-xs font-semibold uppercase tracking-[0.16em] text-primary">
Fragen vor dem Start? Häufige Fragen
<br /> </p>
<span className="text-muted-foreground/70"> <h2 className="text-3xl font-semibold tracking-tight text-balance sm:text-4xl">
Hier finden Sie schnelle Antworten. Vor dem Start soll nichts schwammig bleiben.
</span>
</h2> </h2>
<p className="text-lg text-muted-foreground md:text-xl"> <p className="text-base leading-7 text-muted-foreground">
Falls noch etwas offen ist, schreiben Sie mir gern ueber das Falls noch etwas offen ist, schreiben Sie mir gern über das
<a href="#" className="mx-1 whitespace-nowrap underline"> <a
href="#kontakt"
className="mx-1 whitespace-nowrap underline underline-offset-4 transition-colors hover:text-foreground"
>
Kontaktformular Kontaktformular
</a> </a>
. .
</p> </p>
<Button size="lg" variant="outline" className="w-fit"> <Button asChild size="lg" variant="outline" className="w-fit rounded-md">
Alle Fragen ansehen <a href="#kontakt">Frage stellen</a>
</Button> </Button>
</div> </div>
<Accordion type="multiple"> <Accordion type="multiple" className="rounded-lg border border-border bg-card px-4">
{faqs.map((faq, index) => ( {faqs.map((faq, index) => (
<AccordionItem key={index} value={`item-${index}`}> <AccordionItem key={index} value={`item-${index}`}>
<AccordionTrigger className="text-left"> <AccordionTrigger className="text-left text-base font-semibold">
{faq.question} {faq.question}
</AccordionTrigger> </AccordionTrigger>
<AccordionContent>{faq.answer}</AccordionContent> <AccordionContent className="text-muted-foreground">
{faq.answer}
</AccordionContent>
</AccordionItem> </AccordionItem>
))} ))}
</Accordion> </Accordion>

View File

@@ -1,44 +1,43 @@
import { HelpCircleIcon } from "lucide-react"; import {
import React from "react"; Gauge,
Handshake,
MapPinned,
Search,
Smartphone,
} from "lucide-react";
import { GlowingEffect } from "@/components/ui/glowing-effect";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
const featureData = [ const featureData = [
{ {
desc: "Ihre Website erklaert in wenigen Sekunden, fuer wen Sie arbeiten und was Sie konkret anbieten.", desc: "Die Startseite sagt schnell, für wen Sie arbeiten, was Sie anbieten und wie Interessenten Kontakt aufnehmen.",
img: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/guri3/img1.jpeg",
title: "Klare Positionierung", title: "Klare Positionierung",
badgeTitle: "Vorteil 01", badgeTitle: "01",
gridClass: "md:col-span-1", icon: MapPinned,
}, },
{ {
desc: "Ein zeitgemaesses Design sorgt fuer einen starken ersten Eindruck und passt zu Ihrem Unternehmen.", desc: "Gestaltung, Texte und Struktur wirken seriös, ohne den Charakter eines regionalen Betriebs glattzubügeln.",
img: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/guri3/img7.jpeg", title: "Glaubwürdiger Auftritt",
title: "Modernes Erscheinungsbild", badgeTitle: "02",
badgeTitle: "Vorteil 02", icon: Handshake,
gridClass: "lg:col-span-2",
}, },
{ {
desc: "Ihre Inhalte funktionieren sauber auf Smartphone, Tablet und Desktop - ohne Umwege fuer Besucher.", desc: "Telefonnummer, Formular und zentrale Informationen bleiben auf Smartphone und Desktop leicht erreichbar.",
img: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/guri3/img11.jpeg", title: "Mobil sauber geführt",
title: "Mobil optimiert", badgeTitle: "03",
badgeTitle: "Vorteil 03", icon: Smartphone,
gridClass: "md:col-span-1 lg:row-span-2 ",
}, },
{ {
desc: "Klare Kontaktwege mit gut sichtbaren Handlungsaufforderungen machen den naechsten Schritt leicht.", desc: "Technik, Bilder und Inhalte werden so umgesetzt, dass die Seite schnell lädt und stabil bleibt.",
img: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/guri3/img2.jpeg", title: "Schnell und robust",
title: "Anfragen ohne Huerden", badgeTitle: "04",
badgeTitle: "Vorteil 04", icon: Gauge,
gridClass: "lg:col-span-2",
}, },
{ {
desc: "Die Seite bleibt wartbar aufgebaut, damit Inhalte spaeter schnell angepasst oder erweitert werden koennen.", desc: "Google findet die richtigen Inhalte: Leistungen, Region, Kontakt und die wichtigsten Suchbegriffe.",
img: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/guri3/img4.jpeg", title: "Für Suche vorbereitet",
title: "Pflegeleicht aufgebaut", badgeTitle: "05",
badgeTitle: "Vorteil 05", icon: Search,
gridClass: "md:col-span-1",
}, },
]; ];
@@ -48,45 +47,43 @@ interface Feature284Props {
const Feature284 = ({ className }: Feature284Props) => { const Feature284 = ({ className }: Feature284Props) => {
return ( return (
<section className={cn("h-full overflow-hidden py-32", className)}> <section className={cn("px-4 py-20 sm:px-6 lg:px-8 lg:py-28", className)}>
<div className="container flex h-full w-full items-center justify-center"> <div className="mx-auto max-w-6xl">
<div className="grid w-full max-w-6xl grid-cols-1 grid-rows-2 gap-4 md:grid-cols-2 lg:h-[800px] lg:grid-cols-4"> <div className="grid gap-8 border-t border-border/80 pt-10 lg:grid-cols-[minmax(0,0.8fr)_minmax(0,1.4fr)] lg:gap-16">
<div className="max-w-md">
<p className="text-xs font-semibold uppercase tracking-[0.16em] text-primary">
Was die Seite leisten muss
</p>
<h2 className="mt-4 text-3xl font-semibold tracking-tight text-balance sm:text-4xl">
Professionell heißt hier: verständlich, erreichbar, belastbar.
</h2>
</div>
<div className="grid gap-3 sm:grid-cols-2">
{featureData.map((feature, index) => ( {featureData.map((feature, index) => (
<div <div
key={index} key={index}
className={cn( className="group flex min-h-52 flex-col justify-between rounded-lg border border-border bg-card p-5 transition-colors hover:border-primary/40"
"relative flex flex-col gap-2 rounded-3xl border p-4",
feature.gridClass,
)}
> >
<GlowingEffect <div className="flex items-center justify-between">
spread={40} <p className="text-xs font-semibold uppercase tracking-[0.16em] text-muted-foreground">
glow={true} {feature.badgeTitle}
disabled={false} </p>
proximity={64} <feature.icon
inactiveZone={0.01} className="size-5 text-primary transition-transform group-hover:-translate-y-0.5"
/> aria-hidden
<div className="flex w-full items-center justify-between">
<p className="text-muted-foreground">{feature.badgeTitle}</p>
<HelpCircleIcon className="size-4 text-muted-foreground" />
</div>
<div
className={cn(
"w-full flex-1 overflow-hidden rounded-3xl bg-muted",
)}
>
<img
src={feature.img}
alt={feature.title}
className="pointer-events-none h-full w-full object-cover"
/> />
</div> </div>
<h3 className="mt-4 text-2xl font-semibold tracking-tight"> <div className="mt-10 space-y-3">
{feature.title} <h3 className="text-xl font-semibold tracking-tight">
</h3> {feature.title}
<p className="text-muted-foreground">{feature.desc}</p> </h3>
<p className="text-sm leading-6 text-muted-foreground">
{feature.desc}
</p>
</div>
</div> </div>
))} ))}
</div>
</div> </div>
</div> </div>
</section> </section>

View File

@@ -1,10 +1,6 @@
"use client"; import { ArrowRight, Mail, Phone } from "lucide-react";
import { motion } from "framer-motion";
import { ArrowUpRight } from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
interface Footer27Props { interface Footer27Props {
@@ -12,137 +8,65 @@ interface Footer27Props {
} }
const Footer27 = ({ className }: Footer27Props) => { const Footer27 = ({ className }: Footer27Props) => {
const socialLinks = [
{ name: "E-Mail", href: "#" },
{ name: "LinkedIn", href: "#" },
];
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
duration: 0.6,
staggerChildren: 0.1,
},
},
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.5 },
},
};
return ( return (
<section className={cn("py-32", className)}> <footer className={cn("px-4 pb-10 sm:px-6 lg:px-8", className)}>
<div className="container"> <div className="mx-auto max-w-6xl border-t border-border/80 pt-8">
<footer> <div className="grid gap-8 lg:grid-cols-[minmax(0,1fr)_auto] lg:items-start">
<div> <div>
<motion.div <h2 className="max-w-2xl text-3xl font-semibold tracking-tight text-balance sm:text-4xl">
variants={containerVariants} Bereit für eine Website, die Ihr Unternehmen klarer erklärt?
initial="hidden" </h2>
whileInView="visible" <p className="mt-4 max-w-xl text-base leading-7 text-muted-foreground">
viewport={{ once: true }} Eine kurze Nachricht reicht. Ich prüfe, welcher Weg sinnvoll ist,
className="flex flex-col justify-between md:flex-row md:items-center" und melde mich mit einer ehrlichen Einschätzung.
> </p>
<div className="space-y-8"> <Button asChild size="lg" className="mt-6 h-11 rounded-md px-5">
<motion.div variants={itemVariants} className="space-y-6"> <a href="#kontakt">
<h2 className="text-4xl leading-tight font-bold text-foreground lg:text-5xl"> Kostenloses Angebot anfordern
Bereit für eine Website, die Kunden bringt? <ArrowRight className="shrink-0" aria-hidden />
</h2> </a>
<p className="max-w-md text-lg leading-relaxed text-muted-foreground"> </Button>
Erzählen Sie mir kurz von Ihrem Unternehmen ich melde mich innerhalb von 24 Stunden mit einem unverbindlichen Angebot.
</p>
</motion.div>
<motion.div variants={itemVariants}>
<Button size="lg">Kostenloses Angebot anfordern</Button>
</motion.div>
</div>
<div className="mt-5 space-y-8 md:mt-0">
<motion.div variants={itemVariants}>
<div className="space-y-6">
{socialLinks.map((link) => (
<motion.div
key={link.name}
variants={itemVariants}
whileHover={{ x: 4 }}
transition={{
type: "spring",
stiffness: 300,
damping: 20,
}}
>
<a
href={link.href}
className="group flex items-center gap-2 py-2 text-foreground transition-colors hover:text-foreground/80"
>
<span className="text-xl font-medium">
{link.name}
</span>
<ArrowUpRight className="h-6 w-6 transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5" />
</a>
</motion.div>
))}
</div>
</motion.div>
</div>
</motion.div>
<motion.div
variants={containerVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
className="mt-16"
>
<motion.div variants={itemVariants}>
<Separator className="mb-8" />
</motion.div>
<motion.div
variants={itemVariants}
className="flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center"
>
<p className="text-sm text-muted-foreground">
© 2025 Matthias Meister Webdesign Crimmitschau
</p>
<div className="flex items-center gap-6 text-sm">
<span className="text-muted-foreground">
Kontakt:{" "}
<a href="mailto:info@matthias-meister-webdesign.de" className="underline underline-offset-4 transition-colors hover:text-foreground">
info@matthias-meister-webdesign.de
</a>
</span>
<span className="text-muted-foreground">
Tel:{" "}
<a href="tel:037627984400" className="underline underline-offset-4 transition-colors hover:text-foreground">
03762 798 4400
</a>
</span>
<span className="text-muted-foreground">
<a href="/impressum" className="underline underline-offset-4 transition-colors hover:text-foreground">
Impressum
</a>
</span>
<span className="text-muted-foreground">
<a href="/datenschutz" className="underline underline-offset-4 transition-colors hover:text-foreground">
Datenschutz
</a>
</span>
</div>
</motion.div>
</motion.div>
</div> </div>
</footer>
<address className="not-italic">
<div className="space-y-3 text-sm text-muted-foreground">
<a
href="mailto:info@matthias-meister-webdesign.de"
className="flex items-center gap-2 transition-colors hover:text-foreground"
>
<Mail className="size-4" aria-hidden />
info@matthias-meister-webdesign.de
</a>
<a
href="tel:037627984400"
className="flex items-center gap-2 transition-colors hover:text-foreground"
>
<Phone className="size-4" aria-hidden />
03762 798 4400
</a>
</div>
</address>
</div>
<div className="mt-10 flex flex-col gap-4 border-t border-border/80 pt-6 text-sm text-muted-foreground sm:flex-row sm:items-center sm:justify-between">
<p>© 2026 Matthias Meister Webdesign Crimmitschau</p>
<div className="flex flex-wrap items-center gap-x-5 gap-y-2">
<a
href="/impressum"
className="underline underline-offset-4 transition-colors hover:text-foreground"
>
Impressum
</a>
<a
href="/datenschutz"
className="underline underline-offset-4 transition-colors hover:text-foreground"
>
Datenschutz
</a>
</div>
</div>
</div> </div>
</section> </footer>
); );
}; };

View File

@@ -1,4 +1,4 @@
import { ArrowRight } from "lucide-react"; import { ArrowRight, Mail, MapPin, Phone } from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -9,38 +9,103 @@ interface Hero235Props {
const Hero235 = ({ className }: Hero235Props) => { const Hero235 = ({ className }: Hero235Props) => {
return ( return (
<section className={cn("px-4 sm:px-6 lg:px-8", className)}> <section className={cn("px-4 pt-5 sm:px-6 lg:px-8", className)}>
<div className="mx-auto max-w-5xl py-20 sm:py-24 lg:py-28"> <div className="mx-auto max-w-6xl">
<div className="mb-8 flex flex-col gap-3 border-b border-border/70 pb-5 sm:mb-10 sm:flex-row sm:items-center sm:justify-between"> <header className="flex flex-col gap-4 border-b border-border/80 pb-4 sm:flex-row sm:items-center sm:justify-between">
<p className="text-xs font-medium uppercase tracking-[0.18em] text-muted-foreground"> <a
Matthias Meister | Webdesign für KMU aus der Region href="/"
</p> className="text-sm font-semibold tracking-tight text-foreground"
<p className="text-sm text-muted-foreground"> >
Rückmeldung innerhalb von 24 Stunden Matthias Meister Webdesign
</p> </a>
</div> <nav
<div className="flex max-w-4xl flex-col gap-7"> aria-label="Direkte Kontaktwege"
<h1 className="max-w-[13ch] text-4xl font-semibold tracking-tight text-balance text-foreground sm:text-5xl lg:text-6xl"> className="flex flex-wrap items-center gap-x-5 gap-y-2 text-sm text-muted-foreground"
Websites für Unternehmen aus der Region - klar, schnell und >
glaubwürdig. <a
</h1> href="tel:037627984400"
<p className="max-w-[65ch] text-base leading-7 text-muted-foreground sm:text-lg"> className="inline-flex items-center gap-1.5 transition-colors hover:text-foreground"
Ich arbeite direkt für Handwerk, Praxen und kleine Betriebe aus >
der Region. Ohne Baukasten-Look, Agenturshow oder technischen <Phone className="size-3.5" aria-hidden />
Umweg - sondern mit einer Website, die Ihr Angebot klar zeigt und 03762 798 4400
Anfragen leichter macht. </a>
</p> <a
<div className="flex flex-wrap items-center gap-4 pt-1"> href="mailto:info@matthias-meister-webdesign.de"
<Button asChild size="lg" className="h-11 rounded-full px-5"> className="inline-flex items-center gap-1.5 transition-colors hover:text-foreground"
<a href="#kontakt"> >
Projekt anfragen <Mail className="size-3.5" aria-hidden />
<ArrowRight className="shrink-0" aria-hidden /> E-Mail
</a> </a>
</Button> <span className="inline-flex items-center gap-1.5">
<p className="text-sm text-muted-foreground"> <MapPin className="size-3.5" aria-hidden />
Kurze Nachricht reicht - Sie erhalten direkt eine erste Crimmitschau
Einschätzung und den passenden nächsten Schritt. </span>
</nav>
</header>
<div className="grid gap-10 py-16 sm:py-20 lg:grid-cols-[minmax(0,1.05fr)_minmax(320px,0.95fr)] lg:items-end lg:py-24">
<div className="flex max-w-3xl flex-col gap-7">
<p className="text-xs font-semibold uppercase tracking-[0.16em] text-primary">
Webdesign für regionale KMU
</p> </p>
<h1 className="max-w-[12ch] text-5xl font-semibold leading-[0.95] tracking-tight text-balance text-foreground sm:text-6xl lg:text-7xl">
Websites, die vor Ort Vertrauen schaffen.
</h1>
<p className="max-w-[62ch] text-lg leading-8 text-muted-foreground">
Für Handwerk, Praxen und kleine Betriebe: klar erklärt, schnell
gebaut und so strukturiert, dass Besucher ohne Umwege verstehen,
warum sie gerade Sie anfragen sollten.
</p>
<div className="flex flex-col gap-3 pt-1 sm:flex-row sm:items-center">
<Button asChild size="lg" className="h-11 rounded-md px-5">
<a href="#kontakt">
Projekt anfragen
<ArrowRight className="shrink-0" aria-hidden />
</a>
</Button>
<Button
asChild
size="lg"
variant="outline"
className="h-11 rounded-md px-5"
>
<a href="#preise">Pakete ansehen</a>
</Button>
</div>
<dl className="grid gap-4 border-t border-border/80 pt-6 sm:grid-cols-3">
{[
["24h", "Rückmeldung"],
["2 Wochen", "typischer Go-Live"],
["Sachsen", "Hosting & Betrieb"],
].map(([value, label]) => (
<div key={label}>
<dt className="text-2xl font-semibold tracking-tight text-foreground">
{value}
</dt>
<dd className="mt-1 text-sm text-muted-foreground">
{label}
</dd>
</div>
))}
</dl>
</div>
<div className="relative">
<figure className="overflow-hidden rounded-lg border border-border bg-card">
<img
src="/about.jpg"
alt="Arbeitsplatz von Matthias Meister beim Entwickeln einer Website"
className="h-[19rem] w-full object-cover sm:h-[24rem] lg:h-[31rem]"
/>
<figcaption className="grid gap-2 border-t border-border bg-card/95 p-5 sm:grid-cols-[1fr_auto] sm:items-center">
<span className="text-sm font-medium text-foreground">
Direkt mit dem Entwickler statt mit wechselnden Agenturrollen.
</span>
<span className="text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground">
Persönlich geplant
</span>
</figcaption>
</figure>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -5,217 +5,211 @@ import { useState } from "react";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
interface PricingPlan { interface PricingPlan {
name: string; name: string;
badge: string;
price: string; price: string;
description?: string; period: string;
description: string;
features: string[]; features: string[];
buttonText: string;
isPopular?: boolean; isPopular?: boolean;
} }
interface Pricing4Props { interface Pricing4Props {
title?: string; title?: string;
description?: string; description?: string;
plans?: PricingPlan[];
className?: string; className?: string;
} }
const developmentPlans: PricingPlan[] = [
{
name: "Basis",
price: "799 €",
period: "Einmalpreis",
description: "Für einen klaren Webauftritt mit den wichtigsten Inhalten.",
features: [
"Eine Seite mit fünf Sektionen",
"Kontaktformular",
"Impressum und Datenschutz",
"Mobilfreundlich und für Google vorbereitet",
"Cookiefreie Analytics ohne Banner",
],
},
{
name: "Profi",
price: "1.499 €",
period: "Einmalpreis",
description: "Für Betriebe, die mehrere Leistungen sauber erklären wollen.",
features: [
"Bis zu fünf Unterseiten",
"Google Maps Integration",
"SEO-Basis für lokale Auffindbarkeit",
"Optionaler Blog",
"Alles aus Basis inklusive",
],
isPopular: true,
},
{
name: "Maßarbeit",
price: "2.499 €",
period: "Einmalpreis",
description: "Für individuelle Anforderungen, CMS und spätere Erweiterungen.",
features: [
"Individuelles Design nach Ihren Anforderungen",
"CMS zur eigenen Inhaltspflege",
"Erweiterbare Struktur",
"Alles aus Profi inklusive",
],
},
];
const servicePlans: PricingPlan[] = [
{
name: "Hosting",
price: "19 €",
period: "pro Monat",
description: "Solide technische Basis für kleine Unternehmensseiten.",
features: [
"Hosting auf deutschen Servern in Sachsen",
"SSL, Domain und tägliche Backups",
"Monatlicher Einblick in Besucherzahlen",
],
},
{
name: "Wartung",
price: "39 €",
period: "pro Monat",
description: "Für Unternehmen, die Betrieb und Sicherheit abgeben möchten.",
features: [
"Alles aus Hosting inklusive",
"Regelmäßige Updates und Sicherheitschecks",
"1 Stunde Support pro Monat",
"Monitoring bei technischen Problemen",
],
isPopular: true,
},
{
name: "Full Service",
price: "69 €",
period: "pro Monat",
description: "Für laufende Änderungen ohne jedes Mal ein neues Projekt.",
features: [
"Alles aus Wartung inklusive",
"Kleinere Inhaltsänderungen bis 2 Stunden pro Monat",
"Häufigerer Einblick in Besucherzahlen",
],
},
];
const Pricing4 = ({ const Pricing4 = ({
title = "Entwicklungspakete", title = "Pakete mit klarer Kante.",
description = description =
"Alle Websites laufen auf deutschen Servern, sind DSGVO-konform und kommen ohne Cookie-Banner aus. Auf Wunsch erhalten Sie monatlich einen Einblick, wie viele Menschen Ihre Website besuchen haben — und woher sie kommen.", "Die Preise sind bewusst nachvollziehbar gehalten. Im Gespräch klären wir, welches Paket passt und wo ein schlankerer Weg sinnvoller ist.",
plans = [
{
name: "BASIS",
badge: "799 €",
price: "799 €",
features: [
"Eine Seite, fünf Sektionen",
"Kontaktformular",
"Impressum & Datenschutz",
"Mobilfreundlich & für Google optimiert",
"DSGVO-konformes Kontaktformular",
"Cookiefreies Analytics — ohne Abmahnrisiko",
],
buttonText: "Kostenloses Angebot anfordern",
},
{
name: "PROFI",
badge: "1.499 € ⭐ Empfehlung",
price: "1.499 €",
features: [
"Bis zu 5 Unterseiten",
"Google Maps Integration",
"SEO-Basis (bessere Auffindbarkeit bei Google)",
"Optionaler Blog",
"DSGVO-konformes Kontaktformular",
"Cookiefreies Analytics — ohne Abmahnrisiko",
"Alles aus Basis inklusive",
],
buttonText: "Kostenloses Angebot anfordern",
isPopular: true,
},
{
name: "MASSARBEIT",
badge: "2.499 €",
price: "2.499 €",
features: [
"Individuelles Design nach Ihren Wünschen",
"CMS — Sie pflegen Inhalte selbst",
"DSGVO-konformes Kontaktformular",
"Cookiefreies Analytics — ohne Abmahnrisiko",
"Alles aus Profi inklusive",
],
buttonText: "Kostenloses Angebot anfordern",
},
],
className, className,
}: Pricing4Props) => { }: Pricing4Props) => {
const [isMonthly, setIsMonthly] = useState(false); const [activeTab, setActiveTab] = useState<"development" | "service">(
"development",
);
const plans = activeTab === "development" ? developmentPlans : servicePlans;
return ( return (
<section className={cn("py-32", className)}> <section
<div className="container mx-auto"> id="preise"
<div className="flex flex-col gap-6"> className={cn("px-4 py-20 sm:px-6 lg:px-8 lg:py-28", className)}
<h2 className="text-4xl font-semibold text-pretty lg:text-6xl"> >
{title} <div className="mx-auto max-w-6xl">
</h2> <div className="grid gap-8 lg:grid-cols-[minmax(0,0.8fr)_minmax(0,1.4fr)] lg:items-end lg:gap-16">
<div className="flex flex-col justify-between gap-10 md:flex-row"> <div className="max-w-xl">
<p className="max-w-3xl text-muted-foreground lg:text-xl"> <p className="text-xs font-semibold uppercase tracking-[0.16em] text-primary">
Preise
</p>
<h2 className="mt-4 text-3xl font-semibold tracking-tight text-balance sm:text-4xl">
{title}
</h2>
<p className="mt-5 text-base leading-7 text-muted-foreground">
{description} {description}
</p> </p>
<Tabs </div>
value={isMonthly ? "monthly" : "yearly"} <Tabs
onValueChange={(value: string) => value={activeTab}
setIsMonthly(value === "monthly") onValueChange={(value: string) =>
} setActiveTab(value as "development" | "service")
className="w-fit shrink-0" }
aria-label="Leistungsvariante" className="w-fit lg:justify-self-end"
aria-label="Paketart auswählen"
>
<TabsList className="grid h-11 w-full grid-cols-2 rounded-md border border-border bg-card p-1 sm:w-max">
<TabsTrigger
value="development"
className="h-full min-h-0 px-5 py-0 text-sm font-semibold data-active:bg-primary data-active:text-primary-foreground"
>
Entwicklung
</TabsTrigger>
<TabsTrigger
value="service"
className="h-full min-h-0 px-5 py-0 text-sm font-semibold data-active:bg-primary data-active:text-primary-foreground"
>
Hosting & Wartung
</TabsTrigger>
</TabsList>
</Tabs>
</div>
<div className="mt-10 grid gap-4 lg:grid-cols-3">
{plans.map((plan) => (
<article
key={plan.name}
className={cn(
"flex min-h-[32rem] flex-col rounded-lg border bg-card p-6",
plan.isPopular
? "border-primary shadow-[0_18px_60px_oklch(0.285_0.045_148/0.12)]"
: "border-border",
)}
> >
<TabsList className="grid h-11 w-max grid-cols-2 gap-0 rounded-md p-1 text-lg"> <div className="flex items-start justify-between gap-4">
<TabsTrigger <div>
value="monthly" <h3 className="text-xl font-semibold tracking-tight">
className="h-full min-h-0 px-7 py-0 font-semibold text-muted-foreground data-active:text-foreground" {plan.name}
>
Entwicklung
</TabsTrigger>
<TabsTrigger
value="yearly"
className="h-full min-h-0 px-7 py-0 font-semibold text-muted-foreground data-active:text-foreground"
>
Hosting & Wartung
</TabsTrigger>
</TabsList>
</Tabs>
</div>
<div className="flex w-full flex-col items-stretch gap-6 md:flex-row">
{isMonthly ? (
plans.map((plan) => (
<div
key={plan.name}
className={`flex w-full flex-col rounded-xl border shadow-sm p-6 text-left ${
plan.isPopular ? "bg-muted" : ""
}`}
>
<Badge className="mb-8 block w-fit uppercase">
{plan.badge}
</Badge>
<h3 className="font-mono text-4xl lg:text-5xl">
{plan.price}
</h3> </h3>
<p className="text-muted-foreground">Einmalpreis</p> <p className="mt-2 text-sm leading-6 text-muted-foreground">
<Separator className="my-6" /> {plan.description}
<div className="flex h-full flex-col justify-between gap-20"> </p>
<ul className="space-y-4 text-muted-foreground md:leading-snug">
{plan.features.map((feature, featureIndex) => (
<li
key={featureIndex}
className="flex items-center gap-2"
>
<Check className="size-4 shrink-0" aria-hidden="true" />
<span>{feature}</span>
</li>
))}
</ul>
<Button className="w-full">{plan.buttonText}</Button>
</div>
</div> </div>
)) {plan.isPopular && (
) : ( <Badge variant="outline" className="rounded-md">
[ Empfohlen
{
name: "BASIC HOSTING",
badge: "19 €/Monat",
price: "19 €",
features: [
"Hosting auf deutschen Servern in Sachsen",
"Grünes Schloss im Browser (SSL) — sicher & von Google bevorzugt",
"Tägliche Backups — Ihre Daten sind immer geschützt",
"Domain inklusive",
"Monatlicher Einblick in Ihre Besucherzahlen",
],
},
{
name: "WARTUNG",
badge: "39 €/Monat ⭐ Empfehlung",
price: "39 €",
features: [
"Alles aus Basic Hosting inklusive",
"Regelmäßige Updates & Sicherheitschecks",
"1 Stunde Support pro Monat",
"Monitoring — ich merke bevor Sie es tun, wenn etwas nicht stimmt",
"Wöchentlicher Einblick in Ihre Besucherzahlen",
],
isPopular: true,
},
{
name: "FULL SERVICE",
badge: "69 €/Monat",
price: "69 €",
features: [
"Alles aus Wartung inklusive",
"Kleinere Inhaltsänderungen (bis 2 Stunden/Monat)",
"Täglicher Einblick in Ihre Besucherzahlen",
],
},
].map((plan) => (
<div
key={plan.name}
className={`flex w-full flex-col rounded-xl border shadow-sm p-6 text-left ${
plan.isPopular ? "bg-muted" : ""
}`}
>
<Badge className="mb-8 block w-fit uppercase">
{plan.badge}
</Badge> </Badge>
<h3 className="font-mono text-4xl lg:text-5xl"> )}
{plan.price} </div>
</h3>
<p className="text-muted-foreground">Monatlicher Preis</p> <div className="mt-8">
<Separator className="my-6" /> <p className="text-4xl font-semibold tracking-tight">
<div className="flex h-full flex-col justify-between gap-20"> {plan.price}
<ul className="space-y-4 text-muted-foreground md:leading-snug"> </p>
{plan.features.map((feature, featureIndex) => ( <p className="mt-1 text-sm text-muted-foreground">
<li {plan.period}
key={featureIndex} </p>
className="flex items-center gap-2" </div>
>
<Check className="size-4 shrink-0" aria-hidden="true" /> <ul className="mt-8 flex-1 space-y-4 text-sm leading-6 text-muted-foreground">
<span>{feature}</span> {plan.features.map((feature) => (
</li> <li key={feature} className="flex gap-3">
))} <Check
</ul> className="mt-0.5 size-4 shrink-0 text-primary"
<Button className="w-full">Kostenloses Angebot anfordern</Button> aria-hidden="true"
</div> />
</div> <span>{feature}</span>
)) </li>
)} ))}
</div> </ul>
<Button asChild className="mt-8 w-full rounded-md">
<a href="#kontakt">Kostenloses Angebot anfordern</a>
</Button>
</article>
))}
</div> </div>
</div> </div>
</section> </section>

View File

@@ -5,59 +5,39 @@ interface Stats11Props {
} }
const Stats11 = ({ className }: Stats11Props) => { const Stats11 = ({ className }: Stats11Props) => {
return ( const stats = [
<section className={cn("py-32", className)}> ["SEO-ready", "Leistungsseiten, Region und Kontakt sauber strukturiert."],
<div className="container"> ["< 1 Sek.", "Auf schnelle Ladezeiten und klare Technik ausgelegt."],
<div className="relative isolate overflow-hidden bg-linear-to-b from-primary/10 to-transparent md:border-x md:border-border"> ["ab 799 €", "Transparente Einstiegspreise ohne Paketnebel."],
<div className="absolute right-0 -left-px -z-20 h-full w-full bg-[linear-gradient(90deg,var(--muted-foreground)_1px,transparent_1px)] mask-[linear-gradient(transparent_25%,black_25%,black_75%,transparent_75%)] bg-size-[calc(100%/16)_100%] mask-size-[100%_16px] opacity-20 [-webkit-mask-image:linear-gradient(transparent_25%,black_25%,black_75%,transparent_75%)] [-webkit-mask-size:100%_16px]" /> ["2 Wochen", "Typischer Zeitraum vom Startgespräch bis zur Vorschau."],
];
return (
<section className={cn("px-4 py-20 sm:px-6 lg:px-8 lg:py-28", className)}>
<div className="mx-auto max-w-6xl">
<div className="rounded-lg border border-border bg-primary p-6 text-primary-foreground sm:p-8 lg:grid lg:grid-cols-[minmax(0,0.85fr)_minmax(0,1.45fr)] lg:gap-12 lg:p-10">
<div> <div>
<h2 className="mb-16 max-w-3xl text-3xl leading-10 font-semibold sm:mb-24 md:mx-10"> <p className="text-xs font-semibold uppercase tracking-[0.16em] text-primary-foreground/65">
Für Google optimiert, schnell geladen und klar kalkulierbar. Messbare Grundlagen
<span className="font-medium text-primary/50"> </p>
{" "} <h2 className="mt-4 max-w-xl text-3xl font-semibold tracking-tight text-balance sm:text-4xl">
Genau die Zahlen, die bei einer Website wirklich zählen. Gute Websites hlen sich ruhig an, weil die Basis stimmt.
</span>
</h2> </h2>
<div className="relative grid max-w-2xl gap-4 border-x border-border pb-32 sm:grid-cols-2 sm:gap-10 sm:pb-44 md:ml-10 md:border-0"> </div>
<div className="absolute inset-0 top-[-1100px] -left-[calc(1000px-22vw)] -z-10 size-[1500px] rounded-full border border-primary bg-background sm:top-[-480%] sm:left-[-185%] sm:size-[2000px] md:top-[-906%] md:left-[-294%] md:size-[3500px] lg:top-[-1186%] lg:left-[-380%] lg:size-[4500px] xl:top-[-1200%] xl:left-[-350%] 2xl:top-[-1196%] 2xl:left-[-345%]"></div> <div className="mt-10 grid gap-4 sm:grid-cols-2 lg:mt-0">
<div className="flex flex-col gap-2"> {stats.map(([value, label]) => (
<span className="flex gap-5 text-3xl font-semibold"> <div
<span className="relative -left-px w-px bg-primary/50"></span> key={value}
SEO-ready className="rounded-md border border-primary-foreground/15 bg-primary-foreground/[0.06] p-5"
</span> >
<p className="pl-5 font-medium text-muted-foreground/80"> <p className="text-3xl font-semibold tracking-tight">
Für Google optimiert {value}
</p>
<p className="mt-3 text-sm leading-6 text-primary-foreground/72">
{label}
</p> </p>
</div> </div>
<div className="flex flex-col gap-2"> ))}
<span className="flex gap-5 text-3xl font-semibold">
<span className="relative -left-px w-px bg-primary/50"></span>
{"< 1 Sek."}
</span>
<p className="pl-5 font-medium text-muted-foreground/80">
Ladezeit
</p>
</div>
<div className="flex flex-col gap-2">
<span className="flex gap-5 text-3xl font-semibold">
<span className="relative -left-px w-px bg-primary/50"></span>
ab 799
</span>
<p className="pl-5 font-medium text-muted-foreground/80">
Transparenter Einmalpreis
</p>
</div>
<div className="flex flex-col gap-2">
<span className="flex gap-5 text-3xl font-semibold">
<span className="relative -left-px w-px bg-primary/50"></span>
2 Wochen
</span>
<p className="pl-5 font-medium text-muted-foreground/80">
Bis zum Go-Live
</p>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -25,8 +25,8 @@ import "@/styles/global.css";
defer></script> defer></script>
</head> </head>
<body> <body>
<div class="mx-auto w-full max-w-7xl"> <main class="w-full overflow-x-hidden">
<Hero235 client:load /> <Hero235 client:load />
<CTASection client:load /> <CTASection client:load />
<Feature284 client:load /> <Feature284 client:load />
<Stats11 client:load /> <Stats11 client:load />
@@ -35,6 +35,6 @@ import "@/styles/global.css";
<About19 client:load /> <About19 client:load />
<Contact21 client:load /> <Contact21 client:load />
<Footer27 client:load /> <Footer27 client:load />
</div> </main>
</body> </body>
</html> </html>

View File

@@ -49,38 +49,38 @@
} }
:root { :root {
--background: oklch(1 0 0); --background: oklch(0.982 0.012 82);
--foreground: oklch(0.145 0 0); --foreground: oklch(0.235 0.024 72);
--card: oklch(1 0 0); --card: oklch(0.996 0.006 82);
--card-foreground: oklch(0.145 0 0); --card-foreground: oklch(0.235 0.024 72);
--popover: oklch(1 0 0); --popover: oklch(0.996 0.006 82);
--popover-foreground: oklch(0.145 0 0); --popover-foreground: oklch(0.235 0.024 72);
--primary: oklch(0.205 0 0); --primary: oklch(0.285 0.045 148);
--primary-foreground: oklch(0.985 0 0); --primary-foreground: oklch(0.985 0.011 82);
--secondary: oklch(0.97 0 0); --secondary: oklch(0.915 0.028 82);
--secondary-foreground: oklch(0.205 0 0); --secondary-foreground: oklch(0.275 0.031 72);
--muted: oklch(0.97 0 0); --muted: oklch(0.94 0.018 82);
--muted-foreground: oklch(0.556 0 0); --muted-foreground: oklch(0.485 0.024 72);
--accent: oklch(0.97 0 0); --accent: oklch(0.705 0.079 141);
--accent-foreground: oklch(0.205 0 0); --accent-foreground: oklch(0.17 0.026 148);
--destructive: oklch(0.577 0.245 27.325); --destructive: oklch(0.56 0.19 27);
--border: oklch(0.922 0 0); --border: oklch(0.86 0.018 80);
--input: oklch(0.922 0 0); --input: oklch(0.84 0.018 80);
--ring: oklch(0.708 0 0); --ring: oklch(0.53 0.071 142);
--chart-1: oklch(0.87 0 0); --chart-1: oklch(0.66 0.08 142);
--chart-2: oklch(0.556 0 0); --chart-2: oklch(0.55 0.055 72);
--chart-3: oklch(0.439 0 0); --chart-3: oklch(0.47 0.04 215);
--chart-4: oklch(0.371 0 0); --chart-4: oklch(0.72 0.055 93);
--chart-5: oklch(0.269 0 0); --chart-5: oklch(0.38 0.046 148);
--radius: 0.625rem; --radius: 0.5rem;
--sidebar: oklch(0.985 0 0); --sidebar: oklch(0.965 0.012 82);
--sidebar-foreground: oklch(0.145 0 0); --sidebar-foreground: oklch(0.235 0.024 72);
--sidebar-primary: oklch(0.205 0 0); --sidebar-primary: oklch(0.285 0.045 148);
--sidebar-primary-foreground: oklch(0.985 0 0); --sidebar-primary-foreground: oklch(0.985 0.011 82);
--sidebar-accent: oklch(0.97 0 0); --sidebar-accent: oklch(0.93 0.02 82);
--sidebar-accent-foreground: oklch(0.205 0 0); --sidebar-accent-foreground: oklch(0.275 0.031 72);
--sidebar-border: oklch(0.922 0 0); --sidebar-border: oklch(0.86 0.018 80);
--sidebar-ring: oklch(0.708 0 0); --sidebar-ring: oklch(0.53 0.071 142);
} }
.dark { .dark {
@@ -122,9 +122,20 @@
@apply border-border outline-ring/50; @apply border-border outline-ring/50;
} }
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground antialiased;
} }
html { html {
@apply font-sans; @apply scroll-smooth font-sans;
} }
} }
body {
background-image:
linear-gradient(to bottom, oklch(0.988 0.01 82), var(--background) 46rem),
linear-gradient(90deg, oklch(0.86 0.018 80 / 0.28) 1px, transparent 1px);
background-size: auto, 6rem 6rem;
}
::selection {
background: oklch(0.78 0.075 141 / 0.35);
}