ktg-plugin-marketplace/shared/playground-examples/components/kanban.html
Kjell Tore Guttormsen f1fecf39b8 feat(shared): Tier 3 wave 2 (12 components) + self-hosted fonts
Two changes in one commit because they were prepared together and the
component demos depend on the new self-hosted fonts.css.

Tier 3 wave 2 — 12 new components
---------------------------------
Adds components-tier3-supplement.css (886 lines) and 12 isolated demo
HTML pages under shared/playground-examples/components/:
toxic-flow chain, fleet-overview, kanban Keep/Review/Remove,
maturity-ladder, classify-and-transform, cycle-ribbon,
persistent-antipattern, suppressed-signals, ExpansionCard, ReadMore,
FormProgress, Aspirational-vs-Committed.

Reuses existing tokens — no new CSS custom properties. Honors the
Phase 1 feedback rules: no large pink areas for body text, severity-red
distinct from failure-red, dark mode via existing [data-theme="dark"].

Provenance: components-tier3-supplement.css and the 12 demo bodies were
authored by claude.ai/design (separate Anthropic instance) on 2026-05-03.
This commit only integrates them — path rewrites, font swap, generic
name substitution in fleet-overview demo data, README updates.
base.css from the export was deliberately NOT taken in because it
reverted the inline-message contrast fix from v0.1.

Self-hosted fonts (Inter, JetBrains Mono, Source Serif 4)
---------------------------------------------------------
Replaces all fonts.googleapis.com / fonts.gstatic.com requests with
.woff2 files bundled at shared/playground-design-system/fonts/.

Why:
- No data leaked to Google about end-user IPs and User-Agents.
- GDPR-safe for Norwegian public-sector deployments.
- Works offline / behind air-gapped firewalls.
- Forkers downloading the marketplace get a complete bundle.

All three families are SIL Open Font License 1.1 — license texts
included alongside the woff2 files. Source Serif 4 woff2 generated
locally from the upstream OTF release using
fonttools ttLib.woff2 compress; Inter and JetBrains Mono are
unmodified upstream webfont releases.

Total bundle: 9 woff2 files, ~940 KB. New fonts.css declares all
@font-face rules with font-display: swap. All 6 example HTMLs and 12
new component demos load it via a single relative path.

