The shared base tsconfig had strict off, so null-safety and union narrowing were not checked. Strict is now on for apps/web with 5 small typing fixes. check-db.ts and migrate-drafts.js depended on Payload, which is no longer a dependency, and are removed. CLAUDE.md no longer claims Payload. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
358 lines
13 KiB
TypeScript
358 lines
13 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState } from "react";
|
|
import { ComponentShareButton } from "../ComponentShareButton";
|
|
import { Reveal } from "../Reveal";
|
|
import {
|
|
ComposedChart,
|
|
Line,
|
|
Bar,
|
|
XAxis,
|
|
YAxis,
|
|
CartesianGrid,
|
|
Tooltip,
|
|
ResponsiveContainer,
|
|
} from "recharts";
|
|
import { ShieldAlert, Rocket, Database, Lock } from "lucide-react";
|
|
|
|
const archData = [
|
|
{
|
|
year: "Jahr 1",
|
|
saasInvest: 50,
|
|
saasIndep: 40,
|
|
customInvest: 120,
|
|
customIndep: 80,
|
|
},
|
|
{
|
|
year: "Jahr 2",
|
|
saasInvest: 100,
|
|
saasIndep: 30,
|
|
customInvest: 140,
|
|
customIndep: 90,
|
|
},
|
|
{
|
|
year: "Jahr 3",
|
|
saasInvest: 155,
|
|
saasIndep: 20,
|
|
customInvest: 155,
|
|
customIndep: 95,
|
|
},
|
|
{
|
|
year: "Jahr 4",
|
|
saasInvest: 220,
|
|
saasIndep: 10,
|
|
customInvest: 170,
|
|
customIndep: 100,
|
|
},
|
|
{
|
|
year: "Jahr 5",
|
|
saasInvest: 290,
|
|
saasIndep: 5,
|
|
customInvest: 185,
|
|
customIndep: 100,
|
|
},
|
|
];
|
|
|
|
export function ArchitectureBuilder({
|
|
className = "",
|
|
}: {
|
|
className?: string;
|
|
}) {
|
|
const [stack, setStack] = useState<"saas" | "custom">("custom");
|
|
|
|
return (
|
|
<Reveal direction="up" delay={0.1}>
|
|
<div
|
|
className={`not-prose max-w-4xl mx-auto my-12 relative group ${className}`}
|
|
>
|
|
<div
|
|
className={`absolute -inset-1 blur opacity-30 rounded-3xl transition-all duration-700 ${stack === "saas" ? "bg-gradient-to-r from-orange-200 to-red-100" : "bg-gradient-to-r from-emerald-200 to-blue-100"}`}
|
|
/>
|
|
|
|
<div
|
|
id="sim-arch-builder"
|
|
className="relative bg-white rounded-2xl border border-slate-200 shadow-xl shadow-slate-200/50 overflow-hidden flex flex-col"
|
|
>
|
|
<div
|
|
data-share-wrapper="true"
|
|
className="absolute top-4 right-4 md:opacity-0 group-hover:opacity-100 transition-opacity z-50"
|
|
>
|
|
<ComponentShareButton
|
|
targetId="sim-arch-builder"
|
|
title="Architektur: Investment vs. Unabhängigkeit"
|
|
shareText="Sehen Sie sich den zeitlichen Verlauf an: Software-Miete vs. digitales Eigentum."
|
|
/>
|
|
</div>
|
|
|
|
<div className="p-6 md:p-8 border-b border-slate-200 bg-slate-50 relative overflow-hidden">
|
|
<div className="absolute top-0 right-0 w-64 h-64 bg-slate-200 rounded-full blur-3xl -mr-32 -mt-32 opacity-50" />
|
|
<div className="relative z-10">
|
|
<div className="flex items-center gap-3 mb-2">
|
|
<span className="text-2xl">🏗️</span>
|
|
<h3 className="font-bold text-slate-900 m-0 text-xl">
|
|
Zeitlicher Verlauf: Investment vs. Unabhängigkeit
|
|
</h3>
|
|
</div>
|
|
<p className="text-sm text-slate-500 m-0 leading-relaxed max-w-2xl">
|
|
Wie Plattform-Abhängigkeit (Lock-in) über die Jahre Ihre
|
|
Skalierbarkeit zerstört, während ein Headless-System echte
|
|
Souveränität aufbaut.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex flex-col">
|
|
{/* Control Tabs */}
|
|
<div className="flex border-b border-slate-100">
|
|
<button
|
|
onClick={() => setStack("saas")}
|
|
className={`flex-1 p-4 flex flex-col items-center justify-center gap-2 transition-all ${stack === "saas" ? "bg-white border-b-2 border-orange-500" : "bg-slate-50 text-slate-400 hover:bg-slate-100"}`}
|
|
>
|
|
<div
|
|
className={`p-2 rounded-full ${stack === "saas" ? "bg-orange-100 text-orange-600" : "bg-slate-200"}`}
|
|
>
|
|
<Lock size={20} />
|
|
</div>
|
|
<div className="text-center">
|
|
<div
|
|
className={`font-bold text-sm ${stack === "saas" ? "text-slate-900" : ""}`}
|
|
>
|
|
SaaS / Baukasten
|
|
</div>
|
|
<div className="text-[10px] uppercase tracking-wider mt-0.5 opacity-70">
|
|
Gekauft ist schneller
|
|
</div>
|
|
</div>
|
|
</button>
|
|
<button
|
|
onClick={() => setStack("custom")}
|
|
className={`flex-1 p-4 flex flex-col items-center justify-center gap-2 transition-all ${stack === "custom" ? "bg-white border-b-2 border-emerald-500" : "bg-slate-50 text-slate-400 hover:bg-slate-100"}`}
|
|
>
|
|
<div
|
|
className={`p-2 rounded-full ${stack === "custom" ? "bg-emerald-100 text-emerald-600" : "bg-slate-200"}`}
|
|
>
|
|
<Rocket size={20} />
|
|
</div>
|
|
<div className="text-center">
|
|
<div
|
|
className={`font-bold text-sm ${stack === "custom" ? "text-slate-900" : ""}`}
|
|
>
|
|
Build-First / Headless
|
|
</div>
|
|
<div className="text-[10px] uppercase tracking-wider mt-0.5 opacity-70">
|
|
Architektur als Asset
|
|
</div>
|
|
</div>
|
|
</button>
|
|
</div>
|
|
|
|
{/* Chart Area */}
|
|
<div className="p-6 md:p-8 bg-white">
|
|
<div className="h-[350px] w-full">
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
<ComposedChart
|
|
data={archData}
|
|
margin={{ top: 20, right: 30, left: 20, bottom: 0 }}
|
|
>
|
|
<defs>
|
|
<linearGradient id="barSaas" x1="0" y1="0" x2="0" y2="1">
|
|
<stop
|
|
offset="0%"
|
|
stopColor="#fb923c"
|
|
stopOpacity={0.8}
|
|
/>
|
|
<stop
|
|
offset="100%"
|
|
stopColor="#fb923c"
|
|
stopOpacity={0.3}
|
|
/>
|
|
</linearGradient>
|
|
<linearGradient
|
|
id="barCustom"
|
|
x1="0"
|
|
y1="0"
|
|
x2="0"
|
|
y2="1"
|
|
>
|
|
<stop
|
|
offset="0%"
|
|
stopColor="#94a3b8"
|
|
stopOpacity={0.8}
|
|
/>
|
|
<stop
|
|
offset="100%"
|
|
stopColor="#94a3b8"
|
|
stopOpacity={0.3}
|
|
/>
|
|
</linearGradient>
|
|
</defs>
|
|
|
|
<CartesianGrid
|
|
strokeDasharray="3 3"
|
|
vertical={false}
|
|
stroke="#e2e8f0"
|
|
/>
|
|
|
|
<XAxis
|
|
dataKey="year"
|
|
axisLine={false}
|
|
tickLine={false}
|
|
tick={{ fill: "#64748b", fontSize: 12, fontWeight: 500 }}
|
|
dy={10}
|
|
/>
|
|
|
|
{/* Left Y-Axis: Cost */}
|
|
<YAxis
|
|
yAxisId="left"
|
|
tickFormatter={(val) => `€${val}k`}
|
|
axisLine={false}
|
|
tickLine={false}
|
|
tick={{ fill: "#94a3b8", fontSize: 12 }}
|
|
dx={-10}
|
|
/>
|
|
|
|
{/* Right Y-Axis: Independence */}
|
|
<YAxis
|
|
yAxisId="right"
|
|
orientation="right"
|
|
tickFormatter={(val) => `${val}%`}
|
|
axisLine={false}
|
|
tickLine={false}
|
|
tick={{
|
|
fill: "#14b8a6",
|
|
fontSize: 12,
|
|
fontWeight: "bold",
|
|
}}
|
|
dx={10}
|
|
domain={[0, 100]}
|
|
/>
|
|
|
|
<Tooltip
|
|
contentStyle={{
|
|
borderRadius: "12px",
|
|
border: "1px solid #e2e8f0",
|
|
boxShadow: "0 10px 15px -3px rgb(0 0 0 / 0.1)",
|
|
}}
|
|
formatter={(value, name): [string, string] => {
|
|
if (name === "saasInvest")
|
|
return [
|
|
`€${value}.000`,
|
|
"Kumuliertes Investment (Miete + Addons)",
|
|
];
|
|
if (name === "customInvest")
|
|
return [
|
|
`€${value}.000`,
|
|
"Kumuliertes Investment (Bau + Betrieb)",
|
|
];
|
|
if (name === "saasIndep")
|
|
return [
|
|
`${value}%`,
|
|
"Technische Unabhängigkeit (Lock-in sinkt)",
|
|
];
|
|
if (name === "customIndep")
|
|
return [
|
|
`${value}%`,
|
|
"Technische Unabhängigkeit & Datenhoheit",
|
|
];
|
|
return [String(value), String(name)];
|
|
}}
|
|
labelStyle={{
|
|
fontWeight: "bold",
|
|
color: "#0f172a",
|
|
marginBottom: "8px",
|
|
}}
|
|
/>
|
|
|
|
{stack === "saas" ? (
|
|
<>
|
|
<Bar
|
|
yAxisId="left"
|
|
dataKey="saasInvest"
|
|
fill="#fb923c"
|
|
radius={[4, 4, 0, 0]}
|
|
barSize={40}
|
|
animationDuration={1000}
|
|
/>
|
|
<Line
|
|
yAxisId="right"
|
|
type="monotone"
|
|
dataKey="saasIndep"
|
|
stroke="#ef4444"
|
|
strokeWidth={4}
|
|
dot={{
|
|
r: 6,
|
|
fill: "#ef4444",
|
|
strokeWidth: 2,
|
|
stroke: "#fff",
|
|
}}
|
|
animationDuration={1500}
|
|
/>
|
|
</>
|
|
) : (
|
|
<>
|
|
<Bar
|
|
yAxisId="left"
|
|
dataKey="customInvest"
|
|
fill="#94a3b8"
|
|
radius={[4, 4, 0, 0]}
|
|
barSize={40}
|
|
animationDuration={1000}
|
|
/>
|
|
<Line
|
|
yAxisId="right"
|
|
type="monotone"
|
|
dataKey="customIndep"
|
|
stroke="#10b981"
|
|
strokeWidth={4}
|
|
dot={{
|
|
r: 6,
|
|
fill: "#10b981",
|
|
strokeWidth: 2,
|
|
stroke: "#fff",
|
|
}}
|
|
animationDuration={1500}
|
|
/>
|
|
</>
|
|
)}
|
|
</ComposedChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
|
|
{/* Impact Summary */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-8">
|
|
<div
|
|
className={`p-4 rounded-xl border flex gap-4 ${stack === "saas" ? "bg-orange-50 border-orange-100" : "bg-emerald-50 border-emerald-100"}`}
|
|
>
|
|
<div
|
|
className={`mt-1 rounded-full p-2 h-fit ${stack === "saas" ? "bg-orange-100 text-orange-600" : "bg-emerald-100 text-emerald-600"}`}
|
|
>
|
|
{stack === "saas" ? (
|
|
<ShieldAlert size={20} />
|
|
) : (
|
|
<Database size={20} />
|
|
)}
|
|
</div>
|
|
<div>
|
|
<div
|
|
className={`font-bold text-sm ${stack === "saas" ? "text-orange-900" : "text-emerald-900"}`}
|
|
>
|
|
{stack === "saas"
|
|
? "Massiver Platform Lock-in"
|
|
: "100% Datenhoheit"}
|
|
</div>
|
|
<div className="text-xs mt-1 text-slate-600 leading-relaxed">
|
|
{stack === "saas"
|
|
? "Ihre Daten und Prozesse liegen bei Dritten. Umzüge werden nach Jahr 2 exponentiell teuers und riskant."
|
|
: "Sie besitzen die Kern-Datenbanken und APIs. Keine Lizenzschocks, keine Blackbox-Updates."}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Reveal>
|
|
);
|
|
}
|