nitrostack 1.0.70 → 1.0.72

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.
Files changed (35) hide show
  1. package/package.json +1 -1
  2. package/src/studio/app/api/chat/route.ts +33 -15
  3. package/src/studio/app/auth/callback/page.tsx +6 -6
  4. package/src/studio/app/chat/page.tsx +1124 -415
  5. package/src/studio/app/chat/page.tsx.backup +1046 -187
  6. package/src/studio/app/globals.css +361 -191
  7. package/src/studio/app/health/page.tsx +72 -76
  8. package/src/studio/app/layout.tsx +9 -11
  9. package/src/studio/app/logs/page.tsx +29 -30
  10. package/src/studio/app/page.tsx +134 -230
  11. package/src/studio/app/prompts/page.tsx +115 -97
  12. package/src/studio/app/resources/page.tsx +115 -124
  13. package/src/studio/app/settings/page.tsx +1080 -125
  14. package/src/studio/app/tools/page.tsx +343 -0
  15. package/src/studio/components/EnlargeModal.tsx +76 -65
  16. package/src/studio/components/LogMessage.tsx +5 -5
  17. package/src/studio/components/MarkdownRenderer.tsx +4 -4
  18. package/src/studio/components/Sidebar.tsx +150 -210
  19. package/src/studio/components/SplashScreen.tsx +109 -0
  20. package/src/studio/components/ToolCard.tsx +50 -41
  21. package/src/studio/components/VoiceOrbOverlay.tsx +469 -0
  22. package/src/studio/components/WidgetRenderer.tsx +8 -3
  23. package/src/studio/components/tools/ToolsCanvas.tsx +327 -0
  24. package/src/studio/lib/llm-service.ts +104 -1
  25. package/src/studio/lib/store.ts +36 -21
  26. package/src/studio/lib/types.ts +1 -1
  27. package/src/studio/package-lock.json +3303 -0
  28. package/src/studio/package.json +3 -1
  29. package/src/studio/public/NitroStudio Isotype Color.png +0 -0
  30. package/src/studio/tailwind.config.ts +63 -17
  31. package/templates/typescript-starter/package-lock.json +4112 -0
  32. package/templates/typescript-starter/package.json +2 -3
  33. package/templates/typescript-starter/src/modules/calculator/calculator.tools.ts +100 -5
  34. package/src/studio/app/auth/page.tsx +0 -560
  35. package/src/studio/app/ping/page.tsx +0 -209
@@ -1,42 +1,49 @@
1
1
  'use client';
2
2
 
3
3
  import { useStudioStore } from '@/lib/store';
4
+ import { api } from '@/lib/api';
4
5
  import type { TabType } from '@/lib/types';
5
6
  import { useRouter, usePathname } from 'next/navigation';
6
7
  import { useEffect, useState } from 'react';
7
8
  import Image from 'next/image';
8
- import {
9
- Wrench,
10
- MessageSquare,
11
- Package,
12
- FileText,
13
- Activity,
14
- Shield,
15
- Wifi,
16
- Zap,
17
- Settings,
18
- Sparkles,
19
- Terminal
20
- } from 'lucide-react';
9
+ import {
10
+ WrenchScrewdriverIcon,
11
+ ChatBubbleLeftIcon,
12
+ CubeIcon,
13
+ DocumentTextIcon,
14
+ HeartIcon,
15
+ ShieldCheckIcon,
16
+ WifiIcon,
17
+ Cog6ToothIcon,
18
+ CommandLineIcon
19
+ } from '@heroicons/react/24/outline';
21
20
 
