feat(redesign): Design-Tokens + Ebene-2 "Cyber trifft Safety" (additiv)
CI / detect-changes (push) Successful in 19s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / build-sha-integrity (push) Successful in 7s
CI / validate-canonical-controls (push) Successful in 4s
CI / loc-budget (push) Successful in 21s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 3m13s
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Has been skipped
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped

Schritt A (Tokens): zentrale Design-Sprache aus dem Claude-Design-Handoff —
Tailwind-Tokens (re/geltung/severity/domain) + Fonts (Public Sans / Source
Serif 4 / IBM Plex Mono) + components/redesign/{tokens.ts,Chips.tsx}
(GeltungChip, SeverityChip, DomainTag, MonoId) + Showcase /sdk/design-system.
Bestehende Farben/sans unangetastet.

Schritt B (Ebene 2): CyberMeetsSafety als USP-Hero im CRA/Cyber-Tab
(/sdk/iace/[id]/cra) — Domaenen-Bar, Hazard-Karten (CE-gemildert -> Cyber-Befund
-> Restrisiko, Warum-Box, Pflicht/Empfehlung-Massnahmen, aufklappbarer
Norm-Bezug), Massnahmen-Backlog mit Geltung-Filter. Gebunden an echte
cross_links/findings/open_measures. Bisheriger CRACyberView bleibt eingeklappt
erhalten -> kein Inhaltsverlust.

