feat(shared): Tier 3 wave 2 (12 components) + self-hosted fonts

Two changes in one commit because they were prepared together and the
component demos depend on the new self-hosted fonts.css.

Tier 3 wave 2 — 12 new components
---------------------------------
Adds components-tier3-supplement.css (886 lines) and 12 isolated demo
HTML pages under shared/playground-examples/components/:
toxic-flow chain, fleet-overview, kanban Keep/Review/Remove,
maturity-ladder, classify-and-transform, cycle-ribbon,
persistent-antipattern, suppressed-signals, ExpansionCard, ReadMore,
FormProgress, Aspirational-vs-Committed.

Reuses existing tokens — no new CSS custom properties. Honors the
Phase 1 feedback rules: no large pink areas for body text, severity-red
distinct from failure-red, dark mode via existing [data-theme="dark"].

Provenance: components-tier3-supplement.css and the 12 demo bodies were
authored by claude.ai/design (separate Anthropic instance) on 2026-05-03.
This commit only integrates them — path rewrites, font swap, generic
name substitution in fleet-overview demo data, README updates.
base.css from the export was deliberately NOT taken in because it
reverted the inline-message contrast fix from v0.1.

Self-hosted fonts (Inter, JetBrains Mono, Source Serif 4)
---------------------------------------------------------
Replaces all fonts.googleapis.com / fonts.gstatic.com requests with
.woff2 files bundled at shared/playground-design-system/fonts/.

Why:
- No data leaked to Google about end-user IPs and User-Agents.
- GDPR-safe for Norwegian public-sector deployments.
- Works offline / behind air-gapped firewalls.
- Forkers downloading the marketplace get a complete bundle.

All three families are SIL Open Font License 1.1 — license texts
included alongside the woff2 files. Source Serif 4 woff2 generated
locally from the upstream OTF release using
fonttools ttLib.woff2 compress; Inter and JetBrains Mono are
unmodified upstream webfont releases.

Total bundle: 9 woff2 files, ~940 KB. New fonts.css declares all
@font-face rules with font-display: swap. All 6 example HTMLs and 12
new component demos load it via a single relative path.

Verified
--------
- Privacy grep returns empty across plugins/ and shared/
- Google Fonts grep returns empty across shared/*.html
- Smoke test via python -m http.server: HTML + 7 stylesheets +
  Inter-Regular.woff2 all return 200

Doc updates
-----------
- shared/playground-design-system/README.md: file tree updated,
  Quick start snippet shows fonts.css link, "Self-hosted fonts"
  section added
- shared/playground-design-system/fonts/LICENSES.md: combined attribution
- README.md (root): Tier 3 wave 1+2 component list, Privacy-first bullet
- CLAUDE.md (root): tree entry expanded for new components + fonts

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-05-03 05:08:07 +02:00
commit f1fecf39b8
36 changed files with 2470 additions and 29 deletions

View file

@ -20,7 +20,14 @@ shared/
│ ├── base.css # Reset, typography, primitives, focus, print
│ ├── components.css # Tier 1: radar, matrix, findings-browser, critique-card, wizard, live-meter
│ ├── components-tier2.css # Tier 2: decision-tree, traffic-lights, diff-review, treemap, distribution, command-pipeline, pyramide, pipeline-cockpit, verdict-pill+risk-meter, codepoint-reveal, small-multiples, OWASP badges
│ ├── components-tier3.css # Tier 3: 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
│ └── schemas/ # Cross-plugin JSON schemas
│ ├── 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)
├── ros-lier-kommune.html # Scenario A — ms-ai-architect ROS report
├── okr-baerum.html # Scenario B — OKR live writer
├── security-direktorat.html # Scenario C — llm-security findings review
├── security-direktorat.html # Scenario C — llm-security findings review
├── templates.html # Skeleton + print-template demos
├── tier3-preview.html # Tier 3 wave 1 visual preview
├── components/ # Tier 3 wave 2 — 12 isolated demo pages
│ ├── sankey-toxic-flow.html
│ ├── fleet-overview.html
│ ├── kanban.html
│ ├── maturity-ladder.html
│ ├── classify-transform.html
│ ├── cycle-ribbon.html
│ ├── persistent-antipattern.html
│ ├── suppressed-signals.html
│ ├── expansion-card.html
│ ├── read-more.html
│ ├── form-progress.html
│ └── aspirational-committed.html
├── ros-app.js # Scenario A interactivity
└── ros-data.js # Scenario A mock data
```
@ -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/components.css">
<link rel="stylesheet" href="../../shared/playground-design-system/components-tier2.css">
<!-- Optional: include components-tier3.css for Tier 3 wave 1 components -->
<!-- Optional: include components-tier3-supplement.css for Tier 3 wave 2 (12 additional components) -->
<!-- Optional: only include print.css if scenario produces a printable A4 report -->
<link rel="stylesheet" href="../../shared/playground-design-system/print.css">
<!-- Self-hosted fonts (no external requests) -->
<link rel="stylesheet" href="../../shared/playground-design-system/fonts.css">
</head>
<body>
<header class="app-header">
@ -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.
## 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.
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.
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.
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.
1. **No JavaScript framework.** Components are CSS-first. Interactivity (e.g. `aria-selected` toggling, sidepanel open/close, live-meter updates) must be wired by each Playground using vanilla JS. See `playground-examples/ros-app.js` for a reference implementation pattern.
2. **No icon set bundled.** The system assumes Lucide or Phosphor SVG sprites are inlined per Playground. Iconography is intentionally out-of-system to keep the shared system small.
3. **Mobile responsiveness is partial.** The 5×5 matrix, findings-browser, codepoint-reveal split-pane, and small-multiples grid have explicit `@media (max-width: ...)` rules. Other components may need polish for narrow viewports.
## Self-hosted fonts
All three font families (Inter, JetBrains Mono, Source Serif 4) are bundled as woff2 in `fonts/` and loaded via `fonts.css`. No external requests to Google Fonts or any CDN. All three are SIL OFL 1.1 — see `fonts/LICENSES.md` for full attribution.
## Versioning

View 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; }

View 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");
}

Binary file not shown.

View file

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

View file

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

View file

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

View file

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