Compare commits
8 Commits
add89b0f92
...
243978bfdd
| Author | SHA1 | Date | |
|---|---|---|---|
| 243978bfdd | |||
| aac9e52bfb | |||
| e57678a68d | |||
| 55a189e78e | |||
| e2ca8074b8 | |||
| 0d8b56864a | |||
| 73299fd18a | |||
| 117839058b |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,6 +5,7 @@ dist/
|
|||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
node_modules/
|
node_modules/
|
||||||
|
.worktrees/
|
||||||
|
|
||||||
# logs
|
# logs
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
|
|||||||
14
backlog/config.yml
Normal file
14
backlog/config.yml
Normal 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 -->
|
||||||
41
docs/superpowers/plans/2026-05-05-canva-redesign.md
Normal file
41
docs/superpowers/plans/2026-05-05-canva-redesign.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Canva Redesign Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Rework the dev landing page into a bold black, red, and white editorial site inspired by the Canva creative brief deck.
|
||||||
|
|
||||||
|
**Architecture:** Replace the current stacked section imports on the homepage with one focused React landing component. Keep existing UI primitives available but avoid broad refactors of old sections so the redesign remains easy to review or revert.
|
||||||
|
|
||||||
|
**Tech Stack:** Astro 6, React 19, Tailwind CSS 4, lucide-react, Node test runner.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: Landing Smoke Test
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `tests/landing-content.test.mjs`
|
||||||
|
- Modify: `package.json`
|
||||||
|
|
||||||
|
- [x] Add a Node smoke test that checks the new component source for key content anchors: `Projektbrief`, `01`, `Website`, `Kontakt`.
|
||||||
|
- [x] Run `node --test tests/landing-content.test.mjs` and confirm it fails before the component exists.
|
||||||
|
|
||||||
|
### Task 2: Canva-Inspired Landing Page
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/components/canva-landing.tsx`
|
||||||
|
- Modify: `src/pages/index.astro`
|
||||||
|
- Modify: `src/styles/global.css`
|
||||||
|
|
||||||
|
- [ ] Build a single-page layout with a dark editorial shell, red accent panels, large German headline, numbered sections, pricing/service strips, and a contact brief section.
|
||||||
|
- [ ] Replace the existing homepage component stack with the new `CanvaLanding` component.
|
||||||
|
- [ ] Update global tokens for the dark, high-contrast Canva reference style.
|
||||||
|
|
||||||
|
### Task 3: Verification
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `backlog/tasks/task-1 - Redesign-dev-website-from-Canva-reference.md`
|
||||||
|
|
||||||
|
- [ ] Run `node --test tests/landing-content.test.mjs` and confirm it passes.
|
||||||
|
- [ ] Run `CI=true pnpm run build` and confirm Astro builds the page.
|
||||||
|
- [ ] Start the local dev server and visually review the page in a browser/screenshot.
|
||||||
|
- [ ] Check off remaining acceptance criteria that have direct evidence.
|
||||||
4
pnpm-workspace.yaml
Normal file
4
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
allowBuilds:
|
||||||
|
esbuild: true
|
||||||
|
msw: true
|
||||||
|
sharp: true
|
||||||
11
solo.yml
Normal file
11
solo.yml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# Visit https://soloterm.com to learn more
|
||||||
|
name: Dev-Landing
|
||||||
|
icon: null
|
||||||
|
processes:
|
||||||
|
Astro:
|
||||||
|
command: pnpm run dev
|
||||||
|
working_dir: null
|
||||||
|
auto_start: false
|
||||||
|
auto_restart: true
|
||||||
|
restart_when_changed: []
|
||||||
|
env: {}
|
||||||
@@ -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
|
||||||
|
bleiben.
|
||||||
|
</h2>
|
||||||
|
<div className="mt-8 space-y-5 text-base leading-8 text-muted-foreground">
|
||||||
|
<p>
|
||||||
|
Ich bin in der Region aufgewachsen, war durch die Bundeswehr
|
||||||
|
viele Jahre weg und bin jetzt zurück.
|
||||||
</p>
|
</p>
|
||||||
<p className="text-base text-foreground/40 lg:text-lg">
|
<p>
|
||||||
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.
|
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>
|
||||||
<p className="text-base text-foreground/40 lg:text-lg">
|
<p>
|
||||||
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.
|
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>
|
||||||
|
|||||||
249
src/components/canva-landing.tsx
Normal file
249
src/components/canva-landing.tsx
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
import {
|
||||||
|
ArrowUpRight,
|
||||||
|
Check,
|
||||||
|
CornerDownRight,
|
||||||
|
Mail,
|
||||||
|
MapPin,
|
||||||
|
Phone,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
|
const services = [
|
||||||
|
{
|
||||||
|
number: "01",
|
||||||
|
title: "Website",
|
||||||
|
text: "Eine klare Startseite oder ein kompletter Auftritt, der sofort zeigt, warum man Ihnen vertrauen kann.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
number: "02",
|
||||||
|
title: "Struktur",
|
||||||
|
text: "Angebot, Beweise, Ablauf und Kontakt werden so sortiert, dass Besucher nicht suchen müssen.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
number: "03",
|
||||||
|
title: "Technik",
|
||||||
|
text: "Schnell, mobil sauber, DSGVO-arm und so gebaut, dass spätere Änderungen nicht zum Projekt werden.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const deliverables = [
|
||||||
|
"Strategie und Seitenstruktur",
|
||||||
|
"Individuelles Screen-Design",
|
||||||
|
"Astro/React Umsetzung",
|
||||||
|
"Kontaktformular und Datenschutz",
|
||||||
|
"Hosting, Wartung und Analytics",
|
||||||
|
];
|
||||||
|
|
||||||
|
const packages = [
|
||||||
|
{
|
||||||
|
name: "Basis",
|
||||||
|
price: "799 EUR",
|
||||||
|
detail: "Eine starke Seite für ein klares Angebot.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Profi",
|
||||||
|
price: "1.499 EUR",
|
||||||
|
detail: "Mehrere Seiten für Betriebe mit erklärungsbedürftigem Angebot.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Maßarbeit",
|
||||||
|
price: "2.499 EUR+",
|
||||||
|
detail: "Individuelle Struktur, CMS und besondere Anforderungen.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const CanvaLanding = () => {
|
||||||
|
return (
|
||||||
|
<main className="min-h-screen overflow-hidden bg-background text-foreground">
|
||||||
|
<section className="relative grid min-h-screen grid-cols-1 border-b border-border lg:grid-cols-[1.08fr_0.92fr]">
|
||||||
|
<div className="flex min-h-[640px] flex-col justify-between px-5 py-5 sm:px-8 lg:px-12">
|
||||||
|
<header className="grid gap-4 border-b border-border pb-5 text-xs uppercase tracking-[0.28em] text-muted-foreground sm:grid-cols-[1fr_auto]">
|
||||||
|
<a href="/" className="font-semibold text-foreground">
|
||||||
|
Matthias Meister
|
||||||
|
</a>
|
||||||
|
<nav className="flex flex-wrap gap-5">
|
||||||
|
<a href="#leistungen">Leistungen</a>
|
||||||
|
<a href="#pakete">Pakete</a>
|
||||||
|
<a href="#kontakt">Kontakt</a>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div className="max-w-5xl py-16 sm:py-20 lg:py-24">
|
||||||
|
<p className="mb-6 max-w-sm text-sm uppercase tracking-[0.32em] text-primary">
|
||||||
|
Projektbrief für regionale Unternehmen
|
||||||
|
</p>
|
||||||
|
<h1 className="max-w-[11ch] text-[clamp(4.25rem,13vw,11.5rem)] font-black uppercase leading-[0.78] tracking-normal text-foreground">
|
||||||
|
Website ohne Umweg
|
||||||
|
</h1>
|
||||||
|
<div className="mt-8 grid gap-7 border-t border-border pt-7 lg:grid-cols-[0.72fr_1fr]">
|
||||||
|
<p className="text-sm uppercase tracking-[0.24em] text-muted-foreground">
|
||||||
|
Strategie trifft Umsetzung
|
||||||
|
</p>
|
||||||
|
<p className="max-w-2xl text-lg leading-8 text-foreground/78">
|
||||||
|
Ich baue Websites für Handwerk, Praxen, Salons und
|
||||||
|
Dienstleister aus der Region. Direkt, glaubwürdig und so
|
||||||
|
reduziert, dass der nächste Kontakt naheliegt.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-4 border-t border-border pt-5 text-sm uppercase tracking-[0.2em] text-muted-foreground sm:grid-cols-3">
|
||||||
|
<span>Antwort in 24h</span>
|
||||||
|
<span>DSGVO-arm</span>
|
||||||
|
<span>Hosting aus DE</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<aside className="relative flex min-h-[520px] flex-col justify-between border-t border-primary-foreground/25 bg-primary px-5 py-5 text-primary-foreground sm:px-8 lg:border-l lg:border-t-0 lg:px-12">
|
||||||
|
<div className="flex items-start justify-between border-b border-primary-foreground/30 pb-5 text-xs uppercase tracking-[0.28em]">
|
||||||
|
<span>Creative Direction</span>
|
||||||
|
<span>2026</span>
|
||||||
|
</div>
|
||||||
|
<div className="absolute bottom-32 right-10 h-36 w-36 border border-primary-foreground/35" />
|
||||||
|
<div className="relative mt-28 max-w-xl">
|
||||||
|
<p className="text-[clamp(4rem,10vw,9rem)] font-black uppercase leading-[0.75] tracking-normal">
|
||||||
|
01
|
||||||
|
</p>
|
||||||
|
<p className="mt-7 max-w-sm text-2xl font-semibold uppercase leading-none">
|
||||||
|
Klarer Auftritt. Harte Kante. Weniger Agenturlärm.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
href="#kontakt"
|
||||||
|
className="group inline-flex w-fit items-center gap-3 border border-primary-foreground px-5 py-4 text-sm font-semibold uppercase tracking-[0.18em] transition hover:bg-primary-foreground hover:text-primary"
|
||||||
|
>
|
||||||
|
Projekt anfragen
|
||||||
|
<ArrowUpRight className="size-5 transition group-hover:-translate-y-0.5 group-hover:translate-x-0.5" />
|
||||||
|
</a>
|
||||||
|
</aside>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section
|
||||||
|
id="leistungen"
|
||||||
|
className="grid border-b border-border lg:grid-cols-[0.36fr_0.64fr]"
|
||||||
|
>
|
||||||
|
<div className="border-b border-border px-5 py-12 sm:px-8 lg:border-b-0 lg:border-r lg:px-12 lg:py-20">
|
||||||
|
<p className="text-sm uppercase tracking-[0.3em] text-primary">
|
||||||
|
Leistungen (02)
|
||||||
|
</p>
|
||||||
|
<h2 className="mt-6 max-w-[9ch] text-5xl font-black uppercase leading-[0.86] sm:text-7xl">
|
||||||
|
Vom Brief zur Seite
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div className="divide-y divide-border">
|
||||||
|
{services.map((service) => (
|
||||||
|
<article
|
||||||
|
key={service.number}
|
||||||
|
className="grid gap-8 px-5 py-10 sm:px-8 md:grid-cols-[7rem_0.4fr_1fr] lg:px-12"
|
||||||
|
>
|
||||||
|
<span className="text-5xl font-black text-primary">
|
||||||
|
{service.number}
|
||||||
|
</span>
|
||||||
|
<h3 className="text-3xl font-black uppercase leading-none">
|
||||||
|
{service.title}
|
||||||
|
</h3>
|
||||||
|
<p className="max-w-2xl text-lg leading-8 text-muted-foreground">
|
||||||
|
{service.text}
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="grid border-b border-border lg:grid-cols-2">
|
||||||
|
<div className="bg-foreground px-5 py-14 text-background sm:px-8 lg:px-12 lg:py-24">
|
||||||
|
<p className="text-sm uppercase tracking-[0.3em] text-primary">
|
||||||
|
Deliverables (03)
|
||||||
|
</p>
|
||||||
|
<h2 className="mt-8 max-w-[10ch] text-5xl font-black uppercase leading-[0.86] sm:text-7xl">
|
||||||
|
Was am Ende steht
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div className="grid content-center gap-4 px-5 py-12 sm:px-8 lg:px-12">
|
||||||
|
{deliverables.map((item) => (
|
||||||
|
<div
|
||||||
|
key={item}
|
||||||
|
className="flex items-center gap-4 border-b border-border pb-4 text-lg font-semibold uppercase tracking-[0.08em]"
|
||||||
|
>
|
||||||
|
<Check className="size-5 text-primary" />
|
||||||
|
<span>{item}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="pakete" className="border-b border-border px-5 py-14 sm:px-8 lg:px-12 lg:py-24">
|
||||||
|
<div className="grid gap-8 lg:grid-cols-[0.45fr_0.55fr]">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm uppercase tracking-[0.3em] text-primary">
|
||||||
|
Pakete (04)
|
||||||
|
</p>
|
||||||
|
<h2 className="mt-8 max-w-[9ch] text-5xl font-black uppercase leading-[0.86] sm:text-7xl">
|
||||||
|
Kosten ohne Nebel
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-4">
|
||||||
|
{packages.map((item) => (
|
||||||
|
<article
|
||||||
|
key={item.name}
|
||||||
|
className="grid gap-4 border border-border p-5 sm:grid-cols-[0.5fr_0.5fr] sm:p-6"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm uppercase tracking-[0.24em] text-primary">
|
||||||
|
{item.name}
|
||||||
|
</p>
|
||||||
|
<p className="mt-4 text-4xl font-black uppercase">
|
||||||
|
{item.price}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p className="self-end text-lg leading-7 text-muted-foreground">
|
||||||
|
{item.detail}
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section
|
||||||
|
id="kontakt"
|
||||||
|
className="grid min-h-[620px] lg:grid-cols-[0.72fr_0.28fr]"
|
||||||
|
>
|
||||||
|
<div className="px-5 py-14 sm:px-8 lg:px-12 lg:py-24">
|
||||||
|
<p className="text-sm uppercase tracking-[0.3em] text-primary">
|
||||||
|
Kontakt (05)
|
||||||
|
</p>
|
||||||
|
<h2 className="mt-8 max-w-[12ch] text-5xl font-black uppercase leading-[0.86] sm:text-7xl lg:text-8xl">
|
||||||
|
Erzählen Sie mir kurz vom Projekt
|
||||||
|
</h2>
|
||||||
|
<p className="mt-8 max-w-2xl text-xl leading-8 text-muted-foreground">
|
||||||
|
Ein paar Sätze reichen: Was bieten Sie an, was soll die Website
|
||||||
|
leisten, und wann soll sie online sein?
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="mailto:hallo@matthias-meister.com"
|
||||||
|
className="mt-10 inline-flex items-center gap-3 bg-primary px-6 py-5 text-sm font-black uppercase tracking-[0.18em] text-primary-foreground transition hover:bg-foreground hover:text-background"
|
||||||
|
>
|
||||||
|
<CornerDownRight className="size-5" />
|
||||||
|
Anfrage per Mail senden
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col justify-end gap-6 bg-primary px-5 py-10 text-primary-foreground sm:px-8 lg:px-10">
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<Mail className="size-5" />
|
||||||
|
<span>hallo@matthias-meister.com</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<Phone className="size-5" />
|
||||||
|
<span>Rückmeldung innerhalb von 24 Stunden</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<MapPin className="size-5" />
|
||||||
|
<span>Regionale KMU in Deutschland</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { CanvaLanding };
|
||||||
@@ -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">
|
||||||
|
Kontakt
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-5 flex items-center gap-4 lg:mt-20">
|
<h2 className="mt-4 text-3xl font-semibold tracking-tight text-balance sm:text-4xl">
|
||||||
<img
|
Erzählen Sie kurz, worum es geht.
|
||||||
src="https://deifkwefumgah.cloudfront.net/shadcnblocks/block/guri3/avatar3.png"
|
</h2>
|
||||||
className="size-12"
|
<p className="mt-5 text-base leading-7 text-muted-foreground">
|
||||||
alt="Matthias Meister"
|
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,19 +101,15 @@ 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">
|
|
||||||
Jetzt Website anfordern
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
{isSubmitted && (
|
{isSubmitted && (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"mb-4 rounded-lg border border-green-500/20 bg-green-500/10 p-4 text-center transition-opacity duration-500",
|
"mb-4 rounded-lg border border-primary/20 bg-primary/10 p-4 text-center transition-opacity duration-500",
|
||||||
showSuccess ? "opacity-100" : "opacity-0",
|
showSuccess ? "opacity-100" : "opacity-0",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<p className="text-sm font-medium text-green-600 dark:text-green-400">
|
<p className="text-sm font-medium text-primary">
|
||||||
Vielen Dank! Ich melde mich in Kürze bei Ihnen.
|
Vielen Dank! Ich melde mich in Kürze bei Ihnen.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -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 ? (
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,47 +47,45 @@ 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">
|
||||||
|
<h3 className="text-xl font-semibold tracking-tight">
|
||||||
{feature.title}
|
{feature.title}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-muted-foreground">{feature.desc}</p>
|
<p className="text-sm leading-6 text-muted-foreground">
|
||||||
|
{feature.desc}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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"
|
|
||||||
whileInView="visible"
|
|
||||||
viewport={{ once: true }}
|
|
||||||
className="flex flex-col justify-between md:flex-row md:items-center"
|
|
||||||
>
|
|
||||||
<div className="space-y-8">
|
|
||||||
<motion.div variants={itemVariants} className="space-y-6">
|
|
||||||
<h2 className="text-4xl leading-tight font-bold text-foreground lg:text-5xl">
|
|
||||||
Bereit für eine Website, die Kunden bringt?
|
|
||||||
</h2>
|
</h2>
|
||||||
<p className="max-w-md text-lg leading-relaxed text-muted-foreground">
|
<p className="mt-4 max-w-xl text-base leading-7 text-muted-foreground">
|
||||||
Erzählen Sie mir kurz von Ihrem Unternehmen — ich melde mich innerhalb von 24 Stunden mit einem unverbindlichen Angebot.
|
Eine kurze Nachricht reicht. Ich prüfe, welcher Weg sinnvoll ist,
|
||||||
|
und melde mich mit einer ehrlichen Einschätzung.
|
||||||
</p>
|
</p>
|
||||||
</motion.div>
|
<Button asChild size="lg" className="mt-6 h-11 rounded-md px-5">
|
||||||
|
<a href="#kontakt">
|
||||||
<motion.div variants={itemVariants}>
|
Kostenloses Angebot anfordern
|
||||||
<Button size="lg">Kostenloses Angebot anfordern</Button>
|
<ArrowRight className="shrink-0" aria-hidden />
|
||||||
</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>
|
</a>
|
||||||
</motion.div>
|
</Button>
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
<motion.div
|
<address className="not-italic">
|
||||||
variants={containerVariants}
|
<div className="space-y-3 text-sm text-muted-foreground">
|
||||||
initial="hidden"
|
<a
|
||||||
whileInView="visible"
|
href="mailto:info@matthias-meister-webdesign.de"
|
||||||
viewport={{ once: true }}
|
className="flex items-center gap-2 transition-colors hover:text-foreground"
|
||||||
className="mt-16"
|
|
||||||
>
|
>
|
||||||
<motion.div variants={itemVariants}>
|
<Mail className="size-4" aria-hidden />
|
||||||
<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
|
info@matthias-meister-webdesign.de
|
||||||
</a>
|
</a>
|
||||||
</span>
|
<a
|
||||||
<span className="text-muted-foreground">
|
href="tel:037627984400"
|
||||||
Tel:{" "}
|
className="flex items-center gap-2 transition-colors hover:text-foreground"
|
||||||
<a href="tel:037627984400" className="underline underline-offset-4 transition-colors hover:text-foreground">
|
>
|
||||||
|
<Phone className="size-4" aria-hidden />
|
||||||
03762 798 4400
|
03762 798 4400
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</div>
|
||||||
<span className="text-muted-foreground">
|
</address>
|
||||||
<a href="/impressum" className="underline underline-offset-4 transition-colors hover:text-foreground">
|
</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
|
Impressum
|
||||||
</a>
|
</a>
|
||||||
</span>
|
<a
|
||||||
<span className="text-muted-foreground">
|
href="/datenschutz"
|
||||||
<a href="/datenschutz" className="underline underline-offset-4 transition-colors hover:text-foreground">
|
className="underline underline-offset-4 transition-colors hover:text-foreground"
|
||||||
|
>
|
||||||
Datenschutz
|
Datenschutz
|
||||||
</a>
|
</a>
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</div>
|
||||||
</motion.div>
|
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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="/"
|
||||||
|
className="text-sm font-semibold tracking-tight text-foreground"
|
||||||
|
>
|
||||||
|
Matthias Meister Webdesign
|
||||||
|
</a>
|
||||||
|
<nav
|
||||||
|
aria-label="Direkte Kontaktwege"
|
||||||
|
className="flex flex-wrap items-center gap-x-5 gap-y-2 text-sm text-muted-foreground"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="tel:037627984400"
|
||||||
|
className="inline-flex items-center gap-1.5 transition-colors hover:text-foreground"
|
||||||
|
>
|
||||||
|
<Phone className="size-3.5" aria-hidden />
|
||||||
|
03762 798 4400
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="mailto:info@matthias-meister-webdesign.de"
|
||||||
|
className="inline-flex items-center gap-1.5 transition-colors hover:text-foreground"
|
||||||
|
>
|
||||||
|
<Mail className="size-3.5" aria-hidden />
|
||||||
|
E-Mail
|
||||||
|
</a>
|
||||||
|
<span className="inline-flex items-center gap-1.5">
|
||||||
|
<MapPin className="size-3.5" aria-hidden />
|
||||||
|
Crimmitschau
|
||||||
|
</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>
|
||||||
<p className="text-sm text-muted-foreground">
|
<h1 className="max-w-[12ch] text-5xl font-semibold leading-[0.95] tracking-tight text-balance text-foreground sm:text-6xl lg:text-7xl">
|
||||||
Rückmeldung innerhalb von 24 Stunden
|
Websites, die vor Ort Vertrauen schaffen.
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex max-w-4xl flex-col gap-7">
|
|
||||||
<h1 className="max-w-[13ch] text-4xl font-semibold tracking-tight text-balance text-foreground sm:text-5xl lg:text-6xl">
|
|
||||||
Websites für Unternehmen aus der Region - klar, schnell und
|
|
||||||
glaubwürdig.
|
|
||||||
</h1>
|
</h1>
|
||||||
<p className="max-w-[65ch] text-base leading-7 text-muted-foreground sm:text-lg">
|
<p className="max-w-[62ch] text-lg leading-8 text-muted-foreground">
|
||||||
Ich arbeite direkt für Handwerk, Praxen und kleine Betriebe aus
|
Für Handwerk, Praxen und kleine Betriebe: klar erklärt, schnell
|
||||||
der Region. Ohne Baukasten-Look, Agenturshow oder technischen
|
gebaut und so strukturiert, dass Besucher ohne Umwege verstehen,
|
||||||
Umweg - sondern mit einer Website, die Ihr Angebot klar zeigt und
|
warum sie gerade Sie anfragen sollten.
|
||||||
Anfragen leichter macht.
|
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-wrap items-center gap-4 pt-1">
|
<div className="flex flex-col gap-3 pt-1 sm:flex-row sm:items-center">
|
||||||
<Button asChild size="lg" className="h-11 rounded-full px-5">
|
<Button asChild size="lg" className="h-11 rounded-md px-5">
|
||||||
<a href="#kontakt">
|
<a href="#kontakt">
|
||||||
Projekt anfragen
|
Projekt anfragen
|
||||||
<ArrowRight className="shrink-0" aria-hidden />
|
<ArrowRight className="shrink-0" aria-hidden />
|
||||||
</a>
|
</a>
|
||||||
</Button>
|
</Button>
|
||||||
<p className="text-sm text-muted-foreground">
|
<Button
|
||||||
Kurze Nachricht reicht - Sie erhalten direkt eine erste
|
asChild
|
||||||
Einschätzung und den passenden nächsten Schritt.
|
size="lg"
|
||||||
</p>
|
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>
|
||||||
|
|||||||
@@ -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 Pricing4 = ({
|
const developmentPlans: PricingPlan[] = [
|
||||||
title = "Entwicklungspakete",
|
|
||||||
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.",
|
|
||||||
plans = [
|
|
||||||
{
|
{
|
||||||
name: "BASIS",
|
name: "Basis",
|
||||||
badge: "799 €",
|
|
||||||
price: "799 €",
|
price: "799 €",
|
||||||
|
period: "Einmalpreis",
|
||||||
|
description: "Für einen klaren Webauftritt mit den wichtigsten Inhalten.",
|
||||||
features: [
|
features: [
|
||||||
"Eine Seite, fünf Sektionen",
|
"Eine Seite mit fünf Sektionen",
|
||||||
"Kontaktformular",
|
"Kontaktformular",
|
||||||
"Impressum & Datenschutz",
|
"Impressum und Datenschutz",
|
||||||
"Mobilfreundlich & für Google optimiert",
|
"Mobilfreundlich und für Google vorbereitet",
|
||||||
"DSGVO-konformes Kontaktformular",
|
"Cookiefreie Analytics ohne Banner",
|
||||||
"Cookiefreies Analytics — ohne Abmahnrisiko",
|
|
||||||
],
|
],
|
||||||
buttonText: "Kostenloses Angebot anfordern",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "PROFI",
|
name: "Profi",
|
||||||
badge: "1.499 € ⭐ Empfehlung",
|
|
||||||
price: "1.499 €",
|
price: "1.499 €",
|
||||||
|
period: "Einmalpreis",
|
||||||
|
description: "Für Betriebe, die mehrere Leistungen sauber erklären wollen.",
|
||||||
features: [
|
features: [
|
||||||
"Bis zu 5 Unterseiten",
|
"Bis zu fünf Unterseiten",
|
||||||
"Google Maps Integration",
|
"Google Maps Integration",
|
||||||
"SEO-Basis (bessere Auffindbarkeit bei Google)",
|
"SEO-Basis für lokale Auffindbarkeit",
|
||||||
"Optionaler Blog",
|
"Optionaler Blog",
|
||||||
"DSGVO-konformes Kontaktformular",
|
|
||||||
"Cookiefreies Analytics — ohne Abmahnrisiko",
|
|
||||||
"Alles aus Basis inklusive",
|
"Alles aus Basis inklusive",
|
||||||
],
|
],
|
||||||
buttonText: "Kostenloses Angebot anfordern",
|
|
||||||
isPopular: true,
|
isPopular: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "MASSARBEIT",
|
name: "Maßarbeit",
|
||||||
badge: "2.499 €",
|
|
||||||
price: "2.499 €",
|
price: "2.499 €",
|
||||||
|
period: "Einmalpreis",
|
||||||
|
description: "Für individuelle Anforderungen, CMS und spätere Erweiterungen.",
|
||||||
features: [
|
features: [
|
||||||
"Individuelles Design nach Ihren Wünschen",
|
"Individuelles Design nach Ihren Anforderungen",
|
||||||
"CMS — Sie pflegen Inhalte selbst",
|
"CMS zur eigenen Inhaltspflege",
|
||||||
"DSGVO-konformes Kontaktformular",
|
"Erweiterbare Struktur",
|
||||||
"Cookiefreies Analytics — ohne Abmahnrisiko",
|
|
||||||
"Alles aus Profi inklusive",
|
"Alles aus Profi inklusive",
|
||||||
],
|
],
|
||||||
buttonText: "Kostenloses Angebot anfordern",
|
|
||||||
},
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
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 = ({
|
||||||
|
title = "Pakete mit klarer Kante.",
|
||||||
|
description =
|
||||||
|
"Die Preise sind bewusst nachvollziehbar gehalten. Im Gespräch klären wir, welches Paket passt und wo ein schlankerer Weg sinnvoller ist.",
|
||||||
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">
|
>
|
||||||
|
<div className="mx-auto max-w-6xl">
|
||||||
|
<div className="grid gap-8 lg:grid-cols-[minmax(0,0.8fr)_minmax(0,1.4fr)] lg:items-end lg:gap-16">
|
||||||
|
<div className="max-w-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}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="flex flex-col justify-between gap-10 md:flex-row">
|
<p className="mt-5 text-base leading-7 text-muted-foreground">
|
||||||
<p className="max-w-3xl text-muted-foreground lg:text-xl">
|
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
<Tabs
|
<Tabs
|
||||||
value={isMonthly ? "monthly" : "yearly"}
|
value={activeTab}
|
||||||
onValueChange={(value: string) =>
|
onValueChange={(value: string) =>
|
||||||
setIsMonthly(value === "monthly")
|
setActiveTab(value as "development" | "service")
|
||||||
}
|
}
|
||||||
className="w-fit shrink-0"
|
className="w-fit lg:justify-self-end"
|
||||||
aria-label="Leistungsvariante"
|
aria-label="Paketart auswählen"
|
||||||
>
|
>
|
||||||
<TabsList className="grid h-11 w-max grid-cols-2 gap-0 rounded-md p-1 text-lg">
|
<TabsList className="grid h-11 w-full grid-cols-2 rounded-md border border-border bg-card p-1 sm:w-max">
|
||||||
<TabsTrigger
|
<TabsTrigger
|
||||||
value="monthly"
|
value="development"
|
||||||
className="h-full min-h-0 px-7 py-0 font-semibold text-muted-foreground data-active:text-foreground"
|
className="h-full min-h-0 px-5 py-0 text-sm font-semibold data-active:bg-primary data-active:text-primary-foreground"
|
||||||
>
|
>
|
||||||
Entwicklung
|
Entwicklung
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger
|
<TabsTrigger
|
||||||
value="yearly"
|
value="service"
|
||||||
className="h-full min-h-0 px-7 py-0 font-semibold text-muted-foreground data-active:text-foreground"
|
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
|
Hosting & Wartung
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full flex-col items-stretch gap-6 md:flex-row">
|
|
||||||
{isMonthly ? (
|
<div className="mt-10 grid gap-4 lg:grid-cols-3">
|
||||||
plans.map((plan) => (
|
{plans.map((plan) => (
|
||||||
<div
|
<article
|
||||||
key={plan.name}
|
key={plan.name}
|
||||||
className={`flex w-full flex-col rounded-xl border shadow-sm p-6 text-left ${
|
className={cn(
|
||||||
plan.isPopular ? "bg-muted" : ""
|
"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",
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<Badge className="mb-8 block w-fit uppercase">
|
<div className="flex items-start justify-between gap-4">
|
||||||
{plan.badge}
|
<div>
|
||||||
</Badge>
|
<h3 className="text-xl font-semibold tracking-tight">
|
||||||
<h3 className="font-mono text-4xl lg:text-5xl">
|
{plan.name}
|
||||||
{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}
|
|
||||||
</h3>
|
|
||||||
<p className="text-muted-foreground">Monatlicher Preis</p>
|
|
||||||
<Separator className="my-6" />
|
|
||||||
<div className="flex h-full flex-col justify-between gap-20">
|
|
||||||
<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">Kostenloses Angebot anfordern</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-8">
|
||||||
|
<p className="text-4xl font-semibold tracking-tight">
|
||||||
|
{plan.price}
|
||||||
|
</p>
|
||||||
|
<p className="mt-1 text-sm text-muted-foreground">
|
||||||
|
{plan.period}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul className="mt-8 flex-1 space-y-4 text-sm leading-6 text-muted-foreground">
|
||||||
|
{plan.features.map((feature) => (
|
||||||
|
<li key={feature} className="flex gap-3">
|
||||||
|
<Check
|
||||||
|
className="mt-0.5 size-4 shrink-0 text-primary"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<span>{feature}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -1,13 +1,5 @@
|
|||||||
---
|
---
|
||||||
import { About19 } from "@/components/about19";
|
import { CanvaLanding } from "@/components/canva-landing";
|
||||||
import { Contact21 } from "@/components/contact21";
|
|
||||||
import { Faq7 } from "@/components/faq7";
|
|
||||||
import { Feature284 } from "@/components/feature284";
|
|
||||||
import { Footer27 } from "@/components/footer27";
|
|
||||||
import { Hero235 } from "@/components/hero235";
|
|
||||||
import { Pricing4 } from "@/components/pricing4";
|
|
||||||
import { Stats11 } from "@/components/stats11";
|
|
||||||
import CTASection from "@/components/cta";
|
|
||||||
import "@/styles/global.css";
|
import "@/styles/global.css";
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -25,16 +17,6 @@ import "@/styles/global.css";
|
|||||||
defer></script>
|
defer></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="mx-auto w-full max-w-7xl">
|
<CanvaLanding />
|
||||||
<Hero235 client:load />
|
|
||||||
<CTASection client:load />
|
|
||||||
<Feature284 client:load />
|
|
||||||
<Stats11 client:load />
|
|
||||||
<Pricing4 client:load />
|
|
||||||
<Faq7 client:load />
|
|
||||||
<About19 client:load />
|
|
||||||
<Contact21 client:load />
|
|
||||||
<Footer27 client:load />
|
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -49,38 +49,38 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--background: oklch(1 0 0);
|
--background: oklch(0.115 0.012 22);
|
||||||
--foreground: oklch(0.145 0 0);
|
--foreground: oklch(0.965 0.013 76);
|
||||||
--card: oklch(1 0 0);
|
--card: oklch(0.16 0.014 22);
|
||||||
--card-foreground: oklch(0.145 0 0);
|
--card-foreground: oklch(0.965 0.013 76);
|
||||||
--popover: oklch(1 0 0);
|
--popover: oklch(0.16 0.014 22);
|
||||||
--popover-foreground: oklch(0.145 0 0);
|
--popover-foreground: oklch(0.965 0.013 76);
|
||||||
--primary: oklch(0.205 0 0);
|
--primary: oklch(0.61 0.235 27);
|
||||||
--primary-foreground: oklch(0.985 0 0);
|
--primary-foreground: oklch(0.985 0.01 76);
|
||||||
--secondary: oklch(0.97 0 0);
|
--secondary: oklch(0.22 0.016 22);
|
||||||
--secondary-foreground: oklch(0.205 0 0);
|
--secondary-foreground: oklch(0.965 0.013 76);
|
||||||
--muted: oklch(0.97 0 0);
|
--muted: oklch(0.19 0.014 22);
|
||||||
--muted-foreground: oklch(0.556 0 0);
|
--muted-foreground: oklch(0.73 0.021 76);
|
||||||
--accent: oklch(0.97 0 0);
|
--accent: oklch(0.61 0.235 27);
|
||||||
--accent-foreground: oklch(0.205 0 0);
|
--accent-foreground: oklch(0.985 0.01 76);
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
--destructive: oklch(0.577 0.245 27.325);
|
||||||
--border: oklch(0.922 0 0);
|
--border: oklch(0.965 0.013 76 / 20%);
|
||||||
--input: oklch(0.922 0 0);
|
--input: oklch(0.965 0.013 76 / 20%);
|
||||||
--ring: oklch(0.708 0 0);
|
--ring: oklch(0.61 0.235 27);
|
||||||
--chart-1: oklch(0.87 0 0);
|
--chart-1: oklch(0.87 0 0);
|
||||||
--chart-2: oklch(0.556 0 0);
|
--chart-2: oklch(0.556 0 0);
|
||||||
--chart-3: oklch(0.439 0 0);
|
--chart-3: oklch(0.439 0 0);
|
||||||
--chart-4: oklch(0.371 0 0);
|
--chart-4: oklch(0.371 0 0);
|
||||||
--chart-5: oklch(0.269 0 0);
|
--chart-5: oklch(0.269 0 0);
|
||||||
--radius: 0.625rem;
|
--radius: 0.625rem;
|
||||||
--sidebar: oklch(0.985 0 0);
|
--sidebar: oklch(0.16 0.014 22);
|
||||||
--sidebar-foreground: oklch(0.145 0 0);
|
--sidebar-foreground: oklch(0.965 0.013 76);
|
||||||
--sidebar-primary: oklch(0.205 0 0);
|
--sidebar-primary: oklch(0.61 0.235 27);
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
--sidebar-primary-foreground: oklch(0.985 0.01 76);
|
||||||
--sidebar-accent: oklch(0.97 0 0);
|
--sidebar-accent: oklch(0.22 0.016 22);
|
||||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
--sidebar-accent-foreground: oklch(0.965 0.013 76);
|
||||||
--sidebar-border: oklch(0.922 0 0);
|
--sidebar-border: oklch(0.965 0.013 76 / 20%);
|
||||||
--sidebar-ring: oklch(0.708 0 0);
|
--sidebar-ring: oklch(0.61 0.235 27);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
@@ -123,8 +123,10 @@
|
|||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
|
text-rendering: geometricPrecision;
|
||||||
}
|
}
|
||||||
html {
|
html {
|
||||||
@apply font-sans;
|
@apply font-sans;
|
||||||
|
scroll-behavior: smooth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
32
tests/landing-content.test.mjs
Normal file
32
tests/landing-content.test.mjs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { readFile } from "node:fs/promises";
|
||||||
|
import test from "node:test";
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
|
||||||
|
const componentPath = new URL("../src/components/canva-landing.tsx", import.meta.url);
|
||||||
|
|
||||||
|
test("Canva landing component contains the core brief anchors", async () => {
|
||||||
|
const source = await readFile(componentPath, "utf8");
|
||||||
|
|
||||||
|
for (const phrase of ["Projektbrief", "01", "Website", "Kontakt", "für", "müssen", "Änderungen"]) {
|
||||||
|
assert.match(source, new RegExp(phrase));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Canva landing component uses real German umlauts in visible copy", async () => {
|
||||||
|
const source = await readFile(componentPath, "utf8");
|
||||||
|
|
||||||
|
for (const asciiFallback of [
|
||||||
|
"fuer",
|
||||||
|
"muessen",
|
||||||
|
"spaetere",
|
||||||
|
"Aenderungen",
|
||||||
|
"glaubwuerdig",
|
||||||
|
"naechste",
|
||||||
|
"Agenturlaerm",
|
||||||
|
"Erzaehlen",
|
||||||
|
"Saetze",
|
||||||
|
"Rueckmeldung",
|
||||||
|
]) {
|
||||||
|
assert.doesNotMatch(source, new RegExp(asciiFallback));
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user