create-fluxstack 1.18.1 → 1.20.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 (69) hide show
  1. package/CHANGELOG.md +132 -0
  2. package/LLMD/INDEX.md +1 -1
  3. package/LLMD/MAINTENANCE.md +197 -197
  4. package/LLMD/MIGRATION.md +44 -1
  5. package/LLMD/agent.md +20 -7
  6. package/LLMD/config/declarative-system.md +268 -268
  7. package/LLMD/config/environment-vars.md +3 -6
  8. package/LLMD/config/runtime-reload.md +401 -401
  9. package/LLMD/core/build-system.md +599 -599
  10. package/LLMD/core/framework-lifecycle.md +249 -229
  11. package/LLMD/core/plugin-system.md +154 -100
  12. package/LLMD/patterns/anti-patterns.md +397 -397
  13. package/LLMD/patterns/project-structure.md +264 -264
  14. package/LLMD/patterns/type-safety.md +61 -5
  15. package/LLMD/reference/cli-commands.md +31 -7
  16. package/LLMD/reference/plugin-hooks.md +4 -2
  17. package/LLMD/reference/troubleshooting.md +364 -364
  18. package/LLMD/resources/controllers.md +465 -465
  19. package/LLMD/resources/live-auth.md +178 -1
  20. package/LLMD/resources/live-binary-delta.md +3 -1
  21. package/LLMD/resources/live-components.md +1192 -1041
  22. package/LLMD/resources/live-logging.md +3 -1
  23. package/LLMD/resources/live-rooms.md +1 -1
  24. package/LLMD/resources/live-upload.md +228 -181
  25. package/LLMD/resources/plugins-external.md +8 -7
  26. package/LLMD/resources/rest-auth.md +290 -290
  27. package/LLMD/resources/routes-eden.md +254 -254
  28. package/app/client/src/App.tsx +7 -7
  29. package/app/client/src/components/AppLayout.tsx +60 -23
  30. package/app/client/src/components/ColorWheel.tsx +195 -0
  31. package/app/client/src/components/DemoPage.tsx +5 -3
  32. package/app/client/src/components/LiveUploadWidget.tsx +1 -1
  33. package/app/client/src/components/ThemePicker.tsx +307 -0
  34. package/app/client/src/config/theme.config.ts +127 -0
  35. package/app/client/src/hooks/useThemeClock.ts +66 -0
  36. package/app/client/src/index.css +193 -0
  37. package/app/client/src/lib/theme-clock.ts +201 -0
  38. package/app/client/src/live/AuthDemo.tsx +9 -9
  39. package/app/client/src/live/CounterDemo.tsx +10 -10
  40. package/app/client/src/live/FormDemo.tsx +8 -8
  41. package/app/client/src/live/PingPongDemo.tsx +10 -10
  42. package/app/client/src/live/RoomChatDemo.tsx +10 -10
  43. package/app/client/src/live/SharedCounterDemo.tsx +5 -5
  44. package/app/client/src/pages/ApiTestPage.tsx +5 -5
  45. package/app/client/src/pages/HomePage.tsx +12 -12
  46. package/app/server/index.ts +8 -0
  47. package/app/server/live/auto-generated-components.ts +1 -1
  48. package/core/build/index.ts +1 -1
  49. package/core/cli/command-registry.ts +1 -1
  50. package/core/cli/commands/build.ts +25 -6
  51. package/core/cli/commands/plugin-deps.ts +1 -2
  52. package/core/cli/generators/plugin.ts +433 -581
  53. package/core/framework/server.ts +22 -8
  54. package/core/index.ts +6 -5
  55. package/core/plugins/index.ts +71 -199
  56. package/core/plugins/types.ts +76 -461
  57. package/core/server/index.ts +1 -1
  58. package/core/utils/logger/startup-banner.ts +26 -4
  59. package/create-fluxstack.ts +216 -107
  60. package/package.json +108 -107
  61. package/tsconfig.json +2 -1
  62. package/core/plugins/config.ts +0 -356
  63. package/core/plugins/dependency-manager.ts +0 -481
  64. package/core/plugins/discovery.ts +0 -379
  65. package/core/plugins/executor.ts +0 -353
  66. package/core/plugins/manager.ts +0 -645
  67. package/core/plugins/module-resolver.ts +0 -227
  68. package/core/plugins/registry.ts +0 -913
  69. package/vitest.config.live.ts +0 -69
@@ -3,6 +3,10 @@ import { Link, Outlet, useLocation } from 'react-router'
3
3
  import { FaBook, FaGithub, FaBars, FaTimes } from 'react-icons/fa'
