edu-boardgame-generator/editor.html
Spiel-Generator Workshop 016be6ea90 feat: initial project structure with modular mini-game system
- editor.html: 5-step game editor with live Python code split-screen
- game.html: playable board game engine
- codegen.js: Python live-code generator (extracted from editor)
- logo.png: PH Weingarten logo (extracted from Base64)
- minigames/_api.js: shared Mini-Game API (makeCanvas, onResult, helpers)
- 12 Mini-Games as individual modules (launch + preview API):
  snake, flappy, memory, quiz, reaction,
  basketball, catch, maze, simon, typing, puzzle, spotdiff
- README.md with architecture docs and deployment guide

Refactoring highlights:
  - editor.html: 251 KB → 102 KB (-59%)
  - Mini-games fully decoupled, each ~100-200 lines
  - All 12 games now have working launch() + preview()
  - Maze uses recursive backtracker algorithm
  - spotdiff uses canvas-drawn scene with 5 differences
2026-03-14 22:12:25 +00:00

1664 lines
106 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.0"/>
<title>Spiel-Generator</title>
<link href="https://fonts.googleapis.com/css2?family=Fredoka+One&family=Nunito:wght@400;600;700;800;900&display=swap" rel="stylesheet">
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{
--bg:#0f0e17;--surface:#1a1827;--card:#22203a;
--accent:#f5a623;--accent2:#7c3aed;--accent3:#10b981;
--danger:#ef4444;--text:#fffffe;--muted:#a7a3c2;--border:#2e2b4a;--radius:16px;
}
html,body{height:100%;margin:0;padding:0}
body{
color:var(--text);font-family:'Nunito',sans-serif;
background:var(--bg);
background-image:radial-gradient(ellipse at 20% 10%,rgba(124,58,237,0.18) 0%,transparent 50%),radial-gradient(ellipse at 80% 90%,rgba(245,166,35,0.10) 0%,transparent 50%);
background-attachment:fixed;
display:flex;flex-direction:column;height:100vh;overflow:hidden;
}
/* ─── SPLIT LAYOUT ─── */
#splitWrap{
display:flex;flex-direction:row;flex:1 1 0;overflow:hidden;min-height:0;width:100%;
}
#editorPane{
flex:0 0 65%;width:65%;overflow-y:auto;overflow-x:hidden;min-width:0;
border-right:1px solid var(--border);
order:1;
scrollbar-width:thin;scrollbar-color:rgba(124,58,237,0.3) transparent;
}
#editorPane::-webkit-scrollbar{width:5px}
#editorPane::-webkit-scrollbar-thumb{background:rgba(124,58,237,0.25);border-radius:3px}
#editorPane::-webkit-scrollbar-thumb:hover{background:rgba(124,58,237,0.5)}
#resizerBar{
flex:0 0 5px;width:5px;background:var(--border);cursor:col-resize;
transition:background 0.2s;user-select:none;
display:flex;align-items:center;justify-content:center;
position:relative;z-index:10;order:2;
}
#resizerBar:hover,#resizerBar.dragging{background:var(--accent2);}
#resizerBar::after{
content:'⋮';color:rgba(255,255,255,0.25);font-size:1.1rem;letter-spacing:-2px;
pointer-events:none;
}
#codePane{
flex:1 1 0;min-width:0;overflow:hidden;display:flex;flex-direction:column;
background:#0d0c1a;order:3;
}
/* ─── PROGRESS BAR ─── */
#progressBar{position:relative;z-index:200;flex-shrink:0;width:100%;box-sizing:border-box;background:rgba(15,14,23,0.95);backdrop-filter:blur(14px);border-bottom:1px solid rgba(255,255,255,0.07);padding:10px 24px}
.pb-inner{max-width:100%;margin:0 auto}
.pb-top{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}
.pb-logo{font-family:'Fredoka One',cursive;font-size:0.95rem;color:var(--accent);display:flex;align-items:center;gap:8px}
.pb-phwg{display:flex;align-items:center;gap:10px}
.pb-phwg img{height:32px;width:auto;object-fit:contain;opacity:0.92}
.pb-phwg-text{display:flex;flex-direction:column;gap:1px}
.pb-phwg-studiengaenge{font-size:9px;font-weight:700;color:var(--muted);letter-spacing:0.3px;line-height:1.4}
.pb-phwg-studiengaenge span{color:rgba(245,166,35,0.7)}
@media(max-width:640px){.pb-phwg-text{display:none}}
.pb-steps{display:flex;align-items:center;gap:0}
.pb-step{display:flex;align-items:center;gap:7px;flex:1;cursor:default;opacity:0.38;transition:opacity 0.3s}
.pb-step.done{opacity:0.7;cursor:pointer}
.pb-step.done:hover{opacity:1}
.pb-step.active{opacity:1}
.pb-dot{width:28px;height:28px;border-radius:50%;border:2.5px solid var(--muted);display:flex;align-items:center;justify-content:center;font-family:'Fredoka One',cursive;font-size:0.85rem;flex-shrink:0;transition:all 0.3s}
.pb-step.active .pb-dot{border-color:var(--accent);background:var(--accent);color:#000;box-shadow:0 0 12px rgba(245,166,35,0.5)}
.pb-step.done .pb-dot{border-color:var(--accent3);background:var(--accent3);color:#fff}
.pb-label{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:0.5px;white-space:nowrap}
.pb-step.active .pb-label{color:var(--accent)}
.pb-step.done .pb-label{color:var(--accent3)}
.pb-connector{flex:1;height:2px;background:var(--border);margin:0 6px;transition:background 0.4s;min-width:12px}
.pb-connector.done{background:var(--accent3)}
@media(max-width:580px){.pb-label{display:none}}
.pb-track{height:3px;background:var(--border);border-radius:999px;margin-top:10px;overflow:hidden}
.pb-fill{height:100%;background:linear-gradient(90deg,var(--accent3),var(--accent));border-radius:999px;transition:width 0.5s cubic-bezier(0.34,1.56,0.64,1)}
/* ─── SCREENS ─── */
.screen{display:none;max-width:680px;margin:0 auto;padding:32px 16px 80px}
.screen.active{display:block}
.slide-in{animation:slideIn 0.38s cubic-bezier(0.16,1,0.3,1) both}
.slide-out{animation:slideOut 0.22s ease both}
@keyframes slideIn{from{opacity:0;transform:translateX(40px)}to{opacity:1;transform:translateX(0)}}
@keyframes slideOut{from{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(-30px)}}
@keyframes fadeUp{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:translateY(0)}}
@keyframes popIn{from{opacity:0;transform:scale(0.9)}to{opacity:1;transform:scale(1)}}
@keyframes float{0%,100%{transform:translateY(0)}50%{transform:translateY(-10px)}}
/* ─── SCREEN HEADER ─── */
.sh{text-align:center;margin-bottom:28px}
.sh-badge{display:inline-block;background:var(--accent2);color:#fff;font-size:11px;font-weight:700;letter-spacing:2px;text-transform:uppercase;padding:4px 14px;border-radius:999px;margin-bottom:12px}
.sh-title{font-family:'Fredoka One',cursive;font-size:clamp(1.8rem,4vw,2.5rem)}
.sh-title em{color:var(--accent);font-style:normal}
.sh-sub{color:var(--muted);font-size:14px;margin-top:8px;line-height:1.6}
/* ─── STEP SUMMARY BANNER ─── */
.summary-banner{background:linear-gradient(135deg,rgba(16,185,129,0.12),rgba(16,185,129,0.04));border:1.5px solid rgba(16,185,129,0.4);border-radius:14px;padding:18px 22px;margin-bottom:20px;display:none;animation:fadeUp 0.4s ease both}
.summary-banner.visible{display:block}
.sb-title{font-family:'Fredoka One',cursive;font-size:1.1rem;color:var(--accent3);margin-bottom:8px;display:flex;align-items:center;gap:8px}
.sb-chips{display:flex;gap:8px;flex-wrap:wrap}
.sb-chip{background:rgba(16,185,129,0.15);border:1px solid rgba(16,185,129,0.3);border-radius:8px;padding:4px 12px;font-size:12px;font-weight:700;color:var(--accent3)}
/* ─── CARD ─── */
.card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);padding:26px;margin-bottom:18px}
.card-title{font-family:'Fredoka One',cursive;font-size:1.15rem;margin-bottom:5px;display:flex;align-items:center;gap:10px}
.card-sub{color:var(--muted);font-size:13px;margin-bottom:18px;line-height:1.6}
.divider{height:1px;background:var(--border);margin:20px 0}
/* ─── FORM ─── */
label.lbl{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:0.8px;color:var(--muted);margin-bottom:7px;display:block}
.finput,.ftarea{width:100%;background:var(--surface);border:1.5px solid var(--border);border-radius:10px;color:var(--text);font-family:'Nunito',sans-serif;font-size:15px;font-weight:600;padding:12px 16px;outline:none;transition:border-color 0.2s,box-shadow 0.2s}
.finput:focus,.ftarea:focus{border-color:var(--accent2);box-shadow:0 0 0 3px rgba(124,58,237,0.14)}
.finput::placeholder,.ftarea::placeholder{color:#4a476a}
.ftarea{resize:none}
.char-row{display:flex;justify-content:flex-end;font-size:11px;color:var(--muted);margin-top:4px}
.char-row.warn{color:var(--accent)}
/* ─── NAV BUTTONS ─── */
.btn-row{display:flex;justify-content:space-between;gap:12px;margin-top:28px}
.btn-back{background:var(--surface);color:var(--muted);font-family:'Fredoka One',cursive;font-size:1rem;border:1.5px solid var(--border);border-radius:12px;padding:13px 24px;cursor:pointer;transition:all 0.2s}
.btn-back:hover{border-color:var(--muted);color:var(--text)}
.btn-next{background:linear-gradient(135deg,var(--accent2),var(--accent));color:#fff;font-family:'Fredoka One',cursive;font-size:1rem;border:none;border-radius:12px;padding:14px 32px;cursor:pointer;display:flex;align-items:center;gap:8px;transition:transform 0.15s,box-shadow 0.15s,opacity 0.2s;box-shadow:0 4px 20px rgba(124,58,237,0.35)}
.btn-next:hover:not(:disabled){transform:translateY(-2px);box-shadow:0 8px 28px rgba(124,58,237,0.45)}
.btn-next:disabled{opacity:0.32;cursor:not-allowed;transform:none}
/* ─── PREVIEW CARD ─── */
.preview-card{background:linear-gradient(135deg,rgba(124,58,237,0.15),rgba(245,166,35,0.08));border:1px solid rgba(124,58,237,0.35);border-radius:14px;padding:18px 22px;margin-bottom:18px;animation:fadeUp 0.3s ease both}
.preview-card .pv-label{font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:1px;color:var(--accent2);margin-bottom:6px}
.preview-card .pv-name{font-family:'Fredoka One',cursive;font-size:1.5rem;color:var(--accent)}
.preview-card .pv-desc{font-size:13px;color:var(--muted);margin-top:4px;line-height:1.5}
/* ─── SUGGESTIONS ─── */
.suggestions{display:flex;flex-wrap:wrap;gap:6px;margin-top:10px}
.sug{background:var(--surface);border:1px solid var(--border);border-radius:7px;padding:4px 12px;font-size:12px;color:var(--muted);cursor:pointer;transition:all 0.15s}
.sug:hover{border-color:var(--accent2);color:var(--text)}
/* ─── SELECTION GRID (step 2) ─── */
.sel-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(90px,1fr));gap:9px;margin-bottom:14px}
.sel-card{background:var(--surface);border:2px solid var(--border);border-radius:12px;padding:14px 6px;text-align:center;cursor:pointer;transition:all 0.2s;user-select:none}
.sel-card:hover{border-color:var(--accent2);transform:translateY(-2px)}
.sel-card.selected{border-color:var(--accent);background:rgba(245,166,35,0.07);box-shadow:0 0 14px rgba(245,166,35,0.2)}
.sel-card .se{font-size:1.9rem;display:block;margin-bottom:5px}
.sel-card .sl{font-size:11px;font-weight:700;color:var(--muted)}
.sel-card.selected .sl{color:var(--accent)}
.combo-preview{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:18px;text-align:center;margin-top:10px;animation:popIn 0.3s ease both}
.combo-preview .cp-scene{font-size:3rem;display:block;margin-bottom:6px;animation:float 3s ease-in-out infinite}
.combo-preview .cp-label{font-size:12px;color:var(--muted);font-weight:700}
/* ─── RULES (step 3) ─── */
.rules-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:4px}
@media(max-width:560px){.rules-grid{grid-template-columns:1fr}}
.rule-block{background:var(--surface);border:1.5px solid var(--border);border-radius:12px;padding:15px}
.rule-block-title{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:0.8px;color:var(--muted);margin-bottom:11px}
.toggle-group{display:flex;flex-direction:column;gap:8px}
.toggle-option{display:flex;align-items:center;gap:9px;padding:10px 12px;border-radius:9px;border:2px solid var(--border);cursor:pointer;transition:all 0.2s;user-select:none}
.toggle-option:hover{border-color:var(--accent2)}
.toggle-option.selected{border-color:var(--accent);background:rgba(245,166,35,0.07)}
.tog-emoji{font-size:1.2rem;flex-shrink:0}
.tog-text{font-size:13px;font-weight:700}
.tog-sub{font-size:11px;color:var(--muted)}
.tog-check{margin-left:auto;width:18px;height:18px;border-radius:50%;border:2px solid var(--border);transition:all 0.2s;flex-shrink:0}
.toggle-option.selected .tog-check{background:var(--accent);border-color:var(--accent)}
.sub-opts{display:none;gap:7px;margin-top:9px;flex-wrap:wrap}
.sub-opts.visible{display:flex}
.sub-opt{background:var(--card);border:1.5px solid var(--border);border-radius:8px;padding:6px 13px;font-size:13px;font-weight:700;cursor:pointer;transition:all 0.15s;color:var(--muted)}
.sub-opt:hover{border-color:var(--accent2);color:var(--text)}
.sub-opt.selected{border-color:var(--accent);color:var(--accent);background:rgba(245,166,35,0.07)}
.count-row{display:flex;align-items:center;gap:14px;background:var(--surface);border:1.5px solid var(--border);border-radius:12px;padding:14px 18px;margin-bottom:18px}
.count-row label{font-size:13px;font-weight:800;text-transform:uppercase;letter-spacing:0.5px;color:var(--muted)}
.count-ctrl{display:flex;align-items:center;gap:10px;margin-left:auto}
.cnt-btn{width:36px;height:36px;border-radius:8px;background:var(--card);border:1.5px solid var(--border);color:var(--text);font-size:1.2rem;font-weight:800;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all 0.15s}
.cnt-btn:hover:not(:disabled){border-color:var(--accent);color:var(--accent)}
.cnt-btn:disabled{opacity:0.3;cursor:not-allowed}
.cnt-val{font-family:'Fredoka One',cursive;font-size:1.8rem;color:var(--accent);min-width:36px;text-align:center}
/* ─── PALETTE ─── */
.pal-tabs{display:flex;gap:8px;margin-bottom:12px}
.pal-tab{background:var(--surface);border:1.5px solid var(--border);border-radius:9px;padding:7px 16px;font-size:12px;font-weight:800;cursor:pointer;color:var(--muted);transition:all 0.2s;text-transform:uppercase;letter-spacing:0.5px}
.pal-tab.active{background:var(--accent2);border-color:var(--accent2);color:#fff}
.pal-panel{display:none}
.pal-panel.active{display:grid;grid-template-columns:repeat(auto-fill,minmax(105px,1fr));gap:9px;margin-bottom:18px}
.mg-card{background:var(--surface);border:2px solid var(--border);border-radius:11px;padding:11px 7px 8px;display:flex;flex-direction:column;align-items:center;gap:4px;cursor:grab;transition:all 0.2s;user-select:none;position:relative}
.mg-card:hover:not(.disabled){border-color:var(--accent2);transform:translateY(-2px);box-shadow:0 5px 18px rgba(124,58,237,0.2)}
.mg-card.click-sel{outline:3px solid var(--accent);outline-offset:2px;background:rgba(245,166,35,0.06)}
.mg-card.disabled{opacity:0.28;cursor:not-allowed;filter:grayscale(0.5);pointer-events:none}
.mg-emoji{font-size:1.6rem;pointer-events:none}
.mg-lbl{font-size:11px;font-weight:700;color:var(--muted);text-align:center;line-height:1.3;pointer-events:none}
.mg-tag{font-size:9px;font-weight:800;letter-spacing:0.5px;text-transform:uppercase;padding:2px 7px;border-radius:999px;pointer-events:none}
.mg-tag.once{background:rgba(239,68,68,0.2);color:#f87171}
.mg-tag.multi{background:rgba(16,185,129,0.2);color:var(--accent3)}
.mg-tag.unlim{background:rgba(245,166,35,0.2);color:var(--accent)}
.mg-tag.story-tag{background:rgba(6,182,212,0.2);color:#22d3ee}
.use-count{position:absolute;top:4px;right:4px;background:var(--accent);color:#000;font-size:10px;font-weight:900;width:17px;height:17px;border-radius:50%;display:none;align-items:center;justify-content:center;pointer-events:none}
.mg-card.used .use-count{display:flex}
.story-counter{display:inline-flex;align-items:center;gap:5px;background:rgba(6,182,212,0.12);border:1px solid rgba(6,182,212,0.4);border-radius:999px;padding:3px 10px;font-size:11px;font-weight:800;color:#22d3ee}
.story-counter.maxed{background:rgba(239,68,68,0.12);border-color:var(--danger);color:var(--danger)}
.sec-label{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:1px;color:var(--muted);margin-bottom:10px;display:flex;align-items:center;justify-content:space-between}
/* ─── BOARD ─── */
.board-builder{display:flex;flex-direction:column;gap:2px}
.drop-zone{height:10px;border-radius:7px;border:2px dashed transparent;transition:all 0.2s;display:flex;align-items:center;justify-content:center;font-size:11px;color:transparent;font-weight:700;cursor:pointer}
.drop-zone.story-hoverable{height:28px;border-color:rgba(6,182,212,0.3);color:rgba(6,182,212,0.4)}
.drop-zone.drag-over{height:38px;background:rgba(6,182,212,0.12);border-color:#22d3ee;color:#22d3ee}
.field-row{display:flex;align-items:center;gap:10px;background:var(--surface);border:1.5px solid var(--border);border-radius:9px;padding:9px 13px;transition:all 0.2s;cursor:pointer}
.field-row:hover:not(.start-f):not(.end-f){border-color:var(--accent2)}
.field-row.filled{border-color:rgba(124,58,237,0.5)}
.field-row.start-f{border-color:var(--accent3);cursor:default}
.field-row.end-f{border-color:var(--accent);cursor:default}
.field-row.drag-target{background:rgba(124,58,237,0.1);border-color:var(--accent)!important;border-style:dashed}
.f-num{font-family:'Fredoka One',cursive;font-size:0.85rem;color:var(--muted);min-width:54px;flex-shrink:0}
.field-row.start-f .f-num{color:var(--accent3)}
.field-row.end-f .f-num{color:var(--accent)}
.f-icon{font-size:1.2rem;min-width:26px;text-align:center;flex-shrink:0}
.f-name{font-size:13px;font-weight:700;flex:1}
.f-empty{color:var(--muted);font-style:italic;font-weight:400}
.f-clear{background:none;border:none;color:var(--muted);cursor:pointer;font-size:0.9rem;padding:4px 6px;border-radius:6px;transition:all 0.15s;display:none}
.field-row.filled .f-clear{display:block}
.f-clear:hover{color:var(--danger);background:rgba(239,68,68,0.1)}
.story-row{display:flex;align-items:flex-start;gap:10px;background:rgba(6,182,212,0.07);border:1.5px solid rgba(6,182,212,0.4);border-radius:9px;padding:11px 13px;animation:fadeUp 0.25s ease both}
.story-icon{font-size:1.3rem;cursor:pointer;transition:transform 0.2s;flex-shrink:0;margin-top:2px}
.story-icon:hover{transform:scale(1.2)}
.story-content{flex:1}
.story-lbl{font-size:9px;font-weight:800;text-transform:uppercase;letter-spacing:1px;color:#22d3ee;margin-bottom:4px}
.story-ta{width:100%;background:rgba(6,182,212,0.06);border:1.5px solid rgba(6,182,212,0.3);border-radius:7px;color:var(--text);font-family:'Nunito',sans-serif;font-size:13px;font-weight:600;padding:7px 10px;resize:none;outline:none;transition:border-color 0.2s}
.story-ta:focus{border-color:#22d3ee}
.story-ta::placeholder{color:#3a6e7a}
.story-meta{display:flex;align-items:center;gap:6px;margin-top:5px}
.story-pos-lbl{font-size:10px;font-weight:700;color:#22d3ee;background:rgba(6,182,212,0.12);padding:2px 8px;border-radius:999px}
.story-chars{font-size:10px;color:var(--muted);margin-left:auto}
.story-clear{background:none;border:none;color:var(--muted);cursor:pointer;font-size:0.85rem;padding:4px;border-radius:5px;transition:all 0.15s;flex-shrink:0}
.story-clear:hover{color:var(--danger);background:rgba(239,68,68,0.1)}
.emoji-picker{position:fixed;background:var(--card);border:1.5px solid var(--border);border-radius:12px;padding:10px;display:grid;grid-template-columns:repeat(6,1fr);gap:4px;z-index:1000;box-shadow:0 8px 30px rgba(0,0,0,0.5);display:none}
.emoji-picker.open{display:grid}
.ep-it{font-size:1.2rem;cursor:pointer;padding:4px;border-radius:6px;text-align:center;transition:background 0.15s}
.ep-it:hover{background:var(--surface)}
.board-stats{display:flex;gap:8px;flex-wrap:wrap;margin-top:12px}
.stat-pill{background:var(--surface);border:1px solid var(--border);border-radius:999px;padding:5px 13px;font-size:12px;font-weight:700;color:var(--muted);display:flex;align-items:center;gap:5px}
.stat-pill span{color:var(--text)}
#dragGhost{position:fixed;pointer-events:none;z-index:9999;background:var(--card);border:2px solid var(--accent);border-radius:10px;padding:7px 14px;font-size:1rem;display:flex;align-items:center;gap:7px;box-shadow:0 8px 28px rgba(0,0,0,0.6);transform:translate(-50%,-50%);opacity:0;transition:opacity 0.1s;font-family:'Nunito',sans-serif;font-weight:700;color:var(--text)}
/* ─── QUIZ (step 4) ─── */
.quiz-card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);padding:22px;margin-bottom:14px;position:relative;overflow:hidden}
.quiz-card::before{content:'';position:absolute;left:0;top:0;bottom:0;width:4px;background:linear-gradient(180deg,var(--accent2),var(--accent))}
.quiz-card.complete::before{background:linear-gradient(180deg,var(--accent3),#059669)}
.qc-head{display:flex;align-items:center;gap:10px;margin-bottom:14px}
.qc-num{width:32px;height:32px;border-radius:50%;background:linear-gradient(135deg,var(--accent2),var(--accent));color:#fff;font-family:'Fredoka One',cursive;font-size:0.9rem;display:flex;align-items:center;justify-content:center;flex-shrink:0}
.quiz-card.complete .qc-num{background:linear-gradient(135deg,var(--accent3),#059669)}
.qc-title{font-family:'Fredoka One',cursive;font-size:1rem;flex:1}
.qc-status{font-size:11px;font-weight:800;text-transform:uppercase;padding:3px 9px;border-radius:999px}
.qc-status.done{background:rgba(16,185,129,0.12);color:var(--accent3);border:1px solid var(--accent3)}
.qc-status.todo{background:rgba(245,166,35,0.08);color:var(--accent);border:1px solid var(--accent)}
.q-input{width:100%;background:var(--surface);border:1.5px solid var(--border);border-radius:9px;color:var(--text);font-family:'Nunito',sans-serif;font-size:14px;font-weight:600;padding:11px 13px;outline:none;transition:border-color 0.2s,box-shadow 0.2s;margin-bottom:12px;resize:none}
.q-input:focus{border-color:var(--accent2);box-shadow:0 0 0 3px rgba(124,58,237,0.12)}
.q-input::placeholder{color:#4a476a}
.ans-grid{display:grid;grid-template-columns:1fr 1fr;gap:7px;margin-bottom:12px}
@media(max-width:480px){.ans-grid{grid-template-columns:1fr}}
.ans-wrap{position:relative}
.ans-letter{position:absolute;left:10px;top:50%;transform:translateY(-50%);font-family:'Fredoka One',cursive;font-size:0.85rem;width:22px;height:22px;border-radius:6px;display:flex;align-items:center;justify-content:center;background:var(--border);color:var(--muted);pointer-events:none;z-index:1;transition:all 0.2s}
.ans-wrap.correct .ans-letter{background:var(--accent3);color:#fff}
.ans-input{width:100%;background:var(--surface);border:1.5px solid var(--border);border-radius:9px;color:var(--text);font-family:'Nunito',sans-serif;font-size:13px;font-weight:600;padding:9px 11px 9px 40px;outline:none;transition:border-color 0.2s}
.ans-input:focus{border-color:var(--accent2)}
.ans-wrap.correct .ans-input{border-color:var(--accent3);background:rgba(16,185,129,0.06)}
.correct-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.correct-lbl{font-size:12px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:0.5px}
.correct-btns{display:flex;gap:7px}
.correct-btn{width:33px;height:33px;border-radius:8px;background:var(--surface);border:1.5px solid var(--border);font-family:'Fredoka One',cursive;font-size:0.85rem;color:var(--muted);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all 0.2s}
.correct-btn:hover{border-color:var(--accent3);color:var(--accent3)}
.correct-btn.selected{background:var(--accent3);border-color:var(--accent3);color:#fff;box-shadow:0 0 10px rgba(16,185,129,0.35)}
.tips-btn{background:none;border:none;color:var(--accent2);font-size:12px;font-weight:800;cursor:pointer;padding:0;margin-top:10px;display:flex;align-items:center;gap:5px;text-transform:uppercase;letter-spacing:0.5px}
.tips-box{background:rgba(124,58,237,0.07);border:1px solid rgba(124,58,237,0.25);border-radius:9px;padding:11px 13px;margin-top:7px;font-size:13px;color:var(--muted);line-height:1.7;display:none}
.tips-box.open{display:block}
.no-quiz{text-align:center;padding:36px 20px}
.no-quiz .nq-icon{font-size:3rem;margin-bottom:10px}
.no-quiz h3{font-family:'Fredoka One',cursive;font-size:1.2rem;color:var(--muted);margin-bottom:6px}
.af-bar{display:flex;align-items:center;gap:12px;background:var(--surface);border:1px solid var(--border);border-radius:11px;padding:13px 16px;margin-bottom:14px}
.af-icon{font-size:1.4rem;flex-shrink:0}
.af-text .af-title{font-family:'Fredoka One',cursive;font-size:0.95rem}
.af-text .af-sub{font-size:12px;color:var(--muted)}
.btn-sm{background:linear-gradient(135deg,var(--accent2),var(--accent));color:#fff;font-family:'Fredoka One',cursive;font-size:0.85rem;border:none;border-radius:8px;padding:9px 16px;cursor:pointer;transition:transform 0.15s;white-space:nowrap;flex-shrink:0}
.btn-sm:hover{transform:translateY(-1px)}
.ov-bar{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:14px}
.ov-pill{background:var(--card);border:1px solid var(--border);border-radius:999px;padding:5px 13px;font-size:12px;font-weight:700;color:var(--muted);display:flex;align-items:center;gap:5px}
.ov-pill span{color:var(--text)}
.ov-pill.good span{color:var(--accent3)}
.ov-pill.warn span{color:var(--accent)}
/* ─── REVIEW + TEST (step 5) ─── */
.hero-card{background:linear-gradient(135deg,rgba(124,58,237,0.18),rgba(245,166,35,0.08));border:1px solid rgba(124,58,237,0.35);border-radius:var(--radius);padding:26px;margin-bottom:18px;display:flex;align-items:center;gap:18px}
.hero-scene{font-size:3rem;line-height:1;flex-shrink:0;position:relative}
.hero-fig{position:absolute;bottom:-4px;right:-14px;font-size:2rem}
.hero-info .hi-name{font-family:'Fredoka One',cursive;font-size:1.7rem;color:var(--accent)}
.hero-info .hi-desc{color:var(--muted);font-size:13px;margin-top:4px;line-height:1.5}
.hero-tags{display:flex;gap:7px;flex-wrap:wrap;margin-top:9px}
.hero-tag{background:rgba(124,58,237,0.18);border:1px solid rgba(124,58,237,0.3);border-radius:999px;padding:3px 11px;font-size:11px;font-weight:700;color:#c4b5fd}
.info-grid{display:grid;grid-template-columns:1fr 1fr;gap:9px}
@media(max-width:500px){.info-grid{grid-template-columns:1fr}}
.info-block{background:var(--surface);border:1px solid var(--border);border-radius:11px;padding:13px}
.ib-lbl{font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:1px;color:var(--muted);margin-bottom:4px}
.ib-val{font-family:'Fredoka One',cursive;font-size:1.05rem}
.tl{display:flex;flex-direction:column}
.tl-item{display:flex;align-items:flex-start;gap:11px;padding:7px 0;position:relative}
.tl-item:not(:last-child)::after{content:'';position:absolute;left:16px;top:38px;bottom:-7px;width:2px;background:var(--border)}
.tl-item.story-it:not(:last-child)::after{background:rgba(6,182,212,0.3)}
.tl-dot{width:34px;height:34px;border-radius:50%;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:0.95rem;border:2px solid var(--border);background:var(--surface)}
.tl-dot.s{background:rgba(16,185,129,0.12);border-color:var(--accent3)}
.tl-dot.e{background:rgba(245,166,35,0.12);border-color:var(--accent)}
.tl-dot.st{background:rgba(6,182,212,0.12);border-color:#22d3ee}
.tl-dot.g{background:rgba(124,58,237,0.12);border-color:var(--accent2)}
.tl-dot.em{opacity:0.35}
.tl-lbl{font-size:13px;font-weight:800;padding-top:6px}
.tl-lbl.s{color:var(--accent3)}.tl-lbl.e{color:var(--accent)}.tl-lbl.st{color:#22d3ee}.tl-lbl.g,.tl-lbl.em{color:var(--muted)}
.tl-sub{font-size:12px;color:var(--muted);margin-top:1px;line-height:1.5}
.tl-badge{display:inline-block;background:rgba(124,58,237,0.12);border:1px solid var(--accent2);border-radius:6px;padding:2px 7px;font-size:10px;font-weight:800;color:var(--accent2);margin-top:2px;text-transform:uppercase;letter-spacing:0.5px}
.tl-badge.st{background:rgba(6,182,212,0.1);border-color:#22d3ee;color:#22d3ee}
.qp-list{display:flex;flex-direction:column;gap:7px}
.qp-item{background:var(--surface);border:1px solid var(--border);border-radius:9px;padding:11px 13px}
.qp-q{font-size:13px;font-weight:700;margin-bottom:6px}
.qp-ans-grid{display:grid;grid-template-columns:1fr 1fr;gap:4px}
.qp-ans{font-size:11px;padding:4px 8px;border-radius:6px;background:var(--card);border:1px solid var(--border);color:var(--muted);display:flex;align-items:center;gap:4px}
.qp-ans.cor{background:rgba(16,185,129,0.1);border-color:var(--accent3);color:var(--accent3);font-weight:700}
.checklist{display:flex;flex-direction:column;gap:6px}
.chk-item{background:var(--surface);border:1px solid var(--border);border-radius:9px;padding:9px 13px;display:flex;align-items:center;gap:8px;font-size:13px}
.chk-item.ok{border-color:rgba(16,185,129,0.3);color:var(--accent3)}
.chk-item.warn{border-color:rgba(245,166,35,0.3);color:var(--accent)}
/* ─── LAUNCH CARD ─── */
.launch-hero{background:linear-gradient(135deg,rgba(124,58,237,0.22),rgba(245,166,35,0.1));border-bottom:1px solid var(--border);padding:30px;text-align:center;position:relative;overflow:hidden}
.launch-hero::before{content:'';position:absolute;inset:0;background:radial-gradient(ellipse at 50% 0%,rgba(124,58,237,0.28),transparent 70%)}
.lh-fig{font-size:3.8rem;display:block;margin-bottom:9px;animation:float 3s ease-in-out infinite;position:relative;z-index:1}
.lh-name{font-family:'Fredoka One',cursive;font-size:1.7rem;color:var(--accent);position:relative;z-index:1}
.lh-desc{color:var(--muted);font-size:13px;margin-top:4px;max-width:360px;margin-left:auto;margin-right:auto;line-height:1.5;position:relative;z-index:1}
.lh-pills{display:flex;gap:7px;justify-content:center;flex-wrap:wrap;margin-top:11px;position:relative;z-index:1}
.lh-pill{background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.13);border-radius:999px;padding:4px 12px;font-size:12px;font-weight:700;color:rgba(255,255,255,0.72)}
.launch-body{padding:22px 26px}
.launch-chk{display:flex;flex-direction:column;gap:6px;margin-bottom:18px}
.lc-item{display:flex;align-items:center;gap:9px;background:var(--surface);border:1px solid var(--border);border-radius:9px;padding:9px 13px;font-size:13px;font-weight:700}
.lc-check{color:var(--accent3);margin-left:auto}
.btn-launch{width:100%;background:linear-gradient(135deg,var(--accent2),var(--accent));color:#fff;font-family:'Fredoka One',cursive;font-size:1.15rem;border:none;border-radius:13px;padding:17px;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:9px;box-shadow:0 5px 26px rgba(124,58,237,0.4);transition:transform 0.15s,box-shadow 0.15s,opacity 0.2s;position:relative;overflow:hidden}
.btn-launch::after{content:'';position:absolute;inset:0;background:linear-gradient(135deg,rgba(255,255,255,0.1),transparent)}
.btn-launch:hover:not(:disabled){transform:translateY(-2px);box-shadow:0 9px 32px rgba(124,58,237,0.5)}
.btn-launch:disabled{opacity:0.5;cursor:not-allowed;transform:none}
.btn-launch-sub{text-align:center;font-size:12px;color:var(--muted);margin-top:7px;font-weight:700}
.played-badge{display:none;background:rgba(16,185,129,0.1);border:1px solid var(--accent3);border-radius:9px;padding:11px 15px;margin-top:12px;font-size:13px;font-weight:700;color:var(--accent3);align-items:center;gap:7px}
.played-badge.visible{display:flex}
/* ─── FEEDBACK ─── */
.fb-prog{height:4px;background:var(--border);border-radius:999px;margin-bottom:22px;overflow:hidden}
.fb-prog-fill{height:100%;background:linear-gradient(90deg,var(--accent2),var(--accent));border-radius:999px;transition:width 0.4s ease}
.fb-qnum{font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:1px;color:var(--accent2);margin-bottom:5px}
.fb-q{font-family:'Fredoka One',cursive;font-size:1.1rem;margin-bottom:11px;line-height:1.4}
.fb-sub{font-size:13px;color:var(--muted);margin-bottom:11px;margin-top:-5px}
.mc-opts{display:flex;flex-direction:column;gap:7px}
.mc-opt{background:var(--surface);border:2px solid var(--border);border-radius:9px;padding:11px 15px;cursor:pointer;transition:all 0.2s;display:flex;align-items:center;gap:10px;font-size:14px;font-weight:600}
.mc-opt:hover{border-color:var(--accent2)}
.mc-opt.sel{border-color:var(--accent);background:rgba(245,166,35,0.07);color:var(--accent)}
.mc-dot{width:19px;height:19px;border-radius:50%;border:2px solid var(--border);flex-shrink:0;transition:all 0.2s}
.mc-opt.sel .mc-dot{background:var(--accent);border-color:var(--accent)}
.scale-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.scale-btn{width:46px;height:46px;border-radius:9px;border:2px solid var(--border);background:var(--surface);font-family:'Fredoka One',cursive;font-size:1.05rem;cursor:pointer;color:var(--muted);transition:all 0.2s;display:flex;align-items:center;justify-content:center}
.scale-btn:hover{border-color:var(--accent2);color:var(--text)}
.scale-btn.sel{background:var(--accent);border-color:var(--accent);color:#000}
.scale-lbls{display:flex;justify-content:space-between;font-size:11px;color:var(--muted);margin-top:5px;font-weight:700}
.fb-ta{width:100%;background:var(--surface);border:1.5px solid var(--border);border-radius:9px;color:var(--text);font-family:'Nunito',sans-serif;font-size:14px;font-weight:600;padding:11px 13px;outline:none;resize:none;transition:border-color 0.2s}
.fb-ta:focus{border-color:var(--accent2)}
.fb-ta::placeholder{color:#4a476a}
.fb-nav{display:flex;gap:9px;margin-top:18px}
.btn-fb-prev{background:var(--surface);color:var(--muted);font-family:'Fredoka One',cursive;font-size:0.9rem;border:1.5px solid var(--border);border-radius:10px;padding:11px 20px;cursor:pointer;transition:all 0.2s}
.btn-fb-prev:hover{border-color:var(--muted);color:var(--text)}
.btn-fb-next{flex:1;background:linear-gradient(135deg,var(--accent2),var(--accent));color:#fff;font-family:'Fredoka One',cursive;font-size:1rem;border:none;border-radius:10px;padding:12px;cursor:pointer;transition:transform 0.15s,opacity 0.2s;box-shadow:0 4px 14px rgba(124,58,237,0.3)}
.btn-fb-next:hover:not(:disabled){transform:translateY(-1px)}
.btn-fb-next:disabled{opacity:0.32;cursor:not-allowed}
/* ─── SUMMARY ─── */
.sum-hero{text-align:center;padding:30px 22px 18px}
.sum-icon{font-size:3.5rem;display:block;margin-bottom:10px}
.sum-hero h2{font-family:'Fredoka One',cursive;font-size:1.9rem;color:var(--accent3)}
.sum-hero p{color:var(--muted);font-size:13px;margin-top:5px;line-height:1.6}
.sum-scores{display:flex;gap:9px;justify-content:center;flex-wrap:wrap;margin-top:14px}
.sum-sc{background:var(--surface);border:1px solid var(--border);border-radius:11px;padding:11px 16px;text-align:center}
.sum-val{font-family:'Fredoka One',cursive;font-size:1.5rem;color:var(--accent)}
.sum-lbl{font-size:10px;font-weight:800;text-transform:uppercase;color:var(--muted);margin-top:2px}
.sum-notes{display:flex;flex-direction:column;gap:6px;margin-bottom:18px}
.sn-item{display:flex;align-items:flex-start;gap:8px;font-size:13px;padding:8px 0;border-bottom:1px solid var(--border)}
.sn-item:last-child{border-bottom:none}
.final-btns{display:flex;gap:10px;justify-content:center;flex-wrap:wrap}
.btn-revise{background:var(--surface);color:var(--text);font-family:'Fredoka One',cursive;font-size:1rem;border:1.5px solid var(--border);border-radius:11px;padding:13px 22px;cursor:pointer;transition:all 0.2s}
.btn-revise:hover{border-color:var(--accent2)}
.btn-publish{background:linear-gradient(135deg,var(--accent3),#059669);color:#fff;font-family:'Fredoka One',cursive;font-size:1.05rem;border:none;border-radius:11px;padding:14px 28px;cursor:pointer;transition:transform 0.15s,box-shadow 0.15s;box-shadow:0 4px 18px rgba(16,185,129,0.38)}
.btn-publish:hover{transform:translateY(-2px);box-shadow:0 8px 26px rgba(16,185,129,0.48)}
/* ─── TOAST ─── */
#toast{position:fixed;bottom:22px;left:50%;transform:translateX(-50%) translateY(16px);background:rgba(16,185,129,0.96);color:#fff;font-family:'Fredoka One',cursive;font-size:0.95rem;padding:11px 26px;border-radius:11px;z-index:500;opacity:0;transition:all 0.3s;pointer-events:none;white-space:nowrap;box-shadow:0 4px 18px rgba(0,0,0,0.3)}
#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
/* ─── MINI-GAME TEST POPUP ─── */
.mg-card{position:relative;}
.mg-test-btn{display:block;width:100%;margin-top:6px;background:rgba(124,58,237,0.25);border:1px solid rgba(124,58,237,0.5);border-radius:6px;color:#c4b5fd;font-size:10px;font-weight:800;padding:4px 0;cursor:pointer;text-transform:uppercase;letter-spacing:0.5px;transition:all 0.15s;z-index:2;}
.mg-test-btn:hover{background:rgba(124,58,237,0.55);color:#fff;border-color:#7c3aed;}
.mg-card.disabled .mg-test-btn{display:none;}
#mgTestOverlay{position:fixed;inset:0;background:rgba(0,0,0,0.75);z-index:5000;display:none;align-items:center;justify-content:center;backdrop-filter:blur(4px);}
#mgTestOverlay.open{display:flex;}
#mgTestBox{background:#1a1827;border:1.5px solid #2e2b4a;border-radius:20px;padding:0;width:min(480px,95vw);max-height:90vh;overflow:hidden;display:flex;flex-direction:column;box-shadow:0 24px 60px rgba(0,0,0,0.7);}
#mgTestHeader{display:flex;align-items:center;gap:12px;padding:18px 22px;border-bottom:1px solid #2e2b4a;background:#22203a;}
#mgTestEmoji{font-size:2rem;}
#mgTestTitle{font-family:'Fredoka One',cursive;font-size:1.3rem;color:#f5a623;flex:1;}
#mgTestClose{background:none;border:none;color:#a7a3c2;font-size:1.4rem;cursor:pointer;padding:4px 8px;border-radius:8px;transition:all 0.15s;line-height:1;}
#mgTestClose:hover{background:rgba(239,68,68,0.15);color:#ef4444;}
#mgTestBody{padding:20px;overflow-y:auto;}
#mgTestCanvas{display:block;margin:0 auto;border-radius:10px;border:1px solid #2e2b4a;}
#mgTestMsg{text-align:center;font-family:'Fredoka One',cursive;font-size:1.1rem;margin-top:14px;min-height:28px;}
#mgTestMsg.win{color:#10b981;}#mgTestMsg.lose{color:#ef4444;}
#mgTestControls{display:flex;gap:8px;justify-content:center;flex-wrap:wrap;margin-top:14px;}
.mg-ctrl-btn{background:linear-gradient(135deg,#7c3aed,#f5a623);color:#fff;font-family:'Fredoka One',cursive;font-size:0.9rem;border:none;border-radius:9px;padding:10px 20px;cursor:pointer;transition:transform 0.15s;}
.mg-ctrl-btn:hover{transform:translateY(-1px);}
.mg-ctrl-btn.secondary{background:#22203a;border:1.5px solid #2e2b4a;color:#a7a3c2;}
#mgTestDesc{font-size:13px;color:#a7a3c2;text-align:center;line-height:1.6;margin-bottom:14px;}
/* ─── CODE PANE ─── */
#codePane{font-family:'Fira Code','Cascadia Code','Consolas','Monaco',monospace}
#codeHeader{
display:flex;align-items:center;justify-content:space-between;
background:#131221;border-bottom:1px solid rgba(255,255,255,0.07);
padding:9px 16px;flex-shrink:0;
}
#codeHeaderLeft{display:flex;align-items:center;gap:7px}
.code-dot{width:11px;height:11px;border-radius:50%;display:inline-block}
.code-dot.red{background:#ff5f57}.code-dot.yellow{background:#febc2e}.code-dot.green{background:#28c840}
#codeFilename{color:#a7a3c2;font-size:12px;font-weight:600;margin-left:6px;font-family:'Nunito',sans-serif}
#codeHeaderRight{display:flex;align-items:center;gap:12px}
#codeLang{font-size:11px;font-weight:700;color:var(--accent2);font-family:'Nunito',sans-serif;
background:rgba(124,58,237,0.15);border:1px solid rgba(124,58,237,0.3);border-radius:5px;padding:2px 8px}
#codeLines{font-size:10px;color:rgba(255,255,255,0.25);font-family:'Nunito',sans-serif}
#codeScroll{
display:flex;flex:1;overflow-y:auto;overflow-x:hidden;min-height:0;
scrollbar-width:thin;scrollbar-color:rgba(124,58,237,0.3) transparent;
}
#codeScroll::-webkit-scrollbar{width:5px}
#codeScroll::-webkit-scrollbar-thumb{background:rgba(124,58,237,0.3);border-radius:3px}
#codeLineNums{
padding:16px 12px 16px 10px;text-align:right;
color:rgba(255,255,255,0.15);font-size:12px;line-height:1.65;
user-select:none;flex-shrink:0;min-width:38px;border-right:1px solid rgba(255,255,255,0.05);
background:#0a0917;
}
#codeContent{
flex:1;padding:16px 16px 80px 14px;margin:0;white-space:pre;
font-size:12.5px;line-height:1.65;color:#e2e0f0;overflow-x:auto;
background:transparent;border:none;outline:none;
}
/* ── Syntax colors (applied via spans) */
.py-kw{color:#c678dd} /* keywords: def, class, if, for, return, import */
.py-fn{color:#61afef} /* function names */
.py-str{color:#98c379} /* strings */
.py-num{color:#d19a66} /* numbers */
.py-cm{color:#5c6370;font-style:italic} /* comments */
.py-var{color:#e06c75} /* variables (left of =) */
.py-val{color:#abb2bf} /* plain values */
.py-punc{color:#abb2bf} /* punctuation */
.py-cls{color:#e5c07b} /* class names */
.py-dec{color:#56b6c2} /* decorators / special */
.py-hi{ /* active/focused line highlight */
background:rgba(245,166,35,0.13);display:inline-block;width:100%;
border-left:3px solid var(--accent);padding-left:6px;margin-left:-6px;
animation:codeFlash 0.4s ease both;
}
@keyframes codeFlash{
0% {background:rgba(245,166,35,0.35);border-left-color:var(--accent)}
100%{background:rgba(245,166,35,0.10);border-left-color:var(--accent)}
}
#codeFooter{
display:flex;align-items:center;justify-content:space-between;
background:#0d0c1a;border-top:1px solid rgba(255,255,255,0.05);
padding:5px 14px;flex-shrink:0;
}
#codeStatus{font-size:10px;color:var(--accent3);font-weight:700;font-family:'Nunito',sans-serif;display:flex;align-items:center;gap:5px}
#codeInfo{font-size:10px;color:rgba(255,255,255,0.2);font-family:'Nunito',sans-serif}
/* typing cursor in code */
.py-cursor{display:inline-block;width:2px;height:14px;background:var(--accent2);vertical-align:middle;animation:blink 1s step-end infinite;margin-left:1px}
@keyframes blink{0%,100%{opacity:1}50%{opacity:0}}
/* Responsive: hide code pane on small screens */
@media(max-width:900px){
#resizerBar,#codePane{display:none}
#editorPane{flex:1;border-right:none}
}
</style>
</head>
<body>
<!-- ═══ PROGRESS BAR ═══ -->
<div id="progressBar">
<div class="pb-inner">
<div class="pb-top">
<div class="pb-logo" style="display:flex;align-items:center;gap:12px">
<span>🎲 Spiel-Generator <span style="color:var(--muted);font-size:0.75rem;font-family:'Nunito',sans-serif;font-weight:700">— Dein Spiel, deine Regeln</span></span>
<button onclick="resetAll()" title="Alles zurücksetzen" style="background:rgba(239,68,68,0.12);border:1px solid rgba(239,68,68,0.3);color:#ef4444;font-size:10px;font-weight:800;font-family:'Nunito',sans-serif;border-radius:6px;padding:3px 9px;cursor:pointer;letter-spacing:0.5px;transition:all 0.2s" onmouseover="this.style.background='rgba(239,68,68,0.25)'" onmouseout="this.style.background='rgba(239,68,68,0.12)'">↺ Reset</button>
</div>
<div class="pb-phwg">
<img src="logo.png" alt="PH Weingarten"/>
<div class="pb-phwg-text">
<div class="pb-phwg-studiengaenge"><span>Medien- und Bildungsmanagement</span></div>
<div class="pb-phwg-studiengaenge"><span>Informatik (Lehramt)</span></div>
</div>
</div>
</div>
<div class="pb-steps">
<div class="pb-step active" id="pb0" onclick="pbClick(0)"><div class="pb-dot">1</div><span class="pb-label">Grundinfos</span></div>
<div class="pb-connector" id="pbc0"></div>
<div class="pb-step" id="pb1" onclick="pbClick(1)"><div class="pb-dot">2</div><span class="pb-label">Gestaltung</span></div>
<div class="pb-connector" id="pbc1"></div>
<div class="pb-step" id="pb2" onclick="pbClick(2)"><div class="pb-dot">3</div><span class="pb-label">Spielfeld</span></div>
<div class="pb-connector" id="pbc2"></div>
<div class="pb-step" id="pb3" onclick="pbClick(3)"><div class="pb-dot">4</div><span class="pb-label">Quiz</span></div>
<div class="pb-connector" id="pbc3"></div>
<div class="pb-step" id="pb4" onclick="pbClick(4)"><div class="pb-dot">5</div><span class="pb-label">Testen</span></div>
</div>
<div class="pb-track"><div class="pb-fill" id="pbFill" style="width:10%"></div></div>
</div>
</div>
</div>
<div id="splitWrap">
<div id="editorPane">
<div id="dragGhost"></div>
<div class="emoji-picker" id="emojiPicker"></div>
<div id="toast">✅ Gespeichert!</div>
<!-- ════════════ SCREEN 1: GRUNDINFOS ════════════ -->
<div class="screen active" id="s0">
<div class="sh">
<div class="sh-badge">Schritt 1 von 5</div>
<h1 class="sh-title">Dein Spiel <em>beginnt hier ✨</em></h1>
<p class="sh-sub">Erstmal wichtig: Wer bist du? Dann gib deinem Spiel einen Namen.</p>
</div>
<div class="card" style="background:linear-gradient(135deg,rgba(124,58,237,0.12),rgba(245,166,35,0.06));border-color:rgba(124,58,237,0.4)">
<div class="card-title">👋 Hallo! Wie heißt du?</div>
<p class="card-sub">Dein Name oder Nickname — er erscheint auf deinem Spiel als Entwickler:in!</p>
<input class="finput" id="s1devname" type="text" onfocus="setCodeFocus('dev')" maxlength="30" placeholder="z.B. Lena, MrCoder, GameMaster42 …" oninput="s1update()" style="border-color:rgba(124,58,237,0.5)"/>
<div id="devGreeting" style="margin-top:10px;min-height:28px;font-family:'Fredoka One',cursive;font-size:1.05rem;color:var(--accent);transition:opacity 0.3s;opacity:0"></div>
</div>
<div class="card">
<label class="lbl">🎮 Spielname</label>
<input class="finput" id="s1name" type="text" onfocus="setCodeFocus('name')" maxlength="40" placeholder="z.B. Abenteuer im Dschungel" oninput="s1update()"/>
<div class="char-row" id="s1nc">0 / 40</div>
<div class="suggestions">
<div class="sug" onclick="suggest('Abenteuer im Dschungel')">🌴 Abenteuer im Dschungel</div>
<div class="sug" onclick="suggest('Mission Weltraum')">🚀 Mission Weltraum</div>
<div class="sug" onclick="suggest('Das große Quiz-Turnier')">❓ Das große Quiz-Turnier</div>
<div class="sug" onclick="suggest('Schatz des Drachen')">🐲 Schatz des Drachen</div>
<div class="sug" onclick="suggest('Flucht aus dem Labyrinth')">🌀 Flucht aus dem Labyrinth</div>
</div>
<div class="divider"></div>
<label class="lbl">📝 Beschreibung <span style="color:var(--muted);font-weight:400;font-size:10px">(optional)</span></label>
<textarea class="ftarea" id="s1desc" rows="3" onfocus="setCodeFocus('desc')" maxlength="120" placeholder="Worum geht es? Was ist das Ziel?" oninput="s1update()"></textarea>
<div class="char-row" id="s1dc">0 / 120</div>
</div>
<div id="s1preview" style="display:none">
<div class="preview-card">
<div class="pv-label">👁️ Vorschau</div>
<div class="pv-name" id="s1pvname"></div>
<div class="pv-desc" id="s1pvdesc"></div>
<div id="s1pvdev" style="margin-top:6px;font-size:11px;font-weight:700;color:var(--accent2)"></div>
</div>
</div>
<div class="btn-row">
<div></div>
<button class="btn-next" id="s1next" onclick="goTo(1)" disabled>Weiter <span></span></button>
</div>
</div>
<!-- ════════════ SCREEN 2: GESTALTUNG ════════════ -->
<div class="screen" id="s1">
<div class="sh">
<div class="sh-badge">Schritt 2 von 5</div>
<h1 class="sh-title">Figur & <em>Setting 🎨</em></h1>
<p class="sh-sub">Wähle deine Spielfigur und die Spielwelt.</p>
</div>
<div class="summary-banner" id="sum0">
<div class="sb-title">✅ Schritt 1 abgeschlossen</div>
<div class="sb-chips" id="sum0chips"></div>
</div>
<div class="card">
<div class="card-title">👤 Spielfigur</div>
<p class="card-sub">Das bist du im Spiel!</p>
<div class="sel-grid" id="figGrid"></div>
</div>
<div class="card">
<div class="card-title">🌍 Spielwelt</div>
<p class="card-sub">Wo findet das Abenteuer statt?</p>
<div class="sel-grid" id="bgGrid"></div>
<div id="s2combo" style="display:none">
<div class="combo-preview">
<span class="cp-scene" id="s2scene"></span>
<div class="cp-label" id="s2clabel"></div>
</div>
</div>
</div>
<div class="btn-row">
<button class="btn-back" onclick="goTo(0)">← Zurück</button>
<button class="btn-next" id="s2next" onclick="goTo(2)" disabled>Weiter <span></span></button>
</div>
</div>
<!-- ════════════ SCREEN 3: SPIELFELD ════════════ -->
<div class="screen" id="s2">
<div class="sh">
<div class="sh-badge">Schritt 3 von 5</div>
<h1 class="sh-title">Bau dein <em>Spielfeld 🎲</em></h1>
<p class="sh-sub">Lege Regeln fest und bestücke die Felder mit Mini-Games.</p>
</div>
<div class="summary-banner" id="sum1">
<div class="sb-title">✅ Schritt 2 abgeschlossen</div>
<div class="sb-chips" id="sum1chips"></div>
</div>
<div class="card">
<div class="card-title">⚙️ Spielregeln</div>
<p class="card-sub">Wie soll dein Spiel funktionieren?</p>
<div class="rules-grid">
<div class="rule-block">
<div class="rule-block-title">🎲 Bewegung</div>
<div class="toggle-group">
<div class="toggle-option selected" data-g="movement" data-v="dice" onclick="selRule(this)"><span class="tog-emoji">🎲</span><div><div class="tog-text">Würfeln</div><div class="tog-sub">Zufällig 16 Felder</div></div><div class="tog-check"></div></div>
<div class="toggle-option" data-g="movement" data-v="step" onclick="selRule(this)"><span class="tog-emoji">👣</span><div><div class="tog-text">Ein Feld pro Runde</div><div class="tog-sub">Jedes Feld wird besucht</div></div><div class="tog-check"></div></div>
</div>
</div>
<div class="rule-block">
<div class="rule-block-title">💀 Bei Niederlage</div>
<div class="toggle-group">
<div class="toggle-option selected" data-g="fail" data-v="lives" onclick="selRule(this)"><span class="tog-emoji">❤️</span><div><div class="tog-text">Leben verlieren</div><div class="tog-sub">Bei 0 Leben: Game Over</div></div><div class="tog-check"></div></div>
<div class="toggle-option" data-g="fail" data-v="points" onclick="selRule(this)"><span class="tog-emoji"></span><div><div class="tog-text">Punkte sammeln</div><div class="tog-sub">Kein Verlieren, nur Score</div></div><div class="tog-check"></div></div>
</div>
<div class="sub-opts visible" id="sub_lives">
<div class="sub-opt selected" data-g="lives" data-v="3" onclick="selSub(this)">❤️❤️❤️ 3 Leben</div>
<div class="sub-opt" data-g="lives" data-v="5" onclick="selSub(this)">5 Leben</div>
<div class="sub-opt" data-g="lives" data-v="1" onclick="selSub(this)">☠️ 1 Leben</div>
</div>
<div class="sub-opts" id="sub_points">
<div class="sub-opt selected" data-g="pts" data-v="10" onclick="selSub(this)">+10 Punkte</div>
<div class="sub-opt" data-g="pts" data-v="25" onclick="selSub(this)">+25 Punkte</div>
<div class="sub-opt" data-g="pts" data-v="50" onclick="selSub(this)">+50 Punkte</div>
</div>
</div>
</div>
<div class="divider"></div>
<div class="count-row">
<label>⬛ Anzahl Felder</label>
<div class="count-ctrl">
<button class="cnt-btn" id="cntMinus" onclick="chgCount(-1)"></button>
<div class="cnt-val" id="cntVal">10</div>
<button class="cnt-btn" id="cntPlus" onclick="chgCount(1)">+</button>
<span style="font-size:11px;color:var(--muted)">(620)</span>
</div>
</div>
<div class="sec-label"><span>🕹️ Bausteine</span><div class="story-counter" id="storyCtr">📖 0 / 5</div></div>
<div class="pal-tabs">
<div class="pal-tab active" onclick="palTab('games')">🕹️ Mini-Games</div>
<div class="pal-tab" onclick="palTab('story')">📖 Erzähltexte</div>
</div>
<div class="pal-panel active" id="panGames"></div>
<div class="pal-panel" id="panStory">
<div class="mg-card" id="storyCard" draggable="true" onclick="clickStory()" style="border-color:rgba(6,182,212,0.4)">
<div class="use-count" id="storyUC">0</div>
<div class="mg-emoji">📖</div>
<div class="mg-lbl">Erzähl-Text</div>
<div class="mg-tag story-tag">max.5×</div>
<div style="font-size:10px;color:rgba(6,182,212,0.6);margin-top:2px">Vor/nach Feld</div>
</div>
</div>
<div class="sec-label" style="margin-top:6px">📋 Spielfeld <span style="font-size:10px;font-weight:400;text-transform:none;letter-spacing:0">Klicke auf ein Feld oder ziehe Bausteine hinein</span></div>
<div class="board-builder" id="boardBuilder"></div>
<div class="board-stats" id="boardStats"></div>
</div>
<div class="btn-row">
<button class="btn-back" onclick="goTo(1)">← Zurück</button>
<button class="btn-next" id="s3next" onclick="goTo(3)" disabled>Weiter <span></span></button>
</div>
</div>
<!-- ════════════ SCREEN 4: QUIZ ════════════ -->
<div class="screen" id="s3">
<div class="sh">
<div class="sh-badge">Schritt 4 von 5</div>
<h1 class="sh-title">Deine <em>Quiz-Fragen ❓</em></h1>
<p class="sh-sub">Für jedes Quiz-Feld brauchst du eine Frage mit vier Antworten.</p>
</div>
<div class="summary-banner" id="sum2">
<div class="sb-title">✅ Schritt 3 abgeschlossen</div>
<div class="sb-chips" id="sum2chips"></div>
</div>
<div id="quizArea"></div>
<div class="btn-row">
<button class="btn-back" onclick="goTo(2)">← Zurück</button>
<button class="btn-next" id="s4next" onclick="goTo(4)">Weiter <span></span></button>
</div>
</div>
<!-- ════════════ SCREEN 5: TESTEN ════════════ -->
<div class="screen" id="s4">
<div class="sh">
<div class="sh-badge">Schritt 5 von 5</div>
<h1 class="sh-title">Testen & <em>Feedback 🎮</em></h1>
<p class="sh-sub">Überblick über dein Spiel — dann starten und ausprobieren!</p>
</div>
<div class="summary-banner" id="sum3">
<div class="sb-title">✅ Schritt 4 abgeschlossen</div>
<div class="sb-chips" id="sum3chips"></div>
</div>
<!-- Review filled dynamically -->
<div id="reviewArea"></div>
<!-- Launch card -->
<div class="card" id="launchCard" style="padding:0;overflow:hidden;margin-bottom:18px">
<div class="launch-hero">
<span class="lh-fig" id="lhFig">🎮</span>
<div class="lh-name" id="lhName"></div>
<div class="lh-desc" id="lhDesc"></div>
<div class="lh-pills" id="lhPills"></div>
</div>
<div class="launch-body">
<div class="launch-chk" id="launchChk"></div>
<button class="btn-launch" id="btnLaunch" onclick="doLaunch()">🎮 Spiel starten &amp; testen!</button>
<div class="btn-launch-sub">Öffnet das Spiel in einem neuen Fenster</div>
<div class="played-badge" id="playedBadge">✅ Du hast gespielt! Jetzt bitte Feedback geben ↓</div>
</div>
</div>
<!-- Feedback (hidden until played) -->
<div class="card" id="fbCard" style="display:none">
<div class="card-title">📝 Feedback zu deinem Spiel</div>
<p class="card-sub">Beantworte diese Fragen ehrlich — sie helfen dir, das Spiel zu verbessern!</p>
<div class="fb-prog"><div class="fb-prog-fill" id="fbFill" style="width:0%"></div></div>
<div id="fbArea"></div>
<div class="fb-nav">
<button class="btn-fb-prev" id="fbPrev" onclick="fbBack()" style="display:none">← Zurück</button>
<button class="btn-fb-next" id="fbNext" onclick="fbFwd()" disabled>Weiter →</button>
</div>
</div>
<!-- Summary (hidden until feedback done) -->
<div class="card" id="sumCard" style="display:none;padding:0;overflow:hidden">
<div class="sum-hero">
<span class="sum-icon">🎉</span>
<h2>Test abgeschlossen!</h2>
<p id="sumText"></p>
<div class="sum-scores" id="sumScores"></div>
</div>
<div style="padding:0 22px 6px">
<div class="card-title" style="margin-bottom:9px">📋 Auswertung</div>
<div class="sum-notes" id="sumNotes"></div>
</div>
<div style="padding:14px 22px 26px">
<div class="final-btns">
<button class="btn-revise" onclick="goTo(0)">✏️ Überarbeiten</button>
<button class="btn-publish" onclick="doPublish()">🚀 Spiel veröffentlichen →</button>
</div>
</div>
</div>
<div class="btn-row">
<button class="btn-back" onclick="goTo(3)">← Zurück</button>
<div></div>
</div>
</div>
<script>
/* ══════════════════════════════════════════════
DATA
══════════════════════════════════════════════ */
const FIGURES=[
{id:'robot',e:'🤖',n:'Roboter'},{id:'ninja',e:'🥷',n:'Ninja'},{id:'knight',e:'🧙‍♂️',n:'Zauberer'},
{id:'cat',e:'🐱',n:'Katze'},{id:'rocket',e:'🚀',n:'Rakete'},{id:'dino',e:'🦖',n:'Dino'},
{id:'alien',e:'👾',n:'Alien'},{id:'superhero',e:'🦸',n:'Superheld'},{id:'pirate',e:'🏴‍☠️',n:'Pirat'},
{id:'fox',e:'🦊',n:'Fuchs'},{id:'dragon',e:'🐲',n:'Drache'},{id:'astronaut',e:'👨‍🚀',n:'Astronaut'},
];
const BACKGROUNDS=[
{id:'jungle',e:'🌴',scene:'🌴🦜🌿',n:'Dschungel'},{id:'space',e:'🌌',scene:'🌌🪐⭐',n:'Weltraum'},
{id:'ocean',e:'🌊',scene:'🌊🐠🐙',n:'Unterwasser'},{id:'fantasy',e:'🏰',scene:'🏰🐉✨',n:'Fantasy'},
{id:'school',e:'🏫',scene:'🏫📚✏️',n:'Schule'},{id:'volcano',e:'🌋',scene:'🌋🏝️🔥',n:'Vulkaninsel'},
{id:'snow',e:'❄️',scene:'❄️🏔️⛄',n:'Schneereich'},{id:'city',e:'🌆',scene:'🌆🚗🏙️',n:'Großstadt'},
{id:'candy',e:'🍭',scene:'🍭🍬🎂',n:'Süßigkeitenland'},{id:'desert',e:'🏜️',scene:'🏜️🌵🐪',n:'Wüste'},
{id:'haunted',e:'👻',scene:'👻🏚️🕸️',n:'Geisterhaus'},{id:'future',e:'🤖',scene:'🤖🏙️💡',n:'Zukunft'},
];
const MINIGAMES=[
{id:'quiz',e:'❓',n:'Quiz',multi:'∞'},{id:'reaction',e:'⚡',n:'Reaktion',multi:3},
{id:'puzzle',e:'🧩',n:'Rätsel',multi:3},{id:'spotdiff',e:'🔍',n:'Fehler finden',multi:3},
{id:'typing',e:'⌨️',n:'Tipp-Rennen',multi:3},{id:'snake',e:'🐍',n:'Snake',multi:1},
{id:'flappy',e:'🐦',n:'Flappy Bird',multi:1},{id:'catch',e:'🍎',n:'Äpfel fangen',multi:1},
{id:'basketball',e:'🏀',n:'Basketball',multi:1},{id:'memory',e:'🃏',n:'Memory',multi:1},
{id:'maze',e:'🌀',n:'Labyrinth',multi:1},{id:'simon',e:'🔴',n:'Simon Says',multi:1},
];
const STORY_EMOJIS=['📖','🗺️','⚔️','🧭','💬','🌟','🔥','💡','🎭','🏴‍☠️','🌈','🐉','👁️','🎶','🌙','⚡','🗝️','🌊','🏔️','🎪','🦋','🌺','💎','🎯'];
const LETTERS=['A','B','C','D'];
const QUIZ_EX=[
{q:'Was ist die Hauptstadt von Deutschland?',a:['Paris','London','Berlin','Madrid'],c:2},
{q:'Wie viele Seiten hat ein Hexagon?',a:['5','6','7','8'],c:1},
{q:'Wer schrieb "Romeo und Julia"?',a:['Goethe','Schiller','Shakespeare','Kafka'],c:2},
{q:'Was ist das größte Tier der Welt?',a:['Elefant','Blauwal','Hai','Giraffe'],c:1},
{q:'Wie viele Minuten hat eine Stunde?',a:['50','70','100','60'],c:3},
{q:'Chemische Formel für Wasser?',a:['CO2','H2O','O2','NaCl'],c:1},
{q:'In welchem Kontinent liegt Ägypten?',a:['Asien','Europa','Amerika','Afrika'],c:3},
{q:'Was ist 12 × 12?',a:['132','144','124','148'],c:1},
{q:'Blau + Gelb = ?',a:['Rot','Orange','Grün','Lila'],c:2},
{q:'Höchster Berg der Welt?',a:['K2','Mont Blanc','Everest','Kilimandscharo'],c:2},
];
const FB_QS=[
{id:'fun',t:'scale',q:'Wie viel Spaß hat dein Spiel gemacht?',sub:'1 = gar kein Spaß · 5 = mega viel Spaß',min:'😴 Langweilig',max:'🔥 Super Spaß'},
{id:'difficulty',t:'mc',q:'Wie schwierig war dein Spiel?',opts:['😴 Viel zu einfach','👍 Genau richtig','😅 Etwas zu schwer','💀 Viel zu schwer']},
{id:'flow',t:'mc',q:'Wie gut passt das Spiel zusammen?',sub:'„Flow" — fühlt sich alles stimmig und flüssig an?',opts:['🔀 Wirkt zusammengewürfelt','🤔 Einigermaßen','👌 Passt gut','✨ Perfekt stimmig']},
{id:'clarity',t:'mc',q:'Waren Regeln und Aufgaben klar?',opts:['❓ Oft unklar','🤔 Manchmal unklar','👍 Meistens klar','✅ Immer sofort klar']},
{id:'minigames',t:'scale',q:'Wie gut passen die Mini-Games zum Thema?',sub:'Passen sie zur Geschichte und zum Setting?',min:'🚫 Passen nicht',max:'✨ Passen perfekt'},
{id:'problems',t:'text',q:'Was hat noch nicht so gut funktioniert?',ph:'z.B. "Das Quiz war zu einfach" oder "zu viele leere Felder"...'},
{id:'highlight',t:'text',q:'Was war der beste Teil deines Spiels?',ph:'z.B. "Die Geschichte war spannend" oder "Snake hat Spaß gemacht"...'},
{id:'replay',t:'mc',q:'Würdest du das Spiel nochmal spielen?',opts:['❌ Eher nicht','🤷 Vielleicht','😊 Ja','🚀 Auf jeden Fall!']},
];
/* ══════════════════════════════════════════════
STATE
══════════════════════════════════════════════ */
const ST={
devName:'',name:'',desc:'',figure:'',background:'',
fieldCount:10,fields:Array(10).fill(null),
storyItems:[],quizData:[],
rules:{movement:'dice',fail:'lives',lives:'3',pts:'10'},
highestStep:0,
};
try{
const sv=JSON.parse(localStorage.getItem('gameConfig')||'{}');
if(sv.devName)ST.devName=sv.devName;
if(sv.name)ST.name=sv.name;if(sv.desc)ST.desc=sv.desc;
if(sv.figure)ST.figure=sv.figure;if(sv.background)ST.background=sv.background;
if(sv.fieldCount)ST.fieldCount=sv.fieldCount;
if(sv.fields){ST.fields=sv.fields;while(ST.fields.length<ST.fieldCount)ST.fields.push(null);ST.fields=ST.fields.slice(0,ST.fieldCount);}
if(sv.storyItems)ST.storyItems=sv.storyItems;
if(sv.quizData)ST.quizData=sv.quizData;
if(sv.rules)Object.assign(ST.rules,sv.rules);
if(sv.highestStep)ST.highestStep=sv.highestStep;
}catch(e){}
let cur=0;
function save(){
localStorage.setItem('gameConfig',JSON.stringify({
devName:ST.devName,name:ST.name,desc:ST.desc,figure:ST.figure,background:ST.background,
fieldCount:ST.fieldCount,fields:ST.fields,storyItems:ST.storyItems,
quizData:ST.quizData,rules:ST.rules,highestStep:ST.highestStep,
}));
// Note: code update is triggered by input/change listeners, not here
}
function resetAll(){
if(!confirm('Wirklich alles zurücksetzen?\nAlle Eingaben gehen verloren!')) return;
localStorage.removeItem('gameConfig');
localStorage.removeItem('testFeedback');
location.reload();
}
/* ══════════════════════════════════════════════
NAVIGATION
══════════════════════════════════════════════ */
function goTo(step){
if(step>cur&&!canLeave(cur))return;
save();
const from=document.getElementById('s'+cur);
from.classList.remove('active');
from.style.display='none';
setTimeout(()=>{from.style.display='';},50);
if(step>cur)ST.highestStep=Math.max(ST.highestStep,step);
cur=step;
const to=document.getElementById('s'+cur);
to.classList.add('active','slide-in');
to.addEventListener('animationend',()=>to.classList.remove('slide-in'),{once:true});
const ep=document.getElementById('editorPane');if(ep)ep.scrollTo({top:0,behavior:'smooth'});else window.scrollTo({top:0,behavior:'smooth'});
updatePB();
showSummary(step);
if(step===1)s2init();
if(step===2)s3init();
if(step===3)buildQuiz();
if(step===4)buildReview();
showToast('✅ Schritt '+(step+1)+' geöffnet!');
}
function pbClick(step){if(step<cur&&step<=ST.highestStep){goTo(step);}}
function canLeave(step){
if(step===0)return ST.name.trim().length>0;
if(step===1)return !!(ST.figure&&ST.background);
if(step===2)return ST.fields.slice(1,ST.fieldCount-1).filter(Boolean).length>0;
return true;
}
function updatePB(){
for(let i=0;i<5;i++){
const d=document.getElementById('pb'+i);
d.className='pb-step'+(i===cur?' active':i<cur?' done':'');
const c=document.getElementById('pbc'+i);
if(c)c.className='pb-connector'+(i<cur?' done':'');
}
document.getElementById('pbFill').style.width=((cur+1)/5*100)+'%';
}
function showSummary(step){
const idx=step-1;
if(idx<0)return;
const banner=document.getElementById('sum'+idx);
if(!banner)return;
banner.classList.add('visible');
const chips=document.getElementById('sum'+idx+'chips');
if(!chips)return;
let html='';
if(idx===0){if(ST.devName)html+=`<div class="sb-chip">👋 ${ST.devName}</div>`;html+=`<div class="sb-chip">🎮 ${ST.name}</div>`;if(ST.desc)html+=`<div class="sb-chip">"${ST.desc.slice(0,40)}${ST.desc.length>40?'…':''}"</div>`;}
if(idx===1){const f=FIGURES.find(x=>x.id===ST.figure);const b=BACKGROUNDS.find(x=>x.id===ST.background);if(f)html+=`<div class="sb-chip">${f.e} ${f.n}</div>`;if(b)html+=`<div class="sb-chip">🌍 ${b.n}</div>`;}
if(idx===2){const filled=ST.fields.slice(1,ST.fieldCount-1).filter(Boolean).length;html+=`<div class="sb-chip">⬛ ${ST.fieldCount} Felder</div><div class="sb-chip">🕹️ ${filled} Mini-Games</div><div class="sb-chip">📖 ${ST.storyItems.length} Erzähltexte</div>`;}
if(idx===3){const done=ST.quizData.filter(isQComplete).length;html+=`<div class="sb-chip">❓ ${done}/${ST.quizData.length||0} Quiz-Fragen</div>`;}
chips.innerHTML=html;
}
function showToast(msg){
const t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
setTimeout(()=>t.classList.remove('show'),2000);
}
/* ══════════════════════════════════════════════
STEP 1 — GRUNDINFOS
══════════════════════════════════════════════ */
function s1init(){
document.getElementById('s1devname').value=ST.devName;
document.getElementById('s1name').value=ST.name;
document.getElementById('s1desc').value=ST.desc;
s1update();
}
function s1update(){
ST.devName=document.getElementById('s1devname').value.trim();
ST.name=document.getElementById('s1name').value.trim();
ST.desc=document.getElementById('s1desc').value;
const nl=document.getElementById('s1name').value.length;
const dl=ST.desc.length;
document.getElementById('s1nc').textContent=nl+' / 40';
document.getElementById('s1dc').textContent=dl+' / 120';
document.getElementById('s1dc').className='char-row'+(dl>100?' warn':'');
document.getElementById('s1next').disabled=!ST.name||!ST.devName;
// Persönliche Begrüßung
const grEl=document.getElementById('devGreeting');
if(ST.devName){
const greetings=[
'Hey '+ST.devName+' 👋 Schön, dass du dabei bist!',
'Los geht\'s, '+ST.devName+'! Dein Spiel wartet 🎮',
'Hi '+ST.devName+'! Du wirst ein tolles Spiel bauen ✨',
'Willkommen, '+ST.devName+'! Game on! 🚀',
];
grEl.textContent=greetings[ST.devName.length%greetings.length];
grEl.style.opacity='1';
} else {grEl.style.opacity='0';}
// Vorschau
const show=ST.name.length>0&&ST.devName.length>0;
document.getElementById('s1preview').style.display=show?'':'none';
if(show){
document.getElementById('s1pvname').textContent=ST.name;
document.getElementById('s1pvdesc').textContent=ST.desc||'Keine Beschreibung.';
document.getElementById('s1pvdev').textContent='👨‍💻 Entwickelt von '+ST.devName;
}
}
function suggest(txt){document.getElementById('s1name').value=txt;s1update();}
/* ══════════════════════════════════════════════
STEP 2 — GESTALTUNG
══════════════════════════════════════════════ */
function s2init(){
const fg=document.getElementById('figGrid');
fg.innerHTML='';
FIGURES.forEach(f=>{
const c=document.createElement('div');c.className='sel-card'+(ST.figure===f.id?' selected':'');
c.innerHTML=`<span class="se">${f.e}</span><div class="sl">${f.n}</div>`;
c.onclick=()=>{ST.figure=f.id;setCodeFocus('figure');s2update();fg.querySelectorAll('.sel-card').forEach(x=>x.classList.remove('selected'));c.classList.add('selected');};
fg.appendChild(c);
});
const bg=document.getElementById('bgGrid');
bg.innerHTML='';
BACKGROUNDS.forEach(b=>{
const c=document.createElement('div');c.className='sel-card'+(ST.background===b.id?' selected':'');
c.innerHTML=`<span class="se">${b.e}</span><div class="sl">${b.n}</div>`;
c.onclick=()=>{ST.background=b.id;setCodeFocus('bg');s2update();bg.querySelectorAll('.sel-card').forEach(x=>x.classList.remove('selected'));c.classList.add('selected');};
bg.appendChild(c);
});
s2update();
}
function s2update(){
const ok=!!(ST.figure&&ST.background);
document.getElementById('s2next').disabled=!ok;
const combo=document.getElementById('s2combo');
if(ok){
const f=FIGURES.find(x=>x.id===ST.figure);const b=BACKGROUNDS.find(x=>x.id===ST.background);
combo.style.display='';
document.getElementById('s2scene').textContent=b.scene+' '+f.e;
document.getElementById('s2clabel').textContent=f.n+' im '+b.n;
}else combo.style.display='none';
}
/* ══════════════════════════════════════════════
STEP 3 — SPIELFELD
══════════════════════════════════════════════ */
let selGame=null,dragging=null,epFor=null;
const STORY_MAX=5,MULTI_MAX=3;
function s3init(){
unlockSection('movement'); unlockSection('failmode'); unlockSection('fieldcount'); unlockSection('fields');
document.getElementById('cntVal').textContent=ST.fieldCount;
document.getElementById('cntMinus').disabled=ST.fieldCount<=6;
document.getElementById('cntPlus').disabled=ST.fieldCount>=20;
applyRuleUI();
buildPalette();
buildBoard();
}
function selRule(el){
const g=el.dataset.g;
document.querySelectorAll(`[data-g="${g}"]`).forEach(x=>x.classList.remove('selected'));
el.classList.add('selected');ST.rules[g]=el.dataset.v;
// Map data-g to focus keys
const fkMap={'movement':'movement','fail':'failmode'};
setCodeFocus(fkMap[g]||g);
if(g==='fail'){
document.getElementById('sub_lives').classList.toggle('visible',ST.rules.fail==='lives');
document.getElementById('sub_points').classList.toggle('visible',ST.rules.fail==='points');
}
}
function selSub(el){
const g=el.dataset.g;
document.querySelectorAll(`[data-g="${g}"]`).forEach(x=>x.classList.remove('selected'));
el.classList.add('selected');ST.rules[g]=el.dataset.v;
// lives/pts sub-options
setCodeFocus(g==='lives'?'lives':g==='pts'?'pts':'failmode');
}
function applyRuleUI(){
['movement','fail'].forEach(g=>document.querySelectorAll(`[data-g="${g}"]`).forEach(x=>x.classList.toggle('selected',x.dataset.v===ST.rules[g])));
document.getElementById('sub_lives').classList.toggle('visible',ST.rules.fail==='lives');
document.getElementById('sub_points').classList.toggle('visible',ST.rules.fail==='points');
['lives','pts'].forEach(g=>document.querySelectorAll(`[data-g="${g}"]`).forEach(x=>x.classList.toggle('selected',x.dataset.v===ST.rules[g])));
}
function chgCount(d){
const n=ST.fieldCount+d;if(n<6||n>20)return;
ST.fieldCount=n;setCodeFocus('fieldcount');document.getElementById('cntVal').textContent=n;
document.getElementById('cntMinus').disabled=n<=6;document.getElementById('cntPlus').disabled=n>=20;
while(ST.fields.length<n)ST.fields.push(null);
ST.fields=ST.fields.slice(0,n);
ST.storyItems=ST.storyItems.filter(s=>s.fieldIndex<n);
buildBoard();
}
function palTab(t){
document.querySelectorAll('.pal-tab').forEach((x,i)=>x.classList.toggle('active',(i===0&&t==='games')||(i===1&&t==='story')));
document.getElementById('panGames').classList.toggle('active',t==='games');
document.getElementById('panStory').classList.toggle('active',t==='story');
}
function getUC(id){return ST.fields.filter(f=>f===id).length;}
function mgDis(mg){const c=getUC(mg.id);return mg.multi===1?c>=1:mg.multi===3?c>=MULTI_MAX:false;}
function sCnt(){return ST.storyItems.length;}
function sMaxed(){return ST.storyItems.length>=STORY_MAX;}
function buildPalette(){
const pan=document.getElementById('panGames');pan.innerHTML='';
MINIGAMES.forEach(mg=>{
const tagCls=mg.multi==='∞'?'unlim':mg.multi===3?'multi':'once';
const tagTxt=mg.multi==='∞'?'∞':mg.multi===3?'max.3×':'1×';
const c=document.createElement('div');c.className='mg-card';c.id='mgc_'+mg.id;c.draggable=true;
c.innerHTML=`<div class="use-count" id="uc_${mg.id}">0</div><div class="mg-emoji">${mg.e}</div><div class="mg-lbl">${mg.n}</div><div class="mg-tag ${tagCls}">${tagTxt}</div><button class="mg-test-btn" onclick="openMGTest('${mg.id}',event)">▶ Test</button>`;
c.addEventListener('click',()=>{if(c.classList.contains('disabled'))return;if(selGame===mg.id){selGame=null;c.classList.remove('click-sel');}else{clearSel();selGame=mg.id;c.classList.add('click-sel');}});
c.addEventListener('dragstart',e=>{
if(c.classList.contains('disabled')){e.preventDefault();return;}
dragging='game'; dragId=mg.id; clearSel();
e.dataTransfer.effectAllowed='copy';
e.dataTransfer.setData('text/plain','game:'+mg.id);
// Use the card itself as drag image — offset so cursor is in center
e.dataTransfer.setDragImage(c, c.offsetWidth/2, c.offsetHeight/2);
ghost(mg.e+' '+mg.n);
});
c.addEventListener('dragend',()=>{dragging=null;dragId=null;unghost();cleanDropTargets();});
pan.appendChild(c);
});
const sc=document.getElementById('storyCard');
sc.ondragstart=e=>{
if(sMaxed()){e.preventDefault();return;}
dragging='story'; dragId='story'; clearSel(); selGame='story';
e.dataTransfer.effectAllowed='copy';
e.dataTransfer.setData('text/plain','story');
e.dataTransfer.setDragImage(sc, sc.offsetWidth/2, sc.offsetHeight/2);
ghost('📖 Erzähl-Text');
};
sc.ondragend=()=>{dragging=null;dragId=null;unghost();cleanDropTargets();};
updPal();
}
function clearSel(){selGame=null;document.querySelectorAll('.mg-card').forEach(c=>c.classList.remove('click-sel'));}
function cleanDropTargets(){
document.querySelectorAll('.drop-zone').forEach(z=>{z.classList.remove('story-hoverable','drag-over');});
document.querySelectorAll('.field-row').forEach(r=>r.classList.remove('drag-target'));
}
function updPal(){
MINIGAMES.forEach(mg=>{
const c=document.getElementById('mgc_'+mg.id);const uc=document.getElementById('uc_'+mg.id);if(!c)return;
const cnt=getUC(mg.id);uc.textContent=cnt;
c.classList.toggle('used',cnt>0);c.classList.toggle('disabled',mgDis(mg));
if(mgDis(mg)&&selGame===mg.id)clearSel();
});
const sc=document.getElementById('storyCard');if(sc){sc.classList.toggle('disabled',sMaxed());const suc=document.getElementById('storyUC');if(suc)suc.textContent=sCnt();sc.classList.toggle('used',sCnt()>0);}
const badge=document.getElementById('storyCtr');badge.textContent='📖 '+sCnt()+' / '+STORY_MAX;badge.classList.toggle('maxed',sMaxed());
const filled=ST.fields.slice(1,ST.fieldCount-1).filter(Boolean).length;
document.getElementById('s3next').disabled=filled===0;
}
let dragId=null;
function ghost(txt){const g=document.getElementById('dragGhost');g.textContent=txt;g.style.opacity='1';}
function unghost(){document.getElementById('dragGhost').style.opacity='0';}
// Track mouse position for ghost element
document.addEventListener('dragover',e=>{
e.preventDefault();
const g=document.getElementById('dragGhost');
if(dragging){g.style.left=e.clientX+'px';g.style.top=e.clientY+'px';}
if(dragging==='story'){
document.querySelectorAll('.drop-zone').forEach(z=>z.classList.add('story-hoverable'));
}
});
document.addEventListener('dragend',()=>cleanDropTargets());
function buildBoard(){
const bb=document.getElementById('boardBuilder');bb.innerHTML='';
for(let i=0;i<ST.fieldCount;i++){
if(i>0)bb.appendChild(mkDrop('before',i));
ST.storyItems.filter(s=>s.position==='before'&&s.fieldIndex===i).forEach(s=>bb.appendChild(mkStory(s)));
const g=ST.fields[i];const mg=g?MINIGAMES.find(m=>m.id===g):null;
const isSt=i===0,isEn=i===ST.fieldCount-1;
const row=document.createElement('div');
row.className='field-row'+(mg?' filled':'')+(isSt?' start-f':'')+(isEn?' end-f':'');
const numLbl=isSt?'🟢 Start':isEn?'🏁 Ziel':`Feld ${i}`;
const icon=mg?mg.e:isSt?'🟢':isEn?'🏁':'⬜';
const name=mg?mg.n:isSt?'Startfeld':isEn?'Zielfeld':'Leer — klicken oder hineinziehen';
row.innerHTML=`<div class="f-num">${numLbl}</div><div class="f-icon">${icon}</div><div class="f-name ${mg||isSt||isEn?'':'f-empty'}">${name}</div><button class="f-clear" onclick="clearF(${i},event)">✕</button>`;
if(!isSt&&!isEn){
row.addEventListener('click',()=>handleFClick(i));
row.addEventListener('dragenter',e=>{if(dragging==='game'){e.preventDefault();row.classList.add('drag-target');}});
row.addEventListener('dragover',e=>{if(dragging==='game'){e.preventDefault();e.dataTransfer.dropEffect='copy';row.classList.add('drag-target');}});
row.addEventListener('dragleave',e=>{if(!row.contains(e.relatedTarget))row.classList.remove('drag-target');});
row.addEventListener('drop',e=>{
e.preventDefault();row.classList.remove('drag-target');
const d=e.dataTransfer.getData('text/plain');
if(d.startsWith('game:'))dropGame(i,d.replace('game:',''));
});
}
bb.appendChild(row);
ST.storyItems.filter(s=>s.position==='after'&&s.fieldIndex===i).forEach(s=>bb.appendChild(mkStory(s)));
if(i<ST.fieldCount-1)bb.appendChild(mkDrop('after',i));
}
updPal();
const filled=ST.fields.slice(1,ST.fieldCount-1).filter(Boolean).length;
document.getElementById('boardStats').innerHTML=`<div class="stat-pill">🎯 Belegt: <span>${filled}/${ST.fieldCount-2}</span></div><div class="stat-pill">🕹️ Games: <span>${new Set(ST.fields.filter(Boolean)).size}</span></div><div class="stat-pill">📖 Texte: <span>${sCnt()}</span></div>`;
}
function mkDrop(pos,fi){
const z=document.createElement('div');z.className='drop-zone';
z.textContent='+ Erzähl-Text hier ablegen';
z.addEventListener('dragenter',e=>{if(dragging==='story'){e.preventDefault();z.classList.add('drag-over');}});
z.addEventListener('dragover',e=>{if(dragging==='story'){e.preventDefault();e.dataTransfer.dropEffect='copy';z.classList.add('drag-over');}});
z.addEventListener('dragleave',e=>{if(!z.contains(e.relatedTarget))z.classList.remove('drag-over');});
z.addEventListener('drop',e=>{
e.preventDefault();z.classList.remove('drag-over');
if(e.dataTransfer.getData('text/plain')==='story')dropStory(pos,fi);
});
z.addEventListener('click',()=>{if(selGame==='story')dropStory(pos,fi);});
return z;
}
function mkStory(s){
const row=document.createElement('div');row.className='story-row';row.dataset.sid=s.id;
const posLbl=s.position==='before'?`Vor Feld ${s.fieldIndex}`:`Nach Feld ${s.fieldIndex}`;
row.innerHTML=`<div class="story-icon" onclick="openEP('${s.id}',this)">${s.emoji||'📖'}</div><div class="story-content"><div class="story-lbl">📖 Erzähl-Text</div><textarea class="story-ta" rows="2" maxlength="300" placeholder="Schreib deine Geschichte..." oninput="updStory('${s.id}',this.value)">${s.text||''}</textarea><div class="story-meta"><div class="story-pos-lbl">${posLbl}</div><div class="story-chars">${(s.text||'').length}/300</div></div></div><button class="story-clear" onclick="rmStory('${s.id}')">✕</button>`;
return row;
}
function clickStory(){if(sMaxed())return;if(selGame==='story'){selGame=null;document.getElementById('storyCard').classList.remove('click-sel');}else{clearSel();selGame='story';document.getElementById('storyCard').classList.add('click-sel');}}
function dropStory(pos,fi){
if(sMaxed()){showToast('⚠️ Maximal 5 Erzähltexte!');return;}
const id='st_'+Date.now();
ST.storyItems.push({id,emoji:'📖',text:'',position:pos,fieldIndex:fi});
clearSel();buildBoard();
setTimeout(()=>{const r=document.querySelector(`[data-sid="${id}"] textarea`);if(r)r.focus();},80);
}
function updStory(id,val){const s=ST.storyItems.find(x=>x.id===id);if(!s)return;s.text=val;const row=document.querySelector(`[data-sid="${id}"]`);if(row){const cc=row.querySelector('.story-chars');if(cc)cc.textContent=val.length+'/300';}}
function rmStory(id){ST.storyItems=ST.storyItems.filter(x=>x.id!==id);buildBoard();}
function openEP(sid,iconEl){
const picker=document.getElementById('emojiPicker');
if(epFor===sid){picker.classList.remove('open');epFor=null;return;}
epFor=sid;picker.innerHTML='';
STORY_EMOJIS.forEach(em=>{const btn=document.createElement('div');btn.className='ep-it';btn.textContent=em;btn.addEventListener('click',()=>{const s=ST.storyItems.find(x=>x.id===sid);if(s){s.emoji=em;buildBoard();}picker.classList.remove('open');epFor=null;});picker.appendChild(btn);});
const r=iconEl.getBoundingClientRect();
picker.style.left=Math.min(r.left,window.innerWidth-220)+'px';
picker.style.top=(r.bottom+6)+'px';
picker.classList.add('open');
}
document.addEventListener('click',e=>{if(epFor&&!e.target.classList.contains('story-icon')&&!document.getElementById('emojiPicker').contains(e.target)){document.getElementById('emojiPicker').classList.remove('open');epFor=null;}});
function handleFClick(idx){if(!selGame||selGame==='story')return;const mg=MINIGAMES.find(m=>m.id===selGame);if(!mg||mgDis(mg))return;ST.fields[idx]=selGame;setCodeFocus('fields');buildBoard();}
function dropGame(idx,id){
const mg=MINIGAMES.find(m=>m.id===id);if(!mg)return;
const tmp=[...ST.fields];tmp[idx]=null;
const cnt=tmp.filter(f=>f===id).length;
if((mg.multi===1&&cnt>=1)||(mg.multi===3&&cnt>=MULTI_MAX)){showToast('⚠️ Limit erreicht für '+mg.n+'!');return;}
ST.fields[idx]=id;
setCodeFocus('fields');
buildBoard();
}
function clearF(idx,e){e&&e.stopPropagation();ST.fields[idx]=null;buildBoard();}
/* ══════════════════════════════════════════════
STEP 4 — QUIZ
══════════════════════════════════════════════ */
function isQComplete(q){return q.question.trim()&&q.answers.every(a=>a.trim())&&q.correct!==null;}
function getQ(fi){return ST.quizData.find(q=>q.fieldIndex===fi);}
function buildQuiz(){
const qidx=ST.fields.map((f,i)=>f==='quiz'?i:-1).filter(i=>i>=0);
qidx.forEach(fi=>{if(!ST.quizData.find(q=>q.fieldIndex===fi))ST.quizData.push({fieldIndex:fi,question:'',answers:['','','',''],correct:null});});
ST.quizData=ST.quizData.filter(q=>qidx.includes(q.fieldIndex));
const area=document.getElementById('quizArea');area.innerHTML='';
if(qidx.length===0){
area.innerHTML=`<div class="card"><div class="no-quiz"><div class="nq-icon">❓</div><h3>Keine Quiz-Felder platziert</h3><p style="color:var(--muted);font-size:13px">In Schritt 3 hast du keine Quiz-Felder hinzugefügt.<br>Du kannst trotzdem weitermachen!</p></div></div>`;
document.getElementById('s4next').disabled=false;return;
}
const done=ST.quizData.filter(isQComplete).length;
const ov=document.createElement('div');ov.className='ov-bar';
ov.innerHTML=`<div class="ov-pill ${done===qidx.length?'good':'warn'}">✅ Fertig: <span>${done}/${qidx.length}</span></div><div class="ov-pill">❓ Quiz-Felder: <span>${qidx.length}</span></div>`;
area.appendChild(ov);
const af=document.createElement('div');af.className='af-bar';
af.innerHTML=`<div class="af-icon">✨</div><div class="af-text"><div class="af-title">Beispielfragen</div><div class="af-sub">Alle leeren Felder automatisch ausfüllen</div></div><button class="btn-sm" onclick="autoFill()">Ausfüllen</button>`;
area.appendChild(af);
ST.quizData.forEach((q,i)=>area.appendChild(mkQCard(q,i)));
updQNext();
}
function updQNext(){
const qidx=ST.fields.map((f,i)=>f==='quiz'?i:-1).filter(i=>i>=0);
document.getElementById('s4next').disabled=qidx.length>0&&ST.quizData.filter(isQComplete).length<qidx.length;
const done=ST.quizData.filter(isQComplete).length;
const p=document.querySelector('.ov-pill');if(p){p.className='ov-pill '+(done===qidx.length?'good':'warn');p.innerHTML=`✅ Fertig: <span>${done}/${qidx.length}</span>`;}
}
function mkQCard(q,idx){
const comp=isQComplete(q);
const c=document.createElement('div');c.className='card quiz-card'+(comp?' complete':'');c.id='qc_'+q.fieldIndex;
const fl=q.fieldIndex===0?'Start':q.fieldIndex===ST.fieldCount-1?'Ziel':'Feld '+q.fieldIndex;
c.innerHTML=`
<div class="qc-head">
<div class="qc-num" id="qnum_${q.fieldIndex}">${idx+1}</div>
<div class="qc-title">Quiz-Frage ${idx+1}</div>
<div class="qc-status ${comp?'done':'todo'}" id="qst_${q.fieldIndex}">${comp?'✓ Fertig':'⏳ Ausfüllen'}</div>
</div>
<label class="lbl">Frage (📍 ${fl})</label>
<textarea class="q-input" rows="2" maxlength="200" placeholder="Schreib deine Frage hier..." oninput="updQ(${q.fieldIndex},'q',this.value)">${q.question}</textarea>
<label class="lbl">Die vier Antworten</label>
<div class="ans-grid" id="ag_${q.fieldIndex}">
${LETTERS.map((L,i)=>`<div class="ans-wrap ${q.correct===i?'correct':''}" id="aw_${q.fieldIndex}_${i}"><div class="ans-letter">${L}</div><input type="text" class="ans-input" maxlength="80" placeholder="Antwort ${L}..." value="${q.answers[i]||''}" oninput="updQ(${q.fieldIndex},'a${i}',this.value)"/></div>`).join('')}
</div>
<div class="correct-row">
<span class="correct-lbl">✅ Richtige Antwort:</span>
<div class="correct-btns">${LETTERS.map((L,i)=>`<button class="correct-btn ${q.correct===i?'selected':''}" onclick="setCorr(${q.fieldIndex},${i})">${L}</button>`).join('')}</div>
</div>
<button class="tips-btn" onclick="this.nextElementSibling.classList.toggle('open')">💡 Tipps</button>
<div class="tips-box">• Stelle eine klare, eindeutige Frage<br>• Alle 4 Antworten sollten plausibel klingen<br>• Nur eine Antwort ist korrekt<br>• Beziehe die Frage auf dein Spielthema!</div>
`;
return c;
}
function updQ(fi,field,val){
const q=getQ(fi);if(!q)return;
if(field==='q')q.question=val;
else q.answers[parseInt(field.slice(1))]=val;
refQCard(fi);updQNext();
}
function setCorr(fi,idx){
const q=getQ(fi);if(!q)return;q.correct=idx;
LETTERS.forEach((_,i)=>{const w=document.getElementById(`aw_${fi}_${i}`);if(w)w.className='ans-wrap '+(i===idx?'correct':'');});
const card=document.getElementById('qc_'+fi);if(card)card.querySelectorAll('.correct-btn').forEach((b,i)=>b.classList.toggle('selected',i===idx));
refQCard(fi);updQNext();
}
function refQCard(fi){
const q=getQ(fi);if(!q)return;
const comp=isQComplete(q);
const c=document.getElementById('qc_'+fi);if(!c)return;
c.classList.toggle('complete',comp);
const st=document.getElementById('qst_'+fi);if(st){st.className='qc-status '+(comp?'done':'todo');st.textContent=comp?'✓ Fertig':'⏳ Ausfüllen';}
const num=document.getElementById('qnum_'+fi);if(num)num.style.background=comp?'linear-gradient(135deg,#10b981,#059669)':'linear-gradient(135deg,#7c3aed,#f5a623)';
}
function autoFill(){
ST.quizData.forEach((q,i)=>{if(!isQComplete(q)){const ex=QUIZ_EX[i%QUIZ_EX.length];q.question=ex.q;q.answers=[...ex.a];q.correct=ex.c;}});
buildQuiz();showToast('✅ Beispielfragen eingefügt!');
}
/* ══════════════════════════════════════════════
STEP 5 — REVIEW + TEST
══════════════════════════════════════════════ */
function buildReview(){
const fig=FIGURES.find(f=>f.id===ST.figure)||{e:'🎮',n:'Figur'};
const bg=BACKGROUNDS.find(b=>b.id===ST.background)||{scene:'🌍',n:'Welt'};
const area=document.getElementById('reviewArea');area.innerHTML='';
// Hero card
const hero=document.createElement('div');hero.className='hero-card';
hero.innerHTML=`<div class="hero-scene">${bg.scene}<span class="hero-fig">${fig.e}</span></div><div class="hero-info"><div class="hi-name">${ST.name||'Mein Spiel'}</div><div class="hi-desc">${ST.desc||'Keine Beschreibung.'}</div><div class="hero-tags"><div class="hero-tag">${fig.e} ${fig.n}</div><div class="hero-tag">🌍 ${bg.n}</div><div class="hero-tag">⬛ ${ST.fieldCount} Felder</div></div></div>`;
area.appendChild(hero);
// Rules
const rules=document.createElement('div');rules.className='card';
const filled=ST.fields.slice(1,ST.fieldCount-1).filter(Boolean).length;
rules.innerHTML=`<div class="card-title" style="margin-bottom:12px">⚙️ Spielregeln</div><div class="info-grid"><div class="info-block"><div class="ib-lbl">🎲 Bewegung</div><div class="ib-val">${ST.rules.movement==='dice'?'🎲 Würfeln':'👣 Ein Feld'}</div></div><div class="info-block"><div class="ib-lbl">${ST.rules.fail==='lives'?'❤️ Leben':'⭐ Punkte'}</div><div class="ib-val">${ST.rules.fail==='lives'?ST.rules.lives+' Leben':'+'+ST.rules.pts+' Pts'}</div></div><div class="info-block"><div class="ib-lbl">🕹️ Mini-Games</div><div class="ib-val">${filled} Felder</div></div><div class="info-block"><div class="ib-lbl">📖 Erzähltexte</div><div class="ib-val">${ST.storyItems.length}</div></div></div>`;
area.appendChild(rules);
// Timeline
const tlCard=document.createElement('div');tlCard.className='card';
tlCard.innerHTML='<div class="card-title" style="margin-bottom:12px">🗺️ Spielablauf</div>';
const tl=document.createElement('div');tl.className='tl';
for(let i=0;i<ST.fieldCount;i++){
const g=ST.fields[i];const mg=g?MINIGAMES.find(m=>m.id===g):null;
const isSt=i===0,isEn=i===ST.fieldCount-1;
ST.storyItems.filter(s=>s.position==='before'&&s.fieldIndex===i).forEach(s=>{
const it=document.createElement('div');it.className='tl-item story-it';
it.innerHTML=`<div class="tl-dot st">${s.emoji||'📖'}</div><div><div class="tl-lbl st">Erzähl-Text</div><div class="tl-sub">${s.text||'(noch leer)'}</div><div class="tl-badge st">Vor Feld ${i}</div></div>`;
tl.appendChild(it);
});
const it=document.createElement('div');it.className='tl-item';
let dCls,lCls,lbl,sub,badge='';
if(isSt){dCls='s';lCls='s';lbl='🟢 Start';sub='Das Abenteuer beginnt!';}
else if(isEn){dCls='e';lCls='e';lbl='🏁 Ziel';sub='Hier gewinnt man!';}
else if(mg){dCls='g';lCls='g';lbl='Feld '+i;sub=mg.n;badge=`<div class="tl-badge">${mg.e} ${mg.n}</div>`;}
else{dCls='em';lCls='em';lbl='Feld '+i;sub='Leeres Durchgangsfeld';}
it.innerHTML=`<div class="tl-dot ${dCls}">${isSt?'🟢':isEn?'🏁':mg?mg.e:'⬜'}</div><div><div class="tl-lbl ${lCls}">${lbl}</div><div class="tl-sub">${sub}</div>${badge}</div>`;
tl.appendChild(it);
ST.storyItems.filter(s=>s.position==='after'&&s.fieldIndex===i).forEach(s=>{
const it2=document.createElement('div');it2.className='tl-item story-it';
it2.innerHTML=`<div class="tl-dot st">${s.emoji||'📖'}</div><div><div class="tl-lbl st">Erzähl-Text</div><div class="tl-sub">${s.text||'(noch leer)'}</div><div class="tl-badge st">Nach Feld ${i}</div></div>`;
tl.appendChild(it2);
});
}
tlCard.appendChild(tl);area.appendChild(tlCard);
// Quiz preview
if(ST.quizData.length>0){
const qc=document.createElement('div');qc.className='card';
qc.innerHTML='<div class="card-title" style="margin-bottom:10px">❓ Quiz-Fragen</div>';
const ql=document.createElement('div');ql.className='qp-list';
ST.quizData.forEach((q,i)=>{
const qi=document.createElement('div');qi.className='qp-item';
qi.innerHTML=`<div class="qp-q">Frage ${i+1}: ${q.question||'(leer)'}</div><div class="qp-ans-grid">${LETTERS.map((L,j)=>`<div class="qp-ans ${q.correct===j?'cor':''}">${L} ${q.answers[j]||'?'}${q.correct===j?' ✓':''}</div>`).join('')}</div>`;
ql.appendChild(qi);
});
qc.appendChild(ql);area.appendChild(qc);
}
// Checklist
const cc=document.createElement('div');cc.className='card';cc.innerHTML='<div class="card-title" style="margin-bottom:10px">🔍 Checkliste</div>';
const cl=document.createElement('div');cl.className='checklist';
const qDone=ST.quizData.filter(isQComplete).length;
[
{ok:!!ST.name,t:ST.name?`Spielname: "${ST.name}"`:'Kein Spielname vergeben'},
{ok:!!(ST.figure&&ST.background),t:ST.figure?`Figur & Setting: ${fig.n} / ${bg.n}`:'Figur oder Setting fehlt'},
{ok:filled>0,t:`${filled} von ${ST.fieldCount-2} Spielfeldern belegt`},
{ok:ST.quizData.length===0||qDone===ST.quizData.length,t:ST.quizData.length===0?'Keine Quiz-Felder (ok)':`${qDone}/${ST.quizData.length} Quiz-Fragen ausgefüllt`},
].forEach(ch=>{const it=document.createElement('div');it.className='chk-item '+(ch.ok?'ok':'warn');it.innerHTML=`<span>${ch.ok?'✅':'⚠️'}</span>${ch.t}`;cl.appendChild(it);});
cc.appendChild(cl);area.appendChild(cc);
// Fill launch card
document.getElementById('lhFig').textContent=fig.e;
document.getElementById('lhName').textContent=ST.name||'Mein Spiel';
document.getElementById('lhDesc').textContent=ST.desc||'';
document.getElementById('lhPills').innerHTML=[
`<div class="lh-pill">${fig.e} ${fig.n}</div>`,
`<div class="lh-pill">🌍 ${bg.n}</div>`,
`<div class="lh-pill">⬛ ${ST.fieldCount} Felder</div>`,
`<div class="lh-pill">${ST.rules.fail==='lives'?'❤️ '+ST.rules.lives+' Leben':'⭐ Punkte'}</div>`,
].join('');
const uniqueG=[...new Set(ST.fields.filter(Boolean))];
document.getElementById('launchChk').innerHTML=[
{i:'🎲',t:`${ST.fieldCount} Felder · ${filled} Mini-Games`},
{i:'🕹️',t:`Games: ${uniqueG.map(g=>{const m=MINIGAMES.find(x=>x.id===g);return m?m.e:'❓';}).join(' ')||'—'}`},
{i:'📖',t:`${ST.storyItems.length} Erzähltexte`},
{i:'🎵',t:`Musik-Theme: ${bg.n}`},
].map(x=>`<div class="lc-item"><span>${x.i}</span><span style="flex:1">${x.t}</span><span class="lc-check">✓</span></div>`).join('');
}
let gamePlayed=false;
function doLaunch(){
save();
const w=window.open('game.html','Spiel',`width=${Math.min(1100,screen.width-60)},height=${Math.min(800,screen.height-60)},left=40,top=30,resizable=yes,scrollbars=no`);
if(!w){showToast('⚠️ Popup blockiert! Bitte Popups erlauben.');return;}
document.getElementById('btnLaunch').textContent='⏳ Warte auf Spielende...';
document.getElementById('btnLaunch').disabled=true;
const poll=setInterval(()=>{if(w.closed){clearInterval(poll);onGameDone();}},800);
}
function onGameDone(){
gamePlayed=true;
document.getElementById('btnLaunch').textContent='🔄 Nochmal spielen';
document.getElementById('btnLaunch').disabled=false;
document.getElementById('playedBadge').classList.add('visible');
const fc=document.getElementById('fbCard');fc.style.display='block';
fc.style.animation='slideIn 0.4s ease both';
setTimeout(()=>fc.scrollIntoView({behavior:'smooth',block:'start'}),500);
fbCur=0;fbAnswers={};renderFB();
}
/* ══════════════════════════════════════════════
FEEDBACK
══════════════════════════════════════════════ */
let fbCur=0,fbAnswers={};
function renderFB(){
const q=FB_QS[fbCur];
document.getElementById('fbFill').style.width=(fbCur/FB_QS.length*100)+'%';
document.getElementById('fbPrev').style.display=fbCur>0?'':'none';
document.getElementById('fbNext').textContent=fbCur<FB_QS.length-1?'Weiter →':'Auswertung ✅';
const area=document.getElementById('fbArea');
area.innerHTML=`<div class="fb-qnum">Frage ${fbCur+1} von ${FB_QS.length}</div><div class="fb-q">${q.q}</div>${q.sub?`<div class="fb-sub">${q.sub}</div>`:''}`;
if(q.t==='mc'){
const opts=document.createElement('div');opts.className='mc-opts';
q.opts.forEach((o,i)=>{const btn=document.createElement('div');btn.className='mc-opt'+(fbAnswers[q.id]===i?' sel':'');btn.innerHTML=`<div class="mc-dot"></div>${o}`;btn.addEventListener('click',()=>{fbAnswers[q.id]=i;opts.querySelectorAll('.mc-opt').forEach(b=>b.classList.remove('sel'));btn.classList.add('sel');document.getElementById('fbNext').disabled=false;});opts.appendChild(btn);});
area.appendChild(opts);
}
if(q.t==='scale'){
const row=document.createElement('div');row.className='scale-row';
for(let i=1;i<=5;i++){const btn=document.createElement('button');btn.className='scale-btn'+(fbAnswers[q.id]===i?' sel':'');btn.textContent=i;btn.addEventListener('click',()=>{fbAnswers[q.id]=i;row.querySelectorAll('.scale-btn').forEach(b=>b.classList.remove('sel'));btn.classList.add('sel');document.getElementById('fbNext').disabled=false;});row.appendChild(btn);}
area.appendChild(row);
const lbl=document.createElement('div');lbl.className='scale-lbls';lbl.innerHTML=`<span>${q.min}</span><span>${q.max}</span>`;area.appendChild(lbl);
}
if(q.t==='text'){
const ta=document.createElement('textarea');ta.className='fb-ta';ta.rows=3;ta.placeholder=q.ph||'';ta.value=fbAnswers[q.id]||'';ta.addEventListener('input',()=>{fbAnswers[q.id]=ta.value;document.getElementById('fbNext').disabled=ta.value.trim().length<3;});area.appendChild(ta);
}
document.getElementById('fbNext').disabled=fbAnswers[q.id]===undefined;
}
function fbFwd(){
fbCur++;
if(fbCur>=FB_QS.length){showSummaryCard();return;}
renderFB();
}
function fbBack(){if(fbCur>0){fbCur--;renderFB();}}
/* ══════════════════════════════════════════════
SUMMARY CARD
══════════════════════════════════════════════ */
function showSummaryCard(){
document.getElementById('fbCard').style.display='none';
const sc=document.getElementById('sumCard');sc.style.display='block';
sc.style.animation='slideIn 0.4s ease both';
setTimeout(()=>sc.scrollIntoView({behavior:'smooth',block:'start'}),400);
const fun=fbAnswers.fun||3;const mg=fbAnswers.minigames||3;
const diff=fbAnswers.difficulty;const replay=fbAnswers.replay;
document.getElementById('sumText').textContent='Du hast dein Spiel getestet und Feedback gegeben. Hier ist deine Auswertung:';
document.getElementById('sumScores').innerHTML=`<div class="sum-sc"><div class="sum-val">${fun}/5</div><div class="sum-lbl">Spaßfaktor</div></div><div class="sum-sc"><div class="sum-val">${mg}/5</div><div class="sum-lbl">Mini-Game Passung</div></div><div class="sum-sc"><div class="sum-val">${ST.fieldCount}</div><div class="sum-lbl">Felder</div></div><div class="sum-sc"><div class="sum-val">${ST.storyItems.length}</div><div class="sum-lbl">Erzähltexte</div></div>`;
const notes=[];
if(fun<=2)notes.push({i:'🎯',t:'Der Spaßfaktor ist noch niedrig — überlege, spannendere Mini-Games oder Erzähltexte hinzuzufügen.'});
else if(fun>=4)notes.push({i:'🔥',t:'Super Spaßfaktor! Dein Spiel macht offensichtlich Freude.'});
if(diff===0)notes.push({i:'⚠️',t:'Das Spiel war zu einfach — probiere schwierigere Mini-Games oder weniger Leben.'});
else if(diff===3)notes.push({i:'⚠️',t:'Das Spiel war zu schwer — erhöhe die Lebensanzahl oder wähle einfachere Mini-Games.'});
if(ST.storyItems.length===0)notes.push({i:'📖',t:'Keine Erzähltexte vorhanden — sie machen das Spiel viel lebendiger!'});
if(ST.fields.slice(1,ST.fieldCount-1).filter(Boolean).length<3)notes.push({i:'🕹️',t:'Nur wenige Felder sind belegt — füge mehr Mini-Games hinzu für mehr Abwechslung.'});
if(replay>=2)notes.push({i:'🚀',t:'Das Spiel hat Wiederspielwert — großartig!'});
if(fbAnswers.problems&&fbAnswers.problems.length>5)notes.push({i:'🔧',t:`Problem: "${fbAnswers.problems}" — geh zurück und überarbeite das.`});
if(notes.length===0)notes.push({i:'✅',t:'Alles sieht gut aus! Dein Spiel ist bereit zum Teilen.'});
document.getElementById('sumNotes').innerHTML=notes.map(n=>`<div class="sn-item"><span>${n.i}</span><span>${n.t}</span></div>`).join('');
save();localStorage.setItem('testFeedback',JSON.stringify(fbAnswers));
}
function doPublish(){
showToast('🚀 Link-Generator folgt in Schritt 6!');
setTimeout(()=>alert('🎉 Dein Spiel ist fertig!\n\nDer Link-Generator (Schritt 6) kommt im nächsten Build.\nDann kannst du game.html auf GitHub Pages hochladen und den Link teilen!'),800);
}
/* ══════════════════════════════════════════════
INIT
══════════════════════════════════════════════ */
s1init();
updatePB();
// Restore to last step
if(ST.highestStep>0){
for(let i=0;i<ST.highestStep&&i<4;i++){
document.getElementById('s'+i).classList.remove('active');
document.getElementById('s'+i).style.display='none';
}
cur=ST.highestStep;
document.getElementById('s'+cur).classList.add('active');
updatePB();
showSummary(cur);
if(cur===1)s2init();
if(cur===2)s3init();
if(cur===3)buildQuiz();
if(cur===4)buildReview();
}
/* ══════════════════════════════════════════════
MINI-GAME TEST POPUP
══════════════════════════════════════════════ */
let mgTestLoop=null;
const MG_DEMOS={
snake:{desc:'Steuere die Schlange mit den Pfeiltasten. Friss 3 Äpfel um zu gewinnen!',run:runSnake},
flappy:{desc:'Klicke oder drücke Leertaste um zu fliegen. Durch 3 Hindernisse = Sieg!',run:runFlappy},
memory:{desc:'Finde alle Paare! Klicke auf die Karten um sie umzudrehen.',run:runMemory},
reaction:{desc:'Wenn der Kreis GRÜN wird — so schnell wie möglich klicken!',run:runReaction},
quiz:{desc:'Quiz-Fragen kommen aus deinen eigenen Fragen (Schritt 4).',run:runQuizDemo},
puzzle:{desc:'Ordne die Teile in die richtige Reihenfolge!',run:runPuzzle},
spotdiff:{desc:'Finde den Unterschied zwischen den zwei Bildern!',run:runSpotdiff},
typing:{desc:'Tippe den angezeigten Text so schnell wie möglich!',run:runTyping},
snake:{desc:'Steuere die Schlange mit den Pfeiltasten. Friss 3 Äpfel um zu gewinnen!',run:runSnake},
flappy:{desc:'Klicke oder drücke Leertaste um zu fliegen. Durch 3 Hindernisse = Sieg!',run:runFlappy},
catch:{desc:'Bewege die Schüssel mit der Maus und fange 5 Äpfel!',run:runCatch},
basketball:{desc:'Klicke zum richtigen Zeitpunkt um den Ball zu werfen!',run:runBasketball},
maze:{desc:'Steuere mit den Pfeiltasten durch das Labyrinth!',run:runMaze},
simon:{desc:'Merke dir die Farbreihenfolge und tippe sie nach!',run:runSimon},
};
function openMGTest(id, e){
e&&e.stopPropagation();
const mg=MINIGAMES.find(m=>m.id===id);if(!mg)return;
closeMGTest();
document.getElementById('mgTestEmoji').textContent=mg.e;
document.getElementById('mgTestTitle').textContent=mg.n+' — Probespiel';
const demo=MG_DEMOS[id];
document.getElementById('mgTestDesc').textContent=demo?demo.desc:'';
document.getElementById('mgTestMsg').textContent='';
document.getElementById('mgTestMsg').className='';
const ctrl=document.getElementById('mgTestControls');
ctrl.innerHTML='<button class="mg-ctrl-btn" onclick="restartMGTest()">🔄 Neu starten</button><button class="mg-ctrl-btn secondary" onclick="closeMGTest()">Schließen</button>';
document.getElementById('mgTestOverlay').classList.add('open');
document.body.style.overflow='hidden';
if(demo)demo.run();
}
function closeMGTest(){
if(mgTestLoop){clearInterval(mgTestLoop);cancelAnimationFrame(mgTestLoop);mgTestLoop=null;}
document.removeEventListener('keydown',mgKeyHandler);
document.removeEventListener('mousemove',mgMouseHandler);
document.getElementById('mgTestOverlay').classList.remove('open');
document.body.style.overflow='';
const canvas=document.getElementById('mgTestCanvas');
const ctx=canvas.getContext('2d');ctx.clearRect(0,0,canvas.width,canvas.height);
currentMGId=null;
}
let currentMGId=null;
function restartMGTest(){
if(currentMGId)openMGTest(currentMGId,null);
}
let mgKeyHandler=null,mgMouseHandler=null;
function setMsg(txt,cls=''){const m=document.getElementById('mgTestMsg');m.textContent=txt;m.className=cls;}
/* ─── SNAKE ─── */
// ── Mini-Game Modul-Dispatcher ─────────────────────────────────────────────
// Die run*-Funktionen werden durch das Modul-System ersetzt.
// Jedes Modul (window.MG_snake etc.) stellt eine preview()-Funktion bereit.
function runMGModule(id) {
const mod = window[`MG_${id}`];
if (!mod) { runStub(id, '🎮', 'Modul wird geladen...', id); return; }
currentMGId = id;
const canvas = document.getElementById('mgTestCanvas');
const wrap = canvas.parentNode;
const W = canvas.offsetWidth || 400;
const H = canvas.offsetHeight || 280;
// Canvas leeren, wrap vorbereiten
wrap.innerHTML = '';
// onResult-Handler für Vorschau
window._mgOnResult = (won) => {
setMsg(won ? '🏆 Gewonnen!' : '💀 Verloren!', won ? 'win' : 'lose');
};
MGAPI.onResult = window._mgOnResult;
// Thema aus aktuellem ST-State ableiten
const bg = BACKGROUNDS.find(b => b.id === ST.background);
const cfg = {
theme: bg ? { primary: bg.color, glow: bg.glow } : { primary: '#7c3aed', glow: 'rgba(124,58,237,0.4)' },
quizData: ST.quizData || [],
devName: ST.devName,
gameName: ST.name,
};
if (mgTestActive && mgTestActive.stop) mgTestActive.stop();
mgTestActive = mod.preview(wrap, W, H, cfg);
}
// Alle run* als Aliase auf den Dispatcher
function runSnake() { runMGModule('snake'); }
function runFlappy() { runMGModule('flappy'); }
function runMemory() { runMGModule('memory'); }
function runReaction() { runMGModule('reaction'); }
function runQuizDemo() { runMGModule('quiz'); }
function runCatch() { runMGModule('catch'); }
function runBasketball() { runMGModule('basketball'); }
function runMaze() { runMGModule('maze'); }
function runSimon() { runMGModule('simon'); }
function runTyping() { runMGModule('typing'); }
function runPuzzle() { runMGModule('puzzle'); }
function runSpotdiff() { runMGModule('spotdiff'); }
function runStub(id, emoji, desc, name) {
currentMGId = id;
const canvas = document.getElementById('mgTestCanvas');
canvas.width = 400; canvas.height = 280;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#0f0e17'; ctx.fillRect(0, 0, 400, 280);
ctx.font = '60px serif'; ctx.textAlign = 'center'; ctx.fillText(emoji, 200, 130);
ctx.fillStyle = '#a7a3c2'; ctx.font = 'bold 16px Nunito,sans-serif';
ctx.fillText(desc, 200, 190);
ctx.fillStyle = '#7c3aed'; ctx.font = 'bold 13px Nunito,sans-serif';
ctx.fillText('(Modul wird geladen...)', 200, 220);
}
</script>
<!-- ════ MINI-GAME TEST POPUP ════ -->
<div id="mgTestOverlay" onclick="if(event.target===this)closeMGTest()">
<div id="mgTestBox">
<div id="mgTestHeader">
<span id="mgTestEmoji">🎮</span>
<div id="mgTestTitle">Mini-Game Test</div>
<button id="mgTestClose" onclick="closeMGTest()"></button>
</div>
<div id="mgTestBody">
<div id="mgTestDesc"></div>
<canvas id="mgTestCanvas" width="400" height="280"></canvas>
<div id="mgTestMsg"></div>
<div id="mgTestControls"></div>
</div>
</div>
</div>
</div><!-- /editorPane -->
<div id="resizerBar"></div>
<div id="codePane">
<div id="codeHeader">
<div id="codeHeaderLeft">
<span class="code-dot red"></span>
<span class="code-dot yellow"></span>
<span class="code-dot green"></span>
<span id="codeFilename">boardgame.py</span>
</div>
<div id="codeHeaderRight">
<span id="codeLang">🐍 Python</span>
<span id="codeLines">0 Zeilen</span>
</div>
</div>
<div id="codeScroll">
<div id="codeLineNums"></div>
<pre id="codeContent"></pre>
</div>
<div id="codeFooter">
<span id="codeStatus">● Bereit</span>
<span id="codeInfo">Live-Vorschau deines Spielcodes</span>
</div>
</div><!-- /codePane -->
</div><!-- /splitWrap -->
<script src="codegen.js"></script>
<!-- ═══ Mini-Game Module ═══ -->
<script src="minigames/_api.js"></script>
<script src="minigames/snake.js"></script>
<script src="minigames/flappy.js"></script>
<script src="minigames/memory.js"></script>
<script src="minigames/quiz.js"></script>
<script src="minigames/reaction.js"></script>
<script src="minigames/basketball.js"></script>
<script src="minigames/catch.js"></script>
<script src="minigames/maze.js"></script>
<script src="minigames/simon.js"></script>
<script src="minigames/typing.js"></script>
<script src="minigames/puzzle.js"></script>
<script src="minigames/spotdiff.js"></script>
</body>
</html></script>
</body>
</html>