@girardelli/architect 1.3.0 → 2.2.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 +111 -112
- package/dist/agent-generator.d.ts +106 -0
- package/dist/agent-generator.d.ts.map +1 -0
- package/dist/agent-generator.js +1398 -0
- package/dist/agent-generator.js.map +1 -0
- package/dist/cli.js +132 -15
- package/dist/cli.js.map +1 -1
- package/dist/html-reporter.d.ts +8 -2
- package/dist/html-reporter.d.ts.map +1 -1
- package/dist/html-reporter.js +773 -50
- package/dist/html-reporter.js.map +1 -1
- package/dist/index.d.ts +26 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -1
- package/dist/index.js.map +1 -1
- package/dist/refactor-engine.d.ts +18 -0
- package/dist/refactor-engine.d.ts.map +1 -0
- package/dist/refactor-engine.js +86 -0
- package/dist/refactor-engine.js.map +1 -0
- package/dist/refactor-reporter.d.ts +20 -0
- package/dist/refactor-reporter.d.ts.map +1 -0
- package/dist/refactor-reporter.js +389 -0
- package/dist/refactor-reporter.js.map +1 -0
- package/dist/rules/barrel-optimizer.d.ts +13 -0
- package/dist/rules/barrel-optimizer.d.ts.map +1 -0
- package/dist/rules/barrel-optimizer.js +77 -0
- package/dist/rules/barrel-optimizer.js.map +1 -0
- package/dist/rules/dead-code-detector.d.ts +21 -0
- package/dist/rules/dead-code-detector.d.ts.map +1 -0
- package/dist/rules/dead-code-detector.js +117 -0
- package/dist/rules/dead-code-detector.js.map +1 -0
- package/dist/rules/hub-splitter.d.ts +13 -0
- package/dist/rules/hub-splitter.d.ts.map +1 -0
- package/dist/rules/hub-splitter.js +110 -0
- package/dist/rules/hub-splitter.js.map +1 -0
- package/dist/rules/import-organizer.d.ts +13 -0
- package/dist/rules/import-organizer.d.ts.map +1 -0
- package/dist/rules/import-organizer.js +85 -0
- package/dist/rules/import-organizer.js.map +1 -0
- package/dist/rules/module-grouper.d.ts +13 -0
- package/dist/rules/module-grouper.d.ts.map +1 -0
- package/dist/rules/module-grouper.js +110 -0
- package/dist/rules/module-grouper.js.map +1 -0
- package/dist/types.d.ts +51 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/agent-generator.ts +1526 -0
- package/src/cli.ts +150 -15
- package/src/html-reporter.ts +799 -51
- package/src/index.ts +39 -1
- package/src/refactor-engine.ts +117 -0
- package/src/refactor-reporter.ts +408 -0
- package/src/rules/barrel-optimizer.ts +97 -0
- package/src/rules/dead-code-detector.ts +132 -0
- package/src/rules/hub-splitter.ts +123 -0
- package/src/rules/import-organizer.ts +98 -0
- package/src/rules/module-grouper.ts +124 -0
- package/src/types.ts +52 -0
package/dist/html-reporter.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Features: D3.js force graph, bubble charts, radar chart, animated counters.
|
|
4
4
|
*/
|
|
5
5
|
export class HtmlReportGenerator {
|
|
6
|
-
generateHtml(report) {
|
|
6
|
+
generateHtml(report, plan, agentSuggestion) {
|
|
7
7
|
const grouped = this.groupAntiPatterns(report.antiPatterns);
|
|
8
8
|
const sugGrouped = this.groupSuggestions(report.suggestions);
|
|
9
9
|
return `<!DOCTYPE html>
|
|
@@ -17,15 +17,62 @@ ${this.getStyles()}
|
|
|
17
17
|
</head>
|
|
18
18
|
<body>
|
|
19
19
|
${this.renderHeader(report)}
|
|
20
|
-
<div class="
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
<div class="report-layout">
|
|
21
|
+
<nav class="sidebar" id="reportSidebar">
|
|
22
|
+
<div class="sidebar-title">Navigation</div>
|
|
23
|
+
<a href="#score" class="sidebar-link active" data-section="score">📊 Score</a>
|
|
24
|
+
<a href="#layers" class="sidebar-link" data-section="layers">📐 Layers & Graph</a>
|
|
25
|
+
<a href="#anti-patterns" class="sidebar-link" data-section="anti-patterns">⚠️ Anti-Patterns (${report.antiPatterns.length})</a>
|
|
26
|
+
<a href="#suggestions" class="sidebar-link" data-section="suggestions">💡 Suggestions (${report.suggestions.length})</a>
|
|
27
|
+
${plan ? `<a href="#refactoring" class="sidebar-link" data-section="refactoring">🔧 Refactoring (${plan.steps.length})</a>` : ''}
|
|
28
|
+
${agentSuggestion ? `<a href="#agents" class="sidebar-link" data-section="agents">🤖 Agents</a>` : ''}
|
|
29
|
+
</nav>
|
|
30
|
+
<button class="sidebar-toggle" onclick="document.getElementById('reportSidebar').classList.toggle('sidebar-open')">☰</button>
|
|
31
|
+
|
|
32
|
+
<div class="container">
|
|
33
|
+
<div id="score">
|
|
34
|
+
${this.renderScoreHero(report)}
|
|
35
|
+
${this.renderRadarChart(report)}
|
|
36
|
+
${this.renderStats(report)}
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<details class="section-accordion" id="layers" open>
|
|
40
|
+
<summary class="section-accordion-header">📐 Layer Analysis & Dependencies</summary>
|
|
41
|
+
<div class="section-accordion-body">
|
|
42
|
+
${this.renderLayers(report)}
|
|
43
|
+
${this.renderDependencyGraph(report)}
|
|
44
|
+
</div>
|
|
45
|
+
</details>
|
|
46
|
+
|
|
47
|
+
<details class="section-accordion" id="anti-patterns" open>
|
|
48
|
+
<summary class="section-accordion-header">⚠️ Anti-Patterns (${report.antiPatterns.length})</summary>
|
|
49
|
+
<div class="section-accordion-body">
|
|
50
|
+
${this.renderAntiPatternBubbles(report, grouped)}
|
|
51
|
+
${this.renderAntiPatterns(report, grouped)}
|
|
52
|
+
</div>
|
|
53
|
+
</details>
|
|
54
|
+
|
|
55
|
+
<details class="section-accordion" id="suggestions">
|
|
56
|
+
<summary class="section-accordion-header">💡 Suggestions (${report.suggestions.length})</summary>
|
|
57
|
+
<div class="section-accordion-body">
|
|
58
|
+
${this.renderSuggestions(sugGrouped)}
|
|
59
|
+
</div>
|
|
60
|
+
</details>
|
|
61
|
+
|
|
62
|
+
${plan ? `<details class="section-accordion" id="refactoring" open>
|
|
63
|
+
<summary class="section-accordion-header">🔧 Refactoring Plan (${plan.steps.length} steps, ${plan.totalOperations} operations)</summary>
|
|
64
|
+
<div class="section-accordion-body">
|
|
65
|
+
${this.renderRefactoringPlan(plan)}
|
|
66
|
+
</div>
|
|
67
|
+
</details>` : ''}
|
|
68
|
+
|
|
69
|
+
${agentSuggestion ? `<details class="section-accordion" id="agents" open>
|
|
70
|
+
<summary class="section-accordion-header">🤖 Agent System</summary>
|
|
71
|
+
<div class="section-accordion-body">
|
|
72
|
+
${this.renderAgentSuggestions(agentSuggestion)}
|
|
73
|
+
</div>
|
|
74
|
+
</details>` : ''}
|
|
75
|
+
</div>
|
|
29
76
|
</div>
|
|
30
77
|
${this.renderFooter()}
|
|
31
78
|
${this.getScripts(report)}
|
|
@@ -206,44 +253,70 @@ ${this.getScripts(report)}
|
|
|
206
253
|
renderDependencyGraph(report) {
|
|
207
254
|
if (report.dependencyGraph.edges.length === 0)
|
|
208
255
|
return '';
|
|
209
|
-
// Build
|
|
256
|
+
// Build real file set — only files that appear as SOURCE in edges (these are real scanned files)
|
|
257
|
+
const realFiles = new Set(report.dependencyGraph.edges.map(e => e.from));
|
|
258
|
+
// Count connections only for real files
|
|
210
259
|
const connectionCount = {};
|
|
211
260
|
for (const edge of report.dependencyGraph.edges) {
|
|
212
|
-
|
|
213
|
-
|
|
261
|
+
if (realFiles.has(edge.from)) {
|
|
262
|
+
connectionCount[edge.from] = (connectionCount[edge.from] || 0) + 1;
|
|
263
|
+
}
|
|
264
|
+
if (realFiles.has(edge.to)) {
|
|
265
|
+
connectionCount[edge.to] = (connectionCount[edge.to] || 0) + 1;
|
|
266
|
+
}
|
|
214
267
|
}
|
|
268
|
+
// Build layer map from report layers
|
|
215
269
|
const layerMap = {};
|
|
216
270
|
for (const layer of report.layers) {
|
|
217
271
|
for (const file of layer.files) {
|
|
218
272
|
layerMap[file] = layer.name;
|
|
219
273
|
}
|
|
220
274
|
}
|
|
221
|
-
|
|
275
|
+
// Create nodes only from real files
|
|
276
|
+
const allNodes = [...realFiles].map(n => ({
|
|
222
277
|
id: n,
|
|
223
278
|
name: n.split('/').pop() || n,
|
|
224
279
|
connections: connectionCount[n] || 0,
|
|
225
280
|
layer: layerMap[n] || 'Other',
|
|
226
281
|
}));
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
282
|
+
// Build links only between real files
|
|
283
|
+
const allLinks = report.dependencyGraph.edges
|
|
284
|
+
.filter(e => realFiles.has(e.from) && realFiles.has(e.to))
|
|
285
|
+
.map(e => ({ source: e.from, target: e.to }));
|
|
286
|
+
// Limit to top N most-connected nodes for large projects
|
|
287
|
+
const maxNodes = 60;
|
|
288
|
+
const sortedNodes = [...allNodes].sort((a, b) => b.connections - a.connections);
|
|
289
|
+
const limitedNodes = sortedNodes.slice(0, maxNodes);
|
|
290
|
+
const limitedNodeIds = new Set(limitedNodes.map(n => n.id));
|
|
291
|
+
const limitedLinks = allLinks.filter(l => limitedNodeIds.has(l.source) && limitedNodeIds.has(l.target));
|
|
292
|
+
const isLimited = allNodes.length > maxNodes;
|
|
293
|
+
// Collect unique layers from limited nodes
|
|
294
|
+
const uniqueLayers = [...new Set(limitedNodes.map(n => n.layer))];
|
|
231
295
|
return `
|
|
232
296
|
<h2 class="section-title">🔗 Dependency Graph</h2>
|
|
233
297
|
<div class="card graph-card">
|
|
234
|
-
<div class="graph-
|
|
235
|
-
<
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
298
|
+
<div class="graph-controls">
|
|
299
|
+
<div class="graph-legend">
|
|
300
|
+
<span class="legend-item"><span class="legend-dot" style="background: #ec4899"></span> API</span>
|
|
301
|
+
<span class="legend-item"><span class="legend-dot" style="background: #3b82f6"></span> Service</span>
|
|
302
|
+
<span class="legend-item"><span class="legend-dot" style="background: #10b981"></span> Data</span>
|
|
303
|
+
<span class="legend-item"><span class="legend-dot" style="background: #f59e0b"></span> UI</span>
|
|
304
|
+
<span class="legend-item"><span class="legend-dot" style="background: #8b5cf6"></span> Infra</span>
|
|
305
|
+
<span class="legend-item"><span class="legend-dot" style="background: #64748b"></span> Other</span>
|
|
306
|
+
</div>
|
|
307
|
+
<div class="graph-filters">
|
|
308
|
+
<input type="text" id="graphSearch" class="graph-search" placeholder="🔍 Search node..." oninput="filterGraphNodes(this.value)">
|
|
309
|
+
<div class="graph-layer-filters">
|
|
310
|
+
${uniqueLayers.map(l => `<label class="graph-filter-check"><input type="checkbox" checked data-layer="${l}" onchange="toggleGraphLayer('${l}', this.checked)"><span class="legend-dot" style="background: ${{ 'API': '#ec4899', 'Service': '#3b82f6', 'Data': '#10b981', 'UI': '#f59e0b', 'Infrastructure': '#8b5cf6' }[l] || '#64748b'}"></span> ${l}</label>`).join('')}
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
${isLimited ? `<div class="graph-limit-notice">Showing top ${maxNodes} of ${allNodes.length} source files (most connected) · ${limitedLinks.length} links</div>` : ''}
|
|
241
314
|
</div>
|
|
242
|
-
<div id="dep-graph" style="width:100%; min-height:
|
|
243
|
-
<div class="graph-hint">🖱️ Drag nodes to
|
|
315
|
+
<div id="dep-graph" style="width:100%; min-height:500px;"></div>
|
|
316
|
+
<div class="graph-hint">🖱️ Drag nodes • Scroll to zoom • Double-click to reset • Node size = connections</div>
|
|
244
317
|
</div>
|
|
245
|
-
<script type="application/json" id="graph-nodes">${JSON.stringify(
|
|
246
|
-
<script type="application/json" id="graph-links">${JSON.stringify(
|
|
318
|
+
<script type="application/json" id="graph-nodes">${JSON.stringify(limitedNodes)}<\\/script>
|
|
319
|
+
<script type="application/json" id="graph-links">${JSON.stringify(limitedLinks)}<\\/script>`;
|
|
247
320
|
}
|
|
248
321
|
/**
|
|
249
322
|
* Bubble chart for anti-patterns — bigger = more severe
|
|
@@ -345,8 +418,145 @@ ${this.getScripts(report)}
|
|
|
345
418
|
renderFooter() {
|
|
346
419
|
return `
|
|
347
420
|
<div class="footer">
|
|
348
|
-
<p>Generated by <a href="https://github.com/camilooscargbaptista/architect">🏗️ Architect</a> — AI-powered architecture analysis</p>
|
|
421
|
+
<p>Generated by <a href="https://github.com/camilooscargbaptista/architect">🏗️ Architect v2.0</a> — AI-powered architecture analysis + refactoring engine</p>
|
|
349
422
|
<p>By <strong>Camilo Girardelli</strong> · <a href="https://www.girardellitecnologia.com">Girardelli Tecnologia</a></p>
|
|
423
|
+
</div>`;
|
|
424
|
+
}
|
|
425
|
+
// ── Refactoring Plan Section ──
|
|
426
|
+
opColor(type) {
|
|
427
|
+
switch (type) {
|
|
428
|
+
case 'CREATE': return '#22c55e';
|
|
429
|
+
case 'MOVE': return '#3b82f6';
|
|
430
|
+
case 'MODIFY': return '#f59e0b';
|
|
431
|
+
case 'DELETE': return '#ef4444';
|
|
432
|
+
default: return '#64748b';
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
opIcon(type) {
|
|
436
|
+
switch (type) {
|
|
437
|
+
case 'CREATE': return '➕';
|
|
438
|
+
case 'MOVE': return '📦';
|
|
439
|
+
case 'MODIFY': return '✏️';
|
|
440
|
+
case 'DELETE': return '🗑️';
|
|
441
|
+
default: return '📄';
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
renderRefactoringPlan(plan) {
|
|
445
|
+
if (plan.steps.length === 0) {
|
|
446
|
+
return `
|
|
447
|
+
<h2 class="section-title">✅ Refactoring Plan</h2>
|
|
448
|
+
<div class="card success-card">
|
|
449
|
+
<p>No refactoring needed! Your architecture is already in great shape.</p>
|
|
450
|
+
</div>`;
|
|
451
|
+
}
|
|
452
|
+
const improvement = plan.estimatedScoreAfter.overall - plan.currentScore.overall;
|
|
453
|
+
const metrics = Object.keys(plan.currentScore.breakdown);
|
|
454
|
+
const bars = metrics.map(metric => {
|
|
455
|
+
const before = plan.currentScore.breakdown[metric];
|
|
456
|
+
const after = plan.estimatedScoreAfter.breakdown[metric] ?? before;
|
|
457
|
+
const diff = after - before;
|
|
458
|
+
return `
|
|
459
|
+
<div class="comparison-row">
|
|
460
|
+
<div class="refactor-metric-name">${metric}</div>
|
|
461
|
+
<div class="refactor-metric-bars">
|
|
462
|
+
<div class="rbar-before" style="width: ${before}%; background: ${this.scoreColor(before)}40"><span>${before}</span></div>
|
|
463
|
+
<div class="rbar-after" style="width: ${after}%; background: ${this.scoreColor(after)}"><span>${after}</span></div>
|
|
464
|
+
</div>
|
|
465
|
+
<div class="refactor-metric-diff" style="color: ${diff > 0 ? '#22c55e' : '#64748b'}">
|
|
466
|
+
${diff > 0 ? `+${diff}` : diff === 0 ? '—' : String(diff)}
|
|
467
|
+
</div>
|
|
468
|
+
</div>`;
|
|
469
|
+
}).join('');
|
|
470
|
+
const stepsHtml = plan.steps.map(step => this.renderRefactorStep(step)).join('');
|
|
471
|
+
const criticalCount = plan.steps.filter(s => s.priority === 'CRITICAL').length;
|
|
472
|
+
const highCount = plan.steps.filter(s => s.priority === 'HIGH').length;
|
|
473
|
+
const mediumCount = plan.steps.filter(s => s.priority === 'MEDIUM').length;
|
|
474
|
+
const lowCount = plan.steps.filter(s => s.priority === 'LOW').length;
|
|
475
|
+
return `
|
|
476
|
+
<h2 class="section-title">🔧 Refactoring Plan</h2>
|
|
477
|
+
|
|
478
|
+
<div class="card refactor-score">
|
|
479
|
+
<div class="refactor-score-pair">
|
|
480
|
+
<div class="rscore-box">
|
|
481
|
+
<div class="rscore-num" style="color: ${this.scoreColor(plan.currentScore.overall)}">${plan.currentScore.overall}</div>
|
|
482
|
+
<div class="rscore-label">Current</div>
|
|
483
|
+
</div>
|
|
484
|
+
<div class="rscore-arrow">
|
|
485
|
+
<svg width="60" height="30" viewBox="0 0 60 30">
|
|
486
|
+
<path d="M5 15 L45 15 M40 8 L48 15 L40 22" stroke="#818cf8" stroke-width="2.5" fill="none"/>
|
|
487
|
+
</svg>
|
|
488
|
+
</div>
|
|
489
|
+
<div class="rscore-box">
|
|
490
|
+
<div class="rscore-num" style="color: ${this.scoreColor(plan.estimatedScoreAfter.overall)}">${plan.estimatedScoreAfter.overall}</div>
|
|
491
|
+
<div class="rscore-label">Estimated</div>
|
|
492
|
+
</div>
|
|
493
|
+
<div class="rscore-improvement" style="color: #22c55e">+${improvement} pts</div>
|
|
494
|
+
</div>
|
|
495
|
+
<div class="refactor-bars-section">
|
|
496
|
+
<div class="refactor-legend">
|
|
497
|
+
<span class="rlegend-tag rbefore">Before</span>
|
|
498
|
+
<span class="rlegend-tag rafter">After</span>
|
|
499
|
+
</div>
|
|
500
|
+
${bars}
|
|
501
|
+
</div>
|
|
502
|
+
</div>
|
|
503
|
+
|
|
504
|
+
<div class="refactor-stats-row">
|
|
505
|
+
<div class="rstat">${plan.steps.length} steps</div>
|
|
506
|
+
<div class="rstat">${plan.totalOperations} operations</div>
|
|
507
|
+
<div class="rstat">Tier 1: ${plan.tier1Steps}</div>
|
|
508
|
+
<div class="rstat">Tier 2: ${plan.tier2Steps}</div>
|
|
509
|
+
</div>
|
|
510
|
+
|
|
511
|
+
<div class="priority-bar">
|
|
512
|
+
${criticalCount ? `<div class="prio-seg prio-critical" style="flex: ${criticalCount}">🔴 ${criticalCount}</div>` : ''}
|
|
513
|
+
${highCount ? `<div class="prio-seg prio-high" style="flex: ${highCount}">🟠 ${highCount}</div>` : ''}
|
|
514
|
+
${mediumCount ? `<div class="prio-seg prio-medium" style="flex: ${mediumCount}">🔵 ${mediumCount}</div>` : ''}
|
|
515
|
+
${lowCount ? `<div class="prio-seg prio-low" style="flex: ${lowCount}">🟢 ${lowCount}</div>` : ''}
|
|
516
|
+
</div>
|
|
517
|
+
|
|
518
|
+
<div class="refactor-roadmap">
|
|
519
|
+
${stepsHtml}
|
|
520
|
+
</div>`;
|
|
521
|
+
}
|
|
522
|
+
renderRefactorStep(step) {
|
|
523
|
+
const operationsHtml = step.operations.map(op => `
|
|
524
|
+
<div class="rop">
|
|
525
|
+
<span class="rop-icon">${this.opIcon(op.type)}</span>
|
|
526
|
+
<span class="rop-badge" style="background: ${this.opColor(op.type)}20; color: ${this.opColor(op.type)}; border: 1px solid ${this.opColor(op.type)}40">${op.type}</span>
|
|
527
|
+
<code class="rop-path">${this.escapeHtml(op.path)}</code>
|
|
528
|
+
${op.newPath ? `<span class="rop-arrow">→</span> <code class="rop-path">${this.escapeHtml(op.newPath)}</code>` : ''}
|
|
529
|
+
<div class="rop-desc">${this.escapeHtml(op.description)}</div>
|
|
530
|
+
</div>
|
|
531
|
+
`).join('');
|
|
532
|
+
const impactHtml = step.scoreImpact.map(i => `<span class="rimpact-tag">${i.metric}: ${i.before}→${i.after} <strong>+${i.after - i.before}</strong></span>`).join('');
|
|
533
|
+
return `
|
|
534
|
+
<div class="rstep-card">
|
|
535
|
+
<div class="rstep-header">
|
|
536
|
+
<div class="rstep-number">${step.id}</div>
|
|
537
|
+
<div class="rstep-info">
|
|
538
|
+
<div class="rstep-title-row">
|
|
539
|
+
<h3>${this.escapeHtml(step.title)}</h3>
|
|
540
|
+
<span class="severity-badge severity-${step.priority}">${step.priority}</span>
|
|
541
|
+
<span class="tier-badge">Tier ${step.tier}</span>
|
|
542
|
+
</div>
|
|
543
|
+
<p class="rstep-desc">${this.escapeHtml(step.description)}</p>
|
|
544
|
+
<details class="rstep-details">
|
|
545
|
+
<summary>📖 Why?</summary>
|
|
546
|
+
<p class="rstep-rationale">${this.escapeHtml(step.rationale)}</p>
|
|
547
|
+
</details>
|
|
548
|
+
</div>
|
|
549
|
+
</div>
|
|
550
|
+
<details class="rstep-ops-accordion">
|
|
551
|
+
<summary class="rstep-ops-toggle">📋 Operations (${step.operations.length})</summary>
|
|
552
|
+
<div class="rstep-ops">
|
|
553
|
+
${operationsHtml}
|
|
554
|
+
</div>
|
|
555
|
+
</details>
|
|
556
|
+
<div class="rstep-impact">
|
|
557
|
+
<h4>📈 Score Impact</h4>
|
|
558
|
+
<div class="rimpact-tags">${impactHtml}</div>
|
|
559
|
+
</div>
|
|
350
560
|
</div>`;
|
|
351
561
|
}
|
|
352
562
|
/**
|
|
@@ -370,6 +580,23 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
370
580
|
}, { threshold: 0.5 });
|
|
371
581
|
|
|
372
582
|
counters.forEach(c => observer.observe(c));
|
|
583
|
+
|
|
584
|
+
// ── Sidebar Active Section Tracking ──
|
|
585
|
+
const sectionIds = ['score', 'layers', 'anti-patterns', 'suggestions', 'refactoring', 'agents'];
|
|
586
|
+
const sectionObserver = new IntersectionObserver((entries) => {
|
|
587
|
+
entries.forEach(entry => {
|
|
588
|
+
if (entry.isIntersecting) {
|
|
589
|
+
document.querySelectorAll('.sidebar-link').forEach(l => l.classList.remove('active'));
|
|
590
|
+
const link = document.querySelector('.sidebar-link[data-section="' + entry.target.id + '"]');
|
|
591
|
+
if (link) link.classList.add('active');
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
}, { threshold: 0.15, rootMargin: '-80px 0px -60% 0px' });
|
|
595
|
+
|
|
596
|
+
sectionIds.forEach(id => {
|
|
597
|
+
const el = document.getElementById(id);
|
|
598
|
+
if (el) sectionObserver.observe(el);
|
|
599
|
+
});
|
|
373
600
|
});
|
|
374
601
|
|
|
375
602
|
function animateCounter(el, target) {
|
|
@@ -472,7 +699,7 @@ function animateCounter(el, target) {
|
|
|
472
699
|
|
|
473
700
|
const container = document.getElementById('dep-graph');
|
|
474
701
|
const width = container.clientWidth || 800;
|
|
475
|
-
const height =
|
|
702
|
+
const height = 500;
|
|
476
703
|
container.style.height = height + 'px';
|
|
477
704
|
|
|
478
705
|
const layerColors = {
|
|
@@ -484,26 +711,43 @@ function animateCounter(el, target) {
|
|
|
484
711
|
.attr('width', width).attr('height', height)
|
|
485
712
|
.attr('viewBox', [0, 0, width, height]);
|
|
486
713
|
|
|
714
|
+
// Zoom container
|
|
715
|
+
const g = svg.append('g');
|
|
716
|
+
|
|
717
|
+
// Zoom behavior
|
|
718
|
+
const zoom = d3.zoom()
|
|
719
|
+
.scaleExtent([0.2, 5])
|
|
720
|
+
.on('zoom', (event) => { g.attr('transform', event.transform); });
|
|
721
|
+
svg.call(zoom);
|
|
722
|
+
|
|
723
|
+
// Double-click to reset zoom
|
|
724
|
+
svg.on('dblclick.zoom', () => {
|
|
725
|
+
svg.transition().duration(500).call(zoom.transform, d3.zoomIdentity);
|
|
726
|
+
});
|
|
727
|
+
|
|
487
728
|
// Arrow marker
|
|
488
|
-
|
|
729
|
+
g.append('defs').append('marker')
|
|
489
730
|
.attr('id', 'arrowhead').attr('viewBox', '-0 -5 10 10')
|
|
490
731
|
.attr('refX', 20).attr('refY', 0).attr('orient', 'auto')
|
|
491
732
|
.attr('markerWidth', 6).attr('markerHeight', 6)
|
|
492
733
|
.append('path').attr('d', 'M 0,-5 L 10,0 L 0,5')
|
|
493
734
|
.attr('fill', '#475569');
|
|
494
735
|
|
|
736
|
+
// Tuned simulation for better spread
|
|
495
737
|
const simulation = d3.forceSimulation(nodes)
|
|
496
738
|
.force('link', d3.forceLink(links).id(d => d.id).distance(80))
|
|
497
|
-
.force('charge', d3.forceManyBody().strength(-
|
|
739
|
+
.force('charge', d3.forceManyBody().strength(-250))
|
|
498
740
|
.force('center', d3.forceCenter(width / 2, height / 2))
|
|
499
|
-
.force('
|
|
741
|
+
.force('x', d3.forceX(width / 2).strength(0.05))
|
|
742
|
+
.force('y', d3.forceY(height / 2).strength(0.05))
|
|
743
|
+
.force('collision', d3.forceCollide().radius(d => Math.max(d.connections * 2 + 16, 20)));
|
|
500
744
|
|
|
501
|
-
const link =
|
|
745
|
+
const link = g.append('g')
|
|
502
746
|
.selectAll('line').data(links).join('line')
|
|
503
|
-
.attr('stroke', '#334155').attr('stroke-width', 1
|
|
504
|
-
.attr('stroke-opacity', 0.
|
|
747
|
+
.attr('stroke', '#334155').attr('stroke-width', 1)
|
|
748
|
+
.attr('stroke-opacity', 0.4).attr('marker-end', 'url(#arrowhead)');
|
|
505
749
|
|
|
506
|
-
const node =
|
|
750
|
+
const node = g.append('g')
|
|
507
751
|
.selectAll('g').data(nodes).join('g')
|
|
508
752
|
.call(d3.drag()
|
|
509
753
|
.on('start', (e, d) => { if (!e.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; })
|
|
@@ -511,21 +755,21 @@ function animateCounter(el, target) {
|
|
|
511
755
|
.on('end', (e, d) => { if (!e.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; })
|
|
512
756
|
);
|
|
513
757
|
|
|
514
|
-
// Node circles —
|
|
758
|
+
// Node circles — color by layer
|
|
515
759
|
node.append('circle')
|
|
516
|
-
.attr('r', d => Math.max(d.connections *
|
|
760
|
+
.attr('r', d => Math.max(d.connections * 2.5 + 5, 6))
|
|
517
761
|
.attr('fill', d => layerColors[d.layer] || '#64748b')
|
|
518
|
-
.attr('stroke', '#0f172a').attr('stroke-width',
|
|
519
|
-
.attr('opacity', 0.
|
|
762
|
+
.attr('stroke', '#0f172a').attr('stroke-width', 1.5)
|
|
763
|
+
.attr('opacity', 0.9);
|
|
520
764
|
|
|
521
|
-
// Node labels
|
|
522
|
-
node.append('text')
|
|
765
|
+
// Node labels — only show for nodes with enough connections
|
|
766
|
+
node.filter(d => d.connections >= 2).append('text')
|
|
523
767
|
.text(d => d.name.replace(/\\.[^.]+$/, ''))
|
|
524
|
-
.attr('x', 0).attr('y', d => -(Math.max(d.connections *
|
|
768
|
+
.attr('x', 0).attr('y', d => -(Math.max(d.connections * 2.5 + 5, 6) + 4))
|
|
525
769
|
.attr('text-anchor', 'middle')
|
|
526
|
-
.attr('fill', '#
|
|
770
|
+
.attr('fill', '#e2e8f0').attr('font-size', '9px').attr('font-weight', '500');
|
|
527
771
|
|
|
528
|
-
// Tooltip
|
|
772
|
+
// Tooltip
|
|
529
773
|
node.append('title')
|
|
530
774
|
.text(d => d.id + '\\nConnections: ' + d.connections + '\\nLayer: ' + d.layer);
|
|
531
775
|
|
|
@@ -535,6 +779,31 @@ function animateCounter(el, target) {
|
|
|
535
779
|
.attr('x2', d => d.target.x).attr('y2', d => d.target.y);
|
|
536
780
|
node.attr('transform', d => 'translate(' + d.x + ',' + d.y + ')');
|
|
537
781
|
});
|
|
782
|
+
|
|
783
|
+
// Expose search and filter functions
|
|
784
|
+
window.filterGraphNodes = function(query) {
|
|
785
|
+
if (!query) {
|
|
786
|
+
node.attr('opacity', 1);
|
|
787
|
+
link.attr('opacity', 0.4);
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
query = query.toLowerCase();
|
|
791
|
+
node.attr('opacity', d => d.id.toLowerCase().includes(query) || d.name.toLowerCase().includes(query) ? 1 : 0.1);
|
|
792
|
+
link.attr('opacity', d => {
|
|
793
|
+
const srcMatch = d.source.id.toLowerCase().includes(query);
|
|
794
|
+
const tgtMatch = d.target.id.toLowerCase().includes(query);
|
|
795
|
+
return (srcMatch || tgtMatch) ? 0.6 : 0.05;
|
|
796
|
+
});
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
window.toggleGraphLayer = function(layer, visible) {
|
|
800
|
+
node.filter(d => d.layer === layer)
|
|
801
|
+
.transition().duration(300)
|
|
802
|
+
.attr('opacity', visible ? 1 : 0.05);
|
|
803
|
+
link.filter(d => d.source.layer === layer || d.target.layer === layer)
|
|
804
|
+
.transition().duration(300)
|
|
805
|
+
.attr('opacity', visible ? 0.4 : 0.02);
|
|
806
|
+
};
|
|
538
807
|
})();
|
|
539
808
|
|
|
540
809
|
// ── Bubble Chart ──
|
|
@@ -601,6 +870,264 @@ function animateCounter(el, target) {
|
|
|
601
870
|
.attr('text-anchor', 'middle').attr('dy', '2.5em')
|
|
602
871
|
.attr('fill', '#64748b').attr('font-size', '9px').attr('text-transform', 'uppercase');
|
|
603
872
|
})();
|
|
873
|
+
<\/script>`;
|
|
874
|
+
}
|
|
875
|
+
renderAgentSuggestions(s) {
|
|
876
|
+
const roleIcon = (name) => {
|
|
877
|
+
if (name.includes('ORCHESTRATOR'))
|
|
878
|
+
return '\u{1F3AD}';
|
|
879
|
+
if (name.includes('BACKEND') || name.includes('FRONTEND') || name.includes('DATABASE') || name.includes('FLUTTER'))
|
|
880
|
+
return '\u{1F4BB}';
|
|
881
|
+
if (name.includes('SECURITY'))
|
|
882
|
+
return '\u{1F6E1}\uFE0F';
|
|
883
|
+
if (name.includes('QA'))
|
|
884
|
+
return '\u{1F9EA}';
|
|
885
|
+
if (name.includes('TECH-DEBT'))
|
|
886
|
+
return '\u{1F4CA}';
|
|
887
|
+
return '\u{1F916}';
|
|
888
|
+
};
|
|
889
|
+
const roleLabel = (name) => {
|
|
890
|
+
if (name.includes('ORCHESTRATOR'))
|
|
891
|
+
return 'coordination';
|
|
892
|
+
if (name.includes('SECURITY'))
|
|
893
|
+
return 'protection';
|
|
894
|
+
if (name.includes('QA'))
|
|
895
|
+
return 'quality';
|
|
896
|
+
if (name.includes('TECH-DEBT'))
|
|
897
|
+
return 'governance';
|
|
898
|
+
return 'development';
|
|
899
|
+
};
|
|
900
|
+
const roleColor = (name) => {
|
|
901
|
+
if (name.includes('ORCHESTRATOR'))
|
|
902
|
+
return '#c084fc';
|
|
903
|
+
if (name.includes('SECURITY'))
|
|
904
|
+
return '#f87171';
|
|
905
|
+
if (name.includes('QA'))
|
|
906
|
+
return '#34d399';
|
|
907
|
+
if (name.includes('TECH-DEBT'))
|
|
908
|
+
return '#fbbf24';
|
|
909
|
+
return '#60a5fa';
|
|
910
|
+
};
|
|
911
|
+
// Status helpers
|
|
912
|
+
const statusBadge = (status) => {
|
|
913
|
+
const map = {
|
|
914
|
+
'KEEP': { icon: '✅', label: 'KEEP', color: '#22c55e' },
|
|
915
|
+
'MODIFY': { icon: '🔵', label: 'MODIFY', color: '#3b82f6' },
|
|
916
|
+
'CREATE': { icon: '🟡', label: 'NEW', color: '#f59e0b' },
|
|
917
|
+
'DELETE': { icon: '🔴', label: 'REMOVE', color: '#ef4444' },
|
|
918
|
+
};
|
|
919
|
+
const s = map[status] || map['CREATE'];
|
|
920
|
+
return `<span class="agent-status-badge" style="background:${s.color}20;color:${s.color};border:1px solid ${s.color}40">${s.icon} ${s.label}</span>`;
|
|
921
|
+
};
|
|
922
|
+
const statusBorder = (status) => {
|
|
923
|
+
const map = {
|
|
924
|
+
'KEEP': '#22c55e', 'MODIFY': '#3b82f6', 'CREATE': '#f59e0b', 'DELETE': '#ef4444',
|
|
925
|
+
};
|
|
926
|
+
return map[status] || '#334155';
|
|
927
|
+
};
|
|
928
|
+
const agentCards = s.suggestedAgents.map(a => `<label class="agent-toggle-card" data-category="agents" data-name="${a.name}">
|
|
929
|
+
<input type="checkbox" class="agent-check" ${a.status !== 'DELETE' ? 'checked' : ''} data-type="agents" data-item="${a.name}">
|
|
930
|
+
<div class="agent-toggle-inner" style="border-color:${statusBorder(a.status)}">
|
|
931
|
+
<div class="agent-toggle-icon">${roleIcon(a.name)}</div>
|
|
932
|
+
<div class="agent-toggle-info">
|
|
933
|
+
<span class="agent-toggle-name">${a.name}</span>
|
|
934
|
+
<span class="agent-toggle-role" style="color:${roleColor(a.name)}">${roleLabel(a.name)}</span>
|
|
935
|
+
${a.description ? `<span class="agent-toggle-desc">${a.description}</span>` : ''}
|
|
936
|
+
</div>
|
|
937
|
+
${statusBadge(a.status)}
|
|
938
|
+
<div class="agent-toggle-check">\u2713</div>
|
|
939
|
+
</div>
|
|
940
|
+
</label>`).join('\n');
|
|
941
|
+
const miniCard = (item, icon, type) => `<label class="agent-toggle-card mini" data-category="${type}">
|
|
942
|
+
<input type="checkbox" class="agent-check" ${item.status !== 'DELETE' ? 'checked' : ''} data-type="${type}" data-item="${item.name}">
|
|
943
|
+
<div class="agent-toggle-inner" style="border-color:${statusBorder(item.status)}">
|
|
944
|
+
<span class="agent-toggle-icon">${icon}</span>
|
|
945
|
+
<div class="agent-toggle-info">
|
|
946
|
+
<span class="agent-toggle-name">${item.name}.md</span>
|
|
947
|
+
${item.description ? `<span class="agent-toggle-desc">${item.description}</span>` : ''}
|
|
948
|
+
</div>
|
|
949
|
+
${statusBadge(item.status)}
|
|
950
|
+
<div class="agent-toggle-check">\u2713</div>
|
|
951
|
+
</div>
|
|
952
|
+
</label>`;
|
|
953
|
+
const ruleCards = s.suggestedRules.map(r => miniCard(r, '\u{1F4CF}', 'rules')).join('\n');
|
|
954
|
+
const guardCards = s.suggestedGuards.map(g => miniCard(g, '\u{1F6E1}\uFE0F', 'guards')).join('\n');
|
|
955
|
+
const workflowCards = s.suggestedWorkflows.map(w => miniCard(w, '\u26A1', 'workflows')).join('\n');
|
|
956
|
+
const skillCards = s.suggestedSkills.map(sk => `<label class="agent-toggle-card" data-category="skills">
|
|
957
|
+
<input type="checkbox" class="agent-check" checked data-type="skills" data-item="${sk.source}">
|
|
958
|
+
<div class="agent-toggle-inner" style="border-color:${statusBorder(sk.status)}">
|
|
959
|
+
<span class="agent-toggle-icon">\u{1F9E0}</span>
|
|
960
|
+
<div class="agent-toggle-info">
|
|
961
|
+
<span class="agent-toggle-name">${sk.name}</span>
|
|
962
|
+
<span class="agent-toggle-role" style="color:#34d399">${sk.description}</span>
|
|
963
|
+
</div>
|
|
964
|
+
${statusBadge(sk.status)}
|
|
965
|
+
<div class="agent-toggle-check">\u2713</div>
|
|
966
|
+
</div>
|
|
967
|
+
</label>`).join('\n');
|
|
968
|
+
const auditSection = s.audit.filter(f => f.type !== 'OK').length > 0 ? `
|
|
969
|
+
<div class="agent-audit-section">
|
|
970
|
+
<h3 class="agent-section-subtitle">\u{1F50D} Audit Findings</h3>
|
|
971
|
+
<div class="agent-audit-grid">
|
|
972
|
+
${s.audit.filter(f => f.type !== 'OK').map(f => {
|
|
973
|
+
const icon = f.type === 'MISSING' ? '\u274C' : f.type === 'IMPROVEMENT' ? '\u{1F4A1}' : '\u26A0\uFE0F';
|
|
974
|
+
const cls = f.type === 'MISSING' ? 'audit-missing' : 'audit-improvement';
|
|
975
|
+
return `<div class="agent-audit-item ${cls}">
|
|
976
|
+
<span class="audit-icon">${icon}</span>
|
|
977
|
+
<div class="audit-content">
|
|
978
|
+
<span class="audit-desc">${f.description}</span>
|
|
979
|
+
${f.suggestion ? `<span class="audit-suggestion">\u2192 ${f.suggestion}</span>` : ''}
|
|
980
|
+
</div>
|
|
981
|
+
</div>`;
|
|
982
|
+
}).join('\n')}
|
|
983
|
+
</div>
|
|
984
|
+
</div>` : '';
|
|
985
|
+
const stackPills = [
|
|
986
|
+
`\u{1F527} ${s.stack.primary}`,
|
|
987
|
+
`\u{1F4E6} ${s.stack.frameworks.length > 0 ? s.stack.frameworks.join(', ') : 'No framework'}`,
|
|
988
|
+
s.hasExistingAgents ? '\u{1F4C1} Existing .agent/' : '\u{1F4C1} New .agent/',
|
|
989
|
+
...(s.stack.hasBackend ? ['\u{1F519} Backend'] : []),
|
|
990
|
+
...(s.stack.hasFrontend ? ['\u{1F5A5}\uFE0F Frontend'] : []),
|
|
991
|
+
...(s.stack.hasMobile ? ['\u{1F4F1} Mobile'] : []),
|
|
992
|
+
...(s.stack.hasDatabase ? ['\u{1F5C4}\uFE0F Database'] : []),
|
|
993
|
+
];
|
|
994
|
+
const totalItems = s.suggestedAgents.length + s.suggestedRules.length + s.suggestedGuards.length + s.suggestedWorkflows.length + s.suggestedSkills.length;
|
|
995
|
+
// Status summary counts
|
|
996
|
+
const allItems = [...s.suggestedAgents, ...s.suggestedRules, ...s.suggestedGuards, ...s.suggestedWorkflows];
|
|
997
|
+
const keepCount = allItems.filter(i => i.status === 'KEEP').length;
|
|
998
|
+
const modifyCount = allItems.filter(i => i.status === 'MODIFY').length;
|
|
999
|
+
const createCount = allItems.filter(i => i.status === 'CREATE').length;
|
|
1000
|
+
return `
|
|
1001
|
+
<h2 class="section-title">\u{1F916} Agent System</h2>
|
|
1002
|
+
|
|
1003
|
+
<div class="card agent-system-card">
|
|
1004
|
+
<div class="agent-stack-banner">
|
|
1005
|
+
${stackPills.map(p => `<div class="stack-pill">${p}</div>`).join('\n ')}
|
|
1006
|
+
</div>
|
|
1007
|
+
|
|
1008
|
+
<div class="agent-status-legend">
|
|
1009
|
+
<span class="status-legend-item"><span class="legend-dot" style="background:#22c55e"></span> KEEP (${keepCount})</span>
|
|
1010
|
+
<span class="status-legend-item"><span class="legend-dot" style="background:#3b82f6"></span> MODIFY (${modifyCount})</span>
|
|
1011
|
+
<span class="status-legend-item"><span class="legend-dot" style="background:#f59e0b"></span> NEW (${createCount})</span>
|
|
1012
|
+
</div>
|
|
1013
|
+
|
|
1014
|
+
<div class="agent-controls">
|
|
1015
|
+
<button class="agent-ctrl-btn" onclick="toggleAll(true)">\u2705 Select All</button>
|
|
1016
|
+
<button class="agent-ctrl-btn" onclick="toggleAll(false)">\u2B1C Select None</button>
|
|
1017
|
+
<span class="agent-count-label"><span id="agentSelectedCount">${totalItems}</span> selected</span>
|
|
1018
|
+
</div>
|
|
1019
|
+
|
|
1020
|
+
<h3 class="agent-section-subtitle">\u{1F916} Agents</h3>
|
|
1021
|
+
<div class="agent-toggle-grid">
|
|
1022
|
+
${agentCards}
|
|
1023
|
+
</div>
|
|
1024
|
+
|
|
1025
|
+
<div class="agent-extras-grid">
|
|
1026
|
+
<div>
|
|
1027
|
+
<h3 class="agent-section-subtitle">\u{1F4CF} Rules</h3>
|
|
1028
|
+
<div class="agent-toggle-list">${ruleCards}</div>
|
|
1029
|
+
</div>
|
|
1030
|
+
<div>
|
|
1031
|
+
<h3 class="agent-section-subtitle">\u{1F6E1}\uFE0F Guards</h3>
|
|
1032
|
+
<div class="agent-toggle-list">${guardCards}</div>
|
|
1033
|
+
</div>
|
|
1034
|
+
<div>
|
|
1035
|
+
<h3 class="agent-section-subtitle">\u26A1 Workflows</h3>
|
|
1036
|
+
<div class="agent-toggle-list">${workflowCards}</div>
|
|
1037
|
+
</div>
|
|
1038
|
+
</div>
|
|
1039
|
+
|
|
1040
|
+
<h3 class="agent-section-subtitle">\u{1F9E0} Skills <span style="font-size:0.7rem;color:#94a3b8;font-weight:400">from skills.sh</span></h3>
|
|
1041
|
+
<div class="agent-toggle-grid">
|
|
1042
|
+
${skillCards}
|
|
1043
|
+
</div>
|
|
1044
|
+
|
|
1045
|
+
${auditSection}
|
|
1046
|
+
|
|
1047
|
+
<div class="agent-command-box">
|
|
1048
|
+
<div class="agent-command-header">
|
|
1049
|
+
<span>\u{1F4A1} Command to generate selected items:</span>
|
|
1050
|
+
<button class="agent-copy-btn" onclick="copyAgentCommand()">
|
|
1051
|
+
<span id="copyIcon">\u{1F4CB}</span> Copy
|
|
1052
|
+
</button>
|
|
1053
|
+
</div>
|
|
1054
|
+
<code id="agentCommandOutput" class="agent-command-code">${s.command}</code>
|
|
1055
|
+
</div>
|
|
1056
|
+
</div>
|
|
1057
|
+
|
|
1058
|
+
<style>
|
|
1059
|
+
.agent-system-card { padding: 1.5rem; }
|
|
1060
|
+
.agent-stack-banner { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 1.5rem; }
|
|
1061
|
+
.stack-pill { background: #1e293b; border: 1px solid #334155; border-radius: 99px; padding: 0.4rem 1rem; font-size: 0.8rem; color: #94a3b8; white-space: nowrap; }
|
|
1062
|
+
.agent-status-legend { display: flex; gap: 1.5rem; margin-bottom: 1rem; padding: 0.5rem 0; border-bottom: 1px solid #1e293b; }
|
|
1063
|
+
.status-legend-item { display: flex; align-items: center; gap: 0.4rem; font-size: 0.8rem; color: #94a3b8; }
|
|
1064
|
+
.agent-status-badge { display: inline-flex; align-items: center; gap: 0.25rem; padding: 0.15rem 0.5rem; border-radius: 99px; font-size: 0.65rem; font-weight: 700; flex-shrink: 0; letter-spacing: 0.03em; }
|
|
1065
|
+
.agent-toggle-desc { display: block; font-size: 0.65rem; color: #64748b; margin-top: 0.15rem; line-height: 1.3; }
|
|
1066
|
+
.agent-controls { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1.5rem; }
|
|
1067
|
+
.agent-ctrl-btn { background: #1e293b; border: 1px solid #334155; color: #e2e8f0; padding: 0.4rem 1rem; border-radius: 8px; font-size: 0.8rem; cursor: pointer; transition: all 0.2s; }
|
|
1068
|
+
.agent-ctrl-btn:hover { background: #334155; }
|
|
1069
|
+
.agent-count-label { color: #94a3b8; font-size: 0.85rem; margin-left: auto; }
|
|
1070
|
+
#agentSelectedCount { color: #c084fc; font-weight: 700; }
|
|
1071
|
+
.agent-section-subtitle { color: #e2e8f0; font-size: 1.05rem; font-weight: 700; margin: 1.25rem 0 0.75rem; }
|
|
1072
|
+
.agent-toggle-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 0.75rem; }
|
|
1073
|
+
.agent-toggle-card { cursor: pointer; transition: all 0.3s; }
|
|
1074
|
+
.agent-toggle-card input { display: none; }
|
|
1075
|
+
.agent-toggle-inner { display: flex; align-items: center; gap: 0.75rem; background: #1e293b; border: 2px solid #334155; border-radius: 12px; padding: 0.75rem 1rem; transition: all 0.3s; }
|
|
1076
|
+
.agent-toggle-card input:checked + .agent-toggle-inner { background: #1e1b4b; }
|
|
1077
|
+
.agent-toggle-icon { font-size: 1.3rem; flex-shrink: 0; }
|
|
1078
|
+
.agent-toggle-info { flex: 1; min-width: 0; }
|
|
1079
|
+
.agent-toggle-name { display: block; color: #e2e8f0; font-weight: 600; font-size: 0.85rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
1080
|
+
.agent-toggle-role { display: block; font-size: 0.7rem; margin-top: 0.15rem; }
|
|
1081
|
+
.agent-toggle-check { color: #334155; font-size: 1rem; flex-shrink: 0; transition: color 0.3s; }
|
|
1082
|
+
.agent-toggle-card input:checked + .agent-toggle-inner .agent-toggle-check { color: #818cf8; }
|
|
1083
|
+
.agent-toggle-card.mini .agent-toggle-inner { padding: 0.5rem 0.75rem; border-radius: 8px; }
|
|
1084
|
+
.agent-extras-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin-top: 0.5rem; }
|
|
1085
|
+
@media (max-width: 768px) { .agent-extras-grid { grid-template-columns: 1fr; } }
|
|
1086
|
+
.agent-toggle-list { display: flex; flex-direction: column; gap: 0.5rem; }
|
|
1087
|
+
.agent-audit-section { margin-top: 1.5rem; }
|
|
1088
|
+
.agent-audit-grid { display: flex; flex-direction: column; gap: 0.5rem; }
|
|
1089
|
+
.agent-audit-item { display: flex; gap: 0.75rem; align-items: flex-start; background: #1e293b; padding: 0.75rem 1rem; border-radius: 8px; }
|
|
1090
|
+
.agent-audit-item.audit-missing { border-left: 3px solid #ef4444; }
|
|
1091
|
+
.agent-audit-item.audit-improvement { border-left: 3px solid #fbbf24; }
|
|
1092
|
+
.audit-icon { font-size: 1rem; flex-shrink: 0; margin-top: 2px; }
|
|
1093
|
+
.audit-content { display: flex; flex-direction: column; gap: 0.25rem; }
|
|
1094
|
+
.audit-desc { color: #e2e8f0; font-size: 0.85rem; }
|
|
1095
|
+
.audit-suggestion { color: #94a3b8; font-size: 0.8rem; font-style: italic; }
|
|
1096
|
+
.agent-command-box { margin-top: 1.5rem; background: #0f172a; border-radius: 12px; border: 1px solid #334155; overflow: hidden; }
|
|
1097
|
+
.agent-command-header { display: flex; justify-content: space-between; align-items: center; padding: 0.75rem 1rem; background: #1e293b; font-size: 0.8rem; color: #94a3b8; }
|
|
1098
|
+
.agent-copy-btn { background: #c084fc; color: #0f172a; border: none; border-radius: 6px; padding: 0.4rem 0.8rem; cursor: pointer; font-size: 0.75rem; font-weight: 600; transition: all 0.2s; }
|
|
1099
|
+
.agent-copy-btn:hover { background: #a855f7; transform: scale(1.05); }
|
|
1100
|
+
.agent-command-code { display: block; padding: 1rem; color: #c084fc; font-size: 0.85rem; word-break: break-all; font-family: 'Fira Code', monospace; }
|
|
1101
|
+
</style>
|
|
1102
|
+
|
|
1103
|
+
<script>
|
|
1104
|
+
(function() {
|
|
1105
|
+
var basePath = ${JSON.stringify(s.command.replace('architect agents ', ''))};
|
|
1106
|
+
var totalItems = ${totalItems};
|
|
1107
|
+
function updateCommand() {
|
|
1108
|
+
var checks = document.querySelectorAll('.agent-check');
|
|
1109
|
+
var selected = { agents: [], rules: [], guards: [], workflows: [], skills: [] };
|
|
1110
|
+
var count = 0;
|
|
1111
|
+
checks.forEach(function(cb) { if (cb.checked) { selected[cb.dataset.type].push(cb.dataset.item); count++; } });
|
|
1112
|
+
document.getElementById('agentSelectedCount').textContent = count;
|
|
1113
|
+
var cmd;
|
|
1114
|
+
if (count === totalItems) { cmd = 'architect agents ' + basePath; }
|
|
1115
|
+
else if (count === 0) { cmd = '# No items selected'; }
|
|
1116
|
+
else {
|
|
1117
|
+
var parts = ['architect agents ' + basePath];
|
|
1118
|
+
if (selected.agents.length > 0) parts.push('--agents ' + selected.agents.join(','));
|
|
1119
|
+
if (selected.rules.length > 0) parts.push('--rules ' + selected.rules.join(','));
|
|
1120
|
+
if (selected.guards.length > 0) parts.push('--guards ' + selected.guards.join(','));
|
|
1121
|
+
if (selected.workflows.length > 0) parts.push('--workflows ' + selected.workflows.join(','));
|
|
1122
|
+
if (selected.skills.length > 0) parts.push('&& ' + selected.skills.map(function(sk){ return 'npx skills add ' + sk; }).join(' && '));
|
|
1123
|
+
cmd = parts.join(' ');
|
|
1124
|
+
}
|
|
1125
|
+
document.getElementById('agentCommandOutput').textContent = cmd;
|
|
1126
|
+
}
|
|
1127
|
+
document.querySelectorAll('.agent-check').forEach(function(cb) { cb.addEventListener('change', updateCommand); });
|
|
1128
|
+
window.toggleAll = function(state) { document.querySelectorAll('.agent-check').forEach(function(cb) { cb.checked = state; }); updateCommand(); };
|
|
1129
|
+
window.copyAgentCommand = function() { var cmd = document.getElementById('agentCommandOutput').textContent; navigator.clipboard.writeText(cmd).then(function() { var btn = document.getElementById('copyIcon'); btn.textContent = '\u2705'; setTimeout(function() { btn.textContent = '\ud83d\udccb'; }, 2000); }); };
|
|
1130
|
+
})();
|
|
604
1131
|
<\/script>`;
|
|
605
1132
|
}
|
|
606
1133
|
getStyles() {
|
|
@@ -617,7 +1144,50 @@ function animateCounter(el, target) {
|
|
|
617
1144
|
min-height: 100vh;
|
|
618
1145
|
}
|
|
619
1146
|
|
|
620
|
-
|
|
1147
|
+
html { scroll-behavior: smooth; }
|
|
1148
|
+
|
|
1149
|
+
/* ── Layout ── */
|
|
1150
|
+
.report-layout { display: flex; min-height: 100vh; }
|
|
1151
|
+
|
|
1152
|
+
.sidebar {
|
|
1153
|
+
position: sticky; top: 0; height: 100vh; width: 220px; min-width: 220px;
|
|
1154
|
+
background: linear-gradient(180deg, #0f172a 0%, #1e293b 100%);
|
|
1155
|
+
border-right: 1px solid #334155; padding: 1.5rem 0;
|
|
1156
|
+
display: flex; flex-direction: column; gap: 0.25rem;
|
|
1157
|
+
overflow-y: auto; z-index: 100;
|
|
1158
|
+
}
|
|
1159
|
+
.sidebar-title {
|
|
1160
|
+
font-size: 0.7rem; font-weight: 700; text-transform: uppercase;
|
|
1161
|
+
letter-spacing: 0.15em; color: #475569; padding: 0 1.25rem; margin-bottom: 0.75rem;
|
|
1162
|
+
}
|
|
1163
|
+
.sidebar-link {
|
|
1164
|
+
display: flex; align-items: center; gap: 0.5rem; padding: 0.6rem 1.25rem;
|
|
1165
|
+
color: #94a3b8; text-decoration: none; font-size: 0.8rem; font-weight: 500;
|
|
1166
|
+
border-left: 3px solid transparent; transition: all 0.2s;
|
|
1167
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
1168
|
+
}
|
|
1169
|
+
.sidebar-link:hover { color: #e2e8f0; background: #1e293b; border-left-color: #475569; }
|
|
1170
|
+
.sidebar-link.active { color: #c084fc; background: #c084fc10; border-left-color: #c084fc; font-weight: 700; }
|
|
1171
|
+
|
|
1172
|
+
.sidebar-toggle {
|
|
1173
|
+
display: none; position: fixed; bottom: 1.5rem; right: 1.5rem; z-index: 200;
|
|
1174
|
+
width: 48px; height: 48px; border-radius: 50%; border: none;
|
|
1175
|
+
background: #c084fc; color: #0f172a; font-size: 1.2rem; cursor: pointer;
|
|
1176
|
+
box-shadow: 0 4px 16px rgba(192,132,252,0.4); transition: all 0.2s;
|
|
1177
|
+
}
|
|
1178
|
+
.sidebar-toggle:hover { transform: scale(1.1); }
|
|
1179
|
+
|
|
1180
|
+
@media (max-width: 1024px) {
|
|
1181
|
+
.sidebar {
|
|
1182
|
+
position: fixed; left: -240px; top: 0; width: 240px; min-width: 240px;
|
|
1183
|
+
transition: left 0.3s ease; box-shadow: none;
|
|
1184
|
+
}
|
|
1185
|
+
.sidebar.sidebar-open { left: 0; box-shadow: 4px 0 24px rgba(0,0,0,0.5); }
|
|
1186
|
+
.sidebar-toggle { display: flex; align-items: center; justify-content: center; }
|
|
1187
|
+
.report-layout { flex-direction: column; }
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
.container { max-width: 1200px; margin: 0 auto; padding: 2rem; flex: 1; min-width: 0; }
|
|
621
1191
|
|
|
622
1192
|
/* ── Header ── */
|
|
623
1193
|
.header {
|
|
@@ -696,6 +1266,44 @@ function animateCounter(el, target) {
|
|
|
696
1266
|
display: flex; align-items: center; gap: 0.5rem;
|
|
697
1267
|
}
|
|
698
1268
|
|
|
1269
|
+
/* ── Section Accordion ── */
|
|
1270
|
+
.section-accordion {
|
|
1271
|
+
margin: 1.5rem 0; border: 1px solid #334155; border-radius: 16px;
|
|
1272
|
+
background: transparent; overflow: hidden;
|
|
1273
|
+
}
|
|
1274
|
+
.section-accordion-header {
|
|
1275
|
+
cursor: pointer; list-style: none; display: flex; align-items: center; gap: 0.75rem;
|
|
1276
|
+
font-size: 1.3rem; font-weight: 700; color: #e2e8f0;
|
|
1277
|
+
padding: 1.25rem 1.5rem; background: linear-gradient(135deg, #1e293b, #0f172a);
|
|
1278
|
+
border-bottom: 1px solid transparent; transition: all 0.3s; user-select: none;
|
|
1279
|
+
}
|
|
1280
|
+
.section-accordion-header:hover { background: linear-gradient(135deg, #334155, #1e293b); }
|
|
1281
|
+
.section-accordion[open] > .section-accordion-header { border-bottom-color: #334155; }
|
|
1282
|
+
.section-accordion-header::after {
|
|
1283
|
+
content: '\\25B6'; margin-left: auto; font-size: 0.8rem; color: #818cf8;
|
|
1284
|
+
transition: transform 0.3s;
|
|
1285
|
+
}
|
|
1286
|
+
.section-accordion[open] > .section-accordion-header::after { transform: rotate(90deg); }
|
|
1287
|
+
.section-accordion-header::-webkit-details-marker { display: none; }
|
|
1288
|
+
.section-accordion-body { padding: 0.5rem 0; }
|
|
1289
|
+
|
|
1290
|
+
/* ── Operations Accordion (inside refactoring steps) ── */
|
|
1291
|
+
.rstep-ops-accordion {
|
|
1292
|
+
margin: 0.75rem 0; border: 1px solid #1e293b; border-radius: 10px; overflow: hidden;
|
|
1293
|
+
}
|
|
1294
|
+
.rstep-ops-toggle {
|
|
1295
|
+
cursor: pointer; list-style: none; display: flex; align-items: center; gap: 0.5rem;
|
|
1296
|
+
font-size: 0.9rem; font-weight: 600; color: #94a3b8;
|
|
1297
|
+
padding: 0.75rem 1rem; background: #0f172a; transition: all 0.2s;
|
|
1298
|
+
}
|
|
1299
|
+
.rstep-ops-toggle:hover { background: #1e293b; color: #e2e8f0; }
|
|
1300
|
+
.rstep-ops-toggle::after {
|
|
1301
|
+
content: '\\25B6'; margin-left: auto; font-size: 0.65rem; color: #818cf8;
|
|
1302
|
+
transition: transform 0.3s;
|
|
1303
|
+
}
|
|
1304
|
+
.rstep-ops-accordion[open] > .rstep-ops-toggle::after { transform: rotate(90deg); }
|
|
1305
|
+
.rstep-ops-toggle::-webkit-details-marker { display: none; }
|
|
1306
|
+
|
|
699
1307
|
/* ── Cards ── */
|
|
700
1308
|
.card {
|
|
701
1309
|
background: #1e293b; border-radius: 16px; border: 1px solid #334155;
|
|
@@ -705,17 +1313,42 @@ function animateCounter(el, target) {
|
|
|
705
1313
|
|
|
706
1314
|
/* ── Graph ── */
|
|
707
1315
|
.graph-card { padding: 1rem; }
|
|
1316
|
+
.graph-controls { margin-bottom: 0.75rem; }
|
|
708
1317
|
.graph-legend {
|
|
709
1318
|
display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 0.5rem;
|
|
710
1319
|
justify-content: center;
|
|
711
1320
|
}
|
|
712
1321
|
.legend-item { display: flex; align-items: center; gap: 4px; font-size: 0.75rem; color: #94a3b8; }
|
|
713
|
-
.legend-dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; }
|
|
1322
|
+
.legend-dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; flex-shrink: 0; }
|
|
1323
|
+
.graph-filters {
|
|
1324
|
+
display: flex; gap: 0.75rem; align-items: center; flex-wrap: wrap;
|
|
1325
|
+
justify-content: center; margin-top: 0.5rem;
|
|
1326
|
+
}
|
|
1327
|
+
.graph-search {
|
|
1328
|
+
background: #0f172a; border: 1px solid #334155; border-radius: 8px;
|
|
1329
|
+
padding: 0.4rem 0.75rem; color: #e2e8f0; font-size: 0.8rem;
|
|
1330
|
+
outline: none; width: 180px; transition: border-color 0.2s;
|
|
1331
|
+
}
|
|
1332
|
+
.graph-search:focus { border-color: #818cf8; }
|
|
1333
|
+
.graph-layer-filters {
|
|
1334
|
+
display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;
|
|
1335
|
+
}
|
|
1336
|
+
.graph-filter-check {
|
|
1337
|
+
display: flex; align-items: center; gap: 4px;
|
|
1338
|
+
font-size: 0.75rem; color: #94a3b8; cursor: pointer;
|
|
1339
|
+
}
|
|
1340
|
+
.graph-filter-check input { width: 14px; height: 14px; accent-color: #818cf8; }
|
|
1341
|
+
.graph-limit-notice {
|
|
1342
|
+
text-align: center; font-size: 0.75rem; color: #f59e0b;
|
|
1343
|
+
background: #f59e0b15; padding: 0.3rem 0.75rem; border-radius: 6px;
|
|
1344
|
+
margin-top: 0.5rem;
|
|
1345
|
+
}
|
|
714
1346
|
.graph-hint {
|
|
715
1347
|
text-align: center; font-size: 0.75rem; color: #475569; margin-top: 0.5rem;
|
|
716
1348
|
font-style: italic;
|
|
717
1349
|
}
|
|
718
|
-
#dep-graph svg { background: rgba(0,0,0,0.2); border-radius: 12px; }
|
|
1350
|
+
#dep-graph svg { background: rgba(0,0,0,0.2); border-radius: 12px; cursor: grab; }
|
|
1351
|
+
#dep-graph svg:active { cursor: grabbing; }
|
|
719
1352
|
|
|
720
1353
|
/* ── Layers Grid ── */
|
|
721
1354
|
.layers-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1rem; }
|
|
@@ -765,12 +1398,102 @@ function animateCounter(el, target) {
|
|
|
765
1398
|
.footer a { color: #818cf8; text-decoration: none; }
|
|
766
1399
|
.footer a:hover { text-decoration: underline; }
|
|
767
1400
|
|
|
1401
|
+
/* ── Refactoring Plan ── */
|
|
1402
|
+
.refactor-score { padding: 2rem; }
|
|
1403
|
+
.refactor-score-pair {
|
|
1404
|
+
display: flex; align-items: center; justify-content: center; gap: 1.5rem;
|
|
1405
|
+
margin-bottom: 2rem; flex-wrap: wrap;
|
|
1406
|
+
}
|
|
1407
|
+
.rscore-box { text-align: center; }
|
|
1408
|
+
.rscore-num { font-size: 3rem; font-weight: 900; line-height: 1; }
|
|
1409
|
+
.rscore-label { font-size: 0.8rem; color: #94a3b8; text-transform: uppercase; letter-spacing: 1px; }
|
|
1410
|
+
.rscore-improvement { font-size: 1.3rem; font-weight: 700; }
|
|
1411
|
+
|
|
1412
|
+
.refactor-legend { display: flex; gap: 1rem; margin-bottom: 0.5rem; }
|
|
1413
|
+
.rlegend-tag { font-size: 0.75rem; padding: 0.2rem 0.6rem; border-radius: 6px; }
|
|
1414
|
+
.rlegend-tag.rbefore { background: rgba(255,255,255,0.05); color: #94a3b8; }
|
|
1415
|
+
.rlegend-tag.rafter { background: rgba(129,140,248,0.2); color: #818cf8; }
|
|
1416
|
+
|
|
1417
|
+
.refactor-metric-name { width: 100px; font-size: 0.8rem; text-transform: uppercase; color: #94a3b8; font-weight: 600; }
|
|
1418
|
+
.refactor-metric-bars { flex: 1; position: relative; height: 30px; }
|
|
1419
|
+
.rbar-before, .rbar-after {
|
|
1420
|
+
position: absolute; left: 0; height: 14px; border-radius: 4px;
|
|
1421
|
+
display: flex; align-items: center; padding-left: 6px;
|
|
1422
|
+
font-size: 0.7rem; font-weight: 600;
|
|
1423
|
+
}
|
|
1424
|
+
.rbar-before { top: 0; }
|
|
1425
|
+
.rbar-after { top: 15px; }
|
|
1426
|
+
.refactor-metric-diff { width: 50px; text-align: right; font-weight: 700; font-size: 0.85rem; }
|
|
1427
|
+
|
|
1428
|
+
.refactor-stats-row {
|
|
1429
|
+
display: flex; gap: 1rem; margin-bottom: 1rem; flex-wrap: wrap;
|
|
1430
|
+
}
|
|
1431
|
+
.rstat {
|
|
1432
|
+
background: #1e293b; border: 1px solid #334155; border-radius: 99px;
|
|
1433
|
+
padding: 0.4rem 1rem; font-size: 0.85rem; color: #94a3b8; font-weight: 500;
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
.priority-bar {
|
|
1437
|
+
display: flex; border-radius: 12px; overflow: hidden; height: 32px; margin-bottom: 2rem;
|
|
1438
|
+
}
|
|
1439
|
+
.prio-seg {
|
|
1440
|
+
display: flex; align-items: center; justify-content: center;
|
|
1441
|
+
font-size: 0.75rem; font-weight: 600;
|
|
1442
|
+
}
|
|
1443
|
+
.prio-critical { background: #ef444430; color: #ef4444; }
|
|
1444
|
+
.prio-high { background: #f59e0b30; color: #f59e0b; }
|
|
1445
|
+
.prio-medium { background: #3b82f630; color: #60a5fa; }
|
|
1446
|
+
.prio-low { background: #22c55e30; color: #22c55e; }
|
|
1447
|
+
|
|
1448
|
+
.refactor-roadmap { display: flex; flex-direction: column; gap: 1rem; }
|
|
1449
|
+
.rstep-card {
|
|
1450
|
+
background: #1e293b; border-radius: 16px; border: 1px solid #334155;
|
|
1451
|
+
padding: 1.5rem; transition: border-color 0.2s;
|
|
1452
|
+
}
|
|
1453
|
+
.rstep-card:hover { border-color: #818cf8; }
|
|
1454
|
+
.rstep-header { display: flex; gap: 1rem; margin-bottom: 1rem; }
|
|
1455
|
+
.rstep-number {
|
|
1456
|
+
width: 40px; height: 40px; border-radius: 50%;
|
|
1457
|
+
background: linear-gradient(135deg, #818cf8, #c084fc);
|
|
1458
|
+
display: flex; align-items: center; justify-content: center;
|
|
1459
|
+
font-weight: 800; font-size: 1rem; color: white; flex-shrink: 0;
|
|
1460
|
+
}
|
|
1461
|
+
.rstep-info { flex: 1; }
|
|
1462
|
+
.rstep-title-row { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
|
|
1463
|
+
.rstep-title-row h3 { font-size: 1.1rem; font-weight: 700; }
|
|
1464
|
+
.rstep-desc { color: #94a3b8; font-size: 0.9rem; margin-top: 0.3rem; }
|
|
1465
|
+
.tier-badge {
|
|
1466
|
+
background: #818cf815; color: #818cf8; border: 1px solid #818cf830;
|
|
1467
|
+
padding: 0.15rem 0.5rem; border-radius: 99px; font-size: 0.65rem; font-weight: 600;
|
|
1468
|
+
}
|
|
1469
|
+
.rstep-details { margin-top: 0.5rem; }
|
|
1470
|
+
.rstep-details summary { cursor: pointer; color: #818cf8; font-size: 0.85rem; font-weight: 500; }
|
|
1471
|
+
.rstep-rationale { color: #64748b; font-size: 0.85rem; margin-top: 0.3rem; font-style: italic; }
|
|
1472
|
+
|
|
1473
|
+
.rstep-ops { margin-top: 1rem; padding-top: 1rem; border-top: 1px solid #334155; }
|
|
1474
|
+
.rstep-ops h4 { font-size: 0.85rem; color: #94a3b8; margin-bottom: 0.5rem; }
|
|
1475
|
+
.rop { display: flex; align-items: flex-start; gap: 0.5rem; margin-bottom: 0.5rem; flex-wrap: wrap; }
|
|
1476
|
+
.rop-icon { font-size: 0.9rem; }
|
|
1477
|
+
.rop-badge { padding: 0.1rem 0.4rem; border-radius: 6px; font-size: 0.65rem; font-weight: 700; }
|
|
1478
|
+
.rop-path { background: #0f172a; padding: 1px 6px; border-radius: 4px; font-size: 0.8rem; color: #c084fc; }
|
|
1479
|
+
.rop-arrow { color: #818cf8; font-weight: 700; }
|
|
1480
|
+
.rop-desc { width: 100%; color: #64748b; font-size: 0.8rem; padding-left: 1.8rem; }
|
|
1481
|
+
|
|
1482
|
+
.rstep-impact { margin-top: 0.5rem; }
|
|
1483
|
+
.rstep-impact h4 { font-size: 0.85rem; color: #94a3b8; margin-bottom: 0.3rem; }
|
|
1484
|
+
.rimpact-tags { display: flex; gap: 0.5rem; flex-wrap: wrap; }
|
|
1485
|
+
.rimpact-tag {
|
|
1486
|
+
background: #22c55e10; color: #22c55e; border: 1px solid #22c55e30;
|
|
1487
|
+
padding: 0.2rem 0.6rem; border-radius: 8px; font-size: 0.75rem; font-weight: 500;
|
|
1488
|
+
}
|
|
1489
|
+
|
|
768
1490
|
/* ── Responsive ── */
|
|
769
1491
|
@media (max-width: 768px) {
|
|
770
1492
|
.score-hero { flex-direction: column; gap: 1.5rem; }
|
|
771
1493
|
.score-breakdown { grid-template-columns: 1fr; }
|
|
772
1494
|
.header h1 { font-size: 1.8rem; }
|
|
773
1495
|
.container { padding: 1rem; }
|
|
1496
|
+
.refactor-score-pair { flex-direction: column; }
|
|
774
1497
|
}
|
|
775
1498
|
|
|
776
1499
|
/* ── Print ── */
|