feat(ms-ai-architect): vendor playground-design-system v0.1 [skip-docs]

Initial sync of shared/playground-design-system/ into
plugins/ms-ai-architect/playground/vendor/playground-design-system/
via scripts/sync-design-system.mjs.

Source commit: f1fecf39b8
Files: 25 (7 CSS + 11 fonts/licenses + 3 schemas + README + MANIFEST)

Vendored copy keeps the plugin standalone — playground will load CSS
from ./vendor/ regardless of where the plugin is installed.

Also adds .gitleaks.toml at repo root with a path allowlist for
vendored MANIFEST.json files (SHA-256 file hashes are not secrets).

Docs updated together with the playground HTML refactor that actually
consumes the vendored CSS (next commit). This commit is internal-only.
This commit is contained in:
Kjell Tore Guttormsen 2026-05-03 12:25:42 +02:00
commit 660bd106ce
26 changed files with 4130 additions and 0 deletions

View file

@ -0,0 +1,35 @@
{
"generated_by": "scripts/sync-design-system.mjs",
"do_not_edit": true,
"source": "shared/playground-design-system/",
"source_commit": "f1fecf39b8fc20e86f6ec3c737f0dfda8403712e",
"sync_date": "2026-05-03T10:24:01.408Z",
"file_count": 25,
"files": {
"README.md": "83de0e29b207c979b7b2a3327b7a4ec0c2e1b4d3705ee2677f26c28c3a3ee643",
"base.css": "604fe6839e2ed304bc0ba112a4e045f208b4b3f084f449a1abdb94ce0a1e5263",
"components-tier2.css": "c2cb7e9d76d6af28d50db654030413777feb2f2f2b93213e598de8b686b14523",
"components-tier3-supplement.css": "5b70503ce81a4aefc269e894ab0ae0921ab48370a00de19c737354274f520387",
"components-tier3.css": "c391ea387298ce864bc35078e7e044b2cdd4187e3130456347d91876599ff4b1",
"components.css": "f76b22ba9fd64c2e806b4467536174347105f3e5ccca8a6349a919287d864b86",
"fonts.css": "e3c3df581c6e4d66e25c555f125c745f6512a33038401089d2519a94ea63ee3d",
"fonts/Inter-Bold.woff2": "220976705fbec109f43c5cfdceca639e99ace7e51f3eb67292b105d3575eb39b",
"fonts/Inter-Medium.woff2": "8458f8afa67b5691c1fcbe51607a2dafb53a9839e48131c608a186b65415d96d",
"fonts/Inter-Regular.woff2": "b6f9db9e45be20f3c1312c97fbee7ec36b7d8280f8caa4d53c9ba0408cc9997a",
"fonts/Inter-SemiBold.woff2": "8e52a861dc26ff4608c50bd7ff89b65d0d6216a2afe7b47ce5d84544811ca400",
"fonts/JetBrainsMono-Medium.woff2": "086c48dfbea9ddaff1320f7e09399b8e2924e88ce67453721255db3bdbb5a353",
"fonts/JetBrainsMono-Regular.woff2": "a9cb1cd82332b23a47e3a1239d25d13c86d16c4220695e34b243effa999f45f2",
"fonts/JetBrainsMono-SemiBold.woff2": "918edad542a1da608fd2ba8daebaff9ac802309103fe760eed465b8b4e47faf1",
"fonts/LICENSE-Inter.txt": "262481e844521b326f5ecd053e59b98c8b2da78c8ee1bdbb6e8174305e54935a",
"fonts/LICENSE-JetBrainsMono.txt": "30f0c136e3c88e422d0791acd97238870f9054a9729bc34cf2ff0d4ed8cac4ad",
"fonts/LICENSE-SourceSerif4.md": "75784a295293a8992f5a8d99210566e0064a012e6dab6731305e3787f15896c7",
"fonts/LICENSES.md": "16ef4cb2f4d85233c27be390c3f52ee60d24f1a2a5f72886a0c5dbc8cfcf2c28",
"fonts/SourceSerif4-Regular.woff2": "d5f6830fbdb42425cb60b5cd61d91afa9a2f59b8e99057b1a1d4c2e43b1b06dc",
"fonts/SourceSerif4-Semibold.woff2": "dd00d4b1fea42ca7bd806175662ec51ec09494de986d85087861216cbcf17add",
"print.css": "cd62f08d1b13e0308b5083b6cb5135739eb834e85e88468bd349a642d92b7a6f",
"schemas/finding.schema.json": "0b24797373650582bac232d31a4dd9260593375a0d17259e18f1141a20de8d0c",
"schemas/okr-set.schema.json": "aa27347fb232a956ec9dcee1775115710e2715a665c8d729ac50b90c6884de26",
"schemas/ros-threat.schema.json": "e16497c1a6b79d6e78149d6cf1c28ac9df1e93234627a0c546814fb24d6c96d9",
"tokens.css": "7598d5eda83e189418125cd73b8646e1b0d15a1c195b6f180fafa7944b5ba328"
}
}

View file

