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>
This commit is contained in:
Kjell Tore Guttormsen 2026-05-03 05:08:07 +02:00
commit f1fecf39b8
36 changed files with 2470 additions and 29 deletions

View file

@ -0,0 +1,100 @@
<!doctype html>
<html lang="nb">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Aspirational vs Committed · 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" />
</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 / Aspirational vs Committed</span>
</header>
<main class="container container--default" 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-okr); font-weight: var(--font-weight-semibold);">OKR · visuell modus-skille</span>
<h1 style="margin: 4px 0 6px;">Aspirational vs Committed</h1>
<p class="text-secondary" style="max-width: 65ch;">Modifier på Objective-card. Aspirational (0,7 = success) har stiplet ring + ASP-badge. Committed (1,0 = expected) har solid ring + COM-badge.</p>
</div>
<div style="display:grid; grid-template-columns: 1fr 1fr; gap: var(--space-4);">
<article class="okr-mode" data-mode="aspirational" title="Aspirasjon — 0,7 regnes som vellykket">
<span class="okr-mode__badge">ASP</span>
<div class="okr-mode__row">
<div class="okr-mode__gauge">
<svg viewBox="0 0 100 100" aria-hidden="true">
<circle class="gauge-bg" cx="50" cy="50" r="42"></circle>
<circle class="gauge-fill" cx="50" cy="50" r="42" stroke-dasharray="263.9" stroke-dashoffset="105.6"></circle>
</svg>
<span class="gauge-value">0,60</span>
</div>
<div>
<div class="okr-mode__objective">Bli landets ledende kommune på AI-assistert saksbehandling innen 2027</div>
<div class="okr-mode__hint">Aspirasjon — 0,7 regnes som vellykket</div>
</div>
</div>
</article>
<article class="okr-mode" data-mode="committed" title="Committed — 1,0 forventes oppnådd">
<span class="okr-mode__badge">COM</span>
<div class="okr-mode__row">
<div class="okr-mode__gauge">
<svg viewBox="0 0 100 100" aria-hidden="true">
<circle class="gauge-bg" cx="50" cy="50" r="42"></circle>
<circle class="gauge-fill" cx="50" cy="50" r="42" stroke-dasharray="263.9" stroke-dashoffset="26.4"></circle>
</svg>
<span class="gauge-value">0,90</span>
</div>
<div>
<div class="okr-mode__objective">Innfør sentralisert sensitivity-label-policy for alle 1 850 ansatte før 30. juni</div>
<div class="okr-mode__hint">Committed — 1,0 forventes oppnådd</div>
</div>
</div>
</article>
<article class="okr-mode" data-mode="aspirational">
<span class="okr-mode__badge">ASP</span>
<div class="okr-mode__row">
<div class="okr-mode__gauge">
<svg viewBox="0 0 100 100" aria-hidden="true">
<circle class="gauge-bg" cx="50" cy="50" r="42"></circle>
<circle class="gauge-fill" cx="50" cy="50" r="42" stroke-dasharray="263.9" stroke-dashoffset="184.7"></circle>
</svg>
<span class="gauge-value">0,30</span>
</div>
<div>
<div class="okr-mode__objective">Halver gjennomsnittlig saksbehandlings­tid på byggesøknader</div>
<div class="okr-mode__hint">Aspirasjon — 0,3 så langt, fortsatt rom for å akselerere</div>
</div>
</div>
</article>
<article class="okr-mode" data-mode="committed">
<span class="okr-mode__badge">COM</span>
<div class="okr-mode__row">
<div class="okr-mode__gauge">
<svg viewBox="0 0 100 100" aria-hidden="true">
<circle class="gauge-bg" cx="50" cy="50" r="42"></circle>
<circle class="gauge-fill" cx="50" cy="50" r="42" stroke-dasharray="263.9" stroke-dashoffset="0"></circle>
</svg>
<span class="gauge-value">1,00</span>
</div>
<div>
<div class="okr-mode__objective">Levér T2-rapport til kommunestyret senest 5. september</div>
<div class="okr-mode__hint">Committed — oppnådd</div>
</div>
</div>
</article>
</div>
</main>
</body>
</html>

View file

