feat(llm-security): playground v7.6.0 fase 1-2 — fjern DS-duplikater + page-shell harmonisering

Slett ~50 duplikat-CSS-deklarasjoner fra playground-ens <style>-blokk
som overstyrte DS Tier 3 supplement uten gevinst (.app-shell, .tab-list,
.fleet-tile*, .form-progress, .eyebrow, .page__*, .key-stat*, .field-*,
.expansion (ekskl. body), .stack-*, .card*, .tracks*, .checkbox-row).

JS-fix: 4 modifier-strenger oppdatert fra forkortede ('crit', 'med')
til DS-konsistente fulle navn ('critical', 'medium') i renderKeyStatsGrid-data.

Konsekvens: DS vinner cascade-en, eliminerer subtile visuelle drift
mellom playground og referanse-scenarioer.

Page-shell harmonisering: alle 4 overflater (onboarding, home, catalog,
project) bruker nå DS page__header-klyngen via renderPageShell. Onboarding
konvertert fra custom <header class="onboarding-header"> til samme mønster.
renderPageShell utvidet med opts.meta (page__meta) og opts.hero
(page__header--hero modifier). Hero-mønster på home med
clamp(36px, 5vw, 56px) og letter-spacing -0.025em.

Behold til Sesjon 2: .verdict-pill (erstattes av verdict-pill-lg fase 3),
.form-progress__step* (erstattes av fp-step fase 4), .multi-select
(bevisst input-box-look), .expansion__body (markup-mismatch m/ DS-anim).

Forberedelse til v7.6.0 — Tier 3 referanse-case.
This commit is contained in:
Kjell Tore Guttormsen 2026-05-06 12:55:25 +02:00
commit 9ef0c48c00

View file

