import { IconCloud as Cloud, IconContainer as Container, IconDatabase as Database, IconWorld as Globe, IconDeviceSdCard as HardDrive, IconKey as Key, IconStack2 as Layers, IconRadio as Radio, IconServer as Server, IconBolt as Zap, } from '@tabler/icons-react'; import { motion } from 'framer-motion'; import { FresnelEdge } from '../effects/FresnelEdge'; import { GlowTracker } from '../effects/GlowTracker'; /* ------------------------------------------------------------------ */ /* Layer data */ /* ------------------------------------------------------------------ */ interface TechItem { icon: React.ComponentType<{ className?: string; stroke?: number }>; label: string; accent: 'teal' | 'magenta'; } interface LayerConfig { id: string; title: string; /** Tailwind max-width class — drives the pyramid shape */ maxWidth: string; borderColor: string; glowShadow: string; /** Framer Motion entrance delay (bottom-up stagger) */ delay: number; /** Framer Motion entrance y offset */ yOffset: number; accent: 'teal' | 'magenta'; items: TechItem[]; /** Optional second row of items (Supabase uses a 2×2 + banner layout) */ itemsRow2?: TechItem[]; /** Full-width banner item (PostgreSQL) */ banner?: TechItem; /** Show divider between items (Backend layer) */ divider?: boolean; /** Show dot-grid background (Infrastructure layer) */ dotGrid?: boolean; } const LAYERS: LayerConfig[] = [ { id: 'frontend', title: 'FRONTEND', maxWidth: 'max-w-md', borderColor: 'border-primary/20', glowShadow: '0 8px 32px oklch(0.82 0.14 192 / 0.15)', delay: 0.55, yOffset: 10, accent: 'teal', items: [ { icon: Zap, label: 'React 19', accent: 'teal' }, { icon: Globe, label: 'Vite', accent: 'teal' }, { icon: Layers, label: 'Shadcn UI', accent: 'teal' }, ], }, { id: 'backend', title: 'BACKEND', maxWidth: 'max-w-lg', borderColor: 'border-primary/20', glowShadow: '0 8px 24px oklch(0.82 0.14 192 / 0.10)', delay: 0.4, yOffset: 20, accent: 'teal', divider: true, items: [ { icon: Globe, label: 'Traefik', accent: 'teal' }, { icon: Server, label: 'Hono API', accent: 'teal' }, ], }, { id: 'supabase', title: 'SUPABASE', maxWidth: 'max-w-xl', borderColor: 'border-primary/20', glowShadow: '0 8px 28px oklch(0.75 0.20 270 / 0.12)', delay: 0.25, yOffset: 30, accent: 'teal', items: [ { icon: Key, label: 'Auth', accent: 'teal' }, { icon: Globe, label: 'REST API', accent: 'teal' }, ], itemsRow2: [ { icon: Radio, label: 'Realtime', accent: 'teal' }, { icon: HardDrive, label: 'Storage', accent: 'teal' }, ], banner: { icon: Database, label: 'PostgreSQL + RLS', accent: 'teal' }, }, { id: 'infrastructure', title: 'INFRASTRUCTURE', maxWidth: 'max-w-2xl', borderColor: 'border-secondary-accent/20', glowShadow: '0 8px 32px oklch(0.65 0.26 350 / 0.12)', delay: 0.1, yOffset: 40, accent: 'magenta', dotGrid: true, items: [ { icon: Container, label: 'Docker', accent: 'teal' }, { icon: Layers, label: 'Kubernetes', accent: 'teal' }, { icon: HardDrive, label: 'S3 Storage', accent: 'magenta' }, { icon: Cloud, label: 'Hetzner', accent: 'magenta' }, ], }, ]; /* ------------------------------------------------------------------ */ /* TechChip */ /* ------------------------------------------------------------------ */ function TechChip({ icon: Icon, label, accent }: TechItem) { const styles = { teal: { bg: 'bg-primary/10', icon: 'text-primary', border: 'border-primary/20', hoverGlow: '0 0 12px oklch(0.82 0.14 192 / 0.35)', }, magenta: { bg: 'bg-secondary-accent/10', icon: 'text-secondary-accent', border: 'border-secondary-accent/20', hoverGlow: '0 0 12px oklch(0.65 0.26 350 / 0.35)', }, }[accent]; return ( { (e.currentTarget as HTMLElement).style.boxShadow = styles.hoverGlow; }} onMouseLeave={(e) => { (e.currentTarget as HTMLElement).style.boxShadow = 'none'; }} >
{label}
); } /* ------------------------------------------------------------------ */ /* TechBanner — full-width item (PostgreSQL) */ /* ------------------------------------------------------------------ */ function TechBanner({ icon: Icon, label, accent }: TechItem) { const isTeal = accent === 'teal'; return (
{label}
); } /* ------------------------------------------------------------------ */ /* FlowConnector — animated pulse between layers */ /* ------------------------------------------------------------------ */ function FlowConnector({ delay }: { delay: number }) { return ( {/* Animated gradient line */}
{/* Static fallback for reduced motion */}
{/* Glowing midpoint dot */}
); } /* ------------------------------------------------------------------ */ /* MobileFlowBar — simplified connector for mobile */ /* ------------------------------------------------------------------ */ function MobileFlowBar() { return (
); } /* ------------------------------------------------------------------ */ /* LayerPlatform */ /* ------------------------------------------------------------------ */ function LayerPlatform({ layer }: { layer: LayerConfig }) { const labelBg = layer.accent === 'teal' ? 'bg-primary/10 text-primary' : 'bg-secondary-accent/10 text-secondary-accent'; const mobileBorderColor = layer.accent === 'teal' ? 'oklch(0.82 0.14 192 / 0.6)' : 'oklch(0.65 0.26 350 / 0.6)'; return ( {/* Thick left accent border on mobile only */}