feat: complete zweitmeinu.ng frontend implementation

Full medical second opinion website with:
- 10 routes: home, fachbereiche (overview + 6 detail), faq, so-funktionierts,
  motivation, ueber-uns, kontakt, impressum, datenschutz
- Premium medical design: navy/blue/gold color system, Roboto Condensed
- Layout: TopBar, sticky Header with mega-menu, EmergencyBanner, Footer
- Service detail pages with benefits, checklist, stats, CTA
- FAQ page with search, category filter, accordion, Schema.org structured data
- Contact form with validation and Payload CMS form submission
- @c2s/payload-contracts integration for type-safe API access
- Tailwind CSS v4 design system with custom animations
- PM2 ecosystem config on port 3002

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
CCS Admin 2026-02-21 00:28:52 +00:00
parent 2d38b69d02
commit 69eb87edae
45 changed files with 7113 additions and 2 deletions

41
.gitignore vendored Normal file
View file

@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

View file

@ -1,2 +1,36 @@
# frontend.zweitmeinu.ng This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
Frontend for zweitmeinu.ng - Next.js with Payload CMS
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.

12
ecosystem.config.js Normal file
View file

@ -0,0 +1,12 @@
module.exports = {
apps: [{
name: 'zweitmeinu.ng',
script: 'node_modules/.bin/next',
args: 'start',
cwd: '/home/frontend/frontend.zweitmeinu.ng',
env: {
PORT: 3002,
NODE_ENV: 'production',
},
}],
}

18
eslint.config.mjs Normal file
View file

@ -0,0 +1,18 @@
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
const eslintConfig = defineConfig([
...nextVitals,
...nextTs,
// Override default ignores of eslint-config-next.
globalIgnores([
// Default ignores of eslint-config-next:
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
]),
]);
export default eslintConfig;

13
next.config.ts Normal file
View file

@ -0,0 +1,13 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
transpilePackages: ["@c2s/payload-contracts"],
images: {
remotePatterns: [
{ hostname: "cms.c2sgmbh.de" },
{ hostname: "pl.porwoll.tech" },
],
},
};
export default nextConfig;

31
package.json Normal file
View file

@ -0,0 +1,31 @@
{
"name": "frontend.zweitmeinu.ng",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint"
},
"dependencies": {
"@c2s/payload-contracts": "github:complexcaresolutions/payload-contracts",
"clsx": "^2.1.1",
"lucide-react": "^0.474.0",
"next": "16.1.6",
"react": "19.2.1",
"react-dom": "19.2.1",
"tailwind-merge": "^3.4.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
"@types/node": "^22",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "16.1.6",
"tailwindcss": "^4",
"typescript": "^5"
},
"packageManager": "pnpm@10.6.2"
}

4053
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load diff

2
pnpm-workspace.yaml Normal file
View file

@ -0,0 +1,2 @@
onlyBuiltDependencies:
- "@c2s/payload-contracts"

7
postcss.config.mjs Normal file
View file

@ -0,0 +1,7 @@
const config = {
plugins: {
"@tailwindcss/postcss": {},
},
};
export default config;

1
public/file.svg Normal file
View file

@ -0,0 +1 @@
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 391 B

1
public/globe.svg Normal file
View file

@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 1 KiB

1
public/next.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

1
public/vercel.svg Normal file
View file

@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 128 B

1
public/window.svg Normal file
View file

@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>

After

Width:  |  Height:  |  Size: 385 B

View file

@ -0,0 +1,27 @@
import type { Metadata } from "next"
import { Container } from "@/components/ui/Container"
export const metadata: Metadata = {
title: "Datenschutzerklärung",
description: "DSGVO-konforme Datenschutzerklärung von zweitmeinu.ng.",
}
export default function DatenschutzPage() {
return (
<section className="py-20 bg-white">
<Container size="md">
<h1 className="text-3xl font-bold text-navy mb-8">
Datenschutzerklärung
</h1>
<div className="rounded-2xl overflow-hidden border border-border">
<iframe
src="https://app.alfright.eu/ext/dps/alfright_schutzteam/9f315103c43245bcb0806dd56c2be757?lang=de-de&headercolor=%23131F64&headerfont=Arial&headersize=21px&subheadersize=18px&fontcolor=%23333333&textfont=Arial&textsize=14px&background=%23ffffff&linkcolor=%23337ab7"
title="Datenschutzerklärung"
className="w-full min-h-[800px] border-0"
loading="lazy"
/>
</div>
</Container>
</section>
)
}

View file

