@vibecodeqa/cli 0.35.9 → 0.36.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +5 -4
- package/dist/diagrams/graph.js +1 -1
- package/dist/monitor.js +87 -3
- package/dist/pr-comment.js +4 -2
- package/dist/report/html.js +2 -2
- package/dist/report/pages.d.ts +1 -1
- package/dist/report/pages.js +36 -2
- package/dist/report/styles.d.ts +1 -1
- package/dist/report/styles.js +6 -0
- package/dist/report/svg.js +7 -7
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -832,14 +832,15 @@ async function main() {
|
|
|
832
832
|
if (!patterns?.length)
|
|
833
833
|
continue;
|
|
834
834
|
c.issues = c.issues.filter((i) => {
|
|
835
|
-
if (!i.file)
|
|
835
|
+
if (!i.file || typeof i.file !== "string")
|
|
836
836
|
return true;
|
|
837
|
+
const f = i.file;
|
|
837
838
|
return !patterns.some((p) => {
|
|
838
839
|
if (p.endsWith("/**"))
|
|
839
|
-
return
|
|
840
|
+
return f.startsWith(p.slice(0, -3) + "/");
|
|
840
841
|
if (p.startsWith("*"))
|
|
841
|
-
return
|
|
842
|
-
return
|
|
842
|
+
return f.endsWith(p.slice(1));
|
|
843
|
+
return f.startsWith(p);
|
|
843
844
|
});
|
|
844
845
|
});
|
|
845
846
|
}
|
package/dist/diagrams/graph.js
CHANGED
|
@@ -124,7 +124,7 @@ export function generateArchSVG(details) {
|
|
|
124
124
|
nodesSvg += `<text x="${pos.x + size + 3}" y="${pos.y + 3}" fill="${labelColor}" font-size="${fontSize}" font-weight="${isGod ? "700" : "400"}">${name}</text>`;
|
|
125
125
|
}
|
|
126
126
|
if (!isLarge && (fanIn > 2 || fanOut > 5)) {
|
|
127
|
-
nodesSvg += `<text x="${pos.x + size + 5}" y="${pos.y + 13}" fill="#
|
|
127
|
+
nodesSvg += `<text x="${pos.x + size + 5}" y="${pos.y + 13}" fill="#888" font-size="7">${fanIn}\u2190 ${fanOut}\u2192</text>`;
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
// Legend
|
package/dist/monitor.js
CHANGED
|
@@ -252,6 +252,61 @@ function CheckDetail({ check, height, cursor, copied }) {
|
|
|
252
252
|
return (_jsxs(Box, { flexDirection: "column", marginBottom: 0, children: [_jsxs(Text, { children: [_jsx(Text, { color: sel ? "white" : "gray", children: sel ? "▸" : " " }), _jsxs(Text, { color: sc(iss.severity), bold: true, children: [iss.severity[0].toUpperCase(), " "] }), iss.file && _jsxs(Text, { color: "cyan", children: [String(iss.file), iss.line ? `:${iss.line}` : "", " "] }), iss.rule && _jsxs(Text, { dimColor: true, children: ["(", iss.rule, ")"] })] }), _jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { color: sel ? "white" : "gray", children: " " }), _jsx(Text, { color: sel ? "white" : undefined, children: iss.message })] })] }, idx));
|
|
253
253
|
}), remaining > 0 && _jsxs(Text, { dimColor: true, children: [" +", remaining, " more (\u2193 to scroll)"] })] }))] }));
|
|
254
254
|
}
|
|
255
|
+
function readSourceContext(cwd, file, line, rule) {
|
|
256
|
+
if (!file || typeof file !== "string")
|
|
257
|
+
return null;
|
|
258
|
+
const fullPath = join(cwd, file);
|
|
259
|
+
try {
|
|
260
|
+
if (!existsSync(fullPath))
|
|
261
|
+
return null;
|
|
262
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
263
|
+
const allLines = content.split("\n");
|
|
264
|
+
const target = (line ?? 1) - 1; // 0-indexed
|
|
265
|
+
const contextRadius = 8;
|
|
266
|
+
const start = Math.max(0, target - contextRadius);
|
|
267
|
+
const end = Math.min(allLines.length, target + contextRadius + 1);
|
|
268
|
+
// Determine which lines to highlight based on rule
|
|
269
|
+
const highlightSet = new Set();
|
|
270
|
+
highlightSet.add(target);
|
|
271
|
+
// For multi-line issues, highlight the block
|
|
272
|
+
if (rule === "empty-catch" || rule === "fallback-catch" || rule === "no-assertions" || rule === "empty-test") {
|
|
273
|
+
// Highlight from target to closing brace
|
|
274
|
+
let depth = 0;
|
|
275
|
+
for (let i = target; i < Math.min(target + 15, allLines.length); i++) {
|
|
276
|
+
highlightSet.add(i);
|
|
277
|
+
depth += (allLines[i].match(/\{/g) || []).length;
|
|
278
|
+
depth -= (allLines[i].match(/\}/g) || []).length;
|
|
279
|
+
if (depth <= 0 && i > target)
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
else if (rule === "duplicate-code" || rule === "commented-out-code") {
|
|
284
|
+
// Highlight a block of lines
|
|
285
|
+
for (let i = target; i < Math.min(target + 6, allLines.length); i++) {
|
|
286
|
+
highlightSet.add(i);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
else if (rule === "high-complexity" || rule === "long-function") {
|
|
290
|
+
// Highlight function signature + a few lines
|
|
291
|
+
for (let i = target; i < Math.min(target + 3, allLines.length); i++) {
|
|
292
|
+
highlightSet.add(i);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const lines = [];
|
|
296
|
+
for (let i = start; i < end; i++) {
|
|
297
|
+
lines.push({ num: i + 1, text: allLines[i], highlight: highlightSet.has(i) });
|
|
298
|
+
}
|
|
299
|
+
return { lines, filePath: file };
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function IssueDetail({ issue, checkName, cwd, height, copied }) {
|
|
306
|
+
const ctx = readSourceContext(cwd, issue.file, issue.line, issue.rule);
|
|
307
|
+
const bodyHeight = height - 8;
|
|
308
|
+
return (_jsxs(Box, { flexDirection: "column", height: height, paddingX: 1, overflowY: "hidden", children: [_jsx(Text, { bold: true, color: "magenta", children: " \u25C8 Issue Detail" }), _jsxs(Text, { children: [_jsxs(Text, { color: sc(issue.severity), bold: true, children: [" ", issue.severity.toUpperCase(), " "] }), _jsx(Text, { dimColor: true, children: checkName }), issue.rule && _jsxs(Text, { dimColor: true, children: [" \u00B7 ", issue.rule] }), copied && _jsx(Text, { color: "green", bold: true, children: " \u2713 Copied!" })] }), _jsxs(Text, { wrap: "wrap", children: [" ", issue.message] }), issue.file && (_jsxs(Text, { color: "cyan", children: [" ", issue.file, issue.line ? `:${issue.line}` : ""] })), _jsx(Text, { children: " " }), ctx ? (_jsxs(Box, { flexDirection: "column", height: bodyHeight, overflowY: "hidden", children: [_jsxs(Text, { dimColor: true, children: [" \u2500\u2500\u2500 ", ctx.filePath, " \u2500\u2500\u2500"] }), ctx.lines.slice(0, bodyHeight - 2).map((l) => (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: l.highlight ? "yellow" : "gray", children: l.highlight ? "▸" : " " }), _jsxs(Text, { dimColor: true, children: [String(l.num).padStart(4), "\u2502"] }), _jsx(Text, { color: l.highlight ? "white" : undefined, children: l.text })] }, l.num))), _jsx(Text, { dimColor: true, children: " \u2500\u2500\u2500" })] })) : (_jsxs(Text, { dimColor: true, children: [" Source not available", issue.file ? ` (${issue.file})` : ""] }))] }));
|
|
309
|
+
}
|
|
255
310
|
function MonitorApp({ cwd }) {
|
|
256
311
|
const { exit } = useApp();
|
|
257
312
|
const { stdout } = useStdout();
|
|
@@ -371,10 +426,18 @@ function MonitorApp({ cwd }) {
|
|
|
371
426
|
exit();
|
|
372
427
|
return;
|
|
373
428
|
}
|
|
374
|
-
// Esc: drill up or quit
|
|
429
|
+
// Esc: drill up one level or quit
|
|
375
430
|
if (key.escape) {
|
|
376
431
|
if (mode.view === "config") {
|
|
377
432
|
setPendingCfg(null);
|
|
433
|
+
setMode({ view: "dashboard" });
|
|
434
|
+
setCursor(0);
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
if (mode.view === "issue-detail") {
|
|
438
|
+
setMode({ view: "check-detail", checkName: mode.checkName });
|
|
439
|
+
setCursor(mode.issueIdx);
|
|
440
|
+
return;
|
|
378
441
|
}
|
|
379
442
|
if (mode.view !== "dashboard") {
|
|
380
443
|
setMode({ view: "dashboard" });
|
|
@@ -468,7 +531,7 @@ function MonitorApp({ cwd }) {
|
|
|
468
531
|
return;
|
|
469
532
|
}
|
|
470
533
|
}
|
|
471
|
-
// ── Check detail: ↑↓ scroll, Enter copies
|
|
534
|
+
// ── Check detail: ↑↓ scroll, Enter drills into issue, y copies prompt ──
|
|
472
535
|
if (mode.view === "check-detail") {
|
|
473
536
|
const check = state.checks.find((c) => c.name === mode.checkName);
|
|
474
537
|
if (check) {
|
|
@@ -477,6 +540,10 @@ function MonitorApp({ cwd }) {
|
|
|
477
540
|
if (key.downArrow)
|
|
478
541
|
setCursor((c) => Math.min(check.issues.length - 1, c + 1));
|
|
479
542
|
if (key.return && check.issues[cursor]) {
|
|
543
|
+
setMode({ view: "issue-detail", checkName: mode.checkName, issueIdx: cursor });
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
if (input === "y" && check.issues[cursor]) {
|
|
480
547
|
const prompt = buildFixPrompt(check.name, check.issues[cursor]);
|
|
481
548
|
if (copyToClipboard(prompt)) {
|
|
482
549
|
setCopied(true);
|
|
@@ -486,6 +553,18 @@ function MonitorApp({ cwd }) {
|
|
|
486
553
|
}
|
|
487
554
|
}
|
|
488
555
|
}
|
|
556
|
+
// ── Issue detail: y copies prompt ──
|
|
557
|
+
if (mode.view === "issue-detail") {
|
|
558
|
+
const check = state.checks.find((c) => c.name === mode.checkName);
|
|
559
|
+
if (check && check.issues[mode.issueIdx] && input === "y") {
|
|
560
|
+
const prompt = buildFixPrompt(check.name, check.issues[mode.issueIdx]);
|
|
561
|
+
if (copyToClipboard(prompt)) {
|
|
562
|
+
setCopied(true);
|
|
563
|
+
addLog(`Copied fix prompt`, "info");
|
|
564
|
+
setTimeout(() => setCopied(false), 2000);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
489
568
|
// ── Trends: ↑↓ scroll ──
|
|
490
569
|
if (mode.view === "trends") {
|
|
491
570
|
if (key.upArrow)
|
|
@@ -497,9 +576,14 @@ function MonitorApp({ cwd }) {
|
|
|
497
576
|
const proj = basename(cwd);
|
|
498
577
|
const p = monCfg.panels;
|
|
499
578
|
// ── Render views ──
|
|
579
|
+
if (mode.view === "issue-detail") {
|
|
580
|
+
const check = state.checks.find((c) => c.name === mode.checkName);
|
|
581
|
+
const issue = check?.issues[mode.issueIdx];
|
|
582
|
+
return (_jsxs(Box, { flexDirection: "column", height: rows, children: [_jsx(Header, { proj: proj, stack: stack, workspace: workspace, state: state }), issue ? (_jsx(IssueDetail, { issue: issue, checkName: mode.checkName, cwd: cwd, height: rows - 3, copied: copied })) : (_jsx(Text, { dimColor: true, children: " Issue not found" })), _jsx(Box, { paddingX: 1, children: _jsx(Text, { dimColor: true, children: "Esc back \u00B7 y copy fix prompt \u00B7 q quit" }) })] }));
|
|
583
|
+
}
|
|
500
584
|
if (mode.view === "check-detail") {
|
|
501
585
|
const check = state.checks.find((c) => c.name === mode.checkName);
|
|
502
|
-
return (_jsxs(Box, { flexDirection: "column", height: rows, children: [_jsx(Header, { proj: proj, stack: stack, workspace: workspace, state: state }), check ? _jsx(CheckDetail, { check: check, height: rows - 3, cursor: cursor, copied: copied }) : _jsx(Text, { dimColor: true, children: " Check not found" }), _jsx(Box, { paddingX: 1, children: _jsx(Text, { dimColor: true, children: "Esc back \u00B7 \u2191\u2193 select \u00B7 Enter copy
|
|
586
|
+
return (_jsxs(Box, { flexDirection: "column", height: rows, children: [_jsx(Header, { proj: proj, stack: stack, workspace: workspace, state: state }), check ? _jsx(CheckDetail, { check: check, height: rows - 3, cursor: cursor, copied: copied }) : _jsx(Text, { dimColor: true, children: " Check not found" }), _jsx(Box, { paddingX: 1, children: _jsx(Text, { dimColor: true, children: "Esc back \u00B7 \u2191\u2193 select \u00B7 Enter view source \u00B7 y copy prompt \u00B7 q quit" }) })] }));
|
|
503
587
|
}
|
|
504
588
|
if (mode.view === "trends") {
|
|
505
589
|
return (_jsx(Box, { flexDirection: "column", height: rows, children: _jsx(TrendsScreen, { cwd: cwd, height: rows }) }));
|
package/dist/pr-comment.js
CHANGED
|
@@ -28,8 +28,10 @@ function detectPR(cwd) {
|
|
|
28
28
|
const event = JSON.parse(readFileSync(eventPath, "utf-8"));
|
|
29
29
|
const pr = event.pull_request || event.issue;
|
|
30
30
|
if (pr?.number && process.env.GITHUB_REPOSITORY) {
|
|
31
|
-
const
|
|
32
|
-
|
|
31
|
+
const parts = process.env.GITHUB_REPOSITORY.split("/");
|
|
32
|
+
if (parts.length >= 2) {
|
|
33
|
+
return { owner: parts[0], repo: parts[1], prNumber: pr.number };
|
|
34
|
+
}
|
|
33
35
|
}
|
|
34
36
|
}
|
|
35
37
|
catch {
|
package/dist/report/html.js
CHANGED
|
@@ -87,7 +87,7 @@ export function generatePages(report, historyDir) {
|
|
|
87
87
|
const meta = getCheckMeta(c.name);
|
|
88
88
|
const badge = premium
|
|
89
89
|
? `<span style="color:#6366f1">PRO</span>`
|
|
90
|
-
: `<span style="color:${sk ? "
|
|
90
|
+
: `<span style="color:${sk ? "var(--dim)" : gc(c.grade)}">${sk ? "\u2014" : c.grade} ${sk ? "" : c.score}</span>`;
|
|
91
91
|
sb += `<a class="side-check" href="${cs.file}#${c.name}" title="${e(meta.label)}">${badge} ${e(meta.label)}</a>`;
|
|
92
92
|
}
|
|
93
93
|
}
|
|
@@ -102,7 +102,7 @@ export function generatePages(report, historyDir) {
|
|
|
102
102
|
for (let i = 0; i < GROUPS.length; i++) {
|
|
103
103
|
const g = GROUPS[i];
|
|
104
104
|
const cs = catScores[i];
|
|
105
|
-
pages.set(g.file, w(g.id, categoryPage(cs, fl, allChecks)));
|
|
105
|
+
pages.set(g.file, w(g.id, categoryPage(cs, fl, allChecks, report.meta.cwd)));
|
|
106
106
|
}
|
|
107
107
|
// Feature Map (Pro page — reads dead-patterns check details)
|
|
108
108
|
const deadPatternsCheck = checkMap.get("dead-patterns");
|
package/dist/report/pages.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export interface FileEntry {
|
|
|
16
16
|
}
|
|
17
17
|
type FL = (path: string, line?: number) => string;
|
|
18
18
|
export declare function overviewPage(report: VibeReport, active: CheckResult[], totalIssues: number, catScores: CatScore[], allChecks: CheckResult[], topFiles: FileEntry[], fl: FL, historyDir?: string): string;
|
|
19
|
-
export declare function categoryPage(cs: CatScore, fl: FL, allChecks?: CheckResult[]): string;
|
|
19
|
+
export declare function categoryPage(cs: CatScore, fl: FL, allChecks?: CheckResult[], cwd?: string): string;
|
|
20
20
|
export declare function issuesPage(allChecks: CheckResult[], totalIssues: number, fl: FL): string;
|
|
21
21
|
export declare function filesPage(topFiles: FileEntry[], fileIssues: Map<string, {
|
|
22
22
|
errors: number;
|
package/dist/report/pages.js
CHANGED
|
@@ -1,10 +1,36 @@
|
|
|
1
1
|
/** Page renderers for the HTML report. */
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
2
4
|
import { getCheckMeta } from "../check-meta.js";
|
|
3
5
|
import { buildCoverageMapInput, generateCoverageMap } from "../diagrams/coverage.js";
|
|
4
6
|
import { loadHistory } from "../history.js";
|
|
5
7
|
import { generateArchSVG, generateDSM, generateLayerDiagram, generatePackageDiagram, generateSequenceDiagram, } from "../runners/architecture.js";
|
|
6
8
|
import { det, e, gc, pc } from "./components.js";
|
|
7
9
|
import { buildPyramid, buildRadar, buildRing, buildTimeline } from "./svg.js";
|
|
10
|
+
/** Read source lines around an issue for inline display in the report. */
|
|
11
|
+
function readSourceSnippet(cwd, file, line, radius = 4) {
|
|
12
|
+
try {
|
|
13
|
+
const fullPath = join(cwd, file);
|
|
14
|
+
if (!existsSync(fullPath))
|
|
15
|
+
return null;
|
|
16
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
17
|
+
const lines = content.split("\n");
|
|
18
|
+
const target = line - 1;
|
|
19
|
+
const start = Math.max(0, target - radius);
|
|
20
|
+
const end = Math.min(lines.length, target + radius + 1);
|
|
21
|
+
let html = "";
|
|
22
|
+
for (let i = start; i < end; i++) {
|
|
23
|
+
const num = String(i + 1).padStart(4);
|
|
24
|
+
const hl = i === target;
|
|
25
|
+
const cls = hl ? "src-hl" : "src-ln";
|
|
26
|
+
html += `<div class="${cls}"><span class="src-num">${num}</span>${e(lines[i])}</div>`;
|
|
27
|
+
}
|
|
28
|
+
return html;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
8
34
|
// ── Overview ──────────────────────────────────────────────────────────
|
|
9
35
|
export function overviewPage(report, active, totalIssues, catScores, allChecks, topFiles, fl, historyDir) {
|
|
10
36
|
const hero = `<div class="hero">
|
|
@@ -114,7 +140,7 @@ ${fileHotspotsHtml}
|
|
|
114
140
|
<div class="stack">${stackHtml}</div>`;
|
|
115
141
|
}
|
|
116
142
|
// ── Single category page ──────────────────────────────────────────
|
|
117
|
-
export function categoryPage(cs, fl, allChecks) {
|
|
143
|
+
export function categoryPage(cs, fl, allChecks, cwd) {
|
|
118
144
|
const checkSections = cs.checks
|
|
119
145
|
.map((c) => {
|
|
120
146
|
const meta = getCheckMeta(c.name);
|
|
@@ -148,7 +174,15 @@ export function categoryPage(cs, fl, allChecks) {
|
|
|
148
174
|
const snippetBtn = iss.snippet
|
|
149
175
|
? `<button class="cp-btn" data-prompt="${e(iss.snippet)}" title="Copy snippet to search">\ud83d\udd0d</button>`
|
|
150
176
|
: "";
|
|
151
|
-
|
|
177
|
+
// Source code snippet (collapsible)
|
|
178
|
+
let srcBlock = "";
|
|
179
|
+
if (cwd && iss.line && typeof iss.file === "string") {
|
|
180
|
+
const src = readSourceSnippet(cwd, iss.file, iss.line);
|
|
181
|
+
if (src) {
|
|
182
|
+
srcBlock = `<div class="src-block">${src}</div>`;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
issuesHtml += `<div class="ir ${iss.severity}"><span class="is">${iss.severity[0].toUpperCase()}</span>${iss.line ? `<span class="il">${iss.line}</span>` : ""}<span class="im">${e(iss.message)}</span>${iss.rule ? `<span class="iru">${e(iss.rule)}</span>` : ""}${snippetBtn}<button class="cp-btn" data-prompt="${e(prompt)}" title="Copy fix prompt">\ud83d\udccb</button></div>${srcBlock}`;
|
|
152
186
|
}
|
|
153
187
|
issuesHtml += `</div>`;
|
|
154
188
|
}
|
package/dist/report/styles.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** All CSS for the HTML report, extracted for maintainability. */
|
|
2
|
-
export declare const CSS = "\n:root{--bg:#09090b;--card:#111115;--border:#1e1e24;--text:#e5e5e5;--muted:#6b7280;--pass:#22c55e;--fail:#ef4444;--warn:#eab308;--info:#6366f1;--accent:#818cf8;--side-w:200px;--top-h:42px;--nav-bg:#0c0c0fdd;--side-bg:#0c0c0f;--hover:#14141a;--dim:#555;--card-alt:#0d0d12}\n[data-theme=\"light\"]{--bg:#f5f5f7;--card:#ffffff;--border:#e2e4e9;--text:#1a1a2e;--muted:#64748b;--pass:#16a34a;--fail:#dc2626;--warn:#ca8a04;--info:#4f46e5;--accent:#4f46e5;--nav-bg:#ffffffee;--side-bg:#fafafa;--hover:#eef0f5;--dim:#94a3b8;--card-alt:#f0f0f5}\nhtml{font-size:17px}\n*{margin:0;padding:0;box-sizing:border-box}\nbody{font-family:\"Inter\",system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.5}\ncode{font-family:\"SF Mono\",Menlo,monospace;font-size:0.85em}\n\n/* \u2500\u2500 Top nav \u2500\u2500 */\n.top{position:sticky;top:0;z-index:30;background:var(--nav-bg);backdrop-filter:blur(12px);border-bottom:1px solid var(--border);padding:0 1.5rem;display:flex;align-items:center;height:var(--top-h)}\n.logo{font-weight:800;font-size:1rem;margin-right:0.5rem;flex-shrink:0;text-decoration:none;color:var(--text)}\n.logo span{color:var(--accent)}\n.nav-project{font-size:0.72rem;color:var(--muted);font-weight:600;margin-right:1rem;padding:0.2rem 0.5rem;background:var(--card);border:1px solid var(--border);border-radius:4px;flex-shrink:0}\n.nav-scroll{display:flex;align-items:center;gap:0;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none;flex:1}\n.nav-scroll::-webkit-scrollbar{display:none}\n.tn{padding:0 0.7rem;font-size:0.78rem;color:var(--muted);text-decoration:none;border-bottom:2px solid transparent;transition:all 0.15s;white-space:nowrap;line-height:var(--top-h)}\n.tn:hover{color:var(--text)}\n.tn.active{color:var(--text);border-bottom-color:var(--accent)}\n.hamburger{display:none;background:none;border:none;color:var(--muted);font-size:1.3rem;cursor:pointer;padding:0 0.4rem;line-height:var(--top-h)}\n\n/* \u2500\u2500 Sidebar \u2500\u2500 */\n.side{position:fixed;top:var(--top-h);left:0;bottom:0;width:var(--side-w);background:var(--side-bg);border-right:1px solid var(--border);overflow-y:auto;padding:0.6rem 0;font-size:0.7rem;z-index:20}\n.side-section{padding:0.3rem 0;border-bottom:1px solid var(--border)}\n.side-section:last-child{border-bottom:none}\n.side-label{padding:0.2rem 0.8rem;font-size:0.6rem;text-transform:uppercase;letter-spacing:0.05em;color:var(--dim);font-weight:600}\n.side-score{font-size:1.4rem;font-weight:900;padding:0.2rem 0.8rem}\n.side-cat{display:block;padding:0.3rem 0.8rem;color:var(--muted);font-weight:600;cursor:pointer;text-decoration:none;font-size:0.72rem}\n.side-cat:hover{background:var(--hover);color:var(--text)}\n.side-cat-active{color:var(--text);font-weight:700;border-left:2px solid var(--accent);padding-left:calc(0.8rem - 2px)}\n.side-cat-title{padding:0.3rem 0.8rem;font-size:0.65rem;text-transform:uppercase;letter-spacing:0.04em;color:var(--accent);font-weight:700}\n.side-check{display:block;padding:0.15rem 0.8rem 0.15rem 0.8rem;color:var(--muted);cursor:pointer;text-decoration:none;font-size:0.65rem}\n.side-check:hover{color:var(--text);background:#14141a}\n.side-check span{display:inline-block;min-width:2.5rem;font-weight:700;font-size:0.6rem}\n.side-stat{padding:0.15rem 0.8rem;font-size:0.7rem;color:var(--muted)}\n.side-stat span{font-weight:800;font-size:0.8rem}\n.side-views{padding-top:0.3rem}\n.side-views .side-check{padding-left:0.8rem}\n\n/* \u2500\u2500 Content \u2500\u2500 */\n.content{margin-left:var(--side-w);padding:1.5rem 2rem;max-width:960px}\n\n/* \u2500\u2500 Overview \u2500\u2500 */\n.dash{display:flex;gap:2rem;margin-bottom:2rem;align-items:center;flex-wrap:wrap}\n.hero{display:flex;align-items:center;gap:1rem}\n.hero svg{width:100px;height:100px}\n.hc{display:flex;flex-direction:column}\n.hg{font-size:2.5rem;font-weight:900;line-height:1}\n.hs{font-size:1rem;font-weight:600}\n.hd{font-size:0.68rem;color:var(--muted)}\n.radar{flex:1;display:flex;justify-content:center}\n.radar svg{max-width:240px;width:100%}\n.cats{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:0.6rem;margin-bottom:2rem}\n.cc{background:var(--card);border:1px solid var(--border);border-radius:0.6rem;padding:0.8rem;transition:border-color 0.15s;text-decoration:none;color:var(--text);display:block}\n.cc:hover{border-color:var(--accent)}\n.cc-s{font-size:1.8rem;font-weight:900}\n.cc-l{font-size:0.75rem;color:var(--muted)}\n.cc-m{margin-top:0.3rem;display:flex;gap:0.25rem}\n.mc{font-size:0.65rem;font-weight:800}\nh3{font-size:0.85rem;color:var(--muted);text-transform:uppercase;letter-spacing:0.04em;margin-bottom:0.5rem}\n\n/* \u2500\u2500 Overview sections \u2500\u2500 */\n.ov-section{margin-bottom:1.5rem}\n.ov-issue{font-size:0.68rem;font-family:\"SF Mono\",monospace;padding:0.2rem 0;display:flex;gap:0.4rem;align-items:baseline;border-bottom:1px solid var(--border)}\n.ov-issue .is{flex-shrink:0}\n.ov-issue.error .is{color:var(--fail)}\n.ov-issue.warning .is{color:var(--warn)}\n.ov-check{color:var(--muted);width:70px;flex-shrink:0;font-size:0.62rem}\n.ov-loc{color:var(--accent);flex-shrink:0;font-size:0.62rem;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.ov-msg{flex:1;word-break:break-word}\n.ov-link{display:block;margin-top:0.5rem;font-size:0.72rem;color:var(--accent);text-decoration:none}\n.ov-link:hover{text-decoration:underline}\n\n/* \u2500\u2500 Timeline \u2500\u2500 */\n.timeline{margin:0.5rem 0;overflow-x:auto}\n.timeline svg{max-width:100%}\n\n/* \u2500\u2500 Bar chart \u2500\u2500 */\n.bars{margin-bottom:1.5rem}\n.brow{display:flex;align-items:center;gap:0.4rem;margin-bottom:0.25rem;font-size:0.72rem}\n.bl{width:90px;text-align:right;color:var(--muted);flex-shrink:0}\n.bb{flex:1;height:14px;background:var(--card);border-radius:3px;overflow:hidden;border:1px solid var(--border)}\n.bf{height:100%;border-radius:2px}\n.bv{width:36px;font-weight:700;font-size:0.68rem}\n.stack{display:flex;gap:0.35rem;flex-wrap:wrap;margin-top:1rem}\n.stack span{background:var(--card);border:1px solid var(--border);padding:0.1rem 0.45rem;border-radius:9999px;font-size:0.62rem;color:var(--muted)}\n\n/* \u2500\u2500 Workspace / Repo structure \u2500\u2500 */\n.ws-info{display:flex;gap:0.6rem;align-items:center;flex-wrap:wrap;margin-bottom:0.5rem;font-size:0.72rem;color:var(--muted)}\n.ws-badge{background:var(--accent);color:#fff;padding:0.15rem 0.5rem;border-radius:4px;font-size:0.65rem;font-weight:700}\n.ws-pkgs{display:flex;flex-direction:column;gap:0.15rem}\n.ws-pkg{display:flex;gap:0.6rem;align-items:center;font-size:0.68rem;padding:0.15rem 0.4rem;background:var(--card);border-radius:4px}\n.ws-path{font-family:monospace;color:var(--text);min-width:140px}\n.ws-name{color:var(--muted);flex:1}\n.ws-flags{color:var(--muted);font-size:0.6rem}\n.ws-more{font-size:0.62rem;color:var(--muted);padding:0.2rem 0.4rem}\n\n/* \u2500\u2500 Category pages \u2500\u2500 */\n.cat-head{margin-bottom:0.3rem}\n.bar2{height:4px;background:var(--card);border-radius:2px;margin-bottom:1.5rem;overflow:hidden}\n.bf2{height:100%;border-radius:2px}\n.check-section{margin-bottom:2.5rem;padding-top:0.5rem;border-top:1px solid var(--border)}\n.check-section:first-of-type{border-top:none}\n\n/* \u2500\u2500 Check detail \u2500\u2500 */\n.ch-head{display:flex;align-items:center;gap:0.7rem;margin-bottom:0.8rem}\n.ch-g{font-size:2rem;font-weight:900}\n.ch-s{display:block;font-size:0.7rem;color:var(--muted)}\n.pri{font-size:0.62rem;font-weight:700;text-transform:uppercase;letter-spacing:0.04em;padding:0.15rem 0.5rem;border-radius:9999px;border:1px solid currentColor;flex-shrink:0}\n.info-panel{background:var(--card-alt);border:1px solid var(--border);border-radius:0.5rem;padding:0.7rem 0.9rem;margin-bottom:1rem;font-size:0.72rem;line-height:1.6}\n.ip-row{margin-bottom:0.4rem;display:flex;gap:0.5rem}\n.ip-row:last-child{margin-bottom:0}\n.ip-label{color:var(--accent);font-weight:700;min-width:2.5rem;flex-shrink:0}\n.skip-r{color:var(--muted);font-style:italic;font-size:0.78rem}\n.kvs{display:flex;gap:0.6rem;flex-wrap:wrap;margin-bottom:1rem}\n.kv{background:var(--card);border:1px solid var(--border);border-radius:0.4rem;padding:0.3rem 0.6rem;font-size:0.7rem}\n.k{color:var(--muted);margin-right:0.3rem}\n.v{font-weight:600}\n\n/* \u2500\u2500 Issue list grouped by file \u2500\u2500 */\n.iss-list{margin-top:1rem}\n.fg{margin-bottom:0.8rem}\n.fn{font-size:0.72rem;font-weight:600;font-family:\"SF Mono\",monospace;padding:0.3rem 0;border-bottom:1px solid var(--border);margin-bottom:0.2rem;display:flex;align-items:center;gap:0.5rem}\n.fc{background:var(--border);border-radius:9999px;padding:0 0.4rem;font-size:0.6rem;color:var(--muted)}\n.ir{font-size:0.65rem;font-family:\"SF Mono\",monospace;padding:0.12rem 0 0.12rem 0.5rem;display:flex;gap:0.4rem;align-items:baseline}\n.is{font-weight:800;font-size:0.55rem;width:0.9rem;text-align:center;border-radius:2px;flex-shrink:0}\n.ir.error .is{color:var(--fail);background:#ef444418}\n.ir.warning .is{color:var(--warn);background:#eab30818}\n.ir.info .is{color:var(--info);background:#6366f118}\n.il{color:var(--accent);min-width:2rem;flex-shrink:0}\n.im{flex:1;word-break:break-word}\n.iru{color:var(--dim);font-size:0.55rem}\n\n/* \u2500\u2500 All issues table \u2500\u2500 */\n.isf{color:var(--muted);font-size:0.75rem;margin-bottom:0.8rem}\n.it{width:100%;border-collapse:collapse;font-size:0.68rem}\n.it th{text-align:left;padding:0.35rem 0.4rem;color:var(--muted);font-size:0.62rem;text-transform:uppercase;border-bottom:1px solid var(--border)}\n.it td{padding:0.25rem 0.4rem;border-bottom:1px solid var(--border);font-family:\"SF Mono\",monospace;font-size:0.62rem}\n.it tr.error .is2{color:var(--fail)}\n.it tr.warning .is2{color:var(--warn)}\n.is2{font-weight:800;width:1rem}\n.ic2{color:var(--muted);width:70px}\n.il2{color:var(--muted)}\n.iru2{color:var(--dim);font-size:0.58rem}\n\n/* \u2500\u2500 File health \u2500\u2500 */\n.fr{display:flex;align-items:center;gap:0.5rem;margin-bottom:0.3rem;font-size:0.7rem}\n.ff{width:200px;font-family:\"SF Mono\",monospace;font-size:0.65rem;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.fb{flex:1;height:12px;background:var(--card);border-radius:3px;overflow:hidden;border:1px solid var(--border)}\n.fbf{height:100%;border-radius:2px}\n.fv{width:50px;font-size:0.65rem;color:var(--muted);flex-shrink:0}\n.hm-row{display:flex;align-items:center;gap:0.5rem;margin-bottom:0.2rem;font-size:0.7rem}\n.hm-name{width:200px;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:\"SF Mono\",monospace;font-size:0.65rem}\n.hm-bar{height:14px;border-radius:3px;min-width:4px}\n.hm-count{color:var(--muted);font-size:0.65rem;flex-shrink:0;min-width:50px}\n.hm-checks{font-size:0.58rem;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n\n/* \u2500\u2500 Premium cards \u2500\u2500 */\n.pro-card{background:linear-gradient(135deg,#0f0f1a 0%,#13131f 100%);border:1px solid #2a2a3d;border-radius:0.75rem;padding:1.5rem;position:relative;overflow:hidden}\n.pro-card::before{content:\"\";position:absolute;top:-50%;right:-50%;width:200%;height:200%;background:radial-gradient(circle,#6366f108 0%,transparent 70%);pointer-events:none}\n.pro-badge{display:inline-block;background:linear-gradient(135deg,#6366f1,#818cf8);color:#fff;font-size:0.6rem;font-weight:800;padding:0.15rem 0.5rem;border-radius:9999px;letter-spacing:0.06em;margin-bottom:0.6rem}\n.pro-desc{color:var(--muted);font-size:0.78rem;line-height:1.6;margin-bottom:0.8rem}\n.pro-cta{color:#6366f1;font-size:0.72rem;font-weight:600;margin-top:1rem}\n.sn-pro{opacity:0.7}\n\n/* \u2500\u2500 Trends page \u2500\u2500 */\n.trend-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:1rem;margin-top:0.5rem}\n.trend-card{background:var(--card);border:1px solid var(--border);border-radius:10px;padding:0.8rem}\n.trend-header{display:flex;align-items:center;gap:0.5rem;margin-bottom:0.3rem}\n.trend-name{font-size:0.78rem;font-weight:700;flex:1}\n.trend-score{font-size:1.1rem;font-weight:900}\n.trend-chart{overflow:hidden}\n.trend-chart svg{width:100%;height:60px}\n.trend-table{margin-bottom:1.5rem}\n.trend-row{display:flex;align-items:center;gap:0.5rem;padding:0.25rem 0;border-bottom:1px solid var(--border);font-size:0.75rem}\n.trend-row-name{flex:1;font-weight:600}\n.trend-row-val{width:2rem;text-align:center;color:var(--muted)}\n.trend-row-arrow{color:var(--muted);font-size:0.6rem}\n.trend-row-delta{width:2.5rem;text-align:right;font-weight:700}\n\n.footer{text-align:center;color:var(--muted);font-size:0.58rem;margin-top:2rem;padding:0.8rem 0;border-top:1px solid var(--border)}\n.footer a{color:var(--muted)}\n.muted{color:var(--muted)}\n.deeper-tools code{background:var(--border);padding:0.1rem 0.4rem;border-radius:4px;font-size:0.62rem;color:var(--accent);margin-right:0.3rem}\n.flink{color:var(--accent);text-decoration:none;font-family:\"SF Mono\",monospace}.flink:hover{text-decoration:underline}\n.arch-svg{margin:1rem 0;overflow-x:auto;-webkit-overflow-scrolling:touch}\n.arch-svg svg{border-radius:8px}\n.cp-btn{background:none;border:none;cursor:pointer;font-size:0.6rem;opacity:0.3;padding:0 0.2rem;flex-shrink:0}.cp-btn:hover{opacity:1}\n.ir:hover .cp-btn{opacity:0.6}\n\n/* \u2500\u2500 Mobile: hamburger collapses both navs \u2500\u2500 */\n@media(max-width:768px){\n.hamburger{display:block}\n.nav-scroll{display:none}\n.nav-scroll.open{display:flex;position:absolute;top:var(--top-h);left:0;right:0;background:var(--bg);border-bottom:1px solid var(--border);flex-wrap:wrap;padding:0.3rem 0.5rem;z-index:25}\n.side{display:none}\n.side.open{display:block;z-index:25}\n.top{padding:0 0.8rem}\n.logo{font-size:0.85rem;margin-right:0.5rem}\n.content{margin-left:0;padding:0.8rem}\n.cats{grid-template-columns:1fr 1fr}\n.dash{flex-direction:column;gap:1rem}\n.hero svg{width:80px;height:80px}\n.hg{font-size:2rem}\n.radar svg{max-width:180px}\n.bl{width:60px;font-size:0.62rem}\n.bv{width:30px;font-size:0.6rem}\n.it{display:block;overflow-x:auto;-webkit-overflow-scrolling:touch}\n.ff{width:120px;font-size:0.58rem}\n.hm-name{width:120px;font-size:0.58rem}\n.hm-checks{display:none}\n.ov-check{width:50px}\n.ov-loc{max-width:120px}\n.ir{font-size:0.6rem}\n.ch-head{flex-wrap:wrap}\n.ch-g{font-size:1.5rem}\n.info-panel{font-size:0.68rem;padding:0.5rem 0.6rem}\n.ip-row{flex-direction:column;gap:0.1rem}\n.kvs{gap:0.4rem}\n.kv{font-size:0.62rem;padding:0.2rem 0.4rem}\n.arch-svg svg{min-width:400px}\n}\n@media(max-width:480px){\n.cats{grid-template-columns:1fr}\n.tn{padding:0 0.4rem;font-size:0.65rem}\n.ff{width:90px}\n.hm-name{width:90px}\n.ov-check{display:none}\n}\n\n/* \u2500\u2500 Feature Map (Pro) \u2500\u2500 */\n.fm-header{margin-bottom:1.5rem}\n.fm-header h2{display:flex;align-items:center;gap:0.6rem}\n.fm-stats{display:flex;gap:1.5rem;margin-bottom:2rem;padding:1rem 1.2rem;background:var(--card);border:1px solid var(--border);border-radius:12px}\n.fm-stat{display:flex;flex-direction:column;align-items:center}\n.fm-stat-n{font-size:1.6rem;font-weight:900;line-height:1.2}\n.fm-stat-l{font-size:0.65rem;color:var(--muted);text-transform:uppercase;letter-spacing:0.04em;font-weight:600}\n.fm-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1rem}\n.fm-card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:1.2rem;transition:border-color 0.15s}\n.fm-card:hover{border-color:#333}\n.fm-card-issue{border-color:#eab30830;background:linear-gradient(135deg,var(--card) 0%,#1a1a0f 100%)}\n.fm-card-top{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:0.4rem}\n.fm-card-label{font-weight:800;font-size:0.95rem}\n.fm-card-desc{font-size:0.72rem;color:var(--muted);margin-bottom:0.5rem;line-height:1.4}\n.fm-card-dir{font-size:0.65rem;color:var(--dim);font-family:\"SF Mono\",monospace;margin-bottom:0.6rem}\n.fm-card-badge{font-size:0.6rem;font-weight:700;padding:0.15rem 0.5rem;border-radius:9999px;white-space:nowrap}\n.fm-ok{background:#22c55e18;color:var(--pass)}\n.fm-warn{background:#eab30818;color:var(--warn)}\n.fm-info{background:#6366f118;color:var(--info)}\n.fm-card-files{display:flex;flex-direction:column;gap:0.15rem;margin-bottom:0.6rem}\n.fm-file{font-size:0.68rem;color:var(--muted);font-family:\"SF Mono\",monospace}\n.fm-file a{color:var(--accent);text-decoration:none}\n.fm-file a:hover{text-decoration:underline}\n.fm-more{color:var(--dim);font-style:italic}\n.fm-findings{margin-top:0.6rem;padding-top:0.6rem;border-top:1px solid var(--border);display:flex;flex-direction:column;gap:0.3rem}\n.fm-finding{display:flex;align-items:baseline;gap:0.4rem;font-size:0.68rem;line-height:1.4}\n.fm-f-sev{font-weight:800;font-size:0.6rem;width:1rem;flex-shrink:0}\n.fm-f-warn .fm-f-sev{color:var(--warn)}\n.fm-f-info .fm-f-sev{color:var(--info)}\n.fm-f-loc{color:var(--dim);font-family:\"SF Mono\",monospace;flex-shrink:0}\n.fm-f-loc a{color:var(--accent);text-decoration:none}\n.fm-f-msg{color:var(--text)}\n.fm-f-rule{color:var(--dim);font-size:0.6rem;font-family:\"SF Mono\",monospace}\n\n/* Teaser (no Pro key) */\n.fm-teaser{margin-top:1.5rem}\n.fm-teaser-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:1rem;margin-bottom:2rem}\n.fm-card-blur{filter:blur(3px);opacity:0.5;pointer-events:none;user-select:none}\n.fm-cta{text-align:center;padding:2rem;background:linear-gradient(135deg,#0f0f1a,#13131f);border:1px solid #2a2a3d;border-radius:12px}\n.fm-cta code{background:#1a1a2e;padding:0.2rem 0.5rem;border-radius:4px;font-size:0.8rem}\n@media(max-width:640px){\n.fm-grid,.fm-teaser-grid{grid-template-columns:1fr}\n.fm-stats{flex-wrap:wrap;gap:1rem}\n}\n\n/* \u2500\u2500 Preferences panel \u2500\u2500 */\n.prefs-btn{background:none;border:1px solid var(--border);color:var(--muted);font-size:0.72rem;cursor:pointer;padding:0.2rem 0.5rem;border-radius:6px;margin-left:auto;flex-shrink:0;font-family:inherit;line-height:1.4}\n.prefs-btn:hover{color:var(--text);border-color:var(--dim)}\n.prefs-panel{display:none;position:absolute;top:var(--top-h);right:1rem;background:var(--card);border:1px solid var(--border);border-radius:10px;padding:0.8rem 1rem;z-index:40;min-width:200px;box-shadow:0 8px 30px #0008}\n.prefs-panel.open{display:block}\n.prefs-label{font-size:0.6rem;text-transform:uppercase;letter-spacing:0.05em;color:var(--dim);font-weight:600;margin-bottom:0.3rem}\n.prefs-label:not(:first-child){margin-top:0.7rem}\n.prefs-row{display:flex;gap:0.3rem}\n.prefs-opt{background:var(--card-alt);border:1px solid var(--border);color:var(--muted);font-size:0.68rem;padding:0.25rem 0.6rem;border-radius:6px;cursor:pointer;font-family:inherit;transition:all 0.1s}\n.prefs-opt:hover{color:var(--text);border-color:var(--dim)}\n.prefs-opt.active{background:var(--accent);color:#fff;border-color:var(--accent)}\n[data-theme=\"light\"] .prefs-panel{box-shadow:0 8px 30px #0002}\n";
|
|
2
|
+
export declare const CSS = "\n:root{--bg:#09090b;--card:#111115;--border:#1e1e24;--text:#e5e5e5;--muted:#6b7280;--pass:#22c55e;--fail:#ef4444;--warn:#eab308;--info:#6366f1;--accent:#818cf8;--side-w:200px;--top-h:42px;--nav-bg:#0c0c0fdd;--side-bg:#0c0c0f;--hover:#14141a;--dim:#555;--card-alt:#0d0d12}\n[data-theme=\"light\"]{--bg:#f5f5f7;--card:#ffffff;--border:#e2e4e9;--text:#1a1a2e;--muted:#64748b;--pass:#16a34a;--fail:#dc2626;--warn:#ca8a04;--info:#4f46e5;--accent:#4f46e5;--nav-bg:#ffffffee;--side-bg:#fafafa;--hover:#eef0f5;--dim:#94a3b8;--card-alt:#f0f0f5}\nhtml{font-size:17px}\n*{margin:0;padding:0;box-sizing:border-box}\nbody{font-family:\"Inter\",system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.5}\ncode{font-family:\"SF Mono\",Menlo,monospace;font-size:0.85em}\n\n/* \u2500\u2500 Top nav \u2500\u2500 */\n.top{position:sticky;top:0;z-index:30;background:var(--nav-bg);backdrop-filter:blur(12px);border-bottom:1px solid var(--border);padding:0 1.5rem;display:flex;align-items:center;height:var(--top-h)}\n.logo{font-weight:800;font-size:1rem;margin-right:0.5rem;flex-shrink:0;text-decoration:none;color:var(--text)}\n.logo span{color:var(--accent)}\n.nav-project{font-size:0.72rem;color:var(--muted);font-weight:600;margin-right:1rem;padding:0.2rem 0.5rem;background:var(--card);border:1px solid var(--border);border-radius:4px;flex-shrink:0}\n.nav-scroll{display:flex;align-items:center;gap:0;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none;flex:1}\n.nav-scroll::-webkit-scrollbar{display:none}\n.tn{padding:0 0.7rem;font-size:0.78rem;color:var(--muted);text-decoration:none;border-bottom:2px solid transparent;transition:all 0.15s;white-space:nowrap;line-height:var(--top-h)}\n.tn:hover{color:var(--text)}\n.tn.active{color:var(--text);border-bottom-color:var(--accent)}\n.hamburger{display:none;background:none;border:none;color:var(--muted);font-size:1.3rem;cursor:pointer;padding:0 0.4rem;line-height:var(--top-h)}\n\n/* \u2500\u2500 Sidebar \u2500\u2500 */\n.side{position:fixed;top:var(--top-h);left:0;bottom:0;width:var(--side-w);background:var(--side-bg);border-right:1px solid var(--border);overflow-y:auto;padding:0.6rem 0;font-size:0.7rem;z-index:20}\n.side-section{padding:0.3rem 0;border-bottom:1px solid var(--border)}\n.side-section:last-child{border-bottom:none}\n.side-label{padding:0.2rem 0.8rem;font-size:0.6rem;text-transform:uppercase;letter-spacing:0.05em;color:var(--dim);font-weight:600}\n.side-score{font-size:1.4rem;font-weight:900;padding:0.2rem 0.8rem}\n.side-cat{display:block;padding:0.3rem 0.8rem;color:var(--muted);font-weight:600;cursor:pointer;text-decoration:none;font-size:0.72rem}\n.side-cat:hover{background:var(--hover);color:var(--text)}\n.side-cat-active{color:var(--text);font-weight:700;border-left:2px solid var(--accent);padding-left:calc(0.8rem - 2px)}\n.side-cat-title{padding:0.3rem 0.8rem;font-size:0.65rem;text-transform:uppercase;letter-spacing:0.04em;color:var(--accent);font-weight:700}\n.side-check{display:block;padding:0.15rem 0.8rem 0.15rem 0.8rem;color:var(--muted);cursor:pointer;text-decoration:none;font-size:0.65rem}\n.side-check:hover{color:var(--text);background:#14141a}\n.side-check span{display:inline-block;min-width:2.5rem;font-weight:700;font-size:0.6rem}\n.side-stat{padding:0.15rem 0.8rem;font-size:0.7rem;color:var(--muted)}\n.side-stat span{font-weight:800;font-size:0.8rem}\n.side-views{padding-top:0.3rem}\n.side-views .side-check{padding-left:0.8rem}\n\n/* \u2500\u2500 Content \u2500\u2500 */\n.content{margin-left:var(--side-w);padding:1.5rem 2rem;max-width:960px}\n\n/* \u2500\u2500 Overview \u2500\u2500 */\n.dash{display:flex;gap:2rem;margin-bottom:2rem;align-items:center;flex-wrap:wrap}\n.hero{display:flex;align-items:center;gap:1rem}\n.hero svg{width:100px;height:100px}\n.hc{display:flex;flex-direction:column}\n.hg{font-size:2.5rem;font-weight:900;line-height:1}\n.hs{font-size:1rem;font-weight:600}\n.hd{font-size:0.68rem;color:var(--muted)}\n.radar{flex:1;display:flex;justify-content:center}\n.radar svg{max-width:240px;width:100%}\n.cats{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:0.6rem;margin-bottom:2rem}\n.cc{background:var(--card);border:1px solid var(--border);border-radius:0.6rem;padding:0.8rem;transition:border-color 0.15s;text-decoration:none;color:var(--text);display:block}\n.cc:hover{border-color:var(--accent)}\n.cc-s{font-size:1.8rem;font-weight:900}\n.cc-l{font-size:0.75rem;color:var(--muted)}\n.cc-m{margin-top:0.3rem;display:flex;gap:0.25rem}\n.mc{font-size:0.65rem;font-weight:800}\nh3{font-size:0.85rem;color:var(--muted);text-transform:uppercase;letter-spacing:0.04em;margin-bottom:0.5rem}\n\n/* \u2500\u2500 Overview sections \u2500\u2500 */\n.ov-section{margin-bottom:1.5rem}\n.ov-issue{font-size:0.68rem;font-family:\"SF Mono\",monospace;padding:0.2rem 0;display:flex;gap:0.4rem;align-items:baseline;border-bottom:1px solid var(--border)}\n.ov-issue .is{flex-shrink:0}\n.ov-issue.error .is{color:var(--fail)}\n.ov-issue.warning .is{color:var(--warn)}\n.ov-check{color:var(--muted);width:70px;flex-shrink:0;font-size:0.62rem}\n.ov-loc{color:var(--accent);flex-shrink:0;font-size:0.62rem;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.ov-msg{flex:1;word-break:break-word}\n.ov-link{display:block;margin-top:0.5rem;font-size:0.72rem;color:var(--accent);text-decoration:none}\n.ov-link:hover{text-decoration:underline}\n\n/* \u2500\u2500 Timeline \u2500\u2500 */\n.timeline{margin:0.5rem 0;overflow-x:auto}\n.timeline svg{max-width:100%}\n\n/* \u2500\u2500 Bar chart \u2500\u2500 */\n.bars{margin-bottom:1.5rem}\n.brow{display:flex;align-items:center;gap:0.4rem;margin-bottom:0.25rem;font-size:0.72rem}\n.bl{width:90px;text-align:right;color:var(--muted);flex-shrink:0}\n.bb{flex:1;height:14px;background:var(--card);border-radius:3px;overflow:hidden;border:1px solid var(--border)}\n.bf{height:100%;border-radius:2px}\n.bv{width:36px;font-weight:700;font-size:0.68rem}\n.stack{display:flex;gap:0.35rem;flex-wrap:wrap;margin-top:1rem}\n.stack span{background:var(--card);border:1px solid var(--border);padding:0.1rem 0.45rem;border-radius:9999px;font-size:0.62rem;color:var(--muted)}\n\n/* \u2500\u2500 Workspace / Repo structure \u2500\u2500 */\n.ws-info{display:flex;gap:0.6rem;align-items:center;flex-wrap:wrap;margin-bottom:0.5rem;font-size:0.72rem;color:var(--muted)}\n.ws-badge{background:var(--accent);color:#fff;padding:0.15rem 0.5rem;border-radius:4px;font-size:0.65rem;font-weight:700}\n.ws-pkgs{display:flex;flex-direction:column;gap:0.15rem}\n.ws-pkg{display:flex;gap:0.6rem;align-items:center;font-size:0.68rem;padding:0.15rem 0.4rem;background:var(--card);border-radius:4px}\n.ws-path{font-family:monospace;color:var(--text);min-width:140px}\n.ws-name{color:var(--muted);flex:1}\n.ws-flags{color:var(--muted);font-size:0.6rem}\n.ws-more{font-size:0.62rem;color:var(--muted);padding:0.2rem 0.4rem}\n\n/* \u2500\u2500 Category pages \u2500\u2500 */\n.cat-head{margin-bottom:0.3rem}\n.bar2{height:4px;background:var(--card);border-radius:2px;margin-bottom:1.5rem;overflow:hidden}\n.bf2{height:100%;border-radius:2px}\n.check-section{margin-bottom:2.5rem;padding-top:0.5rem;border-top:1px solid var(--border)}\n.check-section:first-of-type{border-top:none}\n\n/* \u2500\u2500 Check detail \u2500\u2500 */\n.ch-head{display:flex;align-items:center;gap:0.7rem;margin-bottom:0.8rem}\n.ch-g{font-size:2rem;font-weight:900}\n.ch-s{display:block;font-size:0.7rem;color:var(--muted)}\n.pri{font-size:0.62rem;font-weight:700;text-transform:uppercase;letter-spacing:0.04em;padding:0.15rem 0.5rem;border-radius:9999px;border:1px solid currentColor;flex-shrink:0}\n.info-panel{background:var(--card-alt);border:1px solid var(--border);border-radius:0.5rem;padding:0.7rem 0.9rem;margin-bottom:1rem;font-size:0.72rem;line-height:1.6}\n.ip-row{margin-bottom:0.4rem;display:flex;gap:0.5rem}\n.ip-row:last-child{margin-bottom:0}\n.ip-label{color:var(--accent);font-weight:700;min-width:2.5rem;flex-shrink:0}\n.skip-r{color:var(--muted);font-style:italic;font-size:0.78rem}\n.kvs{display:flex;gap:0.6rem;flex-wrap:wrap;margin-bottom:1rem}\n.kv{background:var(--card);border:1px solid var(--border);border-radius:0.4rem;padding:0.3rem 0.6rem;font-size:0.7rem}\n.k{color:var(--muted);margin-right:0.3rem}\n.v{font-weight:600}\n\n/* \u2500\u2500 Issue list grouped by file \u2500\u2500 */\n.iss-list{margin-top:1rem}\n.fg{margin-bottom:0.8rem}\n.fn{font-size:0.72rem;font-weight:600;font-family:\"SF Mono\",monospace;padding:0.3rem 0;border-bottom:1px solid var(--border);margin-bottom:0.2rem;display:flex;align-items:center;gap:0.5rem}\n.fc{background:var(--border);border-radius:9999px;padding:0 0.4rem;font-size:0.6rem;color:var(--muted)}\n.ir{font-size:0.65rem;font-family:\"SF Mono\",monospace;padding:0.12rem 0 0.12rem 0.5rem;display:flex;gap:0.4rem;align-items:baseline}\n.is{font-weight:800;font-size:0.55rem;width:0.9rem;text-align:center;border-radius:2px;flex-shrink:0}\n.ir.error .is{color:var(--fail);background:#ef444418}\n.ir.warning .is{color:var(--warn);background:#eab30818}\n.ir.info .is{color:var(--info);background:#6366f118}\n.il{color:var(--accent);min-width:2rem;flex-shrink:0}\n.im{flex:1;word-break:break-word}\n.iru{color:var(--dim);font-size:0.55rem}\n\n/* \u2500\u2500 Source code snippets \u2500\u2500 */\n.src-block{background:var(--card-alt);border:1px solid var(--border);border-radius:6px;margin:0.3rem 0 0.5rem 0.5rem;padding:0.3rem 0;font-family:\"SF Mono\",Menlo,monospace;font-size:0.62rem;line-height:1.6;overflow-x:auto}\n.src-ln{padding:0 0.5rem;white-space:pre}\n.src-hl{padding:0 0.5rem;white-space:pre;background:#eab30815;border-left:2px solid var(--warn)}\n.src-num{color:var(--dim);margin-right:0.5rem;user-select:none;display:inline-block;min-width:2.5rem;text-align:right}\n\n/* \u2500\u2500 All issues table \u2500\u2500 */\n.isf{color:var(--muted);font-size:0.75rem;margin-bottom:0.8rem}\n.it{width:100%;border-collapse:collapse;font-size:0.68rem}\n.it th{text-align:left;padding:0.35rem 0.4rem;color:var(--muted);font-size:0.62rem;text-transform:uppercase;border-bottom:1px solid var(--border)}\n.it td{padding:0.25rem 0.4rem;border-bottom:1px solid var(--border);font-family:\"SF Mono\",monospace;font-size:0.62rem}\n.it tr.error .is2{color:var(--fail)}\n.it tr.warning .is2{color:var(--warn)}\n.is2{font-weight:800;width:1rem}\n.ic2{color:var(--muted);width:70px}\n.il2{color:var(--muted)}\n.iru2{color:var(--dim);font-size:0.58rem}\n\n/* \u2500\u2500 File health \u2500\u2500 */\n.fr{display:flex;align-items:center;gap:0.5rem;margin-bottom:0.3rem;font-size:0.7rem}\n.ff{width:200px;font-family:\"SF Mono\",monospace;font-size:0.65rem;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.fb{flex:1;height:12px;background:var(--card);border-radius:3px;overflow:hidden;border:1px solid var(--border)}\n.fbf{height:100%;border-radius:2px}\n.fv{width:50px;font-size:0.65rem;color:var(--muted);flex-shrink:0}\n.hm-row{display:flex;align-items:center;gap:0.5rem;margin-bottom:0.2rem;font-size:0.7rem}\n.hm-name{width:200px;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:\"SF Mono\",monospace;font-size:0.65rem}\n.hm-bar{height:14px;border-radius:3px;min-width:4px}\n.hm-count{color:var(--muted);font-size:0.65rem;flex-shrink:0;min-width:50px}\n.hm-checks{font-size:0.58rem;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n\n/* \u2500\u2500 Premium cards \u2500\u2500 */\n.pro-card{background:linear-gradient(135deg,#0f0f1a 0%,#13131f 100%);border:1px solid #2a2a3d;border-radius:0.75rem;padding:1.5rem;position:relative;overflow:hidden}\n.pro-card::before{content:\"\";position:absolute;top:-50%;right:-50%;width:200%;height:200%;background:radial-gradient(circle,#6366f108 0%,transparent 70%);pointer-events:none}\n.pro-badge{display:inline-block;background:linear-gradient(135deg,#6366f1,#818cf8);color:#fff;font-size:0.6rem;font-weight:800;padding:0.15rem 0.5rem;border-radius:9999px;letter-spacing:0.06em;margin-bottom:0.6rem}\n.pro-desc{color:var(--muted);font-size:0.78rem;line-height:1.6;margin-bottom:0.8rem}\n.pro-cta{color:#6366f1;font-size:0.72rem;font-weight:600;margin-top:1rem}\n.sn-pro{opacity:0.7}\n\n/* \u2500\u2500 Trends page \u2500\u2500 */\n.trend-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:1rem;margin-top:0.5rem}\n.trend-card{background:var(--card);border:1px solid var(--border);border-radius:10px;padding:0.8rem}\n.trend-header{display:flex;align-items:center;gap:0.5rem;margin-bottom:0.3rem}\n.trend-name{font-size:0.78rem;font-weight:700;flex:1}\n.trend-score{font-size:1.1rem;font-weight:900}\n.trend-chart{overflow:hidden}\n.trend-chart svg{width:100%;height:60px}\n.trend-table{margin-bottom:1.5rem}\n.trend-row{display:flex;align-items:center;gap:0.5rem;padding:0.25rem 0;border-bottom:1px solid var(--border);font-size:0.75rem}\n.trend-row-name{flex:1;font-weight:600}\n.trend-row-val{width:2rem;text-align:center;color:var(--muted)}\n.trend-row-arrow{color:var(--muted);font-size:0.6rem}\n.trend-row-delta{width:2.5rem;text-align:right;font-weight:700}\n\n.footer{text-align:center;color:var(--muted);font-size:0.58rem;margin-top:2rem;padding:0.8rem 0;border-top:1px solid var(--border)}\n.footer a{color:var(--muted)}\n.muted{color:var(--muted)}\n.deeper-tools code{background:var(--border);padding:0.1rem 0.4rem;border-radius:4px;font-size:0.62rem;color:var(--accent);margin-right:0.3rem}\n.flink{color:var(--accent);text-decoration:none;font-family:\"SF Mono\",monospace}.flink:hover{text-decoration:underline}\n.arch-svg{margin:1rem 0;overflow-x:auto;-webkit-overflow-scrolling:touch}\n.arch-svg svg{border-radius:8px}\n.cp-btn{background:none;border:none;cursor:pointer;font-size:0.6rem;opacity:0.3;padding:0 0.2rem;flex-shrink:0}.cp-btn:hover{opacity:1}\n.ir:hover .cp-btn{opacity:0.6}\n\n/* \u2500\u2500 Mobile: hamburger collapses both navs \u2500\u2500 */\n@media(max-width:768px){\n.hamburger{display:block}\n.nav-scroll{display:none}\n.nav-scroll.open{display:flex;position:absolute;top:var(--top-h);left:0;right:0;background:var(--bg);border-bottom:1px solid var(--border);flex-wrap:wrap;padding:0.3rem 0.5rem;z-index:25}\n.side{display:none}\n.side.open{display:block;z-index:25}\n.top{padding:0 0.8rem}\n.logo{font-size:0.85rem;margin-right:0.5rem}\n.content{margin-left:0;padding:0.8rem}\n.cats{grid-template-columns:1fr 1fr}\n.dash{flex-direction:column;gap:1rem}\n.hero svg{width:80px;height:80px}\n.hg{font-size:2rem}\n.radar svg{max-width:180px}\n.bl{width:60px;font-size:0.62rem}\n.bv{width:30px;font-size:0.6rem}\n.it{display:block;overflow-x:auto;-webkit-overflow-scrolling:touch}\n.ff{width:120px;font-size:0.58rem}\n.hm-name{width:120px;font-size:0.58rem}\n.hm-checks{display:none}\n.ov-check{width:50px}\n.ov-loc{max-width:120px}\n.ir{font-size:0.6rem}\n.ch-head{flex-wrap:wrap}\n.ch-g{font-size:1.5rem}\n.info-panel{font-size:0.68rem;padding:0.5rem 0.6rem}\n.ip-row{flex-direction:column;gap:0.1rem}\n.kvs{gap:0.4rem}\n.kv{font-size:0.62rem;padding:0.2rem 0.4rem}\n.arch-svg svg{min-width:400px}\n}\n@media(max-width:480px){\n.cats{grid-template-columns:1fr}\n.tn{padding:0 0.4rem;font-size:0.65rem}\n.ff{width:90px}\n.hm-name{width:90px}\n.ov-check{display:none}\n}\n\n/* \u2500\u2500 Feature Map (Pro) \u2500\u2500 */\n.fm-header{margin-bottom:1.5rem}\n.fm-header h2{display:flex;align-items:center;gap:0.6rem}\n.fm-stats{display:flex;gap:1.5rem;margin-bottom:2rem;padding:1rem 1.2rem;background:var(--card);border:1px solid var(--border);border-radius:12px}\n.fm-stat{display:flex;flex-direction:column;align-items:center}\n.fm-stat-n{font-size:1.6rem;font-weight:900;line-height:1.2}\n.fm-stat-l{font-size:0.65rem;color:var(--muted);text-transform:uppercase;letter-spacing:0.04em;font-weight:600}\n.fm-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1rem}\n.fm-card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:1.2rem;transition:border-color 0.15s}\n.fm-card:hover{border-color:#333}\n.fm-card-issue{border-color:#eab30830;background:linear-gradient(135deg,var(--card) 0%,#1a1a0f 100%)}\n.fm-card-top{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:0.4rem}\n.fm-card-label{font-weight:800;font-size:0.95rem}\n.fm-card-desc{font-size:0.72rem;color:var(--muted);margin-bottom:0.5rem;line-height:1.4}\n.fm-card-dir{font-size:0.65rem;color:var(--dim);font-family:\"SF Mono\",monospace;margin-bottom:0.6rem}\n.fm-card-badge{font-size:0.6rem;font-weight:700;padding:0.15rem 0.5rem;border-radius:9999px;white-space:nowrap}\n.fm-ok{background:#22c55e18;color:var(--pass)}\n.fm-warn{background:#eab30818;color:var(--warn)}\n.fm-info{background:#6366f118;color:var(--info)}\n.fm-card-files{display:flex;flex-direction:column;gap:0.15rem;margin-bottom:0.6rem}\n.fm-file{font-size:0.68rem;color:var(--muted);font-family:\"SF Mono\",monospace}\n.fm-file a{color:var(--accent);text-decoration:none}\n.fm-file a:hover{text-decoration:underline}\n.fm-more{color:var(--dim);font-style:italic}\n.fm-findings{margin-top:0.6rem;padding-top:0.6rem;border-top:1px solid var(--border);display:flex;flex-direction:column;gap:0.3rem}\n.fm-finding{display:flex;align-items:baseline;gap:0.4rem;font-size:0.68rem;line-height:1.4}\n.fm-f-sev{font-weight:800;font-size:0.6rem;width:1rem;flex-shrink:0}\n.fm-f-warn .fm-f-sev{color:var(--warn)}\n.fm-f-info .fm-f-sev{color:var(--info)}\n.fm-f-loc{color:var(--dim);font-family:\"SF Mono\",monospace;flex-shrink:0}\n.fm-f-loc a{color:var(--accent);text-decoration:none}\n.fm-f-msg{color:var(--text)}\n.fm-f-rule{color:var(--dim);font-size:0.6rem;font-family:\"SF Mono\",monospace}\n\n/* Teaser (no Pro key) */\n.fm-teaser{margin-top:1.5rem}\n.fm-teaser-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:1rem;margin-bottom:2rem}\n.fm-card-blur{filter:blur(3px);opacity:0.5;pointer-events:none;user-select:none}\n.fm-cta{text-align:center;padding:2rem;background:linear-gradient(135deg,#0f0f1a,#13131f);border:1px solid #2a2a3d;border-radius:12px}\n.fm-cta code{background:#1a1a2e;padding:0.2rem 0.5rem;border-radius:4px;font-size:0.8rem}\n@media(max-width:640px){\n.fm-grid,.fm-teaser-grid{grid-template-columns:1fr}\n.fm-stats{flex-wrap:wrap;gap:1rem}\n}\n\n/* \u2500\u2500 Preferences panel \u2500\u2500 */\n.prefs-btn{background:none;border:1px solid var(--border);color:var(--muted);font-size:0.72rem;cursor:pointer;padding:0.2rem 0.5rem;border-radius:6px;margin-left:auto;flex-shrink:0;font-family:inherit;line-height:1.4}\n.prefs-btn:hover{color:var(--text);border-color:var(--dim)}\n.prefs-panel{display:none;position:absolute;top:var(--top-h);right:1rem;background:var(--card);border:1px solid var(--border);border-radius:10px;padding:0.8rem 1rem;z-index:40;min-width:200px;box-shadow:0 8px 30px #0008}\n.prefs-panel.open{display:block}\n.prefs-label{font-size:0.6rem;text-transform:uppercase;letter-spacing:0.05em;color:var(--dim);font-weight:600;margin-bottom:0.3rem}\n.prefs-label:not(:first-child){margin-top:0.7rem}\n.prefs-row{display:flex;gap:0.3rem}\n.prefs-opt{background:var(--card-alt);border:1px solid var(--border);color:var(--muted);font-size:0.68rem;padding:0.25rem 0.6rem;border-radius:6px;cursor:pointer;font-family:inherit;transition:all 0.1s}\n.prefs-opt:hover{color:var(--text);border-color:var(--dim)}\n.prefs-opt.active{background:var(--accent);color:#fff;border-color:var(--accent)}\n[data-theme=\"light\"] .prefs-panel{box-shadow:0 8px 30px #0002}\n";
|
package/dist/report/styles.js
CHANGED
|
@@ -131,6 +131,12 @@ h3{font-size:0.85rem;color:var(--muted);text-transform:uppercase;letter-spacing:
|
|
|
131
131
|
.im{flex:1;word-break:break-word}
|
|
132
132
|
.iru{color:var(--dim);font-size:0.55rem}
|
|
133
133
|
|
|
134
|
+
/* ── Source code snippets ── */
|
|
135
|
+
.src-block{background:var(--card-alt);border:1px solid var(--border);border-radius:6px;margin:0.3rem 0 0.5rem 0.5rem;padding:0.3rem 0;font-family:"SF Mono",Menlo,monospace;font-size:0.62rem;line-height:1.6;overflow-x:auto}
|
|
136
|
+
.src-ln{padding:0 0.5rem;white-space:pre}
|
|
137
|
+
.src-hl{padding:0 0.5rem;white-space:pre;background:#eab30815;border-left:2px solid var(--warn)}
|
|
138
|
+
.src-num{color:var(--dim);margin-right:0.5rem;user-select:none;display:inline-block;min-width:2.5rem;text-align:right}
|
|
139
|
+
|
|
134
140
|
/* ── All issues table ── */
|
|
135
141
|
.isf{color:var(--muted);font-size:0.75rem;margin-bottom:0.8rem}
|
|
136
142
|
.it{width:100%;border-collapse:collapse;font-size:0.68rem}
|
package/dist/report/svg.js
CHANGED
|
@@ -3,7 +3,7 @@ export function buildRing(score, color) {
|
|
|
3
3
|
const r = 42;
|
|
4
4
|
const c = 2 * Math.PI * r;
|
|
5
5
|
const off = c - (score / 100) * c;
|
|
6
|
-
return `<svg viewBox="0 0 100 100" style="width:100px;height:100px"><circle cx="50" cy="50" r="${r}" fill="none" stroke="#
|
|
6
|
+
return `<svg viewBox="0 0 100 100" style="width:100px;height:100px"><circle cx="50" cy="50" r="${r}" fill="none" stroke="#444" stroke-width="7"/><circle cx="50" cy="50" r="${r}" fill="none" stroke="${color}" stroke-width="7" stroke-dasharray="${c}" stroke-dashoffset="${off}" stroke-linecap="round" transform="rotate(-90 50 50)"/></svg>`;
|
|
7
7
|
}
|
|
8
8
|
export function buildRadar(items) {
|
|
9
9
|
const n = items.length;
|
|
@@ -17,12 +17,12 @@ export function buildRadar(items) {
|
|
|
17
17
|
const pts = items
|
|
18
18
|
.map((_, i) => `${cx + rr * Math.cos(i * step - Math.PI / 2)},${cy + rr * Math.sin(i * step - Math.PI / 2)}`)
|
|
19
19
|
.join(" ");
|
|
20
|
-
grid += `<polygon points="${pts}" fill="none" stroke="#
|
|
20
|
+
grid += `<polygon points="${pts}" fill="none" stroke="#444" stroke-width="0.7"/>`;
|
|
21
21
|
}
|
|
22
22
|
let axes = "";
|
|
23
23
|
for (let i = 0; i < n; i++) {
|
|
24
24
|
const a = i * step - Math.PI / 2;
|
|
25
|
-
axes += `<line x1="${cx}" y1="${cy}" x2="${cx + r * Math.cos(a)}" y2="${cy + r * Math.sin(a)}" stroke="#
|
|
25
|
+
axes += `<line x1="${cx}" y1="${cy}" x2="${cx + r * Math.cos(a)}" y2="${cy + r * Math.sin(a)}" stroke="#444" stroke-width="0.7"/>`;
|
|
26
26
|
const lx = cx + (r + 16) * Math.cos(a);
|
|
27
27
|
const ly = cy + (r + 16) * Math.sin(a);
|
|
28
28
|
axes += `<text x="${lx}" y="${ly}" text-anchor="middle" dominant-baseline="middle" fill="#6b7280" font-size="9" font-weight="600">${items[i].label}</text>`;
|
|
@@ -60,8 +60,8 @@ export function buildTimeline(entries, opts) {
|
|
|
60
60
|
let grid = "";
|
|
61
61
|
for (const v of [25, 50, 75]) {
|
|
62
62
|
const y = yScale(v).toFixed(1);
|
|
63
|
-
grid += `<line x1="${pad.left}" y1="${y}" x2="${pad.left + w}" y2="${y}" stroke="#
|
|
64
|
-
grid += `<text x="${pad.left - 6}" y="${y}" text-anchor="end" dominant-baseline="middle" fill="#
|
|
63
|
+
grid += `<line x1="${pad.left}" y1="${y}" x2="${pad.left + w}" y2="${y}" stroke="#444" stroke-width="0.7"/>`;
|
|
64
|
+
grid += `<text x="${pad.left - 6}" y="${y}" text-anchor="end" dominant-baseline="middle" fill="#888" font-size="8">${v}</text>`;
|
|
65
65
|
}
|
|
66
66
|
// Score line + dots
|
|
67
67
|
const points = entries.map((e, i) => `${xScale(i).toFixed(1)},${yScale(e.score).toFixed(1)}`).join(" ");
|
|
@@ -77,7 +77,7 @@ export function buildTimeline(entries, opts) {
|
|
|
77
77
|
const labelIndices = entries.length <= 3 ? entries.map((_, i) => i) : [0, Math.floor(entries.length / 2), entries.length - 1];
|
|
78
78
|
for (const i of labelIndices) {
|
|
79
79
|
const label = entries[i].timestamp.split("T")[0].slice(5); // MM-DD
|
|
80
|
-
xLabels += `<text x="${xScale(i).toFixed(1)}" y="${height - 4}" text-anchor="middle" fill="#
|
|
80
|
+
xLabels += `<text x="${xScale(i).toFixed(1)}" y="${height - 4}" text-anchor="middle" fill="#888" font-size="7">${label}</text>`;
|
|
81
81
|
}
|
|
82
82
|
// Gradient fill under the line
|
|
83
83
|
const areaPoints = `${xScale(0).toFixed(1)},${yScale(0).toFixed(1)} ${points} ${xScale(entries.length - 1).toFixed(1)},${yScale(0).toFixed(1)}`;
|
|
@@ -131,7 +131,7 @@ export function buildBadge(score, grade) {
|
|
|
131
131
|
<linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient>
|
|
132
132
|
<clipPath id="r"><rect width="${totalW}" height="${h}" rx="${r}" fill="#fff"/></clipPath>
|
|
133
133
|
<g clip-path="url(#r)">
|
|
134
|
-
<rect width="${labelW}" height="${h}" fill="#
|
|
134
|
+
<rect width="${labelW}" height="${h}" fill="#888"/>
|
|
135
135
|
<rect x="${labelW}" width="${valueW}" height="${h}" fill="${color}"/>
|
|
136
136
|
<rect width="${totalW}" height="${h}" fill="url(#s)"/>
|
|
137
137
|
</g>
|