@ -0,0 +1,86 @@
<!doctype html>
<html lang="nb">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Classify &amp; Transform · 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" />
</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 / Classify-and-Transform</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-okr); font-weight: var(--font-weight-semibold);">OKR · /okr:skriv strategi-til-OKR</span>
<h1 style="margin: 4px 0 6px;">5-bucket-sorter</h1>
<p class="text-secondary" style="max-width: 65ch;">Lim inn tildelingsbrev øverst — hver krav-setning klassifiseres etter OKR-egnethet (lav, medium, høy).</p>
</div>
<div class="cls-sorter">
<div class="cls-input">
<textarea id="inputText">Bærum kommune skal redusere ventetid på saksbehandling med 30 % innen utgangen av 2026. Innbyggerportalen skal være tilgjengelig 99,5 % av tiden. Andelen selvbetjente saker skal øke fra 42 % til 65 %. Vi skal modernisere innbyggerportalen med AI-assistert chat. Det skal leveres kvartalsrapport til kommunestyret om digitaliseringsfremgang. Copilot for saksbehandlere skal piloteres før Q3.</textarea>
<div style="margin-top: var(--space-3); display: flex; gap: 8px;">
<button class="btn btn--primary btn--sm" onclick="alert('Mock: 6 setninger klassifisert.')">Klassifiser</button>
<span class="text-tertiary" style="font-size: 12px; align-self: center;">6 setninger funnet</span>
</div>
</div>
<div class="cls-buckets" id="buckets"></div>
</div>
</main>
<script>
const buckets = {
drift: { name: "Driftskrav", egnethet: "lav",
items: [{text: "Sikre at innbyggerportalen er tilgjengelig 99,5 % av tiden", action: "→ KPI"}],
cta: "Generer KPI-katalog" },
resultat: { name: "Resultatmål", egnethet: "hoy",
items: [
{text: "Redusere ventetid på saksbehandling med 30 %", action: "→ KR-kandidat"},
{text: "Øke andel selvbetjente saker fra 42 % til 65 %", action: "→ KR-kandidat"},
],
cta: "Generer KR-utkast" },
satsing: { name: "Strategiske satsinger", egnethet: "hoy",
items: [{text: "Modernisere innbyggerportalen med AI-assistert chat", action: "→ Objective-kandidat"}],
cta: "Generer Objective-utkast" },
rapportering: { name: "Rapportering", egnethet: "lav",
items: [{text: "Kvartalsrapport til kommunestyret om digitaliseringsfremgang", action: "→ Rapporteringsrutine"}],
cta: "Skriv rapportmal" },
oppdrag: { name: "Særskilte oppdrag", egnethet: "medium",
items: [{text: "Pilotere Copilot for saksbehandlere før Q3", action: "→ Case by case"}],
cta: "Vurder OKR vs prosjekt" },
};
function egnethetLabel(e) { return e === 'hoy' ? 'Høy egnethet' : e === 'medium' ? 'Medium egnethet' : 'Lav egnethet'; }
function render() {
document.getElementById('buckets').innerHTML = Object.entries(buckets).map(([key, b]) => `
<div class="cls-bucket" data-egnethet="${b.egnethet}" data-key="${key}">
<div class="cls-bucket__head">
<span class="cls-bucket__title">${b.name}</span>
<span class="cls-bucket__egnethet">${egnethetLabel(b.egnethet)}</span>
</div>
${b.items.length ? b.items.map(i => `
<div class="cls-item">
<span>${i.text}</span>
<span class="cls-item__action">${i.action}</span>
</div>
`).join('') : `<div class="cls-bucket__empty">Ingen setninger her.</div>`}
<div class="cls-bucket__action">
<button class="btn btn--secondary btn--sm" style="width:100%;" onclick="alert('Mock: ${b.cta}')">${b.cta}</button>
</div>
</div>
`).join('');
}
render();
</script>
</body>
</html>

View file

@ -0,0 +1,90 @@
<!doctype html>
<html lang="nb">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Cycle Position Ribbon · 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" />
</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 / Cycle Position Ribbon</span>
</header>
<!-- Live ribbon (under header, mock T2-2026 uke 3 av 16) -->
<button type="button" class="cycle-ribbon" data-phase="planning" aria-expanded="false" id="ribbon" style="--cycle-progress: 18%;" onclick="toggleRibbon()">
<span class="cycle-ribbon__id">T2-2026</span>
<span class="cycle-ribbon__week">Uke 3 / 16</span>
<span class="cycle-ribbon__phase">Planning</span>
<span class="cycle-ribbon__msg">Fokuser på check-in-rytme. Første team-check-in bør være innen uke 5.</span>
<span class="cycle-ribbon__chev" aria-hidden="true"></span>
</button>
<div class="cycle-ribbon__panel" id="ribbonPanel">
<div style="display:grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-4); align-items:start;">
<div>
<div class="text-tertiary" style="font-size: 11px; text-transform: uppercase; letter-spacing: .06em;">Periode</div>
<strong>1. mai 31. august 2026</strong>
</div>
<div>
<div class="text-tertiary" style="font-size: 11px; text-transform: uppercase; letter-spacing: .06em;">Fase</div>
<strong>Planning (uke 12)</strong>
<div class="text-secondary" style="font-size: 12px;">Execution starter uke 3, retrospective_prep fra uke 14.</div>
</div>
<div>
<div class="text-tertiary" style="font-size: 11px; text-transform: uppercase; letter-spacing: .06em;">Neste milepel</div>
<strong>Team-check-in 1</strong>
<div class="text-secondary" style="font-size: 12px;">Senest 24. mai 2026 (uke 5).</div>
</div>
</div>
</div>
<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-okr); font-weight: var(--font-weight-semibold);">OKR · persistent header</span>
<h1 style="margin: 4px 0 6px;">Cycle Position Ribbon</h1>
<p class="text-secondary" style="max-width: 65ch;">Persistent stripe under app-header som viser hvor i tertialen brukeren er. Klikk for detaljpanel.</p>
</div>
<h2 style="font-size: var(--font-size-lg); margin: 0 0 var(--space-3);">Alle 3 faser</h2>
<div style="display:flex; flex-direction: column; gap: var(--space-4);">
<div class="cycle-ribbon" data-phase="planning" style="--cycle-progress: 12%; border-radius: var(--radius-md); border: 1px solid var(--color-border-subtle);">
<span class="cycle-ribbon__id">T2-2026</span>
<span class="cycle-ribbon__week">Uke 2 / 16</span>
<span class="cycle-ribbon__phase">Planning</span>
<span class="cycle-ribbon__msg">Sett mål og forankre med ledelse.</span>
<span class="cycle-ribbon__chev"></span>
</div>
<div class="cycle-ribbon" data-phase="execution" style="--cycle-progress: 50%; border-radius: var(--radius-md); border: 1px solid var(--color-border-subtle);">
<span class="cycle-ribbon__id">T2-2026</span>
<span class="cycle-ribbon__week">Uke 8 / 16</span>
<span class="cycle-ribbon__phase">Execution</span>
<span class="cycle-ribbon__msg">Halvveis. Halvveissamtale anbefales denne uka.</span>
<span class="cycle-ribbon__chev"></span>
</div>
<div class="cycle-ribbon" data-phase="retrospective_prep" style="--cycle-progress: 88%; border-radius: var(--radius-md); border: 1px solid var(--color-border-subtle);">
<span class="cycle-ribbon__id">T2-2026</span>
<span class="cycle-ribbon__week">Uke 14 / 16</span>
<span class="cycle-ribbon__phase">Retro-prep</span>
<span class="cycle-ribbon__msg">Forbered scoring og retrospektiv. Frist for KR-scoring: 25. august.</span>
<span class="cycle-ribbon__chev"></span>
</div>
</div>
</main>
<script>
function toggleRibbon() {
const r = document.getElementById('ribbon');
const open = r.getAttribute('aria-expanded') === 'true';
r.setAttribute('aria-expanded', open ? 'false' : 'true');
document.getElementById('ribbonPanel').setAttribute('data-open', open ? 'false' : 'true');
}
</script>
</body>
</html>

