@adiholick/nvstudio-mcp 2.2.3 → 2.4.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/README.md +57 -47
- package/agent-plugin/mcp_config.json +8 -0
- package/agent-plugin/plugin.json +6 -0
- package/agent-plugin/rules/AGENTS.md +118 -0
- package/agent-plugin/skills/nvstudio-animation-vfx/SKILL.md +120 -0
- package/agent-plugin/skills/nvstudio-assets-terrain-guide/SKILL.md +27 -0
- package/agent-plugin/skills/nvstudio-gui-standards/SKILL.md +419 -0
- package/agent-plugin/skills/nvstudio-mcp-guide/SKILL.md +83 -0
- package/agent-plugin/skills/nvstudio-rig-engine/SKILL.md +170 -0
- package/agent-plugin/skills/nvstudio-roblox-sync-guide/SKILL.md +16 -0
- package/dist/build-plugin.js +55 -0
- package/dist/index.js +841 -3
- package/dist/luau-validator.js +99 -34
- package/dist/roblox-bridge.js +152 -4
- package/dist/roblox-docs.js +264 -0
- package/dist/vfx-database.js +507 -0
- package/package.json +6 -4
- package/public/index.html +1590 -180
- package/studio-plugin/Main.server.lua +1 -1
- package/studio-plugin/animate_character.lua +36 -0
- package/studio-plugin/apply_vfx.lua +59 -0
- package/studio-plugin/batch_create_instances.lua +128 -0
- package/studio-plugin/batch_set_properties.lua +69 -0
- package/studio-plugin/edit_rig.lua +35 -0
- package/studio-plugin/insert_asset.lua +85 -12
- package/studio-plugin/nv_anim_dispatcher.lua +239 -0
- package/studio-plugin/nv_auto_rigger.lua +392 -0
- package/studio-plugin/nv_curves.lua +260 -0
- package/studio-plugin/nv_joint_math.lua +138 -0
- package/studio-plugin/nv_kinematics.lua +248 -0
- package/studio-plugin/nv_motion_generator.lua +279 -0
- package/studio-plugin/nv_rig_dispatcher.lua +319 -0
- package/studio-plugin/nv_vfx_serializer.lua +363 -0
- package/studio-plugin/nvstudio_mcp.rbxmx +3824 -60
- package/studio-plugin/preview_asset.lua +99 -0
- package/studio-plugin/scan_security.lua +81 -0
- package/studio-plugin/scan_vfx.lua +85 -0
- package/studio-plugin/scrub_timeline.lua +122 -0
- package/studio-plugin/security_scanner.lua +346 -0
- package/studio-plugin/set_properties.lua +83 -0
- package/studio-plugin/solve_limb_ik.lua +63 -0
- package/studio-plugin/ui_manager.lua +1 -1
package/public/index.html
CHANGED
|
@@ -3,33 +3,41 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>nvstudio-mcp | Bridge Dashboard</title>
|
|
6
|
+
<title>nvstudio-mcp v2.4 | Bridge Dashboard</title>
|
|
7
7
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
8
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
|
-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
10
10
|
<style>
|
|
11
11
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
12
12
|
|
|
13
13
|
:root {
|
|
14
|
-
--bg: #
|
|
15
|
-
--surface: #
|
|
16
|
-
--surface-glass: rgba(
|
|
17
|
-
--surface-card: #
|
|
18
|
-
--surface-hover: #
|
|
14
|
+
--bg: #0c0c0e;
|
|
15
|
+
--surface: #121216;
|
|
16
|
+
--surface-glass: rgba(18, 18, 22, 0.75);
|
|
17
|
+
--surface-card: #18181d;
|
|
18
|
+
--surface-hover: #222228;
|
|
19
19
|
--border: rgba(255, 255, 255, 0.08);
|
|
20
|
-
--border-highlight: rgba(255, 255, 255, 0.
|
|
20
|
+
--border-highlight: rgba(255, 255, 255, 0.16);
|
|
21
21
|
|
|
22
|
-
--text: #
|
|
22
|
+
--text: #f4f4f6;
|
|
23
23
|
--text-muted: #a1a1aa;
|
|
24
24
|
--text-dim: #71717a;
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
--accent
|
|
26
|
+
/* NV-Series Accent Colors */
|
|
27
|
+
--accent: #00aaff;
|
|
28
|
+
--accent-glow: rgba(0, 170, 255, 0.25);
|
|
29
|
+
--cyan-glow: rgba(0, 255, 255, 0.2);
|
|
28
30
|
--success: #10b981;
|
|
29
31
|
--success-glow: rgba(16, 185, 129, 0.25);
|
|
30
32
|
--error: #ef4444;
|
|
31
33
|
--error-glow: rgba(239, 68, 68, 0.2);
|
|
32
|
-
--
|
|
34
|
+
--warning: #f59e0b;
|
|
35
|
+
--warning-glow: rgba(245, 158, 11, 0.25);
|
|
36
|
+
|
|
37
|
+
--task: #a855f7;
|
|
38
|
+
--batch: #00aaff;
|
|
39
|
+
--security: #f59e0b;
|
|
40
|
+
--docs: #10b981;
|
|
33
41
|
--response: #14b8a6;
|
|
34
42
|
--system: #a1a1aa;
|
|
35
43
|
|
|
@@ -50,7 +58,7 @@
|
|
|
50
58
|
.layout {
|
|
51
59
|
display: flex;
|
|
52
60
|
height: 100vh;
|
|
53
|
-
background: radial-gradient(circle at 50% 0%, rgba(
|
|
61
|
+
background: radial-gradient(circle at 50% 0%, rgba(24, 24, 32, 1) 0%, rgba(12, 12, 14, 1) 100%);
|
|
54
62
|
}
|
|
55
63
|
|
|
56
64
|
/* ── SIDEBAR ── */
|
|
@@ -58,7 +66,7 @@
|
|
|
58
66
|
width: var(--sidebar-w);
|
|
59
67
|
min-width: var(--sidebar-w);
|
|
60
68
|
background: var(--surface-glass);
|
|
61
|
-
backdrop-filter: blur(
|
|
69
|
+
backdrop-filter: blur(16px);
|
|
62
70
|
border-right: 1px solid var(--border);
|
|
63
71
|
display: flex;
|
|
64
72
|
flex-direction: column;
|
|
@@ -77,11 +85,11 @@
|
|
|
77
85
|
}
|
|
78
86
|
.sidebar-logo .logo-icon {
|
|
79
87
|
width: 38px; height: 38px;
|
|
80
|
-
background: linear-gradient(135deg, #
|
|
88
|
+
background: linear-gradient(135deg, #0088cc 0%, #00aaff 100%);
|
|
81
89
|
border-radius: 10px;
|
|
82
90
|
display: flex; align-items: center; justify-content: center;
|
|
83
91
|
flex-shrink: 0;
|
|
84
|
-
box-shadow: 0 4px
|
|
92
|
+
box-shadow: 0 4px 14px var(--accent-glow);
|
|
85
93
|
}
|
|
86
94
|
.sidebar-logo .logo-icon svg { color: #fff; width: 22px; height: 22px; }
|
|
87
95
|
|
|
@@ -108,7 +116,7 @@
|
|
|
108
116
|
display: flex;
|
|
109
117
|
align-items: center;
|
|
110
118
|
gap: 12px;
|
|
111
|
-
background: rgba(0, 0, 0, 0.
|
|
119
|
+
background: rgba(0, 0, 0, 0.25);
|
|
112
120
|
}
|
|
113
121
|
.status-indicator-ring {
|
|
114
122
|
position: relative;
|
|
@@ -176,7 +184,7 @@
|
|
|
176
184
|
color: var(--text);
|
|
177
185
|
}
|
|
178
186
|
.nav-item.active {
|
|
179
|
-
background: rgba(
|
|
187
|
+
background: rgba(0, 170, 255, 0.12);
|
|
180
188
|
color: var(--accent);
|
|
181
189
|
border-left: 2px solid var(--accent);
|
|
182
190
|
}
|
|
@@ -192,9 +200,9 @@
|
|
|
192
200
|
color: var(--text-dim);
|
|
193
201
|
}
|
|
194
202
|
.badge-v2 {
|
|
195
|
-
background: rgba(
|
|
203
|
+
background: rgba(0, 170, 255, 0.15);
|
|
196
204
|
color: var(--accent);
|
|
197
|
-
border: 1px solid rgba(
|
|
205
|
+
border: 1px solid rgba(0, 170, 255, 0.3);
|
|
198
206
|
padding: 3px 8px;
|
|
199
207
|
border-radius: 4px;
|
|
200
208
|
font-weight: 600;
|
|
@@ -218,8 +226,8 @@
|
|
|
218
226
|
display: flex;
|
|
219
227
|
align-items: center;
|
|
220
228
|
justify-content: space-between;
|
|
221
|
-
background: rgba(
|
|
222
|
-
backdrop-filter: blur(
|
|
229
|
+
background: rgba(12, 12, 14, 0.6);
|
|
230
|
+
backdrop-filter: blur(12px);
|
|
223
231
|
z-index: 5;
|
|
224
232
|
}
|
|
225
233
|
.page-title {
|
|
@@ -286,8 +294,10 @@
|
|
|
286
294
|
padding: 16px 32px;
|
|
287
295
|
background: var(--surface-glass);
|
|
288
296
|
border-bottom: 1px solid var(--border);
|
|
297
|
+
flex-wrap: wrap;
|
|
298
|
+
gap: 12px;
|
|
289
299
|
}
|
|
290
|
-
.filter-btns { display: flex; gap: 8px; }
|
|
300
|
+
.filter-btns { display: flex; gap: 8px; flex-wrap: wrap; }
|
|
291
301
|
.filter-btn {
|
|
292
302
|
background: var(--surface-card);
|
|
293
303
|
border: 1px solid var(--border);
|
|
@@ -324,7 +334,7 @@
|
|
|
324
334
|
display: flex;
|
|
325
335
|
flex-direction: column;
|
|
326
336
|
gap: 8px;
|
|
327
|
-
height: calc(100vh -
|
|
337
|
+
height: calc(100vh - 142px);
|
|
328
338
|
}
|
|
329
339
|
|
|
330
340
|
.log-empty {
|
|
@@ -350,6 +360,7 @@
|
|
|
350
360
|
font-size: 13px;
|
|
351
361
|
line-height: 1.5;
|
|
352
362
|
transition: transform 0.2s, background 0.2s;
|
|
363
|
+
align-items: flex-start;
|
|
353
364
|
}
|
|
354
365
|
.log-entry:hover {
|
|
355
366
|
background: rgba(255, 255, 255, 0.04);
|
|
@@ -360,26 +371,33 @@
|
|
|
360
371
|
font-size: 12px;
|
|
361
372
|
flex-shrink: 0;
|
|
362
373
|
white-space: nowrap;
|
|
374
|
+
margin-top: 1px;
|
|
363
375
|
}
|
|
364
376
|
.log-entry-badge {
|
|
365
377
|
font-size: 10px; font-weight: 700;
|
|
366
378
|
padding: 2px 8px; border-radius: 4px;
|
|
367
379
|
height: fit-content; flex-shrink: 0;
|
|
380
|
+
letter-spacing: 0.03em;
|
|
368
381
|
}
|
|
369
382
|
|
|
370
|
-
.badge-task { color: var(--task); background: rgba(
|
|
383
|
+
.badge-task { color: var(--task); background: rgba(168, 85, 247, 0.15); border: 1px solid rgba(168, 85, 247, 0.3); }
|
|
384
|
+
.badge-batch { color: var(--batch); background: rgba(0, 170, 255, 0.15); border: 1px solid rgba(0, 170, 255, 0.3); }
|
|
385
|
+
.badge-security { color: var(--security); background: rgba(245, 158, 11, 0.15); border: 1px solid rgba(245, 158, 11, 0.3); }
|
|
386
|
+
.badge-docs { color: var(--docs); background: rgba(16, 185, 129, 0.15); border: 1px solid rgba(16, 185, 129, 0.3); }
|
|
387
|
+
.badge-patch { color: #38bdf8; background: rgba(56, 189, 248, 0.15); border: 1px solid rgba(56, 189, 248, 0.3); }
|
|
388
|
+
.badge-luau { color: #c084fc; background: rgba(192, 132, 252, 0.15); border: 1px solid rgba(192, 132, 252, 0.3); }
|
|
371
389
|
.badge-response { color: var(--response); background: rgba(20, 184, 166, 0.15); border: 1px solid rgba(20, 184, 166, 0.3); }
|
|
372
390
|
.badge-error { color: var(--error); background: rgba(239, 68, 68, 0.15); border: 1px solid rgba(239, 68, 68, 0.3); }
|
|
373
391
|
.badge-system { color: var(--system); background: rgba(161, 161, 170, 0.15); border: 1px solid rgba(161, 161, 170, 0.3); }
|
|
374
392
|
|
|
375
|
-
.log-entry-msg { color: var(--text
|
|
393
|
+
.log-entry-msg { color: var(--text); word-break: break-word; flex: 1; }
|
|
376
394
|
|
|
377
|
-
/* ── STATS PAGE ── */
|
|
395
|
+
/* ── STATS & SESSIONS PAGE ── */
|
|
378
396
|
.stats-grid {
|
|
379
397
|
display: grid;
|
|
380
398
|
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
381
399
|
gap: 20px;
|
|
382
|
-
max-width:
|
|
400
|
+
max-width: 1040px;
|
|
383
401
|
}
|
|
384
402
|
.stat-card {
|
|
385
403
|
background: var(--surface-card);
|
|
@@ -410,11 +428,212 @@
|
|
|
410
428
|
.info-row:last-child { border-bottom: none; padding-bottom: 0; }
|
|
411
429
|
.info-row-label { font-size: 13px; color: var(--text-muted); }
|
|
412
430
|
.info-row-value { font-size: 14px; font-weight: 600; color: var(--text); font-family: 'JetBrains Mono', monospace; }
|
|
413
|
-
|
|
431
|
+
|
|
432
|
+
/* Studio Sessions Table */
|
|
433
|
+
.sessions-table {
|
|
434
|
+
width: 100%;
|
|
435
|
+
border-collapse: collapse;
|
|
436
|
+
margin-top: 16px;
|
|
437
|
+
font-size: 13px;
|
|
438
|
+
}
|
|
439
|
+
.sessions-table th {
|
|
440
|
+
text-align: left;
|
|
441
|
+
padding: 10px 14px;
|
|
442
|
+
color: var(--text-dim);
|
|
443
|
+
font-weight: 600;
|
|
444
|
+
border-bottom: 1px solid var(--border);
|
|
445
|
+
text-transform: uppercase;
|
|
446
|
+
font-size: 11px;
|
|
447
|
+
letter-spacing: 0.05em;
|
|
448
|
+
}
|
|
449
|
+
.sessions-table td {
|
|
450
|
+
padding: 12px 14px;
|
|
451
|
+
border-bottom: 1px solid rgba(255,255,255,0.04);
|
|
452
|
+
color: var(--text);
|
|
453
|
+
font-family: 'JetBrains Mono', monospace;
|
|
454
|
+
}
|
|
455
|
+
.sessions-table tr:hover td {
|
|
456
|
+
background: rgba(255,255,255,0.02);
|
|
457
|
+
}
|
|
458
|
+
.badge-pill {
|
|
459
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
460
|
+
font-size: 11px; padding: 2px 8px; border-radius: 12px; font-weight: 600;
|
|
461
|
+
}
|
|
462
|
+
.badge-ws { background: rgba(0, 170, 255, 0.15); color: var(--accent); border: 1px solid rgba(0, 170, 255, 0.3); }
|
|
463
|
+
.badge-poll { background: rgba(245, 158, 11, 0.15); color: var(--warning); border: 1px solid rgba(245, 158, 11, 0.3); }
|
|
464
|
+
|
|
465
|
+
/* ── DOCS EXPLORER PAGE ── */
|
|
466
|
+
.docs-container {
|
|
467
|
+
max-width: 900px;
|
|
468
|
+
display: flex;
|
|
469
|
+
flex-direction: column;
|
|
470
|
+
gap: 20px;
|
|
471
|
+
}
|
|
472
|
+
.docs-search-card {
|
|
473
|
+
background: var(--surface-card);
|
|
474
|
+
border: 1px solid var(--border);
|
|
475
|
+
border-radius: 12px;
|
|
476
|
+
padding: 24px;
|
|
477
|
+
display: flex;
|
|
478
|
+
flex-direction: column;
|
|
479
|
+
gap: 16px;
|
|
480
|
+
}
|
|
481
|
+
.docs-form {
|
|
482
|
+
display: flex;
|
|
483
|
+
gap: 12px;
|
|
484
|
+
flex-wrap: wrap;
|
|
485
|
+
}
|
|
486
|
+
.docs-input {
|
|
487
|
+
flex: 2;
|
|
488
|
+
min-width: 200px;
|
|
489
|
+
background: #0a0a0c;
|
|
490
|
+
border: 1px solid var(--border);
|
|
491
|
+
color: var(--text);
|
|
492
|
+
padding: 10px 14px;
|
|
493
|
+
border-radius: 8px;
|
|
494
|
+
font-family: inherit;
|
|
495
|
+
font-size: 14px;
|
|
496
|
+
outline: none;
|
|
497
|
+
transition: border-color 0.2s;
|
|
498
|
+
}
|
|
499
|
+
.docs-input:focus {
|
|
500
|
+
border-color: var(--accent);
|
|
501
|
+
box-shadow: 0 0 10px var(--accent-glow);
|
|
502
|
+
}
|
|
503
|
+
.docs-member-input {
|
|
504
|
+
flex: 1;
|
|
505
|
+
min-width: 140px;
|
|
506
|
+
background: #0a0a0c;
|
|
507
|
+
border: 1px solid var(--border);
|
|
508
|
+
color: var(--text);
|
|
509
|
+
padding: 10px 14px;
|
|
510
|
+
border-radius: 8px;
|
|
511
|
+
font-family: inherit;
|
|
512
|
+
font-size: 14px;
|
|
513
|
+
outline: none;
|
|
514
|
+
}
|
|
515
|
+
.docs-member-input:focus { border-color: var(--accent); }
|
|
516
|
+
.docs-btn {
|
|
517
|
+
background: linear-gradient(135deg, #0088cc 0%, #00aaff 100%);
|
|
518
|
+
color: #fff;
|
|
519
|
+
border: none;
|
|
520
|
+
padding: 10px 22px;
|
|
521
|
+
border-radius: 8px;
|
|
522
|
+
font-family: inherit;
|
|
523
|
+
font-size: 14px;
|
|
524
|
+
font-weight: 600;
|
|
525
|
+
cursor: pointer;
|
|
526
|
+
transition: transform 0.15s, box-shadow 0.2s;
|
|
527
|
+
display: flex; align-items: center; gap: 8px;
|
|
528
|
+
}
|
|
529
|
+
.docs-btn:hover {
|
|
530
|
+
transform: translateY(-1px);
|
|
531
|
+
box-shadow: 0 4px 14px var(--accent-glow);
|
|
532
|
+
}
|
|
533
|
+
.docs-chips {
|
|
534
|
+
display: flex;
|
|
535
|
+
gap: 8px;
|
|
536
|
+
flex-wrap: wrap;
|
|
537
|
+
align-items: center;
|
|
538
|
+
}
|
|
539
|
+
.chip-label { font-size: 12px; color: var(--text-dim); }
|
|
540
|
+
.doc-chip {
|
|
541
|
+
font-size: 12px;
|
|
542
|
+
background: rgba(255,255,255,0.04);
|
|
543
|
+
border: 1px solid var(--border);
|
|
544
|
+
color: var(--text-muted);
|
|
545
|
+
padding: 4px 10px;
|
|
546
|
+
border-radius: 6px;
|
|
547
|
+
cursor: pointer;
|
|
548
|
+
transition: all 0.2s;
|
|
549
|
+
font-family: 'JetBrains Mono', monospace;
|
|
550
|
+
}
|
|
551
|
+
.doc-chip:hover {
|
|
552
|
+
background: rgba(0, 170, 255, 0.15);
|
|
553
|
+
color: var(--accent);
|
|
554
|
+
border-color: var(--accent);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
.docs-result-card {
|
|
558
|
+
background: var(--surface-card);
|
|
559
|
+
border: 1px solid var(--border);
|
|
560
|
+
border-radius: 12px;
|
|
561
|
+
padding: 28px;
|
|
562
|
+
min-height: 200px;
|
|
563
|
+
display: flex;
|
|
564
|
+
flex-direction: column;
|
|
565
|
+
position: relative;
|
|
566
|
+
}
|
|
567
|
+
.docs-result-header {
|
|
568
|
+
display: flex;
|
|
569
|
+
justify-content: space-between;
|
|
570
|
+
align-items: center;
|
|
571
|
+
margin-bottom: 16px;
|
|
572
|
+
padding-bottom: 12px;
|
|
573
|
+
border-bottom: 1px solid var(--border);
|
|
574
|
+
}
|
|
575
|
+
.docs-title {
|
|
576
|
+
font-size: 16px;
|
|
577
|
+
font-weight: 600;
|
|
578
|
+
color: var(--text);
|
|
579
|
+
display: flex; align-items: center; gap: 8px;
|
|
580
|
+
}
|
|
581
|
+
.docs-copy-btn {
|
|
582
|
+
background: transparent;
|
|
583
|
+
border: 1px solid var(--border);
|
|
584
|
+
color: var(--text-muted);
|
|
585
|
+
font-size: 12px;
|
|
586
|
+
padding: 4px 10px;
|
|
587
|
+
border-radius: 6px;
|
|
588
|
+
cursor: pointer;
|
|
589
|
+
transition: all 0.2s;
|
|
590
|
+
}
|
|
591
|
+
.docs-copy-btn:hover {
|
|
592
|
+
background: var(--surface-hover);
|
|
593
|
+
color: var(--text);
|
|
594
|
+
}
|
|
595
|
+
.docs-content {
|
|
596
|
+
font-family: 'Inter', sans-serif;
|
|
597
|
+
font-size: 14px;
|
|
598
|
+
line-height: 1.7;
|
|
599
|
+
color: var(--text);
|
|
600
|
+
overflow-x: auto;
|
|
601
|
+
}
|
|
602
|
+
.docs-content pre {
|
|
603
|
+
background: #09090b;
|
|
604
|
+
padding: 14px 18px;
|
|
605
|
+
border-radius: 8px;
|
|
606
|
+
border: 1px solid var(--border);
|
|
607
|
+
margin: 14px 0;
|
|
608
|
+
overflow-x: auto;
|
|
609
|
+
font-family: 'JetBrains Mono', monospace;
|
|
610
|
+
font-size: 13px;
|
|
611
|
+
color: #38bdf8;
|
|
612
|
+
}
|
|
613
|
+
.docs-content code {
|
|
614
|
+
font-family: 'JetBrains Mono', monospace;
|
|
615
|
+
background: rgba(255,255,255,0.06);
|
|
616
|
+
color: var(--accent);
|
|
617
|
+
padding: 2px 6px;
|
|
618
|
+
border-radius: 4px;
|
|
619
|
+
font-size: 12px;
|
|
620
|
+
}
|
|
621
|
+
.docs-content h1, .docs-content h2, .docs-content h3 {
|
|
622
|
+
margin: 18px 0 8px;
|
|
623
|
+
color: #fff;
|
|
624
|
+
}
|
|
625
|
+
.docs-content ul {
|
|
626
|
+
padding-left: 20px;
|
|
627
|
+
margin: 8px 0;
|
|
628
|
+
}
|
|
629
|
+
.docs-content li {
|
|
630
|
+
margin-bottom: 4px;
|
|
631
|
+
}
|
|
632
|
+
|
|
414
633
|
/* ── ABOUT PAGE ── */
|
|
415
|
-
.about-section { max-width:
|
|
634
|
+
.about-section { max-width: 720px; }
|
|
416
635
|
.about-hero {
|
|
417
|
-
background: linear-gradient(145deg, var(--surface-card) 0%, rgba(
|
|
636
|
+
background: linear-gradient(145deg, var(--surface-card) 0%, rgba(14,14,18,1) 100%);
|
|
418
637
|
border: 1px solid var(--border);
|
|
419
638
|
border-radius: 16px;
|
|
420
639
|
padding: 40px;
|
|
@@ -431,7 +650,7 @@
|
|
|
431
650
|
pointer-events: none;
|
|
432
651
|
}
|
|
433
652
|
.about-hero h2 { font-size: 28px; font-weight: 700; color: var(--text); letter-spacing: -0.02em; display: flex; align-items: center; gap: 12px;}
|
|
434
|
-
.v2-chip { font-size: 12px; background: var(--accent); color: white; padding: 2px 8px; border-radius: 12px; vertical-align: middle;}
|
|
653
|
+
.v2-chip { font-size: 12px; background: var(--accent); color: white; padding: 2px 8px; border-radius: 12px; vertical-align: middle; font-weight: 700;}
|
|
435
654
|
.about-hero p { font-size: 15px; color: var(--text-muted); line-height: 1.6; }
|
|
436
655
|
|
|
437
656
|
.about-links { display: flex; gap: 12px; margin-top: 8px; }
|
|
@@ -465,106 +684,523 @@
|
|
|
465
684
|
font-size: 12px;
|
|
466
685
|
}
|
|
467
686
|
|
|
468
|
-
/*
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
<body>
|
|
480
|
-
<div class="layout">
|
|
481
|
-
|
|
482
|
-
<!-- ── SIDEBAR ── -->
|
|
483
|
-
<aside class="sidebar">
|
|
484
|
-
<div class="sidebar-logo">
|
|
485
|
-
<div class="logo-mark">
|
|
486
|
-
<div class="logo-icon">
|
|
487
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
488
|
-
<path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/>
|
|
489
|
-
</svg>
|
|
490
|
-
</div>
|
|
491
|
-
<div class="logo-text-wrapper">
|
|
492
|
-
<div class="logo-name">nvstudio-mcp</div>
|
|
493
|
-
<div class="logo-sub">Zero-Delay Engine</div>
|
|
494
|
-
</div>
|
|
495
|
-
</div>
|
|
496
|
-
</div>
|
|
687
|
+
/* ── VFX Database & Library Styles ── */
|
|
688
|
+
.vfx-sidebar-badge {
|
|
689
|
+
font-size: 10px;
|
|
690
|
+
font-weight: 700;
|
|
691
|
+
background: var(--accent);
|
|
692
|
+
color: #fff;
|
|
693
|
+
padding: 1px 7px;
|
|
694
|
+
border-radius: 10px;
|
|
695
|
+
margin-left: auto;
|
|
696
|
+
letter-spacing: 0.02em;
|
|
697
|
+
}
|
|
497
698
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
699
|
+
.vfx-container {
|
|
700
|
+
display: flex;
|
|
701
|
+
flex-direction: column;
|
|
702
|
+
gap: 20px;
|
|
703
|
+
padding: 24px 32px 60px 32px;
|
|
704
|
+
overflow-y: auto;
|
|
705
|
+
height: 100%;
|
|
706
|
+
}
|
|
505
707
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
708
|
+
.vfx-header {
|
|
709
|
+
display: flex;
|
|
710
|
+
justify-content: space-between;
|
|
711
|
+
align-items: flex-start;
|
|
712
|
+
gap: 16px;
|
|
713
|
+
flex-wrap: wrap;
|
|
714
|
+
}
|
|
715
|
+
.vfx-header-info h2 {
|
|
716
|
+
font-size: 22px;
|
|
717
|
+
font-weight: 700;
|
|
718
|
+
color: var(--text);
|
|
719
|
+
display: flex;
|
|
720
|
+
align-items: center;
|
|
721
|
+
gap: 10px;
|
|
722
|
+
}
|
|
723
|
+
.vfx-header-info p {
|
|
724
|
+
font-size: 13px;
|
|
725
|
+
color: var(--text-muted);
|
|
726
|
+
margin-top: 4px;
|
|
727
|
+
}
|
|
516
728
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
</nav>
|
|
729
|
+
.vfx-actions-bar {
|
|
730
|
+
display: flex;
|
|
731
|
+
gap: 10px;
|
|
732
|
+
align-items: center;
|
|
733
|
+
}
|
|
523
734
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
735
|
+
.vfx-btn-primary {
|
|
736
|
+
background: linear-gradient(135deg, #0088cc 0%, #00aaff 100%);
|
|
737
|
+
color: #fff;
|
|
738
|
+
font-weight: 600;
|
|
739
|
+
font-size: 13px;
|
|
740
|
+
padding: 8px 16px;
|
|
741
|
+
border-radius: 8px;
|
|
742
|
+
border: none;
|
|
743
|
+
cursor: pointer;
|
|
744
|
+
display: inline-flex;
|
|
745
|
+
align-items: center;
|
|
746
|
+
gap: 8px;
|
|
747
|
+
box-shadow: 0 4px 12px var(--accent-glow);
|
|
748
|
+
transition: all 0.2s;
|
|
749
|
+
}
|
|
750
|
+
.vfx-btn-primary:hover {
|
|
751
|
+
transform: translateY(-1px);
|
|
752
|
+
box-shadow: 0 6px 16px var(--accent-glow);
|
|
753
|
+
filter: brightness(1.1);
|
|
754
|
+
}
|
|
529
755
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
756
|
+
.vfx-btn-secondary {
|
|
757
|
+
background: rgba(255, 255, 255, 0.05);
|
|
758
|
+
color: var(--text);
|
|
759
|
+
border: 1px solid var(--border);
|
|
760
|
+
font-weight: 600;
|
|
761
|
+
font-size: 13px;
|
|
762
|
+
padding: 8px 14px;
|
|
763
|
+
border-radius: 8px;
|
|
764
|
+
cursor: pointer;
|
|
765
|
+
display: inline-flex;
|
|
766
|
+
align-items: center;
|
|
767
|
+
gap: 6px;
|
|
768
|
+
transition: all 0.2s;
|
|
769
|
+
}
|
|
770
|
+
.vfx-btn-secondary:hover {
|
|
771
|
+
background: rgba(255, 255, 255, 0.1);
|
|
772
|
+
border-color: var(--border-highlight);
|
|
773
|
+
}
|
|
539
774
|
|
|
540
|
-
|
|
775
|
+
.vfx-search-box {
|
|
776
|
+
position: relative;
|
|
777
|
+
flex: 1;
|
|
778
|
+
min-width: 260px;
|
|
779
|
+
max-width: 480px;
|
|
780
|
+
}
|
|
781
|
+
.vfx-search-box input {
|
|
782
|
+
width: 100%;
|
|
783
|
+
background: #0d0d0d;
|
|
784
|
+
border: 1px solid var(--border);
|
|
785
|
+
border-radius: 8px;
|
|
786
|
+
padding: 9px 14px 9px 36px;
|
|
787
|
+
color: var(--text);
|
|
788
|
+
font-size: 13px;
|
|
789
|
+
outline: none;
|
|
790
|
+
transition: all 0.2s;
|
|
791
|
+
}
|
|
792
|
+
.vfx-search-box input:focus {
|
|
793
|
+
border-color: var(--accent);
|
|
794
|
+
box-shadow: 0 0 10px var(--accent-glow);
|
|
795
|
+
}
|
|
796
|
+
.vfx-search-box svg {
|
|
797
|
+
position: absolute;
|
|
798
|
+
left: 12px;
|
|
799
|
+
top: 50%;
|
|
800
|
+
transform: translateY(-50%);
|
|
801
|
+
width: 15px;
|
|
802
|
+
height: 15px;
|
|
803
|
+
color: var(--text-dim);
|
|
804
|
+
}
|
|
541
805
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
806
|
+
/* Category Filter Pills */
|
|
807
|
+
.vfx-category-tabs {
|
|
808
|
+
display: flex;
|
|
809
|
+
align-items: center;
|
|
810
|
+
gap: 8px;
|
|
811
|
+
overflow-x: auto;
|
|
812
|
+
padding-bottom: 6px;
|
|
813
|
+
}
|
|
814
|
+
.vfx-cat-pill {
|
|
815
|
+
background: #18181d;
|
|
816
|
+
border: 1px solid var(--border);
|
|
817
|
+
color: var(--text-muted);
|
|
818
|
+
font-size: 12px;
|
|
819
|
+
font-weight: 600;
|
|
820
|
+
padding: 6px 14px;
|
|
821
|
+
border-radius: 20px;
|
|
822
|
+
cursor: pointer;
|
|
823
|
+
white-space: nowrap;
|
|
824
|
+
display: inline-flex;
|
|
825
|
+
align-items: center;
|
|
826
|
+
gap: 6px;
|
|
827
|
+
transition: all 0.2s;
|
|
828
|
+
}
|
|
829
|
+
.vfx-cat-pill:hover {
|
|
830
|
+
color: var(--text);
|
|
831
|
+
border-color: var(--border-highlight);
|
|
832
|
+
background: #222228;
|
|
833
|
+
}
|
|
834
|
+
.vfx-cat-pill.active {
|
|
835
|
+
background: var(--accent);
|
|
836
|
+
color: #fff;
|
|
837
|
+
border-color: var(--accent);
|
|
838
|
+
box-shadow: 0 2px 10px var(--accent-glow);
|
|
839
|
+
}
|
|
840
|
+
.vfx-cat-count {
|
|
841
|
+
font-size: 10px;
|
|
842
|
+
background: rgba(0, 0, 0, 0.35);
|
|
843
|
+
padding: 1px 6px;
|
|
844
|
+
border-radius: 10px;
|
|
845
|
+
opacity: 0.9;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/* Summary Stats Cards */
|
|
849
|
+
.vfx-stats-row {
|
|
850
|
+
display: grid;
|
|
851
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
852
|
+
gap: 14px;
|
|
853
|
+
}
|
|
854
|
+
.vfx-stat-box {
|
|
855
|
+
background: var(--surface-card);
|
|
856
|
+
border: 1px solid var(--border);
|
|
857
|
+
border-radius: 10px;
|
|
858
|
+
padding: 16px 20px;
|
|
859
|
+
display: flex;
|
|
860
|
+
flex-direction: column;
|
|
861
|
+
gap: 4px;
|
|
862
|
+
}
|
|
863
|
+
.vfx-stat-label { font-size: 11px; text-transform: uppercase; color: var(--text-dim); font-weight: 600; letter-spacing: 0.05em; }
|
|
864
|
+
.vfx-stat-val { font-size: 24px; font-weight: 700; color: var(--text); }
|
|
865
|
+
.vfx-stat-sub { font-size: 12px; color: var(--text-muted); }
|
|
866
|
+
|
|
867
|
+
/* VFX Grid & Cards */
|
|
868
|
+
.vfx-grid {
|
|
869
|
+
display: grid;
|
|
870
|
+
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
|
871
|
+
gap: 16px;
|
|
872
|
+
}
|
|
873
|
+
.vfx-card {
|
|
874
|
+
background: #18181d;
|
|
875
|
+
border: 1px solid var(--border);
|
|
876
|
+
border-radius: 10px;
|
|
877
|
+
padding: 20px;
|
|
878
|
+
display: flex;
|
|
879
|
+
flex-direction: column;
|
|
880
|
+
gap: 14px;
|
|
881
|
+
transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);
|
|
882
|
+
position: relative;
|
|
883
|
+
}
|
|
884
|
+
.vfx-card:hover {
|
|
885
|
+
border-color: rgba(0, 170, 255, 0.4);
|
|
886
|
+
transform: translateY(-2px);
|
|
887
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
.vfx-card-top {
|
|
891
|
+
display: flex;
|
|
892
|
+
justify-content: space-between;
|
|
893
|
+
align-items: flex-start;
|
|
894
|
+
gap: 10px;
|
|
895
|
+
}
|
|
896
|
+
.vfx-card-title-group h4 {
|
|
897
|
+
font-size: 15px;
|
|
898
|
+
font-weight: 600;
|
|
899
|
+
color: var(--text);
|
|
900
|
+
letter-spacing: -0.01em;
|
|
901
|
+
}
|
|
902
|
+
.vfx-card-date {
|
|
903
|
+
font-size: 11px;
|
|
904
|
+
color: var(--text-dim);
|
|
905
|
+
margin-top: 2px;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
.vfx-badge-cat {
|
|
909
|
+
font-size: 10px;
|
|
910
|
+
font-weight: 700;
|
|
911
|
+
text-transform: uppercase;
|
|
912
|
+
padding: 3px 8px;
|
|
913
|
+
border-radius: 6px;
|
|
914
|
+
letter-spacing: 0.04em;
|
|
915
|
+
}
|
|
916
|
+
.vfx-badge-Slash { background: rgba(0, 170, 255, 0.15); color: #00aaff; border: 1px solid rgba(0, 170, 255, 0.3); }
|
|
917
|
+
.vfx-badge-Explosion { background: rgba(245, 158, 11, 0.15); color: #f59e0b; border: 1px solid rgba(245, 158, 11, 0.3); }
|
|
918
|
+
.vfx-badge-Aura { background: rgba(16, 185, 129, 0.15); color: #10b981; border: 1px solid rgba(16, 185, 129, 0.3); }
|
|
919
|
+
.vfx-badge-Magic { background: rgba(168, 85, 247, 0.15); color: #c084fc; border: 1px solid rgba(168, 85, 247, 0.3); }
|
|
920
|
+
.vfx-badge-Impact { background: rgba(239, 68, 68, 0.15); color: #f87171; border: 1px solid rgba(239, 68, 68, 0.3); }
|
|
921
|
+
.vfx-badge-Beam { background: rgba(6, 182, 212, 0.15); color: #22d3ee; border: 1px solid rgba(6, 182, 212, 0.3); }
|
|
922
|
+
.vfx-badge-Projectile { background: rgba(236, 72, 153, 0.15); color: #f472b6; border: 1px solid rgba(236, 72, 153, 0.3); }
|
|
923
|
+
.vfx-badge-Ambient { background: rgba(161, 161, 170, 0.15); color: #a1a1aa; border: 1px solid rgba(161, 161, 170, 0.3); }
|
|
924
|
+
|
|
925
|
+
.vfx-card-desc {
|
|
926
|
+
font-size: 13px;
|
|
927
|
+
color: var(--text-muted);
|
|
928
|
+
line-height: 1.5;
|
|
929
|
+
min-height: 38px;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
.vfx-composition-badges {
|
|
933
|
+
display: flex;
|
|
934
|
+
flex-wrap: wrap;
|
|
935
|
+
gap: 6px;
|
|
936
|
+
}
|
|
937
|
+
.vfx-comp-pill {
|
|
938
|
+
font-size: 11px;
|
|
939
|
+
background: rgba(255, 255, 255, 0.05);
|
|
940
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
941
|
+
color: var(--text-muted);
|
|
942
|
+
padding: 2px 7px;
|
|
943
|
+
border-radius: 4px;
|
|
944
|
+
font-family: 'JetBrains Mono', monospace;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
.vfx-card-tags {
|
|
948
|
+
display: flex;
|
|
949
|
+
flex-wrap: wrap;
|
|
950
|
+
gap: 4px;
|
|
951
|
+
}
|
|
952
|
+
.vfx-tag-chip {
|
|
953
|
+
font-size: 11px;
|
|
954
|
+
color: var(--text-dim);
|
|
955
|
+
background: #0d0d0d;
|
|
956
|
+
padding: 2px 6px;
|
|
957
|
+
border-radius: 4px;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
.vfx-card-footer {
|
|
961
|
+
margin-top: auto;
|
|
962
|
+
display: flex;
|
|
963
|
+
gap: 8px;
|
|
964
|
+
padding-top: 12px;
|
|
965
|
+
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
|
966
|
+
}
|
|
967
|
+
.vfx-btn-apply {
|
|
968
|
+
flex: 1;
|
|
969
|
+
background: rgba(0, 170, 255, 0.12);
|
|
970
|
+
color: var(--accent);
|
|
971
|
+
border: 1px solid rgba(0, 170, 255, 0.3);
|
|
972
|
+
font-size: 12px;
|
|
973
|
+
font-weight: 600;
|
|
974
|
+
padding: 7px 12px;
|
|
975
|
+
border-radius: 6px;
|
|
976
|
+
cursor: pointer;
|
|
977
|
+
display: inline-flex;
|
|
978
|
+
align-items: center;
|
|
979
|
+
justify-content: center;
|
|
980
|
+
gap: 6px;
|
|
981
|
+
transition: all 0.2s;
|
|
982
|
+
}
|
|
983
|
+
.vfx-btn-apply:hover {
|
|
984
|
+
background: var(--accent);
|
|
985
|
+
color: #fff;
|
|
986
|
+
box-shadow: 0 0 12px var(--accent-glow);
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
.vfx-btn-json {
|
|
990
|
+
background: rgba(255, 255, 255, 0.04);
|
|
991
|
+
color: var(--text-muted);
|
|
992
|
+
border: 1px solid var(--border);
|
|
993
|
+
font-size: 12px;
|
|
994
|
+
padding: 7px 10px;
|
|
995
|
+
border-radius: 6px;
|
|
996
|
+
cursor: pointer;
|
|
997
|
+
transition: all 0.2s;
|
|
998
|
+
}
|
|
999
|
+
.vfx-btn-json:hover {
|
|
1000
|
+
background: rgba(255, 255, 255, 0.09);
|
|
1001
|
+
color: var(--text);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
.vfx-btn-del {
|
|
1005
|
+
background: rgba(239, 68, 68, 0.08);
|
|
1006
|
+
color: #f87171;
|
|
1007
|
+
border: 1px solid rgba(239, 68, 68, 0.2);
|
|
1008
|
+
font-size: 12px;
|
|
1009
|
+
padding: 7px 10px;
|
|
1010
|
+
border-radius: 6px;
|
|
1011
|
+
cursor: pointer;
|
|
1012
|
+
transition: all 0.2s;
|
|
1013
|
+
}
|
|
1014
|
+
.vfx-btn-del:hover {
|
|
1015
|
+
background: #ef4444;
|
|
1016
|
+
color: #fff;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/* Modals */
|
|
1020
|
+
.vfx-modal-backdrop {
|
|
1021
|
+
display: none;
|
|
1022
|
+
position: fixed;
|
|
1023
|
+
top: 0; left: 0; right: 0; bottom: 0;
|
|
1024
|
+
background: rgba(0, 0, 0, 0.7);
|
|
1025
|
+
backdrop-filter: blur(8px);
|
|
1026
|
+
z-index: 1000;
|
|
1027
|
+
align-items: center;
|
|
1028
|
+
justify-content: center;
|
|
1029
|
+
padding: 20px;
|
|
1030
|
+
}
|
|
1031
|
+
.vfx-modal-backdrop.open { display: flex; }
|
|
1032
|
+
.vfx-modal-card {
|
|
1033
|
+
background: #18181d;
|
|
1034
|
+
border: 1px solid var(--border-highlight);
|
|
1035
|
+
border-radius: 12px;
|
|
1036
|
+
max-width: 600px;
|
|
1037
|
+
width: 100%;
|
|
1038
|
+
max-height: 85vh;
|
|
1039
|
+
display: flex;
|
|
1040
|
+
flex-direction: column;
|
|
1041
|
+
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.6);
|
|
1042
|
+
overflow: hidden;
|
|
1043
|
+
}
|
|
1044
|
+
.vfx-modal-header {
|
|
1045
|
+
padding: 18px 24px;
|
|
1046
|
+
border-bottom: 1px solid var(--border);
|
|
1047
|
+
display: flex;
|
|
1048
|
+
align-items: center;
|
|
1049
|
+
justify-content: space-between;
|
|
1050
|
+
}
|
|
1051
|
+
.vfx-modal-header h3 { font-size: 16px; font-weight: 600; color: #fff; }
|
|
1052
|
+
.vfx-modal-close {
|
|
1053
|
+
background: none; border: none; color: var(--text-dim); cursor: pointer; font-size: 20px; line-height: 1;
|
|
1054
|
+
}
|
|
1055
|
+
.vfx-modal-close:hover { color: #fff; }
|
|
1056
|
+
.vfx-modal-body {
|
|
1057
|
+
padding: 20px 24px;
|
|
1058
|
+
overflow-y: auto;
|
|
1059
|
+
display: flex;
|
|
1060
|
+
flex-direction: column;
|
|
1061
|
+
gap: 16px;
|
|
1062
|
+
}
|
|
1063
|
+
.vfx-modal-footer {
|
|
1064
|
+
padding: 16px 24px;
|
|
1065
|
+
border-top: 1px solid var(--border);
|
|
1066
|
+
display: flex;
|
|
1067
|
+
justify-content: flex-end;
|
|
1068
|
+
gap: 10px;
|
|
1069
|
+
background: rgba(0, 0, 0, 0.2);
|
|
1070
|
+
}
|
|
1071
|
+
.vfx-form-group {
|
|
1072
|
+
display: flex;
|
|
1073
|
+
flex-direction: column;
|
|
1074
|
+
gap: 6px;
|
|
1075
|
+
}
|
|
1076
|
+
.vfx-form-label { font-size: 12px; font-weight: 600; color: var(--text-muted); }
|
|
1077
|
+
.vfx-form-input, .vfx-form-select {
|
|
1078
|
+
background: #0d0d0d;
|
|
1079
|
+
border: 1px solid var(--border);
|
|
1080
|
+
border-radius: 6px;
|
|
1081
|
+
padding: 9px 12px;
|
|
1082
|
+
color: var(--text);
|
|
1083
|
+
font-size: 13px;
|
|
1084
|
+
outline: none;
|
|
1085
|
+
}
|
|
1086
|
+
.vfx-form-input:focus, .vfx-form-select:focus {
|
|
1087
|
+
border-color: var(--accent);
|
|
1088
|
+
box-shadow: 0 0 8px var(--accent-glow);
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
/* Scrollbar */
|
|
1092
|
+
::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
1093
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
1094
|
+
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.12); border-radius: 3px; }
|
|
1095
|
+
::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.22); }
|
|
1096
|
+
|
|
1097
|
+
/* Transitions */
|
|
1098
|
+
.log-entry { animation: slideIn 0.3s cubic-bezier(0.16, 1, 0.3, 1); }
|
|
1099
|
+
@keyframes slideIn { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } }
|
|
1100
|
+
</style>
|
|
1101
|
+
</head>
|
|
1102
|
+
<body>
|
|
1103
|
+
<div class="layout">
|
|
1104
|
+
|
|
1105
|
+
<!-- ── SIDEBAR ── -->
|
|
1106
|
+
<aside class="sidebar">
|
|
1107
|
+
<div class="sidebar-logo">
|
|
1108
|
+
<div class="logo-mark">
|
|
1109
|
+
<div class="logo-icon">
|
|
1110
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
1111
|
+
<path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/>
|
|
1112
|
+
</svg>
|
|
1113
|
+
</div>
|
|
1114
|
+
<div class="logo-text-wrapper">
|
|
1115
|
+
<div class="logo-name">nvstudio-mcp</div>
|
|
1116
|
+
<div class="logo-sub">Zero-Delay Bridge</div>
|
|
1117
|
+
</div>
|
|
1118
|
+
</div>
|
|
1119
|
+
</div>
|
|
1120
|
+
|
|
1121
|
+
<div class="sidebar-status">
|
|
1122
|
+
<div class="status-indicator-ring">
|
|
1123
|
+
<div class="status-dot waiting" id="status-dot"></div>
|
|
1124
|
+
<div class="status-ping"></div>
|
|
1125
|
+
</div>
|
|
1126
|
+
<span class="status-label" id="status-label">Menunggu Studio...</span>
|
|
1127
|
+
</div>
|
|
1128
|
+
|
|
1129
|
+
<nav class="sidebar-nav">
|
|
1130
|
+
<div class="nav-group-label">Monitor</div>
|
|
1131
|
+
<div class="nav-item active" data-page="activity">
|
|
1132
|
+
<svg viewBox="0 0 24 24"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>
|
|
1133
|
+
Activity Log
|
|
1134
|
+
</div>
|
|
1135
|
+
<div class="nav-item" data-page="stats">
|
|
1136
|
+
<svg viewBox="0 0 24 24"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg>
|
|
1137
|
+
Diagnostics & Sessions
|
|
1138
|
+
</div>
|
|
1139
|
+
|
|
1140
|
+
<div class="nav-group-label" style="margin-top:20px;">Engine Tools</div>
|
|
1141
|
+
<div class="nav-item" data-page="vfx-database">
|
|
1142
|
+
<svg viewBox="0 0 24 24"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
|
|
1143
|
+
VFX Database
|
|
1144
|
+
<span class="vfx-sidebar-badge" id="vfx-sidebar-count">0</span>
|
|
1145
|
+
</div>
|
|
1146
|
+
<div class="nav-item" data-page="docs">
|
|
1147
|
+
<svg viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>
|
|
1148
|
+
Docs Explorer
|
|
1149
|
+
</div>
|
|
1150
|
+
|
|
1151
|
+
<div class="nav-group-label" style="margin-top:20px;">Info</div>
|
|
1152
|
+
<div class="nav-item" data-page="about">
|
|
1153
|
+
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
|
|
1154
|
+
About v2.4
|
|
1155
|
+
</div>
|
|
1156
|
+
</nav>
|
|
1157
|
+
|
|
1158
|
+
<div class="sidebar-footer">
|
|
1159
|
+
<span class="badge-v2" id="pkg-version">v2.4.0</span>
|
|
1160
|
+
<span style="font-size:10px; opacity:0.6;">Model Context Protocol</span>
|
|
1161
|
+
</div>
|
|
1162
|
+
</aside>
|
|
1163
|
+
|
|
1164
|
+
<!-- ── MAIN ── -->
|
|
1165
|
+
<main class="main">
|
|
1166
|
+
<div class="topbar">
|
|
1167
|
+
<span class="page-title" id="topbar-title">Activity Log</span>
|
|
1168
|
+
<div class="topbar-right">
|
|
1169
|
+
<span class="topbar-badge" id="log-count">0 events</span>
|
|
1170
|
+
<button class="clear-btn" id="clear-btn">Clear</button>
|
|
1171
|
+
</div>
|
|
1172
|
+
</div>
|
|
1173
|
+
|
|
1174
|
+
<div class="content">
|
|
1175
|
+
|
|
1176
|
+
<!-- Activity Log Page -->
|
|
1177
|
+
<div class="page active" id="page-activity" style="padding:0; gap:0;">
|
|
1178
|
+
<div class="log-toolbar">
|
|
1179
|
+
<div class="filter-btns">
|
|
1180
|
+
<button class="filter-btn active" data-filter="all">All Logs</button>
|
|
1181
|
+
<button class="filter-btn" data-filter="task">Task / Command</button>
|
|
1182
|
+
<button class="filter-btn" data-filter="batch">Batch Builder</button>
|
|
1183
|
+
<button class="filter-btn" data-filter="security">Security / Scan</button>
|
|
1184
|
+
<button class="filter-btn" data-filter="response">Response</button>
|
|
1185
|
+
<button class="filter-btn" data-filter="error">Errors</button>
|
|
1186
|
+
<button class="filter-btn" data-filter="system">System</button>
|
|
1187
|
+
</div>
|
|
1188
|
+
<button class="scroll-lock-btn locked" id="scroll-lock-btn">
|
|
1189
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1190
|
+
<line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/>
|
|
1191
|
+
</svg>
|
|
556
1192
|
Auto Scroll
|
|
557
1193
|
</button>
|
|
558
1194
|
</div>
|
|
559
1195
|
<div class="log-list" id="log-list">
|
|
560
1196
|
<div class="log-empty" id="log-empty">
|
|
561
1197
|
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M12 8v4"/><path d="M12 16h.01"/></svg>
|
|
562
|
-
<p>Menunggu aktivitas dari Roblox Studio...</p>
|
|
1198
|
+
<p>Menunggu aktivitas perintah dari AI / Roblox Studio...</p>
|
|
563
1199
|
</div>
|
|
564
1200
|
</div>
|
|
565
1201
|
</div>
|
|
566
1202
|
|
|
567
|
-
<!--
|
|
1203
|
+
<!-- Diagnostics & Sessions Page -->
|
|
568
1204
|
<div class="page" id="page-stats">
|
|
569
1205
|
<div class="stats-grid">
|
|
570
1206
|
<div class="stat-card">
|
|
@@ -592,6 +1228,33 @@
|
|
|
592
1228
|
<div class="progress-bar error" id="bar-error" style="width:0%"></div>
|
|
593
1229
|
</div>
|
|
594
1230
|
</div>
|
|
1231
|
+
|
|
1232
|
+
<!-- Active Connected Studios -->
|
|
1233
|
+
<div class="stat-card full">
|
|
1234
|
+
<div class="stat-card-label" style="display:flex; justify-content:space-between; align-items:center;">
|
|
1235
|
+
<span>🎮 Active Studio Sessions</span>
|
|
1236
|
+
<span id="studios-count-badge" class="badge-v2">0 Terhubung</span>
|
|
1237
|
+
</div>
|
|
1238
|
+
<div id="studios-container" style="margin-top:12px;">
|
|
1239
|
+
<table class="sessions-table" id="sessions-table" style="display:none;">
|
|
1240
|
+
<thead>
|
|
1241
|
+
<tr>
|
|
1242
|
+
<th>Studio ID</th>
|
|
1243
|
+
<th>Place ID / Name</th>
|
|
1244
|
+
<th>Transport</th>
|
|
1245
|
+
<th>Status</th>
|
|
1246
|
+
<th>Last Ping</th>
|
|
1247
|
+
</tr>
|
|
1248
|
+
</thead>
|
|
1249
|
+
<tbody id="sessions-tbody"></tbody>
|
|
1250
|
+
</table>
|
|
1251
|
+
<div id="sessions-empty" style="padding:24px; text-align:center; color:var(--text-dim);">
|
|
1252
|
+
Tidak ada sesi Studio aktif saat ini. Buka game di Roblox Studio dan pastikan plugin <code>nvstudio_mcp</code> aktif.
|
|
1253
|
+
</div>
|
|
1254
|
+
</div>
|
|
1255
|
+
</div>
|
|
1256
|
+
|
|
1257
|
+
<!-- Diagnostics Summary -->
|
|
595
1258
|
<div class="stat-card full">
|
|
596
1259
|
<div class="stat-card-label">Session Diagnostics</div>
|
|
597
1260
|
<div class="info-list">
|
|
@@ -601,14 +1264,14 @@
|
|
|
601
1264
|
</div>
|
|
602
1265
|
<div class="info-row">
|
|
603
1266
|
<span class="info-row-label">Luau Pre-Validation</span>
|
|
604
|
-
<span class="info-row-value" style="color:var(--success);">Active ✓</span>
|
|
1267
|
+
<span class="info-row-value" style="color:var(--success);">Active (AST-Protected) ✓</span>
|
|
605
1268
|
</div>
|
|
606
1269
|
<div class="info-row">
|
|
607
1270
|
<span class="info-row-label">Port Server</span>
|
|
608
1271
|
<span class="info-row-value">3055</span>
|
|
609
1272
|
</div>
|
|
610
1273
|
<div class="info-row">
|
|
611
|
-
<span class="info-row-label">Studio
|
|
1274
|
+
<span class="info-row-label">Overall Studio Status</span>
|
|
612
1275
|
<span class="info-row-value" id="info-studio">Disconnected</span>
|
|
613
1276
|
</div>
|
|
614
1277
|
<div class="info-row">
|
|
@@ -620,16 +1283,213 @@
|
|
|
620
1283
|
</div>
|
|
621
1284
|
</div>
|
|
622
1285
|
|
|
1286
|
+
<!-- Docs Explorer Page -->
|
|
1287
|
+
<div class="page" id="page-docs">
|
|
1288
|
+
<div class="docs-container">
|
|
1289
|
+
<div class="docs-search-card">
|
|
1290
|
+
<div style="font-size:16px; font-weight:600; color:#fff; display:flex; align-items:center; gap:8px;">
|
|
1291
|
+
<span>📚 Roblox Creator Docs Lookup</span>
|
|
1292
|
+
<span class="badge-v2">Official Engine API</span>
|
|
1293
|
+
</div>
|
|
1294
|
+
<p style="font-size:13px; color:var(--text-muted);">
|
|
1295
|
+
Cari dokumentasi resmi Class, Service, Datatype, Enum, atau Library Roblox langsung dari jembatan lokal (dilengkapi cache disk ~2 ms).
|
|
1296
|
+
</p>
|
|
1297
|
+
<form class="docs-form" id="docs-form">
|
|
1298
|
+
<input type="text" class="docs-input" id="docs-query" placeholder="Nama Class / Service (contoh: TweenService, CanvasGroup, UDim2, task)" required />
|
|
1299
|
+
<input type="text" class="docs-member-input" id="docs-member" placeholder="Member opsional (misal: Create)" />
|
|
1300
|
+
<button type="submit" class="docs-btn" id="docs-btn">
|
|
1301
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
|
1302
|
+
Cari Docs
|
|
1303
|
+
</button>
|
|
1304
|
+
</form>
|
|
1305
|
+
<div class="docs-chips">
|
|
1306
|
+
<span class="chip-label">Pencarian Cepat:</span>
|
|
1307
|
+
<span class="doc-chip" data-q="TweenService">TweenService</span>
|
|
1308
|
+
<span class="doc-chip" data-q="CanvasGroup">CanvasGroup</span>
|
|
1309
|
+
<span class="doc-chip" data-q="Frame">Frame</span>
|
|
1310
|
+
<span class="doc-chip" data-q="UDim2">UDim2</span>
|
|
1311
|
+
<span class="doc-chip" data-q="task">task</span>
|
|
1312
|
+
<span class="doc-chip" data-q="UserInputService">UserInputService</span>
|
|
1313
|
+
<span class="doc-chip" data-q="PathfindingService">PathfindingService</span>
|
|
1314
|
+
</div>
|
|
1315
|
+
</div>
|
|
1316
|
+
|
|
1317
|
+
<div class="docs-result-card" id="docs-result-card">
|
|
1318
|
+
<div class="docs-result-header">
|
|
1319
|
+
<span class="docs-title" id="docs-result-title">Hasil Dokumentasi</span>
|
|
1320
|
+
<button class="docs-copy-btn" id="docs-copy-btn" style="display:none;">Salin Markdown</button>
|
|
1321
|
+
</div>
|
|
1322
|
+
<div class="docs-content" id="docs-content">
|
|
1323
|
+
<span style="color:var(--text-dim); font-size:13px;">Ketik kata kunci class di atas atau klik salah satu chip pencarian cepat untuk menampilkan dokumentasi resmi Roblox Engine.</span>
|
|
1324
|
+
</div>
|
|
1325
|
+
</div>
|
|
1326
|
+
</div>
|
|
1327
|
+
</div>
|
|
1328
|
+
|
|
1329
|
+
<!-- VFX Database & Library Page -->
|
|
1330
|
+
<div class="page" id="page-vfx-database">
|
|
1331
|
+
<div class="vfx-container">
|
|
1332
|
+
<div class="vfx-header">
|
|
1333
|
+
<div class="vfx-header-info">
|
|
1334
|
+
<h2>✨ NV-VFX Library & Database <span class="v2-chip" id="vfx-hero-badge">Portable</span></h2>
|
|
1335
|
+
<p>Katalog efek visual portabel lintas-proyek Roblox (ParticleEmitter, Beam, Trail, Light). Pindai dari satu place dan pasang ke place lain secara instan.</p>
|
|
1336
|
+
</div>
|
|
1337
|
+
<div class="vfx-actions-bar">
|
|
1338
|
+
<div class="vfx-search-box">
|
|
1339
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
|
1340
|
+
<input type="text" id="vfx-search-input" placeholder="Cari VFX berdasarkan nama, tag, atau kategori...">
|
|
1341
|
+
</div>
|
|
1342
|
+
<button class="vfx-btn-primary" id="btn-open-scan-modal">
|
|
1343
|
+
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
|
1344
|
+
Scan dari Studio
|
|
1345
|
+
</button>
|
|
1346
|
+
<button class="vfx-btn-secondary" id="btn-refresh-vfx" title="Muat ulang data">
|
|
1347
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M23 4v6h-6"/><path d="M1 20v-6h6"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>
|
|
1348
|
+
</button>
|
|
1349
|
+
</div>
|
|
1350
|
+
</div>
|
|
1351
|
+
|
|
1352
|
+
<!-- Category Pills Segment Bar -->
|
|
1353
|
+
<div class="vfx-category-tabs" id="vfx-category-tabs">
|
|
1354
|
+
<button class="vfx-cat-pill active" data-cat="All">Semua <span class="vfx-cat-count" id="vfx-count-all">0</span></button>
|
|
1355
|
+
</div>
|
|
1356
|
+
|
|
1357
|
+
<!-- Stats Overview Row -->
|
|
1358
|
+
<div class="vfx-stats-row">
|
|
1359
|
+
<div class="vfx-stat-box">
|
|
1360
|
+
<div class="vfx-stat-label">Total Efek Visual</div>
|
|
1361
|
+
<div class="vfx-stat-val" id="vfx-stat-total">0</div>
|
|
1362
|
+
<div class="vfx-stat-sub">Tersimpan di database MCP</div>
|
|
1363
|
+
</div>
|
|
1364
|
+
<div class="vfx-stat-box">
|
|
1365
|
+
<div class="vfx-stat-label">Kategori Aktif</div>
|
|
1366
|
+
<div class="vfx-stat-val" id="vfx-stat-categories" style="color:#00aaff;">0</div>
|
|
1367
|
+
<div class="vfx-stat-sub">Slash, Aura, Magic, Explosion dll</div>
|
|
1368
|
+
</div>
|
|
1369
|
+
<div class="vfx-stat-box">
|
|
1370
|
+
<div class="vfx-stat-label">Total Emitters</div>
|
|
1371
|
+
<div class="vfx-stat-val" id="vfx-stat-emitters" style="color:#f59e0b;">0</div>
|
|
1372
|
+
<div class="vfx-stat-sub">ParticleEmitter aktif</div>
|
|
1373
|
+
</div>
|
|
1374
|
+
<div class="vfx-stat-box">
|
|
1375
|
+
<div class="vfx-stat-label">Beams & Trails</div>
|
|
1376
|
+
<div class="vfx-stat-val" id="vfx-stat-beams" style="color:#10b981;">0</div>
|
|
1377
|
+
<div class="vfx-stat-sub">Elemen trajektori & sinar</div>
|
|
1378
|
+
</div>
|
|
1379
|
+
</div>
|
|
1380
|
+
|
|
1381
|
+
<!-- Cards Grid -->
|
|
1382
|
+
<div class="vfx-grid" id="vfx-cards-grid">
|
|
1383
|
+
<!-- Kartu VFX di-render secara dinamis oleh JavaScript -->
|
|
1384
|
+
</div>
|
|
1385
|
+
</div>
|
|
1386
|
+
</div>
|
|
1387
|
+
|
|
1388
|
+
<!-- Modal: Scan VFX dari Studio -->
|
|
1389
|
+
<div class="vfx-modal-backdrop" id="modal-scan-vfx">
|
|
1390
|
+
<div class="vfx-modal-card">
|
|
1391
|
+
<div class="vfx-modal-header">
|
|
1392
|
+
<h3>📥 Pindai VFX dari Studio Aktif</h3>
|
|
1393
|
+
<button class="vfx-modal-close" onclick="closeVfxModals()">×</button>
|
|
1394
|
+
</div>
|
|
1395
|
+
<div class="vfx-modal-body">
|
|
1396
|
+
<div class="vfx-form-group">
|
|
1397
|
+
<label class="vfx-form-label">Path Objek di Studio (Target)</label>
|
|
1398
|
+
<input type="text" class="vfx-form-input" id="scan-input-target" placeholder="contoh: Workspace.FlameBurst atau Selection" value="Workspace.">
|
|
1399
|
+
</div>
|
|
1400
|
+
<div class="vfx-form-group">
|
|
1401
|
+
<label class="vfx-form-label">Nama VFX</label>
|
|
1402
|
+
<input type="text" class="vfx-form-input" id="scan-input-name" placeholder="contoh: Crimson_Flame_Slash">
|
|
1403
|
+
</div>
|
|
1404
|
+
<div class="vfx-form-group">
|
|
1405
|
+
<label class="vfx-form-label">Kategori</label>
|
|
1406
|
+
<select class="vfx-form-select" id="scan-select-category">
|
|
1407
|
+
<option value="Slash">Slash (Tebasan & Bilah)</option>
|
|
1408
|
+
<option value="Explosion">Explosion (Ledakan & Api)</option>
|
|
1409
|
+
<option value="Aura">Aura (Glow & Perisai)</option>
|
|
1410
|
+
<option value="Magic">Magic (Sihir & Lingkaran Rune)</option>
|
|
1411
|
+
<option value="Beam">Beam (Laser & Sinar Energi)</option>
|
|
1412
|
+
<option value="Impact">Impact (Benturan & Hantaman)</option>
|
|
1413
|
+
<option value="Projectile">Projectile (Proyektil & Peluru)</option>
|
|
1414
|
+
<option value="Ambient">Ambient (Lingkungan & Partikel Halus)</option>
|
|
1415
|
+
</select>
|
|
1416
|
+
</div>
|
|
1417
|
+
<div class="vfx-form-group">
|
|
1418
|
+
<label class="vfx-form-label">Tags (Pisahkan dengan koma)</label>
|
|
1419
|
+
<input type="text" class="vfx-form-input" id="scan-input-tags" placeholder="contoh: anime, sword, fire, fast">
|
|
1420
|
+
</div>
|
|
1421
|
+
</div>
|
|
1422
|
+
<div class="vfx-modal-footer">
|
|
1423
|
+
<button class="vfx-btn-secondary" onclick="closeVfxModals()">Batal</button>
|
|
1424
|
+
<button class="vfx-btn-primary" id="btn-submit-scan">Mulai Pindai</button>
|
|
1425
|
+
</div>
|
|
1426
|
+
</div>
|
|
1427
|
+
</div>
|
|
1428
|
+
|
|
1429
|
+
<!-- Modal: Apply VFX ke Studio -->
|
|
1430
|
+
<div class="vfx-modal-backdrop" id="modal-apply-vfx">
|
|
1431
|
+
<div class="vfx-modal-card">
|
|
1432
|
+
<div class="vfx-modal-header">
|
|
1433
|
+
<h3>⚡ Terapkan VFX ke Studio</h3>
|
|
1434
|
+
<button class="vfx-modal-close" onclick="closeVfxModals()">×</button>
|
|
1435
|
+
</div>
|
|
1436
|
+
<div class="vfx-modal-body">
|
|
1437
|
+
<input type="hidden" id="apply-vfx-id">
|
|
1438
|
+
<div class="vfx-form-group">
|
|
1439
|
+
<label class="vfx-form-label">VFX Terpilih</label>
|
|
1440
|
+
<div id="apply-vfx-title" style="font-weight:600; color:var(--accent);"></div>
|
|
1441
|
+
</div>
|
|
1442
|
+
<div class="vfx-form-group">
|
|
1443
|
+
<label class="vfx-form-label">Target Instance di Studio</label>
|
|
1444
|
+
<input type="text" class="vfx-form-input" id="apply-input-target" placeholder="contoh: Workspace.Dummy.RightHand atau Workspace" value="Workspace">
|
|
1445
|
+
</div>
|
|
1446
|
+
<div class="vfx-form-group">
|
|
1447
|
+
<label class="vfx-form-label">Skala Ukuran (Scale Factor)</label>
|
|
1448
|
+
<input type="number" class="vfx-form-input" id="apply-input-scale" value="1.0" step="0.1" min="0.1" max="10">
|
|
1449
|
+
</div>
|
|
1450
|
+
<div class="vfx-form-group">
|
|
1451
|
+
<label class="vfx-form-label">Status Partikel (Enabled)</label>
|
|
1452
|
+
<select class="vfx-form-select" id="apply-select-enabled">
|
|
1453
|
+
<option value="default">Sesuai Preset / Default</option>
|
|
1454
|
+
<option value="true">Paksa Aktif (Enabled = true)</option>
|
|
1455
|
+
<option value="false">Paksa Nonaktif (Enabled = false)</option>
|
|
1456
|
+
</select>
|
|
1457
|
+
</div>
|
|
1458
|
+
</div>
|
|
1459
|
+
<div class="vfx-modal-footer">
|
|
1460
|
+
<button class="vfx-btn-secondary" onclick="closeVfxModals()">Batal</button>
|
|
1461
|
+
<button class="vfx-btn-primary" id="btn-submit-apply">Terapkan Sekarang</button>
|
|
1462
|
+
</div>
|
|
1463
|
+
</div>
|
|
1464
|
+
</div>
|
|
1465
|
+
|
|
1466
|
+
<!-- Modal: View JSON / Inspect Data -->
|
|
1467
|
+
<div class="vfx-modal-backdrop" id="modal-json-vfx">
|
|
1468
|
+
<div class="vfx-modal-card" style="max-width:700px;">
|
|
1469
|
+
<div class="vfx-modal-header">
|
|
1470
|
+
<h3 id="json-modal-title">Inspeksi Data Murni VFX</h3>
|
|
1471
|
+
<button class="vfx-modal-close" onclick="closeVfxModals()">×</button>
|
|
1472
|
+
</div>
|
|
1473
|
+
<div class="vfx-modal-body" style="padding:0;">
|
|
1474
|
+
<pre id="json-modal-content" style="background:#0d0d0d; padding:16px; margin:0; font-family:'JetBrains Mono', monospace; font-size:12px; color:#e0e0e0; overflow:auto; max-height:55vh;"></pre>
|
|
1475
|
+
</div>
|
|
1476
|
+
<div class="vfx-modal-footer">
|
|
1477
|
+
<button class="vfx-btn-secondary" id="btn-copy-vfx-json">Salin JSON</button>
|
|
1478
|
+
<button class="vfx-btn-primary" onclick="closeVfxModals()">Tutup</button>
|
|
1479
|
+
</div>
|
|
1480
|
+
</div>
|
|
1481
|
+
</div>
|
|
1482
|
+
|
|
623
1483
|
<!-- About Page -->
|
|
624
1484
|
<div class="page" id="page-about">
|
|
625
1485
|
<div class="about-section">
|
|
626
1486
|
<div class="about-hero">
|
|
627
|
-
<h2>nvstudio-mcp <span class="v2-chip">v2.
|
|
628
|
-
<p>Jembatan pintar berbasis <strong>Model Context Protocol (MCP)</strong> yang menghubungkan AI Agent (Antigravity IDE, Cursor, Claude) secara langsung ke dalam <strong>Roblox Studio</strong>.</p>
|
|
1487
|
+
<h2>nvstudio-mcp <span class="v2-chip" id="about-version-chip">v2.4.0</span></h2>
|
|
1488
|
+
<p>Jembatan pintar berbasis <strong>Model Context Protocol (MCP)</strong> yang menghubungkan AI Agent (Antigravity IDE, Cursor, Claude Desktop, Windsurf) secara langsung ke dalam <strong>Roblox Studio</strong>.</p>
|
|
629
1489
|
<div class="about-links">
|
|
630
1490
|
<a class="about-link" href="https://github.com/Adiholick/nvstudio-mcp" target="_blank">
|
|
631
1491
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>
|
|
632
|
-
GitHub
|
|
1492
|
+
GitHub Repository
|
|
633
1493
|
</a>
|
|
634
1494
|
<a class="about-link" href="https://www.npmjs.com/package/@adiholick/nvstudio-mcp" target="_blank">
|
|
635
1495
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor"><path d="M0 0h24v24H0V0zm7.315 5.876H2.17v12.248H12v-9.6h-4.685V5.876zm9.515 0h-5.145v12.248h5.145V5.876zm-7.315 4.8H9.87v4.8H9.515v-4.8z"/></svg>
|
|
@@ -639,22 +1499,28 @@
|
|
|
639
1499
|
</div>
|
|
640
1500
|
|
|
641
1501
|
<div class="about-card">
|
|
642
|
-
<h3
|
|
1502
|
+
<h3>🚀 Fitur Baru v2.4.0</h3>
|
|
643
1503
|
<ul>
|
|
644
|
-
<li><strong
|
|
645
|
-
<li><strong
|
|
646
|
-
<li><strong
|
|
1504
|
+
<li><strong>🦴 NV-Rig Engine:</strong> Rigging headless presisi $O(1)$ untuk R6, R15, dan Custom Rig. Menyediakan inspeksi sendi (<code>inspect_rig</code>), pemindahan poros C0/C1 simultan anti-dislokasi (<code>edit_rig_joint</code>), auto-rigging instan (<code>auto_rig</code>), dan assembly welding senjata/prop (<code>weld_assembly</code>).</li>
|
|
1505
|
+
<li><strong>🎬 NV-Animation Engine:</strong> Pembuatan animasi karakter halus alami (Walk, Slash, Punch, Idle, IK) langsung menjadi <code>KeyframeSequence</code> resmi dengan interpolasi Bezier (Cubic/Quad) dan penegakan batas anatomis aman tanpa glitch.</li>
|
|
1506
|
+
<li><strong>✨ NV-VFX Subsystem & Database:</strong> Serialisasi efek visual (ParticleEmitter, Beam, Trail, PointLight) lintas-proyek dengan tool <code>scan_vfx</code> dan rekonstruksi instan via <code>apply_vfx</code>, dilengkapi web category browser interaktif.</li>
|
|
1507
|
+
<li><strong>⚡ Batch Instance Builder & Security:</strong> Membangun hierarki massal, sanitasi keamanan backdoor aset, dan live Creator Docs lookup.</li>
|
|
647
1508
|
</ul>
|
|
648
1509
|
</div>
|
|
649
1510
|
|
|
650
1511
|
<div class="about-card">
|
|
651
|
-
<h3>🛠️
|
|
1512
|
+
<h3>🛠️ Daftar Semantic MCP Tools Lengkap</h3>
|
|
652
1513
|
<ul>
|
|
653
|
-
<li><code>
|
|
654
|
-
<li><code>
|
|
655
|
-
<li><code>
|
|
656
|
-
<li><code>
|
|
657
|
-
<li><code>
|
|
1514
|
+
<li><code>inspect_rig</code> / <code>edit_rig_joint</code> / <code>auto_rig</code> / <code>weld_assembly</code> — NV-Rig Engine headless.</li>
|
|
1515
|
+
<li><code>animate_character</code> / <code>solve_limb_ik</code> / <code>scrub_timeline</code> — NV-Animation Engine.</li>
|
|
1516
|
+
<li><code>scan_vfx</code> / <code>apply_vfx</code> / <code>list_vfx_database</code> / <code>delete_vfx</code> — NV-VFX Subsystem & Database.</li>
|
|
1517
|
+
<li><code>batch_create_instances</code> / <code>batch_set_properties</code> — Pembuatan objek/hierarki massal pohon bersarang.</li>
|
|
1518
|
+
<li><code>get_roblox_docs</code> — Lookup dokumentasi resmi engine Roblox (Class, Datatype, Service, Enum).</li>
|
|
1519
|
+
<li><code>preview_asset</code> / <code>scan_security</code> — Audit keamanan backdoor & sanitasi script model.</li>
|
|
1520
|
+
<li><code>patch_script_source</code> / <code>update_script_source</code> — Mengedit script Roblox secara terisolasi.</li>
|
|
1521
|
+
<li><code>execute_luau</code> — Eksekusi sembarang kode Luau dinamis di Studio.</li>
|
|
1522
|
+
<li><code>get_full_tree</code> / <code>get_children</code> — Eksplorasi hierarki DataModel.</li>
|
|
1523
|
+
<li><code>insert_asset</code> / <code>generate_terrain</code> — Memasukkan model atau membuat terrain voxel.</li>
|
|
658
1524
|
</ul>
|
|
659
1525
|
</div>
|
|
660
1526
|
</div>
|
|
@@ -671,19 +1537,28 @@
|
|
|
671
1537
|
let activeFilter = 'all';
|
|
672
1538
|
let autoScroll = true;
|
|
673
1539
|
let sessionStartTime = null;
|
|
1540
|
+
let latestDocsMarkdown = '';
|
|
674
1541
|
|
|
675
1542
|
const logList = document.getElementById('log-list');
|
|
676
1543
|
const logEmpty = document.getElementById('log-empty');
|
|
677
1544
|
const logCount = document.getElementById('log-count');
|
|
678
1545
|
const statusDot = document.getElementById('status-dot');
|
|
679
1546
|
const statusLabel = document.getElementById('status-label');
|
|
1547
|
+
const pkgVersion = document.getElementById('pkg-version');
|
|
1548
|
+
const aboutVersionChip = document.getElementById('about-version-chip');
|
|
680
1549
|
|
|
681
1550
|
/* ── Navigation ── */
|
|
682
1551
|
const navItems = document.querySelectorAll('.nav-item');
|
|
683
1552
|
const pages = document.querySelectorAll('.page');
|
|
684
1553
|
const topbarTitle = document.getElementById('topbar-title');
|
|
685
1554
|
const clearBtn = document.getElementById('clear-btn');
|
|
686
|
-
const pageTitles = {
|
|
1555
|
+
const pageTitles = {
|
|
1556
|
+
activity: 'Activity Log',
|
|
1557
|
+
stats: 'Session Diagnostics & Studios',
|
|
1558
|
+
'vfx-database': 'NV-VFX Database & Library',
|
|
1559
|
+
docs: 'Roblox Docs Explorer',
|
|
1560
|
+
about: 'About v2.4'
|
|
1561
|
+
};
|
|
687
1562
|
|
|
688
1563
|
navItems.forEach(item => {
|
|
689
1564
|
item.addEventListener('click', () => {
|
|
@@ -691,7 +1566,8 @@
|
|
|
691
1566
|
navItems.forEach(n => n.classList.remove('active'));
|
|
692
1567
|
pages.forEach(p => p.classList.remove('active'));
|
|
693
1568
|
item.classList.add('active');
|
|
694
|
-
document.getElementById('page-' + page)
|
|
1569
|
+
const targetPage = document.getElementById('page-' + page);
|
|
1570
|
+
if (targetPage) targetPage.classList.add('active');
|
|
695
1571
|
topbarTitle.textContent = pageTitles[page] || page;
|
|
696
1572
|
|
|
697
1573
|
const isActivity = page === 'activity';
|
|
@@ -700,7 +1576,7 @@
|
|
|
700
1576
|
});
|
|
701
1577
|
});
|
|
702
1578
|
|
|
703
|
-
/* ── Clear ── */
|
|
1579
|
+
/* ── Clear Logs ── */
|
|
704
1580
|
clearBtn.addEventListener('click', () => {
|
|
705
1581
|
allLogs = [];
|
|
706
1582
|
renderLogs();
|
|
@@ -723,9 +1599,80 @@
|
|
|
723
1599
|
scrollLockBtn.classList.toggle('locked', autoScroll);
|
|
724
1600
|
});
|
|
725
1601
|
|
|
726
|
-
/* ──
|
|
1602
|
+
/* ── Categorize Log ── */
|
|
1603
|
+
function getLogCategory(log) {
|
|
1604
|
+
const msg = log.message || '';
|
|
1605
|
+
if (msg.includes('batch_create') || msg.includes('batch_set')) return 'batch';
|
|
1606
|
+
if (msg.includes('preview_asset') || msg.includes('scan_security') || msg.includes('security') || msg.includes('strip_scripts')) return 'security';
|
|
1607
|
+
if (msg.includes('get_roblox_docs')) return 'docs';
|
|
1608
|
+
if (msg.includes('patch_script_source')) return 'patch';
|
|
1609
|
+
if (msg.includes('execute_luau')) return 'luau';
|
|
1610
|
+
return log.type;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
/* ── Create Log Element ── */
|
|
1614
|
+
function createLogEl(log) {
|
|
1615
|
+
const el = document.createElement('div');
|
|
1616
|
+
el.className = 'log-entry';
|
|
1617
|
+
const cat = getLogCategory(log);
|
|
1618
|
+
el.dataset.category = cat;
|
|
1619
|
+
|
|
1620
|
+
let displayType = log.type.toUpperCase();
|
|
1621
|
+
let badgeClass = 'badge-' + log.type;
|
|
1622
|
+
|
|
1623
|
+
if (cat === 'batch') {
|
|
1624
|
+
displayType = 'BATCH';
|
|
1625
|
+
badgeClass = 'badge-batch';
|
|
1626
|
+
} else if (cat === 'security') {
|
|
1627
|
+
displayType = 'SECURITY';
|
|
1628
|
+
badgeClass = 'badge-security';
|
|
1629
|
+
} else if (cat === 'docs') {
|
|
1630
|
+
displayType = 'DOCS';
|
|
1631
|
+
badgeClass = 'badge-docs';
|
|
1632
|
+
} else if (cat === 'patch') {
|
|
1633
|
+
displayType = 'PATCH';
|
|
1634
|
+
badgeClass = 'badge-patch';
|
|
1635
|
+
} else if (cat === 'luau') {
|
|
1636
|
+
displayType = 'LUAU';
|
|
1637
|
+
badgeClass = 'badge-luau';
|
|
1638
|
+
} else if (log.type === 'error' && log.message.includes('Syntax Error')) {
|
|
1639
|
+
displayType = 'SYNTAX';
|
|
1640
|
+
badgeClass = 'badge-error';
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
el.innerHTML = `
|
|
1644
|
+
<span class="log-entry-time">${log.time}</span>
|
|
1645
|
+
<span class="log-entry-badge ${badgeClass}">${displayType}</span>
|
|
1646
|
+
<span class="log-entry-msg">${escapeHtml(log.message)}</span>
|
|
1647
|
+
`;
|
|
1648
|
+
return el;
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
function escapeHtml(str) {
|
|
1652
|
+
if (str === null || str === undefined) return '';
|
|
1653
|
+
return String(str)
|
|
1654
|
+
.replace(/&/g, '&')
|
|
1655
|
+
.replace(/</g, '<')
|
|
1656
|
+
.replace(/>/g, '>')
|
|
1657
|
+
.replace(/"/g, '"')
|
|
1658
|
+
.replace(/'/g, ''');
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
function shouldShowLog(log) {
|
|
1662
|
+
if (activeFilter === 'all') return true;
|
|
1663
|
+
const cat = getLogCategory(log);
|
|
1664
|
+
if (activeFilter === 'batch') return cat === 'batch';
|
|
1665
|
+
if (activeFilter === 'security') return cat === 'security';
|
|
1666
|
+
if (activeFilter === 'task') return log.type === 'task';
|
|
1667
|
+
if (activeFilter === 'response') return log.type === 'response';
|
|
1668
|
+
if (activeFilter === 'error') return log.type === 'error';
|
|
1669
|
+
if (activeFilter === 'system') return log.type === 'system';
|
|
1670
|
+
return false;
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
/* ── Render Logs ── */
|
|
727
1674
|
function renderLogs() {
|
|
728
|
-
const filtered =
|
|
1675
|
+
const filtered = allLogs.filter(shouldShowLog);
|
|
729
1676
|
logCount.textContent = `${allLogs.length} event${allLogs.length !== 1 ? 's' : ''}`;
|
|
730
1677
|
|
|
731
1678
|
if (filtered.length === 0) {
|
|
@@ -748,37 +1695,12 @@
|
|
|
748
1695
|
if (autoScroll) logList.scrollTop = 0;
|
|
749
1696
|
}
|
|
750
1697
|
|
|
751
|
-
function createLogEl(log) {
|
|
752
|
-
const el = document.createElement('div');
|
|
753
|
-
el.className = 'log-entry';
|
|
754
|
-
el.dataset.type = log.type;
|
|
755
|
-
|
|
756
|
-
let displayType = log.type.toUpperCase();
|
|
757
|
-
// Custom badges for v2 features based on message content
|
|
758
|
-
if (log.type === 'task' && log.message.includes('patch_script_source')) {
|
|
759
|
-
displayType = 'PATCH';
|
|
760
|
-
} else if (log.type === 'error' && log.message.includes('Syntax Error')) {
|
|
761
|
-
displayType = 'SYNTAX';
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
el.innerHTML = `
|
|
765
|
-
<span class="log-entry-time">${log.time}</span>
|
|
766
|
-
<span class="log-entry-badge badge-${log.type}">${displayType}</span>
|
|
767
|
-
<span class="log-entry-msg">${escapeHtml(log.message)}</span>
|
|
768
|
-
`;
|
|
769
|
-
return el;
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
function escapeHtml(str) {
|
|
773
|
-
return String(str).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
774
|
-
}
|
|
775
|
-
|
|
776
1698
|
function prependLog(log) {
|
|
777
1699
|
allLogs.unshift(log);
|
|
778
|
-
if (allLogs.length > 1000) allLogs.pop();
|
|
1700
|
+
if (allLogs.length > 1000) allLogs.pop();
|
|
779
1701
|
logCount.textContent = `${allLogs.length} event${allLogs.length !== 1 ? 's' : ''}`;
|
|
780
1702
|
|
|
781
|
-
if (
|
|
1703
|
+
if (!shouldShowLog(log)) return;
|
|
782
1704
|
|
|
783
1705
|
logEmpty.style.display = 'none';
|
|
784
1706
|
const el = createLogEl(log);
|
|
@@ -789,17 +1711,69 @@
|
|
|
789
1711
|
if (autoScroll) logList.scrollTop = 0;
|
|
790
1712
|
}
|
|
791
1713
|
|
|
792
|
-
/* ──
|
|
1714
|
+
/* ── Update Connected Studios Table ── */
|
|
1715
|
+
function updateStudiosTable(studios) {
|
|
1716
|
+
const tbody = document.getElementById('sessions-tbody');
|
|
1717
|
+
const table = document.getElementById('sessions-table');
|
|
1718
|
+
const empty = document.getElementById('sessions-empty');
|
|
1719
|
+
const badge = document.getElementById('studios-count-badge');
|
|
1720
|
+
|
|
1721
|
+
const list = Array.isArray(studios) ? studios : [];
|
|
1722
|
+
badge.textContent = `${list.length} Terhubung`;
|
|
1723
|
+
|
|
1724
|
+
if (list.length === 0) {
|
|
1725
|
+
table.style.display = 'none';
|
|
1726
|
+
empty.style.display = 'block';
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
empty.style.display = 'none';
|
|
1731
|
+
table.style.display = 'table';
|
|
1732
|
+
tbody.innerHTML = '';
|
|
1733
|
+
|
|
1734
|
+
const now = Date.now();
|
|
1735
|
+
list.forEach(s => {
|
|
1736
|
+
const tr = document.createElement('tr');
|
|
1737
|
+
const sidShort = s.studioId ? (s.studioId.length > 12 ? s.studioId.substring(0, 10) + '…' : s.studioId) : 'anon';
|
|
1738
|
+
const placeText = (s.placeId && s.placeId > 0) ? `Place #${s.placeId}` : 'Local Place (ID: 0)';
|
|
1739
|
+
const isWs = s.transport === 'websocket';
|
|
1740
|
+
const transportBadge = isWs
|
|
1741
|
+
? '<span class="badge-pill badge-ws">WebSocket ⚡</span>'
|
|
1742
|
+
: '<span class="badge-pill badge-poll">Long-Poll 🔄</span>';
|
|
1743
|
+
const statusBadge = s.isBusy
|
|
1744
|
+
? '<span style="color:var(--warning);">● Sibuk</span>'
|
|
1745
|
+
: '<span style="color:var(--success);">● Siap</span>';
|
|
1746
|
+
const elapsed = s.lastSeen ? Math.max(0, Math.round((now - s.lastSeen) / 1000)) + 'd lalu' : 'Baru saja';
|
|
1747
|
+
|
|
1748
|
+
tr.innerHTML = `
|
|
1749
|
+
<td title="${s.studioId || ''}"><code>${sidShort}</code></td>
|
|
1750
|
+
<td><strong>${placeText}</strong></td>
|
|
1751
|
+
<td>${transportBadge}</td>
|
|
1752
|
+
<td>${statusBadge}</td>
|
|
1753
|
+
<td style="color:var(--text-dim); font-size:12px;">${elapsed}</td>
|
|
1754
|
+
`;
|
|
1755
|
+
tbody.appendChild(tr);
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
/* ── Update Statistics ── */
|
|
793
1760
|
function updateStats(s) {
|
|
794
|
-
|
|
795
|
-
document.getElementById('stat-
|
|
796
|
-
document.getElementById('stat-
|
|
1761
|
+
if (!s) return;
|
|
1762
|
+
if (s.totalTasks !== undefined) document.getElementById('stat-total').textContent = s.totalTasks;
|
|
1763
|
+
if (s.successCount !== undefined) document.getElementById('stat-success').textContent = s.successCount;
|
|
1764
|
+
if (s.errorCount !== undefined) document.getElementById('stat-error').textContent = s.errorCount;
|
|
1765
|
+
|
|
1766
|
+
if (s.version) {
|
|
1767
|
+
if (pkgVersion) pkgVersion.textContent = 'v' + s.version;
|
|
1768
|
+
if (aboutVersionChip) aboutVersionChip.textContent = 'v' + s.version;
|
|
1769
|
+
}
|
|
797
1770
|
|
|
798
|
-
const
|
|
1771
|
+
const total = s.totalTasks || 0;
|
|
1772
|
+
const rate = total > 0 ? Math.round(((s.successCount || 0) / total) * 100) : null;
|
|
799
1773
|
document.getElementById('stat-rate').textContent = rate !== null ? rate + '%' : '—';
|
|
800
1774
|
document.getElementById('bar-success').style.width = (rate || 0) + '%';
|
|
801
1775
|
|
|
802
|
-
const errRate =
|
|
1776
|
+
const errRate = total > 0 ? Math.round(((s.errorCount || 0) / total) * 100) : 0;
|
|
803
1777
|
document.getElementById('bar-error').style.width = errRate + '%';
|
|
804
1778
|
|
|
805
1779
|
if (s.sessionStart) {
|
|
@@ -809,7 +1783,7 @@
|
|
|
809
1783
|
const statusEl = document.getElementById('info-studio');
|
|
810
1784
|
const transportEl = document.getElementById('info-transport');
|
|
811
1785
|
if (s.studioConnected) {
|
|
812
|
-
statusEl.textContent =
|
|
1786
|
+
statusEl.textContent = `Connected ✓ (${s.studioCount || 1} Studio)`;
|
|
813
1787
|
statusEl.style.color = 'var(--success)';
|
|
814
1788
|
if (s.wsStudios && s.wsStudios > 0) {
|
|
815
1789
|
if (transportEl) {
|
|
@@ -817,7 +1791,7 @@
|
|
|
817
1791
|
transportEl.style.color = 'var(--success)';
|
|
818
1792
|
}
|
|
819
1793
|
} else if (transportEl) {
|
|
820
|
-
transportEl.textContent = 'Long-Polling (Fallback)';
|
|
1794
|
+
transportEl.textContent = 'Long-Polling (Active Fallback)';
|
|
821
1795
|
transportEl.style.color = 'var(--accent)';
|
|
822
1796
|
}
|
|
823
1797
|
} else {
|
|
@@ -828,9 +1802,13 @@
|
|
|
828
1802
|
transportEl.style.color = 'var(--accent)';
|
|
829
1803
|
}
|
|
830
1804
|
}
|
|
1805
|
+
|
|
1806
|
+
if (s.connectedStudios) {
|
|
1807
|
+
updateStudiosTable(s.connectedStudios);
|
|
1808
|
+
}
|
|
831
1809
|
}
|
|
832
1810
|
|
|
833
|
-
/* ── Uptime ── */
|
|
1811
|
+
/* ── Uptime Timer ── */
|
|
834
1812
|
setInterval(() => {
|
|
835
1813
|
if (!sessionStartTime) return;
|
|
836
1814
|
const diff = Math.floor((Date.now() - sessionStartTime) / 1000);
|
|
@@ -840,14 +1818,15 @@
|
|
|
840
1818
|
document.getElementById('info-uptime').textContent = `${h}:${m}:${s}`;
|
|
841
1819
|
}, 1000);
|
|
842
1820
|
|
|
843
|
-
/* ── Studio Status ── */
|
|
844
|
-
function setStudioStatus(connected) {
|
|
1821
|
+
/* ── Studio Status Indicator ── */
|
|
1822
|
+
function setStudioStatus(connected, studioCount) {
|
|
845
1823
|
statusDot.className = 'status-dot ' + (connected ? 'connected' : 'waiting');
|
|
846
|
-
|
|
1824
|
+
const countText = studioCount && studioCount > 1 ? ` (${studioCount} Studios)` : '';
|
|
1825
|
+
statusLabel.textContent = connected ? `Studio Connected${countText}` : 'Menunggu Studio...';
|
|
847
1826
|
|
|
848
1827
|
const statusEl = document.getElementById('info-studio');
|
|
849
1828
|
if (connected) {
|
|
850
|
-
statusEl.textContent =
|
|
1829
|
+
statusEl.textContent = `Connected ✓${countText}`;
|
|
851
1830
|
statusEl.style.color = 'var(--success)';
|
|
852
1831
|
} else {
|
|
853
1832
|
statusEl.textContent = 'Disconnected';
|
|
@@ -855,7 +1834,432 @@
|
|
|
855
1834
|
}
|
|
856
1835
|
}
|
|
857
1836
|
|
|
858
|
-
/* ──
|
|
1837
|
+
/* ── Simple Markdown Formatter for Docs ── */
|
|
1838
|
+
function formatMarkdown(md) {
|
|
1839
|
+
if (!md) return '';
|
|
1840
|
+
let html = escapeHtml(md);
|
|
1841
|
+
|
|
1842
|
+
// Fenced code blocks ```
|
|
1843
|
+
html = html.replace(/```([a-zA-Z]*)\n([\s\S]*?)```/g, function(match, lang, code) {
|
|
1844
|
+
return '<pre><code>' + code + '</code></pre>';
|
|
1845
|
+
});
|
|
1846
|
+
|
|
1847
|
+
// Inline code `...`
|
|
1848
|
+
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
1849
|
+
|
|
1850
|
+
// Headers
|
|
1851
|
+
html = html.replace(/^### (.*$)/gim, '<h3>$1</h3>');
|
|
1852
|
+
html = html.replace(/^## (.*$)/gim, '<h2>$1</h2>');
|
|
1853
|
+
html = html.replace(/^# (.*$)/gim, '<h1>$1</h1>');
|
|
1854
|
+
|
|
1855
|
+
// Bold & Italic
|
|
1856
|
+
html = html.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
|
1857
|
+
html = html.replace(/\*([^*]+)\*/g, '<em>$1</em>');
|
|
1858
|
+
|
|
1859
|
+
// Unordered Lists
|
|
1860
|
+
html = html.replace(/^\- (.*$)/gim, '<li>$1</li>');
|
|
1861
|
+
html = html.replace(/(<li>.*<\/li>)/gim, '<ul>$1</ul>');
|
|
1862
|
+
|
|
1863
|
+
// Clean double <ul> tags
|
|
1864
|
+
html = html.replace(/<\/ul>\s*<ul>/g, '');
|
|
1865
|
+
|
|
1866
|
+
// Paragraph breaks
|
|
1867
|
+
html = html.replace(/\n\n/g, '<br><br>');
|
|
1868
|
+
|
|
1869
|
+
return html;
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
/* ── Docs Explorer Handler ── */
|
|
1873
|
+
const docsForm = document.getElementById('docs-form');
|
|
1874
|
+
const docsQuery = document.getElementById('docs-query');
|
|
1875
|
+
const docsMember = document.getElementById('docs-member');
|
|
1876
|
+
const docsBtn = document.getElementById('docs-btn');
|
|
1877
|
+
const docsContent = document.getElementById('docs-content');
|
|
1878
|
+
const docsResultTitle = document.getElementById('docs-result-title');
|
|
1879
|
+
const docsCopyBtn = document.getElementById('docs-copy-btn');
|
|
1880
|
+
|
|
1881
|
+
async function searchDocs(query, member) {
|
|
1882
|
+
if (!query) return;
|
|
1883
|
+
docsQuery.value = query;
|
|
1884
|
+
if (member) docsMember.value = member;
|
|
1885
|
+
|
|
1886
|
+
docsBtn.disabled = true;
|
|
1887
|
+
docsBtn.innerHTML = 'Mencari...';
|
|
1888
|
+
docsContent.innerHTML = '<span style="color:var(--accent);">Mengambil dokumentasi resmi Creator Docs...</span>';
|
|
1889
|
+
|
|
1890
|
+
try {
|
|
1891
|
+
const url = new URL('/api/docs', window.location.origin);
|
|
1892
|
+
url.searchParams.set('query', query);
|
|
1893
|
+
if (member) url.searchParams.set('member', member);
|
|
1894
|
+
|
|
1895
|
+
const res = await fetch(url.toString());
|
|
1896
|
+
const data = await res.json();
|
|
1897
|
+
|
|
1898
|
+
if (!res.ok || data.error) {
|
|
1899
|
+
docsContent.innerHTML = `<span style="color:var(--error);">Error: ${escapeHtml(data.error || 'Gagal mengambil dokumentasi')}</span>`;
|
|
1900
|
+
docsCopyBtn.style.display = 'none';
|
|
1901
|
+
} else {
|
|
1902
|
+
latestDocsMarkdown = data.result;
|
|
1903
|
+
docsResultTitle.innerHTML = `Dokumentasi: <code>${escapeHtml(query)}${member ? '.' + escapeHtml(member) : ''}</code>`;
|
|
1904
|
+
docsContent.innerHTML = formatMarkdown(data.result);
|
|
1905
|
+
docsCopyBtn.style.display = 'inline-block';
|
|
1906
|
+
}
|
|
1907
|
+
} catch (err) {
|
|
1908
|
+
docsContent.innerHTML = `<span style="color:var(--error);">Koneksi gagal: ${escapeHtml(err.message)}</span>`;
|
|
1909
|
+
docsCopyBtn.style.display = 'none';
|
|
1910
|
+
} finally {
|
|
1911
|
+
docsBtn.disabled = false;
|
|
1912
|
+
docsBtn.innerHTML = `
|
|
1913
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
|
1914
|
+
Cari Docs
|
|
1915
|
+
`;
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
docsForm.addEventListener('submit', (e) => {
|
|
1920
|
+
e.preventDefault();
|
|
1921
|
+
searchDocs(docsQuery.value.trim(), docsMember.value.trim());
|
|
1922
|
+
});
|
|
1923
|
+
|
|
1924
|
+
document.querySelectorAll('.doc-chip').forEach(chip => {
|
|
1925
|
+
chip.addEventListener('click', () => {
|
|
1926
|
+
const q = chip.dataset.q;
|
|
1927
|
+
searchDocs(q, '');
|
|
1928
|
+
});
|
|
1929
|
+
});
|
|
1930
|
+
|
|
1931
|
+
docsCopyBtn.addEventListener('click', () => {
|
|
1932
|
+
if (latestDocsMarkdown) {
|
|
1933
|
+
navigator.clipboard.writeText(latestDocsMarkdown);
|
|
1934
|
+
docsCopyBtn.textContent = 'Tersalin ✓';
|
|
1935
|
+
setTimeout(() => { docsCopyBtn.textContent = 'Salin Markdown'; }, 2000);
|
|
1936
|
+
}
|
|
1937
|
+
});
|
|
1938
|
+
|
|
1939
|
+
/* ── VFX Database & Library Controller ── */
|
|
1940
|
+
let currentVfxItems = [];
|
|
1941
|
+
let currentVfxCategories = [];
|
|
1942
|
+
let activeVfxCategory = 'All';
|
|
1943
|
+
let vfxSearchTimeout = null;
|
|
1944
|
+
|
|
1945
|
+
const vfxSidebarCount = document.getElementById('vfx-sidebar-count');
|
|
1946
|
+
const vfxCategoryTabs = document.getElementById('vfx-category-tabs');
|
|
1947
|
+
const vfxCardsGrid = document.getElementById('vfx-cards-grid');
|
|
1948
|
+
const vfxSearchInput = document.getElementById('vfx-search-input');
|
|
1949
|
+
const vfxStatTotal = document.getElementById('vfx-stat-total');
|
|
1950
|
+
const vfxStatCategories = document.getElementById('vfx-stat-categories');
|
|
1951
|
+
const vfxStatEmitters = document.getElementById('vfx-stat-emitters');
|
|
1952
|
+
const vfxStatBeams = document.getElementById('vfx-stat-beams');
|
|
1953
|
+
|
|
1954
|
+
const modalScanVfx = document.getElementById('modal-scan-vfx');
|
|
1955
|
+
const modalApplyVfx = document.getElementById('modal-apply-vfx');
|
|
1956
|
+
const modalJsonVfx = document.getElementById('modal-json-vfx');
|
|
1957
|
+
|
|
1958
|
+
window.closeVfxModals = function() {
|
|
1959
|
+
modalScanVfx.classList.remove('open');
|
|
1960
|
+
modalApplyVfx.classList.remove('open');
|
|
1961
|
+
modalJsonVfx.classList.remove('open');
|
|
1962
|
+
};
|
|
1963
|
+
|
|
1964
|
+
window.openApplyModal = function(id) {
|
|
1965
|
+
const decodedId = decodeURIComponent(id);
|
|
1966
|
+
const item = currentVfxItems.find(i => i.id === decodedId);
|
|
1967
|
+
if (!item) return;
|
|
1968
|
+
document.getElementById('apply-vfx-id').value = item.id;
|
|
1969
|
+
document.getElementById('apply-vfx-title').textContent = item.name;
|
|
1970
|
+
modalApplyVfx.classList.add('open');
|
|
1971
|
+
};
|
|
1972
|
+
|
|
1973
|
+
window.openJsonModal = function(id) {
|
|
1974
|
+
const decodedId = decodeURIComponent(id);
|
|
1975
|
+
const item = currentVfxItems.find(i => i.id === decodedId);
|
|
1976
|
+
if (!item) return;
|
|
1977
|
+
document.getElementById('json-modal-title').textContent = 'Pure Data: ' + item.name;
|
|
1978
|
+
document.getElementById('json-modal-content').textContent = JSON.stringify(item.data, null, 2);
|
|
1979
|
+
modalJsonVfx.classList.add('open');
|
|
1980
|
+
};
|
|
1981
|
+
|
|
1982
|
+
window.deleteVfxItem = async function(id) {
|
|
1983
|
+
const decodedId = decodeURIComponent(id);
|
|
1984
|
+
const item = currentVfxItems.find(i => i.id === decodedId);
|
|
1985
|
+
const name = item ? item.name : decodedId;
|
|
1986
|
+
if (!confirm(`Apakah Anda yakin ingin menghapus VFX "${name}" dari database?`)) return;
|
|
1987
|
+
try {
|
|
1988
|
+
const res = await fetch('/api/vfx/' + encodeURIComponent(decodedId), { method: 'DELETE' });
|
|
1989
|
+
if (res.ok) {
|
|
1990
|
+
loadVfxDatabase();
|
|
1991
|
+
} else {
|
|
1992
|
+
alert('Gagal menghapus VFX');
|
|
1993
|
+
}
|
|
1994
|
+
} catch (err) {
|
|
1995
|
+
alert('Error: ' + err.message);
|
|
1996
|
+
}
|
|
1997
|
+
};
|
|
1998
|
+
|
|
1999
|
+
async function loadVfxDatabase() {
|
|
2000
|
+
try {
|
|
2001
|
+
const [vfxRes, catRes] = await Promise.all([
|
|
2002
|
+
fetch('/api/vfx' + (activeVfxCategory !== 'All' ? `?category=${encodeURIComponent(activeVfxCategory)}` : '')),
|
|
2003
|
+
fetch('/api/vfx/categories')
|
|
2004
|
+
]);
|
|
2005
|
+
if (!vfxRes.ok || !catRes.ok) return;
|
|
2006
|
+
|
|
2007
|
+
const vfxData = await vfxRes.json();
|
|
2008
|
+
const catData = await catRes.json();
|
|
2009
|
+
|
|
2010
|
+
currentVfxItems = vfxData.items || [];
|
|
2011
|
+
currentVfxCategories = catData.categories || [];
|
|
2012
|
+
|
|
2013
|
+
renderVfxCategories();
|
|
2014
|
+
renderVfxStats();
|
|
2015
|
+
renderVfxCards();
|
|
2016
|
+
|
|
2017
|
+
// Hitung total di sidebar
|
|
2018
|
+
let totalCount = 0;
|
|
2019
|
+
currentVfxCategories.forEach(c => totalCount += c.count);
|
|
2020
|
+
if (vfxSidebarCount) vfxSidebarCount.textContent = totalCount;
|
|
2021
|
+
} catch (err) {
|
|
2022
|
+
console.error('Gagal memuat VFX Database:', err);
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
function renderVfxCategories() {
|
|
2027
|
+
if (!vfxCategoryTabs) return;
|
|
2028
|
+
let totalCount = 0;
|
|
2029
|
+
currentVfxCategories.forEach(c => totalCount += c.count);
|
|
2030
|
+
|
|
2031
|
+
let html = `
|
|
2032
|
+
<button class="vfx-cat-pill ${activeVfxCategory === 'All' ? 'active' : ''}" data-cat="All">
|
|
2033
|
+
Semua <span class="vfx-cat-count">${totalCount}</span>
|
|
2034
|
+
</button>
|
|
2035
|
+
`;
|
|
2036
|
+
|
|
2037
|
+
currentVfxCategories.forEach(cat => {
|
|
2038
|
+
html += `
|
|
2039
|
+
<button class="vfx-cat-pill ${activeVfxCategory === cat.name ? 'active' : ''}" data-cat="${escapeHtml(cat.name)}">
|
|
2040
|
+
${escapeHtml(cat.name)} <span class="vfx-cat-count">${cat.count}</span>
|
|
2041
|
+
</button>
|
|
2042
|
+
`;
|
|
2043
|
+
});
|
|
2044
|
+
|
|
2045
|
+
vfxCategoryTabs.innerHTML = html;
|
|
2046
|
+
|
|
2047
|
+
vfxCategoryTabs.querySelectorAll('.vfx-cat-pill').forEach(btn => {
|
|
2048
|
+
btn.addEventListener('click', () => {
|
|
2049
|
+
activeVfxCategory = btn.dataset.cat;
|
|
2050
|
+
vfxCategoryTabs.querySelectorAll('.vfx-cat-pill').forEach(b => b.classList.remove('active'));
|
|
2051
|
+
btn.classList.add('active');
|
|
2052
|
+
loadVfxDatabase();
|
|
2053
|
+
});
|
|
2054
|
+
});
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
function renderVfxStats() {
|
|
2058
|
+
let totalCount = 0;
|
|
2059
|
+
let totalEmitters = 0;
|
|
2060
|
+
let totalBeams = 0;
|
|
2061
|
+
|
|
2062
|
+
currentVfxItems.forEach(item => {
|
|
2063
|
+
totalCount++;
|
|
2064
|
+
if (item.stats) {
|
|
2065
|
+
totalEmitters += (item.stats.emitters || 0);
|
|
2066
|
+
totalBeams += ((item.stats.beams || 0) + (item.stats.trails || 0));
|
|
2067
|
+
}
|
|
2068
|
+
});
|
|
2069
|
+
|
|
2070
|
+
if (vfxStatTotal) vfxStatTotal.textContent = totalCount;
|
|
2071
|
+
if (vfxStatCategories) vfxStatCategories.textContent = currentVfxCategories.length;
|
|
2072
|
+
if (vfxStatEmitters) vfxStatEmitters.textContent = totalEmitters;
|
|
2073
|
+
if (vfxStatBeams) vfxStatBeams.textContent = totalBeams;
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
function renderVfxCards() {
|
|
2077
|
+
if (!vfxCardsGrid) return;
|
|
2078
|
+
if (currentVfxItems.length === 0) {
|
|
2079
|
+
vfxCardsGrid.innerHTML = `
|
|
2080
|
+
<div style="grid-column: 1 / -1; padding: 48px; text-align: center; color: var(--text-dim); background: var(--surface-card); border: 1px dashed var(--border); border-radius: 12px;">
|
|
2081
|
+
<div style="font-size: 32px; margin-bottom: 8px;">✨</div>
|
|
2082
|
+
<div style="font-size: 15px; font-weight: 600; color: var(--text);">Belum ada efek visual di kategori ini</div>
|
|
2083
|
+
<div style="font-size: 13px; margin-top: 4px;">Klik tombol "Scan dari Studio" di atas untuk memindai partikel dari proyek aktif Anda.</div>
|
|
2084
|
+
</div>
|
|
2085
|
+
`;
|
|
2086
|
+
return;
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
const filterQuery = (vfxSearchInput ? vfxSearchInput.value.toLowerCase().trim() : '');
|
|
2090
|
+
const items = filterQuery
|
|
2091
|
+
? currentVfxItems.filter(i =>
|
|
2092
|
+
i.name.toLowerCase().includes(filterQuery) ||
|
|
2093
|
+
i.category.toLowerCase().includes(filterQuery) ||
|
|
2094
|
+
(i.description && i.description.toLowerCase().includes(filterQuery)) ||
|
|
2095
|
+
(i.tags && i.tags.some(t => t.toLowerCase().includes(filterQuery))))
|
|
2096
|
+
: currentVfxItems;
|
|
2097
|
+
|
|
2098
|
+
vfxCardsGrid.innerHTML = items.map(item => {
|
|
2099
|
+
const dateStr = item.createdAt ? new Date(item.createdAt).toLocaleDateString('id-ID', { day:'numeric', month:'short', year:'numeric' }) : 'NVStudio';
|
|
2100
|
+
const stats = item.stats || { emitters: 0, beams: 0, trails: 0, lights: 0 };
|
|
2101
|
+
const compPills = [];
|
|
2102
|
+
if (stats.emitters > 0) compPills.push(`${stats.emitters} Emitters`);
|
|
2103
|
+
if (stats.beams > 0) compPills.push(`${stats.beams} Beams`);
|
|
2104
|
+
if (stats.trails > 0) compPills.push(`${stats.trails} Trails`);
|
|
2105
|
+
if (stats.lights > 0) compPills.push(`${stats.lights} Lights`);
|
|
2106
|
+
if (compPills.length === 0) compPills.push(`${item.nodeCount || 1} Nodes`);
|
|
2107
|
+
|
|
2108
|
+
const tagChips = (item.tags || []).slice(0, 4).map(t => `<span class="vfx-tag-chip">#${escapeHtml(t)}</span>`).join('');
|
|
2109
|
+
|
|
2110
|
+
return `
|
|
2111
|
+
<div class="vfx-card">
|
|
2112
|
+
<div class="vfx-card-top">
|
|
2113
|
+
<div class="vfx-card-title-group">
|
|
2114
|
+
<h4>${escapeHtml(item.name)}</h4>
|
|
2115
|
+
<div class="vfx-card-date">${escapeHtml(dateStr)} • ${escapeHtml(item.author || 'NVStudio')}</div>
|
|
2116
|
+
</div>
|
|
2117
|
+
<span class="vfx-badge-cat vfx-badge-${escapeHtml(item.category || 'Ambient')}">${escapeHtml(item.category || 'Ambient')}</span>
|
|
2118
|
+
</div>
|
|
2119
|
+
|
|
2120
|
+
<div class="vfx-card-desc">${escapeHtml(item.description || 'Efek visual siap pakai untuk proyek Roblox.')}</div>
|
|
2121
|
+
|
|
2122
|
+
<div class="vfx-composition-badges">
|
|
2123
|
+
${compPills.map(p => `<span class="vfx-comp-pill">${escapeHtml(p)}</span>`).join('')}
|
|
2124
|
+
</div>
|
|
2125
|
+
|
|
2126
|
+
${tagChips ? `<div class="vfx-card-tags">${tagChips}</div>` : ''}
|
|
2127
|
+
|
|
2128
|
+
<div class="vfx-card-footer">
|
|
2129
|
+
<button class="vfx-btn-apply" onclick="openApplyModal('${encodeURIComponent(item.id)}')">
|
|
2130
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
|
|
2131
|
+
Apply ke Studio
|
|
2132
|
+
</button>
|
|
2133
|
+
<button class="vfx-btn-json" title="Inspeksi Data JSON" onclick="openJsonModal('${encodeURIComponent(item.id)}')">
|
|
2134
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
|
|
2135
|
+
</button>
|
|
2136
|
+
<button class="vfx-btn-del" title="Hapus dari Database" onclick="deleteVfxItem('${encodeURIComponent(item.id)}')">
|
|
2137
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
|
|
2138
|
+
</button>
|
|
2139
|
+
</div>
|
|
2140
|
+
</div>
|
|
2141
|
+
`;
|
|
2142
|
+
}).join('');
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
// Search debounce
|
|
2146
|
+
if (vfxSearchInput) {
|
|
2147
|
+
vfxSearchInput.addEventListener('input', () => {
|
|
2148
|
+
clearTimeout(vfxSearchTimeout);
|
|
2149
|
+
vfxSearchTimeout = setTimeout(() => {
|
|
2150
|
+
renderVfxCards();
|
|
2151
|
+
}, 180);
|
|
2152
|
+
});
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
// Modal triggers & handlers
|
|
2156
|
+
const btnOpenScanModal = document.getElementById('btn-open-scan-modal');
|
|
2157
|
+
if (btnOpenScanModal) {
|
|
2158
|
+
btnOpenScanModal.addEventListener('click', () => {
|
|
2159
|
+
modalScanVfx.classList.add('open');
|
|
2160
|
+
});
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
const btnRefreshVfx = document.getElementById('btn-refresh-vfx');
|
|
2164
|
+
if (btnRefreshVfx) {
|
|
2165
|
+
btnRefreshVfx.addEventListener('click', () => {
|
|
2166
|
+
loadVfxDatabase();
|
|
2167
|
+
});
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
// Submit Scan form
|
|
2171
|
+
const btnSubmitScan = document.getElementById('btn-submit-scan');
|
|
2172
|
+
if (btnSubmitScan) {
|
|
2173
|
+
btnSubmitScan.addEventListener('click', async () => {
|
|
2174
|
+
const target = document.getElementById('scan-input-target').value.trim();
|
|
2175
|
+
const name = document.getElementById('scan-input-name').value.trim();
|
|
2176
|
+
const category = document.getElementById('scan-select-category').value;
|
|
2177
|
+
const tags = document.getElementById('scan-input-tags').value.split(',').map(t => t.trim()).filter(Boolean);
|
|
2178
|
+
|
|
2179
|
+
if (!target) {
|
|
2180
|
+
alert('Harap isi path target di Studio');
|
|
2181
|
+
return;
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
btnSubmitScan.disabled = true;
|
|
2185
|
+
btnSubmitScan.textContent = 'Memindai...';
|
|
2186
|
+
try {
|
|
2187
|
+
const res = await fetch('/api/vfx/scan', {
|
|
2188
|
+
method: 'POST',
|
|
2189
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2190
|
+
body: JSON.stringify({ target, name, category, tags })
|
|
2191
|
+
});
|
|
2192
|
+
const data = await res.json();
|
|
2193
|
+
if (res.ok && data.success) {
|
|
2194
|
+
closeVfxModals();
|
|
2195
|
+
loadVfxDatabase();
|
|
2196
|
+
alert(`✅ Sukses memindai VFX "${data.item.name}" (${data.item.nodeCount} node)!`);
|
|
2197
|
+
} else {
|
|
2198
|
+
alert('Gagal scan: ' + (data.error || 'Unknown error'));
|
|
2199
|
+
}
|
|
2200
|
+
} catch (err) {
|
|
2201
|
+
alert('Error: ' + err.message);
|
|
2202
|
+
} finally {
|
|
2203
|
+
btnSubmitScan.disabled = false;
|
|
2204
|
+
btnSubmitScan.textContent = 'Mulai Pindai';
|
|
2205
|
+
}
|
|
2206
|
+
});
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
// Submit Apply form
|
|
2210
|
+
const btnSubmitApply = document.getElementById('btn-submit-apply');
|
|
2211
|
+
if (btnSubmitApply) {
|
|
2212
|
+
btnSubmitApply.addEventListener('click', async () => {
|
|
2213
|
+
const id = document.getElementById('apply-vfx-id').value;
|
|
2214
|
+
const target = document.getElementById('apply-input-target').value.trim();
|
|
2215
|
+
const scale = parseFloat(document.getElementById('apply-input-scale').value) || 1.0;
|
|
2216
|
+
const enabledVal = document.getElementById('apply-select-enabled').value;
|
|
2217
|
+
const enabled = enabledVal === 'default' ? undefined : (enabledVal === 'true');
|
|
2218
|
+
|
|
2219
|
+
if (!target) {
|
|
2220
|
+
alert('Harap isi target di Studio');
|
|
2221
|
+
return;
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
btnSubmitApply.disabled = true;
|
|
2225
|
+
btnSubmitApply.textContent = 'Menerapkan...';
|
|
2226
|
+
try {
|
|
2227
|
+
const res = await fetch('/api/vfx/apply', {
|
|
2228
|
+
method: 'POST',
|
|
2229
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2230
|
+
body: JSON.stringify({ id, target, scale, enabled })
|
|
2231
|
+
});
|
|
2232
|
+
const data = await res.json();
|
|
2233
|
+
if (res.ok && data.success) {
|
|
2234
|
+
closeVfxModals();
|
|
2235
|
+
alert(`✅ VFX berhasil diterapkan ke "${target}" di Studio!`);
|
|
2236
|
+
} else {
|
|
2237
|
+
alert('Gagal apply: ' + (data.error || 'Unknown error'));
|
|
2238
|
+
}
|
|
2239
|
+
} catch (err) {
|
|
2240
|
+
alert('Error: ' + err.message);
|
|
2241
|
+
} finally {
|
|
2242
|
+
btnSubmitApply.disabled = false;
|
|
2243
|
+
btnSubmitApply.textContent = 'Terapkan Sekarang';
|
|
2244
|
+
}
|
|
2245
|
+
});
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
// Copy JSON button
|
|
2249
|
+
const btnCopyVfxJson = document.getElementById('btn-copy-vfx-json');
|
|
2250
|
+
if (btnCopyVfxJson) {
|
|
2251
|
+
btnCopyVfxJson.addEventListener('click', () => {
|
|
2252
|
+
const content = document.getElementById('json-modal-content').textContent;
|
|
2253
|
+
navigator.clipboard.writeText(content);
|
|
2254
|
+
btnCopyVfxJson.textContent = 'Tersalin ✓';
|
|
2255
|
+
setTimeout(() => { btnCopyVfxJson.textContent = 'Salin JSON'; }, 1500);
|
|
2256
|
+
});
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
// Muat VFX Database saat pertama kali dibuka
|
|
2260
|
+
loadVfxDatabase();
|
|
2261
|
+
|
|
2262
|
+
/* ── Socket.IO Event Listeners ── */
|
|
859
2263
|
socket.on('connect', () => {});
|
|
860
2264
|
socket.on('disconnect', () => setStudioStatus(false));
|
|
861
2265
|
|
|
@@ -864,7 +2268,7 @@
|
|
|
864
2268
|
renderLogs();
|
|
865
2269
|
if (stats) {
|
|
866
2270
|
updateStats(stats);
|
|
867
|
-
if (stats.studioConnected) setStudioStatus(true);
|
|
2271
|
+
if (stats.studioConnected) setStudioStatus(true, stats.studioCount);
|
|
868
2272
|
}
|
|
869
2273
|
});
|
|
870
2274
|
|
|
@@ -876,9 +2280,15 @@
|
|
|
876
2280
|
updateStats(s);
|
|
877
2281
|
});
|
|
878
2282
|
|
|
879
|
-
socket.on('
|
|
880
|
-
|
|
2283
|
+
socket.on('vfx_updated', () => {
|
|
2284
|
+
loadVfxDatabase();
|
|
2285
|
+
});
|
|
2286
|
+
|
|
2287
|
+
socket.on('studio-status', ({ connected, studioCount, connectedStudios, version }) => {
|
|
2288
|
+
setStudioStatus(connected, studioCount);
|
|
2289
|
+
if (connectedStudios) updateStudiosTable(connectedStudios);
|
|
2290
|
+
if (version && pkgVersion) pkgVersion.textContent = 'v' + version;
|
|
881
2291
|
});
|
|
882
2292
|
</script>
|
|
883
2293
|
</body>
|
|
884
|
-
</html>
|
|
2294
|
+
</html>
|