diff --git a/app/[locale]/(portal)/journey/path-nodes.ts b/app/[locale]/(portal)/journey/path-nodes.ts index f1ed563..e4086a7 100644 --- a/app/[locale]/(portal)/journey/path-nodes.ts +++ b/app/[locale]/(portal)/journey/path-nodes.ts @@ -32,6 +32,17 @@ export type FlowNode = { signOff: { signed: number; total: number }; }; +/** Journey rollup counts. Single source of truth for the aggregate shape + * shared by the board, the preview sample data, and the journey router. */ +export type Aggregate = { + total: number; + done: number; + awaitingSignoff: number; + overdue: number; + dueSoon: number; + open: number; +}; + /** Swimlane columns, in display order. "Management" matches the §38 language. */ export const COLUMNS: { key: ColumnKey; diff --git a/app/[locale]/journey-preview/JourneyBoard.tsx b/app/[locale]/journey-preview/JourneyBoard.tsx new file mode 100644 index 0000000..11f7db8 --- /dev/null +++ b/app/[locale]/journey-preview/JourneyBoard.tsx @@ -0,0 +1,499 @@ +"use client"; + +import { useState } from "react"; +import { useTranslations } from "next-intl"; +import { + ArrowRight, + Check, + ChevronDown, + Clock, + HelpCircle, + RefreshCw, + ShieldCheck, + Sparkles, + AlertTriangle, + ListOrdered, + Flame, +} from "lucide-react"; +import { cn } from "@/lib/utils"; +import { Link } from "@/i18n/navigation"; +import { Button } from "@/components/ui/button"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import type { FlowNode, Band, Aggregate } from "../(portal)/journey/path-nodes"; + +type View = "critical" | "chrono"; + +/** Link target for a single requirement — its own detail page, not the group. */ +function reqHref(n: FlowNode): string { + return `/compliance/${n.categorySlug}/${n.code}`; +} + +/* Chronological (natural process) order by category, matching the platform's + phase sequence: REG -> GOV RSK SUP -> CRY ACC AUT -> PRO INC BCP -> TRN EFF. + Keyed by the numeric code prefix. */ +const CHRONO_RANK: Record = { + "12": 0, "1": 1, "2": 2, "8": 3, "4": 4, "5": 5, "11": 6, "7": 7, "3": 8, "6": 9, "10": 10, "9": 11, +}; +function chronoKey(n: FlowNode): number { + const [cat, sub] = n.code.split("."); + return (CHRONO_RANK[cat] ?? 99) * 100 + Number(sub ?? 0); +} + +/* ------------------------------------------------------------------ */ +/* Coverage — the requirement's real status as a position on a scale. */ +/* `prepared` is the real `needs_review` state (auto-drafted, waiting */ +/* for the user's sign-off); text comes from the `journeyBoard` i18n. */ +/* ------------------------------------------------------------------ */ +type Coverage = "done" | "current" | "prepared" | "open" | "overdue"; + +function coverageOf(n: FlowNode): Coverage { + if (n.status === "current") return "current"; + if (n.isOverdue) return "overdue"; + if (n.status === "done") return "done"; + if (n.rawStatus === "needs_review") return "prepared"; + return "open"; +} + +const COVERAGE_STYLE: Record< + Coverage, + { dot: string; chip: string; Icon: typeof Check } +> = { + done: { + dot: "bg-emerald-500 text-white", + chip: "text-emerald-700 bg-emerald-50 ring-emerald-600/20 dark:text-emerald-300 dark:bg-emerald-500/10", + Icon: Check, + }, + current: { + dot: "bg-primary text-primary-foreground ring-4 ring-primary/15", + chip: "text-primary bg-primary/10 ring-primary/25", + Icon: ArrowRight, + }, + prepared: { + dot: "bg-violet-500 text-white", + chip: "text-violet-700 bg-violet-50 ring-violet-600/20 dark:text-violet-300 dark:bg-violet-500/10", + Icon: Sparkles, + }, + open: { + dot: "bg-muted-foreground/20 text-muted-foreground", + chip: "text-muted-foreground bg-muted ring-border", + Icon: ChevronDown, + }, + overdue: { + dot: "bg-amber-500 text-white", + chip: "text-amber-700 bg-amber-50 ring-amber-600/20 dark:text-amber-300 dark:bg-amber-500/10", + Icon: AlertTriangle, + }, +}; + +/* Owner — team as a calm, brand-cohesive chip (no alarm red/green). The short + code (GF/SEC/IT/OPS) is a fixed abbreviation; name + responsibility are i18n. */ +const OWNER_STYLE: Record = { + leadership: { + short: "GF", + color: "bg-indigo-50 text-indigo-700 ring-indigo-600/15 dark:bg-indigo-500/10 dark:text-indigo-300", + }, + security: { + short: "SEC", + color: "bg-slate-100 text-slate-600 ring-slate-500/15 dark:bg-slate-500/10 dark:text-slate-300", + }, + it: { + short: "IT", + color: "bg-sky-50 text-sky-700 ring-sky-600/15 dark:bg-sky-500/10 dark:text-sky-300", + }, + operations: { + short: "OPS", + color: "bg-teal-50 text-teal-700 ring-teal-600/15 dark:bg-teal-500/10 dark:text-teal-300", + }, +}; + +const BANDS: Band[] = ["minimum", "year", "later"]; + +/* ------------------------------------------------------------------ */ +function ReadinessRing({ pct }: { pct: number }) { + const t = useTranslations("portal"); + const r = 46; + const c = 2 * Math.PI * r; + const offset = c * (1 - pct / 100); + return ( +
+ + + + + + + + + + +
+
{pct}%
+
+ {t("journeyBoard.hero.unit")} +
+
+
+ ); +} + +function Hint({ text, className }: { text: string; className?: string }) { + const t = useTranslations("portal"); + return ( + + + + + + + {text} + + ); +} + +function CoverageDot({ cov }: { cov: Coverage }) { + const t = useTranslations("portal"); + const c = COVERAGE_STYLE[cov]; + return ( + + + + + + + + {t(`journeyBoard.coverage.${cov}Label`)}.{" "} + {t(`journeyBoard.coverage.${cov}Tip`)} + + + ); +} + +function OwnerChip({ column }: { column: FlowNode["column"] }) { + const t = useTranslations("portal"); + const o = OWNER_STYLE[column]; + return ( + + + + {o.short} + + + + {t(`journeyBoard.owner.${column}Name`)}.{" "} + {t(`journeyBoard.owner.${column}Resp`)} + + + ); +} + +/* A single control row. The title is a link to the requirement's own page; + the trailing action links there too. Tooltip chips stay as spans so the + row never nests interactive elements inside an anchor. */ +function ControlRow({ node }: { node: FlowNode }) { + const t = useTranslations("portal"); + const cov = coverageOf(node); + const c = COVERAGE_STYLE[cov]; + const isCurrent = cov === "current"; + const href = reqHref(node); + return ( +
+ + + {node.code} + + + {node.label} + + + {node.frequency ? ( + + + + + + + {t("journeyBoard.row.recurring")} + + ) : null} + {node.legalRef ? ( + + {node.legalRef} + + ) : null} + + + + + {t(`journeyBoard.coverage.${cov}Label`)} + + + {isCurrent ? ( + + ) : ( + + + + )} +
+ ); +} + +/* A collapsible phase group — header carries its own progress. */ +function PhaseSection({ + band, + nodes, + defaultOpen, +}: { + band: Band; + nodes: FlowNode[]; + defaultOpen: boolean; +}) { + const t = useTranslations("portal"); + const [open, setOpen] = useState(defaultOpen); + const total = nodes.length; + const done = nodes.filter((n) => n.status === "done").length; + const pct = total ? Math.round((done / total) * 100) : 0; + + return ( +
+
+ + + + {done}/{total} + +
+
+
+
+ {open ? ( +
+ {nodes.map((n) => ( + + ))} +
+ ) : null} +
+ ); +} + +function ViewToggle({ view, setView }: { view: View; setView: (v: View) => void }) { + const t = useTranslations("portal"); + const opts: { key: View; label: string; Icon: typeof Flame; tip: string }[] = [ + { key: "critical", label: t("journeyBoard.view.critical"), Icon: Flame, tip: t("journeyBoard.view.criticalTip") }, + { key: "chrono", label: t("journeyBoard.view.chrono"), Icon: ListOrdered, tip: t("journeyBoard.view.chronoTip") }, + ]; + return ( +
+ {opts.map((o) => { + const on = o.key === view; + return ( + + + + + {o.tip} + + ); + })} +
+ ); +} + +/* ================================================================== */ +export function JourneyBoard({ + reqNodes, + aggregate, +}: { + reqNodes: FlowNode[]; + aggregate: Aggregate; +}) { + const t = useTranslations("portal"); + const [view, setView] = useState("critical"); + const pct = aggregate.total ? Math.round((aggregate.done / aggregate.total) * 100) : 0; + // Mirror the real journey: nodes come in global order (category sortOrder, + // so REG/registration is first), and each band preserves that order. + const ordered = [...reqNodes].sort((a, b) => chronoKey(a) - chronoKey(b)); + const current = ordered.find((n) => n.status === "current"); + const prepared = aggregate.awaitingSignoff; + + return ( + +
+ {/* Hero: readiness + reassurance (scope as achievement, not backlog) */} +
+ +
+
+ +

+ {t("journeyBoard.hero.title")} +

+
+

+ {t("journeyBoard.hero.subtitle", { done: aggregate.done, total: aggregate.total })} +

+
+ + + {t("journeyBoard.hero.chipPrepared", { count: prepared })} + + + + {t("journeyBoard.hero.chipDone", { count: aggregate.done })} + + {aggregate.overdue > 0 ? ( + + + {t("journeyBoard.hero.chipOverdue", { count: aggregate.overdue })} + + ) : null} +
+
+
+ + {/* The single glowing next step */} + {current ? ( + + + + +
+
+ {t("journeyBoard.next.kicker")} + {current.code} +
+

{current.label}

+
+ + {t("journeyBoard.next.eta")} + + + {t("journeyBoard.next.cta")} + + + ) : null} + + {/* Section header + view toggle */} +
+

+ {t("journeyBoard.path.title")} +

+ +
+ +
+
+ + {view === "critical" ? ( +
+ {BANDS.map((b, i) => { + const nodes = ordered.filter((n) => n.band === b); + if (!nodes.length) return null; + return ; + })} +
+ ) : ( +
+
+ {t("journeyBoard.path.chronoHeader")} +
+
+ {ordered.map((n) => ( + + ))} +
+
+ )} +
+
+ ); +} diff --git a/app/[locale]/journey-preview/page.tsx b/app/[locale]/journey-preview/page.tsx new file mode 100644 index 0000000..ec7e42d --- /dev/null +++ b/app/[locale]/journey-preview/page.tsx @@ -0,0 +1,37 @@ +import { notFound } from "next/navigation"; +import { getLocale, getTranslations } from "next-intl/server"; +import { + SidebarProvider, + SidebarInset, + SidebarTrigger, +} from "@/components/ui/sidebar"; +import { AppSidebar } from "@/components/portal/AppSidebar"; +import { JourneyBoard } from "./JourneyBoard"; +import { SAMPLE_FRAMEWORKS, SAMPLE_USER, buildSampleNodes, sampleAggregate } from "./sample-data"; +import { titlesFor } from "./sample-titles"; + +// Public design route for the portal sidebar + journey redesign. No auth, no +// DB — renders the real components with sample data, localized per route locale, +// so the hero image can be screenshotted in every language. +export default async function JourneyPreviewPage() { + // Design/generation route only: rendered locally to produce the landing hero + // images. Never served on production, so it is not a public URL on nisd2.eu. + if (process.env.NODE_ENV === "production") notFound(); + const locale = await getLocale(); + const t = await getTranslations("portal"); + const nodes = buildSampleNodes(titlesFor(locale)); + const aggregate = sampleAggregate(nodes); + + return ( + + + +
+ + {t("journey")} +
+ +
+
+ ); +} diff --git a/app/[locale]/journey-preview/sample-data.ts b/app/[locale]/journey-preview/sample-data.ts new file mode 100644 index 0000000..af08cee --- /dev/null +++ b/app/[locale]/journey-preview/sample-data.ts @@ -0,0 +1,134 @@ +// Sample data for the /journey-preview design route. No DB, no auth. +// Shapes mirror the real journey (FlowNode + Aggregate) so the redesigned +// board drops into the real page unchanged. Titles are localized separately +// (sample-titles.ts) so the preview can be screenshotted per locale. +import type { FlowNode, Aggregate } from "../(portal)/journey/path-nodes"; +import type { FrameworkGroup } from "@/components/portal/AppSidebar"; + +type Seed = { + code: string; + band: FlowNode["band"]; + column: FlowNode["column"]; + ownerRole: string; + status: FlowNode["status"]; // "done" | "current" | "upcoming" + prepared?: boolean; // auto-drafted -> needs_review (review-and-sign) + frequency?: string | null; + priority?: string | null; + legalRef?: string | null; + dueInDays?: number | null; +}; + +const SEEDS: Seed[] = [ + // Defensible minimum — REG cluster first (global order), then the rest. + { code: "12.1", band: "minimum", column: "leadership", ownerRole: "ceo", status: "current", legalRef: "§ 28 BSIG", priority: "P0" }, + { code: "12.2", band: "minimum", column: "leadership", ownerRole: "ceo", status: "upcoming", legalRef: "§ 33 BSIG", priority: "P0" }, + { code: "1.2", band: "minimum", column: "leadership", ownerRole: "ceo", status: "done", legalRef: "Art. 20 NIS2", priority: "P0" }, + { code: "2.1", band: "minimum", column: "security", ownerRole: "ciso", status: "done", legalRef: "§ 30 BSIG", priority: "P0" }, + { code: "2.2", band: "minimum", column: "security", ownerRole: "ciso", status: "done", legalRef: "§ 30 BSIG", priority: "P0" }, + { code: "2.4", band: "minimum", column: "leadership", ownerRole: "ceo", status: "done", priority: "P0", frequency: "annual", dueInDays: 210 }, + { code: "3.1", band: "minimum", column: "security", ownerRole: "ciso", status: "done", legalRef: "§ 32 BSIG", priority: "P0" }, + { code: "3.3", band: "minimum", column: "security", ownerRole: "ciso", status: "upcoming", prepared: true, legalRef: "§ 32 BSIG", priority: "P0" }, + + // Over the year + { code: "10.1", band: "year", column: "leadership", ownerRole: "ceo", status: "done", legalRef: "§ 38 BSIG", frequency: "annual", dueInDays: 40 }, + { code: "10.2", band: "year", column: "security", ownerRole: "ciso", status: "done", frequency: "annual", dueInDays: -6 }, + { code: "4.1", band: "year", column: "it", ownerRole: "cto", status: "done" }, + { code: "5.1", band: "year", column: "it", ownerRole: "cto", status: "done" }, + { code: "11.1", band: "year", column: "it", ownerRole: "cto", status: "upcoming", prepared: true }, + { code: "7.1", band: "year", column: "it", ownerRole: "cto", status: "upcoming", prepared: true, frequency: "monthly" }, + { code: "6.1", band: "year", column: "operations", ownerRole: "coo", status: "done", dueInDays: 12 }, + { code: "8.1", band: "year", column: "operations", ownerRole: "coo", status: "done" }, + { code: "8.2", band: "year", column: "operations", ownerRole: "coo", status: "upcoming", prepared: true, legalRef: "Art. 21(2)(d)" }, + { code: "6.2", band: "year", column: "operations", ownerRole: "coo", status: "upcoming", prepared: true }, + + // Lower priority — after that + { code: "9.1", band: "later", column: "security", ownerRole: "ciso", status: "upcoming", prepared: true, frequency: "quarterly" }, + { code: "12.3", band: "later", column: "leadership", ownerRole: "ceo", status: "upcoming", prepared: true, frequency: "on-change" }, + { code: "12.4", band: "later", column: "leadership", ownerRole: "ceo", status: "upcoming" }, + { code: "5.2", band: "later", column: "it", ownerRole: "cto", status: "upcoming", prepared: true }, +]; + +/** Build the FlowNode[] for a given locale's requirement titles. rawStatus is + * the real column: done->completed, current->in_progress, prepared->needs_review + * (auto-drafted, awaiting sign-off), otherwise not_started. */ +export function buildSampleNodes(titles: Record): FlowNode[] { + return SEEDS.map((s, i) => { + const rawStatus = + s.status === "done" + ? "completed" + : s.status === "current" + ? "in_progress" + : s.prepared + ? "needs_review" + : "not_started"; + const node: FlowNode = { + id: `sample-${i}`, + code: s.code, + label: titles[s.code] ?? s.code, + categorySlug: s.code.split(".")[0], + categoryCode: s.code.split(".")[0], + band: s.band, + column: s.column, + ownerRole: s.ownerRole, + status: s.status, + rawStatus, + isOverdue: s.dueInDays != null && s.dueInDays < 0, + dueInDays: s.dueInDays ?? null, + priority: s.priority ?? null, + description: null, + legalRef: s.legalRef ?? null, + frequency: s.frequency ?? null, + signOff: { signed: s.status === "done" ? 1 : 0, total: s.column === "leadership" ? 1 : 0 }, + }; + return node; + }); +} + +export function sampleAggregate(nodes: FlowNode[]): Aggregate { + const done = nodes.filter((n) => n.status === "done").length; + return { + total: nodes.length, + done, + awaitingSignoff: nodes.filter((n) => n.rawStatus === "needs_review").length, + overdue: nodes.filter((n) => n.isOverdue).length, + dueSoon: nodes.filter((n) => n.dueInDays != null && n.dueInDays >= 0 && n.dueInDays <= 30).length, + open: nodes.length - done, + }; +} + +export const SAMPLE_USER = { + name: "Simon Orzel", + email: "simon@nisd2.eu", + image: null as string | null, + isPlatformAdmin: false, +}; + +// Header counts derive from SEEDS so they cannot drift from the board aggregate +// (done = status "done", total = all seeds). The per-category breakdown is +// illustrative — categories do not map 1:1 to the SEEDS codes — but its +// completedCount values are kept summing to SAMPLE_DONE so an expanded sidebar +// stays consistent with the header and the board. +const SAMPLE_DONE = SEEDS.filter((s) => s.status === "done").length; +const SAMPLE_TOTAL = SEEDS.length; + +// One NIS2 framework group for the sidebar. Category names only show when the +// group is expanded (collapsed by default), so they need no translation for the +// hero shot; the header count matches the board aggregate. +export const SAMPLE_FRAMEWORKS: FrameworkGroup[] = [ + { + code: "NIS2", + label: "nis2", + codePrefix: "NIS2-", + completed: SAMPLE_DONE, + total: SAMPLE_TOTAL, + steps: [ + { slug: "governance", code: "GOV", name: "Governance", phase: "phaseFoundation", requirementCount: 4, completedCount: 3, requirements: [] }, + { slug: "risk", code: "RSK", name: "Risikomanagement", phase: "phaseFoundation", requirementCount: 5, completedCount: 3, requirements: [] }, + { slug: "supply", code: "SUP", name: "Lieferkette", phase: "phaseFoundation", requirementCount: 3, completedCount: 1, requirements: [] }, + { slug: "crypto", code: "CRY", name: "Kryptografie", phase: "phaseControls", requirementCount: 2, completedCount: 2, requirements: [] }, + { slug: "access", code: "ACC", name: "Zugriffssteuerung", phase: "phaseControls", requirementCount: 3, completedCount: 1, requirements: [] }, + { slug: "incident", code: "INC", name: "Vorfallsbehandlung", phase: "phaseOperations", requirementCount: 3, completedCount: 1, requirements: [] }, + { slug: "training", code: "TRN", name: "Schulung", phase: "phaseVerification", requirementCount: 2, completedCount: 0, requirements: [] }, + ], + }, +]; diff --git a/app/[locale]/journey-preview/sample-titles.ts b/app/[locale]/journey-preview/sample-titles.ts new file mode 100644 index 0000000..2b1e8d0 --- /dev/null +++ b/app/[locale]/journey-preview/sample-titles.ts @@ -0,0 +1,249 @@ +// Per-locale sample requirement titles for the /journey-preview design route. +// Preview/marketing only — the real journey gets titles from the backend (de/en). +// Generated by sales/tools (NIS2 private) from the journey-i18n workflow. +export const SAMPLE_TITLES: Record> = { + de: { + "12.1": "NIS2-Einstufung & Geltungsbereich", + "12.2": "BSI-Registrierung", + "1.2": "Rollen & Verantwortlichkeiten", + "2.1": "Risikomanagement-Methodik", + "2.2": "Asset-Inventar & Klassifizierung", + "2.4": "Risikoakzeptanz & IS-Leitlinie", + "3.1": "Vorfallreaktionsplan & Team", + "3.3": "Meldeprozess an das BSI (24h/72h/1M)", + "10.1": "Schulung der Geschäftsleitung", + "10.2": "Awareness-Schulung Mitarbeitende", + "4.1": "Kryptografie-Konzept", + "5.1": "Zugriffssteuerung & Berechtigungen", + "11.1": "Authentifizierung: wo MFA Pflicht ist", + "7.1": "Patch- & Schwachstellenmanagement", + "6.1": "Backup & Wiederherstellung", + "8.1": "Lieferantenmanagement", + "8.2": "Lieferantenverträge & Klauseln", + "6.2": "Notfall- & Kontinuitätsplan", + "9.1": "Wirksamkeitsprüfung & KPIs", + "12.3": "Registrierungspflege", + "12.4": "Compliance-Nachweise & KRITIS", + "5.2": "Netzwerksegmentierung", + }, + en: { + "12.1": "NIS2 classification & scope", + "12.2": "BSI registration", + "1.2": "Roles & responsibilities", + "2.1": "Risk management methodology", + "2.2": "Asset inventory & classification", + "2.4": "Risk acceptance & IS policy", + "3.1": "Incident response plan & team", + "3.3": "Reporting process to the BSI (24h/72h/1M)", + "10.1": "Management training", + "10.2": "Staff awareness training", + "4.1": "Cryptography concept", + "5.1": "Access control & permissions", + "11.1": "Authentication: where MFA is mandatory", + "7.1": "Patch & vulnerability management", + "6.1": "Backup & recovery", + "8.1": "Supplier management", + "8.2": "Supplier contracts & clauses", + "6.2": "Emergency & continuity plan", + "9.1": "Effectiveness review & KPIs", + "12.3": "Registration upkeep", + "12.4": "Compliance records & KRITIS", + "5.2": "Network segmentation", + }, + nl: { + "12.1": "NIS2-classificatie & toepassingsgebied", + "12.2": "BSI-registratie", + "1.2": "Rollen & verantwoordelijkheden", + "2.1": "Risicomanagementmethodiek", + "2.2": "Asset-inventaris & classificatie", + "2.4": "Risicoacceptatie & IS-beleid", + "3.1": "Incidentresponsplan & team", + "3.3": "Meldproces aan het BSI (24h/72h/1M)", + "10.1": "Training van de directie", + "10.2": "Awarenesstraining medewerkers", + "4.1": "Cryptografieconcept", + "5.1": "Toegangsbeheer & rechten", + "11.1": "Authenticatie: waar MFA verplicht is", + "7.1": "Patch- & kwetsbaarhedenbeheer", + "6.1": "Back-up & herstel", + "8.1": "Leveranciersbeheer", + "8.2": "Leverancierscontracten & clausules", + "6.2": "Nood- & continuïteitsplan", + "9.1": "Effectiviteitstoetsing & KPIs", + "12.3": "Registratieonderhoud", + "12.4": "Compliancebewijs & KRITIS", + "5.2": "Netwerksegmentatie", + }, + fr: { + "12.1": "Classification NIS2 et champ d'application", + "12.2": "Enregistrement auprès du BSI", + "1.2": "Rôles et responsabilités", + "2.1": "Méthodologie de gestion des risques", + "2.2": "Inventaire et classification des actifs", + "2.4": "Acceptation du risque et politique IS", + "3.1": "Plan et équipe de réponse aux incidents", + "3.3": "Processus de notification au BSI (24h/72h/1M)", + "10.1": "Formation de la direction", + "10.2": "Formation de sensibilisation des collaborateurs", + "4.1": "Concept de cryptographie", + "5.1": "Contrôle des accès et autorisations", + "11.1": "Authentification : où MFA est obligatoire", + "7.1": "Gestion des correctifs et des vulnérabilités", + "6.1": "Sauvegarde et restauration", + "8.1": "Gestion des fournisseurs", + "8.2": "Contrats et clauses fournisseurs", + "6.2": "Plan d'urgence et de continuité", + "9.1": "Contrôle d'efficacité et KPIs", + "12.3": "Maintien de l'enregistrement", + "12.4": "Preuves de conformité et KRITIS", + "5.2": "Segmentation du réseau", + }, + it: { + "12.1": "Classificazione NIS2 e ambito di applicazione", + "12.2": "Registrazione presso il BSI", + "1.2": "Ruoli e responsabilità", + "2.1": "Metodologia di gestione del rischio", + "2.2": "Inventario e classificazione degli asset", + "2.4": "Accettazione del rischio e linea guida IS", + "3.1": "Piano e team di risposta agli incidenti", + "3.3": "Processo di notifica al BSI (24h/72h/1M)", + "10.1": "Formazione della direzione", + "10.2": "Formazione di sensibilizzazione del personale", + "4.1": "Concetto di crittografia", + "5.1": "Controllo degli accessi e autorizzazioni", + "11.1": "Autenticazione: dove MFA è obbligatorio", + "7.1": "Gestione delle patch e delle vulnerabilità", + "6.1": "Backup e ripristino", + "8.1": "Gestione dei fornitori", + "8.2": "Contratti e clausole con i fornitori", + "6.2": "Piano di emergenza e continuità", + "9.1": "Verifica dell'efficacia e KPIs", + "12.3": "Mantenimento della registrazione", + "12.4": "Evidenze di conformità e KRITIS", + "5.2": "Segmentazione della rete", + }, + es: { + "12.1": "Clasificación NIS2 y ámbito de aplicación", + "12.2": "Registro ante el BSI", + "1.2": "Roles y responsabilidades", + "2.1": "Metodología de gestión de riesgos", + "2.2": "Inventario y clasificación de activos", + "2.4": "Aceptación de riesgos y directriz IS", + "3.1": "Plan de respuesta a incidentes y equipo", + "3.3": "Proceso de notificación al BSI (24h/72h/1M)", + "10.1": "Formación de la dirección", + "10.2": "Formación de concienciación del personal", + "4.1": "Concepto de criptografía", + "5.1": "Control de acceso y permisos", + "11.1": "Autenticación: dónde es obligatoria la MFA", + "7.1": "Gestión de parches y vulnerabilidades", + "6.1": "Copias de seguridad y recuperación", + "8.1": "Gestión de proveedores", + "8.2": "Contratos y cláusulas de proveedores", + "6.2": "Plan de emergencia y continuidad", + "9.1": "Revisión de eficacia y KPIs", + "12.3": "Mantenimiento del registro", + "12.4": "Evidencias de cumplimiento y KRITIS", + "5.2": "Segmentación de red", + }, + pl: { + "12.1": "Klasyfikacja NIS2 i zakres", + "12.2": "Rejestracja w BSI", + "1.2": "Role i odpowiedzialności", + "2.1": "Metodyka zarządzania ryzykiem", + "2.2": "Inwentarz i klasyfikacja aktywów", + "2.4": "Akceptacja ryzyka i polityka IS", + "3.1": "Plan reagowania na incydenty i zespół", + "3.3": "Proces zgłaszania do BSI (24h/72h/1M)", + "10.1": "Szkolenie kierownictwa", + "10.2": "Szkolenie uświadamiające dla pracowników", + "4.1": "Koncepcja kryptografii", + "5.1": "Kontrola dostępu i uprawnienia", + "11.1": "Uwierzytelnianie: gdzie MFA jest obowiązkowe", + "7.1": "Zarządzanie aktualizacjami i podatnościami", + "6.1": "Kopie zapasowe i odtwarzanie", + "8.1": "Zarządzanie dostawcami", + "8.2": "Umowy z dostawcami i klauzule", + "6.2": "Plan awaryjny i ciągłości działania", + "9.1": "Ocena skuteczności i KPIs", + "12.3": "Utrzymanie rejestracji", + "12.4": "Dowody zgodności i KRITIS", + "5.2": "Segmentacja sieci", + }, + cs: { + "12.1": "Zařazení a rozsah NIS2", + "12.2": "Registrace u BSI", + "1.2": "Role a odpovědnosti", + "2.1": "Metodika řízení rizik", + "2.2": "Inventář a klasifikace aktiv", + "2.4": "Akceptace rizik a IS směrnice", + "3.1": "Plán reakce na incidenty a tým", + "3.3": "Proces hlášení na BSI (24h/72h/1M)", + "10.1": "Školení vedení", + "10.2": "Školení povědomí zaměstnanců", + "4.1": "Koncept kryptografie", + "5.1": "Řízení přístupu a oprávnění", + "11.1": "Autentizace: kde je MFA povinné", + "7.1": "Řízení patchů a zranitelností", + "6.1": "Zálohování a obnova", + "8.1": "Řízení dodavatelů", + "8.2": "Dodavatelské smlouvy a doložky", + "6.2": "Nouzový plán a plán kontinuity", + "9.1": "Kontrola účinnosti a KPIs", + "12.3": "Údržba registrace", + "12.4": "Doklady o shodě a KRITIS", + "5.2": "Segmentace sítě", + }, + pt: { + "12.1": "Classificação NIS2 e âmbito", + "12.2": "Registo no BSI", + "1.2": "Funções e responsabilidades", + "2.1": "Metodologia de gestão de risco", + "2.2": "Inventário de ativos e classificação", + "2.4": "Aceitação de risco e política de IS", + "3.1": "Plano de resposta a incidentes e equipa", + "3.3": "Processo de notificação ao BSI (24h/72h/1M)", + "10.1": "Formação da direção", + "10.2": "Formação de sensibilização dos colaboradores", + "4.1": "Conceito de criptografia", + "5.1": "Controlo de acessos e permissões", + "11.1": "Autenticação: onde a MFA é obrigatória", + "7.1": "Gestão de patches e vulnerabilidades", + "6.1": "Backup e recuperação", + "8.1": "Gestão de fornecedores", + "8.2": "Contratos e cláusulas com fornecedores", + "6.2": "Plano de emergência e continuidade", + "9.1": "Avaliação de eficácia e KPIs", + "12.3": "Manutenção do registo", + "12.4": "Comprovativos de conformidade e KRITIS", + "5.2": "Segmentação de rede", + }, + ro: { + "12.1": "Încadrarea NIS2 și domeniul de aplicare", + "12.2": "Înregistrarea la BSI", + "1.2": "Roluri și responsabilități", + "2.1": "Metodologia managementului riscului", + "2.2": "Inventarul și clasificarea activelor", + "2.4": "Acceptarea riscului și politica IS", + "3.1": "Planul de răspuns la incidente și echipa", + "3.3": "Procesul de raportare către BSI (24h/72h/1M)", + "10.1": "Instruirea conducerii", + "10.2": "Instruirea de conștientizare a angajaților", + "4.1": "Conceptul de criptografie", + "5.1": "Controlul accesului și permisiunile", + "11.1": "Autentificare: unde MFA este obligatoriu", + "7.1": "Managementul patch-urilor și al vulnerabilităților", + "6.1": "Backup și recuperare", + "8.1": "Managementul furnizorilor", + "8.2": "Contracte cu furnizorii și clauze", + "6.2": "Planul de urgență și continuitate", + "9.1": "Evaluarea eficacității și KPIs", + "12.3": "Întreținerea înregistrării", + "12.4": "Dovezi de conformitate și KRITIS", + "5.2": "Segmentarea rețelei", + }, +}; + +export function titlesFor(locale: string): Record { + return SAMPLE_TITLES[locale] ?? SAMPLE_TITLES.en; +} diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx index 1e3b2c1..0ac6d59 100644 --- a/app/[locale]/page.tsx +++ b/app/[locale]/page.tsx @@ -1,7 +1,7 @@ import type { Metadata } from "next"; import { Link } from "@/i18n/navigation"; import { getTranslations, getLocale } from "next-intl/server"; -import { Shield, Check, Code2, Server, ArrowRight } from "lucide-react"; +import { Check, Code2, Server, ArrowRight } from "lucide-react"; import { Button } from "@/components/ui/button"; import { PublicNav } from "@/components/PublicNav"; import { PublicFooter } from "@/components/PublicFooter"; @@ -42,93 +42,155 @@ export default async function LandingPage() { return ( <> -
-
- {/* Left: the pitch */} -
-
- -
- -

