Companion-App: finale Action-Card-Grafiken eingebunden (29/30)

- 29 PNGs als app/cards/s<service>-c<change>.png (PNG-Nr n -> Service floor(n/5),
  Change n%5). Karten-Screen zeigt jetzt die finale Grafik statt Text.
- Helfer cardImg()/cardMedia(); Text-Fallback fuer die noch fehlende Karte
  s0-c0 ("Open Source von oben!").
- Service Worker cacht die Karten-Grafiken vorab (Offline), CACHE -> v2.
- README-Umsetzungsstand ergaenzt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
breitenbach76 2026-06-06 14:12:20 +02:00
parent b12bed892a
commit 43320be1fb
32 changed files with 21 additions and 5 deletions

View file

@ -6,7 +6,9 @@
> (offline-/kioskfähig). Sie führt den kompletten Flow durch (Action Card → > (offline-/kioskfähig). Sie führt den kompletten Flow durch (Action Card →
> Startpunkt → optionale Tour → Station: Diskussion/Quiz/Auflösung → Debrief mit > Startpunkt → optionale Tour → Station: Diskussion/Quiz/Auflösung → Debrief mit
> **Markdown-/JSON-Export**). Inhalte (40 Stationen, 45 Quizfragen, 6 Use-Cases) > **Markdown-/JSON-Export**). Inhalte (40 Stationen, 45 Quizfragen, 6 Use-Cases)
> sind derzeit in `app/index.html` eingebettet. **Deployment:** statisch, siehe > sind derzeit in `app/index.html` eingebettet. Die **finalen Action-Card-Grafiken**
> (Freiburg-digital-Layout) liegen in `app/cards/` (`s<service>-c<change>.png`, 29/30 —
> `s0-c0` „Open Source von oben!" fehlt noch → Text-Fallback). **Deployment:** statisch, siehe
> [`app/DEPLOY.md`](app/DEPLOY.md). **Lokal testen:** `python -m http.server 8099 > [`app/DEPLOY.md`](app/DEPLOY.md). **Lokal testen:** `python -m http.server 8099
> --directory 04_Tablet-Quiz/app` (oder Preview-Config `.claude/launch.json`). > --directory 04_Tablet-Quiz/app` (oder Preview-Config `.claude/launch.json`).

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

View file

@ -843,6 +843,13 @@ 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>` return `<div style="font-weight:800;font-size:18px;color:var(--ink);margin-bottom:6px">${c.titel}</div>`
+ `<div>${c.text}</div>` + `<div>${c.text}</div>`
+ `<div style="color:var(--muted);font-style:italic;margin-top:8px">Was passiert an welchen Stellen?</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). Eine fehlt noch
// (s0-c0 "Open Source von oben!") -> Text-Fallback.
function cardImg(si, ci){ return (si===0 && ci===0) ? null : `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 ====================== */ /* ====================== RENDER: SIDEBAR ====================== */
function renderList(){ function renderList(){
@ -897,7 +904,7 @@ function renderCardScreen(){
<label>Service<select id="serviceSel"></select></label> <label>Service<select id="serviceSel"></select></label>
<label>Change-Typ<select id="changeSel"></select></label> <label>Change-Typ<select id="changeSel"></select></label>
</div> </div>
<div class="ctText" id="cardTrigger">${cardHtml(S.service,S.change)}</div> <div class="ctText" id="cardTrigger">${cardMedia(S.service,S.change)}</div>
<div class="actions"> <div class="actions">
<button class="ghost" id="randomCard">🎲 Zufällig ziehen</button> <button class="ghost" id="randomCard">🎲 Zufällig ziehen</button>
<button class="ghost" id="tourBtn">📘 Geführtes Beispiel</button> <button class="ghost" id="tourBtn">📘 Geführtes Beispiel</button>
@ -909,7 +916,7 @@ function renderCardScreen(){
svc.innerHTML = USE_CASES.map((u,i)=>`<option value="${i}">${u.service}</option>`).join(""); 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(""); ch.innerHTML = CHANGE_TYPES.map((c,i)=>`<option value="${i}">${c}</option>`).join("");
svc.value=S.service; ch.value=S.change; svc.value=S.service; ch.value=S.change;
const refresh=()=>{ $("#cardTrigger").innerHTML=cardHtml(S.service,S.change); }; const refresh=()=>{ $("#cardTrigger").innerHTML=cardMedia(S.service,S.change); };
svc.onchange=()=>{ S.service=+svc.value; save(); refresh(); }; svc.onchange=()=>{ S.service=+svc.value; save(); refresh(); };
ch.onchange=()=>{ S.change=+ch.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(); }; $("#randomCard").onclick=()=>{ S.service=Math.floor(Math.random()*USE_CASES.length); S.change=Math.floor(Math.random()*CHANGE_TYPES.length); save(); draw(); };

View file

@ -1,6 +1,13 @@
/* Service Worker — SLC-Workshop Companion (App-Shell, offline-first) */ /* Service Worker — SLC-Workshop Companion (App-Shell, offline-first) */
const CACHE = "slc-companion-v1"; const CACHE = "slc-companion-v2";
const ASSETS = ["./", "index.html", "manifest.webmanifest", "icon.svg"]; const SHELL = ["./", "index.html", "manifest.webmanifest", "icon.svg"];
// Action-Card-Grafiken (cards/s<service>-c<change>.png) fuer Offline vorab cachen.
const CARDS = [];
for (let s = 0; s <= 5; s++) for (let c = 0; c <= 4; c++) {
if (s === 0 && c === 0) continue; // s0-c0 fehlt (Text-Fallback)
CARDS.push(`cards/s${s}-c${c}.png`);
}
const ASSETS = SHELL.concat(CARDS);
self.addEventListener("install", (e) => { self.addEventListener("install", (e) => {
e.waitUntil( e.waitUntil(