@veams/status-quo 0.1.0 → 1.1.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 (65) hide show
  1. package/.github/workflows/pages.yml +46 -0
  2. package/.github/workflows/release.yml +33 -0
  3. package/CHANGELOG.md +30 -0
  4. package/README.md +260 -124
  5. package/assets/statusquo-logo.png +0 -0
  6. package/dist/hooks/state-subscription.d.ts +1 -2
  7. package/dist/hooks/state-subscription.js +8 -9
  8. package/dist/hooks/state-subscription.js.map +1 -1
  9. package/dist/index.d.ts +2 -2
  10. package/dist/index.js +2 -2
  11. package/dist/index.js.map +1 -1
  12. package/dist/store/__tests__/{state-handler.spec.js → observable-state-handler.spec.js} +14 -14
  13. package/dist/store/__tests__/observable-state-handler.spec.js.map +1 -0
  14. package/dist/store/__tests__/signal-state-handler.spec.js +78 -0
  15. package/dist/store/__tests__/signal-state-handler.spec.js.map +1 -0
  16. package/dist/store/base-state-handler.d.ts +30 -0
  17. package/dist/store/base-state-handler.js +84 -0
  18. package/dist/store/base-state-handler.js.map +1 -0
  19. package/dist/store/index.d.ts +3 -1
  20. package/dist/store/index.js +3 -1
  21. package/dist/store/index.js.map +1 -1
  22. package/dist/store/observable-state-handler.d.ts +26 -0
  23. package/dist/store/observable-state-handler.js +55 -0
  24. package/dist/store/observable-state-handler.js.map +1 -0
  25. package/dist/store/signal-state-handler.d.ts +25 -0
  26. package/dist/store/signal-state-handler.js +49 -0
  27. package/dist/store/signal-state-handler.js.map +1 -0
  28. package/dist/types/types.d.ts +2 -2
  29. package/package.json +22 -11
  30. package/playground/index.html +12 -0
  31. package/playground/src/App.tsx +478 -0
  32. package/playground/src/assets/philosophy-agnostic.svg +18 -0
  33. package/playground/src/assets/philosophy-separation.svg +13 -0
  34. package/playground/src/assets/philosophy-swap.svg +17 -0
  35. package/playground/src/assets/statusquo-logo.png +0 -0
  36. package/playground/src/main.tsx +19 -0
  37. package/playground/src/styles.css +411 -0
  38. package/playground/tsconfig.json +12 -0
  39. package/playground/vite.config.ts +18 -0
  40. package/src/hooks/state-subscription.tsx +21 -14
  41. package/src/index.ts +14 -2
  42. package/src/store/__tests__/{state-handler.spec.ts → observable-state-handler.spec.ts} +15 -15
  43. package/src/store/__tests__/signal-state-handler.spec.ts +97 -0
  44. package/src/store/base-state-handler.ts +119 -0
  45. package/src/store/index.ts +3 -1
  46. package/src/store/observable-state-handler.ts +87 -0
  47. package/src/store/signal-state-handler.ts +76 -0
  48. package/src/types/types.ts +2 -3
  49. package/dist/store/__tests__/state-handler.spec.js.map +0 -1
  50. package/dist/store/state-handler.d.ts +0 -36
  51. package/dist/store/state-handler.js +0 -122
  52. package/dist/store/state-handler.js.map +0 -1
  53. package/dist/types/hooks/index.d.ts +0 -2
  54. package/dist/types/hooks/state-factory.d.ts +0 -2
  55. package/dist/types/hooks/state-singleton.d.ts +0 -2
  56. package/dist/types/hooks/state-subscription.d.ts +0 -3
  57. package/dist/types/index.d.ts +0 -6
  58. package/dist/types/store/dev-tools.d.ts +0 -23
  59. package/dist/types/store/index.d.ts +0 -3
  60. package/dist/types/store/state-handler.d.ts +0 -36
  61. package/dist/types/store/state-singleton.d.ts +0 -5
  62. package/dist/types/types/types.d.ts +0 -7
  63. package/src/store/state-handler.ts +0 -181
  64. /package/dist/store/__tests__/{state-handler.spec.d.ts → observable-state-handler.spec.d.ts} +0 -0
  65. /package/dist/{types/store/__tests__/state-handler.spec.d.ts → store/__tests__/signal-state-handler.spec.d.ts} +0 -0