- {t.rich("title", { - blue: (chunks) => {chunks}, - })} -

+
+ {/* Navy dot-grid, densest behind the product, dissolving to the edges */} +
-

{t("subtitle")}

+
+ {/* Headline: full-width, standing on its own (the logo lives in the nav) */} +

+ {t.rich("title", { + blue: (chunks) => {chunks}, + })} +

- {/* CTAs */} -
- - -
+ {/* Below the headline: pitch column + large frameless product */} +
+
+

+ {t("subtitle")} +

- {/* Supplier door */} -

- - {t("supplierDoor")} - -

+ {/* CTAs stacked for the narrow column */} +
+ + +
- {/* Quiet authority strip (replaces the framework badges) */} -

{t("regLine")}

-
+ {/* Quiet meta row: supplier door + legal citation */} +
+

+ + {t("supplierDoor")} + +

+

+ + {t("regLine")} +

+
+
- {/* Right: credibility card. Proof, what you get, why free, linking to the Trust Center. */} -
-
- - - {t("proofOpenSource")} - - - - {t("proofEuHosted")} - + {/* Product: large, frameless, floating screenshot */} +
+ {/* Per-locale product shot (generated by the NIS2 private + tool). Falls back to the DE image via journey-hero.png. */} + {/* eslint-disable-next-line @next/next/no-img-element */} + nisd2.eu Journey
+
+
-
-