4
4
  import FluxStackLogo from '@client/src/assets/fluxstack.svg'
5
5
  import faviconSvg from '@client/src/assets/fluxstack-static.svg?raw'
6
+ import { useThemeClock } from '../hooks/useThemeClock'
7
+ import { ThemePicker } from './ThemePicker'
8
+ import type { ColorPalette } from '../lib/theme-clock'
9
+ import { themeConfig } from '../config/theme.config'
6
10
 
7
11
  const navItems = [
8
12
  { to: '/', label: 'Home' },
@@ -36,13 +40,16 @@ const faviconUrlCache = new Map<string, string>()
36
40
  export function AppLayout() {
37
41
  const location = useLocation()
38
42
  const [menuOpen, setMenuOpen] = useState(false)
43
+ const autoTheme = useThemeClock()
44
+ const [overrideTheme, setOverrideTheme] = useState<ColorPalette | null>(null)
45
+ const theme = overrideTheme || autoTheme
39
46
 
40
47
  useEffect(() => {
41
48
  const current = navItems.find(item => item.to === location.pathname)
42
49
  document.title = current ? `${current.label} - FluxStack` : 'FluxStack'
43
50
 
44
- // Dynamic favicon with hue-rotate (cached per hue value)
45
- const hue = routeFlameHue[location.pathname] || '0deg'
51
+ // Dynamic favicon with hue-rotate based on theme clock
52
+ const hue = `${Math.round(theme.baseHue - 270)}deg`
46
53
  let url = faviconUrlCache.get(hue)
47
54
  if (!url) {
48
55
  // Evict oldest entry if cache is full, revoking blob URL to free memory
@@ -68,20 +75,27 @@ export function AppLayout() {
68
75
  }
69
76
  link.type = 'image/svg+xml'
70
77
  link.href = url
71
- }, [location.pathname])
78
+ }, [location.pathname, theme.baseHue])
72
79
 
73
80
  return (
74
- <div className="min-h-screen bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900">
75
- <header className="sticky top-0 z-50 backdrop-blur-md bg-slate-900/60 border-b border-white/10">
81
+ <div className="min-h-screen text-white flex flex-col" style={{ backgroundColor: `oklch(8% 0.02 ${theme.baseHue})` }}>
82
+ <header className="sticky top-0 z-50 backdrop-blur-xl bg-[#0a0a1a]/80 border-b border-white/[0.06]">
76
83
  <div className="container mx-auto px-4 sm:px-6 py-3 sm:py-4 flex items-center justify-between gap-4">
77
- <Link to="/" className="flex items-center gap-2 text-white font-semibold tracking-wide">
84
+ <Link to="/" className="flex items-center gap-2 font-semibold tracking-wide">
78
85
  <img
79
86
  src={FluxStackLogo}
80
87
  alt="FluxStack"
81
- className="w-9 h-9 transition-[filter] duration-500 drop-shadow-[0_0_8px_rgba(168,85,247,0.5)]"
82
- style={{ filter: `hue-rotate(${routeFlameHue[location.pathname] || '0deg'})` }}
88
+ className="w-9 h-9 transition-[filter] duration-500"
89
+ style={{
90
+ filter: `hue-rotate(${theme.baseHue - 270}deg) drop-shadow(0 0 8px ${theme.primaryGlow})`,
91
+ }}
83
92
  />
84
- FluxStack
93
+ <span
94
+ className="bg-clip-text text-transparent"
95
+ style={{ backgroundImage: theme.gradientPrimary }}
96
+ >
97
+ FluxStack
98
+ </span>
85
99
  </Link>
86
100
 
87
101
  {/* Desktop nav */}
@@ -94,9 +108,13 @@ export function AppLayout() {
94
108
  to={item.to}
95
109
  className={`px-3 py-1.5 rounded-lg text-sm transition-all ${
96
110
  active
97
- ? 'bg-white/15 text-white'
98
- : 'text-gray-300 hover:bg-white/10'
111
+ ? 'font-medium'
112
+ : 'text-gray-400 hover:text-white hover:bg-white/[0.05]'
99
113
  }`}
114
+ style={active ? {
115
+ backgroundColor: theme.primaryMuted,
116
+ color: theme.textPrimary,
117
+ } : undefined}
100
118
  >
101
119
  {item.label}
102
120
  </Link>
@@ -109,7 +127,7 @@ export function AppLayout() {
109
127
  href="https://live-docs.marcosbrendon.com/"
110
128
  target="_blank"
111
129
  rel="noopener noreferrer"
112
- className="hidden sm:inline-flex items-center gap-2 px-3 py-1.5 bg-purple-500/20 border border-purple-500/30 text-purple-200 rounded-lg text-sm hover:bg-purple-500/30 transition-all"
130
+ className="hidden sm:inline-flex items-center gap-2 px-3 py-1.5 bg-purple-500/20 border border-purple-500/20 text-purple-300 rounded-xl text-sm hover:bg-purple-500/30 transition-all"
113
131
  >
114
132
  <FaBook />
115
133
  Live Docs
@@ -118,7 +136,7 @@ export function AppLayout() {
118
136
  href="/swagger"
119
137
  target="_blank"
120
138
  rel="noopener noreferrer"
121
- className="hidden sm:inline-flex items-center gap-2 px-3 py-1.5 bg-white/10 border border-white/20 text-white rounded-lg text-sm hover:bg-white/20 transition-all"
139
+ className="hidden sm:inline-flex items-center gap-2 px-3 py-1.5 bg-white/[0.03] border border-white/[0.06] text-gray-400 rounded-xl text-sm hover:bg-white/[0.06] hover:text-white transition-all"
122
140
  >
123
141
  <FaBook />
124
142
  API Docs
@@ -127,7 +145,7 @@ export function AppLayout() {
127
145
  href="https://github.com/MarcosBrendonDePaula/FluxStack"
128
146
  target="_blank"
129
147
  rel="noopener noreferrer"
130
- className="hidden sm:inline-flex items-center gap-2 px-3 py-1.5 bg-white/10 border border-white/20 text-white rounded-lg text-sm hover:bg-white/20 transition-all"
148
+ className="hidden sm:inline-flex items-center gap-2 px-3 py-1.5 bg-white/[0.03] border border-white/[0.06] text-gray-400 rounded-xl text-sm hover:bg-white/[0.06] hover:text-white transition-all"
131
149
  >
132
150
  <FaGithub />
133
151
  GitHub
@@ -136,7 +154,7 @@ export function AppLayout() {
136
154
  {/* Mobile menu toggle */}
137
155
  <button
138
156
  onClick={() => setMenuOpen(!menuOpen)}
139
- className="md:hidden p-2 text-gray-300 hover:text-white transition-colors"
157
+ className="md:hidden p-2 text-gray-400 hover:text-white transition-colors"
140
158
  aria-label="Toggle menu"
141
159
  >
142
160
  {menuOpen ? <FaTimes size={20} /> : <FaBars size={20} />}
@@ -146,7 +164,7 @@ export function AppLayout() {
146
164
 
147
165
  {/* Mobile nav */}
148
166
  {menuOpen && (
149
- <div className="md:hidden border-t border-white/10 bg-slate-900/90 backdrop-blur-md">
167
+ <div className="md:hidden border-t border-white/[0.06] bg-[#0a0a1a]/90 backdrop-blur-xl">
150
168
  <nav className="container mx-auto px-4 py-3 flex gap-4 relative">
151
169
  <div className="flex flex-col gap-1 flex-1">
152
170
  {navItems.map((item) => {
@@ -158,20 +176,24 @@ export function AppLayout() {
158
176
  onClick={() => setMenuOpen(false)}
159
177
  className={`px-3 py-2 rounded-lg text-sm transition-all ${
160
178
  active
161
- ? 'bg-white/15 text-white'
162
- : 'text-gray-300 hover:bg-white/10'
179
+ ? 'font-medium'
180
+ : 'text-gray-400 hover:text-white hover:bg-white/[0.05]'
163
181
  }`}
182
+ style={active ? {
183
+ backgroundColor: theme.primaryMuted,
184
+ color: theme.textPrimary,
185
+ } : undefined}
164
186
  >
165
187
  {item.label}
166
188
  </Link>
167
189
  )
168
190
  })}
169
- <div className="flex flex-wrap gap-2 mt-2 pt-2 border-t border-white/10">
191
+ <div className="flex flex-wrap gap-2 mt-2 pt-2 border-t border-white/[0.06]">
170
192
  <a
171
193
  href="https://live-docs.marcosbrendon.com/"
172
194
  target="_blank"
173
195
  rel="noopener noreferrer"
174
- className="inline-flex items-center gap-2 px-3 py-2 bg-purple-500/20 border border-purple-500/30 text-purple-200 rounded-lg text-sm hover:bg-purple-500/30 transition-all"
196
+ className="inline-flex items-center gap-2 px-3 py-2 bg-purple-500/20 border border-purple-500/20 text-purple-300 rounded-xl text-sm hover:bg-purple-500/30 transition-all"
175
197
  >
176
198
  <FaBook />
177
199
  Live Docs
@@ -180,7 +202,7 @@ export function AppLayout() {
180
202
  href="/swagger"
181
203
  target="_blank"
182
204
  rel="noopener noreferrer"
183
- className="inline-flex items-center gap-2 px-3 py-2 bg-white/10 border border-white/20 text-white rounded-lg text-sm hover:bg-white/20 transition-all"
205
+ className="inline-flex items-center gap-2 px-3 py-2 bg-white/[0.03] border border-white/[0.06] text-gray-400 rounded-xl text-sm hover:bg-white/[0.06] hover:text-white transition-all"
184
206
  >
185
207
  <FaBook />
186
208
  API Docs
@@ -189,7 +211,7 @@ export function AppLayout() {
189
211
  href="https://github.com/MarcosBrendonDePaula/FluxStack"
190
212
  target="_blank"
191
213
  rel="noopener noreferrer"
192
- className="inline-flex items-center gap-2 px-3 py-2 bg-white/10 border border-white/20 text-white rounded-lg text-sm hover:bg-white/20 transition-all"
214
+ className="inline-flex items-center gap-2 px-3 py-2 bg-white/[0.03] border border-white/[0.06] text-gray-400 rounded-xl text-sm hover:bg-white/[0.06] hover:text-white transition-all"
193
215
  >
194
216
  <FaGithub />
195
217
  GitHub
@@ -209,7 +231,22 @@ export function AppLayout() {
209
231
  )}
210
232
  </header>
211
233
 
212
- <Outlet />
234
+ <main className="flex-1">
235
+ <Outlet />
236
+ </main>
237
+
238
+ <footer className="border-t border-white/[0.06] py-6 mt-auto">
239
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
240
+ <p className="text-gray-500 text-sm">
241
+ Built with <span style={{ color: theme.primary }}>FluxStack</span> — Bun + Elysia + React
242
+ </p>
243
+ <p className="text-gray-600 text-xs mt-1">
244
+ 🎨 <span style={{ color: theme.primary }}>{theme.period}</span> palette — colors shift with the time of day
245
+ </p>
246
+ </div>
247
+ </footer>
248
+
249
+ {themeConfig.showPicker && <ThemePicker palette={theme} onOverride={setOverrideTheme} />}
213
250
  </div>
214
251
  )
215
252
  }
@@ -0,0 +1,195 @@
1
+ /**
2
+ * ColorWheel — Interactive color wheel like Adobe Color
3
+ *
4
+ * Modes:
5
+ * - Preset harmonies: all dots move together based on base hue
6
+ * - Custom: each dot is independently draggable
7
+ */
8
+ import { useRef, useState, useCallback, useEffect } from 'react'
9
+
10
+ export type HarmonyMode = 'analogous' | 'complementary' | 'triadic' | 'split-complementary' | 'square' | 'monochromatic' | 'custom'
11
+
12
+ interface ColorWheelProps {
13
+ /** Hues for each point: [primary, secondary, tertiary, complement, accent] */
14
+ hues: number[]
15
+ mode: HarmonyMode
16
+ size?: number
17
+ /** Called when any hue changes. index = which point, hue = new value */
18
+ onChange: (index: number, hue: number) => void
19
+ }
20
+
21
+ const POINT_LABELS = ['P', 'S', 'T', 'C', 'A']
22
+
23
+ /** Get harmony offsets for preset modes */
24
+ function getHarmonyOffsets(mode: HarmonyMode): number[] {
25
+ switch (mode) {
26
+ case 'analogous': return [0, -30, 30, -60, 60]
27
+ case 'complementary': return [0, 180]
28
+ case 'triadic': return [0, 120, 240]
29
+ case 'split-complementary': return [0, 150, 210]
30
+ case 'square': return [0, 90, 180, 270]
31
+ case 'monochromatic': return [0]
32
+ case 'custom': return [0, 40, -30, 180, 120] // defaults, but each is independent
33
+ }
34
+ }
35
+
36
+ /** Get number of points for each mode */
37
+ export function getPointCount(mode: HarmonyMode): number {
38
+ return getHarmonyOffsets(mode).length
39
+ }
40
+
41
+ /** Generate initial hues from base hue and mode */
42
+ export function generateHuesFromMode(baseHue: number, mode: HarmonyMode): number[] {
43
+ return getHarmonyOffsets(mode).map(o => ((baseHue + o) % 360 + 360) % 360)
44
+ }
45
+
46
+ function hueToXY(hue: number, radius: number, cx: number, cy: number) {
47
+ const rad = ((hue - 90) * Math.PI) / 180
48
+ return { x: cx + radius * Math.cos(rad), y: cy + radius * Math.sin(rad) }
49
+ }
50
+
51
+ function xyToHue(x: number, y: number, cx: number, cy: number): number {
52
+ return ((Math.atan2(y - cy, x - cx) * 180) / Math.PI + 90 + 360) % 360
53
+ }
54
+
55
+ export function ColorWheel({ hues, mode, size = 220, onChange }: ColorWheelProps) {
56
+ const svgRef = useRef<SVGSVGElement>(null)
57
+ const [dragging, setDragging] = useState<number | null>(null) // index of dragged point
58
+
59
+ const cx = size / 2
60
+ const cy = size / 2
61
+ const outerR = size / 2 - 8
62
+ const innerR = outerR - 24
63
+ const dotR = outerR - 12
64
+
65
+ const isCustom = mode === 'custom'
66
+
67
+ const getMouseHue = useCallback((e: MouseEvent | React.MouseEvent) => {
68
+ const svg = svgRef.current
69
+ if (!svg) return 0
70
+ const rect = svg.getBoundingClientRect()
71
+ return xyToHue(e.clientX - rect.left, e.clientY - rect.top, cx, cy)
72
+ }, [cx, cy])
73
+
74
+ // Mouse down on a specific point
75
+ const handlePointDown = useCallback((e: React.MouseEvent, index: number) => {
76
+ e.preventDefault()
77
+ e.stopPropagation()
78
+ setDragging(index)
79
+ }, [])
80
+
81
+ // Mouse down on wheel background — moves the base (index 0)
82
+ // Click on wheel background = rotate ALL points together (preserving relative positions)
83
+ const handleWheelDown = useCallback((e: React.MouseEvent) => {
84
+ e.preventDefault()
85
+ setDragging(-1) // -1 = rotate all
86
+ const hue = getMouseHue(e)
87
+ const delta = hue - hues[0]
88
+ for (let i = 0; i < hues.length; i++) {
89
+ onChange(i, ((hues[i] + delta) % 360 + 360) % 360)
90
+ }
91
+ }, [getMouseHue, hues, onChange])
92
+
93
+ useEffect(() => {
94
+ if (dragging === null) return
95
+
96
+ const handleMove = (e: MouseEvent) => {
97
+ const hue = getMouseHue(e)
98
+ if (dragging === -1) {
99
+ // Rotate all points together (background drag)
100
+ const delta = hue - hues[0]
101
+ for (let i = 0; i < hues.length; i++) {
102
+ onChange(i, ((hues[i] + delta) % 360 + 360) % 360)
103
+ }
104
+ } else if (isCustom) {
105
+ // Custom mode: move only the dragged point
106
+ onChange(dragging, hue)
107
+ } else {
108
+ // Preset mode: move all points relative to base
109
+ const delta = hue - hues[0]
110
+ for (let i = 0; i < hues.length; i++) {
111
+ onChange(i, ((hues[i] + delta) % 360 + 360) % 360)
112
+ }
113
+ }
114
+ }
115
+ const handleUp = () => setDragging(null)
116
+
117
+ window.addEventListener('mousemove', handleMove)
118
+ window.addEventListener('mouseup', handleUp)
119
+ return () => {
120
+ window.removeEventListener('mousemove', handleMove)
121
+ window.removeEventListener('mouseup', handleUp)
122
+ }
123
+ }, [dragging, getMouseHue, hues, isCustom, onChange])
124
+
125
+ return (
126
+ <svg
127
+ ref={svgRef}
128
+ width={size}
129
+ height={size}
130
+ onMouseDown={handleWheelDown}
131
+ style={{ cursor: dragging !== null ? 'grabbing' : 'pointer', userSelect: 'none' }}
132
+ >
133
+ {/* Hue ring segments */}
134
+ {Array.from({ length: 72 }, (_, i) => {
135
+ const a1 = (i / 72) * 360
136
+ const a2 = ((i + 1) / 72) * 360
137
+ const r1 = ((a1 - 90) * Math.PI) / 180
138
+ const r2 = ((a2 - 90) * Math.PI) / 180
139
+ return (
140
+ <path
141
+ key={i}
142
+ d={`M ${cx + outerR * Math.cos(r1)} ${cy + outerR * Math.sin(r1)} A ${outerR} ${outerR} 0 0 1 ${cx + outerR * Math.cos(r2)} ${cy + outerR * Math.sin(r2)} L ${cx + innerR * Math.cos(r2)} ${cy + innerR * Math.sin(r2)} A ${innerR} ${innerR} 0 0 0 ${cx + innerR * Math.cos(r1)} ${cy + innerR * Math.sin(r1)} Z`}
143
+ fill={`oklch(70% 0.22 ${a1})`}
144
+ />
145
+ )
146
+ })}
147
+
148
+ {/* Inner dark circle */}
149
+ <circle cx={cx} cy={cy} r={innerR - 2} fill="#0a0a1a" />
150
+
151
+ {/* Connection lines */}
152
+ {hues.length > 1 && (
153
+ <polygon
154
+ points={hues.map(h => {
155
+ const p = hueToXY(h, dotR * 0.55, cx, cy)
156
+ return `${p.x},${p.y}`
157
+ }).join(' ')}
158
+ fill={`oklch(65% 0.15 ${hues[0]} / 0.08)`}
159
+ stroke="rgba(255,255,255,0.12)"
160
+ strokeWidth="1"
161
+ />
162
+ )}
163
+
164
+ {/* Center color preview */}
165
+ <circle cx={cx} cy={cy} r={innerR * 0.35} fill={`oklch(65% 0.25 ${hues[0]})`} opacity="0.8" />
166
+ <text x={cx} y={cy - 6} textAnchor="middle" dominantBaseline="central" fill="white" fontSize="11" fontWeight="700">
167
+ {Math.round(hues[0])}°
168
+ </text>
169
+ <text x={cx} y={cy + 8} textAnchor="middle" dominantBaseline="central" fill="rgba(255,255,255,0.5)" fontSize="8">
170
+ {isCustom ? 'CUSTOM' : mode.toUpperCase()}
171
+ </text>
172
+
173
+ {/* Harmony dots — each draggable in custom mode */}
174
+ {hues.map((hue, i) => {
175
+ const pos = hueToXY(hue, dotR, cx, cy)
176
+ const isBase = i === 0
177
+ const isDraggable = isCustom || isBase
178
+ const r = isBase ? 9 : 7
179
+ return (
180
+ <g
181
+ key={i}
182
+ onMouseDown={isDraggable ? (e) => handlePointDown(e, i) : undefined}
183
+ style={{ cursor: isDraggable ? 'grab' : 'default' }}
184
+ >
185
+ <circle cx={pos.x} cy={pos.y} r={r + 3} fill={`oklch(65% 0.25 ${hue} / 0.3)`} />
186
+ <circle cx={pos.x} cy={pos.y} r={r} fill={`oklch(65% 0.25 ${hue})`} stroke="white" strokeWidth={isBase ? 2.5 : 1.5} />
187
+ <text x={pos.x} y={pos.y} textAnchor="middle" dominantBaseline="central" fill="white" fontSize="7" fontWeight="600" pointerEvents="none">
188
+ {POINT_LABELS[i] || ''}
189
+ </text>
190
+ </g>
191
+ )
192
+ })}
193
+ </svg>
194
+ )
195
+ }
@@ -11,9 +11,11 @@ export function DemoPage({ children, note }: { children: ReactNode; note?: React
11
11
  {children}
12
12
  </div>
13
13
  {note && (
14
- <p className="mt-4 sm:mt-6 text-gray-400 text-xs sm:text-sm max-w-md text-center px-2">
15
- {note}
16
- </p>
14
+ <div className="mt-4 sm:mt-6 bg-theme-accent border border-theme rounded-xl px-4 py-3 max-w-md text-center">
15
+ <p className="text-gray-400 text-xs sm:text-sm">
16
+ {note}
17
+ </p>
18
+ </div>
17
19
  )}
18
20
  </div>
19
21
  )
@@ -168,7 +168,7 @@ export function LiveUploadWidget({
168
168
  </div>
169
169
  <div className="w-full h-3 bg-white/10 rounded-full overflow-hidden">
170
170
  <div
171
- className="h-full bg-gradient-to-r from-blue-500 to-purple-500 transition-all"
171
+ className="h-full bg-theme-gradient transition-all"
172
172
  style={{ width: `${live.$state.progress}%` }}
173
173
  />
174
174
  </div>