thevoidforge 21.0.0 → 21.0.1
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/dist/wizard/danger-room.config.json +5 -0
- package/dist/wizard/ui/app.js +1231 -0
- package/dist/wizard/ui/danger-room-prophecy.js +217 -0
- package/dist/wizard/ui/danger-room.html +626 -0
- package/dist/wizard/ui/danger-room.js +880 -0
- package/dist/wizard/ui/deploy.html +177 -0
- package/dist/wizard/ui/deploy.js +582 -0
- package/dist/wizard/ui/favicon.svg +11 -0
- package/dist/wizard/ui/index.html +394 -0
- package/dist/wizard/ui/lobby.html +228 -0
- package/dist/wizard/ui/lobby.js +783 -0
- package/dist/wizard/ui/login.html +110 -0
- package/dist/wizard/ui/login.js +184 -0
- package/dist/wizard/ui/rollback.js +107 -0
- package/dist/wizard/ui/styles.css +1029 -0
- package/dist/wizard/ui/tower.html +171 -0
- package/dist/wizard/ui/tower.js +444 -0
- package/dist/wizard/ui/war-room-prophecy.js +217 -0
- package/dist/wizard/ui/war-room.html +219 -0
- package/dist/wizard/ui/war-room.js +285 -0
- package/package.json +2 -2
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prophecy Visualizer — interactive SVG dependency graph for campaign missions.
|
|
3
|
+
*
|
|
4
|
+
* Reads campaign data from /api/danger-room/campaign and renders a node/edge graph.
|
|
5
|
+
* Nodes = missions. Edges = dependency order. Color = status.
|
|
6
|
+
* Clickable nodes show mission details.
|
|
7
|
+
*/
|
|
8
|
+
(function () {
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
// ── Constants ─────────────────────────────────
|
|
12
|
+
|
|
13
|
+
var NODE_RADIUS = 24;
|
|
14
|
+
var NODE_SPACING_X = 120;
|
|
15
|
+
var NODE_SPACING_Y = 80;
|
|
16
|
+
var PADDING = 40;
|
|
17
|
+
|
|
18
|
+
var STATUS_COLORS = {
|
|
19
|
+
COMPLETE: '#34d399',
|
|
20
|
+
ACTIVE: '#fbbf24',
|
|
21
|
+
BLOCKED: '#ef4444',
|
|
22
|
+
PENDING: '#555',
|
|
23
|
+
STRUCTURAL: '#6366f1'
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
var STATUS_LABELS = {
|
|
27
|
+
COMPLETE: 'Complete',
|
|
28
|
+
ACTIVE: 'In Progress',
|
|
29
|
+
BLOCKED: 'Blocked',
|
|
30
|
+
PENDING: 'Pending',
|
|
31
|
+
STRUCTURAL: 'Structural'
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// ── SVG helpers ───────────────────────────────
|
|
35
|
+
|
|
36
|
+
function svgEl(tag, attrs) {
|
|
37
|
+
var el = document.createElementNS('http://www.w3.org/2000/svg', tag);
|
|
38
|
+
if (attrs) {
|
|
39
|
+
for (var key in attrs) {
|
|
40
|
+
if (Object.prototype.hasOwnProperty.call(attrs, key)) {
|
|
41
|
+
el.setAttribute(key, attrs[key]);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return el;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function escapeText(str) {
|
|
49
|
+
var div = document.createElement('div');
|
|
50
|
+
div.appendChild(document.createTextNode(str));
|
|
51
|
+
return div.innerHTML;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── Layout ────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
function layoutNodes(missions) {
|
|
57
|
+
// Simple left-to-right grid layout
|
|
58
|
+
var cols = Math.ceil(Math.sqrt(missions.length));
|
|
59
|
+
return missions.map(function (m, i) {
|
|
60
|
+
var col = i % cols;
|
|
61
|
+
var row = Math.floor(i / cols);
|
|
62
|
+
return {
|
|
63
|
+
mission: m,
|
|
64
|
+
x: PADDING + col * NODE_SPACING_X + NODE_RADIUS,
|
|
65
|
+
y: PADDING + row * NODE_SPACING_Y + NODE_RADIUS
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ── Rendering ─────────────────────────────────
|
|
71
|
+
|
|
72
|
+
function renderGraph(container, campaignData) {
|
|
73
|
+
container.innerHTML = '';
|
|
74
|
+
|
|
75
|
+
if (!campaignData || !campaignData.missions || campaignData.missions.length === 0) {
|
|
76
|
+
container.innerHTML = '<div style="padding:20px;color:var(--text-dim);font-size:13px;">No campaign data — run /campaign to see the prophecy graph.</div>';
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
var nodes = layoutNodes(campaignData.missions);
|
|
81
|
+
var cols = Math.ceil(Math.sqrt(campaignData.missions.length));
|
|
82
|
+
var rows = Math.ceil(campaignData.missions.length / cols);
|
|
83
|
+
var svgWidth = PADDING * 2 + cols * NODE_SPACING_X;
|
|
84
|
+
var svgHeight = PADDING * 2 + rows * NODE_SPACING_Y;
|
|
85
|
+
|
|
86
|
+
var svg = svgEl('svg', {
|
|
87
|
+
viewBox: '0 0 ' + svgWidth + ' ' + svgHeight,
|
|
88
|
+
width: '100%',
|
|
89
|
+
height: Math.min(svgHeight, 400) + 'px',
|
|
90
|
+
role: 'group',
|
|
91
|
+
'aria-label': 'Campaign mission dependency graph'
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Draw edges (sequential dependency: mission N → mission N+1)
|
|
95
|
+
for (var i = 0; i < nodes.length - 1; i++) {
|
|
96
|
+
var from = nodes[i];
|
|
97
|
+
var to = nodes[i + 1];
|
|
98
|
+
var line = svgEl('line', {
|
|
99
|
+
x1: from.x,
|
|
100
|
+
y1: from.y,
|
|
101
|
+
x2: to.x,
|
|
102
|
+
y2: to.y,
|
|
103
|
+
stroke: '#444',
|
|
104
|
+
'stroke-width': '2',
|
|
105
|
+
'stroke-dasharray': '4,4'
|
|
106
|
+
});
|
|
107
|
+
svg.appendChild(line);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Draw nodes
|
|
111
|
+
nodes.forEach(function (node) {
|
|
112
|
+
var m = node.mission;
|
|
113
|
+
var color = STATUS_COLORS[m.status] || STATUS_COLORS.PENDING;
|
|
114
|
+
var label = STATUS_LABELS[m.status] || m.status;
|
|
115
|
+
|
|
116
|
+
// Node group
|
|
117
|
+
var g = svgEl('g', {
|
|
118
|
+
'data-mission': m.number,
|
|
119
|
+
style: 'cursor:pointer',
|
|
120
|
+
role: 'button',
|
|
121
|
+
tabindex: '0',
|
|
122
|
+
'aria-label': 'Mission ' + m.number + ': ' + escapeText(m.name) + ' — ' + label
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Circle
|
|
126
|
+
var circle = svgEl('circle', {
|
|
127
|
+
cx: node.x,
|
|
128
|
+
cy: node.y,
|
|
129
|
+
r: NODE_RADIUS,
|
|
130
|
+
fill: color,
|
|
131
|
+
opacity: '0.2',
|
|
132
|
+
stroke: color,
|
|
133
|
+
'stroke-width': '2'
|
|
134
|
+
});
|
|
135
|
+
g.appendChild(circle);
|
|
136
|
+
|
|
137
|
+
// Mission number
|
|
138
|
+
var text = svgEl('text', {
|
|
139
|
+
x: node.x,
|
|
140
|
+
y: node.y + 1,
|
|
141
|
+
'text-anchor': 'middle',
|
|
142
|
+
'dominant-baseline': 'central',
|
|
143
|
+
fill: color,
|
|
144
|
+
'font-size': '14',
|
|
145
|
+
'font-weight': '700'
|
|
146
|
+
});
|
|
147
|
+
text.textContent = m.number;
|
|
148
|
+
g.appendChild(text);
|
|
149
|
+
|
|
150
|
+
// Mission name label (below node)
|
|
151
|
+
var nameText = svgEl('text', {
|
|
152
|
+
x: node.x,
|
|
153
|
+
y: node.y + NODE_RADIUS + 14,
|
|
154
|
+
'text-anchor': 'middle',
|
|
155
|
+
fill: '#999',
|
|
156
|
+
'font-size': '9'
|
|
157
|
+
});
|
|
158
|
+
// Truncate long names
|
|
159
|
+
var displayName = m.name.length > 18 ? m.name.substring(0, 16) + '…' : m.name;
|
|
160
|
+
nameText.textContent = displayName;
|
|
161
|
+
g.appendChild(nameText);
|
|
162
|
+
|
|
163
|
+
// Status dot
|
|
164
|
+
var statusDot = svgEl('circle', {
|
|
165
|
+
cx: node.x + NODE_RADIUS - 4,
|
|
166
|
+
cy: node.y - NODE_RADIUS + 4,
|
|
167
|
+
r: '4',
|
|
168
|
+
fill: color
|
|
169
|
+
});
|
|
170
|
+
g.appendChild(statusDot);
|
|
171
|
+
|
|
172
|
+
// Focus indicator — highlight circle on keyboard focus
|
|
173
|
+
g.addEventListener('focus', function () { circle.setAttribute('stroke-width', '4'); });
|
|
174
|
+
g.addEventListener('blur', function () { circle.setAttribute('stroke-width', '2'); });
|
|
175
|
+
|
|
176
|
+
// Click handler — show details in the detail panel
|
|
177
|
+
g.addEventListener('click', function () { showDetail(m); });
|
|
178
|
+
g.addEventListener('keydown', function (e) {
|
|
179
|
+
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); showDetail(m); }
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
svg.appendChild(g);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
container.appendChild(svg);
|
|
186
|
+
|
|
187
|
+
// Legend
|
|
188
|
+
var legend = document.createElement('div');
|
|
189
|
+
legend.style.cssText = 'display:flex;gap:12px;margin-top:8px;font-size:10px;color:var(--text-dim);flex-wrap:wrap;';
|
|
190
|
+
Object.keys(STATUS_COLORS).forEach(function (status) {
|
|
191
|
+
var item = document.createElement('span');
|
|
192
|
+
item.innerHTML = '<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:' +
|
|
193
|
+
STATUS_COLORS[status] + ';margin-right:4px;vertical-align:middle;"></span>' +
|
|
194
|
+
(STATUS_LABELS[status] || status);
|
|
195
|
+
legend.appendChild(item);
|
|
196
|
+
});
|
|
197
|
+
container.appendChild(legend);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ── Detail panel ──────────────────────────────
|
|
201
|
+
|
|
202
|
+
function showDetail(mission) {
|
|
203
|
+
var panel = document.getElementById('prophecy-detail');
|
|
204
|
+
if (!panel) return;
|
|
205
|
+
var color = STATUS_COLORS[mission.status] || STATUS_COLORS.PENDING;
|
|
206
|
+
var label = STATUS_LABELS[mission.status] || escapeText(mission.status);
|
|
207
|
+
panel.innerHTML =
|
|
208
|
+
'<div style="font-weight:700;color:' + color + ';">Mission ' + escapeText(String(mission.number)) + '</div>' +
|
|
209
|
+
'<div style="margin:4px 0;font-size:13px;">' + escapeText(mission.name) + '</div>' +
|
|
210
|
+
'<div style="font-size:11px;color:var(--text-dim);">Status: ' + label + '</div>';
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ── Init ──────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
// Expose render function for the main danger-room.js to call
|
|
216
|
+
window.renderProphecyGraph = renderGraph;
|
|
217
|
+
})();
|