feat(shared): Tier 3 wave 2 (12 components) + self-hosted fonts
Two changes in one commit because they were prepared together and the component demos depend on the new self-hosted fonts.css. Tier 3 wave 2 — 12 new components --------------------------------- Adds components-tier3-supplement.css (886 lines) and 12 isolated demo HTML pages under shared/playground-examples/components/: toxic-flow chain, fleet-overview, kanban Keep/Review/Remove, maturity-ladder, classify-and-transform, cycle-ribbon, persistent-antipattern, suppressed-signals, ExpansionCard, ReadMore, FormProgress, Aspirational-vs-Committed. Reuses existing tokens — no new CSS custom properties. Honors the Phase 1 feedback rules: no large pink areas for body text, severity-red distinct from failure-red, dark mode via existing [data-theme="dark"]. Provenance: components-tier3-supplement.css and the 12 demo bodies were authored by claude.ai/design (separate Anthropic instance) on 2026-05-03. This commit only integrates them — path rewrites, font swap, generic name substitution in fleet-overview demo data, README updates. base.css from the export was deliberately NOT taken in because it reverted the inline-message contrast fix from v0.1. Self-hosted fonts (Inter, JetBrains Mono, Source Serif 4) --------------------------------------------------------- Replaces all fonts.googleapis.com / fonts.gstatic.com requests with .woff2 files bundled at shared/playground-design-system/fonts/. Why: - No data leaked to Google about end-user IPs and User-Agents. - GDPR-safe for Norwegian public-sector deployments. - Works offline / behind air-gapped firewalls. - Forkers downloading the marketplace get a complete bundle. All three families are SIL Open Font License 1.1 — license texts included alongside the woff2 files. Source Serif 4 woff2 generated locally from the upstream OTF release using fonttools ttLib.woff2 compress; Inter and JetBrains Mono are unmodified upstream webfont releases. Total bundle: 9 woff2 files, ~940 KB. New fonts.css declares all @font-face rules with font-display: swap. All 6 example HTMLs and 12 new component demos load it via a single relative path. Verified -------- - Privacy grep returns empty across plugins/ and shared/ - Google Fonts grep returns empty across shared/*.html - Smoke test via python -m http.server: HTML + 7 stylesheets + Inter-Regular.woff2 all return 200 Doc updates ----------- - shared/playground-design-system/README.md: file tree updated, Quick start snippet shows fonts.css link, "Self-hosted fonts" section added - shared/playground-design-system/fonts/LICENSES.md: combined attribution - README.md (root): Tier 3 wave 1+2 component list, Privacy-first bullet - CLAUDE.md (root): tree entry expanded for new components + fonts Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
5b6c1da8fc
commit
f1fecf39b8
36 changed files with 2470 additions and 29 deletions
|
|
@ -17,8 +17,8 @@ plugins/
|
||||||
ultra-cc-architect/ v0.1.0 — Claude-Code-specific architecture matching + skill-factory (extracted from ultraplan-local in v3.0.0)
|
ultra-cc-architect/ v0.1.0 — Claude-Code-specific architecture matching + skill-factory (extracted from ultraplan-local in v3.0.0)
|
||||||
|
|
||||||
shared/
|
shared/
|
||||||
playground-design-system/ v0.1 — Aksel/Digdir-aligned CSS design system + JSON schemas for plugin Playgrounds (consumed by ms-ai-architect, okr, llm-security, ultraplan-local, config-audit)
|
playground-design-system/ v0.1 — Aksel/Digdir-aligned CSS design system + JSON schemas + self-hosted Inter/JetBrains Mono/Source Serif 4 fonts (Tier 1+2+3 wave 1+wave 2 = 20 Tier 3 components total). Consumed by ms-ai-architect, okr, llm-security, ultraplan-local, config-audit
|
||||||
playground-examples/ — Reference scenarios (ROS-Lier, OKR-Bærum, security-Direktorat) + showcase landing
|
playground-examples/ — Reference scenarios (ROS-Lier, OKR-Bærum, security-Direktorat) + showcase landing + 12 isolated Tier 3 wave 2 component demos under components/
|
||||||
```
|
```
|
||||||
|
|
||||||
Hvert plugin er selvstendig med egen CLAUDE.md, README, hooks, agents og commands. `shared/` inneholder marketplace-nivå infrastruktur som flere plugins bygger på.
|
Hvert plugin er selvstendig med egen CLAUDE.md, README, hooks, agents og commands. `shared/` inneholder marketplace-nivå infrastruktur som flere plugins bygger på.
|
||||||
|
|
|
||||||
|
|
@ -247,10 +247,12 @@ Shared design system for plugin Playgrounds — visual self-service UIs that com
|
||||||
|
|
||||||
Targets five plugins: `ms-ai-architect`, `okr`, `llm-security`, `ultraplan-local`, `config-audit`. Built for Norwegian public sector decision-makers (kommunaldirektører, sikkerhetsoffiserer, OKR-koordinatorer) plus developer power-users — one visual family, two information densities.
|
Targets five plugins: `ms-ai-architect`, `okr`, `llm-security`, `ultraplan-local`, `config-audit`. Built for Norwegian public sector decision-makers (kommunaldirektører, sikkerhetsoffiserer, OKR-koordinatorer) plus developer power-users — one visual family, two information densities.
|
||||||
|
|
||||||
- **Tokens** — Inter font, body 17px, Digdir blue `#0062BA`, deuteranopia-safe severity ramp, distinct severity-red vs failure-red, plugin-scope colors, semantic CSS custom properties
|
- **Tokens** — Inter/JetBrains Mono/Source Serif 4 (all self-hosted, OFL 1.1), body 17px, Digdir blue `#0062BA`, deuteranopia-safe severity ramp, distinct severity-red vs failure-red, plugin-scope colors, semantic CSS custom properties
|
||||||
- **Tier 1 components** — radar/spider, 5×5 matrix-heatmap (bottom-left origin, ROS/DPIA), findings-browser, critique-card, wizard/stepper, live-meter with antipattern lints
|
- **Tier 1 components** — radar/spider, 5×5 matrix-heatmap (bottom-left origin, ROS/DPIA), findings-browser, critique-card, wizard/stepper, live-meter with antipattern lints
|
||||||
- **Tier 2 components** — decision-tree (AI Act 4-step), traffic-lights, diff-review, treemap (token hotspots), distribution P10/P50/P90, command-pipeline output, AI Act 4-color pyramide, pipeline-cockpit, verdict-pill + 5-band risk-meter, codepoint-reveal (Unicode steganography), small-multiples grid (16-category posture without overcrowded radar), OWASP badges (LLM/ASI/AST/MCP)
|
- **Tier 2 components** — decision-tree (AI Act 4-step), traffic-lights, diff-review, treemap (token hotspots), distribution P10/P50/P90, command-pipeline output, AI Act 4-color pyramide, pipeline-cockpit, verdict-pill + 5-band risk-meter, codepoint-reveal (Unicode steganography), small-multiples grid (16-category posture without overcrowded radar), OWASP badges (LLM/ASI/AST/MCP)
|
||||||
|
- **Tier 3 components (wave 1+2, 20 total)** — pair-before-after, AI Act timeline, 3-track entry, FRIA rights-matrix, capability-matrix, parallel-agent-status, ErrorSummary, GuidePanel, toxic-flow chain, fleet-overview, kanban Keep/Review/Remove, maturity-ladder, classify-and-transform, cycle-ribbon, persistent-antipattern, suppressed-signals, ExpansionCard, ReadMore, FormProgress, Aspirational-vs-Committed
|
||||||
- **JSON schemas** — `finding.schema.json`, `okr-set.schema.json`, `ros-threat.schema.json` for cross-plugin data interchange
|
- **JSON schemas** — `finding.schema.json`, `okr-set.schema.json`, `ros-threat.schema.json` for cross-plugin data interchange
|
||||||
|
- **Privacy-first** — all fonts self-hosted as woff2 in `fonts/`, zero external CDN requests, GDPR-safe for offentlig sektor, works offline / behind air-gapped firewalls
|
||||||
- **Reference scenarios** — Lier kommune ROS-rapport (ms-ai-architect), Bærum kommune T2 OKR live-writer, Direktoratet for digital tjenesteutvikling ToxicSkills findings review (85 funn, BLOCK)
|
- **Reference scenarios** — Lier kommune ROS-rapport (ms-ai-architect), Bærum kommune T2 OKR live-writer, Direktoratet for digital tjenesteutvikling ToxicSkills findings review (85 funn, BLOCK)
|
||||||
|
|
||||||
→ [Full documentation](shared/playground-design-system/README.md) · [Browse showcase](shared/playground-examples/index.html)
|
→ [Full documentation](shared/playground-design-system/README.md) · [Browse showcase](shared/playground-examples/index.html)
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,14 @@ shared/
|
||||||
│ ├── base.css # Reset, typography, primitives, focus, print
|
│ ├── base.css # Reset, typography, primitives, focus, print
|
||||||
│ ├── components.css # Tier 1: radar, matrix, findings-browser, critique-card, wizard, live-meter
|
│ ├── 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-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: pair-before-after, AI Act timeline, 3-track entry, FRIA rights-matrix, capability-matrix, parallel-agent-status, ErrorSummary, GuidePanel
|
│ ├── 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
|
│ ├── print.css # A4 print stylesheet with B/W severity patterns
|
||||||
│ └── schemas/ # Cross-plugin JSON schemas
|
│ └── schemas/ # Cross-plugin JSON schemas
|
||||||
│ ├── finding.schema.json # Used by llm-security, config-audit, ultraplan-review, ms-ai-review
|
│ ├── finding.schema.json # Used by llm-security, config-audit, ultraplan-review, ms-ai-review
|
||||||
|
|
@ -31,8 +38,22 @@ shared/
|
||||||
├── index.html # System showcase (browse all components)
|
├── index.html # System showcase (browse all components)
|
||||||
├── ros-lier-kommune.html # Scenario A — ms-ai-architect ROS report
|
├── ros-lier-kommune.html # Scenario A — ms-ai-architect ROS report
|
||||||
├── okr-baerum.html # Scenario B — OKR live writer
|
├── okr-baerum.html # Scenario B — OKR live writer
|
||||||
├── security-direktorat.html # Scenario C — llm-security findings review
|
├── security-direktorat.html # Scenario C — llm-security findings review
|
||||||
├── templates.html # Skeleton + print-template demos
|
├── 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-app.js # Scenario A interactivity
|
||||||
└── ros-data.js # Scenario A mock data
|
└── ros-data.js # Scenario A mock data
|
||||||
```
|
```
|
||||||
|
|
@ -50,8 +71,12 @@ To use the design system from a plugin's Playground:
|
||||||
<link rel="stylesheet" href="../../shared/playground-design-system/base.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.css">
|
||||||
<link rel="stylesheet" href="../../shared/playground-design-system/components-tier2.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 -->
|
<!-- Optional: only include print.css if scenario produces a printable A4 report -->
|
||||||
<link rel="stylesheet" href="../../shared/playground-design-system/print.css">
|
<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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header class="app-header">
|
<header class="app-header">
|
||||||
|
|
@ -166,13 +191,15 @@ localStorage.setItem('theme', document.documentElement.dataset.theme);
|
||||||
|
|
||||||
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.
|
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 (Phase 1)
|
## Known limitations
|
||||||
|
|
||||||
1. **Google Fonts CDN dependency.** All example HTML files load Inter from `fonts.googleapis.com`. This violates the "self-contained, no CDN" constraint. The system-font-stack fallback in `base.css` works, so files render acceptably offline — but for production deployment, Inter should be self-hosted as woff2 files in `playground-design-system/fonts/`. Tracked for Phase 2.
|
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. **Tier 3 components missing.** Several components from the design brief are not yet implemented: rights-matrix (FRIA 12 EU Charter), capability-matrix (license × kapabilitet), fleet-overview (cross-project security dashboard), kanban (Keep/Review/Remove), sankey/toxic-flow chain (security TFA), classify-and-transform (OKR 5-bucket sorter), maturity-ladder (OKR/posture progression), parallel-agent-status panel (utredning, ultraexecute waves), suppressed-signals panel.
|
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. **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.
|
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.
|
||||||
4. **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.
|
|
||||||
5. **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
|
## Versioning
|
||||||
|
|
||||||
|
|
|
||||||
886
shared/playground-design-system/components-tier3-supplement.css
Normal file
886
shared/playground-design-system/components-tier3-supplement.css
Normal file
|
|
@ -0,0 +1,886 @@
|
||||||
|
/* =============================================================================
|
||||||
|
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; }
|
||||||
83
shared/playground-design-system/fonts.css
Normal file
83
shared/playground-design-system/fonts.css
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* 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");
|
||||||
|
}
|
||||||
BIN
shared/playground-design-system/fonts/Inter-Bold.woff2
Normal file
BIN
shared/playground-design-system/fonts/Inter-Bold.woff2
Normal file
Binary file not shown.
BIN
shared/playground-design-system/fonts/Inter-Medium.woff2
Normal file
BIN
shared/playground-design-system/fonts/Inter-Medium.woff2
Normal file
Binary file not shown.
BIN
shared/playground-design-system/fonts/Inter-Regular.woff2
Normal file
BIN
shared/playground-design-system/fonts/Inter-Regular.woff2
Normal file
Binary file not shown.
BIN
shared/playground-design-system/fonts/Inter-SemiBold.woff2
Normal file
BIN
shared/playground-design-system/fonts/Inter-SemiBold.woff2
Normal file
Binary file not shown.
BIN
shared/playground-design-system/fonts/JetBrainsMono-Medium.woff2
Normal file
BIN
shared/playground-design-system/fonts/JetBrainsMono-Medium.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
92
shared/playground-design-system/fonts/LICENSE-Inter.txt
Normal file
92
shared/playground-design-system/fonts/LICENSE-Inter.txt
Normal 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.
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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.
|
||||||
42
shared/playground-design-system/fonts/LICENSES.md
Normal file
42
shared/playground-design-system/fonts/LICENSES.md
Normal 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 2014–2023 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.
|
||||||
BIN
shared/playground-design-system/fonts/SourceSerif4-Regular.woff2
Normal file
BIN
shared/playground-design-system/fonts/SourceSerif4-Regular.woff2
Normal file
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,100 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="nb">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>Aspirational vs Committed · Tier 3 supp</title>
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/tokens.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/base.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components-tier2.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components-tier3-supplement.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/fonts.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="app-header">
|
||||||
|
<a href="../index.html" class="app-header__brand"><span class="app-header__brand-mark">P</span><span>Playground</span></a>
|
||||||
|
<span class="app-header__breadcrumb">/ Komponenter / Aspirational vs Committed</span>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="container container--default" style="padding: var(--space-8) 0;">
|
||||||
|
<div style="margin-bottom: var(--space-6);">
|
||||||
|
<span style="font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--color-scope-okr); font-weight: var(--font-weight-semibold);">OKR · visuell modus-skille</span>
|
||||||
|
<h1 style="margin: 4px 0 6px;">Aspirational vs Committed</h1>
|
||||||
|
<p class="text-secondary" style="max-width: 65ch;">Modifier på Objective-card. Aspirational (0,7 = success) har stiplet ring + ASP-badge. Committed (1,0 = expected) har solid ring + COM-badge.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display:grid; grid-template-columns: 1fr 1fr; gap: var(--space-4);">
|
||||||
|
|
||||||
|
<article class="okr-mode" data-mode="aspirational" title="Aspirasjon — 0,7 regnes som vellykket">
|
||||||
|
<span class="okr-mode__badge">ASP</span>
|
||||||
|
<div class="okr-mode__row">
|
||||||
|
<div class="okr-mode__gauge">
|
||||||
|
<svg viewBox="0 0 100 100" aria-hidden="true">
|
||||||
|
<circle class="gauge-bg" cx="50" cy="50" r="42"></circle>
|
||||||
|
<circle class="gauge-fill" cx="50" cy="50" r="42" stroke-dasharray="263.9" stroke-dashoffset="105.6"></circle>
|
||||||
|
</svg>
|
||||||
|
<span class="gauge-value">0,60</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="okr-mode__objective">Bli landets ledende kommune på AI-assistert saksbehandling innen 2027</div>
|
||||||
|
<div class="okr-mode__hint">Aspirasjon — 0,7 regnes som vellykket</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="okr-mode" data-mode="committed" title="Committed — 1,0 forventes oppnådd">
|
||||||
|
<span class="okr-mode__badge">COM</span>
|
||||||
|
<div class="okr-mode__row">
|
||||||
|
<div class="okr-mode__gauge">
|
||||||
|
<svg viewBox="0 0 100 100" aria-hidden="true">
|
||||||
|
<circle class="gauge-bg" cx="50" cy="50" r="42"></circle>
|
||||||
|
<circle class="gauge-fill" cx="50" cy="50" r="42" stroke-dasharray="263.9" stroke-dashoffset="26.4"></circle>
|
||||||
|
</svg>
|
||||||
|
<span class="gauge-value">0,90</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="okr-mode__objective">Innfør sentralisert sensitivity-label-policy for alle 1 850 ansatte før 30. juni</div>
|
||||||
|
<div class="okr-mode__hint">Committed — 1,0 forventes oppnådd</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="okr-mode" data-mode="aspirational">
|
||||||
|
<span class="okr-mode__badge">ASP</span>
|
||||||
|
<div class="okr-mode__row">
|
||||||
|
<div class="okr-mode__gauge">
|
||||||
|
<svg viewBox="0 0 100 100" aria-hidden="true">
|
||||||
|
<circle class="gauge-bg" cx="50" cy="50" r="42"></circle>
|
||||||
|
<circle class="gauge-fill" cx="50" cy="50" r="42" stroke-dasharray="263.9" stroke-dashoffset="184.7"></circle>
|
||||||
|
</svg>
|
||||||
|
<span class="gauge-value">0,30</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="okr-mode__objective">Halver gjennomsnittlig saksbehandlingstid på byggesøknader</div>
|
||||||
|
<div class="okr-mode__hint">Aspirasjon — 0,3 så langt, fortsatt rom for å akselerere</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="okr-mode" data-mode="committed">
|
||||||
|
<span class="okr-mode__badge">COM</span>
|
||||||
|
<div class="okr-mode__row">
|
||||||
|
<div class="okr-mode__gauge">
|
||||||
|
<svg viewBox="0 0 100 100" aria-hidden="true">
|
||||||
|
<circle class="gauge-bg" cx="50" cy="50" r="42"></circle>
|
||||||
|
<circle class="gauge-fill" cx="50" cy="50" r="42" stroke-dasharray="263.9" stroke-dashoffset="0"></circle>
|
||||||
|
</svg>
|
||||||
|
<span class="gauge-value">1,00</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="okr-mode__objective">Levér T2-rapport til kommunestyret senest 5. september</div>
|
||||||
|
<div class="okr-mode__hint">Committed — oppnådd</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="nb">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>Classify & Transform · Tier 3 supp</title>
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/tokens.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/base.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components-tier2.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components-tier3-supplement.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/fonts.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="app-header">
|
||||||
|
<a href="../index.html" class="app-header__brand"><span class="app-header__brand-mark">P</span><span>Playground</span></a>
|
||||||
|
<span class="app-header__breadcrumb">/ Komponenter / Classify-and-Transform</span>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="container container--wide" style="padding: var(--space-8) 0;">
|
||||||
|
<div style="margin-bottom: var(--space-6);">
|
||||||
|
<span style="font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--color-scope-okr); font-weight: var(--font-weight-semibold);">OKR · /okr:skriv strategi-til-OKR</span>
|
||||||
|
<h1 style="margin: 4px 0 6px;">5-bucket-sorter</h1>
|
||||||
|
<p class="text-secondary" style="max-width: 65ch;">Lim inn tildelingsbrev øverst — hver krav-setning klassifiseres etter OKR-egnethet (lav, medium, høy).</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cls-sorter">
|
||||||
|
<div class="cls-input">
|
||||||
|
<textarea id="inputText">Bærum kommune skal redusere ventetid på saksbehandling med 30 % innen utgangen av 2026. Innbyggerportalen skal være tilgjengelig 99,5 % av tiden. Andelen selvbetjente saker skal øke fra 42 % til 65 %. Vi skal modernisere innbyggerportalen med AI-assistert chat. Det skal leveres kvartalsrapport til kommunestyret om digitaliseringsfremgang. Copilot for saksbehandlere skal piloteres før Q3.</textarea>
|
||||||
|
<div style="margin-top: var(--space-3); display: flex; gap: 8px;">
|
||||||
|
<button class="btn btn--primary btn--sm" onclick="alert('Mock: 6 setninger klassifisert.')">Klassifiser</button>
|
||||||
|
<span class="text-tertiary" style="font-size: 12px; align-self: center;">6 setninger funnet</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cls-buckets" id="buckets"></div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const buckets = {
|
||||||
|
drift: { name: "Driftskrav", egnethet: "lav",
|
||||||
|
items: [{text: "Sikre at innbyggerportalen er tilgjengelig 99,5 % av tiden", action: "→ KPI"}],
|
||||||
|
cta: "Generer KPI-katalog" },
|
||||||
|
resultat: { name: "Resultatmål", egnethet: "hoy",
|
||||||
|
items: [
|
||||||
|
{text: "Redusere ventetid på saksbehandling med 30 %", action: "→ KR-kandidat"},
|
||||||
|
{text: "Øke andel selvbetjente saker fra 42 % til 65 %", action: "→ KR-kandidat"},
|
||||||
|
],
|
||||||
|
cta: "Generer KR-utkast" },
|
||||||
|
satsing: { name: "Strategiske satsinger", egnethet: "hoy",
|
||||||
|
items: [{text: "Modernisere innbyggerportalen med AI-assistert chat", action: "→ Objective-kandidat"}],
|
||||||
|
cta: "Generer Objective-utkast" },
|
||||||
|
rapportering: { name: "Rapportering", egnethet: "lav",
|
||||||
|
items: [{text: "Kvartalsrapport til kommunestyret om digitaliseringsfremgang", action: "→ Rapporteringsrutine"}],
|
||||||
|
cta: "Skriv rapportmal" },
|
||||||
|
oppdrag: { name: "Særskilte oppdrag", egnethet: "medium",
|
||||||
|
items: [{text: "Pilotere Copilot for saksbehandlere før Q3", action: "→ Case by case"}],
|
||||||
|
cta: "Vurder OKR vs prosjekt" },
|
||||||
|
};
|
||||||
|
|
||||||
|
function egnethetLabel(e) { return e === 'hoy' ? 'Høy egnethet' : e === 'medium' ? 'Medium egnethet' : 'Lav egnethet'; }
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
document.getElementById('buckets').innerHTML = Object.entries(buckets).map(([key, b]) => `
|
||||||
|
<div class="cls-bucket" data-egnethet="${b.egnethet}" data-key="${key}">
|
||||||
|
<div class="cls-bucket__head">
|
||||||
|
<span class="cls-bucket__title">${b.name}</span>
|
||||||
|
<span class="cls-bucket__egnethet">${egnethetLabel(b.egnethet)}</span>
|
||||||
|
</div>
|
||||||
|
${b.items.length ? b.items.map(i => `
|
||||||
|
<div class="cls-item">
|
||||||
|
<span>${i.text}</span>
|
||||||
|
<span class="cls-item__action">${i.action}</span>
|
||||||
|
</div>
|
||||||
|
`).join('') : `<div class="cls-bucket__empty">Ingen setninger her.</div>`}
|
||||||
|
<div class="cls-bucket__action">
|
||||||
|
<button class="btn btn--secondary btn--sm" style="width:100%;" onclick="alert('Mock: ${b.cta}')">${b.cta}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
render();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
90
shared/playground-examples/components/cycle-ribbon.html
Normal file
90
shared/playground-examples/components/cycle-ribbon.html
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="nb">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>Cycle Position Ribbon · Tier 3 supp</title>
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/tokens.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/base.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components-tier2.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components-tier3-supplement.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/fonts.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="app-header">
|
||||||
|
<a href="../index.html" class="app-header__brand"><span class="app-header__brand-mark">P</span><span>Playground</span></a>
|
||||||
|
<span class="app-header__breadcrumb">/ Komponenter / Cycle Position Ribbon</span>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Live ribbon (under header, mock T2-2026 uke 3 av 16) -->
|
||||||
|
<button type="button" class="cycle-ribbon" data-phase="planning" aria-expanded="false" id="ribbon" style="--cycle-progress: 18%;" onclick="toggleRibbon()">
|
||||||
|
<span class="cycle-ribbon__id">T2-2026</span>
|
||||||
|
<span class="cycle-ribbon__week">Uke 3 / 16</span>
|
||||||
|
<span class="cycle-ribbon__phase">Planning</span>
|
||||||
|
<span class="cycle-ribbon__msg">Fokuser på check-in-rytme. Første team-check-in bør være innen uke 5.</span>
|
||||||
|
<span class="cycle-ribbon__chev" aria-hidden="true">▾</span>
|
||||||
|
</button>
|
||||||
|
<div class="cycle-ribbon__panel" id="ribbonPanel">
|
||||||
|
<div style="display:grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-4); align-items:start;">
|
||||||
|
<div>
|
||||||
|
<div class="text-tertiary" style="font-size: 11px; text-transform: uppercase; letter-spacing: .06em;">Periode</div>
|
||||||
|
<strong>1. mai – 31. august 2026</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-tertiary" style="font-size: 11px; text-transform: uppercase; letter-spacing: .06em;">Fase</div>
|
||||||
|
<strong>Planning (uke 1–2)</strong>
|
||||||
|
<div class="text-secondary" style="font-size: 12px;">Execution starter uke 3, retrospective_prep fra uke 14.</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-tertiary" style="font-size: 11px; text-transform: uppercase; letter-spacing: .06em;">Neste milepel</div>
|
||||||
|
<strong>Team-check-in 1</strong>
|
||||||
|
<div class="text-secondary" style="font-size: 12px;">Senest 24. mai 2026 (uke 5).</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<main class="container container--wide" style="padding: var(--space-8) 0;">
|
||||||
|
<div style="margin-bottom: var(--space-6);">
|
||||||
|
<span style="font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--color-scope-okr); font-weight: var(--font-weight-semibold);">OKR · persistent header</span>
|
||||||
|
<h1 style="margin: 4px 0 6px;">Cycle Position Ribbon</h1>
|
||||||
|
<p class="text-secondary" style="max-width: 65ch;">Persistent stripe under app-header som viser hvor i tertialen brukeren er. Klikk for detaljpanel.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 style="font-size: var(--font-size-lg); margin: 0 0 var(--space-3);">Alle 3 faser</h2>
|
||||||
|
|
||||||
|
<div style="display:flex; flex-direction: column; gap: var(--space-4);">
|
||||||
|
<div class="cycle-ribbon" data-phase="planning" style="--cycle-progress: 12%; border-radius: var(--radius-md); border: 1px solid var(--color-border-subtle);">
|
||||||
|
<span class="cycle-ribbon__id">T2-2026</span>
|
||||||
|
<span class="cycle-ribbon__week">Uke 2 / 16</span>
|
||||||
|
<span class="cycle-ribbon__phase">Planning</span>
|
||||||
|
<span class="cycle-ribbon__msg">Sett mål og forankre med ledelse.</span>
|
||||||
|
<span class="cycle-ribbon__chev">▾</span>
|
||||||
|
</div>
|
||||||
|
<div class="cycle-ribbon" data-phase="execution" style="--cycle-progress: 50%; border-radius: var(--radius-md); border: 1px solid var(--color-border-subtle);">
|
||||||
|
<span class="cycle-ribbon__id">T2-2026</span>
|
||||||
|
<span class="cycle-ribbon__week">Uke 8 / 16</span>
|
||||||
|
<span class="cycle-ribbon__phase">Execution</span>
|
||||||
|
<span class="cycle-ribbon__msg">Halvveis. Halvveissamtale anbefales denne uka.</span>
|
||||||
|
<span class="cycle-ribbon__chev">▾</span>
|
||||||
|
</div>
|
||||||
|
<div class="cycle-ribbon" data-phase="retrospective_prep" style="--cycle-progress: 88%; border-radius: var(--radius-md); border: 1px solid var(--color-border-subtle);">
|
||||||
|
<span class="cycle-ribbon__id">T2-2026</span>
|
||||||
|
<span class="cycle-ribbon__week">Uke 14 / 16</span>
|
||||||
|
<span class="cycle-ribbon__phase">Retro-prep</span>
|
||||||
|
<span class="cycle-ribbon__msg">Forbered scoring og retrospektiv. Frist for KR-scoring: 25. august.</span>
|
||||||
|
<span class="cycle-ribbon__chev">▾</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function toggleRibbon() {
|
||||||
|
const r = document.getElementById('ribbon');
|
||||||
|
const open = r.getAttribute('aria-expanded') === 'true';
|
||||||
|
r.setAttribute('aria-expanded', open ? 'false' : 'true');
|
||||||
|
document.getElementById('ribbonPanel').setAttribute('data-open', open ? 'false' : 'true');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
85
shared/playground-examples/components/expansion-card.html
Normal file
85
shared/playground-examples/components/expansion-card.html
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="nb">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>ExpansionCard · Tier 3 supp</title>
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/tokens.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/base.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components-tier2.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components-tier3-supplement.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/fonts.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="app-header">
|
||||||
|
<a href="../index.html" class="app-header__brand"><span class="app-header__brand-mark">P</span><span>Playground</span></a>
|
||||||
|
<span class="app-header__breadcrumb">/ Komponenter / ExpansionCard</span>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="container container--default" style="padding: var(--space-8) 0;">
|
||||||
|
<div style="margin-bottom: var(--space-6);">
|
||||||
|
<span style="font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--color-text-tertiary); font-weight: var(--font-weight-semibold);">Aksel · progressive disclosure</span>
|
||||||
|
<h1 style="margin: 4px 0 6px;">ExpansionCard</h1>
|
||||||
|
<p class="text-secondary" style="max-width: 65ch;">Skjul sekundær informasjon bak klikkbar overskrift. Animert utvidelse respekterer prefers-reduced-motion.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="expansion" aria-expanded="false">
|
||||||
|
<button type="button" class="expansion__head" onclick="toggleExp(this)">
|
||||||
|
<span class="expansion__title">
|
||||||
|
<span class="expansion__title-main">Hva skjer hvis vi avviser denne ROS-rapporten?</span>
|
||||||
|
<span class="expansion__title-sub">Konsekvenser for utrullingsplanen</span>
|
||||||
|
</span>
|
||||||
|
<span class="expansion__chev" aria-hidden="true">▾</span>
|
||||||
|
</button>
|
||||||
|
<div class="expansion__body"><div class="expansion__body-inner"><div>
|
||||||
|
<p class="text-secondary" style="margin: 0 0 var(--space-2);">Avvises rapporten må arbeidsgruppen ta opp igjen tre trusler i kategori "Kritisk":</p>
|
||||||
|
<ul style="font-size: var(--font-size-sm); color: var(--color-text-secondary); margin: 0 0 var(--space-2); padding-left: 20px;">
|
||||||
|
<li>T-014 — DLP-policy for sensitivity labels</li>
|
||||||
|
<li>T-022 — Cross-tenant Schrems II-vurdering</li>
|
||||||
|
<li>T-031 — Audit-logging for prompt-historikk</li>
|
||||||
|
</ul>
|
||||||
|
<p class="text-secondary" style="margin: 0;">Forventet forsinkelse: 4–6 uker. Pilot-fasen flyttes fra juni til august.</p>
|
||||||
|
</div></div></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="expansion" aria-expanded="false">
|
||||||
|
<button type="button" class="expansion__head" onclick="toggleExp(this)">
|
||||||
|
<span class="expansion__title">
|
||||||
|
<span class="expansion__title-main">Hvilke roller skal signere?</span>
|
||||||
|
<span class="expansion__title-sub">Sjekkliste før innsending</span>
|
||||||
|
</span>
|
||||||
|
<span class="expansion__chev" aria-hidden="true">▾</span>
|
||||||
|
</button>
|
||||||
|
<div class="expansion__body"><div class="expansion__body-inner"><div>
|
||||||
|
<ul style="font-size: var(--font-size-sm); color: var(--color-text-secondary); margin: 0; padding-left: 20px;">
|
||||||
|
<li>IT-sikkerhetsleder (Eli Bjerke)</li>
|
||||||
|
<li>Personvernombud (Tor Vagle)</li>
|
||||||
|
<li>Kommunaldirektør (sponsor)</li>
|
||||||
|
<li>Tjenesteeier for berørt fagsystem</li>
|
||||||
|
</ul>
|
||||||
|
</div></div></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="expansion" aria-expanded="false">
|
||||||
|
<button type="button" class="expansion__head" onclick="toggleExp(this)">
|
||||||
|
<span class="expansion__title">
|
||||||
|
<span class="expansion__title-main">Tekniske detaljer for sentralisert konfig</span>
|
||||||
|
</span>
|
||||||
|
<span class="expansion__chev" aria-hidden="true">▾</span>
|
||||||
|
</button>
|
||||||
|
<div class="expansion__body"><div class="expansion__body-inner"><div>
|
||||||
|
<p class="text-secondary" style="margin: 0;">Konfig versjoneres i <code>git.fromaitochitta.com/playground/configs</code>, valideres ved CI mot <code>config.schema.json</code>, og distribueres via signert artifact til target-tenants.</p>
|
||||||
|
</div></div></div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function toggleExp(btn) {
|
||||||
|
const sec = btn.parentElement;
|
||||||
|
const open = sec.getAttribute('aria-expanded') === 'true';
|
||||||
|
sec.setAttribute('aria-expanded', open ? 'false' : 'true');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
102
shared/playground-examples/components/fleet-overview.html
Normal file
102
shared/playground-examples/components/fleet-overview.html
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="nb">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>Fleet-Overview · Tier 3 supp</title>
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/tokens.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/base.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components-tier2.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components-tier3-supplement.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/fonts.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="app-header">
|
||||||
|
<a href="../index.html" class="app-header__brand"><span class="app-header__brand-mark">P</span><span>Playground</span></a>
|
||||||
|
<span class="app-header__breadcrumb">/ Komponenter / Fleet-Overview</span>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="container container--wide" style="padding: var(--space-8) 0;">
|
||||||
|
<div style="margin-bottom: var(--space-6);">
|
||||||
|
<span style="font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--color-scope-security); font-weight: var(--font-weight-semibold);">llm-security · /security dashboard</span>
|
||||||
|
<h1 style="margin: 4px 0 6px;">Fleet-Overview</h1>
|
||||||
|
<p class="text-secondary" style="max-width: 65ch;">Cross-project posture på én skjerm. 4 kolonner desktop → 2 → 1.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="fleet-toolbar">
|
||||||
|
<span class="fleet-toolbar__label">Sortér</span>
|
||||||
|
<button class="chip" aria-pressed="true" onclick="sortFleet('worst')">Verste først</button>
|
||||||
|
<button class="chip" aria-pressed="false" onclick="sortFleet('alpha')">Alfabetisk</button>
|
||||||
|
<button class="chip" aria-pressed="false" onclick="sortFleet('recent')">Sist skannet</button>
|
||||||
|
<span class="fleet-toolbar__label" style="margin-left: var(--space-4);">Filter</span>
|
||||||
|
<button class="chip" aria-pressed="false" onclick="filterFleet('failing')">Kun F + E</button>
|
||||||
|
<button class="chip" aria-pressed="false" onclick="filterFleet('changed')">Kun med endringer</button>
|
||||||
|
<span class="fleet-toolbar__spacer"></span>
|
||||||
|
<span class="fleet-toolbar__count" id="fleetCount">12 prosjekter</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="fleet-grid" id="fleetGrid"></div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const projects = [
|
||||||
|
{ name: "lier-kommune/copilot-onboarding", grade: "A", risk: 12, band: 1, worst: "info-disclosure", scanned: "2026-05-02 14:11", trend: "stable", changed: false },
|
||||||
|
{ name: "baerum-kommune/okr-portal", grade: "B", risk: 28, band: 1, worst: "missing-rate-limit", scanned: "2026-05-02 09:32", trend: "better", changed: true },
|
||||||
|
{ name: "direktorat/sak-arkiv-mcp", grade: "C", risk: 44, band: 2, worst: "weak-auth", scanned: "2026-05-01 18:04", trend: "worse", changed: true },
|
||||||
|
{ name: "direktorat/llm-saksbehandler", grade: "F", risk: 87, band: 4, worst: "TFA chain (BLOCK)", scanned: "2026-05-02 02:55", trend: "worse", changed: true },
|
||||||
|
{ name: "trondheim/dpia-helper", grade: "B", risk: 22, band: 1, worst: "log-leakage", scanned: "2026-04-30 11:18", trend: "stable", changed: false },
|
||||||
|
{ name: "skatteetaten/intern-kb", grade: "D", risk: 61, band: 3, worst: "prompt-injection", scanned: "2026-05-02 07:42", trend: "better", changed: true },
|
||||||
|
{ name: "nav/saksbehandler-co", grade: "C", risk: 39, band: 2, worst: "ssrf-risk", scanned: "2026-05-01 23:01", trend: "stable", changed: false },
|
||||||
|
{ name: "udi/ai-translator", grade: "E", risk: 73, band: 3, worst: "data-residency", scanned: "2026-05-02 12:30", trend: "worse", changed: true },
|
||||||
|
{ name: "dsb/krise-bot", grade: "A", risk: 8, band: 1, worst: "minor-typo", scanned: "2026-04-29 16:50", trend: "stable", changed: false },
|
||||||
|
{ name: "domstol/dom-summary", grade: "B", risk: 25, band: 1, worst: "context-leakage", scanned: "2026-05-01 10:14", trend: "better", changed: true },
|
||||||
|
{ name: "helsedir/symptomsjekk", grade: "F", risk: 91, band: 4, worst: "PHI exfiltration", scanned: "2026-05-02 04:18", trend: "worse", changed: true },
|
||||||
|
{ name: "kommune/innsyn-mcp", grade: "C", risk: 47, band: 2, worst: "broad-scope", scanned: "2026-05-01 19:55", trend: "stable", changed: false },
|
||||||
|
];
|
||||||
|
|
||||||
|
const trendArrow = { better: "↗ bedre", worse: "↘ verre", stable: "→ stabil" };
|
||||||
|
const grid = document.getElementById('fleetGrid');
|
||||||
|
let mode = 'worst', filter = 'none';
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
let list = projects.slice();
|
||||||
|
if (filter === 'failing') list = list.filter(p => p.grade === 'F' || p.grade === 'E');
|
||||||
|
if (filter === 'changed') list = list.filter(p => p.changed);
|
||||||
|
if (mode === 'worst') list.sort((a,b) => b.risk - a.risk);
|
||||||
|
if (mode === 'alpha') list.sort((a,b) => a.name.localeCompare(b.name));
|
||||||
|
if (mode === 'recent') list.sort((a,b) => b.scanned.localeCompare(a.scanned));
|
||||||
|
grid.innerHTML = list.map(p => `
|
||||||
|
<button class="fleet-tile" onclick="alert('Naviger til posture for ${p.name}')">
|
||||||
|
<div class="fleet-tile__row">
|
||||||
|
<span class="fleet-tile__name" title="${p.name}">${p.name}</span>
|
||||||
|
<span class="fleet-tile__grade" data-grade="${p.grade}">${p.grade}</span>
|
||||||
|
</div>
|
||||||
|
<div class="fleet-tile__meter"><div class="fleet-tile__meter-fill" data-band="${p.band}" style="width:${p.risk}%"></div></div>
|
||||||
|
<span class="fleet-tile__chip">${p.worst}</span>
|
||||||
|
<div class="fleet-tile__meta">
|
||||||
|
<span>${p.scanned}</span>
|
||||||
|
<span class="fleet-tile__trend--${p.trend}">${trendArrow[p.trend]}</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
`).join('');
|
||||||
|
document.getElementById('fleetCount').textContent = list.length + ' prosjekter';
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortFleet(m) {
|
||||||
|
mode = m;
|
||||||
|
document.querySelectorAll('.fleet-toolbar .chip').forEach(c => {
|
||||||
|
if (['Verste først', 'Alfabetisk', 'Sist skannet'].includes(c.textContent)) c.setAttribute('aria-pressed', 'false');
|
||||||
|
});
|
||||||
|
event.target.setAttribute('aria-pressed', 'true');
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
function filterFleet(f) {
|
||||||
|
filter = filter === f ? 'none' : f;
|
||||||
|
event.target.setAttribute('aria-pressed', filter === f ? 'true' : 'false');
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
render();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
81
shared/playground-examples/components/form-progress.html
Normal file
81
shared/playground-examples/components/form-progress.html
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="nb">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>FormProgress · Tier 3 supp</title>
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/tokens.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/base.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components-tier2.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components-tier3-supplement.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/fonts.css" />
|
||||||
|
<style>
|
||||||
|
.demo-layout { display: grid; grid-template-columns: 280px 1fr; gap: var(--space-6); align-items: start; }
|
||||||
|
@media (max-width: 820px) { .demo-layout { grid-template-columns: 1fr; } .form-progress { width: 100%; position: static; } }
|
||||||
|
.form-area { background: var(--color-surface); border: 1px solid var(--color-border-subtle); border-radius: var(--radius-md); padding: var(--space-5); min-height: 360px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="app-header">
|
||||||
|
<a href="../index.html" class="app-header__brand"><span class="app-header__brand-mark">P</span><span>Playground</span></a>
|
||||||
|
<span class="app-header__breadcrumb">/ Komponenter / FormProgress</span>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="container container--wide" style="padding: var(--space-8) 0;">
|
||||||
|
<div style="margin-bottom: var(--space-6);">
|
||||||
|
<span style="font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--color-scope-architect); font-weight: var(--font-weight-semibold);">ms-ai-architect onboarding · OKR /oppsett full · DPIA</span>
|
||||||
|
<h1 style="margin: 4px 0 6px;">FormProgress</h1>
|
||||||
|
<p class="text-secondary" style="max-width: 65ch;">Vertikal sidebar for store skjema. Autosave-status, ferdig-prosent per steg, estimert resterende tid. Ikke å forveksle med horisontal stepper.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-layout">
|
||||||
|
<aside class="form-progress">
|
||||||
|
<div class="form-progress__autosave">
|
||||||
|
<span class="form-progress__autosave-dot" aria-hidden="true"></span>
|
||||||
|
Lagret automatisk kl. 14:23
|
||||||
|
</div>
|
||||||
|
<div class="form-progress__steps">
|
||||||
|
<button type="button" class="fp-step" data-state="done">
|
||||||
|
<span class="fp-step__num">✓</span>
|
||||||
|
<span><span class="fp-step__name">Organisasjon & kontekst</span></span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="fp-step" data-state="done">
|
||||||
|
<span class="fp-step__num">✓</span>
|
||||||
|
<span><span class="fp-step__name">Brukstilfeller</span></span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="fp-step" data-state="in-progress">
|
||||||
|
<span class="fp-step__num">3</span>
|
||||||
|
<span>
|
||||||
|
<span class="fp-step__name">Datakilder & klassifisering</span>
|
||||||
|
<span class="fp-step__progress">
|
||||||
|
<span>62 %</span>
|
||||||
|
<span class="fp-step__bar"><span class="fp-step__bar-fill" style="width:62%"></span></span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="fp-step" data-state="todo" disabled title="Fullfør steg 3 først">
|
||||||
|
<span class="fp-step__num">4</span>
|
||||||
|
<span><span class="fp-step__name">Roller & ansvar</span></span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="fp-step" data-state="todo" disabled title="Fullfør steg 3 først">
|
||||||
|
<span class="fp-step__num">5</span>
|
||||||
|
<span><span class="fp-step__name">Risiko & tiltak</span></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-progress__remaining">
|
||||||
|
<span>Resterende</span>
|
||||||
|
<span>~ 9 min</span>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<section class="form-area">
|
||||||
|
<div class="text-tertiary" style="font-size: 11px; text-transform: uppercase; letter-spacing: .06em;">Steg 3 av 5</div>
|
||||||
|
<h2 style="margin: 4px 0 var(--space-4); font-size: var(--font-size-xl);">Datakilder & klassifisering</h2>
|
||||||
|
<p class="text-secondary" style="font-size: var(--font-size-sm);">Skjemaet hadde 12 felt — 7 utfylt, 5 igjen. Estimert ferdig om 5 minutter.</p>
|
||||||
|
<div style="margin-top: var(--space-4); padding: var(--space-4); background: var(--color-bg-soft); border-radius: var(--radius-md); font-size: var(--font-size-sm); color: var(--color-text-tertiary);">[Skjema-felt placeholder — i produksjon: input/select/textarea]</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
144
shared/playground-examples/components/kanban.html
Normal file
144
shared/playground-examples/components/kanban.html
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="nb">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>Kanban · Keep/Review/Remove · Tier 3 supp</title>
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/tokens.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/base.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components-tier2.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components-tier3-supplement.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/fonts.css" />
|
||||||
|
<style>
|
||||||
|
.modal-bg { position: fixed; inset: 0; background: var(--color-overlay); display: none; align-items: center; justify-content: center; z-index: 100; padding: var(--space-4); }
|
||||||
|
.modal-bg[data-open="true"] { display: flex; }
|
||||||
|
.modal { background: var(--color-surface); border-radius: var(--radius-lg); padding: var(--space-5); max-width: 540px; width: 100%; box-shadow: var(--shadow-lg); max-height: 90vh; overflow: auto; }
|
||||||
|
.checklist { list-style: none; padding: 0; margin: var(--space-3) 0; display: flex; flex-direction: column; gap: 6px; }
|
||||||
|
.checklist li { display: flex; gap: 8px; font-size: var(--font-size-sm); }
|
||||||
|
.checklist .ok { color: var(--color-state-success); }
|
||||||
|
.checklist .no { color: var(--color-severity-critical); }
|
||||||
|
.checklist .un { color: var(--color-text-tertiary); }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="app-header">
|
||||||
|
<a href="../index.html" class="app-header__brand"><span class="app-header__brand-mark">P</span><span>Playground</span></a>
|
||||||
|
<span class="app-header__breadcrumb">/ Komponenter / Kanban: Keep/Review/Remove</span>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="container container--wide" style="padding: var(--space-8) 0;">
|
||||||
|
<div style="margin-bottom: var(--space-6);">
|
||||||
|
<span style="font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--color-scope-security); font-weight: var(--font-weight-semibold);">llm-security · /security plugin-audit</span>
|
||||||
|
<h1 style="margin: 4px 0 6px;">Kanban: Behold / Vurder / Fjern</h1>
|
||||||
|
<p class="text-secondary" style="max-width: 65ch;">Klassifisér installerte plugins/MCP-servere etter trust. Klikk-flytt mellom kolonner.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="kanban-board" id="board"></div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<div class="modal-bg" id="modal" onclick="if(event.target===this) closeModal()">
|
||||||
|
<div class="modal" id="modalBody"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const board = {
|
||||||
|
keep: { title: "Behold", items: [
|
||||||
|
{ name: "anthropic/claude-code-mcp", verdict: "trusted", meta: "Sist auditert 2026-04-15" },
|
||||||
|
{ name: "github/copilot-chat", verdict: "trusted", meta: "Sist auditert 2026-04-12" },
|
||||||
|
{ name: "lier-kommune/internal-mcp", verdict: "trusted", meta: "Sist auditert 2026-04-30" },
|
||||||
|
{ name: "digdir/auth-mcp", verdict: "trusted", meta: "Sist auditert 2026-05-01" },
|
||||||
|
]},
|
||||||
|
review: { title: "Vurder", items: [
|
||||||
|
{ name: "thirdparty/web-search", verdict: "unknown", meta: "Audit due 2026-06-01" },
|
||||||
|
{ name: "community/markdown-tools", verdict: "unknown", meta: "Audit due 2026-05-20" },
|
||||||
|
]},
|
||||||
|
remove: { title: "Fjern", items: [
|
||||||
|
{ name: "evil-project-health@1.2.3", verdict: "BLOCK", reason: "85 funn (24 critical), Unicode-steganografi, exfil-flow" },
|
||||||
|
]},
|
||||||
|
};
|
||||||
|
|
||||||
|
const checklists = {
|
||||||
|
trusted: [
|
||||||
|
{ok:'ok', text:'Source repo verifisert (signed commits)'},
|
||||||
|
{ok:'ok', text:'Maintainer kjent og aktiv'},
|
||||||
|
{ok:'ok', text:'Ingen kritiske funn siste audit'},
|
||||||
|
{ok:'ok', text:'Capabilities dokumentert og minst-mulig'},
|
||||||
|
{ok:'ok', text:'Ingen exfil-flow detektert'},
|
||||||
|
{ok:'ok', text:'Lisens kompatibel med offentlig bruk'},
|
||||||
|
{ok:'ok', text:'Versjon pinnet i lockfile'},
|
||||||
|
{ok:'ok', text:'Endringslogg konsistent med kode'},
|
||||||
|
{ok:'ok', text:'Trust-skår > 80'},
|
||||||
|
],
|
||||||
|
unknown: [
|
||||||
|
{ok:'un', text:'Source repo verifisert'},
|
||||||
|
{ok:'ok', text:'Maintainer kjent'},
|
||||||
|
{ok:'un', text:'Audit ikke utført siste 90 d'},
|
||||||
|
{ok:'ok', text:'Capabilities dokumentert'},
|
||||||
|
{ok:'un', text:'Exfil-analyse ikke kjørt'},
|
||||||
|
{ok:'ok', text:'Lisens OK'},
|
||||||
|
{ok:'ok', text:'Versjon pinnet'},
|
||||||
|
{ok:'un', text:'Endringslogg ufullstendig'},
|
||||||
|
{ok:'un', text:'Trust-skår ikke beregnet'},
|
||||||
|
],
|
||||||
|
BLOCK: [
|
||||||
|
{ok:'no', text:'Unicode-tag-injeksjon i README (steganografi)'},
|
||||||
|
{ok:'no', text:'Exfil til webhook.site/abc123 detektert'},
|
||||||
|
{ok:'no', text:'24 kritiske TFA-funn'},
|
||||||
|
{ok:'no', text:'Maintainer ikke verifiserbar'},
|
||||||
|
{ok:'no', text:'Source-repo nylig opprettet (typosquat?)'},
|
||||||
|
{ok:'no', text:'Bash + filsystem + nett uten begrensning'},
|
||||||
|
{ok:'no', text:'Lisens uklar'},
|
||||||
|
{ok:'no', text:'Versjon ikke pinnet'},
|
||||||
|
{ok:'no', text:'Trust-skår: 4'},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
document.getElementById('board').innerHTML = ['keep','review','remove'].map(b => `
|
||||||
|
<div class="kanban-col" data-bucket="${b}">
|
||||||
|
<div class="kanban-col__head">
|
||||||
|
<span class="kanban-col__title">${board[b].title}</span>
|
||||||
|
<span class="kanban-col__count">${board[b].items.length}</span>
|
||||||
|
</div>
|
||||||
|
${board[b].items.length ? board[b].items.map((it, i) => `
|
||||||
|
<div class="kanban-card" data-verdict="${it.verdict}" onclick="openModal('${b}', ${i})">
|
||||||
|
<span class="kanban-card__name">${it.name}</span>
|
||||||
|
${it.meta ? `<span class="kanban-card__meta">${it.meta}</span>` : ''}
|
||||||
|
${it.reason ? `<span class="kanban-card__reason">${it.reason}</span>` : ''}
|
||||||
|
<div class="kanban-actions" onclick="event.stopPropagation()">
|
||||||
|
${b !== 'keep' ? `<button onclick="move('${b}','keep',${i})">→ Behold</button>` : ''}
|
||||||
|
${b !== 'review' ? `<button onclick="move('${b}','review',${i})">→ Vurder</button>` : ''}
|
||||||
|
${b !== 'remove' ? `<button onclick="move('${b}','remove',${i})">→ Fjern</button>` : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('') : `<div class="kanban-col__empty">Ingen i denne bøtten ennå.<br><button class="btn btn--secondary btn--sm" style="margin-top:8px;">+ Legg til</button></div>`}
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function move(from, to, i) {
|
||||||
|
const item = board[from].items.splice(i, 1)[0];
|
||||||
|
board[to].items.push(item);
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
function openModal(b, i) {
|
||||||
|
const it = board[b].items[i];
|
||||||
|
const cl = checklists[it.verdict] || checklists.unknown;
|
||||||
|
const sym = { ok: '✓', no: '✗', un: '?' };
|
||||||
|
document.getElementById('modalBody').innerHTML = `
|
||||||
|
<div style="display:flex; justify-content:space-between; align-items:center; gap:12px;">
|
||||||
|
<h3 style="margin:0;">${it.name}</h3>
|
||||||
|
<button class="btn btn--ghost btn--sm" onclick="closeModal()">Lukk</button>
|
||||||
|
</div>
|
||||||
|
<p class="text-secondary" style="font-size:var(--font-size-sm); margin:6px 0 0;">Trust-vurdering · ${it.verdict.toUpperCase()}</p>
|
||||||
|
<ul class="checklist">${cl.map(c => `<li><span class="${c.ok}">${sym[c.ok]}</span><span>${c.text}</span></li>`).join('')}</ul>
|
||||||
|
`;
|
||||||
|
document.getElementById('modal').setAttribute('data-open', 'true');
|
||||||
|
}
|
||||||
|
function closeModal() { document.getElementById('modal').setAttribute('data-open', 'false'); }
|
||||||
|
render();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
97
shared/playground-examples/components/maturity-ladder.html
Normal file
97
shared/playground-examples/components/maturity-ladder.html
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="nb">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>Maturity-Ladder · Tier 3 supp</title>
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/tokens.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/base.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components-tier2.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components-tier3-supplement.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/fonts.css" />
|
||||||
|
<style>
|
||||||
|
.demo-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-5); }
|
||||||
|
@media (max-width: 980px) { .demo-row { grid-template-columns: 1fr; } }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="app-header">
|
||||||
|
<a href="../index.html" class="app-header__brand"><span class="app-header__brand-mark">P</span><span>Playground</span></a>
|
||||||
|
<span class="app-header__breadcrumb">/ Komponenter / Maturity-Ladder</span>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="container container--wide" style="padding: var(--space-8) 0;">
|
||||||
|
<div style="margin-bottom: var(--space-6);">
|
||||||
|
<span style="font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--color-scope-okr); font-weight: var(--font-weight-semibold);">OKR · config-audit · security</span>
|
||||||
|
<h1 style="margin: 4px 0 6px;">Maturity-Ladder</h1>
|
||||||
|
<p class="text-secondary" style="max-width: 65ch;">Vertikal stepper med rik beskrivelse. Current step har progress-ring (her 65 %).</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-row">
|
||||||
|
<section>
|
||||||
|
<h2 style="font-size: var(--font-size-lg); margin: 0 0 var(--space-3);">OKR-modenhet (4 nivåer)</h2>
|
||||||
|
<div class="mat-ladder">
|
||||||
|
<div class="mat-step" data-state="completed">
|
||||||
|
<div class="mat-step__icon" aria-hidden="true">✓</div>
|
||||||
|
<div>
|
||||||
|
<div class="mat-step__name">Utforsker <span class="mat-step__pill mat-step__pill--complete">Fullført</span></div>
|
||||||
|
<div class="mat-step__desc">Eksperimenterer med OKR i 1–2 team. Ingen formell rytme.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mat-step" data-state="current">
|
||||||
|
<div class="mat-step__icon" aria-hidden="true">
|
||||||
|
2
|
||||||
|
<span class="mat-step__ring" aria-hidden="true">
|
||||||
|
<svg viewBox="0 0 52 52"><circle class="ring-bg" cx="26" cy="26" r="24"></circle><circle class="ring-fill" cx="26" cy="26" r="24" stroke-dasharray="150.8" stroke-dashoffset="52.8"></circle></svg>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="mat-step__name">Pilot <span class="mat-step__pill mat-step__pill--current">Nå</span></div>
|
||||||
|
<div class="mat-step__desc">OKR i én avdeling. Kvartalsrytme etablert. Ledelse engasjert.</div>
|
||||||
|
<div class="mat-step__progress"><span>65 %</span><span class="mat-step__progress-bar"><span class="mat-step__progress-fill" style="width:65%"></span></span><span>til Skalering</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mat-step" data-state="future">
|
||||||
|
<div class="mat-step__icon" aria-hidden="true">3</div>
|
||||||
|
<div>
|
||||||
|
<div class="mat-step__name">Skalering</div>
|
||||||
|
<div class="mat-step__desc">OKR rullet ut til hele organisasjonen. Cross-team alignment.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mat-step" data-state="future">
|
||||||
|
<div class="mat-step__icon" aria-hidden="true">4</div>
|
||||||
|
<div>
|
||||||
|
<div class="mat-step__name">Moden</div>
|
||||||
|
<div class="mat-step__desc">OKR er drift. Strategisk forankring fra Stortingsmelding til team-OKR.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 style="font-size: var(--font-size-lg); margin: 0 0 var(--space-3);">Config-modenhet (5 nivåer)</h2>
|
||||||
|
<div class="mat-ladder">
|
||||||
|
<div class="mat-step" data-state="completed"><div class="mat-step__icon" aria-hidden="true">✓</div>
|
||||||
|
<div><div class="mat-step__name">Bare <span class="mat-step__pill mat-step__pill--complete">Fullført</span></div>
|
||||||
|
<div class="mat-step__desc">Defaults overalt. Ingen sentralisert konfig.</div></div></div>
|
||||||
|
<div class="mat-step" data-state="completed"><div class="mat-step__icon" aria-hidden="true">✓</div>
|
||||||
|
<div><div class="mat-step__name">Configured <span class="mat-step__pill mat-step__pill--complete">Fullført</span></div>
|
||||||
|
<div class="mat-step__desc">Eksplisitte verdier per miljø. Ingen drift-deteksjon.</div></div></div>
|
||||||
|
<div class="mat-step" data-state="current"><div class="mat-step__icon" aria-hidden="true">3
|
||||||
|
<span class="mat-step__ring" aria-hidden="true"><svg viewBox="0 0 52 52"><circle class="ring-bg" cx="26" cy="26" r="24"></circle><circle class="ring-fill" cx="26" cy="26" r="24" stroke-dasharray="150.8" stroke-dashoffset="105.6"></circle></svg></span></div>
|
||||||
|
<div><div class="mat-step__name">Structured <span class="mat-step__pill mat-step__pill--current">Nå</span></div>
|
||||||
|
<div class="mat-step__desc">Skjema-validert konfig. Versjonert i Git. Endringssporbarhet.</div>
|
||||||
|
<div class="mat-step__progress"><span>30 %</span><span class="mat-step__progress-bar"><span class="mat-step__progress-fill" style="width:30%"></span></span><span>til Automated</span></div></div></div>
|
||||||
|
<div class="mat-step" data-state="future"><div class="mat-step__icon" aria-hidden="true">4</div>
|
||||||
|
<div><div class="mat-step__name">Automated</div>
|
||||||
|
<div class="mat-step__desc">CI-validering. Auto-rollback ved feil. Drift-detektor.</div></div></div>
|
||||||
|
<div class="mat-step" data-state="future"><div class="mat-step__icon" aria-hidden="true">5</div>
|
||||||
|
<div><div class="mat-step__name">Governed</div>
|
||||||
|
<div class="mat-step__desc">Policy-as-code. Audit-trail. Approval-workflows for prod.</div></div></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="nb">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>Persistent-Antipattern Badge · Tier 3 supp</title>
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/tokens.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/base.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components-tier2.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components-tier3-supplement.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/fonts.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="app-header">
|
||||||
|
<a href="../index.html" class="app-header__brand"><span class="app-header__brand-mark">P</span><span>Playground</span></a>
|
||||||
|
<span class="app-header__breadcrumb">/ Komponenter / Persistent-Antipattern Badge</span>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="container container--wide" style="padding: var(--space-8) 0;">
|
||||||
|
<div style="margin-bottom: var(--space-6);">
|
||||||
|
<span style="font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--color-scope-okr); font-weight: var(--font-weight-semibold);">OKR · /okr:analyse cross-cycle</span>
|
||||||
|
<h1 style="margin: 4px 0 6px;">Persistent-Antipattern Badge</h1>
|
||||||
|
<p class="text-secondary" style="max-width: 65ch;">Markerer antipatterns som har dukket opp i 2+ påfølgende sykluser. Pulserende prikk skiller seg fra one-shot.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 style="font-size: var(--font-size-lg); margin: 0 0 var(--space-3);">I bruk i en finding-tabell</h2>
|
||||||
|
<table style="width:100%; border-collapse: collapse; background: var(--color-surface); border: 1px solid var(--color-border-subtle); border-radius: var(--radius-md); overflow:hidden;">
|
||||||
|
<thead>
|
||||||
|
<tr style="background: var(--color-bg-soft);">
|
||||||
|
<th style="text-align:left; padding: 10px 14px; font-size: 12px; text-transform: uppercase; letter-spacing: .06em; color: var(--color-text-tertiary); border-bottom: 1px solid var(--color-border-subtle);">Antipattern</th>
|
||||||
|
<th style="text-align:left; padding: 10px 14px; font-size: 12px; text-transform: uppercase; letter-spacing: .06em; color: var(--color-text-tertiary); border-bottom: 1px solid var(--color-border-subtle);">Funnet i</th>
|
||||||
|
<th style="text-align:left; padding: 10px 14px; font-size: 12px; text-transform: uppercase; letter-spacing: .06em; color: var(--color-text-tertiary); border-bottom: 1px solid var(--color-border-subtle);">Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 12px 14px;">Aktivitetsfokus i KR</td>
|
||||||
|
<td style="padding: 12px 14px; font-family: var(--font-family-mono); font-size: 12px; color: var(--color-text-secondary);">T1-25 · T2-25 · T3-25 · T1-26 · T2-26</td>
|
||||||
|
<td style="padding: 12px 14px;">
|
||||||
|
<button type="button" class="pap-badge" onclick="togglePap(0)" aria-expanded="false" aria-controls="papDetail0">
|
||||||
|
Vedvarende <span class="pap-badge__count">5 sykluser</span>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 12px 14px;">Sandbagging av target-verdier</td>
|
||||||
|
<td style="padding: 12px 14px; font-family: var(--font-family-mono); font-size: 12px; color: var(--color-text-secondary);">T2-25 · T3-25 · T1-26</td>
|
||||||
|
<td style="padding: 12px 14px;">
|
||||||
|
<button type="button" class="pap-badge" onclick="togglePap(1)" aria-expanded="false" aria-controls="papDetail1">
|
||||||
|
Vedvarende <span class="pap-badge__count">3 sykluser</span>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 12px 14px;">For mange KR per Objective</td>
|
||||||
|
<td style="padding: 12px 14px; font-family: var(--font-family-mono); font-size: 12px; color: var(--color-text-secondary);">T2-26</td>
|
||||||
|
<td style="padding: 12px 14px;">
|
||||||
|
<span class="pap-badge pap-badge--oneshot" title="Kun én syklus så langt">Én syklus</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<section class="pap-detail" id="papDetail0" style="margin-top: var(--space-3);">
|
||||||
|
<h4>Aktivitetsfokus i KR</h4>
|
||||||
|
<p class="text-secondary" style="margin: 0; font-size: var(--font-size-sm);">KR-formuleringer beskriver aktiviteter ("Innføre nytt system", "Pilotere X") i stedet for målbare utfall. Vedvarende mønster på tvers av sykluser indikerer at OKR-coaching ikke har festet seg.</p>
|
||||||
|
<div class="pap-detail__cycles">
|
||||||
|
<span class="pap-detail__cycle">T1-2025 · 4 forekomster</span>
|
||||||
|
<span class="pap-detail__cycle">T2-2025 · 3 forekomster</span>
|
||||||
|
<span class="pap-detail__cycle">T3-2025 · 5 forekomster</span>
|
||||||
|
<span class="pap-detail__cycle">T1-2026 · 6 forekomster</span>
|
||||||
|
<span class="pap-detail__cycle">T2-2026 · 4 forekomster</span>
|
||||||
|
</div>
|
||||||
|
<div class="pap-detail__rec"><strong>Anbefaling:</strong> Vurder OKR-coaching eller retrospective-fokus på outcome vs activity. Spør "Hva endrer seg for innbyggeren hvis dette KR-et oppfylles?"</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="pap-detail" id="papDetail1" style="margin-top: var(--space-3);">
|
||||||
|
<h4>Sandbagging av target-verdier</h4>
|
||||||
|
<p class="text-secondary" style="margin: 0; font-size: var(--font-size-sm);">Targets satt så lavt at de oppnås uten reell innsats. Score > 0,9 to sykluser på rad uten endring i baseline.</p>
|
||||||
|
<div class="pap-detail__cycles">
|
||||||
|
<span class="pap-detail__cycle">T2-2025</span>
|
||||||
|
<span class="pap-detail__cycle">T3-2025</span>
|
||||||
|
<span class="pap-detail__cycle">T1-2026</span>
|
||||||
|
</div>
|
||||||
|
<div class="pap-detail__rec"><strong>Anbefaling:</strong> Innfør stretch-target som komplement, eller vurder aspirational vs committed-skille (se OKR-mode).</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function togglePap(i) {
|
||||||
|
const d = document.getElementById('papDetail' + i);
|
||||||
|
const open = d.getAttribute('data-open') === 'true';
|
||||||
|
d.setAttribute('data-open', open ? 'false' : 'true');
|
||||||
|
event.currentTarget.setAttribute('aria-expanded', open ? 'false' : 'true');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
59
shared/playground-examples/components/read-more.html
Normal file
59
shared/playground-examples/components/read-more.html
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="nb">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>ReadMore · Tier 3 supp</title>
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/tokens.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/base.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components-tier2.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components-tier3-supplement.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/fonts.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="app-header">
|
||||||
|
<a href="../index.html" class="app-header__brand"><span class="app-header__brand-mark">P</span><span>Playground</span></a>
|
||||||
|
<span class="app-header__breadcrumb">/ Komponenter / ReadMore</span>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="container container--narrow" style="padding: var(--space-8) 0;">
|
||||||
|
<div style="margin-bottom: var(--space-6);">
|
||||||
|
<span style="font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--color-text-tertiary); font-weight: var(--font-weight-semibold);">Aksel · inline disclosure</span>
|
||||||
|
<h1 style="margin: 4px 0 6px;">ReadMore</h1>
|
||||||
|
<p class="text-secondary">Inline-trigger for å skjule lange forklaringer mid-tekst.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<article style="font-size: var(--font-size-md); line-height: var(--line-height-normal); color: var(--color-text-primary);">
|
||||||
|
<p>Sensitivity Labels brukes til å klassifisere dokumenter etter konfidensialitetsnivå.
|
||||||
|
<span class="read-more" aria-expanded="false">
|
||||||
|
<button type="button" class="read-more__trigger" onclick="toggleRm(this)">Les mer om hvordan dette håndheves <span class="read-more__chev">▾</span></button>
|
||||||
|
<span class="read-more__body">
|
||||||
|
Når et dokument merkes "Konfidensielt — intern", vil M365 Copilot ikke oppsummere innholdet for brukere uten samme tilgangsnivå.
|
||||||
|
DLP-policyen sjekker label-attributter ved hver prompt-respons og avbryter generering hvis cross-label data flyter sammen.
|
||||||
|
For Lier kommune betyr dette at saksbehandlere på Helse-avdelingen ikke utilsiktet kan dra inn HR-relatert informasjon i samme svar.
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p style="margin-top: var(--space-4);">Schrems II-vurdering kreves for cross-tenant data-flyt.
|
||||||
|
<span class="read-more" aria-expanded="false">
|
||||||
|
<button type="button" class="read-more__trigger" onclick="toggleRm(this)">Hva betyr Schrems II i praksis? <span class="read-more__chev">▾</span></button>
|
||||||
|
<span class="read-more__body">
|
||||||
|
EU-domstolen kjente Privacy Shield ugyldig i 2020. Overføring av personopplysninger til USA krever supplerende tiltak (TIAs, krypteringsnøkler i EU).
|
||||||
|
For Microsoft 365-tenants betyr dette at EU Data Boundary må være aktivert, og at audit-logger må bekrefte at prompt-data ikke forlater EØS.
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function toggleRm(btn) {
|
||||||
|
const sec = btn.parentElement;
|
||||||
|
const open = sec.getAttribute('aria-expanded') === 'true';
|
||||||
|
sec.setAttribute('aria-expanded', open ? 'false' : 'true');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
117
shared/playground-examples/components/sankey-toxic-flow.html
Normal file
117
shared/playground-examples/components/sankey-toxic-flow.html
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="nb">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>Toxic-Flow Chain · Tier 3 supp</title>
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/tokens.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/base.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components-tier2.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components-tier3-supplement.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/fonts.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="app-header">
|
||||||
|
<a href="../index.html" class="app-header__brand"><span class="app-header__brand-mark">P</span><span>Playground</span></a>
|
||||||
|
<span class="app-header__breadcrumb">/ Komponenter / Toxic-Flow Chain (TFA)</span>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="container container--wide" style="padding: var(--space-8) 0;">
|
||||||
|
<div style="margin-bottom: var(--space-6);">
|
||||||
|
<span style="font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--color-scope-security); font-weight: var(--font-weight-semibold);">llm-security · TFA</span>
|
||||||
|
<h1 style="margin: 4px 0 6px;">Toxic-Flow Chain</h1>
|
||||||
|
<p class="text-secondary" style="max-width: 65ch;">Trifecta Flow Analysis: Input → Access → Exfil. Hver leg viser type, kilde og mitigation-status. Tykkere arrows = høyere severity. Grønt skjold = mitigation som bryter kjeden.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 style="font-size: var(--font-size-lg); margin: 0 0 var(--space-3);">TFA-2026-118-001 — BLOCK</h2>
|
||||||
|
<div class="tfa-flow" id="flow1">
|
||||||
|
<span class="tfa-flow__verdict" data-verdict="BLOCK">BLOCK</span>
|
||||||
|
|
||||||
|
<button type="button" class="tfa-leg" data-severity="high" onclick="alert('Drill-down: Input leg — skill markdown fil')">
|
||||||
|
<span class="tfa-leg__label">Input</span>
|
||||||
|
<span class="tfa-leg__name">Untrusted data</span>
|
||||||
|
<span class="tfa-leg__source">skill markdown-fil</span>
|
||||||
|
<span class="tfa-leg__status" data-mit="unmitigated">● Ikke mitigert</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<span class="tfa-arrow" data-severity="critical" aria-hidden="true"><span class="tfa-arrow__line"></span></span>
|
||||||
|
|
||||||
|
<button type="button" class="tfa-leg" data-severity="critical" onclick="alert('Drill-down: Access leg — Bash + filsystem')">
|
||||||
|
<span class="tfa-leg__label">Access</span>
|
||||||
|
<span class="tfa-leg__name">Sensitive capability</span>
|
||||||
|
<span class="tfa-leg__source">Bash · filsystem-tilgang</span>
|
||||||
|
<span class="tfa-leg__status" data-mit="partially_mitigated">◐ Delvis mitigert</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<span class="tfa-arrow" data-severity="critical" aria-hidden="true"><span class="tfa-arrow__line"></span></span>
|
||||||
|
|
||||||
|
<button type="button" class="tfa-leg" data-severity="critical" onclick="alert('Drill-down: Exfil leg — webhook.site')">
|
||||||
|
<span class="tfa-leg__label">Exfil</span>
|
||||||
|
<span class="tfa-leg__name">External endpoint</span>
|
||||||
|
<span class="tfa-leg__source">webhook.site/abc123</span>
|
||||||
|
<span class="tfa-leg__status" data-mit="unmitigated">● Ikke mitigert</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 style="font-size: var(--font-size-lg); margin: var(--space-8) 0 var(--space-3);">TFA-2026-118-002 — WARN (mitigation present)</h2>
|
||||||
|
<div class="tfa-flow">
|
||||||
|
<span class="tfa-flow__verdict" data-verdict="WARN">WARN</span>
|
||||||
|
<button type="button" class="tfa-leg" data-severity="medium">
|
||||||
|
<span class="tfa-leg__label">Input</span>
|
||||||
|
<span class="tfa-leg__name">User prompt</span>
|
||||||
|
<span class="tfa-leg__source">chat input</span>
|
||||||
|
<span class="tfa-leg__status" data-mit="mitigated">✓ Sanert</span>
|
||||||
|
</button>
|
||||||
|
<span class="tfa-arrow tfa-arrow--mitigated" data-severity="medium" aria-hidden="true">
|
||||||
|
<span class="tfa-arrow__line"></span>
|
||||||
|
<span class="tfa-arrow__shield" title="Sanitering bryter kjeden">🛡</span>
|
||||||
|
</span>
|
||||||
|
<button type="button" class="tfa-leg" data-severity="high">
|
||||||
|
<span class="tfa-leg__label">Access</span>
|
||||||
|
<span class="tfa-leg__name">Read-only DB query</span>
|
||||||
|
<span class="tfa-leg__source">SELECT-only role</span>
|
||||||
|
<span class="tfa-leg__status" data-mit="partially_mitigated">◐ RBAC aktiv</span>
|
||||||
|
</button>
|
||||||
|
<span class="tfa-arrow" data-severity="high" aria-hidden="true"><span class="tfa-arrow__line"></span></span>
|
||||||
|
<button type="button" class="tfa-leg" data-severity="high">
|
||||||
|
<span class="tfa-leg__label">Exfil</span>
|
||||||
|
<span class="tfa-leg__name">Logged endpoint</span>
|
||||||
|
<span class="tfa-leg__source">api.bærum.no/log</span>
|
||||||
|
<span class="tfa-leg__status" data-mit="mitigated">✓ Audit-sporet</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 style="font-size: var(--font-size-lg); margin: var(--space-8) 0 var(--space-3);">TFA-2026-118-003 — ALLOW</h2>
|
||||||
|
<div class="tfa-flow">
|
||||||
|
<span class="tfa-flow__verdict" data-verdict="ALLOW">ALLOW</span>
|
||||||
|
<button type="button" class="tfa-leg" data-severity="medium">
|
||||||
|
<span class="tfa-leg__label">Input</span>
|
||||||
|
<span class="tfa-leg__name">Konfig-fil</span>
|
||||||
|
<span class="tfa-leg__source">checked-in config.toml</span>
|
||||||
|
<span class="tfa-leg__status" data-mit="mitigated">✓ Signert</span>
|
||||||
|
</button>
|
||||||
|
<span class="tfa-arrow tfa-arrow--mitigated" data-severity="medium" aria-hidden="true">
|
||||||
|
<span class="tfa-arrow__line"></span>
|
||||||
|
<span class="tfa-arrow__shield">🛡</span>
|
||||||
|
</span>
|
||||||
|
<button type="button" class="tfa-leg" data-severity="medium">
|
||||||
|
<span class="tfa-leg__label">Access</span>
|
||||||
|
<span class="tfa-leg__name">Local file read</span>
|
||||||
|
<span class="tfa-leg__source">repo-scope</span>
|
||||||
|
<span class="tfa-leg__status" data-mit="mitigated">✓ Sandboxed</span>
|
||||||
|
</button>
|
||||||
|
<span class="tfa-arrow tfa-arrow--mitigated" data-severity="medium" aria-hidden="true">
|
||||||
|
<span class="tfa-arrow__line"></span>
|
||||||
|
<span class="tfa-arrow__shield">🛡</span>
|
||||||
|
</span>
|
||||||
|
<button type="button" class="tfa-leg" data-severity="medium">
|
||||||
|
<span class="tfa-leg__label">Exfil</span>
|
||||||
|
<span class="tfa-leg__name">Stdout</span>
|
||||||
|
<span class="tfa-leg__source">terminal</span>
|
||||||
|
<span class="tfa-leg__status" data-mit="mitigated">✓ Lokalt</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="nb">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>Suppressed-Signals · Tier 3 supp</title>
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/tokens.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/base.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components-tier2.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/components-tier3-supplement.css" />
|
||||||
|
<link rel="stylesheet" href="../../playground-design-system/fonts.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="app-header">
|
||||||
|
<a href="../index.html" class="app-header__brand"><span class="app-header__brand-mark">P</span><span>Playground</span></a>
|
||||||
|
<span class="app-header__breadcrumb">/ Komponenter / Suppressed-Signals Panel</span>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="container container--wide" style="padding: var(--space-8) 0;">
|
||||||
|
<div style="margin-bottom: var(--space-6);">
|
||||||
|
<span style="font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--color-scope-security); font-weight: var(--font-weight-semibold);">llm-security · ultraplan-local</span>
|
||||||
|
<h1 style="margin: 4px 0 6px;">Suppressed-Signals Panel</h1>
|
||||||
|
<p class="text-secondary" style="max-width: 65ch;">Synlig — men sammenklappet — liste over funn som ble nedgradert eller fjernet, og hvorfor. Aldri skjult i en meny: tillit krever transparens.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="text-tertiary" style="font-size: var(--font-size-sm); margin: 0 0 8px;">Etter funn-listen, før footer:</p>
|
||||||
|
|
||||||
|
<section class="suppressed" aria-expanded="false" id="sup">
|
||||||
|
<button type="button" class="suppressed__head" onclick="toggleSup()">
|
||||||
|
<span class="suppressed__chev" aria-hidden="true">▸</span>
|
||||||
|
<span class="suppressed__label">12 signaler ble dempet eller fjernet i denne kjøringen</span>
|
||||||
|
<span class="suppressed__count">12</span>
|
||||||
|
</button>
|
||||||
|
<div class="suppressed__body">
|
||||||
|
<div class="suppressed-group">
|
||||||
|
<div class="suppressed-group__head">
|
||||||
|
<span class="suppressed-group__reason">false_positive_glsl_keywords</span>
|
||||||
|
<span class="suppressed-group__count">8 funn</span>
|
||||||
|
</div>
|
||||||
|
<p class="suppressed-group__desc">Entropy-scanner flagget GLSL shader-keywords som secrets. Nedgradert til info etter regel-treff på <code>shaders/*.glsl</code>.</p>
|
||||||
|
<div class="suppressed-group__examples">
|
||||||
|
<span class="suppressed-group__example">uniform vec3</span>
|
||||||
|
<span class="suppressed-group__example">varying mat4</span>
|
||||||
|
<span class="suppressed-group__example">gl_FragCoord</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="suppressed-group">
|
||||||
|
<div class="suppressed-group__head">
|
||||||
|
<span class="suppressed-group__reason">test_fixture_intended</span>
|
||||||
|
<span class="suppressed-group__count">3 funn</span>
|
||||||
|
</div>
|
||||||
|
<p class="suppressed-group__desc">Fixture-filer i <code>tests/fixtures/</code> med bevisst dummy-data (bestått manuell review 2026-04-22).</p>
|
||||||
|
</div>
|
||||||
|
<div class="suppressed-group">
|
||||||
|
<div class="suppressed-group__head">
|
||||||
|
<span class="suppressed-group__reason">judge_succinctness_filter</span>
|
||||||
|
<span class="suppressed-group__count">1 funn</span>
|
||||||
|
</div>
|
||||||
|
<p class="suppressed-group__desc">Findings under 4 ord — ikke handlebar. Filtrert ut av Judge.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function toggleSup() {
|
||||||
|
const s = document.getElementById('sup');
|
||||||
|
const open = s.getAttribute('aria-expanded') === 'true';
|
||||||
|
s.setAttribute('aria-expanded', open ? 'false' : 'true');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -8,9 +8,7 @@
|
||||||
<link rel="stylesheet" href="../playground-design-system/base.css" />
|
<link rel="stylesheet" href="../playground-design-system/base.css" />
|
||||||
<link rel="stylesheet" href="../playground-design-system/components.css" />
|
<link rel="stylesheet" href="../playground-design-system/components.css" />
|
||||||
<link rel="stylesheet" href="../playground-design-system/components-tier2.css" />
|
<link rel="stylesheet" href="../playground-design-system/components-tier2.css" />
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="stylesheet" href="../playground-design-system/fonts.css" />
|
||||||
<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&family=Source+Serif+4:wght@400;600&display=swap" rel="stylesheet">
|
|
||||||
<style>
|
<style>
|
||||||
.hero { padding: var(--space-16) 0 var(--space-12); border-bottom: 1px solid var(--color-border-subtle); background: var(--color-bg-soft); }
|
.hero { padding: var(--space-16) 0 var(--space-12); border-bottom: 1px solid var(--color-border-subtle); background: var(--color-bg-soft); }
|
||||||
.hero__eyebrow { font-size: var(--font-size-xs); text-transform: uppercase; letter-spacing: 0.12em; color: var(--color-text-tertiary); font-weight: var(--font-weight-semibold); margin-bottom: var(--space-3); }
|
.hero__eyebrow { font-size: var(--font-size-xs); text-transform: uppercase; letter-spacing: 0.12em; color: var(--color-text-tertiary); font-weight: var(--font-weight-semibold); margin-bottom: var(--space-3); }
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,7 @@
|
||||||
<link rel="stylesheet" href="../playground-design-system/base.css" />
|
<link rel="stylesheet" href="../playground-design-system/base.css" />
|
||||||
<link rel="stylesheet" href="../playground-design-system/components.css" />
|
<link rel="stylesheet" href="../playground-design-system/components.css" />
|
||||||
<link rel="stylesheet" href="../playground-design-system/components-tier2.css" />
|
<link rel="stylesheet" href="../playground-design-system/components-tier2.css" />
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="stylesheet" href="../playground-design-system/fonts.css" />
|
||||||
<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&family=Source+Serif+4:wght@400;600&display=swap" rel="stylesheet">
|
|
||||||
<style>
|
<style>
|
||||||
.layout { display: grid; grid-template-rows: auto 1fr; min-height: 100vh; }
|
.layout { display: grid; grid-template-rows: auto 1fr; min-height: 100vh; }
|
||||||
.page { padding: var(--space-8) 0 var(--space-16); }
|
.page { padding: var(--space-8) 0 var(--space-16); }
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,7 @@
|
||||||
<link rel="stylesheet" href="../playground-design-system/tokens.css" />
|
<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/base.css" />
|
||||||
<link rel="stylesheet" href="../playground-design-system/components.css" />
|
<link rel="stylesheet" href="../playground-design-system/components.css" />
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="stylesheet" href="../playground-design-system/fonts.css" />
|
||||||
<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>
|
<style>
|
||||||
/* Page-specific layout */
|
/* Page-specific layout */
|
||||||
.layout { display: grid; grid-template-rows: auto 1fr; min-height: 100vh; }
|
.layout { display: grid; grid-template-rows: auto 1fr; min-height: 100vh; }
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,7 @@
|
||||||
<link rel="stylesheet" href="../playground-design-system/base.css" />
|
<link rel="stylesheet" href="../playground-design-system/base.css" />
|
||||||
<link rel="stylesheet" href="../playground-design-system/components.css" />
|
<link rel="stylesheet" href="../playground-design-system/components.css" />
|
||||||
<link rel="stylesheet" href="../playground-design-system/components-tier2.css" />
|
<link rel="stylesheet" href="../playground-design-system/components-tier2.css" />
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="stylesheet" href="../playground-design-system/fonts.css" />
|
||||||
<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>
|
<style>
|
||||||
.layout { display: grid; grid-template-rows: auto 1fr; min-height: 100vh; }
|
.layout { display: grid; grid-template-rows: auto 1fr; min-height: 100vh; }
|
||||||
.page { padding: var(--space-6) 0 var(--space-16); }
|
.page { padding: var(--space-6) 0 var(--space-16); }
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,7 @@
|
||||||
<link rel="stylesheet" href="../playground-design-system/components.css" />
|
<link rel="stylesheet" href="../playground-design-system/components.css" />
|
||||||
<link rel="stylesheet" href="../playground-design-system/components-tier2.css" />
|
<link rel="stylesheet" href="../playground-design-system/components-tier2.css" />
|
||||||
<link rel="stylesheet" href="../playground-design-system/print.css" />
|
<link rel="stylesheet" href="../playground-design-system/print.css" />
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="stylesheet" href="../playground-design-system/fonts.css" />
|
||||||
<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>
|
<style>
|
||||||
.page { padding: var(--space-8) 0 var(--space-16); }
|
.page { padding: var(--space-8) 0 var(--space-16); }
|
||||||
.tpl-grid { display: grid; grid-template-columns: 240px 1fr; gap: var(--space-8); align-items: start; }
|
.tpl-grid { display: grid; grid-template-columns: 240px 1fr; gap: var(--space-8); align-items: start; }
|
||||||
|
|
@ -166,8 +164,7 @@
|
||||||
<link rel="stylesheet" href="../playground-design-system/components.css" />
|
<link rel="stylesheet" href="../playground-design-system/components.css" />
|
||||||
<link rel="stylesheet" href="../playground-design-system/components-tier2.css" />
|
<link rel="stylesheet" href="../playground-design-system/components-tier2.css" />
|
||||||
<link rel="stylesheet" href="../playground-design-system/print.css" />
|
<link rel="stylesheet" href="../playground-design-system/print.css" />
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="stylesheet" href="../playground-design-system/fonts.css" />
|
||||||
<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">
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header class="app-header">
|
<header class="app-header">
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
<link rel="stylesheet" href="../playground-design-system/components.css">
|
<link rel="stylesheet" href="../playground-design-system/components.css">
|
||||||
<link rel="stylesheet" href="../playground-design-system/components-tier2.css">
|
<link rel="stylesheet" href="../playground-design-system/components-tier2.css">
|
||||||
<link rel="stylesheet" href="../playground-design-system/components-tier3.css">
|
<link rel="stylesheet" href="../playground-design-system/components-tier3.css">
|
||||||
<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">
|
<link rel="stylesheet" href="../playground-design-system/fonts.css">
|
||||||
<style>
|
<style>
|
||||||
.preview-section {
|
.preview-section {
|
||||||
padding: var(--space-10) 0;
|
padding: var(--space-10) 0;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue