nyte 1.2.0 → 1.2.2

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.
@@ -9,68 +9,63 @@ const jsx_runtime_1 = require("react/jsx-runtime");
9
9
  const react_1 = require("react");
10
10
  const lucide_react_1 = require("lucide-react");
11
11
  function ErrorPage() {
12
- // State for interactive hover effects
12
+ const [path, setPath] = (0, react_1.useState)('/');
13
+ // Estados apenas para mudar a COR do botão, sem mover nada de lugar
13
14
  const [hoverHome, setHoverHome] = (0, react_1.useState)(false);
14
15
  const [hoverRetry, setHoverRetry] = (0, react_1.useState)(false);
15
- const [mounted, setMounted] = (0, react_1.useState)(false);
16
- const [path, setPath] = (0, react_1.useState)('/');
17
16
  (0, react_1.useEffect)(() => {
18
- setMounted(true);
19
17
  if (typeof window !== 'undefined') {
20
18
  setPath(window.location.pathname);
21
19
  }
22
20
  }, []);
23
- // --- GLOBAL STYLES (Reset & Fonts & Solid Background) ---
21
+ // --- GLOBAL STYLES ---
24
22
  const globalStyles = `
25
23
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700;900&family=JetBrains+Mono:wght@400;500&display=swap');
26
24
 
27
25
  body {
28
26
  margin: 0;
29
27
  padding: 0;
30
- /* SOLID DARK BACKGROUND HERE */
31
28
  background-color: #0d0d0d;
32
29
  color: #e2e8f0;
33
30
  font-family: 'Inter', system-ui, sans-serif;
34
- overflow: hidden;
31
+ overflow: hidden; /* Trava scroll */
35
32
  height: 100vh;
36
33
  width: 100vw;
37
- position: relative;
38
34
  }
39
35
 
40
36
  * { box-sizing: border-box; }
41
37
  `;
42
38
  // --- INLINE STYLES ---
43
- // Container updated for absolute perfect centering
44
39
  const containerStyle = {
45
- position: 'absolute',
46
- top: '50%',
47
- left: '50%',
48
- transform: 'translate(-50%, -50%)',
40
+ // MUDANÇA CRÍTICA:
41
+ // Position fixed cobrindo a tela toda (inset: 0)
42
+ // Usamos Flexbox para centralizar. Isso não tem animação, é layout puro.
43
+ position: 'fixed',
44
+ top: 0,
45
+ left: 0,
46
+ right: 0,
47
+ bottom: 0,
48
+ zIndex: 9999,
49
49
  display: 'flex',
50
50
  flexDirection: 'column',
51
- alignItems: 'center',
52
- justifyContent: 'center',
53
- zIndex: 10,
54
- width: '100%',
55
- pointerEvents: 'none', // Allows clicks to pass through container areas not covered by children
51
+ alignItems: 'center', // Centraliza Horizontalmente
52
+ justifyContent: 'center', // Centraliza Verticalmente
53
+ width: '100vw',
54
+ height: '100vh',
55
+ background: '#0d0d0d', // Fundo sólido de segurança
56
56
  };
57
57
  const cardStyle = {
58
58
  width: 'min(90%, 500px)',
59
59
  display: 'flex',
60
60
  flexDirection: 'column',
61
- // Super Glassmorphism Dark (Matching Modal)
62
61
  background: 'rgba(10, 10, 12, 0.95)',
63
- // Cyan Glow Border + Deep Shadow
64
62
  boxShadow: '0 0 0 1px rgba(34, 211, 238, 0.15), 0 40px 80px -20px rgba(0, 0, 0, 0.8)',
65
63
  borderRadius: 20,
66
64
  overflow: 'hidden',
67
65
  position: 'relative',
68
- transform: mounted ? 'scale(1) translateY(0)' : 'scale(0.98) translateY(10px)',
69
- opacity: mounted ? 1 : 0,
70
- transition: 'transform 0.5s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.5s ease',
71
- pointerEvents: 'auto', // Re-enable clicks on the card itself
66
+ // REMOVIDO: transform, transition, opacity
67
+ // O card nasce desenhado na posição final.
72
68
  };
73
- // The Signature Neon Line
74
69
  const neonLine = {
75
70
  height: '1px',
76
71
  width: '100%',
@@ -90,7 +85,6 @@ function ErrorPage() {
90
85
  lineHeight: 1,
91
86
  letterSpacing: '-0.04em',
92
87
  color: '#fff',
93
- // Subtle Gradient Text
94
88
  background: 'linear-gradient(180deg, #ffffff 0%, #94a3b8 100%)',
95
89
  WebkitBackgroundClip: 'text',
96
90
  WebkitTextFillColor: 'transparent',
@@ -109,7 +103,6 @@ function ErrorPage() {
109
103
  textAlign: 'left',
110
104
  color: '#94a3b8',
111
105
  };
112
- // Button Generator (Reusable)
113
106
  const getBtnStyle = (kind, hovering) => {
114
107
  const base = {
115
108
  display: 'flex',
@@ -120,7 +113,8 @@ function ErrorPage() {
120
113
  fontSize: 13,
121
114
  fontWeight: 600,
122
115
  cursor: 'pointer',
123
- transition: 'all 0.2s ease',
116
+ // Mantive a transição de COR (0.2s), mas não move pixel nenhum
117
+ transition: 'background 0.2s ease, color 0.2s ease, box-shadow 0.2s ease',
124
118
  border: 'none',
125
119
  outline: 'none',
126
120
  textDecoration: 'none',
@@ -148,12 +142,11 @@ function ErrorPage() {
148
142
  alignItems: 'center',
149
143
  gap: 10,
150
144
  opacity: 0.4,
151
- transition: 'opacity 0.3s',
145
+ transition: 'opacity 0.3s', // Apenas opacidade muda, sem movimento
152
146
  textDecoration: 'none',
153
147
  color: '#fff',
154
- pointerEvents: 'auto', // Re-enable clicks on brand link
155
148
  };
156
- return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("style", { dangerouslySetInnerHTML: { __html: globalStyles } }), (0, jsx_runtime_1.jsxs)("div", { style: containerStyle, children: [(0, jsx_runtime_1.jsxs)("div", { style: cardStyle, children: [(0, jsx_runtime_1.jsx)("div", { style: neonLine }), (0, jsx_runtime_1.jsxs)("div", { style: contentStyle, children: [(0, jsx_runtime_1.jsx)("div", { style: codeStyle, children: "404" }), (0, jsx_runtime_1.jsxs)("div", { style: terminalBoxStyle, children: [(0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', gap: 6, marginBottom: 8, opacity: 0.5 }, children: [(0, jsx_runtime_1.jsx)("div", { style: { width: 8, height: 8, borderRadius: '50%', background: '#f87171' } }), (0, jsx_runtime_1.jsx)("div", { style: { width: 8, height: 8, borderRadius: '50%', background: '#fbbf24' } }), (0, jsx_runtime_1.jsx)("div", { style: { width: 8, height: 8, borderRadius: '50%', background: '#4ade80' } })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { style: { color: '#c084fc' }, children: "GET" }), ' ', (0, jsx_runtime_1.jsx)("span", { style: { color: '#22d3ee' }, children: path })] }), (0, jsx_runtime_1.jsx)("div", { style: { marginTop: 4, color: '#f87171' }, children: (0, jsx_runtime_1.jsx)("span", { children: "Error: Route not found" }) })] }), (0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', gap: 12, width: '100%' }, children: [(0, jsx_runtime_1.jsxs)("a", { href: "/", style: { ...getBtnStyle('primary', false), flex: 1, justifyContent: 'center' }, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Home, { size: 16 }), "Back Home"] }), (0, jsx_runtime_1.jsxs)("button", { onClick: () => window.location.reload(), style: { ...getBtnStyle('secondary', false), flex: 1, justifyContent: 'center' }, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.RefreshCw, { size: 16 }), "Retry"] })] })] }), (0, jsx_runtime_1.jsxs)("div", { style: {
149
+ return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("style", { dangerouslySetInnerHTML: { __html: globalStyles } }), (0, jsx_runtime_1.jsxs)("div", { style: containerStyle, children: [(0, jsx_runtime_1.jsxs)("div", { style: cardStyle, children: [(0, jsx_runtime_1.jsx)("div", { style: neonLine }), (0, jsx_runtime_1.jsxs)("div", { style: contentStyle, children: [(0, jsx_runtime_1.jsx)("div", { style: codeStyle, children: "404" }), (0, jsx_runtime_1.jsxs)("div", { style: terminalBoxStyle, children: [(0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', gap: 6, marginBottom: 8, opacity: 0.5 }, children: [(0, jsx_runtime_1.jsx)("div", { style: { width: 8, height: 8, borderRadius: '50%', background: '#f87171' } }), (0, jsx_runtime_1.jsx)("div", { style: { width: 8, height: 8, borderRadius: '50%', background: '#fbbf24' } }), (0, jsx_runtime_1.jsx)("div", { style: { width: 8, height: 8, borderRadius: '50%', background: '#4ade80' } })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { style: { color: '#c084fc' }, children: "GET" }), ' ', (0, jsx_runtime_1.jsx)("span", { style: { color: '#22d3ee' }, children: path })] }), (0, jsx_runtime_1.jsx)("div", { style: { marginTop: 4, color: '#f87171' }, children: (0, jsx_runtime_1.jsx)("span", { children: "Error: Route not found" }) })] }), (0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', gap: 12, width: '100%' }, children: [(0, jsx_runtime_1.jsxs)("a", { href: "/", onMouseEnter: () => setHoverHome(true), onMouseLeave: () => setHoverHome(false), style: { ...getBtnStyle('primary', hoverHome), flex: 1, justifyContent: 'center' }, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Home, { size: 16 }), "Back Home"] }), (0, jsx_runtime_1.jsxs)("button", { onClick: () => window.location.reload(), onMouseEnter: () => setHoverRetry(true), onMouseLeave: () => setHoverRetry(false), style: { ...getBtnStyle('secondary', hoverRetry), flex: 1, justifyContent: 'center' }, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.RefreshCw, { size: 16 }), "Retry"] })] })] }), (0, jsx_runtime_1.jsxs)("div", { style: {
157
150
  padding: '12px 32px',
158
151
  background: 'rgba(0,0,0,0.3)',
159
152
  borderTop: '1px solid rgba(255,255,255,0.03)',
@@ -1,3 +1,4 @@
1
+ import React from 'react';
1
2
  export interface NyteBuildError {
2
3
  message?: string;
3
4
  name?: string;
@@ -16,4 +17,4 @@ export declare function ErrorModal({ error, isOpen, onClose, onCopy, }: {
16
17
  isOpen: boolean;
17
18
  onClose: () => void;
18
19
  onCopy?: () => void;
19
- }): import("react/jsx-runtime").JSX.Element | null;
20
+ }): React.ReactPortal | null;
@@ -19,17 +19,18 @@ const jsx_runtime_1 = require("react/jsx-runtime");
19
19
  * limitations under the License.
20
20
  */
21
21
  const react_1 = require("react");
22
+ const react_dom_1 = require("react-dom"); // <--- IMPORTANTE: Importar o Portal
22
23
  // --- ANSI PARSER LOGIC ---
23
24
  const ANSI_COLORS = {
24
- '30': '#94a3b8', // Black (Gray)
25
- '31': '#f87171', // Red (Error)
26
- '32': '#4ade80', // Green
27
- '33': '#facc15', // Yellow (Warning)
28
- '34': '#60a5fa', // Blue
29
- '35': '#c084fc', // Magenta
30
- '36': '#22d3ee', // Cyan (Neon)
31
- '37': '#e2e8f0', // White
32
- '90': '#64748b', // Bright Black
25
+ '30': '#94a3b8',
26
+ '31': '#f87171',
27
+ '32': '#4ade80',
28
+ '33': '#facc15',
29
+ '34': '#60a5fa',
30
+ '35': '#c084fc',
31
+ '36': '#22d3ee',
32
+ '37': '#e2e8f0',
33
+ '90': '#64748b',
33
34
  };
34
35
  function AnsiText({ text }) {
35
36
  const parts = (0, react_1.useMemo)(() => {
@@ -45,7 +46,7 @@ function AnsiText({ text }) {
45
46
  }
46
47
  const code = match[1];
47
48
  if (code === '39' || code === '0') {
48
- currentColor = null; // Reset
49
+ currentColor = null;
49
50
  }
50
51
  else if (ANSI_COLORS[code]) {
51
52
  currentColor = ANSI_COLORS[code];
@@ -65,13 +66,24 @@ function ErrorModal({ error, isOpen, onClose, onCopy, }) {
65
66
  const [visible, setVisible] = (0, react_1.useState)(false);
66
67
  const [isHoveringClose, setIsHoveringClose] = (0, react_1.useState)(false);
67
68
  const [isHoveringCopy, setIsHoveringCopy] = (0, react_1.useState)(false);
69
+ // Estado para garantir que rodamos apenas no client-side (evita erro de SSR com document.body)
70
+ const [mounted, setMounted] = (0, react_1.useState)(false);
71
+ (0, react_1.useEffect)(() => {
72
+ setMounted(true);
73
+ return () => setMounted(false);
74
+ }, []);
68
75
  (0, react_1.useEffect)(() => {
69
76
  if (isOpen) {
77
+ // Trava o scroll do body quando o modal abre
78
+ document.body.style.overflow = 'hidden';
70
79
  setTimeout(() => setVisible(true), 10);
71
80
  }
72
81
  else {
82
+ document.body.style.overflow = '';
73
83
  setVisible(false);
74
84
  }
85
+ // Limpeza de segurança
86
+ return () => { document.body.style.overflow = ''; };
75
87
  }, [isOpen]);
76
88
  (0, react_1.useEffect)(() => {
77
89
  if (!isOpen)
@@ -83,26 +95,29 @@ function ErrorModal({ error, isOpen, onClose, onCopy, }) {
83
95
  window.addEventListener('keydown', onKey);
84
96
  return () => window.removeEventListener('keydown', onKey);
85
97
  }, [isOpen, onClose]);
86
- if (!isOpen || !error)
98
+ if (!mounted || !isOpen || !error)
87
99
  return null;
88
- // Combine raw error text to emulate a real terminal output
89
- // Often build tools put the frame inside the message, so we prioritize message.
90
100
  const rawOutput = error.message || '';
91
101
  const stackOutput = error.stack ? `\n\nStack Trace:\n${error.stack}` : '';
92
102
  // --- STYLES ---
93
103
  const overlayStyle = {
94
104
  position: 'fixed',
95
- inset: 0,
96
- zIndex: 99999,
97
- background: visible ? 'rgba(0, 0, 0, 0.75)' : 'rgba(0,0,0,0)',
98
- backdropFilter: 'blur(8px)',
99
- WebkitBackdropFilter: 'blur(8px)',
105
+ top: 0,
106
+ left: 0,
107
+ width: '100vw',
108
+ height: '100vh',
109
+ zIndex: 2147483647, // Max Z-Index seguro
110
+ // Fundo praticamente sólido (98% opacidade) para esconder o site atrás
111
+ background: visible ? 'rgba(5, 5, 5, 0.98)' : 'rgba(5, 5, 5, 0)',
112
+ backdropFilter: 'blur(10px)', // Blur extra caso algo vaze
113
+ WebkitBackdropFilter: 'blur(10px)',
100
114
  display: 'flex',
101
115
  alignItems: 'center',
102
116
  justifyContent: 'center',
103
117
  padding: 24,
104
118
  transition: 'background 0.3s ease, opacity 0.3s ease',
105
119
  opacity: visible ? 1 : 0,
120
+ boxSizing: 'border-box',
106
121
  };
107
122
  const cardStyle = {
108
123
  width: '100%',
@@ -110,10 +125,8 @@ function ErrorModal({ error, isOpen, onClose, onCopy, }) {
110
125
  maxHeight: '90vh',
111
126
  display: 'flex',
112
127
  flexDirection: 'column',
113
- // Super Glassmorphism Dark
114
- background: 'rgba(10, 10, 12, 0.95)',
115
- // Cyan Glow Border
116
- boxShadow: '0 0 0 1px rgba(34, 211, 238, 0.15), 0 25px 50px -12px rgba(0, 0, 0, 0.9)',
128
+ background: 'rgba(10, 10, 12, 1)', // Card totalmente sólido
129
+ boxShadow: '0 0 0 1px rgba(34, 211, 238, 0.15), 0 50px 100px -20px rgba(0, 0, 0, 1)',
117
130
  borderRadius: 16,
118
131
  overflow: 'hidden',
119
132
  transform: visible ? 'scale(1) translateY(0)' : 'scale(0.98) translateY(10px)',
@@ -175,10 +188,10 @@ function ErrorModal({ error, isOpen, onClose, onCopy, }) {
175
188
  color: hovering ? '#fff' : 'rgba(255,255,255,0.5)',
176
189
  };
177
190
  };
178
- return ((0, jsx_runtime_1.jsx)("div", { style: overlayStyle, onMouseDown: onClose, children: (0, jsx_runtime_1.jsxs)("div", { style: cardStyle, onMouseDown: (e) => e.stopPropagation(), children: [(0, jsx_runtime_1.jsx)("div", { style: neonLine }), (0, jsx_runtime_1.jsxs)("div", { style: headerStyle, children: [(0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', alignItems: 'center', gap: 12 }, children: [(0, jsx_runtime_1.jsx)("span", { style: {
191
+ const modalContent = ((0, jsx_runtime_1.jsx)("div", { style: overlayStyle, onMouseDown: onClose, children: (0, jsx_runtime_1.jsxs)("div", { style: cardStyle, onMouseDown: (e) => e.stopPropagation(), children: [(0, jsx_runtime_1.jsx)("div", { style: neonLine }), (0, jsx_runtime_1.jsxs)("div", { style: headerStyle, children: [(0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', alignItems: 'center', gap: 12 }, children: [(0, jsx_runtime_1.jsx)("span", { style: {
179
192
  fontSize: 12,
180
193
  fontWeight: 800,
181
- color: '#f87171', // Red for "ERROR"
194
+ color: '#f87171',
182
195
  letterSpacing: '0.1em'
183
196
  }, children: "ERROR" }), error.plugin && ((0, jsx_runtime_1.jsx)("span", { style: {
184
197
  fontSize: 11,
@@ -196,4 +209,6 @@ function ErrorModal({ error, isOpen, onClose, onCopy, }) {
196
209
  fontSize: 11,
197
210
  color: 'rgba(255,255,255,0.2)'
198
211
  }, children: [(0, jsx_runtime_1.jsx)("span", { children: "nyte-cli v2.0.0" }), (0, jsx_runtime_1.jsx)("span", { style: { color: '#22d3ee' }, children: "Watching for changes..." })] })] }) }));
212
+ // O PULO DO GATO: Renderiza direto no body para ignorar contexto de scroll dos pais
213
+ return (0, react_dom_1.createPortal)(modalContent, document.body);
199
214
  }
@@ -269,91 +269,95 @@ function DevIndicator({ hasBuildError = false, onClickBuildError, }) {
269
269
  const isReloading = hotState === 'reloading';
270
270
  const isError = !!hasBuildError;
271
271
  return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("style", { children: `
272
- @keyframes nyte-pulse {
273
- 0% { opacity: 0.4; }
274
- 50% { opacity: 1; }
275
- 100% { opacity: 0.4; }
276
- }
277
- @keyframes nyte-spin {
278
- 0% { transform: rotate(0deg); }
279
- 100% { transform: rotate(360deg); }
280
- }
281
- .nyte-dev-badge {
282
- position: fixed;
283
- bottom: 20px;
284
- right: 20px;
285
- z-index: 999999;
286
- display: flex;
287
- align-items: center;
288
- gap: 12px;
289
- padding: 8px 14px;
290
- background: rgba(15, 15, 20, 0.8);
291
- backdrop-filter: blur(12px);
292
- -webkit-backdrop-filter: blur(12px);
293
-
294
- border-radius: 10px;
295
- color: #fff;
296
- font-family: 'Inter', ui-monospace, SFMono-Regular, Menlo, monospace;
297
- font-size: 12px;
298
- font-weight: 600;
299
- letter-spacing: 0.05em;
272
+ @keyframes nyte-pulse {
273
+ 0% { opacity: 0.4; }
274
+ 50% { opacity: 1; }
275
+ 100% { opacity: 0.4; }
276
+ }
277
+ @keyframes nyte-spin {
278
+ 0% { transform: rotate(0deg); }
279
+ 100% { transform: rotate(360deg); }
280
+ }
281
+ .nyte-dev-badge {
282
+ /* AQUI ESTÁ O QUE SEGURA ELE NA TELA: */
283
+ position: sticky;
284
+ bottom: 20px;
285
+ /* float e margens para forçar a direita no modo sticky */
286
+ float: right;
287
+ margin-right: 20px;
288
+ z-index: 999999;
289
+
290
+ display: flex;
291
+ align-items: center;
292
+ gap: 12px;
293
+ padding: 8px 14px;
294
+ background: rgba(15, 15, 20, 0.8);
295
+ backdrop-filter: blur(12px);
296
+ -webkit-backdrop-filter: blur(12px);
300
297
 
301
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
302
- transition: all 0.2s ease;
303
- cursor: default;
304
- user-select: none;
305
- }
306
- .nyte-dev-badge.clickable {
307
- cursor: pointer;
308
- }
309
- .nyte-dev-badge:hover {
310
- border-color: rgba(142, 45, 226, 0.5);
311
- transform: translateY(-2px);
312
- }
313
- .nyte-status-dot {
314
- width: 8px;
315
- height: 8px;
316
- background: #10b981; /* Verde esmeralda */
317
- border-radius: 50%;
318
- box-shadow: 0 0 10px #10b981;
319
- animation: nyte-pulse 2s infinite ease-in-out;
320
- }
321
- .nyte-status-dot.reloading {
322
- background: #f59e0b; /* Amber */
323
- box-shadow: 0 0 10px #f59e0b;
324
- }
325
- .nyte-status-dot.error {
326
- background: #ef4444; /* Red */
327
- box-shadow: 0 0 12px #ef4444;
328
- animation: nyte-pulse 1.2s infinite ease-in-out;
329
- }
330
- .nyte-spinner {
331
- width: 10px;
332
- height: 10px;
333
- border-radius: 50%;
334
- border: 2px solid rgba(255,255,255,0.25);
335
- border-top-color: rgba(255,255,255,0.85);
336
- animation: nyte-spin 0.8s linear infinite;
337
- }
338
- .nyte-logo {
339
- background: linear-gradient(135deg, #00a3a3, #808080);
340
- -webkit-background-clip: text;
341
- -webkit-text-fill-color: transparent;
342
- font-weight: 800;
343
- }
344
- .nyte-error-pill {
345
- margin-left: 6px;
346
- padding: 2px 6px;
347
- border-radius: 999px;
348
- background: rgba(239, 68, 68, 0.18);
349
- border: 1px solid rgba(239, 68, 68, 0.35);
350
- color: rgba(255,255,255,0.9);
351
- font-size: 10px;
352
- font-weight: 800;
353
- letter-spacing: 0.08em;
354
- text-transform: uppercase;
355
- }
356
- ` }), (0, jsx_runtime_1.jsxs)("div", { className: `nyte-dev-badge${isError ? ' clickable' : ''}`, title: isError ? 'Build com erro — clique para ver detalhes' : undefined, onClick: () => {
298
+ border-radius: 10px;
299
+ color: #fff;
300
+ font-family: 'Inter', ui-monospace, SFMono-Regular, Menlo, monospace;
301
+ font-size: 12px;
302
+ font-weight: 600;
303
+ letter-spacing: 0.05em;
304
+
305
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
306
+ transition: all 0.2s ease;
307
+ cursor: default;
308
+ user-select: none;
309
+ }
310
+ .nyte-dev-badge.clickable {
311
+ cursor: pointer;
312
+ }
313
+ .nyte-dev-badge:hover {
314
+ border-color: rgba(142, 45, 226, 0.5);
315
+ transform: translateY(-2px);
316
+ }
317
+ .nyte-status-dot {
318
+ width: 8px;
319
+ height: 8px;
320
+ background: #10b981; /* Verde esmeralda */
321
+ border-radius: 50%;
322
+ box-shadow: 0 0 10px #10b981;
323
+ animation: nyte-pulse 2s infinite ease-in-out;
324
+ }
325
+ .nyte-status-dot.reloading {
326
+ background: #f59e0b; /* Amber */
327
+ box-shadow: 0 0 10px #f59e0b;
328
+ }
329
+ .nyte-status-dot.error {
330
+ background: #ef4444; /* Red */
331
+ box-shadow: 0 0 12px #ef4444;
332
+ animation: nyte-pulse 1.2s infinite ease-in-out;
333
+ }
334
+ .nyte-spinner {
335
+ width: 10px;
336
+ height: 10px;
337
+ border-radius: 50%;
338
+ border: 2px solid rgba(255,255,255,0.25);
339
+ border-top-color: rgba(255,255,255,0.85);
340
+ animation: nyte-spin 0.8s linear infinite;
341
+ }
342
+ .nyte-logo {
343
+ background: linear-gradient(135deg, #00a3a3, #808080);
344
+ -webkit-background-clip: text;
345
+ -webkit-text-fill-color: transparent;
346
+ font-weight: 800;
347
+ }
348
+ .nyte-error-pill {
349
+ margin-left: 6px;
350
+ padding: 2px 6px;
351
+ border-radius: 999px;
352
+ background: rgba(239, 68, 68, 0.18);
353
+ border: 1px solid rgba(239, 68, 68, 0.35);
354
+ color: rgba(255,255,255,0.9);
355
+ font-size: 10px;
356
+ font-weight: 800;
357
+ letter-spacing: 0.08em;
358
+ text-transform: uppercase;
359
+ }
360
+ ` }), (0, jsx_runtime_1.jsxs)("div", { className: `nyte-dev-badge${isError ? ' clickable' : ''}`, title: isError ? 'Build com erro — clique para ver detalhes' : undefined, onClick: () => {
357
361
  if (isError) {
358
362
  onClickBuildError?.();
359
363
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nyte",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Nyte.js is a high-level framework for building web applications with ease and speed. It provides a robust set of tools and features to streamline development and enhance productivity.",
5
5
  "types": "dist/index.d.ts",
6
6
  "author": "itsmuzin",
@@ -6,34 +6,31 @@ import React, { useState, useEffect } from 'react';
6
6
  import { Home, RefreshCw, AlertTriangle } from 'lucide-react';
7
7
 
8
8
  export default function ErrorPage() {
9
- // State for interactive hover effects
9
+ const [path, setPath] = useState('/');
10
+
11
+ // Estados apenas para mudar a COR do botão, sem mover nada de lugar
10
12
  const [hoverHome, setHoverHome] = useState(false);
11
13
  const [hoverRetry, setHoverRetry] = useState(false);
12
- const [mounted, setMounted] = useState(false);
13
- const [path, setPath] = useState('/');
14
14
 
15
15
  useEffect(() => {
16
- setMounted(true);
17
16
  if (typeof window !== 'undefined') {
18
17
  setPath(window.location.pathname);
19
18
  }
20
19
  }, []);
21
20
 
22
- // --- GLOBAL STYLES (Reset & Fonts & Solid Background) ---
21
+ // --- GLOBAL STYLES ---
23
22
  const globalStyles = `
24
23
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700;900&family=JetBrains+Mono:wght@400;500&display=swap');
25
24
 
26
25
  body {
27
26
  margin: 0;
28
27
  padding: 0;
29
- /* SOLID DARK BACKGROUND HERE */
30
28
  background-color: #0d0d0d;
31
29
  color: #e2e8f0;
32
30
  font-family: 'Inter', system-ui, sans-serif;
33
- overflow: hidden;
31
+ overflow: hidden; /* Trava scroll */
34
32
  height: 100vh;
35
33
  width: 100vw;
36
- position: relative;
37
34
  }
38
35
 
39
36
  * { box-sizing: border-box; }
@@ -41,39 +38,41 @@ export default function ErrorPage() {
41
38
 
42
39
  // --- INLINE STYLES ---
43
40
 
44
- // Container updated for absolute perfect centering
45
41
  const containerStyle: React.CSSProperties = {
46
- position: 'absolute',
47
- top: '50%',
48
- left: '50%',
49
- transform: 'translate(-50%, -50%)',
42
+ // MUDANÇA CRÍTICA:
43
+ // Position fixed cobrindo a tela toda (inset: 0)
44
+ // Usamos Flexbox para centralizar. Isso não tem animação, é layout puro.
45
+ position: 'fixed',
46
+ top: 0,
47
+ left: 0,
48
+ right: 0,
49
+ bottom: 0,
50
+ zIndex: 9999,
51
+
50
52
  display: 'flex',
51
53
  flexDirection: 'column',
52
- alignItems: 'center',
53
- justifyContent: 'center',
54
- zIndex: 10,
55
- width: '100%',
56
- pointerEvents: 'none', // Allows clicks to pass through container areas not covered by children
54
+ alignItems: 'center', // Centraliza Horizontalmente
55
+ justifyContent: 'center', // Centraliza Verticalmente
56
+
57
+ width: '100vw',
58
+ height: '100vh',
59
+ background: '#0d0d0d', // Fundo sólido de segurança
57
60
  };
58
61
 
59
62
  const cardStyle: React.CSSProperties = {
60
63
  width: 'min(90%, 500px)',
61
64
  display: 'flex',
62
65
  flexDirection: 'column',
63
- // Super Glassmorphism Dark (Matching Modal)
64
66
  background: 'rgba(10, 10, 12, 0.95)',
65
- // Cyan Glow Border + Deep Shadow
66
67
  boxShadow: '0 0 0 1px rgba(34, 211, 238, 0.15), 0 40px 80px -20px rgba(0, 0, 0, 0.8)',
67
68
  borderRadius: 20,
68
69
  overflow: 'hidden',
69
70
  position: 'relative',
70
- transform: mounted ? 'scale(1) translateY(0)' : 'scale(0.98) translateY(10px)',
71
- opacity: mounted ? 1 : 0,
72
- transition: 'transform 0.5s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.5s ease',
73
- pointerEvents: 'auto', // Re-enable clicks on the card itself
71
+
72
+ // REMOVIDO: transform, transition, opacity
73
+ // O card nasce desenhado na posição final.
74
74
  };
75
75
 
76
- // The Signature Neon Line
77
76
  const neonLine: React.CSSProperties = {
78
77
  height: '1px',
79
78
  width: '100%',
@@ -95,7 +94,6 @@ export default function ErrorPage() {
95
94
  lineHeight: 1,
96
95
  letterSpacing: '-0.04em',
97
96
  color: '#fff',
98
- // Subtle Gradient Text
99
97
  background: 'linear-gradient(180deg, #ffffff 0%, #94a3b8 100%)',
100
98
  WebkitBackgroundClip: 'text',
101
99
  WebkitTextFillColor: 'transparent',
@@ -116,7 +114,6 @@ export default function ErrorPage() {
116
114
  color: '#94a3b8',
117
115
  };
118
116
 
119
- // Button Generator (Reusable)
120
117
  const getBtnStyle = (kind: 'primary' | 'secondary', hovering: boolean): React.CSSProperties => {
121
118
  const base: React.CSSProperties = {
122
119
  display: 'flex',
@@ -127,7 +124,8 @@ export default function ErrorPage() {
127
124
  fontSize: 13,
128
125
  fontWeight: 600,
129
126
  cursor: 'pointer',
130
- transition: 'all 0.2s ease',
127
+ // Mantive a transição de COR (0.2s), mas não move pixel nenhum
128
+ transition: 'background 0.2s ease, color 0.2s ease, box-shadow 0.2s ease',
131
129
  border: 'none',
132
130
  outline: 'none',
133
131
  textDecoration: 'none',
@@ -157,28 +155,22 @@ export default function ErrorPage() {
157
155
  alignItems: 'center',
158
156
  gap: 10,
159
157
  opacity: 0.4,
160
- transition: 'opacity 0.3s',
158
+ transition: 'opacity 0.3s', // Apenas opacidade muda, sem movimento
161
159
  textDecoration: 'none',
162
160
  color: '#fff',
163
- pointerEvents: 'auto', // Re-enable clicks on brand link
164
161
  };
165
162
 
166
163
  return (
167
164
  <div >
168
165
  <style dangerouslySetInnerHTML={{ __html: globalStyles }} />
169
- {/* Removed bgGlowStyle div for solid background */}
170
166
 
171
167
  <div style={containerStyle}>
172
168
  <div style={cardStyle}>
173
- {/* Top Neon Line */}
174
169
  <div style={neonLine} />
175
170
 
176
171
  <div style={contentStyle}>
177
- {/* 404 Title */}
178
172
  <div style={codeStyle}>404</div>
179
173
 
180
-
181
- {/* Tech/Terminal Detail */}
182
174
  <div style={terminalBoxStyle}>
183
175
  <div style={{ display: 'flex', gap: 6, marginBottom: 8, opacity: 0.5 }}>
184
176
  <div style={{width: 8, height: 8, borderRadius: '50%', background: '#f87171'}}/>
@@ -194,18 +186,21 @@ export default function ErrorPage() {
194
186
  </div>
195
187
  </div>
196
188
 
197
- {/* Actions */}
198
189
  <div style={{ display: 'flex', gap: 12, width: '100%' }}>
199
190
  <a
200
191
  href="/"
201
- style={{ ...getBtnStyle('primary', false), flex: 1, justifyContent: 'center' }}
192
+ onMouseEnter={() => setHoverHome(true)}
193
+ onMouseLeave={() => setHoverHome(false)}
194
+ style={{ ...getBtnStyle('primary', hoverHome), flex: 1, justifyContent: 'center' }}
202
195
  >
203
196
  <Home size={16} />
204
197
  Back Home
205
198
  </a>
206
199
  <button
207
200
  onClick={() => window.location.reload()}
208
- style={{ ...getBtnStyle('secondary', false), flex: 1, justifyContent: 'center' }}
201
+ onMouseEnter={() => setHoverRetry(true)}
202
+ onMouseLeave={() => setHoverRetry(false)}
203
+ style={{ ...getBtnStyle('secondary', hoverRetry), flex: 1, justifyContent: 'center' }}
209
204
  >
210
205
  <RefreshCw size={16} />
211
206
  Retry
@@ -213,7 +208,6 @@ export default function ErrorPage() {
213
208
  </div>
214
209
  </div>
215
210
 
216
- {/* Footer Status Bar (Matching Modal Footer) */}
217
211
  <div style={{
218
212
  padding: '12px 32px',
219
213
  background: 'rgba(0,0,0,0.3)',
@@ -232,7 +226,6 @@ export default function ErrorPage() {
232
226
  </div>
233
227
  </div>
234
228
 
235
- {/* Brand Footer */}
236
229
  <a
237
230
  href="https://npmjs.com/package/nyte"
238
231
  target="_blank"
@@ -15,6 +15,7 @@
15
15
  * limitations under the License.
16
16
  */
17
17
  import React, { useEffect, useState, useMemo } from 'react';
18
+ import { createPortal } from 'react-dom'; // <--- IMPORTANTE: Importar o Portal
18
19
 
19
20
  export interface NyteBuildError {
20
21
  message?: string;
@@ -32,15 +33,15 @@ export interface NyteBuildError {
32
33
 
33
34
  // --- ANSI PARSER LOGIC ---
34
35
  const ANSI_COLORS: Record<string, string> = {
35
- '30': '#94a3b8', // Black (Gray)
36
- '31': '#f87171', // Red (Error)
37
- '32': '#4ade80', // Green
38
- '33': '#facc15', // Yellow (Warning)
39
- '34': '#60a5fa', // Blue
40
- '35': '#c084fc', // Magenta
41
- '36': '#22d3ee', // Cyan (Neon)
42
- '37': '#e2e8f0', // White
43
- '90': '#64748b', // Bright Black
36
+ '30': '#94a3b8',
37
+ '31': '#f87171',
38
+ '32': '#4ade80',
39
+ '33': '#facc15',
40
+ '34': '#60a5fa',
41
+ '35': '#c084fc',
42
+ '36': '#22d3ee',
43
+ '37': '#e2e8f0',
44
+ '90': '#64748b',
44
45
  };
45
46
 
46
47
  function AnsiText({ text }: { text: string }) {
@@ -59,7 +60,7 @@ function AnsiText({ text }: { text: string }) {
59
60
 
60
61
  const code = match[1];
61
62
  if (code === '39' || code === '0') {
62
- currentColor = null; // Reset
63
+ currentColor = null;
63
64
  } else if (ANSI_COLORS[code]) {
64
65
  currentColor = ANSI_COLORS[code];
65
66
  }
@@ -102,13 +103,26 @@ export function ErrorModal({
102
103
  const [visible, setVisible] = useState(false);
103
104
  const [isHoveringClose, setIsHoveringClose] = useState(false);
104
105
  const [isHoveringCopy, setIsHoveringCopy] = useState(false);
106
+ // Estado para garantir que rodamos apenas no client-side (evita erro de SSR com document.body)
107
+ const [mounted, setMounted] = useState(false);
108
+
109
+ useEffect(() => {
110
+ setMounted(true);
111
+ return () => setMounted(false);
112
+ }, []);
105
113
 
106
114
  useEffect(() => {
107
115
  if (isOpen) {
116
+ // Trava o scroll do body quando o modal abre
117
+ document.body.style.overflow = 'hidden';
108
118
  setTimeout(() => setVisible(true), 10);
109
119
  } else {
120
+ document.body.style.overflow = '';
110
121
  setVisible(false);
111
122
  }
123
+
124
+ // Limpeza de segurança
125
+ return () => { document.body.style.overflow = ''; };
112
126
  }, [isOpen]);
113
127
 
114
128
  useEffect(() => {
@@ -120,10 +134,8 @@ export function ErrorModal({
120
134
  return () => window.removeEventListener('keydown', onKey);
121
135
  }, [isOpen, onClose]);
122
136
 
123
- if (!isOpen || !error) return null;
137
+ if (!mounted || !isOpen || !error) return null;
124
138
 
125
- // Combine raw error text to emulate a real terminal output
126
- // Often build tools put the frame inside the message, so we prioritize message.
127
139
  const rawOutput = error.message || '';
128
140
  const stackOutput = error.stack ? `\n\nStack Trace:\n${error.stack}` : '';
129
141
 
@@ -131,17 +143,22 @@ export function ErrorModal({
131
143
 
132
144
  const overlayStyle: React.CSSProperties = {
133
145
  position: 'fixed',
134
- inset: 0,
135
- zIndex: 99999,
136
- background: visible ? 'rgba(0, 0, 0, 0.75)' : 'rgba(0,0,0,0)',
137
- backdropFilter: 'blur(8px)',
138
- WebkitBackdropFilter: 'blur(8px)',
146
+ top: 0,
147
+ left: 0,
148
+ width: '100vw',
149
+ height: '100vh',
150
+ zIndex: 2147483647, // Max Z-Index seguro
151
+ // Fundo praticamente sólido (98% opacidade) para esconder o site atrás
152
+ background: visible ? 'rgba(5, 5, 5, 0.98)' : 'rgba(5, 5, 5, 0)',
153
+ backdropFilter: 'blur(10px)', // Blur extra caso algo vaze
154
+ WebkitBackdropFilter: 'blur(10px)',
139
155
  display: 'flex',
140
156
  alignItems: 'center',
141
157
  justifyContent: 'center',
142
158
  padding: 24,
143
159
  transition: 'background 0.3s ease, opacity 0.3s ease',
144
160
  opacity: visible ? 1 : 0,
161
+ boxSizing: 'border-box',
145
162
  };
146
163
 
147
164
  const cardStyle: React.CSSProperties = {
@@ -150,10 +167,8 @@ export function ErrorModal({
150
167
  maxHeight: '90vh',
151
168
  display: 'flex',
152
169
  flexDirection: 'column',
153
- // Super Glassmorphism Dark
154
- background: 'rgba(10, 10, 12, 0.95)',
155
- // Cyan Glow Border
156
- boxShadow: '0 0 0 1px rgba(34, 211, 238, 0.15), 0 25px 50px -12px rgba(0, 0, 0, 0.9)',
170
+ background: 'rgba(10, 10, 12, 1)', // Card totalmente sólido
171
+ boxShadow: '0 0 0 1px rgba(34, 211, 238, 0.15), 0 50px 100px -20px rgba(0, 0, 0, 1)',
157
172
  borderRadius: 16,
158
173
  overflow: 'hidden',
159
174
  transform: visible ? 'scale(1) translateY(0)' : 'scale(0.98) translateY(10px)',
@@ -221,7 +236,7 @@ export function ErrorModal({
221
236
  };
222
237
  };
223
238
 
224
- return (
239
+ const modalContent = (
225
240
  <div style={overlayStyle} onMouseDown={onClose}>
226
241
  <div style={cardStyle} onMouseDown={(e) => e.stopPropagation()}>
227
242
  <div style={neonLine} />
@@ -232,7 +247,7 @@ export function ErrorModal({
232
247
  <span style={{
233
248
  fontSize: 12,
234
249
  fontWeight: 800,
235
- color: '#f87171', // Red for "ERROR"
250
+ color: '#f87171',
236
251
  letterSpacing: '0.1em'
237
252
  }}>
238
253
  ERROR
@@ -275,10 +290,7 @@ export function ErrorModal({
275
290
 
276
291
  {/* Terminal Output */}
277
292
  <div style={terminalContent}>
278
- {/* The star of the show: The ANSI parsed text */}
279
293
  <AnsiText text={rawOutput} />
280
-
281
- {/* Stack trace appended if exists and usually not in message */}
282
294
  {stackOutput && (
283
295
  <div style={{ marginTop: 24, opacity: 0.6, borderTop: '1px dashed rgba(255,255,255,0.1)', paddingTop: 16 }}>
284
296
  <AnsiText text={stackOutput} />
@@ -302,4 +314,7 @@ export function ErrorModal({
302
314
  </div>
303
315
  </div>
304
316
  );
317
+
318
+ // O PULO DO GATO: Renderiza direto no body para ignorar contexto de scroll dos pais
319
+ return createPortal(modalContent, document.body);
305
320
  }
@@ -261,10 +261,12 @@ function App({ componentMap, routes, initialComponentPath, initialParams, layout
261
261
 
262
262
 
263
263
 
264
+
265
+
264
266
  export function DevIndicator({
265
- hasBuildError = false,
266
- onClickBuildError,
267
- }: {
267
+ hasBuildError = false,
268
+ onClickBuildError,
269
+ }: {
268
270
  hasBuildError?: boolean;
269
271
  onClickBuildError?: () => void;
270
272
  }) {
@@ -303,91 +305,95 @@ export function DevIndicator({
303
305
  <>
304
306
  <style>
305
307
  {`
306
- @keyframes nyte-pulse {
307
- 0% { opacity: 0.4; }
308
- 50% { opacity: 1; }
309
- 100% { opacity: 0.4; }
310
- }
311
- @keyframes nyte-spin {
312
- 0% { transform: rotate(0deg); }
313
- 100% { transform: rotate(360deg); }
314
- }
315
- .nyte-dev-badge {
316
- position: fixed;
317
- bottom: 20px;
318
- right: 20px;
319
- z-index: 999999;
320
- display: flex;
321
- align-items: center;
322
- gap: 12px;
323
- padding: 8px 14px;
324
- background: rgba(15, 15, 20, 0.8);
325
- backdrop-filter: blur(12px);
326
- -webkit-backdrop-filter: blur(12px);
327
-
328
- border-radius: 10px;
329
- color: #fff;
330
- font-family: 'Inter', ui-monospace, SFMono-Regular, Menlo, monospace;
331
- font-size: 12px;
332
- font-weight: 600;
333
- letter-spacing: 0.05em;
334
-
335
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
336
- transition: all 0.2s ease;
337
- cursor: default;
338
- user-select: none;
339
- }
340
- .nyte-dev-badge.clickable {
341
- cursor: pointer;
342
- }
343
- .nyte-dev-badge:hover {
344
- border-color: rgba(142, 45, 226, 0.5);
345
- transform: translateY(-2px);
346
- }
347
- .nyte-status-dot {
348
- width: 8px;
349
- height: 8px;
350
- background: #10b981; /* Verde esmeralda */
351
- border-radius: 50%;
352
- box-shadow: 0 0 10px #10b981;
353
- animation: nyte-pulse 2s infinite ease-in-out;
354
- }
355
- .nyte-status-dot.reloading {
356
- background: #f59e0b; /* Amber */
357
- box-shadow: 0 0 10px #f59e0b;
358
- }
359
- .nyte-status-dot.error {
360
- background: #ef4444; /* Red */
361
- box-shadow: 0 0 12px #ef4444;
362
- animation: nyte-pulse 1.2s infinite ease-in-out;
363
- }
364
- .nyte-spinner {
365
- width: 10px;
366
- height: 10px;
367
- border-radius: 50%;
368
- border: 2px solid rgba(255,255,255,0.25);
369
- border-top-color: rgba(255,255,255,0.85);
370
- animation: nyte-spin 0.8s linear infinite;
371
- }
372
- .nyte-logo {
373
- background: linear-gradient(135deg, #00a3a3, #808080);
374
- -webkit-background-clip: text;
375
- -webkit-text-fill-color: transparent;
376
- font-weight: 800;
377
- }
378
- .nyte-error-pill {
379
- margin-left: 6px;
380
- padding: 2px 6px;
381
- border-radius: 999px;
382
- background: rgba(239, 68, 68, 0.18);
383
- border: 1px solid rgba(239, 68, 68, 0.35);
384
- color: rgba(255,255,255,0.9);
385
- font-size: 10px;
386
- font-weight: 800;
387
- letter-spacing: 0.08em;
388
- text-transform: uppercase;
389
- }
390
- `}
308
+ @keyframes nyte-pulse {
309
+ 0% { opacity: 0.4; }
310
+ 50% { opacity: 1; }
311
+ 100% { opacity: 0.4; }
312
+ }
313
+ @keyframes nyte-spin {
314
+ 0% { transform: rotate(0deg); }
315
+ 100% { transform: rotate(360deg); }
316
+ }
317
+ .nyte-dev-badge {
318
+ /* AQUI ESTÁ O QUE SEGURA ELE NA TELA: */
319
+ position: sticky;
320
+ bottom: 20px;
321
+ /* float e margens para forçar a direita no modo sticky */
322
+ float: right;
323
+ margin-right: 20px;
324
+ z-index: 999999;
325
+
326
+ display: flex;
327
+ align-items: center;
328
+ gap: 12px;
329
+ padding: 8px 14px;
330
+ background: rgba(15, 15, 20, 0.8);
331
+ backdrop-filter: blur(12px);
332
+ -webkit-backdrop-filter: blur(12px);
333
+
334
+ border-radius: 10px;
335
+ color: #fff;
336
+ font-family: 'Inter', ui-monospace, SFMono-Regular, Menlo, monospace;
337
+ font-size: 12px;
338
+ font-weight: 600;
339
+ letter-spacing: 0.05em;
340
+
341
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
342
+ transition: all 0.2s ease;
343
+ cursor: default;
344
+ user-select: none;
345
+ }
346
+ .nyte-dev-badge.clickable {
347
+ cursor: pointer;
348
+ }
349
+ .nyte-dev-badge:hover {
350
+ border-color: rgba(142, 45, 226, 0.5);
351
+ transform: translateY(-2px);
352
+ }
353
+ .nyte-status-dot {
354
+ width: 8px;
355
+ height: 8px;
356
+ background: #10b981; /* Verde esmeralda */
357
+ border-radius: 50%;
358
+ box-shadow: 0 0 10px #10b981;
359
+ animation: nyte-pulse 2s infinite ease-in-out;
360
+ }
361
+ .nyte-status-dot.reloading {
362
+ background: #f59e0b; /* Amber */
363
+ box-shadow: 0 0 10px #f59e0b;
364
+ }
365
+ .nyte-status-dot.error {
366
+ background: #ef4444; /* Red */
367
+ box-shadow: 0 0 12px #ef4444;
368
+ animation: nyte-pulse 1.2s infinite ease-in-out;
369
+ }
370
+ .nyte-spinner {
371
+ width: 10px;
372
+ height: 10px;
373
+ border-radius: 50%;
374
+ border: 2px solid rgba(255,255,255,0.25);
375
+ border-top-color: rgba(255,255,255,0.85);
376
+ animation: nyte-spin 0.8s linear infinite;
377
+ }
378
+ .nyte-logo {
379
+ background: linear-gradient(135deg, #00a3a3, #808080);
380
+ -webkit-background-clip: text;
381
+ -webkit-text-fill-color: transparent;
382
+ font-weight: 800;
383
+ }
384
+ .nyte-error-pill {
385
+ margin-left: 6px;
386
+ padding: 2px 6px;
387
+ border-radius: 999px;
388
+ background: rgba(239, 68, 68, 0.18);
389
+ border: 1px solid rgba(239, 68, 68, 0.35);
390
+ color: rgba(255,255,255,0.9);
391
+ font-size: 10px;
392
+ font-weight: 800;
393
+ letter-spacing: 0.08em;
394
+ text-transform: uppercase;
395
+ }
396
+ `}
391
397
  </style>
392
398
 
393
399
  <div