File size: 4,657 Bytes
5008b66
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import { Suspense, useEffect, useRef, useState } from 'react';
import Spline from '@splinetool/react-spline';

interface SplineBackgroundProps {
  scene?: string;
  className?: string;
  onLoad?: () => void;
}

// Fallback component for loading state
function SplineFallback({ message = 'Loading 3D Scene...' }: { message?: string }) {
  return (
    <div className="w-full h-full bg-gradient-surface flex items-center justify-center">
      <div className="animate-pulse space-y-4">
        <div className="w-32 h-32 bg-primary/20 rounded-full mx-auto animate-float"></div>
        <div className="text-center text-muted-foreground">{message}</div>
      </div>
    </div>
  );
}

export default function SplineBackground({ 
  scene = "https://prod.spline.design/6Wq8T6XmHxrGMwni/scene.splinecode", 
  className = "",
  onLoad 
}: SplineBackgroundProps) {
  const [error, setError] = useState<string | null>(null);
  const [ready, setReady] = useState(false);
  const containerRef = useRef<HTMLDivElement | null>(null);
  const isSplineCode = scene.trim().toLowerCase().endsWith('.splinecode');

  // Preflight check: ensure the scene URL is reachable before trying to load Spline
  useEffect(() => {
    let cancelled = false;
    setError(null);
    setReady(false);
    (async () => {
      try {
        // Use no-cors GET: many CDNs block HEAD and CORS; opaque means we cannot inspect but still OK to try
        const res = await fetch(scene, { method: 'GET', mode: 'no-cors' });
        if (!cancelled) setReady(true);
      } catch (e) {
        console.error('Spline scene pre-check failed', e);
        if (!cancelled) setError('Failed to reach 3D scene');
      }
    })();
    return () => { cancelled = true; };
  }, [scene]);

  // Handle WebGL context loss gracefully
  useEffect(() => {
    const el = containerRef.current;
    if (!el) return;

    const onContextLost = (ev: Event) => {
      console.warn('WebGL context lost');
      setError('Graphics context lost');
      ev.preventDefault();
    };
    const onContextRestored = () => {
      console.info('WebGL context restored');
      setError(null);
    };

    el.addEventListener('webglcontextlost', onContextLost as EventListener, false);
    el.addEventListener('webglcontextrestored', onContextRestored as EventListener, false);

    return () => {
      el.removeEventListener('webglcontextlost', onContextLost as EventListener, false);
      el.removeEventListener('webglcontextrestored', onContextRestored as EventListener, false);
    };
  }, []);

  if (error) {
    return (
      <div ref={containerRef} className={`w-full h-full ${className}`}>
        <SplineFallback message={error} />
      </div>
    );
  }

  // If the provided URL is not a .splinecode, use an iframe fallback. This supports community/share links.
  if (!isSplineCode) {
    return (
      <div ref={containerRef} className={`w-full h-full ${className}`}>
        {ready ? (
          <iframe
            src={scene}
            title="Spline Scene"
            loading="lazy"
            referrerPolicy="no-referrer"
            style={{ width: '100%', height: '100%', border: 'none', background: 'transparent' }}
            allow="xr-spatial-tracking; clipboard-read; clipboard-write; accelerometer; magnetometer; gyroscope; autoplay"
          />
        ) : (
          <SplineFallback />
        )}
      </div>
    );
  }

  return (
    <div ref={containerRef} className={`w-full h-full ${className}`}>
      <Suspense fallback={<SplineFallback />}>
        {ready ? (
          <Spline
            scene={scene}
            onLoad={onLoad}
            // If the version supports it, onError will be called; otherwise pre-check and boundary handle it.
            // @ts-expect-error - onError may not exist on older versions
            onError={(e: unknown) => {
              console.error('Spline load error', e);
              setError('Failed to load 3D scene');
            }}
            style={{ width: '100%', height: '100%', background: 'transparent' }}
          />
        ) : (
          <SplineFallback />
        )}
      </Suspense>
    </div>
  );
}

// Alternative Spline scenes for different purposes
export const SplineScenes = {
  // Abstract floating elements
  abstract: "https://prod.spline.design/6Wq8T6XmHxrGMwni/scene.splinecode",
  
  // Microphone visualization 
  microphone: "https://prod.spline.design/kR3K8EqaAlKZXBPv/scene.splinecode",
  
  // Speaker/audio visualization
  speaker: "https://prod.spline.design/m8rFoLJGtKVQZuE5/scene.splinecode",
  
  // Particle system
  particles: "https://prod.spline.design/K7n4XGsOzMqBgEF1/scene.splinecode"
};