@rune-kit/rune 2.18.0 → 2.20.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 +26 -7
- package/compiler/__tests__/comprehension.test.js +916 -0
- package/compiler/__tests__/governance-collector.test.js +376 -0
- package/compiler/__tests__/setup.test.js +338 -7
- package/compiler/analytics.js +5 -0
- package/compiler/bin/rune.js +165 -1
- package/compiler/commands/setup.js +190 -3
- package/compiler/comprehension-client.js +2348 -0
- package/compiler/comprehension.js +1254 -0
- package/compiler/governance-collector.js +382 -0
- package/compiler/schemas/comprehension.schema.json +87 -0
- package/compiler/schemas/governance.schema.json +78 -0
- package/compiler/transforms/branding.js +1 -1
- package/hooks/context-watch/index.cjs +24 -13
- package/hooks/hooks.json +2 -2
- package/hooks/lib/context-key.cjs +37 -0
- package/hooks/metrics-collector/index.cjs +37 -8
- package/hooks/pre-tool-guard/index.cjs +44 -0
- package/hooks/session-start/index.cjs +4 -10
- package/package.json +1 -1
- package/skills/adversary/SKILL.md +36 -3
- package/skills/adversary/references/cross-model-escalation.md +85 -0
- package/skills/adversary/references/reasoning-modes.md +95 -0
- package/skills/autopsy/SKILL.md +41 -0
- package/skills/ba/SKILL.md +44 -2
- package/skills/ba/references/ears-format.md +91 -0
- package/skills/brainstorm/SKILL.md +32 -7
- package/skills/cook/SKILL.md +8 -1
- package/skills/debug/SKILL.md +4 -2
- package/skills/deploy/SKILL.md +20 -1
- package/skills/deploy/references/observability.md +146 -0
- package/skills/graft/SKILL.md +24 -8
- package/skills/onboard/SKILL.md +45 -0
- package/skills/perf/SKILL.md +4 -1
- package/skills/review/SKILL.md +4 -2
|
@@ -0,0 +1,1254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comprehension HTML Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates a fully self-contained HTML dashboard from merged comprehension +
|
|
5
|
+
* governance + analytics + mesh data. No CDN, no external requests, no fonts
|
|
6
|
+
* loaded over the network — font stacks fall back to system fonts.
|
|
7
|
+
*
|
|
8
|
+
* XSS safety: the DATA object is embedded via JSON with `</` → `<\/` escaping
|
|
9
|
+
* so a project name containing `</script>` cannot break out of the script tag.
|
|
10
|
+
*
|
|
11
|
+
* Layout mirrors dashboard-mockup.html (Signal Teal palette, contour bg,
|
|
12
|
+
* Verdict hero, 5-tab nav, KPI row) but driven by real data.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { CLIENT_SCRIPT } from './comprehension-client.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Embed a value as safe inline JSON (escapes `</` so script injection is
|
|
19
|
+
* impossible even when the data contains `</script>`). Also escapes
|
|
20
|
+
* U+2028 (LS) and U+2029 (PS) which are valid JSON but illegal in JS
|
|
21
|
+
* string literals, preventing line-terminator injection.
|
|
22
|
+
* @param {unknown} value
|
|
23
|
+
* @returns {string}
|
|
24
|
+
*/
|
|
25
|
+
// Regex patterns for U+2028/U+2029 built via String.fromCharCode so linters
|
|
26
|
+
// (Biome/Prettier) cannot normalize the literal characters to spaces.
|
|
27
|
+
const _LINE_SEP_RE = new RegExp(String.fromCharCode(0x2028), 'g'); // U+2028 LINE SEPARATOR
|
|
28
|
+
const _PARA_SEP_RE = new RegExp(String.fromCharCode(0x2029), 'g'); // U+2029 PARAGRAPH SEPARATOR
|
|
29
|
+
function safeJson(value) {
|
|
30
|
+
return JSON.stringify(value)
|
|
31
|
+
.replace(/</g, '\\u003c')
|
|
32
|
+
.replace(_LINE_SEP_RE, '\\u2028')
|
|
33
|
+
.replace(_PARA_SEP_RE, '\\u2029');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Compute a Verdict health score (0-100) from the merged data object.
|
|
38
|
+
*
|
|
39
|
+
* Formula (each dimension contributes up to 25 pts):
|
|
40
|
+
* A. Comprehension depth — modules present → up to 25 pts
|
|
41
|
+
* B. Gate coverage — gate skills fired → up to 25 pts
|
|
42
|
+
* C. Compliance — met/total obligations ratio → up to 25 pts
|
|
43
|
+
* D. Analytics activity — sessions > 0 → up to 25 pts
|
|
44
|
+
*
|
|
45
|
+
* Returns null when there is no basis (all four dimensions are empty/zero)
|
|
46
|
+
* so the UI can show "—" rather than a fabricated number.
|
|
47
|
+
*
|
|
48
|
+
* @param {object} data
|
|
49
|
+
* @returns {{ score: number|null, basis: string[] }}
|
|
50
|
+
*/
|
|
51
|
+
export function computeVerdictScore(data) {
|
|
52
|
+
const basis = [];
|
|
53
|
+
let total = 0;
|
|
54
|
+
let hasBasis = false;
|
|
55
|
+
|
|
56
|
+
// A. Comprehension depth (0–25)
|
|
57
|
+
const modules = Array.isArray(data.modules) ? data.modules : [];
|
|
58
|
+
if (modules.length > 0) {
|
|
59
|
+
hasBasis = true;
|
|
60
|
+
basis.push('comprehension');
|
|
61
|
+
const pts = Math.min(25, Math.round((modules.length / 20) * 25));
|
|
62
|
+
total += pts;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// B. Gate coverage (0–25) — only count when at least one gate has actually fired
|
|
66
|
+
// (gates.length > 0 with zero fired must NOT contribute to hasBasis — no fabricated score)
|
|
67
|
+
const gates = Array.isArray(data.gates) ? data.gates : [];
|
|
68
|
+
const firedGates = gates.filter((g) => (g.fired || 0) > 0);
|
|
69
|
+
if (firedGates.length > 0) {
|
|
70
|
+
hasBasis = true;
|
|
71
|
+
basis.push('governance');
|
|
72
|
+
// Reward coverage: GATE_SKILLS has 8 skills, full coverage = 25 pts
|
|
73
|
+
const TOTAL_GATE_SKILLS = 8;
|
|
74
|
+
const pts = Math.min(25, Math.round((firedGates.length / TOTAL_GATE_SKILLS) * 25));
|
|
75
|
+
total += pts;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// C. Compliance (0–25) — met / (met + unknown + gap) obligations
|
|
79
|
+
const compliance = Array.isArray(data.compliance) ? data.compliance : [];
|
|
80
|
+
if (compliance.length > 0) {
|
|
81
|
+
hasBasis = true;
|
|
82
|
+
basis.push('compliance');
|
|
83
|
+
const met = compliance.filter((c) => c.status === 'met').length;
|
|
84
|
+
const pts = Math.round((met / compliance.length) * 25);
|
|
85
|
+
total += pts;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// D. Analytics activity (0–25) — non-zero sessions
|
|
89
|
+
const sessions = data.overview?.total_sessions ?? 0;
|
|
90
|
+
if (sessions > 0) {
|
|
91
|
+
hasBasis = true;
|
|
92
|
+
basis.push('analytics');
|
|
93
|
+
// Up to 25 pts; 20 sessions = full points
|
|
94
|
+
const pts = Math.min(25, Math.round((sessions / 20) * 25));
|
|
95
|
+
total += pts;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!hasBasis) return { score: null, basis: [] };
|
|
99
|
+
return { score: Math.min(100, total), basis };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Canonical list of all Rune core skills — single source of truth for the
|
|
104
|
+
* "installed skills" denominator used in the ROI panel and KPI counters.
|
|
105
|
+
* Update this list whenever a skill is added or removed from the Free tier.
|
|
106
|
+
*/
|
|
107
|
+
const ALL_KNOWN_SKILLS = [
|
|
108
|
+
'cook',
|
|
109
|
+
'plan',
|
|
110
|
+
'scout',
|
|
111
|
+
'brainstorm',
|
|
112
|
+
'design',
|
|
113
|
+
'skill-forge',
|
|
114
|
+
'debug',
|
|
115
|
+
'fix',
|
|
116
|
+
'test',
|
|
117
|
+
'review',
|
|
118
|
+
'db',
|
|
119
|
+
'sentinel',
|
|
120
|
+
'preflight',
|
|
121
|
+
'onboard',
|
|
122
|
+
'deploy',
|
|
123
|
+
'marketing',
|
|
124
|
+
'perf',
|
|
125
|
+
'autopsy',
|
|
126
|
+
'safeguard',
|
|
127
|
+
'surgeon',
|
|
128
|
+
'audit',
|
|
129
|
+
'incident',
|
|
130
|
+
'review-intake',
|
|
131
|
+
'logic-guardian',
|
|
132
|
+
'ba',
|
|
133
|
+
'docs',
|
|
134
|
+
'mcp-builder',
|
|
135
|
+
'adversary',
|
|
136
|
+
'retro',
|
|
137
|
+
'graft',
|
|
138
|
+
'improve-architecture',
|
|
139
|
+
'research',
|
|
140
|
+
'docs-seeker',
|
|
141
|
+
'trend-scout',
|
|
142
|
+
'problem-solver',
|
|
143
|
+
'sequential-thinking',
|
|
144
|
+
'verification',
|
|
145
|
+
'hallucination-guard',
|
|
146
|
+
'completion-gate',
|
|
147
|
+
'constraint-check',
|
|
148
|
+
'sast',
|
|
149
|
+
'integrity-check',
|
|
150
|
+
'context-engine',
|
|
151
|
+
'context-pack',
|
|
152
|
+
'journal',
|
|
153
|
+
'session-bridge',
|
|
154
|
+
'neural-memory',
|
|
155
|
+
'worktree',
|
|
156
|
+
'watchdog',
|
|
157
|
+
'scope-guard',
|
|
158
|
+
'browser-pilot',
|
|
159
|
+
'asset-creator',
|
|
160
|
+
'video-creator',
|
|
161
|
+
'slides',
|
|
162
|
+
'dependency-doctor',
|
|
163
|
+
'git',
|
|
164
|
+
'doc-processor',
|
|
165
|
+
'sentinel-env',
|
|
166
|
+
'team',
|
|
167
|
+
'launch',
|
|
168
|
+
'rescue',
|
|
169
|
+
'scaffold',
|
|
170
|
+
'skill-router',
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Generate the fully self-contained comprehension dashboard HTML string.
|
|
175
|
+
*
|
|
176
|
+
* @param {object} data - Merged data from comprehension.json + governance.json +
|
|
177
|
+
* getAllAnalytics() + collectGraphData(). All fields optional — degrades gracefully.
|
|
178
|
+
* @returns {string} Complete HTML document (no external requests).
|
|
179
|
+
*/
|
|
180
|
+
export function generateComprehensionHTML(data) {
|
|
181
|
+
// Normalise and provide safe defaults for every field we touch
|
|
182
|
+
const d = Object.assign(
|
|
183
|
+
{
|
|
184
|
+
project: '',
|
|
185
|
+
generated_at: null,
|
|
186
|
+
health_score: null,
|
|
187
|
+
modules: [],
|
|
188
|
+
edges: [],
|
|
189
|
+
layers: [],
|
|
190
|
+
domains: [],
|
|
191
|
+
// governance
|
|
192
|
+
gates: [],
|
|
193
|
+
signals: [],
|
|
194
|
+
compliance: [],
|
|
195
|
+
decisions: [],
|
|
196
|
+
window_days: 30,
|
|
197
|
+
// analytics
|
|
198
|
+
overview: {},
|
|
199
|
+
skillFrequency: [],
|
|
200
|
+
modelDistribution: [],
|
|
201
|
+
skillHeatmap: { heatmap: [], dates: [], maxCount: 1 },
|
|
202
|
+
sessionTimeline: [],
|
|
203
|
+
skillChains: [],
|
|
204
|
+
totalInstalledSkills: ALL_KNOWN_SKILLS.length, // single source of truth
|
|
205
|
+
// mesh
|
|
206
|
+
skillMesh: { nodes: [], edges: [] },
|
|
207
|
+
// Phase 5b — tier gating (defaults to 'free' when not supplied by cmdDashboard)
|
|
208
|
+
tier: 'free',
|
|
209
|
+
hasPro: false,
|
|
210
|
+
hasBusiness: false,
|
|
211
|
+
},
|
|
212
|
+
data,
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
// Verdict score — prefer comprehension.json health_score if present, else compute
|
|
216
|
+
// Clamp + NaN-guard the override so bad JSON values never render as >100 or NaN
|
|
217
|
+
const { score: computedScore, basis: scoreBasis } = computeVerdictScore(d);
|
|
218
|
+
const verdictScore = Number.isFinite(d.health_score) ? Math.max(0, Math.min(100, d.health_score)) : computedScore;
|
|
219
|
+
|
|
220
|
+
// Plain-language verdict sentence (escHtml applied to d.project — flows into <h1> via verdictLine)
|
|
221
|
+
const projectLabel = d.project ? `Project "${escHtml(d.project)}"` : 'Your project';
|
|
222
|
+
const firedCount = (d.gates || []).filter((g) => (g.fired || 0) > 0).length;
|
|
223
|
+
const complianceTotal = (d.compliance || []).length;
|
|
224
|
+
const complianceMet = (d.compliance || []).filter((c) => c.status === 'met').length;
|
|
225
|
+
const skillsActive = d.overview?.total_skill_invocations ? (d.skillFrequency?.length ?? 0) : 0;
|
|
226
|
+
const totalSkills = ALL_KNOWN_SKILLS.length; // single source of truth — ALL_KNOWN_SKILLS array
|
|
227
|
+
|
|
228
|
+
let verdictLine;
|
|
229
|
+
if (verdictScore === null) {
|
|
230
|
+
verdictLine = `${projectLabel} — no session data yet. Run Rune skills to start collecting metrics.`;
|
|
231
|
+
} else if (verdictScore >= 75) {
|
|
232
|
+
verdictLine = `${projectLabel} is <b>healthy</b> — ${firedCount} gate${firedCount !== 1 ? 's' : ''} active${complianceMet > 0 ? `, ${complianceMet}/${complianceTotal} obligations met` : ''}.`;
|
|
233
|
+
} else if (verdictScore >= 40) {
|
|
234
|
+
verdictLine =
|
|
235
|
+
firedCount > 0
|
|
236
|
+
? `${projectLabel} is <b>developing</b> — gates active: ${firedCount}. Grow coverage to improve score.`
|
|
237
|
+
: `${projectLabel} is <b>developing</b> — no gate fires recorded yet. Grow coverage to improve score.`;
|
|
238
|
+
} else {
|
|
239
|
+
verdictLine = `${projectLabel} needs <b>attention</b> — limited gate coverage and session data so far.`;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Delta (compare health_score vs computed score as a rough diff, show only when both exist)
|
|
243
|
+
let deltaHtml = '';
|
|
244
|
+
if (typeof d.health_score === 'number' && computedScore !== null && d.health_score !== computedScore) {
|
|
245
|
+
const diff = d.health_score - computedScore;
|
|
246
|
+
const cls = diff >= 0 ? 'up' : 'dn';
|
|
247
|
+
const arrow = diff >= 0 ? '▲' : '▼';
|
|
248
|
+
deltaHtml = `<span class="delta ${cls}">${arrow} ${diff >= 0 ? '+' : ''}${diff}</span>`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const generatedDate = d.generated_at
|
|
252
|
+
? new Date(d.generated_at).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' })
|
|
253
|
+
: new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
|
|
254
|
+
|
|
255
|
+
// KPI values (safe — never show NaN)
|
|
256
|
+
const gatesFired = (d.gates || []).reduce((s, g) => s + (g.fired || 0), 0);
|
|
257
|
+
const compliancePct = complianceTotal > 0 ? Math.round((complianceMet / complianceTotal) * 100) : null;
|
|
258
|
+
const sessions = d.overview?.total_sessions ?? 0;
|
|
259
|
+
|
|
260
|
+
// Profile: initial persona seeded from d.profile (read by cmdDashboard from .rune/dashboard-profile.json).
|
|
261
|
+
// Clamp persona to known values so a tampered dashboard-profile.json can't produce an incoherent view.
|
|
262
|
+
// 'mylens' is a Pro-only persona — only valid when hasPro is true.
|
|
263
|
+
const _VALID_PERSONAS = d.hasPro ? ['exec', 'compliance', 'eng', 'mylens'] : ['exec', 'compliance', 'eng'];
|
|
264
|
+
const initialPersona = _VALID_PERSONAS.includes(d.profile?.persona) ? d.profile.persona : 'exec';
|
|
265
|
+
const initialPinned = Array.isArray(d.profile?.pinnedConcerns) ? d.profile.pinnedConcerns : [];
|
|
266
|
+
|
|
267
|
+
// Embed data safely
|
|
268
|
+
const dataJson = safeJson({
|
|
269
|
+
project: d.project,
|
|
270
|
+
generated_at: d.generated_at,
|
|
271
|
+
health_score: d.health_score,
|
|
272
|
+
verdictScore,
|
|
273
|
+
scoreBasis,
|
|
274
|
+
modules: d.modules,
|
|
275
|
+
edges: d.edges,
|
|
276
|
+
layers: d.layers,
|
|
277
|
+
domains: d.domains,
|
|
278
|
+
gates: d.gates,
|
|
279
|
+
signals: d.signals,
|
|
280
|
+
compliance: d.compliance,
|
|
281
|
+
decisions: d.decisions,
|
|
282
|
+
overview: d.overview,
|
|
283
|
+
skillFrequency: d.skillFrequency,
|
|
284
|
+
modelDistribution: d.modelDistribution,
|
|
285
|
+
skillMesh: d.skillMesh,
|
|
286
|
+
window_days: d.window_days,
|
|
287
|
+
initialPersona,
|
|
288
|
+
initialPinned,
|
|
289
|
+
// Phase 5a
|
|
290
|
+
skillHeatmap: d.skillHeatmap || { heatmap: [], dates: [], maxCount: 1 },
|
|
291
|
+
sessionTimeline: d.sessionTimeline || [],
|
|
292
|
+
skillChains: d.skillChains || [],
|
|
293
|
+
totalInstalledSkills: ALL_KNOWN_SKILLS.length, // single source of truth
|
|
294
|
+
// Phase 5b — tier gating
|
|
295
|
+
tier: d.tier,
|
|
296
|
+
hasPro: d.hasPro,
|
|
297
|
+
hasBusiness: d.hasBusiness,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
return `<!DOCTYPE html>
|
|
301
|
+
<html lang="en">
|
|
302
|
+
<head>
|
|
303
|
+
<meta charset="UTF-8">
|
|
304
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
305
|
+
<title>Rune Dashboard${d.project ? ` — ${escHtml(d.project)}` : ''}</title>
|
|
306
|
+
<style>
|
|
307
|
+
/* ── Reset ── */
|
|
308
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
309
|
+
|
|
310
|
+
/* ── Design tokens (Signal Teal, dark-first) ── */
|
|
311
|
+
:root{
|
|
312
|
+
--bg-base:#0b1120;--bg-card:#111a2e;--bg-elevated:#1b2740;--bg-graph:#080d1a;
|
|
313
|
+
--accent:#2dd4bf;--accent-hover:#5eead4;--accent-deep:#0d9488;--accent-on:#04201b;
|
|
314
|
+
--accent-glow:0 0 24px rgba(45,212,191,.35);
|
|
315
|
+
--text-primary:#f1f5f9;--text-secondary:#94a3b8;
|
|
316
|
+
--text-tertiary:#64748b;--text-disabled:#475569;
|
|
317
|
+
--border:#1e293b;--border-strong:#334155;--border-accent:rgba(45,212,191,.45);
|
|
318
|
+
--pass:#10b981;--warn:#f59e0b;--fail:#ef4444;--info:#38bdf8;--stale:#64748b;
|
|
319
|
+
--c-code:#2dd4bf;--c-service:#60a5fa;--c-data:#a78bfa;
|
|
320
|
+
--c-domain:#fbbf24;--c-docs:#f472b6;--c-infra:#38bdf8;--c-concept:#34d399;
|
|
321
|
+
--r-card:12px;--r-el:8px;--r-pill:9999px;
|
|
322
|
+
--shadow-sm:0 1px 2px rgba(0,0,0,.3);
|
|
323
|
+
--shadow-md:0 4px 12px rgba(0,0,0,.35);
|
|
324
|
+
--shadow-lg:0 12px 32px rgba(0,0,0,.45);
|
|
325
|
+
--font-display:"Space Grotesk",ui-sans-serif,system-ui,sans-serif;
|
|
326
|
+
--font-body:"Inter",ui-sans-serif,system-ui,sans-serif;
|
|
327
|
+
--font-mono:"JetBrains Mono",ui-monospace,Consolas,monospace;
|
|
328
|
+
--t-fast:150ms ease;--t-normal:250ms ease;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/* ── Base ── */
|
|
332
|
+
body{
|
|
333
|
+
font-family:var(--font-body);color:var(--text-primary);
|
|
334
|
+
background:var(--bg-base);min-height:100vh;
|
|
335
|
+
position:relative;overflow-x:hidden;padding-bottom:60px;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/* ── Background: glow blooms (always on) ── */
|
|
339
|
+
body::after{
|
|
340
|
+
content:"";position:fixed;inset:0;pointer-events:none;z-index:0;
|
|
341
|
+
background:
|
|
342
|
+
radial-gradient(900px 600px at 12% -5%, rgba(45,212,191,.10), transparent 60%),
|
|
343
|
+
radial-gradient(800px 500px at 100% 110%, rgba(56,130,246,.08), transparent 55%);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/* ── Background: contour texture (LOCKED) ── */
|
|
347
|
+
body::before{
|
|
348
|
+
content:"";position:fixed;inset:0;pointer-events:none;z-index:0;opacity:.9;
|
|
349
|
+
background-image:
|
|
350
|
+
repeating-radial-gradient(circle at 20% 30%, transparent 0 38px, rgba(148,163,184,.07) 38px 39px),
|
|
351
|
+
repeating-radial-gradient(circle at 85% 80%, transparent 0 46px, rgba(45,212,191,.06) 46px 47px);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/* ── Layout ── */
|
|
355
|
+
.wrap{position:relative;z-index:1;max-width:1180px;margin:0 auto;padding:20px 28px}
|
|
356
|
+
|
|
357
|
+
/* ── Header ── */
|
|
358
|
+
header.top{
|
|
359
|
+
display:flex;align-items:center;gap:18px;padding:10px 0 18px;
|
|
360
|
+
border-bottom:1px solid var(--border);flex-wrap:wrap;
|
|
361
|
+
}
|
|
362
|
+
.logo{
|
|
363
|
+
display:flex;align-items:center;gap:9px;
|
|
364
|
+
font-family:var(--font-display);font-weight:700;font-size:17px;
|
|
365
|
+
color:var(--text-primary);text-decoration:none;
|
|
366
|
+
}
|
|
367
|
+
.logo .dot{
|
|
368
|
+
width:13px;height:13px;border-radius:3px;
|
|
369
|
+
background:var(--accent);box-shadow:var(--accent-glow);
|
|
370
|
+
flex-shrink:0;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/* ── Tabs ── */
|
|
374
|
+
nav.tabs{
|
|
375
|
+
display:flex;gap:2px;background:var(--bg-card);padding:4px;
|
|
376
|
+
border-radius:var(--r-pill);border:1px solid var(--border);
|
|
377
|
+
}
|
|
378
|
+
.tab{
|
|
379
|
+
font:500 13px var(--font-body);color:var(--text-secondary);
|
|
380
|
+
padding:7px 15px;border-radius:var(--r-pill);cursor:pointer;
|
|
381
|
+
border:none;background:transparent;transition:all var(--t-fast);
|
|
382
|
+
min-height:44px;
|
|
383
|
+
}
|
|
384
|
+
.tab.active{background:var(--bg-elevated);color:var(--accent);}
|
|
385
|
+
.tab:hover:not(.active){color:var(--text-primary);}
|
|
386
|
+
.tab:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}
|
|
387
|
+
|
|
388
|
+
/* ── Persona toggle ── */
|
|
389
|
+
.persona{
|
|
390
|
+
margin-left:auto;display:flex;gap:2px;background:var(--bg-card);
|
|
391
|
+
padding:4px;border-radius:var(--r-el);border:1px solid var(--border);
|
|
392
|
+
}
|
|
393
|
+
.persona button{
|
|
394
|
+
font:500 12px var(--font-body);color:var(--text-secondary);
|
|
395
|
+
padding:6px 12px;border:none;background:transparent;
|
|
396
|
+
border-radius:6px;cursor:pointer;transition:all var(--t-fast);
|
|
397
|
+
min-height:44px;
|
|
398
|
+
}
|
|
399
|
+
.persona button.on{background:var(--bg-elevated);color:var(--text-primary);}
|
|
400
|
+
.persona button:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}
|
|
401
|
+
|
|
402
|
+
/* ── Tab panels ── */
|
|
403
|
+
.tab-panel{display:none;}
|
|
404
|
+
.tab-panel.active{display:block;}
|
|
405
|
+
|
|
406
|
+
/* ── Verdict hero ── */
|
|
407
|
+
.hero{
|
|
408
|
+
position:relative;margin-top:22px;padding:30px 34px;
|
|
409
|
+
border-radius:var(--r-card);background:var(--bg-card);
|
|
410
|
+
border:1px solid var(--border);overflow:hidden;
|
|
411
|
+
}
|
|
412
|
+
.hero::before{
|
|
413
|
+
content:"";position:absolute;top:0;left:0;right:0;height:1px;
|
|
414
|
+
background:linear-gradient(90deg,var(--accent),transparent 60%);
|
|
415
|
+
}
|
|
416
|
+
.hero::after{
|
|
417
|
+
content:"";position:absolute;top:-120px;left:-80px;
|
|
418
|
+
width:380px;height:380px;
|
|
419
|
+
background:radial-gradient(circle, rgba(45,212,191,.16),transparent 70%);
|
|
420
|
+
pointer-events:none;
|
|
421
|
+
}
|
|
422
|
+
.hero .live{
|
|
423
|
+
display:inline-flex;align-items:center;gap:7px;font-size:11px;
|
|
424
|
+
letter-spacing:.08em;text-transform:uppercase;
|
|
425
|
+
color:var(--text-tertiary);margin-bottom:12px;
|
|
426
|
+
}
|
|
427
|
+
.hero .live i{
|
|
428
|
+
width:8px;height:8px;border-radius:50%;
|
|
429
|
+
background:var(--accent);box-shadow:var(--accent-glow);
|
|
430
|
+
animation:pulse 2s infinite;
|
|
431
|
+
}
|
|
432
|
+
.hero h1{
|
|
433
|
+
font-family:var(--font-display);font-weight:500;font-size:23px;
|
|
434
|
+
line-height:1.35;max-width:680px;color:var(--text-primary);
|
|
435
|
+
}
|
|
436
|
+
.hero h1 b{font-weight:700;color:var(--accent);}
|
|
437
|
+
.scoreRow{
|
|
438
|
+
display:flex;align-items:flex-end;gap:18px;margin-top:18px;
|
|
439
|
+
position:relative;z-index:1;flex-wrap:wrap;
|
|
440
|
+
}
|
|
441
|
+
.score{
|
|
442
|
+
font-family:var(--font-mono);font-weight:700;font-size:76px;
|
|
443
|
+
line-height:.9;color:#f8fafc;
|
|
444
|
+
text-shadow:0 0 28px rgba(45,212,191,.4);
|
|
445
|
+
}
|
|
446
|
+
.score small{font-size:26px;color:var(--text-tertiary);}
|
|
447
|
+
.delta{
|
|
448
|
+
font-family:var(--font-mono);font-weight:700;font-size:15px;
|
|
449
|
+
padding:5px 11px;border-radius:var(--r-pill);margin-bottom:8px;
|
|
450
|
+
}
|
|
451
|
+
.delta.up{color:var(--pass);background:rgba(16,185,129,.13);}
|
|
452
|
+
.delta.dn{color:var(--fail);background:rgba(239,68,68,.13);}
|
|
453
|
+
.meta{margin-bottom:12px;color:var(--text-secondary);font-size:13px;}
|
|
454
|
+
.meta b{color:var(--text-primary);font-family:var(--font-mono);}
|
|
455
|
+
|
|
456
|
+
/* ── KPI row ── */
|
|
457
|
+
.kpis{
|
|
458
|
+
display:grid;grid-template-columns:repeat(4,1fr);
|
|
459
|
+
gap:16px;margin-top:18px;
|
|
460
|
+
}
|
|
461
|
+
.kpi{
|
|
462
|
+
background:var(--bg-card);border:1px solid var(--border);
|
|
463
|
+
border-radius:var(--r-card);padding:18px 20px;
|
|
464
|
+
transition:transform var(--t-normal),box-shadow var(--t-normal),border-color var(--t-normal);
|
|
465
|
+
}
|
|
466
|
+
.kpi:hover{
|
|
467
|
+
transform:translateY(-2px);box-shadow:var(--shadow-lg);
|
|
468
|
+
border-color:var(--border-strong);
|
|
469
|
+
}
|
|
470
|
+
.k-lbl{
|
|
471
|
+
font-size:11px;text-transform:uppercase;letter-spacing:.05em;
|
|
472
|
+
color:var(--text-tertiary);
|
|
473
|
+
}
|
|
474
|
+
.k-val{
|
|
475
|
+
font-family:var(--font-mono);font-weight:700;font-size:30px;
|
|
476
|
+
margin-top:8px;color:var(--text-primary);
|
|
477
|
+
}
|
|
478
|
+
.k-sub{font-size:12px;margin-top:6px;color:var(--text-secondary);}
|
|
479
|
+
.k-sub.up{color:var(--pass);}
|
|
480
|
+
.k-sub.warn{color:var(--warn);}
|
|
481
|
+
|
|
482
|
+
/* ── Two-column layout ── */
|
|
483
|
+
.cols{display:grid;grid-template-columns:1.15fr .85fr;gap:16px;margin-top:16px;}
|
|
484
|
+
.panel{
|
|
485
|
+
background:var(--bg-card);border:1px solid var(--border);
|
|
486
|
+
border-radius:var(--r-card);padding:20px;
|
|
487
|
+
}
|
|
488
|
+
.panel h3{
|
|
489
|
+
font:600 14px var(--font-body);margin-bottom:14px;
|
|
490
|
+
display:flex;align-items:center;gap:8px;color:var(--text-primary);
|
|
491
|
+
}
|
|
492
|
+
.panel h3 .pill{
|
|
493
|
+
font:500 10px var(--font-body);text-transform:uppercase;letter-spacing:.05em;
|
|
494
|
+
color:var(--accent);background:rgba(45,212,191,.12);
|
|
495
|
+
padding:2px 8px;border-radius:var(--r-pill);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/* ── Tables ── */
|
|
499
|
+
table{width:100%;border-collapse:collapse;font-size:13px;}
|
|
500
|
+
th{
|
|
501
|
+
text-align:left;font:500 11px var(--font-body);text-transform:uppercase;
|
|
502
|
+
letter-spacing:.05em;color:var(--text-tertiary);
|
|
503
|
+
padding:8px 6px;border-bottom:1px solid var(--border);
|
|
504
|
+
}
|
|
505
|
+
td{padding:11px 6px;border-bottom:1px solid var(--border);color:var(--text-secondary);}
|
|
506
|
+
td.mono{font-family:var(--font-mono);color:var(--text-primary);}
|
|
507
|
+
tr:last-child td{border-bottom:none;}
|
|
508
|
+
|
|
509
|
+
/* ── Status badges ── */
|
|
510
|
+
.st{display:inline-flex;align-items:center;gap:6px;font-weight:600;font-size:12px;}
|
|
511
|
+
.st.pass{color:var(--pass);}
|
|
512
|
+
.st.warn{color:var(--warn);}
|
|
513
|
+
.st.fail{color:var(--fail);}
|
|
514
|
+
.st.info{color:var(--info);}
|
|
515
|
+
|
|
516
|
+
/* ── Graph panel ── */
|
|
517
|
+
.graph-wrap{
|
|
518
|
+
background:var(--bg-graph);border-radius:var(--r-el);
|
|
519
|
+
height:280px;position:relative;overflow:hidden;
|
|
520
|
+
}
|
|
521
|
+
.legend{
|
|
522
|
+
display:flex;gap:14px;flex-wrap:wrap;margin-top:12px;
|
|
523
|
+
font-size:11px;color:var(--text-secondary);
|
|
524
|
+
}
|
|
525
|
+
.legend i{
|
|
526
|
+
display:inline-block;width:9px;height:9px;border-radius:50%;
|
|
527
|
+
margin-right:5px;vertical-align:middle;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/* ── Empty state ── */
|
|
531
|
+
.empty{
|
|
532
|
+
text-align:center;padding:60px 20px;color:var(--text-tertiary);
|
|
533
|
+
}
|
|
534
|
+
.empty h4{
|
|
535
|
+
font-family:var(--font-display);font-size:18px;margin-bottom:10px;
|
|
536
|
+
color:var(--text-secondary);
|
|
537
|
+
}
|
|
538
|
+
.empty p{max-width:440px;margin:0 auto;line-height:1.7;font-size:13px;}
|
|
539
|
+
|
|
540
|
+
/* ── Section gap ── */
|
|
541
|
+
.section{margin-top:20px;}
|
|
542
|
+
|
|
543
|
+
/* ── Skill bar chart ── */
|
|
544
|
+
.skill-bar-row{
|
|
545
|
+
display:flex;align-items:center;gap:8px;margin-bottom:6px;
|
|
546
|
+
}
|
|
547
|
+
.skill-bar-name{
|
|
548
|
+
font-family:var(--font-mono);font-size:11px;color:var(--text-secondary);
|
|
549
|
+
width:110px;flex-shrink:0;text-align:right;
|
|
550
|
+
overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
|
|
551
|
+
}
|
|
552
|
+
.skill-bar-track{
|
|
553
|
+
flex:1;height:8px;background:var(--bg-elevated);border-radius:4px;overflow:hidden;
|
|
554
|
+
}
|
|
555
|
+
.skill-bar-fill{
|
|
556
|
+
height:100%;background:var(--accent);border-radius:4px;opacity:.8;
|
|
557
|
+
transition:width 600ms cubic-bezier(.34,1.56,.64,1);
|
|
558
|
+
}
|
|
559
|
+
.skill-bar-count{
|
|
560
|
+
font-family:var(--font-mono);font-size:11px;color:var(--text-tertiary);
|
|
561
|
+
width:30px;text-align:right;flex-shrink:0;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/* ── Compliance list ── */
|
|
565
|
+
.compliance-item{
|
|
566
|
+
padding:10px 0;border-bottom:1px solid var(--border);
|
|
567
|
+
font-size:12px;color:var(--text-secondary);display:flex;gap:10px;align-items:flex-start;
|
|
568
|
+
}
|
|
569
|
+
.compliance-item:last-child{border-bottom:none;}
|
|
570
|
+
.compliance-pack{
|
|
571
|
+
font-family:var(--font-mono);font-size:10px;color:var(--text-tertiary);
|
|
572
|
+
flex-shrink:0;width:130px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
|
|
573
|
+
}
|
|
574
|
+
.compliance-text{flex:1;line-height:1.5;}
|
|
575
|
+
|
|
576
|
+
/* ── Govern tab additions (Phase 3) ── */
|
|
577
|
+
.govern-scorecard{
|
|
578
|
+
display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:16px;
|
|
579
|
+
}
|
|
580
|
+
.govern-scorecard .kpi{margin-top:0;}
|
|
581
|
+
.govern-3col{display:grid;grid-template-columns:1.1fr .9fr;gap:16px;margin-top:0;}
|
|
582
|
+
.govern-full{margin-top:16px;}
|
|
583
|
+
.govern-provenance{margin-top:16px;}
|
|
584
|
+
/* Coverage map */
|
|
585
|
+
.cov-pack{margin-bottom:14px;}
|
|
586
|
+
.cov-pack-header{
|
|
587
|
+
display:flex;align-items:center;justify-content:space-between;
|
|
588
|
+
margin-bottom:5px;font-size:12px;
|
|
589
|
+
}
|
|
590
|
+
.cov-pack-name{
|
|
591
|
+
font-family:var(--font-mono);font-size:11px;color:var(--text-secondary);
|
|
592
|
+
overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:160px;
|
|
593
|
+
}
|
|
594
|
+
.cov-bar-track{height:6px;background:var(--bg-elevated);border-radius:3px;overflow:hidden;}
|
|
595
|
+
.cov-bar-fill{height:100%;border-radius:3px;background:var(--pass);}
|
|
596
|
+
.cov-items{margin-top:6px;}
|
|
597
|
+
.cov-item{
|
|
598
|
+
display:flex;gap:8px;align-items:flex-start;
|
|
599
|
+
padding:5px 0;border-bottom:1px solid var(--border);font-size:11px;
|
|
600
|
+
}
|
|
601
|
+
.cov-item:last-child{border-bottom:none;}
|
|
602
|
+
.cov-item-text{flex:1;color:var(--text-secondary);line-height:1.45;}
|
|
603
|
+
/* Pinned pack highlight */
|
|
604
|
+
.cov-pack.pinned>.cov-pack-header>.cov-pack-name{color:var(--accent);}
|
|
605
|
+
.cov-pack.pinned{
|
|
606
|
+
border-left:2px solid var(--accent-deep);padding-left:8px;
|
|
607
|
+
}
|
|
608
|
+
/* Blocked-events sub-table */
|
|
609
|
+
.blocked-events{margin-top:8px;}
|
|
610
|
+
.blocked-events summary{
|
|
611
|
+
font-size:11px;color:var(--text-tertiary);cursor:pointer;
|
|
612
|
+
padding:4px 0;list-style:none;
|
|
613
|
+
}
|
|
614
|
+
.blocked-events summary:hover{color:var(--text-secondary);}
|
|
615
|
+
.blocked-events table{margin-top:6px;}
|
|
616
|
+
/* Persona-gated visibility */
|
|
617
|
+
.persona-exec-hide{} /* default: show */
|
|
618
|
+
.persona-compliance-hide{}
|
|
619
|
+
.persona-eng-hide{}
|
|
620
|
+
/* Applied dynamically via JS */
|
|
621
|
+
[data-persona-hide]{display:none!important;}
|
|
622
|
+
/* Blocks-caught badge */
|
|
623
|
+
.blocks-badge{
|
|
624
|
+
display:inline-flex;align-items:center;gap:4px;
|
|
625
|
+
font-family:var(--font-mono);font-size:11px;font-weight:700;
|
|
626
|
+
color:var(--fail);background:rgba(239,68,68,.10);
|
|
627
|
+
padding:2px 8px;border-radius:var(--r-pill);
|
|
628
|
+
}
|
|
629
|
+
/* Profile actions */
|
|
630
|
+
.profile-bar{
|
|
631
|
+
display:flex;align-items:center;gap:8px;margin-bottom:14px;flex-wrap:wrap;
|
|
632
|
+
}
|
|
633
|
+
.profile-bar button{
|
|
634
|
+
font:500 11px var(--font-body);padding:5px 12px;border-radius:var(--r-el);
|
|
635
|
+
border:1px solid var(--border-strong);background:transparent;
|
|
636
|
+
color:var(--text-secondary);cursor:pointer;transition:all var(--t-fast);
|
|
637
|
+
}
|
|
638
|
+
.profile-bar button:hover{background:var(--bg-elevated);color:var(--text-primary);border-color:var(--accent);}
|
|
639
|
+
.profile-bar button:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}
|
|
640
|
+
.profile-bar .profile-status{font-size:11px;color:var(--text-tertiary);}
|
|
641
|
+
/* Govern-full multi-section layout */
|
|
642
|
+
.govern-sections{display:flex;flex-direction:column;gap:16px;margin-top:16px;}
|
|
643
|
+
|
|
644
|
+
/* ── Understand graph canvas ── */
|
|
645
|
+
#understand-canvas{width:100%;height:100%;display:block;cursor:crosshair;}
|
|
646
|
+
|
|
647
|
+
/* ── Understand tab: Phase 4 additions ── */
|
|
648
|
+
/* Filter panel */
|
|
649
|
+
.u-layout{display:grid;grid-template-columns:200px 1fr;gap:16px;margin-top:12px;}
|
|
650
|
+
.u-filters{
|
|
651
|
+
background:var(--bg-card);border:1px solid var(--border);
|
|
652
|
+
border-radius:var(--r-card);padding:14px;font-size:12px;
|
|
653
|
+
display:flex;flex-direction:column;gap:14px;
|
|
654
|
+
}
|
|
655
|
+
.u-filters h4{
|
|
656
|
+
font:600 11px var(--font-body);text-transform:uppercase;letter-spacing:.06em;
|
|
657
|
+
color:var(--text-tertiary);
|
|
658
|
+
}
|
|
659
|
+
.u-filter-group{display:flex;flex-direction:column;gap:5px;}
|
|
660
|
+
.u-filter-group-header{
|
|
661
|
+
display:flex;align-items:center;justify-content:space-between;
|
|
662
|
+
margin-bottom:4px;
|
|
663
|
+
}
|
|
664
|
+
.u-filter-group-label{
|
|
665
|
+
font:600 10px var(--font-body);text-transform:uppercase;letter-spacing:.06em;
|
|
666
|
+
color:var(--text-tertiary);
|
|
667
|
+
}
|
|
668
|
+
.u-quick-btns{display:flex;gap:4px;}
|
|
669
|
+
.u-quick-btn{
|
|
670
|
+
font:500 9px var(--font-body);padding:2px 6px;border-radius:3px;
|
|
671
|
+
border:1px solid var(--border-strong);background:transparent;
|
|
672
|
+
color:var(--text-tertiary);cursor:pointer;
|
|
673
|
+
}
|
|
674
|
+
.u-quick-btn:hover{color:var(--text-primary);border-color:var(--accent);}
|
|
675
|
+
.u-quick-btn:focus-visible{outline:2px solid var(--accent);outline-offset:1px;}
|
|
676
|
+
.u-check-row{
|
|
677
|
+
display:flex;align-items:center;gap:6px;
|
|
678
|
+
color:var(--text-secondary);cursor:pointer;
|
|
679
|
+
}
|
|
680
|
+
.u-check-row input[type=checkbox]{accent-color:var(--accent);cursor:pointer;}
|
|
681
|
+
.u-check-label{font-size:11px;}
|
|
682
|
+
.u-badge{
|
|
683
|
+
display:inline-block;min-width:16px;text-align:center;
|
|
684
|
+
font-family:var(--font-mono);font-size:9px;
|
|
685
|
+
background:var(--bg-elevated);border-radius:var(--r-pill);
|
|
686
|
+
padding:1px 4px;color:var(--text-tertiary);margin-left:auto;
|
|
687
|
+
}
|
|
688
|
+
/* Main panel */
|
|
689
|
+
.u-main{display:flex;flex-direction:column;gap:12px;}
|
|
690
|
+
/* Graph toolbar */
|
|
691
|
+
.u-toolbar{
|
|
692
|
+
display:flex;align-items:center;gap:8px;flex-wrap:wrap;
|
|
693
|
+
}
|
|
694
|
+
.u-search-wrap{flex:1;min-width:160px;position:relative;}
|
|
695
|
+
.u-search{
|
|
696
|
+
width:100%;padding:7px 10px 7px 32px;
|
|
697
|
+
background:var(--bg-elevated);border:1px solid var(--border);
|
|
698
|
+
border-radius:var(--r-el);color:var(--text-primary);font-size:12px;
|
|
699
|
+
font-family:var(--font-body);
|
|
700
|
+
}
|
|
701
|
+
.u-search::placeholder{color:var(--text-tertiary);}
|
|
702
|
+
.u-search:focus{outline:none;border-color:var(--accent);}
|
|
703
|
+
.u-search-icon{
|
|
704
|
+
position:absolute;left:9px;top:50%;transform:translateY(-50%);
|
|
705
|
+
color:var(--text-tertiary);font-size:12px;pointer-events:none;
|
|
706
|
+
}
|
|
707
|
+
.u-toggle-btn{
|
|
708
|
+
font:500 11px var(--font-body);padding:7px 12px;
|
|
709
|
+
border:1px solid var(--border-strong);border-radius:var(--r-el);
|
|
710
|
+
background:transparent;color:var(--text-secondary);cursor:pointer;
|
|
711
|
+
transition:all var(--t-fast);white-space:nowrap;
|
|
712
|
+
}
|
|
713
|
+
.u-toggle-btn:hover{background:var(--bg-elevated);color:var(--text-primary);}
|
|
714
|
+
.u-toggle-btn.active{background:rgba(45,212,191,.12);border-color:var(--accent);color:var(--accent);}
|
|
715
|
+
.u-toggle-btn:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}
|
|
716
|
+
/* Export buttons */
|
|
717
|
+
.u-export-group{display:flex;gap:6px;}
|
|
718
|
+
.u-export-btn{
|
|
719
|
+
font:500 10px var(--font-body);padding:6px 10px;
|
|
720
|
+
border:1px solid var(--border-strong);border-radius:var(--r-el);
|
|
721
|
+
background:transparent;color:var(--text-tertiary);cursor:pointer;
|
|
722
|
+
transition:all var(--t-fast);
|
|
723
|
+
}
|
|
724
|
+
.u-export-btn:hover{background:var(--bg-elevated);color:var(--text-primary);}
|
|
725
|
+
.u-export-btn:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}
|
|
726
|
+
/* Graph area (canvas) */
|
|
727
|
+
.u-graph-wrap{
|
|
728
|
+
background:var(--bg-graph);border-radius:var(--r-el);
|
|
729
|
+
height:320px;position:relative;overflow:hidden;
|
|
730
|
+
}
|
|
731
|
+
/* Node inspector side panel */
|
|
732
|
+
.u-inspector{
|
|
733
|
+
background:var(--bg-card);border:1px solid var(--border);
|
|
734
|
+
border-radius:var(--r-card);padding:14px;font-size:12px;display:none;
|
|
735
|
+
}
|
|
736
|
+
.u-inspector.visible{display:block;}
|
|
737
|
+
.u-inspector h4{
|
|
738
|
+
font:600 13px var(--font-body);color:var(--text-primary);margin-bottom:10px;
|
|
739
|
+
display:flex;align-items:center;justify-content:space-between;
|
|
740
|
+
}
|
|
741
|
+
.u-inspector-close{
|
|
742
|
+
background:transparent;border:none;color:var(--text-tertiary);
|
|
743
|
+
cursor:pointer;font-size:14px;padding:2px 4px;
|
|
744
|
+
}
|
|
745
|
+
.u-inspector-close:hover{color:var(--text-primary);}
|
|
746
|
+
.u-inspector-close:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}
|
|
747
|
+
.u-insp-row{
|
|
748
|
+
display:flex;gap:8px;margin-bottom:7px;align-items:baseline;
|
|
749
|
+
}
|
|
750
|
+
.u-insp-label{
|
|
751
|
+
font:500 10px var(--font-body);text-transform:uppercase;letter-spacing:.05em;
|
|
752
|
+
color:var(--text-tertiary);min-width:72px;flex-shrink:0;
|
|
753
|
+
}
|
|
754
|
+
.u-insp-val{color:var(--text-secondary);line-height:1.4;font-size:11px;}
|
|
755
|
+
.u-insp-summary{
|
|
756
|
+
margin:8px 0 0;padding:8px;background:var(--bg-elevated);
|
|
757
|
+
border-radius:var(--r-el);color:var(--text-secondary);
|
|
758
|
+
font-size:11px;line-height:1.55;
|
|
759
|
+
}
|
|
760
|
+
.u-insp-relations{margin-top:10px;}
|
|
761
|
+
.u-insp-relations-label{
|
|
762
|
+
font:500 10px var(--font-body);text-transform:uppercase;letter-spacing:.05em;
|
|
763
|
+
color:var(--text-tertiary);margin-bottom:5px;
|
|
764
|
+
}
|
|
765
|
+
.u-rel-item{
|
|
766
|
+
display:flex;align-items:center;gap:5px;font-size:10px;
|
|
767
|
+
color:var(--text-secondary);padding:3px 0;border-bottom:1px solid var(--border);
|
|
768
|
+
}
|
|
769
|
+
.u-rel-item:last-child{border-bottom:none;}
|
|
770
|
+
.u-rel-dir{
|
|
771
|
+
font-family:var(--font-mono);font-size:9px;color:var(--text-tertiary);
|
|
772
|
+
min-width:16px;text-align:center;
|
|
773
|
+
}
|
|
774
|
+
.u-complexity-badge{
|
|
775
|
+
display:inline-block;font-size:9px;font-weight:600;text-transform:uppercase;
|
|
776
|
+
padding:1px 6px;border-radius:var(--r-pill);letter-spacing:.04em;
|
|
777
|
+
}
|
|
778
|
+
.u-complexity-badge.simple{background:rgba(16,185,129,.15);color:var(--pass);}
|
|
779
|
+
.u-complexity-badge.moderate{background:rgba(245,158,11,.15);color:var(--warn);}
|
|
780
|
+
.u-complexity-badge.complex{background:rgba(239,68,68,.15);color:var(--fail);}
|
|
781
|
+
/* Domain view */
|
|
782
|
+
.u-domain-view{
|
|
783
|
+
display:grid;grid-auto-flow:column;grid-auto-columns:min(220px,80vw);
|
|
784
|
+
gap:12px;overflow-x:auto;padding-bottom:8px;
|
|
785
|
+
}
|
|
786
|
+
.u-domain-col{
|
|
787
|
+
background:var(--bg-card);border:1px solid var(--border);
|
|
788
|
+
border-radius:var(--r-card);padding:14px;
|
|
789
|
+
display:flex;flex-direction:column;gap:10px;
|
|
790
|
+
}
|
|
791
|
+
.u-domain-name{
|
|
792
|
+
font:700 12px var(--font-display);color:var(--accent);margin-bottom:4px;
|
|
793
|
+
}
|
|
794
|
+
.u-domain-summary{
|
|
795
|
+
font-size:10px;color:var(--text-tertiary);line-height:1.5;
|
|
796
|
+
}
|
|
797
|
+
.u-flow{
|
|
798
|
+
border-left:2px solid var(--border-strong);padding-left:10px;
|
|
799
|
+
}
|
|
800
|
+
.u-flow-name{
|
|
801
|
+
font:600 11px var(--font-body);color:var(--text-primary);margin-bottom:5px;
|
|
802
|
+
}
|
|
803
|
+
.u-step{
|
|
804
|
+
font-size:10px;color:var(--text-secondary);padding:3px 0;
|
|
805
|
+
border-bottom:1px dashed var(--border);display:flex;align-items:flex-start;gap:5px;
|
|
806
|
+
}
|
|
807
|
+
.u-step:last-child{border-bottom:none;}
|
|
808
|
+
.u-step-num{
|
|
809
|
+
font-family:var(--font-mono);font-size:9px;color:var(--text-tertiary);
|
|
810
|
+
min-width:14px;margin-top:1px;
|
|
811
|
+
}
|
|
812
|
+
/* Tour panel */
|
|
813
|
+
.u-tour-panel{
|
|
814
|
+
background:var(--bg-card);border:1px solid var(--border-accent);
|
|
815
|
+
border-radius:var(--r-card);padding:14px;font-size:12px;
|
|
816
|
+
display:none;
|
|
817
|
+
}
|
|
818
|
+
.u-tour-panel.visible{display:block;}
|
|
819
|
+
.u-tour-header{
|
|
820
|
+
display:flex;align-items:center;gap:10px;margin-bottom:10px;
|
|
821
|
+
font:600 13px var(--font-body);color:var(--text-primary);
|
|
822
|
+
}
|
|
823
|
+
.u-tour-header span{flex:1;}
|
|
824
|
+
.u-tour-close{
|
|
825
|
+
background:transparent;border:none;color:var(--text-tertiary);
|
|
826
|
+
cursor:pointer;font-size:14px;
|
|
827
|
+
}
|
|
828
|
+
.u-tour-close:hover{color:var(--text-primary);}
|
|
829
|
+
.u-tour-close:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}
|
|
830
|
+
.u-tour-body{display:flex;gap:12px;}
|
|
831
|
+
.u-tour-steps{width:130px;flex-shrink:0;display:flex;flex-direction:column;gap:3px;}
|
|
832
|
+
.u-tour-step-item{
|
|
833
|
+
font-size:10px;padding:5px 7px;border-radius:5px;cursor:pointer;
|
|
834
|
+
color:var(--text-tertiary);border:1px solid transparent;
|
|
835
|
+
transition:all var(--t-fast);
|
|
836
|
+
}
|
|
837
|
+
.u-tour-step-item:hover{color:var(--text-secondary);background:var(--bg-elevated);}
|
|
838
|
+
.u-tour-step-item.active{
|
|
839
|
+
color:var(--accent);background:rgba(45,212,191,.1);
|
|
840
|
+
border-color:var(--accent-deep);
|
|
841
|
+
}
|
|
842
|
+
.u-tour-step-item:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}
|
|
843
|
+
.u-tour-detail{flex:1;}
|
|
844
|
+
.u-tour-step-label{
|
|
845
|
+
font:700 12px var(--font-body);color:var(--text-primary);margin-bottom:5px;
|
|
846
|
+
}
|
|
847
|
+
.u-tour-step-summary{
|
|
848
|
+
font-size:11px;color:var(--text-secondary);line-height:1.55;
|
|
849
|
+
background:var(--bg-elevated);padding:8px;border-radius:var(--r-el);
|
|
850
|
+
}
|
|
851
|
+
.u-tour-nav{display:flex;gap:6px;margin-top:10px;}
|
|
852
|
+
.u-tour-nav-btn{
|
|
853
|
+
font:500 11px var(--font-body);padding:6px 14px;
|
|
854
|
+
border:1px solid var(--border-strong);border-radius:var(--r-el);
|
|
855
|
+
background:transparent;color:var(--text-secondary);cursor:pointer;
|
|
856
|
+
transition:all var(--t-fast);
|
|
857
|
+
}
|
|
858
|
+
.u-tour-nav-btn:hover{background:var(--bg-elevated);color:var(--text-primary);}
|
|
859
|
+
.u-tour-nav-btn:disabled{opacity:.35;cursor:default;}
|
|
860
|
+
.u-tour-nav-btn:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}
|
|
861
|
+
.u-tour-counter{font-family:var(--font-mono);font-size:11px;color:var(--text-tertiary);margin-left:auto;display:flex;align-items:center;}
|
|
862
|
+
/* Graph legend (reused below canvas) */
|
|
863
|
+
.u-legend{
|
|
864
|
+
display:flex;gap:12px;flex-wrap:wrap;margin-top:8px;
|
|
865
|
+
font-size:11px;color:var(--text-secondary);
|
|
866
|
+
}
|
|
867
|
+
.u-legend i{
|
|
868
|
+
display:inline-block;width:9px;height:9px;border-radius:50%;
|
|
869
|
+
margin-right:4px;vertical-align:middle;
|
|
870
|
+
}
|
|
871
|
+
.u-search-match-count{font-size:11px;color:var(--text-tertiary);white-space:nowrap;}
|
|
872
|
+
@media(max-width:880px){
|
|
873
|
+
.u-layout{grid-template-columns:1fr;}
|
|
874
|
+
.u-filters{display:grid;grid-template-columns:repeat(3,1fr);}
|
|
875
|
+
}
|
|
876
|
+
@media(max-width:540px){
|
|
877
|
+
.u-layout{grid-template-columns:1fr;}
|
|
878
|
+
.u-filters{grid-template-columns:1fr;}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/* ── Improve placeholder ── */
|
|
882
|
+
.improve-placeholder{
|
|
883
|
+
display:flex;flex-direction:column;align-items:center;
|
|
884
|
+
justify-content:center;padding:80px 20px;gap:16px;text-align:center;
|
|
885
|
+
}
|
|
886
|
+
.improve-placeholder .ip-icon{font-size:40px;opacity:.3;}
|
|
887
|
+
.improve-placeholder h4{
|
|
888
|
+
font-family:var(--font-display);font-size:20px;color:var(--text-secondary);
|
|
889
|
+
}
|
|
890
|
+
.improve-placeholder p{
|
|
891
|
+
font-size:13px;color:var(--text-tertiary);max-width:420px;line-height:1.7;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/* ── Phase 5a: Measure enrichment ── */
|
|
895
|
+
/* Skill-ROI section */
|
|
896
|
+
.roi-grid{
|
|
897
|
+
display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:12px;
|
|
898
|
+
}
|
|
899
|
+
.roi-card{
|
|
900
|
+
background:var(--bg-elevated);border-radius:var(--r-el);
|
|
901
|
+
padding:14px;font-size:12px;
|
|
902
|
+
}
|
|
903
|
+
.roi-card-title{
|
|
904
|
+
font:500 10px var(--font-body);text-transform:uppercase;letter-spacing:.06em;
|
|
905
|
+
color:var(--text-tertiary);margin-bottom:8px;
|
|
906
|
+
}
|
|
907
|
+
.roi-chip{
|
|
908
|
+
display:inline-flex;align-items:center;gap:5px;
|
|
909
|
+
font-family:var(--font-mono);font-size:11px;
|
|
910
|
+
background:var(--bg-card);border-radius:var(--r-pill);
|
|
911
|
+
padding:3px 9px;margin:2px;color:var(--text-secondary);border:1px solid var(--border);
|
|
912
|
+
}
|
|
913
|
+
.roi-chip.active{color:var(--accent);border-color:rgba(45,212,191,.3);}
|
|
914
|
+
.roi-chip.dormant{color:var(--text-secondary);border-color:transparent;}
|
|
915
|
+
.roi-stat{
|
|
916
|
+
font-family:var(--font-mono);font-weight:700;font-size:22px;
|
|
917
|
+
color:var(--text-primary);margin-bottom:4px;
|
|
918
|
+
}
|
|
919
|
+
.roi-stat-sub{font-size:11px;color:var(--text-secondary);}
|
|
920
|
+
|
|
921
|
+
/* Heatmap */
|
|
922
|
+
.heatmap-wrap{overflow-x:auto;margin-top:10px;}
|
|
923
|
+
.heatmap-table{border-collapse:collapse;font-size:10px;min-width:400px;}
|
|
924
|
+
.heatmap-table th{
|
|
925
|
+
font:500 9px var(--font-body);text-transform:uppercase;letter-spacing:.04em;
|
|
926
|
+
color:var(--text-tertiary);padding:3px 4px;text-align:left;
|
|
927
|
+
white-space:nowrap;
|
|
928
|
+
}
|
|
929
|
+
.hm-skill-label{
|
|
930
|
+
font-family:var(--font-mono);font-size:9px;color:var(--text-secondary);
|
|
931
|
+
max-width:90px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
|
|
932
|
+
padding-right:6px;
|
|
933
|
+
}
|
|
934
|
+
.hm-cell{
|
|
935
|
+
width:16px;height:16px;border-radius:2px;
|
|
936
|
+
transition:opacity var(--t-fast);
|
|
937
|
+
}
|
|
938
|
+
.hm-cell:hover{opacity:.7;}
|
|
939
|
+
/* Timeline */
|
|
940
|
+
.timeline-list{display:flex;flex-direction:column;gap:8px;margin-top:10px;}
|
|
941
|
+
.timeline-item{
|
|
942
|
+
background:var(--bg-elevated);border-radius:var(--r-el);
|
|
943
|
+
padding:10px 12px;display:flex;align-items:flex-start;gap:10px;
|
|
944
|
+
border-left:2px solid var(--border-strong);
|
|
945
|
+
}
|
|
946
|
+
.timeline-item.primary{border-left-color:var(--accent);}
|
|
947
|
+
.timeline-date{
|
|
948
|
+
font-family:var(--font-mono);font-size:10px;color:var(--text-tertiary);
|
|
949
|
+
flex-shrink:0;width:72px;
|
|
950
|
+
}
|
|
951
|
+
.timeline-skills{
|
|
952
|
+
flex:1;display:flex;flex-wrap:wrap;gap:4px;
|
|
953
|
+
}
|
|
954
|
+
.timeline-skill-chip{
|
|
955
|
+
font-family:var(--font-mono);font-size:9px;
|
|
956
|
+
background:var(--bg-card);border-radius:3px;
|
|
957
|
+
padding:2px 6px;color:var(--text-secondary);border:1px solid var(--border);
|
|
958
|
+
}
|
|
959
|
+
.timeline-skill-chip.primary-skill{
|
|
960
|
+
color:var(--accent);border-color:rgba(45,212,191,.3);
|
|
961
|
+
}
|
|
962
|
+
.timeline-meta{
|
|
963
|
+
font-size:10px;color:var(--text-tertiary);flex-shrink:0;text-align:right;
|
|
964
|
+
width:60px;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
/* ── Phase 5a: Improve tab cards ── */
|
|
968
|
+
.improve-grid{
|
|
969
|
+
display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));
|
|
970
|
+
gap:14px;margin-top:16px;
|
|
971
|
+
}
|
|
972
|
+
.improve-card{
|
|
973
|
+
background:var(--bg-card);border:1px solid var(--border);
|
|
974
|
+
border-radius:var(--r-card);padding:16px;
|
|
975
|
+
border-left:3px solid var(--border-strong);
|
|
976
|
+
transition:transform var(--t-fast),box-shadow var(--t-fast);
|
|
977
|
+
}
|
|
978
|
+
.improve-card:hover{
|
|
979
|
+
transform:translateY(-1px);box-shadow:var(--shadow-md);
|
|
980
|
+
}
|
|
981
|
+
.improve-card.sev-warn{border-left-color:var(--warn);}
|
|
982
|
+
.improve-card.sev-info{border-left-color:var(--info);}
|
|
983
|
+
.improve-card.sev-pass{border-left-color:var(--pass);}
|
|
984
|
+
.improve-card-header{
|
|
985
|
+
display:flex;align-items:flex-start;gap:10px;margin-bottom:10px;
|
|
986
|
+
}
|
|
987
|
+
.improve-card-icon{font-size:16px;flex-shrink:0;margin-top:1px;}
|
|
988
|
+
.improve-card-title{
|
|
989
|
+
font:600 13px var(--font-body);color:var(--text-primary);flex:1;
|
|
990
|
+
line-height:1.3;
|
|
991
|
+
}
|
|
992
|
+
.improve-sev-badge{
|
|
993
|
+
font:600 9px var(--font-body);text-transform:uppercase;letter-spacing:.06em;
|
|
994
|
+
padding:2px 7px;border-radius:var(--r-pill);flex-shrink:0;
|
|
995
|
+
}
|
|
996
|
+
.improve-sev-badge.warn{background:rgba(245,158,11,.15);color:var(--warn);}
|
|
997
|
+
.improve-sev-badge.info{background:rgba(56,189,248,.15);color:var(--info);}
|
|
998
|
+
.improve-sev-badge.pass{background:rgba(16,185,129,.15);color:var(--pass);}
|
|
999
|
+
.improve-card-body{
|
|
1000
|
+
font-size:12px;color:var(--text-secondary);line-height:1.6;
|
|
1001
|
+
}
|
|
1002
|
+
.improve-card-action{
|
|
1003
|
+
margin-top:10px;padding-top:10px;border-top:1px solid var(--border);
|
|
1004
|
+
font-size:11px;color:var(--text-tertiary);display:flex;gap:6px;align-items:baseline;
|
|
1005
|
+
}
|
|
1006
|
+
.improve-card-action b{color:var(--accent);font-weight:600;}
|
|
1007
|
+
.improve-header{
|
|
1008
|
+
display:flex;align-items:baseline;gap:12px;margin-bottom:4px;
|
|
1009
|
+
}
|
|
1010
|
+
.improve-header h3{
|
|
1011
|
+
font:600 15px var(--font-display);color:var(--text-primary);
|
|
1012
|
+
}
|
|
1013
|
+
.improve-header .improve-count{
|
|
1014
|
+
font-family:var(--font-mono);font-size:11px;color:var(--text-tertiary);
|
|
1015
|
+
}
|
|
1016
|
+
.improve-subhead{
|
|
1017
|
+
font-size:12px;color:var(--text-tertiary);margin-bottom:16px;line-height:1.5;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
/* ── Canvas keyboard accessibility (sr-only node list) ── */
|
|
1021
|
+
.sr-only{
|
|
1022
|
+
position:absolute;width:1px;height:1px;padding:0;margin:-1px;
|
|
1023
|
+
overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;
|
|
1024
|
+
}
|
|
1025
|
+
/* Canvas focus ring */
|
|
1026
|
+
#understand-canvas:focus-visible{
|
|
1027
|
+
outline:2px solid var(--accent);outline-offset:2px;
|
|
1028
|
+
}
|
|
1029
|
+
/* Focused node ring — drawn on canvas, not CSS */
|
|
1030
|
+
|
|
1031
|
+
/* ── Tier upsell panel (Govern tab — free tier) ── */
|
|
1032
|
+
.tier-upsell{
|
|
1033
|
+
display:flex;flex-direction:column;align-items:center;
|
|
1034
|
+
justify-content:center;padding:60px 20px 48px;gap:18px;text-align:center;
|
|
1035
|
+
}
|
|
1036
|
+
.tier-upsell .upsell-icon{
|
|
1037
|
+
font-size:36px;opacity:.5;line-height:1;
|
|
1038
|
+
pointer-events:none;
|
|
1039
|
+
}
|
|
1040
|
+
.tier-upsell h3{
|
|
1041
|
+
font-family:var(--font-display);font-size:22px;font-weight:600;
|
|
1042
|
+
color:var(--text-primary);max-width:440px;line-height:1.3;
|
|
1043
|
+
}
|
|
1044
|
+
.tier-upsell .upsell-desc{
|
|
1045
|
+
font-size:13px;color:var(--text-secondary);max-width:480px;line-height:1.7;
|
|
1046
|
+
}
|
|
1047
|
+
.upsell-features{
|
|
1048
|
+
display:flex;flex-direction:column;gap:8px;
|
|
1049
|
+
max-width:400px;text-align:left;
|
|
1050
|
+
}
|
|
1051
|
+
.upsell-feature{
|
|
1052
|
+
display:flex;align-items:flex-start;gap:8px;
|
|
1053
|
+
font-size:12px;color:var(--text-secondary);line-height:1.5;
|
|
1054
|
+
}
|
|
1055
|
+
.upsell-feature-icon{
|
|
1056
|
+
font-size:14px;flex-shrink:0;margin-top:1px;
|
|
1057
|
+
pointer-events:none;
|
|
1058
|
+
}
|
|
1059
|
+
.upsell-how{
|
|
1060
|
+
margin-top:4px;font-size:11px;color:var(--text-tertiary);
|
|
1061
|
+
background:var(--bg-elevated);border-radius:var(--r-el);
|
|
1062
|
+
padding:10px 16px;max-width:400px;line-height:1.65;
|
|
1063
|
+
}
|
|
1064
|
+
.upsell-how code{
|
|
1065
|
+
font-family:var(--font-mono);font-size:10px;
|
|
1066
|
+
background:var(--bg-card);padding:1px 6px;border-radius:3px;
|
|
1067
|
+
border:1px solid var(--border);color:var(--accent);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
/* ── My Lens persona (Pro) ── */
|
|
1071
|
+
/* My Lens reuses existing measure-panel styles — no new classes needed.
|
|
1072
|
+
The panel content is injected by renderMyLens() into govern-content. */
|
|
1073
|
+
.mylens-header{
|
|
1074
|
+
display:flex;align-items:center;gap:10px;padding:16px 0 8px;
|
|
1075
|
+
border-bottom:1px solid var(--border);margin-bottom:16px;
|
|
1076
|
+
}
|
|
1077
|
+
.mylens-badge{
|
|
1078
|
+
font:600 10px var(--font-body);text-transform:uppercase;letter-spacing:.06em;
|
|
1079
|
+
padding:2px 8px;border-radius:var(--r-pill);
|
|
1080
|
+
background:rgba(45,212,191,.12);color:var(--accent);flex-shrink:0;
|
|
1081
|
+
}
|
|
1082
|
+
.mylens-title{
|
|
1083
|
+
font-family:var(--font-display);font-size:16px;font-weight:600;
|
|
1084
|
+
color:var(--text-primary);
|
|
1085
|
+
}
|
|
1086
|
+
.mylens-sub{
|
|
1087
|
+
font-size:11px;color:var(--text-tertiary);margin-left:auto;
|
|
1088
|
+
}
|
|
1089
|
+
.mylens-kpis{
|
|
1090
|
+
display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-bottom:16px;
|
|
1091
|
+
}
|
|
1092
|
+
.mylens-kpis .kpi{margin:0;}
|
|
1093
|
+
@media(max-width:640px){.mylens-kpis{grid-template-columns:1fr 1fr;}}
|
|
1094
|
+
|
|
1095
|
+
/* ── Footer ── */
|
|
1096
|
+
.footer{
|
|
1097
|
+
text-align:center;margin-top:28px;
|
|
1098
|
+
font-size:11px;color:var(--text-tertiary);line-height:1.8;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
/* ── Animations ── */
|
|
1102
|
+
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.35}}
|
|
1103
|
+
|
|
1104
|
+
/* ── Motion guard ── */
|
|
1105
|
+
@media(prefers-reduced-motion:reduce){
|
|
1106
|
+
*,*::before,*::after{animation:none!important;transition:none!important;}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
/* ── Responsive ── */
|
|
1110
|
+
@media(max-width:880px){
|
|
1111
|
+
.kpis{grid-template-columns:repeat(2,1fr);}
|
|
1112
|
+
.cols{grid-template-columns:1fr;}
|
|
1113
|
+
.score{font-size:52px;}
|
|
1114
|
+
nav.tabs{flex-wrap:wrap;}
|
|
1115
|
+
}
|
|
1116
|
+
@media(max-width:480px){
|
|
1117
|
+
.kpis{grid-template-columns:1fr;}
|
|
1118
|
+
.wrap{padding:12px 14px;}
|
|
1119
|
+
}
|
|
1120
|
+
</style>
|
|
1121
|
+
</head>
|
|
1122
|
+
<body>
|
|
1123
|
+
<div class="wrap">
|
|
1124
|
+
|
|
1125
|
+
<!-- Header -->
|
|
1126
|
+
<header class="top">
|
|
1127
|
+
<div class="logo" role="banner">
|
|
1128
|
+
<span class="dot" aria-hidden="true"></span>
|
|
1129
|
+
Rune${d.project ? ` — ${escHtml(d.project)}` : ''}
|
|
1130
|
+
</div>
|
|
1131
|
+
<nav class="tabs" role="tablist" aria-label="Dashboard tabs">
|
|
1132
|
+
<button class="tab active" role="tab" aria-selected="true" aria-controls="panel-verdict" id="tab-verdict">Verdict</button>
|
|
1133
|
+
<button class="tab" role="tab" aria-selected="false" aria-controls="panel-govern" id="tab-govern">Govern</button>
|
|
1134
|
+
<button class="tab" role="tab" aria-selected="false" aria-controls="panel-measure" id="tab-measure">Measure</button>
|
|
1135
|
+
<button class="tab" role="tab" aria-selected="false" aria-controls="panel-understand" id="tab-understand">Understand</button>
|
|
1136
|
+
<button class="tab" role="tab" aria-selected="false" aria-controls="panel-improve" id="tab-improve">Improve</button>
|
|
1137
|
+
</nav>
|
|
1138
|
+
<div class="persona" role="group" aria-label="Persona view — changes section emphasis">
|
|
1139
|
+
<button data-persona="exec" aria-pressed="${initialPersona === 'exec' ? 'true' : 'false'}" class="${initialPersona === 'exec' ? 'on' : ''}">Exec</button>
|
|
1140
|
+
<button data-persona="compliance" aria-pressed="${initialPersona === 'compliance' ? 'true' : 'false'}" class="${initialPersona === 'compliance' ? 'on' : ''}">Compliance</button>
|
|
1141
|
+
<button data-persona="eng" aria-pressed="${initialPersona === 'eng' ? 'true' : 'false'}" class="${initialPersona === 'eng' ? 'on' : ''}">Eng</button>
|
|
1142
|
+
${d.hasPro ? `<button data-persona="mylens" aria-pressed="${initialPersona === 'mylens' ? 'true' : 'false'}" class="${initialPersona === 'mylens' ? 'on' : ''}" title="Pro: personal cost, gates fired, and skill ROI focus">My Lens</button>` : ''}
|
|
1143
|
+
</div>
|
|
1144
|
+
</header>
|
|
1145
|
+
|
|
1146
|
+
<main id="dash-main">
|
|
1147
|
+
|
|
1148
|
+
<!-- ── VERDICT TAB ── -->
|
|
1149
|
+
<div class="tab-panel active" id="panel-verdict" role="tabpanel" aria-labelledby="tab-verdict">
|
|
1150
|
+
|
|
1151
|
+
<!-- Verdict hero -->
|
|
1152
|
+
<section class="hero" aria-label="Project verdict">
|
|
1153
|
+
<div class="live" aria-label="Generated date">
|
|
1154
|
+
<i aria-hidden="true"></i>
|
|
1155
|
+
Generated ${escHtml(generatedDate)}
|
|
1156
|
+
</div>
|
|
1157
|
+
<h1 id="verdict-line">${verdictLine}</h1>
|
|
1158
|
+
<div class="scoreRow">
|
|
1159
|
+
<div class="score" id="verdict-score" aria-label="Health score: ${verdictScore !== null ? `${verdictScore} out of 100` : 'not available'}">
|
|
1160
|
+
${
|
|
1161
|
+
verdictScore !== null
|
|
1162
|
+
? `<span id="score-num">${verdictScore}</span><small>/100</small>`
|
|
1163
|
+
: `<span style="font-size:48px;color:var(--text-tertiary);">—</span><small>/100</small>`
|
|
1164
|
+
}
|
|
1165
|
+
</div>
|
|
1166
|
+
${deltaHtml}
|
|
1167
|
+
<div class="meta">
|
|
1168
|
+
${sessions > 0 ? `via <b>${sessions}</b> session${sessions !== 1 ? 's' : ''}` : 'no sessions yet'}
|
|
1169
|
+
${skillsActive > 0 ? ` • <b>${skillsActive}/${totalSkills}</b> skills active` : ''}
|
|
1170
|
+
</div>
|
|
1171
|
+
</div>
|
|
1172
|
+
</section>
|
|
1173
|
+
|
|
1174
|
+
<!-- KPI row -->
|
|
1175
|
+
<section class="kpis" aria-label="Key metrics">
|
|
1176
|
+
<div class="kpi">
|
|
1177
|
+
<div class="k-lbl">Cost / feature</div>
|
|
1178
|
+
<div class="k-val" id="kpi-cost">—</div>
|
|
1179
|
+
<div class="k-sub">not captured yet</div>
|
|
1180
|
+
</div>
|
|
1181
|
+
<div class="kpi">
|
|
1182
|
+
<div class="k-lbl">Gates fired</div>
|
|
1183
|
+
<div class="k-val" id="kpi-gates" data-target="${gatesFired}">0</div>
|
|
1184
|
+
<div class="k-sub ${gatesFired > 0 ? 'up' : ''}">${gatesFired > 0 ? `${gatesFired} total invocations` : 'no gates yet'}</div>
|
|
1185
|
+
</div>
|
|
1186
|
+
<div class="kpi">
|
|
1187
|
+
<div class="k-lbl">Compliance</div>
|
|
1188
|
+
<div class="k-val" id="kpi-compliance">${compliancePct !== null ? `<span id="compliance-num">0</span>%` : '—'}</div>
|
|
1189
|
+
<div class="k-sub">${complianceTotal > 0 ? `${complianceMet}/${complianceTotal} obligations met` : 'no obligations captured'}</div>
|
|
1190
|
+
</div>
|
|
1191
|
+
<div class="kpi">
|
|
1192
|
+
<div class="k-lbl">Skills active</div>
|
|
1193
|
+
<div class="k-val" id="kpi-skills">${skillsActive > 0 ? `<span id="skills-num">0</span><span style="font-size:16px;color:var(--text-tertiary)">/${totalSkills}</span>` : '—'}</div>
|
|
1194
|
+
<div class="k-sub">${skillsActive > 0 ? `${totalSkills - skillsActive} dormant` : 'run skills to track'}</div>
|
|
1195
|
+
</div>
|
|
1196
|
+
</section>
|
|
1197
|
+
|
|
1198
|
+
</div><!-- /panel-verdict -->
|
|
1199
|
+
|
|
1200
|
+
<!-- ── GOVERN TAB ── -->
|
|
1201
|
+
<div class="tab-panel" id="panel-govern" role="tabpanel" aria-labelledby="tab-govern">
|
|
1202
|
+
<div class="section cols" id="govern-content">
|
|
1203
|
+
<!-- ledger + compliance rendered by JS -->
|
|
1204
|
+
</div>
|
|
1205
|
+
</div>
|
|
1206
|
+
|
|
1207
|
+
<!-- ── MEASURE TAB ── -->
|
|
1208
|
+
<div class="tab-panel" id="panel-measure" role="tabpanel" aria-labelledby="tab-measure">
|
|
1209
|
+
<div class="section" id="measure-content">
|
|
1210
|
+
<!-- skill frequency + model distribution rendered by JS -->
|
|
1211
|
+
</div>
|
|
1212
|
+
</div>
|
|
1213
|
+
|
|
1214
|
+
<!-- ── UNDERSTAND TAB ── -->
|
|
1215
|
+
<div class="tab-panel" id="panel-understand" role="tabpanel" aria-labelledby="tab-understand">
|
|
1216
|
+
<div class="section" id="understand-content">
|
|
1217
|
+
<!-- module graph rendered by JS -->
|
|
1218
|
+
</div>
|
|
1219
|
+
</div>
|
|
1220
|
+
|
|
1221
|
+
<!-- ── IMPROVE TAB ── -->
|
|
1222
|
+
<div class="tab-panel" id="panel-improve" role="tabpanel" aria-labelledby="tab-improve">
|
|
1223
|
+
<div class="section" id="improve-content">
|
|
1224
|
+
<!-- data-driven improvement cards rendered by JS -->
|
|
1225
|
+
</div>
|
|
1226
|
+
</div>
|
|
1227
|
+
|
|
1228
|
+
</main>
|
|
1229
|
+
|
|
1230
|
+
<footer class="footer">
|
|
1231
|
+
Generated ${escHtml(generatedDate)}
|
|
1232
|
+
• Rune Dashboard
|
|
1233
|
+
• 100% local — no data leaves your machine
|
|
1234
|
+
</footer>
|
|
1235
|
+
|
|
1236
|
+
</div><!-- /wrap -->
|
|
1237
|
+
|
|
1238
|
+
<script>
|
|
1239
|
+
// ── Safely embedded data ──
|
|
1240
|
+
const D = ${dataJson};
|
|
1241
|
+
${CLIENT_SCRIPT}</script>
|
|
1242
|
+
</body>
|
|
1243
|
+
</html>`;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
// ── Template helper: HTML escape for server-side string insertion ──
|
|
1247
|
+
function escHtml(s) {
|
|
1248
|
+
return String(s == null ? '' : s)
|
|
1249
|
+
.replace(/&/g, '&')
|
|
1250
|
+
.replace(/</g, '<')
|
|
1251
|
+
.replace(/>/g, '>')
|
|
1252
|
+
.replace(/"/g, '"')
|
|
1253
|
+
.replace(/'/g, ''');
|
|
1254
|
+
}
|