full-screen-loader.tsx
1 export function FullScreenLoader(props: { 2 stage?: string | null 3 stalled?: boolean 4 onReload?: () => void 5 onReset?: () => void 6 }) { 7 return ( 8 <div className="h-full flex flex-col items-center justify-center bg-bg overflow-hidden select-none"> 9 {/* Animated orbital ring */} 10 <div className="relative w-[120px] h-[120px] mb-8"> 11 {/* Outer glow pulse */} 12 <div 13 className="absolute inset-[-20px] rounded-full" 14 style={{ 15 background: 'radial-gradient(circle, rgba(99,102,241,0.08) 0%, transparent 70%)', 16 animation: 'sc-glow 2.5s ease-in-out infinite', 17 }} 18 /> 19 20 {/* Orbital ring */} 21 <div 22 className="absolute inset-0 rounded-full border border-white/[0.06]" 23 style={{ animation: 'sc-ring 3s linear infinite' }} 24 /> 25 26 {/* Orbiting dots */} 27 {[0, 1, 2, 3, 4, 5].map((i) => ( 28 <div 29 key={i} 30 className="absolute inset-0" 31 style={{ 32 animation: `sc-orbit 2.4s cubic-bezier(0.4, 0, 0.2, 1) infinite`, 33 animationDelay: `${i * -0.4}s`, 34 }} 35 > 36 <div 37 className="absolute top-0 left-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full" 38 style={{ 39 width: i === 0 ? 8 : 6, 40 height: i === 0 ? 8 : 6, 41 background: i === 0 ? '#818CF8' : `rgba(129, 140, 248, ${0.7 - i * 0.1})`, 42 boxShadow: i === 0 ? '0 0 12px rgba(99,102,241,0.5)' : 'none', 43 }} 44 /> 45 </div> 46 ))} 47 48 {/* Center logo mark */} 49 <div className="absolute inset-0 flex items-center justify-center"> 50 <div 51 className="relative" 52 style={{ animation: 'sc-breathe 2.5s ease-in-out infinite' }} 53 > 54 <svg width="36" height="36" viewBox="0 0 36 36" fill="none"> 55 {/* Hexagonal claw mark */} 56 <path 57 d="M18 4L30 11V25L18 32L6 25V11L18 4Z" 58 stroke="rgba(129, 140, 248, 0.3)" 59 strokeWidth="1" 60 fill="none" 61 /> 62 <path 63 d="M18 9L25 13V23L18 27L11 23V13L18 9Z" 64 stroke="rgba(129, 140, 248, 0.5)" 65 strokeWidth="1.5" 66 fill="rgba(99, 102, 241, 0.06)" 67 /> 68 {/* Claw lines */} 69 <path d="M14 15L18 20L22 15" stroke="#818CF8" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> 70 <path d="M12 13L18 20L24 13" stroke="rgba(129, 140, 248, 0.3)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" /> 71 </svg> 72 </div> 73 </div> 74 </div> 75 76 {/* Brand text */} 77 <div 78 className="text-[15px] font-display font-700 tracking-[0.15em] uppercase" 79 style={{ 80 background: 'linear-gradient(135deg, rgba(255,255,255,0.6), rgba(129, 140, 248, 0.8))', 81 WebkitBackgroundClip: 'text', 82 WebkitTextFillColor: 'transparent', 83 animation: 'sc-text-fade 2s ease-in-out infinite alternate, fade-up 0.6s var(--ease-spring) 0.2s both', 84 }} 85 > 86 SwarmClaw 87 </div> 88 89 {/* Loading bar */} 90 <div className="mt-4 w-[100px] h-[2px] rounded-full bg-white/[0.06] overflow-hidden" style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.3s both' }}> 91 <div 92 className="h-full rounded-full bg-accent-bright/60" 93 style={{ animation: 'sc-progress 1.5s ease-in-out infinite' }} 94 /> 95 </div> 96 97 {props.stage ? ( 98 <p 99 className="mt-4 text-[12px] text-text-3" 100 style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.4s both' }} 101 > 102 {props.stage} 103 </p> 104 ) : null} 105 106 {props.stalled ? ( 107 <div 108 className="mt-6 max-w-[360px] px-4 text-center" 109 style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.5s both' }} 110 > 111 <p className="text-[12px] text-text-2"> 112 Startup is taking longer than expected. This usually means the browser kept stale local state while the dev server restarted. 113 </p> 114 <div className="mt-4 flex items-center justify-center gap-3"> 115 <button 116 type="button" 117 onClick={props.onReload} 118 className="px-4 py-2 rounded-[12px] border border-white/[0.08] bg-surface text-[12px] text-text-2 transition-colors hover:bg-surface-2" 119 > 120 Reload 121 </button> 122 <button 123 type="button" 124 onClick={props.onReset} 125 className="px-4 py-2 rounded-[12px] border border-white/[0.08] bg-transparent text-[12px] text-text-3 transition-colors hover:bg-white/[0.04]" 126 > 127 Reset Local Session 128 </button> 129 </div> 130 </div> 131 ) : null} 132 133 </div> 134 ) 135 }