@livepeer-frameworks/player-react 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +2 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/types/components/DevModePanel.d.ts +47 -0
- package/dist/types/components/DvdLogo.d.ts +4 -0
- package/dist/types/components/Icons.d.ts +33 -0
- package/dist/types/components/IdleScreen.d.ts +16 -0
- package/dist/types/components/LoadingScreen.d.ts +6 -0
- package/dist/types/components/LogoOverlay.d.ts +11 -0
- package/dist/types/components/Player.d.ts +11 -0
- package/dist/types/components/PlayerControls.d.ts +60 -0
- package/dist/types/components/PlayerErrorBoundary.d.ts +23 -0
- package/dist/types/components/SeekBar.d.ts +33 -0
- package/dist/types/components/SkipIndicator.d.ts +14 -0
- package/dist/types/components/SpeedIndicator.d.ts +12 -0
- package/dist/types/components/StatsPanel.d.ts +31 -0
- package/dist/types/components/StreamStateOverlay.d.ts +24 -0
- package/dist/types/components/SubtitleRenderer.d.ts +69 -0
- package/dist/types/components/ThumbnailOverlay.d.ts +4 -0
- package/dist/types/components/TitleOverlay.d.ts +13 -0
- package/dist/types/components/players/DashJsPlayer.d.ts +18 -0
- package/dist/types/components/players/HlsJsPlayer.d.ts +18 -0
- package/dist/types/components/players/MewsWsPlayer/index.d.ts +18 -0
- package/dist/types/components/players/MistPlayer.d.ts +20 -0
- package/dist/types/components/players/MistWebRTCPlayer/index.d.ts +20 -0
- package/dist/types/components/players/NativePlayer.d.ts +19 -0
- package/dist/types/components/players/VideoJsPlayer.d.ts +18 -0
- package/dist/types/context/PlayerContext.d.ts +40 -0
- package/dist/types/context/index.d.ts +5 -0
- package/dist/types/hooks/useMetaTrack.d.ts +54 -0
- package/dist/types/hooks/usePlaybackQuality.d.ts +42 -0
- package/dist/types/hooks/usePlayerController.d.ts +163 -0
- package/dist/types/hooks/usePlayerSelection.d.ts +47 -0
- package/dist/types/hooks/useStreamState.d.ts +27 -0
- package/dist/types/hooks/useTelemetry.d.ts +57 -0
- package/dist/types/hooks/useViewerEndpoints.d.ts +14 -0
- package/dist/types/index.d.ts +33 -0
- package/dist/types/types.d.ts +94 -0
- package/dist/types/ui/badge.d.ts +9 -0
- package/dist/types/ui/button.d.ts +11 -0
- package/dist/types/ui/context-menu.d.ts +27 -0
- package/dist/types/ui/select.d.ts +10 -0
- package/dist/types/ui/slider.d.ts +13 -0
- package/package.json +71 -0
- package/src/assets/logomark.svg +56 -0
- package/src/components/DevModePanel.tsx +822 -0
- package/src/components/DvdLogo.tsx +201 -0
- package/src/components/Icons.tsx +282 -0
- package/src/components/IdleScreen.tsx +664 -0
- package/src/components/LoadingScreen.tsx +710 -0
- package/src/components/LogoOverlay.tsx +75 -0
- package/src/components/Player.tsx +419 -0
- package/src/components/PlayerControls.tsx +820 -0
- package/src/components/PlayerErrorBoundary.tsx +70 -0
- package/src/components/SeekBar.tsx +291 -0
- package/src/components/SkipIndicator.tsx +113 -0
- package/src/components/SpeedIndicator.tsx +57 -0
- package/src/components/StatsPanel.tsx +150 -0
- package/src/components/StreamStateOverlay.tsx +200 -0
- package/src/components/SubtitleRenderer.tsx +235 -0
- package/src/components/ThumbnailOverlay.tsx +90 -0
- package/src/components/TitleOverlay.tsx +48 -0
- package/src/components/players/DashJsPlayer.tsx +56 -0
- package/src/components/players/HlsJsPlayer.tsx +56 -0
- package/src/components/players/MewsWsPlayer/index.tsx +56 -0
- package/src/components/players/MistPlayer.tsx +60 -0
- package/src/components/players/MistWebRTCPlayer/index.tsx +59 -0
- package/src/components/players/NativePlayer.tsx +58 -0
- package/src/components/players/VideoJsPlayer.tsx +56 -0
- package/src/context/PlayerContext.tsx +71 -0
- package/src/context/index.ts +11 -0
- package/src/global.d.ts +4 -0
- package/src/hooks/useMetaTrack.ts +187 -0
- package/src/hooks/usePlaybackQuality.ts +126 -0
- package/src/hooks/usePlayerController.ts +525 -0
- package/src/hooks/usePlayerSelection.ts +117 -0
- package/src/hooks/useStreamState.ts +381 -0
- package/src/hooks/useTelemetry.ts +138 -0
- package/src/hooks/useViewerEndpoints.ts +120 -0
- package/src/index.tsx +75 -0
- package/src/player.css +2 -0
- package/src/types.ts +135 -0
- package/src/ui/badge.tsx +27 -0
- package/src/ui/button.tsx +47 -0
- package/src/ui/context-menu.tsx +193 -0
- package/src/ui/select.tsx +105 -0
- package/src/ui/slider.tsx +67 -0
|
@@ -0,0 +1,710 @@
|
|
|
1
|
+
import React, { useRef, useEffect, useState } from "react";
|
|
2
|
+
import DvdLogo from "./DvdLogo";
|
|
3
|
+
import logomarkAsset from "../assets/logomark.svg";
|
|
4
|
+
|
|
5
|
+
interface AnimatedBubbleProps {
|
|
6
|
+
index: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const AnimatedBubble: React.FC<AnimatedBubbleProps> = ({ index }) => {
|
|
10
|
+
const [position, setPosition] = useState({ top: 0, left: 0 });
|
|
11
|
+
const [size, setSize] = useState(40);
|
|
12
|
+
const [opacity, setOpacity] = useState(0);
|
|
13
|
+
|
|
14
|
+
const getRandomPosition = () => ({
|
|
15
|
+
top: Math.random() * 80 + 10, // 10% to 90%
|
|
16
|
+
left: Math.random() * 80 + 10, // 10% to 90%
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const getRandomSize = () => Math.random() * 60 + 30; // 30px to 90px
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
// Initial random position and size
|
|
23
|
+
setPosition(getRandomPosition());
|
|
24
|
+
setSize(getRandomSize());
|
|
25
|
+
|
|
26
|
+
const animationCycle = () => {
|
|
27
|
+
// Fade in
|
|
28
|
+
setOpacity(0.15);
|
|
29
|
+
|
|
30
|
+
setTimeout(() => {
|
|
31
|
+
// Fade out
|
|
32
|
+
setOpacity(0);
|
|
33
|
+
|
|
34
|
+
setTimeout(() => {
|
|
35
|
+
// Move to new random position with new size while invisible
|
|
36
|
+
setPosition(getRandomPosition());
|
|
37
|
+
setSize(getRandomSize());
|
|
38
|
+
// Start next cycle after a brief delay to ensure position change is complete
|
|
39
|
+
setTimeout(() => {
|
|
40
|
+
animationCycle();
|
|
41
|
+
}, 200);
|
|
42
|
+
}, 1500); // Wait for fade out to complete
|
|
43
|
+
}, 4000 + Math.random() * 3000); // Stay visible for 4-7 seconds (was 2-4)
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Start the animation cycle with staggered timing
|
|
47
|
+
const timeout = setTimeout(animationCycle, index * 500);
|
|
48
|
+
|
|
49
|
+
return () => {
|
|
50
|
+
clearTimeout(timeout);
|
|
51
|
+
};
|
|
52
|
+
}, [index]);
|
|
53
|
+
|
|
54
|
+
// Tokyo Night inspired pastel colors
|
|
55
|
+
const bubbleColors = [
|
|
56
|
+
"rgba(122, 162, 247, 0.2)", // Terminal Blue
|
|
57
|
+
"rgba(187, 154, 247, 0.2)", // Terminal Magenta
|
|
58
|
+
"rgba(158, 206, 106, 0.2)", // Strings/CSS classes
|
|
59
|
+
"rgba(115, 218, 202, 0.2)", // Terminal Green
|
|
60
|
+
"rgba(125, 207, 255, 0.2)", // Terminal Cyan
|
|
61
|
+
"rgba(247, 118, 142, 0.2)", // Keywords/Terminal Red
|
|
62
|
+
"rgba(224, 175, 104, 0.2)", // Terminal Yellow
|
|
63
|
+
"rgba(42, 195, 222, 0.2)", // Language functions
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<div
|
|
68
|
+
style={{
|
|
69
|
+
position: "absolute",
|
|
70
|
+
top: `${position.top}%`,
|
|
71
|
+
left: `${position.left}%`,
|
|
72
|
+
width: `${size}px`,
|
|
73
|
+
height: `${size}px`,
|
|
74
|
+
borderRadius: "50%",
|
|
75
|
+
background: bubbleColors[index % bubbleColors.length],
|
|
76
|
+
opacity: opacity,
|
|
77
|
+
transition: "opacity 1s ease-in-out",
|
|
78
|
+
pointerEvents: "none",
|
|
79
|
+
userSelect: "none",
|
|
80
|
+
WebkitUserSelect: "none",
|
|
81
|
+
MozUserSelect: "none",
|
|
82
|
+
msUserSelect: "none",
|
|
83
|
+
} as React.CSSProperties}
|
|
84
|
+
/>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
interface CenterLogoProps {
|
|
89
|
+
containerRef: React.RefObject<HTMLDivElement>;
|
|
90
|
+
scale?: number;
|
|
91
|
+
onHitmarker?: (e: { clientX: number; clientY: number }) => void;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const CenterLogo: React.FC<CenterLogoProps> = ({ containerRef, scale = 0.2, onHitmarker }) => {
|
|
95
|
+
const [logoSize, setLogoSize] = useState(100);
|
|
96
|
+
const [offset, setOffset] = useState({ x: 0, y: 0 });
|
|
97
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
98
|
+
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
if (containerRef.current) {
|
|
101
|
+
const containerWidth = containerRef.current.clientWidth;
|
|
102
|
+
const containerHeight = containerRef.current.clientHeight;
|
|
103
|
+
const minDimension = Math.min(containerWidth, containerHeight);
|
|
104
|
+
setLogoSize(minDimension * scale);
|
|
105
|
+
}
|
|
106
|
+
}, [containerRef, scale]);
|
|
107
|
+
|
|
108
|
+
const handleLogoClick = (e: React.MouseEvent) => {
|
|
109
|
+
e.stopPropagation(); // Prevent event bubbling to container
|
|
110
|
+
if (onHitmarker) {
|
|
111
|
+
// Get the exact click position on the logo
|
|
112
|
+
onHitmarker({ clientX: e.clientX, clientY: e.clientY });
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const handleMouseMove = (e: MouseEvent) => {
|
|
117
|
+
if (!containerRef.current) return;
|
|
118
|
+
|
|
119
|
+
const rect = containerRef.current.getBoundingClientRect();
|
|
120
|
+
const centerX = rect.left + rect.width / 2;
|
|
121
|
+
const centerY = rect.top + rect.height / 2;
|
|
122
|
+
|
|
123
|
+
const mouseX = e.clientX;
|
|
124
|
+
const mouseY = e.clientY;
|
|
125
|
+
|
|
126
|
+
// Calculate distance from center
|
|
127
|
+
const deltaX = mouseX - centerX;
|
|
128
|
+
const deltaY = mouseY - centerY;
|
|
129
|
+
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
130
|
+
|
|
131
|
+
// If mouse is close to logo, move it away
|
|
132
|
+
const maxDistance = logoSize * 1.5; // Detection radius
|
|
133
|
+
if (distance < maxDistance && distance > 0) {
|
|
134
|
+
const pushStrength = (maxDistance - distance) / maxDistance;
|
|
135
|
+
const pushDistance = 50 * pushStrength; // Max push distance
|
|
136
|
+
|
|
137
|
+
// Normalize direction and apply push
|
|
138
|
+
const pushX = -(deltaX / distance) * pushDistance;
|
|
139
|
+
const pushY = -(deltaY / distance) * pushDistance;
|
|
140
|
+
|
|
141
|
+
setOffset({ x: pushX, y: pushY });
|
|
142
|
+
setIsHovered(true);
|
|
143
|
+
} else {
|
|
144
|
+
setOffset({ x: 0, y: 0 });
|
|
145
|
+
setIsHovered(false);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const handleMouseLeave = () => {
|
|
150
|
+
setOffset({ x: 0, y: 0 });
|
|
151
|
+
setIsHovered(false);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
if (containerRef.current) {
|
|
156
|
+
const container = containerRef.current;
|
|
157
|
+
container.addEventListener('mousemove', handleMouseMove);
|
|
158
|
+
container.addEventListener('mouseleave', handleMouseLeave);
|
|
159
|
+
|
|
160
|
+
return () => {
|
|
161
|
+
container.removeEventListener('mousemove', handleMouseMove);
|
|
162
|
+
container.removeEventListener('mouseleave', handleMouseLeave);
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}, [logoSize, containerRef]);
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<div
|
|
169
|
+
style={{
|
|
170
|
+
position: "absolute",
|
|
171
|
+
top: "50%",
|
|
172
|
+
left: "50%",
|
|
173
|
+
transform: `translate(-50%, -50%) translate(${offset.x}px, ${offset.y}px)`,
|
|
174
|
+
display: "flex",
|
|
175
|
+
alignItems: "center",
|
|
176
|
+
justifyContent: "center",
|
|
177
|
+
zIndex: 10,
|
|
178
|
+
transition: "transform 0.3s ease-out",
|
|
179
|
+
userSelect: "none",
|
|
180
|
+
WebkitUserSelect: "none",
|
|
181
|
+
MozUserSelect: "none",
|
|
182
|
+
msUserSelect: "none",
|
|
183
|
+
}}
|
|
184
|
+
>
|
|
185
|
+
{/* Pulsing circle background */}
|
|
186
|
+
<div
|
|
187
|
+
style={{
|
|
188
|
+
position: "absolute",
|
|
189
|
+
width: `${logoSize * 1.4}px`,
|
|
190
|
+
height: `${logoSize * 1.4}px`,
|
|
191
|
+
borderRadius: "50%",
|
|
192
|
+
background: "rgba(122, 162, 247, 0.15)",
|
|
193
|
+
animation: isHovered ? "logoPulse 1s ease-in-out infinite" : "logoPulse 3s ease-in-out infinite",
|
|
194
|
+
transform: isHovered ? "scale(1.2)" : "scale(1)",
|
|
195
|
+
transition: "transform 0.3s ease-out",
|
|
196
|
+
userSelect: "none",
|
|
197
|
+
WebkitUserSelect: "none",
|
|
198
|
+
MozUserSelect: "none",
|
|
199
|
+
msUserSelect: "none",
|
|
200
|
+
pointerEvents: "none",
|
|
201
|
+
}}
|
|
202
|
+
/>
|
|
203
|
+
|
|
204
|
+
{/* Logo - only the SVG is clickable */}
|
|
205
|
+
<img
|
|
206
|
+
src={logomarkAsset}
|
|
207
|
+
alt="FrameWorks Logo"
|
|
208
|
+
onClick={handleLogoClick}
|
|
209
|
+
style={{
|
|
210
|
+
width: `${logoSize}px`,
|
|
211
|
+
height: `${logoSize}px`,
|
|
212
|
+
position: "relative",
|
|
213
|
+
zIndex: 1,
|
|
214
|
+
filter: isHovered
|
|
215
|
+
? "drop-shadow(0 6px 12px rgba(36, 40, 59, 0.4)) brightness(1.1)"
|
|
216
|
+
: "drop-shadow(0 4px 8px rgba(36, 40, 59, 0.3))",
|
|
217
|
+
transform: isHovered ? "scale(1.1)" : "scale(1)",
|
|
218
|
+
transition: "all 0.3s ease-out",
|
|
219
|
+
cursor: isHovered ? "pointer" : "default",
|
|
220
|
+
userSelect: "none",
|
|
221
|
+
WebkitUserSelect: "none",
|
|
222
|
+
MozUserSelect: "none",
|
|
223
|
+
msUserSelect: "none",
|
|
224
|
+
WebkitUserDrag: "none",
|
|
225
|
+
WebkitTouchCallout: "none",
|
|
226
|
+
} as React.CSSProperties}
|
|
227
|
+
/>
|
|
228
|
+
</div>
|
|
229
|
+
);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
interface LoadingScreenProps {
|
|
233
|
+
message?: string;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
interface Hitmarker {
|
|
237
|
+
id: number;
|
|
238
|
+
x: number;
|
|
239
|
+
y: number;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const LoadingScreen: React.FC<LoadingScreenProps> = ({ message = "Waiting for source..." }) => {
|
|
243
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
244
|
+
const [hitmarkers, setHitmarkers] = useState<Hitmarker[]>([]);
|
|
245
|
+
|
|
246
|
+
const playHitmarkerSound = () => {
|
|
247
|
+
try {
|
|
248
|
+
// Embedded hitmarker sound as base64 data URL
|
|
249
|
+
const hitmarkerDataUrl = 'data:audio/mpeg;base64,SUQzBAAAAAAANFRDT04AAAAHAAADT3RoZXIAVFNTRQAAAA8AAANMYXZmNTcuODMuMTAwAAAAAAAAAAAA' +
|
|
250
|
+
'AAD/+1QAAAAAAAAAAAAAAAAAAAAA' +
|
|
251
|
+
'AAAAAAAAAAAAAAAAAAAAAABJbmZvAAAADwAAAAYAAAnAADs7Ozs7Ozs7Ozs7Ozs7OztiYmJiYmJiYmJi' +
|
|
252
|
+
'YmJiYmJiYomJiYmJiYmJiYmJiYmJiYmxsbGxsbGxsbGxsbGxsbGxsdjY2NjY2NjY2NjY2NjY2NjY////' +
|
|
253
|
+
'/////////////////wAAAABMYXZjNTcuMTAAAAAAAAAAAAAAAAAkAkAAAAAAAAAJwOuMZun/+5RkAA8S' +
|
|
254
|
+
'/F23AGAaAi0AF0AAAAAInXsEAIRXyQ8D4OQgjEhE3cO7ujuHF0XCOu4G7xKbi3Funu7u7p9dw7unu7u7' +
|
|
255
|
+
'p7u7u6fXcW7om7u7uiU3dxdT67u7p7uHdxelN3cW6fXcW7oXXd3eJTd3d0+u4t3iXdw4up70W4uiPruL' +
|
|
256
|
+
'DzMw8Pz79Y99JfkyfPv5/h9uTJoy79Y99Y97q3vyZPJk0ZfrL6x73Vn+J35dKKS/STQyQ8CAiCPNuRAO' +
|
|
257
|
+
'OqquAx+fzJeBKDAsgAMBuWcBsHKhjJTcCwIALyAvABbI0ZIcCmP8jHJe8gZAdVRp2TpnU/kUXV4iQuBA' +
|
|
258
|
+
'AkAQgisLPvwQ2Jz7wIkIpQ8QOl/KFy75w+2HpTFnRqXLQo0fzlSYRe5Ce9yZMEzRM4xesu95Mo8QQsoM' +
|
|
259
|
+
'H4gLg+fJqkmY3GZJE2kwGfMECJiAdIttoEa2yotfC7jsS2mjKgbzAfEMeiwZpGSUFCQwPKQiWXh0TnkN' +
|
|
260
|
+
'or5SmrKvwHlX2zFxKxPCzRL/+5RkIwADvUxLawwb0GdF6Y1hJlgNNJk+DSRwyQwI6AD2JCiBmhaff0dz' +
|
|
261
|
+
'CEBjgFABAcDNFc3YAEV4hQn0L/QvQnevom+n13eIjoTvABLrHg/L9RzdWXYonHbbbE2K0pX+gkL2g56R' +
|
|
262
|
+
'iwrbuWwhoABzQoMKOAIGAfE4UKk6BhSIJpECBq0CEYmZKYIiAJt72H24dNou7y/Ee7a/3v+MgySemSTY' +
|
|
263
|
+
'mnBAFwIAAGfCJ8/D9YfkwQEBcP38uA1d/EB1T5dZKEsgnuhwZirY5fIMRMdRn7U4OcN2m5NWeYdcPBwX' +
|
|
264
|
+
'DBOsJF1DBYks62pAURqz1hGoGHH/QIoRC80tYAJ8g4f3MPD51sywAbhAn/X9P/75tvZww3gZ3pYPDx/+' +
|
|
265
|
+
'ACO/7//ffHj/D/AAfATC4DYGFA3MRABo0lqWjBOl2yAda1C1BdhduXgm8FGnAQB/lDiEi6j9qw9EHigI' +
|
|
266
|
+
'IOLB6F1eIPd+T6Agc4//lMo6+k3tdttJY2gArU7cN07m2FLSm4gCjyz/+5RECwACwSRZawkdLFGi2mVh' +
|
|
267
|
+
'5h4LfFdPVPGACViTavaeMAAV0UkkEsDhxxJwqF04on002mZah8w9+5ItfSAoyZa1dchnPpLmAEKrVMRA' +
|
|
268
|
+
'//sD8w0WsB4xiw4JqaZMB45TdpIuXXUPf8Bpa35p/jQIAOAuZkmUeJoM5W6L2gqqO6rTuHjUTDnhy4Qi' +
|
|
269
|
+
'K348vtFysOizShoHbBpsPRYcSINCbiN4XOLPPAgq3dW2Ga7SlyiKXBV7W1RQl5BiiVGkwayJfEnPxgXk' +
|
|
270
|
+
'QeZxxzyhTuLO2XFUDDstoc6CkM1J8QZAjUN3bM8580cRygNfmPAELGjIH0Z/0A+8csyH/4eHvgAf8APg' +
|
|
271
|
+
'ABmZ98AARAADP////Dw8PHEmIpgGttpJQJsmZjq5nPQ8j5VqWW1evqdjP182PA6tHJZgkC5iSbEQkyJS' +
|
|
272
|
+
'z/BvP3eucLKN0+Wiza4feKKFBqiAEBAMXyYni5NZc16CDl/QY9j6BAcWSmQYcIcoMHYoQNBiIBgIBUAz' +
|
|
273
|
+
'QUMSnjj/+5RkCwADsFLffjEAAjrJe63JHACO6WtlnPMACKaCK1uMMADU5dI6JhW2cam98UlRmY4ihyKF' +
|
|
274
|
+
'rNsgpZd5PYgBALnYofKEt82De0GbW1DLibvFDK+bSeOm8qKdqUFZ7uiK8XMPHyqm3pTxUvcunUfxXEo9' +
|
|
275
|
+
'RNe5b/8vfCD3kzDN7vTtHyaIcntVDAYBAUBAAAAQBI2vguYNsHWm5AR3mZtZib8WAHFvz2Kf9//iYvlR' +
|
|
276
|
+
'B/+n///////////+UH7XoIDMoJAEAMtj8JshJPRwklVqNSpYnalfE+VzNCAISCoxVHEpIo/WrTiMvP7V' +
|
|
277
|
+
'TujOPnOglLbMLN/pq/d2Y4lRJIkSnPlUSJEjSKJqM41d88zWtMzP+fCOORmc9NeM+f1nnO//efM52/fG' +
|
|
278
|
+
'/ef385+5u+u1bRJkwU8FAkEItZpkRYeQYcAgZTEYlaZa2yROLeC0qdX73rZJJ/d2f6v6Or0u/+5FBYcn' +
|
|
279
|
+
'g0MlCiQTR9GUU5LScmSuSlH00IWqXA6jlw4BEcD/+5REEAAi3RtU+eYbGF1E+lk9g0YJzLUgh7BlQVGT' +
|
|
280
|
+
'ZJD0jKhhTNVilqrMzFRK+x/szcMKBWKep4NP1A0DR6RESkTp5Z1Q9Y8REgqMg1DpUBPleeqlRQcerBpM' +
|
|
281
|
+
'jiURHVD4XwAALhAgbxxlxYD5OFkG8oQRPB2EpsxSCNVlgcYUqoAyiVJmaARlkwplICfPoUy/zWEzM2pc' +
|
|
282
|
+
'NYzAQNJDSniEYecSEqxFEzQqEvUFGnvzwUfcRlpZ9T2LCR5QdDQDDhKICAjpJCagpRo9UQRPClZZlg6E' +
|
|
283
|
+
'p9DMTkTl+okuhRIVIzAQEf9L+Mx/DUjqmqN6kX7M36lS4zgLyJV3iV6j3xF8kJduJawVw1nndAlBaLLg' +
|
|
284
|
+
'JupwsTcLkxmJgFLgSzoCmHjSNGSqkGPCpnNqTXIwolf6qlVWN+q/su37HzgrES1pWGg3KnWh0FXCVniJ' +
|
|
285
|
+
'9K5b4iCrpLEuIcFTqwkVLFiqgaDqCCSMVWqxBAVCFOLVrVahm2ahUThUKJnmFCw15hD0Qhb/+5REEAhC' +
|
|
286
|
+
'YSRCSQEb4FOGaBUMI6JIRYC0QIB2SQsgGpgwDghgIlS6FU8VBXDoiBp5Y9gtkVnhEhYBdJFQ7kQ3w1yp' +
|
|
287
|
+
'0NB2CoNPEttZ1/aeDUAAA26FEghWgEKNVAVWkFAQEmMK2Uwk/qI0hqUb/4epVIZH1ai6szf6kzH1f2ar' +
|
|
288
|
+
'xYGS9FcOsN5UlJLQt///+oo0FRDTUQ0FBQr9f5LxXP+mEUfk0AIrf/5GRmQ0//mX//ZbLP5b5GrWSz+W' +
|
|
289
|
+
'SkZMrWyyyy2GRqyggVRyMv////////st//sn/yyVDI1l8mVgoYGDCOqiqIQBxmvxWCggTpZZZD//aWfy' +
|
|
290
|
+
'yWf/y/7KGDA0ssBggTof9k/+WS/8slQyMp/5Nfln8WAqGcUbULCrKxT9ISF+kKsxQWpMQU1FMy4xMDCq' +
|
|
291
|
+
'qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq' +
|
|
292
|
+
'qqqqqqqqqqqqqqqqqqqqqqqqqqo=';
|
|
293
|
+
|
|
294
|
+
const audio = new Audio(hitmarkerDataUrl);
|
|
295
|
+
audio.volume = 0.3;
|
|
296
|
+
audio.play().catch(() => {
|
|
297
|
+
// Fallback to synthetic sound if data URL fails
|
|
298
|
+
createSyntheticHitmarkerSound();
|
|
299
|
+
});
|
|
300
|
+
} catch (error) {
|
|
301
|
+
// Fallback to synthetic sound
|
|
302
|
+
createSyntheticHitmarkerSound();
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const createSyntheticHitmarkerSound = () => {
|
|
307
|
+
try {
|
|
308
|
+
const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
|
|
309
|
+
|
|
310
|
+
// Create a more realistic hitmarker sound with noise and metallic ring
|
|
311
|
+
const oscillator1 = audioContext.createOscillator();
|
|
312
|
+
const oscillator2 = audioContext.createOscillator();
|
|
313
|
+
const noiseBuffer = audioContext.createBuffer(1, audioContext.sampleRate * 0.1, audioContext.sampleRate);
|
|
314
|
+
const noiseSource = audioContext.createBufferSource();
|
|
315
|
+
|
|
316
|
+
// Generate white noise for the initial "crack"
|
|
317
|
+
const noiseData = noiseBuffer.getChannelData(0);
|
|
318
|
+
for (let i = 0; i < noiseData.length; i++) {
|
|
319
|
+
noiseData[i] = Math.random() * 2 - 1;
|
|
320
|
+
}
|
|
321
|
+
noiseSource.buffer = noiseBuffer;
|
|
322
|
+
|
|
323
|
+
const gainNode1 = audioContext.createGain();
|
|
324
|
+
const gainNode2 = audioContext.createGain();
|
|
325
|
+
const noiseGain = audioContext.createGain();
|
|
326
|
+
const masterGain = audioContext.createGain();
|
|
327
|
+
|
|
328
|
+
// Connect everything
|
|
329
|
+
oscillator1.connect(gainNode1);
|
|
330
|
+
oscillator2.connect(gainNode2);
|
|
331
|
+
noiseSource.connect(noiseGain);
|
|
332
|
+
|
|
333
|
+
gainNode1.connect(masterGain);
|
|
334
|
+
gainNode2.connect(masterGain);
|
|
335
|
+
noiseGain.connect(masterGain);
|
|
336
|
+
masterGain.connect(audioContext.destination);
|
|
337
|
+
|
|
338
|
+
// Sharp metallic frequencies
|
|
339
|
+
oscillator1.frequency.setValueAtTime(1800, audioContext.currentTime);
|
|
340
|
+
oscillator1.frequency.exponentialRampToValueAtTime(900, audioContext.currentTime + 0.08);
|
|
341
|
+
|
|
342
|
+
oscillator2.frequency.setValueAtTime(3600, audioContext.currentTime);
|
|
343
|
+
oscillator2.frequency.exponentialRampToValueAtTime(1800, audioContext.currentTime + 0.04);
|
|
344
|
+
|
|
345
|
+
oscillator1.type = 'triangle';
|
|
346
|
+
oscillator2.type = 'sine';
|
|
347
|
+
|
|
348
|
+
// Sharp attack, quick decay
|
|
349
|
+
gainNode1.gain.setValueAtTime(0, audioContext.currentTime);
|
|
350
|
+
gainNode1.gain.linearRampToValueAtTime(0.4, audioContext.currentTime + 0.002);
|
|
351
|
+
gainNode1.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.12);
|
|
352
|
+
|
|
353
|
+
gainNode2.gain.setValueAtTime(0, audioContext.currentTime);
|
|
354
|
+
gainNode2.gain.linearRampToValueAtTime(0.3, audioContext.currentTime + 0.001);
|
|
355
|
+
gainNode2.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.06);
|
|
356
|
+
|
|
357
|
+
// Noise burst for the initial crack
|
|
358
|
+
noiseGain.gain.setValueAtTime(0, audioContext.currentTime);
|
|
359
|
+
noiseGain.gain.linearRampToValueAtTime(0.2, audioContext.currentTime + 0.001);
|
|
360
|
+
noiseGain.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.01);
|
|
361
|
+
|
|
362
|
+
masterGain.gain.setValueAtTime(0.5, audioContext.currentTime);
|
|
363
|
+
|
|
364
|
+
const startTime = audioContext.currentTime;
|
|
365
|
+
const stopTime = startTime + 0.15;
|
|
366
|
+
|
|
367
|
+
oscillator1.start(startTime);
|
|
368
|
+
oscillator2.start(startTime);
|
|
369
|
+
noiseSource.start(startTime);
|
|
370
|
+
|
|
371
|
+
oscillator1.stop(stopTime);
|
|
372
|
+
oscillator2.stop(stopTime);
|
|
373
|
+
noiseSource.stop(startTime + 0.02);
|
|
374
|
+
|
|
375
|
+
} catch (error) {
|
|
376
|
+
console.log('Audio context not available');
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
const createHitmarker = (e: { clientX: number; clientY: number }) => {
|
|
381
|
+
if (!containerRef.current) return;
|
|
382
|
+
|
|
383
|
+
const rect = containerRef.current.getBoundingClientRect();
|
|
384
|
+
const x = e.clientX - rect.left;
|
|
385
|
+
const y = e.clientY - rect.top;
|
|
386
|
+
|
|
387
|
+
const newHitmarker: Hitmarker = {
|
|
388
|
+
id: Date.now() + Math.random(),
|
|
389
|
+
x,
|
|
390
|
+
y,
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
setHitmarkers(prev => [...prev, newHitmarker]);
|
|
394
|
+
|
|
395
|
+
// Play sound
|
|
396
|
+
playHitmarkerSound();
|
|
397
|
+
|
|
398
|
+
// Remove hitmarker after animation
|
|
399
|
+
setTimeout(() => {
|
|
400
|
+
setHitmarkers(prev => prev.filter(h => h.id !== newHitmarker.id));
|
|
401
|
+
}, 600);
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
// Inject CSS animations
|
|
405
|
+
useEffect(() => {
|
|
406
|
+
const styleId = 'loading-screen-animations';
|
|
407
|
+
if (!document.getElementById(styleId)) {
|
|
408
|
+
const style = document.createElement('style');
|
|
409
|
+
style.id = styleId;
|
|
410
|
+
style.textContent = `
|
|
411
|
+
@keyframes fadeInOut {
|
|
412
|
+
0%, 100% {
|
|
413
|
+
opacity: 0.6;
|
|
414
|
+
}
|
|
415
|
+
50% {
|
|
416
|
+
opacity: 0.9;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
@keyframes logoPulse {
|
|
421
|
+
0%, 100% {
|
|
422
|
+
opacity: 0.15;
|
|
423
|
+
transform: scale(1);
|
|
424
|
+
}
|
|
425
|
+
50% {
|
|
426
|
+
opacity: 0.25;
|
|
427
|
+
transform: scale(1.05);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
@keyframes shimmer {
|
|
432
|
+
0% {
|
|
433
|
+
background-position: -200px 0;
|
|
434
|
+
}
|
|
435
|
+
100% {
|
|
436
|
+
background-position: calc(200px + 100%) 0;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
@keyframes floatUp {
|
|
441
|
+
0% {
|
|
442
|
+
transform: translateY(100vh) rotate(0deg);
|
|
443
|
+
opacity: 0;
|
|
444
|
+
}
|
|
445
|
+
10% {
|
|
446
|
+
opacity: 0.6;
|
|
447
|
+
}
|
|
448
|
+
90% {
|
|
449
|
+
opacity: 0.6;
|
|
450
|
+
}
|
|
451
|
+
100% {
|
|
452
|
+
transform: translateY(-100px) rotate(360deg);
|
|
453
|
+
opacity: 0;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
@keyframes gradientShift {
|
|
458
|
+
0%, 100% {
|
|
459
|
+
background-position: 0% 50%;
|
|
460
|
+
}
|
|
461
|
+
50% {
|
|
462
|
+
background-position: 100% 50%;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
@keyframes hitmarkerFade {
|
|
467
|
+
0% {
|
|
468
|
+
opacity: 1;
|
|
469
|
+
transform: scale(0.5);
|
|
470
|
+
}
|
|
471
|
+
20% {
|
|
472
|
+
opacity: 1;
|
|
473
|
+
transform: scale(1.2);
|
|
474
|
+
}
|
|
475
|
+
100% {
|
|
476
|
+
opacity: 0;
|
|
477
|
+
transform: scale(1);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
@keyframes hitmarkerFade45 {
|
|
482
|
+
0% {
|
|
483
|
+
opacity: 1;
|
|
484
|
+
transform: translate(-50%, -50%) rotate(45deg) scale(0.5);
|
|
485
|
+
}
|
|
486
|
+
20% {
|
|
487
|
+
opacity: 1;
|
|
488
|
+
transform: translate(-50%, -50%) rotate(45deg) scale(1.2);
|
|
489
|
+
}
|
|
490
|
+
100% {
|
|
491
|
+
opacity: 0;
|
|
492
|
+
transform: translate(-50%, -50%) rotate(45deg) scale(1);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
@keyframes hitmarkerFadeNeg45 {
|
|
497
|
+
0% {
|
|
498
|
+
opacity: 1;
|
|
499
|
+
transform: translate(-50%, -50%) rotate(-45deg) scale(0.5);
|
|
500
|
+
}
|
|
501
|
+
20% {
|
|
502
|
+
opacity: 1;
|
|
503
|
+
transform: translate(-50%, -50%) rotate(-45deg) scale(1.2);
|
|
504
|
+
}
|
|
505
|
+
100% {
|
|
506
|
+
opacity: 0;
|
|
507
|
+
transform: translate(-50%, -50%) rotate(-45deg) scale(1);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
`;
|
|
511
|
+
document.head.appendChild(style);
|
|
512
|
+
}
|
|
513
|
+
}, []);
|
|
514
|
+
|
|
515
|
+
return (
|
|
516
|
+
<div
|
|
517
|
+
ref={containerRef}
|
|
518
|
+
className="fw-player-root"
|
|
519
|
+
style={{
|
|
520
|
+
position: "relative",
|
|
521
|
+
width: "100%",
|
|
522
|
+
height: "100%",
|
|
523
|
+
minHeight: "300px",
|
|
524
|
+
background: `
|
|
525
|
+
linear-gradient(135deg,
|
|
526
|
+
hsl(var(--tn-bg-dark, 235 21% 11%)) 0%,
|
|
527
|
+
hsl(var(--tn-bg, 233 23% 17%)) 25%,
|
|
528
|
+
hsl(var(--tn-bg-dark, 235 21% 11%)) 50%,
|
|
529
|
+
hsl(var(--tn-bg, 233 23% 17%)) 75%,
|
|
530
|
+
hsl(var(--tn-bg-dark, 235 21% 11%)) 100%
|
|
531
|
+
)
|
|
532
|
+
`,
|
|
533
|
+
backgroundSize: "400% 400%",
|
|
534
|
+
animation: "gradientShift 16s ease-in-out infinite",
|
|
535
|
+
display: "flex",
|
|
536
|
+
flexDirection: "column",
|
|
537
|
+
alignItems: "center",
|
|
538
|
+
justifyContent: "center",
|
|
539
|
+
overflow: "hidden",
|
|
540
|
+
borderRadius: "0", /* Slab - no rounded corners */
|
|
541
|
+
userSelect: "none",
|
|
542
|
+
WebkitUserSelect: "none",
|
|
543
|
+
MozUserSelect: "none",
|
|
544
|
+
msUserSelect: "none",
|
|
545
|
+
} as React.CSSProperties}
|
|
546
|
+
>
|
|
547
|
+
{/* Hitmarkers */}
|
|
548
|
+
{hitmarkers.map(hitmarker => (
|
|
549
|
+
<div
|
|
550
|
+
key={hitmarker.id}
|
|
551
|
+
style={{
|
|
552
|
+
position: "absolute",
|
|
553
|
+
left: `${hitmarker.x}px`,
|
|
554
|
+
top: `${hitmarker.y}px`,
|
|
555
|
+
transform: "translate(-50%, -50%)",
|
|
556
|
+
pointerEvents: "none",
|
|
557
|
+
zIndex: 100,
|
|
558
|
+
width: "40px",
|
|
559
|
+
height: "40px",
|
|
560
|
+
}}
|
|
561
|
+
>
|
|
562
|
+
{/* Top-left diagonal line */}
|
|
563
|
+
<div
|
|
564
|
+
style={{
|
|
565
|
+
position: "absolute",
|
|
566
|
+
top: "25%",
|
|
567
|
+
left: "25%",
|
|
568
|
+
width: "12px",
|
|
569
|
+
height: "3px",
|
|
570
|
+
backgroundColor: "#ffffff",
|
|
571
|
+
transform: "translate(-50%, -50%) rotate(45deg)",
|
|
572
|
+
animation: "hitmarkerFade45 0.6s ease-out forwards",
|
|
573
|
+
boxShadow: "0 0 8px rgba(255, 255, 255, 0.8)",
|
|
574
|
+
borderRadius: "1px",
|
|
575
|
+
}}
|
|
576
|
+
/>
|
|
577
|
+
{/* Top-right diagonal line */}
|
|
578
|
+
<div
|
|
579
|
+
style={{
|
|
580
|
+
position: "absolute",
|
|
581
|
+
top: "25%",
|
|
582
|
+
left: "75%",
|
|
583
|
+
width: "12px",
|
|
584
|
+
height: "3px",
|
|
585
|
+
backgroundColor: "#ffffff",
|
|
586
|
+
transform: "translate(-50%, -50%) rotate(-45deg)",
|
|
587
|
+
animation: "hitmarkerFadeNeg45 0.6s ease-out forwards",
|
|
588
|
+
boxShadow: "0 0 8px rgba(255, 255, 255, 0.8)",
|
|
589
|
+
borderRadius: "1px",
|
|
590
|
+
}}
|
|
591
|
+
/>
|
|
592
|
+
{/* Bottom-left diagonal line */}
|
|
593
|
+
<div
|
|
594
|
+
style={{
|
|
595
|
+
position: "absolute",
|
|
596
|
+
top: "75%",
|
|
597
|
+
left: "25%",
|
|
598
|
+
width: "12px",
|
|
599
|
+
height: "3px",
|
|
600
|
+
backgroundColor: "#ffffff",
|
|
601
|
+
transform: "translate(-50%, -50%) rotate(-45deg)",
|
|
602
|
+
animation: "hitmarkerFadeNeg45 0.6s ease-out forwards",
|
|
603
|
+
boxShadow: "0 0 8px rgba(255, 255, 255, 0.8)",
|
|
604
|
+
borderRadius: "1px",
|
|
605
|
+
}}
|
|
606
|
+
/>
|
|
607
|
+
{/* Bottom-right diagonal line */}
|
|
608
|
+
<div
|
|
609
|
+
style={{
|
|
610
|
+
position: "absolute",
|
|
611
|
+
top: "75%",
|
|
612
|
+
left: "75%",
|
|
613
|
+
width: "12px",
|
|
614
|
+
height: "3px",
|
|
615
|
+
backgroundColor: "#ffffff",
|
|
616
|
+
transform: "translate(-50%, -50%) rotate(45deg)",
|
|
617
|
+
animation: "hitmarkerFade45 0.6s ease-out forwards",
|
|
618
|
+
boxShadow: "0 0 8px rgba(255, 255, 255, 0.8)",
|
|
619
|
+
borderRadius: "1px",
|
|
620
|
+
}}
|
|
621
|
+
/>
|
|
622
|
+
</div>
|
|
623
|
+
))}
|
|
624
|
+
|
|
625
|
+
{/* Floating particles */}
|
|
626
|
+
{[...Array(12)].map((_, index) => (
|
|
627
|
+
<div
|
|
628
|
+
key={`particle-${index}`}
|
|
629
|
+
style={{
|
|
630
|
+
position: "absolute",
|
|
631
|
+
left: `${Math.random() * 100}%`,
|
|
632
|
+
width: `${Math.random() * 4 + 2}px`,
|
|
633
|
+
height: `${Math.random() * 4 + 2}px`,
|
|
634
|
+
borderRadius: "50%",
|
|
635
|
+
background: [
|
|
636
|
+
"#7aa2f7", // Terminal Blue
|
|
637
|
+
"#bb9af7", // Terminal Magenta
|
|
638
|
+
"#9ece6a", // Strings/CSS classes
|
|
639
|
+
"#73daca", // Terminal Green
|
|
640
|
+
"#7dcfff", // Terminal Cyan
|
|
641
|
+
"#f7768e", // Keywords/Terminal Red
|
|
642
|
+
"#e0af68", // Terminal Yellow
|
|
643
|
+
"#2ac3de", // Language functions
|
|
644
|
+
][index % 8],
|
|
645
|
+
opacity: 0,
|
|
646
|
+
animation: `floatUp ${8 + Math.random() * 4}s linear infinite`,
|
|
647
|
+
animationDelay: `${Math.random() * 8}s`,
|
|
648
|
+
pointerEvents: "none",
|
|
649
|
+
userSelect: "none",
|
|
650
|
+
}}
|
|
651
|
+
/>
|
|
652
|
+
))}
|
|
653
|
+
|
|
654
|
+
{/* Animated bubbles with Tokyo Night colors */}
|
|
655
|
+
{[...Array(8)].map((_, index) => (
|
|
656
|
+
<AnimatedBubble key={index} index={index} />
|
|
657
|
+
))}
|
|
658
|
+
|
|
659
|
+
{/* Center logo */}
|
|
660
|
+
<CenterLogo containerRef={containerRef as React.RefObject<HTMLDivElement>} onHitmarker={createHitmarker} />
|
|
661
|
+
|
|
662
|
+
{/* Bouncing DVD Logo */}
|
|
663
|
+
<DvdLogo parentRef={containerRef as React.RefObject<HTMLDivElement>} scale={0.08} />
|
|
664
|
+
|
|
665
|
+
{/* Message */}
|
|
666
|
+
<div
|
|
667
|
+
style={{
|
|
668
|
+
position: "absolute",
|
|
669
|
+
bottom: "20%",
|
|
670
|
+
left: "50%",
|
|
671
|
+
transform: "translateX(-50%)",
|
|
672
|
+
color: "#a9b1d6",
|
|
673
|
+
fontSize: "16px",
|
|
674
|
+
fontWeight: "500",
|
|
675
|
+
textAlign: "center",
|
|
676
|
+
animation: "fadeInOut 2s ease-in-out infinite",
|
|
677
|
+
textShadow: "0 2px 4px rgba(36, 40, 59, 0.5)",
|
|
678
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
|
679
|
+
userSelect: "none",
|
|
680
|
+
WebkitUserSelect: "none",
|
|
681
|
+
MozUserSelect: "none",
|
|
682
|
+
msUserSelect: "none",
|
|
683
|
+
pointerEvents: "none",
|
|
684
|
+
}}
|
|
685
|
+
>
|
|
686
|
+
{message}
|
|
687
|
+
</div>
|
|
688
|
+
|
|
689
|
+
{/* Subtle overlay texture */}
|
|
690
|
+
<div
|
|
691
|
+
style={{
|
|
692
|
+
position: "absolute",
|
|
693
|
+
top: 0,
|
|
694
|
+
left: 0,
|
|
695
|
+
right: 0,
|
|
696
|
+
bottom: 0,
|
|
697
|
+
background: `
|
|
698
|
+
radial-gradient(circle at 20% 80%, rgba(122, 162, 247, 0.03) 0%, transparent 50%),
|
|
699
|
+
radial-gradient(circle at 80% 20%, rgba(187, 154, 247, 0.03) 0%, transparent 50%),
|
|
700
|
+
radial-gradient(circle at 40% 40%, rgba(158, 206, 106, 0.02) 0%, transparent 50%)
|
|
701
|
+
`,
|
|
702
|
+
pointerEvents: "none",
|
|
703
|
+
userSelect: "none",
|
|
704
|
+
}}
|
|
705
|
+
/>
|
|
706
|
+
</div>
|
|
707
|
+
);
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
export default LoadingScreen;
|