303 lines
14 KiB
TypeScript
303 lines
14 KiB
TypeScript
import React, { useState, useEffect, useRef } from 'react';
|
||
import { TerminalWindow } from './TerminalWindow';
|
||
|
||
export const MultiModelAnimation: React.FC = () => {
|
||
const [stage, setStage] = useState(0);
|
||
const containerRef = useRef<HTMLDivElement>(null);
|
||
const [isVisible, setIsVisible] = useState(false);
|
||
|
||
// Intersection Observer
|
||
useEffect(() => {
|
||
const observer = new IntersectionObserver(
|
||
([entry]) => {
|
||
if (entry.isIntersecting) {
|
||
setIsVisible(true);
|
||
observer.disconnect();
|
||
}
|
||
},
|
||
{ threshold: 0.3 }
|
||
);
|
||
|
||
if (containerRef.current) {
|
||
observer.observe(containerRef.current);
|
||
}
|
||
|
||
return () => observer.disconnect();
|
||
}, []);
|
||
|
||
// Animation Sequence
|
||
useEffect(() => {
|
||
if (!isVisible) return;
|
||
|
||
const timeline = [
|
||
{ s: 1, delay: 500 }, // Start typing command
|
||
{ s: 2, delay: 1300 }, // Opus line
|
||
{ s: 3, delay: 1900 }, // Sonnet line
|
||
{ s: 4, delay: 2500 }, // Haiku line
|
||
{ s: 5, delay: 3100 }, // Subagent line
|
||
{ s: 6, delay: 3600 }, // Connected success
|
||
{ s: 7, delay: 4200 }, // Draw lines
|
||
{ s: 8, delay: 5000 }, // Msg 1
|
||
{ s: 9, delay: 5500 }, // Msg 2
|
||
{ s: 10, delay: 6000 }, // Msg 3
|
||
{ s: 11, delay: 7000 }, // Tagline 1
|
||
{ s: 12, delay: 7500 }, // Tagline 2
|
||
{ s: 13, delay: 8200 }, // Tagline 3
|
||
];
|
||
|
||
let timeouts: ReturnType<typeof setTimeout>[] = [];
|
||
|
||
timeline.forEach(step => {
|
||
timeouts.push(setTimeout(() => setStage(step.s), step.delay));
|
||
});
|
||
|
||
return () => timeouts.forEach(clearTimeout);
|
||
}, [isVisible]);
|
||
|
||
return (
|
||
<div ref={containerRef} className="max-w-6xl mx-auto my-16 relative">
|
||
{/* Background Ambience */}
|
||
<div className={`absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[120%] h-[120%] bg-claude-ish/5 blur-[120px] rounded-full transition-opacity duration-1000 pointer-events-none ${stage >= 6 ? 'opacity-100' : 'opacity-0'}`} />
|
||
|
||
<div className="bg-[#050505] rounded-3xl p-1 border border-white/5 shadow-2xl relative overflow-hidden flex flex-col gap-8">
|
||
|
||
{/* Terminal Section */}
|
||
<div className="relative z-10 px-4 pt-4 md:px-12 md:pt-12">
|
||
<TerminalWindow
|
||
title="claudish — zsh — 120×24"
|
||
className="w-full shadow-2xl border-white/10 min-h-[300px] bg-[#0c0c0c]"
|
||
noPadding={false}
|
||
>
|
||
<div className="font-mono text-xs md:text-[13px] space-y-2.5 leading-relaxed text-gray-300">
|
||
{/* Command */}
|
||
<div className={`transition-opacity duration-300 flex items-center ${stage >= 1 ? 'opacity-100' : 'opacity-0'}`}>
|
||
<span className="text-claude-ish mr-2 font-bold">➜</span>
|
||
<span className="text-white font-semibold">claudish</span>
|
||
<span className="text-gray-600 ml-2">\</span>
|
||
</div>
|
||
|
||
{/* Flags */}
|
||
<div className="flex flex-col gap-1.5 ml-1">
|
||
<CommandRow
|
||
visible={stage >= 2}
|
||
flag="--model-opus"
|
||
flagColor="text-purple-400"
|
||
value="google/gemini-3-pro-preview"
|
||
comment="Complex planning & vision"
|
||
/>
|
||
<CommandRow
|
||
visible={stage >= 3}
|
||
flag="--model-sonnet"
|
||
flagColor="text-blue-400"
|
||
value="openai/gpt-5.1-codex"
|
||
comment="Main coding logic"
|
||
/>
|
||
<CommandRow
|
||
visible={stage >= 4}
|
||
flag="--model-haiku"
|
||
flagColor="text-green-400"
|
||
value="x-ai/grok-code-fast-1"
|
||
comment="Fast context processing"
|
||
/>
|
||
<CommandRow
|
||
visible={stage >= 5}
|
||
flag="--model-subagent"
|
||
flagColor="text-orange-400"
|
||
value="minimax/minimax-m2"
|
||
comment="Background worker agents"
|
||
/>
|
||
</div>
|
||
|
||
{/* Success State */}
|
||
<div className={`pt-6 space-y-1 transition-opacity duration-500 ${stage >= 6 ? 'opacity-100' : 'opacity-0'}`}>
|
||
<div className="flex items-center gap-2 text-[#3fb950]">
|
||
<span>✓</span> Connection established to 4 distinct providers
|
||
</div>
|
||
<div className="flex items-center gap-2 text-[#3fb950]">
|
||
<span>✓</span> Semantic complexity router: <b>Active</b>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Ready State */}
|
||
<div className={`pt-4 transition-all duration-500 flex items-center ${stage >= 6 ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-2'}`}>
|
||
<span className="text-claude-ish font-bold mr-2 text-base">»</span>
|
||
<span className="text-white font-bold">Ready. Orchestrating multi-model mesh.</span>
|
||
<span className={`inline-block w-2.5 h-4 bg-claude-ish/50 ml-2 ${stage >= 13 ? 'hidden' : 'animate-cursor-blink'}`}></span>
|
||
</div>
|
||
</div>
|
||
</TerminalWindow>
|
||
</div>
|
||
|
||
{/* Visual Badges & Lines */}
|
||
<div className="relative pb-12 px-2 md:px-8">
|
||
{/* Connection Lines (SVG) */}
|
||
<svg className="absolute inset-0 w-full h-full pointer-events-none z-0" overflow="visible">
|
||
<defs>
|
||
<filter id="neon-glow" x="-20%" y="-20%" width="140%" height="140%">
|
||
<feGaussianBlur stdDeviation="3" result="coloredBlur" />
|
||
<feMerge>
|
||
<feMergeNode in="coloredBlur"/>
|
||
<feMergeNode in="SourceGraphic"/>
|
||
</feMerge>
|
||
</filter>
|
||
<linearGradient id="line-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||
<stop offset="0%" stopColor="rgba(168, 85, 247, 0.4)" />
|
||
<stop offset="33%" stopColor="rgba(59, 130, 246, 0.4)" />
|
||
<stop offset="66%" stopColor="rgba(34, 197, 94, 0.4)" />
|
||
<stop offset="100%" stopColor="rgba(249, 115, 22, 0.4)" />
|
||
</linearGradient>
|
||
</defs>
|
||
{stage >= 7 && (
|
||
<g className="stroke-[url(#line-gradient)] stroke-[2] fill-none opacity-60" style={{ filter: 'drop-shadow(0 0 4px rgba(0, 212, 170, 0.3))' }}>
|
||
{/* Desktop: Connecting Top to Bottom */}
|
||
<path d="M15% 90 Q 15% 120, 38% 120" className="hidden md:block animate-draw [stroke-dasharray:1000] [stroke-dashoffset:1000]" />
|
||
<path d="M38% 120 L 62% 120" className="hidden md:block animate-draw [stroke-dasharray:1000] [stroke-dashoffset:1000]" />
|
||
<path d="M85% 90 Q 85% 120, 62% 120" className="hidden md:block animate-draw [stroke-dasharray:1000] [stroke-dashoffset:1000]" />
|
||
|
||
{/* Mobile: Simple connections */}
|
||
<path d="M25% 120 L 75% 120" className="md:hidden animate-draw" />
|
||
</g>
|
||
)}
|
||
</svg>
|
||
|
||
{/* Badges Grid */}
|
||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 md:gap-6 relative z-10 px-2 md:px-4">
|
||
<Badge
|
||
active={stage >= 2}
|
||
color="purple"
|
||
role="PLANNING NODE"
|
||
modelName="GEMINI 3"
|
||
icon="◈"
|
||
mapping="maps to --model-opus"
|
||
/>
|
||
<Badge
|
||
active={stage >= 3}
|
||
color="blue"
|
||
role="CODING NODE"
|
||
modelName="GPT 5.1"
|
||
icon="❖"
|
||
mapping="maps to --model-sonnet"
|
||
/>
|
||
<Badge
|
||
active={stage >= 4}
|
||
color="green"
|
||
role="FAST NODE"
|
||
modelName="GROK FAST"
|
||
icon="⚡"
|
||
mapping="maps to --model-haiku"
|
||
/>
|
||
<Badge
|
||
active={stage >= 5}
|
||
color="orange"
|
||
role="BACKGROUND"
|
||
modelName="MINIMAX M2"
|
||
icon="⟁"
|
||
mapping="maps to --model-subagent"
|
||
/>
|
||
</div>
|
||
|
||
{/* Info Pills */}
|
||
<div className="flex flex-wrap justify-center gap-4 mt-8 md:mt-12 relative z-10">
|
||
<InfoPill visible={stage >= 8} text="Unified Context Window" delay={0} />
|
||
<InfoPill visible={stage >= 9} text="Standardized Tool Use" delay={100} />
|
||
<InfoPill visible={stage >= 10} text="Complexity Routing" delay={200} />
|
||
</div>
|
||
|
||
{/* Tagline Reveal */}
|
||
<div className="mt-12 text-center min-h-[4rem] flex flex-col md:flex-row items-center justify-center gap-2 md:gap-8">
|
||
<div className={`text-xl md:text-2xl font-bold transition-opacity duration-300 ${stage >= 11 ? 'opacity-100' : 'opacity-0'} ${stage >= 13 ? 'text-gray-600 line-through decoration-gray-700' : 'text-gray-400'}`}>
|
||
Not switching.
|
||
</div>
|
||
<div className={`text-xl md:text-2xl font-bold transition-opacity duration-300 ${stage >= 12 ? 'opacity-100' : 'opacity-0'} ${stage >= 13 ? 'text-gray-600 line-through decoration-gray-700' : 'text-gray-400'}`}>
|
||
Not merging.
|
||
</div>
|
||
<div className={`text-2xl md:text-4xl font-black text-transparent bg-clip-text bg-gradient-to-r from-claude-ish to-white transition-all duration-500 ${stage >= 13 ? 'opacity-100 scale-110 blur-0' : 'opacity-0 scale-90 blur-md'}`}>
|
||
Collaborating.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
// Helper: Command Row in Terminal
|
||
const CommandRow: React.FC<{ visible: boolean; flag: string; flagColor: string; value: string; comment: string }> = ({
|
||
visible, flag, flagColor, value, comment
|
||
}) => (
|
||
<div className={`pl-6 md:pl-8 flex flex-wrap items-baseline gap-x-3 gap-y-1 transition-all duration-300 ${visible ? 'opacity-100 translate-x-0' : 'opacity-0 -translate-x-4'}`}>
|
||
<span className={`${flagColor} font-bold tracking-tight min-w-[140px]`}>{flag}</span>
|
||
<span className="text-gray-200">{value}</span>
|
||
<span className="text-gray-600 italic text-[11px] md:text-xs"># {comment}</span>
|
||
</div>
|
||
);
|
||
|
||
// Helper: Badge Component
|
||
const Badge: React.FC<{
|
||
active: boolean;
|
||
color: 'purple' | 'blue' | 'green' | 'orange';
|
||
role: string;
|
||
modelName: string;
|
||
icon: string;
|
||
mapping: string;
|
||
}> = ({ active, color, role, modelName, icon, mapping }) => {
|
||
|
||
const colors = {
|
||
purple: { border: 'border-purple-500', text: 'text-purple-400', bg: 'bg-purple-500/10', glow: 'shadow-purple-500/20' },
|
||
blue: { border: 'border-blue-500', text: 'text-blue-400', bg: 'bg-blue-500/10', glow: 'shadow-blue-500/20' },
|
||
green: { border: 'border-green-500', text: 'text-green-400', bg: 'bg-green-500/10', glow: 'shadow-green-500/20' },
|
||
orange: { border: 'border-orange-500', text: 'text-orange-400', bg: 'bg-orange-500/10', glow: 'shadow-orange-500/20' },
|
||
};
|
||
|
||
const c = colors[color];
|
||
|
||
return (
|
||
<div className={`
|
||
relative overflow-hidden rounded-xl border transition-all duration-700 ease-out group
|
||
${active
|
||
? `${c.border} ${c.bg} shadow-[0_0_40px_-10px_rgba(0,0,0,0)] ${c.glow} translate-y-0 opacity-100`
|
||
: 'border-white/5 bg-[#0f0f0f] shadow-none translate-y-4 opacity-40'
|
||
}
|
||
`}>
|
||
<div className="p-5 flex flex-col h-full min-h-[140px]">
|
||
<div className="flex justify-between items-start mb-4">
|
||
<span className={`text-[10px] font-bold tracking-[0.2em] uppercase ${active ? c.text : 'text-gray-600'}`}>
|
||
{role}
|
||
</span>
|
||
<span className={`text-lg transition-all duration-500 ${active ? 'text-white scale-110 drop-shadow-[0_0_8px_rgba(255,255,255,0.5)]' : 'text-gray-700'}`}>
|
||
{icon}
|
||
</span>
|
||
</div>
|
||
|
||
<div className={`text-2xl md:text-3xl font-bold tracking-tight mb-auto transition-colors duration-500 ${active ? 'text-white' : 'text-gray-600'}`}>
|
||
{modelName}
|
||
</div>
|
||
|
||
<div className="mt-4 pt-3 border-t border-white/5">
|
||
<div className="text-[10px] font-mono text-gray-500 flex items-center gap-1.5">
|
||
<span className={`w-1 h-1 rounded-full ${active ? `bg-${color}-500` : 'bg-gray-700'}`}></span>
|
||
{mapping}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Active Glow Line */}
|
||
<div className={`absolute bottom-0 left-0 h-[2px] w-full transition-all duration-1000 ${active ? `bg-${color}-500 opacity-100` : 'opacity-0'}`} />
|
||
</div>
|
||
);
|
||
};
|
||
|
||
// Helper: Info Pill
|
||
const InfoPill: React.FC<{ visible: boolean; text: string; delay: number }> = ({ visible, text, delay }) => (
|
||
<div
|
||
className={`
|
||
border border-white/10 bg-[#111] rounded-full py-2 px-6 text-xs md:text-sm font-mono text-gray-400
|
||
transition-all duration-700 backdrop-blur-md shadow-lg
|
||
${visible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-4'}
|
||
`}
|
||
style={{ transitionDelay: `${delay}ms` }}
|
||
>
|
||
{text}
|
||
</div>
|
||
); |