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.
Files changed (54) hide show
  1. package/CHANGELOG.md +132 -0
  2. package/app/client/src/App.tsx +7 -7
  3. package/app/client/src/components/AppLayout.tsx +60 -23
  4. package/app/client/src/components/ColorWheel.tsx +195 -0
  5. package/app/client/src/components/DemoPage.tsx +5 -3
  6. package/app/client/src/components/LiveUploadWidget.tsx +1 -1
  7. package/app/client/src/components/ThemePicker.tsx +307 -0
  8. package/app/client/src/config/theme.config.ts +127 -0
  9. package/app/client/src/hooks/useThemeClock.ts +66 -0
  10. package/app/client/src/index.css +193 -0
  11. package/app/client/src/lib/theme-clock.ts +201 -0
  12. package/app/client/src/live/AuthDemo.tsx +9 -9
  13. package/app/client/src/live/CounterDemo.tsx +10 -10
  14. package/app/client/src/live/FormDemo.tsx +8 -8
  15. package/app/client/src/live/PingPongDemo.tsx +10 -10
  16. package/app/client/src/live/RoomChatDemo.tsx +10 -10
  17. package/app/client/src/live/SharedCounterDemo.tsx +5 -5
  18. package/app/client/src/pages/ApiTestPage.tsx +5 -5
  19. package/app/client/src/pages/HomePage.tsx +12 -12
  20. package/app/server/index.ts +8 -0
  21. package/app/server/live/auto-generated-components.ts +1 -1
  22. package/app/server/live/rooms/ChatRoom.ts +13 -8
  23. package/app/server/routes/index.ts +20 -10
  24. package/core/build/index.ts +1 -1
  25. package/core/cli/command-registry.ts +1 -1
  26. package/core/cli/commands/build.ts +25 -6
  27. package/core/cli/commands/plugin-deps.ts +1 -2
  28. package/core/cli/generators/plugin.ts +433 -581
  29. package/core/framework/server.ts +34 -8
  30. package/core/index.ts +6 -5
  31. package/core/plugins/index.ts +71 -199
  32. package/core/plugins/types.ts +76 -461
  33. package/core/server/index.ts +1 -1
  34. package/core/utils/logger/startup-banner.ts +26 -4
  35. package/core/utils/version.ts +6 -6
  36. package/create-fluxstack.ts +216 -107
  37. package/package.json +108 -107
  38. package/tsconfig.json +2 -1
  39. package/app/client/.live-stubs/LiveAdminPanel.js +0 -15
  40. package/app/client/.live-stubs/LiveCounter.js +0 -9
  41. package/app/client/.live-stubs/LiveForm.js +0 -11
  42. package/app/client/.live-stubs/LiveLocalCounter.js +0 -8
  43. package/app/client/.live-stubs/LivePingPong.js +0 -10
  44. package/app/client/.live-stubs/LiveRoomChat.js +0 -11
  45. package/app/client/.live-stubs/LiveSharedCounter.js +0 -10
  46. package/app/client/.live-stubs/LiveUpload.js +0 -15
  47. package/core/plugins/config.ts +0 -356
  48. package/core/plugins/dependency-manager.ts +0 -481
  49. package/core/plugins/discovery.ts +0 -379
  50. package/core/plugins/executor.ts +0 -353
  51. package/core/plugins/manager.ts +0 -645
  52. package/core/plugins/module-resolver.ts +0 -227
  53. package/core/plugins/registry.ts +0 -913
  54. 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-purple-500 border-t-transparent rounded-full animate-spin" />
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-purple-300">auth.required + roles: [&apos;admin&apos;]</code>
120
+ Requer: <code className="text-theme">auth.required + roles: [&apos;admin&apos;]</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-purple-500/50"
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-purple-500/20 border border-purple-500/30 text-purple-200 text-sm hover:bg-purple-500/30"
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-purple-500/50"
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-purple-500/20 text-purple-300 hover:bg-purple-500/30"
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-purple-300">Server:</strong> <code>static auth = &#123; required: true, roles: [&apos;admin&apos;] &#125;</code></p>
325
- <p><strong className="text-purple-300">Server:</strong> <code>static actionAuth = &#123; deleteUser: &#123; permissions: [&apos;users.delete&apos;] &#125; &#125;</code></p>
326
- <p><strong className="text-purple-300">Server:</strong> <code>this.$auth.hasRole(&apos;admin&apos;)</code> dentro das actions</p>
324
+ <p><strong className="text-theme">Server:</strong> <code>static auth = &#123; required: true, roles: [&apos;admin&apos;] &#125;</code></p>
325
+ <p><strong className="text-theme">Server:</strong> <code>static actionAuth = &#123; deleteUser: &#123; permissions: [&apos;users.delete&apos;] &#125; &#125;</code></p>
326
+ <p><strong className="text-theme">Server:</strong> <code>this.$auth.hasRole(&apos;admin&apos;)</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(&#123; token &#125;)</code> para login</p>
329
329
  <p><strong className="text-blue-300">Client:</strong> <code>&lt;LiveComponentsProvider auth=&#123;&#123; token &#125;&#125;&gt;</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-to-r from-blue-400 via-purple-400 to-pink-400 bg-clip-text text-transparent">
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 bg-red-500/20 hover:bg-red-500/30 border border-red-500/30 text-red-300 rounded-xl transition-all disabled:opacity-50"
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 bg-gray-500/20 hover:bg-gray-500/30 border border-gray-500/30 text-gray-300 rounded-xl transition-all disabled:opacity-50"
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 bg-emerald-500/20 hover:bg-emerald-500/30 border border-emerald-500/30 text-emerald-300 rounded-xl transition-all disabled:opacity-50"
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-purple-500 border-t-transparent rounded-full animate-spin" />
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-purple-400">Room Events</code>
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 bg-red-500/20 hover:bg-red-500/30 border border-red-500/30 text-red-300 rounded-xl transition-all disabled:opacity-50"
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 bg-gray-500/20 hover:bg-gray-500/30 border border-gray-500/30 text-gray-300 rounded-xl transition-all disabled:opacity-50"
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 bg-emerald-500/20 hover:bg-emerald-500/30 border border-emerald-500/30 text-emerald-300 rounded-xl transition-all disabled:opacity-50"
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-purple-500 border-t-transparent rounded-full animate-spin" />
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 px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-all"
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-purple-400 text-xs">(sync: blur)</span>
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 px-4 py-3 bg-white/5 border border-white/20 rounded-lg text-white placeholder-gray-500 focus:border-purple-500 focus:outline-none"
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 px-4 py-3 bg-white/5 border border-white/20 rounded-lg text-white placeholder-gray-500 focus:border-purple-500 focus:outline-none"
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 px-4 py-3 bg-white/5 border border-white/20 rounded-lg text-white placeholder-gray-500 focus:border-purple-500 focus:outline-none resize-none"
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-to-r from-purple-500 to-pink-500 text-white rounded-lg font-medium hover:shadow-lg hover:shadow-purple-500/30 transition-all disabled:opacity-50"
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 bg-gray-600 text-white rounded-lg hover:bg-gray-500 transition-all"
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-purple-400">blur:</span> Sincroniza ao sair do campo</p>
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-cyan-400">msgpack</code> — round-trip latency demo
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-cyan-500/10 border border-cyan-500/20">
112
- <span className="text-xs text-cyan-300">{username}</span>
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="bg-gray-800/50 border border-white/10 rounded-xl p-4 text-center">
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="bg-gray-800/50 border border-white/10 rounded-xl p-4 text-center">
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="bg-gray-800/50 border border-white/10 rounded-xl p-4 text-center">
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-cyan-500/20 border border-cyan-500/30 text-cyan-300 text-lg font-bold hover:bg-cyan-500/30 active:scale-95 disabled:opacity-50 transition-all"
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-cyan-400">msgpack</code> (binary)
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-purple-400">LiveRoom</code> + <code className="text-cyan-400">msgpack codec</code></p>
198
- <p>Wire format: binary frames <code className="text-cyan-400">0x02</code> (event) / <code className="text-cyan-400">0x03</code> (state)</p>
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-purple-400 hover:text-purple-300"
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-purple-500/20 text-purple-300' : isJoined ? 'bg-white/5 text-gray-300 hover:bg-white/10' : 'text-gray-500 hover:bg-white/5'}
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-purple-500/30 text-purple-100' : 'bg-white/10 text-gray-200'}`}>
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-purple-500/50 text-sm sm:text-base"
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-purple-500/30 text-purple-200 hover:bg-purple-500/40 disabled:opacity-50 text-sm sm:text-base"
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-purple-500/50 text-sm"
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-purple-500/50 text-sm"
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-purple-500/30 text-purple-200 hover:bg-purple-500/40 disabled:opacity-50 text-sm"
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-purple-500/50 text-sm mb-3"
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-purple-500/30 text-purple-200 hover:bg-purple-500/40 disabled:opacity-50 text-sm"
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-purple-500/10 border border-purple-500/20">
73
- <span className="text-xs text-purple-300">{username}</span>
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-purple-500/20 rounded-full blur-3xl" />
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-purple-400">LiveRoom</code> + <code className="text-purple-400">CounterRoom</code></p>
132
- <p>Estado via component state + eventos via <code className="text-cyan-400">$room().on()</code></p>
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-to-r from-blue-400 via-purple-400 to-pink-400 bg-clip-text text-transparent">
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-purple-500/20 border-purple-500/30 text-purple-300 hover:bg-purple-500/30"
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-purple-400">api.health.get()</code> - Full type inference from server</p>
74
- <p>OK <code className="text-purple-400">api.users.post({'{ name, email }'})</code> - Request body is typed</p>
75
- <p>OK <code className="text-purple-400">{'{ data, error }'}</code> - Response is typed automatically</p>
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>