SLC_Game/04_Tablet-Quiz/app/index.html
2026-06-07 14:44:36 +02:00

1506 lines
99 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>SLC-Workshop — Companion-App</title>
<meta name="description" content="Begleit-App zum SLC-Workshop-Tabletop: führt durch die Stationen, vermittelndes Quiz und Auflösung. Offline lauffähig." />
<meta name="theme-color" content="#1d2430" />
<link rel="manifest" href="manifest.webmanifest" />
<link rel="apple-touch-icon" href="icon.svg" />
<link rel="icon" href="icon.svg" type="image/svg+xml" />
<style>
:root {
--bg: #f4f5f7;
--panel: #ffffff;
--ink: #1d2430;
--muted: #6b7686;
--line: #e2e6ec;
--accent: #e2001a; /* Freiburg-Rot */
--ok: #1f9d57;
--bad: #d23b3b;
--design: #2f80c9;
--transition: #e8862b;
--operation: #2f9e57;
--support: #18a9a0;
--review: #8358c6;
--radius: 14px;
--shadow: 0 1px 3px rgba(20,30,50,.08), 0 6px 24px rgba(20,30,50,.06);
}
* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; }
body {
font: 16px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
color: var(--ink); background: var(--bg);
-webkit-font-smoothing: antialiased;
}
header {
display: flex; align-items: center; gap: 16px;
padding: 12px 20px; background: var(--panel);
border-bottom: 1px solid var(--line); position: sticky; top: 0; z-index: 5;
}
header .brand { font-weight: 700; letter-spacing: .2px; }
header .brand b { color: var(--accent); }
header .tag { font-size: 12px; color: var(--muted); border:1px solid var(--line); padding:2px 8px; border-radius:999px; }
header .spacer { flex: 1; }
.scenario { display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
.scenario select, button {
font: inherit; border-radius: 10px; border: 1px solid var(--line);
background: #fff; color: var(--ink); padding: 8px 12px; cursor: pointer;
}
.scenario select { max-width: 260px; }
button.primary { background: var(--accent); color: #fff; border-color: var(--accent); font-weight: 600; }
button.ghost { background: #fff; }
button:disabled { opacity: .45; cursor: default; }
.progress { height: 6px; background: var(--line); }
.progress > div { height: 100%; background: var(--accent); transition: width .3s; }
.layout { display: grid; grid-template-columns: 1fr; gap: 0; min-height: calc(100% - 55px); }
/* Stationsliste als aufklappbares Overlay (Header-Button ☰ Stationen) */
aside {
position: fixed; top:0; left:0; bottom:0; width: 320px; max-width: 86vw;
background: var(--panel); border-right:1px solid var(--line); padding: 14px;
overflow:auto; z-index: 30;
transform: translateX(-100%); transition: transform .22s ease;
box-shadow: 0 0 40px rgba(20,30,50,.18);
}
body.navOpen aside { transform: translateX(0); }
.navBackdrop { position:fixed; inset:0; background:rgba(15,22,35,.42); z-index:29; opacity:0; pointer-events:none; transition:opacity .2s; }
body.navOpen .navBackdrop { opacity:1; pointer-events:auto; }
body.akteOpen .navBackdrop { opacity:1; pointer-events:auto; }
/* Service-Akte als Overlay von rechts (Header-Button 📁 Akte) */
#akteList {
position: fixed; top:0; right:0; bottom:0; width: 340px; max-width: 88vw;
background: var(--panel); border-left:1px solid var(--line); padding: 14px;
overflow:auto; z-index: 30;
transform: translateX(100%); transition: transform .22s ease;
box-shadow: 0 0 40px rgba(20,30,50,.18);
}
body.akteOpen #akteList { transform: translateX(0); }
body:not(.runMode) #akteList { display:none; }
body:not(.runMode) #akteBtn { display:none; }
#akteList h3 { font-size:11px; text-transform:uppercase; letter-spacing:.8px; color:var(--muted); margin:14px 0 4px; }
.akteCount { font-size:13px; color:var(--muted); margin:2px 0 6px; font-variant-numeric:tabular-nums; }
.akteItem { display:flex; align-items:center; gap:10px; padding:7px 4px; border-radius:8px; font-size:13px; opacity:.5; }
.akteItem.have { opacity:1; }
.akteItem .aId { color:#fff; font-size:11px; font-weight:700; border-radius:6px; padding:2px 7px; min-width:30px; text-align:center; flex:none; }
.akteItem .aNm { flex:1; }
.akteItem .aNm i { color:var(--muted); font-style:italic; }
.akteItem .aChk { color:var(--ok); font-weight:700; }
/* Gate: Artefakt-Anforderung (harte Kopplung) */
.gateReq { border-radius:8px; padding:9px 12px; margin:8px 0 12px; font-size:14px; font-weight:600; line-height:1.4; }
.gateReq.ok { background:#f1faf4; color:#177a44; border:1px solid #cde6d6; }
.gateReq.bad { background:#fdf3f3; color:#b5202a; border:1px solid #f0c9c9; }
.choice[disabled] { opacity:.45; cursor:default; }
.navTop { display:flex; align-items:center; justify-content:space-between; margin-bottom:8px; }
.navTop b { font-size:13px; text-transform:uppercase; letter-spacing:.6px; color:var(--muted); }
.navTop button { border:none; background:none; font-size:20px; line-height:1; color:var(--muted); cursor:pointer; padding:4px 8px; }
aside h3 { font-size: 11px; text-transform: uppercase; letter-spacing: .8px; color: var(--muted); margin: 16px 0 6px; }
.stationItem {
display:flex; align-items:center; gap:8px; padding:8px 10px; border-radius:10px;
cursor:pointer; font-size: 14px;
}
.stationItem:hover { background: #f7f9fb; }
.stationItem.active { background: #eef4fb; font-weight:600; }
.stationItem .dot { width:10px; height:10px; border-radius:50%; flex:none; }
.stationItem .id { color: var(--muted); font-variant-numeric: tabular-nums; font-size:12px; }
.stationItem.done .id::after { content:" ✓"; color: var(--ok); }
main { padding: 28px clamp(20px, 5vw, 64px); overflow:auto; }
.card { background: var(--panel); border:1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); padding: 26px; max-width: 860px; margin: 0 auto; }
.phaseChip { display:inline-flex; align-items:center; gap:8px; font-size:12px; font-weight:700; text-transform:uppercase; letter-spacing:.6px; color:#fff; padding:5px 12px; border-radius:999px; }
.gateChip { background: var(--ink); }
.stationName { font-size: 28px; font-weight: 700; margin: 14px 0 4px; line-height:1.2; }
.stationId { color: var(--muted); font-size: 14px; }
.token { font-size:13px; color:var(--muted); margin-top:10px; }
.token b { color: var(--ink); }
.ctChip { display:inline-block; margin-left:6px; font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:.4px; color:var(--accent); border:1px solid var(--accent); border-radius:999px; padding:2px 9px; vertical-align:middle; }
.ctText { margin-top:8px; font-size:14px; color:var(--ink); background:#fff7f7; border-left:3px solid var(--accent); border-radius:8px; padding:10px 14px; max-width:760px; }
.step { margin-top: 22px; }
.stepHead { display:flex; align-items:center; gap:10px; font-size:12px; text-transform:uppercase; letter-spacing:.8px; color:var(--muted); margin-bottom:10px; }
.stepHead .n { width:22px; height:22px; border-radius:50%; background:var(--ink); color:#fff; display:grid; place-items:center; font-size:12px; }
.discuss { background:#fbfdff; border:1px dashed var(--line); border-radius:12px; padding:18px 20px; }
.discuss ul { margin:8px 0 0; padding-left:20px; }
.discuss li { margin:4px 0; }
.q { border:1px solid var(--line); border-radius:12px; padding:18px 20px; margin-bottom:14px; }
.q .frage { font-weight:600; margin-bottom:12px; }
.opts { display:grid; gap:8px; }
.opt { text-align:left; padding:12px 14px; border-radius:10px; border:1px solid var(--line); background:#fff; cursor:pointer; }
.opt:hover { border-color:#c9d2dd; }
.opt.sel { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(226,0,26,.12); }
.opt.correct { border-color: var(--ok); background:#f1faf4; }
.opt.wrong { border-color: var(--bad); background:#fdf3f3; }
.opt .mark { float:right; font-weight:700; }
.qExpl { margin-top:10px; font-size:14px; color:var(--muted); border-left:3px solid var(--line); padding-left:12px; }
.reveal h4 { margin: 18px 0 6px; font-size: 13px; text-transform:uppercase; letter-spacing:.6px; color:var(--muted); }
.reveal p { margin: 0 0 8px; }
.reveal ul { margin: 4px 0; padding-left: 20px; }
table.raci { width:100%; border-collapse: collapse; font-size:14px; }
table.raci th, table.raci td { text-align:left; padding:8px 10px; border-bottom:1px solid var(--line); }
table.raci th { color:var(--muted); font-weight:600; font-size:12px; text-transform:uppercase; letter-spacing:.5px; }
.raciBadge { display:inline-block; min-width:22px; text-align:center; font-weight:700; border-radius:6px; padding:2px 6px; font-size:12px; background:#eef0f3; color:#5a6675; }
.raci-A { background:#fbe3e3; color:#b5202a; }
.raci-R { background:#e3eefb; color:#1f5fae; }
.raci-C { background:#fff1dd; color:#b5701a; }
.raci-I { background:#eef0f3; color:#5a6675; }
.pfade { display:grid; gap:8px; }
.pfad { border:1px solid var(--line); border-left:4px solid var(--transition); border-radius:8px; padding:10px 12px; }
.pfad b { display:block; }
/* ---- Minimal Run-Screens ---- */
.sHead{display:flex;align-items:center;gap:10px}
.sHead .sId{color:var(--muted);font-size:13px;font-variant-numeric:tabular-nums}
.sTitle{font-size:22px;font-weight:700;line-height:1.25;margin:10px 0 4px}
.caseLine{color:var(--muted);font-size:13px;margin:0 0 22px}
.lead{font-size:16px;margin:0 0 16px}
.todo{margin:0 0 8px;padding-left:22px}
.todo li{margin:8px 0}
.crit{margin:8px 0 0;padding-left:20px;color:var(--muted)}
.crit li{margin:4px 0}
details.det{margin:8px 0 4px;border-top:1px solid var(--line);padding-top:12px}
details.det>summary{cursor:pointer;color:var(--muted);font-size:14px;font-weight:600;list-style:none}
details.det>summary::-webkit-details-marker{display:none}
details.det>summary::before{content:"▸ ";color:var(--muted)}
details.det[open]>summary::before{content:"▾ "}
details.det>div,details.det>p,details.det>ul{margin-top:10px}
/* Schrittweise Aktivitaet: Aufloesung */
.aufBox{background:#f1faf4;border:1px solid #cde6d6;border-left:3px solid var(--ok);border-radius:10px;padding:14px 16px;margin:14px 0 4px}
.aufBox .aufH{margin:0 0 6px;font-size:12px;text-transform:uppercase;letter-spacing:.5px;color:var(--muted)}
.aufBox ul{margin:6px 0 0;padding-left:20px}
.aufBox ul li{margin:3px 0}
.roleChips{display:flex;flex-wrap:wrap;gap:6px}
.roleChip{background:#eef0f3;border-radius:999px;padding:3px 11px;font-size:13px}
/* RACI-Legende */
.raciLegend{border:1px solid var(--line);border-radius:10px;padding:12px 14px;margin:10px 0 4px;background:#f7f9fb}
.raciLegend .rlHead{font-size:11px;text-transform:uppercase;letter-spacing:.6px;color:var(--muted);font-weight:700;margin-bottom:8px}
.raciLegend .rlRow{display:flex;align-items:flex-start;gap:10px;margin:5px 0;font-size:14px;line-height:1.45}
.raciLegend .rlRow .raciBadge{flex:none;margin-top:1px}
/* Aufgaben-Kasten (Anweisung hervorgehoben) */
.frageBox{background:#f7f9fc;border:1px solid var(--line);border-left:4px solid var(--accent);border-radius:12px;padding:14px 18px;margin:6px 0;font-size:16px;line-height:1.55}
.frageBox .frageLabel{font-size:11px;text-transform:uppercase;letter-spacing:.7px;color:var(--muted);font-weight:700;margin-bottom:5px}
/* Phasen-Abschluss-Feedback (an Gates) */
.phaseDone{margin:16px 0 4px;padding:16px 18px;border-radius:12px;background:#fffdf5;border:1px solid var(--line);border-left:4px solid var(--accent)}
.phaseDone .pdTitle{font-weight:800;font-size:18px;margin-bottom:6px}
.phaseDone p{color:var(--ink)}
/* Aktivitaets-Abschluss-Feedback */
.actDone{margin:16px 0 4px;padding:14px 16px;border-radius:12px;background:#fff;border:1px solid var(--line);border-left:4px solid var(--ok)}
.actDone .adTitle{font-weight:800;font-size:16px;color:#177a44;margin-bottom:2px}
.actDone .adPhase{margin:10px 0 0;padding-top:10px;border-top:1px dashed var(--line);font-size:15px}
.actDone.big{margin:8px 0 4px;padding:22px 24px}
.actDone.big .adTitle{font-size:22px;margin-bottom:6px}
.actDone.big p{font-size:16px}
.actions { display:flex; gap:10px; align-items:center; margin-top:24px; flex-wrap:wrap; }
.actions .spacer { flex:1; }
/* Setup-Screens (Action Card / Startpunkt) */
body:not(.runMode) aside { display:none; }
body:not(.runMode) .layout { grid-template-columns: 1fr; }
body:not(.runMode) .progress { display:none; }
body:not(.runMode) #stationsBtn { display:none; }
.cardBadge { display:none; align-items:center; gap:8px; font-size:13px; }
.cardBadge .cb-svc { font-weight:600; }
.setupHead { font-size:12px; text-transform:uppercase; letter-spacing:.8px; color:var(--muted); }
.setupTitle { font-size:26px; margin:6px 0 4px; line-height:1.2; }
.muted { color:var(--muted); }
.cardForm { display:flex; gap:16px; flex-wrap:wrap; margin:18px 0; }
.cardForm label { display:flex; flex-direction:column; gap:6px; font-size:12px; text-transform:uppercase; letter-spacing:.5px; color:var(--muted); }
.cardForm select { font:inherit; padding:10px 12px; border-radius:10px; border:1px solid var(--line); min-width:280px; background:#fff; color:var(--ink); }
.pickerList { margin:14px 0 4px; }
.pickerPhase { font-size:11px; text-transform:uppercase; letter-spacing:.8px; color:var(--muted); margin:16px 0 6px; }
.pickerItem { display:flex; align-items:center; gap:10px; padding:9px 12px; border:1px solid var(--line); border-radius:10px; margin-bottom:6px; cursor:pointer; }
.pickerItem:hover { border-color:#c9d2dd; background:#f7f9fb; }
.pickerItem.sel { border-color:var(--accent); box-shadow:0 0 0 2px rgba(226,0,26,.12); }
.pickerItem .dot { width:10px; height:10px; border-radius:50%; flex:none; }
.pickerItem .id { color:var(--muted); font-size:12px; font-variant-numeric:tabular-nums; }
.pickerItem .gate { margin-left:auto; font-size:11px; font-weight:700; color:#fff; background:var(--ink); padding:1px 8px; border-radius:999px; }
.pickerItem.correct { border-color:var(--ok); background:#f1faf4; }
.pickerItem.wrongPick { border-color:var(--bad); background:#fdf3f3; }
.recBox { border:1px solid var(--line); border-left:4px solid var(--ok); border-radius:10px; padding:14px 16px; margin:14px 0; }
.recBox h4 { margin:0 0 6px; font-size:13px; text-transform:uppercase; letter-spacing:.5px; color:var(--muted); }
/* Geführte Tour */
.tourBanner { background:#fff7f7; border:1px solid var(--line); border-left:4px solid var(--accent); border-radius:10px; padding:10px 14px; margin-bottom:16px; font-size:13px; line-height:1.45; }
.tourBanner b { color:var(--ink); }
.tourProg { font-size:12px; color:var(--muted); margin-bottom:8px; font-variant-numeric:tabular-nums; }
.tourNarr { background:#eef4fb; border-radius:12px; padding:16px 18px; margin:14px 0; font-size:16px; line-height:1.55; }
.tourNarr h4 { margin:0 0 8px; font-size:12px; text-transform:uppercase; letter-spacing:.6px; color:var(--design); }
/* ---- Neuer Einstiegs-Flow (Deck / Classify / Entry) ---- */
.deck{display:flex;flex-direction:column;gap:16px}
.deckGroup .deckSvc{font-size:13px;font-weight:700;color:var(--muted);margin-bottom:6px}
.deckRow{display:grid;grid-template-columns:repeat(5,1fr);gap:10px}
.deckCard{padding:0;border:1px solid var(--line);background:#fff;border-radius:10px;cursor:pointer;overflow:hidden;transition:transform .08s,box-shadow .08s}
.deckCard:hover{transform:translateY(-2px);box-shadow:0 4px 14px rgba(0,0,0,.16)}
.deckCard img{display:block;width:100%;height:auto}
.cardThumb{display:block;width:150px;border-radius:8px;margin:0 auto 14px;box-shadow:0 2px 10px rgba(0,0,0,.12)}
.choiceGrid{display:flex;flex-direction:column;gap:8px;margin:10px 0}
.choiceGrid.grid2{display:grid;grid-template-columns:repeat(2,1fr);gap:10px}
@media(max-width:440px){.choiceGrid.grid2{grid-template-columns:1fr}}
/* Klassifizieren: links große Karte, rechts Frage + Antworten */
.classifyTop{display:grid;grid-template-columns:minmax(220px,320px) 1fr;gap:26px;align-items:start;margin:18px 0 6px}
.classifyCard{display:block;width:100%;border-radius:12px;box-shadow:0 3px 16px rgba(0,0,0,.16)}
.classifyMain{min-width:0}
.classifyMain .phaseRow{grid-template-columns:repeat(2,1fr);margin-top:12px}
@media(max-width:680px){.classifyTop{grid-template-columns:1fr}.classifyCard{max-width:300px;margin:0 auto}.classifyMain{margin-top:6px}}
.choice{text-align:left;padding:12px 14px;border:1px solid var(--line);border-radius:10px;background:#fff;cursor:pointer;font-size:15px;font-weight:600}
.choice:hover{border-color:var(--ink)}
.choice.bad{border-color:var(--bad);background:#fdf3f3}
.legend{border:1px solid var(--line);border-radius:10px;padding:10px 14px;margin-top:12px;background:#f7f9fb}
.legend h4{margin:0 0 8px;font-size:12px;text-transform:uppercase;letter-spacing:.5px;color:var(--muted)}
.legend dt{font-weight:700;font-size:13px}
.legend dd{margin:2px 0 8px;color:var(--muted);font-size:13px}
.hint{font-weight:600;margin:8px 0}
.hint.bad{color:var(--bad)} .hint.ok{color:var(--ok)}
.phaseRow{display:grid;grid-template-columns:repeat(5,1fr);gap:8px;margin:14px 0}
.phaseZone{padding:22px 8px;border-radius:12px;color:#fff;font-weight:800;font-size:14px;text-align:center;cursor:pointer;border:0}
.phaseZone:hover{filter:brightness(1.06)}
.phaseZone.bad{outline:3px solid var(--bad);outline-offset:2px}
@media(max-width:760px){.deckRow{grid-template-columns:repeat(3,1fr)}.phaseRow{grid-template-columns:repeat(3,1fr)}}
</style>
</head>
<body>
<header>
<div class="brand">SLC&nbsp;<b>Companion</b></div>
<span class="tag">v0.6</span>
<div id="cardBadge" class="cardBadge"></div>
<div class="spacer"></div>
<button class="ghost" id="akteBtn" title="Service-Akte (gesammelte Artefakte)">📁&nbsp;Akte</button>
<button class="ghost" id="stationsBtn" title="Stationsübersicht">&nbsp;Stationen</button>
<button class="ghost" id="resetBtn" title="Neue Action Card / Durchlauf zurücksetzen">Neu starten</button>
</header>
<div class="progress"><div id="progressBar" style="width:0%"></div></div>
<div class="navBackdrop" id="navBackdrop"></div>
<div class="layout">
<aside id="stationList"></aside>
<div id="akteList"></div>
<main><div class="card" id="panel"></div></main>
</div>
<script>
/* Empfohlener Einstiegspunkt je Change-Typ (didaktische Auflösung in Schritt 2).
Reihenfolge entspricht CHANGE_TYPES. */
const START_EMPFEHLUNG = [
{ id:"ds_01", grund:"Ein Major Change ist strategisch getrieben und betrifft den Service grundlegend er durchläuft den vollen Lebenszyklus ab dem Design (Service-Definition)." },
{ id:"tr_01", grund:"Ein Normal Change ist geplant und dokumentiert, aber nicht strategisch. Er steigt an Gate 1 ein, wo Build-oder-Konfiguration entschieden wird meist der Konfigurationspfad (tr_05)." },
{ id:"op_03", grund:"Ein Standard Change ist vorab genehmigt und im Katalog hinterlegt. Er braucht keine Gates und kein Design, sondern wird im laufenden Betrieb umgesetzt (op_03)." },
{ id:"tr_10", grund:"Ein Emergency Change muss die Störung sofort beheben. Der Fix wird beschleunigt ausgerollt (tr_10); die formale Freigabe (Gate 3) erfolgt nachgelagert." }
];
/* Geführtes Beispiel ("for dummies"-Tour): EIN konkreter Fall durch den ganzen
Lifecycle. Online-Bürgerportal · Major Change Top-Level (neues Landesgesetz zur
digitalen Bürgerbeteiligung → Erweiterung um Beteiligungs-Module). */
const TOUR = {
service: 2, change: 0,
text: {
ds_01: "Das neue Landesgesetz verlangt Online-Beteiligung. Zuerst klären Fachamt und (designierter) Service Owner, was das Beteiligungs-Modul leisten muss: Wer nutzt es (Bürger:innen, Planungsamt)? Welchen Nutzen bringt es? Welche Fristen und Verfügbarkeiten sind gesetzlich nötig? Ergebnis ist ein erster Entwurf der Service-Definition.",
ds_02: "Jetzt entsteht das Lösungsdesign: welche Module (Online-Konsultation, Kommentar-Workflows, Veröffentlichung von Plänen), welche Schnittstellen zum bestehenden Portal und zum DMS, welche Datenschutz- und Barrierefreiheits-Vorgaben. Das ist das Service Design Document.",
ds_03: "Hier wird geplant, WIE das Modul organisatorisch eingeführt wird noch nicht technisch: Wer schult die Sachbearbeiter:innen im Planungsamt? Welche Prozesse und Rollen ändern sich? Wann kann der Betrieb übernehmen? Ergebnis: Implementation Blueprint.",
ds_04: "Letzte Vorbereitung vor der Transition: Betrieb und Support werden abgestimmt, Tools und Strukturen vorbereitet, ein Early-Life-Support-Konzept skizziert, und der Service wird vollständig im Portfolio erfasst.",
tr_01: "Erstes Tor die SOR entscheidet: bauen oder nur konfigurieren? Da völlig neue Beteiligungs-Module gebraucht werden, fällt die Entscheidung auf ENTWICKLUNG → weiter zu tr_02. (Bei einer reinen Einstellung wäre es Konfiguration, und tr_02tr_04 würden übersprungen.)",
tr_02: "Die Projektleitung steuert die Umsetzung: Abstimmung mit dem Dienstleister, der die Module baut, dazu Termine, Budget und Arbeitspakete.",
tr_03: "Projektteam bzw. Dienstleister entwickeln die Beteiligungs-Module: Formulare, Kommentar-Workflows, Veröffentlichungsfunktion und die Schnittstellen.",
tr_04: "Die fertigen Komponenten werden geprüft und angenommen (Vollständigkeit, Qualität, Dokumentation) und ans Testmanagement übergeben.",
tr_05: "Parameter, Rollen, Zugänge und die Anbindung an Portal und Service-Katalog werden eingerichtet. (Genau hier wäre der Einstieg gewesen, hätte Gate 1 'Konfiguration' entschieden.)",
tr_06: "Die Betriebs- und Supportunterlagen entstehen: Betriebshandbuch, Supportanleitungen, Monitoring-Vorgaben und Eskalationswege für das neue Modul.",
tr_07: "Das Testmanagement prüft Funktion, Integration und Abnahme etwa ob eine Online-Konsultation fristgerecht startet und endet und Eingaben korrekt gespeichert werden.",
tr_08: "Der Build ist fertig. Alle Unterlagen und Testprotokolle werden für die Prüfung an Gate 2 zusammengestellt.",
tr_09: "Zweites Tor der Service Owner entscheidet allein: Ist alles übergabefähig? Doku vollständig, Abnahme liegt vor, Betrieb und Support vorbereitet → FREIGABE → weiter zu tr_10.",
tr_10: "Das Betriebsteam rollt die Module in die produktive Umgebung aus: Systeme konfigurieren, Daten migrieren, Monitoring aktivieren, Zugänge schalten.",
tr_11: "Letzter Check vor dem Go-Live: Betriebsdoku, Überwachung und Eskalationswege werden geprüft, die Go-Live-Kommunikation an die Ämter vorbereitet und der Gate-3-Antrag erstellt.",
tr_12: "Drittes Tor die SOR entscheidet im Konsent: Go-Live? Portfolio passt, Betrieb und Support sind bereit, SLAs vereinbart → GO-LIVE. Der Service wird formal ins Portfolio aufgenommen und geht in die Operation.",
op_01: "Direkt nach dem Start läuft das Modul unter erhöhter Aufmerksamkeit (Early Life Support): enge Beobachtung, schnelle Hilfe, erste Bürger-Rückmeldungen werden aufgearbeitet. Der Service Owner entscheidet, wann der Normalbetrieb beginnt.",
op_02: "Betriebshandbuch und Betriebsprozesse sind bereitgestellt klare Regeln und Zuständigkeiten für den täglichen Betrieb des Beteiligungs-Moduls.",
op_03: "Tagesgeschäft: Benutzer und Berechtigungen, Backups, Routinepflege und die Umsetzung freigegebener Standard-Changes (z. B. ein neues Beteiligungsverfahren anlegen).",
op_04: "Es wird sichergestellt, dass genug Personal, Technik und Budget vorhanden sind inklusive Steuerung des Dienstleisters.",
op_05: "Verfügbarkeit und Performance werden überwacht besonders wichtig, wenn zum Ende einer Beteiligungsfrist viele Bürger:innen gleichzeitig zugreifen.",
op_06: "Regelmäßiger Qualitätsbericht: Wurden SLAs/SLOs eingehalten? Wie war die Verfügbarkeit während der Konsultationen? Das ist die Grundlage fürs Review.",
op_07: "Über das reine Monitoring hinaus: Trends erkennen etwa wiederkehrende Lastspitzen oder ein Formular, das auffällig oft abgebrochen wird.",
sp_01: "Der Support bekommt seinen Rahmen: Incident- und Request-Prozesse, Reaktionszeiten und Ticketkategorien für das Beteiligungs-Modul.",
sp_02: "Lösungen, Workarounds und FAQ werden gepflegt damit der 1st Level häufige Bürgerfragen schnell beantworten kann.",
sp_03: "Eingehende Tickets von Bürger:innen und Ämtern werden gesichtet, priorisiert und an die richtige Stelle geroutet.",
sp_04: "Standardanfragen werden erledigt z. B. ein Zugang für eine neue Sachbearbeiterin im Planungsamt.",
sp_05: "Typische Störungen löst der 1st Level direkt etwa wenn ein Nutzer den Kommentar-Button nicht findet (Anleitung aus der Wissensdatenbank).",
sp_06: "Knifflige Fälle gehen an den 2nd Level z. B. Kommentare werden unter hoher Last nicht gespeichert. Bleibt es ungelöst, entsteht daraus ein Problem Record.",
sp_07: "Der Fall wird sauber abgeschlossen: Lösung validiert, Nutzer informiert, Dokumentation und FAQ aktualisiert.",
sp_08: "Endgültiges Schließen inklusive Klassifizierung für die Statistik und Erkennen wiederkehrender Fälle.",
sp_09: "Ein Incident bleibt ungelöst eine strukturelle Ursache wird vermutet. Der Problem Manager legt einen Problem Record an.",
sp_10: "Mehrere ähnliche Speicher-Fehler häufen sich → sie werden geclustert und gebündelt als Problem Record erfasst.",
sp_11: "Ursachensuche: Das Speicherproblem entsteht durch einen Timeout unter Last. Es gibt einen Workaround (Eintrag in die Wissensdatenbank) und die Entscheidung, dass ein Change nötig ist.",
rv_01: "Im Service-Review werden die Betriebsdaten des Beteiligungs-Moduls ausgewertet: KPIs, die gehäuften Speicher-Incidents, Kundenfeedback und die Infrastruktur — alles fließt ins Service-Review-Dokument.",
rv_02: "Die Ergebnisse werden bewertet: Der wiederkehrende Speicher-Fehler rechtfertigt einen RFC. Der Bericht geht an die SOR, die ganzheitlich bewertet.",
rv_03: "Auf Basis der Bewertung werden Änderungsvorschläge formuliert (z. B. Last-/Timeout-Optimierung), konsolidiert und die ausgewählte Änderung beschrieben.",
rv_04: "Die Änderung wird gestartet. Es ist eine überschaubare Anpassung → Normal-Change-Logik: Umsetzung planen. (Wäre sie grundlegend, würde hier das Routing RUN/DPM/MB geklärt und ein Change-Steckbrief erstellt.)",
rv_05: "Die Änderung wird umgesetzt: Im Weg RUN führt der Service Owner sie durch, dokumentiert und schließt sie ab. Der Service läuft verbessert weiter — der Kreis schließt sich."
}
};
/* =========================================================================
STATIONEN — vollständiger Lifecycle, abgeleitet aus den Blueprint-YAMLs
(service-lifecycle_design/transition/operation/support/review.yaml v3.2 +
spm_rollen.yaml). Reihenfolge: Design → Transition (inkl. Gate 13) →
Operation ⇄ Support → Review. Quiz-Fragen sind vermittelnd formuliert.
Im Echtbetrieb generiert ein Build-Skript questions.json aus den YAMLs.
========================================================================= */
const ROLLEN = {
spm:"Service-Portfolio-Manager", sor:"Service Operations Runde (SOR)",
service_owner:"Service Owner", al_basis_cloud:"AL Basis & Cloud",
al_applikationen:"AL Applikationen", support_manager:"Support Manager",
problem_manager:"Problem Manager", projektleitung:"Projektleitung",
betriebsteam:"Betriebsteam", service_support_team:"Service-Support Team",
projektteam:"Projektteam", queue_koordinator:"Queue Koordinator",
first_level_agent:"1st Level Agent", second_level_agent:"2nd Level Agent",
testmanagement:"Testmanagement", architektur:"Architektur",
lieferant:"Lieferant", operations_manager:"Betrieb (AL B&C / AL App)", dpm:"Demand Portfolio Manager"
};
const PHASEN = {
design:{label:"Design", color:"var(--design)"},
transition:{label:"Transition", color:"var(--transition)"},
operation:{label:"Operation", color:"var(--operation)"},
support:{label:"Support", color:"var(--support)"},
review:{label:"Review", color:"var(--review)"}
};
/* Action Cards: 6 Services × 5 Change-Typen (aus „Use Cases mit möglichen Changes"). */
const CHANGE_TYPES = [
"Major Change",
"Normal Change",
"Standard Change",
"Emergency Change"
];
const CHANGE_LEGEND = [
"Strategisch/grundlegend, braucht ein echtes Design — durchläuft den vollen Lebenszyklus ab dem Design. Freigabe in der SOR; reicht deren Ressourcen-/Entscheidungshoheit nicht, wird daraus ein Demand (Routing DPM → Mission Board).",
"Geplant und dokumentiert, aber nicht strategisch — Einstieg an Gate 1 (Bauen oder Konfigurieren), meist Konfiguration.",
"Vorab genehmigt und im Katalog hinterlegt — keine Gates, kein Design, direkt im laufenden Betrieb.",
"Muss eine Störung sofort beheben — beschleunigt umgesetzt; die formale Freigabe erfolgt nachgelagert."
];
const USE_CASES = [
{ service:"Zentrale VDI (Virtual-Desktop-Infrastructure)",
desc:"Bereitstellung von virtuellen Windows-Desktops über das interne Rechenzentrum.",
changes:[
{titel:"Open Source von oben!", text:"Der OB gibt die Richtung vor: Die proprietäre VDI-Lösung soll auf eine Open-Source-Alternative (OpenStack + Thin-Client) umgestellt werden."},
{titel:"Tapetenwechsel", text:"Die Stadt bekommt ein neues Logo — der Desktop-Hintergrund aller virtuellen Arbeitsplätze muss angepasst werden."},
{titel:"Quartalspflege", text:"Das turnusmäßige Firmware-Update der VDI-Host-Hypervisoren steht an — im Standard-Change-Katalog längst hinterlegt."},
{titel:"Blackout!", text:"Ein Stromausfall reißt ein ganzes VDI-Host-Cluster aus dem Betrieb — die Sitzungen müssen sofort auf ein Backup-Cluster migriert werden."}
]},
{ service:"Managed VPN-Access Service",
desc:"Zentral verwalteter VPN-Dienst, der Mitarbeitenden der Fachämter sicheren Remote-Zugriff auf interne Systeme (Intranet, Fachanwendungen, Datenbanken) ermöglicht.",
changes:[
{titel:"Brüssel ruft!", text:"Eine neue EU-weite IT-Sicherheitsverordnung zwingt dazu, die gesamte VPN-Architektur neu aufzustellen."},
{titel:"Heimvorteil", text:"Ein neues Intranet-Portal soll in die Split-Tunnel-Liste, damit Mitarbeitende auch aus dem Homeoffice darauf zugreifen."},
{titel:"Monatsroutine", text:"Das monatliche Firmware-Update der VPN-Appliance steht an — als Standard-Change bereits freigegeben."},
{titel:"Gephisht!", text:"Ein erfolgreicher Phishing-Angriff hat eine VPN-Zertifikatskette kompromittiert — sofort sperren und neu ausstellen."}
]},
{ service:"Online-Bürgerportal für Meldungen & Anträge",
desc:"Zentrales Web-Portal, über das Bürger*innen Meldungen (z. B. Straßenreparatur, Lärm) und Anträge (z. B. Baugenehmigung, Personalausweis) digital einreichen und den Status verfolgen.",
changes:[
{titel:"Mitreden, Pflicht!", text:"Ein neues Landesgesetz schreibt digitale Bürgerbeteiligung vor — das Portal muss um komplette Beteiligungs-Module erweitert werden."},
{titel:"Rotstift gefragt", text:"Der Bürgerservice meldet einen Rechtschreibfehler in einem statischen Hinweistext, der korrigiert werden muss."},
{titel:"Patchday", text:"Das monatliche Sicherheits-Patch des Webservers (Apache/Nginx) steht an — im Change-Katalog definiert."},
{titel:"Lücke im Formular!", text:"In einem Eingabe-Formular wird eine kritische XSS-Schwachstelle entdeckt — ein Hotfix muss sofort raus."}
]},
{ service:"Zentrales Dokumenten-Management-System (DMS)",
desc:"Elektronisches Archiv, das alle ein- und ausgehenden Dokumente (PDF, Scans, E-Mails) zentral speichert, versioniert und revisionssicher archiviert.",
changes:[
{titel:"Datendiät", text:"Eine DSGVO-Ergänzung zur Datenminimierung erzwingt die komplette Neugestaltung von Metadaten-Modell und Archivierungs-Policies."},
{titel:"Dropdown-Wunsch", text:"Das Finanzamt bittet um ein neues Metadaten-Feld „Kostenstelle“ als Auswahlliste."},
{titel:"Versionspflege", text:"Das quartalsweise Update der DMS-Software (Sicherheits- und Funktions-Patches) steht an."},
{titel:"Ransomware!", text:"Alarm: Dokumente werden verdächtig verschlüsselt — Storage sofort isolieren und aus den letzten Snapshots wiederherstellen."}
]},
{ service:"Kommunales GIS-Portal (Geoinformations-System)",
desc:"Web-basiertes Karten- und Analyse-Portal, das Fachämtern (Bau, Umwelt, Verkehr) räumliche Daten (Flurstücke, Infrastruktur, Umweltzonen) und bearbeitbare Layer bereitstellt.",
changes:[
{titel:"Norm-Zwang", text:"Eine bundesweite Vorgabe zu EU-Standards erzwingt die komplette Migration des GIS-Stacks auf konforme Services und Datenmodelle."},
{titel:"Falsch beschriftet", text:"Das Bauamt meldet eine falsche Beschriftung eines Karten-Layers, die korrigiert werden muss."},
{titel:"GeoServer-Update", text:"Das monatliche Update der GIS-Software (GeoServer 2.23 → 2.24) steht an — im Standard-Change-Katalog."},
{titel:"Schnittstelle offen!", text:"An einer Schnittstelle wird eine kritische Schwachstelle entdeckt, die unautorisierten Datenzugriff erlaubt — Dienst sofort abschalten und patchen."}
]},
{ service:"Beschaffungs- und Vertrags-System für Fachämter",
desc:"Web-Applikation, über die Fachämter interne Beschaffungen (Material, Dienstleistungen) anlegen, prüfen und Verträge digital verwalten.",
changes:[
{titel:"Vergabe neu!", text:"Eine neue EU-Vergaberichtlinie zwingt zur Einführung von E-Invoicing und erweiterten Transparenz-Reports."},
{titel:"Vierstellig, bitte", text:"Das Finanzamt wünscht eine kleine Anpassung: aus dem Label „Kostenstelle“ wird „Kostenstelle (4-stellig)“."},
{titel:"Patch-Quartal", text:"Das quartalsweise Sicherheits-Patch des Anwendungsservers steht an — bereits im Change-Katalog."},
{titel:"Upload-Falle!", text:"Im Vertrags-Upload wird eine kritische Lücke entdeckt, über die sich Schadcode hochladen lässt — Endpoint sofort sperren, Hotfix einspielen."}
]}
];
const STATIONEN = [
{ id:"ds_01", phase:"design", typ:"aktivitaet",
name:"Definieren der erforderlichen Service-Eigenschaften",
beschreibung:"Grundlegende Definition des neuen oder geänderten Services aus fachlicher Perspektive. Startpunkt für die Service-Definition als zentrales Artefakt.",
umfasst:["Zweck, Nutzen, Zielgruppen","Utility & Warranty","SLA-/SLO-Anforderungen","Abhängigkeiten zu unterstützenden Services","Fachliche & technische Anforderungen"],
artefakt:"Service-Definition (Entwurf)",
raci:[["service_owner","A"],["projektleitung","R"],["betriebsteam","C"],["architektur","C"],["spm","C"]],
quiz:[
{frage:"Wer trägt die Ergebnisverantwortung (A) für diese Aktivität?",
optionen:["Service Owner","Projektleitung","SPM","Architektur"], richtig:0,
expl:"Der (designierte) Service Owner ist Accountable; die Projektleitung führt operativ aus (R)."},
{frage:"Welches zentrale Artefakt entsteht hier?",
optionen:["Betriebshandbuch","Service-Definition","Review-Bericht","Incident Record"], richtig:1,
expl:"Die Service-Definition ist das zentrale Artefakt der Design-Phase."}
]},
{ id:"ds_02", phase:"design", typ:"aktivitaet",
name:"Designen der erforderlichen Service- und Service-Management-Komponenten",
beschreibung:"Einarbeiten der Service-Eigenschaften in ein vollständiges Designmodell.",
umfasst:["Servicearchitektur (Komponenten, Schnittstellen)","Design der Betriebs- & Supportprozesse","Sicherheit/Compliance/Datenschutz","Monitoring & Reporting","Rollenmodell"],
artefakt:"Service Design Document",
raci:[["service_owner","A"],["architektur","R"],["projektteam","R"],["spm","I"]],
quiz:[
{frage:"Wer führt das Architektur-Design operativ aus (R)?",
optionen:["SPM","Service Owner","Architektur","Support Manager"], richtig:2,
expl:"Architektur und Projektteam sind Responsible; der Service Owner bleibt Accountable."}
]},
{ id:"ds_03", phase:"design", typ:"aktivitaet",
name:"Beschreiben des Vorgehens zur Implementierung",
beschreibung:"Planen, WIE der Service organisatorisch eingeführt wird (nicht technisch deployt wird).",
umfasst:["Organisatorische Integration","Rollenübergaben","Anpassung Richtlinien/Prozesse/Tools","Trainings & Kommunikation","Time-to-Operate"],
artefakt:"Implementation Blueprint",
raci:[["service_owner","A"],["projektleitung","R"],["betriebsteam","C"],["service_support_team","C"],["spm","I"]],
quiz:[
{frage:"Was wird hier geplant?",
optionen:["Das technische Deployment","Wie der Service organisatorisch eingeführt wird","Die Incident-Bearbeitung","Die Portfolio-Strategie"], richtig:1,
expl:"ds_03 plant die organisatorische Einführung — bewusst getrennt vom technischen Build."}
]},
{ id:"ds_04", phase:"design", typ:"aktivitaet",
name:"Vorbereiten der Service-Implementierung",
beschreibung:"Finalisieren aller organisatorischen Voraussetzungen für die spätere Übergabe in den Betrieb.",
umfasst:["Abstimmung mit Betrieb & Support","Prozesse/Tools/Strukturen vorbereiten","ELS-Konzept (Early Life Support)","Service vollständig im Portfolio erfasst"],
artefakt:"Übergabe-Readiness (organisatorisch)",
raci:[["service_owner","A"],["projektleitung","R"],["betriebsteam","C"],["service_support_team","C"],["spm","I"]],
quiz:[
{frage:"Wofür steht das ELS-Konzept?",
optionen:["Early Life Support","End of Life Service","Enterprise License System","Externer Lieferanten-Support"], richtig:0,
expl:"Early Life Support = erhöhte Betreuung direkt nach Go-Live (siehe op_01)."}
]},
{ id:"tr_01", phase:"transition", typ:"gate", gateNr:1,
name:"Gate 1: Entwicklung oder Konfiguration?",
beschreibung:"Entry-Gate der Transition: Entscheidung, ob die Service-Komponenten entwickelt oder nur konfiguriert werden. Erfordert SOR-Approval (Budget- und Ressourcenimplikationen).",
umfasst:["Aufwandsschätzung (Build vs. Configure)","Technische Risiken","Budget-Abgleich","ggf. Lieferanten-Einbindung","SOR-Vorlage für Freigabe"],
artefakt:"Gate-Entscheidung + SOR-Vorlage",
pfade:[
["Entwicklung","Neuentwicklung/wesentliche Anpassung → weiter zu tr_02"],
["Konfiguration","Bestehende Komponenten konfigurieren → springt zu tr_05"]
],
pruef:[
["Design-Vollständigkeit","Ist das Service Design Document vollständig?"],
["Budget-Verfügbarkeit","Ist Budget für die Strategie verfügbar?"],
["Projektressourcen","Können Ressourcen mobilisiert werden?"],
["Betriebs-/Support-Bereitschaft","Grundsätzliches Commitment vorhanden?"]
],
raci:[["sor","A"],["service_owner","R"],["projektleitung","R"],["architektur","R"],["spm","C"],["lieferant","I"]],
quiz:[
{frage:"Wer entscheidet an Gate 1 (Accountable)?",
optionen:["Service Owner","SOR","SPM","Projektleitung"], richtig:1,
expl:"Gate 1 ist eine SOR-Gremienentscheidung (Budget-/Ressourcenimplikationen)."},
{frage:"Welche zwei Pfade öffnet Gate 1?",
optionen:["Go oder Stop","Intern oder Extern","Entwicklung oder Konfiguration","Test oder Release"], richtig:2,
expl:"Entwicklung (tr_02) vs. Konfiguration (tr_05) — Konfiguration überspringt tr_02tr_04."}
]},
{ id:"tr_02", phase:"transition", typ:"aktivitaet",
name:"Koordinieren der Entwicklungs- und Beschaffungsaktivitäten",
beschreibung:"Steuern der Entwicklungsaktivitäten im Projekt.",
umfasst:["Abstimmung mit Lieferanten","Ressourcenplanung","Termin- und Budgetnachführung","Sicherstellung von Change-Kontrollen","Definition von Build- und Konfigurationspaketen"],
artefakt:"Projektsteuerung / Build- & Konfigurationspakete",
raci:[["projektleitung","A"],["architektur","C"],["service_owner","I"],["lieferant","C/I"]],
quiz:[
{frage:"Wer trägt die Ergebnisverantwortung (A) für die Koordination der Entwicklung?",
optionen:["Projektleitung","Architektur","Service Owner","Lieferant"], richtig:0,
expl:"Die Projektleitung ist Accountable; Architektur berät (C), der Service Owner ist informiert (I)."}
]},
{ id:"tr_03", phase:"transition", typ:"aktivitaet",
name:"Entwickeln von Anwendungen und Systemen",
beschreibung:"Realisierung der technischen Service-Komponenten.",
umfasst:["Softwareentwicklung","Customizing","Schnittstellenentwicklung","Infrastrukturaufbau","Versionierung & Dokumentation","Testdaten & Migrationspfade vorbereiten"],
artefakt:"Entwickelte Service-Komponenten",
raci:[["projektleitung","A"],["projektteam","R"],["lieferant","R"],["service_owner","C"],["architektur","C"]],
quiz:[
{frage:"Wer führt die Entwicklung operativ aus (R)?",
optionen:["SPM","Projektteam bzw. Lieferant","Service Owner","Testmanagement"], richtig:1,
expl:"Projektteam (intern) und Lieferant (extern) sind Responsible; die Projektleitung bleibt Accountable."}
]},
{ id:"tr_04", phase:"transition", typ:"aktivitaet",
name:"Entgegennehmen der Service-Komponenten",
beschreibung:"Qualitätssicherung beim Übergang von der Entwicklung zur Testphase.",
umfasst:["Eingangskontrolle","Prüfung von Vollständigkeit / Qualität","Sicherstellung der Dokumentationen","Übergabe an Testmanagement"],
artefakt:"Abgenommene Service-Komponenten",
raci:[["service_owner","A"],["projektleitung","R"],["testmanagement","R"],["lieferant","C"]],
quiz:[
{frage:"An wen werden die entgegengenommenen Komponenten übergeben?",
optionen:["An den Betrieb","An das Testmanagement","An die SOR","An das SPM"], richtig:1,
expl:"tr_04 ist die Qualitätssicherung beim Übergang in die Testphase — Übergabe an das Testmanagement."}
]},
{ id:"tr_05", phase:"transition", typ:"aktivitaet",
name:"Konfiguration der Service-Komponenten",
beschreibung:"Einrichtung von Konfigurationsparametern, Settings, Rollen und Zugängen. Einstiegspunkt bei Gate-1-Entscheidung Konfiguration (überspringt tr_02tr_04).",
umfasst:["Setup von Parametern, Policies, Templates","Toolkonfiguration (ITSM-Systeme, Monitoring)","Anpassung bestehender Komponenten","Verknüpfung mit Service-Katalog / Portfolio"],
artefakt:"Konfigurierte Service-Komponenten",
raci:[["service_owner","A"],["projektteam","R"],["betriebsteam","C"],["service_support_team","C"]],
quiz:[
{frage:"Bei welcher Gate-1-Entscheidung ist tr_05 der Einstiegspunkt?",
optionen:["Entwicklung","Konfiguration","Ablehnung","Go-Live"], richtig:1,
expl:"Bei der Entscheidung Konfiguration werden tr_02tr_04 übersprungen und direkt bei tr_05 eingestiegen."}
]},
{ id:"tr_06", phase:"transition", typ:"aktivitaet",
name:"Erstellen oder Aktualisieren der Betriebs-Dokumentation",
beschreibung:"Alle Betriebsunterlagen werden erstellt oder aktualisiert.",
umfasst:["Service Operation Manual","Supportanleitungen","Monitoring-Spezifikationen","Eskalationswege","Standard Changes","Konfigurations-/Betriebsrichtlinien"],
artefakt:"Betriebsdokumentation (Service Operation Manual)",
raci:[["service_owner","A"],["projektteam","R"],["betriebsteam","C"],["service_support_team","C"]],
quiz:[
{frage:"Welches Artefakt entsteht hier vor allem?",
optionen:["Service-Definition","Betriebsdokumentation (Service Operation Manual)","Gate-Vorlage","Review-Bericht"], richtig:1,
expl:"tr_06 erstellt/aktualisiert alle Betriebsunterlagen (u.a. Service Operation Manual, Supportanleitungen)."}
]},
{ id:"tr_07", phase:"transition", typ:"aktivitaet",
name:"Testen der Service-Komponenten",
beschreibung:"Verifizierung, dass alle Servicekomponenten funktionsfähig und betriebsbereit sind.",
umfasst:["Funktionstests","Integrationstests","Abnahmetests","Nachweis der Betriebsreife","Testprotokolle & Freigaben"],
artefakt:"Testprotokolle & Freigaben",
raci:[["projektleitung","A"],["testmanagement","R"],["service_owner","C"],["lieferant","C/I"]],
quiz:[
{frage:"Wer ist Responsible (R) für das Testen der Komponenten?",
optionen:["Projektleitung","Testmanagement","Service Owner","Betriebsteam"], richtig:1,
expl:"Das Testmanagement ist Responsible; die Projektleitung ist Accountable."}
]},
{ id:"tr_08", phase:"transition", typ:"aktivitaet",
name:"Formale Übergabe (Build abgeschlossen)",
beschreibung:"Der Build ist abgeschlossen. Alle Artefakte werden für die Entry-Prüfung (Gate 2) bereitgestellt.",
umfasst:["Bereitstellung aller Betriebsunterlagen","Bereitstellung der Testprotokolle","Dokumentierter Übergabe-Termin","Vorbereitung Gate-2-Antrag"],
artefakt:"Übergabepaket (Gate-2-Antrag)",
raci:[["projektleitung","A"],["service_owner","R"],["betriebsteam","C"],["service_support_team","C"],["spm","I"]],
quiz:[
{frage:"Worauf bereitet die formale Übergabe (Build abgeschlossen) vor?",
optionen:["Gate 1","Gate 2","Gate 3","ELS-Exit"], richtig:1,
expl:"tr_08 stellt alle Artefakte für die Entry-Prüfung an Gate 2 (tr_09) bereit."}
]},
{ id:"tr_09", phase:"transition", typ:"gate", gateNr:2,
name:"Gate 2: Entry-Prüfung nach Build",
beschreibung:"Validierung der Übergabefähigkeit nach Abschluss des Builds. Dies ist eine SO-Einzelentscheidung (keine Gremienentscheidung).",
umfasst:["Prüfung der Übergabe-Vollständigkeit","Prüfung des Abnahme-Status","Ressourcen-Outlook (Betrieb/Support vorbereitet)","Pilot-Entscheidung (falls erforderlich)","Dokumentation im Transition-Steckbrief"],
artefakt:"Gate-2-Entscheidung (Transition-Steckbrief)",
pfade:[
["Freigabe","Weiter zu den Deploy-Aktivitäten → tr_10"],
["Freigabe mit Auflagen","Weiter, definierte Punkte müssen nachgearbeitet werden"],
["Zurück an Build","Nachbesserung erforderlich → tr_02 (ggf. tr_05tr_07)"],
["Ablehnung","Endgültige Ablehnung — erfordert SOR-Eskalation"]
],
pruef:[
["Übergabe-Vollständigkeit","Sind alle Artefakte vorhanden?"],
["Abnahme-Status","Liegt die Auftraggeber-Abnahme vor?"],
["Ressourcen-Outlook","Sind Betrieb und Support prinzipiell vorbereitet?"],
["Pilot-Entscheidung","Ist ein Pilotbetrieb erforderlich?"]
],
raci:[["service_owner","A"],["projektleitung","R"],["betriebsteam","C"],["service_support_team","C"],["spm","I"]],
quiz:[
{frage:"Wer entscheidet an Gate 2 (Accountable)?",
optionen:["SOR-Gremium","Service Owner allein","SPM","Projektleitung"], richtig:1,
expl:"Gate 2 ist eine SO-Einzelentscheidung; bei Ablehnung erfolgt SOR-Eskalation."},
{frage:"Was prüft Gate 2 vor allem?",
optionen:["Die Budgetfreigabe für das Design","Die Übergabefähigkeit nach dem Build","Die Portfolio-Strategie","Die Incident-Quote"], richtig:1,
expl:"Gate 2 validiert die Übergabefähigkeit nach Abschluss des Builds (Vollständigkeit, Abnahme, Ressourcen)."}
]},
{ id:"tr_10", phase:"transition", typ:"aktivitaet",
name:"Ausrollen der Service-Komponenten",
beschreibung:"Technische Bereitstellung/Deployment des Services in die produktive Umgebung.",
umfasst:["Deployment von Anwendungen, Systemen, Komponenten","Konfiguration produktiver Systeme","Migration von Daten","Aktivierung von Monitoring","Zugänge, Rollen, Berechtigungen","Schnittstellen-Integration","Technische Abnahmetests"],
artefakt:"Ausgerollter Service (produktiv)",
raci:[["service_owner","A"],["betriebsteam","R"],["spm","C"],["lieferant","C/I"]],
quiz:[
{frage:"Wer rollt die Service-Komponenten aus (R)?",
optionen:["Betriebsteam","Projektteam","Testmanagement","SPM"], richtig:0,
expl:"Das Betriebsteam ist Responsible für das Deployment; der Service Owner ist Accountable."}
]},
{ id:"tr_11", phase:"transition", typ:"aktivitaet",
name:"Vorbereiten der Service-Aktivierung",
beschreibung:"Prüfung der Betriebsbereitschaft und Vorbereitung des Go-Live-Antrags für Gate 3.",
umfasst:["Review der Betriebsdokumentation","Prüfung der Überwachungsregeln","Prüfung der Rollen- und Eskalationswege","Zugriffe & Toolanbindung sicherstellen","Vorbereitung der Go-Live-Kommunikation","Validierung mit Supportteams","Vorbereitung Gate-3-Antrag (SOR-Vorlage)"],
artefakt:"Go-Live-Readiness (Gate-3-Antrag)",
raci:[["service_owner","A"],["betriebsteam","R"],["spm","C"]],
quiz:[
{frage:"Worauf bereitet tr_11 vor?",
optionen:["Den Build-Start","Den Go-Live-Antrag für Gate 3","Das ELS-Ende","Die Außerbetriebnahme"], richtig:1,
expl:"tr_11 prüft die Betriebsbereitschaft und bereitet den Gate-3-Antrag (SOR-Vorlage) vor."}
]},
{ id:"tr_12", phase:"transition", typ:"gate", gateNr:3,
name:"Gate 3: Go-Live-Freigabe",
beschreibung:"Portfolio-Freigabe und formale Aktivierung des Services. Exit-Gate der Transition — SOR-Entscheidung nach dem Konsent-Prinzip.",
umfasst:["Prüfung der Portfolio-Konsistenz","Prüfung der Betriebsreife (SO-Bestätigung)","Prüfung der Support-Bereitschaft","Prüfung der SLA/SLO-Vereinbarung","Prüfung der Auflagen-Erfüllung aus Gate 2","Bewertung der Geschäftskritikalität","Formale Aufnahme ins Service-Portfolio"],
artefakt:"Gate-3-Entscheidung + Portfolio-Aufnahme",
pfade:[
["Go-Live","Service wird aktiviert → Operation (op_01)"],
["Go-Live mit Auflagen","Aktivierung, definierte Punkte nacharbeiten → Operation"],
["Zurück an Transition","Nachbesserung erforderlich → tr_10 (ggf. tr_09)"],
["Ablehnung","Endgültige Ablehnung des Service-Vorhabens"]
],
raci:[["sor","A"],["service_owner","R"],["spm","C"],["projektleitung","C"],["operations_manager","C"],["support_manager","C"]],
quiz:[
{frage:"Wer entscheidet an Gate 3 (Accountable)?",
optionen:["Service Owner","SOR (Konsent)","Support Manager","Betrieb"], richtig:1,
expl:"Gate 3 ist eine SOR-Entscheidung nach dem Konsent-Prinzip — das Exit-Gate der Transition."},
{frage:"Was bewirkt die Go-Live-Freigabe zusätzlich?",
optionen:["Die Aufnahme ins Service-Portfolio","Den Start des Designs","Die Schließung aller Incidents","Den ELS-Exit"], richtig:0,
expl:"Mit der Freigabe wird der Service formal ins Portfolio aufgenommen und der Katalog-Eintrag aktiviert."}
]},
{ id:"op_01", phase:"operation", typ:"aktivitaet",
name:"Early Life Support (ELS)",
beschreibung:"Phase erhöhter Aufmerksamkeit direkt nach Go-Live: produktiv, aber intensiver beobachtet und betreut als im Normalbetrieb.",
umfasst:["Erhöhte Monitoring-Bereitschaft","Direkte Eskalation zum Projektteam","Schnelles Troubleshooting","Erste Incidents aufarbeiten","ELS-Exit-Entscheidung (SO)"],
artefakt:"Stabilisierter Service (ELS-Exit)",
raci:[["service_owner","A"],["support_manager","R"],["operations_manager","R"],["projektteam","C"],["spm","I"]],
quiz:[
{frage:"Wer entscheidet über den ELS-Exit?",
optionen:["SOR","Service Owner","Support Manager","Betrieb"], richtig:1,
expl:"Der Service Owner entscheidet eigenständig (Informationspflicht an SPM)."}
]},
{ id:"op_02", phase:"operation", typ:"aktivitaet",
name:"Bereitstellen von Leitlinien für den Service-Betrieb",
beschreibung:"Strukturelle Grundlage für den täglichen Servicebetrieb.",
umfasst:["Betriebshandbuch bereitstellen/pflegen","Betriebsprozesse & Arbeitsanweisungen","Rollen, Eskalation, Kommunikation klar","Standard Changes & Known Errors"],
artefakt:"Betriebshandbuch",
raci:[["service_owner","A"],["operations_manager","R"],["spm","C/I"]],
quiz:[
{frage:"Welches Artefakt ist hier zentral?",
optionen:["Service-Definition","Betriebshandbuch","Testbericht","Gate-Vorlage"], richtig:1,
expl:"Das Betriebshandbuch ist die strukturelle Grundlage des laufenden Betriebs."}
]},
{ id:"op_03", phase:"operation", typ:"aktivitaet",
name:"Durchführen laufender Betriebsaufgaben",
beschreibung:"Täglich wiederkehrende Betriebsaktivitäten zur Erbringung des Services.",
umfasst:["Benutzerverwaltung & Berechtigungen","Backup/Restore","Technische Routineaufgaben","Konfigurationspflege","Pflege von Logs, Diensten, Überwachungsobjekten","Security- & Compliance-Anforderungen","Umsetzung freigegebener Standard-Changes"],
artefakt:"Erbrachte Betriebsleistung",
raci:[["operations_manager","A"],["betriebsteam","R"],["service_owner","C"],["lieferant","C/I"],["spm","I"]],
quiz:[
{frage:"Wer ist Responsible (R) für die laufenden Betriebsaufgaben?",
optionen:["Betriebsteam","Service Owner","SPM","Support Manager"], richtig:0,
expl:"Das Betriebsteam führt aus (R); der Betrieb (AL B&C / AL App) ist Accountable."}
]},
{ id:"op_04", phase:"operation", typ:"aktivitaet",
name:"Ressourcen, Dienstleister und Budget managen",
beschreibung:"Sicherstellen, dass der Servicebetrieb über die nötigen Mittel verfügt — stabil und wirtschaftlich.",
umfasst:["Personelle und technische Ressourcen sicherstellen","Koordination mit externen Dienstleistern","Überwachung des Betriebsbudgets","Abstimmung mit Supplier-/Finanz-/Vertragsmanagement","Sicherstellen von Wartung & Lizenzen"],
artefakt:"Ressourcen- & Budget-Status",
raci:[["operations_manager","A"],["betriebsteam","R"],["service_owner","C"],["spm","I"]],
quiz:[
{frage:"Was ist das Ziel dieser Aktivität?",
optionen:["Incidents lösen","Ressourcen, Dienstleister und Budget steuern","Den Service designen","Das Review durchführen"], richtig:1,
expl:"op_04 sichert die nötigen personellen, technischen und finanziellen Mittel für einen stabilen, wirtschaftlichen Betrieb."}
]},
{ id:"op_05", phase:"operation", typ:"aktivitaet",
name:"Überwachen der Services",
beschreibung:"Strukturierte Überwachung des Services anhand definierter KPIs und Metriken.",
umfasst:["Verfügbarkeit, Performance, Auslastung überwachen","Schnittstellen und Konfigurationsobjekte überwachen","Trends / drohende Störungen erkennen","Verknüpfung mit Events, Alerts, Dashboards","Monitoring-Regeln im Betrieb erstellen"],
artefakt:"Monitoring-Daten / Alerts",
raci:[["operations_manager","A"],["betriebsteam","R"],["service_owner","C"],["service_support_team","I"],["spm","I"]],
quiz:[
{frage:"Welche Aktivität beschreibt op_05?",
optionen:["Strukturierte Überwachung anhand von KPIs/Metriken","Erstellung der Service-Definition","Lösen von Incidents","Gate-Entscheidung"], richtig:0,
expl:"op_05 überwacht Verfügbarkeit, Performance und Auslastung und verknüpft mit Events/Alerts/Dashboards."}
]},
{ id:"op_06", phase:"operation", typ:"aktivitaet",
name:"Erstellen des Service-Qualitätsbericht",
beschreibung:"Regelmäßige formale Berichte über die erreichte Servicequalität.",
umfasst:["SLA-/SLO-Auswertung","Auswertung technischer KPIs","Abgleich gegen Qualitätsziele","Identifikation von Verbesserungspotenzialen","Zuarbeit für Service Review & SPM"],
artefakt:"Service-Qualitätsbericht",
raci:[["service_owner","A"],["betriebsteam","R"],["spm","C"]],
quiz:[
{frage:"Welches Artefakt entsteht hier?",
optionen:["Betriebshandbuch","Service-Qualitätsbericht","Problem Record","Testbericht"], richtig:1,
expl:"op_06 erstellt den Service-Qualitätsbericht (SLA-/SLO-Auswertung, KPIs) als Zuarbeit für das Review."}
]},
{ id:"op_07", phase:"operation", typ:"aktivitaet",
name:"Proaktive Problem Identifikation",
beschreibung:"Geht über Monitoring hinaus: aktive Suche nach strukturellen Problemen.",
umfasst:["Trendanalysen aus Monitoringdaten","Analyse von Engpässen, Ressourcenproblemen","Technische Analyse von Systemmustern","Erkennen potenzieller SLA-Verletzungen"],
artefakt:"Identifizierte strukturelle Probleme",
raci:[["service_owner","A"],["betriebsteam","R"],["spm","I"]],
quiz:[
{frage:"Wodurch unterscheidet sich op_07 vom reinen Monitoring?",
optionen:["Es ist rein reaktiv","Es ist die aktive Suche nach strukturellen Problemen","Es schließt Tickets","Es entscheidet über Go-Live"], richtig:1,
expl:"Proaktive Problem-Identifikation nutzt Trend-/Musteranalyse zur Früherkennung struktureller Probleme."}
]},
{ id:"sp_01", phase:"support", typ:"aktivitaet",
name:"Bereitstellen von Leitlinien für den Service-Support",
beschreibung:"Strukturelle Rahmenbedingungen, damit der Service-Support effizient arbeiten kann.",
umfasst:["Unterstützungsprozesse (Incident-/Request-Modell)","Vorgaben zu Eskalationen, Reaktionszeiten, Rollen","Tool-Konfigurationen (Ticketklassen, Templates)","Zugriffsmöglichkeiten & Security-Policies","Konsistentes Wissensmanagement"],
artefakt:"Support-Leitlinien",
raci:[["service_owner","A"],["support_manager","R"],["spm","C/I"]],
quiz:[
{frage:"Wer ist Responsible (R) für die Support-Leitlinien?",
optionen:["Support Manager","Service Owner","SPM","1st Level Agent"], richtig:0,
expl:"Der Support Manager führt aus und pflegt die Leitlinien; der Service Owner verantwortet die fachlichen Vorgaben (A)."}
]},
{ id:"sp_02", phase:"support", typ:"aktivitaet",
name:"Wissensdatenbank",
beschreibung:"Strukturierter Wissensspeicher für Support-Lösungen, Betriebsinfos, Workarounds und Standardanfragen.",
umfasst:["Lösungen zu Incidents speichern","Workarounds dokumentieren","FAQ, Standard-Requests, Anleitungen","Pflege & Aktualisierung","Zentrales Arbeitsmittel für 1st & 2nd Level"],
artefakt:"Gepflegte Wissensdatenbank",
raci:[["service_owner","A"],["service_support_team","R"],["problem_manager","C"]],
quiz:[
{frage:"Wer liefert Workarounds und Known Errors für die Wissensdatenbank (C)?",
optionen:["Problem Manager","Queue Koordinator","SPM","Testmanagement"], richtig:0,
expl:"Der Problem Manager liefert Workarounds/Known Errors; das Service-Support-Team pflegt die Inhalte (R)."}
]},
{ id:"sp_03", phase:"support", typ:"aktivitaet",
name:"Überwachen / Verteilung von Incidents und Service Requests",
beschreibung:"Koordination eingehender Störungen/Anfragen: Priorisierung, Routing, korrekte Aufnahme und Verteilung (Ticket-Queue-Koordination).",
umfasst:["Sichtung neuer Tickets","Automatisches oder manuelles Routing","Prioritäts- und Impact-Bewertung","SLA-Konformität sicherstellen","Eskalation bei Bedarf"],
artefakt:"Priorisierte/geroutete Tickets",
raci:[["support_manager","A"],["queue_koordinator","R"]],
quiz:[
{frage:"Wer übernimmt Sichtung und Routing der Tickets (R)?",
optionen:["Queue Koordinator","Support Manager","2nd Level Agent","Service Owner"], richtig:0,
expl:"Der Queue Koordinator erledigt die initiale Sichtung und das Routing; der Support Manager ist Accountable."}
]},
{ id:"sp_04", phase:"support", typ:"aktivitaet",
name:"Bearbeiten von Requests",
beschreibung:"Behandlung aller Standard-Serviceanfragen von Nutzern (z.B. Passwort, Berechtigungen, Informationen).",
umfasst:["Klassifizierung gemäß Request-Katalog","Prüfung von Berechtigungen","Erfüllung standardisierter Anfragen","Dokumentation der Erledigung"],
artefakt:"Erledigter Service Request",
raci:[["support_manager","A"],["first_level_agent","R"]],
quiz:[
{frage:"Wer bearbeitet einfache Standard-Requests (R)?",
optionen:["1st Level Agent","2nd Level Agent","Problem Manager","Betriebsteam"], richtig:0,
expl:"Der 1st Level Agent bearbeitet Standard-Requests gemäß Katalog; der Support Manager ist Accountable."}
]},
{ id:"sp_05", phase:"support", typ:"aktivitaet",
name:"Lösen von Incidents im 1st Level Support",
beschreibung:"Schnelle Lösung typischer Störungen mit bekanntem Wissen.",
umfasst:["Erstdiagnose","Nutzung der Wissensdatenbank","Anwenderunterstützung","Workarounds anwenden","Lösung dokumentieren","Entscheidung über Weiterleitung an 2nd Level"],
artefakt:"Gelöster Incident (1st Level)",
raci:[["support_manager","A"],["first_level_agent","R"]],
quiz:[
{frage:"Wer löst typische Incidents im 1st Level (R)?",
optionen:["1st Level Agent","2nd Level Agent","Problem Manager","Lieferant"], richtig:0,
expl:"Der 1st Level Agent löst mit bekanntem Wissen; reicht es nicht, erfolgt Weiterleitung an den 2nd Level."}
]},
{ id:"sp_06", phase:"support", typ:"aktivitaet",
name:"Lösen von Incidents im 2nd Level Support",
beschreibung:"Bearbeitung von Incidents, die im 1st Level nicht lösbar sind — tiefergehende Diagnostik und technische Lösung.",
umfasst:["Komplexe technische Diagnosen","Zusammenarbeit mit Betrieb, Fachverfahren, Lieferanten","Ggf. Einbezug von Spezialisten/Herstellern","Rückmeldung an 1st Level / Anwender","Lösung dokumentieren","Ungelöst → Problem Record"],
artefakt:"Gelöster Incident (2nd Level)",
raci:[["support_manager","A"],["second_level_agent","R"],["lieferant","C"],["service_owner","I"]],
quiz:[
{frage:"Wer übernimmt Incidents, die der 1st Level nicht lösen kann (R)?",
optionen:["2nd Level Agent","Queue Koordinator","SPM","Service Owner"], richtig:0,
expl:"Der 2nd Level Agent macht die tiefergehende Diagnose; bleibt es ungelöst, entsteht ein Problem Record (sp_09)."}
]},
{ id:"sp_07", phase:"support", typ:"aktivitaet",
name:"Incident Record (Gelöst) / Request Record (Gelöst)",
beschreibung:"Abschluss der Ticketbearbeitung inkl. Dokumentation und Qualitätssicherung.",
umfasst:["Validierung, ob wirklich gelöst","Kommunikation an den Endnutzer","Dokumentationen / FAQs aktualisieren","Klassifikation für Trendanalysen","Übergabe an Schließen von Incidents"],
artefakt:"Incident/Request Record (gelöst)",
raci:[["support_manager","A"],["first_level_agent","R"],["second_level_agent","R"]],
quiz:[
{frage:"Was passiert in sp_07?",
optionen:["Eröffnen eines Problem Records","Abschluss der Bearbeitung inkl. Validierung und Doku","Routing der Tickets","Root-Cause-Analyse"], richtig:1,
expl:"sp_07 validiert die Lösung, kommuniziert an den Nutzer und bereitet den finalen Abschluss (sp_08) vor."}
]},
{ id:"sp_08", phase:"support", typ:"aktivitaet",
name:"Schließen von Incidents & Service Requests",
beschreibung:"Finaler Ticketabschluss inkl. Dokumentation, Reporting und Einordnung für spätere Verbesserungen.",
umfasst:["Ticket final schließen","Prüfung vollständiger Dokumentation","Ticketklassifizierung / KPI-Zuordnung","Rückmeldung an Monitoring/Reporting","Wiederkehrende Incidents identifizieren"],
artefakt:"Geschlossenes Ticket + KPI-Zuordnung",
raci:[["support_manager","A"],["first_level_agent","R"],["second_level_agent","R"]],
quiz:[
{frage:"Wer ist Accountable für das Schließen von Incidents und Requests?",
optionen:["Support Manager","1st Level Agent","Problem Manager","SOR"], richtig:0,
expl:"Der Support Manager ist Accountable; 1st und 2nd Level sind Responsible für den finalen Abschluss."}
]},
{ id:"sp_09", phase:"support", typ:"aktivitaet",
name:"Anlegen Problem Record für nicht lösbare Incidents",
beschreibung:"Ist ein Incident auch im 2nd Level nicht lösbar, wird eine strukturelle Ursache vermutet.",
umfasst:["Nicht-lösbar-Entscheidung","Dokumentation der Symptome","Eröffnen eines Problem Records","Übergabe an Root-Cause-Analyse"],
artefakt:"Problem Record",
raci:[["problem_manager","A"],["second_level_agent","R"],["service_owner","I"]],
quiz:[
{frage:"Welches Artefakt entsteht, wenn ein Incident nicht lösbar ist?",
optionen:["Workaround","Problem Record","Service-Definition","Testprotokoll"], richtig:1,
expl:"sp_09 eröffnet einen Problem Record; der Problem Manager ist Accountable, der 2nd Level Agent Responsible."}
]},
{ id:"sp_10", phase:"support", typ:"aktivitaet",
name:"Wiederkehrende Incidents analysieren & Problem Record anlegen",
beschreibung:"Reaktiver Weg zur strukturierten Problemerkennung, wenn dieselbe Störung mehrfach auftritt.",
umfasst:["Clustering wiederkehrender Incidents","Analyse gemeinsamer Ursachen","Entscheidung: Problem Record notwendig","Übergabe an Problem Management"],
artefakt:"Problem Record (aus wiederkehrenden Incidents)",
raci:[["problem_manager","A"],["support_manager","R"],["second_level_agent","C"],["service_owner","I"]],
quiz:[
{frage:"Wann wird in sp_10 ein Problem Record angelegt?",
optionen:["Bei jedem Incident","Wenn dieselbe Störung mehrfach auftritt","Nur an Gates","Bei Go-Live"], richtig:1,
expl:"sp_10 clustert wiederkehrende Incidents und legt bei gemeinsamer Ursache einen Problem Record an."}
]},
{ id:"sp_11", phase:"support", typ:"aktivitaet",
name:"Operative Root-Cause-Analyse durchführen & Workaround bereitstellen",
beschreibung:"Start der Problembehandlung zur Ermittlung der Ursache und Schaffung eines Workarounds.",
umfasst:["Root-Cause-Analyse","Erstellen eines Workarounds","Eintrag in die Wissensdatenbank","Entscheidung über Change-Bedarf"],
artefakt:"Workaround + aktualisierter Problem Record",
raci:[["service_owner","A"],["problem_manager","R"],["second_level_agent","C"],["lieferant","C/I"]],
quiz:[
{frage:"Was wird in der operativen Root-Cause-Analyse zusätzlich bereitgestellt?",
optionen:["Ein Workaround","Ein Betriebshandbuch","Eine Gate-Vorlage","Ein Review-Bericht"], richtig:0,
expl:"sp_11 ermittelt die Ursache, erstellt einen Workaround und entscheidet über Change-Bedarf (aktualisiert den Problem Record)."}
]},
/* Review-Phase = ARBEITSSTAND (Vorschlag Frank, Change-Enablement) — noch NICHT
im Blueprint-YAML; vor Konzept-Uebernahme mit Michael abstimmen.
RACI + Quiz hier abgeleitet (Franks Entwurf nennt nur die Aktivitaeten). */
{ id:"rv_01", phase:"review", typ:"aktivitaet",
name:"Durchführen von Service-Reviews",
beschreibung:"Den Service systematisch auswerten und die Ergebnisse im Service-Review-Dokument festhalten.",
umfasst:["KPIs & Monitoring auswerten","Problems & Incidents auswerten","Kundenfeedback sammeln/einholen","zugrunde liegende Infrastruktur bewerten","Service-Review-Dokument ausfüllen"],
artefakt:"Service-Review-Dokument",
raci:[["service_owner","A"],["betriebsteam","R"],["service_support_team","C"],["problem_manager","C"]],
quiz:[
{frage:"Welches Dokument entsteht beim Service-Review?",
optionen:["RFC","Service-Review-Dokument","Change-Steckbrief","Incident Record"], richtig:1,
expl:"KPIs, Incidents, Feedback und Infrastrukturbewertung werden im Service-Review-Dokument zusammengeführt."}
]},
{ id:"rv_02", phase:"review", typ:"aktivitaet",
name:"Bewertung der Review-Ergebnisse",
beschreibung:"Die Review-Ergebnisse bewerten und bei Änderungsbedarf einen RFC erstellen; relevante Berichte gehen an die SOR.",
umfasst:["RFC für Normal- bzw. Major-Change erstellen","Berichte bei Bedarf an die SOR weiterleiten","Ergebnisse in der SOR ganzheitlich bewerten"],
artefakt:"RFC (Request for Change)",
raci:[["sor","A"],["service_owner","R"],["spm","C"]],
quiz:[
{frage:"Was wird erstellt, wenn die Bewertung Änderungsbedarf zeigt?",
optionen:["Ein RFC (Request for Change)","Ein Incident Record","Ein Test-Report","Eine Service-Definition"], richtig:0,
expl:"Bei Normal- oder Major-Change entsteht ein RFC; Berichte gehen an die SOR."},
{frage:"Wer bewertet die Review-Ergebnisse ganzheitlich?",
optionen:["Der 1st Level","Die SOR","Das Testmanagement","Der Lieferant"], richtig:1,
expl:"Die SOR bewertet die Ergebnisse als Gremium ganzheitlich."}
]},
{ id:"rv_03", phase:"review", typ:"aktivitaet",
name:"Definieren von Service-Änderungen",
beschreibung:"Auf Basis der Bewertung konkrete Änderungsvorschläge formulieren, konsolidieren und die ausgewählte Änderung beschreiben.",
umfasst:["passende Änderungsvorschläge formulieren","Vorschläge bewerten & konsolidieren","ausgewählte Änderung beschreiben"],
artefakt:"Beschriebene Service-Änderung",
raci:[["service_owner","A"],["sor","C"],["architektur","C"]],
quiz:[
{frage:"Was ist das Ziel dieser Aktivität?",
optionen:["Incidents schließen","Änderungsvorschläge formulieren, konsolidieren und beschreiben","Den Service abschalten","Das Budget planen"], richtig:1,
expl:"Aus der Bewertung werden konkrete, beschriebene Service-Änderungen abgeleitet."}
]},
{ id:"rv_04", phase:"review", typ:"aktivitaet",
name:"Starten von Service-Änderungen",
beschreibung:"Die Änderung anstoßen: bei Normal Change die Umsetzung planen; bei Major Change das Routing klären und den Change-Steckbrief ausfüllen.",
umfasst:["Normal Change: Umsetzung planen","Major Change: Routing klären (RUN / DPM / MB)","Major Change: Change-Steckbrief ausfüllen & weiterleiten"],
artefakt:"Change-Steckbrief (bei Major Change)",
raci:[["service_owner","A"],["sor","C"],["spm","C"],["dpm","I"]],
pfade:[["RUN","Durchführung im laufenden Betrieb (Service Owner)"],["DPM","über den Demand- & Projektprozess"],["MB","direkt in den Projektprozess oder RUN"]],
quiz:[
{frage:"Was muss beim Major Change vor der Umsetzung geklärt werden?",
optionen:["Das Routing: RUN, DPM oder MB","Die Ticket-Queue","Der Eskalationsweg","Die Lizenzkosten"], richtig:0,
expl:"Beim Major Change wird das Routing (RUN/DPM/MB) geklärt und ein Change-Steckbrief erstellt; beim Normal Change wird direkt die Umsetzung geplant."}
]},
{ id:"rv_05", phase:"review", typ:"aktivitaet",
name:"Implementieren von Service-Änderungen",
beschreibung:"Die Änderung gemäß gewähltem Weg umsetzen, dokumentieren und abschließen.",
umfasst:["Normal & Major (Weg RUN): SO führt durch, dokumentiert, schließt ab","Major (Weg DPM): Demand- & Projektprozess","Major (Weg MB): Projektprozess oder RUN"],
artefakt:"Umgesetzte & dokumentierte Service-Änderung",
raci:[["service_owner","A"],["projektteam","R"],["dpm","C"]],
quiz:[
{frage:"Wer führt eine Änderung auf dem Weg „RUN“ durch?",
optionen:["Der Service Owner","Der Demand Portfolio Manager","Ein externes Projektteam","Die SOR"], richtig:0,
expl:"Im Weg RUN führt der Service Owner die Änderung durch, dokumentiert und schließt sie ab."}
]}
];
/* ====================== SERVICE-AKTE (Artefakte A1-A15, App-gefuehrt) ======================
Die Akte ist rein digital: erzeugte Artefakte werden per Choice bestimmt und
gesammelt; Gates sind hart gekoppelt (oeffnen nur mit den geforderten Artefakten). */
const ARTEFAKTE = {
A1:{name:"Projektauftrag", phase:"design"},
A2:{name:"Service-Definition", phase:"design", live:true},
A3:{name:"Service Design Document", phase:"design"},
A4:{name:"Implementation Blueprint", phase:"design"},
A5:{name:"Gate-/SOR-Vorlage", phase:"transition"},
A6:{name:"Betriebsdokumentation", phase:"transition"},
A7:{name:"Test-Report", phase:"transition"},
A8:{name:"Aktivierter Service", phase:"transition"},
A9:{name:"Service-Qualitätsbericht", phase:"operation"},
A10:{name:"Incident Record", phase:"support"},
A11:{name:"Problem Record", phase:"support", live:true},
A12:{name:"Workaround", phase:"support"},
A13:{name:"Wissensdatenbank-Eintrag", phase:"support", live:true},
A14:{name:"Service-Review-Bericht", phase:"review"},
A15:{name:"DPM-Rücklauf", phase:"review"}
};
// Welche Station erzeugt welches A-Artefakt (Choice-Schritt -> Aufnahme in die Akte).
const STATION_ARTEFAKT = {
ds_01:"A2", ds_02:"A3", ds_03:"A4",
tr_06:"A6", tr_07:"A7", tr_08:"A5",
op_06:"A9",
sp_02:"A13", sp_07:"A10", sp_09:"A11", sp_11:"A12",
rv_02:"A14", rv_05:"A15"
};
// Gate erzeugt Artefakt (beim Vorwaerts-Durchschreiten).
const GATE_PRODUCES = { tr_12:"A8" };
// Geforderte Artefakte je Gate (HARTE Kopplung).
const GATE_REQ = { tr_01:["A2","A3","A4"], tr_09:["A6","A7"], tr_12:["A6","A7","A2"] };
function addArtefakt(a){ if(a){ S.akte = S.akte || {}; S.akte[a] = true; } }
// Beim Start nach Einstiegspunkt vorbefuellen: alles, was VOR der Einstiegs-Station
// (bzw. vor durchschrittenen Gates) entsteht, "liegt schon vor".
function seedAkte(entryIdx){
S.akte = { A1:true };
for(const sid in STATION_ARTEFAKT){
const j = STATIONEN.findIndex(s=>s.id===sid);
if(j>=0 && j < entryIdx) addArtefakt(STATION_ARTEFAKT[sid]);
}
for(const gid in GATE_PRODUCES){
const j = STATIONEN.findIndex(s=>s.id===gid);
if(j>=0 && j < entryIdx) addArtefakt(GATE_PRODUCES[gid]);
}
}
/* ====================== STATE ====================== */
const LS_KEY = "slc-companion-proto";
function defaultState(){
return { view:"deck", service:null, change:null,
classifyDone:false, classifyWrong:null,
entryDone:false, entryWrong:null,
index:0, stage:"discuss", quizIndex:0,
actStep:0, actReveal:false, actDone:false, arteWrong:null,
picks:{}, done:{}, akte:{},
loopback:null, revisit:{}, endReason:null, endGate:null };
}
let S = load();
function load(){ try{ return Object.assign(defaultState(), JSON.parse(localStorage.getItem(LS_KEY)||"{}")); }catch(e){ return defaultState(); } }
function save(){ localStorage.setItem(LS_KEY, JSON.stringify(S)); }
/* ====================== HELPERS ====================== */
const $ = s => document.querySelector(s);
const cur = () => STATIONEN[S.index];
function pkey(sid, qi){ return sid+"#"+qi; }
function roleLabel(id){ return ROLLEN[id] || id; }
function acard(si, ci){ return USE_CASES[si].changes[ci]; } // {titel, text}
function cardHtml(si, ci){ const c = acard(si, ci);
return `<div style="font-weight:800;font-size:18px;color:var(--ink);margin-bottom:6px">${c.titel}</div>`
+ `<div>${c.text}</div>`
+ `<div style="color:var(--muted);font-style:italic;margin-top:8px">Was passiert an welchen Stellen?</div>`; }
// Finale Action-Card-Grafik (cards/s<service>-c<change>.png) — alle 30 vorhanden.
function cardImg(si, ci){ return `cards/s${si}-c${ci}.png`; }
function cardMedia(si, ci){ const f = cardImg(si, ci);
return f ? `<img class="acImg" src="${f}" alt="Action Card: ${acard(si,ci).titel}"
style="display:block;width:100%;max-width:300px;margin:6px auto;border-radius:8px;box-shadow:0 2px 10px rgba(0,0,0,.12)">`
: cardHtml(si, ci); }
/* ====================== RENDER: SIDEBAR ====================== */
function renderList(){
const groups = {};
STATIONEN.forEach((st,i)=>{ (groups[st.phase]=groups[st.phase]||[]).push({st,i}); });
let html = `<div class="navTop"><b>Stationen</b><button id="navClose" title="Schließen">✕</button></div>`;
for(const ph in PHASEN){
if(!groups[ph]) continue;
html += `<h3>${PHASEN[ph].label}</h3>`;
groups[ph].forEach(({st,i})=>{
const cls = [ "stationItem", i===S.index?"active":"", S.done[st.id]?"done":"" ].join(" ");
html += `<div class="${cls}" data-i="${i}">
<span class="dot" style="background:${PHASEN[ph].color}"></span>
<span class="id">${st.id}</span>
<span class="nm">${st.typ==="gate"?"⛩ ":""}${st.name.length>34?st.name.slice(0,32)+"…":st.name}</span>
</div>`;
});
}
$("#stationList").innerHTML = html;
$("#stationList").querySelectorAll(".stationItem").forEach(el=>{
el.onclick = ()=>{ document.body.classList.remove("navOpen"); enterStation(+el.dataset.i); save(); draw(); };
});
const nc = $("#navClose"); if(nc) nc.onclick = ()=> document.body.classList.remove("navOpen");
const pct = Math.round(Object.keys(S.done).length / STATIONEN.length * 100);
$("#progressBar").style.width = pct+"%";
}
/* ====================== RENDER: SERVICE-AKTE (Overlay) ====================== */
function renderAkte(){
const order = ["design","transition","operation","support","review"];
const ids = Object.keys(ARTEFAKTE);
const have = S.akte || {};
const n = ids.filter(a=>have[a]).length;
let html = `<div class="navTop"><b>📁 Service-Akte</b><button id="akteClose" title="Schließen">✕</button></div>`;
html += `<div class="akteCount">${n}/15 Artefakten gesammelt</div>`;
for(const ph of order){
const group = ids.filter(a => ARTEFAKTE[a].phase === ph);
if(!group.length) continue;
html += `<h3>${PHASEN[ph].label}</h3>`;
group.forEach(a=>{
const ok = !!have[a];
html += `<div class="akteItem ${ok?'have':''}">
<span class="aId" style="background:${PHASEN[ph].color}">${a}</span>
<span class="aNm">${ARTEFAKTE[a].name}${ARTEFAKTE[a].live?' · <i>lebend</i>':''}</span>
<span class="aChk">${ok?'✓':'○'}</span>
</div>`;
});
}
$("#akteList").innerHTML = html;
const c = $("#akteClose"); if(c) c.onclick = ()=> document.body.classList.remove("akteOpen");
}
/* ====================== VIEW DISPATCH ====================== */
function draw(){
document.body.classList.toggle("runMode", S.view==="run");
if(S.view!=="run"){ document.body.classList.remove("navOpen"); document.body.classList.remove("akteOpen"); }
renderCardBadge();
if(S.view==="deck") return renderDeck();
if(S.view==="classify") return renderClassify();
if(S.view==="entry") return renderEntry();
if(S.view==="end") return renderEnd();
renderRun();
}
function renderCardBadge(){
const el = $("#cardBadge");
if(S.view==="deck" || S.service==null){ el.style.display="none"; el.innerHTML=""; return; }
el.style.display="flex";
const chip = S.classifyDone ? `<span class="ctChip">${CHANGE_TYPES[S.change]}</span>` : ``;
el.innerHTML = `<span class="cb-svc">${USE_CASES[S.service].service}</span>${chip}`;
}
/* ---------- Schritt 1: Action Card ziehen (Raster aller Karten) ---------- */
function renderDeck(){
let grid = "";
USE_CASES.forEach((u,si)=>{
grid += `<div class="deckGroup"><div class="deckSvc">${u.service}</div><div class="deckRow">`;
u.changes.forEach((c,ci)=>{
grid += `<button class="deckCard" data-s="${si}" data-c="${ci}" title="${c.titel}">
<img src="cards/s${si}-c${ci}.png" alt="${c.titel}" loading="lazy"></button>`;
});
grid += `</div></div>`;
});
$("#panel").innerHTML = `
<div class="setupHead">Schritt 1 · Action Card ziehen</div>
<h2 class="setupTitle">Welche Karte zieht ihr?</h2>
<p class="muted">Tippt auf eine Action Card, um sie zu ziehen.</p>
<div class="deck">${grid}</div>`;
$("#panel").querySelectorAll(".deckCard").forEach(el=>{
el.onclick=()=>{ S.service=+el.dataset.s; S.change=+el.dataset.c;
S.classifyDone=false; S.classifyWrong=null; S.entryDone=false; S.entryWrong=null;
S.view="classify"; save(); draw(); };
});
}
/* ---------- Schritt 2+3: Change-Art bestimmen (retry bis richtig) -------- */
function renderClassify(){
const correct = S.change;
const card = acard(S.service,S.change);
const cardBig = `<img class="classifyCard" src="cards/s${S.service}-c${S.change}.png" alt="${card.titel}">`;
if(!S.classifyDone){
const choices = CHANGE_TYPES.map((t,i)=>
`<button class="choice ${S.classifyWrong===i?'bad':''}" data-i="${i}">${t}</button>`).join("");
const legend = `<div class="legend"><h4>Legende — Change-Arten</h4><dl>` +
CHANGE_TYPES.map((t,i)=>`<dt>${t}</dt><dd>${CHANGE_LEGEND[i]}</dd>`).join("") + `</dl></div>`;
const hint = S.classifyWrong!=null
? `<div class="hint bad">Nicht ganz — überlegt nochmal und probiert es erneut.</div>` : ``;
$("#panel").innerHTML = `
<div class="setupHead">Schritt 2 · Change-Art bestimmen</div>
<div class="classifyTop">
${cardBig}
<div class="classifyMain">
<h2 class="setupTitle" style="margin-top:0">Welche Art von Change ist das?</h2>
<p class="muted">Überlegt gemeinsam und wählt die passende Change-Art. Die Legende hilft beim Einordnen.</p>
${hint}
<div class="choiceGrid grid2">${choices}</div>
${legend}
</div>
</div>
<div class="actions"><button class="ghost" id="backDeck">← Andere Karte</button></div>`;
$("#panel").querySelectorAll(".choice").forEach(el=>{
el.onclick=()=>{ const i=+el.dataset.i;
if(i===correct){ S.classifyWrong=null; S.classifyDone=true; } else { S.classifyWrong=i; }
save(); renderClassify(); };
});
$("#backDeck").onclick=()=>{ S.view="deck"; save(); draw(); };
} else {
$("#panel").innerHTML = `
<div class="setupHead">Schritt 3 · Erfolgreiche Kategorisierung</div>
<div class="classifyTop">
${cardBig}
<div class="classifyMain">
<div class="hint ok">✓ Richtig: ${CHANGE_TYPES[correct]}</div>
<div class="recBox"><h4>Warum?</h4>
<p style="margin:0;color:var(--muted)">${CHANGE_LEGEND[correct]}</p></div>
</div>
</div>
<div class="actions">
<button class="ghost" id="backDeck">← Andere Karte</button>
<div class="spacer"></div>
<button class="primary" id="toEntry">Weiter → Einstieg finden</button>
</div>`;
$("#backDeck").onclick=()=>{ S.view="deck"; save(); draw(); };
$("#toEntry").onclick=()=>{ S.view="entry"; S.entryDone=false; S.entryWrong=null; save(); draw(); };
}
}
/* ---------- Schritt 4+5: Einstieg finden (Phase anklicken) -------------- */
function renderEntry(){
const rec = START_EMPFEHLUNG[S.change];
const recIndex = STATIONEN.findIndex(s=>s.id===rec.id);
const correctPhase = STATIONEN[recIndex].phase;
const order = ["design","transition","operation","support","review"];
const cardBig = `<img class="classifyCard" src="cards/s${S.service}-c${S.change}.png" alt="${acard(S.service,S.change).titel}">`;
if(!S.entryDone){
const zones = order.map(ph=>
`<button class="phaseZone ${S.entryWrong===ph?'bad':''}" data-ph="${ph}"
style="background:${PHASEN[ph].color}">${PHASEN[ph].label}</button>`).join("");
const hint = S.entryWrong
? `<div class="hint bad">Diese Phase passt nicht zur Change-Art — denkt an die Definition und probiert es erneut.</div>` : ``;
$("#panel").innerHTML = `
<div class="setupHead">Schritt 4 · Einstieg finden</div>
<div class="classifyTop">
${cardBig}
<div class="classifyMain">
<div class="hint ok">Change-Art: ${CHANGE_TYPES[S.change]}</div>
<h2 class="setupTitle" style="margin-top:8px">In welcher Phase startet dieser Change?</h2>
<p class="muted">Klickt auf die Lebenszyklus-Phase, in der dieser Change einsteigt.</p>
${hint}
<div class="phaseRow">${zones}</div>
</div>
</div>
<div class="actions"><button class="ghost" id="backClassify">← zurück</button></div>`;
$("#panel").querySelectorAll(".phaseZone").forEach(el=>{
el.onclick=()=>{ const ph=el.dataset.ph;
if(ph===correctPhase){ S.entryWrong=null; S.entryDone=true; } else { S.entryWrong=ph; }
save(); renderEntry(); };
});
$("#backClassify").onclick=()=>{ S.view="classify"; save(); draw(); };
} else {
$("#panel").innerHTML = `
<div class="setupHead">Schritt 5 · Los geht's</div>
<div class="classifyTop">
${cardBig}
<div class="classifyMain">
<div class="hint ok">✓ Einstieg: ${PHASEN[correctPhase].label}</div>
<div class="recBox"><h4>Start-Station</h4>
<p style="margin:0 0 6px"><b>${rec.id}${STATIONEN[recIndex].name}</b></p>
<p style="margin:0;color:var(--muted)">${rec.grund}</p></div>
</div>
</div>
<div class="actions">
<button class="ghost" id="backClassify">← zurück</button>
<div class="spacer"></div>
<button class="primary" id="startRun">Los geht's →</button>
</div>`;
$("#backClassify").onclick=()=>{ S.view="classify"; save(); draw(); };
$("#startRun").onclick=()=>{ seedAkte(recIndex); enterStation(recIndex); S.view="run"; save(); draw(); };
}
}
/* ====================== RENDER: RUN (Station) ====================== */
const GATE_FLOW = {
tr_01: ["next","tr_05"], // Entwicklung / Konfiguration (Konfig ueberspringt Build)
tr_09: ["next","next","tr_02","end"], // Freigabe / m. Auflagen / Zurueck an Build / Ablehnung
tr_12: ["next","next","tr_10","end"] // Go-Live / m. Auflagen / Zurueck / Ablehnung
};
function enterStation(idx){
idx = Math.max(0, Math.min(idx, STATIONEN.length-1));
// Echte Rueckschleife: erreicht man das Gate wieder (oder ueberholt es), Schleife schliessen.
if(S.loopback && idx >= S.loopback.untilIdx){
if(idx === S.loopback.untilIdx) (S.revisit = S.revisit || {})[STATIONEN[idx].id] = true;
S.loopback = null;
}
S.index = idx;
S.stage = STATIONEN[idx].typ==="gate" ? "gate" : "act";
S.gatePick = null; S.quizIndex = 0;
S.actStep = 0; S.actReveal = false; S.actDone = false; S.arteWrong = null;
}
function gateGoto(st, i){
S.done[st.id] = true;
const t = (GATE_FLOW[st.id] || [])[i] || "next";
if(t==="end"){ S.view="end"; S.endReason="rejected"; S.endGate=st.id; save(); draw(); return; }
if(t==="next" && GATE_PRODUCES[st.id]) addArtefakt(GATE_PRODUCES[st.id]); // z. B. Gate 3 Go-Live -> A8
if(t==="next"){ enterStation(S.index+1); }
else {
const j = STATIONEN.findIndex(s=>s.id===t);
const target = j>=0 ? j : S.index+1;
// Sprung nach hinten = echte Rueckschleife: Banner setzen, Gate wird danach erneut vorgelegt.
if(target < S.index){ S.loopback = { gateId: st.id, gateNr: st.gateNr, untilIdx: S.index }; }
enterStation(target);
}
save(); draw();
}
/* Zielstation einer Gate-Entscheidung (für Phasenwechsel-Feedback). -1 = Ende/Ablehnung. */
function gateTargetIndex(st, i){
const t = (GATE_FLOW[st.id] || [])[i] || "next";
if(t==="end") return -1;
if(t==="next") return Math.min(S.index+1, STATIONEN.length-1);
const j = STATIONEN.findIndex(s=>s.id===t);
return j>=0 ? j : Math.min(S.index+1, STATIONEN.length-1);
}
function raciTable(st){
const rows = st.raci.map(([r,c])=>`<tr><td>${roleLabel(r)}</td><td><span class="raciBadge raci-${c}">${c}</span></td></tr>`).join("");
return `<table class="raci"><thead><tr><th>Rolle</th><th>RACI</th></tr></thead><tbody>${rows}</tbody></table>`;
}
/* RACI-Legende (deutsch) — wird bei der RACI-Frage immer mit angezeigt. */
function raciLegendHtml(){
const items = [
["R","Responsible","verantwortlich für die Durchführung — erledigt die Aufgabe operativ"],
["A","Accountable","rechenschaftspflichtig — trägt die Ergebnisverantwortung (genau eine Rolle)"],
["C","Consulted","konsultiert — wird vorab um Rat/Beitrag gefragt"],
["I","Informed","informiert — wird über das Ergebnis in Kenntnis gesetzt"]
];
return `<div class="raciLegend"><div class="rlHead">Wofür RACI steht</div>` +
items.map(([l,en,de])=>`<div class="rlRow"><span class="raciBadge raci-${l}">${l}</span><span><b>${en}</b> — ${de}</span></div>`).join("") +
`</div>`;
}
function renderRun(){
renderList();
renderAkte();
const st = cur();
const ph = PHASEN[st.phase];
const chip = st.typ==="gate"
? `<span class="phaseChip gateChip">⛩ Gate ${st.gateNr}</span>`
: `<span class="phaseChip" style="background:${ph.color}">${ph.label}</span>`;
const loopBanner = (S.loopback && S.index < S.loopback.untilIdx)
? `<div class="tourBanner">↩ <b>Nacharbeit nach Gate ${S.loopback.gateNr}</b> — überarbeitet die folgenden Stationen; danach wird das Gate erneut vorgelegt und entscheidet neu.</div>`
: ``;
const cardBig = `<img class="classifyCard" src="cards/s${S.service}-c${S.change}.png" alt="${acard(S.service,S.change).titel}">`;
const stepBody = st.typ==="gate"
? (S.stage==="gateDone" ? renderGateDone(st) : renderGate(st))
: renderActivity(st);
let body = `
${loopBanner}
<div class="classifyTop">
${cardBig}
<div class="classifyMain">
<div class="sHead">${chip}<span class="sId">${st.id}</span></div>
<h2 class="sTitle" style="margin-top:8px">${st.name}</h2>
${stepBody}
</div>
</div>`;
$("#panel").innerHTML = body;
wire(st);
}
/* Aktivitaet — schrittweiser Mikro-Ablauf: 4 Fragen, je einzeln + Aufloesung.
1) Was steckt hinter der Ueberschrift? 2) Beteiligte Rollen 3) RACI 4) Artefakt */
function activitySteps(st){
return [
{ label:"Diskussion",
frage:`Diskutiert gemeinsam: Was fällt alles unter <b>„${st.name}"</b>? Was stellt ihr euch darunter vor? Nennt Beispiele.`,
auf:`<p style="margin:0 0 8px">${st.beschreibung}</p><h4 class="aufH">Das fällt darunter</h4><ul>${st.umfasst.map(u=>`<li>${u}</li>`).join("")}</ul>` },
{ label:"Beteiligte Rollen",
frage:`Welche Rollen sind an dieser Aktivität beteiligt? Stellt ihre Figuren auf die <b>Mulden des Station-Pucks</b>.`,
auf:`<h4 class="aufH">Beteiligte Rollen</h4><div class="roleChips">${st.raci.map(([r])=>`<span class="roleChip">${roleLabel(r)}</span>`).join("")}</div>` },
{ label:"RACI",
frage:`Klärt die <b>RACI</b>: Wer ist R, A, C, I? Sortiert die Figuren ins <b>Aktiv-Feld (R·A·C·I)</b>.`,
legend: raciLegendHtml(),
auf:`<h4 class="aufH">RACI</h4>${raciTable(st)}` },
{ label:"Artefakt", artefakt:true,
frage:`Welche <b>Artefaktkarte</b> entsteht hier und gehört in die <b>Service-Akte</b>?`,
auf:`<h4 class="aufH">Artefakt</h4><p style="margin:0"><b>${st.artefakt}</b></p>` }
];
}
// Antwort-Optionen fuer die Artefakt-Choice: richtiges A + 3 Distraktoren
// (bevorzugt aus derselben Phase), deterministisch nach A-Nummer sortiert.
function arteOptions(correct){
const ids = Object.keys(ARTEFAKTE);
const ph = ARTEFAKTE[correct].phase;
const same = ids.filter(a => a!==correct && ARTEFAKTE[a].phase===ph);
const other = ids.filter(a => a!==correct && ARTEFAKTE[a].phase!==ph);
const opts = [correct].concat(same.concat(other).slice(0,3));
return opts.sort((a,b)=> a.localeCompare(b, "en", {numeric:true}));
}
function renderActivity(st){
const phaseColor = PHASEN[st.phase].color;
const next = STATIONEN[S.index+1];
const lastOverall = S.index >= STATIONEN.length-1;
const phaseEnd = next && next.phase !== st.phase;
/* Eigener Abschluss-Screen nach der letzten Aufgabe */
if(S.actDone){
const phaseLine = phaseEnd
? `<p class="adPhase">🎉 Damit habt ihr die Phase <b style="color:${phaseColor}">${PHASEN[st.phase].label}</b> zu Ende gespielt — weiter mit der Phase <b style="color:${PHASEN[next.phase].color}">${PHASEN[next.phase].label}</b>.</p>`
: ``;
const btn = lastOverall ? `<button class="primary" id="finish">Durchlauf abschließen</button>`
: phaseEnd ? `<button class="primary" id="nextStation">→ Weiter zur Phase ${PHASEN[next.phase].label}</button>`
: `<button class="primary" id="nextStation">Nächste Station →</button>`;
return `<div class="actDone big">
<div class="adTitle">✓ Aktivität abgeschlossen</div>
<p style="margin:6px 0">Gut gemacht! Ihr habt <b>${st.id}${st.name}</b> durchgespielt.</p>
${phaseLine}
</div>
<div class="actions">
<button class="ghost" id="actBack">← zurück</button>
<div class="spacer"></div>
${btn}
</div>`;
}
/* Schrittweiser Frage-Flow */
const steps = activitySteps(st);
const i = Math.min(S.actStep||0, steps.length-1);
const step = steps[i];
const isLast = i === steps.length-1;
const arteId = STATION_ARTEFAKT[st.id]; // A-Nummer, falls diese Station eine erzeugt
const isArteChoice = step.artefakt && arteId; // Artefakt-Schritt mit echter Choice
let html = `<div class="tourProg">Schritt ${i+1}/${steps.length} · ${step.label}</div>
<div class="frageBox" style="border-left-color:${phaseColor}">
<div class="frageLabel">Aufgabe ${i+1}</div>
${step.frage}
</div>`;
if(step.legend) html += step.legend;
if(isArteChoice && !S.actReveal){
// Auswahl: welches Artefakt entsteht? (richtiges A + Distraktoren)
const opts = arteOptions(arteId).map(a =>
`<button class="choice arteChoice ${S.arteWrong===a?'bad':''}" data-a="${a}">${a}${ARTEFAKTE[a].name}</button>`).join("");
html += `<div class="choiceGrid">${opts}</div>`;
if(S.arteWrong) html += `<div class="hint bad">Nicht ganz — überlegt nochmal, welches Ergebnis diese Station liefert.</div>`;
} else if(S.actReveal){
if(isArteChoice)
html += `<div class="aufBox"><h4 class="aufH">Artefakt</h4><p style="margin:0">✓ <b>${arteId}${ARTEFAKTE[arteId].name}</b> in die Service-Akte gelegt.</p></div>`;
else
html += `<div class="aufBox">${step.auf}</div>`;
}
let actions = `<div class="actions">`;
if(S.actReveal || i>0) actions += `<button class="ghost" id="actBack">← zurück</button>`;
actions += `<div class="spacer"></div>`;
if(isArteChoice && !S.actReveal){ /* Antwort per Klick auf eine Option — kein Auflösen-Button */ }
else if(!S.actReveal) actions += `<button class="primary" id="actReveal">Auflösen →</button>`;
else if(!isLast) actions += `<button class="primary" id="actNext">Weiter →</button>`;
else actions += `<button class="primary" id="actToDone">Weiter →</button>`;
actions += `</div>`;
return html + actions;
}
/* Gate — Entscheidung nach Kriterien */
function renderGate(st){
const keeper = (st.raci.find(([r,c])=>c==="A")||[])[0];
const pruef = (st.pruef||[]).map(([n,d])=>`<li><b>${n}</b> — ${d}</li>`).join("");
// Harte Artefakt-Kopplung: Gate "oeffnet" nur mit den geforderten Artefakten in der Akte.
const req = GATE_REQ[st.id] || [];
const missing = req.filter(a => !(S.akte && S.akte[a]));
const blocked = missing.length > 0;
const opts = (st.pfade||[]).map(([n,d],i)=>
`<button class="choice" data-i="${i}" ${blocked?"disabled":""}><b>${n}</b><br><span style="color:var(--muted);font-weight:400">${d}</span></button>`).join("");
const reqLine = req.length === 0 ? `` : blocked
? `<div class="gateReq bad">🔒 Gate öffnet nicht — es fehlt in der Akte: ${missing.map(a=>a+" "+ARTEFAKTE[a].name).join(" · ")}. Erzeugt diese Artefakte zuerst.</div>`
: `<div class="gateReq ok">✓ Alle geforderten Artefakte liegen in der Akte (${req.join(" · ")}).</div>`;
const revisitNote = (S.revisit && S.revisit[st.id])
? `<div class="hint ok">↩ Erneute Vorlage nach Nacharbeit — prüft erneut und entscheidet neu.</div>` : ``;
return `
${revisitNote}
<p class="lead"><b>Entscheidet:</b> ${roleLabel(keeper)}</p>
${reqLine}
<div class="choiceGrid">${opts}</div>
<details class="det">
<summary>Worum geht's & Prüf-Kriterien</summary>
<div>
<p>${st.beschreibung}</p>
<ul class="crit">${pruef}</ul>
<p class="muted">Artefaktkarten in der Akte? Pflicht-Figuren am Gate-Puck?</p>
</div>
</details>`;
}
/* Gate — Konsequenz der Entscheidung */
function renderGateDone(st){
const i = S.gatePick || 0;
const pf = st.pfade[i] || st.pfade[0];
const keeper = (st.raci.find(([r,c])=>c==="A")||[])[0];
const sorNote = (keeper==="sor")
? `<p class="muted">Reicht die <b>Ressourcen-/Entscheidungshoheit der SOR</b> nicht (zusätzliche Mittel nötig), wird der Change zum <b>Demand</b> → über DPM ans <b>Mission Board</b>.</p>` : ``;
const tIdx = gateTargetIndex(st, i);
const target = tIdx>=0 ? STATIONEN[tIdx] : null;
const phaseEnd = target && target.phase !== st.phase;
let feedback = `<p class="muted">Die entscheidende Rolle (<b>${roleLabel(keeper)}</b>) bleibt als Marker am Gate-Puck stehen.</p>`;
let nextLabel = `Weiter →`;
if(phaseEnd){
const curColor = PHASEN[st.phase].color, nextColor = PHASEN[target.phase].color;
nextLabel = `→ Weiter zur Phase ${PHASEN[target.phase].label}`;
feedback += `<div class="phaseDone" style="border-left-color:${nextColor}">
<div class="pdTitle">🎉 Phase abgeschlossen</div>
<p style="margin:4px 0">Gratulation — ihr habt die Phase <b style="color:${curColor}">${PHASEN[st.phase].label}</b> zu Ende gespielt!</p>
<p style="margin:4px 0">Weiter geht's mit der Phase <b style="color:${nextColor}">${PHASEN[target.phase].label}</b>.</p>
</div>`;
}
return `
<div class="step reveal">
<div class="stepHead"><span class="n">⛩</span> Entscheidung getroffen</div>
<div class="recBox"><h4>${pf[0]}</h4><p style="margin:0;color:var(--muted)">${pf[1]}</p></div>
${sorNote}
${feedback}
</div>
<div class="actions">
<button class="ghost" id="gateBack">← andere Entscheidung</button>
<div class="spacer"></div>
<button class="primary" id="gateNext">${nextLabel}</button>
</div>`;
}
/* ====================== WIRING ====================== */
function wire(st){
const b = id => $("#"+id);
if(b("actReveal")) b("actReveal").onclick = ()=>{ S.actReveal=true; save(); draw(); };
if(b("actNext")) b("actNext").onclick = ()=>{ S.actStep=(S.actStep||0)+1; S.actReveal=false; save(); draw(); };
if(b("actToDone")) b("actToDone").onclick = ()=>{ S.actDone=true; save(); draw(); };
// Artefakt-Choice
$("#panel").querySelectorAll(".arteChoice[data-a]").forEach(el=>{
el.onclick = ()=>{ const a = el.dataset.a;
if(a === STATION_ARTEFAKT[st.id]){ S.arteWrong=null; addArtefakt(a); S.actReveal=true; }
else { S.arteWrong = a; }
save(); draw(); };
});
if(b("actBack")) b("actBack").onclick = ()=>{
if(S.actDone){ S.actDone=false; }
else if(S.actReveal){ S.actReveal=false; }
else if((S.actStep||0)>0){ S.actStep--; S.actReveal=true; }
save(); draw(); };
if(b("nextStation")) b("nextStation").onclick = ()=>{ S.done[st.id]=true; enterStation(S.index+1); save(); draw(); };
if(b("finish")) b("finish").onclick = ()=>{ S.done[st.id]=true; S.view="end"; S.endReason="done"; save(); draw(); };
// Gate
$("#panel").querySelectorAll(".choiceGrid .choice[data-i]").forEach(el=>{
el.onclick = ()=>{ S.gatePick=+el.dataset.i; S.stage="gateDone"; save(); draw(); };
});
if(b("gateBack")) b("gateBack").onclick = ()=>{ S.stage="gate"; save(); draw(); };
if(b("gateNext")) b("gateNext").onclick = ()=>{ gateGoto(st, S.gatePick||0); };
}
/* ====================== ABSCHLUSS-SCREEN ====================== */
function renderEnd(){
const rejected = S.endReason==="rejected";
const gate = rejected ? STATIONEN.find(s=>s.id===S.endGate) : null;
const head = rejected ? "Change abgelehnt" : "Durchlauf abgeschlossen";
const icon = rejected ? "✗" : "✓";
const box = rejected
? `<div class="recBox" style="border-left-color:var(--bad)">
<h4>Abgelehnt an ${gate ? "Gate "+gate.gateNr : "einem Gate"}</h4>
<p style="margin:0;color:var(--muted)">Das Vorhaben wird in dieser Form nicht weiterverfolgt. Eine endgültige Ablehnung erfordert die <b>SOR-Eskalation</b>.</p></div>`
: `<div class="recBox">
<h4>Service-Lifecycle durchlaufen</h4>
<p style="margin:0;color:var(--muted)">Ihr habt den Change von der Einordnung bis zur Umsetzung begleitet. Im Workshop schließt hier das gemeinsame <b>Debriefing am Tisch</b> an (Reflexion der Stationen, offene Fragen).</p></div>`;
$("#panel").innerHTML = `
<div class="setupHead">Abschluss</div>
<h2 class="setupTitle">${icon} ${head}</h2>
<div class="hint ${rejected?"bad":"ok"}">${USE_CASES[S.service].service} · ${CHANGE_TYPES[S.change]}</div>
${box}
<div class="actions">
<div class="spacer"></div>
<button class="primary" id="endRestart">Neue Action Card →</button>
</div>`;
$("#endRestart").onclick = ()=>{ S = defaultState(); save(); draw(); };
}
/* ====================== INIT ====================== */
(function init(){
$("#resetBtn").onclick = ()=>{ if(confirm("Neue Action Card ziehen und Durchlauf zurücksetzen?")){ S=defaultState(); save(); draw(); } };
$("#stationsBtn").onclick = ()=>{ document.body.classList.remove("akteOpen"); document.body.classList.toggle("navOpen"); };
$("#akteBtn").onclick = ()=>{ document.body.classList.remove("navOpen"); document.body.classList.toggle("akteOpen"); };
$("#navBackdrop").onclick = ()=>{ document.body.classList.remove("navOpen"); document.body.classList.remove("akteOpen"); };
draw();
})();
// PWA: Service Worker fuer Offline-/Kiosk-Betrieb (nur ueber http/https aktiv)
if ("serviceWorker" in navigator) {
window.addEventListener("load", ()=> navigator.serviceWorker.register("sw.js").catch(()=>{}));
}
</script>
</body>
</html>