create-fluxstack 1.18.0 → 1.19.0
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/CHANGELOG.md +132 -0
- package/app/client/src/App.tsx +7 -7
- package/app/client/src/components/AppLayout.tsx +60 -23
- package/app/client/src/components/ColorWheel.tsx +195 -0
- package/app/client/src/components/DemoPage.tsx +5 -3
- package/app/client/src/components/LiveUploadWidget.tsx +1 -1
- package/app/client/src/components/ThemePicker.tsx +307 -0
- package/app/client/src/config/theme.config.ts +127 -0
- package/app/client/src/hooks/useThemeClock.ts +66 -0
- package/app/client/src/index.css +193 -0
- package/app/client/src/lib/theme-clock.ts +201 -0
- package/app/client/src/live/AuthDemo.tsx +9 -9
- package/app/client/src/live/CounterDemo.tsx +10 -10
- package/app/client/src/live/FormDemo.tsx +8 -8
- package/app/client/src/live/PingPongDemo.tsx +10 -10
- package/app/client/src/live/RoomChatDemo.tsx +10 -10
- package/app/client/src/live/SharedCounterDemo.tsx +5 -5
- package/app/client/src/pages/ApiTestPage.tsx +5 -5
- package/app/client/src/pages/HomePage.tsx +12 -12
- package/app/server/index.ts +8 -0
- package/app/server/live/auto-generated-components.ts +1 -1
- package/app/server/live/rooms/ChatRoom.ts +13 -8
- package/app/server/routes/index.ts +20 -10
- package/core/build/index.ts +1 -1
- package/core/cli/command-registry.ts +1 -1
- package/core/cli/commands/build.ts +25 -6
- package/core/cli/commands/plugin-deps.ts +1 -2
- package/core/cli/generators/plugin.ts +433 -581
- package/core/framework/server.ts +34 -8
- package/core/index.ts +6 -5
- package/core/plugins/index.ts +71 -199
- package/core/plugins/types.ts +76 -461
- package/core/server/index.ts +1 -1
- package/core/utils/logger/startup-banner.ts +26 -4
- package/core/utils/version.ts +6 -6
- package/create-fluxstack.ts +216 -107
- package/package.json +108 -107
- package/tsconfig.json +2 -1
- package/app/client/.live-stubs/LiveAdminPanel.js +0 -15
- package/app/client/.live-stubs/LiveCounter.js +0 -9
- package/app/client/.live-stubs/LiveForm.js +0 -11
- package/app/client/.live-stubs/LiveLocalCounter.js +0 -8
- package/app/client/.live-stubs/LivePingPong.js +0 -10
- package/app/client/.live-stubs/LiveRoomChat.js +0 -11
- package/app/client/.live-stubs/LiveSharedCounter.js +0 -10
- package/app/client/.live-stubs/LiveUpload.js +0 -15
- package/core/plugins/config.ts +0 -356
- package/core/plugins/dependency-manager.ts +0 -481
- package/core/plugins/discovery.ts +0 -379
- package/core/plugins/executor.ts +0 -353
- package/core/plugins/manager.ts +0 -645
- package/core/plugins/module-resolver.ts +0 -227
- package/core/plugins/registry.ts +0 -913
- package/vitest.config.live.ts +0 -69
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Clock — Generates complete color palettes that shift with time
|
|
3
|
+
*
|
|
4
|
+
* Based on color theory (like Adobe Color Wheel):
|
|
5
|
+
* - Base hue rotates continuously through 360° over 24 hours
|
|
6
|
+
* - Palette is generated using color harmony rules (analogous, complementary, triadic)
|
|
7
|
+
* - Colors use OKLCH for perceptual uniformity (consistent brightness across hues)
|
|
8
|
+
*
|
|
9
|
+
* Periods & their base hues:
|
|
10
|
+
* - 00:00 → 270° (violet) — deep night
|
|
11
|
+
* - 04:00 → 330° (magenta) — pre-dawn
|
|
12
|
+
* - 06:00 → 30° (orange) — sunrise
|
|
13
|
+
* - 09:00 → 60° (gold) — morning
|
|
14
|
+
* - 12:00 → 150° (teal) — midday
|
|
15
|
+
* - 15:00 → 210° (blue) — afternoon
|
|
16
|
+
* - 18:00 → 330° (pink) — sunset
|
|
17
|
+
* - 21:00 → 270° (purple) — evening
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// ===== Color Math =====
|
|
21
|
+
|
|
22
|
+
/** Normalize hue to 0-360 range */
|
|
23
|
+
function normalizeHue(h: number): number {
|
|
24
|
+
return ((h % 360) + 360) % 360
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Generate an OKLCH color string */
|
|
28
|
+
function oklch(l: number, c: number, h: number, alpha?: number): string {
|
|
29
|
+
const hNorm = normalizeHue(h)
|
|
30
|
+
return alpha !== undefined
|
|
31
|
+
? `oklch(${l}% ${c} ${hNorm} / ${alpha})`
|
|
32
|
+
: `oklch(${l}% ${c} ${hNorm})`
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ===== Palette Generation =====
|
|
36
|
+
|
|
37
|
+
export interface ColorPalette {
|
|
38
|
+
/** Base hue (0-360) — rotates with time */
|
|
39
|
+
baseHue: number
|
|
40
|
+
|
|
41
|
+
/** Primary accent — most vibrant */
|
|
42
|
+
primary: string
|
|
43
|
+
/** Secondary — analogous offset (+40°) */
|
|
44
|
+
secondary: string
|
|
45
|
+
/** Tertiary — analogous offset (-30°) */
|
|
46
|
+
tertiary: string
|
|
47
|
+
/** Complement — opposite side of wheel (+180°) */
|
|
48
|
+
complement: string
|
|
49
|
+
/** Accent — triadic offset (+120°) */
|
|
50
|
+
accent: string
|
|
51
|
+
|
|
52
|
+
/** Muted versions for backgrounds */
|
|
53
|
+
primaryMuted: string
|
|
54
|
+
secondaryMuted: string
|
|
55
|
+
|
|
56
|
+
/** Glow/shadow colors with transparency */
|
|
57
|
+
primaryGlow: string
|
|
58
|
+
secondaryGlow: string
|
|
59
|
+
|
|
60
|
+
/** Background accent (very subtle) */
|
|
61
|
+
bgAccent: string
|
|
62
|
+
bgAccent2: string
|
|
63
|
+
|
|
64
|
+
/** Text on dark backgrounds */
|
|
65
|
+
textPrimary: string
|
|
66
|
+
textSecondary: string
|
|
67
|
+
textMuted: string
|
|
68
|
+
|
|
69
|
+
/** Border colors */
|
|
70
|
+
border: string
|
|
71
|
+
borderActive: string
|
|
72
|
+
|
|
73
|
+
/** Current time period */
|
|
74
|
+
period: 'night' | 'dawn' | 'morning' | 'midday' | 'afternoon' | 'sunset' | 'evening' | 'dusk'
|
|
75
|
+
|
|
76
|
+
/** CSS gradient for logo/titles */
|
|
77
|
+
gradientPrimary: string
|
|
78
|
+
/** CSS gradient for backgrounds */
|
|
79
|
+
gradientBg: string
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Generate a complete color palette for the given time
|
|
84
|
+
*/
|
|
85
|
+
export function generatePalette(date = new Date()): ColorPalette {
|
|
86
|
+
const hours = date.getHours()
|
|
87
|
+
const minutes = date.getMinutes()
|
|
88
|
+
const totalMinutes = hours * 60 + minutes
|
|
89
|
+
|
|
90
|
+
// Base hue — full rotation over 24h, offset so midnight = 270° (purple)
|
|
91
|
+
const baseHue = normalizeHue((totalMinutes / 1440) * 360 + 270)
|
|
92
|
+
|
|
93
|
+
// Determine period
|
|
94
|
+
let period: ColorPalette['period']
|
|
95
|
+
if (hours >= 0 && hours < 4) period = 'night'
|
|
96
|
+
else if (hours >= 4 && hours < 6) period = 'dawn'
|
|
97
|
+
else if (hours >= 6 && hours < 10) period = 'morning'
|
|
98
|
+
else if (hours >= 10 && hours < 14) period = 'midday'
|
|
99
|
+
else if (hours >= 14 && hours < 17) period = 'afternoon'
|
|
100
|
+
else if (hours >= 17 && hours < 19) period = 'sunset'
|
|
101
|
+
else if (hours >= 19 && hours < 22) period = 'evening'
|
|
102
|
+
else period = 'dusk'
|
|
103
|
+
|
|
104
|
+
return buildPaletteFromHues(baseHue, period)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Build palette from individual hue values
|
|
109
|
+
* Used by all modes: auto, fixed, custom
|
|
110
|
+
*/
|
|
111
|
+
export function buildPaletteFromHues(
|
|
112
|
+
baseHue: number,
|
|
113
|
+
period: ColorPalette['period'] = 'midday',
|
|
114
|
+
hues?: { secondary?: number; tertiary?: number; complement?: number; accent?: number }
|
|
115
|
+
): ColorPalette {
|
|
116
|
+
const h2 = normalizeHue(hues?.secondary ?? baseHue + 40)
|
|
117
|
+
const h3 = normalizeHue(hues?.tertiary ?? baseHue - 30)
|
|
118
|
+
const hComp = normalizeHue(hues?.complement ?? baseHue + 180)
|
|
119
|
+
const hAccent = normalizeHue(hues?.accent ?? baseHue + 120)
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
baseHue,
|
|
123
|
+
period,
|
|
124
|
+
|
|
125
|
+
primary: oklch(65, 0.25, baseHue),
|
|
126
|
+
secondary: oklch(70, 0.20, h2),
|
|
127
|
+
tertiary: oklch(60, 0.22, h3),
|
|
128
|
+
complement: oklch(68, 0.18, hComp),
|
|
129
|
+
accent: oklch(72, 0.20, hAccent),
|
|
130
|
+
|
|
131
|
+
primaryMuted: oklch(65, 0.25, baseHue, 0.15),
|
|
132
|
+
secondaryMuted: oklch(70, 0.20, h2, 0.10),
|
|
133
|
+
|
|
134
|
+
primaryGlow: oklch(65, 0.25, baseHue, 0.3),
|
|
135
|
+
secondaryGlow: oklch(70, 0.20, h2, 0.2),
|
|
136
|
+
|
|
137
|
+
bgAccent: oklch(65, 0.25, baseHue, 0.08),
|
|
138
|
+
bgAccent2: oklch(70, 0.20, h2, 0.05),
|
|
139
|
+
|
|
140
|
+
textPrimary: oklch(80, 0.15, baseHue),
|
|
141
|
+
textSecondary: oklch(75, 0.12, h2),
|
|
142
|
+
textMuted: oklch(55, 0.05, baseHue),
|
|
143
|
+
|
|
144
|
+
border: oklch(65, 0.25, baseHue, 0.12),
|
|
145
|
+
borderActive: oklch(65, 0.25, baseHue, 0.3),
|
|
146
|
+
|
|
147
|
+
gradientPrimary: `linear-gradient(to right, ${oklch(65, 0.25, baseHue)}, ${oklch(70, 0.20, h2)})`,
|
|
148
|
+
gradientBg: `linear-gradient(135deg, ${oklch(65, 0.25, baseHue, 0.05)}, ${oklch(70, 0.20, hComp, 0.03)})`,
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Generate palette for a specific hue (fixed mode)
|
|
154
|
+
*/
|
|
155
|
+
export function generatePaletteForHue(hue: number): ColorPalette {
|
|
156
|
+
// Create a fake date that produces this hue
|
|
157
|
+
const totalMinutes = ((hue - 270 + 360) % 360) / 360 * 1440
|
|
158
|
+
const fakeDate = new Date()
|
|
159
|
+
fakeDate.setHours(Math.floor(totalMinutes / 60), Math.floor(totalMinutes % 60))
|
|
160
|
+
return generatePalette(fakeDate)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Apply palette to CSS custom properties on :root
|
|
165
|
+
*/
|
|
166
|
+
export function applyPalette(palette: ColorPalette): void {
|
|
167
|
+
const root = document.documentElement
|
|
168
|
+
const set = (k: string, v: string) => root.style.setProperty(`--theme-${k}`, v)
|
|
169
|
+
|
|
170
|
+
set('hue', String(palette.baseHue))
|
|
171
|
+
set('primary', palette.primary)
|
|
172
|
+
set('secondary', palette.secondary)
|
|
173
|
+
set('tertiary', palette.tertiary)
|
|
174
|
+
set('complement', palette.complement)
|
|
175
|
+
set('accent', palette.accent)
|
|
176
|
+
set('primary-muted', palette.primaryMuted)
|
|
177
|
+
set('secondary-muted', palette.secondaryMuted)
|
|
178
|
+
set('primary-glow', palette.primaryGlow)
|
|
179
|
+
set('secondary-glow', palette.secondaryGlow)
|
|
180
|
+
set('bg-accent', palette.bgAccent)
|
|
181
|
+
set('bg-accent2', palette.bgAccent2)
|
|
182
|
+
set('text-primary', palette.textPrimary)
|
|
183
|
+
set('text-secondary', palette.textSecondary)
|
|
184
|
+
set('text-muted', palette.textMuted)
|
|
185
|
+
set('border', palette.border)
|
|
186
|
+
set('border-active', palette.borderActive)
|
|
187
|
+
set('gradient-primary', palette.gradientPrimary)
|
|
188
|
+
set('gradient-bg', palette.gradientBg)
|
|
189
|
+
|
|
190
|
+
root.setAttribute('data-theme-period', palette.period)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Start the palette clock — updates every 30 seconds for smooth transitions
|
|
195
|
+
*/
|
|
196
|
+
export function startPaletteClock(): () => void {
|
|
197
|
+
const update = () => applyPalette(generatePalette())
|
|
198
|
+
update()
|
|
199
|
+
const interval = setInterval(update, 30_000)
|
|
200
|
+
return () => clearInterval(interval)
|
|
201
|
+
}
|
|
@@ -83,7 +83,7 @@ function AdminSection() {
|
|
|
83
83
|
return (
|
|
84
84
|
<div className="bg-white/5 border border-white/10 rounded-xl p-6">
|
|
85
85
|
<div className="flex items-center gap-2 text-gray-400">
|
|
86
|
-
<div className="w-4 h-4 border-2 border-
|
|
86
|
+
<div className="w-4 h-4 border-2 border-theme border-t-transparent rounded-full animate-spin" />
|
|
87
87
|
Montando painel admin...
|
|
88
88
|
</div>
|
|
89
89
|
</div>
|
|
@@ -117,7 +117,7 @@ function AdminSection() {
|
|
|
117
117
|
<div>
|
|
118
118
|
<h3 className="text-lg font-semibold text-white">Painel Admin</h3>
|
|
119
119
|
<p className="text-gray-400 text-xs">
|
|
120
|
-
Requer: <code className="text-
|
|
120
|
+
Requer: <code className="text-theme">auth.required + roles: ['admin']</code>
|
|
121
121
|
</p>
|
|
122
122
|
</div>
|
|
123
123
|
<div className="text-xs text-right">
|
|
@@ -162,11 +162,11 @@ function AdminSection() {
|
|
|
162
162
|
onChange={e => setNewUserName(e.target.value)}
|
|
163
163
|
onKeyDown={e => e.key === 'Enter' && handleAddUser()}
|
|
164
164
|
placeholder="Nome do usuário..."
|
|
165
|
-
className="flex-1 px-3 py-2 rounded-lg bg-white/10 border border-white/20 text-white text-sm placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-
|
|
165
|
+
className="flex-1 px-3 py-2 rounded-lg bg-white/10 border border-white/20 text-white text-sm placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-[var(--theme-primary-glow)]"
|
|
166
166
|
/>
|
|
167
167
|
<button
|
|
168
168
|
onClick={handleAddUser}
|
|
169
|
-
className="px-4 py-2 rounded-lg bg-
|
|
169
|
+
className="px-4 py-2 rounded-lg bg-theme-muted border border-theme-active text-theme text-sm hover:bg-theme-muted"
|
|
170
170
|
>
|
|
171
171
|
Adicionar
|
|
172
172
|
</button>
|
|
@@ -242,7 +242,7 @@ function AuthControls() {
|
|
|
242
242
|
onChange={e => setToken(e.target.value)}
|
|
243
243
|
onKeyDown={e => e.key === 'Enter' && handleLogin()}
|
|
244
244
|
placeholder="Token (JWT, API key, etc.)"
|
|
245
|
-
className="flex-1 px-3 py-2 rounded-lg bg-white/10 border border-white/20 text-white text-sm placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-
|
|
245
|
+
className="flex-1 px-3 py-2 rounded-lg bg-white/10 border border-white/20 text-white text-sm placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-[var(--theme-primary-glow)]"
|
|
246
246
|
/>
|
|
247
247
|
<div className="flex gap-2">
|
|
248
248
|
<button
|
|
@@ -268,7 +268,7 @@ function AuthControls() {
|
|
|
268
268
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-2 text-xs">
|
|
269
269
|
<button
|
|
270
270
|
onClick={() => { setToken('admin-token'); }}
|
|
271
|
-
className="px-2 py-1 rounded bg-
|
|
271
|
+
className="px-2 py-1 rounded bg-theme-muted text-theme hover:bg-theme-muted"
|
|
272
272
|
>
|
|
273
273
|
admin-token
|
|
274
274
|
</button>
|
|
@@ -321,9 +321,9 @@ export function AuthDemo() {
|
|
|
321
321
|
|
|
322
322
|
<div className="bg-white/5 border border-white/10 rounded-xl p-4 sm:p-6 text-xs text-gray-500 space-y-2 overflow-x-auto">
|
|
323
323
|
<h4 className="text-sm font-semibold text-gray-300 mb-3">Como funciona</h4>
|
|
324
|
-
<p><strong className="text-
|
|
325
|
-
<p><strong className="text-
|
|
326
|
-
<p><strong className="text-
|
|
324
|
+
<p><strong className="text-theme">Server:</strong> <code>static auth = { required: true, roles: ['admin'] }</code></p>
|
|
325
|
+
<p><strong className="text-theme">Server:</strong> <code>static actionAuth = { deleteUser: { permissions: ['users.delete'] } }</code></p>
|
|
326
|
+
<p><strong className="text-theme">Server:</strong> <code>this.$auth.hasRole('admin')</code> dentro das actions</p>
|
|
327
327
|
<p><strong className="text-blue-300">Client:</strong> <code>component.$authenticated</code> no proxy</p>
|
|
328
328
|
<p><strong className="text-blue-300">Client:</strong> <code>useLiveComponents().authenticate({ token })</code> para login</p>
|
|
329
329
|
<p><strong className="text-blue-300">Client:</strong> <code><LiveComponentsProvider auth={{ token }}></code> para auth na conexão</p>
|
|
@@ -75,7 +75,7 @@ export function CounterDemo() {
|
|
|
75
75
|
</div>
|
|
76
76
|
|
|
77
77
|
<div className="text-center mb-6 sm:mb-8 flex-1 flex flex-col justify-center">
|
|
78
|
-
<div className="text-6xl sm:text-8xl font-bold bg-gradient
|
|
78
|
+
<div className="text-6xl sm:text-8xl font-bold bg-theme-gradient bg-clip-text text-transparent">
|
|
79
79
|
{counter.$state.count}
|
|
80
80
|
</div>
|
|
81
81
|
|
|
@@ -90,7 +90,7 @@ export function CounterDemo() {
|
|
|
90
90
|
<button
|
|
91
91
|
onClick={handleDecrement}
|
|
92
92
|
disabled={counter.$loading}
|
|
93
|
-
className="w-14 h-14 flex items-center justify-center text-3xl
|
|
93
|
+
className="w-14 h-14 flex items-center justify-center text-3xl btn-complement disabled:opacity-50"
|
|
94
94
|
>
|
|
95
95
|
−
|
|
96
96
|
</button>
|
|
@@ -98,7 +98,7 @@ export function CounterDemo() {
|
|
|
98
98
|
<button
|
|
99
99
|
onClick={handleReset}
|
|
100
100
|
disabled={counter.$loading}
|
|
101
|
-
className="px-6 h-14 flex items-center justify-center text-sm
|
|
101
|
+
className="px-6 h-14 flex items-center justify-center text-sm btn-theme-outline disabled:opacity-50"
|
|
102
102
|
>
|
|
103
103
|
Reset
|
|
104
104
|
</button>
|
|
@@ -106,7 +106,7 @@ export function CounterDemo() {
|
|
|
106
106
|
<button
|
|
107
107
|
onClick={handleIncrement}
|
|
108
108
|
disabled={counter.$loading}
|
|
109
|
-
className="w-14 h-14 flex items-center justify-center text-3xl
|
|
109
|
+
className="w-14 h-14 flex items-center justify-center text-3xl btn-accent disabled:opacity-50"
|
|
110
110
|
>
|
|
111
111
|
+
|
|
112
112
|
</button>
|
|
@@ -114,13 +114,13 @@ export function CounterDemo() {
|
|
|
114
114
|
|
|
115
115
|
{counter.$loading && (
|
|
116
116
|
<div className="flex justify-center mt-4">
|
|
117
|
-
<div className="w-5 h-5 border-2 border-
|
|
117
|
+
<div className="w-5 h-5 border-2 border-theme border-t-transparent rounded-full animate-spin" />
|
|
118
118
|
</div>
|
|
119
119
|
)}
|
|
120
120
|
|
|
121
121
|
<div className="mt-8 pt-6 border-t border-white/10">
|
|
122
122
|
<p className="text-gray-500 text-xs text-center">
|
|
123
|
-
✨ Usando <code className="text-
|
|
123
|
+
✨ Usando <code className="text-theme">Room Events</code>
|
|
124
124
|
</p>
|
|
125
125
|
</div>
|
|
126
126
|
</div>
|
|
@@ -157,7 +157,7 @@ export function CounterDemo() {
|
|
|
157
157
|
<button
|
|
158
158
|
onClick={handleDecrement}
|
|
159
159
|
disabled={localCounter.$loading}
|
|
160
|
-
className="w-14 h-14 flex items-center justify-center text-3xl
|
|
160
|
+
className="w-14 h-14 flex items-center justify-center text-3xl btn-complement disabled:opacity-50"
|
|
161
161
|
>
|
|
162
162
|
−
|
|
163
163
|
</button>
|
|
@@ -165,7 +165,7 @@ export function CounterDemo() {
|
|
|
165
165
|
<button
|
|
166
166
|
onClick={handleReset}
|
|
167
167
|
disabled={localCounter.$loading}
|
|
168
|
-
className="px-6 h-14 flex items-center justify-center text-sm
|
|
168
|
+
className="px-6 h-14 flex items-center justify-center text-sm btn-theme-outline disabled:opacity-50"
|
|
169
169
|
>
|
|
170
170
|
Reset
|
|
171
171
|
</button>
|
|
@@ -173,7 +173,7 @@ export function CounterDemo() {
|
|
|
173
173
|
<button
|
|
174
174
|
onClick={handleIncrement}
|
|
175
175
|
disabled={localCounter.$loading}
|
|
176
|
-
className="w-14 h-14 flex items-center justify-center text-3xl
|
|
176
|
+
className="w-14 h-14 flex items-center justify-center text-3xl btn-accent disabled:opacity-50"
|
|
177
177
|
>
|
|
178
178
|
+
|
|
179
179
|
</button>
|
|
@@ -181,7 +181,7 @@ export function CounterDemo() {
|
|
|
181
181
|
|
|
182
182
|
{localCounter.$loading && (
|
|
183
183
|
<div className="flex justify-center mt-4">
|
|
184
|
-
<div className="w-5 h-5 border-2 border-
|
|
184
|
+
<div className="w-5 h-5 border-2 border-theme border-t-transparent rounded-full animate-spin" />
|
|
185
185
|
</div>
|
|
186
186
|
)}
|
|
187
187
|
</div>
|
|
@@ -18,7 +18,7 @@ export function FormDemo() {
|
|
|
18
18
|
</p>
|
|
19
19
|
<button
|
|
20
20
|
onClick={() => form.reset()}
|
|
21
|
-
className="mt-4
|
|
21
|
+
className="mt-4 btn-theme"
|
|
22
22
|
>
|
|
23
23
|
Novo Formulário
|
|
24
24
|
</button>
|
|
@@ -41,12 +41,12 @@ export function FormDemo() {
|
|
|
41
41
|
{/* Nome - sync on blur */}
|
|
42
42
|
<div>
|
|
43
43
|
<label className="block text-gray-300 text-sm mb-1">
|
|
44
|
-
Nome <span className="text-
|
|
44
|
+
Nome <span className="text-theme text-xs">(sync: blur)</span>
|
|
45
45
|
</label>
|
|
46
46
|
<input
|
|
47
47
|
{...form.$field('name', { syncOn: 'blur' })}
|
|
48
48
|
placeholder="Seu nome"
|
|
49
|
-
className="w-full
|
|
49
|
+
className="w-full input-theme"
|
|
50
50
|
/>
|
|
51
51
|
</div>
|
|
52
52
|
|
|
@@ -59,7 +59,7 @@ export function FormDemo() {
|
|
|
59
59
|
{...form.$field('email', { syncOn: 'change', debounce: 500 })}
|
|
60
60
|
type="email"
|
|
61
61
|
placeholder="seu@email.com"
|
|
62
|
-
className="w-full
|
|
62
|
+
className="w-full input-theme"
|
|
63
63
|
/>
|
|
64
64
|
</div>
|
|
65
65
|
|
|
@@ -72,7 +72,7 @@ export function FormDemo() {
|
|
|
72
72
|
{...form.$field('message', { syncOn: 'blur' })}
|
|
73
73
|
rows={3}
|
|
74
74
|
placeholder="Sua mensagem..."
|
|
75
|
-
className="w-full
|
|
75
|
+
className="w-full input-theme resize-none"
|
|
76
76
|
/>
|
|
77
77
|
</div>
|
|
78
78
|
|
|
@@ -88,13 +88,13 @@ export function FormDemo() {
|
|
|
88
88
|
}
|
|
89
89
|
}}
|
|
90
90
|
disabled={!form.$connected || form.$loading}
|
|
91
|
-
className="flex-1 px-4 py-3 bg-gradient
|
|
91
|
+
className="flex-1 px-4 py-3 bg-theme-gradient text-white rounded-lg font-medium hover:shadow-theme transition-all disabled:opacity-50"
|
|
92
92
|
>
|
|
93
93
|
{form.$loading ? 'Enviando...' : 'Enviar'}
|
|
94
94
|
</button>
|
|
95
95
|
<button
|
|
96
96
|
onClick={() => form.reset()}
|
|
97
|
-
className="px-4 py-3
|
|
97
|
+
className="px-4 py-3 btn-theme-ghost"
|
|
98
98
|
>
|
|
99
99
|
Limpar
|
|
100
100
|
</button>
|
|
@@ -103,7 +103,7 @@ export function FormDemo() {
|
|
|
103
103
|
|
|
104
104
|
{/* Legenda */}
|
|
105
105
|
<div className="mt-4 p-3 bg-white/5 rounded-lg text-xs text-gray-400 space-y-1">
|
|
106
|
-
<p><span className="text-
|
|
106
|
+
<p><span className="text-theme">blur:</span> Sincroniza ao sair do campo</p>
|
|
107
107
|
<p><span className="text-blue-400">500ms:</span> Sincroniza 500ms após parar de digitar</p>
|
|
108
108
|
</div>
|
|
109
109
|
|
|
@@ -95,7 +95,7 @@ export function PingPongDemo() {
|
|
|
95
95
|
<div className="text-center">
|
|
96
96
|
<h2 className="text-2xl font-bold text-white mb-2">Ping Pong Binary</h2>
|
|
97
97
|
<p className="text-sm text-gray-400">
|
|
98
|
-
Mensagens binárias via <code className="text-
|
|
98
|
+
Mensagens binárias via <code className="text-theme-secondary">msgpack</code> — round-trip latency demo
|
|
99
99
|
</p>
|
|
100
100
|
</div>
|
|
101
101
|
|
|
@@ -108,26 +108,26 @@ export function PingPongDemo() {
|
|
|
108
108
|
<div className="flex items-center gap-2 px-3 py-1 rounded-full bg-white/5 border border-white/10">
|
|
109
109
|
<span className="text-sm text-gray-400">{onlineCount} online</span>
|
|
110
110
|
</div>
|
|
111
|
-
<div className="px-3 py-1 rounded-full bg-
|
|
112
|
-
<span className="text-xs text-
|
|
111
|
+
<div className="px-3 py-1 rounded-full bg-theme-accent border border-theme">
|
|
112
|
+
<span className="text-xs text-theme">{username}</span>
|
|
113
113
|
</div>
|
|
114
114
|
</div>
|
|
115
115
|
|
|
116
116
|
{/* Stats */}
|
|
117
117
|
<div className="grid grid-cols-3 gap-4 w-full">
|
|
118
|
-
<div className="
|
|
118
|
+
<div className="card-theme p-4 text-center">
|
|
119
119
|
<div className="text-2xl font-bold text-white tabular-nums">
|
|
120
120
|
{avgRtt != null ? `${avgRtt}ms` : '--'}
|
|
121
121
|
</div>
|
|
122
122
|
<div className="text-xs text-gray-500 mt-1">AVG RTT</div>
|
|
123
123
|
</div>
|
|
124
|
-
<div className="
|
|
124
|
+
<div className="card-theme p-4 text-center">
|
|
125
125
|
<div className="text-2xl font-bold text-emerald-400 tabular-nums">
|
|
126
126
|
{minRtt != null ? `${minRtt}ms` : '--'}
|
|
127
127
|
</div>
|
|
128
128
|
<div className="text-xs text-gray-500 mt-1">MIN RTT</div>
|
|
129
129
|
</div>
|
|
130
|
-
<div className="
|
|
130
|
+
<div className="card-theme p-4 text-center">
|
|
131
131
|
<div className="text-2xl font-bold text-red-400 tabular-nums">
|
|
132
132
|
{maxRtt != null ? `${maxRtt}ms` : '--'}
|
|
133
133
|
</div>
|
|
@@ -140,7 +140,7 @@ export function PingPongDemo() {
|
|
|
140
140
|
<button
|
|
141
141
|
onClick={sendPing}
|
|
142
142
|
disabled={!live.$connected || live.$loading}
|
|
143
|
-
className="px-8 h-14 rounded-2xl bg-
|
|
143
|
+
className="px-8 h-14 rounded-2xl bg-theme-muted border border-theme-active text-theme text-lg font-bold hover:bg-theme-muted active:scale-95 disabled:opacity-50 transition-all"
|
|
144
144
|
>
|
|
145
145
|
Ping!
|
|
146
146
|
</button>
|
|
@@ -168,7 +168,7 @@ export function PingPongDemo() {
|
|
|
168
168
|
<div className="px-4 py-2 bg-white/5 border-b border-white/10 flex items-center justify-between">
|
|
169
169
|
<span className="text-xs text-gray-400 font-medium">Ping Log</span>
|
|
170
170
|
<span className="text-xs text-gray-600">
|
|
171
|
-
wire format: <code className="text-
|
|
171
|
+
wire format: <code className="text-theme-secondary">msgpack</code> (binary)
|
|
172
172
|
</span>
|
|
173
173
|
</div>
|
|
174
174
|
<div className="max-h-60 overflow-y-auto">
|
|
@@ -194,8 +194,8 @@ export function PingPongDemo() {
|
|
|
194
194
|
|
|
195
195
|
{/* Info */}
|
|
196
196
|
<div className="text-center text-xs text-gray-600 space-y-1">
|
|
197
|
-
<p>Powered by <code className="text-
|
|
198
|
-
<p>Wire format: binary frames <code className="text-
|
|
197
|
+
<p>Powered by <code className="text-theme">LiveRoom</code> + <code className="text-theme-secondary">msgpack codec</code></p>
|
|
198
|
+
<p>Wire format: binary frames <code className="text-theme-secondary">0x02</code> (event) / <code className="text-theme-secondary">0x03</code> (state)</p>
|
|
199
199
|
</div>
|
|
200
200
|
</div>
|
|
201
201
|
)
|
|
@@ -174,7 +174,7 @@ export function RoomChatDemo() {
|
|
|
174
174
|
<p className="text-xs text-gray-500">SALAS</p>
|
|
175
175
|
<button
|
|
176
176
|
onClick={() => dispatch({ type: 'OPEN_CREATE_MODAL' })}
|
|
177
|
-
className="text-xs text-
|
|
177
|
+
className="text-xs text-theme hover:text-theme"
|
|
178
178
|
>+ Criar</button>
|
|
179
179
|
</div>
|
|
180
180
|
{allRooms.map(room => {
|
|
@@ -187,7 +187,7 @@ export function RoomChatDemo() {
|
|
|
187
187
|
onClick={() => handleJoinRoom(room.id, room.name, room.isPrivate && !isJoined ? true : undefined)}
|
|
188
188
|
className={`
|
|
189
189
|
flex items-center justify-between px-3 py-2 rounded-lg cursor-pointer mb-1 transition-all group
|
|
190
|
-
${isActive ? 'bg-
|
|
190
|
+
${isActive ? 'bg-theme-muted text-theme' : isJoined ? 'bg-white/5 text-gray-300 hover:bg-white/10' : 'text-gray-500 hover:bg-white/5'}
|
|
191
191
|
`}
|
|
192
192
|
>
|
|
193
193
|
<span className="flex items-center gap-2 min-w-0">
|
|
@@ -249,7 +249,7 @@ export function RoomChatDemo() {
|
|
|
249
249
|
) : (
|
|
250
250
|
activeMessages.map(msg => (
|
|
251
251
|
<div key={msg.id} className={`flex flex-col ${msg.user === chat.$state.username ? 'items-end' : 'items-start'}`}>
|
|
252
|
-
<div className={`max-w-[85%] sm:max-w-[80%] rounded-2xl px-3 sm:px-4 py-2 ${msg.user === chat.$state.username ? 'bg-
|
|
252
|
+
<div className={`max-w-[85%] sm:max-w-[80%] rounded-2xl px-3 sm:px-4 py-2 ${msg.user === chat.$state.username ? 'bg-theme-muted text-white' : 'bg-white/10 text-gray-200'}`}>
|
|
253
253
|
<p className="text-xs text-gray-400 mb-1">{msg.user}</p>
|
|
254
254
|
<p className="text-sm sm:text-base">{msg.text}</p>
|
|
255
255
|
</div>
|
|
@@ -267,12 +267,12 @@ export function RoomChatDemo() {
|
|
|
267
267
|
onChange={(e) => dispatch({ type: 'SET_TEXT', text: e.target.value })}
|
|
268
268
|
onKeyDown={(e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSendMessage() } }}
|
|
269
269
|
placeholder="Digite uma mensagem..."
|
|
270
|
-
className="flex-1 px-3 sm:px-4 py-2 rounded-xl bg-white/10 border border-white/20 text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-
|
|
270
|
+
className="flex-1 px-3 sm:px-4 py-2 rounded-xl bg-white/10 border border-white/20 text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-[var(--theme-primary-glow)] text-sm sm:text-base"
|
|
271
271
|
/>
|
|
272
272
|
<button
|
|
273
273
|
onClick={handleSendMessage}
|
|
274
274
|
disabled={!ui.text.trim()}
|
|
275
|
-
className="px-4 sm:px-6 py-2 rounded-xl bg-
|
|
275
|
+
className="px-4 sm:px-6 py-2 rounded-xl bg-theme-muted text-theme hover:bg-theme-muted disabled:opacity-50 text-sm sm:text-base"
|
|
276
276
|
>Enviar</button>
|
|
277
277
|
</div>
|
|
278
278
|
</div>
|
|
@@ -306,7 +306,7 @@ export function RoomChatDemo() {
|
|
|
306
306
|
value={ui.createModal.name}
|
|
307
307
|
onChange={e => dispatch({ type: 'UPDATE_CREATE_FORM', name: e.target.value })}
|
|
308
308
|
placeholder="Minha Sala"
|
|
309
|
-
className="w-full px-3 py-2 rounded-lg bg-white/10 border border-white/20 text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-
|
|
309
|
+
className="w-full px-3 py-2 rounded-lg bg-white/10 border border-white/20 text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-[var(--theme-primary-glow)] text-sm"
|
|
310
310
|
autoFocus
|
|
311
311
|
onKeyDown={e => { if (e.key === 'Enter') handleCreateRoom() }}
|
|
312
312
|
/>
|
|
@@ -318,7 +318,7 @@ export function RoomChatDemo() {
|
|
|
318
318
|
value={ui.createModal.password}
|
|
319
319
|
onChange={e => dispatch({ type: 'UPDATE_CREATE_FORM', password: e.target.value })}
|
|
320
320
|
placeholder="Deixe vazio para sala publica"
|
|
321
|
-
className="w-full px-3 py-2 rounded-lg bg-white/10 border border-white/20 text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-
|
|
321
|
+
className="w-full px-3 py-2 rounded-lg bg-white/10 border border-white/20 text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-[var(--theme-primary-glow)] text-sm"
|
|
322
322
|
onKeyDown={e => { if (e.key === 'Enter') handleCreateRoom() }}
|
|
323
323
|
/>
|
|
324
324
|
</div>
|
|
@@ -330,7 +330,7 @@ export function RoomChatDemo() {
|
|
|
330
330
|
<button
|
|
331
331
|
onClick={handleCreateRoom}
|
|
332
332
|
disabled={!ui.createModal.name.trim()}
|
|
333
|
-
className="flex-1 px-4 py-2 rounded-lg bg-
|
|
333
|
+
className="flex-1 px-4 py-2 rounded-lg bg-theme-muted text-theme hover:bg-theme-muted disabled:opacity-50 text-sm"
|
|
334
334
|
>Criar</button>
|
|
335
335
|
</div>
|
|
336
336
|
</div>
|
|
@@ -351,7 +351,7 @@ export function RoomChatDemo() {
|
|
|
351
351
|
value={ui.passwordPrompt.input}
|
|
352
352
|
onChange={e => dispatch({ type: 'SET_PASSWORD_INPUT', input: e.target.value })}
|
|
353
353
|
placeholder="Digite a senha..."
|
|
354
|
-
className="w-full px-3 py-2 rounded-lg bg-white/10 border border-white/20 text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-
|
|
354
|
+
className="w-full px-3 py-2 rounded-lg bg-white/10 border border-white/20 text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-[var(--theme-primary-glow)] text-sm mb-3"
|
|
355
355
|
autoFocus
|
|
356
356
|
onKeyDown={e => { if (e.key === 'Enter') handlePasswordSubmit() }}
|
|
357
357
|
/>
|
|
@@ -363,7 +363,7 @@ export function RoomChatDemo() {
|
|
|
363
363
|
<button
|
|
364
364
|
onClick={handlePasswordSubmit}
|
|
365
365
|
disabled={!ui.passwordPrompt.input}
|
|
366
|
-
className="flex-1 px-4 py-2 rounded-lg bg-
|
|
366
|
+
className="flex-1 px-4 py-2 rounded-lg bg-theme-muted text-theme hover:bg-theme-muted disabled:opacity-50 text-sm"
|
|
367
367
|
>Entrar</button>
|
|
368
368
|
</div>
|
|
369
369
|
</div>
|
|
@@ -69,14 +69,14 @@ export function SharedCounterDemo() {
|
|
|
69
69
|
<div className="flex items-center gap-2 px-3 py-1 rounded-full bg-white/5 border border-white/10">
|
|
70
70
|
<span className="text-sm text-gray-400">{onlineCount} online</span>
|
|
71
71
|
</div>
|
|
72
|
-
<div className="px-3 py-1 rounded-full bg-
|
|
73
|
-
<span className="text-xs text-
|
|
72
|
+
<div className="px-3 py-1 rounded-full bg-theme-accent border border-theme">
|
|
73
|
+
<span className="text-xs text-theme">{username}</span>
|
|
74
74
|
</div>
|
|
75
75
|
</div>
|
|
76
76
|
|
|
77
77
|
{/* Counter Display */}
|
|
78
78
|
<div className="relative">
|
|
79
|
-
<div className="absolute inset-0 bg-
|
|
79
|
+
<div className="absolute inset-0 bg-theme-muted rounded-full blur-3xl" />
|
|
80
80
|
<div className="relative bg-gray-800/50 border border-white/10 rounded-3xl px-16 py-10 flex flex-col items-center">
|
|
81
81
|
<span className={`text-7xl font-black tabular-nums transition-colors ${
|
|
82
82
|
count > 0 ? 'text-emerald-400' : count < 0 ? 'text-red-400' : 'text-white'
|
|
@@ -128,8 +128,8 @@ export function SharedCounterDemo() {
|
|
|
128
128
|
|
|
129
129
|
{/* Info */}
|
|
130
130
|
<div className="text-center text-xs text-gray-600 space-y-1">
|
|
131
|
-
<p>Powered by <code className="text-
|
|
132
|
-
<p>Estado via component state + eventos via <code className="text-
|
|
131
|
+
<p>Powered by <code className="text-theme">LiveRoom</code> + <code className="text-theme">CounterRoom</code></p>
|
|
132
|
+
<p>Estado via component state + eventos via <code className="text-theme-secondary">$room().on()</code></p>
|
|
133
133
|
</div>
|
|
134
134
|
|
|
135
135
|
{/* CSS animation */}
|
|
@@ -18,7 +18,7 @@ export function ApiTestPage({
|
|
|
18
18
|
<div className="container mx-auto px-3 sm:px-4 py-6 sm:py-8 max-w-4xl">
|
|
19
19
|
<div className="flex items-center gap-3 sm:gap-4 mb-6 sm:mb-8">
|
|
20
20
|
<BackButton />
|
|
21
|
-
<h1 className="text-2xl sm:text-3xl font-bold bg-gradient
|
|
21
|
+
<h1 className="text-2xl sm:text-3xl font-bold bg-theme-gradient bg-clip-text text-transparent">
|
|
22
22
|
Eden Treaty API Test
|
|
23
23
|
</h1>
|
|
24
24
|
</div>
|
|
@@ -46,7 +46,7 @@ export function ApiTestPage({
|
|
|
46
46
|
subtitle="Create User"
|
|
47
47
|
onClick={onCreateUser}
|
|
48
48
|
disabled={isLoading}
|
|
49
|
-
className="bg-
|
|
49
|
+
className="bg-theme-muted border-theme text-theme hover:bg-theme-accent"
|
|
50
50
|
/>
|
|
51
51
|
</div>
|
|
52
52
|
|
|
@@ -70,9 +70,9 @@ export function ApiTestPage({
|
|
|
70
70
|
<div className="mt-6 sm:mt-8 bg-white/5 border border-white/10 rounded-xl p-4 sm:p-6">
|
|
71
71
|
<h3 className="text-lg font-semibold text-white mb-3">How it works</h3>
|
|
72
72
|
<div className="text-gray-400 text-sm space-y-2">
|
|
73
|
-
<p>OK <code className="text-
|
|
74
|
-
<p>OK <code className="text-
|
|
75
|
-
<p>OK <code className="text-
|
|
73
|
+
<p>OK <code className="text-theme">api.health.get()</code> - Full type inference from server</p>
|
|
74
|
+
<p>OK <code className="text-theme">api.users.post({'{ name, email }'})</code> - Request body is typed</p>
|
|
75
|
+
<p>OK <code className="text-theme">{'{ data, error }'}</code> - Response is typed automatically</p>
|
|
76
76
|
</div>
|
|
77
77
|
</div>
|
|
78
78
|
</div>
|