View file

@ -0,0 +1,85 @@
<!doctype html>
<html lang="nb">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>ExpansionCard · 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" />
</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 / ExpansionCard</span>
</header>
<main class="container container--default" 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-text-tertiary); font-weight: var(--font-weight-semibold);">Aksel · progressive disclosure</span>
<h1 style="margin: 4px 0 6px;">ExpansionCard</h1>
<p class="text-secondary" style="max-width: 65ch;">Skjul sekundær informasjon bak klikkbar overskrift. Animert utvidelse respekterer prefers-reduced-motion.</p>
</div>
<section class="expansion" aria-expanded="false">
<button type="button" class="expansion__head" onclick="toggleExp(this)">
<span class="expansion__title">
<span class="expansion__title-main">Hva skjer hvis vi avviser denne ROS-rapporten?</span>
<span class="expansion__title-sub">Konsekvenser for utrullingsplanen</span>
</span>
<span class="expansion__chev" aria-hidden="true"></span>
</button>
<div class="expansion__body"><div class="expansion__body-inner"><div>
<p class="text-secondary" style="margin: 0 0 var(--space-2);">Avvises rapporten må arbeidsgruppen ta opp igjen tre trusler i kategori "Kritisk":</p>
<ul style="font-size: var(--font-size-sm); color: var(--color-text-secondary); margin: 0 0 var(--space-2); padding-left: 20px;">
<li>T-014 — DLP-policy for sensitivity labels</li>
<li>T-022 — Cross-tenant Schrems II-vurdering</li>
<li>T-031 — Audit-logging for prompt-historikk</li>
</ul>
<p class="text-secondary" style="margin: 0;">Forventet forsinkelse: 46 uker. Pilot-fasen flyttes fra juni til august.</p>
</div></div></div>
</section>
<section class="expansion" aria-expanded="false">
<button type="button" class="expansion__head" onclick="toggleExp(this)">
<span class="expansion__title">
<span class="expansion__title-main">Hvilke roller skal signere?</span>
<span class="expansion__title-sub">Sjekkliste før innsending</span>
</span>
<span class="expansion__chev" aria-hidden="true"></span>
</button>
<div class="expansion__body"><div class="expansion__body-inner"><div>
<ul style="font-size: var(--font-size-sm); color: var(--color-text-secondary); margin: 0; padding-left: 20px;">
<li>IT-sikkerhetsleder (Eli Bjerke)</li>
<li>Personvernombud (Tor Vagle)</li>
<li>Kommunaldirektør (sponsor)</li>
<li>Tjenesteeier for berørt fagsystem</li>
</ul>
</div></div></div>
</section>
<section class="expansion" aria-expanded="false">
<button type="button" class="expansion__head" onclick="toggleExp(this)">
<span class="expansion__title">
<span class="expansion__title-main">Tekniske detaljer for sentralisert konfig</span>
</span>
<span class="expansion__chev" aria-hidden="true"></span>
</button>
<div class="expansion__body"><div class="expansion__body-inner"><div>
<p class="text-secondary" style="margin: 0;">Konfig versjoneres i <code>git.fromaitochitta.com/playground/configs</code>, valideres ved CI mot <code>config.schema.json</code>, og distribueres via signert artifact til target-tenants.</p>
</div></div></div>
</section>
</main>
<script>
function toggleExp(btn) {
const sec = btn.parentElement;
const open = sec.getAttribute('aria-expanded') === 'true';
sec.setAttribute('aria-expanded', open ? 'false' : 'true');
}
</script>
</body>
</html>

View file