@ -0,0 +1,234 @@
# Playground Design System
A shared design system for plugin Playgrounds — visual self-service UIs that complement terminal slash-commands. Built for Norwegian public sector with WCAG 2.1 AA compliance, Aksel/Digdir-aligned aesthetics, and self-contained HTML deployment.
**Version:** 0.1 (Phase 1 — 2026-05-02)
## Provenance
This design system was generated by **[claude.ai/design](https://claude.ai/design)** (Anthropic) in a dialog-based design session driven by a comprehensive brief covering five plugins (`ms-ai-architect`, `okr`, `llm-security`, `ultraplan-local`, `config-audit`), Norwegian public-sector design conventions (Aksel/Digdir), and domain-specific visual standards (NS 5814 risk matrices, EU AI Act 4-tier pyramide, Doerr OKR scoring, NIST CSF, OWASP threat modeling).
Integration into the marketplace (file organization, path normalization, README authoring, root-doc cross-references) was performed in a separate Claude Code session. Per Anthropic Consumer Terms §4, ownership of outputs is assigned to the user; this design system is licensed MIT alongside the rest of the marketplace.
## Directory layout
```
shared/
├── playground-design-system/ # The design system (this directory)
│ ├── README.md # This file
│ ├── tokens.css # CSS custom properties (Aksel/Digdir-aligned)
│ ├── base.css # Reset, typography, primitives, focus, print
│ ├── components.css # Tier 1: radar, matrix, findings-browser, critique-card, wizard, live-meter
│ ├── components-tier2.css # Tier 2: decision-tree, traffic-lights, diff-review, treemap, distribution, command-pipeline, pyramide, pipeline-cockpit, verdict-pill+risk-meter, codepoint-reveal, small-multiples, OWASP badges
│ ├── components-tier3.css # Tier 3 wave 1: pair-before-after, AI Act timeline, 3-track entry, FRIA rights-matrix, capability-matrix, parallel-agent-status, ErrorSummary, GuidePanel
│ ├── components-tier3-supplement.css # Tier 3 wave 2 (12): toxic-flow, fleet-overview, kanban Keep/Review/Remove, maturity-ladder, classify-and-transform, cycle-ribbon, persistent-antipattern, suppressed-signals, ExpansionCard, ReadMore, FormProgress, Aspirational-vs-Committed
│ ├── fonts.css # @font-face declarations for self-hosted fonts
│ ├── fonts/ # Self-hosted woff2 + license attribution
│ │ ├── Inter-{Regular,Medium,SemiBold,Bold}.woff2
│ │ ├── JetBrainsMono-{Regular,Medium,SemiBold}.woff2
│ │ ├── SourceSerif4-{Regular,Semibold}.woff2
│ │ └── LICENSES.md # All three are SIL OFL 1.1
│ ├── print.css # A4 print stylesheet with B/W severity patterns
│ └── schemas/ # Cross-plugin JSON schemas
│ ├── finding.schema.json # Used by llm-security, config-audit, ultraplan-review, ms-ai-review
│ ├── okr-set.schema.json # Used by OKR plugin
│ └── ros-threat.schema.json # Used by ms-ai-architect ROS workflow
└── playground-examples/ # Showcase + reference scenarios
├── index.html # System showcase (browse all components)
├── ros-lier-kommune.html # Scenario A — ms-ai-architect ROS report
├── okr-baerum.html # Scenario B — OKR live writer
├── security-direktorat.html # Scenario C — llm-security findings review
├── templates.html # Skeleton + print-template demos
├── tier3-preview.html # Tier 3 wave 1 visual preview
├── components/ # Tier 3 wave 2 — 12 isolated demo pages
│ ├── sankey-toxic-flow.html
│ ├── fleet-overview.html
│ ├── kanban.html
│ ├── maturity-ladder.html
│ ├── classify-transform.html
│ ├── cycle-ribbon.html
│ ├── persistent-antipattern.html
│ ├── suppressed-signals.html
│ ├── expansion-card.html
│ ├── read-more.html
│ ├── form-progress.html
│ └── aspirational-committed.html
├── ros-app.js # Scenario A interactivity
└── ros-data.js # Scenario A mock data
```
## Quick start
To use the design system from a plugin's Playground:
```html
<!doctype html>
<html lang="nb" data-theme="light">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="../../shared/playground-design-system/tokens.css">
<link rel="stylesheet" href="../../shared/playground-design-system/base.css">
<link rel="stylesheet" href="../../shared/playground-design-system/components.css">
<link rel="stylesheet" href="../../shared/playground-design-system/components-tier2.css">
<!-- Optional: include components-tier3.css for Tier 3 wave 1 components -->
<!-- Optional: include components-tier3-supplement.css for Tier 3 wave 2 (12 additional components) -->
<!-- Optional: only include print.css if scenario produces a printable A4 report -->
<link rel="stylesheet" href="../../shared/playground-design-system/print.css">
<!-- Self-hosted fonts (no external requests) -->
<link rel="stylesheet" href="../../shared/playground-design-system/fonts.css">
</head>
<body>
<header class="app-header">
<a class="app-header__brand" href="...">
<span class="app-header__brand-mark">MS</span>
ms-ai-architect
</a>
<span class="app-header__breadcrumb">/ Playground</span>
<div class="app-header__spacer"></div>
<button class="theme-toggle" data-theme-toggle>Mørk modus</button>
</header>
<!-- Your Playground content using design-system classes -->
</body>
</html>
```
The relative path `../../shared/playground-design-system/` assumes the plugin's Playground lives at `plugins/{plugin-name}/playground/index.html`. Adjust the prefix to match your plugin's structure.
## Design principles
1. **Aksel/Digdir-aligned.** Inter font, body 17px, Digdir blue `#0062BA`, semantic CSS tokens. Norwegian public sector users recognize this DNA.
2. **WCAG 2.1 AA non-negotiable.** Required by `Forskrift om universell utforming av IKT` for Norwegian public sector. Every component ships with proper focus rings, ARIA attributes, keyboard navigation, and contrast that passes deuteranopia simulators.
3. **Vanilla HTML/CSS/JS.** No React, no Tailwind, no build step. A plugin can copy a Playground HTML file to disk and it will render correctly.
4. **Self-contained per Playground.** Each plugin's `playground/*.html` should be openable offline with only the design-system CSS files alongside.
5. **Print-aware.** The `print.css` stylesheet ensures matrix cells use B/W-safe hatching patterns when printed, severity badges become outlined boxes with patterns, and interactive chrome disappears. Designed for A4 reports going to Datatilsynet, kommunestyre, statsråd.
6. **Severity is universal.** All severity-coded UI uses the same five-level ramp (low/medium/high/critical/extreme) with deuteranopia-safe hex values defined in `tokens.css`. Distinct from "state" tokens (failed/blocked/queued/running) used in pipeline contexts — never mix severity-red with failure-red.
7. **Two-spor strategy.** The system supports both non-technical decision makers (Spor 1: ms-ai-architect, OKR, llm-security) and developer power-users (Spor 2: ultraplan-local, config-audit) — same component library, different information densities.
## Token system
See `tokens.css` for full reference. Highlights:
- **Typography:** `--font-family-sans` (Inter), `--font-size-md` (17px body), `--measure` (65ch line length)
- **Primary:** `--color-primary-500` = `#0062BA` (Digdir blue), with 50/100/300/500/700/900 ramp
- **Severity:** `--color-severity-{low,medium,high,critical,extreme}` + `-soft` (background) + `-on` (foreground) variants. Deuteranopia-safe.
- **State:** `--color-state-{success,warning,failed,blocked,info,running,queued,pending,done}` — distinct from severity
- **Surface:** Warm off-white `#FBFAF7` (light), graphite `#0F1419` (dark). Theme via `[data-theme="dark"]` on `<html>` or `<body>`
- **Plugin scope:** `--color-scope-{architect,okr,security,ultraplan,config}` for visual differentiation between plugins
- **Spacing:** 4px grid, scale 1-20 (4px to 80px)
- **Radius:** `--radius-sm` (3px) / `-md` (5px) / `-lg` (8px) / `-pill` (999px) — max 8px (no consumer-app rounded corners)
- **Motion:** Respects `prefers-reduced-motion`
## Component reference
### Tier 1 (`components.css`)
| Component | Class prefix | Used by |
|---|---|---|
| Radar / Spider chart | `.radar` | OKR maturity (7-axis), ms-ai security (6), ms-ai ROS dimensions (7), ultraplan plan-critic (7) |
| Matrix / 5×5 heatmap | `.matrix` | ms-ai ROS, DPIA, OKR coverage, security scanner, license map |
| Findings-browser | `.findings` | llm-security, ultraplan-review, config-audit, ms-ai-review |
| Critique-card | `.critique-card` | llm-security findings, ultraplan, config-audit feature-gap, OKR antipatterns |
| Wizard / Stepper | `.stepper`, `.wizard__panel` | ms-ai 5-step intake, security clean, config-audit audit, ultraplan, OKR onboarding |
| Live-meter | `.live-meter`, `.lint-annotation` | OKR writer, ultraplan brief-reviewer, cost, config-audit |
Plus app-shell primitives: `.app-header`, `.sidepanel`, `.scrim`, `.theme-toggle`.
### Tier 3 (`components-tier3.css`)
Critical components for ms-ai-architect Playground v3 plus universal Aksel patterns. Authored 2026-05-02 in Claude Code (not via claude.ai/design — visual coherence verified against Tier 1+2 in `playground-examples/tier3-preview.html`).
| Component | Class prefix | Used by |
|---|---|---|
| Inherent + residual pair | `.pair-before-after` | ms-ai ROS before/after, DPIA, AI Act mitigations, OKR check-ins |
| AI Act compliance-tidslinje | `.aiact-timeline`, `.aiact-countdown` | ms-ai-architect classify flow + dashboard |
| 3-track entry | `.tracks` | All plugins — entry-level UX choice (Guide/Explore/Expert) |
| FRIA rights-matrix | `.rights-matrix` | ms-ai-architect FRIA (Art. 27, 12 EU Charter rights × impact) |
| Capability-matrix | `.capability-matrix` | ms-ai-architect license × kapabilitet mapping |
| Parallel-agent-status | `.agent-grid`, `.agent-card` | ms-ai utredning multi-worker, ultraplan multi-wave execute |
| ErrorSummary | `.error-summary` | All plugins — Aksel/GOV.UK form-validation pattern |
| GuidePanel | `.guide-panel` | All plugins — Aksel friendly inline guidance with optional CTA |
### Tier 2 (`components-tier2.css`)
| Component | Class prefix | Used by |
|---|---|---|
| Decision-tree | `.decision-tree`, `.dt-node`, `.dt-edge` | ms-ai AI Act 4-step classifier, security MAESTRO drill |
| Traffic-lights | `.traffic-light` | ms-ai compliance, OKR KR-status, security pre-deploy, config-audit risk |
| Diff-review | `.diff` | security diff, config-audit drift, ultraplan triage |
| Treemap | `.treemap` | config-audit token-hotspots |
| Distribution / range-viz | `.distribution` | ms-ai cost P10/P50/P90, security risk-score, OKR progress |
| Command-pipeline | `.cmd-pipeline`, `.cmd-step` | All plugins — final export of slash-command sequence |
| Pyramide (4-tier) | `.pyramide` | ms-ai AI Act risk classification |
| Pipeline-cockpit | `.pipeline-cockpit`, `.pc-stage` | ultraplan 6-stage flow, ms-ai utredning, config-audit audit |
| Verdict-pill + risk-meter | `.verdict-pill-lg`, `.risk-meter` | llm-security BLOCK/WARNING/ALLOW + 0-100 risk-score |
| Codepoint-reveal | `.codepoint-reveal` | llm-security Unicode steganography demo |
| Small-multiples grid | `.small-multiples`, `.sm-card` | llm-security 16-category posture (alternative to overcrowded radar) |
| OWASP badges | `.badge--owasp-{llm,asi,ast,mcp}` | llm-security finding cross-mapping (4 frameworks) |
## Schemas
`schemas/` contains JSON schemas for cross-plugin data interchange:
- **`finding.schema.json`** — universal "finding" shape (id, title, severity, source, evidence, rationale, recommendation, status). Consumed by llm-security, config-audit, ultraplan-review, ms-ai-review. Maps directly to the `.critique-card` component.
- **`okr-set.schema.json`** — OKR shape (objectives + key results, scoring, antipattern annotations). Consumed by OKR plugin.
- **`ros-threat.schema.json`** — ROS threat shape (likelihood × consequence, mitigation references, residual risk). Consumed by ms-ai-architect.
A plugin command can output JSON conforming to these schemas, and a Playground can render the result without further translation.
## Theming
Default is light. Toggle dark via `data-theme="dark"` attribute on `<html>` or `<body>`. The system also respects `prefers-color-scheme: dark` when no explicit theme is set:
```js
// Toggle dark/light
document.documentElement.dataset.theme =
document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark';
localStorage.setItem('theme', document.documentElement.dataset.theme);
```
## Print mode
Include `print.css` if your scenario produces an A4 report. Then add `class="no-print"` to interactive chrome (header, buttons, theme toggle), and use `class="page-break"` to force page breaks. Severity-coded matrix cells will automatically render as B/W-safe hatching patterns when printed. The `.print-header` and `.print-footer` blocks support kommune-logo slots and signature lines for offentlige dokumenter.
## Known limitations
1. **No JavaScript framework.** Components are CSS-first. Interactivity (e.g. `aria-selected` toggling, sidepanel open/close, live-meter updates) must be wired by each Playground using vanilla JS. See `playground-examples/ros-app.js` for a reference implementation pattern.
2. **No icon set bundled.** The system assumes Lucide or Phosphor SVG sprites are inlined per Playground. Iconography is intentionally out-of-system to keep the shared system small.
3. **Mobile responsiveness is partial.** The 5×5 matrix, findings-browser, codepoint-reveal split-pane, and small-multiples grid have explicit `@media (max-width: ...)` rules. Other components may need polish for narrow viewports.
## Self-hosted fonts
All three font families (Inter, JetBrains Mono, Source Serif 4) are bundled as woff2 in `fonts/` and loaded via `fonts.css`. No external requests to Google Fonts or any CDN. All three are SIL OFL 1.1 — see `fonts/LICENSES.md` for full attribution.
## Versioning
This system follows semver:
- **Major:** Breaking token rename, component class rename, schema field removal/rename
- **Minor:** New tokens, new components, new schema fields, new variants
- **Patch:** Bugfixes, accessibility improvements, visual polish without contract changes
Every plugin Playground that consumes the design system should declare the version in a comment at the top of its HTML:
```html
<!-- playground-design-system v0.1 -->
```
## License
MIT, same as the parent ktg-plugin-marketplace. Reuse freely; attribution appreciated.
## Contributing
This is a solo project. PRs are not accepted, but issues and suggestions are welcome at the marketplace repo (Forgejo: `git.fromaitochitta.com/open/ktg-plugin-marketplace`).
When adding a new component:
1. Add CSS to `components.css` (Tier 1) or `components-tier2.css` (Tier 2)
2. Use BEM naming convention: `.component-name__element--modifier`
3. Reference only `tokens.css` custom properties — never hard-code colors, spacing, or fonts
4. Test in light + dark themes, with deuteranopia simulator (Stark, Sim Daltonism)
5. Test keyboard navigation and screen reader (NVDA on Windows, VoiceOver on Mac)
6. Add a print rule if the component appears in printable reports
7. Document in this README under the appropriate Tier table

View file

@ -0,0 +1,264 @@
/* Code generated by sync-design-system.mjs; DO NOT EDIT. */
/* =============================================================================
base.css reset, typography, layout primitives, focus, print
============================================================================= */
*, *::before, *::after { box-sizing: border-box; }
html {
-webkit-text-size-adjust: 100%;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
}
body {
margin: 0;
font-family: var(--font-family-sans);
font-size: var(--font-size-md);
line-height: var(--line-height-normal);
color: var(--color-text-primary);
background: var(--color-bg);
font-feature-settings: "ss01", "cv11";
}
h1, h2, h3, h4, h5, h6 {
margin: 0;
font-weight: var(--font-weight-semibold);
line-height: var(--line-height-tight);
letter-spacing: -0.01em;
color: var(--color-text-primary);
text-wrap: balance;
}
h1 { font-size: var(--font-size-3xl); letter-spacing: -0.02em; }
h2 { font-size: var(--font-size-2xl); letter-spacing: -0.015em; }
h3 { font-size: var(--font-size-xl); }
h4 { font-size: var(--font-size-lg); }
h5 { font-size: var(--font-size-md); }
p {
margin: 0;
text-wrap: pretty;
max-width: var(--measure);
}
small { font-size: var(--font-size-sm); color: var(--color-text-secondary); }
code, kbd, samp { font-family: var(--font-family-mono); font-size: 0.92em; }
kbd {
display: inline-block;
padding: 1px 6px;
font-size: 0.85em;
border: 1px solid var(--color-border-moderate);
border-bottom-width: 2px;
border-radius: var(--radius-sm);
background: var(--color-surface);
color: var(--color-text-secondary);
line-height: 1;
}
a {
color: var(--color-text-link);
text-decoration: underline;
text-underline-offset: 2px;
text-decoration-thickness: 1px;
}
a:hover { color: var(--color-text-link-hover); text-decoration-thickness: 2px; }
button { font-family: inherit; }
/* Focus rings — WCAG */
:focus-visible {
outline: 2px solid var(--color-border-focus);
outline-offset: 2px;
border-radius: var(--radius-sm);
}
:focus:not(:focus-visible) { outline: none; }
/* ---------- Buttons ---------- */
.btn {
display: inline-flex;
align-items: center;
gap: var(--space-2);
padding: 9px 16px;
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
line-height: 1.3;
border-radius: var(--radius-md);
border: 1px solid transparent;
cursor: pointer;
transition: background var(--duration-fast) var(--ease-default),
border-color var(--duration-fast) var(--ease-default),
color var(--duration-fast) var(--ease-default);
white-space: nowrap;
text-decoration: none;
}
.btn:disabled, .btn[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; }
.btn--primary { background: var(--color-primary-500); color: var(--color-text-on-primary); }
.btn--primary:hover { background: var(--color-primary-700); }
.btn--secondary {
background: var(--color-surface);
color: var(--color-text-primary);
border-color: var(--color-border-moderate);
}
.btn--secondary:hover { background: var(--color-bg-soft); border-color: var(--color-border-strong); }
.btn--ghost {
background: transparent;
color: var(--color-text-primary);
border-color: transparent;
}
.btn--ghost:hover { background: var(--color-bg-soft); }
.btn--destructive { background: var(--color-severity-critical); color: #fff; }
.btn--destructive:hover { background: var(--color-severity-extreme); }
.btn--sm { padding: 5px 10px; font-size: var(--font-size-xs); }
.btn--lg { padding: 12px 20px; font-size: var(--font-size-md); }
/* ---------- Badges / pills ---------- */
.badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
line-height: 1.4;
border-radius: var(--radius-pill);
border: 1px solid var(--color-border-subtle);
background: var(--color-bg-soft);
color: var(--color-text-secondary);
white-space: nowrap;
}
.badge--severity-low { background: var(--color-severity-low-soft); color: var(--color-severity-low-on); border-color: transparent; }
.badge--severity-medium { background: var(--color-severity-medium-soft); color: var(--color-severity-medium-on); border-color: transparent; }
.badge--severity-high { background: var(--color-severity-high-soft); color: var(--color-severity-high-on); border-color: transparent; }
.badge--severity-critical { background: var(--color-severity-critical); color: var(--color-severity-critical-on); border-color: transparent; }
.badge--severity-extreme { background: var(--color-severity-extreme); color: var(--color-severity-extreme-on); border-color: transparent; }
.badge--owasp { font-family: var(--font-family-mono); font-size: 11px; padding: 1px 6px; }
.badge--scope-architect { background: var(--color-scope-architect); color: #fff; border-color: transparent; }
.badge--scope-okr { background: var(--color-scope-okr); color: #fff; border-color: transparent; }
.badge--scope-security { background: var(--color-scope-security); color: #fff; border-color: transparent; }
.badge--scope-ultraplan { background: var(--color-scope-ultraplan); color: #fff; border-color: transparent; }
.badge--scope-config { background: var(--color-scope-config); color: #fff; border-color: transparent; }
/* ---------- Cards / surfaces ---------- */
.card {
background: var(--color-surface);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-lg);
padding: var(--space-6);
}
.card--sunken { background: var(--color-surface-sunken); }
.card--raised { box-shadow: var(--shadow-sm); }
/* ---------- Inline messages (Aksel 3-tier) ---------- */
.inline-message {
display: flex;
gap: var(--space-3);
padding: var(--space-3) var(--space-4);
border-radius: var(--radius-md);
border-left: 4px solid;
background: var(--color-bg-soft);
font-size: var(--font-size-sm);
line-height: var(--line-height-snug);
}
.inline-message--info { border-color: var(--color-state-info); background: #EAF3FB; color: #08416B; }
.inline-message--success { border-color: var(--color-state-success); background: var(--color-severity-low-soft); color: var(--color-severity-low-on); }
.inline-message--warning { border-color: var(--color-state-warning); background: var(--color-severity-medium-soft); color: var(--color-severity-medium-on); }
.inline-message--error { border-color: var(--color-severity-critical); background: var(--color-surface); color: var(--color-text-primary); }
.inline-message--error strong, .inline-message--error b { color: var(--color-severity-critical); }
[data-theme="dark"] .inline-message--info { background: #0E2A3F; color: #9CC0EA; }
[data-theme="dark"] .inline-message--error { background: var(--color-surface); color: var(--color-text-primary); }
[data-theme="dark"] .inline-message--error strong, [data-theme="dark"] .inline-message--error b { color: #F09095; }
/* ---------- Form controls ---------- */
.input, .select, .textarea {
width: 100%;
padding: 9px 12px;
font-family: inherit;
font-size: var(--font-size-sm);
line-height: 1.4;
color: var(--color-text-primary);
background: var(--color-surface);
border: 1px solid var(--color-border-moderate);
border-radius: var(--radius-md);
transition: border-color var(--duration-fast) var(--ease-default),
box-shadow var(--duration-fast) var(--ease-default);
}
.input:hover, .select:hover, .textarea:hover { border-color: var(--color-border-strong); }
.input:focus, .select:focus, .textarea:focus {
outline: none;
border-color: var(--color-primary-500);
box-shadow: var(--shadow-focus);
}
.textarea { min-height: 96px; resize: vertical; line-height: var(--line-height-normal); }
.label {
display: block;
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
color: var(--color-text-primary);
margin-bottom: 6px;
}
.label__hint { display: block; font-size: var(--font-size-xs); color: var(--color-text-tertiary); font-weight: 400; margin-top: 2px; }
/* ---------- Layout primitives ---------- */
.stack { display: flex; flex-direction: column; gap: var(--space-4); }
.stack--lg { gap: var(--space-8); }
.stack--sm { gap: var(--space-2); }
.row { display: flex; gap: var(--space-4); align-items: center; }
.row--wrap { flex-wrap: wrap; }
.row--between { justify-content: space-between; }
.container { max-width: var(--container-default); margin: 0 auto; padding: 0 var(--space-6); }
.container--wide { max-width: var(--container-wide); }
.container--narrow { max-width: var(--container-narrow); }
.divider {
height: 1px;
background: var(--color-border-subtle);
border: none;
margin: 0;
}
/* ---------- Utilities ---------- */
.text-secondary { color: var(--color-text-secondary); }
.text-tertiary { color: var(--color-text-tertiary); }
.text-mono { font-family: var(--font-family-mono); }
.text-sm { font-size: var(--font-size-sm); }
.text-xs { font-size: var(--font-size-xs); }
.text-lg { font-size: var(--font-size-lg); }
.font-medium { font-weight: var(--font-weight-medium); }
.font-semibold { font-weight: var(--font-weight-semibold); }
.tabular { font-variant-numeric: tabular-nums; }
.sr-only {
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
/* ---------- Reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
/* ---------- Print ---------- */
@media print {
body { background: #fff; color: #000; font-size: 11pt; }
.no-print, button.btn, nav, .nav, .toolbar, .tweaks-panel { display: none !important; }
.card { border: 1px solid #000; box-shadow: none; break-inside: avoid; }
a { color: #000; text-decoration: underline; }
h1, h2, h3 { break-after: avoid; }
.matrix-cell { print-color-adjust: exact; -webkit-print-color-adjust: exact; }
@page { margin: 18mm; }
}

View file

@ -0,0 +1,352 @@
/* Code generated by sync-design-system.mjs; DO NOT EDIT. */
/* =============================================================================
components-tier2.css Tier 2 components (Phase 2)
7. Decision-tree (AI Act 4-step)
8. Traffic-lights
9. Diff-review
10. Treemap (config-audit token hotspots)
11. Distribution / range-viz (P10/P50/P90)
12. Command-pipeline output
13. Pyramide (AI Act 4-tier)
14. Pipeline-cockpit
15. Verdict-pill with risk-meter
16. Codepoint-reveal (security Unicode steg)
17. Inherent + residual pair (already partially in Tier 1, formalize)
18. Small-multiples grid
============================================================================= */
/* DECISION-TREE — vertical flowchart with 4 colored terminals */
.decision-tree { display: flex; flex-direction: column; align-items: center; gap: 0; }
.dt-node {
padding: 12px 18px;
background: var(--color-surface);
border: 1px solid var(--color-border-moderate);
border-radius: var(--radius-md);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
text-align: center;
min-width: 240px;
max-width: 340px;
}
.dt-edge {
width: 1px; height: 28px; background: var(--color-border-moderate);
position: relative;
}
.dt-edge__label {
position: absolute;
left: 8px; top: 50%; transform: translateY(-50%);
font-size: 11px; color: var(--color-text-tertiary);
white-space: nowrap;
font-family: var(--font-family-mono);
}
.dt-node--terminal { color: #fff; border: none; padding: 14px 20px; font-weight: var(--font-weight-semibold); }
.dt-node--forbidden { background: var(--color-severity-extreme); }
.dt-node--high { background: var(--color-severity-critical); }
.dt-node--limited { background: var(--color-severity-medium); color: var(--color-severity-medium-on); }
.dt-node--minimal { background: var(--color-severity-low); }
.dt-row { display: flex; gap: var(--space-3); }
/* TRAFFIC-LIGHTS */
.traffic-light {
display: inline-flex; align-items: center; gap: 8px;
padding: 6px 12px;
border-radius: var(--radius-md);
background: var(--color-bg-soft);
border: 1px solid var(--color-border-subtle);
font-size: var(--font-size-sm);
}
.traffic-light__dot {
width: 10px; height: 10px; border-radius: 50%;
flex-shrink: 0;
}
.traffic-light[data-status="green"] .traffic-light__dot { background: var(--color-state-success); }
.traffic-light[data-status="yellow"] .traffic-light__dot { background: var(--color-severity-medium); }
.traffic-light[data-status="red"] .traffic-light__dot { background: var(--color-severity-critical); }
.traffic-light[data-status="gray"] .traffic-light__dot { background: var(--color-text-tertiary); }
.traffic-light__label { font-weight: var(--font-weight-medium); }
.traffic-light__why { color: var(--color-text-tertiary); font-size: var(--font-size-xs); }
/* DIFF-REVIEW */
.diff { border: 1px solid var(--color-border-subtle); border-radius: var(--radius-md); overflow: hidden; }
.diff__row { display: grid; grid-template-columns: 1fr 1fr; border-top: 1px solid var(--color-border-subtle); }
.diff__row:first-child { border-top: none; }
.diff__cell { padding: 10px 14px; font-size: var(--font-size-sm); font-family: var(--font-family-mono); }
.diff__cell--removed { background: var(--color-severity-critical-soft); color: var(--color-severity-critical-on); border-right: 1px solid var(--color-border-subtle); }
.diff__cell--added { background: var(--color-severity-low-soft); color: var(--color-severity-low-on); }
.diff__cell--unchanged { color: var(--color-text-secondary); border-right: 1px solid var(--color-border-subtle); }
.diff__summary { display: flex; gap: var(--space-4); padding: 12px 16px; background: var(--color-bg-soft); border-bottom: 1px solid var(--color-border-subtle); font-size: var(--font-size-sm); }
.diff__summary-item { display: flex; gap: 6px; align-items: baseline; }
.diff__summary-count { font-weight: var(--font-weight-semibold); font-variant-numeric: tabular-nums; }
/* TREEMAP — pure CSS treemap with grid */
.treemap {
display: grid;
grid-template-columns: repeat(12, 1fr);
grid-auto-rows: 36px;
gap: 2px;
background: var(--color-border-subtle);
border-radius: var(--radius-md);
overflow: hidden;
padding: 2px;
}
.treemap__tile {
padding: 8px 10px;
font-size: var(--font-size-xs);
display: flex;
flex-direction: column;
justify-content: space-between;
color: #fff;
overflow: hidden;
cursor: pointer;
position: relative;
}
.treemap__tile-label { font-weight: var(--font-weight-semibold); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.treemap__tile-tokens { font-family: var(--font-family-mono); font-size: 11px; opacity: 0.85; }
.treemap__tile[data-kind="claudemd"] { background: #4338CA; }
.treemap__tile[data-kind="plugin"] { background: #0F6E76; }
.treemap__tile[data-kind="skill"] { background: #9A6700; }
.treemap__tile[data-kind="mcp"] { background: #3F5963; }
.treemap__tile[data-kind="hook"] { background: #A40E26; }
/* DISTRIBUTION / range-viz */
.distribution { display: flex; flex-direction: column; gap: var(--space-3); }
.distribution__row { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-3); align-items: center; font-size: var(--font-size-sm); }
.distribution__label { color: var(--color-text-secondary); }
.distribution__track {
position: relative; height: 28px;
background: var(--color-surface-sunken);
border-radius: var(--radius-sm);
overflow: visible;
}
.distribution__band {
position: absolute; top: 6px; bottom: 6px;
background: var(--color-primary-300);
border-radius: var(--radius-pill);
opacity: 0.4;
}
.distribution__median {
position: absolute; top: 0; bottom: 0; width: 2px;
background: var(--color-primary-700);
}
.distribution__median-label {
position: absolute; top: -18px; left: 50%; transform: translateX(-50%);
font-size: 11px; font-family: var(--font-family-mono); white-space: nowrap;
color: var(--color-text-primary); font-weight: var(--font-weight-semibold);
}
.distribution__axis {
display: grid; grid-template-columns: 140px 1fr; gap: var(--space-3);
font-size: 11px; color: var(--color-text-tertiary); font-family: var(--font-family-mono);
margin-top: 4px;
}
.distribution__axis-ticks { display: flex; justify-content: space-between; }
/* COMMAND-PIPELINE OUTPUT */
.cmd-pipeline { display: flex; flex-direction: column; gap: var(--space-2); }
.cmd-step {
display: grid;
grid-template-columns: 32px 1fr auto;
gap: var(--space-3);
padding: 12px 14px;
background: var(--color-surface-sunken);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-md);
align-items: center;
}
.cmd-step__num {
width: 24px; height: 24px;
border-radius: 50%;
background: var(--color-text-primary);
color: var(--color-bg);
display: flex; align-items: center; justify-content: center;
font-family: var(--font-family-mono);
font-size: 11px; font-weight: var(--font-weight-bold);
}
.cmd-step__cmd {
font-family: var(--font-family-mono);
font-size: var(--font-size-sm);
color: var(--color-text-primary);
word-break: break-all;
}
.cmd-step__cmd .cmd-flag { color: var(--color-state-info); }
.cmd-step__cmd .cmd-arg { color: var(--color-severity-medium-on); }
/* PYRAMIDE — AI Act 4-tier */
.pyramide { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.pyramide__tier {
display: flex; align-items: center; justify-content: space-between;
padding: 10px 18px;
color: #fff;
font-weight: var(--font-weight-semibold);
font-size: var(--font-size-sm);
border-radius: var(--radius-sm);
width: 100%;
}
.pyramide__tier--forbidden { background: var(--color-severity-extreme); max-width: 30%; }
.pyramide__tier--high { background: var(--color-severity-critical); max-width: 50%; }
.pyramide__tier--limited { background: var(--color-severity-medium); color: var(--color-severity-medium-on); max-width: 75%; }
.pyramide__tier--minimal { background: var(--color-severity-low); max-width: 100%; }
.pyramide__tier-label { display: flex; gap: var(--space-2); align-items: center; }
.pyramide__tier-share { font-family: var(--font-family-mono); font-size: 11px; opacity: 0.85; }
/* PIPELINE-COCKPIT */
.pipeline-cockpit {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 0;
align-items: stretch;
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-md);
overflow: hidden;
background: var(--color-surface);
}
.pc-stage {
padding: var(--space-3) var(--space-4);
border-right: 1px solid var(--color-border-subtle);
display: flex; flex-direction: column; gap: 4px;
position: relative;
}
.pc-stage:last-child { border-right: none; }
.pc-stage__num { font-family: var(--font-family-mono); font-size: 11px; color: var(--color-text-tertiary); }
.pc-stage__name { font-weight: var(--font-weight-semibold); font-size: var(--font-size-sm); }
.pc-stage__state {
font-size: 11px; padding: 2px 8px; border-radius: var(--radius-pill);
align-self: flex-start; margin-top: 4px;
font-weight: var(--font-weight-medium);
}
.pc-stage__state[data-state="done"] { background: var(--color-severity-low-soft); color: var(--color-severity-low-on); }
.pc-stage__state[data-state="running"] { background: var(--color-severity-medium-soft); color: var(--color-severity-medium-on); }
.pc-stage__state[data-state="empty"] { background: var(--color-bg-soft); color: var(--color-text-tertiary); }
.pc-stage__state[data-state="failed"] { background: var(--color-severity-critical); color: #fff; }
.pc-stage[data-current="true"] { background: var(--color-primary-50); }
[data-theme="dark"] .pc-stage[data-current="true"] { background: var(--color-primary-900); }
/* VERDICT-PILL with risk-meter */
.verdict-block {
display: grid;
grid-template-columns: auto 1fr;
gap: var(--space-6);
align-items: center;
padding: var(--space-5) var(--space-6);
background: var(--color-surface);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-lg);
}
.verdict-pill-lg {
display: flex; flex-direction: column; align-items: center; gap: 2px;
padding: var(--space-4) var(--space-5);
border-radius: var(--radius-md);
font-weight: var(--font-weight-bold);
letter-spacing: 0.04em;
}
.verdict-pill-lg__verdict { font-size: var(--font-size-xl); }
.verdict-pill-lg__sub { font-size: 11px; font-weight: var(--font-weight-medium); opacity: 0.8; text-transform: uppercase; letter-spacing: 0.1em; }
.verdict-pill-lg[data-verdict="block"] { background: var(--color-severity-critical); color: #fff; }
.verdict-pill-lg[data-verdict="warning"] { background: var(--color-severity-medium); color: var(--color-severity-medium-on); }
.verdict-pill-lg[data-verdict="allow"] { background: var(--color-severity-low); color: #fff; }
.risk-meter { display: flex; flex-direction: column; gap: 6px; }
.risk-meter__track {
position: relative;
height: 12px;
background: linear-gradient(to right,
var(--color-severity-low) 0%, var(--color-severity-low) 14%,
var(--color-severity-medium) 14%, var(--color-severity-medium) 39%,
var(--color-severity-high) 39%, var(--color-severity-high) 64%,
var(--color-severity-critical) 64%, var(--color-severity-critical) 84%,
var(--color-severity-extreme) 84%, var(--color-severity-extreme) 100%);
border-radius: var(--radius-pill);
}
.risk-meter__pointer {
position: absolute; top: -4px; bottom: -4px;
width: 4px;
background: var(--color-text-primary);
border-radius: 2px;
box-shadow: 0 0 0 2px var(--color-bg);
}
.risk-meter__scale {
display: flex; justify-content: space-between;
font-size: 11px; color: var(--color-text-tertiary);
font-family: var(--font-family-mono);
}
.risk-meter__bands {
display: flex; justify-content: space-between;
font-size: 11px; color: var(--color-text-secondary);
}
.risk-meter__readout {
display: flex; align-items: baseline; gap: 8px;
}
.risk-meter__score {
font-size: var(--font-size-3xl); font-weight: var(--font-weight-bold);
font-variant-numeric: tabular-nums;
letter-spacing: -0.02em;
}
.risk-meter__band-label { font-size: var(--font-size-sm); color: var(--color-text-secondary); }
/* CODEPOINT-REVEAL */
.codepoint-reveal { background: var(--color-surface-sunken); border: 1px solid var(--color-border-subtle); border-radius: var(--radius-md); overflow: hidden; }
.codepoint-reveal__head { padding: 10px 14px; background: var(--color-bg-soft); border-bottom: 1px solid var(--color-border-subtle); display: flex; justify-content: space-between; align-items: center; }
.codepoint-reveal__body { padding: var(--space-4); display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4); }
.codepoint-reveal__col { display: flex; flex-direction: column; gap: 8px; }
.codepoint-reveal__col-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-tertiary); font-weight: var(--font-weight-semibold); }
.codepoint-reveal__source {
font-family: var(--font-family-mono);
font-size: var(--font-size-sm);
padding: 12px;
background: var(--color-surface);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-sm);
min-height: 64px;
word-break: break-all;
white-space: pre-wrap;
}
.cp-tag { background: var(--color-severity-critical); color: #fff; padding: 1px 4px; border-radius: 2px; font-size: 11px; }
.cp-zw { background: var(--color-severity-medium); color: var(--color-severity-medium-on); padding: 1px 4px; border-radius: 2px; font-size: 11px; }
.cp-bidi { background: var(--color-severity-high); color: #fff; padding: 1px 4px; border-radius: 2px; font-size: 11px; }
.codepoint-reveal__decoded {
font-family: var(--font-family-mono);
font-size: var(--font-size-sm);
padding: 12px;
background: var(--color-text-primary);
color: var(--color-bg);
border-radius: var(--radius-sm);
word-break: break-all;
}
/* SMALL-MULTIPLES GRID (16-category posture) */
.small-multiples {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: var(--space-3);
}
.sm-card {
padding: var(--space-3);
background: var(--color-surface);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-md);
display: flex; flex-direction: column; gap: 6px;
}
.sm-card__header { display: flex; justify-content: space-between; align-items: baseline; }
.sm-card__name { font-size: var(--font-size-xs); font-weight: var(--font-weight-semibold); color: var(--color-text-secondary); text-transform: uppercase; letter-spacing: 0.04em; }
.sm-card__grade {
font-family: var(--font-family-mono);
font-size: var(--font-size-lg);
font-weight: var(--font-weight-bold);
width: 28px; height: 28px;
display: flex; align-items: center; justify-content: center;
border-radius: var(--radius-sm);
}
.sm-card__grade[data-grade="A"] { background: var(--color-severity-low); color: #fff; }
.sm-card__grade[data-grade="B"] { background: var(--color-severity-low-soft); color: var(--color-severity-low-on); }
.sm-card__grade[data-grade="C"] { background: var(--color-severity-medium-soft); color: var(--color-severity-medium-on); }
.sm-card__grade[data-grade="D"] { background: var(--color-severity-high-soft); color: var(--color-severity-high-on); }
.sm-card__grade[data-grade="F"] { background: var(--color-severity-critical); color: #fff; }
.sm-card__bar { height: 4px; background: var(--color-surface-sunken); border-radius: var(--radius-pill); overflow: hidden; }
.sm-card__bar-fill { height: 100%; background: var(--color-primary-500); }
.sm-card__status { font-size: 11px; color: var(--color-text-tertiary); }
@media (max-width: 880px) { .small-multiples { grid-template-columns: repeat(2, 1fr); } }
/* OWASP badges (specific colors) */
.badge--owasp-llm { background: #1F2328; color: #fff; }
.badge--owasp-asi { background: #4338CA; color: #fff; }
.badge--owasp-ast { background: #9A6700; color: #fff; }
.badge--owasp-mcp { background: #0F6E76; color: #fff; }

View file

@ -0,0 +1,887 @@
/* Code generated by sync-design-system.mjs; DO NOT EDIT. */
/* =============================================================================
components-tier3-supplement.css
Tier 3 supplement 12 components added after Tier 3 main set.
Pinned rules:
- No big pink fills for text. Use surface bg + colored border + dark body text.
- severity-critical (#A40E26) state-failed (#7D1A1A). Don't conflate.
- Light + dark theme via existing tokens only.
============================================================================= */
/* =========================================================================
1. Sankey / Toxic-Flow Chain (.tfa-flow)
3-step: Input Access Exfil with mitigation shields breaking the chain.
========================================================================= */
.tfa-flow {
display: grid;
grid-template-columns: 1fr auto 1fr auto 1fr;
gap: 0;
align-items: stretch;
background: var(--color-surface);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-lg);
padding: var(--space-5);
position: relative;
}
.tfa-flow__verdict {
position: absolute;
top: -12px; right: var(--space-5);
padding: 4px 10px;
font-size: 11px;
font-weight: var(--font-weight-bold);
letter-spacing: 0.06em;
border-radius: var(--radius-pill);
background: var(--color-severity-critical);
color: #fff;
}
.tfa-flow__verdict[data-verdict="ALLOW"] { background: var(--color-state-success); }
.tfa-flow__verdict[data-verdict="WARN"] { background: var(--color-severity-medium); color: #fff; }
.tfa-flow__verdict[data-verdict="BLOCK"] { background: var(--color-severity-critical); }
.tfa-leg {
display: flex; flex-direction: column; gap: 6px;
padding: var(--space-3);
background: var(--color-surface);
border: 1px solid var(--color-border-subtle);
border-left-width: 4px;
border-radius: var(--radius-md);
cursor: pointer;
transition: background var(--duration-fast) var(--ease-default);
text-align: left;
}
.tfa-leg:hover { background: var(--color-bg-soft); }
.tfa-leg:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.tfa-leg[data-severity="medium"] { border-left-color: var(--color-severity-medium); }
.tfa-leg[data-severity="high"] { border-left-color: var(--color-severity-high); }
.tfa-leg[data-severity="critical"] { border-left-color: var(--color-severity-critical); }
.tfa-leg__label {
font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em;
color: var(--color-text-tertiary); font-weight: var(--font-weight-semibold);
}
.tfa-leg__name { font-size: var(--font-size-md); font-weight: var(--font-weight-semibold); color: var(--color-text-primary); }
.tfa-leg__source { font-family: var(--font-family-mono); font-size: 12px; color: var(--color-text-secondary); }
.tfa-leg__status {
margin-top: auto;
font-size: 11px;
font-weight: var(--font-weight-medium);
display: inline-flex; align-items: center; gap: 4px;
}
.tfa-leg__status[data-mit="unmitigated"] { color: var(--color-severity-critical); }
.tfa-leg__status[data-mit="partially_mitigated"] { color: var(--color-severity-medium); }
.tfa-leg__status[data-mit="mitigated"] { color: var(--color-state-success); }
/* Arrow connectors. Width grows with severity */
.tfa-arrow {
display: flex; align-items: center; justify-content: center;
position: relative;
min-width: 56px;
padding: 0 4px;
}
.tfa-arrow__line {
height: 4px;
width: 100%;
background: var(--color-border-moderate);
position: relative;
}
.tfa-arrow[data-severity="medium"] .tfa-arrow__line { background: var(--color-severity-medium); height: 6px; }
.tfa-arrow[data-severity="high"] .tfa-arrow__line { background: var(--color-severity-high); height: 8px; }
.tfa-arrow[data-severity="critical"] .tfa-arrow__line { background: var(--color-severity-critical); height: 10px; }
.tfa-arrow__line::after {
content: ""; position: absolute; right: -1px; top: 50%;
width: 0; height: 0;
border-left: 10px solid currentColor;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
transform: translateY(-50%);
color: inherit;
}
.tfa-arrow[data-severity="medium"] .tfa-arrow__line { color: var(--color-severity-medium); }
.tfa-arrow[data-severity="high"] .tfa-arrow__line { color: var(--color-severity-high); }
.tfa-arrow[data-severity="critical"] .tfa-arrow__line { color: var(--color-severity-critical); }
.tfa-arrow__shield {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
width: 32px; height: 32px;
background: var(--color-state-success);
color: #fff;
border-radius: 50%;
display: flex; align-items: center; justify-content: center;
border: 3px solid var(--color-surface);
font-size: 16px;
}
.tfa-arrow--mitigated .tfa-arrow__line {
background: repeating-linear-gradient(90deg, var(--color-state-success) 0 4px, transparent 4px 8px);
}
@media (max-width: 720px) {
.tfa-flow {
grid-template-columns: 1fr;
grid-template-rows: auto auto auto auto auto;
}
.tfa-arrow { min-height: 48px; min-width: auto; }
.tfa-arrow__line { width: 4px; height: 100%; }
.tfa-arrow[data-severity="medium"] .tfa-arrow__line { width: 6px; height: 100%; }
.tfa-arrow[data-severity="high"] .tfa-arrow__line { width: 8px; height: 100%; }
.tfa-arrow[data-severity="critical"] .tfa-arrow__line { width: 10px; height: 100%; }
.tfa-arrow__line::after {
right: 50%; top: auto; bottom: -1px; transform: translateX(50%);
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-top: 10px solid currentColor;
border-bottom: none;
}
}
/* =========================================================================
2. Fleet-Overview (.fleet-grid, .fleet-tile)
========================================================================= */
.fleet-toolbar {
display: flex; gap: var(--space-3); flex-wrap: wrap;
align-items: center;
padding: var(--space-3) var(--space-4);
background: var(--color-bg-soft);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-md);
margin-bottom: var(--space-3);
}
.fleet-toolbar__label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-tertiary); font-weight: var(--font-weight-semibold); }
.fleet-toolbar__spacer { flex: 1; }
.fleet-toolbar__count { font-size: var(--font-size-sm); color: var(--color-text-secondary); }
.fleet-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: var(--space-3);
}
@media (max-width: 980px) { .fleet-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 540px) { .fleet-grid { grid-template-columns: 1fr; } }
.fleet-tile {
background: var(--color-surface);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-md);
padding: var(--space-3);
display: grid;
grid-template-rows: auto auto auto auto;
gap: 6px;
cursor: pointer;
transition: border-color var(--duration-fast), transform var(--duration-fast);
}
.fleet-tile:hover { border-color: var(--color-primary-300); transform: translateY(-1px); }
.fleet-tile:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.fleet-tile__row { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.fleet-tile__name {
font-family: var(--font-family-mono);
font-size: 12px;
color: var(--color-text-primary);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
flex: 1;
}
.fleet-tile__grade {
width: 28px; height: 28px;
display: flex; align-items: center; justify-content: center;
font-weight: var(--font-weight-bold);
font-size: 13px;
border-radius: var(--radius-sm);
color: #fff;
flex-shrink: 0;
}
.fleet-tile__grade[data-grade="A"] { background: var(--color-state-success); }
.fleet-tile__grade[data-grade="B"] { background: #4D8E2F; }
.fleet-tile__grade[data-grade="C"] { background: var(--color-severity-medium); }
.fleet-tile__grade[data-grade="D"] { background: var(--color-severity-high); }
.fleet-tile__grade[data-grade="E"] { background: var(--color-severity-critical); }
.fleet-tile__grade[data-grade="F"] { background: var(--color-severity-extreme); }
.fleet-tile__meter {
height: 6px; border-radius: 3px;
background: var(--color-bg-soft);
overflow: hidden;
position: relative;
}
.fleet-tile__meter-fill { height: 100%; border-radius: 3px; }
.fleet-tile__meter-fill[data-band="1"] { background: var(--color-state-success); }
.fleet-tile__meter-fill[data-band="2"] { background: var(--color-severity-medium); }
.fleet-tile__meter-fill[data-band="3"] { background: var(--color-severity-high); }
.fleet-tile__meter-fill[data-band="4"] { background: var(--color-severity-critical); }
.fleet-tile__chip {
display: inline-flex; align-items: center;
font-size: 11px;
padding: 2px 8px;
border-radius: var(--radius-pill);
background: var(--color-bg-soft);
color: var(--color-text-secondary);
border: 1px solid var(--color-border-subtle);
width: fit-content;
}
.fleet-tile__meta {
display: flex; justify-content: space-between;
font-size: 11px; color: var(--color-text-tertiary);
font-family: var(--font-family-mono);
}
.fleet-tile__trend--better { color: var(--color-state-success); }
.fleet-tile__trend--worse { color: var(--color-severity-critical); }
.fleet-tile__trend--stable { color: var(--color-text-tertiary); }
/* =========================================================================
3. Kanban Keep / Review / Remove (.kanban-board)
========================================================================= */
.kanban-board {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--space-4);
}
@media (max-width: 820px) { .kanban-board { grid-template-columns: 1fr; } }
.kanban-col {
background: var(--color-bg-soft);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-md);
padding: var(--space-3);
display: flex; flex-direction: column; gap: var(--space-3);
min-height: 320px;
}
.kanban-col__head {
display: flex; align-items: center; justify-content: space-between;
padding-bottom: var(--space-2);
border-bottom: 2px solid var(--color-border-subtle);
}
.kanban-col[data-bucket="keep"] .kanban-col__head { border-bottom-color: var(--color-state-success); }
.kanban-col[data-bucket="review"] .kanban-col__head { border-bottom-color: var(--color-state-warning); }
.kanban-col[data-bucket="remove"] .kanban-col__head { border-bottom-color: var(--color-severity-critical); }
.kanban-col__title { font-size: var(--font-size-md); font-weight: var(--font-weight-semibold); color: var(--color-text-primary); }
.kanban-col__count {
font-family: var(--font-family-mono);
font-size: 12px;
background: var(--color-surface);
padding: 2px 8px;
border-radius: var(--radius-pill);
color: var(--color-text-secondary);
border: 1px solid var(--color-border-subtle);
}
.kanban-card {
background: var(--color-surface);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-md);
padding: var(--space-3);
cursor: grab;
display: flex; flex-direction: column; gap: 6px;
transition: box-shadow var(--duration-fast);
}
.kanban-card:hover { box-shadow: var(--shadow-md); }
.kanban-card[data-verdict="BLOCK"] { border-color: var(--color-severity-critical); border-left-width: 4px; }
.kanban-card[data-verdict="trusted"] { border-left: 4px solid var(--color-state-success); }
.kanban-card[data-verdict="unknown"] { border-left: 4px solid var(--color-state-warning); }
.kanban-card__name { font-family: var(--font-family-mono); font-size: 13px; color: var(--color-text-primary); word-break: break-all; }
.kanban-card__meta { font-size: 11px; color: var(--color-text-tertiary); }
.kanban-card__reason { font-size: 12px; color: var(--color-text-secondary); }
.kanban-col__empty {
margin: auto;
text-align: center;
color: var(--color-text-tertiary);
font-size: var(--font-size-sm);
padding: var(--space-4);
}
.kanban-col__empty button { margin-top: var(--space-2); }
.kanban-actions { display: flex; gap: 4px; margin-top: 4px; }
.kanban-actions button {
flex: 1; font-size: 11px; padding: 4px 6px;
background: var(--color-bg-soft); border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-sm); color: var(--color-text-secondary);
cursor: pointer; font-family: inherit;
}
.kanban-actions button:hover { background: var(--color-surface-sunken); color: var(--color-text-primary); }
/* =========================================================================
4. Maturity-Ladder (.mat-ladder)
========================================================================= */
.mat-ladder {
display: flex; flex-direction: column;
background: var(--color-surface);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-md);
padding: var(--space-4);
gap: 0;
}
.mat-step {
display: grid;
grid-template-columns: 56px 1fr;
gap: var(--space-4);
padding: var(--space-3) 0;
position: relative;
}
.mat-step + .mat-step { border-top: 1px dashed var(--color-border-subtle); }
.mat-step__icon {
width: 44px; height: 44px;
border-radius: 50%;
display: flex; align-items: center; justify-content: center;
background: var(--color-surface);
border: 2px solid var(--color-border-moderate);
color: var(--color-text-tertiary);
font-weight: var(--font-weight-semibold);
font-size: 15px;
position: relative;
z-index: 1;
}
.mat-step[data-state="completed"] .mat-step__icon {
background: var(--color-state-success);
border-color: var(--color-state-success);
color: #fff;
}
.mat-step[data-state="current"] .mat-step__icon {
border-color: var(--color-primary-500);
color: var(--color-primary-700);
background: var(--color-surface);
}
/* progress ring around current step */
.mat-step__ring {
position: absolute;
inset: -4px;
border-radius: 50%;
pointer-events: none;
}
.mat-step__ring svg { width: 100%; height: 100%; transform: rotate(-90deg); }
.mat-step__ring circle { fill: none; stroke-width: 3; }
.mat-step__ring .ring-bg { stroke: var(--color-border-subtle); }
.mat-step__ring .ring-fill { stroke: var(--color-primary-500); }
.mat-step__name {
font-size: var(--font-size-md);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
display: flex; align-items: center; gap: 8px;
}
.mat-step[data-state="completed"] .mat-step__name { color: var(--color-text-secondary); }
.mat-step[data-state="future"] .mat-step__name { color: var(--color-text-tertiary); }
.mat-step__pill {
font-size: 11px; padding: 2px 8px; border-radius: var(--radius-pill);
text-transform: uppercase; letter-spacing: 0.06em; font-weight: var(--font-weight-semibold);
}
.mat-step__pill--current { background: var(--color-primary-100); color: var(--color-primary-700); }
.mat-step__pill--complete { background: transparent; color: var(--color-state-success); border: 1px solid currentColor; }
.mat-step__desc {
font-size: var(--font-size-sm);
color: var(--color-text-secondary);
margin-top: 2px;
max-width: 60ch;
}
.mat-step__progress {
margin-top: 6px;
display: flex; align-items: center; gap: 8px;
font-size: 12px; color: var(--color-text-tertiary);
}
.mat-step__progress-bar {
flex: 1; height: 4px;
background: var(--color-bg-soft);
border-radius: 2px;
overflow: hidden;
max-width: 200px;
}
.mat-step__progress-fill { height: 100%; background: var(--color-primary-500); border-radius: 2px; }
/* =========================================================================
5. Classify-and-Transform / 5-Bucket-Sorter (.cls-sorter)
========================================================================= */
.cls-sorter { display: flex; flex-direction: column; gap: var(--space-4); }
.cls-input {
background: var(--color-surface);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-md);
padding: var(--space-3);
}
.cls-input textarea {
width: 100%; min-height: 100px;
font-family: var(--font-family-sans);
font-size: var(--font-size-sm);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-sm);
padding: var(--space-2) var(--space-3);
background: var(--color-bg);
color: var(--color-text-primary);
resize: vertical;
}
.cls-input textarea:focus { outline: none; box-shadow: var(--shadow-focus); border-color: var(--color-border-focus); }
.cls-buckets {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: var(--space-3);
}
@media (max-width: 1100px) { .cls-buckets { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 720px) { .cls-buckets { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 460px) { .cls-buckets { grid-template-columns: 1fr; } }
.cls-bucket {
background: var(--color-surface);
border: 1px solid var(--color-border-subtle);
border-top-width: 4px;
border-radius: var(--radius-md);
padding: var(--space-3);
display: flex; flex-direction: column; gap: var(--space-2);
min-height: 200px;
}
.cls-bucket[data-egnethet="lav"] { border-top-color: var(--color-text-tertiary); }
.cls-bucket[data-egnethet="medium"] { border-top-color: var(--color-state-info); }
.cls-bucket[data-egnethet="hoy"] { border-top-color: var(--color-state-success); }
.cls-bucket__head {
display: flex; flex-direction: column; gap: 2px;
padding-bottom: var(--space-2);
border-bottom: 1px solid var(--color-border-subtle);
}
.cls-bucket__title { font-size: var(--font-size-sm); font-weight: var(--font-weight-semibold); color: var(--color-text-primary); }
.cls-bucket__egnethet {
font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em;
color: var(--color-text-tertiary); font-weight: var(--font-weight-semibold);
}
.cls-bucket[data-egnethet="lav"] .cls-bucket__egnethet { color: var(--color-text-tertiary); }
.cls-bucket[data-egnethet="medium"] .cls-bucket__egnethet { color: var(--color-state-info); }
.cls-bucket[data-egnethet="hoy"] .cls-bucket__egnethet { color: var(--color-state-success); }
.cls-item {
background: var(--color-bg-soft);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-sm);
padding: 6px 8px;
font-size: 12px;
color: var(--color-text-primary);
cursor: grab;
display: flex; flex-direction: column; gap: 2px;
}
.cls-item__action {
font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em;
color: var(--color-text-tertiary); font-weight: var(--font-weight-medium);
}
.cls-bucket__action {
margin-top: auto;
padding-top: var(--space-2);
border-top: 1px dashed var(--color-border-subtle);
}
.cls-bucket__empty {
font-size: 12px; color: var(--color-text-tertiary);
font-style: italic;
text-align: center;
padding: var(--space-3);
}
/* =========================================================================
6. Cycle Position Ribbon (.cycle-ribbon)
========================================================================= */
.cycle-ribbon {
position: relative;
background: var(--color-surface);
border-bottom: 1px solid var(--color-border-subtle);
padding: 8px var(--space-5);
display: flex; align-items: center; gap: var(--space-4);
font-size: 13px;
cursor: pointer;
overflow: hidden;
}
.cycle-ribbon::before {
content: ""; position: absolute; inset: 0;
background: var(--color-state-info);
opacity: 0.06;
width: var(--cycle-progress, 0%);
transition: width var(--duration-normal);
}
.cycle-ribbon[data-phase="planning"] { border-bottom-color: var(--color-state-info); }
.cycle-ribbon[data-phase="planning"]::before { background: var(--color-state-info); }
.cycle-ribbon[data-phase="execution"] { border-bottom-color: var(--color-state-success); }
.cycle-ribbon[data-phase="execution"]::before { background: var(--color-state-success); }
.cycle-ribbon[data-phase="retrospective_prep"] { border-bottom-color: var(--color-severity-medium); }
.cycle-ribbon[data-phase="retrospective_prep"]::before { background: var(--color-severity-medium); }
.cycle-ribbon > * { position: relative; z-index: 1; }
.cycle-ribbon__id { font-family: var(--font-family-mono); font-weight: var(--font-weight-semibold); color: var(--color-text-primary); white-space: nowrap; flex-shrink: 0; }
.cycle-ribbon__week { color: var(--color-text-secondary); font-family: var(--font-family-mono); white-space: nowrap; flex-shrink: 0; }
.cycle-ribbon__phase {
font-size: 11px; padding: 2px 8px;
border-radius: var(--radius-pill);
text-transform: uppercase; letter-spacing: 0.06em;
font-weight: var(--font-weight-semibold);
white-space: nowrap; flex-shrink: 0;
}
.cycle-ribbon[data-phase="planning"] .cycle-ribbon__phase { background: var(--color-primary-100); color: var(--color-primary-700); }
.cycle-ribbon[data-phase="execution"] .cycle-ribbon__phase { background: var(--color-severity-low-soft); color: var(--color-severity-low-on); }
.cycle-ribbon[data-phase="retrospective_prep"] .cycle-ribbon__phase { background: var(--color-severity-medium-soft); color: var(--color-severity-medium-on); }
.cycle-ribbon__msg { color: var(--color-text-secondary); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cycle-ribbon__chev { color: var(--color-text-tertiary); transition: transform var(--duration-fast); }
.cycle-ribbon[aria-expanded="true"] .cycle-ribbon__chev { transform: rotate(180deg); }
.cycle-ribbon__panel {
background: var(--color-bg-soft);
border-bottom: 1px solid var(--color-border-subtle);
padding: var(--space-4) var(--space-5);
display: none;
font-size: var(--font-size-sm);
}
.cycle-ribbon__panel[data-open="true"] { display: block; }
@media (max-width: 720px) {
.cycle-ribbon__msg { display: none; }
}
/* =========================================================================
7. Persistent-Antipattern Badge (.pap-badge)
========================================================================= */
.pap-badge {
display: inline-flex; align-items: center; gap: 6px;
padding: 4px 10px;
background: var(--color-surface);
border: 1px solid var(--color-severity-critical);
border-radius: var(--radius-pill);
font-size: 12px;
font-weight: var(--font-weight-medium);
color: var(--color-severity-critical);
cursor: pointer;
position: relative;
}
.pap-badge::before {
content: "";
width: 8px; height: 8px;
border-radius: 50%;
background: var(--color-severity-critical);
animation: pap-pulse 2.4s var(--ease-default) infinite;
}
@keyframes pap-pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.45; transform: scale(0.7); }
}
@media (prefers-reduced-motion: reduce) {
.pap-badge::before { animation: none; opacity: 1; }
}
.pap-badge__count { font-family: var(--font-family-mono); font-weight: var(--font-weight-semibold); }
.pap-detail {
margin-top: var(--space-3);
background: var(--color-surface);
border: 1px solid var(--color-severity-critical);
border-left-width: 4px;
border-radius: var(--radius-md);
padding: var(--space-4);
display: none;
}
.pap-detail[data-open="true"] { display: block; }
.pap-detail h4 { margin: 0 0 4px; color: var(--color-severity-critical); font-size: var(--font-size-md); }
.pap-detail__cycles { display: flex; gap: 4px; flex-wrap: wrap; margin: var(--space-2) 0; }
.pap-detail__cycle {
font-family: var(--font-family-mono);
font-size: 11px;
padding: 2px 6px;
background: var(--color-bg-soft);
border-radius: var(--radius-sm);
color: var(--color-text-secondary);
}
.pap-detail__rec {
background: var(--color-bg-soft);
border-radius: var(--radius-sm);
padding: var(--space-2) var(--space-3);
margin-top: var(--space-2);
font-size: var(--font-size-sm);
color: var(--color-text-primary);
}
/* one-shot variant */
.pap-badge--oneshot {
border-style: dashed;
border-color: var(--color-severity-medium);
color: var(--color-severity-medium);
}
.pap-badge--oneshot::before { display: none; }
/* =========================================================================
8. Suppressed-Signals Panel (.suppressed)
========================================================================= */
.suppressed {
background: var(--color-bg-soft);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-md);
overflow: hidden;
}
.suppressed__head {
width: 100%;
display: flex; align-items: center; gap: var(--space-3);
padding: var(--space-3) var(--space-4);
background: transparent;
border: 0;
cursor: pointer;
font-family: inherit;
text-align: left;
color: var(--color-text-secondary);
}
.suppressed__head:hover { background: var(--color-surface-sunken); color: var(--color-text-primary); }
.suppressed__head:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.suppressed__chev { color: var(--color-text-tertiary); transition: transform var(--duration-fast); }
.suppressed[aria-expanded="true"] .suppressed__chev { transform: rotate(90deg); }
.suppressed__label { font-size: var(--font-size-sm); }
.suppressed__count {
font-family: var(--font-family-mono);
font-size: 12px;
background: var(--color-surface);
padding: 2px 8px;
border-radius: var(--radius-pill);
color: var(--color-text-secondary);
border: 1px solid var(--color-border-subtle);
margin-left: auto;
}
.suppressed__body {
display: none;
padding: 0 var(--space-4) var(--space-4);
}
.suppressed[aria-expanded="true"] .suppressed__body { display: block; }
.suppressed-group {
background: var(--color-surface);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-sm);
padding: var(--space-3);
}
.suppressed-group + .suppressed-group { margin-top: var(--space-2); }
.suppressed-group__head {
display: flex; justify-content: space-between; align-items: center; gap: 8px;
margin-bottom: 4px;
}
.suppressed-group__reason { font-family: var(--font-family-mono); font-size: 12px; color: var(--color-text-tertiary); }
.suppressed-group__count { font-size: 11px; color: var(--color-text-tertiary); }
.suppressed-group__desc { font-size: var(--font-size-sm); color: var(--color-text-secondary); margin: 0 0 6px; }
.suppressed-group__examples {
display: flex; gap: 4px; flex-wrap: wrap;
}
.suppressed-group__example {
font-family: var(--font-family-mono);
font-size: 11px;
background: var(--color-bg-soft);
padding: 2px 6px;
border-radius: var(--radius-sm);
color: var(--color-text-secondary);
}
/* =========================================================================
9. ExpansionCard (Aksel) (.expansion)
========================================================================= */
.expansion {
background: var(--color-surface);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-md);
overflow: hidden;
}
.expansion + .expansion { margin-top: var(--space-2); }
.expansion__head {
width: 100%;
display: flex; align-items: flex-start; gap: var(--space-3);
padding: var(--space-3) var(--space-4);
background: transparent;
border: 0;
cursor: pointer;
font-family: inherit;
text-align: left;
}
.expansion__head:hover { background: var(--color-bg-soft); }
.expansion__head:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.expansion__title { flex: 1; }
.expansion__title-main { font-size: var(--font-size-md); color: var(--color-text-primary); font-weight: var(--font-weight-medium); }
.expansion__title-sub { font-size: var(--font-size-sm); color: var(--color-text-secondary); margin-top: 2px; }
.expansion__chev {
color: var(--color-text-tertiary);
transition: transform var(--duration-normal) var(--ease-default);
flex-shrink: 0;
margin-top: 2px;
}
.expansion[aria-expanded="true"] .expansion__chev { transform: rotate(180deg); }
.expansion__body {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows var(--duration-normal) var(--ease-default);
}
.expansion[aria-expanded="true"] .expansion__body { grid-template-rows: 1fr; }
.expansion__body-inner { overflow: hidden; }
.expansion__body-inner > div {
padding: 0 var(--space-4) var(--space-4);
border-top: 1px solid var(--color-border-subtle);
padding-top: var(--space-3);
margin-top: -1px;
}
@media (prefers-reduced-motion: reduce) {
.expansion__body { transition: none; }
}
/* =========================================================================
10. ReadMore (Aksel) (.read-more)
========================================================================= */
.read-more {
display: inline;
}
.read-more__trigger {
display: inline-flex; align-items: center; gap: 4px;
background: transparent;
border: 0;
color: var(--color-text-link);
font-family: inherit;
font-size: inherit;
font-weight: var(--font-weight-medium);
cursor: pointer;
padding: 0;
text-decoration: underline;
text-decoration-thickness: 1px;
text-underline-offset: 3px;
}
.read-more__trigger:hover { color: var(--color-text-link-hover); }
.read-more__trigger:focus-visible { outline: none; box-shadow: var(--shadow-focus); border-radius: 2px; }
.read-more__chev { transition: transform var(--duration-fast); }
.read-more[aria-expanded="true"] .read-more__chev { transform: rotate(180deg); }
.read-more__body { display: none; margin-top: var(--space-2); }
.read-more[aria-expanded="true"] .read-more__body { display: block; }
/* =========================================================================
11. FormProgress (Aksel multi-step skjema) (.form-progress)
========================================================================= */
.form-progress {
background: var(--color-surface);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-md);
padding: var(--space-4);
display: flex; flex-direction: column; gap: var(--space-3);
width: 280px;
position: sticky;
top: var(--space-4);
}
.form-progress__autosave {
display: flex; align-items: center; gap: 6px;
font-size: 12px;
color: var(--color-text-tertiary);
padding-bottom: var(--space-2);
border-bottom: 1px solid var(--color-border-subtle);
}
.form-progress__autosave-dot {
width: 6px; height: 6px;
border-radius: 50%;
background: var(--color-state-success);
}
.form-progress__steps { display: flex; flex-direction: column; gap: 2px; }
.fp-step {
display: grid;
grid-template-columns: 28px 1fr;
gap: var(--space-2);
align-items: start;
padding: 8px;
border-radius: var(--radius-sm);
text-align: left;
background: transparent;
border: 0;
cursor: pointer;
font-family: inherit;
position: relative;
}
.fp-step:hover { background: var(--color-bg-soft); }
.fp-step:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.fp-step[disabled] { cursor: not-allowed; opacity: 0.5; }
.fp-step__num {
width: 22px; height: 22px;
border-radius: 50%;
display: flex; align-items: center; justify-content: center;
background: var(--color-surface);
border: 1.5px solid var(--color-border-moderate);
color: var(--color-text-tertiary);
font-size: 11px;
font-weight: var(--font-weight-semibold);
}
.fp-step[data-state="done"] .fp-step__num {
background: var(--color-state-success);
border-color: var(--color-state-success);
color: #fff;
}
.fp-step[data-state="in-progress"] .fp-step__num {
border-color: var(--color-primary-500);
color: var(--color-primary-700);
font-weight: var(--font-weight-bold);
}
.fp-step__name { font-size: var(--font-size-sm); color: var(--color-text-primary); font-weight: var(--font-weight-medium); }
.fp-step[data-state="done"] .fp-step__name { color: var(--color-text-secondary); font-weight: var(--font-weight-regular); }
.fp-step[data-state="in-progress"] .fp-step__name { color: var(--color-primary-700); font-weight: var(--font-weight-semibold); }
.fp-step__progress {
margin-top: 4px;
font-size: 11px;
color: var(--color-text-tertiary);
display: flex; align-items: center; gap: 6px;
}
.fp-step__bar {
flex: 1; height: 3px;
background: var(--color-bg-soft);
border-radius: 2px; overflow: hidden;
max-width: 80px;
}
.fp-step__bar-fill { height: 100%; background: var(--color-primary-500); }
.form-progress__remaining {
padding-top: var(--space-2);
border-top: 1px solid var(--color-border-subtle);
font-size: 12px; color: var(--color-text-tertiary);
display: flex; justify-content: space-between;
}
/* =========================================================================
12. Aspirational vs Committed Visual (.okr-mode)
Modifier added to OKR Objective cards
========================================================================= */
.okr-mode {
position: relative;
background: var(--color-surface);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-md);
padding: var(--space-4);
}
.okr-mode__gauge {
position: relative;
width: 88px; height: 88px;
display: flex; align-items: center; justify-content: center;
flex-shrink: 0;
}
.okr-mode__gauge svg { position: absolute; inset: 0; transform: rotate(-90deg); width: 100%; height: 100%; }
.okr-mode__gauge circle.gauge-bg { fill: none; stroke: var(--color-border-subtle); stroke-width: 6; }
.okr-mode__gauge circle.gauge-fill { fill: none; stroke: var(--color-state-success); stroke-width: 6; stroke-linecap: round; }
.okr-mode__gauge .gauge-value { font-family: var(--font-family-mono); font-size: 22px; font-weight: var(--font-weight-bold); color: var(--color-text-primary); position: relative; z-index: 1; }
/* aspirational variant — dashed stroke */
.okr-mode[data-mode="aspirational"] .okr-mode__gauge circle.gauge-fill {
stroke: var(--color-scope-okr);
stroke-dasharray: 6 4;
}
.okr-mode__badge {
position: absolute;
top: var(--space-2); right: var(--space-2);
font-size: 10px; font-weight: var(--font-weight-bold); letter-spacing: 0.08em;
padding: 2px 8px;
border-radius: var(--radius-sm);
}
.okr-mode[data-mode="aspirational"] .okr-mode__badge {
background: transparent;
color: var(--color-scope-okr);
border: 1px dashed var(--color-scope-okr);
}
.okr-mode[data-mode="committed"] .okr-mode__badge {
background: var(--color-primary-700);
color: #fff;
}
.okr-mode__row { display: flex; gap: var(--space-4); align-items: center; }
.okr-mode__objective { font-size: var(--font-size-md); color: var(--color-text-primary); flex: 1; }
.okr-mode__hint { font-size: 12px; color: var(--color-text-tertiary); margin-top: 4px; }

View file

@ -0,0 +1,717 @@
/* Code generated by sync-design-system.mjs; DO NOT EDIT. */
/* =============================================================================
components-tier3.css Tier 3 components (Phase 2)
Critical components for ms-ai-architect Playground v3 + universal Aksel patterns.
19. Inherent + residual pair (before/after matrix transition)
20. AI Act compliance-tidslinje (4-milepel timeline + countdown)
21. 3-track entry (Guide/Explore/Expert carried from Playground v2)
22. FRIA rights-matrix (12 EU Charter rights × impact level)
23. Capability-matrix (license × kapabilitet available/cost/missing/conditional)
24. Parallel-agent-status panel (multi-worker status grid)
25. ErrorSummary (Aksel/GOV.UK form error pattern)
26. GuidePanel (Aksel friendly inline guidance)
============================================================================= */
/* =============================================================================
19. INHERENT + RESIDUAL PAIR
Used by: ROS (before/after mitigation), DPIA, AI Act mitigations, OKR check-ins
Pattern: two cells/scores side-by-side with arrow showing transition.
============================================================================= */
.pair-before-after {
display: grid;
grid-template-columns: 1fr auto 1fr;
gap: var(--space-4);
align-items: center;
}
.pair-before-after__cell {
background: var(--color-surface);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-md);
padding: var(--space-3) var(--space-4);
display: flex;
flex-direction: column;
gap: 4px;
}
.pair-before-after__cell-label {
font-size: var(--font-size-xs);
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--color-text-tertiary);
font-weight: var(--font-weight-semibold);
}
.pair-before-after__cell-value {
font-size: var(--font-size-2xl);
font-weight: var(--font-weight-bold);
font-variant-numeric: tabular-nums;
letter-spacing: -0.02em;
line-height: 1;
}
.pair-before-after__cell-meta {
font-size: var(--font-size-xs);
color: var(--color-text-secondary);
}
.pair-before-after__cell--severity-low { border-left: 4px solid var(--color-severity-low); }
.pair-before-after__cell--severity-medium { border-left: 4px solid var(--color-severity-medium); }
.pair-before-after__cell--severity-high { border-left: 4px solid var(--color-severity-high); }
.pair-before-after__cell--severity-critical { border-left: 4px solid var(--color-severity-critical); }
.pair-before-after__cell--severity-extreme { border-left: 4px solid var(--color-severity-extreme); }
.pair-before-after__arrow {
display: flex;
align-items: center;
justify-content: center;
font-size: var(--font-size-2xl);
color: var(--color-text-tertiary);
line-height: 1;
user-select: none;
}
.pair-before-after__arrow::before { content: "→"; font-family: var(--font-family-sans); }
.pair-before-after__arrow--down::before { content: "↓"; }
.pair-before-after__delta {
display: inline-flex;
align-items: baseline;
gap: 4px;
font-family: var(--font-family-mono);
font-size: var(--font-size-xs);
padding: 2px 8px;
border-radius: var(--radius-pill);
margin-top: 2px;
}
.pair-before-after__delta--improved {
background: var(--color-severity-low-soft);
color: var(--color-severity-low-on);
}
.pair-before-after__delta--worsened {
background: var(--color-severity-critical-soft);
color: var(--color-severity-critical-on);
}
@media (max-width: 640px) {
.pair-before-after { grid-template-columns: 1fr; }
.pair-before-after__arrow { transform: rotate(90deg); }
}
/* =============================================================================
20. AI ACT COMPLIANCE-TIDSLINJE
Horizontal timeline with 4 fixed EU AI Act milestones (2025-02-02, 2025-08-02,
2026-08-02, 2027-08-02) plus a "today" marker and per-system countdown chips.
============================================================================= */
.aiact-timeline {
position: relative;
padding: var(--space-8) 0 var(--space-4);
margin: var(--space-4) 0;
}
.aiact-timeline__track {
position: relative;
height: 4px;
background: var(--color-border-subtle);
border-radius: var(--radius-pill);
margin: 0 12px;
}
.aiact-timeline__progress {
position: absolute;
top: 0; bottom: 0; left: 0;
background: var(--color-primary-500);
border-radius: var(--radius-pill);
/* width set inline based on today vs milestone span */
}
.aiact-timeline__milestone {
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
/* left set inline as percentage based on date span */
}
.aiact-timeline__dot {
width: 16px; height: 16px;
border-radius: 50%;
background: var(--color-surface);
border: 3px solid var(--color-border-moderate);
cursor: pointer;
transition: transform var(--duration-fast) var(--ease-default),
border-color var(--duration-fast) var(--ease-default);
}
.aiact-timeline__dot:hover { transform: scale(1.15); }
.aiact-timeline__milestone[data-state="passed"] .aiact-timeline__dot {
background: var(--color-primary-500);
border-color: var(--color-primary-500);
}
.aiact-timeline__milestone[data-state="active"] .aiact-timeline__dot {
background: var(--color-severity-critical);
border-color: var(--color-severity-critical);
box-shadow: 0 0 0 4px var(--color-severity-critical-soft);
}
.aiact-timeline__milestone[data-state="upcoming"] .aiact-timeline__dot {
background: var(--color-surface);
border-color: var(--color-border-strong);
}
.aiact-timeline__today {
position: absolute;
top: -6px; bottom: -6px;
width: 2px;
background: var(--color-text-primary);
/* left set inline based on current date */
}
.aiact-timeline__today::after {
content: "I dag";
position: absolute;
top: -22px;
left: 50%;
transform: translateX(-50%);
font-size: 10px;
font-family: var(--font-family-mono);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
background: var(--color-bg);
padding: 2px 6px;
border-radius: var(--radius-sm);
white-space: nowrap;
}
.aiact-timeline__label {
position: absolute;
top: 22px; left: 50%;
transform: translateX(-50%);
text-align: center;
white-space: nowrap;
font-size: 11px;
font-family: var(--font-family-mono);
color: var(--color-text-secondary);
}
.aiact-timeline__label-date { font-weight: var(--font-weight-semibold); display: block; }
.aiact-timeline__label-name { color: var(--color-text-tertiary); display: block; margin-top: 1px; max-width: 140px; white-space: normal; line-height: 1.2; }
.aiact-countdown {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
font-size: var(--font-size-xs);
font-family: var(--font-family-mono);
border-radius: var(--radius-pill);
background: var(--color-bg-soft);
border: 1px solid var(--color-border-subtle);
}
.aiact-countdown__days {
font-weight: var(--font-weight-bold);
font-variant-numeric: tabular-nums;
}
.aiact-countdown[data-urgency="urgent"] { background: var(--color-severity-critical-soft); color: var(--color-severity-critical-on); border-color: transparent; }
.aiact-countdown[data-urgency="soon"] { background: var(--color-severity-medium-soft); color: var(--color-severity-medium-on); border-color: transparent; }
.aiact-countdown[data-urgency="distant"] { background: var(--color-severity-low-soft); color: var(--color-severity-low-on); border-color: transparent; }
/* =============================================================================
21. 3-TRACK ENTRY (Guide / Explore / Expert)
Carried forward from Playground v2 the most-validated UX pattern in our
fleet. Three large cards as the very first decision the user makes.
============================================================================= */
.tracks {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--space-5);
margin: var(--space-8) 0;
}
.tracks__card {
display: flex;
flex-direction: column;
gap: var(--space-3);
padding: var(--space-6);
background: var(--color-surface);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-lg);
cursor: pointer;
transition: border-color var(--duration-fast) var(--ease-default),
transform var(--duration-fast) var(--ease-default),
box-shadow var(--duration-fast) var(--ease-default);
text-decoration: none;
color: inherit;
position: relative;
overflow: hidden;
}
.tracks__card::before {
content: "";
position: absolute;
top: 0; left: 0; right: 0;
height: 4px;
background: var(--color-border-moderate);
transition: background var(--duration-fast) var(--ease-default);
}
.tracks__card:hover {
border-color: var(--color-border-strong);
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.tracks__card--guided::before { background: var(--color-state-success); }
.tracks__card--explore::before { background: var(--color-primary-500); }
.tracks__card--expert::before { background: var(--color-text-primary); }
.tracks__card-icon {
width: 40px; height: 40px;
border-radius: var(--radius-md);
background: var(--color-bg-soft);
display: flex; align-items: center; justify-content: center;
color: var(--color-text-secondary);
}
.tracks__card-title {
font-size: var(--font-size-lg);
font-weight: var(--font-weight-semibold);
margin: 0;
}
.tracks__card-desc {
font-size: var(--font-size-sm);
color: var(--color-text-secondary);
line-height: var(--line-height-snug);
margin: 0;
}
.tracks__card-meta {
margin-top: auto;
padding-top: var(--space-3);
display: flex; justify-content: space-between; align-items: baseline;
font-size: var(--font-size-xs);
color: var(--color-text-tertiary);
font-family: var(--font-family-mono);
}
.tracks__card-cta {
font-family: var(--font-family-sans);
font-weight: var(--font-weight-medium);
color: var(--color-text-primary);
}
@media (max-width: 880px) {
.tracks { grid-template-columns: 1fr; }
}
/* =============================================================================
22. FRIA RIGHTS-MATRIX
12 EU Charter rights × impact level. Long left labels, compact right cells.
Each cell shows checkmark + severity color when right is impacted.
============================================================================= */
.rights-matrix {
display: grid;
grid-template-columns: 1fr;
gap: 1px;
background: var(--color-border-subtle);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-md);
overflow: hidden;
}
.rights-matrix__head,
.rights-matrix__row {
display: grid;
grid-template-columns: 1fr repeat(5, 64px);
background: var(--color-surface);
}
.rights-matrix__head {
background: var(--color-bg-soft);
}
.rights-matrix__head-cell,
.rights-matrix__name,
.rights-matrix__cell {
padding: 10px 12px;
font-size: var(--font-size-sm);
display: flex;
align-items: center;
}
.rights-matrix__head-cell {
font-size: var(--font-size-xs);
font-weight: var(--font-weight-semibold);
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--color-text-secondary);
justify-content: center;
}
.rights-matrix__head-cell--name { justify-content: flex-start; }
.rights-matrix__name {
font-weight: var(--font-weight-medium);
color: var(--color-text-primary);
}
.rights-matrix__name-meta {
display: block;
font-size: var(--font-size-xs);
color: var(--color-text-tertiary);
font-weight: var(--font-weight-regular);
margin-top: 2px;
}
.rights-matrix__cell {
justify-content: center;
font-family: var(--font-family-mono);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-semibold);
font-variant-numeric: tabular-nums;
color: var(--color-text-tertiary);
border-left: 1px solid var(--color-border-subtle);
}
.rights-matrix__cell[data-impact="0"]::before { content: "—"; color: var(--color-text-tertiary); }
.rights-matrix__cell[data-impact="1"] { background: var(--color-severity-low-soft); color: var(--color-severity-low-on); }
.rights-matrix__cell[data-impact="2"] { background: var(--color-severity-medium-soft); color: var(--color-severity-medium-on); }
.rights-matrix__cell[data-impact="3"] { background: var(--color-severity-high-soft); color: var(--color-severity-high-on); }
.rights-matrix__cell[data-impact="4"] { background: var(--color-severity-critical-soft); color: var(--color-severity-critical-on); }
.rights-matrix__cell[data-impact="5"] { background: var(--color-severity-critical); color: var(--color-severity-critical-on); }
@media (max-width: 720px) {
.rights-matrix__head,
.rights-matrix__row { grid-template-columns: 1fr repeat(5, 44px); }
.rights-matrix__head-cell,
.rights-matrix__cell { padding: 8px 6px; font-size: var(--font-size-xs); }
}
/* =============================================================================
23. CAPABILITY-MATRIX
Rows = capabilities (e.g. "Generate text via M365 Chat"), columns = licenses
(E3, E5, Copilot, etc.). Cells use one of four states with explicit icon +
color so meaning never depends solely on color.
============================================================================= */
.capability-matrix {
display: grid;
gap: 1px;
background: var(--color-border-subtle);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-md);
overflow: hidden;
font-size: var(--font-size-sm);
}
.capability-matrix__head,
.capability-matrix__row {
display: grid;
background: var(--color-surface);
/* grid-template-columns set inline based on license count */
}
.capability-matrix__head { background: var(--color-bg-soft); }
.capability-matrix__head-cell,
.capability-matrix__name,
.capability-matrix__cell {
padding: 10px 12px;
display: flex;
align-items: center;
gap: 6px;
}
.capability-matrix__head-cell {
font-size: var(--font-size-xs);
font-weight: var(--font-weight-semibold);
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--color-text-secondary);
justify-content: center;
}
.capability-matrix__head-cell--name { justify-content: flex-start; }
.capability-matrix__name {
font-weight: var(--font-weight-medium);
border-right: 1px solid var(--color-border-subtle);
}
.capability-matrix__cell {
justify-content: center;
font-family: var(--font-family-mono);
font-size: var(--font-size-md);
border-left: 1px solid var(--color-border-subtle);
}
.capability-matrix__cell-icon {
font-style: normal;
width: 22px; height: 22px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 50%;
font-size: 13px;
font-weight: var(--font-weight-bold);
}
.capability-matrix__cell[data-status="available"] { background: var(--color-severity-low-soft); }
.capability-matrix__cell[data-status="available"] .capability-matrix__cell-icon { background: var(--color-severity-low); color: #fff; }
.capability-matrix__cell[data-status="available"] .capability-matrix__cell-icon::before { content: "✓"; }
.capability-matrix__cell[data-status="cost"] { background: var(--color-severity-medium-soft); }
.capability-matrix__cell[data-status="cost"] .capability-matrix__cell-icon { background: var(--color-severity-medium); color: #fff; }
.capability-matrix__cell[data-status="cost"] .capability-matrix__cell-icon::before { content: "kr"; font-size: 10px; }
.capability-matrix__cell[data-status="conditional"] { background: var(--color-severity-high-soft); }
.capability-matrix__cell[data-status="conditional"] .capability-matrix__cell-icon { background: var(--color-severity-high); color: #fff; }
.capability-matrix__cell[data-status="conditional"] .capability-matrix__cell-icon::before { content: "!"; }
.capability-matrix__cell[data-status="missing"] { background: var(--color-bg-soft); }
.capability-matrix__cell[data-status="missing"] .capability-matrix__cell-icon { background: var(--color-text-tertiary); color: #fff; }
.capability-matrix__cell[data-status="missing"] .capability-matrix__cell-icon::before { content: "×"; }
.capability-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);
}
.capability-matrix__legend-item {
display: inline-flex;
align-items: center;
gap: 6px;
}
/* =============================================================================
24. PARALLEL-AGENT-STATUS PANEL
Used by ms-ai-architect utredning (4 parallel workers security-worker,
cost-worker, dpia-worker, diagram-worker writing to .work/-files) and
ultraplan-local multi-wave execute. Grid of agent cards with state pills,
progress bars, and per-agent metrics.
============================================================================= */
.agent-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: var(--space-3);
}
.agent-card {
background: var(--color-surface);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-md);
padding: var(--space-4);
display: flex;
flex-direction: column;
gap: var(--space-2);
position: relative;
}
.agent-card__head {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: var(--space-2);
}
.agent-card__name {
font-weight: var(--font-weight-semibold);
font-size: var(--font-size-sm);
margin: 0;
}
.agent-card__role {
font-family: var(--font-family-mono);
font-size: 11px;
color: var(--color-text-tertiary);
}
.agent-card__state {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
font-size: 11px;
font-weight: var(--font-weight-medium);
border-radius: var(--radius-pill);
white-space: nowrap;
}
.agent-card__state[data-state="queued"] { background: var(--color-bg-soft); color: var(--color-text-tertiary); }
.agent-card__state[data-state="running"] { background: var(--color-severity-medium-soft); color: var(--color-severity-medium-on); }
.agent-card__state[data-state="done"] { background: var(--color-severity-low-soft); color: var(--color-severity-low-on); }
.agent-card__state[data-state="failed"] { background: var(--color-state-failed); color: #fff; }
.agent-card__state[data-state="blocked"] { background: var(--color-state-blocked); color: #fff; }
.agent-card__state-dot {
width: 6px; height: 6px;
border-radius: 50%;
background: currentColor;
}
.agent-card__state[data-state="running"] .agent-card__state-dot {
animation: agent-pulse 1.4s var(--ease-default) infinite;
}
@keyframes agent-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.35; }
}
.agent-card__progress {
height: 4px;
background: var(--color-surface-sunken);
border-radius: var(--radius-pill);
overflow: hidden;
}
.agent-card__progress-fill {
height: 100%;
background: var(--color-primary-500);
transition: width var(--duration-normal) var(--ease-default);
}
.agent-card__metrics {
display: flex;
gap: var(--space-3);
font-size: var(--font-size-xs);
color: var(--color-text-secondary);
}
.agent-card__metric { display: flex; gap: 4px; align-items: baseline; }
.agent-card__metric-value {
font-variant-numeric: tabular-nums;
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
}
.agent-card__output {
font-family: var(--font-family-mono);
font-size: 11px;
background: var(--color-surface-sunken);
padding: 6px 8px;
border-radius: var(--radius-sm);
max-height: 56px;
overflow: hidden;
color: var(--color-text-secondary);
white-space: pre-wrap;
word-break: break-word;
}
.agent-card__output::after {
content: "";
position: absolute;
bottom: var(--space-4);
left: var(--space-4);
right: var(--space-4);
height: 18px;
background: linear-gradient(to bottom, transparent, var(--color-surface));
pointer-events: none;
}
/* =============================================================================
25. ERROR-SUMMARY (Aksel/GOV.UK pattern)
Concentrated list of validation errors at top of a form. Each error
anchor-links to the offending field. Required for accessible long forms.
============================================================================= */
.error-summary {
background: var(--color-surface);
border: 1px solid var(--color-severity-critical);
border-left-width: 4px;
border-radius: var(--radius-md);
padding: var(--space-4) var(--space-5);
display: flex;
flex-direction: column;
gap: var(--space-2);
}
.error-summary__heading {
display: flex;
align-items: center;
gap: 8px;
font-size: var(--font-size-md);
font-weight: var(--font-weight-semibold);
color: var(--color-severity-critical);
margin: 0;
}
[data-theme="dark"] .error-summary__heading { color: #F09095; }
.error-summary__heading::before {
content: "!";
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px; height: 20px;
border-radius: 50%;
background: var(--color-severity-critical);
color: #fff;
font-size: 14px;
font-weight: var(--font-weight-bold);
flex-shrink: 0;
}
.error-summary__body {
font-size: var(--font-size-sm);
color: var(--color-text-primary);
line-height: var(--line-height-snug);
}
.error-summary__list {
margin: var(--space-2) 0 0;
padding: 0 0 0 var(--space-5);
list-style: disc;
color: var(--color-text-primary);
}
.error-summary__item { margin-bottom: 4px; }
.error-summary__link {
color: var(--color-severity-critical);
text-decoration: underline;
text-underline-offset: 2px;
text-decoration-thickness: 1px;
font-weight: var(--font-weight-medium);
}
.error-summary__link:hover { text-decoration-thickness: 2px; color: var(--color-severity-extreme); }
[data-theme="dark"] .error-summary__link { color: #F09095; }
[data-theme="dark"] .error-summary__link:hover { color: #FFB7BA; }
/* =============================================================================
26. GUIDE-PANEL (Aksel pattern)
Friendly inline guidance with optional illustration and CTA. Used to scaffold
first-time users through unfamiliar territory without scolding tone.
============================================================================= */
.guide-panel {
display: grid;
grid-template-columns: 56px 1fr auto;
gap: var(--space-4);
align-items: start;
background: var(--color-bg-soft);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-lg);
padding: var(--space-4) var(--space-5);
}
.guide-panel--info { background: #EAF3FB; border-color: rgba(9, 105, 218, 0.25); }
.guide-panel--success { background: var(--color-severity-low-soft); border-color: rgba(26, 127, 55, 0.3); }
.guide-panel--warn { background: var(--color-severity-medium-soft); border-color: rgba(191, 135, 0, 0.3); }
[data-theme="dark"] .guide-panel--info { background: #0E2A3F; border-color: rgba(111, 165, 221, 0.3); }
.guide-panel__icon {
width: 56px; height: 56px;
border-radius: var(--radius-md);
background: var(--color-surface);
border: 1px solid var(--color-border-subtle);
display: flex; align-items: center; justify-content: center;
color: var(--color-primary-500);
}
.guide-panel--info .guide-panel__icon { color: var(--color-state-info); }
.guide-panel--success .guide-panel__icon { color: var(--color-state-success); }
.guide-panel--warn .guide-panel__icon { color: var(--color-severity-medium); }
.guide-panel__body {
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
}
.guide-panel__title {
font-size: var(--font-size-md);
font-weight: var(--font-weight-semibold);
margin: 0;
color: var(--color-text-primary);
}
.guide-panel__text {
font-size: var(--font-size-sm);
color: var(--color-text-secondary);
line-height: var(--line-height-snug);
margin: 0;
max-width: var(--measure);
}
.guide-panel__action {
align-self: center;
white-space: nowrap;
}
.guide-panel__dismiss {
position: absolute;
top: var(--space-2);
right: var(--space-2);
background: none;
border: none;
cursor: pointer;
width: 28px; height: 28px;
border-radius: var(--radius-sm);
display: flex; align-items: center; justify-content: center;
color: var(--color-text-tertiary);
font-family: inherit;
}
.guide-panel__dismiss:hover { background: rgba(0,0,0,0.06); color: var(--color-text-primary); }
@media (max-width: 640px) {
.guide-panel {
grid-template-columns: 40px 1fr;
gap: var(--space-3);
}
.guide-panel__icon { width: 40px; height: 40px; }
.guide-panel__action {
grid-column: 1 / -1;
align-self: stretch;
}
}
/* =============================================================================
Print rules for Tier 3
============================================================================= */
@media print {
.pair-before-after { page-break-inside: avoid; }
.aiact-timeline { page-break-inside: avoid; }
.agent-grid { page-break-inside: avoid; }
.tracks { display: none; } /* entry choice = screen-only */
.guide-panel__dismiss { display: none; } /* dismiss only meaningful on screen */
.error-summary {
background: #FFF !important;
border: 1pt solid #000 !important;
color: #000 !important;
}
.error-summary__heading,
.error-summary__body,
.error-summary__link { color: #000 !important; }
}

View file

@ -0,0 +1,650 @@
/* Code generated by sync-design-system.mjs; DO NOT EDIT. */
/* =============================================================================
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; }

View file

@ -0,0 +1,84 @@
/* Code generated by sync-design-system.mjs; DO NOT EDIT. */
/*
* Self-hosted web fonts for Playground Design System.
*
* All three families are licensed under SIL Open Font License 1.1.
* Full license text and provenance: ./fonts/LICENSES.md
*
* Why self-hosted:
* - No external requests (no fonts.googleapis.com, no IP/UA leakage).
* - Works offline / behind air-gapped firewalls.
* - GDPR-compliant for Norwegian public-sector deployments.
*
* Bundle size: ~940 KB total across 9 woff2 files.
* Loaded via font-display: swap to avoid FOIT.
*/
/* ========== Inter (UI / body) ========== */
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("./fonts/Inter-Regular.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("./fonts/Inter-Medium.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("./fonts/Inter-SemiBold.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 700;
font-display: swap;
src: url("./fonts/Inter-Bold.woff2") format("woff2");
}
/* ========== JetBrains Mono (code) ========== */
@font-face {
font-family: "JetBrains Mono";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("./fonts/JetBrainsMono-Regular.woff2") format("woff2");
}
@font-face {
font-family: "JetBrains Mono";
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("./fonts/JetBrainsMono-Medium.woff2") format("woff2");
}
@font-face {
font-family: "JetBrains Mono";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("./fonts/JetBrainsMono-SemiBold.woff2") format("woff2");
}
/* ========== Source Serif 4 (occasional editorial accents) ========== */
@font-face {
font-family: "Source Serif 4";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("./fonts/SourceSerif4-Regular.woff2") format("woff2");
}
@font-face {
font-family: "Source Serif 4";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("./fonts/SourceSerif4-Semibold.woff2") format("woff2");
}

View file

@ -0,0 +1,92 @@
Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION AND CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View file

@ -0,0 +1,93 @@
Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View file

@ -0,0 +1,93 @@
Copyright 2014 - 2023 Adobe (http://www.adobe.com/), with Reserved Font Name Source. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View file

@ -0,0 +1,42 @@
# Font Licenses
All three font families bundled with Playground Design System are licensed
under the SIL Open Font License, Version 1.1 (OFL-1.1). They are free to
use, modify, embed, and redistribute under the terms of OFL-1.1.
Full license text per family:
- **Inter** (Regular, Medium, SemiBold, Bold) — `LICENSE-Inter.txt`
Copyright (c) 2016 The Inter Project Authors
Source: https://github.com/rsms/inter
Version bundled: 4.0
- **JetBrains Mono** (Regular, Medium, SemiBold) — `LICENSE-JetBrainsMono.txt`
Copyright 2020 The JetBrains Mono Project Authors
Source: https://github.com/JetBrains/JetBrainsMono
Version bundled: 2.304
- **Source Serif 4** (Regular, Semibold) — `LICENSE-SourceSerif4.md`
Copyright 20142023 Adobe (Reserved Font Name "Source")
Source: https://github.com/adobe-fonts/source-serif
Version bundled: 4.005
## Provenance
Files in this directory were obtained from the upstream release artifacts
linked above on 2026-05-03. Source Serif 4 woff2 files were generated locally
from the desktop OTF release using `fonttools ttLib.woff2 compress`; all
others are unmodified from upstream webfont releases.
## Why bundled
These fonts ship with the design system to eliminate runtime requests to
external CDNs (e.g., fonts.googleapis.com). This guarantees:
- No data leakage about end-user IPs / User-Agents to third parties.
- GDPR compliance for Norwegian public-sector deployments.
- Functioning Playgrounds in offline / air-gapped environments.
Each Playground HTML loads `../shared/playground-design-system/fonts.css`,
which declares all `@font-face` rules pointing at the .woff2 files in this
directory.

View file

@ -0,0 +1,176 @@
/* Code generated by sync-design-system.mjs; DO NOT EDIT. */
/* =============================================================================
print.css A4 print stylesheet for offentlige dokumenter
- Severity-mønstre (skravur) som fungerer i B/W
- Header/footer med kommune-logo-slot, signaturfelt, paginering
- 12pt minimum kropp, 11pt for metadata
- Skjuler interaktiv chrome (header, knapper, toggles)
============================================================================= */
@page {
size: A4 portrait;
margin: 22mm 18mm 24mm 18mm;
@bottom-right { content: counter(page) " / " counter(pages); font-family: "Inter", sans-serif; font-size: 9pt; color: #555; }
}
@page :first { @top-left { content: none; } }
@page landscape { size: A4 landscape; }
/* SVG severity-mønstre (skravur) definert i print-only inline-svg.
For å bruke: legg til class .pattern-low/.pattern-medium/etc. elementet
som ellers fyller med severity-fargen. */
@media print {
:root {
--color-bg: #FFFFFF;
--color-surface: #FFFFFF;
--color-surface-sunken: #F5F5F5;
--color-bg-soft: #F7F7F7;
--color-border-subtle: #C7C7C7;
--color-border-moderate: #888888;
--color-text-primary: #000000;
--color-text-secondary: #2A2A2A;
--color-text-tertiary: #555555;
}
html, body { background: #FFFFFF !important; color: #000 !important; font-size: 11pt !important; }
body { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
/* Hide interactive chrome */
.app-header, header.app-header,
.theme-toggle, #theme-toggle, #themeToggle,
.filter-bar, .view-toggle, .screen-tabs,
.btn--primary, .btn--secondary, .btn--ghost,
.live-dot, .pane__head .badge,
.accept-banner button,
.scenario-card .btn,
.footer { display: none !important; }
/* Container = full width on print */
.container, .container--wide { max-width: none !important; padding: 0 !important; }
/* Body type */
body, p, li, dd, dt, td, th, .field__value {
font-family: "Inter", sans-serif;
font-size: 11pt; line-height: 1.45; color: #000;
}
h1 { font-size: 22pt; line-height: 1.2; margin: 0 0 6pt; }
h2 { font-size: 16pt; line-height: 1.25; margin: 18pt 0 6pt; page-break-after: avoid; }
h3 { font-size: 13pt; margin: 12pt 0 4pt; page-break-after: avoid; }
h4 { font-size: 11pt; margin: 10pt 0 3pt; }
/* Page breaks */
.page-break { page-break-before: always; }
.avoid-break, .finding, .critique, .scenario-card, table, figure {
page-break-inside: avoid;
}
/* Severity patterns (B/W-safe). Stack pattern-bg + dotted/diag border indicators. */
.matrix__cell[data-score],
.badge--severity-low, .badge--severity-medium, .badge--severity-high,
.badge--severity-critical, .badge--severity-extreme {
background-color: #FFF !important;
color: #000 !important;
border: 1px solid #000 !important;
}
.badge--severity-low::before, .badge--severity-medium::before,
.badge--severity-high::before, .badge--severity-critical::before,
.badge--severity-extreme::before {
content: ""; display: inline-block;
width: 7pt; height: 7pt; margin-right: 4pt;
border: 1px solid #000;
vertical-align: middle;
}
.badge--severity-low::before { background: #FFF; }
.badge--severity-medium::before { background: repeating-linear-gradient(45deg, #000 0 0.6pt, transparent 0.6pt 3pt); }
.badge--severity-high::before { background: repeating-linear-gradient(45deg, #000 0 1pt, transparent 1pt 2.5pt); }
.badge--severity-critical::before { background: repeating-linear-gradient(0deg, #000 0 0.5pt, transparent 0.5pt 2pt),
repeating-linear-gradient(90deg, #000 0 0.5pt, transparent 0.5pt 2pt); }
.badge--severity-extreme::before { background: #000; }
/* Matrix cells in print: skravur i stedet for farge */
.matrix__cell { color: #000 !important; border: 0.5pt solid #888 !important; }
.matrix__cell[data-score]:not([data-score="0"]) { background: #FFF !important; }
.matrix__cell[data-score="1"], .matrix__cell[data-score="2"],
.matrix__cell[data-score="3"], .matrix__cell[data-score="4"] {
background: #FFF !important;
}
.matrix__cell[data-score="5"], .matrix__cell[data-score="6"], .matrix__cell[data-score="8"] {
background: repeating-linear-gradient(45deg, rgba(0,0,0,0.18) 0 0.5pt, transparent 0.5pt 4pt) !important;
}
.matrix__cell[data-score="9"], .matrix__cell[data-score="10"], .matrix__cell[data-score="12"] {
background: repeating-linear-gradient(45deg, rgba(0,0,0,0.32) 0 0.7pt, transparent 0.7pt 3pt) !important;
}
.matrix__cell[data-score="15"], .matrix__cell[data-score="16"], .matrix__cell[data-score="20"] {
background: repeating-linear-gradient(45deg, rgba(0,0,0,0.48) 0 1pt, transparent 1pt 2pt) !important;
}
.matrix__cell[data-score="25"] { background: #000 !important; color: #FFF !important; }
.matrix__cell[data-score="25"] .matrix__cell-score { color: #FFF !important; }
/* Surfaces flat */
.card, .pane, .finding, .critique, .scenario-card, .posture-summary, .verdict-block {
background: #FFF !important;
border: 0.5pt solid #888 !important;
box-shadow: none !important;
border-radius: 0 !important;
}
/* Links visible but not underlined-everything */
a { color: #000; text-decoration: none; }
a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 9pt; color: #555; }
a[href^="#"]::after, a[href^="/"]::after, a:not([href*="://"])::after { content: ""; }
/* Standard footer block: signaturfelt for offentlige dokumenter */
.print-footer {
margin-top: 24pt;
padding-top: 10pt;
border-top: 0.5pt solid #888;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 18pt;
font-size: 10pt;
}
.print-signature { display: flex; flex-direction: column; gap: 28pt; }
.print-signature__line {
border-bottom: 0.5pt solid #000;
height: 28pt;
}
.print-signature__caption {
font-size: 9pt;
color: #555;
}
/* Header for offisielle rapporter — kommune-logo-slot */
.print-header {
display: grid;
grid-template-columns: auto 1fr;
gap: 14pt;
align-items: center;
padding-bottom: 10pt;
margin-bottom: 16pt;
border-bottom: 0.5pt solid #888;
}
.print-header__logo {
width: 40pt; height: 40pt;
border: 0.5pt solid #888;
display: flex; align-items: center; justify-content: center;
font-family: "Inter", sans-serif; font-size: 9pt; color: #888;
}
.print-header__meta { font-size: 9pt; color: #555; }
.print-header__meta strong { color: #000; }
/* Avoid orphan headings */
h2, h3, h4 { orphans: 3; widows: 3; }
p, li { orphans: 2; widows: 2; }
}
/* Screen-mode preview class — see print preview without actually printing */
.preview-print { background: #ddd; padding: var(--space-8); }
.preview-print .a4 {
width: 210mm; min-height: 297mm;
margin: 0 auto;
background: #fff;
padding: 22mm 18mm;
box-shadow: 0 6px 24px rgba(0,0,0,0.18);
font-size: 11pt; line-height: 1.45; color: #000;
}
.preview-print .a4 + .a4 { margin-top: 12mm; }

View file

@ -0,0 +1,88 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://playground-ds.no/schemas/finding.json",
"title": "Finding",
"description": "Et enkelt funn fra en plugin-skanning. Brukes av llm-security, config-audit, ultraplan-review og ms-ai-review.",
"type": "object",
"required": ["id", "title", "severity", "source"],
"properties": {
"id": {
"type": "string",
"description": "Stabil ID, f.eks. DDT-2026-118-F-001",
"pattern": "^[A-Z0-9-]{4,}$"
},
"title": { "type": "string", "minLength": 4, "maxLength": 140 },
"severity": {
"enum": ["info", "low", "medium", "high", "critical"],
"description": "Standard 5-trinns skala. Maps til CSS-tokens --color-severity-*."
},
"score": {
"type": "number", "minimum": 0, "maximum": 10,
"description": "CVSS-lignende numerisk score. Valgfri — severity er primær."
},
"rules": {
"type": "array",
"items": { "type": "string", "pattern": "^[A-Z]{2,4}[0-9]{2}(\\.[0-9]+)?$" },
"description": "Regler/categories truffet, f.eks. LLM01, ASI02, DDT01"
},
"source": {
"type": "object",
"required": ["kind", "ref"],
"properties": {
"kind": { "enum": ["document", "prompt-response", "code-file", "config-file", "okr-set"] },
"ref": { "type": "string", "description": "Filnavn / URL / sak-ID" },
"line": { "type": "integer", "minimum": 1 },
"col": { "type": "integer", "minimum": 0 },
"snippet": { "type": "string", "maxLength": 800 }
}
},
"evidence": {
"type": "array",
"items": {
"type": "object",
"required": ["kind", "value"],
"properties": {
"kind": { "enum": ["text", "codepoint", "metric", "url", "image"] },
"value": { "type": "string" },
"label": { "type": "string" }
}
}
},
"rationale": { "type": "string", "description": "Norsk forklaring av hvorfor dette er et problem i denne konteksten" },
"recommendation": {
"type": "object",
"properties": {
"summary": { "type": "string" },
"steps": { "type": "array", "items": { "type": "string" } },
"ttf": { "type": "string", "description": "Tid til løsning, f.eks. '2 t', '1 d', '5 d'" },
"owner": { "type": "string", "description": "Foreslått eier (rolle eller person)" }
}
},
"references": {
"type": "array",
"items": {
"type": "object",
"properties": {
"label": { "type": "string" },
"url": { "type": "string", "format": "uri" }
}
}
},
"status": {
"enum": ["new", "acknowledged", "in-progress", "fixed", "accepted-risk", "false-positive"],
"default": "new"
},
"acceptance": {
"type": "object",
"description": "Påkrevd hvis status = accepted-risk og severity ≥ high",
"properties": {
"approver": { "type": "string" },
"date": { "type": "string", "format": "date" },
"rationale": { "type": "string" },
"review_by": { "type": "string", "format": "date" }
}
},
"created": { "type": "string", "format": "date-time" },
"updated": { "type": "string", "format": "date-time" }
}
}

View file

@ -0,0 +1,78 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://playground-ds.no/schemas/okr-set.json",
"title": "OKR-sett",
"description": "Et OKR-sett: ett mål (Objective) med 16 nøkkelresultater (KR). Brukes av OKR live-writer.",
"type": "object",
"required": ["id", "objective", "key_results", "owner", "period"],
"properties": {
"id": { "type": "string" },
"owner": {
"type": "object",
"required": ["name", "unit"],
"properties": {
"name": { "type": "string" },
"unit": { "type": "string", "description": "Avdeling/seksjon" },
"org": { "type": "string", "description": "Kommune/etat" }
}
},
"period": {
"type": "object",
"required": ["kind", "label", "start", "end"],
"properties": {
"kind": { "enum": ["tertial", "kvartal", "halvår", "år"] },
"label": { "type": "string", "description": "f.eks. 'T2 2026'" },
"start": { "type": "string", "format": "date" },
"end": { "type": "string", "format": "date" }
}
},
"objective": {
"type": "object",
"required": ["text"],
"properties": {
"text": { "type": "string", "minLength": 10, "maxLength": 240 },
"rationale": { "type": "string" }
}
},
"key_results": {
"type": "array", "minItems": 1, "maxItems": 6,
"items": {
"type": "object",
"required": ["id", "text"],
"properties": {
"id": { "type": "string", "pattern": "^KR[0-9]+$" },
"text": { "type": "string" },
"metric": {
"type": "object",
"properties": {
"name": { "type": "string" },
"unit": { "type": "string", "description": "%, dager, kr, antall, …" },
"baseline": { "type": "number" },
"target": { "type": "number" },
"stretch": { "type": "number" },
"source": { "type": "string", "description": "KPI-katalog ref / Tableau-sett / etc." }
}
},
"deadline": { "type": "string", "format": "date" }
}
}
},
"score": {
"type": "object",
"description": "Generert av OKR-writer ved kvalitetsanalyse",
"properties": {
"overall": { "type": "number", "minimum": 0, "maximum": 100 },
"measurability": { "type": "number" },
"specificity": { "type": "number" },
"ambition": { "type": "number" },
"actionability": { "type": "number" }
}
},
"critiques": {
"type": "array",
"items": { "$ref": "https://playground-ds.no/schemas/finding.json" }
},
"version": { "type": "string", "description": "Semver eller utkast 0.4-stil" },
"status": { "enum": ["draft", "in-review", "approved", "active", "closed"], "default": "draft" }
}
}

View file

@ -0,0 +1,59 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://playground-ds.no/schemas/ros-threat.json",
"title": "ROS-trussel",
"description": "Én identifisert trussel i en risiko- og sårbarhetsanalyse. NS 5814-justert.",
"type": "object",
"required": ["id", "title", "category", "inherent"],
"properties": {
"id": { "type": "string", "pattern": "^T-[0-9]{3,}$" },
"title": { "type": "string" },
"description": { "type": "string" },
"category": {
"enum": ["personvern", "informasjonssikkerhet", "datakvalitet",
"compliance", "dataintegritet", "leverandørrisiko",
"tilgjengelighet", "omdømme", "økonomi", "andre"]
},
"actors": {
"type": "array",
"items": { "enum": ["intern-bruker", "saksbehandler", "innbygger", "ekstern-aktør", "leverandør", "system", "ai-modell"] }
},
"inherent": {
"type": "object",
"required": ["likelihood", "consequence"],
"properties": {
"likelihood": { "type": "integer", "minimum": 1, "maximum": 5 },
"consequence": { "type": "integer", "minimum": 1, "maximum": 5 },
"rationale": { "type": "string" }
}
},
"controls": {
"type": "array",
"items": {
"type": "object",
"required": ["id", "title"],
"properties": {
"id": { "type": "string", "pattern": "^M-[0-9]{3,}$" },
"title": { "type": "string" },
"kind": { "enum": ["preventiv", "deteksjon", "korreksjon", "policy", "opplæring", "teknisk"] },
"status": { "enum": ["planlagt", "implementert", "validert", "ute-av-drift"] },
"owner": { "type": "string" },
"due": { "type": "string", "format": "date" }
}
}
},
"residual": {
"type": "object",
"properties": {
"likelihood": { "type": "integer", "minimum": 1, "maximum": 5 },
"consequence": { "type": "integer", "minimum": 1, "maximum": 5 },
"rationale": { "type": "string" }
}
},
"regulatory_refs": {
"type": "array",
"items": { "type": "string", "description": "GDPR Art. 35, AI Act Art. 6, NS 5814, …" }
},
"status": { "enum": ["open", "mitigating", "monitored", "closed", "transferred"], "default": "open" }
}
}

View file

@ -0,0 +1,186 @@
/* Code generated by sync-design-system.mjs; DO NOT EDIT. */
/* =============================================================================
Playground Design System tokens.css
v0.1 Phase 1
Aksel/Digdir-aligned. Norwegian public sector. WCAG 2.1 AA.
============================================================================= */
:root {
/* ---------- Typography -------------------------------------------------- */
--font-family-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
--font-family-mono: "JetBrains Mono", "SF Mono", "Fira Code", ui-monospace, monospace;
--font-family-serif: "Source Serif 4", Georgia, serif;
--font-size-xs: 13px;
--font-size-sm: 15px;
--font-size-md: 17px; /* body default */
--font-size-lg: 19px;
--font-size-xl: 23px;
--font-size-2xl: 28px;
--font-size-3xl: 34px;
--font-size-4xl: 44px;
--line-height-tight: 1.2;
--line-height-snug: 1.4;
--line-height-normal: 1.55;
--measure: 65ch;
--font-weight-regular: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
/* ---------- Primary (Digdir) ------------------------------------------- */
--color-primary-50: #E8F1FB;
--color-primary-100: #C6DCF4;
--color-primary-200: #9CC0EA;
--color-primary-300: #6FA5DD;
--color-primary-400: #3B83CB;
--color-primary-500: #0062BA; /* Digdir blue */
--color-primary-600: #00569F;
--color-primary-700: #004A8F;
--color-primary-800: #003A70;
--color-primary-900: #002F5C;
/* ---------- Severity ramp (deuteranopia-safe) ------------------------- */
--color-severity-low: #1A7F37;
--color-severity-medium: #BF8700;
--color-severity-high: #CC5A00;
--color-severity-critical: #A40E26;
--color-severity-extreme: #66050F;
/* Soft fills (matrix cells, badges) */
--color-severity-low-soft: #DDF4E4;
--color-severity-medium-soft: #FBF0CC;
--color-severity-high-soft: #FCE0CC;
--color-severity-critical-soft: #F8D7DC;
--color-severity-extreme-soft: #E8C7CC;
/* Foreground on severity bg */
--color-severity-low-on: #0E4A20;
--color-severity-medium-on: #5C3F00;
--color-severity-high-on: #5C2900;
--color-severity-critical-on: #FFFFFF;
--color-severity-extreme-on: #FFFFFF;
/* ---------- State (distinct from severity) --------------------------- */
--color-state-success: #1A7F37;
--color-state-warning: #BF8700;
--color-state-failed: #7D1A1A; /* dark desaturated red — "broke" */
--color-state-blocked: #5C2D91; /* purple — distinct */
--color-state-info: #0969DA;
--color-state-running: #BF8700;
--color-state-queued: #6E7781;
--color-state-pending: #4D7DAD;
--color-state-done: #1A7F37;
/* ---------- Surface / background ------------------------------------- */
--color-bg: #FBFAF7; /* warm off-white page */
--color-bg-soft: #F4F2EC; /* subtle section */
--color-surface: #FFFFFF;
--color-surface-raised: #FFFFFF;
--color-surface-sunken: #F1EEE7;
--color-overlay: rgba(15, 18, 22, 0.45);
/* ---------- Border --------------------------------------------------- */
--color-border-subtle: #E4E0D6;
--color-border-moderate: #C8C2B3;
--color-border-strong: #6E7781;
--color-border-focus: #0062BA;
/* ---------- Text ----------------------------------------------------- */
--color-text-primary: #1F2328;
--color-text-secondary: #4D5663;
--color-text-tertiary: #6E7781;
--color-text-on-primary: #FFFFFF;
--color-text-link: #00569F;
--color-text-link-hover: #002F5C;
/* ---------- Plugin scope colors -------------------------------------- */
--color-scope-architect: #0F6E76; /* ms-ai-architect — petrol */
--color-scope-okr: #9A6700; /* OKR — amber */
--color-scope-security: #A40E26; /* llm-security — crimson */
--color-scope-ultraplan: #4338CA; /* ultraplan-local — indigo */
--color-scope-config: #3F5963; /* config-audit — slate */
/* ---------- Spacing -------------------------------------------------- */
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 20px;
--space-6: 24px;
--space-8: 32px;
--space-10: 40px;
--space-12: 48px;
--space-16: 64px;
--space-20: 80px;
/* ---------- Radius --------------------------------------------------- */
--radius-sm: 3px;
--radius-md: 5px;
--radius-lg: 8px;
--radius-pill: 999px;
/* ---------- Shadow --------------------------------------------------- */
--shadow-sm: 0 1px 2px rgba(15, 18, 22, 0.04), 0 0 0 1px rgba(15, 18, 22, 0.04);
--shadow-md: 0 2px 4px rgba(15, 18, 22, 0.06), 0 4px 12px rgba(15, 18, 22, 0.04);
--shadow-lg: 0 4px 8px rgba(15, 18, 22, 0.06), 0 12px 32px rgba(15, 18, 22, 0.06);
--shadow-focus: 0 0 0 3px rgba(0, 98, 186, 0.35);
/* ---------- Motion --------------------------------------------------- */
--duration-instant: 100ms;
--duration-fast: 150ms;
--duration-normal: 250ms;
--duration-slow: 400ms;
--ease-default: cubic-bezier(0.2, 0, 0, 1);
/* ---------- Layout --------------------------------------------------- */
--container-narrow: 720px;
--container-default: 1080px;
--container-wide: 1280px;
--sidebar-width: 280px;
}
[data-theme="dark"] {
--color-bg: #0F1419;
--color-bg-soft: #161B22;
--color-surface: #1A2027;
--color-surface-raised: #232A33;
--color-surface-sunken: #0B1015;
--color-border-subtle: #2A323C;
--color-border-moderate: #3B4452;
--color-border-strong: #6E7781;
--color-text-primary: #E6EDF3;
--color-text-secondary: #B0BAC4;
--color-text-tertiary: #8B96A2;
--color-text-link: #6FA5DD;
--color-text-link-hover: #9CC0EA;
/* Severity soft fills tuned for dark surfaces */
--color-severity-low-soft: #163322;
--color-severity-medium-soft: #3A2C0A;
--color-severity-high-soft: #3D260F;
--color-severity-critical-soft: #3B0F18;
--color-severity-extreme-soft: #2A0408;
--color-severity-low-on: #7FE0A0;
--color-severity-medium-on: #F2C66B;
--color-severity-high-on: #F09060;
--color-severity-critical-on: #FFFFFF;
--color-severity-extreme-on: #FFFFFF;
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.04);
--shadow-md: 0 2px 4px rgba(0, 0, 0, 0.4), 0 4px 12px rgba(0, 0, 0, 0.3);
--shadow-lg: 0 4px 8px rgba(0, 0, 0, 0.5), 0 12px 32px rgba(0, 0, 0, 0.4);
--shadow-focus: 0 0 0 3px rgba(111, 165, 221, 0.45);
}
/* Auto dark when no override */
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) {
color-scheme: dark;
}
}