nitrostack 1.0.71 → 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.
- package/package.json +1 -1
- package/src/studio/app/api/chat/route.ts +3 -1
- package/src/studio/app/auth/callback/page.tsx +6 -6
- package/src/studio/app/chat/page.tsx +1099 -408
- package/src/studio/app/chat/page.tsx.backup +1046 -187
- package/src/studio/app/globals.css +361 -191
- package/src/studio/app/health/page.tsx +72 -76
- package/src/studio/app/layout.tsx +9 -11
- package/src/studio/app/logs/page.tsx +29 -30
- package/src/studio/app/page.tsx +134 -230
- package/src/studio/app/prompts/page.tsx +115 -97
- package/src/studio/app/resources/page.tsx +115 -124
- package/src/studio/app/settings/page.tsx +1080 -125
- package/src/studio/app/tools/page.tsx +343 -0
- package/src/studio/components/EnlargeModal.tsx +76 -65
- package/src/studio/components/LogMessage.tsx +5 -5
- package/src/studio/components/MarkdownRenderer.tsx +4 -4
- package/src/studio/components/Sidebar.tsx +150 -210
- package/src/studio/components/SplashScreen.tsx +109 -0
- package/src/studio/components/ToolCard.tsx +50 -41
- package/src/studio/components/VoiceOrbOverlay.tsx +469 -0
- package/src/studio/components/WidgetRenderer.tsx +8 -3
- package/src/studio/components/tools/ToolsCanvas.tsx +327 -0
- package/src/studio/lib/store.ts +15 -0
- package/src/studio/package-lock.json +3303 -0
- package/src/studio/package.json +3 -1
- package/src/studio/public/NitroStudio Isotype Color.png +0 -0
- package/src/studio/tailwind.config.ts +63 -17
- package/src/studio/app/auth/page.tsx +0 -560
- 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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
23
|
-
{
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
{/*
|
|
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="
|
|
84
|
-
|
|
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
|
-
|
|
98
|
-
|
|
180
|
+
</div>
|
|
99
181
|
{!isCollapsed && (
|
|
100
|
-
<div className="flex-1 overflow-hidden">
|
|
101
|
-
<h1 className="text-
|
|
102
|
-
|
|
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
|
-
{
|
|
112
|
-
|
|
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
|
-
{/*
|
|
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=
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
{
|
|
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
|
-
{/*
|
|
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
|
-
{/*
|
|
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="
|
|
264
|
-
|
|
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
|
+
}
|