@ -0,0 +1,102 @@
<!doctype html>
<html lang="nb">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Fleet-Overview · 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" />
</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 / Fleet-Overview</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 dashboard</span>
<h1 style="margin: 4px 0 6px;">Fleet-Overview</h1>
<p class="text-secondary" style="max-width: 65ch;">Cross-project posture på én skjerm. 4 kolonner desktop → 2 → 1.</p>
</div>
<div class="fleet-toolbar">
<span class="fleet-toolbar__label">Sortér</span>
<button class="chip" aria-pressed="true" onclick="sortFleet('worst')">Verste først</button>
<button class="chip" aria-pressed="false" onclick="sortFleet('alpha')">Alfabetisk</button>
<button class="chip" aria-pressed="false" onclick="sortFleet('recent')">Sist skannet</button>
<span class="fleet-toolbar__label" style="margin-left: var(--space-4);">Filter</span>
<button class="chip" aria-pressed="false" onclick="filterFleet('failing')">Kun F + E</button>
<button class="chip" aria-pressed="false" onclick="filterFleet('changed')">Kun med endringer</button>
<span class="fleet-toolbar__spacer"></span>
<span class="fleet-toolbar__count" id="fleetCount">12 prosjekter</span>
</div>
<div class="fleet-grid" id="fleetGrid"></div>
</main>
<script>
const projects = [
{ name: "lier-kommune/copilot-onboarding", grade: "A", risk: 12, band: 1, worst: "info-disclosure", scanned: "2026-05-02 14:11", trend: "stable", changed: false },
{ name: "baerum-kommune/okr-portal", grade: "B", risk: 28, band: 1, worst: "missing-rate-limit", scanned: "2026-05-02 09:32", trend: "better", changed: true },
{ name: "direktorat/sak-arkiv-mcp", grade: "C", risk: 44, band: 2, worst: "weak-auth", scanned: "2026-05-01 18:04", trend: "worse", changed: true },
{ name: "direktorat/llm-saksbehandler", grade: "F", risk: 87, band: 4, worst: "TFA chain (BLOCK)", scanned: "2026-05-02 02:55", trend: "worse", changed: true },
{ name: "trondheim/dpia-helper", grade: "B", risk: 22, band: 1, worst: "log-leakage", scanned: "2026-04-30 11:18", trend: "stable", changed: false },
{ name: "skatteetaten/intern-kb", grade: "D", risk: 61, band: 3, worst: "prompt-injection", scanned: "2026-05-02 07:42", trend: "better", changed: true },
{ name: "nav/saksbehandler-co", grade: "C", risk: 39, band: 2, worst: "ssrf-risk", scanned: "2026-05-01 23:01", trend: "stable", changed: false },
{ name: "udi/ai-translator", grade: "E", risk: 73, band: 3, worst: "data-residency", scanned: "2026-05-02 12:30", trend: "worse", changed: true },
{ name: "dsb/krise-bot", grade: "A", risk: 8, band: 1, worst: "minor-typo", scanned: "2026-04-29 16:50", trend: "stable", changed: false },
{ name: "domstol/dom-summary", grade: "B", risk: 25, band: 1, worst: "context-leakage", scanned: "2026-05-01 10:14", trend: "better", changed: true },
{ name: "helsedir/symptomsjekk", grade: "F", risk: 91, band: 4, worst: "PHI exfiltration", scanned: "2026-05-02 04:18", trend: "worse", changed: true },
{ name: "kommune/innsyn-mcp", grade: "C", risk: 47, band: 2, worst: "broad-scope", scanned: "2026-05-01 19:55", trend: "stable", changed: false },
];
const trendArrow = { better: "↗ bedre", worse: "↘ verre", stable: "→ stabil" };
const grid = document.getElementById('fleetGrid');
let mode = 'worst', filter = 'none';
function render() {
let list = projects.slice();
if (filter === 'failing') list = list.filter(p => p.grade === 'F' || p.grade === 'E');
if (filter === 'changed') list = list.filter(p => p.changed);
if (mode === 'worst') list.sort((a,b) => b.risk - a.risk);
if (mode === 'alpha') list.sort((a,b) => a.name.localeCompare(b.name));
if (mode === 'recent') list.sort((a,b) => b.scanned.localeCompare(a.scanned));
grid.innerHTML = list.map(p => `
<button class="fleet-tile" onclick="alert('Naviger til posture for ${p.name}')">
<div class="fleet-tile__row">
<span class="fleet-tile__name" title="${p.name}">${p.name}</span>
<span class="fleet-tile__grade" data-grade="${p.grade}">${p.grade}</span>
</div>
<div class="fleet-tile__meter"><div class="fleet-tile__meter-fill" data-band="${p.band}" style="width:${p.risk}%"></div></div>
<span class="fleet-tile__chip">${p.worst}</span>
<div class="fleet-tile__meta">
<span>${p.scanned}</span>
<span class="fleet-tile__trend--${p.trend}">${trendArrow[p.trend]}</span>
</div>
</button>
`).join('');
document.getElementById('fleetCount').textContent = list.length + ' prosjekter';
}
function sortFleet(m) {
mode = m;
document.querySelectorAll('.fleet-toolbar .chip').forEach(c => {
if (['Verste først', 'Alfabetisk', 'Sist skannet'].includes(c.textContent)) c.setAttribute('aria-pressed', 'false');
});
event.target.setAttribute('aria-pressed', 'true');
render();
}
function filterFleet(f) {
filter = filter === f ? 'none' : f;
event.target.setAttribute('aria-pressed', filter === f ? 'true' : 'false');
render();
}
render();
</script>
</body>
</html>

View file

