ktg-plugin-marketplace/shared/playground-design-system/components.css
Kjell Tore Guttormsen 4a2bf3567a feat(shared): add Playground design system v0.1 with Tier 1+2 components
Aksel/Digdir-aligned design system for plugin Playgrounds — visual self-service
UIs that complement terminal slash-commands. Targets ms-ai-architect, okr,
llm-security, ultraplan-local, config-audit. Built for Norwegian public sector
decision-makers plus developer power-users — one visual family, two info
densities.

Generated by claude.ai/design (Anthropic) in a dialog-based design session
driven by a comprehensive brief covering all five target plugins, Aksel/Digdir
conventions, and domain-specific visual standards (NS 5814 ROS matrices, EU AI
Act 4-tier pyramide, Doerr OKR scoring, NIST CSF, OWASP threat modeling).
Per Anthropic Consumer Terms §4, ownership of outputs is assigned to the user;
licensed MIT.

shared/playground-design-system/ (5874 lines CSS + JSON):
- tokens.css: Inter font, Digdir blue #0062BA, deuteranopia-safe severity ramp,
  distinct severity-red (#A40E26) vs failure-red (#7D1A1A), plugin scope colors,
  light + dark themes
- base.css: reset, typography (17px body, 65ch measure), focus rings, buttons,
  badges, forms, Aksel 3-tier inline messages, prefers-reduced-motion support
- components.css: Tier 1 — radar/spider, 5x5 matrix-heatmap (bottom-left
  origin, ROS/DPIA), findings-browser, critique-card, wizard/stepper,
  live-meter with antipattern lints
- components-tier2.css: Tier 2 — decision-tree, traffic-lights with rationale,
  diff-review, treemap, distribution P10/P50/P90, command-pipeline output, AI
  Act 4-color pyramide, pipeline-cockpit, verdict-pill + 5-band risk-meter,
  codepoint-reveal (Unicode steg), small-multiples grid (16-cat posture),
  OWASP badges (LLM/ASI/AST/MCP)
- print.css: A4 stylesheet with BW severity hatching, kommune-logo slot,
  signature lines for offentlige dokumenter
- schemas/: finding.schema.json, okr-set.schema.json, ros-threat.schema.json
- README.md: usage guide, design principles, component reference, provenance

shared/playground-examples/:
- index.html: system showcase with all components live
- ros-lier-kommune.html: Lier kommune Copilot ROS-rapport (Scenario A)
- okr-baerum.html: Baerum kommune T2-2026 OKR live writer (Scenario B)
- security-vegvesen.html: SVV ToxicSkills findings review, 85 funn BLOCK
  (Scenario C)
- templates.html: A4 print template demos
- ros-app.js + ros-data.js: Scenario A interactivity

WCAG 2.1 AA throughout (UU-loven krav for offentlig sektor): focus rings, ARIA
attributes, keyboard navigation, severity numerical redundancy for deuteranopia
and BW print, semantic HTML.

Known limitation: Inter loaded via Google Fonts CDN violates self-contained
no-CDN constraint. System-stack fallback works offline. Self-host woff2 files
in Phase 2.
2026-05-02 06:59:19 +02:00

649 lines
20 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* =============================================================================
components.css — Tier 1 components (Phase 1)
1. Radar / Spider
2. Matrix / Heatmap (5x5 ROS)
3. Findings-browser
4. Critique-card
5. Wizard / Stepper
6. Live-meter / Quality-validator
============================================================================= */
/* =============================================================================
1. RADAR
============================================================================= */
.radar {
display: grid;
grid-template-columns: 1fr 240px;
gap: var(--space-6);
align-items: start;
}
.radar__chart {
position: relative;
width: 100%;
aspect-ratio: 1 / 1;
max-width: 460px;
}
.radar__svg { width: 100%; height: 100%; display: block; overflow: visible; }
.radar__grid-line { fill: none; stroke: var(--color-border-subtle); stroke-width: 1; }
.radar__axis { stroke: var(--color-border-moderate); stroke-width: 1; }
.radar__label {
font-family: var(--font-family-sans);
font-size: 12px;
font-weight: var(--font-weight-medium);
fill: var(--color-text-secondary);
text-anchor: middle;
}
.radar__tick { font-size: 10px; fill: var(--color-text-tertiary); }
.radar__series {
fill: var(--color-primary-500);
fill-opacity: 0.18;
stroke: var(--color-primary-500);
stroke-width: 2;
stroke-linejoin: round;
}
.radar__series--target {
fill: none;
stroke: var(--color-text-tertiary);
stroke-width: 1.5;
stroke-dasharray: 4 4;
}
.radar__point { fill: var(--color-primary-500); r: 4; }
.radar__point--target { fill: var(--color-bg); stroke: var(--color-text-tertiary); stroke-width: 1.5; r: 3; }
.radar__legend { display: flex; flex-direction: column; gap: var(--space-3); font-size: var(--font-size-sm); }
.radar__legend-item { display: flex; align-items: baseline; gap: var(--space-2); }
.radar__legend-swatch { width: 12px; height: 12px; border-radius: 2px; flex-shrink: 0; transform: translateY(1px); }
.radar__legend-swatch--current { background: var(--color-primary-500); }
.radar__legend-swatch--target {
background: transparent;
border: 1.5px dashed var(--color-text-tertiary);
}
.radar__scores {
margin-top: var(--space-4);
border-top: 1px solid var(--color-border-subtle);
padding-top: var(--space-3);
display: grid;
gap: 4px;
}
.radar__score-row { display: flex; justify-content: space-between; font-size: var(--font-size-xs); }
.radar__score-row dt { color: var(--color-text-secondary); }
.radar__score-row dd { margin: 0; font-variant-numeric: tabular-nums; font-weight: var(--font-weight-medium); }
@media (max-width: 720px) {
.radar { grid-template-columns: 1fr; }
}
/* =============================================================================
2. MATRIX / HEATMAP (5x5 ROS)
============================================================================= */
.matrix {
display: grid;
grid-template-columns: auto 1fr;
gap: var(--space-3);
}
.matrix__y-label {
writing-mode: vertical-rl;
transform: rotate(180deg);
text-align: center;
font-size: var(--font-size-sm);
font-weight: var(--font-weight-semibold);
color: var(--color-text-secondary);
letter-spacing: 0.06em;
text-transform: uppercase;
align-self: stretch;
display: flex;
align-items: center;
justify-content: center;
}
.matrix__main { display: flex; flex-direction: column; gap: var(--space-2); }
.matrix__grid {
display: grid;
grid-template-columns: 32px repeat(5, 1fr);
grid-template-rows: repeat(5, 1fr) 32px;
gap: 4px;
aspect-ratio: 5 / 5;
width: 100%;
}
.matrix__y-tick {
display: flex; align-items: center; justify-content: center;
font-size: var(--font-size-sm); font-weight: var(--font-weight-semibold);
color: var(--color-text-secondary);
font-variant-numeric: tabular-nums;
}
.matrix__x-tick {
display: flex; align-items: center; justify-content: center;
font-size: var(--font-size-sm); font-weight: var(--font-weight-semibold);
color: var(--color-text-secondary);
font-variant-numeric: tabular-nums;
}
.matrix__corner { /* empty bottom-left */ }
.matrix__cell {
position: relative;
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-sm);
cursor: pointer;
border: 1px solid transparent;
transition: transform var(--duration-fast) var(--ease-default),
box-shadow var(--duration-fast) var(--ease-default);
min-height: 64px;
background: var(--color-severity-low-soft);
}
.matrix__cell:hover { transform: scale(1.02); box-shadow: var(--shadow-md); z-index: 2; }
.matrix__cell[aria-selected="true"] {
outline: 3px solid var(--color-primary-500);
outline-offset: 2px;
z-index: 3;
}
/* Severity zones based on score (sannsynlighet × konsekvens, 1-25) */
.matrix__cell[data-score="1"],
.matrix__cell[data-score="2"],
.matrix__cell[data-score="3"],
.matrix__cell[data-score="4"] { background: var(--color-severity-low-soft); }
.matrix__cell[data-score="5"],
.matrix__cell[data-score="6"],
.matrix__cell[data-score="8"] { background: var(--color-severity-low-soft); }
.matrix__cell[data-score="9"],
.matrix__cell[data-score="10"],
.matrix__cell[data-score="12"] { background: var(--color-severity-medium-soft); }
.matrix__cell[data-score="15"],
.matrix__cell[data-score="16"] { background: var(--color-severity-high-soft); }
.matrix__cell[data-score="20"],
.matrix__cell[data-score="25"] { background: var(--color-severity-critical-soft); }
.matrix__cell-score {
position: absolute;
top: 4px;
left: 6px;
font-size: 11px;
font-weight: var(--font-weight-semibold);
color: var(--color-text-tertiary);
font-variant-numeric: tabular-nums;
}
.matrix__cell-bubbles {
display: flex;
flex-wrap: wrap;
gap: 3px;
align-items: center;
justify-content: center;
padding: 12px 6px 6px;
}
.matrix__bubble {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 22px;
height: 22px;
padding: 0 6px;
font-size: 10px;
font-weight: var(--font-weight-semibold);
font-family: var(--font-family-mono);
color: var(--color-text-primary);
background: rgba(255, 255, 255, 0.85);
border: 1px solid rgba(15, 18, 22, 0.18);
border-radius: var(--radius-pill);
}
.matrix__bubble--count {
background: var(--color-text-primary);
color: var(--color-bg);
border: none;
}
[data-theme="dark"] .matrix__bubble { background: rgba(0,0,0,0.45); color: var(--color-text-primary); border-color: rgba(255,255,255,0.15); }
.matrix__x-label {
text-align: center;
font-size: var(--font-size-sm);
font-weight: var(--font-weight-semibold);
color: var(--color-text-secondary);
letter-spacing: 0.06em;
text-transform: uppercase;
margin-top: var(--space-1);
}
.matrix__legend {
display: flex; gap: var(--space-4); flex-wrap: wrap;
font-size: var(--font-size-xs);
margin-top: var(--space-3);
color: var(--color-text-secondary);
}
.matrix__legend-swatch {
display: inline-block; width: 14px; height: 14px;
border-radius: 3px; margin-right: 6px; vertical-align: -3px;
}
/* =============================================================================
3. FINDINGS-BROWSER
============================================================================= */
.findings {
display: grid;
grid-template-columns: 360px 1fr;
gap: var(--space-6);
align-items: start;
}
.findings__list {
background: var(--color-surface);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-lg);
overflow: hidden;
max-height: 640px;
display: flex;
flex-direction: column;
}
.findings__toolbar {
display: flex;
gap: var(--space-2);
padding: var(--space-3);
border-bottom: 1px solid var(--color-border-subtle);
background: var(--color-bg-soft);
align-items: center;
}
.findings__search {
flex: 1;
padding: 6px 10px;
font-size: var(--font-size-xs);
border: 1px solid var(--color-border-moderate);
border-radius: var(--radius-md);
background: var(--color-surface);
color: inherit;
font-family: inherit;
}
.findings__group {
border-bottom: 1px solid var(--color-border-subtle);
}
.findings__group-header {
padding: 8px 12px;
font-size: var(--font-size-xs);
text-transform: uppercase;
letter-spacing: 0.08em;
font-weight: var(--font-weight-semibold);
color: var(--color-text-secondary);
background: var(--color-bg-soft);
display: flex;
justify-content: space-between;
align-items: center;
}
.findings__items {
list-style: none;
margin: 0;
padding: 0;
overflow-y: auto;
}
.findings__item {
padding: 10px 12px;
border-top: 1px solid var(--color-border-subtle);
cursor: pointer;
display: grid;
grid-template-columns: auto 1fr;
gap: 8px 10px;
align-items: start;
transition: background var(--duration-fast) var(--ease-default);
}
.findings__item:first-child { border-top: none; }
.findings__item:hover { background: var(--color-bg-soft); }
.findings__item[aria-selected="true"] {
background: var(--color-primary-50);
box-shadow: inset 3px 0 0 var(--color-primary-500);
}
[data-theme="dark"] .findings__item[aria-selected="true"] { background: var(--color-primary-900); }
.findings__item-id {
font-family: var(--font-family-mono);
font-size: 11px;
color: var(--color-text-tertiary);
grid-column: 2;
}
.findings__item-title {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
line-height: 1.4;
color: var(--color-text-primary);
grid-column: 2;
}
.findings__item-meta {
display: flex;
gap: 6px;
flex-wrap: wrap;
grid-column: 2;
}
.findings__item-severity-dot {
width: 8px; height: 8px; border-radius: 50%;
margin-top: 7px;
grid-row: 1 / span 3;
}
.findings__item-severity-dot[data-severity="critical"] { background: var(--color-severity-critical); }
.findings__item-severity-dot[data-severity="high"] { background: var(--color-severity-high); }
.findings__item-severity-dot[data-severity="medium"] { background: var(--color-severity-medium); }
.findings__item-severity-dot[data-severity="low"] { background: var(--color-severity-low); }
.findings__item-severity-dot[data-severity="info"] { background: var(--color-text-tertiary); }
.findings__detail {
background: var(--color-surface);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-lg);
padding: var(--space-6);
}
@media (max-width: 880px) { .findings { grid-template-columns: 1fr; } }
/* =============================================================================
4. CRITIQUE-CARD
============================================================================= */
.critique-card {
background: var(--color-surface);
border: 1px solid var(--color-border-subtle);
border-left: 4px solid var(--color-border-moderate);
border-radius: var(--radius-md);
padding: var(--space-4) var(--space-5);
display: flex;
flex-direction: column;
gap: var(--space-3);
}
.critique-card[data-severity="critical"] { border-left-color: var(--color-severity-critical); }
.critique-card[data-severity="high"] { border-left-color: var(--color-severity-high); }
.critique-card[data-severity="medium"] { border-left-color: var(--color-severity-medium); }
.critique-card[data-severity="low"] { border-left-color: var(--color-severity-low); }
.critique-card[data-severity="info"] { border-left-color: var(--color-state-info); }
.critique-card__header {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: var(--space-3);
}
.critique-card__title {
font-size: var(--font-size-md);
font-weight: var(--font-weight-semibold);
margin: 0;
}
.critique-card__meta { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.critique-card__id {
font-family: var(--font-family-mono);
font-size: var(--font-size-xs);
color: var(--color-text-tertiary);
}
.critique-card__evidence {
font-family: var(--font-family-mono);
font-size: var(--font-size-xs);
background: var(--color-surface-sunken);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-sm);
padding: 8px 10px;
white-space: pre-wrap;
word-break: break-word;
color: var(--color-text-secondary);
}
.critique-card__recommendation {
font-size: var(--font-size-sm);
color: var(--color-text-primary);
line-height: var(--line-height-snug);
}
.critique-card__actions {
display: flex;
gap: var(--space-2);
margin-top: 4px;
flex-wrap: wrap;
}
.critique-card[data-status="approved"] { opacity: 0.65; background: var(--color-bg-soft); }
.critique-card[data-status="rejected"] { opacity: 0.5; }
/* =============================================================================
5. WIZARD / STEPPER
============================================================================= */
.stepper {
display: flex;
gap: 0;
margin-bottom: var(--space-8);
border-bottom: 1px solid var(--color-border-subtle);
padding-bottom: var(--space-4);
overflow-x: auto;
}
.stepper__step {
flex: 1;
min-width: 140px;
display: flex;
align-items: center;
gap: var(--space-3);
padding: 0 var(--space-4) 0 0;
text-align: left;
background: none;
border: none;
cursor: pointer;
position: relative;
font-family: inherit;
color: var(--color-text-tertiary);
}
.stepper__step:not(:last-child)::after {
content: '';
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
width: 16px;
height: 1px;
background: var(--color-border-moderate);
}
.stepper__step-number {
display: flex;
align-items: center;
justify-content: center;
width: 28px; height: 28px;
border-radius: 50%;
border: 1.5px solid var(--color-border-moderate);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-semibold);
color: var(--color-text-tertiary);
background: var(--color-surface);
flex-shrink: 0;
font-variant-numeric: tabular-nums;
}
.stepper__step-text {
display: flex;
flex-direction: column;
gap: 1px;
min-width: 0;
}
.stepper__step-label {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
color: inherit;
line-height: 1.3;
}
.stepper__step-hint {
font-size: var(--font-size-xs);
color: var(--color-text-tertiary);
line-height: 1.3;
}
.stepper__step[data-state="active"] { color: var(--color-text-primary); }
.stepper__step[data-state="active"] .stepper__step-number { border-color: var(--color-primary-500); background: var(--color-primary-500); color: #fff; }
.stepper__step[data-state="complete"] { color: var(--color-text-secondary); }
.stepper__step[data-state="complete"] .stepper__step-number { border-color: var(--color-state-success); background: var(--color-state-success); color: #fff; }
.stepper__step[data-state="complete"] .stepper__step-number::before { content: '✓'; font-size: 14px; }
.stepper__step[data-state="complete"] .stepper__step-number-text { display: none; }
.wizard__panel { display: none; }
.wizard__panel[data-active="true"] { display: block; }
.wizard__nav {
display: flex;
justify-content: space-between;
margin-top: var(--space-8);
padding-top: var(--space-6);
border-top: 1px solid var(--color-border-subtle);
}
/* =============================================================================
6. LIVE-METER
============================================================================= */
.live-meter {
display: grid;
gap: var(--space-3);
}
.live-meter__row {
display: grid;
grid-template-columns: 180px 1fr 56px;
gap: var(--space-3);
align-items: center;
font-size: var(--font-size-sm);
}
.live-meter__label { color: var(--color-text-secondary); }
.live-meter__bar {
height: 8px;
background: var(--color-surface-sunken);
border-radius: var(--radius-pill);
overflow: hidden;
position: relative;
}
.live-meter__bar-fill {
height: 100%;
background: var(--color-primary-500);
border-radius: var(--radius-pill);
transition: width var(--duration-normal) var(--ease-default);
}
.live-meter__bar-fill[data-state="pass"] { background: var(--color-state-success); }
.live-meter__bar-fill[data-state="weak"] { background: var(--color-severity-medium); }
.live-meter__bar-fill[data-state="fail"] { background: var(--color-severity-critical); }
.live-meter__value {
text-align: right;
font-variant-numeric: tabular-nums;
font-weight: var(--font-weight-semibold);
font-size: var(--font-size-sm);
}
.live-meter__overall {
display: flex;
justify-content: space-between;
align-items: baseline;
padding: var(--space-3) var(--space-4);
background: var(--color-bg-soft);
border-radius: var(--radius-md);
margin-top: var(--space-2);
}
.live-meter__overall-value {
font-size: var(--font-size-2xl);
font-weight: var(--font-weight-bold);
font-variant-numeric: tabular-nums;
letter-spacing: -0.02em;
}
/* Antipattern annotations (inline, subtle) */
.lint-annotation {
display: inline-flex;
gap: 6px;
padding: 6px 10px;
margin-top: 6px;
background: var(--color-severity-medium-soft);
border-left: 3px solid var(--color-severity-medium);
border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
font-size: var(--font-size-xs);
color: var(--color-severity-medium-on);
line-height: var(--line-height-snug);
}
.lint-annotation--error {
background: var(--color-severity-critical-soft);
color: var(--color-severity-critical);
border-left-color: var(--color-severity-critical);
}
.lint-annotation__code {
font-family: var(--font-family-mono);
font-weight: var(--font-weight-semibold);
}
/* =============================================================================
App shell — header / nav (used by Scenario A and showcase)
============================================================================= */
.app-header {
position: sticky;
top: 0;
z-index: 50;
background: var(--color-surface);
border-bottom: 1px solid var(--color-border-subtle);
padding: var(--space-3) var(--space-6);
display: flex;
align-items: center;
gap: var(--space-4);
}
.app-header__brand {
display: flex;
align-items: center;
gap: var(--space-3);
font-weight: var(--font-weight-semibold);
font-size: var(--font-size-md);
text-decoration: none;
color: var(--color-text-primary);
}
.app-header__brand-mark {
width: 28px; height: 28px;
background: var(--color-primary-500);
border-radius: var(--radius-sm);
display: flex; align-items: center; justify-content: center;
color: #fff;
font-family: var(--font-family-mono);
font-size: 13px;
font-weight: 700;
}
.app-header__breadcrumb {
color: var(--color-text-tertiary);
font-size: var(--font-size-sm);
display: flex; gap: var(--space-2); align-items: center;
}
.app-header__spacer { flex: 1; }
.app-header__actions { display: flex; gap: var(--space-2); align-items: center; }
.theme-toggle {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 10px;
border: 1px solid var(--color-border-moderate);
border-radius: var(--radius-md);
background: var(--color-surface);
color: var(--color-text-secondary);
font-size: var(--font-size-xs);
font-family: inherit;
cursor: pointer;
}
.theme-toggle:hover { border-color: var(--color-border-strong); color: var(--color-text-primary); }
/* Detail sidepanel (slides from right) */
.sidepanel {
position: fixed;
inset: 0 0 0 auto;
width: min(560px, 92vw);
background: var(--color-surface);
border-left: 1px solid var(--color-border-subtle);
box-shadow: var(--shadow-lg);
transform: translateX(100%);
transition: transform var(--duration-normal) var(--ease-default);
z-index: 100;
display: flex;
flex-direction: column;
overflow: hidden;
}
.sidepanel[data-open="true"] { transform: translateX(0); }
.sidepanel__header {
padding: var(--space-4) var(--space-6);
border-bottom: 1px solid var(--color-border-subtle);
display: flex; justify-content: space-between; align-items: flex-start;
gap: var(--space-3);
}
.sidepanel__body {
flex: 1;
overflow-y: auto;
padding: var(--space-6);
}
.sidepanel__close {
background: none; border: none; cursor: pointer;
width: 32px; height: 32px;
border-radius: var(--radius-sm);
display: flex; align-items: center; justify-content: center;
color: var(--color-text-secondary);
}
.sidepanel__close:hover { background: var(--color-bg-soft); color: var(--color-text-primary); }
.scrim {
position: fixed; inset: 0;
background: var(--color-overlay);
opacity: 0;
pointer-events: none;
transition: opacity var(--duration-normal) var(--ease-default);
z-index: 99;
}
.scrim[data-open="true"] { opacity: 1; pointer-events: auto; }