22
- const navItems: Array<{ id: TabType | 'settings' | 'logs'; label: string; icon: any; path: string }> = [
23
- { id: 'tools', label: 'Tools', icon: Wrench, path: '/' },
24
- { id: 'chat', label: 'AI Chat', icon: MessageSquare, path: '/chat' },
25
- { id: 'resources', label: 'Resources', icon: Package, path: '/resources' },
26
- { id: 'prompts', label: 'Prompts', icon: FileText, path: '/prompts' },
27
- { id: 'health', label: 'Health', icon: Activity, path: '/health' },
28
- { id: 'logs', label: 'Logs', icon: Terminal, path: '/logs' },
29
- { id: 'auth', label: 'OAuth 2.1', icon: Shield, path: '/auth' },
30
- { id: 'ping', label: 'Ping', icon: Wifi, path: '/ping' },
31
- { id: 'settings', label: 'Settings', icon: Settings, path: '/settings' },
21
+ const navItems = [
22
+ {
23
+ id: 'app',
24
+ label: 'App',
25
+ icon: CubeIcon,
26
+ path: '/',
27
+ children: [
28
+ { id: 'tools', label: 'Tools', icon: WrenchScrewdriverIcon, path: '/tools' },
29
+ { id: 'resources', label: 'Resources', icon: CubeIcon, path: '/resources' },
30
+ { id: 'prompts', label: 'Prompts', icon: DocumentTextIcon, path: '/prompts' },
31
+ ]
32
+ },
33
+ { id: 'chat', label: 'AI Chat', icon: ChatBubbleLeftIcon, path: '/chat' },
34
+ { id: 'health', label: 'Health', icon: HeartIcon, path: '/health' },
35
+ { id: 'logs', label: 'Logs', icon: CommandLineIcon, path: '/logs' },
36
+ { id: 'settings', label: 'Settings', icon: Cog6ToothIcon, path: '/settings' },
32
37
  ];
33
38
 
34
39
  export function Sidebar() {
35
- const { connection } = useStudioStore();
40
+ const { connection, setConnection } = useStudioStore();
36
41
  const router = useRouter();
37
42
  const pathname = usePathname();
38
43
  const [mounted, setMounted] = useState(false);
39
44
  const [isCollapsed, setIsCollapsed] = useState(false);
45
+ // Default expanded parents
46
+ const [expandedItems, setExpandedItems] = useState<string[]>(['app']);
40
47
 
41
48
  useEffect(() => {
42
49
  setMounted(true);
@@ -45,12 +52,34 @@ export function Sidebar() {
45
52
  // Load collapse state from localStorage
46
53
  const saved = localStorage.getItem('sidebar_collapsed');
47
54
  if (saved !== null) setIsCollapsed(saved === 'true');
55
+
56
+ // Initialize Connection (Global)
57
+ const initConnection = async () => {
58
+ try {
59
+ await api.initialize();
60
+ const health = await api.checkConnection();
61
+ setConnection({
62
+ connected: health.connected,
63
+ status: health.connected ? 'connected' : 'disconnected',
64
+ });
65
+ } catch (e) {
66
+ setConnection({ connected: false, status: 'disconnected' });
67
+ }
68
+ };
69
+ initConnection();
48
70
  }, []);
49
71
 
50
72
  const handleNavigation = (path: string) => {
51
73
  router.push(path);
52
74
  };
53
75
 
76
+ const toggleExpand = (id: string, e: React.MouseEvent) => {
77
+ e.stopPropagation();
78
+ setExpandedItems(prev =>
79
+ prev.includes(id) ? prev.filter(item => item !== id) : [...prev, id]
80
+ );
81
+ };
82
+
54
83
  const getConnectionStatus = () => {
55
84
  if (connection.status === 'connected') return 'connected';
56
85
  if (connection.status === 'connecting') return 'connecting';
@@ -61,235 +90,146 @@ export function Sidebar() {
61
90
  const newState = !isCollapsed;
62
91
  setIsCollapsed(newState);
63
92
  localStorage.setItem('sidebar_collapsed', String(newState));
64
- // Dispatch custom event to update layout
65
93
  window.dispatchEvent(new Event('sidebar-toggle'));
66
94
  };
67
95
 
96
+ const renderNavItem = (item: any, depth = 0) => {
97
+ const Icon = item.icon;
98
+ const isActive = pathname === item.path || (item.children && item.children.some((child: any) => pathname === child.path));
99
+ const isExpanded = expandedItems.includes(item.id);
100
+ const hasChildren = item.children && item.children.length > 0;
101
+
102
+ return (
103
+ <div key={item.id} className="w-full">
104
+ <button
105
+ onClick={(e) => {
106
+ if (hasChildren && !isCollapsed) {
107
+ toggleExpand(item.id, e);
108
+ // Also navigate if it has a path
109
+ if (item.path && item.path !== '#') handleNavigation(item.path);
110
+ } else {
111
+ handleNavigation(item.path);
112
+ }
113
+ }}
114
+ title={isCollapsed ? item.label : undefined}
115
+ className={`
116
+ relative flex items-center gap-2 text-sm font-medium rounded-lg transition-all duration-300 group overflow-hidden
117
+ ${isCollapsed ? 'w-10 h-10 justify-center mx-auto mb-1' : 'w-full px-3 py-2'}
118
+ ${isActive && !hasChildren
119
+ ? 'bg-gradient-to-r from-primary/15 to-secondary/10 text-primary shadow-md ring-1 ring-primary/30'
120
+ : 'text-foreground/70 hover:bg-primary/5 hover:text-primary'
121
+ }
122
+ ${depth > 0 && !isCollapsed ? 'ml-4 w-[calc(100%-1rem)] border-l border-border/50 pl-2' : ''}
123
+ `}
124
+ >
125
+ {/* Active indicator (Left strip) - Only for leaf nodes or exact match */}
126
+ {isActive && !isCollapsed && !hasChildren && (
127
+ <div className="absolute left-0 top-1/2 -translate-y-1/2 w-0.5 h-6 bg-gradient-to-b from-primary to-secondary rounded-r-full" />
128
+ )}
129
+
130
+ {/* Icon */}
131
+ <div className={`relative flex-shrink-0 ${isActive ? 'scale-105' : 'group-hover:scale-105'} transition-transform duration-300`}>
132
+ <Icon
133
+ className={`h-5 w-5 ${isActive
134
+ ? 'text-primary'
135
+ : 'text-muted-foreground group-hover:text-primary'
136
+ }`}
137
+ />
138
+ </div>
139
+
140
+ {/* Label & Chevron - only show when expanded */}
141
+ {!isCollapsed && (
142
+ <div className="flex-1 flex items-center justify-between overflow-hidden">
143
+ <span className="text-xs whitespace-nowrap overflow-hidden">
144
+ {item.label}
145
+ </span>
146
+ {hasChildren && (
147
+ <div className={`transition-transform duration-200 ${isExpanded ? 'rotate-90' : ''}`}>
148
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-muted-foreground/50">
149
+ <path d="M9 18l6-6-6-6" />
150
+ </svg>
151
+ </div>
152
+ )}
153
+ </div>
154
+ )}
155
+ </button>
156
+
157
+ {/* Render Children */}
158
+ {!isCollapsed && hasChildren && isExpanded && (
159
+ <div className="mt-0.5 space-y-0.5">
160
+ {item.children.map((child: any) => renderNavItem(child, depth + 1))}
161
+ </div>
162
+ )}
163
+ </div>
164
+ );
165
+ };
166
+
68
167
  if (!mounted) return null;
69
168
 
70
169
  return (
71
- <nav className={`fixed left-0 top-0 h-screen glass flex flex-col z-50 border-r border-border/50 transition-all duration-300 ease-in-out ${
72
- isCollapsed ? 'w-16' : 'w-60 md:w-60'
73
- } ${isCollapsed ? '' : 'max-md:w-16'}`}>
74
- {/* Compact Professional Header */}
170
+ <nav className={`fixed left-0 top-0 h-screen glass flex flex-col z-50 border-r border-border/50 transition-all duration-300 ease-in-out ${isCollapsed ? 'w-16' : 'w-60 md:w-60'} ${isCollapsed ? '' : 'max-md:w-16'}`}>
171
+ {/* Header */}
75
172
  <div className="relative p-3 border-b border-border/50 bg-gradient-to-b from-card/80 to-transparent">
76
173
  <div className="flex items-center justify-between mb-2">
77
- {/* Minimalist Professional Logo */}
78
- <div
79
- className="flex items-center gap-2 group cursor-pointer flex-1"
80
- onClick={() => handleNavigation('/')}
81
- >
174
+ {/* Logo */}
175
+ <div className="flex items-center gap-2 group cursor-pointer flex-1" onClick={() => handleNavigation('/')}>
82
176
  <div className="relative flex-shrink-0">
83
- <div className="absolute inset-0 rounded-lg bg-gradient-to-br from-primary to-amber-500 blur opacity-30 group-hover:opacity-50 transition-opacity duration-300" />
84
- <div className="relative w-9 h-9 rounded-lg bg-gradient-to-br from-slate-900 to-slate-800 border border-primary/30 flex items-center justify-center shadow-lg group-hover:shadow-primary/30 transition-all duration-300 overflow-hidden">
85
- {/* NitroCloud Logo */}
86
- <Image
87
- src="/nitrocloud.png"
88
- alt="NitroCloud Logo"
89
- width={36}
90
- height={36}
91
- className="relative z-10 object-contain"
92
- unoptimized
93
- />
94
- {/* Subtle glow effect */}
95
- <div className="absolute inset-0 bg-gradient-to-tr from-primary/20 to-amber-500/20 group-hover:opacity-100 opacity-0 transition-opacity duration-300" />
177
+ <div className="relative w-10 h-10 rounded bg-white/5 border border-primary/20 flex items-center justify-center shadow-lg group-hover:border-primary/40 transition-all duration-300 overflow-hidden backdrop-blur-sm">
178
+ <Image src="/NitroStudio Isotype Color.png" alt="NitroStudio Logo" width={28} height={28} className="relative z-10 object-contain" unoptimized />
96
179
  </div>
97
- </div>
98
-
180
+ </div>
99
181
  {!isCollapsed && (
100
- <div className="flex-1 overflow-hidden">
101
- <h1 className="text-base font-bold bg-gradient-to-r from-primary to-amber-500 bg-clip-text text-transparent tracking-tight whitespace-nowrap">
102
- NitroStudio
103
- </h1>
104
- <p className="text-[9px] text-muted-foreground font-medium uppercase tracking-wider">
105
- MCP Suite
106
- </p>
182
+ <div className="flex-1 overflow-hidden ml-2.5">
183
+ <h1 className="text-sm font-bold bg-gradient-to-r from-primary via-primary to-secondary bg-clip-text text-transparent tracking-wide whitespace-nowrap leading-tight" style={{ fontFamily: 'Space Grotesk, sans-serif' }}>NitroStudio</h1>
184
+ <p className="text-[8px] text-muted-foreground/70 font-semibold uppercase tracking-wider leading-none mt-0.5" style={{ fontFamily: 'IBM Plex Sans, sans-serif' }}>MCP Suite</p>
107
185
  </div>
108
186
  )}
109
187
  </div>
110
-
111
- {/* Toggle Button */}
112
- <button
113
- onClick={toggleSidebar}
114
- className="flex-shrink-0 w-7 h-7 rounded-md bg-muted/50 hover:bg-muted border border-border/50 hover:border-primary/30 flex items-center justify-center transition-all duration-300 group/toggle"
115
- title={isCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
116
- >
117
- <svg
118
- width="14"
119
- height="14"
120
- viewBox="0 0 24 24"
121
- fill="none"
122
- className={`text-muted-foreground group-hover/toggle:text-primary transition-all duration-300 ${
123
- isCollapsed ? 'rotate-180' : ''
124
- }`}
125
- >
126
- <path d="M15 18L9 12L15 6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
127
- </svg>
188
+ {/* Toggle */}
189
+ <button onClick={toggleSidebar} className="flex-shrink-0 w-7 h-7 rounded-md bg-muted/50 hover:bg-muted border border-border/50 flex items-center justify-center transition-all duration-300">
190
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" className={`text-muted-foreground transition-all duration-300 ${isCollapsed ? 'rotate-180' : ''}`}><path d="M15 18L9 12L15 6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /></svg>
128
191
  </button>
129
192
  </div>
130
193
 
131
- {/* Compact Status Indicators */}
194
+ {/* Status */}
132
195
  {!isCollapsed ? (
133
196
  <div className="space-y-1.5">
134
- {/* Connection Status */}
135
197
  <div className="relative group">
136
198
  <div className="relative flex items-center gap-2 px-2 py-1.5 rounded-md bg-card/60 border border-border/50 backdrop-blur-sm group-hover:border-primary/30 transition-all duration-300">
137
- <div className="relative flex items-center justify-center flex-shrink-0">
138
- {getConnectionStatus() === 'connected' && (
139
- <div className="absolute inset-0 rounded-full bg-emerald-500/30 animate-ping" />
140
- )}
141
- <div
142
- className={`relative w-2 h-2 rounded-full transition-all duration-300 ${
143
- getConnectionStatus() === 'connected'
144
- ? 'bg-emerald-500'
145
- : getConnectionStatus() === 'connecting'
146
- ? 'bg-amber-500 animate-pulse'
147
- : 'bg-rose-500'
148
- }`}
149
- style={{
150
- boxShadow: getConnectionStatus() === 'connected'
151
- ? '0 0 8px rgba(34, 197, 94, 0.6)'
152
- : getConnectionStatus() === 'connecting'
153
- ? '0 0 8px rgba(245, 158, 11, 0.6)'
154
- : '0 0 8px rgba(239, 68, 68, 0.6)'
155
- }}
156
- />
157
- </div>
158
- <span className="text-[10px] font-semibold uppercase tracking-wide text-foreground">
159
- {connection.status}
160
- </span>
161
- </div>
162
- </div>
163
-
164
- {/* Transport Mode */}
199
+ <div className={`relative w-2 h-2 rounded-full transition-all duration-300 ${getConnectionStatus() === 'connected' ? 'bg-emerald-500' : getConnectionStatus() === 'connecting' ? 'bg-status-warning animate-pulse' : 'bg-rose-500'}`} />
200
+ <span className="text-[10px] font-semibold uppercase tracking-wide text-foreground">{connection.status}</span>
201
+ </div>
202
+ </div>
165
203
  <div className="relative group">
166
204
  <div className="relative px-2 py-1.5 rounded-md bg-primary/5 border border-primary/20 backdrop-blur-sm group-hover:border-primary/30 transition-all duration-300">
167
205
  <div className="flex items-center justify-between text-[10px]">
168
206
  <span className="font-medium text-muted-foreground">Transport:</span>
169
- <span className="font-bold text-primary uppercase">
170
- STDIO
171
- </span>
207
+ <span className="font-bold text-primary uppercase">STDIO</span>
172
208
  </div>
173
209
  </div>
174
210
  </div>
175
211
  </div>
176
212
  ) : (
177
213
  <div className="flex flex-col items-center gap-1.5">
178
- {/* Collapsed: Just the status dot */}
179
- <div
180
- className="relative w-7 h-7 rounded-md bg-card/60 border border-border/50 flex items-center justify-center group hover:border-primary/30 transition-all"
181
- title={`Status: ${connection.status}`}
182
- >
183
- {getConnectionStatus() === 'connected' && (
184
- <div className="absolute inset-0 rounded-md bg-emerald-500/20 animate-ping" />
185
- )}
186
- <div
187
- className={`relative w-2 h-2 rounded-full ${
188
- getConnectionStatus() === 'connected'
189
- ? 'bg-emerald-500'
190
- : getConnectionStatus() === 'connecting'
191
- ? 'bg-amber-500 animate-pulse'
192
- : 'bg-rose-500'
193
- }`}
194
- style={{
195
- boxShadow: getConnectionStatus() === 'connected'
196
- ? '0 0 8px rgba(34, 197, 94, 0.8)'
197
- : getConnectionStatus() === 'connecting'
198
- ? '0 0 8px rgba(245, 158, 11, 0.8)'
199
- : '0 0 8px rgba(239, 68, 68, 0.8)'
200
- }}
201
- />
202
- </div>
214
+ <div className={`relative w-2 h-2 rounded-full ${getConnectionStatus() === 'connected' ? 'bg-emerald-500' : 'bg-rose-500'}`} />
203
215
  </div>
204
216
  )}
205
217
  </div>
206
218
 
207
- {/* Compact Navigation */}
219
+ {/* Navigation */}
208
220
  <div className="flex-1 overflow-y-auto py-2 px-2 scrollbar-thin scrollbar-thumb-border scrollbar-track-transparent">
209
221
  <div className={`space-y-0.5 ${isCollapsed ? 'flex flex-col items-center' : ''}`}>
210
- {navItems.map((item) => {
211
- const Icon = item.icon;
212
- const isActive = pathname === item.path;
213
-
214
- return (
215
- <button
216
- key={item.id}
217
- onClick={() => handleNavigation(item.path)}
218
- title={isCollapsed ? item.label : undefined}
219
- className={`
220
- relative flex items-center gap-2 text-sm font-medium rounded-lg transition-all duration-300 group overflow-hidden
221
- ${isCollapsed ? 'w-10 h-10 justify-center' : 'w-full px-3 py-2'}
222
- ${isActive
223
- ? 'bg-gradient-to-r from-primary/15 to-amber-500/10 text-primary shadow-md ring-1 ring-primary/30'
224
- : 'text-foreground/70 hover:bg-primary/5 hover:text-primary'
225
- }
226
- `}
227
- >
228
- {/* Active indicator */}
229
- {isActive && !isCollapsed && (
230
- <div className="absolute left-0 top-1/2 -translate-y-1/2 w-0.5 h-6 bg-gradient-to-b from-primary to-amber-500 rounded-r-full" />
231
- )}
232
-
233
- {/* Icon */}
234
- <div className={`relative flex-shrink-0 ${isActive ? 'scale-105' : 'group-hover:scale-105'} transition-transform duration-300`}>
235
- <Icon
236
- className={`w-4 h-4 ${
237
- isActive
238
- ? 'text-primary'
239
- : 'text-muted-foreground group-hover:text-primary'
240
- }`}
241
- strokeWidth={isActive ? 2.5 : 2}
242
- />
243
- </div>
244
-
245
- {/* Label - only show when expanded */}
246
- {!isCollapsed && (
247
- <span className="text-xs whitespace-nowrap overflow-hidden">
248
- {item.label}
249
- </span>
250
- )}
251
-
252
- {/* Subtle shine effect */}
253
- <div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/5 to-transparent -translate-x-full group-hover:translate-x-full transition-transform duration-700 ease-out pointer-events-none" />
254
- </button>
255
- );
256
- })}
222
+ {navItems.map((item) => renderNavItem(item))}
257
223
  </div>
258
224
  </div>
259
225
 
260
- {/* Compact Footer */}
226
+ {/* Footer */}
261
227
  <div className="p-2 border-t border-border/50 bg-gradient-to-t from-card/60 to-transparent backdrop-blur-sm">
262
228
  {!isCollapsed ? (
263
- <div className="space-y-1.5">
264
- {/* Version info */}
265
- <div className="px-2 py-1.5 rounded-md bg-muted/30 border border-border/30">
266
- <div className="flex items-center justify-between text-[9px]">
267
- <span className="font-medium text-muted-foreground uppercase tracking-wide">MCP v1.0</span>
268
- <span className="font-bold text-foreground">NitroStack</span>
269
- </div>
270
- </div>
271
-
272
- {/* Copyright */}
273
- <div className="text-center">
274
- <p className="text-[8px] text-muted-foreground/50 font-medium">
275
- © 2025 NitroCloud
276
- </p>
277
- </div>
278
- </div>
279
- ) : (
280
- <div className="flex flex-col items-center">
281
- {/* Collapsed: Minimal version indicator */}
282
- <div
283
- className="w-10 h-10 rounded-md bg-muted/30 border border-border/30 flex items-center justify-center group hover:border-primary/30 transition-all"
284
- title="MCP v1.0 • NitroStack"
285
- >
286
- <span className="text-[9px] font-bold text-primary group-hover:scale-110 transition-transform">
287
- v1
288
- </span>
289
- </div>
290
- </div>
291
- )}
229
+ <div className="text-center"><p className="text-[8px] text-muted-foreground/50 font-medium">© 2025 NitroCloud</p></div>
230
+ ) : null}
292
231
  </div>
232
+
293
233
  </nav>
294
234
  );
295
235
  }
@@ -0,0 +1,109 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+ import Image from 'next/image';
5
+
6
+ const LOADING_TEXTS = [
7
+ "Initializing NitroStudio...",
8
+ "Waking up the agents...",
9
+ "Aligning gradients...",
10
+ "Charging flux capacitors...",
11
+ "Assembling pixels...",
12
+ "Connecting to the matrix...",
13
+ "Spinning up the studio...",
14
+ "Almost there..."
15
+ ];
16
+
17
+ export function SplashScreen() {
18
+ const [show, setShow] = useState(true);
19
+ const [textIndex, setTextIndex] = useState(0);
20
+ const [fadeOut, setFadeOut] = useState(false);
21
+
22
+ useEffect(() => {
23
+ // Initial load delay logic
24
+ const timer = setTimeout(() => {
25
+ setFadeOut(true);
26
+ setTimeout(() => setShow(false), 500); // 500ms fade out duration
27
+ }, 3000); // Slightly longer for the "system check" vibe
28
+
29
+ return () => clearTimeout(timer);
30
+ }, []);
31
+
32
+ useEffect(() => {
33
+ if (!show) return;
34
+
35
+ // Cycle text every 800ms
36
+ const interval = setInterval(() => {
37
+ setTextIndex((prev) => (prev + 1) % LOADING_TEXTS.length);
38
+ }, 800);
39
+
40
+ return () => clearInterval(interval);
41
+ }, [show]);
42
+
43
+ if (!show) return null;
44
+
45
+ return (
46
+ <div
47
+ className={`fixed inset-0 z-[100] flex flex-col items-center justify-center bg-background transition-opacity duration-500 font-mono ${fadeOut ? 'opacity-0' : 'opacity-100'
48
+ }`}
49
+ >
50
+ {/* Tech Background Grid - Subtle & Professional */}
51
+ <div
52
+ className="absolute inset-0 opacity-[0.03]"
53
+ style={{
54
+ backgroundImage: `linear-gradient(#fff 1px, transparent 1px), linear-gradient(90deg, #fff 1px, transparent 1px)`,
55
+ backgroundSize: '40px 40px',
56
+ mask: 'radial-gradient(circle at center, black 40%, transparent 100%)',
57
+ WebkitMask: 'radial-gradient(circle at center, black 40%, transparent 100%)'
58
+ }}
59
+ />
60
+
61
+ {/* Center Content */}
62
+ <div className="relative flex items-center justify-center mb-8">
63
+ {/* Glowing Gradient Ring Container */}
64
+ {/* Outer pulsing glow */}
65
+ <div className="absolute inset-[-12px] rounded-full animate-pulse bg-primary/10 blur-xl"></div>
66
+
67
+ {/* Spinning Gradient Ring */}
68
+ <div className="absolute inset-[-4px] rounded-full animate-spin-slow"
69
+ style={{
70
+ background: 'conic-gradient(from 0deg, transparent 0deg, var(--primary) 360deg)',
71
+ mask: 'radial-gradient(farthest-side, transparent calc(100% - 2px), black calc(100% - 2px))',
72
+ WebkitMask: 'radial-gradient(farthest-side, transparent calc(100% - 2px), black calc(100% - 2px))'
73
+ }}
74
+ ></div>
75
+
76
+ {/* Secondary thinner ring for detail */}
77
+ <div className="absolute inset-[-8px] rounded-full animate-spin-slow"
78
+ style={{
79
+ animationDirection: 'reverse',
80
+ opacity: 0.3,
81
+ background: 'conic-gradient(from 0deg, transparent 0deg, var(--primary) 360deg)',
82
+ mask: 'radial-gradient(farthest-side, transparent calc(100% - 1px), black calc(100% - 1px))',
83
+ WebkitMask: 'radial-gradient(farthest-side, transparent calc(100% - 1px), black calc(100% - 1px))'
84
+ }}
85
+ ></div>
86
+
87
+ {/* Logo Container */}
88
+ <div className="relative z-10 bg-background rounded-full p-8 shadow-2xl border border-primary/10">
89
+ <div className="relative w-20 h-20">
90
+ <Image
91
+ src="/NitroStudio Isotype Color.png"
92
+ alt="NitroStudio Logo"
93
+ fill
94
+ className="object-contain"
95
+ priority
96
+ />
97
+ </div>
98
+ </div>
99
+ </div>
100
+
101
+ {/* Loading Text with Glitch-like Vibe */}
102
+ <div className="h-6 overflow-hidden flex flex-col items-center justify-center">
103
+ <p className="text-xs font-mono text-primary/80 animate-pulse tracking-[0.2em] uppercase">
104
+ {LOADING_TEXTS[textIndex]}
105
+ </p>
106
+ </div>
107
+ </div>
108
+ );
109
+ }