@ -0,0 +1,81 @@
<!doctype html>
<html lang="nb">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>FormProgress · 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>
.demo-layout { display: grid; grid-template-columns: 280px 1fr; gap: var(--space-6); align-items: start; }
@media (max-width: 820px) { .demo-layout { grid-template-columns: 1fr; } .form-progress { width: 100%; position: static; } }
.form-area { background: var(--color-surface); border: 1px solid var(--color-border-subtle); border-radius: var(--radius-md); padding: var(--space-5); min-height: 360px; }
</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 / FormProgress</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-architect); font-weight: var(--font-weight-semibold);">ms-ai-architect onboarding · OKR /oppsett full · DPIA</span>
<h1 style="margin: 4px 0 6px;">FormProgress</h1>
<p class="text-secondary" style="max-width: 65ch;">Vertikal sidebar for store skjema. Autosave-status, ferdig-prosent per steg, estimert resterende tid. Ikke å forveksle med horisontal stepper.</p>
</div>
<div class="demo-layout">
<aside class="form-progress">
<div class="form-progress__autosave">
<span class="form-progress__autosave-dot" aria-hidden="true"></span>
Lagret automatisk kl. 14:23
</div>
<div class="form-progress__steps">
<button type="button" class="fp-step" data-state="done">
<span class="fp-step__num"></span>
<span><span class="fp-step__name">Organisasjon &amp; kontekst</span></span>
</button>
<button type="button" class="fp-step" data-state="done">
<span class="fp-step__num"></span>
<span><span class="fp-step__name">Brukstilfeller</span></span>
</button>
<button type="button" class="fp-step" data-state="in-progress">
<span class="fp-step__num">3</span>
<span>
<span class="fp-step__name">Datakilder &amp; klassifisering</span>
<span class="fp-step__progress">
<span>62 %</span>
<span class="fp-step__bar"><span class="fp-step__bar-fill" style="width:62%"></span></span>
</span>
</span>
</button>
<button type="button" class="fp-step" data-state="todo" disabled title="Fullfør steg 3 først">
<span class="fp-step__num">4</span>
<span><span class="fp-step__name">Roller &amp; ansvar</span></span>
</button>
<button type="button" class="fp-step" data-state="todo" disabled title="Fullfør steg 3 først">
<span class="fp-step__num">5</span>
<span><span class="fp-step__name">Risiko &amp; tiltak</span></span>
</button>
</div>
<div class="form-progress__remaining">
<span>Resterende</span>
<span>~ 9 min</span>
</div>
</aside>
<section class="form-area">
<div class="text-tertiary" style="font-size: 11px; text-transform: uppercase; letter-spacing: .06em;">Steg 3 av 5</div>
<h2 style="margin: 4px 0 var(--space-4); font-size: var(--font-size-xl);">Datakilder &amp; klassifisering</h2>
<p class="text-secondary" style="font-size: var(--font-size-sm);">Skjemaet hadde 12 felt — 7 utfylt, 5 igjen. Estimert ferdig om 5 minutter.</p>
<div style="margin-top: var(--space-4); padding: var(--space-4); background: var(--color-bg-soft); border-radius: var(--radius-md); font-size: var(--font-size-sm); color: var(--color-text-tertiary);">[Skjema-felt placeholder — i produksjon: input/select/textarea]</div>
</section>
</div>
</main>
</body>
</html>

View file

@ -0,0 +1,144 @@
<!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>

View file

@ -0,0 +1,97 @@
<!doctype html>
<html lang="nb">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Maturity-Ladder · 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>
.demo-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-5); }
@media (max-width: 980px) { .demo-row { grid-template-columns: 1fr; } }
</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 / Maturity-Ladder</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-okr); font-weight: var(--font-weight-semibold);">OKR · config-audit · security</span>
<h1 style="margin: 4px 0 6px;">Maturity-Ladder</h1>
<p class="text-secondary" style="max-width: 65ch;">Vertikal stepper med rik beskrivelse. Current step har progress-ring (her 65 %).</p>
</div>
<div class="demo-row">
<section>
<h2 style="font-size: var(--font-size-lg); margin: 0 0 var(--space-3);">OKR-modenhet (4 nivåer)</h2>
<div class="mat-ladder">
<div class="mat-step" data-state="completed">
<div class="mat-step__icon" aria-hidden="true"></div>
<div>
<div class="mat-step__name">Utforsker <span class="mat-step__pill mat-step__pill--complete">Fullført</span></div>
<div class="mat-step__desc">Eksperimenterer med OKR i 12 team. Ingen formell rytme.</div>
</div>
</div>
<div class="mat-step" data-state="current">
<div class="mat-step__icon" aria-hidden="true">
2
<span class="mat-step__ring" aria-hidden="true">
<svg viewBox="0 0 52 52"><circle class="ring-bg" cx="26" cy="26" r="24"></circle><circle class="ring-fill" cx="26" cy="26" r="24" stroke-dasharray="150.8" stroke-dashoffset="52.8"></circle></svg>
</span>
</div>
<div>
<div class="mat-step__name">Pilot <span class="mat-step__pill mat-step__pill--current"></span></div>
<div class="mat-step__desc">OKR i én avdeling. Kvartalsrytme etablert. Ledelse engasjert.</div>
<div class="mat-step__progress"><span>65 %</span><span class="mat-step__progress-bar"><span class="mat-step__progress-fill" style="width:65%"></span></span><span>til Skalering</span></div>
</div>
</div>
<div class="mat-step" data-state="future">
<div class="mat-step__icon" aria-hidden="true">3</div>
<div>
<div class="mat-step__name">Skalering</div>
<div class="mat-step__desc">OKR rullet ut til hele organisasjonen. Cross-team alignment.</div>
</div>
</div>
<div class="mat-step" data-state="future">
<div class="mat-step__icon" aria-hidden="true">4</div>
<div>
<div class="mat-step__name">Moden</div>
<div class="mat-step__desc">OKR er drift. Strategisk forankring fra Stortingsmelding til team-OKR.</div>
</div>
</div>
</div>
</section>
<section>
<h2 style="font-size: var(--font-size-lg); margin: 0 0 var(--space-3);">Config-modenhet (5 nivåer)</h2>
<div class="mat-ladder">
<div class="mat-step" data-state="completed"><div class="mat-step__icon" aria-hidden="true"></div>
<div><div class="mat-step__name">Bare <span class="mat-step__pill mat-step__pill--complete">Fullført</span></div>
<div class="mat-step__desc">Defaults overalt. Ingen sentralisert konfig.</div></div></div>
<div class="mat-step" data-state="completed"><div class="mat-step__icon" aria-hidden="true"></div>
<div><div class="mat-step__name">Configured <span class="mat-step__pill mat-step__pill--complete">Fullført</span></div>
<div class="mat-step__desc">Eksplisitte verdier per miljø. Ingen drift-deteksjon.</div></div></div>
<div class="mat-step" data-state="current"><div class="mat-step__icon" aria-hidden="true">3
<span class="mat-step__ring" aria-hidden="true"><svg viewBox="0 0 52 52"><circle class="ring-bg" cx="26" cy="26" r="24"></circle><circle class="ring-fill" cx="26" cy="26" r="24" stroke-dasharray="150.8" stroke-dashoffset="105.6"></circle></svg></span></div>
<div><div class="mat-step__name">Structured <span class="mat-step__pill mat-step__pill--current"></span></div>
<div class="mat-step__desc">Skjema-validert konfig. Versjonert i Git. Endringssporbarhet.</div>
<div class="mat-step__progress"><span>30 %</span><span class="mat-step__progress-bar"><span class="mat-step__progress-fill" style="width:30%"></span></span><span>til Automated</span></div></div></div>
<div class="mat-step" data-state="future"><div class="mat-step__icon" aria-hidden="true">4</div>
<div><div class="mat-step__name">Automated</div>
<div class="mat-step__desc">CI-validering. Auto-rollback ved feil. Drift-detektor.</div></div></div>
<div class="mat-step" data-state="future"><div class="mat-step__icon" aria-hidden="true">5</div>
<div><div class="mat-step__name">Governed</div>
<div class="mat-step__desc">Policy-as-code. Audit-trail. Approval-workflows for prod.</div></div></div>
</div>
</section>
</div>
</main>
</body>
</html>