@ -0,0 +1,357 @@
import type { Metadata } from "next"
import { notFound } from "next/navigation"
import { Container } from "@/components/ui/Container"
import { Button } from "@/components/ui/Button"
import {
Phone, Mail, ArrowRight, Check, Heart, Brain,
Stethoscope, Pill, FlaskConical, Activity, Shield,
FileCheck, Users, Clock, HeartHandshake,
} from "lucide-react"
// Static service data — will be enhanced with CMS data when populated
const servicesData: Record<string, {
title: string
fullTitle: string
category: string
icon: React.ElementType
shortDescription: string
description: string[]
benefits: Array<{ title: string; description: string; icon: React.ElementType }>
checklist: Array<{ title: string; description: string }>
seo: { title: string; description: string }
}> = {
"zweitmeinung-intensivmedizin": {
title: "Intensivmedizin",
fullTitle: "Zweitmeinung Intensivmedizin",
category: "Notfall",
icon: Activity,
shortDescription: "Unabhängige ärztliche Zweitmeinung bei laufender oder geplanter Intensivbehandlung. Wir prüfen medizinische Indikation, Patientenwille und Behandlungsalternativen.",
description: [
"Wenn intensivmedizinische Entscheidungen anstehen, brauchen Patient:innen und ihre Angehörigen mehr als nur medizinische Information sie brauchen Orientierung, Sicherheit und eine unabhängige fachliche Einschätzung.",
"Unsere Dienstleistung richtet sich an Menschen in sehr schwerer gesundheitlicher Lage, etwa bei Langzeitbeatmung, Wachkoma oder im palliativen Kontext.",
],
benefits: [
{ title: "Unabhängige Beurteilung durch erfahrene Intensivmediziner", description: "Neutrale Einschätzung ohne wirtschaftliche Eigeninteressen ausschließlich in Ihrem Interesse.", icon: Shield },
{ title: "Vermeidung unnötiger Eingriffe bei fehlender Indikation", description: "Schutz vor überflüssigen invasiven Behandlungen durch fundierte medizinische Bewertung.", icon: FileCheck },
{ title: "Besseres Verständnis der Risiken und Therapieziele", description: "Umfassende Aufklärung über alle Behandlungsoptionen und deren Auswirkungen.", icon: Users },
{ title: "Stärkung Ihrer Entscheidungssicherheit und Eigenverantwortung", description: "Fundierte Basis für selbstbestimmte Entscheidungen über Ihre Versorgung.", icon: HeartHandshake },
],
checklist: [
{ title: "Strukturierte Beratung durch erfahrene Case Manager:innen", description: "Individuelle Betreuung für Ihre weitere Behandlungsplanung." },
{ title: "Unabhängige ärztliche Zweitmeinung inkl. schriftlichem Gutachten", description: "Fundierte Einschätzung durch Fachärzt:innen für Intensivmedizin." },
{ title: "Bewertung von Therapiezielen, Indikationen und Prognose", description: "Umfassende Analyse aller relevanten medizinischen Aspekte." },
{ title: "Unterstützung bei palliativer Umsteuerung bis Pflegeüberleitung", description: "Begleitung bei der Umsetzung der empfohlenen Maßnahmen." },
],
seo: { title: "Zweitmeinung Intensivmedizin", description: "Unabhängige ärztliche Zweitmeinung bei intensivmedizinischer Behandlung. Jetzt fundierte Empfehlung einholen." },
},
"zweitmeinung-kardiologie": {
title: "Kardiologie",
fullTitle: "Zweitmeinung Kardiologie",
category: "Beratung",
icon: Heart,
shortDescription: "Unabhängige ärztliche Zweitmeinung vor Herzkatheter, Stent oder OP. Fundierte Empfehlung durch erfahrene Kardiolog:innen verständlich, sicher, neutral.",
description: [
"Herzbeschwerden verunsichern und geplante Eingriffe wie eine Stent-Implantation werfen viele Fragen auf.",
"Unsere kardiologische Zweitmeinung gibt Ihnen Klarheit und Sicherheit bei wichtigen Herzentscheidungen.",
],
benefits: [
{ title: "Unabhängige Beurteilung durch erfahrene Kardiolog:innen", description: "Neutrale Einschätzung Ihrer kardiologischen Befunde ohne wirtschaftliche Interessen.", icon: Shield },
{ title: "Vermeidung unnötiger Eingriffe bei fehlender Indikation", description: "Schutz vor überflüssigen invasiven Behandlungen durch fundierte Bewertung.", icon: FileCheck },
{ title: "Besseres Verständnis der Risiken und Therapieziele", description: "Umfassende Aufklärung über alle Behandlungsoptionen.", icon: Users },
{ title: "Stärkung Ihrer Entscheidungssicherheit", description: "Fundierte Basis für selbstbestimmte Entscheidungen über Ihre Herzgesundheit.", icon: HeartHandshake },
],
checklist: [
{ title: "Bewertung Ihrer Diagnosen und EKG-/Katheterbefunde", description: "Sach- und leitliniengerechte Beurteilung durch Fachärzt:innen." },
{ title: "Zweitmeinung bei geplanter PCI, Bypass-OP oder Umstellung", description: "Unabhängige Einschätzung aller kardiologischen Behandlungsoptionen." },
{ title: "Schriftliches ärztliches Gutachten mit klarer Empfehlung", description: "Nachvollziehbare, fundierte Empfehlung für Ihre Entscheidung." },
{ title: "Persönliche Erläuterung telefonisch oder per Video", description: "Direkter Austausch mit unseren Kardiologie-Expert:innen." },
],
seo: { title: "Zweitmeinung Kardiologie", description: "Unabhängige Zweitmeinung bei geplanter PCI oder Herzoperation." },
},
"zweitmeinung-onkologie": {
title: "Onkologie",
fullTitle: "Zweitmeinung Onkologie",
category: "Beratung",
icon: FlaskConical,
shortDescription: "Unabhängige ärztliche Zweitmeinung bei Krebs. Fundierte Einschätzung von Therapieoptionen durch erfahrene Onkolog:innen.",
description: [
"Eine Krebsdiagnose ist ein Einschnitt. Neben der seelischen Belastung stellt sich oft die Frage: Ist die empfohlene Behandlung wirklich die beste Wahl?",
"Unsere onkologische Zweitmeinung hilft Ihnen, die richtige Therapieentscheidung zu treffen.",
],
benefits: [
{ title: "Unabhängige Beurteilung durch erfahrene Onkolog:innen", description: "Neutrale Einschätzung Ihrer Krebsdiagnose und Therapieoptionen.", icon: Shield },
{ title: "Bewertung von Wirksamkeit und Nebenwirkungen", description: "Transparente Analyse aller Behandlungswege und deren Auswirkungen auf Ihre Lebensqualität.", icon: FileCheck },
{ title: "Einbindung von Case Management und Palliativberatung", description: "Ganzheitliche Betreuung über die reine Diagnose hinaus.", icon: Users },
{ title: "Stärkung Ihrer Entscheidungssicherheit", description: "Fundierte Grundlage für informierte Therapieentscheidungen.", icon: HeartHandshake },
],
checklist: [
{ title: "Auswertung Ihrer Diagnose und Befunde", description: "Umfassende Prüfung durch erfahrene Fachärzt:innen." },
{ title: "Bewertung der geplanten Therapie", description: "Analyse von Wirksamkeit, Nebenwirkungen und Lebensqualität." },
{ title: "Schriftliches Zweitmeinungsgutachten", description: "Nachvollziehbare, medizinisch fundierte Empfehlung." },
{ title: "Optionales Gespräch per Telefon oder Video", description: "Persönliche Erläuterung und Beantwortung Ihrer Fragen." },
],
seo: { title: "Zweitmeinung Onkologie", description: "Unabhängige ärztliche Zweitmeinung bei Krebs. Behandlungsalternativen prüfen." },
},
"zweitmeinung-nephrologie": {
title: "Nephrologie",
fullTitle: "Zweitmeinung Nephrologie",
category: "Beratung",
icon: Stethoscope,
shortDescription: "Unabhängige ärztliche Einschätzung bei Nierenerkrankungen, Dialyseempfehlung oder Transplantationsvorbereitung.",
description: [
"Die Diagnose einer chronischen Nierenerkrankung oder die Empfehlung zur Dialyse ist ein gravierender Einschnitt.",
"Unsere nephrologische Zweitmeinung gibt Ihnen Klarheit bei wichtigen Nierenentscheidungen.",
],
benefits: [
{ title: "Unabhängige Beurteilung durch erfahrene Nephrolog:innen", description: "Neutrale Einschätzung Ihrer nephrologischen Diagnostik.", icon: Shield },
{ title: "Prüfung der Dialyse-Notwendigkeit", description: "Bewertung ob und wann eine Dialyse tatsächlich erforderlich ist.", icon: FileCheck },
{ title: "Bewertung konservativer Behandlungsoptionen", description: "Prüfung alternativer Therapiewege vor invasiven Maßnahmen.", icon: Users },
{ title: "Stärkung Ihrer Entscheidungssicherheit", description: "Fundierte Basis für selbstbestimmte Entscheidungen.", icon: HeartHandshake },
],
checklist: [
{ title: "Prüfung der nephrologischen Diagnostik und Laborwerte", description: "Umfassende Analyse Ihrer Nierenfunktionswerte." },
{ title: "Zweitmeinung durch Fachärzt:innen für Nephrologie", description: "Unabhängige Einschätzung erfahrener Spezialist:innen." },
{ title: "Gutachten zur Notwendigkeit einer Dialyse", description: "Klare Empfehlung zum Zeitpunkt und zur Art der Behandlung." },
{ title: "Bewertung konservativer Behandlungsoptionen", description: "Prüfung aller verfügbaren Therapiealternativen." },
],
seo: { title: "Zweitmeinung Nephrologie", description: "Unabhängige ärztliche Zweitmeinung bei chronischer Niereninsuffizienz oder Dialyseempfehlung." },
},
"zweitmeinung-gallenblase": {
title: "Gallenblase",
fullTitle: "Zweitmeinung Gallenblase",
category: "Beratung",
icon: Pill,
shortDescription: "Unabhängige ärztliche Zweitmeinung vor einer geplanten Gallenblasen-OP. Wir prüfen, ob der Eingriff medizinisch notwendig ist.",
description: [
"Viele Menschen erhalten bei Gallensteinen die Empfehlung, die Gallenblase entfernen zu lassen. Doch nicht in jedem Fall ist eine Operation notwendig.",
"Unsere Zweitmeinung hilft Ihnen, unnötige Operationen zu vermeiden.",
],
benefits: [
{ title: "Unabhängige Beurteilung durch erfahrene Chirurg:innen", description: "Neutrale Einschätzung der OP-Indikation.", icon: Shield },
{ title: "Vermeidung unnötiger Operationen", description: "Schutz vor überflüssigen Eingriffen durch fundierte Bewertung.", icon: FileCheck },
{ title: "Aufklärung über konservative Alternativen", description: "Information über nicht-operative Behandlungsmöglichkeiten.", icon: Users },
{ title: "Verständliche Erklärung der Befunde", description: "Klare Darstellung von Risiken und Nutzen.", icon: HeartHandshake },
],
checklist: [
{ title: "Bewertung Ihrer Beschwerden und Untersuchungsergebnisse", description: "Sorgfältige Analyse aller vorliegenden Befunde." },
{ title: "Prüfung der OP-Indikation nach medizinischen Leitlinien", description: "Leitlinienbasierte Bewertung der Operationsnotwendigkeit." },
{ title: "Zweitmeinung durch Viszeralchirurg:innen oder Gastroenterolog:innen", description: "Unabhängige Einschätzung spezialisierter Fachärzt:innen." },
{ title: "Verständliches Gutachten mit klarer Empfehlung", description: "Nachvollziehbare Entscheidungsgrundlage." },
],
seo: { title: "Zweitmeinung Gallenblase", description: "Gallenblasen-OP empfohlen? Holen Sie sich eine unabhängige Zweitmeinung." },
},
"zweitmeinung-schilddruese": {
title: "Schilddrüse",
fullTitle: "Zweitmeinung Schilddrüse",
category: "Beratung",
icon: Brain,
shortDescription: "Unabhängige ärztliche Einschätzung vor einer geplanten Schilddrüsen-OP durch erfahrene Endokrinolog:innen.",
description: [
"Die Empfehlung zur Entfernung der Schilddrüse ist für viele Menschen mit Sorgen verbunden. Doch ist eine Operation wirklich notwendig?",
"Unsere Zweitmeinung gibt Ihnen Sicherheit bei der Entscheidung.",
],
benefits: [
{ title: "Unabhängige Beurteilung durch erfahrene Endokrinolog:innen", description: "Neutrale Einschätzung Ihrer Schilddrüsenbefunde.", icon: Shield },
{ title: "Prüfung der OP-Notwendigkeit", description: "Bewertung ob eine Operation tatsächlich indiziert ist.", icon: FileCheck },
{ title: "Bewertung konservativer Alternativen", description: "Prüfung medikamentöser oder abwartender Therapieoptionen.", icon: Users },
{ title: "Verständliche Erklärung der Befunde", description: "Nachvollziehbare Darstellung aller Optionen.", icon: HeartHandshake },
],
checklist: [
{ title: "Prüfung von Ultraschallbefunden, Szintigrammen, Laborwerten", description: "Umfassende Analyse aller diagnostischen Ergebnisse." },
{ title: "Zweitmeinung durch Endokrinolog:innen oder Schilddrüsenchirurg:innen", description: "Spezialisierte Fachärzt:innen bewerten Ihren Fall." },
{ title: "Schriftliches Gutachten mit nachvollziehbarer Empfehlung", description: "Fundierte Entscheidungsgrundlage für Sie und Ihre Ärzt:innen." },
{ title: "Bei Bedarf: telefonische Erläuterung", description: "Persönliches Gespräch zu allen offenen Fragen." },
],
seo: { title: "Zweitmeinung Schilddrüse", description: "Schilddrüsen-OP empfohlen? Lassen Sie die Notwendigkeit prüfen." },
},
}
const slugs = Object.keys(servicesData)
export async function generateStaticParams() {
return slugs.map((slug) => ({ slug }))
}
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>
}): Promise<Metadata> {
const { slug } = await params
const service = servicesData[slug]
if (!service) return {}
return { title: service.seo.title, description: service.seo.description }
}
export default async function ServiceDetailPage({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
const service = servicesData[slug]
if (!service) notFound()
const Icon = service.icon
return (
<>
{/* Hero */}
<section className="bg-gradient-to-br from-navy via-navy-dark to-[#001a2e] py-20 sm:py-28">
<Container size="md">
<div className="text-center">
<div className="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-white/10 text-white mb-6">
<Icon className="h-8 w-8" />
</div>
<p className="text-primary-light text-sm font-semibold mb-2">
Fachbereiche / {service.title}
</p>
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-bold text-white mb-6">
{service.fullTitle} klare Empfehlungen bei{" "}
{service.title === "Intensivmedizin"
? "kritischen Entscheidungen"
: `${service.title}-Entscheidungen`}
</h1>
<p className="text-white/60 text-lg max-w-2xl mx-auto mb-10">
{service.shortDescription}
</p>
<div className="flex flex-wrap justify-center gap-4">
<Button href="tel:+4980080441000" variant="gold" size="lg">
<Phone className="h-4 w-4" />
Jetzt beraten lassen
</Button>
<Button href="#was-wir-tun" variant="outline" size="lg">
Mehr erfahren
<ArrowRight className="h-4 w-4" />
</Button>
</div>
</div>
</Container>
</section>
{/* When is a second opinion useful */}
<section className="py-20 bg-white">
<Container size="md">
<h2 className="text-2xl sm:text-3xl font-bold text-center text-navy mb-4">
Wann ist eine Zweitmeinung sinnvoll?
</h2>
<p className="text-text-muted text-center max-w-2xl mx-auto mb-12">
{service.description[0]}
</p>
<div className="grid sm:grid-cols-2 gap-6">
{service.benefits.map((b) => (
<div
key={b.title}
className="bg-bg rounded-xl p-6 border border-border"
>
<b.icon className="h-6 w-6 text-primary mb-3" />
<h3 className="font-bold text-navy mb-2">{b.title}</h3>
<p className="text-sm text-text-muted">{b.description}</p>
</div>
))}
</div>
</Container>
</section>
{/* What we do for you */}
<section id="was-wir-tun" className="py-20 bg-bg">
<Container size="md">
<h2 className="text-2xl sm:text-3xl font-bold text-center text-navy mb-3">
Was wir für Sie tun
</h2>
<p className="text-text-muted text-center mb-12">
{service.description[1]}
</p>
<div className="space-y-4 max-w-2xl mx-auto">
{service.checklist.map((item) => (
<div
key={item.title}
className="flex gap-4 bg-white rounded-xl p-5 border border-border"
>
<div className="shrink-0 w-8 h-8 rounded-full bg-gold/20 flex items-center justify-center">
<Check className="h-4 w-4 text-gold-hover" />
</div>
<div>
<h3 className="font-bold text-navy">{item.title}</h3>
<p className="text-sm text-text-muted mt-1">
{item.description}
</p>
</div>
</div>
))}
</div>
</Container>
</section>
{/* Stats */}
<section className="py-16 bg-white border-y border-border">
<Container size="md">
<h2 className="text-2xl font-bold text-center text-navy mb-3">
Warum complex care solutions?
</h2>
<p className="text-text-muted text-center max-w-2xl mx-auto mb-12">
Unsere Expert:innen arbeiten unabhängig und ausschließlich im
Interesse der Patient:innen.
</p>
<div className="grid grid-cols-3 gap-8 text-center">
<div>
<div className="text-4xl sm:text-5xl font-bold text-gold">
500+
</div>
<p className="text-sm text-text-muted mt-1">
{service.title === "Kardiologie"
? "Kardiologische Zweitmeinungen"
: "Medizinische Zweitmeinungen"}
</p>
</div>
<div>
<div className="text-4xl sm:text-5xl font-bold text-gold">
15+
</div>
<p className="text-sm text-text-muted mt-1">Jahre Erfahrung</p>
</div>
<div>
<div className="text-4xl sm:text-5xl font-bold text-gold">
95%
</div>
<p className="text-sm text-text-muted mt-1">
Patientenzufriedenheit
</p>
</div>
</div>
</Container>
</section>
{/* CTA */}
<section className="py-20 bg-bg">
<Container size="sm">
<div className="bg-white rounded-2xl p-8 sm:p-12 text-center border border-border shadow-sm">
<div className="inline-flex items-center justify-center w-14 h-14 rounded-2xl bg-primary/10 text-primary mb-6">
<Icon className="h-7 w-7" />
</div>
<h2 className="text-2xl sm:text-3xl font-bold text-navy mb-4">
Bereit für Ihre {service.title === "Intensivmedizin" ? "intensivmedizinische" : service.title.toLowerCase() === "gallenblase" ? "Gallenblasen-" : service.title.toLowerCase() === "schilddrüse" ? "Schilddrüsen-" : `${service.title.toLowerCase()}ische`} Zweitmeinung?
</h2>
<p className="text-text-muted mb-8">
Kontaktieren Sie uns für eine unabhängige, professionelle
Einschätzung.
</p>
<div className="flex flex-wrap justify-center gap-4">
<Button href="tel:+4980080441000" variant="gold" size="lg">
<Phone className="h-4 w-4" />
+49 800 80 44 100
</Button>
<Button href="/kontakt" variant="secondary" size="lg">
<Mail className="h-4 w-4" />
E-Mail schreiben
</Button>
</div>
<p className="text-xs text-text-muted mt-4">
Kostenlos für gesetzlich und privat Versicherte
</p>
</div>
</Container>
</section>
</>
)
}

View file

@ -0,0 +1,142 @@
import type { Metadata } from "next"
import Link from "next/link"
import { ArrowRight, Heart, Brain, Stethoscope, Pill, FlaskConical, Activity } from "lucide-react"
import { Container } from "@/components/ui/Container"
import { Button } from "@/components/ui/Button"
import { Phone } from "lucide-react"
export const metadata: Metadata = {
title: "Fachbereiche",
description: "Medizinische Zweitmeinung in 6 Fachbereichen: Intensivmedizin, Kardiologie, Onkologie, Nephrologie, Gallenblase und Schilddrüse.",
}
const services = [
{
title: "Intensivmedizin",
slug: "zweitmeinung-intensivmedizin",
description: "Unabhängige ärztliche Zweitmeinung bei laufender oder geplanter Intensivbehandlung. Wir prüfen medizinische Indikation, Patientenwille und Behandlungsalternativen.",
icon: Activity,
category: "Notfall",
color: "bg-red-50 text-red-600",
},
{
title: "Kardiologie",
slug: "zweitmeinung-kardiologie",
description: "Unabhängige ärztliche Zweitmeinung vor Herzkatheter, Stent oder OP. Fundierte Empfehlung durch erfahrene Kardiolog:innen.",
icon: Heart,
category: "Beratung",
color: "bg-primary/10 text-primary",
},
{
title: "Onkologie",
slug: "zweitmeinung-onkologie",
description: "Unabhängige ärztliche Zweitmeinung bei Krebs. Fundierte Einschätzung von Therapieoptionen durch erfahrene Onkolog:innen.",
icon: FlaskConical,
category: "Beratung",
color: "bg-purple-50 text-purple-600",
},
{
title: "Nephrologie",
slug: "zweitmeinung-nephrologie",
description: "Unabhängige ärztliche Einschätzung bei Nierenerkrankungen, Dialyseempfehlung oder Transplantationsvorbereitung.",
icon: Stethoscope,
category: "Beratung",
color: "bg-emerald-50 text-emerald-600",
},
{
title: "Gallenblase",
slug: "zweitmeinung-gallenblase",
description: "Unabhängige ärztliche Zweitmeinung vor einer geplanten Gallenblasen-OP. Ist der Eingriff wirklich notwendig?",
icon: Pill,
category: "Beratung",
color: "bg-amber-50 text-amber-600",
},
{
title: "Schilddrüse",
slug: "zweitmeinung-schilddruese",
description: "Unabhängige ärztliche Einschätzung vor einer geplanten Schilddrüsen-OP durch erfahrene Endokrinolog:innen.",
icon: Brain,
category: "Beratung",
color: "bg-cyan-50 text-cyan-600",
},
]
export default function FachbereichePage() {
return (
<>
{/* Hero */}
<section className="bg-gradient-to-br from-navy via-navy-dark to-[#001a2e] py-20">
<Container size="md">
<div className="text-center">
<p className="text-primary-light font-semibold text-sm uppercase tracking-wider mb-3">
Unsere Fachbereiche
</p>
<h1 className="text-4xl sm:text-5xl font-bold text-white mb-4">
Medizinische Zweitmeinung
</h1>
<p className="text-white/60 text-lg max-w-2xl mx-auto">
Unabhängige, fundierte Zweitmeinungen in 6 medizinischen
Fachbereichen. Von erfahrenen Fachärzt:innen verständlich,
neutral und sicher.
</p>
</div>
</Container>
</section>
{/* Service Grid */}
<section className="py-20 bg-bg">
<Container>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
{services.map((s) => (
<Link
key={s.slug}
href={`/fachbereiche/${s.slug}`}
className="group bg-white rounded-2xl p-8 border border-border hover:border-primary/30 hover:shadow-xl transition-all"
>
<div className="flex items-start justify-between mb-5">
<div className={`w-14 h-14 rounded-2xl ${s.color} flex items-center justify-center`}>
<s.icon className="h-7 w-7" />
</div>
{s.category === "Notfall" && (
<span className="text-xs font-bold bg-red-100 text-red-700 px-3 py-1 rounded-full">
24/7 Notfall
</span>
)}
</div>
<h2 className="text-xl font-bold mb-3 text-navy group-hover:text-primary transition-colors">
Zweitmeinung {s.title}
</h2>
<p className="text-text-muted text-sm leading-relaxed mb-6">
{s.description}
</p>
<span className="inline-flex items-center gap-1 text-sm font-semibold text-primary group-hover:gap-2 transition-all">
Mehr erfahren
<ArrowRight className="h-4 w-4" />
</span>
</Link>
))}
</div>
</Container>
</section>
{/* CTA */}
<section className="py-16 bg-white">
<Container size="md">
<div className="text-center">
<h2 className="text-2xl sm:text-3xl font-bold text-navy mb-4">
Sie wissen nicht, welcher Fachbereich?
</h2>
<p className="text-text-muted mb-8 max-w-xl mx-auto">
Rufen Sie uns an wir helfen Ihnen, den richtigen Fachbereich zu
finden und beraten Sie unverbindlich.
</p>
<Button href="tel:+4980080441000" variant="gold" size="lg">
<Phone className="h-4 w-4" />
0800 80 44 100 (kostenlos)
</Button>
</div>
</Container>
</section>
</>
)
}

119
src/app/faq/page.tsx Normal file
View file

@ -0,0 +1,119 @@
import type { Metadata } from "next"
import { Container } from "@/components/ui/Container"
import { Button } from "@/components/ui/Button"
import { FAQClient } from "@/components/faq/FAQClient"
import { Phone, Mail, HelpCircle } from "lucide-react"
export const metadata: Metadata = {
title: "FAQ - Häufige Fragen",
description:
"Antworten auf die wichtigsten Fragen zur medizinischen Zweitmeinung. Von Onkologie über Kardiologie bis Intensivmedizin.",
}
const categories = [
{ name: "Allgemeine Fragen", slug: "allgemein" },
{ name: "Intensivmedizin", slug: "intensivmedizin" },
{ name: "Kardiologie", slug: "kardiologie" },
{ name: "Onkologie", slug: "onkologie" },
{ name: "Nephrologie", slug: "nephrologie" },
{ name: "Gallenblase", slug: "gallenblase" },
{ name: "Schilddrüse", slug: "schilddruese" },
]
const faqData = [
{ question: "Was bringt mir eine Zweitmeinung bei Krebs?", answer: "Eine Zweitmeinung kann Ihnen Sicherheit geben - vor allem bei schweren Diagnosen oder belastenden Therapien. Sie hilft, Behandlungsoptionen besser zu verstehen, Alternativen zu erkennen und eine informierte Entscheidung zu treffen.", category: "allgemein" },
{ question: "Wie läuft das Zweitmeinungsverfahren ab?", answer: "Nach einem telefonischen Vorgespräch prüfen unsere Fachärzt:innen Ihre Unterlagen. Anschließend erhalten Sie ein schriftliches Gutachten mit einer klaren, medizinisch fundierten Empfehlung. Wenn gewünscht, besprechen wir das Ergebnis zusätzlich persönlich mit Ihnen.", category: "allgemein" },
{ question: "Muss ich alle meine Unterlagen selbst zusammensuchen?", answer: "Nein. Unsere Case Manager:innen unterstützen Sie bei der Zusammenstellung aller relevanten medizinischen Unterlagen. Sie müssen lediglich eine Schweigepflichtentbindung unterschreiben.", category: "allgemein" },
{ question: "Kann ich die Zweitmeinung auch einholen, wenn die Therapie schon begonnen hat?", answer: "Ja, auch während einer laufenden Behandlung kann eine Zweitmeinung sinnvoll sein - z.B. um die Therapie zu überprüfen oder Alternativen zu bewerten.", category: "allgemein" },
{ question: "Beeinflusst die Zweitmeinung meine Behandlung?", answer: "Die Zweitmeinung ist eine unabhängige Empfehlung. Sie ersetzt nicht die Behandlung durch Ihre Ärzt:innen, sondern ergänzt sie. Die Entscheidung liegt immer bei Ihnen.", category: "allgemein" },
{ question: "Wann ist eine Zweitmeinung vor einem Herzkatheter sinnvoll?", answer: "Immer dann, wenn ein planbarer Eingriff wie eine PCI (Stent) oder eine OP empfohlen wurde. Auch bei Unsicherheit über Nutzen und Risiken ist eine Zweitmeinung sinnvoll.", category: "kardiologie" },
{ question: "Wer erstellt die kardiologische Zweitmeinung?", answer: "Ausschließlich erfahrene, unabhängige Fachärzt:innen für Kardiologie mit Leitlinienkompetenz und klinischer Erfahrung.", category: "kardiologie" },
{ question: "Welche Unterlagen brauche ich für die kardiologische Zweitmeinung?", answer: "Ideal sind aktuelle Befunde, EKGs, Herzkatheterberichte, Medikamente und ggf. OP-Empfehlungen. Wir helfen Ihnen gern bei der Beschaffung und Sichtung der Unterlagen.", category: "kardiologie" },
{ question: "Muss ich den Eingriff absagen, wenn ich eine Zweitmeinung einhole?", answer: "Nein. Die Zweitmeinung führt Ihren bestehenden Entscheidungsprozess nicht in Verzug. Sie entscheiden selbst, wie Sie mit dem Ergebnis umgehen.", category: "kardiologie" },
{ question: "Was passiert, wenn die Einschätzung abweicht?", answer: "Dann besprechen wir mit Ihnen nachvollziehbar, warum das so ist - z.B. weil die Leitlinien einen anderen Weg vorsehen oder weil Ihre individuelle Situation neu bewertet wurde.", category: "kardiologie" },
{ question: "Wann ist eine Zweitmeinung zur Schilddrüsen-OP sinnvoll?", answer: "Bei empfohlener OP wegen Knoten, Struma, Autonomie oder unklarer Laborwerte.", category: "schilddruese" },
{ question: "Welche Unterlagen werden für die Schilddrüsen-Zweitmeinung benötigt?", answer: "Ultraschallbefunde, Szintigramme, Laborwerte (TSH, fT3, fT4) und ggf. Feinnadelpunktionsergebnisse.", category: "schilddruese" },
{ question: "Wer erstellt die Schilddrüsen-Zweitmeinung?", answer: "Fachärzt:innen für Endokrinologie oder Schilddrüsenchirurgie mit langjähriger Erfahrung.", category: "schilddruese" },
{ question: "Ist die Zweitmeinung verbindlich?", answer: "Nein, die Zweitmeinung ist eine unabhängige Empfehlung. Sie sind nicht verpflichtet, ihr zu folgen.", category: "schilddruese" },
{ question: "Kostet die Schilddrüsen-Zweitmeinung etwas?", answer: "Für gesetzlich Versicherte ist die Zweitmeinung in vielen Fällen kostenfrei. Sprechen Sie uns an.", category: "schilddruese" },
{ question: "Wann ist eine Zweitmeinung zur Gallenblasenentfernung sinnvoll?", answer: "Wenn eine Cholezystektomie empfohlen wurde und Sie unsicher sind, ob die OP tatsächlich notwendig ist.", category: "gallenblase" },
{ question: "Wer erstellt die Gallenblase-Zweitmeinung?", answer: "Fachärzt:innen für Viszeralchirurgie oder Gastroenterologie.", category: "gallenblase" },
{ question: "Welche Unterlagen brauche ich für die Gallenblase-Zweitmeinung?", answer: "Ultraschallbefunde, Laborbefunde und die OP-Empfehlung Ihres behandelnden Arztes.", category: "gallenblase" },
{ question: "Gibt es Alternativen zur Gallenblasen-OP?", answer: "In manchen Fällen ja - etwa medikamentöse Therapie oder abwartendes Beobachten. Das hängt von Ihren individuellen Befunden ab.", category: "gallenblase" },
{ question: "Was kostet die Gallenblase-Zweitmeinung?", answer: "Sprechen Sie uns an - in vielen Fällen übernimmt Ihre Krankenkasse die Kosten.", category: "gallenblase" },
{ question: "Wann ist eine Zweitmeinung bei Nierenerkrankungen sinnvoll?", answer: "Bei Diagnose einer chronischen Niereninsuffizienz, Empfehlung zur Dialyse oder vor einer Transplantation.", category: "nephrologie" },
{ question: "Welche Unterlagen sind für die nephrologische Zweitmeinung wichtig?", answer: "Laborwerte (Kreatinin, GFR, Harnstoff), Urinbefunde, Ultraschall und bisherige Behandlungsdokumentation.", category: "nephrologie" },
{ question: "Kann ich die Zweitmeinung auch einholen, wenn die Dialyse bereits begonnen hat?", answer: "Ja, auch bei laufender Dialyse kann eine Zweitmeinung sinnvoll sein - etwa zur Prüfung von Therapiealternativen.", category: "nephrologie" },
{ question: "Kostet die nephrologische Zweitmeinung etwas?", answer: "In vielen Fällen wird die Zweitmeinung von Ihrer Krankenkasse übernommen. Wir beraten Sie gerne.", category: "nephrologie" },
]
export default function FAQPage() {
return (
<>
{/* Hero */}
<section className="bg-gradient-to-br from-navy via-navy-dark to-[#001a2e] py-20">
<Container size="md">
<div className="text-center">
<h1 className="text-4xl sm:text-5xl font-bold text-white mb-4">
FAQ - Häufige Fragen
</h1>
<p className="text-white/60 text-lg max-w-2xl mx-auto">
Antworten auf die wichtigsten Fragen zur medizinischen Zweitmeinung
</p>
</div>
</Container>
</section>
{/* FAQ Content (Client Component for search/filter) */}
<FAQClient categories={categories} faqs={faqData} />
{/* CTA */}
<section className="py-20 bg-navy">
<Container size="sm">
<div className="text-center">
<div className="inline-flex items-center justify-center w-14 h-14 rounded-2xl bg-gold/20 text-gold mb-6">
<HelpCircle className="h-7 w-7" />
</div>
<h2 className="text-2xl sm:text-3xl font-bold text-white mb-4">
Ihre Frage nicht dabei?
</h2>
<p className="text-white/60 mb-8 max-w-xl mx-auto">
Unsere Experten beantworten gerne Ihre individuellen Fragen zur
medizinischen Zweitmeinung. Kontaktieren Sie uns für eine
kostenlose Erstberatung.
</p>
<div className="flex flex-wrap justify-center gap-4">
<Button href="tel:+4980080441000" variant="gold" size="lg">
<Phone className="h-4 w-4" />
Jetzt anrufen
</Button>
<Button href="/kontakt" variant="outline" size="lg">
<Mail className="h-4 w-4" />
E-Mail schreiben
</Button>
</div>
<p className="text-white/40 text-sm mt-6">
Kostenlose Hotline &bull; Mo-Fr 9:00-16:00 Uhr &bull; Notfall 24/7
</p>
</div>
</Container>
</section>
{/* Schema.org FAQ structured data */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({
"@context": "https://schema.org",
"@type": "FAQPage",
mainEntity: faqData.map((faq) => ({
"@type": "Question",
name: faq.question,
acceptedAnswer: { "@type": "Answer", text: faq.answer },
})),
}),
}}
/>
</>
)
}

