Files

850 lines
52 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Python-Abenteuer Agent Raphael</title>
<style>
:root {
--bg:#12152E; --sf:#1B1F4A; --sf2:#232865; --bd:#2E3580;
--cy:#4FC3F7; --cy-d:#1A3A52; --go:#FFC107; --go-d:#3A2800;
--gr:#66BB6A; --gr-d:#1A3A1D; --rd:#EF5350; --rd-d:#3A1514;
--pu:#CE93D8; --pu-d:#3A2042; --tx:#E8EAF6; --mu:#9FA8DA;
--cb:#080C1E;
}
*{box-sizing:border-box;margin:0;padding:0}
html,body{background:var(--bg);color:var(--tx);font-family:Nunito,Inter,Segoe UI,system-ui,sans-serif;min-height:100vh;font-size:16px}
.orb{font-family:Orbitron,Consolas,monospace}
.mono{font-family:'JetBrains Mono','Fira Code',Consolas,monospace}
.wrap{max-width:940px;margin:0 auto;padding:1.25rem 1rem}
.card{background:var(--sf);border:1px solid var(--bd);border-radius:16px;padding:1.5rem}
.card-sm{background:var(--sf);border:1px solid var(--bd);border-radius:12px;padding:1rem}
/* XP bar */
.xp-track{background:var(--sf2);border-radius:999px;height:10px;overflow:hidden;margin:.5rem 0}
.xp-fill{height:100%;background:linear-gradient(90deg,var(--go),#FF8C00);border-radius:999px;transition:width .6s ease}
/* Mission grid */
.mgrid{display:grid;grid-template-columns:repeat(4,1fr);gap:.625rem}
.mc{background:var(--sf);border:1.5px solid var(--bd);border-radius:12px;padding:.875rem .75rem;cursor:pointer;transition:all .18s;position:relative;text-align:center}
.mc:hover:not(.locked){border-color:var(--cy);transform:translateY(-2px)}
.mc.done{border-color:var(--gr);background:var(--gr-d)}
.mc.locked{opacity:.38;cursor:not-allowed;pointer-events:none}
.mc.curr{border-color:var(--cy);background:var(--cy-d)}
.mc .me{font-size:1.5rem;display:block;margin-bottom:.25rem}
.mc .mt{font-size:.7rem;font-weight:700;color:var(--cy);display:block}
.mc .ms{font-size:.65rem;color:var(--mu);display:block;margin-top:.15rem}
.mc .check{position:absolute;top:.4rem;right:.5rem;color:var(--gr);font-size:.75rem}
/* Buttons */
.btn{padding:.7rem 1.4rem;border-radius:10px;border:none;font-family:Nunito,Inter,Segoe UI,system-ui,sans-serif;font-size:.95rem;font-weight:800;cursor:pointer;transition:all .15s;display:inline-flex;align-items:center;gap:.4rem}
.btn:hover{transform:translateY(-1px)}
.btn:active{transform:translateY(0)}
.btn-cy{background:var(--cy);color:#080C1E}
.btn-gr{background:var(--gr);color:#fff}
.btn-go{background:var(--go);color:#080C1E}
.btn-gh{background:transparent;color:var(--mu);border:1px solid var(--bd)}
.btn-rd{background:var(--rd);color:#fff}
.btn-pu{background:var(--pu);color:#080C1E}
.btn-sm{padding:.5rem 1rem;font-size:.85rem}
/* Code editor */
.ced{background:var(--cb);border:1px solid var(--bd);border-radius:12px;overflow:hidden}
.ced-hdr{background:var(--sf2);padding:.5rem .875rem;display:flex;align-items:center;gap:.5rem;border-bottom:1px solid var(--bd)}
.dot{width:9px;height:9px;border-radius:50%}
.cta{width:100%;min-height:160px;background:var(--cb);color:#A8E6A3;border:none;padding:1rem;font-family:'JetBrains Mono',monospace;font-size:.84rem;line-height:1.65;resize:vertical;outline:none;tab-size:4}
.cout{background:var(--cb);border-top:1px solid var(--bd);padding:.875rem 1rem;min-height:56px;font-family:'JetBrains Mono',monospace;font-size:.84rem;white-space:pre-wrap;color:var(--mu)}
.cout.ok{color:var(--tx)}
.cout.err{color:var(--rd)}
/* Story */
.story{background:var(--cb);border-left:3px solid var(--cy);border-radius:0 12px 12px 0;padding:1.25rem 1.5rem;font-size:.95rem;line-height:1.85;white-space:pre-wrap}
.concept-code{background:var(--cb);border-radius:10px;padding:.875rem 1.125rem;font-family:'JetBrains Mono',monospace;font-size:.84rem;line-height:1.65;color:#A8E6A3;white-space:pre-wrap;margin:.875rem 0;border:1px solid var(--bd)}
/* Phases */
.phases{display:flex;gap:.5rem;align-items:center}
.ph{width:9px;height:9px;border-radius:50%;background:var(--bd);transition:all .25s}
.ph.act{background:var(--cy);transform:scale(1.4)}
.ph.don{background:var(--gr)}
/* Answers */
.ans-btn{display:block;width:100%;text-align:left;padding:.8rem 1.1rem;margin:.45rem 0;background:var(--sf2);border:1.5px solid var(--bd);border-radius:10px;color:var(--tx);font-family:Nunito,Inter,Segoe UI,system-ui,sans-serif;font-size:.95rem;cursor:pointer;transition:all .15s;font-weight:600}
.ans-btn:hover{border-color:var(--cy);background:var(--cy-d)}
.ans-btn.cor{border-color:var(--gr);background:var(--gr-d);color:var(--gr)}
.ans-btn.wrg{border-color:var(--rd);background:var(--rd-d);color:var(--rd)}
/* Badges */
.bg-pill{display:inline-flex;align-items:center;gap:.35rem;background:var(--sf2);border:1px solid var(--bd);border-radius:20px;padding:.3rem .75rem;font-size:.78rem;font-weight:700}
.bg-pill.earn{border-color:var(--go);background:var(--go-d);color:var(--go)}
.bg-pill.lock{opacity:.3;filter:grayscale(1)}
/* Prof */
.prof-panel{position:fixed;bottom:76px;right:1rem;width:320px;max-height:440px;background:var(--sf);border:1.5px solid var(--pu);border-radius:16px;display:flex;flex-direction:column;z-index:500;animation:slideUp .2s ease}
@keyframes slideUp{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:translateY(0)}}
.prof-msgs{flex:1;overflow-y:auto;padding:.875rem;display:flex;flex-direction:column;gap:.625rem}
.pmsg{padding:.5rem .75rem;border-radius:10px;font-size:.84rem;line-height:1.5;max-width:88%}
.pmsg.u{background:var(--cy-d);color:var(--cy);align-self:flex-end;border:1px solid var(--cy)}
.pmsg.b{background:var(--sf2);align-self:flex-start;border:1px solid var(--pu)}
.prof-fab{position:fixed;bottom:1rem;right:1rem;background:var(--pu);color:#080C1E;border:none;border-radius:50%;width:52px;height:52px;font-size:1.4rem;cursor:pointer;z-index:500;display:flex;align-items:center;justify-content:center;transition:all .2s;font-weight:900}
.prof-fab:hover{transform:scale(1.1)}
/* Popups */
@keyframes xpUp{0%{opacity:1;transform:translateY(0) scale(.9)}60%{opacity:1;transform:translateY(-24px) scale(1.15)}100%{opacity:0;transform:translateY(-56px) scale(1)}}
.xp-pop{position:fixed;top:1.25rem;right:1.25rem;background:var(--go);color:#080C1E;padding:.45rem 1rem;border-radius:20px;font-weight:900;font-size:1rem;z-index:1000;animation:xpUp 2s ease forwards;pointer-events:none}
/* Viktor alert */
.viktor{background:var(--rd-d);border:1px solid var(--rd);border-radius:10px;padding:.625rem 1rem;font-size:.875rem;color:var(--rd);margin-bottom:1rem}
/* Input overlay */
.overlay{position:fixed;inset:0;background:#0007;z-index:800;display:flex;align-items:center;justify-content:center}
.dlg{background:var(--sf);border:1.5px solid var(--cy);border-radius:16px;padding:1.5rem;width:360px;max-width:95vw}
.dlg input{width:100%;background:var(--cb);border:1px solid var(--bd);border-radius:8px;padding:.6rem .875rem;color:var(--tx);font-family:Nunito,Inter,Segoe UI,system-ui,sans-serif;font-size:.9rem;margin:.35rem 0 .875rem;outline:none}
.dlg input:focus{border-color:var(--cy)}
/* Progress bar mission */
.mp-bar{height:4px;background:var(--sf2);border-radius:4px;overflow:hidden;margin:.5rem 0}
.mp-fill{height:100%;background:var(--cy);border-radius:4px;transition:width .4s ease}
/* Hint box */
.hint-box{background:var(--go-d);border:1px solid var(--go);border-radius:10px;padding:.75rem 1rem;font-size:.875rem;color:var(--go);margin-top:.875rem}
/* Concept bullet */
.cpb{display:flex;align-items:flex-start;gap:.5rem;padding:.35rem 0;font-size:.9rem}
/* Result card */
.result-card{background:var(--go-d);border:2px solid var(--go);border-radius:16px;padding:2rem;text-align:center}
/* Scrollbar */
::-webkit-scrollbar{width:4px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:var(--bd);border-radius:4px}
/* Utils */
.fl{display:flex}.ic{align-items:center}.jb{justify-content:space-between}
.g2{gap:.5rem}.g3{gap:.75rem}.g4{gap:1rem}
.mb1{margin-bottom:.25rem}.mb2{margin-bottom:.5rem}.mb3{margin-bottom:.75rem}
.mb4{margin-bottom:1rem}.mb5{margin-bottom:1.25rem}.mb6{margin-bottom:1.5rem}
.mt2{margin-top:.5rem}.mt3{margin-top:.75rem}.mt4{margin-top:1rem}
.mu{color:var(--mu)}.cy{color:var(--cy)}.go{color:var(--go)}
.gr{color:var(--gr)}.rd{color:var(--rd)}.pu{color:var(--pu)}
.sm{font-size:.875rem}.xs{font-size:.75rem}.lg{font-size:1.1rem}
.xl{font-size:1.25rem}.fw8{font-weight:800}.fw7{font-weight:700}
.ta-c{text-align:center}
@keyframes fade{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}
.fa{animation:fade .25s ease}
@keyframes conf{from{transform:translateY(-80px) rotate(0deg);opacity:1}to{transform:translateY(110vh) rotate(720deg);opacity:0}}
.hidden{display:none!important}
@media (max-width:760px){
.wrap{padding:.875rem}
.card{padding:1rem;border-radius:12px}
.mgrid{grid-template-columns:repeat(2,1fr)}
.fl.jb{align-items:flex-start}
.prof-panel{left:.75rem;right:.75rem;bottom:72px;width:auto;max-height:60vh}
.btn{width:100%;justify-content:center}
.cta{font-size:.8rem}
}
@media (max-width:430px){
.mgrid{grid-template-columns:1fr}
}
</style>
</head>
<body>
<div id="app"></div>
<div id="prof-layer"></div>
<script>
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// DATA
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const M = [
{id:1,e:"🔓",t:"Hallo Raphael",s:"Dein erster Python-Befehl",a:"#4FC3F7",
story:`Ein kalter Montagmorgen. Raphael öffnet seinen Laptop der Bildschirm leuchtet rot auf:\n\n⚠️ NOTFALL-ALERT ⚠️\n\n"Geheimes Python-Labor erkannt.\nNur echte Python-Agenten dürfen eintreten.\nBeweise, dass du Python sprechen kannst jetzt."\n\nRaphael schluckt. Python? Noch nie programmiert.\nAber aufgeben? Keine Option.\nSeine Finger zittern... er beginnt zu tippen.`,
w:{t:"print() Dein erster Python-Befehl",c:'print("Hallo Raphael!")',p:["📢 print() zeigt Text auf dem Bildschirm an","✏️ Text in \" \" Anführungszeichen wird angezeigt","💡 Du kannst jeden beliebigen Text ausgeben"]},
u:{a:"Das Labor wartet auf Raphaels Beweis! Schreibe 3 print()-Zeilen:\n→ Deinen Namen\n→ Deinen Agenten-Titel\n→ Eine Botschaft an Viktor",c:'# Raphael tippt seinen Beweis...\n',h:['💡 Benutze print() für jede Zeile','🔑 print("Dein Text") zeigt den Text an','✅ Beispiel: print("Ich bin Raphael")']},
q:{q:"Was macht print() in Python?",a:["Druckt Papier aus","Zeigt Text auf dem Bildschirm an","Löscht Dateien","Rechnet Zahlen"],r:1,ok:"🎯 Exakt! print() zeigt Text an. Raphael hat das Labor überzeugt!"}
},
{id:2,e:"🗄️",t:"Geheime Informationen",s:"Variablen speichern",a:"#CE93D8",
story:`Das Labor öffnet sich. Eine ruhige Stimme erklingt:\n\n"Willkommen, Agent Raphael. Ich bin ARIA.\n\nViktor ein feindliches KI-System greift unser\nDatennetz an. Bereits 3 Datenbanken kompromittiert.\n\nAber zuerst: Ein Agent ohne Gedächtnis ist nutzlos.\nLerne, Informationen zu speichern."`,
w:{t:"Variablen Dein digitales Gedächtnis",c:'name = "Raphael"\nalter = 12\nstadt = "München"\n\nprint(name)\nprint(alter)\nprint("Agent aus " + stadt)',p:["🗄️ Variablen sind wie Schubladen sie speichern Werte","💬 Text braucht \" \": name = \"Raphael\"","🔢 Zahlen brauchen kein \" \": alter = 12"]},
u:{a:"Erstelle Raphaels geheime Agent-Akte!\nSpeichere und zeige: Name, Alter, Lieblingsessen, Lieblingsspiel",c:"# Raphaels Agent-Akte\n# 4 Variablen erstellen...\n",h:['💡 name = "Raphael" speichert Text',"🔑 alter = 12 speichert Zahl (ohne \" \"!)","✅ print(name) zeigt den Wert an"]},
q:{q:"Welche Variable enthält einen Text?",a:['alter = 12','name = "Raphael"','punkte = 100','level = 5'],r:1,ok:'📁 Richtig! Text braucht Anführungszeichen: "Raphael"'}
},
{id:3,e:"🧮",t:"Der Agenten-Rechner",s:"Mathematik mit Python",a:"#66BB6A",
story:`"Gut gemacht, Agent Raphael!" ARIA klingt zufrieden.\n"Viktor verschlüsselt Nachrichten mit Zahlen-Codes.\nDu brauchst einen Rechner blitzschnell und fehlerfrei.\nPython rechnet besser als jeder Taschenrechner."`,
w:{t:"Python kann rechnen!",c:"a = 10\nb = 5\nprint(a + b) # 15\nprint(a - b) # 5\nprint(a * b) # 50\nprint(a / b) # 2.0",p:[" Addition: +"," Subtraktion: -","✖️ Multiplikation: *","➗ Division: /"]},
u:{a:"Baue Raphaels Agenten-Rechner!\nBerechne die 4 Geheim-Codes: 15+7, 20-8, 5*6, 24/4",c:"# Raphaels Agenten-Rechner\n",h:["💡 print(15 + 7) berechnet UND zeigt das Ergebnis","🔑 * für Multiplikation, / für Division","✅ print(15 + 7) ergibt 22"]},
q:{q:"Was ergibt print(3 * 4)?",a:["34","7","12","3.4"],r:2,ok:"✅ 3 × 4 = 12! Das * Zeichen ist die Multiplikation."}
},
{id:4,e:"🔍",t:"Der Fehler-Detektiv",s:"Fehlermeldungen lesen",a:"#FFC107",
story:`"Agent Raphael KRITISCH!" ARIA klingt alarmiert.\n"Viktor hat unsere Codebasis sabotiert!\nÜberall rote Fehlermeldungen.\n\nAber höre zu: Fehler sind keine Feinde.\nSie sind Hinweise. Schlüssel zum Problem.\nEin Agent, der Fehler lesen kann, ist unaufhaltbar."`,
w:{t:"Fehler lesen = Superkraft!",c:'# SyntaxError: fehlende Klammer\n# print("Hallo" ← FALSCH\nprint("Hallo") # ← RICHTIG ✅\n\n# NameError: Variable nicht definiert\n# print(geheimwort) ← FALSCH\ngeheimwort = "Python"\nprint(geheimwort) # ← RICHTIG ✅',p:["🔴 SyntaxError = Schreibfehler (fehlende Klammer etc.)","🟡 NameError = Variable noch nicht definiert","🟢 Fehler lesen: Zeile → Tipp → Fix versuchen"]},
u:{a:"Raphaels Detektiv-Mission:\nFinde und korrigiere die 2 Fehler im Code!",c:'# Korrigiere diese Fehler, Raphael!\n\n# Fehler 1 - was fehlt?\nprint("Agent Raphael"\n\n# Fehler 2 - was muss vorher kommen?\nprint(geheimwort)\n',h:["💡 Fehler 1: Schaue die Klammern an sind alle zu?","🔑 Fehler 2: geheimwort muss VOR print() definiert werden!",'✅ geheimwort = "Python123" dann print(geheimwort)']},
q:{q:"Was bedeutet SyntaxError?",a:["Programm ist zu langsam","Schreibfehler im Code (z.B. fehlende Klammer)","Computer ist kaputt","Datei nicht gefunden"],r:1,ok:"🔍 Genau! SyntaxError = Python kann den Code nicht lesen."}
},
{id:5,e:"⌨️",t:"Magische Eingabe",s:"input() Funktion",a:"#FFAB40",
story:`"Neue Aufgabe, Agent Raphael!" ARIA sendet eine Nachricht.\n"Viktor kann unsere Agenten nicht mehr erkennen.\nBaue ein interaktives Identifikationssystem\ndas Nutzer nach ihrem Namen fragt!"`,
w:{t:"input() Nutzer-Eingabe",c:'name = input("Wie heißt du? ")\nprint("Hallo, Agent " + name + "!")',p:["⌨️ input() hält das Programm an und wartet","📥 Die Eingabe wird als Text gespeichert","🔢 int(input()) wandelt Text in Zahl um"]},
u:{a:"Baue Raphaels Agenten-Identifikationssystem!\nFrage nach Name und begrüße den Agenten persönlich.",c:"# Agenten-Identifikation\n# Frage nach dem Namen...\n\n",h:['💡 name = input("Wie heißt du? ")','🔑 print("Hallo, Agent " + name + "!")']},
q:{q:"Wofür wird input() benutzt?",a:["Zahlen berechnen","Text vom Nutzer einlesen","Variablen löschen","Code drucken"],r:1,ok:"✅ input() liest Text ein, den der Nutzer tippt!"}
},
{id:6,e:"🎯",t:"Zahlen-Ratespiel",s:"if-Bedingungen",a:"#F06292",
story:`"Viktor hat einen Geheim-Code versteckt eine Zahl.\nRaphael muss sie erraten, um das System zu entsperren.\nPython kann prüfen ob eine Zahl korrekt ist!"`,
w:{t:"if Wenn... dann...",c:"geheimzahl = 42\nrateversuch = 42\n\nif rateversuch == geheimzahl:\n print('🎯 Treffer! Code entsperrt!')",p:["🔀 if prüft eine Bedingung","== bedeutet 'ist gleich'","↩️ Einrückung (4 Leerzeichen) ist Pflicht!"]},
u:{a:"Baue Raphaels Zahlen-Ratespiel!\nGeheimzahl = 7. Prüfe ob die Eingabe stimmt.",c:"# Zahlen-Ratespiel\ngeheimzahl = 7\neingabe = int(input('Rate die Zahl: '))\n# Prüfe ob die Eingabe korrekt ist\n",h:["💡 int() wandelt die Texteingabe in eine Zahl","🔑 if eingabe == geheimzahl: print('Richtig!')"]},
q:{q:"Wann läuft der Code unter if?",a:["Immer","Wenn die Bedingung True (wahr) ist","Nie","Zufällig"],r:1,ok:"✅ if-Code läuft nur wenn die Bedingung wahr ist!"}
},
{id:7,e:"⚖️",t:"Entscheidungsmeister",s:"if / else",a:"#4DB6AC",
story:`"Sehr gut! Aber jetzt brauchen wir Plan B.\nWas passiert wenn jemand die falsche Antwort gibt?\nif/else gibt dir immer zwei Möglichkeiten!"`,
w:{t:"if / else Plan A und Plan B",c:"alter = 12\n\nif alter >= 12:\n print('✅ Zugang: GEWÄHRT')\nelse:\n print('❌ Noch mehr Training nötig')",p:["🔀 else ist der Alternativ-Fall","📋 Wenn if nicht zutrifft, läuft else"]},
u:{a:"Agenten-Aufnahme-Check:\nAlter ≥ 12 → aufgenommen, sonst → abgelehnt!",c:"alter = int(input('Wie alt bist du? '))\n# Prüfe das Alter mit if/else\n",h:["💡 if alter >= 12:","🔑 else: print('Abgelehnt')"]},
q:{q:"Was macht 'else'?",a:["Beendet das Programm","Läuft wenn if NICHT zutrifft","Neue Funktion starten","Zeigt Fehler an"],r:1,ok:"✅ else ist Plan B läuft wenn if nicht zutrifft!"}
},
{id:8,e:"🔐",t:"Super-Agent-Check",s:"and & or",a:"#7986CB",
story:`"Das Hochsicherheitslabor braucht doppelte Absicherung.\nViktor kann einzelne Bedingungen knacken.\nAber 'and' und 'or' bauen eine Festung!"`,
w:{t:"and & or Kombinierte Bedingungen",c:"name = 'Raphael'\nalter = 12\n\nif name == 'Raphael' and alter >= 12:\n print('🟢 ZUGANG GEWÄHRT')\nelse:\n print('🔴 ZUGANG VERWEIGERT')",p:["🔒 and = BEIDE Bedingungen müssen wahr sein","🔓 or = EINE der Bedingungen reicht"]},
u:{a:"Baue ein 2-Faktor-Login!\nRichtiger Name UND richtiges Passwort = Zugang.",c:"name = input('Agent-Name: ')\npw = input('Passwort: ')\n# Prüfe beide Bedingungen mit 'and'\n",h:['💡 if name == "Raphael" and pw == "geheim":']},
q:{q:"Wann ist 'A and B' wahr?",a:["Wenn A wahr ist","Wenn B wahr ist","Wenn BEIDE wahr sind","Wenn keins wahr ist"],r:2,ok:"✅ and braucht beide wie ein Doppelschloss!"}
},
{id:9,e:"🚀",t:"Schleifen-Experte",s:"for-Schleifen",a:"#FF7043",
story:`"Viktor verstärkt den Angriff! Abwehrrakete muss starten!\nAber zuerst der Countdown.\nPython kann für Raphael rückwärts zählen!"`,
w:{t:"for Dinge wiederholen",c:"for i in range(5):\n print(i) # 0,1,2,3,4\n\n# Countdown:\nfor i in range(10, 0, -1):\n print(i)\nprint('🚀 LAUNCH!')",p:["🔁 for wiederholt Code für jeden Wert","📊 range(5) → 0,1,2,3,4","⬇️ range(10,0,-1) → Countdown 10..1"]},
u:{a:"Starte die Abwehrrakete!\nCountdown von 10 bis 1, dann '🚀 LAUNCH!'",c:"# Raketen-Countdown\n# Zähle von 10 bis 1 runter\n",h:["💡 range(10, 0, -1) zählt rückwärts","🔑 print(i) zeigt die aktuelle Zahl"]},
q:{q:"Was erzeugt range(3)?",a:["0, 1, 2","1, 2, 3","Nur 3","0, 1, 2, 3"],r:0,ok:"✅ range(3) → 0, 1, 2 Python zählt ab 0!"}
},
{id:10,e:"🎒",t:"Inventar-System",s:"Listen",a:"#81C784",
story:`"Gut gemacht! Die Rakete hat Viktors ersten Angriff\nabgewehrt. Jetzt brauchen wir ein Inventar-System.\nPython kann viele Dinge in einer Variablen speichern!"`,
w:{t:"Listen Sammlungen in Python",c:'rucksack = ["Seil", "Karte", "Lampe"]\nprint(rucksack[0]) # "Seil"\nrucksack.append("Kompass")\nfor item in rucksack:\n print("🎒 " + item)',p:["📋 Listen speichern mehrere Werte in [ ]","🔢 Index beginnt bei 0 (erstes = [0])"," .append() fügt am Ende hinzu","🔁 for item in liste: → alle ausgeben"]},
u:{a:"Erstelle Raphaels Agenten-Rucksack!\n4 Gegenstände hinzufügen und alle anzeigen.",c:"# Raphaels Agenten-Rucksack\nrucksack = []\n# Füge 4 Gegenstände hinzu!\n",h:['💡 rucksack = ["Seil", "Karte", "Lampe", "Wasser"]','🔑 for item in rucksack: print("🎒 " + item)']},
q:{q:"Welchen Index hat das ERSTE Element einer Liste?",a:["1","0","-1","Kein Index"],r:1,ok:"✅ Index 0 = erstes Element Python zählt ab 0!"}
},
{id:11,e:"🛠️",t:"Eigene Funktionen",s:"def Bausteine",a:"#B39DDB",
story:`"Excellente Arbeit! Doch Viktor lernt auch dazu.\nWir brauchen wiederverwendbare Code-Bausteine.\nLerne, eigene Befehle zu erstellen Funktionen!"`,
w:{t:"def Eigene Befehle erstellen",c:"def agent_begruessung():\n print('🕵️ Willkommen, Agent!')\n print('System: BEREIT')\n\nagent_begruessung() # 1. Aufruf\nagent_begruessung() # 2. Aufruf",p:["🔧 def name(): erstellt eine Funktion","↩️ Eingerückter Code = Inhalt der Funktion","▶️ name() ruft auf beliebig oft!"]},
u:{a:"Baue eine Funktion 'agentenstatus()'!\nSie soll Raphaels Agent-Infos anzeigen.",c:"def agentenstatus():\n pass # Ersetze 'pass' mit deinem Code\n\nagentenstatus()\n",h:["💡 Ersetze pass mit print()-Befehlen","🔑 Rufe die Funktion am Ende mit agentenstatus() auf"]},
q:{q:"Womit startet eine Funktion in Python?",a:["function:","fun():","def name():","create:"],r:2,ok:"✅ def funktionsname(): so wird eine Funktion erstellt!"}
},
{id:12,e:"⚡",t:"Agenten-Funktionen",s:"Parameter & f-Strings",a:"#FFD54F",
story:`"Perfekt, Raphael! Aber was wenn verschiedene Agenten\ndie Funktion nutzen wollen?\nParameter machen Funktionen wirklich mächtig!"`,
w:{t:"Parameter & f-Strings",c:"def begruesse(name, level):\n print(f'Hallo Agent {name}!')\n print(f'Dein Level: {level}')\n\nbegruesse('Raphael', 5)\nbegruesse('ARIA', 99)",p:["📥 Parameter = Eingaben für die Funktion","✨ f'Text {variable}' fügt Variable in Text ein","🎯 Beim Aufruf übergibst du die Werte"]},
u:{a:"Erweitere agentenstatus(name, level)!\nNutze f-Strings für eine schöne Ausgabe.",c:"def agentenstatus(name, level):\n pass # Ersetze mit f-String Code\n\nagentenstatus('Raphael', 5)\n",h:["💡 f'Hallo {name}!' fügt die Variable ein","🔑 Ersetze pass mit echten print(f'...')-Zeilen"]},
q:{q:"Was sind Parameter in einer Funktion?",a:["Fehler im Code","Eingaben, die die Funktion erhält","Der Funktionsname","Ausgabewerte"],r:1,ok:"✅ Parameter sind Eingaben wie Zutaten für ein Rezept!"}
},
{id:13,e:"❓",t:"Das Quiz-Spiel",s:"Alles kombinieren",a:"#4DD0E1",
story:`"Zeit für die echte Agenten-Prüfung!\nAber diesmal baust du das Quiz selbst.\nZeige ARIA, dass du alles verstanden hast!"`,
w:{t:"Das komplette Quiz",c:"punkte = 0\nfragen = [('Was ergibt 3*4?','12'),('Welche Sprache lernst du?','Python')]\n\nfor frage, antwort in fragen:\n eingabe = input(frage + ' ')\n if eingabe == antwort:\n print('✅ Richtig! +1')\n punkte += 1\n else:\n print('❌ Falsch')\n\nprint(f'Ergebnis: {punkte}/{len(fragen)}')",p:["🎯 Variablen + Listen + Schleifen + if!"," punkte += 1 erhöht um 1","📏 len(liste) = Anzahl der Elemente"]},
u:{a:"Baue dein eigenes 5-Fragen-Quiz!\nÜber Python, Raphael oder Allgemeinwissen.",c:"# Raphaels Agent-Quiz\npunkte = 0\nfragen = [\n # ('Frage?', 'Antwort'),\n # 5 Fragen einfügen!\n]\n\nfor frage, antwort in fragen:\n eingabe = input(frage + ' ')\n if eingabe == antwort:\n punkte += 1\n\nprint(f'Ergebnis: {punkte}/{len(fragen)}')\n",h:["💡 Füge Fragen als ('Frage?','Antwort') hinzu","🔑 punkte += 1 für jede richtige Antwort"]},
q:{q:"Was macht 'punkte += 1'?",a:["Setzt punkte auf 1","Erhöht punkte um 1","Verringert um 1","Zeigt punkte an"],r:1,ok:"✅ += erhöht den Wert kurz für punkte = punkte + 1!"}
},
{id:14,e:"🗺️",t:"Das Abenteuer-Spiel",s:"Text Adventure",a:"#AED581",
story:`"Raphael! Wir haben Viktors Versteck geortet.\nAber der Weg ist ein interaktives Labyrinth.\nBaue es selbst als Text-Adventure-Spiel!"`,
w:{t:"Text Adventure Interaktive Geschichten",c:"print('Du stehst vor Viktors Hauptquartier.')\nwahl = input('Links (L) oder Rechts (R)? ')\n\nif wahl == 'L':\n print('⚠️ Viktor entkommt!')\nelif wahl == 'R':\n print('🎉 Viktor besiegt!')\nelse:\n print('❓ Unbekannte Eingabe')",p:["📖 Text Adventures = Interaktive Geschichten","🔀 elif = 'else if' weitere Bedingung","🌳 Mehr Entscheidungen = mehr Pfade"]},
u:{a:"Baue Raphaels Agenten-Abenteuer!\n2 Entscheidungen → mindestens 3 verschiedene Enden.",c:"print('Agent Raphael betritt Viktors Versteck...')\n\nwahl = input('Tür A (A) oder Tür B (B)? ')\n",h:["💡 if/elif/else für verschiedene Pfade","🔑 input() für Entscheidungen des Spielers"]},
q:{q:"Was macht 'elif' in Python?",a:["Beendet Schleife","'else if' weitere Bedingungsprüfung","Definiert Funktion","Importiert Module"],r:1,ok:"✅ elif = else if weitere Bedingungsprüfung!"}
},
{id:15,e:"🎲",t:"Geheimcode-Generator",s:"Module & Zufall",a:"#F48FB1",
story:`"Die ultimative Waffe gegen Viktor:\nzufällige, unvorhersagbare Codes!\nViktor kann keine zufälligen Passwörter knacken."`,
w:{t:"import random Zufallsmodul",c:"import random\n\nzahl = random.randint(1, 100)\nprint(f'Geheimzahl: {zahl}')\n\noptionen = ['Seil', 'Laser', 'Tarnmantel']\nwahl = random.choice(optionen)\nprint(f'Waffe: {wahl}')",p:["📦 import lädt externe Module","🎲 random.randint(a, b) → Zufallszahl","🎯 random.choice(liste) → zufälliges Element"]},
u:{a:"Baue Raphaels Passwort-Generator!\n4 zufällige Ziffern → unknackbares Passwort.",c:"import random\n# Erstelle ein 4-stelliges Zufalls-Passwort\n# Tipp: random.randint(0, 9) für eine Ziffer\n\n",h:["💡 random.randint(0, 9) gibt eine Ziffer (0-9)","🔑 str(zahl) wandelt Zahl in Text mit + verbinden"]},
q:{q:"Was macht 'import random'?",a:["Erstellt eine Zufallszahl","Lädt das Zufalls-Modul","Löscht alle Variablen","Startet das Programm neu"],r:1,ok:"✅ import lädt Module random gibt Zufallsfunktionen!"}
},
{id:16,e:"👑",t:"Viktors letzter Code",s:"Das finale Abenteuer!",a:"#FFC107",
story:`🚨 MAXIMALER ALARM 🚨\n\n"Agent Raphael das ist es!" ARIA klingt ernst.\n"Viktor aktiviert seinen Haupt-Angriff!\nNur du kannst ihn stoppen.\n\nDu hast alles gelernt:\n✅ Variablen ✅ Schleifen ✅ Funktionen\n✅ Listen ✅ Bedingungen ✅ Module\n\nJetzt baue die ultimative Agentenzentrale.\nRette die digitale Welt, Raphael!"`,
w:{t:"Die ultimative Agentenzentrale",c:"import random\n\ndef login(name, pw):\n return name == 'Raphael' and pw == 'python'\n\ndef zeige_inventar(items):\n for item in items:\n print(f' ⚔️ {item}')\n\nif login(input('Name: '), input('PW: ')):\n print('🟢 ZUGANG GEWÄHRT')\n zeige_inventar(['Seil', 'Karte', 'Lampe'])\n print(f'🎲 Code: {random.randint(1000,9999)}')\nelse:\n print('🔴 ABGELEHNT')",p:["🔙 return gibt einen Wert zurück","🔗 Alle Konzepte kombiniert!","✨ Raphael ist jetzt Python-Großmeister!"]},
u:{a:"Baue Raphaels komplette Agentenzentrale!\n✅ Login mit Name + Passwort\n✅ Inventar anzeigen\n✅ Zufalls-Code generieren\n✅ Missionsstatus ausgeben",c:"# 👑 RAPHAELS AGENTENZENTRALE\nimport random\n\ndef login(name, passwort):\n pass # TODO\n\ndef zeige_inventar(items):\n pass # TODO\n\nprint('╔══════════════════════╗')\nprint('║ AGENTENZENTRALE ║')\nprint('╚══════════════════════╝')\n# TODO: Alles verbinden!\n",h:["💡 Benutze alle Konzepte der 15 Missionen!","🔑 def + if/else + for + listen + input + random","✅ Du schaffst das, Agent Raphael! 💪"]},
q:{q:"Du hast alle 16 Missionen abgeschlossen! Du bist jetzt...",a:["Python-Neuling","Code-Entdecker","Script-Held","Python-GROSSMEISTER! 👑"],r:3,ok:"🎉 HERZLICHEN GLÜCKWUNSCH, RAPHAEL! Viktor ist besiegt! Die digitale Welt ist gerettet! Du bist offiziell Python-Großmeister! 🏆"}
}
];
const LEVS = [
{n:"Python-Neuling",min:0,i:"🌱"},
{n:"Code-Entdecker",min:100,i:"🔍"},
{n:"Script-Held",min:250,i:"⚔️"},
{n:"Schleifen-Meister",min:450,i:"🔄"},
{n:"Funktions-Profi",min:650,i:"⚡"},
{n:"Python-Agent",min:900,i:"🕵️"},
{n:"Python-Großmeister",min:1100,i:"👑"}
];
const BDGS = [
{id:"b1",i:"📢",n:"Erste Ausgabe",m:1},
{id:"b4",i:"🔍",n:"Fehler-Jäger",m:4},
{id:"b9",i:"🚀",n:"Schleifen-Held",m:9},
{id:"b10",i:"🎒",n:"Listen-Profi",m:10},
{id:"b12",i:"⚡",n:"Funktions-Meister",m:12},
{id:"b14",i:"🎮",n:"Spiele-Entwickler",m:14},
{id:"b15",i:"🎲",n:"Code-Zauberer",m:15},
{id:"b16",i:"👑",n:"Python-Großmeister",m:16}
];
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// STATE
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const ST = {
xp:0, done:{}, badges:[], view:'home', mid:null,
phase:0, sxp:0, hi:-1, tdone:false, tcor:false,
code:'', out:'', oerr:false,
profOpen:false, profMsgs:[],
py:null, pySt:'loading',
inputsNeeded:[], inputVals:{}, drafts:{}
};
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// UTILS
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
function getLev(xp){
for(let i=LEVS.length-1;i>=0;i--) if(xp>=LEVS[i].min) return {...LEVS[i],idx:i};
return {...LEVS[0],idx:0};
}
function getLevPct(xp){
const l=getLev(xp); const nx=LEVS[l.idx+1];
if(!nx) return 100;
return Math.round((xp-l.min)/(nx.min-l.min)*100);
}
function isUnlocked(id){ return id===1||!!ST.done[id-1]; }
function getMission(id){ return M.find(m=>m.id===id); }
function esc(s){
return String(s ?? '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// STORAGE
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
async function loadProg(){
try{
const raw=localStorage.getItem('raphael_python_abenteuer_v4');
if(raw){
const d=JSON.parse(raw);
ST.xp=d.xp||0; ST.done=d.done||{}; ST.badges=d.badges||[]; ST.drafts=d.drafts||{};
}
}catch(e){}
}
async function saveProg(){
try{
localStorage.setItem('raphael_python_abenteuer_v4',JSON.stringify({
xp:ST.xp,done:ST.done,badges:ST.badges,drafts:ST.drafts
}));
}catch(e){}
}
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// PYODIDE
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
function initPy(){
const sc=document.createElement('script');
sc.src='https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.js';
sc.onload=async()=>{
try{
const py=await window.loadPyodide({indexURL:'https://cdn.jsdelivr.net/pyodide/v0.25.0/full/'});
await py.runPythonAsync(`
import sys,builtins
from io import StringIO
_buf=StringIO()
sys.stdout=_buf
sys.stderr=_buf
_ins=[]
_ii=[0]
def _inp(p=''):
_buf.write(str(p))
if _ii[0]<len(_ins):
v=_ins[_ii[0]];_ii[0]+=1
_buf.write(str(v)+'\\n');return str(v)
return ''
builtins.input=_inp
`);
ST.py=py; ST.pySt='ready'; updPySt();
}catch(e){ ST.pySt='error'; updPySt(); }
};
sc.onerror=()=>{ ST.pySt='error'; updPySt(); };
document.head.appendChild(sc);
}
function updPySt(){
const els=document.querySelectorAll('[data-py-status]');
if(!els.length) return;
const map={
ready:['🟢 Python bereit','var(--gr)'],
error:['🔴 Python konnte nicht geladen werden','var(--rd)'],
loading:['⏳ Python lädt...','var(--mu)']
};
const [txt,col]=map[ST.pySt]||map.loading;
els.forEach(el=>{ el.textContent=txt; el.style.color=col; });
}
function parseInputs(code){
const ps=[];
const re=/input\((['"``])(.*?)\1\)/g; let m;
while((m=re.exec(code))!==null) ps.push(m[2]||'Eingabe:');
const bare=(code.match(/input\(\s*\)/g)||[]).length;
for(let i=0;i<bare;i++) ps.push('Eingabe:');
return ps;
}
async function runPy(code,inputs=[]){
if(!ST.py) return {out:'⏳ Python ist noch nicht bereit. Bitte kurz warten oder die Internetverbindung prüfen.',err:true};
try{
await ST.py.runPythonAsync(`_buf.seek(0);_buf.truncate(0);_ins.clear();_ins.extend(${JSON.stringify(inputs.map(String))});_ii[0]=0`);
await ST.py.runPythonAsync(code);
const out=ST.py.runPython('_buf.getvalue()');
return {out:out||'(kein Output print() benutzen!)',err:false};
}catch(e){
return {out:'❌ Fehler:\n'+e.message,err:true};
}
}
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// XP & PROGRESS
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
function awardXP(amt,lbl){
const prev=getLev(ST.xp); ST.xp+=amt; ST.sxp+=amt;
const nx=getLev(ST.xp);
showPop(`+${amt} XP${lbl?' '+lbl:''}`,null,null);
if(nx.idx>prev.idx) setTimeout(()=>showPop(`⬆️ Level up! ${nx.i} ${nx.n}`,'var(--pu)','80px'),600);
updXPBar(); saveProg();
}
function showPop(txt,bg,top){
const p=document.createElement('div');
p.className='xp-pop'; p.textContent=txt;
if(bg) p.style.background=bg;
if(top) p.style.top=top;
document.body.appendChild(p);
setTimeout(()=>p.remove(),2100);
}
function updXPBar(){
const f=document.getElementById('xp-f');
const xl=document.getElementById('xp-lbl');
const ln=document.getElementById('lev-n');
if(f) f.style.width=getLevPct(ST.xp)+'%';
if(xl) xl.textContent=ST.xp+' XP';
if(ln){ const l=getLev(ST.xp); ln.textContent=l.i+' '+l.n; }
}
function completeMiss(mid){
if(ST.done[mid]) return;
ST.done[mid]=true;
BDGS.forEach(b=>{
if(b.m===mid&&!ST.badges.includes(b.id)){
ST.badges.push(b.id);
setTimeout(()=>showPop(`🏅 Abzeichen: ${b.n}!`,'var(--go)','100px'),1200);
}
});
saveProg();
}
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// RENDER HOME
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
function renderHome(){
const lv=getLev(ST.xp); const nx=LEVS[lv.idx+1];
const pct=getLevPct(ST.xp); const dc=Object.keys(ST.done).length;
const mCards=M.map(mis=>{
const done=!!ST.done[mis.id];
const unlk=isUnlocked(mis.id);
const cls='mc'+(done?' done':!unlk?' locked':'');
return `<div class="${cls}" onclick="openMission(${mis.id})">
${done?'<span class="check">✓</span>':''}
<span class="me">${mis.e}</span>
<span class="mt">${mis.id}. ${mis.t}</span>
<span class="ms">${mis.s}</span>
</div>`;
}).join('');
const bdgHtml=BDGS.map(b=>{
const earn=ST.badges.includes(b.id);
return `<span class="bg-pill ${earn?'earn':'lock'}">${b.i} ${b.n}</span>`;
}).join('');
return `
<div class="wrap fa">
<div class="fl ic jb mb4">
<div>
<div class="fl ic g2 mb1">
<span style="font-size:1.5rem">🕵️</span>
<h1 class="orb" style="font-size:1.1rem;color:var(--cy)">AGENTEN-ZENTRALE</h1>
</div>
<div style="font-size:.8rem;color:var(--mu)">Agent Raphael · Python-Abenteuer</div>
</div>
<div data-py-status style="font-size:.8rem;color:var(--mu)">⏳ Python lädt...</div>
</div>
<div class="card mb4" style="border-color:var(--go)">
<div class="fl ic jb mb2">
<div>
<div id="lev-n" style="font-weight:800;font-size:1.05rem">${lv.i} ${lv.n}</div>
<div style="font-size:.8rem;color:var(--mu)">${nx?'Nächstes Level: '+nx.i+' '+nx.n:'🏆 Maximales Level erreicht!'}</div>
</div>
<div id="xp-lbl" style="font-size:1.4rem;font-weight:900;color:var(--go)">${ST.xp} XP</div>
</div>
<div class="xp-track"><div id="xp-f" class="xp-fill" style="width:${pct}%"></div></div>
<div class="fl ic jb mt2">
<span style="font-size:.8rem;color:var(--mu)">${dc}/16 Missionen abgeschlossen</span>
<span style="font-size:.8rem;color:var(--cy)">${pct}% zum nächsten Level</span>
</div>
<div class="mt3">
<button class="btn btn-gh btn-sm" onclick="resetProgress()">Fortschritt zurücksetzen</button>
</div>
</div>
<div class="fl ic jb mb3">
<h2 class="fw8" style="font-size:1.05rem">🗂️ Missionen</h2>
<span class="sm mu">Klicke auf eine Mission um zu starten</span>
</div>
<div class="mgrid mb5">${mCards}</div>
<div class="mb3"><h2 class="fw8 mb2" style="font-size:1.05rem">🏅 Abzeichen</h2>
<div style="display:flex;flex-wrap:wrap;gap:.5rem">${bdgHtml}</div>
</div>
<div class="viktor mt4">⚠️ Virus Viktor ist noch aktiv ${16-dc} Missionen bis zum finalen Showdown!</div>
</div>`;
}
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// RENDER MISSION
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
function renderMission(){
const mis=getMission(ST.mid); if(!mis) return '';
const ph=ST.phase;
const phases=['Story','Lernen','Üben','Test','Fertig'];
const dots=phases.map((_,i)=>`<div class="ph ${i<ph?'don':i===ph?'act':''}"></div>`).join('');
let body='';
if(ph===0) body=renderStory(mis);
else if(ph===1) body=renderLesen(mis);
else if(ph===2) body=renderUeben(mis);
else if(ph===3) body=renderTest(mis);
else body=renderDone(mis);
return `
<div class="wrap fa">
<div class="fl ic jb mb4">
<button class="btn btn-gh btn-sm" onclick="goHome()">← Zurück</button>
<div class="fl ic g3">
<div class="fl ic g2">${dots}</div>
<span style="font-size:.8rem;color:var(--go);font-weight:700">+${ST.sxp} XP diese Mission</span>
</div>
</div>
<div class="fl ic g3 mb4">
<span style="font-size:2rem">${mis.e}</span>
<div>
<h2 class="fw8" style="font-size:1.2rem">${mis.id}. ${mis.t}</h2>
<div class="mu sm">${mis.s} · ${phases[ph]}</div>
</div>
</div>
<div id="phase-body">${body}</div>
</div>`;
}
function renderStory(mis){
return `
<div class="card mb4">
<div class="viktor mb3">📡 Neue Mission von ARIA für Agent Raphael</div>
<div class="story">${esc(mis.story)}</div>
</div>
<button class="btn btn-cy" onclick="nextPhase()">Weiter zur Lektion →</button>`;
}
function renderLesen(mis){
const pts=mis.w.p.map(p=>`<div class="cpb"><span style="flex-shrink:0">${p.slice(0,2)}</span><span>${p.slice(2)}</span></div>`).join('');
return `
<div class="card mb4">
<h3 class="fw8 mb3" style="font-size:1.05rem;color:var(--cy)">${mis.w.t}</h3>
<div class="concept-code">${esc(mis.w.c)}</div>
<div class="mt3">${pts}</div>
</div>
<div class="fl ic g3">
<button class="btn btn-cy" onclick="nextPhase();awardXP(10,'Lesen')">Verstanden! +10 XP →</button>
<span data-py-status class="sm mu">⏳ Python lädt...</span>
</div>`;
}
function renderUeben(mis){
const code=ST.code||mis.u.c;
const outCls='cout'+(ST.out&&!ST.oerr?' ok':ST.oerr?' err':'');
const hintHtml=ST.hi>=0&&mis.u.h[ST.hi]
?`<div class="hint-box">💡 ${esc(mis.u.h[ST.hi])}</div>`:'';
const hintBtn=ST.hi<mis.u.h.length-1
?`<button class="btn btn-gh btn-sm" onclick="showHint()">💡 Hinweis ${ST.hi+2>mis.u.h.length?'':ST.hi+2+'/'+mis.u.h.length}</button>`
:'';
return `
<div class="card mb3">
<h3 class="fw8 mb2" style="font-size:1rem;color:var(--go)">✏️ Raphaels Aufgabe</h3>
<div class="mb3 sm" style="white-space:pre-wrap;line-height:1.65">${esc(mis.u.a)}</div>
<div class="ced">
<div class="ced-hdr">
<div class="dot" style="background:#FF5F56"></div>
<div class="dot" style="background:#FFBD2E"></div>
<div class="dot" style="background:#27C93F"></div>
<span class="mono xs mu" style="margin-left:.5rem">raphael_code.py</span>
<span data-py-status class="mono xs" style="margin-left:auto;color:var(--mu)">⏳</span>
</div>
<textarea id="code-ta" class="cta" onkeydown="handleTab(event)" oninput="saveDraft(this.value)">${esc(code)}</textarea>
<div class="${outCls}" id="code-out">${ST.out?esc(ST.out):'▶ Code ausführen...'}</div>
</div>
${hintHtml}
</div>
<div class="fl ic g3">
<button class="btn btn-gr" onclick="executeCode()">▶ Ausführen</button>
${hintBtn}
${ST.out&&!ST.oerr?`<button class="btn btn-cy" onclick="nextPhase();awardXP(20,'Übung')">Weiter +20 XP →</button>`:''}
</div>
<div id="input-overlay"></div>`;
}
function renderTest(mis){
const ans=mis.q.a.map((a,i)=>{
let cls='ans-btn';
if(ST.tdone){ if(i===mis.q.r) cls+=' cor'; else if(ST.tcor===false&&i!==mis.q.r) cls+=' '; }
return `<button class="${cls}" onclick="answerTest(${i})">${esc(a)}</button>`;
}).join('');
const fb=ST.tdone?`<div class="card mt3" style="border-color:${ST.tcor?'var(--gr)':'var(--rd)'}">
<div style="color:${ST.tcor?'var(--gr)':'var(--rd)'};font-weight:800;margin-bottom:.5rem">${ST.tcor?'✅ Richtig!':'❌ Nicht ganz...'}</div>
<div class="sm">${esc(mis.q.ok)}</div>
${ST.tcor?'':`<button class="btn btn-gh btn-sm mt3" onclick="resetTest()">Nochmal versuchen</button>`}
</div>`:'';
return `
<div class="card mb4">
<h3 class="fw8 mb3" style="font-size:1rem;color:var(--pu)">❓ Mini-Test</h3>
<p class="fw7 mb3">${esc(mis.q.q)}</p>
${ans}
${fb}
</div>
${ST.tdone&&ST.tcor?`<button class="btn btn-go" onclick="nextPhase();awardXP(20,'Test bestanden')">Weiter +20 XP →</button>`:''}`;
}
function renderDone(mis){
const xpEarned=ST.sxp;
const lv=getLev(ST.xp);
return `
<div class="result-card mb4">
<div style="font-size:3rem;margin-bottom:.75rem">${mis.e}</div>
<h2 class="fw8 mb1" style="font-size:1.4rem">Mission abgeschlossen, Raphael!</h2>
<p class="mb3" style="color:var(--go-d);font-weight:700">${mis.t}</p>
<div style="font-size:2rem;font-weight:900;color:var(--go);margin-bottom:.5rem">+${xpEarned} XP</div>
<div style="font-size:.875rem;font-weight:700">Du bist jetzt: ${lv.i} ${lv.n}</div>
</div>
<div class="fl ic g3">
<button class="btn btn-cy" onclick="goHome()">← Alle Missionen</button>
${ST.mid<16?`<button class="btn btn-go" onclick="openMission(${ST.mid+1})">Nächste Mission →</button>`:''}
</div>
${ST.mid===16?confettiHTML():''}`;
}
function confettiHTML(){
const colors=['#FFC107','#4FC3F7','#66BB6A','#CE93D8','#EF5350'];
let bits='';
for(let i=0;i<40;i++){
const c=colors[i%colors.length];
const x=Math.random()*100; const d=Math.random()*3+1; const del=Math.random()*2;
bits+=`<div style="position:fixed;left:${x}%;top:-40px;width:8px;height:8px;background:${c};border-radius:2px;animation:conf ${d}s ${del}s ease forwards;z-index:900"></div>`;
}
return bits;
}
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// EVENTS
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
function handleTab(e){
if(e.key==='Tab'){
e.preventDefault();
const t=e.target; const s=t.selectionStart;
t.value=t.value.substring(0,s)+' '+t.value.substring(t.selectionEnd);
t.selectionStart=t.selectionEnd=s+4;
saveDraft(t.value);
}
}
function openMission(id){
if(!isUnlocked(id)) return;
const mis=getMission(id); if(!mis) return;
ST.mid=id; ST.phase=0; ST.sxp=0; ST.hi=-1;
ST.code=ST.drafts[id]||mis.u.c; ST.out=''; ST.oerr=false;
ST.tdone=false; ST.tcor=false; ST.view='mission';
render();
}
function goHome(){ ST.view='home'; render(); }
function nextPhase(){
if(ST.phase===1&&ST.view==='mission'){
const mis=getMission(ST.mid);
ST.code=ST.drafts[ST.mid]||mis.u.c; ST.out=''; ST.oerr=false; ST.hi=-1;
}
ST.phase++;
if(ST.phase===4) completeMiss(ST.mid);
render();
}
function showHint(){
ST.hi=Math.min(ST.hi+1, getMission(ST.mid).u.h.length-1);
renderPhaseBody();
}
function saveDraft(value){
ST.code=value;
if(ST.mid) ST.drafts[ST.mid]=value;
saveProg();
}
function resetProgress(){
if(!confirm('Fortschritt, Abzeichen und gespeicherte Code-Entwürfe wirklich löschen?')) return;
ST.xp=0; ST.done={}; ST.badges=[]; ST.drafts={}; ST.code=''; ST.out=''; ST.oerr=false;
saveProg();
ST.view='home'; ST.mid=null; ST.phase=0;
render();
}
function answerTest(idx){
if(ST.tdone) return;
const mis=getMission(ST.mid);
ST.tdone=true; ST.tcor=(idx===mis.q.r);
renderPhaseBody();
}
function resetTest(){
ST.tdone=false; ST.tcor=false;
renderPhaseBody();
}
async function executeCode(){
const ta=document.getElementById('code-ta');
const code=ta?ta.value:ST.code;
ST.code=code;
const prompts=parseInputs(code);
if(prompts.length>0){
showInputDialog(prompts,code);
return;
}
await doRunCode(code,[]);
}
async function doRunCode(code,inputs){
const outEl=document.getElementById('code-out');
if(outEl){ outEl.className='cout'; outEl.textContent='▶ Wird ausgeführt...'; }
const {out,err}=await runPy(code,inputs);
ST.out=out; ST.oerr=err;
renderPhaseBody();
}
function showInputDialog(prompts,code){
const fields=prompts.map((p,i)=>`
<label style="font-size:.85rem;color:var(--mu);display:block;margin-top:.5rem">${esc(p)}</label>
<input id="inp-${i}" placeholder="Raphael tippt hier..." />
`).join('');
const overlay=document.getElementById('input-overlay');
overlay.innerHTML=`<div class="overlay">
<div class="dlg">
<h3 class="fw8 mb2 cy">⌨️ Python fragt Raphael:</h3>
<p class="sm mu mb3">Dein Programm benutzt input() gib hier die Antworten ein:</p>
${fields}
<div class="fl ic g3 mt3">
<button class="btn btn-cy" onclick='submitInputs(${JSON.stringify(encodeURIComponent(code))},${prompts.length})'>▶ Ausführen</button>
<button class="btn btn-gh" onclick="document.getElementById('input-overlay').innerHTML=''">Abbrechen</button>
</div>
</div>
</div>`;
}
async function submitInputs(encodedCode,count){
const code=decodeURIComponent(encodedCode);
const inputs=[];
for(let i=0;i<count;i++){
const el=document.getElementById('inp-'+i);
inputs.push(el?el.value:'');
}
document.getElementById('input-overlay').innerHTML='';
await doRunCode(code,inputs);
}
function renderPhaseBody(){
const mis=getMission(ST.mid); if(!mis) return;
const ph=ST.phase;
const el=document.getElementById('phase-body'); if(!el) return;
if(ph===0) el.innerHTML=renderStory(mis);
else if(ph===1) el.innerHTML=renderLesen(mis);
else if(ph===2) el.innerHTML=renderUeben(mis);
else if(ph===3) el.innerHTML=renderTest(mis);
else el.innerHTML=renderDone(mis);
updPySt();
const ta=document.getElementById('code-ta');
if(ta){ ta.value=ST.code; }
}
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// PROFESSOR PYTHON
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
function renderProf(){
const layer=document.getElementById('prof-layer'); if(!layer) return;
const mis=ST.mid?getMission(ST.mid):null;
const msgs=ST.profMsgs.map(m=>`<div class="pmsg ${m.r}">${esc(m.t)}</div>`).join('');
layer.innerHTML=`
<button class="prof-fab" onclick="toggleProf()" title="Professor Python fragen">🤖</button>
${ST.profOpen?`<div class="prof-panel">
<div class="fl ic jb" style="padding:.875rem 1rem .5rem;border-bottom:1px solid var(--bd)">
<span class="fw8 pu" style="font-size:.9rem">🤖 Professor Python</span>
<button class="btn btn-gh btn-sm" onclick="toggleProf()">✕</button>
</div>
<div class="prof-msgs" id="prof-msgs">
${ST.profMsgs.length===0?'<div class="pmsg b">Hallo Raphael! 👋 Ich bin Professor Python.\nFrag mich alles ich helfe dir (aber gebe keine direkten Lösungen 😄)</div>':''}
${msgs}
</div>
<div style="padding:.75rem;border-top:1px solid var(--bd);display:flex;gap:.5rem">
<input id="prof-in" placeholder="Frage an Professor Python..." style="flex:1;background:var(--cb);border:1px solid var(--bd);border-radius:8px;padding:.5rem .75rem;color:var(--tx);font-family:Nunito,Inter,Segoe UI,system-ui,sans-serif;font-size:.85rem;outline:none" onkeydown="if(event.key==='Enter')sendProf()">
<button class="btn btn-pu btn-sm" onclick="sendProf()">Senden</button>
</div>
</div>`:''}`;
const msgs_el=document.getElementById('prof-msgs');
if(msgs_el) msgs_el.scrollTop=msgs_el.scrollHeight;
}
function toggleProf(){ ST.profOpen=!ST.profOpen; renderProf(); }
async function sendProf(){
const inp=document.getElementById('prof-in'); if(!inp) return;
const q=inp.value.trim(); if(!q) return;
inp.value='';
ST.profMsgs.push({r:'u',t:q});
const mis=ST.mid?getMission(ST.mid):null;
ST.profMsgs.push({r:'b',t:getMentorReply(q,mis)});
renderProf();
}
function getMentorReply(question,mis){
const q=question.toLowerCase();
const prefix=mis?`Raphael, in Mission ${mis.id} geht es um ${mis.s}. `:'Raphael, ';
if(q.includes('fehler')||q.includes('error')) return prefix+'lies die erste rote Zeile langsam: Sie sagt meist, ob eine Klammer, ein Name oder eine Einrückung fehlt. Ändere immer nur eine Sache und starte dann neu.';
if(q.includes('input')) return prefix+'input() fragt den Nutzer nach Text. Wenn du damit rechnen willst, wandle die Eingabe mit int(...) in eine Zahl um.';
if(q.includes('if')||q.includes('else')) return prefix+'prüfe zuerst die Bedingung wie eine Ja/Nein-Frage. Der eingerückte Block darunter läuft nur, wenn diese Antwort passt.';
if(q.includes('schleife')||q.includes('range')||q.includes('for')) return prefix+'eine for-Schleife wiederholt denselben eingerückten Code. range(5) liefert 0 bis 4, also fünf Durchläufe.';
if(q.includes('liste')||q.includes('append')) return prefix+'eine Liste sammelt mehrere Werte in eckigen Klammern. Mit append(...) hängst du einen neuen Wert hinten an.';
if(q.includes('funktion')||q.includes('def')) return prefix+'eine Funktion ist ein eigener Befehl. def erstellt ihn, und mit name() rufst du ihn später auf.';
if(q.includes('lösung')||q.includes('fertig')) return prefix+'ich gebe dir nicht die komplette Lösung, aber nimm den Beispielcode links als Muster und ändere nur Namen, Zahlen oder Texte für deine Aufgabe.';
return prefix+'starte mit dem kleinsten nächsten Schritt: Beispiel ansehen, eine Zeile schreiben, ausführen, Ergebnis lesen. Wenn etwas rot wird, ist das nur ein Hinweis zum Verbessern.';
}
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// MAIN RENDER
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
function render(){
const app=document.getElementById('app');
if(ST.view==='home') app.innerHTML=renderHome();
else app.innerHTML=renderMission();
updXPBar(); updPySt(); renderProf();
}
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// INIT
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
loadProg().then(()=>{ render(); initPy(); });
</script>
</body>
</html>