View file

@ -0,0 +1,99 @@
<!doctype html>
<html lang="nb">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Persistent-Antipattern Badge · 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" />
</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 / Persistent-Antipattern Badge</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-okr); font-weight: var(--font-weight-semibold);">OKR · /okr:analyse cross-cycle</span>
<h1 style="margin: 4px 0 6px;">Persistent-Antipattern Badge</h1>
<p class="text-secondary" style="max-width: 65ch;">Markerer antipatterns som har dukket opp i 2+ påfølgende sykluser. Pulserende prikk skiller seg fra one-shot.</p>
</div>
<h2 style="font-size: var(--font-size-lg); margin: 0 0 var(--space-3);">I bruk i en finding-tabell</h2>
<table style="width:100%; border-collapse: collapse; background: var(--color-surface); border: 1px solid var(--color-border-subtle); border-radius: var(--radius-md); overflow:hidden;">
<thead>
<tr style="background: var(--color-bg-soft);">
<th style="text-align:left; padding: 10px 14px; font-size: 12px; text-transform: uppercase; letter-spacing: .06em; color: var(--color-text-tertiary); border-bottom: 1px solid var(--color-border-subtle);">Antipattern</th>
<th style="text-align:left; padding: 10px 14px; font-size: 12px; text-transform: uppercase; letter-spacing: .06em; color: var(--color-text-tertiary); border-bottom: 1px solid var(--color-border-subtle);">Funnet i</th>
<th style="text-align:left; padding: 10px 14px; font-size: 12px; text-transform: uppercase; letter-spacing: .06em; color: var(--color-text-tertiary); border-bottom: 1px solid var(--color-border-subtle);">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding: 12px 14px;">Aktivitetsfokus i KR</td>
<td style="padding: 12px 14px; font-family: var(--font-family-mono); font-size: 12px; color: var(--color-text-secondary);">T1-25 · T2-25 · T3-25 · T1-26 · T2-26</td>
<td style="padding: 12px 14px;">
<button type="button" class="pap-badge" onclick="togglePap(0)" aria-expanded="false" aria-controls="papDetail0">
Vedvarende <span class="pap-badge__count">5 sykluser</span>
</button>
</td>
</tr>
<tr>
<td style="padding: 12px 14px;">Sandbagging av target-verdier</td>
<td style="padding: 12px 14px; font-family: var(--font-family-mono); font-size: 12px; color: var(--color-text-secondary);">T2-25 · T3-25 · T1-26</td>
<td style="padding: 12px 14px;">
<button type="button" class="pap-badge" onclick="togglePap(1)" aria-expanded="false" aria-controls="papDetail1">
Vedvarende <span class="pap-badge__count">3 sykluser</span>
</button>
</td>
</tr>
<tr>
<td style="padding: 12px 14px;">For mange KR per Objective</td>
<td style="padding: 12px 14px; font-family: var(--font-family-mono); font-size: 12px; color: var(--color-text-secondary);">T2-26</td>
<td style="padding: 12px 14px;">
<span class="pap-badge pap-badge--oneshot" title="Kun én syklus så langt">Én syklus</span>
</td>
</tr>
</tbody>
</table>
<section class="pap-detail" id="papDetail0" style="margin-top: var(--space-3);">
<h4>Aktivitetsfokus i KR</h4>
<p class="text-secondary" style="margin: 0; font-size: var(--font-size-sm);">KR-formuleringer beskriver aktiviteter ("Innføre nytt system", "Pilotere X") i stedet for målbare utfall. Vedvarende mønster på tvers av sykluser indikerer at OKR-coaching ikke har festet seg.</p>
<div class="pap-detail__cycles">
<span class="pap-detail__cycle">T1-2025 · 4 forekomster</span>
<span class="pap-detail__cycle">T2-2025 · 3 forekomster</span>
<span class="pap-detail__cycle">T3-2025 · 5 forekomster</span>
<span class="pap-detail__cycle">T1-2026 · 6 forekomster</span>
<span class="pap-detail__cycle">T2-2026 · 4 forekomster</span>
</div>
<div class="pap-detail__rec"><strong>Anbefaling:</strong> Vurder OKR-coaching eller retrospective-fokus på outcome vs activity. Spør "Hva endrer seg for innbyggeren hvis dette KR-et oppfylles?"</div>
</section>
<section class="pap-detail" id="papDetail1" style="margin-top: var(--space-3);">
<h4>Sandbagging av target-verdier</h4>
<p class="text-secondary" style="margin: 0; font-size: var(--font-size-sm);">Targets satt så lavt at de oppnås uten reell innsats. Score &gt; 0,9 to sykluser på rad uten endring i baseline.</p>
<div class="pap-detail__cycles">
<span class="pap-detail__cycle">T2-2025</span>
<span class="pap-detail__cycle">T3-2025</span>
<span class="pap-detail__cycle">T1-2026</span>
</div>
<div class="pap-detail__rec"><strong>Anbefaling:</strong> Innfør stretch-target som komplement, eller vurder aspirational vs committed-skille (se OKR-mode).</div>
</section>
</main>
<script>
function togglePap(i) {
const d = document.getElementById('papDetail' + i);
const open = d.getAttribute('data-open') === 'true';
d.setAttribute('data-open', open ? 'false' : 'true');
event.currentTarget.setAttribute('aria-expanded', open ? 'false' : 'true');
}
</script>
</body>
</html>

