Files
mintel.me/apps/web/src/components/simulations/ArchitectureBuilder.tsx
T
mmintelandClaude Sonnet 5.5 d33a5d6c63 chore: enable TypeScript strict mode and drop dead Payload scripts
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>
2026-10-03 18:59:47 +02:00

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>
);
}