Verified
--------
- Privacy grep returns empty across plugins/ and shared/
- Google Fonts grep returns empty across shared/*.html
- Smoke test via python -m http.server: HTML + 7 stylesheets +
  Inter-Regular.woff2 all return 200

Doc updates
-----------
- shared/playground-design-system/README.md: file tree updated,
  Quick start snippet shows fonts.css link, "Self-hosted fonts"
  section added
- shared/playground-design-system/fonts/LICENSES.md: combined attribution
- README.md (root): Tier 3 wave 1+2 component list, Privacy-first bullet
- CLAUDE.md (root): tree entry expanded for new components + fonts

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 05:08:07 +02:00

144 lines
7 KiB
HTML

<!doctype html>
<html lang="nb">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Kanban · Keep/Review/Remove · Tier 3 supp</title>
<link rel="stylesheet" href="../../playground-design-system/tokens.css" />
<link rel="stylesheet" href="../../playground-design-system/base.css" />
<link rel="stylesheet" href="../../playground-design-system/components.css" />
<link rel="stylesheet" href="../../playground-design-system/components-tier2.css" />
<link rel="stylesheet" href="../../playground-design-system/components-tier3-supplement.css" />
<link rel="stylesheet" href="../../playground-design-system/fonts.css" />
<style>
.modal-bg { position: fixed; inset: 0; background: var(--color-overlay); display: none; align-items: center; justify-content: center; z-index: 100; padding: var(--space-4); }
.modal-bg[data-open="true"] { display: flex; }
.modal { background: var(--color-surface); border-radius: var(--radius-lg); padding: var(--space-5); max-width: 540px; width: 100%; box-shadow: var(--shadow-lg); max-height: 90vh; overflow: auto; }
.checklist { list-style: none; padding: 0; margin: var(--space-3) 0; display: flex; flex-direction: column; gap: 6px; }
.checklist li { display: flex; gap: 8px; font-size: var(--font-size-sm); }
.checklist .ok { color: var(--color-state-success); }
.checklist .no { color: var(--color-severity-critical); }
.checklist .un { color: var(--color-text-tertiary); }
</style>
</head>
<body>
<header class="app-header">
<a href="../index.html" class="app-header__brand"><span class="app-header__brand-mark">P</span><span>Playground</span></a>
<span class="app-header__breadcrumb">/ Komponenter / Kanban: Keep/Review/Remove</span>
</header>
<main class="container container--wide" style="padding: var(--space-8) 0;">
<div style="margin-bottom: var(--space-6);">
<span style="font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--color-scope-security); font-weight: var(--font-weight-semibold);">llm-security · /security plugin-audit</span>
<h1 style="margin: 4px 0 6px;">Kanban: Behold / Vurder / Fjern</h1>
<p class="text-secondary" style="max-width: 65ch;">Klassifisér installerte plugins/MCP-servere etter trust. Klikk-flytt mellom kolonner.</p>
</div>
<div class="kanban-board" id="board"></div>
</main>
<div class="modal-bg" id="modal" onclick="if(event.target===this) closeModal()">
<div class="modal" id="modalBody"></div>
</div>
<script>
const board = {
keep: { title: "Behold", items: [
{ name: "anthropic/claude-code-mcp", verdict: "trusted", meta: "Sist auditert 2026-04-15" },
{ name: "github/copilot-chat", verdict: "trusted", meta: "Sist auditert 2026-04-12" },
{ name: "lier-kommune/internal-mcp", verdict: "trusted", meta: "Sist auditert 2026-04-30" },
{ name: "digdir/auth-mcp", verdict: "trusted", meta: "Sist auditert 2026-05-01" },
]},
review: { title: "Vurder", items: [
{ name: "thirdparty/web-search", verdict: "unknown", meta: "Audit due 2026-06-01" },
{ name: "community/markdown-tools", verdict: "unknown", meta: "Audit due 2026-05-20" },
]},
remove: { title: "Fjern", items: [
{ name: "evil-project-health@1.2.3", verdict: "BLOCK", reason: "85 funn (24 critical), Unicode-steganografi, exfil-flow" },
]},
};
const checklists = {
trusted: [
{ok:'ok', text:'Source repo verifisert (signed commits)'},
{ok:'ok', text:'Maintainer kjent og aktiv'},
{ok:'ok', text:'Ingen kritiske funn siste audit'},
{ok:'ok', text:'Capabilities dokumentert og minst-mulig'},
{ok:'ok', text:'Ingen exfil-flow detektert'},
{ok:'ok', text:'Lisens kompatibel med offentlig bruk'},
{ok:'ok', text:'Versjon pinnet i lockfile'},
{ok:'ok', text:'Endringslogg konsistent med kode'},
{ok:'ok', text:'Trust-skår &gt; 80'},
],
unknown: [
{ok:'un', text:'Source repo verifisert'},
{ok:'ok', text:'Maintainer kjent'},
{ok:'un', text:'Audit ikke utført siste 90 d'},
{ok:'ok', text:'Capabilities dokumentert'},
{ok:'un', text:'Exfil-analyse ikke kjørt'},
{ok:'ok', text:'Lisens OK'},
{ok:'ok', text:'Versjon pinnet'},
{ok:'un', text:'Endringslogg ufullstendig'},
{ok:'un', text:'Trust-skår ikke beregnet'},
],
BLOCK: [
{ok:'no', text:'Unicode-tag-injeksjon i README (steganografi)'},
{ok:'no', text:'Exfil til webhook.site/abc123 detektert'},
{ok:'no', text:'24 kritiske TFA-funn'},
{ok:'no', text:'Maintainer ikke verifiserbar'},
{ok:'no', text:'Source-repo nylig opprettet (typosquat?)'},
{ok:'no', text:'Bash + filsystem + nett uten begrensning'},
{ok:'no', text:'Lisens uklar'},
{ok:'no', text:'Versjon ikke pinnet'},
{ok:'no', text:'Trust-skår: 4'},
],
};
function render() {
document.getElementById('board').innerHTML = ['keep','review','remove'].map(b => `
<div class="kanban-col" data-bucket="${b}">
<div class="kanban-col__head">
<span class="kanban-col__title">${board[b].title}</span>
<span class="kanban-col__count">${board[b].items.length}</span>
</div>
${board[b].items.length ? board[b].items.map((it, i) => `
<div class="kanban-card" data-verdict="${it.verdict}" onclick="openModal('${b}', ${i})">
<span class="kanban-card__name">${it.name}</span>
${it.meta ? `<span class="kanban-card__meta">${it.meta}</span>` : ''}
${it.reason ? `<span class="kanban-card__reason">${it.reason}</span>` : ''}
<div class="kanban-actions" onclick="event.stopPropagation()">
${b !== 'keep' ? `<button onclick="move('${b}','keep',${i})">→ Behold</button>` : ''}
${b !== 'review' ? `<button onclick="move('${b}','review',${i})">→ Vurder</button>` : ''}
${b !== 'remove' ? `<button onclick="move('${b}','remove',${i})">→ Fjern</button>` : ''}
</div>
</div>
`).join('') : `<div class="kanban-col__empty">Ingen i denne bøtten ennå.<br><button class="btn btn--secondary btn--sm" style="margin-top:8px;">+ Legg til</button></div>`}
</div>
`).join('');
}
function move(from, to, i) {
const item = board[from].items.splice(i, 1)[0];
board[to].items.push(item);
render();
}
function openModal(b, i) {
const it = board[b].items[i];
const cl = checklists[it.verdict] || checklists.unknown;
const sym = { ok: '✓', no: '✗', un: '?' };
document.getElementById('modalBody').innerHTML = `
<div style="display:flex; justify-content:space-between; align-items:center; gap:12px;">
<h3 style="margin:0;">${it.name}</h3>
<button class="btn btn--ghost btn--sm" onclick="closeModal()">Lukk</button>
</div>
<p class="text-secondary" style="font-size:var(--font-size-sm); margin:6px 0 0;">Trust-vurdering · ${it.verdict.toUpperCase()}</p>
<ul class="checklist">${cl.map(c => `<li><span class="${c.ok}">${sym[c.ok]}</span><span>${c.text}</span></li>`).join('')}</ul>
`;
document.getElementById('modal').setAttribute('data-open', 'true');
}
function closeModal() { document.getElementById('modal').setAttribute('data-open', 'false'); }
render();
</script>
</body>
</html>