BIN
src/app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

91
src/app/globals.css Normal file
View file

@ -0,0 +1,91 @@
/* Google Font Import — must precede @import "tailwindcss" */
@import url('https://fonts.googleapis.com/css2?family=Roboto+Condensed:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400&display=swap');
@import "tailwindcss";
@theme {
/* Brand Colors */
--color-navy: #004166;
--color-navy-light: #005580;
--color-navy-dark: #003050;
--color-primary: #1278B3;
--color-primary-light: #1590D6;
--color-primary-dark: #0E5F8F;
--color-gold: #B3AF09;
--color-gold-hover: #8F8C07;
--color-gold-light: #D4D10A;
--color-accent: #0091BD;
--color-bg: #f0f1f4;
--color-bg-white: #ffffff;
--color-text: #1a1a2e;
--color-text-muted: #64748b;
--color-text-light: #94a3b8;
--color-border: #e2e8f0;
--color-border-light: #f1f5f9;
/* Typography */
--font-family-heading: "Roboto Condensed", "Arial Narrow", Arial, sans-serif;
--font-family-body: "Roboto Condensed", "Arial Narrow", Arial, sans-serif;
}
/* Base styles */
html {
scroll-behavior: smooth;
}
body {
font-family: var(--font-family-body);
color: var(--color-text);
background-color: var(--color-bg);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Headings */
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-family-heading);
font-weight: 700;
line-height: 1.2;
}
/* Pulse animation for emergency CTA */
@keyframes pulse-gold {
0%, 100% {
box-shadow: 0 0 0 0 rgba(179, 175, 9, 0.4);
}
50% {
box-shadow: 0 0 0 12px rgba(179, 175, 9, 0);
}
}
.animate-pulse-gold {
animation: pulse-gold 2s ease-in-out infinite;
}
/* Fade-in animation for scroll-triggered sections */
@keyframes fade-in-up {
from {
opacity: 0;
transform: translateY(24px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in-up {
animation: fade-in-up 0.6s ease-out forwards;
}
/* Focus styles for accessibility */
:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}
/* Selection color */
::selection {
background-color: var(--color-primary);
color: white;
}

View file

@ -0,0 +1,96 @@
import type { Metadata } from "next"
import { Container } from "@/components/ui/Container"
export const metadata: Metadata = {
title: "Impressum",
description: "Impressum der complex care solutions GmbH.",
}
export default function ImpressumPage() {
return (
<section className="py-20 bg-white">
<Container size="sm">
<article className="prose prose-gray max-w-none">
<h1 className="text-3xl font-bold text-navy mb-8">Impressum</h1>
<p>
<strong>complex care solutions GmbH</strong>
<br />
Hans-Böckler-Str. 19
<br />
46236 Bottrop
</p>
<p>
Handelsregister: HRB 15753
<br />
Registergericht: Gelsenkirchen
</p>
<p>
<strong>Vertreten durch:</strong>
<br />
Martin Porwoll
</p>
<h2 className="text-xl font-bold text-navy mt-8 mb-4">Kontakt</h2>
<p>
Telefon: 0800 80 44 100
<br />
Telefax: 0800 80 44 190
<br />
E-Mail: kontakt@complexcaresolutions.de
</p>
<h2 className="text-xl font-bold text-navy mt-8 mb-4">
Umsatzsteuer-ID
</h2>
<p>
Umsatzsteuer-Identifikationsnummer gemäß § 27 a
Umsatzsteuergesetz:
<br />
DE334815479
</p>
<p>
<strong>Redaktionell verantwortlich:</strong>
<br />
Martin Porwoll
<br />
Hans-Böckler-Str. 19
<br />
46236 Bottrop
</p>
<h2 className="text-xl font-bold text-navy mt-8 mb-4">
EU-Streitschlichtung
</h2>
<p>
Die Europäische Kommission stellt eine Plattform zur
Online-Streitbeilegung (OS) bereit:{" "}
<a
href="https://ec.europa.eu/consumers/odr/"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline"
>
https://ec.europa.eu/consumers/odr/
</a>
.
<br />
Unsere E-Mail-Adresse finden Sie oben im Impressum.
</p>
<h2 className="text-xl font-bold text-navy mt-8 mb-4">
Verbraucherstreitbeilegung
</h2>
<p>
Wir sind nicht bereit oder verpflichtet, an
Streitbeilegungsverfahren vor einer
Verbraucherschlichtungsstelle teilzunehmen.
</p>
</article>
</Container>
</section>
)
}

115
src/app/kontakt/page.tsx Normal file
View file

@ -0,0 +1,115 @@
import type { Metadata } from "next"
import { Container } from "@/components/ui/Container"
import { ContactForm } from "@/components/contact/ContactForm"
import { Phone, Mail, MapPin, Clock, Printer } from "lucide-react"
export const metadata: Metadata = {
title: "Kontakt",
description: "Kontaktieren Sie uns für eine kostenlose Erstberatung. Telefon: 0800 80 44 100.",
}
export default function KontaktPage() {
return (
<>
{/* Hero */}
<section className="bg-gradient-to-br from-navy via-navy-dark to-[#001a2e] py-20">
<Container size="md">
<div className="text-center">
<h1 className="text-4xl sm:text-5xl font-bold text-white mb-4">
Kontakt
</h1>
<p className="text-white/60 text-lg">
Wir sind für Sie da persönlich, kompetent und vertraulich.
</p>
</div>
</Container>
</section>
{/* Contact Content */}
<section className="py-20 bg-bg">
<Container>
<div className="grid lg:grid-cols-5 gap-12">
{/* Left: Contact Info */}
<div className="lg:col-span-2 space-y-8">
<div>
<h2 className="text-xl font-bold text-navy mb-6">
So erreichen Sie uns
</h2>
<div className="space-y-5">
<a
href="tel:+4980080441000"
className="flex items-start gap-4 group"
>
<div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center shrink-0 group-hover:bg-primary group-hover:text-white transition-colors">
<Phone className="h-5 w-5 text-primary group-hover:text-white" />
</div>
<div>
<div className="font-semibold text-navy">Telefon (kostenlos)</div>
<div className="text-primary font-bold">0800 80 44 100</div>
</div>
</a>
<div className="flex items-start gap-4">
<div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center shrink-0">
<Printer className="h-5 w-5 text-primary" />
</div>
<div>
<div className="font-semibold text-navy">Telefax</div>
<div className="text-text-muted">0800 80 44 190</div>
</div>
</div>
<a
href="mailto:kontakt@zweitmeinu.ng"
className="flex items-start gap-4 group"
>
<div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center shrink-0 group-hover:bg-primary group-hover:text-white transition-colors">
<Mail className="h-5 w-5 text-primary group-hover:text-white" />
</div>
<div>
<div className="font-semibold text-navy">E-Mail</div>
<div className="text-primary">kontakt@zweitmeinu.ng</div>
</div>
</a>
<div className="flex items-start gap-4">
<div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center shrink-0">
<MapPin className="h-5 w-5 text-primary" />
</div>
<div>
<div className="font-semibold text-navy">Adresse</div>
<div className="text-text-muted text-sm">
complex care solutions GmbH<br />
Hans-Böckler-Str. 19<br />
46236 Bottrop
</div>
</div>
</div>
<div className="flex items-start gap-4">
<div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center shrink-0">
<Clock className="h-5 w-5 text-primary" />
</div>
<div>
<div className="font-semibold text-navy">Öffnungszeiten</div>
<div className="text-text-muted text-sm">
Mo Fr: 09:00 16:00 Uhr<br />
<span className="text-gold font-semibold">Notfall-Hotline: 24/7</span>
</div>
</div>
</div>
</div>
</div>
</div>
{/* Right: Contact Form */}
<div className="lg:col-span-3">
<div className="bg-white rounded-2xl p-6 sm:p-8 border border-border shadow-sm">
<h2 className="text-xl font-bold text-navy mb-6">
Nachricht senden
</h2>
<ContactForm />
</div>
</div>
</div>
</Container>
</section>
</>
)
}

55
src/app/layout.tsx Normal file
View file

@ -0,0 +1,55 @@
import type { Metadata } from "next"
import { TopBar, Header, Footer, EmergencyBanner } from "@/components/layout"
import { getNavigation, getSiteSettings, getSocialLinks } from "@/lib/api"
import "./globals.css"
export const metadata: Metadata = {
title: {
default: "zweitmeinu.ng Medizinische Zweitmeinung",
template: "%s | zweitmeinu.ng",
},
description:
"Ihr zentrales Portal für qualifizierte medizinische Zweitmeinungen. Zugang zu erfahrenen Fachärzt:innen aus über 50 Fachbereichen.",
metadataBase: new URL(
process.env.NEXT_PUBLIC_SITE_URL || "https://zweitmeinu.ng",
),
openGraph: {
type: "website",
locale: "de_DE",
siteName: "zweitmeinu.ng",
},
}
export default async function RootLayout({
children,
}: {
children: React.ReactNode
}) {
const [navigation, settings, socialLinks] = await Promise.all([
getNavigation(),
getSiteSettings(),
getSocialLinks(),
])
return (
<html lang="de">
<body className="font-body antialiased">
<a
href="#main-content"
className="sr-only focus:not-sr-only focus:fixed focus:top-4 focus:left-4 focus:z-[200] focus:bg-white focus:px-4 focus:py-2 focus:rounded-lg focus:shadow-lg"
>
Zum Hauptinhalt springen
</a>
<div className="flex min-h-screen flex-col">
<TopBar />
<Header mainMenu={navigation?.mainMenu ?? null} />
<main id="main-content" className="flex-1">
{children}
</main>
<EmergencyBanner />
<Footer socialLinks={socialLinks} />
</div>
</body>
</html>
)
}

165
src/app/motivation/page.tsx Normal file
View file

@ -0,0 +1,165 @@
import type { Metadata } from "next"
import { Container } from "@/components/ui/Container"
import { Heart, Shield, Eye, Users, Lightbulb, Scale } from "lucide-react"
export const metadata: Metadata = {
title: "Motivation & Geschichte",
description: "Erfahren Sie, warum zweitmeinu.ng gegründet wurde und welche Werte uns antreiben.",
}
const values = [
{ title: "Patientenwohl", description: "Der Mensch steht im Mittelpunkt jeder Entscheidung.", icon: Heart },
{ title: "Unabhängigkeit", description: "Frei von wirtschaftlichen Interessen und institutionellen Bindungen.", icon: Shield },
{ title: "Transparenz", description: "Offene Kommunikation und nachvollziehbare Prozesse.", icon: Eye },
{ title: "Qualität", description: "Höchste Standards in medizinischer Expertise und Beratung.", icon: Lightbulb },
{ title: "Empathie", description: "Verständnis und Mitgefühl in jeder Situation.", icon: Users },
{ title: "Gerechtigkeit", description: "Gleicher Zugang zu medizinischer Expertise für alle.", icon: Scale },
]
export default function MotivationPage() {
return (
<>
{/* Hero */}
<section className="bg-gradient-to-br from-navy via-navy-dark to-[#001a2e] py-20">
<Container size="md">
<div className="text-center">
<h1 className="text-4xl sm:text-5xl font-bold text-white mb-4">
Patientenwohl im Mittelpunkt
</h1>
<p className="text-white/60 text-lg">
Wir sind Streiter für das Patientenwohl
</p>
</div>
</Container>
</section>
{/* Story sections */}
<section className="py-20 bg-white">
<Container size="md">
<div className="space-y-16">
{/* Section 1 */}
<div>
<h2 className="text-2xl font-bold text-navy mb-4">Unser Fokus</h2>
<p className="text-text-muted leading-relaxed mb-4">
Seit der Gründung konzentrieren wir uns darauf,
Versorgungsangebote zu optimieren und dabei die Bedürfnisse der
Patienten in den Mittelpunkt zu stellen.
</p>
<ul className="space-y-2 text-text-muted">
<li className="flex gap-2">
<span className="text-primary font-bold">&#8250;</span>
Wir verfolgen einen patientenzentrierten Ansatz.
</li>
<li className="flex gap-2">
<span className="text-primary font-bold">&#8250;</span>
Wir legen besonderen Wert auf Transparenz, Unabhängigkeit und Qualitätssicherung.
</li>
<li className="flex gap-2">
<span className="text-primary font-bold">&#8250;</span>
Mit unserem nationalen und internationalen Expertennetzwerk entwickeln wir innovative Lösungen.
</li>
</ul>
</div>
{/* Section 2 */}
<div>
<h2 className="text-2xl font-bold text-navy mb-4">
Motivation und Geschichte
</h2>
<p className="text-text-muted leading-relaxed mb-6">
Complex care solutions wurde von Martin Porwoll, dem
Whistleblower des Bottroper Zytoskandals, gegründet.
</p>
<blockquote className="border-l-4 border-gold pl-6 py-2 bg-gold/5 rounded-r-lg">
<p className="text-navy italic">
&ldquo;Aus seinen Erfahrungen und der Erkenntnis um die Bedeutung
von Transparenz und Patientenwohl entstand die Idee, ein
unabhängiges Unternehmen zu etablieren.&rdquo;
</p>
</blockquote>
</div>
{/* Section 3 */}
<div>
<h2 className="text-2xl font-bold text-navy mb-4">
Der Zytoskandal Bottrop und Martin Porwoll
</h2>
<p className="text-text-muted leading-relaxed mb-4">
Im Jahr 2016 deckte Martin Porwoll als Whistleblower den
sogenannten Zytoskandal in Bottrop auf, bei dem ein Apotheker
über Jahre hinweg Krebsmedikamente für tausende Patienten
gestreckt hatte.
</p>
<blockquote className="border-l-4 border-primary pl-6 py-2">
<p className="text-navy italic">
&ldquo;Der Bottroper Zytoskandal, den ich im Jahr 2016 als
Whistleblower aufgedeckt habe, hat mich zutiefst
erschüttert.&rdquo;
</p>
<cite className="text-sm text-text-muted mt-2 block not-italic">
Martin Porwoll, Gründer &amp; Geschäftsführer
</cite>
</blockquote>
</div>
{/* Section 4 */}
<div>
<p className="text-text-muted leading-relaxed">
Die Erfahrungen mit dem Zytoskandal haben Martin Porwoll zu
einem engagierten Kämpfer für das Patientenwohl und gegen
Missstände im Gesundheitswesen gemacht. So gründete er Complex
care solutions mit dem Ziel, Patienten in komplexen
Versorgungssituationen bestmöglich zu unterstützen.
</p>
</div>
</div>
</Container>
</section>
{/* Core Values */}
<section className="py-20 bg-bg">
<Container>
<div className="text-center mb-12">
<h2 className="text-2xl sm:text-3xl font-bold text-navy mb-3">
Unsere Grundwerte
</h2>
<p className="text-text-muted">
Diese Prinzipien leiten uns bei allem, was wir für eine bessere
Patientenversorgung tun.
</p>
</div>
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
{values.map((v) => (
<div key={v.title} className="bg-white rounded-xl p-6 border border-border">
<v.icon className="h-8 w-8 text-primary mb-4" />
<h3 className="font-bold text-navy mb-2">{v.title}</h3>
<p className="text-text-muted text-sm">{v.description}</p>
</div>
))}
</div>
</Container>
</section>
{/* Mission Statement */}
<section className="py-20 bg-navy">
<Container size="sm">
<div className="text-center">
<Heart className="h-10 w-10 text-gold mx-auto mb-6" />
<h2 className="text-2xl font-bold text-white mb-6">
Unsere Mission
</h2>
<blockquote className="text-white/80 text-lg leading-relaxed italic">
&ldquo;Wir setzen uns dafür ein, dass jeder Patient die bestmögliche
Versorgung erhält. Durch innovative Technologie, Transparenz und
unabhängige Expertise schaffen wir Vertrauen und verbessern die
Gesundheitsversorgung.&rdquo;
</blockquote>
<cite className="text-white/50 text-sm mt-4 block not-italic">
Das Team von zweitmeinu.ng
</cite>
</div>
</Container>
</section>
</>
)
}

15
src/app/page.tsx Normal file
View file

@ -0,0 +1,15 @@
import { HeroSection } from "@/components/home/HeroSection"
import { WhySection } from "@/components/home/WhySection"
import { ServiceOverview } from "@/components/home/ServiceOverview"
import { HomeCTA } from "@/components/home/HomeCTA"
export default function HomePage() {
return (
<>
<HeroSection />
<WhySection />
<ServiceOverview />
<HomeCTA />
</>
)
}

View file

@ -0,0 +1,121 @@
import type { Metadata } from "next"
import { Container } from "@/components/ui/Container"
import { Button } from "@/components/ui/Button"
import {
Phone, FileText, UserCheck, ClipboardCheck,
MessageSquare, HeartHandshake, Shield, Clock,
BadgeCheck, Users, ArrowRight,
} from "lucide-react"
export const metadata: Metadata = {
title: "So funktioniert's",
description: "Ihr Weg zur medizinischen Zweitmeinung in 6 einfachen Schritten. Transparent, sicher und patientenorientiert.",
}
const steps = [
{ num: "01", title: "Kontaktaufnahme", description: "Sie rufen uns an oder schreiben uns. Unser Team nimmt Ihre Anfrage auf und klärt erste Fragen.", icon: Phone },
{ num: "02", title: "Unterlagen sammeln", description: "Unsere Case Manager:innen unterstützen Sie bei der Zusammenstellung aller relevanten medizinischen Unterlagen.", icon: FileText },
{ num: "03", title: "Experten-Zuordnung", description: "Wir ordnen Ihren Fall einem passenden Facharzt bzw. einer Fachärztin aus unserem Netzwerk zu.", icon: UserCheck },
{ num: "04", title: "Medizinische Prüfung", description: "Der/die Fachärzt:in prüft Ihre Unterlagen und erstellt eine unabhängige medizinische Einschätzung.", icon: ClipboardCheck },
{ num: "05", title: "Schriftliches Gutachten", description: "Sie erhalten ein verständliches Zweitmeinungsgutachten mit klarer Empfehlung.", icon: MessageSquare },
{ num: "06", title: "Nachbetreuung", description: "Bei Bedarf besprechen wir das Ergebnis persönlich und unterstützen bei der weiteren Umsetzung.", icon: HeartHandshake },
]
const whyCards = [
{ title: "Unabhängig", description: "Unsere Expert:innen haben keine wirtschaftlichen Eigeninteressen.", icon: Shield },
{ title: "Schnell", description: "In der Regel erhalten Sie Ihr Gutachten innerhalb weniger Tage.", icon: Clock },
{ title: "Qualifiziert", description: "Nur erfahrene Fachärzt:innen mit nachgewiesener Expertise.", icon: BadgeCheck },
{ title: "Persönlich", description: "Case Management und persönliche Betreuung von Anfang an.", icon: Users },
]
export default function SoFunktionierts() {
return (
<>
{/* Hero */}
<section className="bg-gradient-to-br from-navy via-navy-dark to-[#001a2e] py-20">
<Container size="md">
<div className="text-center">
<h1 className="text-4xl sm:text-5xl font-bold text-white mb-4">
So funktioniert&apos;s
</h1>
<p className="text-white/60 text-lg max-w-2xl mx-auto">
Ihr Weg zur medizinischen Zweitmeinung in 6 einfachen Schritten.
Transparent, sicher und patientenorientiert.
</p>
</div>
</Container>
</section>
{/* Process Steps */}
<section className="py-20 bg-bg">
<Container size="md">
<div className="space-y-6">
{steps.map((step) => (
<div key={step.num} className="flex gap-6 bg-white rounded-xl p-6 border border-border">
<div className="shrink-0">
<div className="w-14 h-14 rounded-2xl bg-primary/10 flex items-center justify-center">
<step.icon className="h-6 w-6 text-primary" />
</div>
</div>
<div>
<div className="flex items-center gap-3 mb-1">
<span className="text-xs font-bold text-primary bg-primary/10 px-2 py-0.5 rounded-full">
Schritt {step.num}
</span>
</div>
<h3 className="text-lg font-bold text-navy mb-1">{step.title}</h3>
<p className="text-text-muted text-sm">{step.description}</p>
</div>
</div>
))}
</div>
</Container>
</section>
{/* Why choose us cards */}
<section className="py-20 bg-white">
<Container>
<h2 className="text-2xl sm:text-3xl font-bold text-center text-navy mb-12">
Warum complex care solutions?
</h2>
<div className="grid sm:grid-cols-2 lg:grid-cols-4 gap-6">
{whyCards.map((card) => (
<div key={card.title} className="bg-bg rounded-xl p-6 text-center border border-border">
<div className="inline-flex items-center justify-center w-12 h-12 rounded-xl bg-primary/10 text-primary mb-4">
<card.icon className="h-6 w-6" />
</div>
<h3 className="font-bold text-navy mb-2">{card.title}</h3>
<p className="text-text-muted text-sm">{card.description}</p>
</div>
))}
</div>
</Container>
</section>
{/* CTA */}
<section className="py-20 bg-bg">
<Container size="sm">
<div className="bg-white rounded-2xl p-8 sm:p-12 text-center border border-border">
<h2 className="text-2xl sm:text-3xl font-bold text-navy mb-4">
Bereit für Ihre Zweitmeinung?
</h2>
<p className="text-text-muted mb-8">
Starten Sie jetzt und erhalten Sie in kürzester Zeit eine
fundierte, unabhängige Einschätzung.
</p>
<div className="flex flex-wrap justify-center gap-4">
<Button href="tel:+4980080441000" variant="gold" size="lg">
<Phone className="h-4 w-4" />
0800 80 44 100
</Button>
<Button href="/kontakt" variant="secondary" size="lg">
Kontaktformular
<ArrowRight className="h-4 w-4" />
</Button>
</div>
</div>
</Container>
</section>
</>
)
}

129
src/app/ueber-uns/page.tsx Normal file
View file

@ -0,0 +1,129 @@
import type { Metadata } from "next"
import { Container } from "@/components/ui/Container"
import { Button } from "@/components/ui/Button"
import {
Shield, Award, Users, Heart, Phone,
CheckCircle, Building, Globe,
} from "lucide-react"
export const metadata: Metadata = {
title: "Über uns",
description: "complex care solutions GmbH Ihr Partner für unabhängige medizinische Zweitmeinungen.",
}
const qualityCards = [
{ title: "Unabhängigkeit", description: "Unsere Gutachter haben keine wirtschaftlichen Verbindungen zu behandelnden Einrichtungen.", icon: Shield },
{ title: "Expertise", description: "Nur Fachärzt:innen mit nachgewiesener Expertise und langjähriger Erfahrung.", icon: Award },
{ title: "Patientenorientierung", description: "Der Mensch steht im Mittelpunkt nicht die Diagnose.", icon: Heart },
{ title: "Netzwerk", description: "Über 1000 Expert:innen aus über 50 Fachbereichen deutschlandweit.", icon: Globe },
]
export default function UeberUnsPage() {
return (
<>
{/* Hero */}
<section className="bg-gradient-to-br from-navy via-navy-dark to-[#001a2e] py-20">
<Container size="md">
<div className="text-center">
<h1 className="text-4xl sm:text-5xl font-bold text-white mb-4">
Über uns
</h1>
<p className="text-white/60 text-lg max-w-2xl mx-auto">
complex care solutions GmbH Ihr Partner für unabhängige
medizinische Zweitmeinungen seit über 15 Jahren.
</p>
</div>
</Container>
</section>
{/* Quality */}
<section className="py-20 bg-white">
<Container>
<h2 className="text-2xl sm:text-3xl font-bold text-center text-navy mb-12">
Was uns auszeichnet
</h2>
<div className="grid sm:grid-cols-2 lg:grid-cols-4 gap-6">
{qualityCards.map((c) => (
<div key={c.title} className="bg-bg rounded-xl p-6 text-center border border-border">
<div className="inline-flex items-center justify-center w-12 h-12 rounded-xl bg-primary/10 text-primary mb-4">
<c.icon className="h-6 w-6" />
</div>
<h3 className="font-bold text-navy mb-2">{c.title}</h3>
<p className="text-text-muted text-sm">{c.description}</p>
</div>
))}
</div>
</Container>
</section>
{/* Company info */}
<section className="py-20 bg-bg">
<Container size="md">
<div className="bg-white rounded-2xl p-8 sm:p-12 border border-border">
<div className="flex items-center gap-3 mb-6">
<Building className="h-6 w-6 text-primary" />
<h2 className="text-2xl font-bold text-navy">
complex care solutions GmbH
</h2>
</div>
<div className="grid sm:grid-cols-2 gap-8">
<div className="space-y-4 text-sm text-text-muted">
<p>
Die complex care solutions GmbH mit Sitz in Bottrop ist ein
unabhängiges Gesundheitsunternehmen, das sich auf medizinische
Zweitmeinungen spezialisiert hat.
</p>
<p>
Gegründet von Martin Porwoll, dem Whistleblower des Bottroper
Zytoskandals, verfolgen wir das Ziel, Patient:innen in
komplexen medizinischen Situationen bestmöglich zu
unterstützen.
</p>
</div>
<div className="space-y-3">
<div className="flex items-center gap-2 text-sm">
<CheckCircle className="h-4 w-4 text-gold shrink-0" />
<span className="text-text-muted">Über 15 Jahre Erfahrung im Gesundheitswesen</span>
</div>
<div className="flex items-center gap-2 text-sm">
<CheckCircle className="h-4 w-4 text-gold shrink-0" />
<span className="text-text-muted">Netzwerk aus über 1000 Fachärzt:innen</span>
</div>
<div className="flex items-center gap-2 text-sm">
<CheckCircle className="h-4 w-4 text-gold shrink-0" />
<span className="text-text-muted">DSGVO-konform und datenschutzgeprüft</span>
</div>
<div className="flex items-center gap-2 text-sm">
<CheckCircle className="h-4 w-4 text-gold shrink-0" />
<span className="text-text-muted">Sitz in Bottrop, Hans-Böckler-Str. 19</span>
</div>
<div className="flex items-center gap-2 text-sm">
<CheckCircle className="h-4 w-4 text-gold shrink-0" />
<span className="text-text-muted">Kostenlose Servicehotline: 0800 80 44 100</span>
</div>
</div>
</div>
</div>
</Container>
</section>
{/* CTA */}
<section className="py-16 bg-white">
<Container size="sm">
<div className="text-center">
<h2 className="text-2xl font-bold text-navy mb-4">
Haben Sie Fragen?
</h2>
<p className="text-text-muted mb-8">
Wir beraten Sie gerne unverbindlich und kostenfrei.
</p>
<Button href="tel:+4980080441000" variant="gold" size="lg">
<Phone className="h-4 w-4" />
0800 80 44 100 (kostenlos)
</Button>
</div>
</Container>
</section>
</>
)
}

View file

@ -0,0 +1,203 @@
"use client"
import { useState } from "react"
import { Send, Loader2, CheckCircle } from "lucide-react"
const PAYLOAD_URL = process.env.NEXT_PUBLIC_PAYLOAD_URL || "https://cms.c2sgmbh.de"
export function ContactForm() {
const [form, setForm] = useState({
name: "",
email: "",
phone: "",
subject: "",
urgency: "normal",
message: "",
})
const [status, setStatus] = useState<"idle" | "sending" | "success" | "error">("idle")
const [errorMsg, setErrorMsg] = useState("")
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setStatus("sending")
setErrorMsg("")
try {
const res = await fetch(`${PAYLOAD_URL}/api/form-submissions`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
form: 1,
submissionData: [
{ field: "name", value: form.name },
{ field: "email", value: form.email },
{ field: "phone", value: form.phone },
{ field: "subject", value: form.subject },
{ field: "urgency", value: form.urgency },
{ field: "message", value: form.message },
],
}),
})
if (res.ok) {
setStatus("success")
setForm({ name: "", email: "", phone: "", subject: "", urgency: "normal", message: "" })
} else {
setStatus("error")
setErrorMsg("Beim Senden ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.")
}
} catch {
setStatus("error")
setErrorMsg("Verbindungsfehler. Bitte versuchen Sie es später erneut.")
}
}
if (status === "success") {
return (
<div className="text-center py-12">
<CheckCircle className="h-16 w-16 text-green-500 mx-auto mb-4" />
<h3 className="text-xl font-bold text-navy mb-2">Nachricht gesendet</h3>
<p className="text-text-muted">
Vielen Dank für Ihre Nachricht. Wir melden uns schnellstmöglich bei Ihnen.
</p>
<button
onClick={() => setStatus("idle")}
className="mt-6 text-sm text-primary hover:underline"
>
Weitere Nachricht senden
</button>
</div>
)
}
return (
<form onSubmit={handleSubmit} className="space-y-5">
<div className="grid sm:grid-cols-2 gap-5">
<div>
<label htmlFor="name" className="block text-sm font-medium text-navy mb-1.5">
Name *
</label>
<input
id="name"
type="text"
required
value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
className="w-full px-4 py-2.5 rounded-lg border border-border bg-bg focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary text-sm"
placeholder="Ihr vollständiger Name"
/>
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-navy mb-1.5">
E-Mail *
</label>
<input
id="email"
type="email"
required
value={form.email}
onChange={(e) => setForm({ ...form, email: e.target.value })}
className="w-full px-4 py-2.5 rounded-lg border border-border bg-bg focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary text-sm"
placeholder="ihre@email.de"
/>
</div>
</div>
<div className="grid sm:grid-cols-2 gap-5">
<div>
<label htmlFor="phone" className="block text-sm font-medium text-navy mb-1.5">
Telefon
</label>
<input
id="phone"
type="tel"
value={form.phone}
onChange={(e) => setForm({ ...form, phone: e.target.value })}
className="w-full px-4 py-2.5 rounded-lg border border-border bg-bg focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary text-sm"
placeholder="+49 ..."
/>
</div>
<div>
<label htmlFor="subject" className="block text-sm font-medium text-navy mb-1.5">
Fachbereich *
</label>
<select
id="subject"
required
value={form.subject}
onChange={(e) => setForm({ ...form, subject: e.target.value })}
className="w-full px-4 py-2.5 rounded-lg border border-border bg-bg focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary text-sm"
>
<option value="">Bitte wählen...</option>
<option value="intensivmedizin">Intensivmedizin</option>
<option value="kardiologie">Kardiologie</option>
<option value="onkologie">Onkologie</option>
<option value="nephrologie">Nephrologie</option>
<option value="gallenblase">Gallenblase</option>
<option value="schilddruese">Schilddrüse</option>
<option value="sonstiges">Sonstiges</option>
</select>
</div>
</div>
<div>
<label htmlFor="urgency" className="block text-sm font-medium text-navy mb-1.5">
Dringlichkeit
</label>
<select
id="urgency"
value={form.urgency}
onChange={(e) => setForm({ ...form, urgency: e.target.value })}
className="w-full px-4 py-2.5 rounded-lg border border-border bg-bg focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary text-sm"
>
<option value="normal">Normal</option>
<option value="dringend">Dringend (innerhalb 48h)</option>
<option value="notfall">Notfall (sofort)</option>
</select>
</div>
<div>
<label htmlFor="message" className="block text-sm font-medium text-navy mb-1.5">
Nachricht *
</label>
<textarea
id="message"
required
rows={5}
value={form.message}
onChange={(e) => setForm({ ...form, message: e.target.value })}
className="w-full px-4 py-2.5 rounded-lg border border-border bg-bg focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary text-sm resize-y"
placeholder="Beschreiben Sie Ihr Anliegen..."
/>
</div>
{status === "error" && (
<div className="text-sm text-red-600 bg-red-50 px-4 py-3 rounded-lg">
{errorMsg}
</div>
)}
<button
type="submit"
disabled={status === "sending"}
className="w-full sm:w-auto inline-flex items-center justify-center gap-2 bg-primary hover:bg-primary-dark text-white font-semibold px-8 py-3 rounded-lg transition-colors disabled:opacity-50"
>
{status === "sending" ? (
<>
<Loader2 className="h-4 w-4 animate-spin" />
Wird gesendet...
</>
) : (
<>
<Send className="h-4 w-4" />
Nachricht senden
</>
)}
</button>
<p className="text-xs text-text-light">
* Pflichtfelder. Ihre Daten werden vertraulich behandelt und nicht an Dritte weitergegeben.
</p>
</form>
)
}