{t("cardGetTitle")}

-
    -
  • - - {t("cardGet1")} + {/* Below the hero: what you get + why free. Aligned to the hero + width and left edge, split by a hairline, so it reads as an + intentional section rather than a floating centered card. */} +
    +
    +
    +

    + {t("cardGetTitle")} +

    +
      +
    • + + + + {t("cardGet1")}
    • -
    • - - {t("cardGet2")} +
    • + + + + {t("cardGet2")}
    • -
    • - - {t("cardGet3")} +
    • + + + + {t("cardGet3")}
    - -
    -

    {t("cardWhyFreeTitle")}

    -

    {t("cardWhyFree")}

    +
    +

    + {t("cardWhyFreeTitle")} +

    +

    + {t("cardWhyFree")} +

    +
    + + + {t("proofOpenSource")} + + + + {t("proofEuHosted")} + +
    + + {t("cardTrustLink")} + +
    - - - {t("cardTrustLink")} - -
    -
    +
diff --git a/app/[locale]/wiki/umsetzung/nis2-roadmap/page.tsx b/app/[locale]/wiki/umsetzung/nis2-roadmap/page.tsx index 7e75490..bf409d5 100644 --- a/app/[locale]/wiki/umsetzung/nis2-roadmap/page.tsx +++ b/app/[locale]/wiki/umsetzung/nis2-roadmap/page.tsx @@ -1,10 +1,16 @@ import type { Metadata } from "next"; import { getTranslations } from "next-intl/server"; import { Card, CardContent } from "@/components/ui/card"; -import { Badge } from "@/components/ui/badge"; -import { Separator } from "@/components/ui/separator"; import { Link } from "@/i18n/navigation"; -import { ArrowRight, ShieldCheck } from "lucide-react"; +import { + ArrowRight, + ShieldCheck, + Compass, + Clock, + Check, + CalendarClock, + User, +} from "lucide-react"; import { JsonLd } from "@/components/JsonLd"; import { PrintShareActions } from "./PrintShareActions"; import { pageAlternates, pageOg, type Locale } from "@/lib/seo"; @@ -58,6 +64,49 @@ export default async function Nis2RoadmapPage({ params }: { params: Promise<{ lo isPersonal: key === PERSONAL_STEP, })); + // Primary action: the platform runs this exact roadmap as a guided journey. + // Auth is expected here (the user is choosing to enter the app), unlike a + // cold landing CTA, so linking straight to /journey is fine. + const platformCta = ( + + +
+
+ + {t("platform.heading")} +
+

