diff --git a/04_Tablet-Quiz/README.md b/04_Tablet-Quiz/README.md index ac09aea..023ceed 100644 --- a/04_Tablet-Quiz/README.md +++ b/04_Tablet-Quiz/README.md @@ -3,9 +3,10 @@ **Status:** App lauffähig (PWA) · Deploy vorbereitet · **Typ:** eigenständiges Software-Teilprojekt des SLC-Workshops > **Umsetzungsstand:** Die App liegt unter [`app/`](app/) als statische **PWA** -> (offline-/kioskfähig). Sie führt den kompletten Flow durch (Action Card → -> Startpunkt → optionale Tour → Station: Diskussion/Quiz/Auflösung → Debrief mit -> **Markdown-/JSON-Export**). Inhalte (40 Stationen, 45 Quizfragen, 6 Use-Cases) +> (offline-/kioskfähig). Flow: **Karten-Raster** (Action Card ziehen) → **Change-Art +> bestimmen** (mit Legende, „nochmal versuchen" bis richtig) → **Phasen-Einstieg** +> (Lebenszyklus-Phase anklicken) → **Stationen** (Diskussion/Quiz/Auflösung) → +> **Debrief** mit **Markdown-/JSON-Export**. Inhalte (Stationen, Quizfragen, Use-Cases) > sind derzeit in `app/index.html` eingebettet. Die **finalen Action-Card-Grafiken** > (Freiburg-digital-Layout) liegen in `app/cards/` (`s-c.png`, **alle 30**). > **Deployment:** statisch, siehe @@ -61,8 +62,12 @@ steht, sondern in der App liegt. ## 4. Ablauf (UI-Flow) ``` -[Start] → Szenario wählen (= Action Card) - → App führt zur aktuellen Station (linearer Lifecycle, Fortschritt sichtbar) +[1] Action Card ziehen → Raster aller Karten-Grafiken, eine antippen +[2] Change-Art bestimmen → 5 Change-Arten + Legende; falsch = "nochmal", richtig = weiter +[3] Erfolgreiche Kategorisierung → kurze Bestätigung + Warum +[4] Einstieg finden → Lebenszyklus groß: Phase anklicken (falsch = "nochmal") +[5] Los geht's → App nennt Start-Station + Begründung + → App führt ab Start-Station durch die Stationen (Fortschritt sichtbar) → Station: → Gruppe diskutiert am Board anhand der Kurzbezeichnung (App noch zu) → Quiz (vermittelnd): Frage(n) → Gruppentipp → "Auflösen" → richtig/falsch diff --git a/04_Tablet-Quiz/app/index.html b/04_Tablet-Quiz/app/index.html index 91843bc..0d0adfa 100644 --- a/04_Tablet-Quiz/app/index.html +++ b/04_Tablet-Quiz/app/index.html @@ -160,6 +160,30 @@ .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} + .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)}} @@ -286,6 +310,13 @@ const CHANGE_TYPES = [ "Standard Change", "Emergency Change" ]; +const CHANGE_LEGEND = [ + "Strategisch getrieben, verändert den Service grundlegend — voller Lebenszyklus ab dem Design; Freigabe auf oberster Ebene.", + "Bringt neue Komponenten/Funktionen und braucht ein echtes Design — aber kleinere Tragweite und Freigabe-Ebene als Top-Level.", + "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.", @@ -825,8 +856,10 @@ const STATIONEN = [ /* ====================== STATE ====================== */ const LS_KEY = "slc-companion-proto"; function defaultState(){ - return { view:"card", service:0, change:0, startIndex:null, startRevealed:false, - tourIndex:0, index:0, stage:"discuss", quizIndex:0, + return { view:"deck", service:null, change:null, + classifyDone:false, classifyWrong:null, + entryDone:false, entryWrong:null, + index:0, stage:"discuss", quizIndex:0, picks:{}, done:{}, unclear:{} }; } let S = load(); @@ -880,152 +913,128 @@ function renderList(){ 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(); + if(S.view==="deck") return renderDeck(); + if(S.view==="classify") return renderClassify(); + if(S.view==="entry") return renderEntry(); renderRun(); } function renderCardBadge(){ const el = $("#cardBadge"); - if(S.view==="card" || S.view==="tour"){ el.style.display="none"; el.innerHTML=""; return; } + if(S.view==="deck" || S.service==null){ el.style.display="none"; el.innerHTML=""; return; } el.style.display="flex"; - el.innerHTML = `${USE_CASES[S.service].service}${CHANGE_TYPES[S.change]}`; + const chip = S.classifyDone ? `${CHANGE_TYPES[S.change]}` : ``; + el.innerHTML = `${USE_CASES[S.service].service}${chip}`; } -/* ---------- Screen 1: Action Card ziehen ---------- */ -function renderCardScreen(){ +/* ---------- Schritt 1: Action Card ziehen (Raster aller Karten) ---------- */ +function renderDeck(){ + let grid = ""; + USE_CASES.forEach((u,si)=>{ + grid += `
${u.service}
`; + u.changes.forEach((c,ci)=>{ + grid += ``; + }); + grid += `
`; + }); $("#panel").innerHTML = ` -
Schritt 1 · Action Card
-

Welches Szenario zieht ihr?

-

Wählt Service und Change-Typ der gezogenen Action Card – oder zieht zufällig. Diese Karte liegt an der aktuellen Station und wandert mit durch alle Stationen.

-
- - -
-
${cardMedia(S.service,S.change)}
-
- - -
- -
-

Neu hier? Das „Geführte Beispiel" spielt einen kompletten Fall (Bürgerportal) Station für Station durch – zum Verstehen, ohne Quiz.

`; - const svc=$("#serviceSel"), ch=$("#changeSel"); - svc.innerHTML = USE_CASES.map((u,i)=>``).join(""); - ch.innerHTML = CHANGE_TYPES.map((c,i)=>``).join(""); - svc.value=S.service; ch.value=S.change; - const refresh=()=>{ $("#cardTrigger").innerHTML=cardMedia(S.service,S.change); }; - 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(); }; - $("#tourBtn").onclick=()=>{ S.view="tour"; S.tourIndex=0; save(); draw(); }; - $("#toStart").onclick=()=>{ S.view="start"; S.startRevealed=false; save(); draw(); }; +
Schritt 1 · Action Card ziehen
+

Welche Karte zieht ihr?

+

Tippt auf eine Action Card, um sie zu ziehen.

+
${grid}
`; + $("#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(); }; + }); } -/* ---------- 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" - ? `⛩ Gate ${st.gateNr}` - : `${ph.label}`; - const raci = st.raci.map(([r,c])=>`${roleLabel(r)}${c}`).join(""); - let extra=""; - if(st.pfade){ - extra += `

Entscheidungspfade

` + - st.pfade.map(([n,d])=>`
${n}${d}
`).join("") + `
`; +/* ---------- Schritt 2+3: Change-Art bestimmen (retry bis richtig) -------- */ +function renderClassify(){ + const correct = S.change; + const card = acard(S.service,S.change); + const thumb = `${card.titel}`; + if(!S.classifyDone){ + const choices = CHANGE_TYPES.map((t,i)=> + ``).join(""); + const legend = `

Legende — Change-Arten

` + + CHANGE_TYPES.map((t,i)=>`
${t}
${CHANGE_LEGEND[i]}
`).join("") + `
`; + const hint = S.classifyWrong!=null + ? `
Nicht ganz — überlegt nochmal und probiert es erneut.
` : ``; + $("#panel").innerHTML = ` +
Schritt 2 · Change-Art bestimmen
+ ${thumb} +

Welche Art von Change ist das?

+

Überlegt gemeinsam und wählt die passende Change-Art. Die Legende hilft beim Einordnen.

+ ${hint} +
${choices}
+ ${legend} +
`; + $("#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 = ` +
Schritt 3 · Erfolgreiche Kategorisierung
+ ${thumb} +
✓ Richtig: ${CHANGE_TYPES[correct]}
+

Warum?

+

${CHANGE_LEGEND[correct]}

+
+ +
+ +
`; + $("#backDeck").onclick=()=>{ S.view="deck"; save(); draw(); }; + $("#toEntry").onclick=()=>{ S.view="entry"; S.entryDone=false; S.entryWrong=null; save(); draw(); }; } - const narr = TOUR.text[st.id] || "—"; - const last = S.tourIndex === STATIONEN.length-1; - $("#panel").innerHTML = ` -
📘 Geführtes Beispiel · ${USE_CASES[TOUR.service].service} — ${CHANGE_TYPES[TOUR.change]}
- ${acard(TOUR.service,TOUR.change).titel} — ${acard(TOUR.service,TOUR.change).text}
-
Station ${S.tourIndex+1} von ${STATIONEN.length}
- ${chip} -
${st.name}
-
${st.id}
-

In diesem Beispiel

${narr}
-
-

Fachlich (aus dem Blueprint)

-

${st.beschreibung}

-

Umfasst

    ${st.umfasst.map(u=>`
  • ${u}
  • `).join("")}
-

Rollen / RACI

- ${raci}
RolleRACI
-

Artefakt

${st.artefakt}

- ${extra} -
-
- - -
- -
`; - $("#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(){ +/* ---------- 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 revealed = S.startRevealed; - const groups={}; - STATIONEN.forEach((st,i)=>{ (groups[st.phase]=groups[st.phase]||[]).push({st,i}); }); - let list=""; - for(const ph in PHASEN){ - if(!groups[ph]) continue; - list += `
${PHASEN[ph].label}
`; - groups[ph].forEach(({st,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 += `
- - ${st.id} - ${st.name} - ${st.typ==="gate"?`Gate ${st.gateNr}`:''} -
`; + const correctPhase = STATIONEN[recIndex].phase; + const order = ["design","transition","operation","support","review"]; + if(!S.entryDone){ + const zones = order.map(ph=> + ``).join(""); + const hint = S.entryWrong + ? `
Diese Phase passt nicht zur Change-Art — denkt an die Definition und probiert es erneut.
` : ``; + $("#panel").innerHTML = ` +
Schritt 4 · Einstieg finden
+
Change-Art: ${CHANGE_TYPES[S.change]}
+

In welcher Phase startet dieser Change?

+

Klickt auf die Lebenszyklus-Phase, in der dieser Change einsteigt.

+ ${hint} +
${zones}
+
`; + $("#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(); }; }); - } - let head, actions; - if(!revealed){ - head = `

Welches Tile ist der sinnvolle Einstieg für diese Action Card? Diskutiert in der Gruppe, wählt ein Tile und löst dann auf.

`; - actions = ` -
- `; + $("#backClassify").onclick=()=>{ S.view="classify"; save(); draw(); }; } else { - const tip = S.startIndex==null ? `` : - (S.startIndex===recIndex - ? `

Euer Tipp ${STATIONEN[S.startIndex].id} passt zum empfohlenen Einstieg. ✓

` - : `

Euer Tipp war ${STATIONEN[S.startIndex].id} — nicht der empfohlene Einstieg.

`); - head = `${tip}

Empfohlener Einstieg · ${CHANGE_TYPES[S.change]}

-

${rec.id} — ${STATIONEN[recIndex].name}

-

${rec.grund}

`; - const ownDiff = (S.startIndex!=null && S.startIndex!==recIndex); - actions = ` -
- ${ownDiff?``:``} - `; + $("#panel").innerHTML = ` +
Schritt 5 · Los geht's
+
✓ Einstieg: ${PHASEN[correctPhase].label}
+

Start-Station

+

${rec.id} — ${STATIONEN[recIndex].name}

+

${rec.grund}

+
+ +
+ +
`; + $("#backClassify").onclick=()=>{ S.view="classify"; save(); draw(); }; + $("#startRun").onclick=()=>{ S.index=recIndex; S.view="run"; S.stage="discuss"; S.quizIndex=0; save(); draw(); }; } - $("#panel").innerHTML = ` -
Schritt 2 · Startpunkt
-

Wo startet ihr sinnvollerweise?

- ${head} -
${list}
-
${actions}
`; - if(!revealed){ - $("#panel").querySelectorAll(".pickerItem").forEach(el=>{ - el.onclick=()=>{ S.startIndex=+el.dataset.i; save(); renderStartScreen(); }; - }); - } - $("#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) ====================== */