create-fluxstack 1.13.0 → 1.14.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/LLMD/patterns/anti-patterns.md +100 -0
- package/LLMD/reference/routing.md +39 -39
- package/LLMD/resources/live-auth.md +20 -2
- package/LLMD/resources/live-components.md +94 -10
- package/LLMD/resources/live-logging.md +95 -33
- package/LLMD/resources/live-upload.md +59 -8
- package/app/client/index.html +2 -2
- package/app/client/public/favicon.svg +46 -0
- package/app/client/src/App.tsx +2 -1
- package/app/client/src/assets/fluxstack-static.svg +46 -0
- package/app/client/src/assets/fluxstack.svg +183 -0
- package/app/client/src/components/AppLayout.tsx +138 -9
- package/app/client/src/components/BackButton.tsx +13 -13
- package/app/client/src/components/DemoPage.tsx +4 -4
- package/app/client/src/live/AuthDemo.tsx +23 -21
- package/app/client/src/live/ChatDemo.tsx +2 -2
- package/app/client/src/live/CounterDemo.tsx +12 -12
- package/app/client/src/live/FormDemo.tsx +2 -2
- package/app/client/src/live/LiveDebuggerPanel.tsx +779 -0
- package/app/client/src/live/RoomChatDemo.tsx +24 -16
- package/app/client/src/main.tsx +13 -13
- package/app/client/src/pages/ApiTestPage.tsx +6 -6
- package/app/client/src/pages/HomePage.tsx +80 -52
- package/app/server/live/LiveAdminPanel.ts +1 -0
- package/app/server/live/LiveChat.ts +78 -77
- package/app/server/live/LiveCounter.ts +1 -1
- package/app/server/live/LiveForm.ts +1 -0
- package/app/server/live/LiveLocalCounter.ts +38 -37
- package/app/server/live/LiveProtectedChat.ts +1 -0
- package/app/server/live/LiveRoomChat.ts +1 -0
- package/app/server/live/LiveUpload.ts +1 -0
- package/app/server/live/register-components.ts +19 -19
- package/config/system/runtime.config.ts +4 -0
- package/core/build/optimizer.ts +235 -235
- package/core/client/components/Live.tsx +17 -11
- package/core/client/components/LiveDebugger.tsx +1324 -0
- package/core/client/hooks/AdaptiveChunkSizer.ts +215 -215
- package/core/client/hooks/useLiveComponent.ts +11 -1
- package/core/client/hooks/useLiveDebugger.ts +392 -0
- package/core/client/index.ts +14 -0
- package/core/plugins/built-in/index.ts +134 -134
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +4 -0
- package/core/plugins/built-in/vite/index.ts +75 -21
- package/core/server/index.ts +15 -15
- package/core/server/live/ComponentRegistry.ts +55 -26
- package/core/server/live/FileUploadManager.ts +188 -24
- package/core/server/live/LiveDebugger.ts +462 -0
- package/core/server/live/LiveLogger.ts +38 -5
- package/core/server/live/LiveRoomManager.ts +17 -1
- package/core/server/live/StateSignature.ts +87 -27
- package/core/server/live/WebSocketConnectionManager.ts +11 -10
- package/core/server/live/auto-generated-components.ts +1 -1
- package/core/server/live/websocket-plugin.ts +233 -8
- package/core/server/plugins/static-files-plugin.ts +179 -69
- package/core/types/build.ts +219 -219
- package/core/types/plugin.ts +107 -107
- package/core/types/types.ts +145 -9
- package/core/utils/logger/startup-banner.ts +82 -82
- package/core/utils/version.ts +6 -6
- package/package.json +1 -1
- package/app/client/src/assets/react.svg +0 -1
|
@@ -1,56 +1,100 @@
|
|
|
1
1
|
# Live Logging
|
|
2
2
|
|
|
3
|
-
**Version:** 1.12.
|
|
3
|
+
**Version:** 1.12.1 | **Updated:** 2025-02-22
|
|
4
4
|
|
|
5
5
|
## Quick Facts
|
|
6
6
|
|
|
7
7
|
- Per-component logging control — silent by default
|
|
8
|
-
-
|
|
8
|
+
- Two output channels: **console** (`LIVE_LOGGING`) and **debug panel** (`DEBUG_LIVE`)
|
|
9
|
+
- Both off by default — opt-in only
|
|
9
10
|
- 6 categories: `lifecycle`, `messages`, `state`, `performance`, `rooms`, `websocket`
|
|
10
|
-
- Global (non-component) logs controlled by `LIVE_LOGGING` env var
|
|
11
11
|
- `console.error` always visible regardless of config
|
|
12
|
+
- All `liveLog`/`liveWarn` calls are forwarded to the Live Debugger as `LOG` events when `DEBUG_LIVE=true`
|
|
12
13
|
|
|
13
|
-
##
|
|
14
|
+
## Two Logging Channels
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
| Channel | Env Var | Default | Purpose |
|
|
17
|
+
|---------|---------|---------|---------|
|
|
18
|
+
| **Console** | `LIVE_LOGGING` | `false` | Server terminal output |
|
|
19
|
+
| **Debug Panel** | `DEBUG_LIVE` | `false` | Live Debugger WebSocket stream |
|
|
20
|
+
|
|
21
|
+
The debug panel receives **all** `liveLog`/`liveWarn` calls as `LOG` events (with `category`, `level`, `message`, and `details`) regardless of the `LIVE_LOGGING` console setting. This keeps the console clean while making everything visible in the debug panel.
|
|
22
|
+
|
|
23
|
+
### Recommended Workflow
|
|
24
|
+
|
|
25
|
+
- **Normal development**: both off — clean console, no debug overhead
|
|
26
|
+
- **Debugging live components**: `DEBUG_LIVE=true` — open the debug panel at `/api/live/debug/ws`
|
|
27
|
+
- **Quick console debugging**: `LIVE_LOGGING=lifecycle,state` — targeted categories to console
|
|
28
|
+
- **Per-component debugging**: `static logging = true` on the specific component class
|
|
29
|
+
|
|
30
|
+
## Console Logging
|
|
31
|
+
|
|
32
|
+
### Per-Component (static logging)
|
|
16
33
|
|
|
17
34
|
```typescript
|
|
18
|
-
// app/server/live/
|
|
19
|
-
export class
|
|
20
|
-
static componentName = '
|
|
35
|
+
// app/server/live/LiveChat.ts
|
|
36
|
+
export class LiveChat extends LiveComponent<typeof LiveChat.defaultState> {
|
|
37
|
+
static componentName = 'LiveChat'
|
|
21
38
|
|
|
22
|
-
// ✅ All categories
|
|
39
|
+
// ✅ All categories to console
|
|
23
40
|
static logging = true
|
|
24
41
|
|
|
25
42
|
// ✅ Specific categories only
|
|
26
|
-
static logging = ['lifecycle', '
|
|
43
|
+
static logging = ['lifecycle', 'rooms'] as const
|
|
27
44
|
|
|
28
45
|
// ✅ Silent (default — omit property or set false)
|
|
29
|
-
// static logging
|
|
46
|
+
// No static logging needed
|
|
30
47
|
}
|
|
31
48
|
```
|
|
32
49
|
|
|
33
|
-
### Global
|
|
50
|
+
### Global (LIVE_LOGGING env var)
|
|
34
51
|
|
|
35
|
-
Logs not tied to a specific component (
|
|
52
|
+
Logs not tied to a specific component (connection cleanup, key rotation, etc.):
|
|
36
53
|
|
|
37
54
|
```bash
|
|
38
55
|
# .env
|
|
39
|
-
LIVE_LOGGING=true # All global logs
|
|
56
|
+
LIVE_LOGGING=true # All global logs to console
|
|
40
57
|
LIVE_LOGGING=lifecycle,rooms # Specific categories only
|
|
41
58
|
# (unset or 'false') # Silent (default)
|
|
42
59
|
```
|
|
43
60
|
|
|
61
|
+
## Debug Panel (DEBUG_LIVE)
|
|
62
|
+
|
|
63
|
+
When `DEBUG_LIVE=true`, all `liveLog`/`liveWarn` calls emit `LOG` events to the Live Debugger, regardless of `LIVE_LOGGING` or `static logging` settings.
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# .env
|
|
67
|
+
DEBUG_LIVE=true # Enable debug panel events
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Each `LOG` event contains:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
{
|
|
74
|
+
type: 'LOG',
|
|
75
|
+
componentId: string | null,
|
|
76
|
+
componentName: null,
|
|
77
|
+
data: {
|
|
78
|
+
category: 'lifecycle' | 'messages' | 'state' | 'performance' | 'rooms' | 'websocket',
|
|
79
|
+
level: 'info' | 'warn',
|
|
80
|
+
message: string,
|
|
81
|
+
details?: unknown // Extra args passed to liveLog/liveWarn
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The debug panel also receives all other debug events (`COMPONENT_MOUNT`, `STATE_CHANGE`, `ACTION_CALL`, etc.) — see [Live Components](./live-components.md) for the full event list.
|
|
87
|
+
|
|
44
88
|
## Categories
|
|
45
89
|
|
|
46
90
|
| Category | What It Logs |
|
|
47
91
|
|----------|-------------|
|
|
48
92
|
| `lifecycle` | Mount, unmount, rehydration, recovery, migration |
|
|
49
|
-
| `messages` | Received/sent WebSocket messages, file uploads |
|
|
93
|
+
| `messages` | Received/sent WebSocket messages, file uploads, queue operations |
|
|
50
94
|
| `state` | Signing, backup, compression, encryption, validation |
|
|
51
95
|
| `performance` | Monitoring init, alerts, optimization suggestions |
|
|
52
96
|
| `rooms` | Room create/join/leave, emit, broadcast |
|
|
53
|
-
| `websocket` | Connection open/close, auth |
|
|
97
|
+
| `websocket` | Connection open/close/cleanup, pool management, auth |
|
|
54
98
|
|
|
55
99
|
## Type Definition
|
|
56
100
|
|
|
@@ -69,12 +113,12 @@ static logging = ['lifecycle', 'messages'] as const
|
|
|
69
113
|
|
|
70
114
|
## API (Framework Internal)
|
|
71
115
|
|
|
72
|
-
These functions are used by the framework — app developers only need `static logging
|
|
116
|
+
These functions are used by the framework — app developers only need `static logging` or env vars:
|
|
73
117
|
|
|
74
118
|
```typescript
|
|
75
119
|
import { liveLog, liveWarn, registerComponentLogging, unregisterComponentLogging } from '@core/server/live'
|
|
76
120
|
|
|
77
|
-
// Log gated by component config
|
|
121
|
+
// Log gated by component config (console) + always forwarded to debug panel
|
|
78
122
|
liveLog('lifecycle', componentId, '🚀 Mounted component')
|
|
79
123
|
liveLog('rooms', componentId, `📡 Joined room '${roomId}'`)
|
|
80
124
|
|
|
@@ -89,70 +133,88 @@ unregisterComponentLogging(componentId)
|
|
|
89
133
|
## How It Works
|
|
90
134
|
|
|
91
135
|
1. **Mount**: `ComponentRegistry` reads `static logging` from the class and calls `registerComponentLogging(componentId, config)`
|
|
92
|
-
2. **Runtime**: All `liveLog()`/`liveWarn()` calls
|
|
136
|
+
2. **Runtime**: All `liveLog()`/`liveWarn()` calls:
|
|
137
|
+
- Forward to the Live Debugger as `LOG` events (when `DEBUG_LIVE=true`)
|
|
138
|
+
- Check the registry before emitting to console (when `LIVE_LOGGING` or `static logging` is active)
|
|
93
139
|
3. **Unmount**: `unregisterComponentLogging(componentId)` removes the entry
|
|
94
140
|
4. **Global logs**: Fall back to `LIVE_LOGGING` env var when `componentId` is `null`
|
|
95
141
|
|
|
96
142
|
## Examples
|
|
97
143
|
|
|
98
|
-
### Debug
|
|
144
|
+
### Debug via Panel (Recommended)
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# .env
|
|
148
|
+
DEBUG_LIVE=true
|
|
149
|
+
# No LIVE_LOGGING needed — console stays clean
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Open the debug panel WebSocket at `/api/live/debug/ws` to see all events in real-time.
|
|
153
|
+
|
|
154
|
+
### Debug a Specific Component (Console)
|
|
99
155
|
|
|
100
156
|
```typescript
|
|
101
|
-
// Only this component will show logs
|
|
157
|
+
// Only this component will show console logs
|
|
102
158
|
export class LiveChat extends LiveComponent<typeof LiveChat.defaultState> {
|
|
103
159
|
static componentName = 'LiveChat'
|
|
104
|
-
static logging = true // See everything for this component
|
|
160
|
+
static logging = true // See everything for this component in console
|
|
105
161
|
}
|
|
106
162
|
|
|
107
|
-
// All other components remain silent
|
|
163
|
+
// All other components remain silent in console
|
|
108
164
|
export class LiveCounter extends LiveComponent<typeof LiveCounter.defaultState> {
|
|
109
165
|
static componentName = 'LiveCounter'
|
|
110
|
-
// No static logging → silent
|
|
166
|
+
// No static logging → silent in console
|
|
111
167
|
}
|
|
112
168
|
```
|
|
113
169
|
|
|
114
|
-
### Monitor Only Room Activity
|
|
170
|
+
### Monitor Only Room Activity (Console)
|
|
115
171
|
|
|
116
172
|
```typescript
|
|
117
173
|
export class LiveChat extends LiveComponent<typeof LiveChat.defaultState> {
|
|
118
174
|
static componentName = 'LiveChat'
|
|
119
|
-
static logging = ['rooms'] as const // Only room events
|
|
175
|
+
static logging = ['rooms'] as const // Only room events in console
|
|
120
176
|
}
|
|
121
177
|
```
|
|
122
178
|
|
|
123
179
|
### Production: Silent Everywhere
|
|
124
180
|
|
|
125
181
|
```bash
|
|
126
|
-
# .env (no LIVE_LOGGING
|
|
127
|
-
#
|
|
128
|
-
#
|
|
182
|
+
# .env (no LIVE_LOGGING, no DEBUG_LIVE)
|
|
183
|
+
# Console: silent
|
|
184
|
+
# Debug panel: disabled
|
|
129
185
|
```
|
|
130
186
|
|
|
131
187
|
## Files Reference
|
|
132
188
|
|
|
133
189
|
| File | Purpose |
|
|
134
190
|
|------|---------|
|
|
135
|
-
| `core/server/live/LiveLogger.ts` | Logger implementation, registry, shouldLog logic |
|
|
136
|
-
| `core/server/live/
|
|
191
|
+
| `core/server/live/LiveLogger.ts` | Logger implementation, registry, shouldLog logic, debugger forwarding |
|
|
192
|
+
| `core/server/live/LiveDebugger.ts` | Debug event bus, `LOG` event type, debug client management |
|
|
193
|
+
| `core/server/live/ComponentRegistry.ts` | Reads `static logging` on mount/unmount, uses `liveLog` |
|
|
137
194
|
| `core/server/live/websocket-plugin.ts` | Uses `liveLog` for WebSocket events |
|
|
195
|
+
| `core/server/live/WebSocketConnectionManager.ts` | Uses `liveLog`/`liveWarn` for connection pool management |
|
|
196
|
+
| `core/server/live/FileUploadManager.ts` | Uses `liveLog`/`liveWarn` for upload operations |
|
|
138
197
|
| `core/server/live/StateSignature.ts` | Uses `liveLog`/`liveWarn` for state operations |
|
|
139
198
|
| `core/server/live/LiveRoomManager.ts` | Uses `liveLog` for room lifecycle |
|
|
140
199
|
| `core/server/live/LiveComponentPerformanceMonitor.ts` | Uses `liveLog`/`liveWarn` for perf |
|
|
200
|
+
| `config/system/runtime.config.ts` | `DEBUG_LIVE` env var config |
|
|
141
201
|
| `core/types/types.ts` | `LiveComponent` base class with `static logging` property |
|
|
142
202
|
|
|
143
203
|
## Critical Rules
|
|
144
204
|
|
|
145
205
|
**ALWAYS:**
|
|
146
206
|
- Use `as const` on logging arrays for type safety
|
|
147
|
-
- Keep components silent by default
|
|
207
|
+
- Keep components silent by default (no `static logging`)
|
|
208
|
+
- Use `DEBUG_LIVE=true` for debugging instead of `static logging` on components
|
|
148
209
|
- Use specific categories instead of `true` when possible
|
|
149
210
|
|
|
150
211
|
**NEVER:**
|
|
151
212
|
- Use `console.log` directly in Live Component code — use `liveLog()`
|
|
152
213
|
- Forget that `console.error` is always visible (not gated)
|
|
214
|
+
- Enable `LIVE_LOGGING` or `DEBUG_LIVE` in production
|
|
153
215
|
|
|
154
216
|
## Related
|
|
155
217
|
|
|
156
218
|
- [Live Components](./live-components.md) - Base component system
|
|
157
219
|
- [Live Rooms](./live-rooms.md) - Room system (logged under `rooms` category)
|
|
158
|
-
- [Environment Variables](../config/environment-vars.md) - `LIVE_LOGGING` reference
|
|
220
|
+
- [Environment Variables](../config/environment-vars.md) - `LIVE_LOGGING` and `DEBUG_LIVE` reference
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Live Upload (Chunked Upload via WebSocket)
|
|
2
2
|
|
|
3
|
-
**Version:** 1.
|
|
3
|
+
**Version:** 1.14.0 | **Updated:** 2025-02-21
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
@@ -8,6 +8,19 @@ FluxStack supports chunked file upload over the Live Components WebSocket. The
|
|
|
8
8
|
server tracks progress and assembles the file in `uploads/`. The client streams
|
|
9
9
|
chunks without loading the entire file into memory.
|
|
10
10
|
|
|
11
|
+
## Security Features
|
|
12
|
+
|
|
13
|
+
The upload system includes multiple layers of security:
|
|
14
|
+
|
|
15
|
+
- **MIME type allowlist** - Only safe file types accepted (images, PDF, text, JSON, archives)
|
|
16
|
+
- **Extension blocklist** - 31 dangerous extensions blocked (.exe, .bat, .sh, .dll, etc.)
|
|
17
|
+
- **Double extension prevention** - Detects `malware.exe.jpg` style attacks
|
|
18
|
+
- **Magic bytes validation** - Verifies actual file content matches claimed MIME type
|
|
19
|
+
- **Per-user upload quota** - 500MB/day per user to prevent disk exhaustion
|
|
20
|
+
- **File size limit** - 50MB max per file
|
|
21
|
+
- **Filename sanitization** - Path traversal prevention via `path.basename()`
|
|
22
|
+
- **Stale upload cleanup** - Abandoned uploads removed after 60 seconds
|
|
23
|
+
|
|
11
24
|
## Server: LiveUpload Component
|
|
12
25
|
|
|
13
26
|
```typescript
|
|
@@ -18,6 +31,8 @@ import { liveUploadDefaultState, type LiveUploadState } from '@app/shared'
|
|
|
18
31
|
export const defaultState: LiveUploadState = liveUploadDefaultState
|
|
19
32
|
|
|
20
33
|
export class LiveUpload extends LiveComponent<LiveUploadState> {
|
|
34
|
+
static componentName = 'LiveUpload'
|
|
35
|
+
static publicActions = ['startUpload', 'updateProgress', 'completeUpload', 'failUpload', 'reset'] as const
|
|
21
36
|
static defaultState = defaultState
|
|
22
37
|
|
|
23
38
|
constructor(initialState: Partial<typeof defaultState>, ws: any, options?: { room?: string; userId?: string }) {
|
|
@@ -109,22 +124,58 @@ export function UploadDemo() {
|
|
|
109
124
|
|
|
110
125
|
1. Client calls `startUpload()` (Live Component action).
|
|
111
126
|
2. Client streams file chunks over WebSocket with `useChunkedUpload`.
|
|
112
|
-
3. Server
|
|
113
|
-
4.
|
|
127
|
+
3. Server receives chunks and validates size/count.
|
|
128
|
+
4. On completion, server validates **magic bytes** against claimed MIME type.
|
|
129
|
+
5. Server assembles file in `uploads/` with UUID filename and returns `/uploads/...`.
|
|
130
|
+
6. Client maps to `/api/uploads/...` for access.
|
|
131
|
+
|
|
132
|
+
## Magic Bytes Validation
|
|
133
|
+
|
|
134
|
+
The server validates actual file content against known magic byte signatures before assembling:
|
|
135
|
+
|
|
136
|
+
| MIME Type | Magic Bytes |
|
|
137
|
+
|-----------|-------------|
|
|
138
|
+
| `image/jpeg` | `FF D8 FF` |
|
|
139
|
+
| `image/png` | `89 50 4E 47 0D 0A 1A 0A` |
|
|
140
|
+
| `image/gif` | `47 49 46 38 37 61` or `47 49 46 38 39 61` |
|
|
141
|
+
| `image/webp` | `52 49 46 46` (RIFF header) |
|
|
142
|
+
| `application/pdf` | `25 50 44 46` (%PDF) |
|
|
143
|
+
| `application/zip` | `50 4B 03 04` or `50 4B 05 06` |
|
|
144
|
+
| `application/gzip` | `1F 8B` |
|
|
145
|
+
|
|
146
|
+
Text-based types (text/plain, text/csv, application/json, image/svg+xml) skip binary validation.
|
|
147
|
+
|
|
148
|
+
## Per-User Upload Quotas
|
|
149
|
+
|
|
150
|
+
Each authenticated user has a daily upload quota (default: 500MB/day):
|
|
151
|
+
|
|
152
|
+
- Quota is checked before upload starts
|
|
153
|
+
- Quota is reserved when upload begins (even if upload doesn't complete)
|
|
154
|
+
- Quotas reset daily
|
|
155
|
+
- Anonymous uploads (no userId) bypass quota checks
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
// Check user's remaining quota
|
|
159
|
+
const usage = fileUploadManager.getUserUploadUsage(userId)
|
|
160
|
+
// { used: 104857600, limit: 524288000, remaining: 419430400 }
|
|
161
|
+
```
|
|
114
162
|
|
|
115
163
|
## Error Handling
|
|
116
164
|
|
|
117
165
|
- If an action throws, the error surfaces in `live.$error` on the client.
|
|
118
166
|
- The widget shows `localError || state.error || $error`.
|
|
167
|
+
- Magic bytes validation failure: `"File content does not match claimed type 'image/jpeg'"`
|
|
168
|
+
- Quota exceeded: `"Upload quota exceeded for user"`
|
|
169
|
+
- Double extension: `"Suspicious double extension detected: .exe in malware.exe.jpg"`
|
|
119
170
|
|
|
120
171
|
## Files Involved
|
|
121
172
|
|
|
122
173
|
**Server**
|
|
123
174
|
- `app/server/live/LiveUpload.ts`
|
|
124
|
-
- `core/server/live/FileUploadManager.ts`
|
|
125
|
-
- `core/server/live/websocket-plugin.ts`
|
|
175
|
+
- `core/server/live/FileUploadManager.ts` (chunk handling, magic bytes, quotas, file assembly)
|
|
176
|
+
- `core/server/live/websocket-plugin.ts` (upload message routing, userId passthrough)
|
|
126
177
|
|
|
127
178
|
**Client**
|
|
128
|
-
- `core/client/hooks/useChunkedUpload.ts`
|
|
129
|
-
- `core/client/hooks/useLiveUpload.ts`
|
|
130
|
-
- `app/client/src/components/LiveUploadWidget.tsx`
|
|
179
|
+
- `core/client/hooks/useChunkedUpload.ts` (streaming chunks)
|
|
180
|
+
- `core/client/hooks/useLiveUpload.ts` (Live Component wrapper)
|
|
181
|
+
- `app/client/src/components/LiveUploadWidget.tsx` (UI)
|
package/app/client/index.html
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
|
-
<link rel="icon" type="image/svg+xml" href="/
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
-
<title>
|
|
7
|
+
<title>FluxStack</title>
|
|
8
8
|
</head>
|
|
9
9
|
<body>
|
|
10
10
|
<div id="root"></div>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<svg viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="outer" x1="50%" y1="100%" x2="50%" y2="0%">
|
|
4
|
+
<stop stop-color="#4338CA"/>
|
|
5
|
+
<stop offset=".25" stop-color="#7C3AED"/>
|
|
6
|
+
<stop offset=".5" stop-color="#A855F7"/>
|
|
7
|
+
<stop offset=".75" stop-color="#C084FC"/>
|
|
8
|
+
<stop offset="1" stop-color="#22D3EE"/>
|
|
9
|
+
</linearGradient>
|
|
10
|
+
<linearGradient id="mid" x1="50%" y1="100%" x2="50%" y2="0%">
|
|
11
|
+
<stop stop-color="#6D28D9"/>
|
|
12
|
+
<stop offset=".3" stop-color="#A855F7"/>
|
|
13
|
+
<stop offset=".7" stop-color="#C4B5FD"/>
|
|
14
|
+
<stop offset="1" stop-color="#67E8F9"/>
|
|
15
|
+
</linearGradient>
|
|
16
|
+
<linearGradient id="inner" x1="50%" y1="100%" x2="50%" y2="0%">
|
|
17
|
+
<stop stop-color="#A78BFA"/>
|
|
18
|
+
<stop offset=".4" stop-color="#C4B5FD"/>
|
|
19
|
+
<stop offset=".8" stop-color="#E0E7FF"/>
|
|
20
|
+
<stop offset="1" stop-color="#ECFEFF"/>
|
|
21
|
+
</linearGradient>
|
|
22
|
+
<linearGradient id="core" x1="50%" y1="100%" x2="50%" y2="0%">
|
|
23
|
+
<stop stop-color="#DDD6FE"/>
|
|
24
|
+
<stop offset="1" stop-color="#F0F9FF"/>
|
|
25
|
+
</linearGradient>
|
|
26
|
+
</defs>
|
|
27
|
+
|
|
28
|
+
<!-- Main outer flame -->
|
|
29
|
+
<path fill="url(#outer)" opacity=".92"
|
|
30
|
+
d="M64 10C62 10 54 26 50 36C46 46 36 48 34 60C32 72 35 82 40 90C46 98 54 106 64 108C74 106 82 98 88 90C93 82 96 72 94 60C92 48 82 46 78 36C74 26 84 20 86 14C82 22 76 30 74 38C72 48 78 56 76 64C74 54 70 40 68 30C66 20 64 10 64 10Z"/>
|
|
31
|
+
|
|
32
|
+
<!-- Middle flame -->
|
|
33
|
+
<path fill="url(#mid)" opacity=".88"
|
|
34
|
+
d="M64 30C62 30 52 46 48 56C44 66 46 78 50 86C54 94 58 100 64 102C70 100 74 94 78 86C82 78 84 66 80 56C78 48 82 38 82 30C80 38 76 46 74 54C72 44 68 36 64 30Z"/>
|
|
35
|
+
|
|
36
|
+
<!-- Inner flame -->
|
|
37
|
+
<path fill="url(#inner)" opacity=".85"
|
|
38
|
+
d="M64 50C60 56 54 66 54 76C54 86 58 94 64 100C70 94 74 86 74 76C74 66 68 56 64 50Z"/>
|
|
39
|
+
|
|
40
|
+
<!-- Hot core -->
|
|
41
|
+
<path fill="url(#core)" opacity=".7"
|
|
42
|
+
d="M64 72C60 78 56 84 58 92C60 97 62 100 64 101C66 100 68 97 70 92C72 84 68 78 64 72Z"/>
|
|
43
|
+
|
|
44
|
+
<!-- White hot center -->
|
|
45
|
+
<ellipse cx="64" cy="92" rx="5" ry="7" fill="#F0F9FF" opacity=".5"/>
|
|
46
|
+
</svg>
|
package/app/client/src/App.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react'
|
|
2
2
|
import { Routes, Route } from 'react-router'
|
|
3
3
|
import { api } from './lib/eden-api'
|
|
4
|
-
import { LiveComponentsProvider } from '@/core/client'
|
|
4
|
+
import { LiveComponentsProvider, LiveDebugger } from '@/core/client'
|
|
5
5
|
import { FormDemo } from './live/FormDemo'
|
|
6
6
|
import { CounterDemo } from './live/CounterDemo'
|
|
7
7
|
import { UploadDemo } from './live/UploadDemo'
|
|
@@ -153,6 +153,7 @@ function App() {
|
|
|
153
153
|
debug={false}
|
|
154
154
|
>
|
|
155
155
|
<AppContent />
|
|
156
|
+
{import.meta.env.DEV && <LiveDebugger />}
|
|
156
157
|
</LiveComponentsProvider>
|
|
157
158
|
)
|
|
158
159
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<svg viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="outer" x1="50%" y1="100%" x2="50%" y2="0%">
|
|
4
|
+
<stop stop-color="#4338CA"/>
|
|
5
|
+
<stop offset=".25" stop-color="#7C3AED"/>
|
|
6
|
+
<stop offset=".5" stop-color="#A855F7"/>
|
|
7
|
+
<stop offset=".75" stop-color="#C084FC"/>
|
|
8
|
+
<stop offset="1" stop-color="#22D3EE"/>
|
|
9
|
+
</linearGradient>
|
|
10
|
+
<linearGradient id="mid" x1="50%" y1="100%" x2="50%" y2="0%">
|
|
11
|
+
<stop stop-color="#6D28D9"/>
|
|
12
|
+
<stop offset=".3" stop-color="#A855F7"/>
|
|
13
|
+
<stop offset=".7" stop-color="#C4B5FD"/>
|
|
14
|
+
<stop offset="1" stop-color="#67E8F9"/>
|
|
15
|
+
</linearGradient>
|
|
16
|
+
<linearGradient id="inner" x1="50%" y1="100%" x2="50%" y2="0%">
|
|
17
|
+
<stop stop-color="#A78BFA"/>
|
|
18
|
+
<stop offset=".4" stop-color="#C4B5FD"/>
|
|
19
|
+
<stop offset=".8" stop-color="#E0E7FF"/>
|
|
20
|
+
<stop offset="1" stop-color="#ECFEFF"/>
|
|
21
|
+
</linearGradient>
|
|
22
|
+
<linearGradient id="core" x1="50%" y1="100%" x2="50%" y2="0%">
|
|
23
|
+
<stop stop-color="#DDD6FE"/>
|
|
24
|
+
<stop offset="1" stop-color="#F0F9FF"/>
|
|
25
|
+
</linearGradient>
|
|
26
|
+
</defs>
|
|
27
|
+
|
|
28
|
+
<!-- Main outer flame -->
|
|
29
|
+
<path fill="url(#outer)" opacity=".92"
|
|
30
|
+
d="M64 10C62 10 54 26 50 36C46 46 36 48 34 60C32 72 35 82 40 90C46 98 54 106 64 108C74 106 82 98 88 90C93 82 96 72 94 60C92 48 82 46 78 36C74 26 84 20 86 14C82 22 76 30 74 38C72 48 78 56 76 64C74 54 70 40 68 30C66 20 64 10 64 10Z"/>
|
|
31
|
+
|
|
32
|
+
<!-- Middle flame -->
|
|
33
|
+
<path fill="url(#mid)" opacity=".88"
|
|
34
|
+
d="M64 30C62 30 52 46 48 56C44 66 46 78 50 86C54 94 58 100 64 102C70 100 74 94 78 86C82 78 84 66 80 56C78 48 82 38 82 30C80 38 76 46 74 54C72 44 68 36 64 30Z"/>
|
|
35
|
+
|
|
36
|
+
<!-- Inner flame -->
|
|
37
|
+
<path fill="url(#inner)" opacity=".85"
|
|
38
|
+
d="M64 50C60 56 54 66 54 76C54 86 58 94 64 100C70 94 74 86 74 76C74 66 68 56 64 50Z"/>
|
|
39
|
+
|
|
40
|
+
<!-- Hot core -->
|
|
41
|
+
<path fill="url(#core)" opacity=".7"
|
|
42
|
+
d="M64 72C60 78 56 84 58 92C60 97 62 100 64 101C66 100 68 97 70 92C72 84 68 78 64 72Z"/>
|
|
43
|
+
|
|
44
|
+
<!-- White hot center -->
|
|
45
|
+
<ellipse cx="64" cy="92" rx="5" ry="7" fill="#F0F9FF" opacity=".5"/>
|
|
46
|
+
</svg>
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
<svg viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="outer" x1="50%" y1="100%" x2="50%" y2="0%">
|
|
4
|
+
<stop stop-color="#4338CA"/>
|
|
5
|
+
<stop offset=".25" stop-color="#7C3AED"/>
|
|
6
|
+
<stop offset=".5" stop-color="#A855F7"/>
|
|
7
|
+
<stop offset=".75" stop-color="#C084FC"/>
|
|
8
|
+
<stop offset="1" stop-color="#22D3EE"/>
|
|
9
|
+
</linearGradient>
|
|
10
|
+
<linearGradient id="mid" x1="50%" y1="100%" x2="50%" y2="0%">
|
|
11
|
+
<stop stop-color="#6D28D9"/>
|
|
12
|
+
<stop offset=".3" stop-color="#A855F7"/>
|
|
13
|
+
<stop offset=".7" stop-color="#C4B5FD"/>
|
|
14
|
+
<stop offset="1" stop-color="#67E8F9"/>
|
|
15
|
+
</linearGradient>
|
|
16
|
+
<linearGradient id="inner" x1="50%" y1="100%" x2="50%" y2="0%">
|
|
17
|
+
<stop stop-color="#A78BFA"/>
|
|
18
|
+
<stop offset=".4" stop-color="#C4B5FD"/>
|
|
19
|
+
<stop offset=".8" stop-color="#E0E7FF"/>
|
|
20
|
+
<stop offset="1" stop-color="#ECFEFF"/>
|
|
21
|
+
</linearGradient>
|
|
22
|
+
<linearGradient id="core" x1="50%" y1="100%" x2="50%" y2="0%">
|
|
23
|
+
<stop stop-color="#DDD6FE"/>
|
|
24
|
+
<stop offset="1" stop-color="#F0F9FF"/>
|
|
25
|
+
</linearGradient>
|
|
26
|
+
<linearGradient id="tongue" x1="50%" y1="100%" x2="50%" y2="0%">
|
|
27
|
+
<stop stop-color="#7C3AED"/>
|
|
28
|
+
<stop offset="1" stop-color="#38BDF8"/>
|
|
29
|
+
</linearGradient>
|
|
30
|
+
<linearGradient id="spark" x1="50%" y1="100%" x2="50%" y2="0%">
|
|
31
|
+
<stop stop-color="#A78BFA"/>
|
|
32
|
+
<stop offset="1" stop-color="#67E8F9"/>
|
|
33
|
+
</linearGradient>
|
|
34
|
+
</defs>
|
|
35
|
+
|
|
36
|
+
<!-- ============ OUTER GLOW / HEAT HAZE ============ -->
|
|
37
|
+
<ellipse cx="64" cy="60" rx="38" ry="50" fill="#7C3AED" opacity=".04">
|
|
38
|
+
<animate attributeName="rx" dur="2s" repeatCount="indefinite" values="38;42;36;38"/>
|
|
39
|
+
<animate attributeName="opacity" dur="2s" repeatCount="indefinite" values=".04;.06;.03;.04"/>
|
|
40
|
+
</ellipse>
|
|
41
|
+
|
|
42
|
+
<!-- ============ LEFT TONGUE (far) ============ -->
|
|
43
|
+
<path fill="url(#tongue)" opacity=".5">
|
|
44
|
+
<animate attributeName="d" dur="0.6s" repeatCount="indefinite" calcMode="spline" keySplines="0.4 0 0.6 1;0.4 0 0.6 1;0.4 0 0.6 1"
|
|
45
|
+
values="
|
|
46
|
+
M44 74C40 62 36 52 38 38C39 30 43 24 43 24C44 34 45 44 48 52C50 60 43 68 44 74Z;
|
|
47
|
+
M42 74C38 64 33 54 36 36C37 28 41 22 41 22C43 32 43 46 46 54C48 62 41 69 42 74Z;
|
|
48
|
+
M45 74C41 60 38 50 40 40C41 32 44 26 44 26C45 36 46 42 49 50C51 58 44 67 45 74Z;
|
|
49
|
+
M44 74C40 62 36 52 38 38C39 30 43 24 43 24C44 34 45 44 48 52C50 60 43 68 44 74Z"/>
|
|
50
|
+
<animate attributeName="opacity" dur="0.7s" repeatCount="indefinite" values=".5;.3;.6;.4;.5"/>
|
|
51
|
+
</path>
|
|
52
|
+
|
|
53
|
+
<!-- ============ RIGHT TONGUE (far) ============ -->
|
|
54
|
+
<path fill="url(#tongue)" opacity=".45">
|
|
55
|
+
<animate attributeName="d" dur="0.7s" repeatCount="indefinite" calcMode="spline" keySplines="0.4 0 0.6 1;0.4 0 0.6 1;0.4 0 0.6 1"
|
|
56
|
+
values="
|
|
57
|
+
M84 72C88 58 92 46 89 30C88 22 84 16 84 16C85 28 83 40 81 50C79 58 83 66 84 72Z;
|
|
58
|
+
M86 72C90 56 94 44 91 28C90 20 86 14 86 14C87 26 84 38 82 48C80 56 85 65 86 72Z;
|
|
59
|
+
M83 72C87 60 90 48 87 32C86 24 83 18 83 18C84 30 82 42 80 52C78 60 82 67 83 72Z;
|
|
60
|
+
M84 72C88 58 92 46 89 30C88 22 84 16 84 16C85 28 83 40 81 50C79 58 83 66 84 72Z"/>
|
|
61
|
+
<animate attributeName="opacity" dur="0.8s" repeatCount="indefinite" values=".45;.6;.3;.5;.45"/>
|
|
62
|
+
</path>
|
|
63
|
+
|
|
64
|
+
<!-- ============ LEFT WISP (small) ============ -->
|
|
65
|
+
<path fill="url(#tongue)" opacity=".35">
|
|
66
|
+
<animate attributeName="d" dur="0.5s" repeatCount="indefinite"
|
|
67
|
+
values="
|
|
68
|
+
M38 80C35 72 32 64 34 54C35 48 38 44 38 44C38 50 40 58 41 64C42 70 37 76 38 80Z;
|
|
69
|
+
M36 80C33 74 30 66 33 52C34 46 36 42 36 42C37 48 38 60 40 66C41 72 35 77 36 80Z;
|
|
70
|
+
M39 80C36 71 34 62 36 56C37 50 39 46 39 46C39 52 41 56 42 62C43 68 38 75 39 80Z;
|
|
71
|
+
M38 80C35 72 32 64 34 54C35 48 38 44 38 44C38 50 40 58 41 64C42 70 37 76 38 80Z"/>
|
|
72
|
+
</path>
|
|
73
|
+
|
|
74
|
+
<!-- ============ RIGHT WISP (small) ============ -->
|
|
75
|
+
<path fill="url(#tongue)" opacity=".3">
|
|
76
|
+
<animate attributeName="d" dur="0.55s" repeatCount="indefinite"
|
|
77
|
+
values="
|
|
78
|
+
M90 78C93 70 96 60 93 50C92 44 89 40 89 40C90 48 88 56 87 62C86 68 89 74 90 78Z;
|
|
79
|
+
M92 78C95 68 97 58 95 48C94 42 91 38 91 38C92 46 89 54 88 60C87 66 91 73 92 78Z;
|
|
80
|
+
M89 78C92 72 94 62 92 52C91 46 88 42 88 42C89 50 87 58 86 64C85 70 88 75 89 78Z;
|
|
81
|
+
M90 78C93 70 96 60 93 50C92 44 89 40 89 40C90 48 88 56 87 62C86 68 89 74 90 78Z"/>
|
|
82
|
+
</path>
|
|
83
|
+
|
|
84
|
+
<!-- ============ MAIN OUTER FLAME ============ -->
|
|
85
|
+
<path fill="url(#outer)" opacity=".92">
|
|
86
|
+
<animate attributeName="d" dur="1.2s" repeatCount="indefinite" calcMode="spline" keySplines="0.4 0 0.6 1;0.4 0 0.6 1;0.4 0 0.6 1;0.4 0 0.6 1"
|
|
87
|
+
values="
|
|
88
|
+
M64 10C62 10 54 26 50 36C46 46 36 48 34 60C32 72 35 82 40 90C46 98 54 106 64 108C74 106 82 98 88 90C93 82 96 72 94 60C92 48 82 46 78 36C74 26 84 20 86 14C82 22 76 30 74 38C72 48 78 56 76 64C74 54 70 40 68 30C66 20 64 10 64 10Z;
|
|
89
|
+
M64 8C62 8 52 24 48 35C44 46 34 50 32 62C30 74 34 84 38 92C44 100 53 107 64 109C75 107 84 100 90 92C94 84 98 74 96 62C94 50 84 44 80 35C76 24 86 18 88 12C84 20 78 28 76 36C74 46 80 54 78 62C76 52 72 38 70 28C68 18 64 8 64 8Z;
|
|
90
|
+
M64 12C62 12 56 28 52 37C48 46 38 46 36 58C34 70 36 80 41 88C46 96 55 105 64 107C73 105 82 96 87 88C92 80 94 70 92 58C90 46 80 48 76 37C72 28 82 22 84 16C80 24 74 32 72 40C70 50 76 58 74 66C72 56 68 42 66 32C64 24 64 12 64 12Z;
|
|
91
|
+
M64 9C62 9 53 25 49 34C45 44 35 49 33 61C31 73 34 83 39 91C45 99 53 106 64 108C75 106 83 99 89 91C94 83 97 73 95 61C93 49 83 45 79 34C75 25 85 19 87 13C83 21 77 29 75 37C73 47 79 55 77 63C75 53 71 39 69 29C67 19 64 9 64 9Z;
|
|
92
|
+
M64 10C62 10 54 26 50 36C46 46 36 48 34 60C32 72 35 82 40 90C46 98 54 106 64 108C74 106 82 98 88 90C93 82 96 72 94 60C92 48 82 46 78 36C74 26 84 20 86 14C82 22 76 30 74 38C72 48 78 56 76 64C74 54 70 40 68 30C66 20 64 10 64 10Z"/>
|
|
93
|
+
</path>
|
|
94
|
+
|
|
95
|
+
<!-- ============ MIDDLE FLAME ============ -->
|
|
96
|
+
<path fill="url(#mid)" opacity=".88">
|
|
97
|
+
<animate attributeName="d" dur="0.9s" repeatCount="indefinite" calcMode="spline" keySplines="0.4 0 0.6 1;0.4 0 0.6 1;0.4 0 0.6 1;0.4 0 0.6 1"
|
|
98
|
+
values="
|
|
99
|
+
M64 30C62 30 52 46 48 56C44 66 46 78 50 86C54 94 58 100 64 102C70 100 74 94 78 86C82 78 84 66 80 56C78 48 82 38 82 30C80 38 76 46 74 54C72 44 68 36 64 30Z;
|
|
100
|
+
M64 28C62 28 50 44 46 55C42 66 45 80 49 88C53 96 57 101 64 103C71 101 75 96 79 88C83 80 86 66 82 55C80 46 84 36 84 28C82 36 77 44 75 52C73 42 69 34 64 28Z;
|
|
101
|
+
M64 32C62 32 53 48 49 57C45 67 47 77 51 85C55 93 59 99 64 101C69 99 73 93 77 85C81 77 83 67 79 57C77 50 81 40 81 32C79 40 75 48 73 55C71 46 67 38 64 32Z;
|
|
102
|
+
M64 29C62 29 51 45 47 54C43 64 46 79 50 87C54 95 58 100 64 102C70 100 74 95 78 87C82 79 85 64 81 54C79 47 83 37 83 29C81 37 76 45 74 53C72 43 68 35 64 29Z;
|
|
103
|
+
M64 30C62 30 52 46 48 56C44 66 46 78 50 86C54 94 58 100 64 102C70 100 74 94 78 86C82 78 84 66 80 56C78 48 82 38 82 30C80 38 76 46 74 54C72 44 68 36 64 30Z"/>
|
|
104
|
+
</path>
|
|
105
|
+
|
|
106
|
+
<!-- ============ INNER FLAME ============ -->
|
|
107
|
+
<path fill="url(#inner)" opacity=".85">
|
|
108
|
+
<animate attributeName="d" dur="0.7s" repeatCount="indefinite" calcMode="spline" keySplines="0.4 0 0.6 1;0.4 0 0.6 1;0.4 0 0.6 1"
|
|
109
|
+
values="
|
|
110
|
+
M64 50C60 56 54 66 54 76C54 86 58 94 64 100C70 94 74 86 74 76C74 66 68 56 64 50Z;
|
|
111
|
+
M64 48C60 54 52 64 52 75C52 86 57 95 64 101C71 95 76 86 76 75C76 64 68 54 64 48Z;
|
|
112
|
+
M64 52C60 58 55 68 55 77C55 86 59 93 64 99C69 93 73 86 73 77C73 68 68 58 64 52Z;
|
|
113
|
+
M64 50C60 56 54 66 54 76C54 86 58 94 64 100C70 94 74 86 74 76C74 66 68 56 64 50Z"/>
|
|
114
|
+
</path>
|
|
115
|
+
|
|
116
|
+
<!-- ============ HOT CORE ============ -->
|
|
117
|
+
<path fill="url(#core)" opacity=".7">
|
|
118
|
+
<animate attributeName="d" dur="0.5s" repeatCount="indefinite"
|
|
119
|
+
values="
|
|
120
|
+
M64 72C60 78 56 84 58 92C60 97 62 100 64 101C66 100 68 97 70 92C72 84 68 78 64 72Z;
|
|
121
|
+
M64 70C60 76 55 83 57 91C59 97 61 100 64 102C67 100 69 97 71 91C73 83 68 76 64 70Z;
|
|
122
|
+
M64 74C60 80 57 85 59 93C61 97 63 100 64 100C65 100 67 97 69 93C71 85 68 80 64 74Z;
|
|
123
|
+
M64 72C60 78 56 84 58 92C60 97 62 100 64 101C66 100 68 97 70 92C72 84 68 78 64 72Z"/>
|
|
124
|
+
<animate attributeName="opacity" dur="0.4s" repeatCount="indefinite" values=".7;.55;.85;.65;.7"/>
|
|
125
|
+
</path>
|
|
126
|
+
|
|
127
|
+
<!-- ============ WHITE HOT CENTER ============ -->
|
|
128
|
+
<ellipse cx="64" cy="92" fill="#F0F9FF">
|
|
129
|
+
<animate attributeName="rx" dur="0.4s" repeatCount="indefinite" values="5;3.5;6;4;5"/>
|
|
130
|
+
<animate attributeName="ry" dur="0.4s" repeatCount="indefinite" values="7;5;8;6;7"/>
|
|
131
|
+
<animate attributeName="opacity" dur="0.35s" repeatCount="indefinite" values=".5;.3;.6;.35;.5"/>
|
|
132
|
+
<animate attributeName="cy" dur="0.6s" repeatCount="indefinite" values="92;90;93;91;92"/>
|
|
133
|
+
</ellipse>
|
|
134
|
+
|
|
135
|
+
<!-- ============ SPARKS / EMBERS ============ -->
|
|
136
|
+
<!-- Spark 1 - rises left -->
|
|
137
|
+
<circle r="1.2" fill="url(#spark)">
|
|
138
|
+
<animate attributeName="cx" dur="1.8s" repeatCount="indefinite" values="56;52;48;44"/>
|
|
139
|
+
<animate attributeName="cy" dur="1.8s" repeatCount="indefinite" values="40;28;16;4"/>
|
|
140
|
+
<animate attributeName="opacity" dur="1.8s" repeatCount="indefinite" values="0;.7;.5;0"/>
|
|
141
|
+
<animate attributeName="r" dur="1.8s" repeatCount="indefinite" values="1.2;1;.6;.2"/>
|
|
142
|
+
</circle>
|
|
143
|
+
|
|
144
|
+
<!-- Spark 2 - rises right -->
|
|
145
|
+
<circle r="1" fill="url(#spark)">
|
|
146
|
+
<animate attributeName="cx" dur="2s" repeatCount="indefinite" values="72;76;80;82"/>
|
|
147
|
+
<animate attributeName="cy" dur="2s" repeatCount="indefinite" values="36;22;10;0"/>
|
|
148
|
+
<animate attributeName="opacity" dur="2s" repeatCount="indefinite" values="0;.6;.4;0"/>
|
|
149
|
+
<animate attributeName="r" dur="2s" repeatCount="indefinite" values="1;.8;.5;.1"/>
|
|
150
|
+
</circle>
|
|
151
|
+
|
|
152
|
+
<!-- Spark 3 - rises center -->
|
|
153
|
+
<circle r="1.5" fill="#E0E7FF">
|
|
154
|
+
<animate attributeName="cx" dur="1.5s" repeatCount="indefinite" values="64;62;60;58"/>
|
|
155
|
+
<animate attributeName="cy" dur="1.5s" repeatCount="indefinite" values="32;18;6;-4"/>
|
|
156
|
+
<animate attributeName="opacity" dur="1.5s" repeatCount="indefinite" values="0;.8;.4;0"/>
|
|
157
|
+
<animate attributeName="r" dur="1.5s" repeatCount="indefinite" values="1.5;1.2;.7;.2"/>
|
|
158
|
+
</circle>
|
|
159
|
+
|
|
160
|
+
<!-- Spark 4 - rises far left -->
|
|
161
|
+
<circle r=".8" fill="#C4B5FD">
|
|
162
|
+
<animate attributeName="cx" dur="2.2s" repeatCount="indefinite" values="50;46;42;40"/>
|
|
163
|
+
<animate attributeName="cy" dur="2.2s" repeatCount="indefinite" values="46;32;18;6"/>
|
|
164
|
+
<animate attributeName="opacity" dur="2.2s" repeatCount="indefinite" values="0;.5;.3;0"/>
|
|
165
|
+
<animate attributeName="r" dur="2.2s" repeatCount="indefinite" values=".8;.6;.4;.1"/>
|
|
166
|
+
</circle>
|
|
167
|
+
|
|
168
|
+
<!-- Spark 5 - rises far right -->
|
|
169
|
+
<circle r=".9" fill="#67E8F9">
|
|
170
|
+
<animate attributeName="cx" dur="1.7s" repeatCount="indefinite" values="76;80;84;86" begin="0.3s"/>
|
|
171
|
+
<animate attributeName="cy" dur="1.7s" repeatCount="indefinite" values="42;26;12;0" begin="0.3s"/>
|
|
172
|
+
<animate attributeName="opacity" dur="1.7s" repeatCount="indefinite" values="0;.6;.3;0" begin="0.3s"/>
|
|
173
|
+
<animate attributeName="r" dur="1.7s" repeatCount="indefinite" values=".9;.7;.4;.1" begin="0.3s"/>
|
|
174
|
+
</circle>
|
|
175
|
+
|
|
176
|
+
<!-- Spark 6 - tiny center delayed -->
|
|
177
|
+
<circle r=".7" fill="#F0F9FF">
|
|
178
|
+
<animate attributeName="cx" dur="1.4s" repeatCount="indefinite" values="66;68;70;72" begin="0.6s"/>
|
|
179
|
+
<animate attributeName="cy" dur="1.4s" repeatCount="indefinite" values="28;14;4;-6" begin="0.6s"/>
|
|
180
|
+
<animate attributeName="opacity" dur="1.4s" repeatCount="indefinite" values="0;.7;.3;0" begin="0.6s"/>
|
|
181
|
+
<animate attributeName="r" dur="1.4s" repeatCount="indefinite" values=".7;.5;.3;0" begin="0.6s"/>
|
|
182
|
+
</circle>
|
|
183
|
+
</svg>
|