{t("platform.body")}

+
+ + {t("platform.cta")} + + +
+
+ ); + + // Compact repeat for the bottom: same button, no duplicated pitch copy. + const platformCtaCompact = ( + + +
+ + {t("platform.compact")} +
+ + {t("platform.cta")} + + +
+
+ ); + return (
@@ -89,171 +138,189 @@ export default async function Nis2RoadmapPage({ params }: { params: Promise<{ lo {/* Hero */}
- + + {t("heroBadge")} - -

+ +

{t("heroTitle")}

-

+

{t("heroSubtitle")}

- {/* Triage opener */} - - -
-
{t("triageHeading")}
-

{t("triageBody")}

-
- - {t("triageCta")} - - -
-
+ {/* Primary action: run this roadmap as a guided journey in the platform */} + {platformCta} - + {/* Triage line */} +
+
+ {t("triageHeading")}{" "} + {t("triageBody")} +
+ + {t("triageCta")} + + +
- {/* Steps */} + {/* Steps: one calm card, six journey-style rows */}
-

{t("stepsHeading")}

-
- {steps.map((step, i) => ( - - -
-
+

+ {t("stepsHeading")} +

+ + + {steps.map((step, i) => { + const isExternal = step.actionHref.startsWith("http"); + return ( +
+ {/* Numbered marker */} + {i + 1} -
-
-
-

{step.title}

- {step.isPersonal ? ( - + + + {/* Title + one-line what + owner */} +
+
+ {step.title} + + {step.isPersonal ? ( - {t("personalBadge")} - - ) : ( - {t("delegableBadge")} - )} + ) : ( + + )} + {step.isPersonal ? t("personalBadge") : t("delegableBadge")} +
-

- {step.what} -

-
-
-
- {t("lawLabel")}: -
{" "} -
- {step.law} -
-
-
-
- {t("ownerLabel")}: -
{" "} -
- {step.owner} -
-
-
-
- {t("timeLabel")}: -
{" "} -
- {step.time} -
-
-
-
- {t("deadlineLabel")}: -
{" "} -
- {step.deadline} -
-
-
-
- {step.actionHref.startsWith("http") ? ( - - {step.actionText} - - - ) : ( - - {step.actionText} - - - )} +

{step.what}

+
+ + + {t("ownerLabel")}:{" "} + {step.owner} +
+ + {/* Right-aligned compact meta cluster */} +
+ + {step.law} + + + + {step.time} + + + + {step.deadline} + + {isExternal ? ( + + {step.actionText} + + + ) : ( + + {step.actionText} + + + )} +
- - - ))} -
+ ); + })} + +
{/* Anchor: personal time summary */} - + -

{t("anchor.heading")}

+
+ + {t("anchor.heading")} +
    -
  • • {t("anchor.lineSchulung")}
  • -
  • • {t("anchor.lineSign")}
  • -
  • • {t("anchor.lineYearly")}
  • +
  • + + {t("anchor.lineSchulung")} +
  • +
  • + + {t("anchor.lineSign")} +
  • +
  • + + {t("anchor.lineYearly")} +
-

+

{t("anchor.totalLabel")}: {t("anchor.total")}

{t("anchor.footer")}

+ {/* Compact repeat after the time anchor (no duplicated pitch copy) */} + {platformCtaCompact} + {/* Guided-help fork (DE/EN only; NL roadmap copy is not authored yet) */} {locale !== "nl" && ( - - -
-
{t("guided.heading")}
-

{t("guided.body")}

-
- - {t("guided.cta")} - - -
-
+
+
+ {t("guided.heading")}{" "} + {t("guided.body")} +
+ + {t("guided.cta")} + + +
)} {/* Disclaimer */} -

+

{t("disclaimer")}

diff --git a/components/portal/AppSidebar.tsx b/components/portal/AppSidebar.tsx index 48af403..ff65eb4 100644 --- a/components/portal/AppSidebar.tsx +++ b/components/portal/AppSidebar.tsx @@ -13,6 +13,7 @@ import { FileText, ScrollText, Server, + ShieldCheck, Users, } from "lucide-react"; import type { LucideIcon } from "lucide-react"; @@ -35,7 +36,6 @@ import { CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; -import { ComplianceProgress } from "@/components/compliance/ComplianceProgress"; import { UserNav } from "./UserNav"; import { PortalSwitcher } from "./PortalSwitcher"; @@ -163,73 +163,81 @@ export function AppSidebar({ } } + const pct = + fw.total > 0 ? Math.round((fw.completed / fw.total) * 100) : 0; + return ( -
- - - - - {t(fw.label)} - - - + + + + + + + + + {t(fw.label)} + + {fw.completed}/{fw.total} + + + + + + - -
- +
+
- {phases.map((phase) => ( -
-

- {t(phase.label)} -

- - {phase.steps.map((step) => { - const categoryPath = `/compliance/${step.slug}`; - const isCompleted = - step.completedCount >= step.requirementCount && - step.requirementCount > 0; - const codePrefix = fw.codePrefix; - - return ( - - - - - {step.code.replace(codePrefix, "")} - - {step.name} - - - - {isCompleted ? ( - - ) : ( - - {step.completedCount}/{step.requirementCount} - - )} - - - ); - })} - -
- ))} - +
+ {phases.map((phase) => ( +
+

+ {t(phase.label)} +

+ + {phase.steps.map((step) => { + const categoryPath = `/compliance/${step.slug}`; + const isCompleted = + step.completedCount >= step.requirementCount && + step.requirementCount > 0; + return ( + + + + + {step.code.replace(fw.codePrefix, "")} + + {step.name} + + + + {isCompleted ? ( + + ) : ( + + {step.completedCount}/{step.requirementCount} + + )} + + + ); + })} + +
+ ))} - -
+
+
); })} diff --git a/messages/info/de.json b/messages/info/de.json index d428bf0..43da5a2 100644 --- a/messages/info/de.json +++ b/messages/info/de.json @@ -342,9 +342,18 @@ "heading": "Die drei Meldestufen", "description": "Artikel 23 Absatz 4 NIS 2, § 32 Absatz 1 BSIG.", "items": { - "s1": { "title": "Frühwarnung binnen 24 Stunden", "text": "Eine erste Frühwarnung an das BSI binnen 24 Stunden nach Kenntnis des erheblichen Vorfalls, mit der Angabe, ob ein rechtswidriger oder böswilliger Auslöser vermutet wird und ob grenzüberschreitende Auswirkungen möglich sind." }, - "s2": { "title": "Meldung binnen 72 Stunden", "text": "Eine ausführlichere Meldung binnen 72 Stunden, die die Frühwarnung um eine erste Bewertung des Vorfalls, seine Schwere und Auswirkungen sowie verfügbare Kompromittierungsindikatoren ergänzt." }, - "s3": { "title": "Abschlussbericht nach einem Monat", "text": "Ein Abschlussbericht spätestens einen Monat nach der Meldung: eine ausführliche Beschreibung, die Art der Bedrohung oder die Ursache, die ergriffenen Abhilfemaßnahmen und etwaige grenzüberschreitende Auswirkungen." } + "s1": { + "title": "Frühwarnung binnen 24 Stunden", + "text": "Eine erste Frühwarnung an das BSI binnen 24 Stunden nach Kenntnis des erheblichen Vorfalls, mit der Angabe, ob ein rechtswidriger oder böswilliger Auslöser vermutet wird und ob grenzüberschreitende Auswirkungen möglich sind." + }, + "s2": { + "title": "Meldung binnen 72 Stunden", + "text": "Eine ausführlichere Meldung binnen 72 Stunden, die die Frühwarnung um eine erste Bewertung des Vorfalls, seine Schwere und Auswirkungen sowie verfügbare Kompromittierungsindikatoren ergänzt." + }, + "s3": { + "title": "Abschlussbericht nach einem Monat", + "text": "Ein Abschlussbericht spätestens einen Monat nach der Meldung: eine ausführliche Beschreibung, die Art der Bedrohung oder die Ursache, die ergriffenen Abhilfemaßnahmen und etwaige grenzüberschreitende Auswirkungen." + } } }, "significant": { @@ -364,11 +373,26 @@ }, "faq": { "heading": "Häufige Fragen", - "q1": { "q": "Ab wann läuft die 24-Stunden-Frist?", "a": "Ab Kenntnis des erheblichen Vorfalls, nicht ab seinem Beginn." }, - "q2": { "q": "Gibt es feste Euro-Schwellen für einen erheblichen Vorfall?", "a": "Nur für die in der CIR (EU) 2024/2690 genannten digitalen Diensteanbieter. Für alle anderen Einrichtungen gelten die qualitativen Kriterien aus Artikel 23 Absatz 3 NIS 2." }, - "q3": { "q": "Was, wenn ich unsicher bin, ob ein Vorfall erheblich ist?", "a": "Dokumentieren Sie Ihre Einschätzung und die Gründe. Die begründete Entscheidung ist, was ein Prüfer sehen will; das Fehlen jeder Einschätzung ist der eigentliche Fehler." }, - "q4": { "q": "Muss ich auch nach DSGVO melden?", "a": "Sind personenbezogene Daten betroffen, prüfen Sie Artikel 33 DSGVO gesondert. Er hat eine eigene 72-Stunden-Frist und einen anderen Adressaten." }, - "q5": { "q": "Was passiert nach der Meldung?", "a": "Das BSI kann weitere Informationen anfordern, und der Abschlussbericht folgt spätestens einen Monat nach der Meldung." } + "q1": { + "q": "Ab wann läuft die 24-Stunden-Frist?", + "a": "Ab Kenntnis des erheblichen Vorfalls, nicht ab seinem Beginn." + }, + "q2": { + "q": "Gibt es feste Euro-Schwellen für einen erheblichen Vorfall?", + "a": "Nur für die in der CIR (EU) 2024/2690 genannten digitalen Diensteanbieter. Für alle anderen Einrichtungen gelten die qualitativen Kriterien aus Artikel 23 Absatz 3 NIS 2." + }, + "q3": { + "q": "Was, wenn ich unsicher bin, ob ein Vorfall erheblich ist?", + "a": "Dokumentieren Sie Ihre Einschätzung und die Gründe. Die begründete Entscheidung ist, was ein Prüfer sehen will; das Fehlen jeder Einschätzung ist der eigentliche Fehler." + }, + "q4": { + "q": "Muss ich auch nach DSGVO melden?", + "a": "Sind personenbezogene Daten betroffen, prüfen Sie Artikel 33 DSGVO gesondert. Er hat eine eigene 72-Stunden-Frist und einen anderen Adressaten." + }, + "q5": { + "q": "Was passiert nach der Meldung?", + "a": "Das BSI kann weitere Informationen anfordern, und der Abschlussbericht folgt spätestens einen Monat nach der Meldung." + } }, "ctaCard": { "heading": "Strukturieren Sie Ihren Meldeprozess, bevor Sie ihn brauchen", @@ -6519,6 +6543,12 @@ "openSource": "Open-Source-Werkzeuge (MIT + CC BY 4.0)", "openSourceUrl": "https://github.com/NISD2", "hosting": "Hosting in Deutschland · DSGVO-konform" + }, + "platform": { + "heading": "Der ganze Weg, in der Plattform", + "body": "Sie müssen die Schritte nicht einzeln von Hand abarbeiten. Die kostenlose Plattform führt Sie durch genau diese Roadmap, entwirft die meisten Nachweise vorab und behält Freigaben und Fristen im Blick. Sie prüfen und geben frei. Open Source, kein Lock-in.", + "cta": "In der Plattform starten", + "compact": "Bereit für den geführten Weg?" } }, "corrections": { diff --git a/messages/info/en.json b/messages/info/en.json index 3805824..d770bc4 100644 --- a/messages/info/en.json +++ b/messages/info/en.json @@ -342,9 +342,18 @@ "heading": "The three reporting stages", "description": "Article 23(4) NIS 2, Section 32(1) BSIG.", "items": { - "s1": { "title": "Early warning within 24 hours", "text": "An initial early warning to the BSI within 24 hours of becoming aware of the significant incident, stating whether it is suspected to be caused by unlawful or malicious acts and whether it could have a cross-border impact." }, - "s2": { "title": "Incident notification within 72 hours", "text": "A fuller notification within 72 hours, updating the early warning with an initial assessment of the incident, its severity and impact, and indicators of compromise where available." }, - "s3": { "title": "Final report after one month", "text": "A final report no later than one month after the notification: a detailed description, the type of threat or root cause, the mitigation applied, and any cross-border impact." } + "s1": { + "title": "Early warning within 24 hours", + "text": "An initial early warning to the BSI within 24 hours of becoming aware of the significant incident, stating whether it is suspected to be caused by unlawful or malicious acts and whether it could have a cross-border impact." + }, + "s2": { + "title": "Incident notification within 72 hours", + "text": "A fuller notification within 72 hours, updating the early warning with an initial assessment of the incident, its severity and impact, and indicators of compromise where available." + }, + "s3": { + "title": "Final report after one month", + "text": "A final report no later than one month after the notification: a detailed description, the type of threat or root cause, the mitigation applied, and any cross-border impact." + } } }, "significant": { @@ -364,11 +373,26 @@ }, "faq": { "heading": "Frequently asked questions", - "q1": { "q": "When does the 24-hour clock start?", "a": "When the entity becomes aware of the significant incident, not when it began." }, - "q2": { "q": "Are there fixed euro thresholds for a significant incident?", "a": "Only for the digital service providers named in CIR (EU) 2024/2690. For all other entities, the qualitative criteria of Article 23(3) NIS 2 apply." }, - "q3": { "q": "What if I am unsure whether an incident is significant?", "a": "Document your assessment and the reasons. The reasoned judgement is what an auditor expects to see; the absence of any assessment is the real failure." }, - "q4": { "q": "Do I also have to report under the GDPR?", "a": "If personal data is affected, check Article 33 GDPR separately. It has its own 72-hour clock and a different addressee." }, - "q5": { "q": "What happens after the notification?", "a": "The BSI may request further information, and the final report follows no later than one month after the notification." } + "q1": { + "q": "When does the 24-hour clock start?", + "a": "When the entity becomes aware of the significant incident, not when it began." + }, + "q2": { + "q": "Are there fixed euro thresholds for a significant incident?", + "a": "Only for the digital service providers named in CIR (EU) 2024/2690. For all other entities, the qualitative criteria of Article 23(3) NIS 2 apply." + }, + "q3": { + "q": "What if I am unsure whether an incident is significant?", + "a": "Document your assessment and the reasons. The reasoned judgement is what an auditor expects to see; the absence of any assessment is the real failure." + }, + "q4": { + "q": "Do I also have to report under the GDPR?", + "a": "If personal data is affected, check Article 33 GDPR separately. It has its own 72-hour clock and a different addressee." + }, + "q5": { + "q": "What happens after the notification?", + "a": "The BSI may request further information, and the final report follows no later than one month after the notification." + } }, "ctaCard": { "heading": "Structure your reporting process before you need it", @@ -6526,6 +6550,12 @@ "openSource": "Open-source tooling (MIT + CC BY 4.0)", "openSourceUrl": "https://github.com/NISD2", "hosting": "Hosted in Germany · GDPR-compliant" + }, + "platform": { + "heading": "The whole path, in the platform", + "body": "You do not have to work through the steps by hand. The free platform guides you through exactly this roadmap, drafts most of the evidence for you, and keeps sign-offs and deadlines in view. You review and approve. Open source, no lock-in.", + "cta": "Start in the platform", + "compact": "Ready to run it as a guided journey?" } }, "corrections": { diff --git a/messages/portal/cs.json b/messages/portal/cs.json index 0e84584..2ec2fea 100644 --- a/messages/portal/cs.json +++ b/messages/portal/cs.json @@ -50,6 +50,70 @@ "phaseAdmin": "Administrace", "blockedBy": "Nejprve dokončete: {codes}", "journey": "Cesta", - "administration": "Správa" + "administration": "Správa", + "journeyBoard": { + "hero": { + "title": "Vaše zavádění NIS2", + "subtitle": "{done} z {total} záznamů je hotovo. Většina vzniká automaticky z vaší práce, vy je zkontrolujete a schválíte.", + "unit": "hotovo", + "chipPrepared": "{count} pro vás připraveno", + "chipDone": "{count} hotovo", + "chipOverdue": "{count} po termínu" + }, + "next": { + "kicker": "Nyní na řadě", + "eta": "~15 min", + "cta": "Pokračovat" + }, + "path": { + "title": "Celá cesta", + "hint": "Všechna opatření. Přepínejte mezi Nejdřív kritické (naše doporučení) a Chronologicky (přirozené pořadí).", + "chronoHeader": "Jak proces obvykle probíhá" + }, + "view": { + "critical": "Nejdřív kritické", + "criticalTip": "Nejdřív největší riziko a odpovědnost. Naše doporučení.", + "chrono": "Chronologicky", + "chronoTip": "V přirozeném pořadí procesu, jak se obvykle zpracovává." + }, + "coverage": { + "doneLabel": "Hotovo", + "doneTip": "Zavedeno a zdokumentováno. Zůstává aktuální s malým úsilím.", + "currentLabel": "Nyní na řadě", + "currentTip": "Váš další krok. Jedno kliknutí a jdete dál.", + "preparedLabel": "Připraveno", + "preparedTip": "Připravili jsme to za vás. Zkontrolujete a schválíte, místo abyste začínali od nuly.", + "openLabel": "Otevřené", + "openTip": "Zatím nezahájeno. Přijde na řadu, až budou hotové předchozí kroky.", + "overdueLabel": "Po termínu", + "overdueTip": "Opakující se úkol je po termínu, například roční obnovení. Krátká aktualizace." + }, + "owner": { + "leadershipName": "Vedení", + "leadershipResp": "Schvaluje a nese odpovědnost. Governance, rozpočet a povinnosti podle § 38 BSIG.", + "securityName": "Bezpečnost (CISO)", + "securityResp": "Řídí riziko, incidenty, přístup, školení a účinnost.", + "itName": "IT", + "itResp": "Zavádí technická opatření: kryptografii, patche, autentizaci.", + "operationsName": "Provoz", + "operationsResp": "Odpovídá za kontinuitu, zálohy a řízení dodavatelů." + }, + "band": { + "minimumName": "Obhajitelné minimum", + "minimumPhase": "Tento měsíc", + "minimumHint": "Povinná, základní opatření. Základ, který musí stát jako první.", + "yearName": "V průběhu roku", + "yearPhase": "Další 3 měsíce", + "yearHint": "Zhruba dvě hodiny týdně. Ne sprint, ale klidný rytmus.", + "laterName": "Nižší priorita", + "laterPhase": "Poté", + "laterHint": "Až bude zbytek hotový. Důležité, ale ne naléhavé." + }, + "row": { + "recurring": "Opakující se, automaticky připomínáno.", + "open": "Otevřít", + "explain": "Vysvětlení" + } + } } } diff --git a/messages/portal/de.json b/messages/portal/de.json index 4c32034..c9371b7 100644 --- a/messages/portal/de.json +++ b/messages/portal/de.json @@ -50,6 +50,70 @@ "phaseAdmin": "Verwaltung", "blockedBy": "Zuerst abschliessen: {codes}", "journey": "Weg", - "administration": "Verwaltung" + "administration": "Verwaltung", + "journeyBoard": { + "hero": { + "title": "Ihre NIS2-Umsetzung", + "subtitle": "{done} von {total} Nachweisen stehen. Die meisten entstehen automatisch aus Ihrer Arbeit, Sie prüfen und geben frei.", + "unit": "umgesetzt", + "chipPrepared": "{count} für Sie vorbereitet", + "chipDone": "{count} umgesetzt", + "chipOverdue": "{count} fällig" + }, + "next": { + "kicker": "Jetzt dran", + "eta": "~15 Min", + "cta": "Weiter" + }, + "path": { + "title": "Der ganze Weg", + "hint": "Alle Maßnahmen. Wechseln Sie zwischen Kritisch zuerst (unsere Empfehlung) und Chronologisch (natürliche Reihenfolge).", + "chronoHeader": "So läuft der Prozess normalerweise" + }, + "view": { + "critical": "Kritisch zuerst", + "criticalTip": "Größtes Risiko und größte Haftung zuerst. Unsere Empfehlung.", + "chrono": "Chronologisch", + "chronoTip": "In der natürlichen Reihenfolge des Prozesses, wie üblich abgearbeitet." + }, + "coverage": { + "doneLabel": "Umgesetzt", + "doneTip": "Umgesetzt und dokumentiert. Bleibt mit wenig Aufwand aktuell.", + "currentLabel": "Jetzt dran", + "currentTip": "Ihr nächster Schritt. Ein Klick, dann geht es weiter.", + "preparedLabel": "Vorbereitet", + "preparedTip": "Haben wir für Sie entworfen. Sie prüfen und geben frei, statt bei null zu starten.", + "openLabel": "Offen", + "openTip": "Noch nicht begonnen. Kommt an die Reihe, wenn die Schritte davor stehen.", + "overdueLabel": "Fällig", + "overdueTip": "Wiederkehrende Aufgabe fällig, etwa eine jährliche Auffrischung. Kurz aktualisieren." + }, + "owner": { + "leadershipName": "Geschäftsführung", + "leadershipResp": "Genehmigt und verantwortet. Governance, Budget und die Pflichten nach § 38 BSIG.", + "securityName": "Sicherheit (CISO)", + "securityResp": "Steuert Risiko, Vorfälle, Zugriff, Schulung und Wirksamkeit.", + "itName": "IT", + "itResp": "Setzt technische Maßnahmen um: Kryptografie, Patches, Authentifizierung.", + "operationsName": "Betrieb", + "operationsResp": "Verantwortet Kontinuität, Backups und Lieferantenmanagement." + }, + "band": { + "minimumName": "Belastbares Minimum", + "minimumPhase": "Diesen Monat", + "minimumHint": "Die verpflichtenden, grundlegenden Maßnahmen. Die Basis, die zuerst stehen muss.", + "yearName": "Im Lauf des Jahres", + "yearPhase": "Nächste 3 Monate", + "yearHint": "Rund zwei Stunden pro Woche. Kein Sprint, sondern ein ruhiger Rhythmus.", + "laterName": "Geringere Priorität", + "laterPhase": "Danach", + "laterHint": "Wenn der Rest steht. Wichtig, aber nicht dringend." + }, + "row": { + "recurring": "Wiederkehrend, automatisch erinnert.", + "open": "Öffnen", + "explain": "Erklärung" + } + } } } diff --git a/messages/portal/en.json b/messages/portal/en.json index a8a85ba..c7e721d 100644 --- a/messages/portal/en.json +++ b/messages/portal/en.json @@ -50,6 +50,70 @@ "phaseAdmin": "Admin", "blockedBy": "Complete first: {codes}", "journey": "Journey", - "administration": "Administration" + "administration": "Administration", + "journeyBoard": { + "hero": { + "title": "Your NIS2 implementation", + "subtitle": "{done} of {total} records are in place. Most build themselves from your work; you review and sign off.", + "unit": "in place", + "chipPrepared": "{count} prepared for you", + "chipDone": "{count} in place", + "chipOverdue": "{count} due" + }, + "next": { + "kicker": "Up next", + "eta": "~15 min", + "cta": "Continue" + }, + "path": { + "title": "The whole path", + "hint": "Every measure. Switch between Critical first (our recommendation) and Chronological (the natural order).", + "chronoHeader": "How the process usually runs" + }, + "view": { + "critical": "Critical first", + "criticalTip": "Biggest risk and liability first. Our recommendation.", + "chrono": "Chronological", + "chronoTip": "In the natural order of the process, the way it is usually worked through." + }, + "coverage": { + "doneLabel": "In place", + "doneTip": "Implemented and documented. Stays current with little effort.", + "currentLabel": "Up next", + "currentTip": "Your next step. One click and you move on.", + "preparedLabel": "Prepared", + "preparedTip": "We drafted it for you. You review and sign off instead of starting from scratch.", + "openLabel": "Open", + "openTip": "Not started yet. Comes up once the steps before it are in place.", + "overdueLabel": "Due", + "overdueTip": "A recurring task is due, such as an annual refresh. A quick update." + }, + "owner": { + "leadershipName": "Management", + "leadershipResp": "Approves and owns it. Governance, budget and the duties under § 38 BSIG.", + "securityName": "Security (CISO)", + "securityResp": "Runs risk, incidents, access, training and effectiveness.", + "itName": "IT", + "itResp": "Implements the technical measures: cryptography, patches, authentication.", + "operationsName": "Operations", + "operationsResp": "Owns continuity, backups and supplier management." + }, + "band": { + "minimumName": "Defensible minimum", + "minimumPhase": "This month", + "minimumHint": "The mandatory, foundational measures. The base that has to stand first.", + "yearName": "Over the year", + "yearPhase": "Next 3 months", + "yearHint": "About two hours a week. Not a sprint, a calm rhythm.", + "laterName": "Lower priority", + "laterPhase": "After that", + "laterHint": "Once the rest is in place. Important, but not urgent." + }, + "row": { + "recurring": "Recurring, reminded automatically.", + "open": "Open", + "explain": "Explanation" + } + } } } diff --git a/messages/portal/es.json b/messages/portal/es.json index d410dfe..7695117 100644 --- a/messages/portal/es.json +++ b/messages/portal/es.json @@ -48,6 +48,70 @@ "phaseOperations": "Operaciones", "phaseVerification": "Verificación", "phaseAdmin": "Administración", - "blockedBy": "Complete primero: {codes}" + "blockedBy": "Complete primero: {codes}", + "journeyBoard": { + "hero": { + "title": "Su implementación de NIS2", + "subtitle": "{done} de {total} registros están listos. La mayoría surgen automáticamente de su trabajo, usted revisa y aprueba.", + "unit": "listo", + "chipPrepared": "{count} preparados para usted", + "chipDone": "{count} listos", + "chipOverdue": "{count} pendientes" + }, + "next": { + "kicker": "Ahora toca", + "eta": "~15 min", + "cta": "Continuar" + }, + "path": { + "title": "El camino completo", + "hint": "Todas las medidas. Cambie entre Lo crítico primero (nuestra recomendación) y Cronológico (el orden natural).", + "chronoHeader": "Cómo transcurre el proceso normalmente" + }, + "view": { + "critical": "Lo crítico primero", + "criticalTip": "Primero el mayor riesgo y la mayor responsabilidad. Nuestra recomendación.", + "chrono": "Cronológico", + "chronoTip": "En el orden natural del proceso, como se suele trabajar." + }, + "coverage": { + "doneLabel": "Listo", + "doneTip": "Implementado y documentado. Se mantiene al día con poco esfuerzo.", + "currentLabel": "Ahora toca", + "currentTip": "Su próximo paso. Un clic y sigue adelante.", + "preparedLabel": "Preparado", + "preparedTip": "Lo hemos redactado para usted. Usted revisa y aprueba, en lugar de empezar de cero.", + "openLabel": "Abierto", + "openTip": "Aún sin empezar. Le llegará el turno cuando los pasos previos estén listos.", + "overdueLabel": "Pendiente", + "overdueTip": "Una tarea recurrente vence, por ejemplo una actualización anual. Basta con actualizarla brevemente." + }, + "owner": { + "leadershipName": "Dirección", + "leadershipResp": "Aprueba y asume la responsabilidad. Gobernanza, presupuesto y las obligaciones del § 38 BSIG.", + "securityName": "Seguridad (CISO)", + "securityResp": "Gestiona riesgo, incidentes, acceso, formación y eficacia.", + "itName": "IT", + "itResp": "Implementa las medidas técnicas: criptografía, parches, autenticación.", + "operationsName": "Operaciones", + "operationsResp": "Responsable de continuidad, copias de seguridad y gestión de proveedores." + }, + "band": { + "minimumName": "Mínimo defendible", + "minimumPhase": "Este mes", + "minimumHint": "Las medidas obligatorias y fundamentales. La base que debe estar primero.", + "yearName": "A lo largo del año", + "yearPhase": "Próximos 3 meses", + "yearHint": "Unas dos horas por semana. No es un sprint, sino un ritmo tranquilo.", + "laterName": "Menor prioridad", + "laterPhase": "Después", + "laterHint": "Cuando lo demás esté listo. Importante, pero no urgente." + }, + "row": { + "recurring": "Recurrente, con recordatorio automático.", + "open": "Abrir", + "explain": "Explicación" + } + } } } diff --git a/messages/portal/fr.json b/messages/portal/fr.json index 1794a93..980d7f2 100644 --- a/messages/portal/fr.json +++ b/messages/portal/fr.json @@ -50,6 +50,70 @@ "phaseAdmin": "Administration", "blockedBy": "Terminez d'abord : {codes}", "journey": "Parcours", - "administration": "Administration" + "administration": "Administration", + "journeyBoard": { + "hero": { + "title": "Votre mise en oeuvre NIS2", + "subtitle": "{done} preuves sur {total} sont en place. La plupart se construisent à partir de votre travail, vous vérifiez et validez.", + "unit": "en place", + "chipPrepared": "{count} préparées pour vous", + "chipDone": "{count} en place", + "chipOverdue": "{count} à échéance" + }, + "next": { + "kicker": "À faire maintenant", + "eta": "~15 min", + "cta": "Continuer" + }, + "path": { + "title": "Le parcours complet", + "hint": "Toutes les mesures. Basculez entre Priorité au critique (notre recommandation) et Chronologique (l'ordre naturel).", + "chronoHeader": "Comment le processus se déroule habituellement" + }, + "view": { + "critical": "Priorité au critique", + "criticalTip": "Le plus grand risque et la plus grande responsabilité d'abord. Notre recommandation.", + "chrono": "Chronologique", + "chronoTip": "Dans l'ordre naturel du processus, tel qu'on le traite habituellement." + }, + "coverage": { + "doneLabel": "En place", + "doneTip": "Mis en oeuvre et documenté. Reste à jour sans grand effort.", + "currentLabel": "À faire maintenant", + "currentTip": "Votre prochaine étape. Un clic et vous avancez.", + "preparedLabel": "Préparé", + "preparedTip": "Nous l'avons rédigé pour vous. Vous vérifiez et validez au lieu de partir de zéro.", + "openLabel": "À faire", + "openTip": "Pas encore commencé. Viendra une fois les étapes précédentes en place.", + "overdueLabel": "À échéance", + "overdueTip": "Une tâche récurrente est due, par exemple une mise à jour annuelle. Une actualisation rapide." + }, + "owner": { + "leadershipName": "Direction", + "leadershipResp": "Approuve et en assume la responsabilité. Gouvernance, budget et les obligations selon § 38 BSIG.", + "securityName": "Sécurité (CISO)", + "securityResp": "Pilote le risque, les incidents, les accès, la formation et l'efficacité.", + "itName": "IT", + "itResp": "Met en oeuvre les mesures techniques : cryptographie, correctifs, authentification.", + "operationsName": "Exploitation", + "operationsResp": "Assure la continuité, les sauvegardes et la gestion des fournisseurs." + }, + "band": { + "minimumName": "Minimum défendable", + "minimumPhase": "Ce mois-ci", + "minimumHint": "Les mesures obligatoires et fondamentales. La base qui doit tenir en premier.", + "yearName": "Au cours de l'année", + "yearPhase": "3 prochains mois", + "yearHint": "Environ deux heures par semaine. Pas un sprint, un rythme tranquille.", + "laterName": "Priorité moindre", + "laterPhase": "Ensuite", + "laterHint": "Une fois le reste en place. Important, mais pas urgent." + }, + "row": { + "recurring": "Récurrent, rappel automatique.", + "open": "Ouvrir", + "explain": "Explication" + } + } } } diff --git a/messages/portal/it.json b/messages/portal/it.json index a05ff0f..790cb9b 100644 --- a/messages/portal/it.json +++ b/messages/portal/it.json @@ -50,6 +50,70 @@ "phaseAdmin": "Amministrazione", "blockedBy": "Completa prima: {codes}", "journey": "Percorso", - "administration": "Amministrazione" + "administration": "Amministrazione", + "journeyBoard": { + "hero": { + "title": "La vostra attuazione NIS2", + "subtitle": "{done} di {total} evidenze sono pronte. La maggior parte nasce automaticamente dal vostro lavoro, voi verificate e approvate.", + "unit": "attuata", + "chipPrepared": "{count} preparate per voi", + "chipDone": "{count} attuate", + "chipOverdue": "{count} in scadenza" + }, + "next": { + "kicker": "Prossimo passo", + "eta": "~15 min", + "cta": "Continua" + }, + "path": { + "title": "L'intero percorso", + "hint": "Tutte le misure. Passate da Prima le critiche (la nostra raccomandazione) a Cronologico (l'ordine naturale).", + "chronoHeader": "Come si svolge di solito il processo" + }, + "view": { + "critical": "Prima le critiche", + "criticalTip": "Prima il rischio e la responsabilità maggiori. La nostra raccomandazione.", + "chrono": "Cronologico", + "chronoTip": "Nell'ordine naturale del processo, come di norma si procede." + }, + "coverage": { + "doneLabel": "Attuata", + "doneTip": "Attuata e documentata. Resta aggiornata con poco sforzo.", + "currentLabel": "Prossimo passo", + "currentTip": "Il vostro passo successivo. Un clic e proseguite.", + "preparedLabel": "Preparata", + "preparedTip": "L'abbiamo redatta per voi. Voi verificate e approvate, invece di partire da zero.", + "openLabel": "Aperta", + "openTip": "Non ancora iniziata. Arriva quando i passi precedenti sono pronti.", + "overdueLabel": "In scadenza", + "overdueTip": "Un'attività ricorrente in scadenza, per esempio un aggiornamento annuale. Una rapida revisione." + }, + "owner": { + "leadershipName": "Direzione", + "leadershipResp": "Approva e ne risponde. Governance, budget e gli obblighi ai sensi del § 38 BSIG.", + "securityName": "Sicurezza (CISO)", + "securityResp": "Gestisce rischio, incidenti, accessi, formazione ed efficacia.", + "itName": "IT", + "itResp": "Attua le misure tecniche: crittografia, patch, autenticazione.", + "operationsName": "Operazioni", + "operationsResp": "Risponde di continuità, backup e gestione dei fornitori." + }, + "band": { + "minimumName": "Minimo difendibile", + "minimumPhase": "Questo mese", + "minimumHint": "Le misure obbligatorie e fondamentali. La base che deve reggere per prima.", + "yearName": "Nel corso dell'anno", + "yearPhase": "Prossimi 3 mesi", + "yearHint": "Circa due ore a settimana. Non uno sprint, ma un ritmo tranquillo.", + "laterName": "Priorità minore", + "laterPhase": "In seguito", + "laterHint": "Quando il resto è pronto. Importante, ma non urgente." + }, + "row": { + "recurring": "Ricorrente, con promemoria automatico.", + "open": "Apri", + "explain": "Spiegazione" + } + } } } diff --git a/messages/portal/nl.json b/messages/portal/nl.json index f443d41..c4a932d 100644 --- a/messages/portal/nl.json +++ b/messages/portal/nl.json @@ -50,6 +50,70 @@ "phaseAdmin": "Beheer", "blockedBy": "Voltooi eerst: {codes}", "journey": "Traject", - "administration": "Beheer" + "administration": "Beheer", + "journeyBoard": { + "hero": { + "title": "Uw NIS2-implementatie", + "subtitle": "{done} van {total} bewijsstukken staan. De meeste ontstaan vanzelf uit uw werk, u controleert en geeft vrij.", + "unit": "gereed", + "chipPrepared": "{count} voor u voorbereid", + "chipDone": "{count} gereed", + "chipOverdue": "{count} openstaand" + }, + "next": { + "kicker": "Nu aan de beurt", + "eta": "~15 min", + "cta": "Verder" + }, + "path": { + "title": "Het hele traject", + "hint": "Alle maatregelen. Wissel tussen Kritiek eerst (onze aanbeveling) en Chronologisch (de natuurlijke volgorde).", + "chronoHeader": "Zo verloopt het proces meestal" + }, + "view": { + "critical": "Kritiek eerst", + "criticalTip": "Grootste risico en aansprakelijkheid eerst. Onze aanbeveling.", + "chrono": "Chronologisch", + "chronoTip": "In de natuurlijke volgorde van het proces, zoals het gewoonlijk wordt afgewerkt." + }, + "coverage": { + "doneLabel": "Gereed", + "doneTip": "Geïmplementeerd en gedocumenteerd. Blijft met weinig moeite actueel.", + "currentLabel": "Nu aan de beurt", + "currentTip": "Uw volgende stap. Eén klik en u gaat verder.", + "preparedLabel": "Voorbereid", + "preparedTip": "Hebben wij voor u opgesteld. U controleert en geeft vrij, in plaats van vanaf nul te beginnen.", + "openLabel": "Open", + "openTip": "Nog niet begonnen. Komt aan de beurt zodra de stappen ervoor staan.", + "overdueLabel": "Openstaand", + "overdueTip": "Een terugkerende taak is aan de beurt, bijvoorbeeld een jaarlijkse opfrissing. Even bijwerken." + }, + "owner": { + "leadershipName": "Directie", + "leadershipResp": "Keurt goed en draagt de verantwoordelijkheid. Governance, budget en de plichten volgens § 38 BSIG.", + "securityName": "Beveiliging (CISO)", + "securityResp": "Stuurt risico, incidenten, toegang, training en effectiviteit aan.", + "itName": "IT", + "itResp": "Implementeert de technische maatregelen: cryptografie, patches, authenticatie.", + "operationsName": "Operatie", + "operationsResp": "Verantwoordelijk voor continuïteit, back-ups en leveranciersbeheer." + }, + "band": { + "minimumName": "Verdedigbaar minimum", + "minimumPhase": "Deze maand", + "minimumHint": "De verplichte, fundamentele maatregelen. De basis die eerst moet staan.", + "yearName": "In de loop van het jaar", + "yearPhase": "Komende 3 maanden", + "yearHint": "Ongeveer twee uur per week. Geen sprint, maar een rustig ritme.", + "laterName": "Lagere prioriteit", + "laterPhase": "Daarna", + "laterHint": "Zodra de rest staat. Belangrijk, maar niet dringend." + }, + "row": { + "recurring": "Terugkerend, automatische herinnering.", + "open": "Openen", + "explain": "Uitleg" + } + } } } diff --git a/messages/portal/pl.json b/messages/portal/pl.json index 9661689..0326e42 100644 --- a/messages/portal/pl.json +++ b/messages/portal/pl.json @@ -50,6 +50,70 @@ "phaseAdmin": "Administracja", "blockedBy": "Najpierw ukończ: {codes}", "journey": "Ścieżka", - "administration": "Administracja" + "administration": "Administracja", + "journeyBoard": { + "hero": { + "title": "Twoje wdrożenie NIS2", + "subtitle": "{done} z {total} dowodów jest gotowych. Większość powstaje automatycznie z Twojej pracy, Ty sprawdzasz i zatwierdzasz.", + "unit": "wdrożone", + "chipPrepared": "{count} przygotowanych dla Ciebie", + "chipDone": "{count} wdrożonych", + "chipOverdue": "{count} do zrobienia" + }, + "next": { + "kicker": "Teraz", + "eta": "~15 min", + "cta": "Dalej" + }, + "path": { + "title": "Cała droga", + "hint": "Wszystkie środki. Przełączaj między Najpierw krytyczne (nasza rekomendacja) i Chronologicznie (naturalna kolejność).", + "chronoHeader": "Tak zwykle przebiega proces" + }, + "view": { + "critical": "Najpierw krytyczne", + "criticalTip": "Najpierw największe ryzyko i odpowiedzialność. Nasza rekomendacja.", + "chrono": "Chronologicznie", + "chronoTip": "W naturalnej kolejności procesu, tak jak zwykle się to realizuje." + }, + "coverage": { + "doneLabel": "Wdrożone", + "doneTip": "Wdrożone i udokumentowane. Utrzymanie aktualności wymaga niewiele wysiłku.", + "currentLabel": "Teraz", + "currentTip": "Twój następny krok. Jedno kliknięcie i idziesz dalej.", + "preparedLabel": "Przygotowane", + "preparedTip": "Przygotowaliśmy to dla Ciebie. Sprawdzasz i zatwierdzasz, zamiast zaczynać od zera.", + "openLabel": "Otwarte", + "openTip": "Jeszcze nierozpoczęte. Przyjdzie kolej, gdy wcześniejsze kroki będą gotowe.", + "overdueLabel": "Do zrobienia", + "overdueTip": "Zadanie cykliczne wymaga wykonania, np. coroczne odświeżenie. Krótka aktualizacja." + }, + "owner": { + "leadershipName": "Zarząd", + "leadershipResp": "Zatwierdza i odpowiada. Ład organizacyjny, budżet i obowiązki wynikające z § 38 BSIG.", + "securityName": "Bezpieczeństwo (CISO)", + "securityResp": "Kieruje ryzykiem, incydentami, dostępem, szkoleniami i skutecznością.", + "itName": "IT", + "itResp": "Wdraża środki techniczne: kryptografię, aktualizacje, uwierzytelnianie.", + "operationsName": "Operacje", + "operationsResp": "Odpowiada za ciągłość, kopie zapasowe i zarządzanie dostawcami." + }, + "band": { + "minimumName": "Solidne minimum", + "minimumPhase": "W tym miesiącu", + "minimumHint": "Obowiązkowe, podstawowe środki. Baza, która musi powstać najpierw.", + "yearName": "W ciągu roku", + "yearPhase": "Najbliższe 3 miesiące", + "yearHint": "Około dwóch godzin tygodniowo. Nie sprint, lecz spokojny rytm.", + "laterName": "Niższy priorytet", + "laterPhase": "Później", + "laterHint": "Gdy reszta będzie gotowa. Ważne, ale nie pilne." + }, + "row": { + "recurring": "Cykliczne, przypomnienie automatyczne.", + "open": "Otwórz", + "explain": "Wyjaśnienie" + } + } } } diff --git a/messages/portal/pt.json b/messages/portal/pt.json index ac28b78..cf673ea 100644 --- a/messages/portal/pt.json +++ b/messages/portal/pt.json @@ -50,6 +50,70 @@ "phaseAdmin": "Administração", "blockedBy": "Concluir primeiro: {codes}", "journey": "Percurso", - "administration": "Administração" + "administration": "Administração", + "journeyBoard": { + "hero": { + "title": "A sua implementação NIS2", + "subtitle": "{done} de {total} registos estão prontos. A maioria surge automaticamente do seu trabalho, você verifica e aprova.", + "unit": "implementado", + "chipPrepared": "{count} preparados para si", + "chipDone": "{count} implementados", + "chipOverdue": "{count} pendentes" + }, + "next": { + "kicker": "Agora", + "eta": "~15 min", + "cta": "Continuar" + }, + "path": { + "title": "O caminho completo", + "hint": "Todas as medidas. Alterne entre Críticas primeiro (a nossa recomendação) e Cronológico (a ordem natural).", + "chronoHeader": "Como o processo costuma decorrer" + }, + "view": { + "critical": "Críticas primeiro", + "criticalTip": "Maior risco e maior responsabilidade primeiro. A nossa recomendação.", + "chrono": "Cronológico", + "chronoTip": "Na ordem natural do processo, como habitualmente se avança." + }, + "coverage": { + "doneLabel": "Implementado", + "doneTip": "Implementado e documentado. Mantém-se atual com pouco esforço.", + "currentLabel": "Agora", + "currentTip": "O seu próximo passo. Um clique e avança.", + "preparedLabel": "Preparado", + "preparedTip": "Redigimos para si. Você verifica e aprova, em vez de começar do zero.", + "openLabel": "Em aberto", + "openTip": "Ainda não iniciado. Chega a vez quando os passos anteriores estiverem prontos.", + "overdueLabel": "Pendente", + "overdueTip": "Tarefa recorrente pendente, por exemplo uma atualização anual. Uma atualização breve." + }, + "owner": { + "leadershipName": "Direção", + "leadershipResp": "Aprova e assume a responsabilidade. Governance, orçamento e os deveres nos termos do § 38 BSIG.", + "securityName": "Segurança (CISO)", + "securityResp": "Gere risco, incidentes, acessos, formação e eficácia.", + "itName": "IT", + "itResp": "Implementa as medidas técnicas: criptografia, patches, autenticação.", + "operationsName": "Operações", + "operationsResp": "Responsável por continuidade, backups e gestão de fornecedores." + }, + "band": { + "minimumName": "Mínimo defensável", + "minimumPhase": "Este mês", + "minimumHint": "As medidas obrigatórias e fundamentais. A base que tem de estar primeiro.", + "yearName": "Ao longo do ano", + "yearPhase": "Próximos 3 meses", + "yearHint": "Cerca de duas horas por semana. Não um sprint, mas um ritmo calmo.", + "laterName": "Prioridade menor", + "laterPhase": "Depois disso", + "laterHint": "Quando o resto estiver pronto. Importante, mas não urgente." + }, + "row": { + "recurring": "Recorrente, com lembrete automático.", + "open": "Abrir", + "explain": "Explicação" + } + } } } diff --git a/messages/portal/ro.json b/messages/portal/ro.json index 362bf80..e758da0 100644 --- a/messages/portal/ro.json +++ b/messages/portal/ro.json @@ -50,6 +50,70 @@ "phaseAdmin": "Administrare", "blockedBy": "Finalizați mai întâi: {codes}", "journey": "Parcurs", - "administration": "Administrare" + "administration": "Administrare", + "journeyBoard": { + "hero": { + "title": "Implementarea dvs. NIS2", + "subtitle": "{done} din {total} dovezi sunt gata. Cele mai multe rezultă automat din munca dvs., dvs. verificați și aprobați.", + "unit": "gata", + "chipPrepared": "{count} pregătite pentru dvs.", + "chipDone": "{count} gata", + "chipOverdue": "{count} scadente" + }, + "next": { + "kicker": "Acum urmează", + "eta": "~15 min", + "cta": "Continuați" + }, + "path": { + "title": "Tot parcursul", + "hint": "Toate măsurile. Comutați între Critice mai întâi (recomandarea noastră) și Cronologic (ordinea firească).", + "chronoHeader": "Cum decurge de obicei procesul" + }, + "view": { + "critical": "Critice mai întâi", + "criticalTip": "Riscul și răspunderea cele mai mari mai întâi. Recomandarea noastră.", + "chrono": "Cronologic", + "chronoTip": "În ordinea firească a procesului, așa cum se parcurge de obicei." + }, + "coverage": { + "doneLabel": "Gata", + "doneTip": "Implementat și documentat. Rămâne actual cu efort redus.", + "currentLabel": "Acum urmează", + "currentTip": "Următorul dvs. pas. Un clic și mergeți mai departe.", + "preparedLabel": "Pregătit", + "preparedTip": "L-am întocmit pentru dvs. Verificați și aprobați, în loc să porniți de la zero.", + "openLabel": "Deschis", + "openTip": "Neînceput încă. Vine la rând când pașii de dinainte sunt gata.", + "overdueLabel": "Scadent", + "overdueTip": "O sarcină recurentă este scadentă, de exemplu o reînnoire anuală. O actualizare scurtă." + }, + "owner": { + "leadershipName": "Conducerea", + "leadershipResp": "Aprobă și își asumă. Guvernanță, buget și obligațiile conform § 38 BSIG.", + "securityName": "Securitate (CISO)", + "securityResp": "Coordonează riscul, incidentele, accesul, instruirea și eficacitatea.", + "itName": "IT", + "itResp": "Implementează măsurile tehnice: criptografie, patch-uri, autentificare.", + "operationsName": "Operațiuni", + "operationsResp": "Răspunde de continuitate, backupuri și managementul furnizorilor." + }, + "band": { + "minimumName": "Minimul defensabil", + "minimumPhase": "Luna aceasta", + "minimumHint": "Măsurile obligatorii, de bază. Fundamentul care trebuie să fie așezat primul.", + "yearName": "Pe parcursul anului", + "yearPhase": "Următoarele 3 luni", + "yearHint": "Cam două ore pe săptămână. Nu un sprint, ci un ritm liniștit.", + "laterName": "Prioritate mai mică", + "laterPhase": "După aceea", + "laterHint": "Când restul este gata. Important, dar nu urgent." + }, + "row": { + "recurring": "Recurent, cu reamintire automată.", + "open": "Deschideți", + "explain": "Explicație" + } + } } } diff --git a/proxy.ts b/proxy.ts index 0408a8c..6e5bcd0 100644 --- a/proxy.ts +++ b/proxy.ts @@ -99,6 +99,8 @@ const CANONICAL_PUBLIC_EXACT: readonly string[] = [ "/start", "/pitch", "/supplier-portal", + // Design playground for the portal sidebar + journey redesign (preview only). + "/journey-preview", ]; const CANONICAL_PUBLIC_PREFIXES: readonly string[] = [ diff --git a/public/journey-hero-cs.png b/public/journey-hero-cs.png new file mode 100644 index 0000000..b59b152 Binary files /dev/null and b/public/journey-hero-cs.png differ diff --git a/public/journey-hero-de.png b/public/journey-hero-de.png new file mode 100644 index 0000000..f1da80b Binary files /dev/null and b/public/journey-hero-de.png differ diff --git a/public/journey-hero-en.png b/public/journey-hero-en.png new file mode 100644 index 0000000..c4dec15 Binary files /dev/null and b/public/journey-hero-en.png differ diff --git a/public/journey-hero-es.png b/public/journey-hero-es.png new file mode 100644 index 0000000..281abcb Binary files /dev/null and b/public/journey-hero-es.png differ diff --git a/public/journey-hero-fr.png b/public/journey-hero-fr.png new file mode 100644 index 0000000..e36e558 Binary files /dev/null and b/public/journey-hero-fr.png differ diff --git a/public/journey-hero-it.png b/public/journey-hero-it.png new file mode 100644 index 0000000..2390260 Binary files /dev/null and b/public/journey-hero-it.png differ diff --git a/public/journey-hero-nl.png b/public/journey-hero-nl.png new file mode 100644 index 0000000..8d25754 Binary files /dev/null and b/public/journey-hero-nl.png differ diff --git a/public/journey-hero-pl.png b/public/journey-hero-pl.png new file mode 100644 index 0000000..1d6801e Binary files /dev/null and b/public/journey-hero-pl.png differ diff --git a/public/journey-hero-pt.png b/public/journey-hero-pt.png new file mode 100644 index 0000000..c92a0dd Binary files /dev/null and b/public/journey-hero-pt.png differ diff --git a/public/journey-hero-ro.png b/public/journey-hero-ro.png new file mode 100644 index 0000000..f7da1d2 Binary files /dev/null and b/public/journey-hero-ro.png differ diff --git a/public/journey-hero.png b/public/journey-hero.png new file mode 100644 index 0000000..f1da80b Binary files /dev/null and b/public/journey-hero.png differ