View file

@ -0,0 +1,59 @@
<!doctype html>
<html lang="nb">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>ReadMore · 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" />
</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 / ReadMore</span>
</header>
<main class="container container--narrow" 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-text-tertiary); font-weight: var(--font-weight-semibold);">Aksel · inline disclosure</span>
<h1 style="margin: 4px 0 6px;">ReadMore</h1>
<p class="text-secondary">Inline-trigger for å skjule lange forklaringer mid-tekst.</p>
</div>
<article style="font-size: var(--font-size-md); line-height: var(--line-height-normal); color: var(--color-text-primary);">
<p>Sensitivity Labels brukes til å klassifisere dokumenter etter konfidensialitetsnivå.
<span class="read-more" aria-expanded="false">
<button type="button" class="read-more__trigger" onclick="toggleRm(this)">Les mer om hvordan dette håndheves <span class="read-more__chev"></span></button>
<span class="read-more__body">
Når et dokument merkes "Konfidensielt — intern", vil M365 Copilot ikke oppsummere innholdet for brukere uten samme tilgangsnivå.
DLP-policyen sjekker label-attributter ved hver prompt-respons og avbryter generering hvis cross-label data flyter sammen.
For Lier kommune betyr dette at saksbehandlere på Helse-avdelingen ikke utilsiktet kan dra inn HR-relatert informasjon i samme svar.
</span>
</span>
</p>
<p style="margin-top: var(--space-4);">Schrems II-vurdering kreves for cross-tenant data-flyt.
<span class="read-more" aria-expanded="false">
<button type="button" class="read-more__trigger" onclick="toggleRm(this)">Hva betyr Schrems II i praksis? <span class="read-more__chev"></span></button>
<span class="read-more__body">
EU-domstolen kjente Privacy Shield ugyldig i 2020. Overføring av personopplysninger til USA krever supplerende tiltak (TIAs, krypteringsnøkler i EU).
For Microsoft 365-tenants betyr dette at EU Data Boundary må være aktivert, og at audit-logger må bekrefte at prompt-data ikke forlater EØS.
</span>
</span>
</p>
</article>
</main>
<script>
function toggleRm(btn) {
const sec = btn.parentElement;
const open = sec.getAttribute('aria-expanded') === 'true';
sec.setAttribute('aria-expanded', open ? 'false' : 'true');
}
</script>
</body>
</html>

View file