View file

@ -0,0 +1,161 @@
"use client"
import { useState, useMemo } from "react"
import { Search, ChevronDown } from "lucide-react"
import { Container } from "@/components/ui/Container"
interface FAQ {
question: string
answer: string
category: string
}
interface Category {
name: string
slug: string
}
interface FAQClientProps {
categories: Category[]
faqs: FAQ[]
}
export function FAQClient({ categories, faqs }: FAQClientProps) {
const [search, setSearch] = useState("")
const [activeCategory, setActiveCategory] = useState<string | null>(null)
const [openItems, setOpenItems] = useState<Set<number>>(new Set())
const filtered = useMemo(() => {
let items = faqs
if (activeCategory) {
items = items.filter((f) => f.category === activeCategory)
}
if (search.trim()) {
const q = search.toLowerCase()
items = items.filter(
(f) =>
f.question.toLowerCase().includes(q) ||
f.answer.toLowerCase().includes(q),
)
}
return items
}, [faqs, search, activeCategory])
const toggle = (i: number) => {
setOpenItems((prev) => {
const next = new Set(prev)
if (next.has(i)) next.delete(i)
else next.add(i)
return next
})
}
const faqCountByCategory = useMemo(() => {
const map: Record<string, number> = {}
for (const f of faqs) {
map[f.category] = (map[f.category] || 0) + 1
}
return map
}, [faqs])
return (
<>
{/* Search */}
<section className="py-8 bg-white border-b border-border">
<Container size="md">
<div className="relative max-w-xl mx-auto">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 h-5 w-5 text-text-muted" />
<input
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Suchen Sie nach Stichworten oder Fragen..."
className="w-full pl-12 pr-4 py-3.5 rounded-xl border border-border bg-bg focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary text-sm"
/>
</div>
</Container>
</section>
{/* Categories */}
<section className="py-12 bg-bg">
<Container>
<p className="text-center text-text-muted text-sm mb-6">
Wählen Sie Ihren Fachbereich für spezifische Antworten
</p>
<div className="flex flex-wrap justify-center gap-3">
<button
onClick={() => setActiveCategory(null)}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
activeCategory === null
? "bg-primary text-white"
: "bg-white text-text-muted hover:bg-gray-50 border border-border"
}`}
>
Alle ({faqs.length})
</button>
{categories.map((cat) => (
<button
key={cat.slug}
onClick={() =>
setActiveCategory(activeCategory === cat.slug ? null : cat.slug)
}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
activeCategory === cat.slug
? "bg-primary text-white"
: "bg-white text-text-muted hover:bg-gray-50 border border-border"
}`}
>
{cat.name} ({faqCountByCategory[cat.slug] || 0})
</button>
))}
</div>
</Container>
</section>
{/* FAQ Accordion */}
<section className="py-12 bg-white">
<Container size="md">
{filtered.length === 0 ? (
<p className="text-center text-text-muted py-12">
Keine Ergebnisse gefunden. Versuchen Sie es mit anderen
Suchbegriffen oder kontaktieren Sie uns direkt.
</p>
) : (
<div className="space-y-3">
{filtered.map((faq, i) => (
<div
key={i}
className="border border-border rounded-xl overflow-hidden"
>
<button
onClick={() => toggle(i)}
className="w-full flex items-center justify-between px-6 py-4 text-left hover:bg-gray-50 transition-colors"
>
<span className="font-semibold text-navy pr-4">
{faq.question}
</span>
<ChevronDown
className={`h-5 w-5 text-text-muted shrink-0 transition-transform ${
openItems.has(i) ? "rotate-180" : ""
}`}
/>
</button>
{openItems.has(i) && (
<div className="px-6 pb-5 text-text-muted text-sm leading-relaxed border-t border-border pt-4">
{faq.answer}
</div>
)}
</div>
))}
</div>
)}
{filtered.length > 0 && (
<p className="text-center text-text-light text-sm mt-6">
{filtered.length} Frage{filtered.length !== 1 ? "n" : ""} angezeigt
</p>
)}
</Container>
</section>
</>
)
}