Guardrail-Doku: design/redesign/ (HANDOFF_README, CONTENT_INVENTORY mit
40-Screen-Mapping + Waisen-Liste, Arbeitsbereich.dc.html-Referenz).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Benjamin Bönisch
2026-06-18 16:49:04 +02:00
parent 43e02f794a
commit 42d4b4d9c5
10 changed files with 1312 additions and 3 deletions
+7 -2
View File
@@ -1,8 +1,13 @@
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import { Inter, Public_Sans, Source_Serif_4, IBM_Plex_Mono } from 'next/font/google'
import './globals.css'
const inter = Inter({ subsets: ['latin'] })
// Redesign fonts (design/redesign) — exposed as CSS variables; the new
// design-language components opt in via Tailwind font-publicSans/-sourceSerif/-plexMono.
const publicSans = Public_Sans({ subsets: ['latin'], weight: ['400', '500', '600', '700', '800'], variable: '--font-public-sans' })
const sourceSerif = Source_Serif_4({ subsets: ['latin'], weight: ['400', '500', '600'], style: ['normal', 'italic'], variable: '--font-source-serif' })
const plexMono = IBM_Plex_Mono({ subsets: ['latin'], weight: ['400', '500', '600'], variable: '--font-plex-mono' })
export const metadata: Metadata = {
title: 'BreakPilot Admin Compliance',
@@ -15,7 +20,7 @@ export default function RootLayout({
children: React.ReactNode
}) {
return (
<html lang="de">
<html lang="de" className={`${publicSans.variable} ${sourceSerif.variable} ${plexMono.variable}`}>
<body className={inter.className}>{children}</body>
</html>
)
@@ -0,0 +1,114 @@
'use client'
// Design-system showcase (Schritt A verification). Renders the redesign tokens +
// chips so the design language can be reviewed in isolation. Internal reference
// page (not in the customer sidebar).
import { GeltungChip, SeverityChip, DomainTag, MonoId } from '@/components/redesign/Chips'
import { COLORS, DOMAIN } from '@/components/redesign/tokens'
function Section({ title, children }: { title: string; children: React.ReactNode }) {
return (
<section style={{ marginTop: 38 }}>
<h2 className="font-publicSans" style={{ fontSize: 19, fontWeight: 800, letterSpacing: '-0.01em', color: COLORS.ink }}>{title}</h2>
<div style={{ marginTop: 14 }}>{children}</div>
</section>
)
}
function Swatch({ name, hex }: { name: string; hex: string }) {
return (
<div className="font-publicSans" style={{ fontSize: 12 }}>
<div style={{ height: 44, borderRadius: 10, background: hex, border: `1px solid ${COLORS.border}` }} />
<div style={{ marginTop: 6, color: COLORS.ink, fontWeight: 600 }}>{name}</div>
<div className="font-plexMono" style={{ color: COLORS.faint, fontSize: 11 }}>{hex}</div>
</div>
)
}
export default function DesignSystemPage() {
return (
<div className="font-publicSans" style={{ background: COLORS.page, minHeight: '100%', margin: -24, padding: '30px 24px 90px' }}>
<div style={{ maxWidth: 1100, margin: '0 auto' }}>
<h1 style={{ fontSize: 30, fontWeight: 800, letterSpacing: '-0.02em', color: COLORS.ink, lineHeight: 1.1 }}>
Design-Sprache
</h1>
<p style={{ marginTop: 8, color: COLORS.muted, fontSize: 14, maxWidth: 640 }}>
Schritt A Tokens & Bausteine des Redesigns (Geltung, Severity, Domänen, Typografie).
Referenz: <span className="font-plexMono" style={{ fontSize: 12 }}>design/redesign/HANDOFF_README.md</span>.
</p>
<Section title="Geltung — Pflicht / Empfehlung / Kann">
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', alignItems: 'center' }}>
<GeltungChip value="pflicht" />
<GeltungChip value="empfehlung" />
<GeltungChip value="kann" />
<span style={{ color: COLORS.faint, fontSize: 12 }}>+ Klartext-Maßnahme</span>
<MonoId>M542</MonoId>
</div>
</Section>
<Section title="Severity — Dringlichkeit (getönt, kein Neon)">
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
<SeverityChip value="kritisch" />
<SeverityChip value="hoch" />
<SeverityChip value="mittel" />
<SeverityChip value="niedrig" />
</div>
</Section>
<Section title="Domänen — Safety / Cyber / Schnittstelle">
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', marginBottom: 14 }}>
<DomainTag value="safety" />
<DomainTag value="cyber" />
<DomainTag value="bridge" />
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: 12 }}>
{(['safety', 'cyber', 'bridge'] as const).map((k) => (
<div key={k} style={{ background: DOMAIN[k].tint, border: `1px solid ${DOMAIN[k].border}`, borderLeft: `4px solid ${DOMAIN[k].accent}`, borderRadius: 12, padding: 14 }}>
<div style={{ fontWeight: 700, color: DOMAIN[k].accent, fontSize: 13 }}>{DOMAIN[k].label}</div>
<div className="font-plexMono" style={{ color: COLORS.faint, fontSize: 11, marginTop: 4 }}>{DOMAIN[k].accent}</div>
</div>
))}
</div>
</Section>
<Section title="Typografie">
<div style={{ display: 'grid', gap: 10 }}>
<div className="font-publicSans" style={{ fontSize: 18, fontWeight: 800, color: COLORS.ink }}>Public Sans UI & Überschriften (800)</div>
<div className="font-sourceSerif" style={{ fontSize: 15, fontStyle: 'italic', color: COLORS.muted }}>Source Serif 4 Normzitate / rechtliche Texte (kursiv)</div>
<div className="font-plexMono" style={{ fontSize: 13, color: COLORS.muted }}>IBM Plex Mono interne IDs · CRA-AI-8 · R = S × (F + W + P)</div>
</div>
</Section>
<Section title="Neutrale & Marke">
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5,1fr)', gap: 12 }}>
<Swatch name="bg/page" hex={COLORS.page} />
<Swatch name="surface" hex={COLORS.surface} />
<Swatch name="border" hex={COLORS.border} />
<Swatch name="ink" hex={COLORS.ink} />
<Swatch name="primary" hex={COLORS.brand} />
</div>
</Section>
<Section title="Leitprinzipien">
<div style={{ background: COLORS.panel, borderRadius: 14, padding: 22 }}>
<ol style={{ color: COLORS.panelText2, fontSize: 13, lineHeight: 1.7, paddingLeft: 0, listStyle: 'none' }}>
{[
'3 Ebenen pro Screen: Überblick → Cyber×Safety → Technik (eingeklappt).',
'Klartext führt. Interne IDs nur in Monospace nachgestellt.',
'Co-Pilot statt Roboter-Anwalt — keine Panik-Rot-Blöcke.',
'Pflicht / Empfehlung / Kann immer visuell getrennt.',
].map((t, i) => (
<li key={i} style={{ display: 'flex', gap: 10, marginBottom: 6 }}>
<span className="font-plexMono" style={{ color: COLORS.panelAccent, fontWeight: 600 }}>{i + 1}</span>
<span style={{ color: COLORS.panelText }}>{t}</span>
</li>
))}
</ol>
</div>
</Section>
</div>
</div>
)
}
@@ -0,0 +1,189 @@
'use client'
// Ebene 2 — "Cyber trifft Safety" (Redesign-Herzstück / USP).
// Macht sichtbar, wo ein Cyber-Befund eine bereits mechanisch gemilderte
// CE-Gefährdung wieder aufreißt. Bindet an die ECHTEN Bridge-Daten (cross_links)
// + findings + open_measures aus useCRA. Design nach design/redesign/HANDOFF_README.md.
// Additiv: ersetzt den bestehenden CRACyberView NICHT.
import { useState } from 'react'
import { CRADemo, CrossLink, CRAFinding, Measure } from '../_hooks/useCRADemo'
import { GeltungChip, MonoId } from '@/components/redesign/Chips'
import { COLORS, DOMAIN, Geltung } from '@/components/redesign/tokens'
// Maßnahme → Geltung: technische Schutzmaßnahmen = Pflicht; Info/Hardening-Guides
// = Empfehlung. Heuristik (kein Geltung-Feld in den Daten); nie still „Kann".
function measureGeltung(name: string, id: string): Geltung {
const hay = `${id} ${name}`.toLowerCase()
return /info|guide|hardening|dokumentation|beilegen|hinweis|schulung/.test(hay) ? 'empfehlung' : 'pflicht'
}
function ChainNode({ tone, marker, label, text }: {
tone: 'ce' | 'cyber' | 'residual'; marker: string; label: string; text: string
}) {
const s = tone === 'ce'
? { bg: DOMAIN.safety.tint, border: DOMAIN.safety.border, accent: DOMAIN.safety.accent }
: tone === 'cyber'
? { bg: DOMAIN.cyber.tint, border: DOMAIN.cyber.border, accent: DOMAIN.cyber.accent }
: { bg: '#FBECEA', border: '#F3D2CC', accent: '#A23323' }
return (
<div style={{ flex: 1, minWidth: 200, background: s.bg, border: `1px solid ${s.border}`, borderRadius: 12, padding: '12px 14px' }}>
<div className="font-publicSans" style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 10.5, fontWeight: 700, letterSpacing: '.06em', textTransform: 'uppercase', color: s.accent }}>
<span aria-hidden>{marker}</span>{label}
</div>
<div className="font-publicSans" style={{ marginTop: 6, fontSize: 12.5, color: COLORS.ink, lineHeight: 1.4 }}>{text}</div>
</div>
)
}
function HazardCard({ link, findings, measures, defaultOpen }: {
link: CrossLink; findings: CRAFinding[]; measures: Measure[]; defaultOpen: boolean
}) {
const [open, setOpen] = useState(defaultOpen)
const triggers = findings.filter((f) => link.cyber_finding_ids.includes(f.id))
const cyberText = triggers.map((f) => f.title).join(' · ') || link.cyber_finding_ids.join(', ')
const measureIds = Array.from(new Set(triggers.flatMap((f) => f.measures)))
const measureObjs = measureIds.map((id) => measures.find((m) => m.id === id) || { id, name: id, description: '', norm_refs: [] })
const normPills = Array.from(new Set(triggers.flatMap((f) => [f.annex_anchor, ...f.requirement_ids, ...f.iso27001_ref]).filter(Boolean)))
return (
<div style={{ background: COLORS.surface, border: `1px solid ${COLORS.border}`, borderRadius: 16, padding: 20 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12, flexWrap: 'wrap' }}>
<div>
<div className="font-publicSans" style={{ fontSize: 11, color: COLORS.muted2, fontWeight: 600 }}>Mechanische Gefährdung</div>
<h3 className="font-publicSans" style={{ fontSize: 17, fontWeight: 800, letterSpacing: '-0.01em', color: COLORS.ink, marginTop: 2 }}>{link.safety_hazard}</h3>
</div>
<span className="font-publicSans" style={{ display: 'inline-flex', alignItems: 'center', gap: 6, background: '#FBECEA', color: '#A23323', border: '1px solid #F3D2CC', borderRadius: 999, padding: '4px 11px', fontSize: 11.5, fontWeight: 700 }}>
<span aria-hidden style={{ width: 7, height: 7, borderRadius: 2, background: '#C0362C', display: 'inline-block' }} />
Restrisiko: wieder offen
</span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 14, flexWrap: 'wrap' }}>
<ChainNode tone="ce" marker="✓" label="CE — gemildert" text={link.original_measure} />
<span aria-hidden style={{ color: '#C5A86F', fontSize: 18 }}></span>
<ChainNode tone="cyber" marker="⚡" label="Cyber-Befund" text={cyberText} />
<span aria-hidden style={{ color: '#C5A86F', fontSize: 18 }}></span>
<ChainNode tone="residual" marker="!" label="Restrisiko" text="Schutzfunktion aushebelbar — Gefährdung wieder offen" />
</div>
<div style={{ marginTop: 14, background: '#FBFAF7', border: '1px solid #EFEADF', borderRadius: 11, padding: '12px 14px' }}>
<span className="font-publicSans" style={{ fontWeight: 700, color: DOMAIN.bridge.warn, fontSize: 12.5 }}>Warum: </span>
<span className="font-publicSans" style={{ color: COLORS.ink, fontSize: 13, lineHeight: 1.5 }}>{link.cyber_breaks_it}</span>
</div>
{measureObjs.length > 0 && (
<div style={{ marginTop: 14 }}>
<div className="font-publicSans" style={{ fontSize: 11, fontWeight: 700, letterSpacing: '.06em', textTransform: 'uppercase', color: COLORS.muted2, marginBottom: 8 }}>Empfohlene Maßnahmen</div>
<ul style={{ display: 'grid', gap: 8, margin: 0, padding: 0, listStyle: 'none' }}>
{measureObjs.map((m) => (
<li key={m.id} style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<GeltungChip value={measureGeltung(m.name, m.id)} />
<span className="font-publicSans" style={{ flex: 1, fontSize: 13, color: COLORS.ink }}>{m.name}</span>
<MonoId>{m.id}</MonoId>
</li>
))}
</ul>
</div>
)}
<div style={{ marginTop: 14, borderTop: `1px solid ${COLORS.borderSoft}`, paddingTop: 12 }}>
<button onClick={() => setOpen((v) => !v)} className="font-publicSans" style={{ background: 'none', border: 'none', cursor: 'pointer', color: COLORS.brandText, fontSize: 12.5, fontWeight: 600, padding: 0 }}>
{open ? '▾' : '▸'} Auslösende Befunde & Norm-Bezug (Ebene 3)
</button>
{open && (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2,1fr)', gap: 16, marginTop: 12 }}>
<div>
<div className="font-publicSans" style={{ fontSize: 10.5, fontWeight: 700, letterSpacing: '.06em', textTransform: 'uppercase', color: COLORS.muted2, marginBottom: 6 }}>Auslösende Cyber-Befunde</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
{triggers.map((f) => <MonoId key={f.id} className="rounded px-2 py-1" >{f.location || f.id}</MonoId>)}
</div>
</div>
<div>
<div className="font-publicSans" style={{ fontSize: 10.5, fontWeight: 700, letterSpacing: '.06em', textTransform: 'uppercase', color: COLORS.muted2, marginBottom: 6 }}>Norm- & Annex-Bezug</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
{normPills.map((n) => (
<span key={n} className="font-plexMono" style={{ fontSize: 11, background: COLORS.brandTint, color: COLORS.brandText, borderRadius: 6, padding: '3px 8px' }}>{n}</span>
))}
</div>
</div>
</div>
)}
</div>
</div>
)
}
export function CyberMeetsSafety({ data }: { data: CRADemo }) {
const links = data.cross_links || []
const measures = data.open_measures || []
const [filter, setFilter] = useState<'alle' | 'pflicht' | 'empfehlung'>('alle')
if (links.length === 0) {
return (
<div style={{ background: DOMAIN.cyber.tint, border: `1px solid ${DOMAIN.cyber.border}`, borderRadius: 14, padding: 18 }}>
<div className="font-publicSans" style={{ fontWeight: 700, color: COLORS.ink }}>Cyber trifft Safety</div>
<p className="font-publicSans" style={{ fontSize: 13, color: COLORS.muted, marginTop: 4 }}>
Aktuell keine Cyber-Befunde, die eine CE-Gefährdung wieder öffnen. Sobald Befunde vorliegen, erscheinen sie hier.
</p>
</div>
)
}
const backlog = measures.map((m) => ({ m, g: measureGeltung(m.name, m.id) }))
.filter((x) => filter === 'alle' || x.g === filter)
return (
<section style={{ display: 'grid', gap: 14 }}>
{/* Domänen-Bar */}
<div style={{ background: COLORS.surface, border: `1px solid ${COLORS.border}`, borderRadius: 14, padding: 16, display: 'flex', gap: 12, alignItems: 'stretch', flexWrap: 'wrap' }}>
<div style={{ flex: 1, minWidth: 180, borderLeft: `4px solid ${DOMAIN.safety.accent}`, paddingLeft: 12 }}>
<div className="font-publicSans" style={{ fontWeight: 700, color: DOMAIN.safety.accent, fontSize: 13 }}>Safety (Maschine / CE)</div>
<div className="font-publicSans" style={{ fontSize: 12, color: COLORS.muted, marginTop: 2 }}>Mechanisch gemilderte Gefährdungen</div>
</div>
<div style={{ minWidth: 180, background: DOMAIN.bridge.tint, border: `1px solid ${DOMAIN.bridge.border}`, borderRadius: 10, padding: '10px 14px', textAlign: 'center' }}>
<div className="font-publicSans" style={{ fontSize: 20 }} aria-hidden></div>
<div className="font-publicSans" style={{ fontWeight: 800, color: DOMAIN.bridge.warn, fontSize: 14 }}>{links.length} wieder geöffnet</div>
</div>
<div style={{ flex: 1, minWidth: 180, borderLeft: `4px solid ${DOMAIN.cyber.accent}`, paddingLeft: 12 }}>
<div className="font-publicSans" style={{ fontWeight: 700, color: DOMAIN.cyber.accent, fontSize: 13 }}>Cyber (CRA)</div>
<div className="font-publicSans" style={{ fontSize: 12, color: COLORS.muted, marginTop: 2 }}>Befunde, die Schutzfunktionen aushebeln</div>
</div>
</div>
{links.map((link, i) => (
<HazardCard key={i} link={link} findings={data.findings} measures={measures} defaultOpen={i === 0} />
))}
{/* Maßnahmen-Backlog */}
<div style={{ background: COLORS.surface, border: `1px solid ${COLORS.border}`, borderRadius: 14, padding: 18 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
<div className="font-publicSans" style={{ fontWeight: 800, color: COLORS.ink, fontSize: 15 }}>
Maßnahmen-Backlog <span style={{ fontWeight: 500, color: COLORS.faint }}>· {measures.length} Maßnahmen · nach Geltung</span>
</div>
<div style={{ display: 'flex', gap: 4, background: COLORS.page, borderRadius: 8, padding: 3 }}>
{(['alle', 'pflicht', 'empfehlung'] as const).map((f) => (
<button key={f} onClick={() => setFilter(f)} className="font-publicSans"
style={{ display: 'inline-flex', alignItems: 'center', gap: 6, border: 'none', cursor: 'pointer', borderRadius: 6, padding: '4px 10px', fontSize: 12, fontWeight: 600,
background: filter === f ? COLORS.surface : 'transparent', color: filter === f ? COLORS.ink : COLORS.muted }}>
{filter === f && <span aria-hidden style={{ width: 6, height: 6, borderRadius: 999, background: COLORS.brand, display: 'inline-block' }} />}
{f === 'alle' ? 'Alle' : f === 'pflicht' ? 'Pflicht' : 'Empfehlung'}
</button>
))}
</div>
</div>
<ul style={{ marginTop: 12, display: 'grid', gap: 2, padding: 0, listStyle: 'none' }}>
{backlog.map(({ m, g }) => (
<li key={m.id} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '8px 0', borderTop: `1px solid ${COLORS.borderSoft}` }}>
<span style={{ width: 96 }}><GeltungChip value={g} /></span>
<span className="font-publicSans" style={{ flex: 1, fontSize: 13, color: COLORS.ink }}>{m.name}</span>
<span className="font-publicSans" style={{ width: 200, fontSize: 12, color: COLORS.faint }}>{m.norm_refs?.[0] || 'Sicherheit'}</span>
<MonoId>{m.id}</MonoId>
</li>
))}
{backlog.length === 0 && <li className="font-publicSans" style={{ fontSize: 13, color: COLORS.faint, paddingTop: 8 }}>Keine Maßnahmen in diesem Filter.</li>}
</ul>
</div>
</section>
)
}
@@ -3,6 +3,7 @@
import { useParams } from 'next/navigation'
import { useCRA } from './_hooks/useCRA'
import { CRACyberView } from './_components/CRACyberView'
import { CyberMeetsSafety } from './_components/CyberMeetsSafety'
import { WeightsControl } from './_components/WeightsControl'
import { SnapshotPanel } from './_components/SnapshotPanel'
import { ScannerRepoPicker } from './_components/ScannerRepoPicker'
@@ -31,7 +32,22 @@ export default function CRAPage() {
)}
<ScannerRepoPicker value={scannerRepo} onChange={setScannerRepo} />
<WeightsControl weights={weights} onChange={setWeights} />
<CRACyberView data={data} scannerRepo={scannerRepo} />
{/* Ebene 2 — Cyber trifft Safety (Redesign, neue Design-Sprache) */}
<div>
<h2 className="font-publicSans" style={{ fontSize: 19, fontWeight: 800, letterSpacing: '-0.01em', color: '#1A1D29', marginBottom: 12 }}>
Cyber trifft Safety
</h2>
<CyberMeetsSafety data={data} />
</div>
{/* Bisherige Detailansicht (bleibt erhalten, bis das Redesign 100% abdeckt) */}
<details className="rounded-lg border border-gray-200 dark:border-gray-700">
<summary className="cursor-pointer px-4 py-2 text-sm text-gray-500">Bisherige Detailansicht (CRACyberView)</summary>
<div className="p-1">
<CRACyberView data={data} scannerRepo={scannerRepo} />
</div>
</details>
<SnapshotPanel snapshots={snapshots} onSave={saveSnapshot} onView={viewSnapshot} />
</div>
)
@@ -0,0 +1,64 @@
'use client'
// Reusable design-language chips. Geltung (Pflicht/Empfehlung/Kann) and Severity
// follow the handoff specs exactly (bg/text/border + marker glyph). Klartext label
// leads; internal IDs are never shown by the chip itself. See ./tokens.
import {
GELTUNG, PFLICHT_MARKER, SEVERITY, DOMAIN,
normalizeGeltung, normalizeSeverity, Geltung, Severity, Domain,
} from './tokens'
function GeltungMarker({ g }: { g: Geltung }) {
const m = GELTUNG[g].marker
if (m === 'square') {
return <span aria-hidden style={{ width: 7, height: 7, borderRadius: 2, background: PFLICHT_MARKER, display: 'inline-block' }} />
}
// diamond ◇ (Empfehlung) / circle ○ (Kann) — open glyphs in the chip text color
return <span aria-hidden style={{ fontSize: 10, lineHeight: 1 }}>{m === 'diamond' ? '◇' : '○'}</span>
}
export function GeltungChip({ value, className = '' }: { value: Geltung | string; className?: string }) {
const g = normalizeGeltung(value)
const t = GELTUNG[g]
return (
<span
className={`inline-flex items-center gap-1.5 rounded-md font-publicSans font-bold ${className}`}
style={{ background: t.bg, color: t.text, border: `1px solid ${t.border}`, padding: '3px 9px', fontSize: 11 }}
>
<GeltungMarker g={g} />
{t.label}
</span>
)
}
export function SeverityChip({ value, className = '' }: { value: Severity | string; className?: string }) {
const s = normalizeSeverity(value)
const t = SEVERITY[s]
return (
<span
className={`inline-flex items-center rounded-[7px] font-publicSans font-bold ${className}`}
style={{ background: t.bg, color: t.text, padding: '4px 11px', fontSize: 12 }}
>
{t.label}
</span>
)
}
// Small domain tag (Safety / Cyber / Schnittstelle) — tinted, not neon.
export function DomainTag({ value, label, className = '' }: { value: Domain; label?: string; className?: string }) {
const d = DOMAIN[value]
return (
<span
className={`inline-flex items-center rounded-md font-publicSans font-semibold ${className}`}
style={{ background: d.tint, color: d.warn || d.accent, border: `1px solid ${d.border}`, padding: '3px 9px', fontSize: 11 }}
>
{label || d.label}
</span>
)
}
// Monospace internal ID — IDs are ALWAYS secondary/nachgestellt, never a heading.
export function MonoId({ children, className = '' }: { children: React.ReactNode; className?: string }) {
return <span className={`font-plexMono ${className}`} style={{ fontSize: 11.5, color: '#A2A8B8' }}>{children}</span>
}
@@ -0,0 +1,55 @@
// Design-language tokens — single source of truth for the redesign.
// Mirrors design/redesign/HANDOFF_README.md (Claude Design handoff). The same
// values are mirrored into tailwind.config.ts (namespaces re/geltung/severity/domain)
// for utility-class use; components that need the exact chip look import from here.
export const COLORS = {
page: '#EDEFF3', surface: '#FFFFFF', border: '#E4E7EE', borderSoft: '#F0F1F5',
ink: '#1A1D29', muted: '#5A6273', muted2: '#6B7184', faint: '#8089A0', fainter: '#9AA1B2',
brand: '#4338CA', brandText: '#3B36B0', brandTint: '#EEF0FF', brandTint2: '#F6F4FF',
panel: '#15182A', panelText: '#E8EAF2', panelText2: '#C7CBDA', panelAccent: '#9B8BF5',
} as const
// --- Geltung: Pflicht / Empfehlung / Kann (the core 3-tier obligation system) ---
export type Geltung = 'pflicht' | 'empfehlung' | 'kann'
export const GELTUNG: Record<Geltung, {
label: string; bg: string; text: string; border: string; marker: 'square' | 'diamond' | 'circle'
}> = {
pflicht: { label: 'Pflicht', bg: '#FBECEA', text: '#A23323', border: '#F3D2CC', marker: 'square' },
empfehlung: { label: 'Empfehlung', bg: '#EEF0FF', text: '#3B36B0', border: '#DAD9F7', marker: 'diamond' },
kann: { label: 'Kann', bg: '#F1F3F7', text: '#5A6273', border: '#E2E6EE', marker: 'circle' },
}
export const PFLICHT_MARKER = '#C0362C' // filled square color
export function normalizeGeltung(v: string | Geltung): Geltung {
const s = String(v || '').toLowerCase()
if (['pflicht', 'mandatory', 'required', 'must', 'core'].includes(s)) return 'pflicht'
if (['empfehlung', 'recommended', 'should', 'review'].includes(s)) return 'empfehlung'
if (['kann', 'optional', 'may', 'can'].includes(s)) return 'kann'
return 'empfehlung' // unknown → recommendation (never silently drop; never over-state as Pflicht)
}
// --- Severity (Dringlichkeit) ---
export type Severity = 'kritisch' | 'hoch' | 'mittel' | 'niedrig'
export const SEVERITY: Record<Severity, { label: string; bg: string; text: string }> = {
kritisch: { label: 'Kritisch', bg: '#FBE9E7', text: '#B5362A' },
hoch: { label: 'Hoch', bg: '#FBF1E0', text: '#9A6410' },
mittel: { label: 'Mittel', bg: '#FAF6DD', text: '#897209' },
niedrig: { label: 'Niedrig', bg: '#E9F5EF', text: '#2C7A52' },
}
export function normalizeSeverity(v: string | Severity): Severity {
const s = String(v || '').toLowerCase()
if (['kritisch', 'critical'].includes(s)) return 'kritisch'
if (['hoch', 'high'].includes(s)) return 'hoch'
if (['mittel', 'medium', 'moderate'].includes(s)) return 'mittel'
if (['niedrig', 'low', 'minor'].includes(s)) return 'niedrig'
return 'mittel'
}
// --- Domains (Safety / Cyber / Schnittstelle) ---
export type Domain = 'safety' | 'cyber' | 'bridge'
export const DOMAIN: Record<Domain, { label: string; accent: string; tint: string; border: string; warn?: string }> = {
safety: { label: 'Safety (Maschine/CE)', accent: '#0E8A66', tint: '#F3FAF7', border: '#D7ECE3' },
cyber: { label: 'Cyber (CRA)', accent: '#6A43D6', tint: '#F6F1FE', border: '#E4D8F7' },
bridge: { label: 'Cyber × Safety', accent: '#BE7714', tint: '#FCF6EF', border: '#F2E6D5', warn: '#9A6410' },
}
+30
View File
@@ -48,9 +48,39 @@ const config: Config = {
900: '#0c4a6e',
950: '#082f49',
},
// === Redesign design-language tokens (2026-06, see design/redesign) ===
// Additive + namespaced ('re', 'geltung', 'severity', 'domain') so nothing
// existing is overridden. Single source of truth: components/redesign/tokens.ts.
re: {
page: '#EDEFF3', surface: '#FFFFFF', border: '#E4E7EE', 'border-soft': '#F0F1F5',
ink: '#1A1D29', muted: '#5A6273', 'muted-2': '#6B7184', faint: '#8089A0', fainter: '#9AA1B2',
brand: '#4338CA', 'brand-text': '#3B36B0', 'brand-tint': '#EEF0FF', 'brand-tint-2': '#F6F4FF',
panel: '#15182A', 'panel-text': '#E8EAF2', 'panel-text-2': '#C7CBDA', 'panel-accent': '#9B8BF5',
},
geltung: {
pflicht: { bg: '#FBECEA', text: '#A23323', border: '#F3D2CC', marker: '#C0362C' },
empfehlung: { bg: '#EEF0FF', text: '#3B36B0', border: '#DAD9F7' },
kann: { bg: '#F1F3F7', text: '#5A6273', border: '#E2E6EE' },
},
severity: {
kritisch: { bg: '#FBE9E7', text: '#B5362A' },
hoch: { bg: '#FBF1E0', text: '#9A6410' },
mittel: { bg: '#FAF6DD', text: '#897209' },
niedrig: { bg: '#E9F5EF', text: '#2C7A52' },
},
domain: {
safety: '#0E8A66', 'safety-tint': '#F3FAF7', 'safety-border': '#D7ECE3',
cyber: '#6A43D6', 'cyber-tint': '#F6F1FE', 'cyber-border': '#E4D8F7',
bridge: '#BE7714', 'bridge-tint': '#FCF6EF', 'bridge-border': '#F2E6D5', 'bridge-warn': '#9A6410',
},
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
// Redesign fonts (loaded via next/font in app/layout.tsx as CSS variables).
publicSans: ['var(--font-public-sans)', 'Inter', 'system-ui', 'sans-serif'],
sourceSerif: ['var(--font-source-serif)', 'Georgia', 'serif'],
plexMono: ['var(--font-plex-mono)', 'ui-monospace', 'SFMono-Regular', 'monospace'],
},
},
},
+501
View File
@@ -0,0 +1,501 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Public+Sans:ital,wght@0,400;0,500;0,600;0,700;0,800&family=Source+Serif+4:opsz,wght@8..60,400;8..60,500;8..60,600&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style>
*{box-sizing:border-box}
html,body{margin:0;padding:0}
body{background:#EDEFF3;font-family:'Public Sans',system-ui,-apple-system,sans-serif;-webkit-font-smoothing:antialiased;color:#1A1D29}
::selection{background:#DCD8FB}
a{color:inherit}
button{font-family:inherit}
@keyframes fadeUp{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
</style>
</helmet>
<div style="min-height:100vh;background:#EDEFF3;padding:30px 24px 90px;">
<div style="max-width:1320px;margin:0 auto;">
<!-- ============ HEADER / KONTEXT ============ -->
<div style="display:flex;align-items:center;gap:10px;font-size:12.5px;color:#6B7184;font-weight:500;margin-bottom:14px;">
<span style="display:inline-flex;align-items:center;gap:7px;font-weight:700;color:#3B36B0;">
<span style="width:22px;height:22px;border-radius:7px;background:#4338CA;display:inline-flex;align-items:center;justify-content:center;color:#fff;font-size:12px;"></span>
BreakPilot
</span>
<span style="color:#C3C7D2;">/</span>
<span>Produkt-Compliance</span>
<span style="color:#C3C7D2;">/</span>
<span style="color:#1A1D29;font-weight:600;">DEMO Kistenhub CRA</span>
<span style="margin-left:auto;display:inline-flex;align-items:center;gap:6px;padding:4px 11px;border-radius:999px;background:#F0EBFD;color:#5B34C7;font-weight:700;font-size:11.5px;letter-spacing:.02em;">REDESIGN-VORSCHLAG</span>
</div>
<div style="background:#fff;border:1px solid #E4E7EE;border-radius:18px;padding:26px 30px;box-shadow:0 1px 2px rgba(20,24,40,.04);display:flex;gap:30px;align-items:flex-end;flex-wrap:wrap;">
<div style="flex:1;min-width:340px;">
<div style="display:flex;align-items:center;gap:12px;margin-bottom:8px;">
<span style="display:inline-flex;align-items:center;gap:6px;padding:4px 11px;border-radius:999px;background:#F1F3F7;color:#5A6273;font-weight:600;font-size:12px;"><span style="width:7px;height:7px;border-radius:50%;background:#9AA1B2;"></span>Entwurf</span>
<span style="font-family:'IBM Plex Mono',monospace;font-size:12px;color:#9AA1B2;">V001</span>
</div>
<h1 style="margin:0;font-size:30px;font-weight:800;letter-spacing:-.02em;line-height:1.1;">DEMO Kistenhub CRA</h1>
<p style="margin:8px 0 0;font-size:15px;color:#5A6273;max-width:560px;line-height:1.5;">Vernetztes Kistenhubgerät mit App-Parametrierung und Fernwartung — Produkt-Compliance an der Schnittstelle von Maschinensicherheit&nbsp;(CE) und Cyber-Resilienz&nbsp;(CRA).</p>
<div style="margin-top:14px;display:inline-flex;align-items:center;gap:10px;padding:9px 14px;border-radius:11px;background:#F6F4FF;border:1px solid #E7E1FB;font-size:13.5px;color:#4A4570;">
<span style="font-size:15px;">🧭</span>
<span><strong style="color:#3B36B0;">Wir analysieren — Sie entscheiden</strong> mit DSB&nbsp;/&nbsp;Anwalt. Nichts wird automatisch als Verstoß gewertet.</span>
</div>
</div>
<div style="width:280px;">
<div style="display:flex;justify-content:space-between;align-items:baseline;margin-bottom:7px;">
<span style="font-size:12.5px;font-weight:600;color:#6B7184;">Konformität</span>
<span style="font-size:20px;font-weight:800;color:#4338CA;">38<span style="font-size:13px;">%</span></span>
</div>
<div style="height:8px;border-radius:999px;background:#ECEEF3;overflow:hidden;"><div style="width:38%;height:100%;background:linear-gradient(90deg,#5B52E0,#4338CA);border-radius:999px;"></div></div>
<div style="margin-top:14px;display:flex;gap:8px;">
<div style="flex:1;border:1px solid #E8EAF0;border-radius:11px;padding:10px 12px;">
<div style="font-size:19px;font-weight:800;letter-spacing:-.02em;">541</div>
<div style="font-size:11.5px;color:#6B7184;line-height:1.3;margin-top:2px;">Tage bis CE-Pflicht</div>
</div>
<div style="flex:1;border:1px solid #F2DFD0;background:#FCF6EF;border-radius:11px;padding:10px 12px;">
<div style="font-size:19px;font-weight:800;letter-spacing:-.02em;color:#9A6410;">85</div>
<div style="font-size:11.5px;color:#9A6410;line-height:1.3;margin-top:2px;">Tage bis Reporting-Pflicht</div>
</div>
</div>
</div>
</div>
<!-- ============ BODY: NAV + MAIN ============ -->
<div style="display:flex;gap:24px;margin-top:24px;align-items:flex-start;">
<!-- LEFT EBENEN-NAV -->
<div style="width:212px;flex:none;position:sticky;top:20px;">
<div style="font-size:11px;font-weight:700;letter-spacing:.08em;color:#9AA1B2;padding:0 6px 10px;">DREI EBENEN</div>
<button onClick="{{ goE1 }}" style="width:100%;text-align:left;background:none;border:none;cursor:pointer;padding:11px 12px;border-radius:11px;display:flex;gap:11px;align-items:flex-start;" style-hover="background:#fff;">
<span style="width:24px;height:24px;flex:none;border-radius:8px;background:#EEF0FF;color:#3B36B0;font-weight:800;font-size:12px;display:inline-flex;align-items:center;justify-content:center;">1</span>
<span><span style="display:block;font-weight:700;font-size:13.5px;">Überblick</span><span style="display:block;font-size:11.5px;color:#8089A0;margin-top:1px;">Management · Klartext</span></span>
</button>
<button onClick="{{ goE2 }}" style="width:100%;text-align:left;background:none;border:none;cursor:pointer;padding:11px 12px;border-radius:11px;display:flex;gap:11px;align-items:flex-start;" style-hover="background:#fff;">
<span style="width:24px;height:24px;flex:none;border-radius:8px;background:#FBF1DF;color:#9A6410;font-weight:800;font-size:12px;display:inline-flex;align-items:center;justify-content:center;">2</span>
<span><span style="display:block;font-weight:700;font-size:13.5px;">Cyber × Safety</span><span style="display:block;font-size:11.5px;color:#8089A0;margin-top:1px;">Die Schnittstelle</span></span>
</button>
<button onClick="{{ goE3 }}" style="width:100%;text-align:left;background:none;border:none;cursor:pointer;padding:11px 12px;border-radius:11px;display:flex;gap:11px;align-items:flex-start;" style-hover="background:#fff;">
<span style="width:24px;height:24px;flex:none;border-radius:8px;background:#EEF1F5;color:#5A6273;font-weight:800;font-size:12px;display:inline-flex;align-items:center;justify-content:center;">3</span>
<span><span style="display:block;font-weight:700;font-size:13.5px;">Technische Tiefe</span><span style="display:block;font-size:11.5px;color:#8089A0;margin-top:1px;">Normen · Audit</span></span>
</button>
<div style="height:1px;background:#E2E5EC;margin:14px 8px;"></div>
<button onClick="{{ goSys }}" style="width:100%;text-align:left;background:none;border:none;cursor:pointer;padding:11px 12px;border-radius:11px;display:flex;gap:11px;align-items:center;" style-hover="background:#fff;">
<span style="width:24px;height:24px;flex:none;border-radius:8px;background:#E9F5EF;color:#0E8A66;font-size:13px;display:inline-flex;align-items:center;justify-content:center;"></span>
<span style="font-weight:700;font-size:13.5px;">Design-Sprache</span>
</button>
</div>
<!-- MAIN -->
<div style="flex:1;min-width:0;">
<!-- ===== EBENE 1 — ÜBERBLICK ===== -->
<section ref="{{ refE1 }}">
<div style="display:flex;align-items:center;gap:10px;margin-bottom:14px;">
<span style="width:26px;height:26px;border-radius:8px;background:#EEF0FF;color:#3B36B0;font-weight:800;font-size:13px;display:inline-flex;align-items:center;justify-content:center;">1</span>
<h2 style="margin:0;font-size:19px;font-weight:800;letter-spacing:-.01em;">Überblick</h2>
<span style="font-size:13px;color:#8089A0;">Was trifft das Produkt — in einem Blick.</span>
</div>
<div style="display:grid;grid-template-columns:repeat(2,1fr);gap:14px;">
<!-- Tile: Geltung -->
<div style="background:#fff;border:1px solid #E4E7EE;border-radius:14px;padding:18px 20px;">
<div style="font-size:11.5px;font-weight:700;letter-spacing:.05em;color:#0E8A66;margin-bottom:8px;">GELTUNG</div>
<div style="font-size:17px;font-weight:700;letter-spacing:-.01em;line-height:1.25;">Fällt unter CRA <span style="color:#C3C7D2;">&</span> Maschinenverordnung</div>
<div style="font-size:13px;color:#5A6273;margin-top:7px;line-height:1.45;">Vernetzt &amp; mit Software-Updates → „Produkt mit digitalem Element".</div>
<button onClick="{{ goE3 }}" style="margin-top:11px;background:none;border:none;padding:0;cursor:pointer;font-size:12.5px;font-weight:600;color:#3B36B0;">Warum? → Begründung &amp; Quellen</button>
</div>
<!-- Tile: Frist -->
<div style="background:#fff;border:1px solid #E4E7EE;border-radius:14px;padding:18px 20px;">
<div style="font-size:11.5px;font-weight:700;letter-spacing:.05em;color:#9A6410;margin-bottom:8px;">FRISTEN</div>
<div style="font-size:17px;font-weight:700;letter-spacing:-.01em;line-height:1.25;">CE-Pflicht ab 11.12.2027</div>
<div style="font-size:13px;color:#5A6273;margin-top:7px;line-height:1.45;">Vulnerability-Reporting (24h&nbsp;/&nbsp;72h) bereits ab <strong style="color:#9A6410;">11.09.2026</strong> — in 85 Tagen.</div>
<div style="margin-top:11px;font-size:12.5px;color:#8089A0;">Maßgeblich: das Inverkehrbringen, nicht der Entwicklungszeitpunkt.</div>
</div>
<!-- Tile: Lage (attention) -->
<div style="background:#fff;border:1px solid #E4E7EE;border-radius:14px;padding:18px 20px;">
<div style="font-size:11.5px;font-weight:700;letter-spacing:.05em;color:#B5362A;margin-bottom:8px;">OFFENE PUNKTE</div>
<div style="font-size:17px;font-weight:700;letter-spacing:-.01em;line-height:1.25;">6 kritische Anforderungen offen</div>
<div style="font-size:13px;color:#5A6273;margin-top:7px;line-height:1.45;">von 40 CRA-Anforderungen. Dazu: <strong style="color:#BE7714;">2 Safety-Gefährdungen durch Cyber wieder geöffnet.</strong></div>
<button onClick="{{ goE2 }}" style="margin-top:11px;background:none;border:none;padding:0;cursor:pointer;font-size:12.5px;font-weight:600;color:#3B36B0;">Zu den 2 Punkten →</button>
</div>
<!-- Tile: Pfad (good) -->
<div style="background:#fff;border:1px solid #E4E7EE;border-radius:14px;padding:18px 20px;">
<div style="font-size:11.5px;font-weight:700;letter-spacing:.05em;color:#3B36B0;margin-bottom:8px;">KONFORMITÄTSPFAD</div>
<div style="font-size:17px;font-weight:700;letter-spacing:-.01em;line-height:1.25;">Selbstbewertung <span style="font-weight:500;color:#8089A0;">(Modul A)</span></div>
<div style="font-size:13px;color:#5A6273;margin-top:7px;line-height:1.45;">Kein externes Audit nötig. Technische Doku + Konformitätserklärung bleiben Pflicht.</div>
<div style="margin-top:11px;display:inline-flex;align-items:center;gap:6px;font-size:12.5px;font-weight:600;color:#0E8A66;"><span style="width:7px;height:7px;border-radius:50%;background:#0E8A66;"></span>Klassifikation: Standard</div>
</div>
</div>
</section>
<!-- ===== EBENE 2 — CYBER × SAFETY ===== -->
<section ref="{{ refE2 }}" style="margin-top:38px;">
<div style="display:flex;align-items:center;gap:10px;margin-bottom:6px;">
<span style="width:26px;height:26px;border-radius:8px;background:#FBF1DF;color:#9A6410;font-weight:800;font-size:13px;display:inline-flex;align-items:center;justify-content:center;">2</span>
<h2 style="margin:0;font-size:19px;font-weight:800;letter-spacing:-.01em;">Cyber trifft Safety</h2>
</div>
<p style="margin:0 0 16px 36px;font-size:13.5px;color:#5A6273;max-width:680px;line-height:1.5;">Der USP sichtbar gemacht: Wo ein <strong>Cyber-Befund</strong> eine bereits mechanisch <strong>gemilderte Gefährdung wieder aufreißt</strong> — der Schnittpunkt von Maschinenverordnung und Cyber Resilience Act.</p>
<!-- Domänen-Bar -->
<div style="display:flex;align-items:stretch;gap:0;background:#fff;border:1px solid #E4E7EE;border-radius:14px;overflow:hidden;margin-bottom:18px;flex-wrap:wrap;">
<div style="flex:1;min-width:220px;padding:16px 20px;border-left:4px solid #0E8A66;">
<div style="font-size:11.5px;font-weight:700;letter-spacing:.04em;color:#0E8A66;">MASCHINENSICHERHEIT · CE</div>
<div style="font-size:14px;color:#3A4053;margin-top:6px;line-height:1.45;">3 Gefährdungen bewertet · Risikomatrix: <strong>0 kritisch, 2 mittel</strong></div>
</div>
<div style="flex:none;display:flex;align-items:center;justify-content:center;padding:0 18px;background:#FCF6EF;border-left:1px solid #F2E6D5;border-right:1px solid #F2E6D5;">
<div style="text-align:center;">
<div style="font-size:22px;line-height:1;"></div>
<div style="font-size:11.5px;font-weight:700;color:#BE7714;margin-top:6px;max-width:120px;line-height:1.3;">2 wieder geöffnet</div>
</div>
</div>
<div style="flex:1;min-width:220px;padding:16px 20px;border-left:4px solid #6A43D6;">
<div style="font-size:11.5px;font-weight:700;letter-spacing:.04em;color:#6A43D6;">CYBER-RESILIENZ · CRA</div>
<div style="font-size:14px;color:#3A4053;margin-top:6px;line-height:1.45;">CRA-Readiness <strong>78%</strong> · 6 kritische Befunde offen</div>
</div>
</div>
<!-- HAZARD CARDS -->
<sc-for list="{{ hazards }}" as="hz" hint-placeholder-count="2">
<div style="background:#fff;border:1px solid #E4E7EE;border-radius:16px;padding:0;margin-bottom:14px;overflow:hidden;">
<!-- header strip -->
<div style="padding:18px 22px 0;">
<div style="display:flex;align-items:flex-start;gap:12px;">
<div style="flex:1;">
<div style="font-size:11.5px;font-weight:600;color:#8089A0;">{{ hz.domain }}</div>
<h3 style="margin:3px 0 0;font-size:18px;font-weight:800;letter-spacing:-.01em;">{{ hz.name }}</h3>
</div>
<span style="flex:none;display:inline-flex;align-items:center;gap:6px;padding:5px 11px;border-radius:999px;background:#FBECEA;color:#A23323;font-weight:700;font-size:12px;"><span style="width:7px;height:7px;border-radius:2px;background:#C0362C;"></span>Restrisiko: wieder offen</span>
</div>
</div>
<!-- chain -->
<div style="padding:16px 22px;display:flex;align-items:stretch;gap:12px;flex-wrap:wrap;">
<div style="flex:1;min-width:200px;border:1px solid #D7ECE3;background:#F3FAF7;border-radius:12px;padding:13px 15px;">
<div style="display:flex;align-items:center;gap:7px;font-size:11.5px;font-weight:700;color:#0E8A66;"><span style="width:16px;height:16px;border-radius:50%;background:#0E8A66;color:#fff;display:inline-flex;align-items:center;justify-content:center;font-size:10px;"></span>CE — GEMILDERT</div>
<div style="font-size:13.5px;color:#33503F;margin-top:7px;line-height:1.4;">{{ hz.ce }}</div>
</div>
<div style="flex:none;display:flex;align-items:center;color:#C5A86F;font-size:20px;"></div>
<div style="flex:1;min-width:200px;border:1px solid #E4D8F7;background:#F6F1FE;border-radius:12px;padding:13px 15px;">
<div style="display:flex;align-items:center;gap:7px;font-size:11.5px;font-weight:700;color:#6A43D6;"><span style="font-size:13px;"></span>CYBER-BEFUND</div>
<div style="font-size:13.5px;color:#473463;margin-top:7px;line-height:1.4;">{{ hz.cyber }}</div>
</div>
<div style="flex:none;display:flex;align-items:center;color:#C5A86F;font-size:20px;"></div>
<div style="flex:1;min-width:200px;border:1px solid #F2D9D4;background:#FCF3F1;border-radius:12px;padding:13px 15px;">
<div style="display:flex;align-items:center;gap:7px;font-size:11.5px;font-weight:700;color:#B5362A;"><span style="width:16px;height:16px;border-radius:50%;background:#C0362C;color:#fff;display:inline-flex;align-items:center;justify-content:center;font-size:11px;">!</span>RESTRISIKO</div>
<div style="font-size:13.5px;color:#7A2E26;margin-top:7px;line-height:1.4;">Schutzfunktion aushebelbar — Gefährdung wieder offen</div>
</div>
</div>
<!-- mechanism -->
<div style="padding:0 22px 16px;">
<div style="background:#FBFAF7;border:1px solid #EFEADF;border-radius:11px;padding:13px 16px;font-size:13.5px;color:#4A4536;line-height:1.5;"><strong style="color:#9A6410;">Warum:</strong> {{ hz.mechanism }}</div>
</div>
<!-- measures -->
<div style="padding:0 22px 18px;">
<div style="font-size:12px;font-weight:700;letter-spacing:.04em;color:#6B7184;margin-bottom:9px;">EMPFOHLENE MASSNAHMEN</div>
<sc-for list="{{ hz.measures }}" as="m" hint-placeholder-count="2">
<div style="display:flex;align-items:center;gap:11px;padding:9px 0;border-top:1px solid #F0F1F5;">
<sc-if value="{{ m.isPflicht }}" hint-placeholder-val="{{ true }}"><span style="flex:none;display:inline-flex;align-items:center;gap:5px;padding:3px 9px;border-radius:6px;background:#FBECEA;color:#A23323;border:1px solid #F3D2CC;font-weight:700;font-size:11px;"><span style="width:7px;height:7px;border-radius:2px;background:#C0362C;"></span>Pflicht</span></sc-if>
<sc-if value="{{ m.isEmpf }}"><span style="flex:none;display:inline-flex;align-items:center;gap:5px;padding:3px 9px;border-radius:6px;background:#EEF0FF;color:#3B36B0;border:1px solid #DAD9F7;font-weight:700;font-size:11px;">◇ Empfehlung</span></sc-if>
<span style="flex:1;font-size:14px;color:#272B38;">{{ m.text }}</span>
<span style="flex:none;font-family:'IBM Plex Mono',monospace;font-size:11.5px;color:#A2A8B8;">{{ m.id }}</span>
</div>
</sc-for>
</div>
<!-- tech detail toggle -->
<div style="border-top:1px solid #EEF0F4;background:#FBFBFC;padding:0 22px;">
<button onClick="{{ hz.toggle }}" style="width:100%;background:none;border:none;cursor:pointer;padding:13px 0;display:flex;align-items:center;gap:8px;font-size:12.5px;font-weight:600;color:#6B7184;">
<span style="font-family:'IBM Plex Mono',monospace;">{{ hz.caret }}</span> Auslösende Befunde &amp; Norm-Bezug (Ebene 3)
</button>
<sc-if value="{{ hz.open }}" hint-placeholder-val="{{ true }}">
<div style="padding:0 0 18px;display:grid;grid-template-columns:1fr 1fr;gap:18px;">
<div>
<div style="font-size:11px;font-weight:700;letter-spacing:.05em;color:#9AA1B2;margin-bottom:8px;">AUSLÖSENDE CYBER-BEFUNDE</div>
<div style="display:flex;flex-direction:column;gap:6px;">
<sc-for list="{{ hz.findings }}" as="f" hint-placeholder-count="3">
<span style="font-family:'IBM Plex Mono',monospace;font-size:11.5px;color:#5A6273;background:#fff;border:1px solid #E6E8EE;border-radius:6px;padding:5px 9px;">{{ f }}</span>
</sc-for>
</div>
</div>
<div>
<div style="font-size:11px;font-weight:700;letter-spacing:.05em;color:#9AA1B2;margin-bottom:8px;">NORM- &amp; ANNEX-BEZUG</div>
<div style="display:flex;flex-wrap:wrap;gap:6px;">
<sc-for list="{{ hz.norms }}" as="n" hint-placeholder-count="3">
<span style="font-family:'IBM Plex Mono',monospace;font-size:11.5px;color:#3B36B0;background:#F4F4FE;border:1px solid #E1E0F6;border-radius:6px;padding:5px 9px;">{{ n }}</span>
</sc-for>
</div>
</div>
</div>
</sc-if>
</div>
</div>
</sc-for>
<!-- MASSNAHMEN-BACKLOG mit Geltung-Filter -->
<div style="background:#fff;border:1px solid #E4E7EE;border-radius:16px;padding:20px 22px;margin-top:6px;">
<div style="display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-bottom:14px;">
<h3 style="margin:0;font-size:16px;font-weight:800;letter-spacing:-.01em;">Maßnahmen-Backlog</h3>
<span style="font-size:13px;color:#8089A0;">{{ backlogCount }} Maßnahmen · nach Geltung</span>
<div style="margin-left:auto;display:inline-flex;gap:6px;background:#F2F3F7;padding:4px;border-radius:10px;">
<sc-for list="{{ filters }}" as="f" hint-placeholder-count="3">
<button onClick="{{ f.setFn }}" style="background:none;border:none;cursor:pointer;padding:6px 13px;border-radius:7px;font-size:12.5px;font-weight:600;color:#5A6273;display:inline-flex;align-items:center;gap:6px;" style-hover="color:#1A1D29;">
<sc-if value="{{ f.active }}" hint-placeholder-val="{{ true }}"><span style="width:6px;height:6px;border-radius:50%;background:#4338CA;"></span></sc-if>
{{ f.label }}
</button>
</sc-for>
</div>
</div>
<sc-for list="{{ backlog }}" as="b" hint-placeholder-count="6">
<div style="display:flex;align-items:center;gap:12px;padding:11px 0;border-top:1px solid #F0F1F5;">
<sc-if value="{{ b.isPflicht }}" hint-placeholder-val="{{ true }}"><span style="flex:none;width:96px;display:inline-flex;align-items:center;gap:5px;padding:3px 9px;border-radius:6px;background:#FBECEA;color:#A23323;border:1px solid #F3D2CC;font-weight:700;font-size:11px;"><span style="width:7px;height:7px;border-radius:2px;background:#C0362C;"></span>Pflicht</span></sc-if>
<sc-if value="{{ b.isEmpf }}"><span style="flex:none;width:96px;display:inline-flex;align-items:center;gap:5px;padding:3px 9px;border-radius:6px;background:#EEF0FF;color:#3B36B0;border:1px solid #DAD9F7;font-weight:700;font-size:11px;">◇ Empfehlung</span></sc-if>
<sc-if value="{{ b.isKann }}"><span style="flex:none;width:96px;display:inline-flex;align-items:center;gap:5px;padding:3px 9px;border-radius:6px;background:#F1F3F7;color:#5A6273;border:1px solid #E2E6EE;font-weight:700;font-size:11px;">○ Kann</span></sc-if>
<span style="flex:1;font-size:14px;font-weight:500;color:#1A1D29;">{{ b.text }}</span>
<span style="flex:none;font-size:12.5px;color:#8089A0;width:150px;">{{ b.kat }}</span>
<span style="flex:none;font-size:12.5px;color:#5A6273;width:44px;text-align:right;">{{ b.aufwand }}</span>
<span style="flex:none;font-family:'IBM Plex Mono',monospace;font-size:11.5px;color:#A2A8B8;width:64px;text-align:right;">{{ b.id }}</span>
</div>
</sc-for>
</div>
</section>
<!-- ===== EBENE 3 — TECHNISCHE TIEFE ===== -->
<section ref="{{ refE3 }}" style="margin-top:38px;">
<div style="display:flex;align-items:center;gap:10px;margin-bottom:14px;flex-wrap:wrap;">
<span style="width:26px;height:26px;border-radius:8px;background:#EEF1F5;color:#5A6273;font-weight:800;font-size:13px;display:inline-flex;align-items:center;justify-content:center;">3</span>
<h2 style="margin:0;font-size:19px;font-weight:800;letter-spacing:-.01em;">Technische Tiefe</h2>
<span style="font-size:13px;color:#8089A0;">Für Auditor:innen &amp; Ingenieur:innen — standardmäßig eingeklappt.</span>
<button onClick="{{ toggleTech }}" style="margin-left:auto;background:#fff;border:1px solid #D7DAE3;cursor:pointer;padding:8px 14px;border-radius:9px;font-size:12.5px;font-weight:600;color:#272B38;" style-hover="border-color:#4338CA;color:#3B36B0;">{{ techLabel }}</button>
</div>
<sc-if value="{{ showTech }}">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:14px;">
<!-- Normen -->
<div style="background:#fff;border:1px solid #E4E7EE;border-radius:14px;padding:18px 20px;">
<div style="font-size:13px;font-weight:700;margin-bottom:12px;">Norm- &amp; Annex-Referenzen</div>
<div style="display:flex;align-items:center;gap:10px;padding:9px 0;border-top:1px solid #F0F1F5;">
<span style="font-family:'IBM Plex Mono',monospace;font-size:12px;color:#1A1D29;flex:1;">ISO 12100:2010</span>
<span style="padding:2px 8px;border-radius:5px;background:#FBECEA;color:#A23323;font-weight:700;font-size:10.5px;">Pflicht</span>
<span style="font-size:11.5px;color:#8089A0;">Grundnorm</span>
</div>
<div style="display:flex;align-items:center;gap:10px;padding:9px 0;border-top:1px solid #F0F1F5;">
<span style="font-family:'IBM Plex Mono',monospace;font-size:12px;color:#1A1D29;flex:1;">EN 62061</span>
<span style="padding:2px 8px;border-radius:5px;background:#E9F5EF;color:#0E8A66;font-weight:700;font-size:10.5px;">B-Norm</span>
<span style="font-size:11.5px;color:#8089A0;">SIL/Steuerung</span>
</div>
<div style="display:flex;align-items:center;gap:10px;padding:9px 0;border-top:1px solid #F0F1F5;">
<span style="font-family:'IBM Plex Mono',monospace;font-size:12px;color:#1A1D29;flex:1;">CRA · Annex I 1(3)(d)</span>
<span style="padding:2px 8px;border-radius:5px;background:#F4F4FE;color:#3B36B0;font-weight:700;font-size:10.5px;">CRA-AI-8</span>
<span style="font-size:11.5px;color:#8089A0;">Auth</span>
</div>
<div style="display:flex;align-items:center;gap:10px;padding:9px 0;border-top:1px solid #F0F1F5;">
<span style="font-family:'IBM Plex Mono',monospace;font-size:12px;color:#1A1D29;flex:1;">CRA · Annex I 1(4)</span>
<span style="padding:2px 8px;border-radius:5px;background:#F4F4FE;color:#3B36B0;font-weight:700;font-size:10.5px;">CRA-AI-29</span>
<span style="font-size:11.5px;color:#8089A0;">Updates</span>
</div>
<div style="margin-top:12px;background:#FBFAF7;border:1px solid #EFEADF;border-radius:9px;padding:10px 13px;font-family:'Source Serif 4',serif;font-size:12.5px;color:#5A5340;line-height:1.5;font-style:italic;">Normtexte werden nie reproduziert — nur Nummern, Titel &amp; Abschnitts­verweise. Beschaffung separat (z.&nbsp;B. beuth.de).</div>
</div>
<!-- Risiko-Formeln -->
<div style="background:#fff;border:1px solid #E4E7EE;border-radius:14px;padding:18px 20px;">
<div style="font-size:13px;font-weight:700;margin-bottom:12px;">Risiko-Modelle (zwei pro Gefährdung)</div>
<div style="border:1px solid #ECEEF3;border-radius:10px;padding:12px 14px;margin-bottom:10px;">
<div style="display:flex;justify-content:space-between;align-items:center;"><span style="font-weight:700;font-size:13px;">EN-62061-Stil</span><span style="padding:2px 8px;border-radius:5px;background:#FAF6DD;color:#897209;font-weight:700;font-size:10.5px;">mittel</span></div>
<div style="font-family:'IBM Plex Mono',monospace;font-size:12.5px;color:#3A4053;margin-top:8px;">R = S × (F + W + P) = 3 × (2+3+4) = <strong>27</strong></div>
</div>
<div style="border:1px solid #ECEEF3;border-radius:10px;padding:12px 14px;">
<div style="display:flex;justify-content:space-between;align-items:center;"><span style="font-weight:700;font-size:13px;">Fine-Kinney (US)</span><span style="padding:2px 8px;border-radius:5px;background:#FBF1E0;color:#9A6410;font-weight:700;font-size:10.5px;">möglich</span></div>
<div style="font-family:'IBM Plex Mono',monospace;font-size:12.5px;color:#3A4053;margin-top:8px;">R = P × E × C = 3 × 3 × 7 = <strong>63</strong></div>
</div>
<div style="margin-top:12px;font-size:12px;color:#8089A0;line-height:1.5;">Begründete Vorschlagswerte aus öffentlichen Datenquellen (ESAW/NIOSH/OSHA) — vom Sachverständigen anpassbar.</div>
</div>
<!-- Pipeline -->
<div style="grid-column:1 / -1;background:#fff;border:1px solid #E4E7EE;border-radius:14px;padding:18px 20px;">
<div style="display:flex;align-items:center;gap:10px;margin-bottom:12px;"><span style="font-size:13px;font-weight:700;">Deterministische Pipeline</span><span style="font-size:11.5px;color:#0E8A66;font-weight:600;background:#E9F5EF;padding:2px 8px;border-radius:5px;">reproduzierbar ohne LLM</span></div>
<div style="display:flex;flex-wrap:wrap;gap:7px;">
<sc-for list="{{ pipeline }}" as="p" hint-placeholder-count="8">
<span style="font-family:'IBM Plex Mono',monospace;font-size:11.5px;color:#5A6273;background:#F7F8FA;border:1px solid #E6E8EE;border-radius:6px;padding:5px 9px;">{{ p }}</span>
</sc-for>
</div>
</div>
</div>
</sc-if>
<sc-if value="{{ techHidden }}">
<div style="background:#fff;border:1px dashed #D7DAE3;border-radius:14px;padding:26px;text-align:center;color:#8089A0;font-size:13.5px;">Normen, Risiko-Formeln, Pipeline-Trace &amp; Nachweise — eingeklappt. <button onClick="{{ toggleTech }}" style="background:none;border:none;cursor:pointer;color:#3B36B0;font-weight:600;font-size:13.5px;padding:0;">Anzeigen</button></div>
</sc-if>
</section>
<!-- ===== DESIGN-SPRACHE ===== -->
<section ref="{{ refSys }}" style="margin-top:44px;">
<div style="display:flex;align-items:center;gap:10px;margin-bottom:6px;">
<span style="width:26px;height:26px;border-radius:8px;background:#E9F5EF;color:#0E8A66;font-size:14px;display:inline-flex;align-items:center;justify-content:center;"></span>
<h2 style="margin:0;font-size:19px;font-weight:800;letter-spacing:-.01em;">Design-Sprache</h2>
</div>
<p style="margin:0 0 16px 36px;font-size:13.5px;color:#5A6273;max-width:680px;line-height:1.5;">Ein konsistentes System für beide Zielgruppen — Anwält:innen/DSB und Ingenieur:innen.</p>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:14px;">
<!-- Geltung -->
<div style="background:#fff;border:1px solid #E4E7EE;border-radius:14px;padding:18px 20px;">
<div style="font-size:13px;font-weight:700;margin-bottom:13px;">Geltung — was ist verbindlich?</div>
<div style="display:flex;align-items:center;gap:11px;margin-bottom:11px;"><span style="flex:none;display:inline-flex;align-items:center;gap:5px;padding:3px 9px;border-radius:6px;background:#FBECEA;color:#A23323;border:1px solid #F3D2CC;font-weight:700;font-size:11px;"><span style="width:7px;height:7px;border-radius:2px;background:#C0362C;"></span>Pflicht</span><span style="font-size:13px;color:#5A6273;">Rechtlich zwingend — gefüllter Marker</span></div>
<div style="display:flex;align-items:center;gap:11px;margin-bottom:11px;"><span style="flex:none;display:inline-flex;align-items:center;gap:5px;padding:3px 9px;border-radius:6px;background:#EEF0FF;color:#3B36B0;border:1px solid #DAD9F7;font-weight:700;font-size:11px;">◇ Empfehlung</span><span style="font-size:13px;color:#5A6273;">Best Practice — Raute, offen</span></div>
<div style="display:flex;align-items:center;gap:11px;"><span style="flex:none;display:inline-flex;align-items:center;gap:5px;padding:3px 9px;border-radius:6px;background:#F1F3F7;color:#5A6273;border:1px solid #E2E6EE;font-weight:700;font-size:11px;">○ Kann</span><span style="font-size:13px;color:#5A6273;">Optional — neutraler Kreis</span></div>
</div>
<!-- Severity -->
<div style="background:#fff;border:1px solid #E4E7EE;border-radius:14px;padding:18px 20px;">
<div style="font-size:13px;font-weight:700;margin-bottom:13px;">Severity — wie dringend?</div>
<div style="display:flex;flex-wrap:wrap;gap:8px;">
<span style="padding:4px 11px;border-radius:7px;background:#FBE9E7;color:#B5362A;font-weight:700;font-size:12px;">Kritisch</span>
<span style="padding:4px 11px;border-radius:7px;background:#FBF1E0;color:#9A6410;font-weight:700;font-size:12px;">Hoch</span>
<span style="padding:4px 11px;border-radius:7px;background:#FAF6DD;color:#897209;font-weight:700;font-size:12px;">Mittel</span>
<span style="padding:4px 11px;border-radius:7px;background:#E9F5EF;color:#2C7A52;font-weight:700;font-size:12px;">Niedrig</span>
</div>
<div style="margin-top:14px;font-size:12px;color:#8089A0;line-height:1.5;">Getönte Flächen statt Neon-Blöcke — Severity informiert, sie schüchtert nicht ein. Rot ist echten kritischen Punkten und Fristen vorbehalten.</div>
</div>
<!-- Domänen -->
<div style="background:#fff;border:1px solid #E4E7EE;border-radius:14px;padding:18px 20px;">
<div style="font-size:13px;font-weight:700;margin-bottom:13px;">Domänen-Farben</div>
<div style="display:flex;gap:10px;">
<div style="flex:1;border-radius:10px;border:1px solid #D7ECE3;background:#F3FAF7;padding:12px;"><span style="display:block;width:18px;height:18px;border-radius:6px;background:#0E8A66;margin-bottom:8px;"></span><span style="font-size:12px;font-weight:700;color:#0E8A66;">Safety</span><span style="display:block;font-size:11px;color:#5A6273;margin-top:2px;">Maschine / CE</span></div>
<div style="flex:1;border-radius:10px;border:1px solid #E4D8F7;background:#F6F1FE;padding:12px;"><span style="display:block;width:18px;height:18px;border-radius:6px;background:#6A43D6;margin-bottom:8px;"></span><span style="font-size:12px;font-weight:700;color:#6A43D6;">Cyber</span><span style="display:block;font-size:11px;color:#5A6273;margin-top:2px;">CRA / Resilienz</span></div>
<div style="flex:1;border-radius:10px;border:1px solid #F2E6D5;background:#FCF6EF;padding:12px;"><span style="display:block;width:18px;height:18px;border-radius:6px;background:#BE7714;margin-bottom:8px;"></span><span style="font-size:12px;font-weight:700;color:#BE7714;">Schnittstelle</span><span style="display:block;font-size:11px;color:#5A6273;margin-top:2px;">Cyber × Safety</span></div>
</div>
</div>
<!-- Typo -->
<div style="background:#fff;border:1px solid #E4E7EE;border-radius:14px;padding:18px 20px;">
<div style="font-size:13px;font-weight:700;margin-bottom:13px;">Typografie</div>
<div style="margin-bottom:12px;"><div style="font-size:18px;font-weight:800;letter-spacing:-.01em;">Public Sans</div><div style="font-size:11.5px;color:#8089A0;">Klartext führt — Überschriften &amp; UI</div></div>
<div style="margin-bottom:12px;"><div style="font-family:'Source Serif 4',serif;font-size:17px;">Source Serif 4</div><div style="font-size:11.5px;color:#8089A0;">Normzitate &amp; rechtliche Texte</div></div>
<div><div style="font-family:'IBM Plex Mono',monospace;font-size:15px;">CRA-AI-8 · M542</div><div style="font-size:11.5px;color:#8089A0;">Interne IDs in Mono — nie als Überschrift</div></div>
</div>
<!-- Prinzipien -->
<div style="grid-column:1 / -1;background:#15182A;border:1px solid #15182A;border-radius:14px;padding:22px 24px;color:#E8EAF2;">
<div style="font-size:13px;font-weight:700;margin-bottom:16px;color:#fff;">Leitprinzipien</div>
<div style="display:grid;grid-template-columns:repeat(2,1fr);gap:16px 28px;">
<div style="display:flex;gap:11px;"><span style="color:#9B8BF5;font-size:15px;"></span><span style="font-size:13.5px;line-height:1.5;color:#C7CBDA;"><strong style="color:#fff;">3 Ebenen pro Screen</strong> — Überblick → Cyber×Safety → technische Tiefe einklappbar.</span></div>
<div style="display:flex;gap:11px;"><span style="color:#9B8BF5;font-size:15px;"></span><span style="font-size:13.5px;line-height:1.5;color:#C7CBDA;"><strong style="color:#fff;">Klartext führt</strong> — interne IDs nie als Überschrift, in Mono nachgestellt.</span></div>
<div style="display:flex;gap:11px;"><span style="color:#9B8BF5;font-size:15px;"></span><span style="font-size:13.5px;line-height:1.5;color:#C7CBDA;"><strong style="color:#fff;">Co-Pilot, kein Roboter-Anwalt</strong> — keine Panik-Rot-Blöcke, keine Bußgeld-Drohung zuerst.</span></div>
<div style="display:flex;gap:11px;"><span style="color:#9B8BF5;font-size:15px;"></span><span style="font-size:13.5px;line-height:1.5;color:#C7CBDA;"><strong style="color:#fff;">Pflicht / Empfehlung / Kann</strong> — immer visuell getrennt, durchgängig konsistent.</span></div>
</div>
</div>
</div>
</section>
</div>
</div>
</div>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props="{
&quot;$preview&quot;: { &quot;width&quot;: 1320, &quot;height&quot;: 1040 },
&quot;technikDefault&quot;: { &quot;editor&quot;: &quot;boolean&quot;, &quot;default&quot;: false, &quot;tsType&quot;: &quot;boolean&quot; },
&quot;startFilter&quot;: { &quot;editor&quot;: &quot;enum&quot;, &quot;options&quot;: [&quot;alle&quot;, &quot;pflicht&quot;, &quot;empfehlung&quot;], &quot;default&quot;: &quot;alle&quot;, &quot;tsType&quot;: &quot;string&quot; },
&quot;hazardOpen&quot;: { &quot;editor&quot;: &quot;enum&quot;, &quot;options&quot;: [&quot;erste&quot;, &quot;alle&quot;, &quot;keine&quot;], &quot;default&quot;: &quot;erste&quot;, &quot;tsType&quot;: &quot;string&quot; }
}">
class Component extends DCLogic {
state = {
showTech: this.props.technikDefault ?? false,
geltung: this.props.startFilter ?? 'alle',
open: this._initOpen(this.props.hazardOpen ?? 'erste')
};
_initOpen(mode) {
if (mode === 'alle') return { 0: true, 1: true };
if (mode === 'keine') return {};
return { 0: true };
}
_refs = {};
setRef(id) { return (el) => { if (el) this._refs[id] = el; }; }
scrollToEl(id) {
const el = this._refs[id];
if (el) window.scrollTo({ top: el.getBoundingClientRect().top + window.scrollY - 16, behavior: 'smooth' });
}
toggleTech() { this.setState(s => ({ showTech: !s.showTech })); }
toggleHazard(i) { this.setState(s => ({ open: { ...s.open, [i]: !s.open[i] } })); }
setGeltung(g) { this.setState({ geltung: g }); }
renderVals() {
const open = this.state.open;
const hazardData = [
{
name: 'Quetschen zwischen Last und Rahmen',
domain: 'Mechanische Gefährdung',
ce: 'Zweihandschaltung + trennende Schutzeinrichtung (PL d)',
cyber: 'Authentifizierungs-Umgehung · manipulierte Firmware',
mechanism: 'Über eine Authentifizierungs-Umgehung oder manipulierte Firmware lässt sich die Zweihandschaltung aushebeln — die mechanisch gemilderte Quetschgefahr ist damit wieder offen.',
findings: ['Bedienpanel_(HMI)-hmi-cleartext', 'Hub-Steuerung_(SPS)-ctrl-noauth', 'Fernwartungs-Gateway-gw-bruteforce'],
norms: ['ISO 12100:2010', 'EN 62061', 'CRA-AI-8 · Annex I 1(3)(d)', 'CRA-AI-29 · Annex I 1(4)'],
measures: [
{ text: 'Initiale Default-Passwörter beim ersten Start erzwingen', geltung: 'pflicht', id: 'M542' },
{ text: 'Signierte Firmware-Updates mit Rollback-Schutz', geltung: 'pflicht', id: 'M541' },
{ text: 'Cybersecurity-Hardening-Guide für den Anwender belegen', geltung: 'empfehlung', id: 'M-INFO-2' }
]
},
{
name: 'Lastabsturz durch Überlast',
domain: 'Mechanische Gefährdung',
ce: 'Überlastsicherung / Lastmomentbegrenzer',
cyber: 'manipulierte Signale / Parameter',
mechanism: 'Über manipulierte Firmware oder verfälschte Sensor-Parameter lässt sich die Überlastsicherung übersteuern — die mechanisch gemilderte Absturzgefahr ist damit wieder offen.',
findings: ['Hub-Steuerung_(SPS)-ctrl-fw', 'Fernwartungs-Gateway-gw-telemetry', 'Hub-Steuerung_(SPS)-ctrl-debug'],
norms: ['ISO 12100:2010', 'EN 62061', 'CRA-AI-30 · Annex I 1(4)', 'CRA-AI-15 · Annex I 1(3)(e)'],
measures: [
{ text: 'Updates über authentisierten Kanal mit Integritätsprüfung', geltung: 'pflicht', id: 'M547' },
{ text: 'Sichere Systemarchitektur mit Netzwerkzonen', geltung: 'pflicht', id: 'M601' },
{ text: 'Sensor-Plausibilisierung dokumentieren', geltung: 'empfehlung', id: 'M-INFO-5' }
]
}
];
const hazards = hazardData.map((h, i) => ({
...h,
open: !!open[i],
caret: open[i] ? '▾' : '▸',
toggle: () => this.toggleHazard(i),
measures: h.measures.map(m => ({ ...m, isPflicht: m.geltung === 'pflicht', isEmpf: m.geltung === 'empfehlung' }))
}));
const allBacklog = [
{ text: 'Keine Default-Passwörter', kat: 'Authentifizierung', id: 'M542', aufwand: '2 PT', geltung: 'pflicht' },
{ text: 'Software Bill of Materials (SBOM)', kat: 'Supply Chain', id: 'M540', aufwand: '2 PT', geltung: 'pflicht' },
{ text: 'Coordinated Vulnerability Disclosure', kat: 'Vulnerability Handling', id: 'M543', aufwand: '2 PT', geltung: 'pflicht' },
{ text: 'Signierte Updates mit Rollback-Schutz', kat: 'Updates', id: 'M541', aufwand: '3 PT', geltung: 'pflicht' },
{ text: 'Cybersecurity-Hardening-Guide für Anwender', kat: 'Information', id: 'M-INFO-2', aufwand: '3 PT', geltung: 'empfehlung' },
{ text: 'Sensor-Plausibilisierung dokumentieren', kat: 'Architektur', id: 'M-INFO-5', aufwand: '2 PT', geltung: 'kann' }
];
const g = this.state.geltung;
const backlog = (g === 'alle' ? allBacklog : allBacklog.filter(b => b.geltung === g))
.map(b => ({ ...b, isPflicht: b.geltung === 'pflicht', isEmpf: b.geltung === 'empfehlung', isKann: b.geltung === 'kann' }));
const filters = [
{ key: 'alle', label: 'Alle' },
{ key: 'pflicht', label: 'Pflicht' },
{ key: 'empfehlung', label: 'Empfehlung' }
].map(f => ({ ...f, active: g === f.key, setFn: () => this.setGeltung(f.key) }));
return {
hazards,
backlog,
backlogCount: backlog.length,
filters,
showTech: this.state.showTech,
techHidden: !this.state.showTech,
techLabel: this.state.showTech ? 'Technische Details ausblenden' : 'Technische Details anzeigen',
toggleTech: () => this.toggleTech(),
pipeline: ['1 · Grenzen-Formular', '2 · ParseNarrative', '3 · Pattern-Engine (Gates)', '4 · Relevanz-Backstop', '5 · Kategorie-Caps', '6 · Gefährdungen', '7 · Maßnahmen', '8 · Risiko (S/F/W/P)', '9 · Normen (A/B/C)', '10 · Risiko-Matrix / Benchmark'],
refE1: this.setRef('e1'), refE2: this.setRef('e2'), refE3: this.setRef('e3'), refSys: this.setRef('sys'),
goE1: () => this.scrollToEl('e1'), goE2: () => this.scrollToEl('e2'), goE3: () => this.scrollToEl('e3'), goSys: () => this.scrollToEl('sys')
};
}
}
</script>
</body>
</html>
+81
View File
@@ -0,0 +1,81 @@
# Content-Inventar & Mapping — Redesign-Guardrail (IACE + CRA + Gap)
> **Zweck:** Sicherstellen, dass das Frontend-Redesign **keine Inhalte verliert.**
> Jeder bestehende Screen/Tab ist hier mit seinen Inhalten erfasst und einem
> neuen Zuhause zugeordnet. Was kein klares Zuhause hat, steht unter
> **„Waisen / Aktiv entscheiden"** — nichts wird stillschweigend entfernt.
> Stand 2026-06-18. Quellen: `ux-screenshots/` + Code (`admin-compliance/app/sdk/…`).
## Redesign-Modell (so verlieren wir nichts)
1. **Design-Sprache (Tokens) auf ALLE Screens** anwenden — reines Re-Skin, kein Inhaltsverlust.
2. **Projekt-Dashboard → „Arbeitsbereich"** mit 3 Ebenen (Überblick → Cyber×Safety → Technik, eingeklappt). Konsolidiert die *Übersicht*, **verlinkt** in die Detail-Tabs.
3. **Detail-Tabs bleiben** als (re-skinned) Deep-Dive-Seiten, erreichbar aus dem Arbeitsbereich. **Keine Tab-Löschung** ohne explizite Freigabe.
4. **Additiv bauen:** alte Routen bleiben, bis der neue View nachweislich 100 % abdeckt.
Neue Heimat-Codes: **E1** = Ebene 1 Überblick · **E2** = Ebene 2 Cyber×Safety · **E3** = Ebene 3 Technik · **D** = bleibt Detail-/Deep-Dive-Seite (re-skinned) · **?** = Entscheidung nötig.
---
## IACE — Maschinensicherheit (CE)
| Screen / Tab | Wesentliche Inhalte (dürfen nicht verloren gehen) | Neue Heimat |
|---|---|---|
| `/iace` (Liste) | Projektkarten (Maschinenname/-typ/Hersteller, Status, Vollständigkeit %, Risiko-Summary), Schnellzugriff Linien+Bibliothek, Prozess-Flow, Neu-Projekt-Form, Init aus Firmenprofil | **D** (Projektliste, re-skinned) |
| `/iace/library` | Tabs Normen / Patterns / Maßnahmen / Dokumente (Counts) | **D** (intern/Referenz) |
| `/iace/lines` + `/lines/[lineId]` | Produktionslinien: Stationen, Aggregat-Panel, Stationsfluss/Transfer | **D** |
| `/iace/[projectId]` (Dashboard) | Statusworkflow, Maschineninfos, Risikozusammenfassung, 6-Schritt-Fortschritt, Compliance-Alerts, Varianten-Panel, Suggested-Norms, 8 Quick-Actions | **→ Arbeitsbereich E1** (Überblick) + Nav |
| `…/order` | Auftraggeber/Auftrag/Angebot/Notizen | **D** |
| `…/interview` (Grenzen) | ISO-12100-Grenzenformular (Bezeichnung, Zweck, Betriebsarten, Fehlanwendungen, räuml./zeitl. Grenzen, Personengruppen…), Init/Reinit | **D** (E1 verlinkt „Grenzen") |
| `…/clarifications` | Klärungen mit Anlagenbauer (Frage, Quelle, Status, Antwort, Begründung, Kommentare, Historie), CSV/PDF-Export, CRA-Banner | **D** |
| `…/classification` | Reg-Klassifikation (AI-Act/MaschVO/CRA/NIS2): Ergebnis, Risiko-Level, Konfidenz, Begründung | **E1** (Geltung) + **D** Detail |
| `…/components` | Komponentenbaum (SW/FW/AI/HMI/HW), present/nicht_vorhanden/gelöscht, CE-Marked, Bibliothek-Import | **D** |
| `…/hazards` | Hazard-Log, 4-Faktor-Risiko (S×F×P×A), Pattern-Match, Bibliothek, KI-Gap-Review, 3 Views (Liste/Risiko/Blöcke), Restrisiko-Filter | **D** (Kern-Detail) |
| `…/mitigations` | Maßnahmen (Design→Schutz→Information), Status, Relevanz, Norm-Refs, OSHA-Abstände, Vorschläge | **D** |
| `…/risikobewertung` | EN-62061 + Fine-Kinney-Modelle, Risiko-Matrix, ESAW/NIOSH/OSHA-Vorschläge, Live-Formeln | **E3** (Risiko-Modelle) + **D** |
| `…/fmea` | FMEA-Worksheet (S×O×D=RPZ), KI-Vorschlag je Komponente, Quelldokumente/Provenienz, Export | **D** |
| `…/operational-states` | 9 ISO-Betriebszustände, Übergänge, Delta-Analyse, Init | **D** |
| `…/verification` | Verifikation (Kundenstandard / Nachweis-Referenz), Stats, Verify-Modal | **D** |
| `…/norms` | Normenrecherche A/B/C, Suggested-Norms, Dokument-Upload | **E3** (Norm-Bezug) + **D** |
| `…/evidence` | Nachweise (Upload, Verknüpfung zu Maßnahmen/Verifikation) | **D** |
| `…/customer-standards` | Maßnahmen aus früheren Kundenprojekten übernehmen (Bulk) | **D** |
| `…/tech-file` | CE-Akte: Sektionen, Generierung, Approval, CID, Export (PDF/Word/HTML) | **D** |
| `…/knowledge-graph` | Komponenten→Gefährdung→Maßnahme als Graph | **E3** / **?** (Auditor-Tool) |
| `…/architektur` | Engine-Architektur, Pipeline-Stufen, Wissensbasen, Datenquellen/Lizenzen | **E3** / **?** (Auditor/intern) |
| `…/benchmark` | Ground-Truth-Vergleich (Coverage %, Korrelation, Abstände) | **?** (Dev/QA — intern gaten?) |
| `…/monitoring` | Post-Market: Ereignisse (Incident/Update/Reg-Change/Feedback), Stats, Resolve | **D** |
| `…/cra` (CE×Cyber) | **Bridge:** CE-Gefährdung durch Cyber wieder geöffnet; ScannerRepoPicker, Weights, CRACyberView, Snapshots | **→ Arbeitsbereich E2 (HERO)** + **D** |
---
## CRA — Cyber Resilience + Gap + Meldewesen
| Screen / Tab | Wesentliche Inhalte | Neue Heimat |
|---|---|---|
| `/gap-analysis` | Standortbestimmung: ProductWizard, GapDashboard (anwendbare Vorschriften, Lücken, Compliance %, Aufwand), Filter, DSMS-Archiv-CID | **E1**-Einstieg / **D** · **?** Überschneidung mit Intake/Scope |
| `/cra` (Liste) | ReadinessCheck (Producer-Type, Fragen, Verdict, Evidence → Klassifikation), DatasheetExtract, Projektkarten (Klassifikation, Konformitätspfad, Status), Neu-Modal | **D** (Liste + Eingangstür) |
| `/cra/[projectId]` (Dashboard) | Status-Stepper, KPIs (Annex-I-Count, Critical, Tage bis CE-Pflicht, Compliance %), Top-10-Backlog, Info-Karten (Intake/Klassifikation/Pfad/Status), Next-Step | **→ Arbeitsbereich E1** + Nav |
| `…/intake` | Software-Profil (Name, Zweck, Repo, Sprache, Flags: Firmware/Internet/Updates/PII/KRITIS) | **D** |
| `…/scope` | Annex-III/IV-Klassifikation, Begründung | **E1** (Geltung) + **D** |
| `…/path` | Konformitätspfad (Modul A / Harm. Norm / EUCC / Modul B+C), Details, Auswahl | **D** |
| `…/requirements` | 59 Annex-I-Anforderungen, Filter, ISO-27001-Mapping, Maßnahmen, Aufwand | **E3** + **D** |
| `…/backlog` | Priorisierung (Severity×Frist×Aufwand), Score, Maßnahmen, Jira-Export | **E2** (Maßnahmen-Backlog) + **D** |
| `…/checks` | Automatisierte Checks (security.txt/TLS/Update-Policy/CVD), Status, Run | **D** |
| `…/sbom` | SBOM-Upload (CycloneDX/SPDX), Versionen, Scan-Status | **D** |
| `…/vuln` | Schwachstellen + CRA-Meldefristen (24h/72h), CVSS, Reporter, Status | **D** (+ E2-Hinweis) |
| `…/documents` | CRA-Dok-Generierung (DoC, TechDoc, CVD-Policy, Update-Policy, SBOM-Report), Approval, Download | **D** |
| `…/monitoring` | CRA-Stichtage, Reporting-Verstöße, KPIs, Post-Market-Checkliste | **D** |
| `/cra-meldewesen` | Art.-14-Kaskade 24h/72h/14d, Vorfall-Form, Fristen-Countdown, ENISA-Export | **D** (eigener Workflow) |
---
## ⚠️ Waisen / Aktiv entscheiden (vor dem Bau)
1. **20-Tab-Konsolidierung (IACE):** Der „Arbeitsbereich" ist EIN Screen mit 3 Ebenen — IACE hat 20 Tabs. **Vorschlag:** Tabs bleiben als Detail-Seiten, im Arbeitsbereich nach den bestehenden Flow-Paketen gruppiert (Vorbereitung / Analyse / Doku / Betrieb) statt 20 gleichwertige Tabs. → **Bestätigen.**
2. **Auditor/Dev-Tabs `architektur`, `benchmark`, `knowledge-graph`:** kundenseitig sichtbar lassen oder intern gaten (wie Screening, per `visibleWhen`)? → **Entscheidung.**
3. **Ebene 2 im STANDALONE-CRA-Projekt:** Der echte CE×Cyber-Bridge braucht eine IACE-Maschine (Gefährdungen). Ein reines CRA-Projekt (nur Software) hat keine CE-Gefährdungen. **Was zeigt Ebene 2 dort?** (z. B. „nur Cyber-Risiken" + Hinweis „mit CE-Projekt verknüpfen"). → **Entscheidung.**
4. **Doppelte CRA-Sicht:** `/cra/[projectId]` (standalone CRA-Flow) **und** `/iace/[projectId]/cra` (CE×Cyber im Maschinenprojekt). Beide behalten + klar abgrenzen, oder zusammenführen? → **Entscheidung (kein Löschen ohne OK).**
5. **`gap-analysis` vs. `intake`/`scope`:** beide beantworten „welche Pflichten treffen das Produkt?". Beide behalten (Gap = produktübergreifend, Intake/Scope = pro CRA-Projekt) oder Einstieg zusammenführen? → **Entscheidung.**
6. **`library` (IACE):** Referenz-/Kuratierungs-Tool — kundenseitig oder intern? → **Entscheidung.**
## Abdeckungs-Zusage
Jeder oben gelistete Screen ist **entweder** einer Ebene (E1/E2/E3) zugeordnet **oder** bleibt als Detail-Seite (**D**) erhalten. Kein Element ist als „entfällt" markiert. Reduktionen passieren nur über die **Waisen-Liste** mit deiner ausdrücklichen Entscheidung.
+254
View File
@@ -0,0 +1,254 @@
# Handoff: BreakPilot Compliance — Redesign (Design-Sprache + Projekt-Arbeitsbereich)
> Sprache: Deutsch (UI-Texte sind Teil der Spezifikation). Diese README ist
> selbsttragend — sie reicht aus, um das Redesign ohne Kenntnis des
> ursprünglichen Gesprächs umzusetzen.
## Overview
Redesign der Compliance-Plattform „BreakPilot" (Produkt-Compliance physischer
Produkte mit digitalen Elementen — Verbindung von Maschinensicherheit/CE und
Cyber-Sicherheit/CRA). Ziel: eine Oberfläche, die **zwei Zielgruppen** gleichzeitig
bedient — (1) Jurist:innen/DSB (Nachvollziehbarkeit, Normbezug, Versionierung,
Pflicht-vs-Empfehlung) und (2) Ingenieur:innen (Bauteile, Gefährdungen,
Schutzfunktionen, konkrete Maßnahmen, ohne Jurist:in zu sein).
Dieser Handoff enthält die **durchgängige Design-Sprache** und einen
**vollständig spezifizierten Referenz-Screen** (Projekt-Arbeitsbereich), der alle
Prinzipien demonstriert — inklusive des USP „Cyber trifft Safety".
## About the Design Files
Die Datei `Arbeitsbereich.dc.html` ist eine **Design-Referenz in HTML** — ein
Prototyp, der Aussehen und Verhalten zeigt, **kein** direkt zu kopierender
Produktionscode. Aufgabe: dieses Design in der **bestehenden Codebasis**
(React/Next + eurem Styling-Stack) mit euren etablierten Patterns nachbauen.
Alle Layout-/Farb-/Typo-Werte stehen als Inline-Styles im Markup und sind hier
unten zusätzlich als Tokens dokumentiert.
> Hinweis zur Datei: `Arbeitsbereich.dc.html` ist als „Design Component" gebaut
> und braucht zum Rendern eine Plattform-Laufzeit (`support.js`), die **nicht**
> Teil dieses Pakets ist. Für die Umsetzung ist das egal — Struktur, Inline-Styles
> und Daten/Logik sind im Klartext lesbar. Die dynamischen Stellen sind als
> `{{ … }}`-Platzhalter markiert; die zugehörigen Daten stehen in der Logik-Klasse
> am Ende der Datei (`class Component extends DCLogic`).
## Fidelity
**High-fidelity.** Finale Farben (Hex), Typografie, Abstände und Interaktionen.
Pixelnah nachbauen, aber mit euren vorhandenen Komponenten/Bibliotheken.
---
## Leitprinzipien (gelten als Akzeptanzkriterien)
1. **3 Ebenen pro Screen** (progressive disclosure):
Ebene 1 Überblick (Management/Klartext) → Ebene 2 Cyber × Safety → Ebene 3
technische Tiefe (standardmäßig **eingeklappt**).
2. **Klartext führt.** Interne IDs (`CRA-AI-8`, `M542`) nie als Überschrift —
immer in Monospace **nachgestellt/sekundär**.
3. **Co-Pilot statt Roboter-Anwalt.** Keine Panik-Rot-Blöcke, keine
Bußgeld-Drohung zuerst. Leitsatz sichtbar: „Wir analysieren — Sie entscheiden
mit DSB/Anwalt."
4. **Pflicht / Empfehlung / Kann** immer visuell getrennt und durchgängig
konsistent (eigenes Chip-System, siehe Tokens).
5. **Status/Fortschritt sichtbar** (geführter Flow, nicht 30 gleichwertige Tabs).
6. Rot ist **echten kritischen Punkten und Fristen** vorbehalten — Severity wird
sonst über getönte Flächen kommuniziert, nicht über Neon-Blöcke.
---
## Design Tokens
### Farben — Neutrale / Flächen
| Token | Hex | Verwendung |
|---|---|---|
| `bg/page` | `#EDEFF3` | Seitenhintergrund |
| `surface` | `#FFFFFF` | Karten |
| `border` | `#E4E7EE` | Standard-Kartenrahmen |
| `border/soft` | `#F0F1F5` | Trennlinien in Listen |
| `ink` | `#1A1D29` | Primärtext |
| `text/muted` | `#5A6273` | Fließtext sekundär |
| `text/muted-2` | `#6B7184` | Labels |
| `text/faint` | `#8089A0` | Hilfstext |
| `text/fainter` | `#9AA1B2` | Versions-/Metainfo |
### Farben — Marke (Indigo)
| Token | Hex | Verwendung |
|---|---|---|
| `primary` | `#4338CA` | Primär-Button-Fläche |
| `primary/text` | `#3B36B0` | Links, aktive Zustände, betonte Marke |
| `primary/grad` | `#5B52E0 → #4338CA` | Fortschrittsbalken (linear 90deg) |
| `primary/tint` | `#EEF0FF` | Chip-/Badge-Hintergrund |
| `primary/tint-2` | `#F6F4FF` / `#F0EBFD` | Co-Pilot-Banner / Marken-Badge |
### Farben — Geltung (Pflicht / Empfehlung / Kann) — Kernsystem
| Stufe | bg | text | border | Marker |
|---|---|---|---|---|
| **Pflicht** | `#FBECEA` | `#A23323` | `#F3D2CC` | gefülltes Quadrat `#C0362C` (7×7px, radius 2px) |
| **Empfehlung** | `#EEF0FF` | `#3B36B0` | `#DAD9F7` | Glyph `◇` (offene Raute) |
| **Kann** | `#F1F3F7` | `#5A6273` | `#E2E6EE` | Glyph `○` (offener Kreis) |
Chip-Form: `padding:3px 9px; border-radius:6px; font-weight:700; font-size:11px; 1px solid <border>`.
### Farben — Severity (Dringlichkeit)
| Stufe | bg | text |
|---|---|---|
| Kritisch | `#FBE9E7` | `#B5362A` |
| Hoch | `#FBF1E0` | `#9A6410` |
| Mittel | `#FAF6DD` | `#897209` |
| Niedrig | `#E9F5EF` | `#2C7A52` |
Chip-Form: `padding:4px 11px; border-radius:7px; font-weight:700; font-size:12px`. Getönte Flächen, kein Neon.
### Farben — Domänen
| Domäne | Akzent | bg-tint | border |
|---|---|---|---|
| **Safety** (Maschine/CE) | `#0E8A66` | `#F3FAF7` | `#D7ECE3` |
| **Cyber** (CRA/Resilienz) | `#6A43D6` | `#F6F1FE` | `#E4D8F7` |
| **Schnittstelle** (Cyber × Safety) | `#BE7714` | `#FCF6EF` | `#F2E6D5` (Warntext `#9A6410`) |
### Dark-Panel (Leitprinzipien-Block)
bg `#15182A` · Text `#E8EAF2` (stark) / `#C7CBDA` (normal) · Akzent-Ziffern `#9B8BF5`.
### Typografie
- **Public Sans** (Google Fonts; 400/500/600/700/800) — gesamte UI & Überschriften.
- **Source Serif 4** (Google Fonts; italic) — Normzitate / rechtliche Texte.
- **IBM Plex Mono** (Google Fonts; 400/500/600) — interne IDs, Codes, Risiko-Formeln.
| Rolle | Größe / Gewicht / Tracking |
|---|---|
| H1 (Projektname) | 30px / 800 / -0.02em / line-height 1.1 |
| H2 (Ebenen-Titel) | 19px / 800 / -0.01em |
| Kartentitel | 1718px / 700800 / -0.01em |
| Body | 1315px / 400500 / line-height 1.451.5 |
| Label/Caption | 11.512.5px / 600700, `letter-spacing:.04.08em` für Großbuchstaben-Labels |
| Mono (IDs) | 11.512.5px |
### Radius / Schatten / Spacing
- Radius: Header `18px` · Hazard-Karten `16px` · Standardkarten `14px` · innere Boxen `1011px` · Controls `710px` · Pills/Chips `6px`/`999px`.
- Schatten: nur sehr dezent — `0 1px 2px rgba(20,24,40,.04)` auf der Header-Karte; Karten sonst flach mit Border.
- Spacing-Basis 8px. Karten-Gaps `14px`; Sektionsabstand `margin-top:3844px`; Karten-Padding `1820px` (Standard) bzw. `2226px` (groß).
---
## Layout
- Container: `max-width:1320px; margin:0 auto`. Seiten-Padding `30px 24px 90px`.
- **Header-Karte** (volle Breite): links Projektinfo (Status-Pill „Entwurf", Version
`V001` in Mono, H1, Beschreibung, Co-Pilot-Banner), rechts (280px) Konformitäts-%
+ Fortschrittsbalken + zwei Frist-Kacheln (Tage bis CE-Pflicht / Tage bis
Reporting-Pflicht).
- **Body**: `display:flex; gap:24px; align-items:flex-start`.
- **Linke Nav** (212px, `position:sticky; top:20px`): die drei Ebenen als
Sprungmarken (nummerierte Quadrat-Icons in den Ebenen-Farben) + „Design-Sprache".
Klick scrollt sanft zur Sektion (`window.scrollTo`, **nicht** `scrollIntoView`).
- **Main** (`flex:1`): vier Sektionen gestapelt (Ebene 1 → 2 → 3 → Design-Sprache).
---
## Screens / Views (eine Seite, vier Sektionen)
### Sektion „Ebene 1 — Überblick"
- **Zweck:** Management-Blick: Was trifft das Produkt?
- **Layout:** 2×2-Grid (`grid-template-columns:repeat(2,1fr); gap:14px`) aus 4 Kacheln.
- **Kacheln** (je: farbiges Großbuchstaben-Label, plakative Klartext-Aussage,
1 Zeile Erklärung, optional „Warum?"-Link in `primary/text`):
1. **GELTUNG** (Label `#0E8A66`): „Fällt unter CRA & Maschinenverordnung" · „Vernetzt & mit Software-Updates → Produkt mit digitalem Element." · Link „Warum? → Begründung & Quellen".
2. **FRISTEN** (Label `#9A6410`): „CE-Pflicht ab 11.12.2027" · „Vulnerability-Reporting (24h/72h) bereits ab **11.09.2026** — in 85 Tagen." · „Maßgeblich: das Inverkehrbringen, nicht der Entwicklungszeitpunkt."
3. **OFFENE PUNKTE** (Label `#B5362A`): „6 kritische Anforderungen offen" · „von 40 CRA-Anforderungen. Dazu: **2 Safety-Gefährdungen durch Cyber wieder geöffnet.**" (Hervorhebung in `#BE7714`) · Link „Zu den 2 Punkten →".
4. **KONFORMITÄTSPFAD** (Label `#3B36B0`): „Selbstbewertung (Modul A)" · „Kein externes Audit nötig. Technische Doku + Konformitätserklärung bleiben Pflicht." · grüner Status-Dot „Klassifikation: Standard".
### Sektion „Ebene 2 — Cyber trifft Safety" (Herzstück / USP)
- **Zweck:** sichtbar machen, wo ein **Cyber-Befund** eine bereits mechanisch
**gemilderte Gefährdung wieder aufreißt**.
- **Domänen-Bar** (eine Karte, 3 Spalten, `flex`): links Safety-Panel (Border-left
4px `#0E8A66`), Mitte Schnittstellen-Knoten (bg `#FCF6EF`, ⚡-Icon, „2 wieder
geöffnet" in `#BE7714`), rechts Cyber-Panel (Border-left 4px `#6A43D6`).
- **Hazard-Karten** (eine pro betroffener Gefährdung), Aufbau von oben nach unten:
1. **Kopf:** kleines Domänen-Label (z. B. „Mechanische Gefährdung", grau) + H3
Klartext-Name; rechts Pill „Restrisiko: wieder offen" (bg `#FBECEA`, Text
`#A23323`, gefülltes Quadrat-Marker).
2. **Kette (3 Knoten + Pfeile `→`):**
`[CE — GEMILDERT]` (Safety-tint, ✓-Kreis, z. B. „Zweihandschaltung +
trennende Schutzeinrichtung (PL d)") → `[CYBER-BEFUND]` (Cyber-tint, ⚡, z. B.
„Authentifizierungs-Umgehung · manipulierte Firmware") → `[RESTRISIKO]`
(Severity-rot-tint, !-Kreis, „Schutzfunktion aushebelbar — Gefährdung wieder
offen"). Knoten `flex:1; min-width:200px; border-radius:12px`, Pfeile in
`#C5A86F`. Auf schmal umbrechend.
3. **„Warum:"-Box** (Schnittstellen-tint `#FBFAF7`/`#EFEADF`): Mechanismus in
Klartext, z. B. „Über eine Authentifizierungs-Umgehung oder manipulierte
Firmware lässt sich die Zweihandschaltung aushebeln — die mechanisch
gemilderte Quetschgefahr ist damit wieder offen."
4. **EMPFOHLENE MASSNAHMEN:** Liste, je Zeile [Geltung-Chip] + Klartext-Maßnahme
+ Mono-ID rechts (`#A2A8B8`).
5. **Ebene-3-Aufklapper** (Fuß der Karte, bg `#FBFBFC`): Button „▸/▾ Auslösende
Befunde & Norm-Bezug (Ebene 3)". Aufgeklappt: 2 Spalten —
„AUSLÖSENDE CYBER-BEFUNDE" (Mono-Pills, z. B. `Hub-Steuerung_(SPS)-ctrl-noauth`)
und „NORM- & ANNEX-BEZUG" (Mono-Pills in `primary`-tint, z. B.
`ISO 12100:2010`, `CRA-AI-8 · Annex I 1(3)(d)`).
- **Maßnahmen-Backlog** (Karte): Titel + Zähler „{n} Maßnahmen · nach Geltung";
rechts Segmented-Filter **Alle / Pflicht / Empfehlung** (aktiv = gefüllter
Indigo-Dot vor dem Label). Tabelle: [Geltung-Chip 96px] · Maßnahme · Kategorie
(150px, grau) · Aufwand (z. B. „2 PT") · Mono-ID rechts. Filter blendet Zeilen
nach Geltung.
### Sektion „Ebene 3 — Technische Tiefe"
- **Zweck:** Auditor:innen & Ingenieur:innen; standardmäßig **eingeklappt**.
- Toggle-Button rechts „Technische Details anzeigen/ausblenden". Eingeklappt: gestrichelte
Platzhalter-Karte mit Inline-„Anzeigen". Aufgeklappt: 2-Spalten-Grid mit
- **Norm- & Annex-Referenzen** (Tabelle: Mono-Norm-ID · Geltungs-/Typ-Badge ·
Kurzlabel) + Serif-Hinweis kursiv: „Normtexte werden nie reproduziert — nur
Nummern, Titel & Abschnittsverweise."
- **Risiko-Modelle**: zwei Boxen, Formeln in Mono — `R = S × (F + W + P) = 27`
(EN-62061-Stil) und `R = P × E × C = 63` (Fine-Kinney).
- **Deterministische Pipeline** (volle Breite): 10 Mono-Step-Pills + grünes Badge
„reproduzierbar ohne LLM".
### Sektion „Design-Sprache"
Referenz-Legende: Geltung (3 Chips + Bedeutung), Severity (4 Chips), Domänen-Farben
(3 Swatches), Typografie (3 Specimens), und ein dunkler **Leitprinzipien**-Block (4 Punkte).
---
## Interactions & Behavior
- **Linke Nav → Sprung:** sanftes Scrollen zur Sektion via
`window.scrollTo({top: el.getBoundingClientRect().top + scrollY - 16, behavior:'smooth'})`.
**Niemals `scrollIntoView`.**
- **Hazard-Aufklapper:** pro Karte unabhängig auf/zu (Caret ▸/▾).
- **Technik-Toggle:** globaler Show/Hide für Ebene-3-Inhalte; Button-Label wechselt.
- **Geltung-Filter:** Alle/Pflicht/Empfehlung filtert den Backlog; aktiver Zustand
via gefülltem Indigo-Dot (kein Layout-Shift).
- **Hover:** Nav-Items bekommen weißen Hintergrund; Buttons leichte
Farb-/Rahmen-Verschiebung. Transitionen ~120160ms.
## State Management
| State | Typ | Trigger / Default |
|---|---|---|
| `showTech` | bool | Technik-Toggle · default `false` |
| `geltung` | `'alle' \| 'pflicht' \| 'empfehlung'` | Filter-Buttons · default `'alle'` |
| `open[hazardIndex]` | bool-Map | Hazard-Aufklapper · default: erste Karte offen |
Konfigurierbare Defaults (im Prototyp als Props): `technikDefault`,
`startFilter`, `hazardOpen` (`erste`/`alle`/`keine`).
## Beispiel-Daten (im Prototyp hinterlegt — als Platzhalter ersetzen)
Zwei Hazards: „Quetschen zwischen Last und Rahmen" (CE: Zweihandschaltung +
trennende Schutzeinrichtung PL d; Cyber: Authentifizierungs-Umgehung/Firmware;
Maßnahmen M542 Pflicht, M541 Pflicht, M-INFO-2 Empfehlung) und „Lastabsturz durch
Überlast" (CE: Überlastsicherung/Lastmomentbegrenzer; Cyber: manipulierte
Signale/Parameter; Maßnahmen M547 Pflicht, M601 Pflicht, M-INFO-5 Empfehlung).
Backlog-Top-6, Norm-Refs und Pipeline-Steps siehe `renderVals()` in der Datei.
## Assets
Keine Bilddateien. Icons sind Unicode-Glyphen (◈ ◑ ◇ ○ ✓ ! ⚡ → 🧭) — im
Zielsystem durch euer Icon-Set ersetzen. Fonts via Google Fonts (s. o.).
## Files
- `Arbeitsbereich.dc.html` — Referenz-Markup (Inline-Styles) + Daten/Logik am Ende.
---
## Vorschlag für den ersten Claude-Code-Prompt
> „Implementiere die Sektion *Ebene 2 — Cyber trifft Safety* aus
> `design_handoff_breakpilot_redesign/README.md` als React-Komponente in unserem
> bestehenden Stack. Nutze unsere vorhandenen Card-/Badge-/Button-Komponenten,
> aber führe das **Geltung-Chip-System** (Pflicht/Empfehlung/Kann) und die
> **Domänen-Farben** (Safety/Cyber/Schnittstelle) exakt nach den Tokens ein.
> Halte die 3-Ebenen-Hierarchie und den Co-Pilot-Ton ein. IDs nur in Monospace
> nachgestellt, nie als Überschrift."