@ -0,0 +1,117 @@
<!doctype html>
<html lang="nb">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Toxic-Flow Chain · 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" />
</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 / Toxic-Flow Chain (TFA)</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 · TFA</span>
<h1 style="margin: 4px 0 6px;">Toxic-Flow Chain</h1>
<p class="text-secondary" style="max-width: 65ch;">Trifecta Flow Analysis: Input → Access → Exfil. Hver leg viser type, kilde og mitigation-status. Tykkere arrows = høyere severity. Grønt skjold = mitigation som bryter kjeden.</p>
</div>
<h2 style="font-size: var(--font-size-lg); margin: 0 0 var(--space-3);">TFA-2026-118-001 — BLOCK</h2>
<div class="tfa-flow" id="flow1">
<span class="tfa-flow__verdict" data-verdict="BLOCK">BLOCK</span>
<button type="button" class="tfa-leg" data-severity="high" onclick="alert('Drill-down: Input leg — skill markdown fil')">
<span class="tfa-leg__label">Input</span>
<span class="tfa-leg__name">Untrusted data</span>
<span class="tfa-leg__source">skill markdown-fil</span>
<span class="tfa-leg__status" data-mit="unmitigated">● Ikke mitigert</span>
</button>
<span class="tfa-arrow" data-severity="critical" aria-hidden="true"><span class="tfa-arrow__line"></span></span>
<button type="button" class="tfa-leg" data-severity="critical" onclick="alert('Drill-down: Access leg — Bash + filsystem')">
<span class="tfa-leg__label">Access</span>
<span class="tfa-leg__name">Sensitive capability</span>
<span class="tfa-leg__source">Bash · filsystem-tilgang</span>
<span class="tfa-leg__status" data-mit="partially_mitigated">◐ Delvis mitigert</span>
</button>
<span class="tfa-arrow" data-severity="critical" aria-hidden="true"><span class="tfa-arrow__line"></span></span>
<button type="button" class="tfa-leg" data-severity="critical" onclick="alert('Drill-down: Exfil leg — webhook.site')">
<span class="tfa-leg__label">Exfil</span>
<span class="tfa-leg__name">External endpoint</span>
<span class="tfa-leg__source">webhook.site/abc123</span>
<span class="tfa-leg__status" data-mit="unmitigated">● Ikke mitigert</span>
</button>
</div>
<h2 style="font-size: var(--font-size-lg); margin: var(--space-8) 0 var(--space-3);">TFA-2026-118-002 — WARN (mitigation present)</h2>
<div class="tfa-flow">
<span class="tfa-flow__verdict" data-verdict="WARN">WARN</span>
<button type="button" class="tfa-leg" data-severity="medium">
<span class="tfa-leg__label">Input</span>
<span class="tfa-leg__name">User prompt</span>
<span class="tfa-leg__source">chat input</span>
<span class="tfa-leg__status" data-mit="mitigated">✓ Sanert</span>
</button>
<span class="tfa-arrow tfa-arrow--mitigated" data-severity="medium" aria-hidden="true">
<span class="tfa-arrow__line"></span>
<span class="tfa-arrow__shield" title="Sanitering bryter kjeden">🛡</span>
</span>
<button type="button" class="tfa-leg" data-severity="high">
<span class="tfa-leg__label">Access</span>
<span class="tfa-leg__name">Read-only DB query</span>
<span class="tfa-leg__source">SELECT-only role</span>
<span class="tfa-leg__status" data-mit="partially_mitigated">◐ RBAC aktiv</span>
</button>
<span class="tfa-arrow" data-severity="high" aria-hidden="true"><span class="tfa-arrow__line"></span></span>
<button type="button" class="tfa-leg" data-severity="high">
<span class="tfa-leg__label">Exfil</span>
<span class="tfa-leg__name">Logged endpoint</span>
<span class="tfa-leg__source">api.bærum.no/log</span>
<span class="tfa-leg__status" data-mit="mitigated">✓ Audit-sporet</span>
</button>
</div>
<h2 style="font-size: var(--font-size-lg); margin: var(--space-8) 0 var(--space-3);">TFA-2026-118-003 — ALLOW</h2>
<div class="tfa-flow">
<span class="tfa-flow__verdict" data-verdict="ALLOW">ALLOW</span>
<button type="button" class="tfa-leg" data-severity="medium">
<span class="tfa-leg__label">Input</span>
<span class="tfa-leg__name">Konfig-fil</span>
<span class="tfa-leg__source">checked-in config.toml</span>
<span class="tfa-leg__status" data-mit="mitigated">✓ Signert</span>
</button>
<span class="tfa-arrow tfa-arrow--mitigated" data-severity="medium" aria-hidden="true">
<span class="tfa-arrow__line"></span>
<span class="tfa-arrow__shield">🛡</span>
</span>
<button type="button" class="tfa-leg" data-severity="medium">
<span class="tfa-leg__label">Access</span>
<span class="tfa-leg__name">Local file read</span>
<span class="tfa-leg__source">repo-scope</span>
<span class="tfa-leg__status" data-mit="mitigated">✓ Sandboxed</span>
</button>
<span class="tfa-arrow tfa-arrow--mitigated" data-severity="medium" aria-hidden="true">
<span class="tfa-arrow__line"></span>
<span class="tfa-arrow__shield">🛡</span>
</span>
<button type="button" class="tfa-leg" data-severity="medium">
<span class="tfa-leg__label">Exfil</span>
<span class="tfa-leg__name">Stdout</span>
<span class="tfa-leg__source">terminal</span>
<span class="tfa-leg__status" data-mit="mitigated">✓ Lokalt</span>
</button>
</div>
</main>
</body>
</html>

View file

@ -0,0 +1,74 @@
<!doctype html>
<html lang="nb">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Suppressed-Signals · 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" />
</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 / Suppressed-Signals Panel</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 · ultraplan-local</span>
<h1 style="margin: 4px 0 6px;">Suppressed-Signals Panel</h1>
<p class="text-secondary" style="max-width: 65ch;">Synlig — men sammenklappet — liste over funn som ble nedgradert eller fjernet, og hvorfor. Aldri skjult i en meny: tillit krever transparens.</p>
</div>
<p class="text-tertiary" style="font-size: var(--font-size-sm); margin: 0 0 8px;">Etter funn-listen, før footer:</p>
<section class="suppressed" aria-expanded="false" id="sup">
<button type="button" class="suppressed__head" onclick="toggleSup()">
<span class="suppressed__chev" aria-hidden="true"></span>
<span class="suppressed__label">12 signaler ble dempet eller fjernet i denne kjøringen</span>
<span class="suppressed__count">12</span>
</button>
<div class="suppressed__body">
<div class="suppressed-group">
<div class="suppressed-group__head">
<span class="suppressed-group__reason">false_positive_glsl_keywords</span>
<span class="suppressed-group__count">8 funn</span>
</div>
<p class="suppressed-group__desc">Entropy-scanner flagget GLSL shader-keywords som secrets. Nedgradert til info etter regel-treff på <code>shaders/*.glsl</code>.</p>
<div class="suppressed-group__examples">
<span class="suppressed-group__example">uniform vec3</span>
<span class="suppressed-group__example">varying mat4</span>
<span class="suppressed-group__example">gl_FragCoord</span>
</div>
</div>
<div class="suppressed-group">
<div class="suppressed-group__head">
<span class="suppressed-group__reason">test_fixture_intended</span>
<span class="suppressed-group__count">3 funn</span>
</div>
<p class="suppressed-group__desc">Fixture-filer i <code>tests/fixtures/</code> med bevisst dummy-data (bestått manuell review 2026-04-22).</p>
</div>
<div class="suppressed-group">
<div class="suppressed-group__head">
<span class="suppressed-group__reason">judge_succinctness_filter</span>
<span class="suppressed-group__count">1 funn</span>
</div>
<p class="suppressed-group__desc">Findings under 4 ord — ikke handlebar. Filtrert ut av Judge.</p>
</div>
</div>
</section>
</main>
<script>
function toggleSup() {
const s = document.getElementById('sup');
const open = s.getAttribute('aria-expanded') === 'true';
s.setAttribute('aria-expanded', open ? 'false' : 'true');
}
</script>
</body>
</html>