View file

@ -0,0 +1,113 @@
"use client"
import { useState, useEffect, useCallback } from "react"
import { Phone, ArrowRight, ChevronLeft, ChevronRight } from "lucide-react"
import { Button } from "@/components/ui/Button"
const slides = [
{
badge: "Medizinische Zweitmeinung",
title: "Sicherheit durch\nunabhängige Expertise",
subtitle:
"Fundierte ärztliche Zweitmeinungen für die wichtigsten Entscheidungen Ihres Lebens.",
},
{
badge: "Über 50 Fachbereiche",
title: "Kompetenz in\nallen Disziplinen",
subtitle:
"Von Kardiologie bis Onkologie erfahrene Fachärzt:innen bewerten Ihren Fall unabhängig.",
},
{
badge: "Schnell & Vertraulich",
title: "Ihre Gesundheit\nverdient Klarheit",
subtitle:
"Innerhalb weniger Tage eine medizinisch fundierte Einschätzung. DSGVO-konform und sicher.",
},
]
export function HeroSection() {
const [current, setCurrent] = useState(0)
const next = useCallback(() => setCurrent((c) => (c + 1) % slides.length), [])
const prev = useCallback(
() => setCurrent((c) => (c - 1 + slides.length) % slides.length),
[],
)
useEffect(() => {
const timer = setInterval(next, 6000)
return () => clearInterval(timer)
}, [next])
const slide = slides[current]
return (
<section className="relative bg-gradient-to-br from-navy via-navy-dark to-[#001a2e] overflow-hidden">
{/* Decorative gradient orbs */}
<div className="absolute top-0 right-0 w-[600px] h-[600px] bg-primary/10 rounded-full blur-3xl -translate-y-1/2 translate-x-1/3" />
<div className="absolute bottom-0 left-0 w-[400px] h-[400px] bg-accent/10 rounded-full blur-3xl translate-y-1/2 -translate-x-1/3" />
<div className="relative mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-24 sm:py-32 lg:py-40">
<div className="max-w-2xl">
{/* Badge */}
<div className="inline-flex items-center gap-2 bg-white/10 backdrop-blur-sm border border-white/20 rounded-full px-4 py-1.5 mb-6">
<div className="w-2 h-2 rounded-full bg-gold animate-pulse" />
<span className="text-sm text-white/90">{slide.badge}</span>
</div>
{/* Title */}
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold text-white leading-[1.1] mb-6 whitespace-pre-line">
{slide.title}
</h1>
{/* Subtitle */}
<p className="text-lg sm:text-xl text-white/70 mb-10 max-w-lg">
{slide.subtitle}
</p>
{/* CTAs */}
<div className="flex flex-wrap gap-4">
<Button href="/kontakt" variant="primary" size="lg">
<Phone className="h-4 w-4" />
Jetzt beraten lassen
</Button>
<Button href="/fachbereiche" variant="outline" size="lg">
Fachbereiche entdecken
<ArrowRight className="h-4 w-4" />
</Button>
</div>
</div>
{/* Slide navigation */}
<div className="flex items-center gap-4 mt-16">
<button
onClick={prev}
className="p-2 rounded-full border border-white/20 text-white/60 hover:text-white hover:border-white/40 transition-colors"
aria-label="Previous slide"
>
<ChevronLeft className="h-5 w-5" />
</button>
<div className="flex gap-2">
{slides.map((_, i) => (
<button
key={i}
onClick={() => setCurrent(i)}
className={`h-2 rounded-full transition-all duration-300 ${
i === current ? "w-8 bg-white" : "w-2 bg-white/30"
}`}
aria-label={`Go to slide ${i + 1}`}
/>
))}
</div>
<button
onClick={next}
className="p-2 rounded-full border border-white/20 text-white/60 hover:text-white hover:border-white/40 transition-colors"
aria-label="Next slide"
>
<ChevronRight className="h-5 w-5" />
</button>
</div>
</div>
</section>
)
}

