- 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
1664 lines
106 KiB
HTML
1664 lines
106 KiB
HTML
<!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 1–6 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)">(6–20)</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 & 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>
|