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
|
|
@ -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
|
||||
|
||||
|
|
|
|||
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.
Loading…
Add table
Add a link
Reference in a new issue