View file

@ -0,0 +1,34 @@
import { Phone, Mail } from "lucide-react"
import { Container } from "@/components/ui/Container"
import { Button } from "@/components/ui/Button"
export function HomeCTA() {
return (
<section className="py-20 bg-white">
<Container size="md">
<div className="text-center">
<p className="text-primary font-semibold text-sm uppercase tracking-wider mb-2">
Bereit für Ihre zweite Meinung?
</p>
<h2 className="text-2xl sm:text-3xl font-bold text-navy mb-4">
Kontaktieren Sie uns noch heute und erhalten Sie die medizinische
Sicherheit, die Sie verdienen.
</h2>
<div className="flex flex-wrap justify-center gap-4 mt-8">
<Button href="tel:+4980080441000" variant="gold" size="lg">
<Phone className="h-4 w-4" />
Jetzt kostenlos beraten lassen
</Button>
<Button href="/kontakt" variant="secondary" size="lg">
<Mail className="h-4 w-4" />
E-Mail schreiben
</Button>
</div>
<p className="text-text-muted text-sm mt-6">
Kostenlose Hotline &bull; Mo-Fr 9:00-16:00 Uhr &bull; Notfall 24/7
</p>
</div>
</Container>
</section>
)
}

View file

@ -0,0 +1,95 @@
import Link from "next/link"
import { ArrowRight, Heart, Brain, Stethoscope, Pill, FlaskConical, Activity } from "lucide-react"
import { Container } from "@/components/ui/Container"
const services = [
{
title: "Intensivmedizin",
slug: "zweitmeinung-intensivmedizin",
description: "Unabhängige Zweitmeinung bei laufender oder geplanter Intensivbehandlung.",
icon: Activity,
category: "Notfall",
},
{
title: "Kardiologie",
slug: "zweitmeinung-kardiologie",
description: "Fundierte Empfehlung vor Herzkatheter, Stent oder kardiologischer OP.",
icon: Heart,
category: "Beratung",
},
{
title: "Onkologie",
slug: "zweitmeinung-onkologie",
description: "Unabhängige Einschätzung bei Krebsdiagnosen und Therapieoptionen.",
icon: FlaskConical,
category: "Beratung",
},
{
title: "Nephrologie",
slug: "zweitmeinung-nephrologie",
description: "Ärztliche Einschätzung bei Nierenerkrankungen und Dialyseempfehlung.",
icon: Stethoscope,
category: "Beratung",
},
{
title: "Gallenblase",
slug: "zweitmeinung-gallenblase",
description: "Zweitmeinung vor geplanter Gallenblasen-OP ist sie wirklich nötig?",
icon: Pill,
category: "Beratung",
},
{
title: "Schilddrüse",
slug: "zweitmeinung-schilddruese",
description: "Fundierte Einschätzung vor einer geplanten Schilddrüsen-Operation.",
icon: Brain,
category: "Beratung",
},
]
export function ServiceOverview() {
return (
<section className="py-20 bg-bg">
<Container>
<div className="text-center mb-14">
<p className="text-primary font-semibold text-sm uppercase tracking-wider mb-2">
Unsere Fachbereiche
</p>
<h2 className="text-3xl sm:text-4xl font-bold text-navy">
Zweitmeinung in 6 Fachbereichen
</h2>
</div>
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
{services.map((s) => (
<Link
key={s.slug}
href={`/fachbereiche/${s.slug}`}
className="group bg-white rounded-xl p-6 border border-border hover:border-primary/30 hover:shadow-lg transition-all"
>
<div className="flex items-start justify-between mb-4">
<div className="w-12 h-12 rounded-xl bg-primary/10 text-primary flex items-center justify-center group-hover:bg-primary group-hover:text-white transition-colors">
<s.icon className="h-6 w-6" />
</div>
{s.category === "Notfall" && (
<span className="text-xs font-bold bg-gold/20 text-gold-hover px-2 py-1 rounded-full">
24/7
</span>
)}
</div>
<h3 className="font-bold text-lg mb-2 text-navy group-hover:text-primary transition-colors">
Zweitmeinung {s.title}
</h3>
<p className="text-text-muted text-sm leading-relaxed mb-4">
{s.description}
</p>
<span className="inline-flex items-center gap-1 text-sm font-semibold text-primary">
Mehr erfahren
<ArrowRight className="h-4 w-4 group-hover:translate-x-1 transition-transform" />
</span>
</Link>
))}
</div>
</Container>
</section>
)
}

