fusee-framework 1.5.2 → 1.5.3
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/README.md +1 -1
- package/app/routes/Home.js +44 -6
- package/app/routes/Home.ts +49 -3
- package/framework/core/directives.js +41 -0
- package/framework/core/signal.js +21 -0
- package/framework/types/directives.d.ts +1 -0
- package/index.html +253 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Fusée Framework 🚀
|
|
2
|
-
**v1.5.
|
|
2
|
+
**v1.5.3 — Signals-First JS Framework | Atomic Reactivity | Peak Performance**
|
|
3
3
|
|
|
4
4
|
Fusée is a custom, high-performance fine-grained reactive JavaScript framework built for speed and simplicity. It features a recursive, non-greedy compiler, a signals-based reactivity engine, Dependency Injection for components and a comprehensive CLI for instant application scaffolding.
|
|
5
5
|
|
package/app/routes/Home.js
CHANGED
|
@@ -9,29 +9,67 @@ export const Home = defineComponent({
|
|
|
9
9
|
setup() {
|
|
10
10
|
const title = signal('Welcome!')
|
|
11
11
|
inspect(title)
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
const isActive = signal(false)
|
|
14
|
+
const isDisabled = signal(false)
|
|
15
|
+
const isLarge = signal(false)
|
|
16
|
+
const basicClasses = computed(() => ({
|
|
17
|
+
active: isActive(),
|
|
18
|
+
disabled: isDisabled(),
|
|
19
|
+
'is-large': isLarge()
|
|
20
|
+
}))
|
|
21
|
+
|
|
22
|
+
const toggleActive = () => isActive(!isActive())
|
|
23
|
+
const toggleDisabled = () => isDisabled(!isDisabled())
|
|
24
|
+
const toggleLarge = () => isLarge(!isLarge())
|
|
25
|
+
|
|
13
26
|
return {
|
|
14
27
|
title,
|
|
15
28
|
changeTitle: () => title('Changed Title!'),
|
|
16
|
-
|
|
29
|
+
|
|
30
|
+
isActive,
|
|
31
|
+
isDisabled,
|
|
32
|
+
isLarge,
|
|
33
|
+
basicClasses,
|
|
34
|
+
toggleActive,
|
|
35
|
+
toggleDisabled,
|
|
36
|
+
toggleLarge,
|
|
37
|
+
|
|
17
38
|
// 🧪 Emit Event Handlers
|
|
18
39
|
handleCounterChange: (value) => console.log('📢 Counter changed to:', value),
|
|
19
40
|
handleCounterReset: () => console.log('🔄 Counter reset requested!'),
|
|
20
41
|
handleCustomEvent: (count, multiplier) => console.log('🎯 Custom event:', { count, multiplier }),
|
|
21
|
-
|
|
42
|
+
|
|
22
43
|
// 🧪 Emit Test Handlers
|
|
23
44
|
handleSimpleClick: () => console.log('✅ SIMPLE CLICK received from child!'),
|
|
24
45
|
handleClickWithData: (count, label, timestamp) => console.log('✅ CLICK WITH DATA:', { count, label, timestamp }),
|
|
25
46
|
handleObjectEvent: (data) => console.log('✅ OBJECT EVENT received:', data),
|
|
26
|
-
|
|
47
|
+
|
|
27
48
|
template: `
|
|
28
49
|
<div class="page">
|
|
29
50
|
<h1>{{ title.toUpperCase() }}</h1>
|
|
30
51
|
<p>This is an SPA built with my own JavaScript custom framework!</p>
|
|
31
52
|
<p>Made with passion by Sebastian Șomu</p>
|
|
32
|
-
|
|
53
|
+
|
|
33
54
|
<button @click="changeTitle" class="primary-btn" style="margin-bottom: 20px;">Change App Title</button>
|
|
34
|
-
|
|
55
|
+
|
|
56
|
+
<!-- 🎨 f-classList Demo -->
|
|
57
|
+
<h3 style="margin-top: 40px; margin-bottom: 20px; color: #f59e0b;">🎨 f-classList Demo</h3>
|
|
58
|
+
<div class="demo-box" f-classList="basicClasses">
|
|
59
|
+
Demo Box (hover me!)
|
|
60
|
+
</div>
|
|
61
|
+
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 1rem;">
|
|
62
|
+
<button @click="toggleActive" :class="isActive ? 'btn-on' : 'btn-off'" style="padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer;">
|
|
63
|
+
{{ isActive ? '✓ Active' : '○ Active' }}
|
|
64
|
+
</button>
|
|
65
|
+
<button @click="toggleDisabled" :class="isDisabled ? 'btn-on' : 'btn-off'" style="padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer;">
|
|
66
|
+
{{ isDisabled ? '✓ Disabled' : '○ Disabled' }}
|
|
67
|
+
</button>
|
|
68
|
+
<button @click="toggleLarge" :class="isLarge ? 'btn-on' : 'btn-off'" style="padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer;">
|
|
69
|
+
{{ isLarge ? '✓ Large' : '○ Large' }}
|
|
70
|
+
</button>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
35
73
|
{{ UserForm }}
|
|
36
74
|
|
|
37
75
|
{{ CounterSection @counter-change="handleCounterChange" @counter-reset="handleCounterReset" @custom-event="handleCustomEvent" :pageTitle="title" }}
|
package/app/routes/Home.ts
CHANGED
|
@@ -4,26 +4,72 @@ import { CounterSection } from '../components/CounterSection.js'
|
|
|
4
4
|
type HomeResult = {
|
|
5
5
|
title: Signal<string>
|
|
6
6
|
changeTitle: () => void
|
|
7
|
+
isActive: Signal<boolean>
|
|
8
|
+
isDisabled: Signal<boolean>
|
|
9
|
+
isLarge: Signal<boolean>
|
|
10
|
+
basicClasses: Computed<ClassListObject>
|
|
11
|
+
toggleActive: () => void
|
|
12
|
+
toggleDisabled: () => void
|
|
13
|
+
toggleLarge: () => void
|
|
7
14
|
template: string
|
|
8
15
|
}
|
|
9
16
|
|
|
17
|
+
type ClassListObject = Record<string, boolean>
|
|
18
|
+
|
|
10
19
|
export const Home = defineComponent({
|
|
11
20
|
components: { UserForm, CounterSection },
|
|
12
21
|
setup(): HomeResult {
|
|
13
22
|
const title = signal<string>('Welcome!')
|
|
23
|
+
|
|
24
|
+
const isActive = signal(false)
|
|
25
|
+
const isDisabled = signal(false)
|
|
26
|
+
const isLarge = signal(false)
|
|
27
|
+
const basicClasses = computed(() => ({
|
|
28
|
+
active: isActive(),
|
|
29
|
+
disabled: isDisabled(),
|
|
30
|
+
'is-large': isLarge()
|
|
31
|
+
}))
|
|
32
|
+
|
|
33
|
+
const toggleActive = () => isActive(!isActive())
|
|
34
|
+
const toggleDisabled = () => isDisabled(!isDisabled())
|
|
35
|
+
const toggleLarge = () => isLarge(!isLarge())
|
|
36
|
+
|
|
14
37
|
return {
|
|
15
38
|
title,
|
|
16
39
|
changeTitle: () => title('Changed Title!'),
|
|
40
|
+
isActive,
|
|
41
|
+
isDisabled,
|
|
42
|
+
isLarge,
|
|
43
|
+
basicClasses,
|
|
44
|
+
toggleActive,
|
|
45
|
+
toggleDisabled,
|
|
46
|
+
toggleLarge,
|
|
17
47
|
template: `
|
|
18
48
|
<div class="page">
|
|
19
49
|
<h1>{{ title.toUpperCase() }}</h1>
|
|
20
50
|
<p>This is an SPA built with my own (TypeScript!) custom framework!</p>
|
|
21
51
|
<p>Made with passion by Sebastian Șomu</p>
|
|
22
|
-
|
|
52
|
+
|
|
23
53
|
<button @click="changeTitle" class="primary-btn" style="margin-bottom: 20px;">Change App Title</button>
|
|
24
|
-
|
|
54
|
+
|
|
55
|
+
<h3 style="margin-top: 40px; margin-bottom: 20px; color: #f59e0b;">🎨 f-classList Demo</h3>
|
|
56
|
+
<div class="demo-box" f-classList="basicClasses">
|
|
57
|
+
Demo Box (hover me!)
|
|
58
|
+
</div>
|
|
59
|
+
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 1rem;">
|
|
60
|
+
<button @click="toggleActive" :class="isActive ? 'btn-on' : 'btn-off'" style="padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer;">
|
|
61
|
+
{{ isActive ? '✓ Active' : '○ Active' }}
|
|
62
|
+
</button>
|
|
63
|
+
<button @click="toggleDisabled" :class="isDisabled ? 'btn-on' : 'btn-off'" style="padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer;">
|
|
64
|
+
{{ isDisabled ? '✓ Disabled' : '○ Disabled' }}
|
|
65
|
+
</button>
|
|
66
|
+
<button @click="toggleLarge" :class="isLarge ? 'btn-on' : 'btn-off'" style="padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer;">
|
|
67
|
+
{{ isLarge ? '✓ Large' : '○ Large' }}
|
|
68
|
+
</button>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
25
71
|
{{ UserForm }}
|
|
26
|
-
|
|
72
|
+
|
|
27
73
|
{{ CounterSection :pageTitle="title" }}
|
|
28
74
|
</div>
|
|
29
75
|
`
|
|
@@ -81,6 +81,7 @@ export function processDirectives(root, context, components, effects) {
|
|
|
81
81
|
processText(root, context, effects)
|
|
82
82
|
processEvents(root, context, effects)
|
|
83
83
|
processCustomDirectives(root, context, effects)
|
|
84
|
+
processClassList(root, context, effects)
|
|
84
85
|
processCloak(root)
|
|
85
86
|
|
|
86
87
|
return false
|
|
@@ -589,3 +590,43 @@ export function processCloak(el) {
|
|
|
589
590
|
const els = el.querySelectorAll('[f-cloak]')
|
|
590
591
|
for (const e of els) e.removeAttribute('f-cloak')
|
|
591
592
|
}
|
|
593
|
+
|
|
594
|
+
export function processClassList(el, context, effects) {
|
|
595
|
+
if (el.nodeType !== 1 || !el.hasAttribute('f-classList')) return
|
|
596
|
+
|
|
597
|
+
const expr = el.getAttribute('f-classList')
|
|
598
|
+
el.removeAttribute('f-classList')
|
|
599
|
+
|
|
600
|
+
const appliedClasses = new Set()
|
|
601
|
+
|
|
602
|
+
const e = effect(() => {
|
|
603
|
+
const result = evaluateExpression(expr, context)
|
|
604
|
+
|
|
605
|
+
if (!result || typeof result !== 'object') {
|
|
606
|
+
console.warn(`[framework] f-classList must evaluate to an object, got: ${typeof result}`)
|
|
607
|
+
return
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
for (const [className, shouldApply] of Object.entries(result)) {
|
|
611
|
+
const isApplied = appliedClasses.has(className)
|
|
612
|
+
|
|
613
|
+
if (shouldApply && !isApplied) {
|
|
614
|
+
el.classList.add(className)
|
|
615
|
+
appliedClasses.add(className)
|
|
616
|
+
} else if (!shouldApply && isApplied) {
|
|
617
|
+
el.classList.remove(className)
|
|
618
|
+
appliedClasses.delete(className)
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const currentClasses = new Set(Object.keys(result))
|
|
623
|
+
for (const className of [...appliedClasses]) {
|
|
624
|
+
if (!currentClasses.has(className)) {
|
|
625
|
+
el.classList.remove(className)
|
|
626
|
+
appliedClasses.delete(className)
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
})
|
|
630
|
+
|
|
631
|
+
effects.push(e)
|
|
632
|
+
}
|
package/framework/core/signal.js
CHANGED
|
@@ -61,6 +61,7 @@ export function signal(initialValue) {
|
|
|
61
61
|
export function effect(fn) {
|
|
62
62
|
let active = true
|
|
63
63
|
let running = false
|
|
64
|
+
const nestedEffects = new Set()
|
|
64
65
|
|
|
65
66
|
const run = () => {
|
|
66
67
|
if (!active || running) {
|
|
@@ -69,14 +70,28 @@ export function effect(fn) {
|
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
running = true
|
|
73
|
+
|
|
74
|
+
for (const childCleanup of nestedEffects) {
|
|
75
|
+
childCleanup()
|
|
76
|
+
}
|
|
77
|
+
nestedEffects.clear()
|
|
78
|
+
|
|
72
79
|
for (const dep of run.deps) dep.delete(run)
|
|
73
80
|
run.deps.clear()
|
|
74
81
|
|
|
75
82
|
const prevEffect = currentEffect
|
|
83
|
+
const prevOnEffectCreated = onEffectCreated
|
|
84
|
+
|
|
85
|
+
onEffectCreated = (childCleanup) => {
|
|
86
|
+
nestedEffects.add(childCleanup)
|
|
87
|
+
if (prevOnEffectCreated) prevOnEffectCreated(childCleanup)
|
|
88
|
+
}
|
|
89
|
+
|
|
76
90
|
currentEffect = run
|
|
77
91
|
try { fn() }
|
|
78
92
|
finally {
|
|
79
93
|
currentEffect = prevEffect
|
|
94
|
+
onEffectCreated = prevOnEffectCreated
|
|
80
95
|
running = false
|
|
81
96
|
}
|
|
82
97
|
}
|
|
@@ -88,6 +103,12 @@ export function effect(fn) {
|
|
|
88
103
|
pendingEffects.delete(run)
|
|
89
104
|
for (const dep of run.deps) dep.delete(run)
|
|
90
105
|
run.deps.clear()
|
|
106
|
+
|
|
107
|
+
// Cleanup all nested effects when parent stops
|
|
108
|
+
for (const childCleanup of nestedEffects) {
|
|
109
|
+
childCleanup()
|
|
110
|
+
}
|
|
111
|
+
nestedEffects.clear()
|
|
91
112
|
}
|
|
92
113
|
|
|
93
114
|
if (onEffectCreated) onEffectCreated(cleanup)
|
|
@@ -36,6 +36,7 @@ export function processShow(root: HTMLElement, context: ComponentResult, effects
|
|
|
36
36
|
export function processHtml(root: HTMLElement, context: ComponentResult, effects: (() => void)[]): void
|
|
37
37
|
export function processText(root: HTMLElement, context: ComponentResult, effects: (() => void)[]): void
|
|
38
38
|
export function processEvents(root: HTMLElement, context: ComponentResult): void
|
|
39
|
+
export function processClassList(root: HTMLElement, context: ComponentResult, effects: (() => void)[]): void
|
|
39
40
|
export function processCloak(root: HTMLElement): void
|
|
40
41
|
|
|
41
42
|
export interface DirectiveBinding<V = any> {
|
package/index.html
CHANGED
|
@@ -293,6 +293,259 @@
|
|
|
293
293
|
.cmp-input:focus {
|
|
294
294
|
border-color: #8b8bff;
|
|
295
295
|
}
|
|
296
|
+
|
|
297
|
+
/* ── f-classList demo styles (Home page) ─────────────────────── */
|
|
298
|
+
.demo-box {
|
|
299
|
+
width: 200px;
|
|
300
|
+
height: 100px;
|
|
301
|
+
background: #1a1a24;
|
|
302
|
+
border: 2px solid #3a3a5a;
|
|
303
|
+
border-radius: 8px;
|
|
304
|
+
display: flex;
|
|
305
|
+
align-items: center;
|
|
306
|
+
justify-content: center;
|
|
307
|
+
font-weight: 600;
|
|
308
|
+
transition: all 0.3s ease;
|
|
309
|
+
margin-top: 1rem;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.demo-box.active {
|
|
313
|
+
border-color: #8b8bff;
|
|
314
|
+
background: rgba(139, 139, 255, 0.1);
|
|
315
|
+
box-shadow: 0 0 20px rgba(139, 139, 255, 0.3);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.demo-box.disabled {
|
|
319
|
+
opacity: 0.5;
|
|
320
|
+
cursor: not-allowed;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.demo-box.is-large {
|
|
324
|
+
width: 300px;
|
|
325
|
+
height: 150px;
|
|
326
|
+
font-size: 1.2rem;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.btn-on {
|
|
330
|
+
background: #8b8bff !important;
|
|
331
|
+
border-color: #8b8bff !important;
|
|
332
|
+
color: #fff !important;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.btn-off {
|
|
336
|
+
background: #1a1a24 !important;
|
|
337
|
+
border-color: #555 !important;
|
|
338
|
+
color: #888 !important;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.test-classlist h1 {
|
|
342
|
+
color: #f59e0b;
|
|
343
|
+
text-shadow: 0 0 20px rgba(245, 158, 11, 0.3);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.test-classlist .subtitle {
|
|
347
|
+
color: #aaa;
|
|
348
|
+
font-size: 0.9rem;
|
|
349
|
+
margin-bottom: 2rem;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.demo-section {
|
|
353
|
+
background: #13131d;
|
|
354
|
+
border: 1px solid #2a2a3a;
|
|
355
|
+
border-radius: 12px;
|
|
356
|
+
padding: 1.5rem;
|
|
357
|
+
margin-bottom: 1.5rem;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.demo-section h2 {
|
|
361
|
+
font-size: 1rem;
|
|
362
|
+
font-weight: 600;
|
|
363
|
+
color: #e8e8f0;
|
|
364
|
+
margin-bottom: 1rem;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.demo-box {
|
|
368
|
+
width: 200px;
|
|
369
|
+
height: 100px;
|
|
370
|
+
background: #1a1a24;
|
|
371
|
+
border: 2px solid #3a3a5a;
|
|
372
|
+
border-radius: 8px;
|
|
373
|
+
display: flex;
|
|
374
|
+
align-items: center;
|
|
375
|
+
justify-content: center;
|
|
376
|
+
font-weight: 600;
|
|
377
|
+
transition: all 0.3s ease;
|
|
378
|
+
margin-bottom: 1rem;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.demo-box.active {
|
|
382
|
+
border-color: #8b8bff;
|
|
383
|
+
background: rgba(139, 139, 255, 0.1);
|
|
384
|
+
box-shadow: 0 0 20px rgba(139, 139, 255, 0.3);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.demo-box.disabled {
|
|
388
|
+
opacity: 0.5;
|
|
389
|
+
cursor: not-allowed;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.demo-box.is-large {
|
|
393
|
+
width: 300px;
|
|
394
|
+
height: 150px;
|
|
395
|
+
font-size: 1.2rem;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.controls {
|
|
399
|
+
display: flex;
|
|
400
|
+
gap: 0.5rem;
|
|
401
|
+
flex-wrap: wrap;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.controls button {
|
|
405
|
+
padding: 0.5rem 1rem;
|
|
406
|
+
border: 1px solid #3a3a5a;
|
|
407
|
+
background: #0f0f1a;
|
|
408
|
+
color: #e8e8f0;
|
|
409
|
+
border-radius: 6px;
|
|
410
|
+
cursor: pointer;
|
|
411
|
+
transition: all 0.15s;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
.controls button:hover {
|
|
415
|
+
background: #2a2a3a;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.controls button.btn-on {
|
|
419
|
+
background: #8b8bff;
|
|
420
|
+
border-color: #8b8bff;
|
|
421
|
+
color: #fff;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.controls button.btn-off {
|
|
425
|
+
background: #1a1a24;
|
|
426
|
+
border-color: #555;
|
|
427
|
+
color: #888;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.theme-box {
|
|
431
|
+
padding: 1.5rem;
|
|
432
|
+
border-radius: 8px;
|
|
433
|
+
border: 2px solid #3a3a5a;
|
|
434
|
+
transition: all 0.3s ease;
|
|
435
|
+
margin-bottom: 1rem;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.theme-box.theme-light {
|
|
439
|
+
background: rgba(245, 158, 11, 0.1);
|
|
440
|
+
border-color: #f59e0b;
|
|
441
|
+
color: #f59e0b;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.theme-box.theme-dark {
|
|
445
|
+
background: rgba(139, 139, 255, 0.1);
|
|
446
|
+
border-color: #8b8bff;
|
|
447
|
+
color: #8b8bff;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
.theme-toggle {
|
|
451
|
+
padding: 0.6rem 1.25rem;
|
|
452
|
+
background: #f59e0b;
|
|
453
|
+
color: #000;
|
|
454
|
+
border: none;
|
|
455
|
+
border-radius: 6px;
|
|
456
|
+
font-weight: 600;
|
|
457
|
+
cursor: pointer;
|
|
458
|
+
transition: background 0.15s;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
.theme-toggle:hover {
|
|
462
|
+
background: #d97706;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
.action-btn {
|
|
466
|
+
padding: 0.75rem 1.5rem;
|
|
467
|
+
border: 2px solid #3a3a5a;
|
|
468
|
+
background: #1a1a24;
|
|
469
|
+
color: #e8e8f0;
|
|
470
|
+
border-radius: 8px;
|
|
471
|
+
font-weight: 600;
|
|
472
|
+
cursor: pointer;
|
|
473
|
+
transition: all 0.3s ease;
|
|
474
|
+
margin-bottom: 1rem;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.action-btn.btn-primary {
|
|
478
|
+
border-color: #8b8bff;
|
|
479
|
+
color: #8b8bff;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
.action-btn.btn-primary:hover {
|
|
483
|
+
background: rgba(139, 139, 255, 0.1);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
.action-btn.btn-loading {
|
|
487
|
+
border-color: #f59e0b;
|
|
488
|
+
color: #f59e0b;
|
|
489
|
+
animation: pulse 1.5s infinite;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
.action-btn.btn-error {
|
|
493
|
+
border-color: #ff3333;
|
|
494
|
+
background: rgba(255, 51, 51, 0.1);
|
|
495
|
+
color: #ff3333;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
.action-btn.btn-active {
|
|
499
|
+
transform: scale(0.95);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
@keyframes pulse {
|
|
503
|
+
0%, 100% { opacity: 1; }
|
|
504
|
+
50% { opacity: 0.5; }
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
.info-section {
|
|
508
|
+
background: #0f0f1a;
|
|
509
|
+
border: 1px solid #2a2a3a;
|
|
510
|
+
border-radius: 12px;
|
|
511
|
+
padding: 1.5rem;
|
|
512
|
+
margin-top: 2rem;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.info-section h2 {
|
|
516
|
+
color: #34d399;
|
|
517
|
+
margin-bottom: 1rem;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
.info-section ul {
|
|
521
|
+
margin-left: 1.5rem;
|
|
522
|
+
margin-bottom: 1.5rem;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
.info-section li {
|
|
526
|
+
color: #aaa;
|
|
527
|
+
margin-bottom: 0.5rem;
|
|
528
|
+
line-height: 1.5;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
.info-section li strong {
|
|
532
|
+
color: #e8e8f0;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.code-example {
|
|
536
|
+
background: #1a1a24;
|
|
537
|
+
border: 1px solid #2a2a3a;
|
|
538
|
+
border-radius: 8px;
|
|
539
|
+
padding: 1rem;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
.code-example pre {
|
|
543
|
+
color: #8b8bff;
|
|
544
|
+
font-family: 'Courier New', monospace;
|
|
545
|
+
font-size: 0.85rem;
|
|
546
|
+
margin: 0;
|
|
547
|
+
overflow-x: auto;
|
|
548
|
+
}
|
|
296
549
|
</style>
|
|
297
550
|
</head>
|
|
298
551
|
|