@kage-core/kage-graph-mcp 1.1.38 → 1.3.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/dist/cli.js +137 -4
- package/dist/daemon.js +5 -1
- package/dist/index.js +85 -14
- package/dist/kernel.js +537 -15
- package/package.json +1 -1
- package/viewer/app.js +93 -4
- package/viewer/index.html +8 -6
- package/viewer/styles.css +99 -2
package/package.json
CHANGED
package/viewer/app.js
CHANGED
|
@@ -47,7 +47,9 @@
|
|
|
47
47
|
lifecycle: null,
|
|
48
48
|
timeline: null,
|
|
49
49
|
lineage: null,
|
|
50
|
-
setup: null
|
|
50
|
+
setup: null,
|
|
51
|
+
trust: null,
|
|
52
|
+
suppressed: null
|
|
51
53
|
},
|
|
52
54
|
pendingPackets: [],
|
|
53
55
|
reviewText: "",
|
|
@@ -183,6 +185,8 @@
|
|
|
183
185
|
edgeCount: document.getElementById("edgeCount"),
|
|
184
186
|
reviewCount: document.getElementById("reviewCount"),
|
|
185
187
|
dashboardStats: document.getElementById("dashboardStats"),
|
|
188
|
+
trustHero: document.getElementById("trustHero"),
|
|
189
|
+
suppressionShelf: document.getElementById("suppressionShelf"),
|
|
186
190
|
repoXray: document.getElementById("repoXray"),
|
|
187
191
|
repoXrayStatus: document.getElementById("repoXrayStatus"),
|
|
188
192
|
repoXrayScript: document.getElementById("repoXrayScript"),
|
|
@@ -233,8 +237,8 @@
|
|
|
233
237
|
var PAGE_META = {
|
|
234
238
|
overview: {
|
|
235
239
|
eyebrow: "kage://overview",
|
|
236
|
-
title: "
|
|
237
|
-
summary: "
|
|
240
|
+
title: "Repository overview",
|
|
241
|
+
summary: "Whether this repo's agent memory can be trusted, what needs review, and what's ready to hand off."
|
|
238
242
|
},
|
|
239
243
|
graph: {
|
|
240
244
|
eyebrow: "kage://graph",
|
|
@@ -622,6 +626,8 @@
|
|
|
622
626
|
var timelinePath = params.get("timeline") || params.get("memoryTimeline") || params.get("memory-timeline");
|
|
623
627
|
var lineagePath = params.get("lineage") || params.get("memoryLineage") || params.get("memory-lineage");
|
|
624
628
|
var setupPath = params.get("setup") || params.get("setupDoctor") || params.get("setup-doctor");
|
|
629
|
+
var trustPath = params.get("trust") || params.get("trustBenchmark") || params.get("trust-benchmark");
|
|
630
|
+
var suppressedPath = params.get("suppressed") || params.get("suppressedMemory") || params.get("suppressed-memory");
|
|
625
631
|
var inferredRoot = inferMemoryRoot(graphPaths[0] || "");
|
|
626
632
|
if (!inboxPath && inferredRoot) inboxPath = inferredRoot + "/inbox.json";
|
|
627
633
|
if (!reviewPath && inferredRoot) reviewPath = inferredRoot + "/review/memory-review.md";
|
|
@@ -648,6 +654,8 @@
|
|
|
648
654
|
if (!timelinePath) timelinePath = inferredRoot + "/reports/timeline.json";
|
|
649
655
|
if (!lineagePath) lineagePath = inferredRoot + "/reports/lineage.json";
|
|
650
656
|
if (!setupPath) setupPath = inferredRoot + "/reports/setup.json";
|
|
657
|
+
if (!trustPath) trustPath = inferredRoot + "/reports/trust.json";
|
|
658
|
+
if (!suppressedPath) suppressedPath = inferredRoot + "/reports/suppressed.json";
|
|
651
659
|
}
|
|
652
660
|
var jobs = [];
|
|
653
661
|
if (metricsPath) jobs.push(fetchJson(metricsPath).then(function (metrics) { state.metrics = metrics; }));
|
|
@@ -675,6 +683,8 @@
|
|
|
675
683
|
if (timelinePath) jobs.push(fetchJson(timelinePath).then(function (report) { state.reports.timeline = report; }).catch(function () { state.reports.timeline = null; }));
|
|
676
684
|
if (lineagePath) jobs.push(fetchJson(lineagePath).then(function (report) { state.reports.lineage = report; }).catch(function () { state.reports.lineage = null; }));
|
|
677
685
|
if (setupPath) jobs.push(fetchJson(setupPath).then(function (report) { state.reports.setup = report; }).catch(function () { state.reports.setup = null; }));
|
|
686
|
+
if (trustPath) jobs.push(fetchJson(trustPath).then(function (report) { state.reports.trust = report; }).catch(function () { state.reports.trust = null; }));
|
|
687
|
+
if (suppressedPath) jobs.push(fetchJson(suppressedPath).then(function (report) { state.reports.suppressed = report; }).catch(function () { state.reports.suppressed = null; }));
|
|
678
688
|
if (!graphPaths.length && !jobs.length) {
|
|
679
689
|
loadHostedDefault();
|
|
680
690
|
return;
|
|
@@ -723,7 +733,9 @@
|
|
|
723
733
|
fetchJson("./data/kage/reports/lifecycle.json").catch(function () { return null; }),
|
|
724
734
|
fetchJson("./data/kage/reports/timeline.json").catch(function () { return null; }),
|
|
725
735
|
fetchJson("./data/kage/reports/lineage.json").catch(function () { return null; }),
|
|
726
|
-
fetchJson("./data/kage/reports/setup.json").catch(function () { return null; })
|
|
736
|
+
fetchJson("./data/kage/reports/setup.json").catch(function () { return null; }),
|
|
737
|
+
fetchJson("./data/kage/reports/trust.json").catch(function () { return null; }),
|
|
738
|
+
fetchJson("./data/kage/reports/suppressed.json").catch(function () { return null; })
|
|
727
739
|
]).then(function (items) {
|
|
728
740
|
var merged = mergeNormalizedGraphs([normalizeGraph(items[0]), normalizeGraph(items[1])]);
|
|
729
741
|
state.metrics = items[2];
|
|
@@ -747,6 +759,8 @@
|
|
|
747
759
|
state.reports.timeline = items[20];
|
|
748
760
|
state.reports.lineage = items[21];
|
|
749
761
|
state.reports.setup = items[22];
|
|
762
|
+
state.reports.trust = items[23];
|
|
763
|
+
state.reports.suppressed = items[24];
|
|
750
764
|
loadNormalizedGraph(merged, "Kage repo graph");
|
|
751
765
|
setAutoLoad("Kage repo graph loaded", true);
|
|
752
766
|
}).catch(function () {
|
|
@@ -2983,6 +2997,75 @@
|
|
|
2983
2997
|
].forEach(function (card) { els.debugOverview.appendChild(card); });
|
|
2984
2998
|
}
|
|
2985
2999
|
|
|
3000
|
+
function renderTrustHero(trust) {
|
|
3001
|
+
if (!els.trustHero) return;
|
|
3002
|
+
var metrics = (trust && trust.metrics) || {};
|
|
3003
|
+
var score = trust && typeof trust.trust_score === "number" ? trust.trust_score : null;
|
|
3004
|
+
var status = score == null ? "idle" : (score >= 90 ? "ok" : score >= 70 ? "warn" : "alert");
|
|
3005
|
+
var bars = [
|
|
3006
|
+
["Hallucinated citations rejected", metrics.hallucinated_citation_rejection_rate],
|
|
3007
|
+
["Stale memory excluded from recall", metrics.stale_memory_exclusion_rate],
|
|
3008
|
+
["Live memory grounded to code", metrics.live_grounding_rate]
|
|
3009
|
+
];
|
|
3010
|
+
var note = score == null
|
|
3011
|
+
? "Run <code>kage benchmark --trust</code> to score this repo."
|
|
3012
|
+
: (status === "ok"
|
|
3013
|
+
? "Verified — agents recall only memory that is grounded and current."
|
|
3014
|
+
: "Some memory needs review before agents should trust it.");
|
|
3015
|
+
var barsHtml = bars.map(function (bar) {
|
|
3016
|
+
var raw = bar[1];
|
|
3017
|
+
var has = !(raw === null || raw === undefined || isNaN(raw));
|
|
3018
|
+
var pct = has ? Math.max(0, Math.min(100, Number(raw))) : 0;
|
|
3019
|
+
return '<div class="trust-bar">'
|
|
3020
|
+
+ '<span class="trust-bar-label"></span>'
|
|
3021
|
+
+ '<span class="trust-bar-track"><i style="width:' + pct + '%"></i></span>'
|
|
3022
|
+
+ '<b class="trust-bar-value">' + (has ? pct + "%" : "—") + '</b>'
|
|
3023
|
+
+ '</div>';
|
|
3024
|
+
}).join("");
|
|
3025
|
+
els.trustHero.setAttribute("data-status", status);
|
|
3026
|
+
els.trustHero.innerHTML =
|
|
3027
|
+
'<div class="trust-hero-score">'
|
|
3028
|
+
+ '<span class="trust-hero-eyebrow">Memory Trust</span>'
|
|
3029
|
+
+ '<div class="trust-hero-number"><strong>' + (score == null ? "—" : score) + '</strong><span>/100</span></div>'
|
|
3030
|
+
+ '<p class="trust-hero-note">' + note + '</p>'
|
|
3031
|
+
+ '</div>'
|
|
3032
|
+
+ '<div class="trust-hero-bars">' + barsHtml + '</div>';
|
|
3033
|
+
// Set labels via textContent to avoid any HTML injection from labels.
|
|
3034
|
+
var labelEls = els.trustHero.querySelectorAll(".trust-bar-label");
|
|
3035
|
+
for (var i = 0; i < labelEls.length; i += 1) labelEls[i].textContent = bars[i][0];
|
|
3036
|
+
}
|
|
3037
|
+
|
|
3038
|
+
function renderSuppressionShelf(report) {
|
|
3039
|
+
if (!els.suppressionShelf) return;
|
|
3040
|
+
var items = (report && report.items) || [];
|
|
3041
|
+
if (!items.length) {
|
|
3042
|
+
els.suppressionShelf.innerHTML = "";
|
|
3043
|
+
els.suppressionShelf.hidden = true;
|
|
3044
|
+
return;
|
|
3045
|
+
}
|
|
3046
|
+
els.suppressionShelf.hidden = false;
|
|
3047
|
+
var plural = items.length === 1 ? "memory is" : "memories are";
|
|
3048
|
+
els.suppressionShelf.innerHTML =
|
|
3049
|
+
'<div class="suppression-head">'
|
|
3050
|
+
+ '<div><span class="suppression-eyebrow">Withheld from recall</span>'
|
|
3051
|
+
+ '<h2>' + items.length + ' ' + plural + ' being withheld from your agents</h2></div>'
|
|
3052
|
+
+ '<strong class="suppression-flag">Needs review</strong>'
|
|
3053
|
+
+ '</div>'
|
|
3054
|
+
+ '<p class="suppression-sub">Recall is hiding these because their cited evidence was deleted or expired. Verify, update, or supersede each before agents should trust them.</p>'
|
|
3055
|
+
+ '<div class="suppression-list">'
|
|
3056
|
+
+ items.slice(0, 8).map(function () {
|
|
3057
|
+
return '<div class="suppression-item"><span class="suppression-item-title"></span><span class="suppression-item-reason"></span></div>';
|
|
3058
|
+
}).join("")
|
|
3059
|
+
+ '</div>'
|
|
3060
|
+
+ (items.length > 8 ? '<p class="suppression-more">+ ' + (items.length - 8) + ' more — run <code>kage suppressed</code></p>' : '');
|
|
3061
|
+
var titleEls = els.suppressionShelf.querySelectorAll(".suppression-item-title");
|
|
3062
|
+
var reasonEls = els.suppressionShelf.querySelectorAll(".suppression-item-reason");
|
|
3063
|
+
for (var i = 0; i < titleEls.length; i += 1) {
|
|
3064
|
+
titleEls[i].textContent = items[i].title;
|
|
3065
|
+
reasonEls[i].textContent = items[i].reason;
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
|
|
2986
3069
|
function renderDashboard() {
|
|
2987
3070
|
if (!els.dashboardStats) return;
|
|
2988
3071
|
var metrics = state.metrics || {};
|
|
@@ -3009,6 +3092,8 @@
|
|
|
3009
3092
|
var readiness = handoff || dashboardReadiness(metrics, pendingReview, staleFlags, duplicateFlags, missingContext);
|
|
3010
3093
|
var memoryCoverage = dashboardMemoryCoverage(reports, memoryCodeEdges, memoryGraph, memoryNodes);
|
|
3011
3094
|
var riskHealth = riskTargets.length || hotspots ? (riskTargets.length + hotspots) + " checks" : "No flags";
|
|
3095
|
+
renderTrustHero(reports.trust);
|
|
3096
|
+
renderSuppressionShelf(reports.suppressed);
|
|
3012
3097
|
var statRows = [
|
|
3013
3098
|
["Handoff", readiness.label, readiness.detail, readiness.status],
|
|
3014
3099
|
["Memory", memoryCoverage.label, memoryCoverage.detail, memoryCoverage.status],
|
|
@@ -3547,6 +3632,10 @@
|
|
|
3547
3632
|
return state.entities.filter(function (entity) { return entity.type === type; }).length;
|
|
3548
3633
|
}
|
|
3549
3634
|
|
|
3635
|
+
function numOrDash(value) {
|
|
3636
|
+
return (value === null || value === undefined || isNaN(value)) ? "—" : value;
|
|
3637
|
+
}
|
|
3638
|
+
|
|
3550
3639
|
function formatDashboardValue(value) {
|
|
3551
3640
|
if (typeof value === "number" && Number.isFinite(value)) return value.toLocaleString();
|
|
3552
3641
|
return String(value == null ? "n/a" : value);
|
package/viewer/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>Kage viewer</title>
|
|
7
|
-
<link rel="stylesheet" href="./styles.css?v=
|
|
7
|
+
<link rel="stylesheet" href="./styles.css?v=39">
|
|
8
8
|
</head>
|
|
9
9
|
<body>
|
|
10
10
|
<div class="viewer-shell">
|
|
@@ -54,16 +54,18 @@
|
|
|
54
54
|
|
|
55
55
|
<main class="layout">
|
|
56
56
|
<section class="dashboard-panel" aria-label="Kage repo dashboard">
|
|
57
|
+
<div id="trustHero" class="trust-hero" aria-label="Memory trust score"></div>
|
|
58
|
+
<div id="suppressionShelf" class="suppression-shelf" aria-label="Memory withheld from recall" hidden></div>
|
|
57
59
|
<div id="dashboardStats" class="dashboard-stats" aria-label="Repo dashboard stats"></div>
|
|
58
|
-
<section id="repoXray" class="repo-xray" aria-label="
|
|
60
|
+
<section id="repoXray" class="repo-xray" aria-label="Repository map">
|
|
59
61
|
<div class="repo-xray-head">
|
|
60
62
|
<div>
|
|
61
|
-
<span>
|
|
62
|
-
<h2>
|
|
63
|
+
<span>Repository map</span>
|
|
64
|
+
<h2>What Kage understands about this repo</h2>
|
|
63
65
|
</div>
|
|
64
66
|
<strong id="repoXrayStatus">waiting</strong>
|
|
65
67
|
</div>
|
|
66
|
-
<p id="repoXrayScript">
|
|
68
|
+
<p id="repoXrayScript">Entry points, core modules, change risk, tests, and memory overlays — mapped from the code graph.</p>
|
|
67
69
|
<div id="repoXrayLayers" class="repo-xray-layers"></div>
|
|
68
70
|
</section>
|
|
69
71
|
<div id="dashboardCharts" class="dashboard-charts" aria-label="Repo health charts"></div>
|
|
@@ -302,6 +304,6 @@
|
|
|
302
304
|
</section>
|
|
303
305
|
</div>
|
|
304
306
|
|
|
305
|
-
<script src="./app.js?v=
|
|
307
|
+
<script src="./app.js?v=53"></script>
|
|
306
308
|
</body>
|
|
307
309
|
</html>
|
package/viewer/styles.css
CHANGED
|
@@ -424,6 +424,103 @@ body.viewer-page-data .graph-panel {
|
|
|
424
424
|
white-space: nowrap;
|
|
425
425
|
}
|
|
426
426
|
|
|
427
|
+
.trust-hero {
|
|
428
|
+
display: grid;
|
|
429
|
+
grid-template-columns: minmax(170px, 250px) minmax(0, 1fr);
|
|
430
|
+
gap: 30px;
|
|
431
|
+
align-items: center;
|
|
432
|
+
margin-bottom: 18px;
|
|
433
|
+
padding: 24px 28px;
|
|
434
|
+
border: 1px solid rgba(65, 255, 143, 0.24);
|
|
435
|
+
border-radius: 14px;
|
|
436
|
+
background:
|
|
437
|
+
radial-gradient(circle at 0 0, rgba(65, 255, 143, 0.12), transparent 380px),
|
|
438
|
+
linear-gradient(135deg, rgba(8, 17, 13, 0.96), rgba(10, 16, 22, 0.82));
|
|
439
|
+
box-shadow: var(--shadow);
|
|
440
|
+
}
|
|
441
|
+
.trust-hero[data-status="warn"] { border-color: rgba(255, 209, 102, 0.34); }
|
|
442
|
+
.trust-hero[data-status="alert"] { border-color: rgba(255, 107, 107, 0.38); }
|
|
443
|
+
.trust-hero[data-status="idle"] { border-color: var(--line); }
|
|
444
|
+
.trust-hero-eyebrow {
|
|
445
|
+
display: block;
|
|
446
|
+
color: var(--terminal-dim);
|
|
447
|
+
font-size: 11px;
|
|
448
|
+
font-weight: 800;
|
|
449
|
+
letter-spacing: 0.14em;
|
|
450
|
+
text-transform: uppercase;
|
|
451
|
+
}
|
|
452
|
+
.trust-hero-number { display: flex; align-items: baseline; gap: 5px; margin-top: 8px; }
|
|
453
|
+
.trust-hero-number strong {
|
|
454
|
+
font-size: 60px;
|
|
455
|
+
line-height: 1;
|
|
456
|
+
color: var(--terminal-strong);
|
|
457
|
+
text-shadow: 0 0 24px rgba(65, 255, 143, 0.38);
|
|
458
|
+
}
|
|
459
|
+
.trust-hero[data-status="warn"] .trust-hero-number strong { color: var(--warn); text-shadow: 0 0 24px rgba(255, 209, 102, 0.32); }
|
|
460
|
+
.trust-hero[data-status="alert"] .trust-hero-number strong { color: var(--danger); text-shadow: 0 0 24px rgba(255, 107, 107, 0.32); }
|
|
461
|
+
.trust-hero[data-status="idle"] .trust-hero-number strong { color: var(--muted); text-shadow: none; }
|
|
462
|
+
.trust-hero-number span { font-size: 19px; color: var(--muted); }
|
|
463
|
+
.trust-hero-note { margin: 12px 0 0; color: var(--muted); font-size: 12.5px; line-height: 1.45; max-width: 250px; }
|
|
464
|
+
.trust-hero-note code { color: var(--accent); background: var(--accent-soft); padding: 1px 6px; border-radius: 5px; font-size: 11.5px; }
|
|
465
|
+
.trust-hero-bars { display: grid; gap: 15px; }
|
|
466
|
+
.trust-bar {
|
|
467
|
+
display: grid;
|
|
468
|
+
grid-template-columns: minmax(0, 1fr) 52px;
|
|
469
|
+
grid-template-areas: "label value" "track value";
|
|
470
|
+
align-items: center;
|
|
471
|
+
gap: 4px 14px;
|
|
472
|
+
}
|
|
473
|
+
.trust-bar-label { grid-area: label; color: var(--text); font-size: 13px; }
|
|
474
|
+
.trust-bar-track { grid-area: track; height: 8px; border-radius: 999px; background: rgba(215, 249, 223, 0.07); overflow: hidden; }
|
|
475
|
+
.trust-bar-track i { display: block; height: 100%; border-radius: 999px; background: linear-gradient(90deg, var(--brand), var(--accent)); box-shadow: 0 0 12px rgba(65, 255, 143, 0.3); transition: width 0.5s ease; }
|
|
476
|
+
.trust-bar-value { grid-area: value; text-align: right; color: var(--terminal-strong); font-size: 15px; font-weight: 800; }
|
|
477
|
+
@media (max-width: 860px) {
|
|
478
|
+
.trust-hero { grid-template-columns: 1fr; gap: 20px; }
|
|
479
|
+
.trust-hero-note { max-width: none; }
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
.suppression-shelf {
|
|
483
|
+
margin-bottom: 18px;
|
|
484
|
+
padding: 20px 24px;
|
|
485
|
+
border: 1px solid rgba(255, 209, 102, 0.34);
|
|
486
|
+
border-radius: 14px;
|
|
487
|
+
background:
|
|
488
|
+
radial-gradient(circle at 0 0, rgba(255, 209, 102, 0.08), transparent 360px),
|
|
489
|
+
linear-gradient(135deg, rgba(24, 18, 8, 0.92), rgba(16, 14, 10, 0.82));
|
|
490
|
+
}
|
|
491
|
+
.suppression-shelf[hidden] { display: none; }
|
|
492
|
+
.suppression-head { display: flex; align-items: start; justify-content: space-between; gap: 16px; }
|
|
493
|
+
.suppression-eyebrow { display: block; color: var(--warn); font-size: 11px; font-weight: 800; letter-spacing: 0.14em; text-transform: uppercase; }
|
|
494
|
+
.suppression-head h2 { margin-top: 5px; color: var(--text); text-shadow: none; font-size: 17px; }
|
|
495
|
+
.suppression-flag {
|
|
496
|
+
flex: none;
|
|
497
|
+
color: var(--warn);
|
|
498
|
+
font-size: 11px;
|
|
499
|
+
font-weight: 800;
|
|
500
|
+
text-transform: uppercase;
|
|
501
|
+
letter-spacing: 0.08em;
|
|
502
|
+
padding: 5px 10px;
|
|
503
|
+
border: 1px solid rgba(255, 209, 102, 0.4);
|
|
504
|
+
border-radius: 999px;
|
|
505
|
+
background: var(--warn-soft);
|
|
506
|
+
}
|
|
507
|
+
.suppression-sub { margin: 8px 0 14px; color: var(--muted); font-size: 12.5px; line-height: 1.45; max-width: 760px; }
|
|
508
|
+
.suppression-list { display: grid; gap: 8px; }
|
|
509
|
+
.suppression-item {
|
|
510
|
+
display: grid;
|
|
511
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
512
|
+
align-items: center;
|
|
513
|
+
gap: 16px;
|
|
514
|
+
padding: 11px 14px;
|
|
515
|
+
border: 1px solid rgba(255, 209, 102, 0.16);
|
|
516
|
+
border-radius: 9px;
|
|
517
|
+
background: rgba(8, 17, 13, 0.5);
|
|
518
|
+
}
|
|
519
|
+
.suppression-item-title { color: var(--text); font-size: 13.5px; font-weight: 650; overflow-wrap: anywhere; }
|
|
520
|
+
.suppression-item-reason { color: var(--warn); font-size: 12px; text-align: right; opacity: 0.92; }
|
|
521
|
+
.suppression-more { margin: 12px 0 0; color: var(--muted); font-size: 12px; }
|
|
522
|
+
.suppression-more code { color: var(--accent); background: var(--accent-soft); padding: 1px 5px; border-radius: 5px; }
|
|
523
|
+
|
|
427
524
|
.dashboard-stats {
|
|
428
525
|
display: grid;
|
|
429
526
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
@@ -431,8 +528,8 @@ body.viewer-page-data .graph-panel {
|
|
|
431
528
|
margin-bottom: 22px;
|
|
432
529
|
}
|
|
433
530
|
.dashboard-stat {
|
|
434
|
-
min-height:
|
|
435
|
-
padding: 22px;
|
|
531
|
+
min-height: 120px;
|
|
532
|
+
padding: 20px 22px;
|
|
436
533
|
border: 1px solid rgba(65, 255, 143, 0.12);
|
|
437
534
|
border-radius: 10px;
|
|
438
535
|
background: rgba(8, 17, 13, 0.82);
|