This commit is contained in:
2026-01-30 22:33:29 +01:00
parent 615e8b4b4b
commit 6beaf3c0aa
2 changed files with 360 additions and 26 deletions

View File

@@ -5,14 +5,14 @@ import { PageHeader } from '../../src/components/PageHeader';
import { Reveal } from '../../src/components/Reveal';
import { Section } from '../../src/components/Section';
import {
ConceptSystem,
ConceptCode,
ConceptWebsite,
ConceptAutomation,
ConceptCommunication,
ConceptMessy,
ConceptTarget
} from '../../src/components/Landing/Illustrations';
SystemArchitecture,
SpeedPerformance,
SolidFoundation,
LayerSeparation,
DirectService,
AgencyChaos,
TaskDone
} from '../../src/components/Landing/Illustrations/WebsitesDescriptive';
import { motion } from 'framer-motion';
export default function WebsitesPage() {
@@ -24,7 +24,7 @@ export default function WebsitesPage() {
backgroundSize: '80px 80px'
}} />
<PageHeader
<PageHeader
title={<>Websites, die <br /><span className="text-slate-300">einfach funktionieren.</span></>}
description="Die meisten Websites funktionieren. Bis jemand sie anfasst. Ich baue Websites so, dass das alles egal ist."
backLink={{ href: '/', label: 'Zurück' }}
@@ -36,13 +36,13 @@ export default function WebsitesPage() {
number="01"
title="Der Ansatz"
borderTop
illustration={<ConceptSystem className="w-16 h-16" />}
illustration={<SystemArchitecture className="w-32 h-32" />}
>
<div className="space-y-12">
<Reveal>
<h3 className="text-3xl md:text-5xl font-bold text-slate-900 leading-tight tracking-tight max-w-3xl">
Ich baue Websites wie Systeme <br />
<span className="text-slate-300 font-serif italic">nicht wie Broschüren.</span>
<span className="text-slate-300">nicht wie Broschüren.</span>
</h3>
</Reveal>
<Reveal delay={0.2}>
@@ -74,13 +74,13 @@ export default function WebsitesPage() {
number="02"
title="Performance"
borderTop
illustration={<ConceptTarget className="w-16 h-16" />}
illustration={<SpeedPerformance className="w-32 h-32" />}
>
<div className="space-y-12">
<Reveal>
<h3 className="text-3xl md:text-5xl font-bold text-slate-900 leading-tight tracking-tight max-w-3xl">
Geschwindigkeit ist <br />
<span className="text-slate-300 font-serif italic">kein Extra. Sie ist Standard.</span>
<span className="text-slate-300">kein Extra. Sie ist Standard.</span>
</h3>
</Reveal>
<Reveal delay={0.2}>
@@ -119,13 +119,13 @@ export default function WebsitesPage() {
number="03"
title="Wartungsfrei"
borderTop
illustration={<ConceptCode className="w-16 h-16" />}
illustration={<SolidFoundation className="w-32 h-32" />}
>
<div className="space-y-12">
<Reveal>
<h3 className="text-3xl md:text-5xl font-bold text-slate-900 leading-tight tracking-tight max-w-3xl">
Keine Plugins. <br />
<span className="text-slate-300 font-serif italic">Keine Wartungshölle.</span>
<span className="text-slate-300">Keine Wartungshölle.</span>
</h3>
</Reveal>
<Reveal delay={0.2}>
@@ -154,13 +154,13 @@ export default function WebsitesPage() {
number="04"
title="Inhalte"
borderTop
illustration={<ConceptAutomation className="w-16 h-16" />}
illustration={<LayerSeparation className="w-32 h-32" />}
>
<div className="space-y-12">
<Reveal>
<h3 className="text-3xl md:text-5xl font-bold text-slate-900 leading-tight tracking-tight max-w-3xl">
Inhalte & Technik <br />
<span className="text-slate-300 font-serif italic">sind getrennt. Absichtlich.</span>
<span className="text-slate-300">sind getrennt. Absichtlich.</span>
</h3>
</Reveal>
<Reveal delay={0.2}>
@@ -196,13 +196,13 @@ export default function WebsitesPage() {
number="05"
title="Service"
borderTop
illustration={<ConceptCommunication className="w-16 h-16" />}
illustration={<DirectService className="w-32 h-32" />}
>
<div className="space-y-12">
<Reveal>
<h3 className="text-3xl md:text-5xl font-bold text-slate-900 leading-tight tracking-tight max-w-3xl">
Änderungen sind <br />
<span className="text-slate-300 font-serif italic">einfach. Wirklich.</span>
<span className="text-slate-300">einfach. Wirklich.</span>
</h3>
</Reveal>
<Reveal delay={0.2}>
@@ -233,13 +233,13 @@ export default function WebsitesPage() {
number="06"
title="Warum"
borderTop
illustration={<ConceptMessy className="w-16 h-16" />}
illustration={<AgencyChaos className="w-32 h-32" />}
>
<div className="space-y-12">
<Reveal>
<h3 className="text-3xl md:text-5xl font-bold text-slate-900 leading-tight tracking-tight max-w-3xl">
15 Jahre Agenturen <br />
<span className="text-slate-300 font-serif italic">waren genug.</span>
<span className="text-slate-300">waren genug.</span>
</h3>
</Reveal>
<Reveal delay={0.2}>
@@ -252,17 +252,18 @@ export default function WebsitesPage() {
</Section>
{/* Result */}
<Section
number="07"
<Section
number="07"
title="Ergebnis"
borderTop
variant="gray"
illustration={<TaskDone className="w-32 h-32" />}
>
<div className="space-y-16">
<Reveal>
<h2 className="text-5xl md:text-7xl font-bold text-slate-900 tracking-tighter leading-[0.9]">
<h2 className="text-5xl md:text-8xl font-bold text-slate-900 tracking-tighter leading-[0.9]">
Eine Website, die sich wie eine <br />
<span className="text-slate-300 font-serif italic">erledigte Aufgabe</span> anfühlt.
<span className="text-slate-300">erledigte Aufgabe</span> anfühlt.
</h2>
</Reveal>
@@ -282,7 +283,7 @@ export default function WebsitesPage() {
</div>
<Reveal delay={0.4}>
<div className="pt-16 border-t border-slate-100 flex flex-col md:flex-row justify-between items-start md:items-center gap-8">
<div className="pt-16 border-t border-slate-200 flex flex-col md:flex-row justify-between items-start md:items-center gap-8">
<div className="space-y-2">
<div className="text-[10px] font-bold uppercase tracking-[0.3em] text-slate-400">Technik</div>
<p className="text-slate-500 font-serif italic text-lg">

View File

@@ -0,0 +1,333 @@
'use client';
import * as React from 'react';
import { motion } from 'framer-motion';
import { IllustrationProps } from './types';
export const SystemArchitecture: React.FC<IllustrationProps> = ({ className = "", delay = 0 }) => (
<svg className={className} viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
{/* Central Core */}
<motion.rect
x="70" y="70" width="60" height="60" rx="12"
className="fill-slate-900"
initial={{ scale: 0, rotate: -45 }}
whileInView={{ scale: 1, rotate: 0 }}
viewport={{ once: true }}
transition={{ type: "spring", stiffness: 100, delay }}
/>
{/* Orbiting Elements */}
{[0, 90, 180, 270].map((angle, i) => {
const rad = (angle * Math.PI) / 180;
const x = 100 + Math.cos(rad) * 60 - 15;
const y = 100 + Math.sin(rad) * 60 - 15;
return (
<motion.rect
key={i}
x={x} y={y} width="30" height="30" rx="8"
className="fill-white stroke-slate-200"
strokeWidth="1"
initial={{ opacity: 0, scale: 0 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
animate={{
y: [y, y - 5, y],
}}
transition={{
delay: delay + 0.2 + i * 0.1,
duration: 3,
repeat: Infinity,
repeatDelay: i * 0.5,
ease: "easeInOut"
}}
/>
);
})}
{/* Connection Lines */}
<motion.path
d="M 100 40 V 70 M 100 130 V 160 M 40 100 H 70 M 130 100 H 160"
stroke="currentColor" strokeWidth="1"
className="text-slate-200"
strokeDasharray="4 4"
initial={{ pathLength: 0 }}
whileInView={{ pathLength: 1 }}
viewport={{ once: true }}
transition={{ duration: 1, delay: delay + 0.6 }}
/>
</svg>
);
export const SpeedPerformance: React.FC<IllustrationProps> = ({ className = "", delay = 0 }) => (
<svg className={className} viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
{/* The "Built" Foundation */}
<motion.path
d="M 40 160 H 160"
stroke="currentColor" strokeWidth="4" strokeLinecap="round"
className="text-slate-900"
initial={{ pathLength: 0 }}
whileInView={{ pathLength: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.8, delay }}
/>
{/* Bricks forming a structure */}
{[
{ x: 50, y: 130, w: 30 },
{ x: 85, y: 130, w: 30 },
{ x: 120, y: 130, w: 30 },
{ x: 65, y: 105, w: 30 },
{ x: 105, y: 105, w: 30 },
].map((brick, i) => (
<motion.rect
key={i}
x={brick.x} y={brick.y} width={brick.w} height="20" rx="2"
className="fill-slate-100 stroke-slate-200"
strokeWidth="1"
initial={{ opacity: 0, y: brick.y + 10 }}
whileInView={{ opacity: 1, y: brick.y }}
viewport={{ once: true }}
transition={{ delay: delay + 0.2 + i * 0.1 }}
/>
))}
{/* Lightning Bolt emerging from the "Built" structure */}
<motion.path
d="M 110 30 L 80 80 H 100 L 70 130"
stroke="currentColor" strokeWidth="6" strokeLinecap="round" strokeLinejoin="round"
className="text-slate-900"
initial={{ pathLength: 0, filter: "drop-shadow(0 0 0px rgba(0,0,0,0))" }}
whileInView={{ pathLength: 1, filter: "drop-shadow(0 0 8px rgba(0,0,0,0.1))" }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: delay + 1, ease: "easeOut" }}
/>
{/* Speed Lines */}
{[0, 1, 2].map((i) => (
<motion.path
key={i}
d={`M ${130 + i * 10} ${50 + i * 20} H ${150 + i * 10}`}
stroke="currentColor" strokeWidth="2" strokeLinecap="round"
className="text-slate-200"
initial={{ x: -10, opacity: 0 }}
animate={{ x: 10, opacity: [0, 1, 0] }}
transition={{ duration: 0.8, repeat: Infinity, delay: delay + 1.2 + i * 0.2 }}
/>
))}
</svg>
);
export const SolidFoundation: React.FC<IllustrationProps> = ({ className = "", delay = 0 }) => (
<svg className={className} viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
{/* Stacked Blocks */}
<motion.rect
x="40" y="140" width="120" height="30" rx="4"
className="fill-slate-900"
initial={{ y: 20, opacity: 0 }}
whileInView={{ y: 0, opacity: 1 }}
viewport={{ once: true }}
transition={{ delay }}
/>
<motion.rect
x="60" y="100" width="80" height="30" rx="4"
className="fill-slate-400"
initial={{ y: 20, opacity: 0 }}
whileInView={{ y: 0, opacity: 1 }}
viewport={{ once: true }}
transition={{ delay: delay + 0.2 }}
/>
<motion.rect
x="80" y="60" width="40" height="30" rx="4"
className="fill-slate-200"
initial={{ y: 20, opacity: 0 }}
whileInView={{ y: 0, opacity: 1 }}
viewport={{ once: true }}
transition={{ delay: delay + 0.4 }}
/>
{/* Shield Icon Overlay */}
<motion.path
d="M 100 30 L 120 40 V 60 C 120 80 100 90 100 90 C 100 90 80 80 80 60 V 40 L 100 30 Z"
className="fill-white stroke-slate-900"
strokeWidth="2"
initial={{ scale: 0 }}
whileInView={{ scale: 1 }}
viewport={{ once: true }}
transition={{ type: "spring", delay: delay + 0.8 }}
/>
</svg>
);
export const LayerSeparation: React.FC<IllustrationProps> = ({ className = "", delay = 0 }) => (
<svg className={className} viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
{/* Top Layer (Content) */}
<motion.g
initial={{ y: -20, opacity: 0 }}
whileInView={{ y: 0, opacity: 1 }}
viewport={{ once: true }}
transition={{ delay }}
>
<rect x="40" y="40" width="120" height="40" rx="8" className="fill-white stroke-slate-200" strokeWidth="1" />
<rect x="55" y="55" width="30" height="10" rx="2" className="fill-slate-100" />
<rect x="95" y="55" width="50" height="10" rx="2" className="fill-slate-100" />
</motion.g>
{/* Gap with Arrows */}
<motion.path
d="M 100 90 V 110"
stroke="currentColor" strokeWidth="2" strokeLinecap="round"
className="text-slate-200"
strokeDasharray="4 4"
initial={{ pathLength: 0 }}
whileInView={{ pathLength: 1 }}
viewport={{ once: true }}
transition={{ delay: delay + 0.4 }}
/>
{/* Bottom Layer (Code/Logic) */}
<motion.g
initial={{ y: 20, opacity: 0 }}
whileInView={{ y: 0, opacity: 1 }}
viewport={{ once: true }}
transition={{ delay: delay + 0.2 }}
>
<rect x="40" y="120" width="120" height="40" rx="8" className="fill-slate-900" />
<path d="M 60 140 L 70 135 L 60 130 M 140 140 L 130 135 L 140 130" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</motion.g>
</svg>
);
export const DirectService: React.FC<IllustrationProps> = ({ className = "", delay = 0 }) => (
<svg className={className} viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
{/* Two Nodes */}
<motion.circle
cx="50" cy="100" r="15"
className="fill-slate-200"
initial={{ scale: 0 }}
whileInView={{ scale: 1 }}
viewport={{ once: true }}
transition={{ delay }}
/>
<motion.circle
cx="150" cy="100" r="15"
className="fill-slate-900"
initial={{ scale: 0 }}
whileInView={{ scale: 1 }}
viewport={{ once: true }}
transition={{ delay: delay + 0.2 }}
/>
{/* Direct Connection */}
<motion.path
d="M 65 100 H 135"
stroke="currentColor" strokeWidth="3" strokeLinecap="round"
className="text-slate-900"
initial={{ pathLength: 0 }}
whileInView={{ pathLength: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.8, delay: delay + 0.4 }}
/>
{/* Pulse moving between */}
<motion.circle r="4" className="fill-white">
<animateMotion
dur="2s"
repeatCount="indefinite"
path="M 65 100 H 135"
/>
</motion.circle>
</svg>
);
export const AgencyChaos: React.FC<IllustrationProps> = ({ className = "", delay = 0 }) => (
<svg className={className} viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
{/* Messy Path */}
<motion.path
d="M 30 100 C 50 40, 70 160, 90 100 C 110 40, 130 160, 170 100"
stroke="currentColor" strokeWidth="2" strokeLinecap="round"
className="text-slate-200"
initial={{ pathLength: 0 }}
whileInView={{ pathLength: 1 }}
viewport={{ once: true }}
transition={{ duration: 2, delay }}
/>
{/* Intersecting Circles (Meetings) */}
{[0, 1, 2].map((i) => (
<motion.circle
key={i}
cx={60 + i * 40} cy={100 + (i % 2 === 0 ? -30 : 30)} r="20"
className="stroke-slate-100 fill-white"
strokeWidth="1"
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
animate={{ scale: [1, 1.1, 1] }}
transition={{
delay: delay + 0.5 + i * 0.3,
duration: 4,
repeat: Infinity,
repeatDelay: i * 0.5
}}
/>
))}
{/* Slate "X" or Stop signs - NO COLOR */}
<motion.path
d="M 160 90 L 180 110 M 180 90 L 160 110"
stroke="currentColor" strokeWidth="3" strokeLinecap="round"
className="text-slate-300"
initial={{ opacity: 0, scale: 0 }}
whileInView={{ opacity: 0.5, scale: 1 }}
viewport={{ once: true }}
transition={{ delay: delay + 1.5 }}
/>
</svg>
);
export const TaskDone: React.FC<IllustrationProps> = ({ className = "", delay = 0 }) => (
<svg className={className} viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
{/* Checkmark Circle */}
<motion.circle
cx="100" cy="100" r="60"
className="stroke-slate-900"
strokeWidth="2"
initial={{ pathLength: 0 }}
whileInView={{ pathLength: 1 }}
viewport={{ once: true }}
transition={{ duration: 1, delay }}
/>
{/* Checkmark */}
<motion.path
d="M 70 100 L 90 120 L 135 75"
stroke="currentColor" strokeWidth="8" strokeLinecap="round" strokeLinejoin="round"
className="text-slate-900"
initial={{ pathLength: 0 }}
whileInView={{ pathLength: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: delay + 0.8 }}
/>
{/* Confetti/Sparkles */}
{[0, 45, 90, 135, 180, 225, 270, 315].map((angle, i) => {
const rad = (angle * Math.PI) / 180;
const x1 = 100 + Math.cos(rad) * 70;
const y1 = 100 + Math.sin(rad) * 70;
const x2 = 100 + Math.cos(rad) * 85;
const y2 = 100 + Math.sin(rad) * 85;
return (
<motion.line
key={i}
x1={x1} y1={y1} x2={x2} y2={y2}
stroke="currentColor" strokeWidth="2" strokeLinecap="round"
className="text-slate-200"
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ delay: delay + 1.2 + i * 0.05 }}
/>
);
})}
</svg>
);