Files
mintel.me/apps/web/src/components/RevenueLossCalculator.tsx
Marc Mintel b15c8408ff
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 5s
Build & Deploy / 🏗️ Build (push) Failing after 14s
Build & Deploy / 🧪 QA (push) Failing after 1m48s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🩺 Health Check (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
fix(blog): optimize component share logic, typography, and modal layouts
2026-02-22 11:41:28 +01:00

189 lines
12 KiB
TypeScript

"use client";
import React, { useState, useMemo } from "react";
import { TrendingDown, Activity, Settings2, BarChart3 } from "lucide-react";
import { cn } from "../utils/cn";
import { ComponentShareButton } from "./ComponentShareButton";
import { Reveal } from "./Reveal";
export function RevenueLossCalculator() {
const [visitors, setVisitors] = useState(5000);
const [conversionRate] = useState(2.0);
const [orderValue, setOrderValue] = useState(150);
const [loadTime, setLoadTime] = useState(4.0);
const shareId = "revenue-loss-v1";
const LOSS_PER_SECOND = 0.07;
const BASE_SPEED = 2.5;
const results = useMemo(() => {
const delay = Math.max(0, loadTime - BASE_SPEED);
const dropFactor = 1 - (1 - LOSS_PER_SECOND) ** delay;
const currentOrders = visitors * (conversionRate / 100);
const currentRevenue = currentOrders * orderValue;
const potentialRevenue = currentRevenue / (1 - dropFactor);
const lostRevenue = potentialRevenue - currentRevenue;
const lostOrders = (potentialRevenue - currentRevenue) / orderValue;
return {
lostRevenue: Math.round(lostRevenue),
lostOrders: Math.round(lostOrders),
potentialRevenue: Math.round(potentialRevenue),
};
}, [visitors, conversionRate, orderValue, loadTime]);
return (
<Reveal direction="up" delay={0.1}>
<div id={shareId} className="w-full max-w-2xl mx-auto my-16 group relative transition-all duration-500 ease-out z-10 font-sans">
{/* Ambient Glow */}
<div className="absolute -inset-1 bg-gradient-to-r from-red-100/30 to-slate-100/30 rounded-[2.5rem] blur opacity-10 group-hover:opacity-30 transition duration-1000 -z-10" />
<div className="glass bg-white/80 backdrop-blur-xl border border-slate-100 rounded-3xl shadow-sm group-hover:shadow-md group-hover:border-slate-200 transition-all duration-500 overflow-hidden relative">
{/* Header */}
<div className="border-b border-slate-100 p-6 flex items-center justify-between relative z-20">
<div className="flex items-center gap-3">
<div className="p-2 rounded-xl bg-slate-50 border border-slate-100 group-hover:bg-red-50 group-hover:border-red-100 group-hover:text-red-500 transition-colors">
<TrendingDown className="w-5 h-5" />
</div>
<div>
<h4 className="text-sm font-black tracking-[0.2em] text-slate-800 uppercase m-0 leading-none">
REVENUE_SIMULATOR
</h4>
<p className="text-[10px] text-slate-400 font-bold uppercase tracking-widest mt-1">Performance x Conversion</p>
</div>
</div>
<div className="flex items-center gap-4">
<div className="hidden md:flex items-center gap-1.5 px-3 py-1 rounded-full bg-emerald-50 border border-emerald-100">
<Activity className="w-3 h-3 text-emerald-500 animate-pulse" />
<span className="text-[10px] font-black text-emerald-600 uppercase tracking-widest">LIVE_ENGINE</span>
</div>
<div className="opacity-0 group-hover:opacity-100 transition-opacity duration-500">
<ComponentShareButton targetId={shareId} title="Performance Revenue Simulator" />
</div>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-5 relative z-10">
{/* Controls */}
<div className="md:col-span-3 p-8 space-y-10 border-b md:border-b-0 md:border-r border-slate-100 bg-white/40">
{/* Traffic */}
<div className="space-y-5">
<div className="flex justify-between items-end">
<div className="flex items-center gap-2">
<Settings2 className="w-3 h-3 text-slate-400" />
<span className="text-[10px] font-black text-slate-500 uppercase tracking-widest">Monatlicher Traffic</span>
</div>
<span className="text-xl font-black text-slate-900 tabular-nums leading-none">
{visitors.toLocaleString()} <span className="text-[10px] text-slate-400 font-bold">VISITS</span>
</span>
</div>
<div className="relative group/slider">
<input
type="range"
min="500" max="100000" step="500"
value={visitors}
onChange={(e) => setVisitors(Number(e.target.value))}
className="w-full h-1.5 bg-slate-100 rounded-full appearance-none outline-none [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-slate-800 [&::-webkit-slider-thumb]:rounded-full cursor-pointer hover:[&::-webkit-slider-thumb]:scale-110 [&::-webkit-slider-thumb]:transition-transform"
/>
</div>
</div>
{/* Order Value */}
<div className="space-y-5">
<div className="flex justify-between items-end">
<div className="flex items-center gap-2">
<BarChart3 className="w-3 h-3 text-slate-400" />
<span className="text-[10px] font-black text-slate-500 uppercase tracking-widest">Ø Kundenwert</span>
</div>
<span className="text-xl font-black text-slate-900 tabular-nums leading-none">
{orderValue.toLocaleString()}
</span>
</div>
<div className="relative group/slider">
<input
type="range"
min="10" max="5000" step="10"
value={orderValue}
onChange={(e) => setOrderValue(Number(e.target.value))}
className="w-full h-1.5 bg-slate-100 rounded-full appearance-none outline-none [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-slate-800 [&::-webkit-slider-thumb]:rounded-full cursor-pointer hover:[&::-webkit-slider-thumb]:scale-110 [&::-webkit-slider-thumb]:transition-transform"
/>
</div>
</div>
{/* Load Time */}
<div className="space-y-5 pt-8 border-t border-slate-100 border-dashed">
<div className="flex justify-between items-end">
<div className="flex flex-col">
<span className="text-[10px] font-black text-slate-500 uppercase tracking-widest mb-1">Website Ladezeit</span>
<span className={cn("text-[8px] font-bold uppercase px-1.5 py-0.5 rounded w-fit",
loadTime > 2.5 ? "bg-red-50 text-red-500" : "bg-emerald-50 text-emerald-500"
)}>
{loadTime > 2.5 ? "Kritisch > 2.5s" : "Optimal < 2.5s"}
</span>
</div>
<span className={cn("text-3xl font-black tabular-nums tracking-tighter transition-colors",
loadTime > 2.5 ? "text-red-500" : "text-slate-900"
)}>
{loadTime.toFixed(1)}s
</span>
</div>
<div className="relative group/slider">
<input
type="range"
min="1.0" max="15.0" step="0.5"
value={loadTime}
onChange={(e) => setLoadTime(Number(e.target.value))}
className={cn("w-full h-1.5 rounded-full appearance-none outline-none [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-5 [&::-webkit-slider-thumb]:h-5 [&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:rounded-full cursor-pointer hover:[&::-webkit-slider-thumb]:scale-110 transition-all",
loadTime > 2.5 ? "bg-red-100 [&::-webkit-slider-thumb]:border-red-500" : "bg-slate-100 [&::-webkit-slider-thumb]:border-slate-800"
)}
/>
</div>
</div>
</div>
{/* Results */}
<div className="md:col-span-2 p-8 flex flex-col justify-center space-y-10 bg-slate-50/20 backdrop-blur-sm relative overflow-hidden">
{/* Decorative Background Icon */}
<TrendingDown className="absolute -bottom-10 -right-10 w-48 h-48 text-slate-200/20 rotate-12 -z-10" />
<div className="relative">
<span className="block text-[10px] font-black text-slate-400 uppercase tracking-[0.2em] mb-3">Entgangener Umsatz <span className="text-[8px] text-slate-300">(MTL.)</span></span>
<div className="flex items-baseline gap-2">
<span className={cn("text-5xl font-black tracking-tighter transition-colors",
results.lostRevenue > 0 ? "text-red-500" : "text-slate-900"
)}>
{results.lostRevenue.toLocaleString()}
</span>
</div>
</div>
<div className="relative">
<span className="block text-[10px] font-black text-slate-400 uppercase tracking-[0.2em] mb-3">Verlorene Leads <span className="text-[8px] text-slate-300">(MTL.)</span></span>
<div className="flex items-baseline gap-2">
<span className="text-4xl font-extrabold text-slate-800 tracking-tight tracking-tighter">
{results.lostOrders}
</span>
<span className="text-[10px] font-black text-slate-400 uppercase">Nutzer</span>
</div>
</div>
<div className="pt-6 border-t border-slate-100">
<p className="text-[9px] font-bold text-slate-300 uppercase tracking-[0.2em] leading-relaxed">
REF: GOOGLE + AKAMAI_DATA<br />
<span className="text-slate-400">(0.07 DROP RATE PER SECOND)</span>
</p>
</div>
</div>
</div>
</div>
</div>
</Reveal>
);
}