@@ -0,0 +1,478 @@
1
+ import { useEffect } from 'react';
2
+
3
+ import {
4
+ makeStateSingleton,
5
+ ObservableStateHandler,
6
+ SignalStateHandler,
7
+ useStateFactory,
8
+ useStateSingleton,
9
+ } from '@veams/status-quo';
10
+
11
+ import Prism from 'prismjs';
12
+ import 'prismjs/components/prism-typescript';
13
+ import 'prismjs/components/prism-jsx';
14
+ import 'prismjs/components/prism-bash';
15
+
16
+ import philosophySwap from './assets/philosophy-swap.svg';
17
+ import philosophySeparation from './assets/philosophy-separation.svg';
18
+ import philosophyAgnostic from './assets/philosophy-agnostic.svg';
19
+ import statusQuoLogo from './assets/statusquo-logo.png';
20
+
21
+ type CounterState = {
22
+ count: number;
23
+ };
24
+
25
+ type CounterActions = {
26
+ increase: () => void;
27
+ decrease: () => void;
28
+ reset: () => void;
29
+ };
30
+
31
+ class ObservableCounterStateHandler extends ObservableStateHandler<CounterState, CounterActions> {
32
+ constructor(startCount = 0) {
33
+ super({ initialState: { count: startCount } });
34
+ }
35
+
36
+ getActions(): CounterActions {
37
+ return {
38
+ increase: () => {
39
+ this.setState({ count: this.getState().count + 1 }, 'increase');
40
+ },
41
+ decrease: () => {
42
+ const current = this.getState().count;
43
+ this.setState({ count: Math.max(0, current - 1) }, 'decrease');
44
+ },
45
+ reset: () => {
46
+ this.setState(this.getInitialState(), 'reset');
47
+ },
48
+ };
49
+ }
50
+ }
51
+
52
+ class SignalCounterStateHandler extends SignalStateHandler<CounterState, CounterActions> {
53
+ constructor(startCount = 0) {
54
+ super({ initialState: { count: startCount } });
55
+ }
56
+
57
+ getActions(): CounterActions {
58
+ return {
59
+ increase: () => {
60
+ this.setState({ count: this.getState().count + 1 }, 'increase');
61
+ },
62
+ decrease: () => {
63
+ const current = this.getState().count;
64
+ this.setState({ count: Math.max(0, current - 1) }, 'decrease');
65
+ },
66
+ reset: () => {
67
+ this.setState(this.getInitialState(), 'reset');
68
+ },
69
+ };
70
+ }
71
+ }
72
+
73
+ const SingletonCounterStore = makeStateSingleton(() => new ObservableCounterStateHandler(5));
74
+
75
+ function ObservableCounterFactory(start = 0) {
76
+ return new ObservableCounterStateHandler(start);
77
+ }
78
+
79
+ function SignalCounterFactory(start = 0) {
80
+ return new SignalCounterStateHandler(start);
81
+ }
82
+
83
+ type CounterCardProps = {
84
+ title: string;
85
+ subtitle: string;
86
+ state: CounterState;
87
+ actions: CounterActions;
88
+ snippet: string;
89
+ };
90
+
91
+ function CounterCard({ title, subtitle, state, actions, snippet }: CounterCardProps) {
92
+ return (
93
+ <section className="card">
94
+ <div className="card-header">
95
+ <div>
96
+ <p className="eyebrow">{subtitle}</p>
97
+ <h2>{title}</h2>
98
+ </div>
99
+ <div className="count-chip">{state.count}</div>
100
+ </div>
101
+ <pre className="code-block">
102
+ <code className="language-ts">{snippet}</code>
103
+ </pre>
104
+ <div className="count-display">
105
+ <span className="count-label">Count</span>
106
+ <span className="count-value">{state.count}</span>
107
+ </div>
108
+ <div className="actions">
109
+ <button type="button" className="btn" onClick={actions.decrease}>
110
+ Decrease
111
+ </button>
112
+ <button type="button" className="btn primary" onClick={actions.increase}>
113
+ Increase
114
+ </button>
115
+ <button type="button" className="btn ghost" onClick={actions.reset}>
116
+ Reset
117
+ </button>
118
+ </div>
119
+ </section>
120
+ );
121
+ }
122
+
123
+ function SingletonControls() {
124
+ const [state, actions] = useStateSingleton(SingletonCounterStore);
125
+
126
+ return (
127
+ <div className="singleton-card">
128
+ <p className="eyebrow">Controller</p>
129
+ <h3>Shared Counter</h3>
130
+ <p className="muted">Interact with the singleton store.</p>
131
+ <div className="singleton-count">{state.count}</div>
132
+ <div className="actions">
133
+ <button type="button" className="btn" onClick={actions.decrease}>
134
+ Decrease
135
+ </button>
136
+ <button type="button" className="btn primary" onClick={actions.increase}>
137
+ Increase
138
+ </button>
139
+ <button type="button" className="btn ghost" onClick={actions.reset}>
140
+ Reset
141
+ </button>
142
+ </div>
143
+ </div>
144
+ );
145
+ }
146
+
147
+ function SingletonDisplay() {
148
+ const [state] = useStateSingleton(SingletonCounterStore);
149
+
150
+ return (
151
+ <div className="singleton-card">
152
+ <p className="eyebrow">Viewer</p>
153
+ <h3>Another Consumer</h3>
154
+ <p className="muted">Updates whenever the singleton changes.</p>
155
+ <div className="singleton-count highlight">{state.count}</div>
156
+ <div className="singleton-hint">Same store instance, different component.</div>
157
+ </div>
158
+ );
159
+ }
160
+
161
+ export function App() {
162
+ const [observableState, observableActions] = useStateFactory(ObservableCounterFactory, [0]);
163
+ const [signalState, signalActions] = useStateFactory(SignalCounterFactory, [0]);
164
+
165
+ const installSnippet = `npm install @veams/status-quo rxjs @preact/signals-core`;
166
+
167
+ const quickstartSnippet = `import { ObservableStateHandler, useStateFactory } from '@veams/status-quo';
168
+
169
+ class CounterStore extends ObservableStateHandler<CounterState, CounterActions> {
170
+ constructor() {
171
+ super({ initialState: { count: 0 } });
172
+ }
173
+
174
+ getActions() {
175
+ return {
176
+ increase: () => this.setState({ count: this.getState().count + 1 })
177
+ };
178
+ }
179
+ }
180
+
181
+ const [state, actions] = useStateFactory(() => new CounterStore(), []);`;
182
+
183
+ const observableSnippet = `class CounterStore extends ObservableStateHandler<CounterState, CounterActions> {
184
+ constructor() {
185
+ super({ initialState: { count: 0 } });
186
+ }
187
+
188
+ getActions() {
189
+ return {
190
+ increase: () => this.setState({ count: this.getState().count + 1 }),
191
+ decrease: () => this.setState({ count: this.getState().count - 1 })
192
+ };
193
+ }
194
+ }`;
195
+
196
+ const signalSnippet = `class CounterStore extends SignalStateHandler<CounterState, CounterActions> {
197
+ constructor() {
198
+ super({ initialState: { count: 0 } });
199
+ }
200
+
201
+ getActions() {
202
+ return {
203
+ increase: () => this.setState({ count: this.getState().count + 1 }),
204
+ decrease: () => this.setState({ count: this.getState().count - 1 })
205
+ };
206
+ }
207
+ }`;
208
+
209
+ const devToolsSnippet = `class CounterStore extends ObservableStateHandler<CounterState, CounterActions> {
210
+ constructor() {
211
+ super({
212
+ initialState: { count: 0 },
213
+ options: { devTools: { enabled: true, namespace: 'Counter' } }
214
+ });
215
+ }
216
+ }`;
217
+
218
+ const cleanupSnippet = `const unsubscribe = store.subscribe(() => {
219
+ console.log(store.getSnapshot());
220
+ });
221
+
222
+ unsubscribe();
223
+ store.destroy();`;
224
+
225
+ const singletonSnippet = `import { makeStateSingleton, useStateSingleton } from '@veams/status-quo';
226
+
227
+ const CounterSingleton = makeStateSingleton(() => new CounterStore());
228
+
229
+ const [state, actions] = useStateSingleton(CounterSingleton);`;
230
+
231
+ const composeSnippet = `import { combineLatest } from "rxjs";
232
+
233
+ // RxJS: combine handler streams (RxJS shines here)
234
+ class AppSignalStore extends SignalStateHandler<AppState, AppActions> {
235
+ private counter$ = CounterObservableStore.getInstance().getStateAsObservable();
236
+ private card$ = new CardObservableHandler();
237
+
238
+ constructor() {
239
+ super({ initialState: { counter: 0, cardTitle: "" }});
240
+
241
+ this.subscriptions.push(
242
+ combineLatest([
243
+ this.counter$,
244
+ this.card$,
245
+ ]).subscribe(([counterState, cardState]) => {
246
+ this.setState({
247
+ counter: counterState,
248
+ cardTitle: cardState.title,
249
+ }, "sync-combined");
250
+ })
251
+ )
252
+ }
253
+
254
+ }
255
+
256
+ // Signals: combine derived values via computed + bindSubscribable
257
+ import { computed } from "@preact/signals-core";
258
+
259
+ class AppSignalStore extends SignalStateHandler<AppState, AppActions> {
260
+ private counter = CounterSignalHandler.getInstance().getSignal();
261
+ private card = new CardSignalHandler();
262
+ private combined = computed(() => ({
263
+ counter: this.counter.getSignal().value,
264
+ cardTitle: this.card.getSignal().value.title,
265
+ }));
266
+
267
+ constructor() {
268
+ super({ initialState: { counter: 0, cardTitle: "" }});
269
+
270
+ this.bindSubscribable(
271
+ { subscribe: this.combined.subscribe.bind(this.combined), getSnapshot: () => this.combined.value },
272
+ (nextState) => this.setState(nextState, "sync-combined")
273
+ );
274
+ }
275
+ }`;
276
+
277
+ useEffect(() => {
278
+ Prism.highlightAll();
279
+ }, []);
280
+
281
+ return (
282
+ <div className="app">
283
+ <div className="brand-bar">
284
+ <img src={statusQuoLogo} alt="StatusQuo logo" className="brand-logo" />
285
+ </div>
286
+ <nav className="nav">
287
+ <div className="nav-links">
288
+ <a href="#overview">Overview</a>
289
+ <a href="#quickstart">Quickstart</a>
290
+ <a href="#factory">Factory</a>
291
+ <a href="#demo">Demo</a>
292
+ <a href="#singleton">Singleton</a>
293
+ <a href="#singleton-demo">Singleton demo</a>
294
+ <a href="#compose">Compose</a>
295
+ <a href="#devtools">Devtools</a>
296
+ <a href="#cleanup">Cleanup</a>
297
+ </div>
298
+ </nav>
299
+ <header id="overview" className="hero intro">
300
+ <div>
301
+ <p className="eyebrow">Philosophy</p>
302
+ <h1>State management that stays out of your way</h1>
303
+ <p className="subtext">
304
+ <span>StatusQuo</span> treats state handlers as small, composable objects with explicit
305
+ lifecycle and a tiny interface. Components subscribe to snapshots, not
306
+ framework‑specific store APIs. That makes it easy to swap the engine under the
307
+ hood—RxJS for observable streams or Preact Signals for ultra‑light reactive state.
308
+ </p>
309
+ <p className="subtext">
310
+ Handlers encapsulate state transitions, expose actions, and clean up after
311
+ themselves. You decide whether each component should have its own instance
312
+ (factory) or share a singleton, while the UI stays blissfully unaware of the
313
+ chosen implementation.
314
+ </p>
315
+ </div>
316
+ </header>
317
+
318
+ <section className="philosophy-grid">
319
+ <article className="philosophy-card">
320
+ <img src={philosophySwap} alt="Swapping state engines illustration" />
321
+ <div>
322
+ <h3>Swap the engine, keep the API</h3>
323
+ <p>
324
+ Move between RxJS observables and Signals without rewriting your components.
325
+ Stores keep the same shape; only the internal runtime changes.
326
+ </p>
327
+ </div>
328
+ </article>
329
+ <article className="philosophy-card">
330
+ <img src={philosophySeparation} alt="View and state separation illustration" />
331
+ <div>
332
+ <h3>Separate view and state</h3>
333
+ <p>
334
+ Handlers own transitions and expose actions. Views subscribe to snapshots and
335
+ never touch store internals.
336
+ </p>
337
+ </div>
338
+ </article>
339
+ <article className="philosophy-card">
340
+ <img src={philosophyAgnostic} alt="Framework-agnostic state core illustration" />
341
+ <div>
342
+ <h3>Framework‑agnostic core</h3>
343
+ <p>
344
+ Your state logic lives outside any UI library. Hooks provide the glue, not the
345
+ business logic.
346
+ </p>
347
+ </div>
348
+ </article>
349
+ </section>
350
+
351
+ <section id="quickstart" className="doc-section">
352
+ <div className="doc-copy">
353
+ <p className="eyebrow">Quickstart</p>
354
+ <h2>Install and wire up a handler</h2>
355
+ <p>
356
+ Pick either observable or signal stores. The hooks stay the same, so you can
357
+ swap the backend without changing your components.
358
+ </p>
359
+ </div>
360
+ <div className="doc-snippets">
361
+ <pre className="code-block compact">
362
+ <code className="language-bash">{installSnippet}</code>
363
+ </pre>
364
+ <pre className="code-block compact">
365
+ <code className="language-ts">{quickstartSnippet}</code>
366
+ </pre>
367
+ </div>
368
+ </section>
369
+
370
+ <section className="guide">
371
+ <article id="factory" className="guide-card">
372
+ <div>
373
+ <p className="eyebrow">Factory</p>
374
+ <h2>Fresh handler per mount</h2>
375
+ <p>
376
+ Use `useStateFactory` when every component instance needs its own store. You
377
+ pass a factory function and dependencies, and the hook handles lifecycle
378
+ cleanup for you.
379
+ </p>
380
+ </div>
381
+ <pre className="code-block compact">
382
+ <code className="language-ts">{`const [state, actions] = useStateFactory(CounterFactory, [0]);`}</code>
383
+ </pre>
384
+ </article>
385
+ <article id="singleton" className="guide-card">
386
+ <div>
387
+ <p className="eyebrow">Singleton</p>
388
+ <h2>One handler, many consumers</h2>
389
+ <p>
390
+ Use `makeStateSingleton` and `useStateSingleton` for shared stores. Ideal for
391
+ global counters, user sessions, or any shared UI state.
392
+ </p>
393
+ </div>
394
+ <pre className="code-block compact">
395
+ <code className="language-ts">{singletonSnippet}</code>
396
+ </pre>
397
+ </article>
398
+ </section>
399
+
400
+ <div id="demo" className="grid">
401
+ <CounterCard
402
+ title="Observable State Handler"
403
+ subtitle="RxJS BehaviorSubject"
404
+ state={observableState}
405
+ actions={observableActions}
406
+ snippet={observableSnippet}
407
+ />
408
+ <CounterCard
409
+ title="Signal State Handler"
410
+ subtitle="Preact Signals"
411
+ state={signalState}
412
+ actions={signalActions}
413
+ snippet={signalSnippet}
414
+ />
415
+ </div>
416
+
417
+ <section id="singleton-demo" className="doc-section">
418
+ <div className="doc-copy">
419
+ <p className="eyebrow">Singleton demo</p>
420
+ <h2>One store, multiple components</h2>
421
+ <p>
422
+ Both panels below are subscribed to the same singleton handler. Update the
423
+ controller and watch the viewer react instantly.
424
+ </p>
425
+ </div>
426
+ <div className="singleton-grid">
427
+ <SingletonControls />
428
+ <SingletonDisplay />
429
+ </div>
430
+ </section>
431
+
432
+ <section id="compose" className="doc-section">
433
+ <div className="doc-copy">
434
+ <p className="eyebrow">Compose</p>
435
+ <h2>Combine multiple handlers</h2>
436
+ <p>
437
+ Use only the slice you need. RxJS makes multi-source composition powerful and
438
+ declarative with operators like `combineLatest`, `switchMap`, or `debounceTime`.
439
+ Signals can derive values with `computed` and wire them into a parent store via
440
+ `bindSubscribable`. This keeps parent stores lean and focused.
441
+ </p>
442
+ </div>
443
+ <pre className="code-block">
444
+ <code className="language-ts">{composeSnippet}</code>
445
+ </pre>
446
+ </section>
447
+
448
+ <section id="devtools" className="doc-section">
449
+ <div className="doc-copy">
450
+ <p className="eyebrow">Devtools</p>
451
+ <h2>Redux devtools integration</h2>
452
+ <p>
453
+ Enable devtools to inspect actions and state transitions. Both handlers share
454
+ the same options shape.
455
+ </p>
456
+ </div>
457
+ <pre className="code-block">
458
+ <code className="language-ts">{devToolsSnippet}</code>
459
+ </pre>
460
+ </section>
461
+
462
+ <section id="cleanup" className="doc-section">
463
+ <div className="doc-copy">
464
+ <p className="eyebrow">Cleanup</p>
465
+ <h2>Controlled teardown</h2>
466
+ <p>
467
+ Each handler exposes `subscribe`, `getSnapshot`, and `destroy`. Use them when
468
+ you wire custom integrations or combine multiple stores manually.
469
+ </p>
470
+ </div>
471
+ <pre className="code-block">
472
+ <code className="language-ts">{cleanupSnippet}</code>
473
+ </pre>
474
+ </section>
475
+
476
+ </div>
477
+ );
478
+ }
@@ -0,0 +1,18 @@
1
+ <svg width="640" height="420" viewBox="0 0 640 420" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="640" height="420" rx="48" fill="#0E1116"/>
3
+ <circle cx="320" cy="210" r="120" fill="#1B2330" stroke="#2F6BFF" stroke-width="2"/>
4
+ <circle cx="320" cy="210" r="64" fill="#2F6BFF" fill-opacity="0.22"/>
5
+ <circle cx="320" cy="210" r="24" fill="#2F6BFF"/>
6
+ <path d="M320 78V122" stroke="#E7E9EE" stroke-width="4" stroke-linecap="round"/>
7
+ <path d="M320 298V342" stroke="#E7E9EE" stroke-width="4" stroke-linecap="round"/>
8
+ <path d="M188 210H232" stroke="#E7E9EE" stroke-width="4" stroke-linecap="round"/>
9
+ <path d="M408 210H452" stroke="#E7E9EE" stroke-width="4" stroke-linecap="round"/>
10
+ <circle cx="320" cy="78" r="12" fill="#FF9F1C"/>
11
+ <circle cx="320" cy="342" r="12" fill="#7B879A"/>
12
+ <circle cx="188" cy="210" r="12" fill="#FF9F1C"/>
13
+ <circle cx="452" cy="210" r="12" fill="#7B879A"/>
14
+ <path d="M238 134L270 156" stroke="#E7E9EE" stroke-width="3" stroke-linecap="round"/>
15
+ <path d="M402 134L370 156" stroke="#E7E9EE" stroke-width="3" stroke-linecap="round"/>
16
+ <path d="M238 286L270 264" stroke="#E7E9EE" stroke-width="3" stroke-linecap="round"/>
17
+ <path d="M402 286L370 264" stroke="#E7E9EE" stroke-width="3" stroke-linecap="round"/>
18
+ </svg>
@@ -0,0 +1,13 @@
1
+ <svg width="640" height="420" viewBox="0 0 640 420" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="640" height="420" rx="48" fill="#0E1116"/>
3
+ <rect x="64" y="70" width="220" height="280" rx="32" fill="#1B2330" stroke="#2F6BFF" stroke-width="2"/>
4
+ <rect x="356" y="70" width="220" height="280" rx="32" fill="#1B2330" stroke="#7B879A" stroke-width="2"/>
5
+ <rect x="90" y="110" width="170" height="28" rx="14" fill="#2F6BFF" fill-opacity="0.25"/>
6
+ <rect x="90" y="160" width="140" height="28" rx="14" fill="#2F6BFF" fill-opacity="0.25"/>
7
+ <rect x="90" y="210" width="120" height="28" rx="14" fill="#2F6BFF" fill-opacity="0.25"/>
8
+ <rect x="382" y="120" width="168" height="44" rx="22" fill="#2F6BFF"/>
9
+ <rect x="382" y="186" width="168" height="44" rx="22" fill="#FF9F1C"/>
10
+ <rect x="382" y="252" width="168" height="44" rx="22" fill="#7B879A"/>
11
+ <path d="M284 210H356" stroke="#E7E9EE" stroke-width="4" stroke-linecap="round" stroke-dasharray="10 12"/>
12
+ <circle cx="320" cy="210" r="8" fill="#E7E9EE"/>
13
+ </svg>
@@ -0,0 +1,17 @@
1
+ <svg width="640" height="420" viewBox="0 0 640 420" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="640" height="420" rx="48" fill="#0E1116"/>
3
+ <rect x="40" y="70" width="220" height="280" rx="28" fill="#1B2330" stroke="#2F6BFF" stroke-width="2"/>
4
+ <rect x="380" y="70" width="220" height="280" rx="28" fill="#1B2330" stroke="#FF9F1C" stroke-width="2"/>
5
+ <circle cx="150" cy="150" r="44" fill="#2F6BFF" fill-opacity="0.2"/>
6
+ <circle cx="150" cy="150" r="18" fill="#2F6BFF"/>
7
+ <circle cx="490" cy="270" r="44" fill="#FF9F1C" fill-opacity="0.2"/>
8
+ <circle cx="490" cy="270" r="18" fill="#FF9F1C"/>
9
+ <path d="M260 155H330" stroke="#E7E9EE" stroke-width="4" stroke-linecap="round"/>
10
+ <path d="M330 155L312 141" stroke="#E7E9EE" stroke-width="4" stroke-linecap="round"/>
11
+ <path d="M330 155L312 169" stroke="#E7E9EE" stroke-width="4" stroke-linecap="round"/>
12
+ <path d="M380 265H310" stroke="#E7E9EE" stroke-width="4" stroke-linecap="round"/>
13
+ <path d="M310 265L328 251" stroke="#E7E9EE" stroke-width="4" stroke-linecap="round"/>
14
+ <path d="M310 265L328 279" stroke="#E7E9EE" stroke-width="4" stroke-linecap="round"/>
15
+ <rect x="90" y="230" width="120" height="18" rx="9" fill="#2F6BFF" fill-opacity="0.3"/>
16
+ <rect x="430" y="160" width="120" height="18" rx="9" fill="#FF9F1C" fill-opacity="0.3"/>
17
+ </svg>
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+
4
+ import 'prismjs/themes/prism-tomorrow.css';
5
+
6
+ import { App } from './App.js';
7
+ import './styles.css';
8
+
9
+ const rootElement = document.getElementById('root');
10
+
11
+ if (!rootElement) {
12
+ throw new Error('Missing root element');
13
+ }
14
+
15
+ createRoot(rootElement).render(
16
+ <React.StrictMode>
17
+ <App />
18
+ </React.StrictMode>
19
+ );