View file

@ -0,0 +1,54 @@
import { Search, Clock, ShieldCheck } from "lucide-react"
import { Container } from "@/components/ui/Container"
const benefits = [
{
icon: Search,
title: "Präzise Diagnose",
description:
"Bestätigung oder Korrektur bestehender Diagnosen durch unabhängige Fachärzte.",
},
{
icon: Clock,
title: "Schnelle Antwort",
description:
"Erhalten Sie innerhalb von 24-48 Stunden eine fundierte medizinische Einschätzung.",
},
{
icon: ShieldCheck,
title: "Absolut vertraulich",
description:
"Ihre medizinischen Daten werden streng vertraulich und DSGVO-konform behandelt.",
},
]
export function WhySection() {
return (
<section className="py-20 bg-white">
<Container size="md">
<div className="text-center mb-14">
<p className="text-primary font-semibold text-sm uppercase tracking-wider mb-2">
Warum eine zweite Meinung?
</p>
<p className="text-text-muted max-w-2xl mx-auto">
Eine zweite medizinische Meinung kann Ihnen Sicherheit geben und
dabei helfen, die beste Behandlungsentscheidung zu treffen.
</p>
</div>
<div className="grid sm:grid-cols-3 gap-8">
{benefits.map((b) => (
<div key={b.title} className="text-center group">
<div className="inline-flex items-center justify-center w-14 h-14 rounded-2xl bg-primary/10 text-primary mb-5 group-hover:bg-primary group-hover:text-white transition-colors">
<b.icon className="h-6 w-6" />
</div>
<h3 className="font-bold text-lg mb-2">{b.title}</h3>
<p className="text-text-muted text-sm leading-relaxed">
{b.description}
</p>
</div>
))}
</div>
</Container>
</section>
)
}

View file

@ -0,0 +1,22 @@
import { Phone } from "lucide-react"
export function EmergencyBanner() {
return (
<div className="bg-gold">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-4">
<div className="flex flex-col sm:flex-row items-center justify-center gap-3 sm:gap-6">
<span className="font-bold text-navy-dark text-lg">
Medizinischer Notfall?
</span>
<a
href="tel:+4980080441000"
className="inline-flex items-center gap-2 bg-navy-dark hover:bg-navy text-white font-bold px-6 py-2.5 rounded-lg transition-colors"
>
<Phone className="h-4 w-4" />
Jetzt anrufen
</a>
</div>
</div>
</div>
)
}

View file

@ -0,0 +1,153 @@
import Link from "next/link"
import { Phone, Mail, MapPin, Clock, Shield, Lock } from "lucide-react"
import type { SocialLink } from "@c2s/payload-contracts/types"
import { socialLinksToMap } from "@/lib/payload-helpers"
interface FooterProps {
socialLinks?: SocialLink[]
}
const footerColumns = [
{
title: "Top Fachbereiche",
links: [
{ label: "Kardiologie", href: "/fachbereiche/zweitmeinung-kardiologie" },
{ label: "Onkologie", href: "/fachbereiche/zweitmeinung-onkologie" },
{ label: "Intensivmedizin", href: "/fachbereiche/zweitmeinung-intensivmedizin" },
{ label: "Nephrologie", href: "/fachbereiche/zweitmeinung-nephrologie" },
{ label: "Gallenblase", href: "/fachbereiche/zweitmeinung-gallenblase" },
{ label: "Schilddrüse", href: "/fachbereiche/zweitmeinung-schilddruese" },
],
},
{
title: "Services",
links: [
{ label: "So funktioniert's", href: "/so-funktionierts" },
{ label: "Häufige Fragen (FAQ)", href: "/faq" },
{ label: "Alle Fachbereiche", href: "/fachbereiche" },
{ label: "Kontakt", href: "/kontakt" },
],
},
{
title: "Unternehmen",
links: [
{ label: "Über uns", href: "/ueber-uns" },
{ label: "Motivation", href: "/motivation" },
{ label: "complex care solutions GmbH", href: "https://complexcaresolutions.de" },
],
},
{
title: "Rechtliches",
links: [
{ label: "Impressum", href: "/impressum" },
{ label: "Datenschutz", href: "/datenschutz" },
],
},
]
export function Footer({ socialLinks }: FooterProps) {
const socialMap = socialLinksToMap(socialLinks)
return (
<footer className="bg-navy text-white">
{/* Trust Section */}
<div className="border-b border-white/10">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-8">
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-6">
<div>
<div className="flex items-center gap-2 mb-2">
<div className="w-8 h-8 rounded-full bg-primary flex items-center justify-center">
<span className="text-white font-bold text-sm">ZM</span>
</div>
<div>
<div className="font-bold text-lg">zweitmeinu.ng</div>
<div className="text-white/50 text-xs">
Beratung wenn sie wirklich wichtig ist
</div>
</div>
</div>
<p className="text-sm text-white/60 max-w-md mt-3">
Ihr zentrales Portal für medizinische Zweitmeinungen. Eine
Initiative der complex care solutions GmbH.
</p>
</div>
<div className="flex flex-wrap gap-4 text-sm text-white/70">
<span className="flex items-center gap-1.5">
<Shield className="h-4 w-4 text-gold" />
Datenschutz geprüft
</span>
<span className="flex items-center gap-1.5">
<Lock className="h-4 w-4 text-gold" />
SSL Verschlüsselt
</span>
</div>
</div>
{/* Trust badges */}
<div className="flex flex-wrap gap-x-8 gap-y-2 mt-6 text-sm text-white/50">
<span>&#10003; 100% unabhängige Experten</span>
<span>&#10003; Datenschutz garantiert</span>
<span>&#10003; Keine versteckten Kosten</span>
<span>&#10003; Full-Service</span>
</div>
</div>
</div>
{/* Links Grid */}
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-12">
<div className="grid grid-cols-2 md:grid-cols-4 gap-8">
{footerColumns.map((col) => (
<div key={col.title}>
<h3 className="font-semibold text-sm mb-4">{col.title}</h3>
<ul className="space-y-2.5">
{col.links.map((link) => (
<li key={link.href}>
<Link
href={link.href}
className="text-sm text-white/60 hover:text-white transition-colors"
>
{link.label}
</Link>
</li>
))}
</ul>
</div>
))}
</div>
</div>
{/* Bottom Bar */}
<div className="border-t border-white/10">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-6">
<div className="flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="flex items-center gap-4">
<span className="text-sm text-white/40">Folgen Sie uns:</span>
{socialMap.linkedin && (
<a href={socialMap.linkedin} target="_blank" rel="noopener noreferrer" className="text-white/50 hover:text-white" aria-label="LinkedIn">
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>
</a>
)}
{socialMap.facebook && (
<a href={socialMap.facebook} target="_blank" rel="noopener noreferrer" className="text-white/50 hover:text-white" aria-label="Facebook">
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>
</a>
)}
{socialMap.instagram && (
<a href={socialMap.instagram} target="_blank" rel="noopener noreferrer" className="text-white/50 hover:text-white" aria-label="Instagram">
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"/></svg>
</a>
)}
{socialMap.youtube && (
<a href={socialMap.youtube} target="_blank" rel="noopener noreferrer" className="text-white/50 hover:text-white" aria-label="YouTube">
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"><path d="M23.498 6.186a3.016 3.016 0 00-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 00.502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 002.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 002.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg>
</a>
)}
</div>
<p className="text-xs text-white/40">
&copy; {new Date().getFullYear()} complex care solutions GmbH. Alle Rechte vorbehalten.
</p>
</div>
</div>
</div>
</footer>
)
}

View file

@ -0,0 +1,150 @@
"use client"
import { useState, useEffect } from "react"
import Link from "next/link"
import { Phone, ChevronDown, Menu } from "lucide-react"
import { MobileMenu } from "./MobileMenu"
import type { Navigation } from "@c2s/payload-contracts/types"
interface HeaderProps {
mainMenu: Navigation["mainMenu"] | null
}
const services = [
{ name: "Intensivmedizin", slug: "zweitmeinung-intensivmedizin", icon: "🏥" },
{ name: "Kardiologie", slug: "zweitmeinung-kardiologie", icon: "❤️" },
{ name: "Onkologie", slug: "zweitmeinung-onkologie", icon: "🔬" },
{ name: "Nephrologie", slug: "zweitmeinung-nephrologie", icon: "🫘" },
{ name: "Gallenblase", slug: "zweitmeinung-gallenblase", icon: "💊" },
{ name: "Schilddrüse", slug: "zweitmeinung-schilddruese", icon: "🦋" },
]
const navLinks = [
{ label: "So funktioniert's", href: "/so-funktionierts" },
{ label: "FAQ", href: "/faq" },
{ label: "Über uns", href: "/ueber-uns" },
{ label: "Motivation", href: "/motivation" },
{ label: "Kontakt", href: "/kontakt" },
]
export function Header({ mainMenu }: HeaderProps) {
const [mobileOpen, setMobileOpen] = useState(false)
const [megaOpen, setMegaOpen] = useState(false)
const [scrolled, setScrolled] = useState(false)
useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 10)
window.addEventListener("scroll", onScroll, { passive: true })
return () => window.removeEventListener("scroll", onScroll)
}, [])
return (
<>
<header
className={`sticky top-0 z-50 bg-navy-dark border-b border-white/10 transition-shadow ${
scrolled ? "shadow-lg" : ""
}`}
>
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16">
{/* Logo */}
<Link href="/" className="flex items-center gap-2 shrink-0">
<div className="w-8 h-8 rounded-full bg-primary flex items-center justify-center">
<span className="text-white font-bold text-sm">ZM</span>
</div>
<div className="hidden sm:block">
<div className="text-white font-bold text-lg leading-tight">
zweitmeinu.ng
</div>
<div className="text-white/50 text-[10px] leading-tight">
Beratung wenn sie wirklich wichtig ist
</div>
</div>
</Link>
{/* Desktop Navigation */}
<nav className="hidden lg:flex items-center gap-1">
{/* Fachbereiche Dropdown */}
<div
className="relative"
onMouseEnter={() => setMegaOpen(true)}
onMouseLeave={() => setMegaOpen(false)}
>
<button className="flex items-center gap-1 px-3 py-2 text-sm text-white/90 hover:text-white transition-colors rounded-lg hover:bg-white/5">
Fachbereiche
<ChevronDown
className={`h-3.5 w-3.5 transition-transform ${megaOpen ? "rotate-180" : ""}`}
/>
</button>
{megaOpen && (
<div className="absolute top-full left-0 pt-2 w-72">
<div className="bg-white rounded-xl shadow-2xl border border-gray-100 overflow-hidden">
<div className="p-2">
{services.map((s) => (
<Link
key={s.slug}
href={`/fachbereiche/${s.slug}`}
className="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-gray-50 transition-colors group"
>
<span className="text-lg">{s.icon}</span>
<span className="text-sm font-medium text-gray-700 group-hover:text-primary">
Zweitmeinung {s.name}
</span>
</Link>
))}
</div>
<div className="border-t bg-gray-50 px-4 py-3">
<Link
href="/fachbereiche"
className="text-sm font-medium text-primary hover:text-primary-dark"
>
Alle Fachbereiche ansehen &rarr;
</Link>
</div>
</div>
</div>
)}
</div>
{navLinks.map((link) => (
<Link
key={link.href}
href={link.href}
className="px-3 py-2 text-sm text-white/90 hover:text-white transition-colors rounded-lg hover:bg-white/5"
>
{link.label}
</Link>
))}
</nav>
{/* Emergency CTA + Mobile Toggle */}
<div className="flex items-center gap-3">
<a
href="tel:+4980080441000"
className="hidden sm:flex items-center gap-2 bg-gold hover:bg-gold-hover text-navy-dark font-bold text-sm px-4 py-2 rounded-lg transition-colors animate-pulse-gold"
>
<Phone className="h-4 w-4" />
Notfall-Zweitmeinung
</a>
<button
onClick={() => setMobileOpen(true)}
className="lg:hidden p-2 text-white hover:bg-white/10 rounded-lg"
aria-label="Menu"
>
<Menu className="h-6 w-6" />
</button>
</div>
</div>
</div>
</header>
<MobileMenu
open={mobileOpen}
onClose={() => setMobileOpen(false)}
services={services}
navLinks={navLinks}
/>
</>
)
}

