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

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

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

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

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

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

518 lines
31 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

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

<!doctype html>
<html lang="nb">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>ROS — M365 Copilot — Lier kommune</title>
<link rel="stylesheet" href="../playground-design-system/tokens.css" />
<link rel="stylesheet" href="../playground-design-system/base.css" />
<link rel="stylesheet" href="../playground-design-system/components.css" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style>
/* Page-specific layout */
.layout { display: grid; grid-template-rows: auto 1fr; min-height: 100vh; }
.page { padding: var(--space-8) 0 var(--space-16); }
.page__header {
display: flex; justify-content: space-between; align-items: flex-end;
gap: var(--space-6); margin-bottom: var(--space-6);
border-bottom: 1px solid var(--color-border-subtle);
padding-bottom: var(--space-4);
}
.page__title { display: flex; flex-direction: column; gap: 4px; }
.page__eyebrow {
font-size: var(--font-size-xs); text-transform: uppercase; letter-spacing: 0.1em;
color: var(--color-scope-architect); font-weight: var(--font-weight-semibold);
}
.page__meta { display: flex; gap: var(--space-4); font-size: var(--font-size-sm); color: var(--color-text-secondary); }
.page__meta-item { display: flex; gap: 6px; align-items: baseline; }
.page__meta-label { color: var(--color-text-tertiary); font-size: var(--font-size-xs); text-transform: uppercase; letter-spacing: 0.06em; }
.verdict {
display: inline-flex; align-items: center; gap: 8px;
padding: 6px 14px;
border-radius: var(--radius-pill);
font-weight: var(--font-weight-semibold);
font-size: var(--font-size-sm);
}
.verdict[data-verdict="go-with-conditions"] { background: var(--color-severity-medium-soft); color: var(--color-severity-medium-on); }
.verdict[data-verdict="block"] { background: var(--color-severity-critical); color: #fff; }
.verdict[data-verdict="approved"] { background: var(--color-severity-low-soft); color: var(--color-severity-low-on); }
.verdict__dot { width: 8px; height: 8px; border-radius: 50%; background: currentColor; }
.screen-tabs {
display: flex; gap: var(--space-1); padding: 4px;
background: var(--color-bg-soft); border-radius: var(--radius-md);
width: fit-content;
}
.screen-tab {
padding: 8px 14px; font-size: var(--font-size-sm); font-weight: var(--font-weight-medium);
background: transparent; border: none; border-radius: var(--radius-sm); cursor: pointer;
color: var(--color-text-secondary); font-family: inherit;
}
.screen-tab[aria-current="true"] { background: var(--color-surface); color: var(--color-text-primary); box-shadow: var(--shadow-sm); }
.screen { display: none; }
.screen[data-active="true"] { display: block; }
/* Two-col with sidebar */
.ros-layout { display: grid; grid-template-columns: 1fr 320px; gap: var(--space-8); align-items: start; }
@media (max-width: 980px) { .ros-layout { grid-template-columns: 1fr; } }
.key-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-4); margin-bottom: var(--space-6); }
.key-stat { padding: var(--space-4); background: var(--color-surface); border: 1px solid var(--color-border-subtle); border-radius: var(--radius-md); }
.key-stat__label { font-size: var(--font-size-xs); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-tertiary); margin-bottom: 4px; }
.key-stat__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.1; }
.key-stat__hint { font-size: var(--font-size-xs); color: var(--color-text-secondary); margin-top: 2px; }
.key-stat--critical .key-stat__value { color: var(--color-severity-critical); }
.key-stat--medium .key-stat__value { color: var(--color-severity-medium-on); }
@media (max-width: 720px) { .key-stats { grid-template-columns: repeat(2, 1fr); } }
/* Top risks list */
.top-risks { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.top-risk { display: grid; grid-template-columns: auto 36px 1fr auto; gap: var(--space-3); align-items: center; padding: 10px 12px; border: 1px solid var(--color-border-subtle); border-radius: var(--radius-md); background: var(--color-surface); cursor: pointer; }
.top-risk:hover { border-color: var(--color-border-moderate); }
.top-risk__rank { font-family: var(--font-family-mono); font-size: var(--font-size-xs); color: var(--color-text-tertiary); width: 24px; }
.top-risk__score { font-weight: var(--font-weight-semibold); font-variant-numeric: tabular-nums; padding: 4px 8px; border-radius: var(--radius-sm); text-align: center; min-width: 36px; font-size: var(--font-size-sm); }
.top-risk__score[data-zone="critical"] { background: var(--color-severity-critical); color: #fff; }
.top-risk__score[data-zone="high"] { background: var(--color-severity-high); color: #fff; }
.top-risk__score[data-zone="medium"] { background: var(--color-severity-medium-soft); color: var(--color-severity-medium-on); }
.top-risk__score[data-zone="low"] { background: var(--color-severity-low-soft); color: var(--color-severity-low-on); }
.top-risk__title { font-size: var(--font-size-sm); font-weight: var(--font-weight-medium); }
.top-risk__id { font-family: var(--font-family-mono); font-size: 11px; color: var(--color-text-tertiary); margin-bottom: 2px; }
.top-risk__delta { font-size: var(--font-size-xs); color: var(--color-text-tertiary); font-variant-numeric: tabular-nums; }
/* Detail panel */
.threat-detail { display: flex; flex-direction: column; gap: var(--space-5); }
.threat-detail__title { font-size: var(--font-size-xl); font-weight: var(--font-weight-semibold); line-height: 1.3; }
.threat-detail__id { font-family: var(--font-family-mono); font-size: var(--font-size-xs); color: var(--color-text-tertiary); }
.threat-detail__scores {
display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3);
padding: var(--space-4); background: var(--color-bg-soft); border-radius: var(--radius-md);
}
.threat-detail__score-block { display: flex; flex-direction: column; gap: 2px; }
.threat-detail__score-label { font-size: var(--font-size-xs); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-tertiary); }
.threat-detail__score-value { font-size: var(--font-size-xl); font-weight: var(--font-weight-bold); font-variant-numeric: tabular-nums; }
.threat-detail__section h4 { font-size: var(--font-size-sm); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-secondary); margin-bottom: 6px; font-weight: var(--font-weight-semibold); }
.threat-detail__section p { font-size: var(--font-size-sm); line-height: var(--line-height-normal); }
.residual-pair { display: grid; grid-template-columns: 1fr auto 1fr; gap: var(--space-3); align-items: center; padding: var(--space-3); background: var(--color-surface); border: 1px solid var(--color-border-subtle); border-radius: var(--radius-md); }
.residual-cell { text-align: center; padding: var(--space-3); border-radius: var(--radius-sm); }
.residual-cell__label { font-size: var(--font-size-xs); text-transform: uppercase; letter-spacing: 0.06em; }
.residual-cell__value { font-size: var(--font-size-2xl); font-weight: var(--font-weight-bold); font-variant-numeric: tabular-nums; line-height: 1.1; margin-top: 2px; }
.residual-cell[data-zone="critical"] { background: var(--color-severity-critical-soft); color: var(--color-severity-critical-on); }
.residual-cell[data-zone="critical"] .residual-cell__value { color: var(--color-severity-critical); }
.residual-cell[data-zone="high"] { background: var(--color-severity-high-soft); color: var(--color-severity-high-on); }
.residual-cell[data-zone="medium"] { background: var(--color-severity-medium-soft); color: var(--color-severity-medium-on); }
.residual-cell[data-zone="low"] { background: var(--color-severity-low-soft); color: var(--color-severity-low-on); }
.residual-arrow { color: var(--color-text-tertiary); font-size: 22px; }
.mitigation-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.mitigation { display: grid; grid-template-columns: auto 1fr auto; gap: var(--space-3); padding: 10px 12px; border: 1px solid var(--color-border-subtle); border-radius: var(--radius-sm); align-items: center; font-size: var(--font-size-sm); }
.mitigation__id { font-family: var(--font-family-mono); font-size: 11px; color: var(--color-text-tertiary); }
.mitigation__status { font-size: var(--font-size-xs); padding: 2px 8px; border-radius: var(--radius-pill); font-weight: var(--font-weight-medium); }
.mitigation__status[data-status="implemented"] { background: var(--color-severity-low-soft); color: var(--color-severity-low-on); }
.mitigation__status[data-status="planned"] { background: var(--color-severity-medium-soft); color: var(--color-severity-medium-on); }
.mitigation__status[data-status="proposed"] { background: var(--color-bg-soft); color: var(--color-text-secondary); }
/* Wizard form */
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-5) var(--space-6); }
.form-grid > .form-grid__full { grid-column: 1 / -1; }
@media (max-width: 720px) { .form-grid { grid-template-columns: 1fr; } }
.chip-group { display: flex; flex-wrap: wrap; gap: 6px; }
.chip-input { display: none; }
.chip-input + label {
display: inline-flex; align-items: center; gap: 6px;
padding: 7px 12px; font-size: var(--font-size-sm);
border: 1px solid var(--color-border-moderate);
border-radius: var(--radius-pill);
background: var(--color-surface);
color: var(--color-text-secondary);
cursor: pointer;
user-select: none;
}
.chip-input + label:hover { border-color: var(--color-border-strong); color: var(--color-text-primary); }
.chip-input:checked + label {
background: var(--color-primary-500); color: #fff; border-color: var(--color-primary-500);
}
.chip-input:focus-visible + label { box-shadow: var(--shadow-focus); }
/* Summary screen */
.summary-grid { display: grid; grid-template-columns: 1.4fr 1fr; gap: var(--space-6); }
@media (max-width: 980px) { .summary-grid { grid-template-columns: 1fr; } }
.recommendation-card {
padding: var(--space-6); border: 1px solid var(--color-border-subtle); border-radius: var(--radius-lg);
background: var(--color-surface);
}
.recommendation-card__verdict-line { display: flex; gap: var(--space-3); align-items: center; margin-bottom: var(--space-4); }
.recommendation-card__verdict-line h2 { margin: 0; }
.recommendation-card__conditions { list-style: none; padding: 0; margin: var(--space-3) 0 0; }
.recommendation-card__conditions li {
padding: 8px 0 8px 28px; position: relative; font-size: var(--font-size-sm); line-height: var(--line-height-snug);
border-top: 1px solid var(--color-border-subtle);
}
.recommendation-card__conditions li:first-child { border-top: none; }
.recommendation-card__conditions li::before {
content: ''; position: absolute; left: 0; top: 14px;
width: 16px; height: 16px; border-radius: 50%;
border: 1.5px solid var(--color-border-moderate);
}
/* Print rules */
@media print {
.app-header, .screen-tabs, .wizard__nav, .no-print { display: none !important; }
.screen { display: block !important; page-break-after: always; }
.ros-layout { grid-template-columns: 1fr; }
.matrix__cell { print-color-adjust: exact; }
}
</style>
</head>
<body>
<div class="layout">
<!-- ============== HEADER ============== -->
<header class="app-header no-print">
<a href="index.html" class="app-header__brand">
<span class="app-header__brand-mark">A</span>
<span>ms-ai-architect</span>
</a>
<span class="app-header__breadcrumb">
<span aria-hidden="true">/</span>
<span>Playground</span>
<span aria-hidden="true">/</span>
<span>ROS-analyse</span>
</span>
<span class="app-header__spacer"></span>
<span class="badge badge--scope-architect">ms-ai-architect</span>
<button type="button" class="theme-toggle" id="themeToggle" aria-label="Bytt tema">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
<span id="themeLabel">Mørkt</span>
</button>
<button type="button" class="btn btn--secondary btn--sm" onclick="window.print()">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M6 9V2h12v7M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2M6 14h12v8H6z"/></svg>
Skriv ut
</button>
</header>
<main class="page">
<div class="container container--wide">
<!-- Page header -->
<header class="page__header">
<div class="page__title">
<span class="page__eyebrow">ROS — Risiko- og Sårbarhetsanalyse · NS 5814</span>
<h1>M365 Copilot Enterprise — Lier kommune</h1>
<div class="page__meta" style="margin-top: 8px;">
<span class="page__meta-item"><span class="page__meta-label">ID</span> <code>ROS-2026-LIER-COPILOT-01</code></span>
<span class="page__meta-item"><span class="page__meta-label">Brukerantall</span> <span class="tabular">1 850</span></span>
<span class="page__meta-item"><span class="page__meta-label">Sektor</span> Kommune (~28 000)</span>
<span class="page__meta-item"><span class="page__meta-label">Sist oppdatert</span> 1. mai 2026</span>
</div>
</div>
<div class="row" style="gap: var(--space-3);">
<span class="verdict" data-verdict="go-with-conditions">
<span class="verdict__dot" aria-hidden="true"></span>
GO med betingelser
</span>
</div>
</header>
<!-- Tabs -->
<nav class="screen-tabs no-print" role="tablist" aria-label="ROS-skjermer">
<button type="button" class="screen-tab" role="tab" aria-current="true" data-screen="intake">1 · Intake</button>
<button type="button" class="screen-tab" role="tab" aria-current="false" data-screen="matrix">2 · Risikomatrise</button>
<button type="button" class="screen-tab" role="tab" aria-current="false" data-screen="findings">3 · Funn</button>
<button type="button" class="screen-tab" role="tab" aria-current="false" data-screen="summary">4 · Sammendrag</button>
</nav>
<!-- ========================================================
SCREEN 1 — INTAKE WIZARD
======================================================== -->
<section class="screen" data-screen="intake" data-active="false" style="margin-top: var(--space-6);">
<nav class="stepper" aria-label="Intake-steg">
<button type="button" class="stepper__step" data-state="active">
<span class="stepper__step-number"><span class="stepper__step-number-text">1</span></span>
<span class="stepper__step-text">
<span class="stepper__step-label">Org-profil</span>
<span class="stepper__step-hint">Kommune, sektor, størrelse</span>
</span>
</button>
<button type="button" class="stepper__step" data-state="pending">
<span class="stepper__step-number"><span class="stepper__step-number-text">2</span></span>
<span class="stepper__step-text">
<span class="stepper__step-label">System</span>
<span class="stepper__step-hint">Lisens, residens, brukere</span>
</span>
</button>
<button type="button" class="stepper__step" data-state="pending">
<span class="stepper__step-number"><span class="stepper__step-number-text">3</span></span>
<span class="stepper__step-text">
<span class="stepper__step-label">Datasensitivitet</span>
<span class="stepper__step-hint">Persondata-kategorier</span>
</span>
</button>
<button type="button" class="stepper__step" data-state="pending">
<span class="stepper__step-number"><span class="stepper__step-number-text">4</span></span>
<span class="stepper__step-text">
<span class="stepper__step-label">Compliance</span>
<span class="stepper__step-hint">Rammeverk og krav</span>
</span>
</button>
<button type="button" class="stepper__step" data-state="pending">
<span class="stepper__step-number"><span class="stepper__step-number-text">5</span></span>
<span class="stepper__step-text">
<span class="stepper__step-label">Bekreft</span>
<span class="stepper__step-hint">Generer ROS</span>
</span>
</button>
</nav>
<div class="stack stack--lg" style="max-width: 880px;">
<h2>Organisasjonsprofil</h2>
<p class="text-secondary" style="font-size: var(--font-size-md);">
Vi tilpasser ROS-malen til virksomheten din. Felter merket med skarpere ramme er obligatoriske for å sende inn til Datatilsynet.
</p>
<div class="form-grid">
<div>
<label class="label" for="orgName">Virksomhet
<span class="label__hint">Etat, kommune eller foretak</span>
</label>
<input type="text" class="input" id="orgName" value="Lier kommune" />
</div>
<div>
<label class="label" for="orgSize">Antall ansatte<span class="label__hint">Påvirker brukerbase i scenarioer</span></label>
<input type="number" class="input" id="orgSize" value="1850" />
</div>
<div class="form-grid__full">
<span class="label">Sektor</span>
<div class="chip-group">
<input type="radio" name="sector" id="s-kommune" class="chip-input" checked />
<label for="s-kommune">Kommune</label>
<input type="radio" name="sector" id="s-fylke" class="chip-input" />
<label for="s-fylke">Fylkeskommune</label>
<input type="radio" name="sector" id="s-etat" class="chip-input" />
<label for="s-etat">Statlig etat</label>
<input type="radio" name="sector" id="s-helse" class="chip-input" />
<label for="s-helse">Helseforetak</label>
<input type="radio" name="sector" id="s-foretak" class="chip-input" />
<label for="s-foretak">Statlig foretak</label>
</div>
</div>
<div class="form-grid__full">
<span class="label">Eksisterende lisenser<span class="label__hint">Brukes til å vurdere kapabilitetsmatrise</span></span>
<div class="chip-group">
<input type="checkbox" id="l-e3" class="chip-input" />
<label for="l-e3">M365 E3</label>
<input type="checkbox" id="l-e5" class="chip-input" checked />
<label for="l-e5">M365 E5</label>
<input type="checkbox" id="l-purview" class="chip-input" checked />
<label for="l-purview">Purview</label>
<input type="checkbox" id="l-defender" class="chip-input" checked />
<label for="l-defender">Defender for Cloud Apps</label>
<input type="checkbox" id="l-sov" class="chip-input" />
<label for="l-sov">Cloud for Sovereignty</label>
</div>
</div>
<div class="form-grid__full">
<div class="inline-message inline-message--info">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true" style="flex-shrink:0;"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/></svg>
<span>Lier har ikke aktivert <strong>Microsoft Cloud for Sovereignty</strong>. Vi vurderer Schrems II-eksponering som forhøyet inntil dette er på plass.</span>
</div>
</div>
</div>
<div class="wizard__nav">
<button type="button" class="btn btn--secondary" disabled>Forrige</button>
<div class="row">
<button type="button" class="btn btn--ghost">Lagre utkast</button>
<button type="button" class="btn btn--primary" data-goto="matrix">Neste: System →</button>
</div>
</div>
</div>
</section>
<!-- ========================================================
SCREEN 2 — RISK MATRIX (centerpiece)
======================================================== -->
<section class="screen" data-screen="matrix" data-active="true" style="margin-top: var(--space-6);">
<div class="key-stats">
<div class="key-stat">
<div class="key-stat__label">Identifiserte trusler</div>
<div class="key-stat__value tabular">49</div>
<div class="key-stat__hint">Av 64 i kanonisk katalog</div>
</div>
<div class="key-stat key-stat--critical">
<div class="key-stat__label">Kritiske (rød sone)</div>
<div class="key-stat__value tabular">7</div>
<div class="key-stat__hint">Score 1525 før tiltak</div>
</div>
<div class="key-stat key-stat--medium">
<div class="key-stat__label">Mitigeringer planlagt</div>
<div class="key-stat__value tabular">31</div>
<div class="key-stat__hint">Reduserer 22 trusler</div>
</div>
<div class="key-stat">
<div class="key-stat__label">Restrisiko etter tiltak</div>
<div class="key-stat__value tabular">2</div>
<div class="key-stat__hint">Krever GO-betingelser</div>
</div>
</div>
<div class="ros-layout">
<!-- Matrix -->
<div class="card" style="padding: var(--space-6);">
<div style="display: flex; justify-content: space-between; align-items: baseline; margin-bottom: var(--space-4);">
<div>
<h2>5×5 Risikomatrise</h2>
<p class="text-secondary text-sm" style="margin-top: 4px;">49 trusler plassert etter sannsynlighet × konsekvens. Klikk en celle for å se trusler.</p>
</div>
<div class="row" style="gap: var(--space-2);">
<button type="button" class="btn btn--ghost btn--sm" id="toggleResidual">Vis restrisiko etter tiltak</button>
</div>
</div>
<div class="matrix">
<div class="matrix__y-label">Konsekvens</div>
<div class="matrix__main">
<div class="matrix__grid" id="rosMatrix">
<!-- populated by JS -->
</div>
<div class="matrix__x-label">Sannsynlighet →</div>
<div class="matrix__legend">
<span><span class="matrix__legend-swatch" style="background: var(--color-severity-low-soft)"></span>Lav (18)</span>
<span><span class="matrix__legend-swatch" style="background: var(--color-severity-medium-soft)"></span>Middels (912)</span>
<span><span class="matrix__legend-swatch" style="background: var(--color-severity-high-soft)"></span>Høy (1516)</span>
<span><span class="matrix__legend-swatch" style="background: var(--color-severity-critical-soft)"></span>Kritisk (2025)</span>
</div>
</div>
</div>
</div>
<!-- Sidebar: 7-axis radar -->
<aside class="card">
<h3 style="margin-bottom: var(--space-4);">Dimensjons-radar</h3>
<p class="text-secondary text-sm" style="margin-bottom: var(--space-4);">7 NS 5814-akser, vektet etter dataresidens og brukerantall.</p>
<div class="radar">
<div class="radar__chart">
<svg viewBox="-130 -130 260 260" class="radar__svg" aria-label="Dimensjons-radar">
<g id="radarGrid"></g>
</svg>
</div>
</div>
<div class="radar__legend" style="margin-top: var(--space-3);">
<div class="radar__legend-item"><span class="radar__legend-swatch radar__legend-swatch--current"></span><span>Nåværende risiko</span></div>
<div class="radar__legend-item"><span class="radar__legend-swatch radar__legend-swatch--target"></span><span>Etter mitigeringer</span></div>
</div>
<dl class="radar__scores" id="radarScores"></dl>
</aside>
</div>
</section>
<!-- ========================================================
SCREEN 3 — FINDINGS BROWSER
======================================================== -->
<section class="screen" data-screen="findings" data-active="false" style="margin-top: var(--space-6);">
<div class="findings">
<div class="findings__list" role="region" aria-label="Trusselliste">
<div class="findings__toolbar">
<input type="search" class="findings__search" placeholder="Søk trusler…" aria-label="Søk" />
<button type="button" class="btn btn--ghost btn--sm" aria-label="Filter" title="Filter">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M22 3H2l8 9.46V19l4 2v-8.54L22 3z"/></svg>
</button>
</div>
<div id="findingsGroups" style="overflow-y: auto;"></div>
</div>
<div class="findings__detail" id="findingDetail">
<!-- Populated -->
</div>
</div>
</section>
<!-- ========================================================
SCREEN 4 — SUMMARY (print-ready)
======================================================== -->
<section class="screen" data-screen="summary" data-active="false" style="margin-top: var(--space-6);">
<div class="summary-grid">
<!-- Top risks -->
<div class="card">
<h2>Topp 5 risikoer</h2>
<p class="text-secondary text-sm" style="margin-top: 4px; margin-bottom: var(--space-4);">Sortert etter score før tiltak. Pil viser endring etter mitigering.</p>
<ol class="top-risks" id="topRisks"></ol>
</div>
<!-- Recommendation -->
<div class="recommendation-card">
<div class="recommendation-card__verdict-line">
<span class="verdict" data-verdict="go-with-conditions">
<span class="verdict__dot" aria-hidden="true"></span>
GO med betingelser
</span>
</div>
<h2>Anbefaling</h2>
<p style="margin-top: var(--space-3); font-size: var(--font-size-md); line-height: var(--line-height-normal);">
Utrullingen kan gå videre forutsatt at fire kontroller er på plass før første pilotgruppe får tilgang. To av de syv kritiske truslene har restrisiko som krever oppfølging på tertialvis nivå.
</p>
<h4 style="margin-top: var(--space-5); font-size: var(--font-size-sm); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-secondary);">Betingelser</h4>
<ol class="recommendation-card__conditions">
<li><strong>Sensitivity Labels</strong> aktivert på alle SharePoint-områder med personopplysninger (M-001).</li>
<li><strong>EU Data Boundary</strong> bekreftet før første prompt (M-003).</li>
<li><strong>Endpoint DLP</strong> rullet ut til alle 1 850 ansatte (M-002).</li>
<li><strong>Tertialvis evaluering</strong> av T-007 og T-019 i sikkerhetsforum.</li>
</ol>
<div style="margin-top: var(--space-6); display: flex; gap: var(--space-2); flex-wrap: wrap;">
<button type="button" class="btn btn--primary">Eksporter PDF</button>
<button type="button" class="btn btn--secondary">Kopier slash-pipeline</button>
</div>
</div>
<!-- Compliance -->
<div class="card" style="grid-column: 1 / -1;">
<h2>Rammeverk-dekning</h2>
<p class="text-secondary text-sm" style="margin-top: 4px; margin-bottom: var(--space-4);">Hvilke krav ROS-en hjemler. Klikk for detaljer.</p>
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-3);">
<div class="card card--sunken" style="padding: var(--space-3);">
<div class="text-xs text-secondary" style="text-transform: uppercase; letter-spacing: 0.06em;">NS 5814:2021</div>
<div style="font-weight: 600; font-size: var(--font-size-sm); margin-top: 4px;">Dekket — 7/7 dimensjoner</div>
</div>
<div class="card card--sunken" style="padding: var(--space-3);">
<div class="text-xs text-secondary" style="text-transform: uppercase; letter-spacing: 0.06em;">GDPR Art. 35</div>
<div style="font-weight: 600; font-size: var(--font-size-sm); margin-top: 4px;">Krever DPIA — utløst</div>
</div>
<div class="card card--sunken" style="padding: var(--space-3);">
<div class="text-xs text-secondary" style="text-transform: uppercase; letter-spacing: 0.06em;">EU AI Act</div>
<div style="font-weight: 600; font-size: var(--font-size-sm); margin-top: 4px;">Begrenset risiko (Art. 50)</div>
</div>
<div class="card card--sunken" style="padding: var(--space-3);">
<div class="text-xs text-secondary" style="text-transform: uppercase; letter-spacing: 0.06em;">Digitaliseringsdir.</div>
<div style="font-weight: 600; font-size: var(--font-size-sm); margin-top: 4px;">Veileder fulgt</div>
</div>
</div>
</div>
</div>
</section>
</div>
</main>
</div>
<!-- ============== SIDEPANEL ============== -->
<div class="scrim" id="scrim" aria-hidden="true"></div>
<aside class="sidepanel" id="sidepanel" role="dialog" aria-modal="true" aria-labelledby="sidepanelTitle" aria-hidden="true">
<div class="sidepanel__header">
<div>
<div class="text-xs text-tertiary text-mono" id="sidepanelId" style="margin-bottom: 4px;"></div>
<h2 id="sidepanelTitle" style="font-size: var(--font-size-lg);"></h2>
</div>
<button type="button" class="sidepanel__close" id="sidepanelClose" aria-label="Lukk">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6 6 18M6 6l12 12"/></svg>
</button>
</div>
<div class="sidepanel__body" id="sidepanelBody"></div>
</aside>
<script src="ros-data.js"></script>
<script src="ros-app.js"></script>
</body>
</html>