Files
mintel.me/apps/web/src/components/simulations/LoadTimeSimulator.tsx
Marc Mintel 6b6b2b8ece
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 8s
Build & Deploy / 🏗️ Build (push) Successful in 21m19s
Build & Deploy / 🚀 Deploy (push) Successful in 14s
Build & Deploy / 🧪 QA (push) Successful in 1m28s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 2m59s
Build & Deploy / 🔔 Notify (push) Successful in 9s
Nightly QA / 🔗 Links & Deps (push) Successful in 3m40s
Nightly QA / 🎭 Lighthouse (push) Successful in 4m18s
Nightly QA / 🔍 Static Analysis (push) Successful in 4m34s
Nightly QA / 📝 E2E (push) Successful in 4m45s
Nightly QA / 🔔 Notify (push) Has been skipped
fix(blog): auto-play LoadTimeSimulator, fix Carousel data, filter TableOfContents text, extend CarouselBlock schema
2026-03-06 00:54:45 +01:00

225 lines
14 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import React, { useState, useEffect, useRef } from 'react';
import { ComponentShareButton } from '../ComponentShareButton';
import { Reveal } from '../Reveal';
import { RotateCcw } from 'lucide-react';
export function LoadTimeSimulator({ className = '' }: { className?: string }) {
const [isRunning, setIsRunning] = useState(false);
const [timeElapsed, setTimeElapsed] = useState(0);
const [legacyState, setLegacyState] = useState(0);
const [hasAutoStarted, setHasAutoStarted] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
const [mintelState, setMintelState] = useState(0);
useEffect(() => {
let interval: NodeJS.Timeout;
if (isRunning && timeElapsed < 7500) {
interval = setInterval(() => {
setTimeElapsed(prev => {
const next = prev + 50;
// Legacy Loading Logic (stuttering, slow, layout shifts)
if (next >= 1200 && next < 3000) setLegacyState(1); // Header only
else if (next >= 3000 && next < 5500) setLegacyState(2); // Text appears
else if (next >= 5500 && next < 7000) setLegacyState(3); // Image loads (CLS)
else if (next >= 7000) setLegacyState(4); // Finally interactive
// Mintel Loading Logic (Instant Edge Delivery)
if (next >= 350) setMintelState(1); // Fully loaded instantly
return next;
});
}, 50);
} else if (timeElapsed >= 7500) {
setIsRunning(false);
}
return () => clearInterval(interval);
}, [isRunning, timeElapsed]);
// Auto-start the race when scrolled into viewport
useEffect(() => {
if (hasAutoStarted) return;
const el = containerRef.current;
if (!el) return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setHasAutoStarted(true);
setIsRunning(true);
observer.disconnect();
}
},
{ threshold: 0.4 }
);
observer.observe(el);
return () => observer.disconnect();
}, [hasAutoStarted]);
const startRace = () => {
setTimeElapsed(0);
setLegacyState(0);
setMintelState(0);
setIsRunning(true);
};
return (
<Reveal direction="up" delay={0.1}>
<div ref={containerRef} className={`not-prose max-w-4xl mx-auto my-12 relative group ${className}`}>
<div className="absolute -inset-1 bg-gradient-to-r from-red-100 to-emerald-100 rounded-3xl blur opacity-30" />
<div id="sim-load-time" className="relative bg-white rounded-2xl border border-slate-200 shadow-sm 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-load-time" title="Ladezeit Simulator" />
</div>
<div className="p-6 border-b border-slate-200 bg-slate-50 flex flex-col md:flex-row items-start md:items-center justify-between gap-4">
<div>
<div className="flex items-center gap-2 mb-1">
<span className="text-xl">🏎</span>
<h3 className="font-bold text-slate-900 m-0">Architektur-Rennen: Legacy CMS vs. Mintel Stack</h3>
</div>
<p className="text-xs text-slate-500 m-0 leading-relaxed">
Simulieren Sie den Unterschied zwischen dynamischem Server-Rendering (PHP/MySQL) und statischer Edge-Auslieferung (<span className="font-mono bg-slate-200 px-1 rounded text-[10px]">TTV &lt; 500ms</span>).
</p>
</div>
{timeElapsed > 0 && !isRunning && (
<button
onClick={startRace}
className="shrink-0 flex items-center gap-2 px-6 py-2.5 bg-slate-900 !text-white rounded-full font-bold text-sm hover:hover:bg-black hover:scale-105 active:scale-95 transition-all shadow-md"
>
<RotateCcw size={16} />
Neustart
</button>
)}
</div>
<div className="grid md:grid-cols-2 divide-y md:divide-y-0 md:divide-x divide-slate-100 bg-slate-50/50">
{/* LEGACY LANE */}
<div className="p-6 flex flex-col items-center">
<div className="w-full flex justify-between items-center mb-6">
<span className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">Legacy Monolith</span>
<span className={`font-mono font-bold ${legacyState === 4 ? 'text-red-500' : 'text-slate-400'}`}>
{legacyState === 4 ? "7.00s" : (timeElapsed / 1000).toFixed(2) + "s"}
</span>
</div>
{/* Browser Mockup */}
<div className="w-full max-w-[280px] aspect-[3/4] bg-white border border-slate-200 rounded-lg shadow-sm overflow-hidden flex flex-col">
<div className="h-6 bg-slate-100 border-b border-slate-200 flex items-center px-2 gap-1.5 shrink-0">
<div className="w-2 h-2 rounded-full bg-slate-300" />
<div className="w-2 h-2 rounded-full bg-slate-300" />
<div className="w-2 h-2 rounded-full bg-slate-300" />
</div>
<div className="p-4 flex-1 flex flex-col relative">
{/* Spinner */}
{legacyState === 0 && (
<div className="absolute inset-0 flex flex-col items-center justify-center text-slate-300 gap-2">
<div className="w-6 h-6 border-2 border-slate-200 border-t-slate-400 rounded-full animate-spin" />
<span className="text-[10px] font-mono tracking-widest uppercase">TTFB Waiting...</span>
</div>
)}
{/* Content that shuffles around to simulate CLS */}
<div className={`transition-opacity duration-300 ${legacyState >= 1 ? 'opacity-100' : 'opacity-0'}`}>
<div className="h-4 w-3/4 bg-slate-200 rounded mb-4" /> {/* Header */}
</div>
{/*
Simulate Layout Shift:
Text loads first (state 2), then at state 3 an image drops in above it, pushing the text down.
*/}
<div className="flex flex-col gap-3 transition-opacity duration-300 mt-2">
{legacyState >= 3 && (
<div className="h-24 w-full bg-slate-200 relative overflow-hidden rounded animate-pulse">
<div className="absolute inset-0 flex items-center justify-center">
<span className="text-2xl opacity-50">🖼</span>
</div>
</div>
)}
<div className={`space-y-2 transition-transform duration-300 ${legacyState >= 2 ? 'opacity-100' : 'opacity-0'} ${legacyState === 3 ? 'translate-y-2' : ''}`}>
<div className="h-2 w-full bg-slate-100 rounded" />
<div className="h-2 w-5/6 bg-slate-100 rounded" />
<div className="h-2 w-4/6 bg-slate-100 rounded" />
</div>
</div>
{legacyState === 4 && (
<div className="absolute bottom-4 left-4 right-4 animate-in slide-in-from-bottom-2 fade-in">
<div className="h-8 bg-slate-900 rounded flex items-center justify-center">
<span className="text-[10px] font-bold text-white uppercase">Interaktiv (Hydrated)</span>
</div>
</div>
)}
</div>
</div>
<div className="h-6 mt-4 flex items-center">
{legacyState === 0 && timeElapsed > 0 && <span className="text-[10px] text-slate-400 uppercase tracking-widest animate-pulse">Waiting for Server...</span>}
{legacyState === 1 && <span className="text-[10px] text-amber-500 uppercase tracking-widest">Parsing HTML...</span>}
{legacyState === 2 && <span className="text-[10px] text-amber-500 uppercase tracking-widest">Downloading Assets...</span>}
{legacyState === 3 && <span className="text-[10px] text-red-500 font-bold uppercase tracking-widest">Layout Shift Detected!</span>}
{legacyState === 4 && <span className="text-[10px] text-red-500 font-bold uppercase tracking-widest">Finished in 7.0s</span>}
</div>
</div>
{/* MINTEL LANE */}
<div className="p-6 flex flex-col items-center border-t md:border-t-0 border-slate-100">
<div className="w-full flex justify-between items-center mb-6">
<span className="text-[10px] font-bold text-emerald-500 uppercase tracking-widest">Mintel Stack (Edge)</span>
<span className={`font-mono font-bold ${mintelState === 1 ? 'text-emerald-500' : 'text-slate-400'}`}>
{mintelState === 1 ? "0.35s" : (timeElapsed / 1000).toFixed(2) + "s"}
</span>
</div>
{/* Browser Mockup */}
<div className="w-full max-w-[280px] aspect-[3/4] bg-white border-2 border-emerald-100 rounded-lg shadow-sm overflow-hidden flex flex-col relative ring-4 ring-emerald-50 ring-opacity-50 transition-all duration-500">
<div className="h-6 bg-emerald-50 border-b border-emerald-100 flex items-center px-2 gap-1.5 shrink-0">
<div className="w-2 h-2 rounded-full bg-emerald-200" />
<div className="w-2 h-2 rounded-full bg-emerald-200" />
<div className="w-2 h-2 rounded-full bg-emerald-200" />
</div>
<div className="p-4 flex-1 flex flex-col relative">
{mintelState === 0 && (
<div className="absolute inset-0 flex flex-col items-center justify-center text-emerald-300 gap-2">
<span className="text-[10px] font-mono tracking-widest uppercase">CDN Hit...</span>
</div>
)}
{/* Instant Load: Everything drops in perfectly immediately */}
<div className={`transition-opacity duration-300 h-full flex flex-col ${mintelState === 1 ? 'opacity-100' : 'opacity-0'}`}>
<div className="h-4 w-3/4 bg-emerald-100 rounded mb-4 shrink-0" />
<div className="h-24 w-full bg-emerald-50 relative overflow-hidden rounded mb-3 shrink-0 flex items-center justify-center border border-emerald-100">
<span className="text-2xl opacity-80">🚀</span>
</div>
<div className="space-y-2 shrink-0">
<div className="h-2 w-full bg-slate-100 rounded" />
<div className="h-2 w-5/6 bg-slate-100 rounded" />
<div className="h-2 w-4/6 bg-slate-100 rounded" />
</div>
<div className="mt-auto pt-4 shrink-0">
<div className="h-8 bg-emerald-500 rounded flex items-center justify-center shadow-lg shadow-emerald-500/20">
<span className="text-[10px] font-bold text-white uppercase">Instant Interactive</span>
</div>
</div>
</div>
</div>
</div>
<div className="h-6 mt-4 flex items-center">
{mintelState === 0 && timeElapsed > 0 && <span className="text-[10px] text-emerald-400 uppercase tracking-widest">Routing to Edge...</span>}
{mintelState === 1 && <span className="text-[10px] text-emerald-500 font-bold uppercase tracking-widest">Loaded & Ready in 350ms</span>}
</div>
</div>
</div>
</div>
</div>
</Reveal>
);
}