View file

@ -0,0 +1,97 @@
"use client"
import { useEffect } from "react"
import Link from "next/link"
import { X, Phone, Mail, ChevronRight } from "lucide-react"
interface MobileMenuProps {
open: boolean
onClose: () => void
services: Array<{ name: string; slug: string; icon: string }>
navLinks: Array<{ label: string; href: string }>
}
export function MobileMenu({ open, onClose, services, navLinks }: MobileMenuProps) {
useEffect(() => {
if (open) document.body.style.overflow = "hidden"
else document.body.style.overflow = ""
return () => { document.body.style.overflow = "" }
}, [open])
if (!open) return null
return (
<div className="fixed inset-0 z-[100]">
{/* Backdrop */}
<div className="absolute inset-0 bg-black/60" onClick={onClose} />
{/* Drawer */}
<div className="absolute right-0 top-0 bottom-0 w-full max-w-sm bg-white shadow-2xl overflow-y-auto">
<div className="flex items-center justify-between p-4 border-b">
<div className="font-bold text-navy text-lg">zweitmeinu.ng</div>
<button
onClick={onClose}
className="p-2 hover:bg-gray-100 rounded-lg"
aria-label="Close menu"
>
<X className="h-5 w-5" />
</button>
</div>
<nav className="p-4 space-y-1">
{/* Fachbereiche */}
<div className="pb-2 mb-2 border-b">
<div className="px-3 py-2 text-xs font-semibold text-gray-400 uppercase tracking-wider">
Fachbereiche
</div>
{services.map((s) => (
<Link
key={s.slug}
href={`/fachbereiche/${s.slug}`}
onClick={onClose}
className="flex items-center justify-between px-3 py-2.5 rounded-lg hover:bg-gray-50 text-gray-700"
>
<div className="flex items-center gap-3">
<span>{s.icon}</span>
<span className="text-sm font-medium">{s.name}</span>
</div>
<ChevronRight className="h-4 w-4 text-gray-400" />
</Link>
))}
</div>
{/* Main nav links */}
{navLinks.map((link) => (
<Link
key={link.href}
href={link.href}
onClick={onClose}
className="flex items-center justify-between px-3 py-3 rounded-lg hover:bg-gray-50 text-gray-700 font-medium"
>
{link.label}
<ChevronRight className="h-4 w-4 text-gray-400" />
</Link>
))}
</nav>
{/* Emergency CTA */}
<div className="p-4 border-t">
<a
href="tel:+4980080441000"
className="flex items-center justify-center gap-2 w-full bg-gold hover:bg-gold-hover text-navy-dark font-bold py-3 rounded-xl transition-colors"
>
<Phone className="h-5 w-5" />
Notfall-Zweitmeinung
</a>
<a
href="mailto:kontakt@zweitmeinu.ng"
className="flex items-center justify-center gap-2 w-full mt-3 border border-navy text-navy font-medium py-3 rounded-xl hover:bg-navy hover:text-white transition-colors"
>
<Mail className="h-5 w-5" />
E-Mail schreiben
</a>
</div>
</div>
</div>
)
}

View file

@ -0,0 +1,40 @@
import { Phone, Mail, Clock, AlertTriangle } from "lucide-react"
export function TopBar() {
return (
<div className="bg-navy text-white text-sm">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-10">
<div className="flex items-center gap-4 sm:gap-6">
<a
href="tel:+4980080441000"
className="flex items-center gap-1.5 hover:text-gold transition-colors"
>
<Phone className="h-3.5 w-3.5" />
<span className="font-semibold">+49 800 80 44 100</span>
</a>
<span className="hidden sm:inline text-white/30">|</span>
<a
href="mailto:kontakt@zweitmeinu.ng"
className="hidden sm:flex items-center gap-1.5 hover:text-gold transition-colors"
>
<Mail className="h-3.5 w-3.5" />
<span>kontakt@zweitmeinu.ng</span>
</a>
</div>
<div className="flex items-center gap-4 text-xs">
<span className="hidden md:flex items-center gap-1.5 text-white/70">
<Clock className="h-3 w-3" />
Mo-Fr 09:00-16:00
</span>
<span className="hidden md:inline text-white/30">|</span>
<span className="flex items-center gap-1.5 text-gold font-semibold">
<AlertTriangle className="h-3 w-3" />
24/7 Notfall
</span>
</div>
</div>
</div>
</div>
)
}

View file

@ -0,0 +1,5 @@
export { TopBar } from "./TopBar"
export { Header } from "./Header"
export { MobileMenu } from "./MobileMenu"
export { Footer } from "./Footer"
export { EmergencyBanner } from "./EmergencyBanner"

View file

@ -0,0 +1,69 @@
import { clsx } from "clsx"
import Link from "next/link"
interface ButtonProps {
children: React.ReactNode
href?: string
variant?: "primary" | "secondary" | "outline" | "gold" | "ghost"
size?: "sm" | "md" | "lg"
className?: string
onClick?: () => void
type?: "button" | "submit"
disabled?: boolean
external?: boolean
}
const variants = {
primary: "bg-primary hover:bg-primary-dark text-white",
secondary: "bg-navy hover:bg-navy-light text-white",
outline: "border-2 border-white text-white hover:bg-white hover:text-navy",
gold: "bg-gold hover:bg-gold-hover text-navy-dark font-bold",
ghost: "text-primary hover:bg-primary/10",
}
const sizes = {
sm: "px-4 py-2 text-sm",
md: "px-6 py-2.5 text-sm",
lg: "px-8 py-3.5 text-base",
}
export function Button({
children,
href,
variant = "primary",
size = "md",
className,
onClick,
type = "button",
disabled,
external,
}: ButtonProps) {
const classes = clsx(
"inline-flex items-center justify-center gap-2 rounded-lg font-semibold transition-all duration-150",
variants[variant],
sizes[size],
disabled && "opacity-50 cursor-not-allowed",
className,
)
if (href) {
if (external) {
return (
<a href={href} target="_blank" rel="noopener noreferrer" className={classes}>
{children}
</a>
)
}
return (
<Link href={href} className={classes}>
{children}
</Link>
)
}
return (
<button type={type} onClick={onClick} disabled={disabled} className={classes}>
{children}
</button>
)
}

View file

@ -0,0 +1,26 @@
import { clsx } from "clsx"
interface ContainerProps {
children: React.ReactNode
className?: string
size?: "sm" | "md" | "lg" | "xl"
}
const maxWidths = {
sm: "max-w-3xl",
md: "max-w-5xl",
lg: "max-w-7xl",
xl: "max-w-screen-2xl",
}
export function Container({
children,
className,
size = "lg",
}: ContainerProps) {
return (
<div className={clsx("mx-auto px-4 sm:px-6 lg:px-8", maxWidths[size], className)}>
{children}
</div>
)
}

142
src/lib/api.ts Normal file
View file

@ -0,0 +1,142 @@
/**
* Typed API functions for zweitmeinu.ng
* Uses @c2s/payload-contracts client for all CMS queries.
*/
import { cms } from "./cms"
import type {
Navigation,
Page,
Service,
ServiceCategory,
Faq,
SiteSetting,
SeoSetting,
SocialLink,
PaginatedResponse,
} from "@c2s/payload-contracts/types"
export type { Navigation, Service, ServiceCategory, Faq, SiteSetting, SocialLink }
// ─── Navigation ────────────────────────────────────────
export async function getNavigation(): Promise<Navigation | null> {
try {
return await cms.navigation.getNavigation({ depth: 2 })
} catch {
return null
}
}
// ─── Site Settings ─────────────────────────────────────
export async function getSiteSettings(): Promise<SiteSetting | null> {
try {
return await cms.settings.getSiteSettings({ depth: 2 })
} catch {
return null
}
}
// ─── SEO Settings ──────────────────────────────────────
export async function getSeoSettings(): Promise<SeoSetting | null> {
try {
return await cms.settings.getSeoSettings()
} catch {
return null
}
}
// ─── Social Links ──────────────────────────────────────
export async function getSocialLinks(): Promise<SocialLink[]> {
try {
const result = await cms.client.getCollection("social-links", {
limit: 20,
depth: 0,
where: { "isActive][equals": "true" },
})
return result.docs as SocialLink[]
} catch {
return []
}
}
// ─── Pages ─────────────────────────────────────────────
export async function getPage(slug: string, locale = "de"): Promise<Page | null> {
return cms.pages.getPage(slug, { locale: locale as "de" | "en", depth: 2 })
}
export async function getPages(options: { limit?: number; locale?: string } = {}): Promise<PaginatedResponse<Page>> {
return cms.pages.getPages({
limit: options.limit || 100,
locale: (options.locale || "de") as "de" | "en",
depth: 1,
})
}
// ─── Services ──────────────────────────────────────────
export async function getServices(): Promise<Service[]> {
try {
const result = await cms.services.getServices({ limit: 20, depth: 2 })
return result.docs
} catch {
return []
}
}
export async function getServiceBySlug(slug: string): Promise<Service | null> {
try {
return await cms.services.getServiceBySlug(slug, { depth: 3 })
} catch {
return null
}
}
export async function getServiceCategories(): Promise<ServiceCategory[]> {
try {
const result = await cms.services.getServiceCategories({ depth: 1 })
return result.docs
} catch {
return []
}
}
// ─── FAQs ──────────────────────────────────────────────
export async function getFaqs(): Promise<Faq[]> {
try {
const result = await cms.faqs.getFaqs({ limit: 100, depth: 1 })
return result.docs
} catch {
return []
}
}
export async function getFaqsByCategory(category: string): Promise<Faq[]> {
try {
const result = await cms.faqs.getFaqsByCategory(category, { depth: 1 })
return result.docs
} catch {
return []
}
}
// ─── Contact Form ──────────────────────────────────────
// Note: Form submissions use direct fetch() — contracts client doesn't work client-side
export async function submitContactForm(data: {
name: string
email: string
phone?: string
subject: string
urgency?: string
message: string
formId?: number
}): Promise<{ success: boolean; message?: string }> {
return cms.post("/api/form-submissions", {
form: data.formId || 1,
submissionData: [
{ field: "name", value: data.name },
{ field: "email", value: data.email },
{ field: "phone", value: data.phone || "" },
{ field: "subject", value: data.subject },
{ field: "urgency", value: data.urgency || "normal" },
{ field: "message", value: data.message },
],
})
}

15
src/lib/cms.ts Normal file
View file

@ -0,0 +1,15 @@
/**
* Payload CMS Client initialized from @c2s/payload-contracts
*
* Single shared client instance for all API calls.
* Tenant isolation is handled automatically by the contracts client.
*/
import { createPayloadClient } from "@c2s/payload-contracts/api-client"
export const cms = createPayloadClient({
baseUrl: process.env.NEXT_PUBLIC_PAYLOAD_URL || "https://cms.c2sgmbh.de",
tenantId: process.env.NEXT_PUBLIC_TENANT_ID || "12",
defaultLocale: "de",
defaultDepth: 2,
defaultRevalidate: 60,
})

View file

@ -0,0 +1,50 @@
import type { Media, SocialLink } from "@c2s/payload-contracts/types"
export function resolveRelation<T>(
value: number | T | null | undefined,
): T | null {
if (value == null || typeof value === "number") return null
return value
}
export function resolveMedia(
media: number | Media | null | undefined,
): Media | null {
return resolveRelation<Media>(media)
}
export function getMediaUrl(
media: number | Media | null | undefined,
size?: keyof NonNullable<Media["sizes"]>,
): string | undefined {
const resolved = resolveMedia(media)
if (!resolved) return undefined
if (size && resolved.sizes?.[size]?.url) return resolved.sizes[size]!.url!
return resolved.url ?? undefined
}
export function getMediaAlt(
media: number | Media | null | undefined,
fallback = "",
): string {
const resolved = resolveMedia(media)
return resolved?.alt ?? fallback
}
export function resolveRelationArray<T>(
items: (number | T)[] | null | undefined,
): T[] {
if (!items) return []
return items.filter((item): item is T => typeof item !== "number")
}
export function socialLinksToMap(
links: SocialLink[] | null | undefined,
): Record<string, string> {
if (!links) return {}
const map: Record<string, string> = {}
for (const link of links) {
if (link.isActive !== false && link.url) map[link.platform] = link.url
}
return map
}

34
tsconfig.json Normal file
View file

@ -0,0 +1,34 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts",
"**/*.mts"
],
"exclude": ["node_modules"]
}