@ -43,24 +43,24 @@
<link rel="stylesheet" href="vendor/playground-design-system/print.css" media="print">
<style>
/* App-shell layout. Vendored design-system levner komponent-CSS;
her bor kun side-spesifikk layout-grid (sidebar+main, modals, sub-cards). */
/* Playground-spesifikk layout. Alt komponent-CSS som har en DS-pendant er
fjernet i v7.6.0 fase 1 — DS Tier 3-supplement vinner cascade. Her bor
kun side-spesifikk layout-grid (sidebar+main, modals), playground-only
komponenter (.tracks, .verdict-pill, .form-progress__step*, .field-from-tag),
og bevisste overskrivinger som DS ikke dekker (.expansion__body markup,
.multi-select fieldset-ramme, .checkbox-row accent-color). */
main#app { min-height: 100vh; padding: 0; }
[hidden] { display: none !important; }
/* Onboarding-layout: sidebar + main */
.onboarding-layout { display: grid; grid-template-columns: 280px 1fr; gap: var(--space-6); align-items: start; }
@media (max-width: 880px) { .onboarding-layout { grid-template-columns: 1fr; } .form-progress { position: static; width: auto; } }
.onboarding-header { margin-bottom: var(--space-5); }
.onboarding-header h1 { font-size: var(--font-size-2xl); margin: 0 0 var(--space-2); }
.onboarding-header p { color: var(--color-text-secondary); margin: 0; max-width: 60ch; }
.onboarding-groups { display: flex; flex-direction: column; gap: var(--space-3); margin-bottom: var(--space-6); }
.onboarding-fields { display: flex; flex-direction: column; gap: var(--space-4); padding: var(--space-2) 0; }
.onboarding-actions { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3) 0; flex-wrap: wrap; }
.onboarding-help { font-size: var(--font-size-sm); color: var(--color-text-tertiary); }
/* Home + project list */
.home-hero { display: flex; flex-direction: column; gap: var(--space-2); margin-bottom: var(--space-5); }
.home-section-head { display: flex; align-items: baseline; justify-content: space-between; margin: var(--space-6) 0 var(--space-3); }
.home-section-head h2 { font-size: var(--font-size-xl); }
.home-section-head .home-section-meta { color: var(--color-text-tertiary); font-size: var(--font-size-sm); }
@ -85,33 +85,7 @@
.project-header__chip { display: inline-flex; align-items: center; gap: 6px; padding: 2px 8px; border-radius: var(--radius-sm); background: var(--color-bg-soft); color: var(--color-text-secondary); font-size: var(--font-size-xs); font-family: var(--font-family-mono); }
.scenario-tag { display: inline-flex; align-items: center; padding: 2px 8px; border-radius: var(--radius-pill); background: var(--color-scope-security-soft, var(--color-primary-100)); color: var(--color-scope-security-on, var(--color-primary-700)); font-size: var(--font-size-xs); font-weight: var(--font-weight-medium); }
/* Tracks (DS) som hero på home — fallback hvis ikke i tier3 wave 2 */
.tracks { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-4); margin-bottom: var(--space-5); }
@media (max-width: 880px) { .tracks { grid-template-columns: 1fr; } }
.tracks__card { display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-5); border: 1px solid var(--color-border-subtle); border-radius: var(--radius-md); background: var(--color-surface); cursor: pointer; text-align: left; font-family: inherit; color: var(--color-text-primary); transition: border-color 120ms, transform 120ms; }
.tracks__card:hover { border-color: var(--color-scope-security, var(--color-primary-500)); transform: translateY(-1px); }
.tracks__card-icon { font-size: var(--font-size-2xl); width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; }
.tracks__card-title { font-size: var(--font-size-lg); margin: 0; font-weight: var(--font-weight-semibold); }
.tracks__card-desc { font-size: var(--font-size-sm); color: var(--color-text-secondary); margin: 0; }
.tracks__card-meta { display: flex; justify-content: space-between; font-size: var(--font-size-xs); color: var(--color-text-tertiary); margin-top: var(--space-2); }
.tracks__card-cta { color: var(--color-scope-security, var(--color-primary-500)); font-weight: var(--font-weight-semibold); }
/* Fleet-grid (project list) */
.fleet-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: var(--space-3); }
.fleet-tile { display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-4); border: 1px solid var(--color-border-subtle); border-radius: var(--radius-md); background: var(--color-surface); cursor: pointer; text-align: left; font-family: inherit; color: var(--color-text-primary); transition: border-color 120ms; }
.fleet-tile:hover { border-color: var(--color-scope-security, var(--color-primary-500)); }
.fleet-tile__row { display: flex; justify-content: space-between; align-items: center; gap: var(--space-2); }
.fleet-tile__name { font-weight: var(--font-weight-semibold); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fleet-tile__chip { font-size: var(--font-size-xs); padding: 2px 8px; border-radius: var(--radius-pill); background: var(--color-bg-soft); color: var(--color-text-secondary); white-space: nowrap; }
.fleet-tile__meter { width: 100%; height: 6px; background: var(--color-bg-soft); border-radius: var(--radius-pill); overflow: hidden; }
.fleet-tile__meter-fill { display: block; height: 100%; border-radius: inherit; transition: width 200ms; }
.fleet-tile__meter-fill[data-band="1"] { background: var(--color-severity-low); }
.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__meta { display: flex; justify-content: space-between; font-size: var(--font-size-xs); color: var(--color-text-tertiary); }
/* Command form patterns */
/* Command form patterns (playground-only — DS dekker ikke command-form) */
.command-form { display: flex; flex-direction: column; gap: var(--space-3); }
.command-form__fields { display: flex; flex-direction: column; gap: var(--space-3); }
.command-form__actions { display: flex; gap: var(--space-2); align-items: center; flex-wrap: wrap; }
@ -121,46 +95,30 @@
.form-preview__heading { font-size: var(--font-size-xs); font-weight: var(--font-weight-semibold); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-tertiary); margin: 0 0 var(--space-2); }
.code-block { font-family: var(--font-family-mono); font-size: var(--font-size-sm); color: var(--color-text-primary); margin: 0; white-space: pre-wrap; word-break: break-all; }
/* Field row */
.field-row { display: flex; flex-direction: column; gap: 6px; }
.field-label { font-size: var(--font-size-sm); font-weight: var(--font-weight-medium); color: var(--color-text-primary); display: flex; align-items: center; gap: 6px; }
.field-from-tag { font-size: 10px; font-weight: var(--font-weight-medium); padding: 1px 6px; border-radius: var(--radius-pill); background: var(--color-scope-security-soft, var(--color-primary-100)); color: var(--color-scope-security-on, var(--color-primary-700)); text-transform: uppercase; letter-spacing: 0.06em; cursor: help; }
.field-help { font-size: var(--font-size-xs); color: var(--color-text-tertiary); }
/* Field utility (DS dekker .field-row/.field-label/.field-help/.required-mark/.checkbox-row) */
.field-from-tag { font-size: 10px; font-weight: var(--font-weight-medium); padding: 1px 6px; border-radius: var(--radius-pill); background: var(--color-scope-security-soft, var(--color-primary-100)); color: var(--color-scope-security-on, var(--color-primary-700)); text-transform: uppercase; letter-spacing: 0.06em; cursor: help; margin-left: 6px; vertical-align: middle; }
.input, .textarea, .select { font-family: inherit; font-size: var(--font-size-sm); padding: 8px 10px; border: 1px solid var(--color-border-moderate); border-radius: var(--radius-sm); background: var(--color-surface); color: var(--color-text-primary); transition: border-color 120ms ease, box-shadow 120ms ease; }
.input:hover, .textarea:hover, .select:hover { border-color: var(--color-border-strong); }
.input:focus, .textarea:focus, .select:focus { outline: 2px solid var(--color-scope-security, var(--color-primary-500)); outline-offset: 1px; border-color: var(--color-border-strong); }
.textarea { resize: vertical; font-family: inherit; }
/* Multi-select: bevisst input-box-look (border + padding) — overstyrer DS' flate liste */
.multi-select { display: flex; flex-direction: column; gap: 4px; padding: 8px 10px; border: 1px solid var(--color-border-moderate); border-radius: var(--radius-sm); background: var(--color-surface); }
.multi-select:hover { border-color: var(--color-border-strong); }
.checkbox-row { display: flex; align-items: center; gap: 8px; font-size: var(--font-size-sm); cursor: pointer; }
/* Checkbox accent-color: playground-spesifikk styling. .checkbox-row selve dekkes av DS. */
.checkbox-row input[type="checkbox"] { width: 16px; height: 16px; accent-color: var(--color-scope-security, var(--color-primary-500)); border: 1px solid var(--color-border-strong); }
.visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
/* Card patterns */
.card { border: 1px solid var(--color-border-subtle); border-radius: var(--radius-md); background: var(--color-surface); padding: var(--space-4); display: flex; flex-direction: column; gap: var(--space-3); }
.card__head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-3); }
.card__title { font-size: var(--font-size-md); font-weight: var(--font-weight-semibold); margin: 0 0 4px; }
.card__desc { font-size: var(--font-size-sm); color: var(--color-text-secondary); margin: 0; }
.card__hint { display: inline-block; padding: 2px 6px; border-radius: var(--radius-sm); background: var(--color-bg-soft); font-family: var(--font-family-mono); font-size: 11px; color: var(--color-text-tertiary); margin-top: 6px; }
.card__pill { display: inline-flex; align-items: center; padding: 2px 8px; border-radius: var(--radius-pill); background: var(--color-bg-soft); color: var(--color-text-secondary); font-size: var(--font-size-xs); font-weight: var(--font-weight-medium); white-space: nowrap; }
.card__actions { display: flex; gap: var(--space-2); align-items: center; flex-wrap: wrap; }
/* Catalog */
.catalog-search { width: 100%; max-width: 480px; margin-bottom: var(--space-5); }
.catalog-cards-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: var(--space-3); margin-top: var(--space-3); }
.catalog-tool-notice { padding: 8px 12px; background: var(--color-bg-soft); border-left: 3px solid var(--color-state-info, var(--color-primary-300)); font-size: var(--font-size-xs); color: var(--color-text-secondary); border-radius: var(--radius-sm); }
.expansion { border: 1px solid var(--color-border-subtle); border-radius: var(--radius-md); overflow: hidden; background: var(--color-surface); margin-bottom: var(--space-3); }
.expansion__head { display: flex; align-items: center; gap: var(--space-3); width: 100%; padding: var(--space-3) var(--space-4); background: transparent; border: 0; cursor: pointer; font-family: inherit; text-align: left; color: var(--color-text-primary); }
.expansion__head:hover { background: var(--color-bg-soft); }
.expansion__title { flex: 1; display: flex; flex-direction: column; gap: 2px; }
.expansion__title-main { font-weight: var(--font-weight-semibold); font-size: var(--font-size-md); }
.expansion__title-sub { font-size: var(--font-size-xs); color: var(--color-text-tertiary); }
.expansion__chev { font-size: var(--font-size-md); color: var(--color-text-tertiary); transition: transform 120ms; }
.expansion[aria-expanded="true"] .expansion__chev { transform: rotate(180deg); }
/* Expansion-body: playground-markup mangler .expansion__body-inner-wrapping
som DS' grid-template-rows-animasjon krever. Beholdes til markup-en
evt. oppgraderes (out-of-scope for v7.6.0). */
.expansion__body { padding: 0 var(--space-4) var(--space-4); border-top: 1px solid var(--color-border-subtle); }
.expansion[aria-expanded="false"] .expansion__body { display: none; }
/* Modal */
/* Modal (playground-only — DS har ikke modal-pattern enda) */
.modal-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 100; padding: var(--space-5); }
.modal { background: var(--color-surface); border-radius: var(--radius-md); max-width: 720px; width: 100%; max-height: 90vh; overflow: auto; padding: var(--space-5); display: flex; flex-direction: column; gap: var(--space-4); }
.modal__head { display: flex; justify-content: space-between; align-items: center; gap: var(--space-3); }
@ -168,47 +126,19 @@
.modal__close { background: transparent; border: 0; cursor: pointer; font-size: 24px; line-height: 1; padding: 4px 8px; color: var(--color-text-tertiary); }
.modal__close:hover { color: var(--color-text-primary); }
/* Page shell */
.page__header { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-4); padding: var(--space-5) 0 var(--space-4); border-bottom: 1px solid var(--color-border-subtle); margin-bottom: var(--space-5); }
.page__header-main { display: flex; flex-direction: column; gap: var(--space-2); flex: 1; }
.page__eyebrow { font-size: var(--font-size-xs); text-transform: uppercase; letter-spacing: 0.1em; color: var(--color-scope-security, var(--color-primary-500)); font-weight: var(--font-weight-semibold); }
.page__title { font-size: var(--font-size-2xl); margin: 0; }
.page__lede { font-size: var(--font-size-md); color: var(--color-text-secondary); margin: 0; max-width: 65ch; }
.page__header-aside { flex-shrink: 0; }
.key-stats { display: flex; gap: var(--space-5); flex-wrap: wrap; padding: var(--space-3) 0; background: transparent; border: 0; border-radius: 0; border-bottom: 1px solid var(--color-border-subtle); margin: 0 0 var(--space-5); }
.key-stat { display: flex; flex-direction: column; gap: 2px; min-width: 120px; }
.key-stat__label { font-size: var(--font-size-xs); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-tertiary); }
.key-stat__value { font-size: var(--font-size-xl); font-weight: var(--font-weight-bold); font-variant-numeric: tabular-nums; }
.key-stat__hint { font-size: var(--font-size-xs); color: var(--color-text-secondary); }
.key-stat--crit .key-stat__value { color: var(--color-severity-critical); }
.key-stat--high .key-stat__value { color: var(--color-severity-high); }
.key-stat--med .key-stat__value { color: var(--color-severity-medium); }
.key-stat--low .key-stat__value { color: var(--color-severity-low); }
/* Page-shell hero-modifier — clamp font-size for home-overflate.
DS' .page__title er 3xl (~32px); hero-modifier vipper opp til 36-56px
med editorial letter-spacing. */
.page__header--hero .page__title { font-size: clamp(36px, 5vw, 56px); letter-spacing: -0.025em; }
/* Verdict pill */
/* Verdict-pill (playground-only — Sesjon 2 erstatter med .verdict-pill-lg fra DS) */
.verdict-pill { display: inline-flex; align-items: center; padding: 4px 12px; border-radius: var(--radius-pill); font-size: var(--font-size-xs); font-weight: var(--font-weight-bold); letter-spacing: 0.06em; text-transform: uppercase; }
.verdict-pill[data-verdict="go"], .verdict-pill[data-verdict="approved"], .verdict-pill[data-verdict="allow"] { background: var(--color-state-success-soft, var(--color-severity-low-soft)); color: var(--color-state-success, var(--color-severity-low-on)); }
.verdict-pill[data-verdict="warning"], .verdict-pill[data-verdict="go-with-conditions"] { background: var(--color-severity-medium-soft); color: var(--color-severity-medium-on); }
.verdict-pill[data-verdict="block"], .verdict-pill[data-verdict="failed"] { background: var(--color-severity-critical-soft); color: var(--color-severity-critical-on); }
.verdict-pill[data-verdict="n-a"] { background: var(--color-bg-soft); color: var(--color-text-tertiary); }
/* Stack utilities */
.stack-sm > * + * { margin-top: var(--space-2); }
.stack-md > * + * { margin-top: var(--space-3); }
.stack-lg > * + * { margin-top: var(--space-5); }
/* App-shell */
.app-shell { max-width: 1200px; margin: 0 auto; padding: 0 var(--space-5) var(--space-12); }
.app-shell--wide { max-width: 1440px; }
/* Tab list (project screen tabs) */
.tab-list { display: flex; gap: 2px; margin-bottom: var(--space-4); border-bottom: 1px solid var(--color-border-subtle); }
.tab { background: transparent; border: 0; padding: 8px 14px; cursor: pointer; font-family: inherit; font-size: var(--font-size-sm); color: var(--color-text-secondary); border-bottom: 2px solid transparent; margin-bottom: -1px; }
.tab:hover { color: var(--color-text-primary); }
.tab[aria-current="true"] { color: var(--color-text-primary); border-bottom-color: var(--color-scope-security, var(--color-primary-500)); font-weight: var(--font-weight-semibold); }
/* Form-progress sidebar (onboarding) */
.form-progress { position: sticky; top: var(--space-5); padding: var(--space-4); background: var(--color-surface); border: 1px solid var(--color-border-subtle); border-radius: var(--radius-md); display: flex; flex-direction: column; gap: var(--space-2); }
/* Form-progress steps (playground-only — Sesjon 2 erstatter med .fp-step fra DS) */
.form-progress__heading { font-size: var(--font-size-xs); font-weight: var(--font-weight-semibold); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-tertiary); margin: 0 0 var(--space-2); }
.form-progress__step { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: var(--radius-sm); font-size: var(--font-size-sm); cursor: pointer; background: transparent; border: 0; font-family: inherit; text-align: left; color: var(--color-text-secondary); }
.form-progress__step:hover { background: var(--color-bg-soft); }
@ -216,12 +146,6 @@
.form-progress__step-marker { width: 18px; height: 18px; border-radius: 50%; border: 1.5px solid var(--color-border-default); display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: var(--font-weight-bold); color: var(--color-text-tertiary); flex-shrink: 0; }
.form-progress__step--done .form-progress__step-marker { background: var(--color-scope-security, var(--color-primary-500)); border-color: var(--color-scope-security, var(--color-primary-500)); color: var(--color-text-on-primary, white); }
.form-progress__step[aria-current="step"] .form-progress__step-marker { border-color: var(--color-scope-security, var(--color-primary-500)); color: var(--color-scope-security, var(--color-primary-500)); }
/* Eyebrow utility */
.eyebrow { font-size: var(--font-size-xs); text-transform: uppercase; letter-spacing: 0.1em; color: var(--color-scope-security, var(--color-primary-500)); font-weight: var(--font-weight-semibold); display: block; margin-bottom: var(--space-2); }
/* Required mark */
.required-mark { color: var(--color-severity-critical); margin-left: 2px; }
</style>
</head>
<body>
@ -6707,13 +6631,12 @@
const orgName = (store.state.shared.organization && store.state.shared.organization.name) || '';
const isReturning = !!orgName;
const headerHtml = (
'<header class="onboarding-header">' +
'<span class="eyebrow">' + (isReturning ? 'RE-ONBOARDING' : 'ONBOARDING') + ' · ' + allCompleteCount + ' av ' + ONBOARDING_GROUPS.length + ' grupper komplette</span>' +
'<h1>' + (isReturning ? 'Oppdater fellesfeltene' : 'Velkommen — la oss sette opp llm-security for ' + (orgName || 'din virksomhet')) + '</h1>' +
'<p>Disse 5 gruppene er felles state. De forhåndsutfyller alle command-skjemaer for nye prosjekter, så du slipper å re-skrive samme info.</p>' +
'</header>'
);
const headerHtml = renderPageShell({
eyebrow: (isReturning ? 'RE-ONBOARDING' : 'ONBOARDING') + ' · ' + allCompleteCount + ' av ' + ONBOARDING_GROUPS.length + ' grupper komplette',
title: isReturning ? 'Oppdater fellesfeltene' : 'Velkommen — la oss sette opp llm-security for ' + (orgName || 'din virksomhet'),
lede: 'Disse 5 gruppene er felles state. De forhåndsutfyller alle command-skjemaer for nye prosjekter, så du slipper å re-skrive samme info.',
meta: ['Gruppe ' + (ONBOARDING_GROUPS.findIndex(function (g) { return g.id === group.id; }) + 1) + ' av ' + ONBOARDING_GROUPS.length, group.title || group.id]
}, '');
const stepNavHtml = (
'<div class="onboarding-actions">' +
@ -6880,6 +6803,12 @@
? 'Velg arbeidsspor eller utforsk eksisterende prosjekter. Felles state er aktiv og forhåndsutfyller skjemaer.'
: 'Single-file sikkerhetsskanning + auditing for Claude Code-prosjekter. Start med onboarding for å aktivere felles state.',
verdict: 'n-a',
hero: true,
meta: [
'Plugin v7.5.0',
projects.length + ' prosjekt' + (projects.length === 1 ? '' : 'er'),
CATALOG.commands.length + ' kommandoer'
],
keyStats: [
{ label: 'PROSJEKTER', value: projects.length },
{ label: 'AKTIVE RAPPORTER', value: activeReportCount },
@ -6977,6 +6906,11 @@
title: 'Command-katalog',
lede: 'Alle ' + total + ' kommandoer gruppert på kategori. Bygg pipeline-strenger uten et aktivt prosjekt.',
verdict: 'n-a',
meta: [
total + ' kommandoer',
reportCount + ' rapport-produserende',
toolCount + ' verktøy'
],
keyStats: [
{ label: 'TOTALT', value: total },
{ label: 'RAPPORT-KOMMANDOER', value: reportCount },
@ -7159,6 +7093,11 @@
title: project.name,
lede: project.description || '',
verdict: inferProjectVerdict(project),
meta: [
'Target: ' + (project.target || project.target_path || '—'),
'Sist oppdatert: ' + inferProjectLastUpdated(project),
(project.scenario || project.template || 'standard')
],
keyStats: [
{ label: 'RAPPORTER', value: reportFilled + '/' + reportTotal },
{ label: 'SIST OPPDATERT', value: inferProjectLastUpdated(project) },
@ -7206,17 +7145,30 @@
return '<div class="key-stats">' + items + '</div>';
}
/**
* Render page-shell — DS Tier 3 page__header-klyngen brukt på alle 4 overflater:
* - onboarding: page__eyebrow="ONBOARDING · n av 5 grupper komplette"
* - home: page__eyebrow="HJEM" (m/ hero-modifier for editorial type-hierarki)
* - catalog: page__eyebrow="KATALOG"
* - project: page__eyebrow="PROSJEKT · <TARGET>"
* Pluss alle 18 rapport-renderere (eyebrow per archetype).
* opts: { eyebrow, title, lede, meta:[], verdict, hero, keyStats }
*/
function renderPageShell(opts, bodyHtml) {
opts = opts || {};
const eyebrow = opts.eyebrow ? '<span class="page__eyebrow">' + escapeHtml(opts.eyebrow) + '</span>' : '';
const title = '<h1 class="page__title">' + escapeHtml(opts.title || '') + '</h1>';
const lede = opts.lede ? '<p class="page__lede">' + escapeHtml(opts.lede) + '</p>' : '';
const meta = (opts.meta && opts.meta.length)
? '<div class="page__meta">' + opts.meta.map(function (m) { return '<span>' + escapeHtml(m) + '</span>'; }).join('') + '</div>'
: '';
const verdict = (opts.verdict && opts.verdict !== 'n-a') ? renderVerdictPill(opts.verdict) : '';
const aside = verdict ? '<div class="page__header-aside">' + verdict + '</div>' : '';
const stats = renderKeyStatsGrid(opts.keyStats);
const heroClass = opts.hero ? ' page__header--hero' : '';
return (
'<header class="page__header">' +
'<div class="page__header-main">' + eyebrow + title + lede + '</div>' +
'<header class="page__header' + heroClass + '">' +
'<div class="page__header-main">' + eyebrow + title + lede + meta + '</div>' +
aside +
'</header>' +
stats +
@ -7317,13 +7269,13 @@
const high = fs.filter(function (f) { return /^high|^høy/i.test(f.severity || ''); }).length;
return [
{ label: 'TOTALT', value: fs.length },
{ label: 'KRITISK', value: crit, modifier: crit > 0 ? 'crit' : null },
{ label: 'KRITISK', value: crit, modifier: crit > 0 ? 'critical' : null },
{ label: 'HØY', value: high, modifier: high > 0 ? 'high' : null }
];
},
'findings-grade': function (d) {
const out = [];
if (d.grade) out.push({ label: 'GRADE', value: String(d.grade).toUpperCase(), modifier: /a|b/i.test(d.grade) ? 'low' : (/c|d/i.test(d.grade) ? 'med' : 'crit') });
if (d.grade) out.push({ label: 'GRADE', value: String(d.grade).toUpperCase(), modifier: /a|b/i.test(d.grade) ? 'low' : (/c|d/i.test(d.grade) ? 'medium' : 'critical') });
if (d.score != null) out.push({ label: 'SCORE', value: d.score });
if (d.findings) out.push({ label: 'FUNN', value: d.findings.length });
return out;
@ -7331,7 +7283,7 @@
'risk-score-meter': function (d) {
const out = [];
if (d.risk_score != null) {
const mod = d.risk_score >= 65 ? 'crit' : (d.risk_score >= 15 ? 'med' : 'low');
const mod = d.risk_score >= 65 ? 'critical' : (d.risk_score >= 15 ? 'medium' : 'low');
out.push({ label: 'RISK SCORE', value: d.risk_score, modifier: mod });
}
if (d.riskBand) out.push({ label: 'BAND', value: d.riskBand });
@ -7341,7 +7293,7 @@
return [
{ label: 'TOTALT', value: d.total || 0 },
{ label: 'PASS', value: d.pass_count || 0, modifier: 'low' },
{ label: 'FAIL', value: d.fail_count || 0, modifier: (d.fail_count > 0 ? 'crit' : null) }
{ label: 'FAIL', value: d.fail_count || 0, modifier: (d.fail_count > 0 ? 'critical' : null) }
];
},
'dashboard-fleet': function (d) {