This commit is contained in:
breitenbach76 2026-05-30 16:02:13 +02:00
parent 246aa3b7b9
commit 5043e9ac7b
2 changed files with 207 additions and 20 deletions

View file

@ -93,9 +93,53 @@ Plättchenrückseite stand.
- **Inhalt:** Aktivitäten/Gates/Rollen = Brett-Elemente (`../00_Konzept/`).
- **Ausgang:** Debrief-Daten → Workshop-Dokumentation (`../05_Workshop-Dokumentation/`).
## 8. Offene Punkte
## 8. Companion-Chatbot (optional · Nachschlage-Begleiter)
Idee: Teilnehmende stellen spontane Verständnisfragen, die das feste Quiz nicht
abdeckt — z. B. „Was ist das Service Design Document?" oder „Wer ist an Gate 2
accountable?". Der Bot beantwortet sie aus dem Blueprint.
### Rolle & Grenzen (didaktisch)
- **Nachschlagen, nicht führen.** Der Bot ersetzt **nicht** die Stationsführung.
Das Lernprinzip (erst Gruppendiskussion → Tipp → App-Auflösung) bleibt das
Rückgrat; ein frei führender Bot würde das „produktive Ringen" aushebeln.
- **Freischaltung gated:** pro Station erst **nach dem „Auflösen"** verfügbar
(oder als klar getrennter „Frag nach"-Knopf), damit er das Diskutieren-zuerst
nicht kurzschließt.
- **Scope-begrenzt:** antwortet nur aus dem Blueprint, keine freie Beratung;
bei Unklarheit Verweis auf den „Unklar"-Marker → Debrief.
### Datengrundlage
- Der Lifecycle-Blueprint ist klein (~2.000 Zeilen YAML). **Kein RAG/Vektor-Setup
nötig** — alle `service-lifecycle_*.yaml` + `spm_rollen.yaml` passen in einen
einzigen System-Prompt-Kontext.
- Single Source of Truth bleibt der Blueprint (keine Doppelpflege).
### Wiederverwendung statt Neubau
- Im Repo existiert bereits ein Bot: `#099_tools/digitom-bot/` mit Knowledge-Base
(`SPM_service-lifecycle_*.md`) und Dify-Upload. **Vor Neubau prüfen**, ob dieser
als Begleiter angedockt werden kann.
### Offene Architektur-Entscheidung: Offline vs. Cloud
Ein Laufzeit-LLM braucht einen API-Aufruf → Internet/Endpoint/Key. Das steht im
**Konflikt zur Konzept-Bedingung** (offline, Kiosk, keine Cloud, kein Login;
vgl. Abschnitt 6). Optionen:
| Variante | Pro | Contra |
|----------|-----|--------|
| **(a) Cloud-LLM** (Dify / Anthropic / Azure-OpenAI) | geringster Aufwand, beste Qualität | Online-Pflicht, Datenschutz-/Beschaffungsfreigabe |
| **(b) Lokales LLM** (z. B. Ollama auf dem Gerät) | offline-konform | Hardware-/Setup-Aufwand, schwächere Qualität |
| **(c) Statische FAQ** (vorab generierte Q&A aus Blueprint) | voll offline, keine Kosten | keine Freitextfragen, begrenzte Abdeckung |
Empfehlung für den Pilot: mit **(a)** als separater, optionaler Begleiter starten
und im Pilot evaluieren, ob der Mehrwert die Online-Pflicht rechtfertigt.
## 9. Offene Punkte
- [ ] Format `questions.json` spezifizieren.
- [ ] Entscheidung Framework vs. Vanilla.
- [ ] Wer pflegt/baut? (intern DIGIT vs. extern)
- [ ] Datenschutz: rein lokal, keine personenbezogenen Daten — bestätigen.
- [ ] Companion-Chatbot: Entscheidung Offline vs. Cloud (Abschnitt 8).
- [ ] Prüfen, ob `digitom-bot`/Dify als Begleiter wiederverwendbar ist.
- [ ] Freischalt-Logik des Bots festlegen (nach „Auflösen" vs. jederzeit).

View file

@ -145,6 +145,16 @@
.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); }
</style>
</head>
<body>
@ -178,6 +188,65 @@
</dialog>
<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 auf Top-Level ist strategisch getrieben und betrifft den Service grundlegend er durchläuft den vollen Lebenszyklus ab dem Design (Service-Definition)." },
{ id:"ds_01", grund:"Auch ein Major Change auf Low-Level bringt neue Komponenten/Funktionen und braucht ein echtes Design Einstieg im Design. Unterschied zum Top-Level ist v.a. Tragweite und Freigabe-Ebene, nicht der Einstiegspunkt." },
{ 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: Umsetzung freigegebener Standard-Changes)." },
{ 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. Ausgelöst wird er typischerweise durch einen Incident aus Operation/Support." }
];
/* 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 Review werden Support-KPIs und wiederkehrende Fälle ausgewertet die Speicher-Probleme tauchen klar als Muster auf.",
rv_02: "Der Service wird über vier Dimensionen per Ampel bewertet (Leistung, Stabilität, Nutzerzufriedenheit, Zukunftsfähigkeit). Empfehlung in unserem Fall: IMPROVEMENT.",
rv_03: "Die SOR sichtet das Review und entscheidet. In unserem Beispiel: VERBESSERUNG NÖTIG (klein) → rv_04. (Alternativen wären: weiter wie bisher, Redesign rv_05 oder Außerbetriebnahme rv_06.)",
rv_04: "Die kleine Verbesserung wird geplant und umgesetzt z. B. die Last- und Timeout-Optimierung beim Speichern. Sie fließt zurück in den laufenden Betrieb (Operation). Damit schließt sich der Kreis.",
rv_05: "In unserem Beispiel NICHT gewählt. Wäre die Änderung grundlegend (z. B. eine komplett neue Beteiligungsplattform), würde sie hier als neuer Demand an den Demand-Lifecycle übergeben der Service liefe quasi neu durch Design und Transition.",
rv_06: "Ebenfalls NICHT gewählt. Würde das Modul z. B. wegen eines Nachfolgesystems abgeschaltet, plant die SOR hier die kontrollierte Stilllegung (Retirement-Plan)."
}
};
/* =========================================================================
STATIONEN — vollständiger Lifecycle, abgeleitet aus den Blueprint-YAMLs
(service-lifecycle_design/transition/operation/support/review.yaml v3.2 +
@ -763,8 +832,8 @@ const STATIONEN = [
/* ====================== STATE ====================== */
const LS_KEY = "slc-companion-proto";
function defaultState(){
return { view:"card", service:0, change:0, startIndex:null,
index:0, stage:"discuss", quizIndex:0,
return { view:"card", service:0, change:0, startIndex:null, startRevealed:false,
tourIndex:0, index:0, stage:"discuss", quizIndex:0,
picks:{}, done:{}, unclear:{} };
}
let S = load();
@ -808,13 +877,14 @@ function draw(){
document.body.classList.toggle("runMode", S.view==="run");
renderCardBadge();
if(S.view==="card") return renderCardScreen();
if(S.view==="tour") return renderTour();
if(S.view==="start") return renderStartScreen();
renderRun();
}
function renderCardBadge(){
const el = $("#cardBadge");
if(S.view==="card"){ el.style.display="none"; el.innerHTML=""; return; }
if(S.view==="card" || S.view==="tour"){ el.style.display="none"; el.innerHTML=""; return; }
el.style.display="flex";
el.innerHTML = `<span class="cb-svc">${USE_CASES[S.service].service}</span><span class="ctChip">${CHANGE_TYPES[S.change]}</span>`;
}
@ -832,9 +902,11 @@ function renderCardScreen(){
<div class="ctText" id="cardTrigger">${USE_CASES[S.service].changes[S.change]}</div>
<div class="actions">
<button class="ghost" id="randomCard">🎲 Zufällig ziehen</button>
<button class="ghost" id="tourBtn">📘 Geführtes Beispiel</button>
<div class="spacer"></div>
<button class="primary" id="toStart">Weiter → Startpunkt wählen</button>
</div>`;
</div>
<p class="note" style="text-align:left">Neu hier? Das „Geführte Beispiel" spielt einen kompletten Fall (Bürgerportal) Station für Station durch zum Verstehen, ohne Quiz.</p>`;
const svc=$("#serviceSel"), ch=$("#changeSel");
svc.innerHTML = USE_CASES.map((u,i)=>`<option value="${i}">${u.service}</option>`).join("");
ch.innerHTML = CHANGE_TYPES.map((c,i)=>`<option value="${i}">${c}</option>`).join("");
@ -843,11 +915,58 @@ function renderCardScreen(){
svc.onchange=()=>{ S.service=+svc.value; save(); refresh(); };
ch.onchange=()=>{ S.change=+ch.value; save(); refresh(); };
$("#randomCard").onclick=()=>{ S.service=Math.floor(Math.random()*USE_CASES.length); S.change=Math.floor(Math.random()*CHANGE_TYPES.length); save(); draw(); };
$("#toStart").onclick=()=>{ S.view="start"; save(); draw(); };
$("#tourBtn").onclick=()=>{ S.view="tour"; S.tourIndex=0; save(); draw(); };
$("#toStart").onclick=()=>{ S.view="start"; S.startRevealed=false; save(); draw(); };
}
/* ---------- Screen 2: Startpunkt bestimmen ---------- */
/* ---------- Geführte Tour (ein Beispiel durch alle Stationen) ---------- */
function renderTour(){
const st = STATIONEN[S.tourIndex];
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 raci = st.raci.map(([r,c])=>`<tr><td>${roleLabel(r)}</td><td><span class="raciBadge raci-${c}">${c}</span></td></tr>`).join("");
let extra="";
if(st.pfade){
extra += `<h4>Entscheidungspfade</h4><div class="pfade">` +
st.pfade.map(([n,d])=>`<div class="pfad"><b>${n}</b><span style="color:var(--muted)">${d}</span></div>`).join("") + `</div>`;
}
const narr = TOUR.text[st.id] || "—";
const last = S.tourIndex === STATIONEN.length-1;
$("#panel").innerHTML = `
<div class="tourBanner">📘 Geführtes Beispiel · <b>${USE_CASES[TOUR.service].service}</b> — ${CHANGE_TYPES[TOUR.change]}<br>
<span style="color:var(--muted)">${USE_CASES[TOUR.service].changes[TOUR.change]}</span></div>
<div class="tourProg">Station ${S.tourIndex+1} von ${STATIONEN.length}</div>
${chip}
<div class="stationName">${st.name}</div>
<div class="stationId">${st.id}</div>
<div class="tourNarr"><h4>In diesem Beispiel</h4>${narr}</div>
<div class="reveal">
<h4>Fachlich (aus dem Blueprint)</h4>
<p>${st.beschreibung}</p>
<h4>Umfasst</h4><ul>${st.umfasst.map(u=>`<li>${u}</li>`).join("")}</ul>
<h4>Rollen / RACI</h4>
<table class="raci"><thead><tr><th>Rolle</th><th>RACI</th></tr></thead><tbody>${raci}</tbody></table>
<h4>Artefakt</h4><p>${st.artefakt}</p>
${extra}
</div>
<div class="actions">
<button class="ghost" id="tourExit">Tour beenden</button>
<button class="ghost" id="tourBack" ${S.tourIndex===0?'disabled':''}>← Zurück</button>
<div class="spacer"></div>
<button class="primary" id="tourNext">${last?'Fertig — zur Startseite':'Weiter →'}</button>
</div>`;
$("#tourExit").onclick=()=>{ S.view="card"; save(); draw(); };
$("#tourBack").onclick=()=>{ if(S.tourIndex>0){ S.tourIndex--; save(); renderTour(); } };
$("#tourNext").onclick=()=>{ if(last){ S.view="card"; save(); draw(); } else { S.tourIndex++; save(); renderTour(); } };
}
/* ---------- Screen 2: Startpunkt bestimmen (Tipp → Auflösen) ---------- */
function renderStartScreen(){
const rec = START_EMPFEHLUNG[S.change];
const recIndex = STATIONEN.findIndex(s=>s.id===rec.id);
const revealed = S.startRevealed;
const groups={};
STATIONEN.forEach((st,i)=>{ (groups[st.phase]=groups[st.phase]||[]).push({st,i}); });
let list="";
@ -855,7 +974,12 @@ function renderStartScreen(){
if(!groups[ph]) continue;
list += `<div class="pickerPhase">${PHASEN[ph].label}</div>`;
groups[ph].forEach(({st,i})=>{
list += `<div class="pickerItem ${i===S.startIndex?'sel':''}" data-i="${i}">
let cls="pickerItem";
if(revealed){
if(i===recIndex) cls+=" correct";
else if(i===S.startIndex) cls+=" wrongPick";
} else if(i===S.startIndex) cls+=" sel";
list += `<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.name}</span>
@ -863,22 +987,41 @@ function renderStartScreen(){
</div>`;
});
}
let head, actions;
if(!revealed){
head = `<p class="muted">Welches Tile ist der sinnvolle Einstieg für diese Action Card? Diskutiert in der Gruppe, wählt ein Tile und löst dann auf.</p>`;
actions = `<button class="ghost" id="backToCard">← Action Card</button>
<div class="spacer"></div>
<button class="primary" id="revealStart" ${S.startIndex==null?'disabled':''}>Auflösen</button>`;
} else {
const tip = S.startIndex==null ? `` :
(S.startIndex===recIndex
? `<p style="color:var(--ok);font-weight:600;margin:0 0 8px">Euer Tipp ${STATIONEN[S.startIndex].id} passt zum empfohlenen Einstieg. ✓</p>`
: `<p style="color:var(--bad);font-weight:600;margin:0 0 8px">Euer Tipp war ${STATIONEN[S.startIndex].id} — nicht der empfohlene Einstieg.</p>`);
head = `${tip}<div class="recBox"><h4>Empfohlener Einstieg · ${CHANGE_TYPES[S.change]}</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>`;
const ownDiff = (S.startIndex!=null && S.startIndex!==recIndex);
actions = `<button class="ghost" id="backToCard">← Action Card</button>
<div class="spacer"></div>
${ownDiff?`<button class="ghost" id="startOwn">Auf eigener Wahl starten</button>`:``}
<button class="primary" id="startRec">Auf ${rec.id} starten →</button>`;
}
$("#panel").innerHTML = `
<div class="setupHead">Schritt 2 · Startpunkt</div>
<h2 class="setupTitle">Wo startet ihr sinnvollerweise?</h2>
<p class="muted">Das hängt vom Change-Typ ab ein großer (Major) Change beginnt eher im Design, ein Standard- oder Emergency-Change steigt oft später ein. Diskutiert in der Gruppe und wählt das Tile, auf das der Action-Stein zuerst gestellt wird. Es gibt nicht die eine richtige Antwort.</p>
${head}
<div class="pickerList">${list}</div>
<div class="actions">
<button class="ghost" id="backToCard">← Action Card</button>
<div class="spacer"></div>
<button class="primary" id="confirmStart" ${S.startIndex==null?'disabled':''}>Hier starten →</button>
</div>`;
<div class="actions">${actions}</div>`;
if(!revealed){
$("#panel").querySelectorAll(".pickerItem").forEach(el=>{
el.onclick=()=>{ S.startIndex=+el.dataset.i; save(); renderStartScreen(); };
});
$("#backToCard").onclick=()=>{ S.view="card"; save(); draw(); };
const cs=$("#confirmStart");
if(cs) cs.onclick=()=>{ if(S.startIndex==null) return; S.index=S.startIndex; S.view="run"; S.stage="discuss"; S.quizIndex=0; save(); draw(); };
}
$("#backToCard").onclick=()=>{ S.view="card"; S.startRevealed=false; save(); draw(); };
if($("#revealStart")) $("#revealStart").onclick=()=>{ if(S.startIndex==null) return; S.startRevealed=true; save(); renderStartScreen(); };
if($("#startRec")) $("#startRec").onclick=()=>{ S.index=recIndex; S.view="run"; S.stage="discuss"; S.quizIndex=0; save(); draw(); };
if($("#startOwn")) $("#startOwn").onclick=()=>{ S.index=S.startIndex; S.view="run"; S.stage="discuss"; S.quizIndex=0; save(); draw(); };
}
/* ====================== RENDER: RUN (Station) ====================== */