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
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"
@@ -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 -->
+36 -20
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>
+41 -35
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 ? (
+10 -11
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">
+22 -18
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>
+55 -58
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>
+56 -132
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:[email protected]" 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:[email protected]"
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>
); );
}; };
+97 -32
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:[email protected]"
<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>
+178 -184
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>
+28 -48
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 fü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>
+3 -3
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>
+46 -35
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);
}