/ src / components / ui / full-screen-loader.tsx
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  }