feat(ux): implement modern design tokens, resizable workspace splitters, visual test diffs, and engineering competency model
This commit is contained in:
parent
a86d718aa1
commit
c0635da6cc
8 changed files with 1039 additions and 259 deletions
|
|
@ -10,5 +10,5 @@
|
|||
"streak_tage": 1,
|
||||
"letzter_aktiver_tag": "2026-09-12",
|
||||
"historie": [],
|
||||
"zuletzt_aktualisiert": "2026-09-12T13:29:30.562924"
|
||||
"zuletzt_aktualisiert": "2026-09-15T15:18:26.060048"
|
||||
}
|
||||
|
|
@ -6,33 +6,33 @@
|
|||
|
||||
(function () {
|
||||
const DEFAULT_LEVELS = [
|
||||
{ level: 1, min_xp: 0, max_xp: 150, titel: "Code-Küken 🐣", rang: "Bronze I" },
|
||||
{ level: 2, min_xp: 151, max_xp: 400, titel: "Code-Detektiv 🔍", rang: "Bronze II" },
|
||||
{ level: 3, min_xp: 401, max_xp: 800, titel: "Logik-Bastler ⚙️", rang: "Silber I" },
|
||||
{ level: 4, min_xp: 801, max_xp: 1300, titel: "Klassen-Baumeister 🏗️", rang: "Silber II" },
|
||||
{ level: 5, min_xp: 1301, max_xp: 2000, titel: "Git-Stratege 🌿", rang: "Gold I" },
|
||||
{ level: 6, min_xp: 2001, max_xp: 2900, titel: "TDD-Qualitätsprüfer 🧪", rang: "Gold II" },
|
||||
{ level: 7, min_xp: 2901, max_xp: 4000, titel: "Vererbungs-Meister 🧬", rang: "Platin I" },
|
||||
{ level: 8, min_xp: 4001, max_xp: 5300, titel: "Exception-Wächter 🛡️", rang: "Platin II" },
|
||||
{ level: 9, min_xp: 5301, max_xp: 6800, titel: "GUI-Entwickler 🖥️", rang: "Diamant" },
|
||||
{ level: 10, min_xp: 6801, max_xp: 99999, titel: "Software-Architekt 🏆", rang: "Großmeister" }
|
||||
{ level: 1, min_xp: 0, max_xp: 150, titel: "Junior IT-Trainee I: Syntax & Basistypen", rang: "Stufe 1" },
|
||||
{ level: 2, min_xp: 151, max_xp: 400, titel: "Junior IT-Trainee II: Kontrollfluss & Logik", rang: "Stufe 2" },
|
||||
{ level: 3, min_xp: 401, max_xp: 800, titel: "IT-Specialist: Funktionen & Datenstrukturen", rang: "Stufe 3" },
|
||||
{ level: 4, min_xp: 801, max_xp: 1300, titel: "Software Developer: OOP-Modellierung", rang: "Stufe 4" },
|
||||
{ level: 5, min_xp: 1301, max_xp: 2000, titel: "DevOps Engineer: Versionskontrolle & Tooling", rang: "Stufe 5" },
|
||||
{ level: 6, min_xp: 2001, max_xp: 2900, titel: "Quality Engineer: Test-Driven Development (TDD)", rang: "Stufe 6" },
|
||||
{ level: 7, min_xp: 2901, max_xp: 4000, titel: "Advanced Engineer: Entwurfsmuster & Polymorphie", rang: "Stufe 7" },
|
||||
{ level: 8, min_xp: 4001, max_xp: 5300, titel: "Systems Engineer: Security & Error Handling", rang: "Stufe 8" },
|
||||
{ level: 9, min_xp: 5301, max_xp: 6800, titel: "Application Engineer: Schnittstellen & UI", rang: "Stufe 9" },
|
||||
{ level: 10, min_xp: 6801, max_xp: 99999, titel: "Certified IT Systems Professional (IHK AP2)", rang: "Master-Stufe" }
|
||||
];
|
||||
|
||||
const DEFAULT_BADGES = {
|
||||
rechenkonegin: { icon: "🧮", name: "Rechenkönig", desc: "Grundlagen G01 & G02 abgeschlossen (Zahlen, Operatoren & Typen gemeistert)." },
|
||||
dialog_profi: { icon: "💬", name: "Dialog-Profi", desc: "Grundlagen G03 abgeschlossen (Interaktive Ein-/Ausgabe mit f-Strings)." },
|
||||
weichenstellerin: { icon: "🚦", name: "Weichensteller", desc: "Grundlagen G04 abgeschlossen (Bedingungslogik & Schaltjahre gemeistert)." },
|
||||
schleifen_dompteurin: { icon: "🎡", name: "Schleifen-Dompteur", desc: "Grundlagen G05 abgeschlossen (While- & For-Schleifen gebändigt)." },
|
||||
funktions_zauberin: { icon: "🪄", name: "Funktions-Zauberer", desc: "Grundlagen G06 abgeschlossen (Eigene Funktionen & Module gebaut)." },
|
||||
listen_archivarin: { icon: "📋", name: "Listen-Archivar", desc: "Grundlagen G07 & G08 abgeschlossen (Sequenzen & Strings analysiert)." },
|
||||
daten_strategin: { icon: "🗃️", name: "Daten-Stratege", desc: "Grundlagen G09 & G10 abgeschlossen (Dicts, Sets & Comprehensions gemeistert)." },
|
||||
bug_jaegerin: { icon: "🔍", name: "Bug-Jäger", desc: "Kapitel 00 gelöst (Alle Fehler-Bugs im Warm-up aufgespürt)." },
|
||||
erste_architektin: { icon: "🏗️", name: "Erster Architekt", desc: "Kapitel 01 bis 03 gelöst (Erste OOP-Klassen, Konstruktoren & Methoden)." },
|
||||
zeit_reisende: { icon: "🌿", name: "Git-Zeitreisender", desc: "Kapitel 04c Git-Versionskontrolle verstanden & Spielstände gesichert." },
|
||||
tamagotchi_mama: { icon: "🥚", name: "Tamagotchi-Hüter", desc: "Kapitel 06 Mini-Projekt abgeschlossen (Ein lebendiges Haustier gebaut)." },
|
||||
qualitaets_garantin: { icon: "🧪", name: "TDD-Qualitätsgarant", desc: "Kapitel 09 abgeschlossen (Eigene professionelle Unit Tests geschrieben)." },
|
||||
oop_grossmeisterin: { icon: "🧬", name: "Vererbungs-Koryphäe", desc: "Kapitel 10 bis 12 abgeschlossen (Vererbung, Polymorphie & Exceptions)." },
|
||||
software_architektin: { icon: "🏆", name: "Meister-Entwickler", desc: "Kapitel 16 Master-Projekt abgeschlossen & vollwertige Desktop-App gebaut!" }
|
||||
rechenkonegin: { icon: "🧮", name: "Zertifikat: Numerische Typen & Arithmetik", desc: "Grundlagen G01 & G02 abgeschlossen (Zahlen, Operatoren & Typen gemeistert)." },
|
||||
dialog_profi: { icon: "💬", name: "Zertifikat: Interaktive I/O & String-Interpolation", desc: "Grundlagen G03 abgeschlossen (Interaktive Ein-/Ausgabe mit f-Strings)." },
|
||||
weichenstellerin: { icon: "🚦", name: "Zertifikat: Verzweigungslogik & Boolesche Algebra", desc: "Grundlagen G04 abgeschlossen (Bedingungslogik & Kontrollfluss gemeistert)." },
|
||||
schleifen_dompteurin: { icon: "🎡", name: "Zertifikat: Iterationslogik & Sequenzverarbeitung", desc: "Grundlagen G05 abgeschlossen (While- & For-Schleifen gemeistert)." },
|
||||
funktions_zauberin: { icon: "📐", name: "Zertifikat: Modulare Funktionsarchitektur", desc: "Grundlagen G06 abgeschlossen (Modulare Funktionen & Parameterübergabe)." },
|
||||
listen_archivarin: { icon: "📋", name: "Zertifikat: Datenstrukturen: Listen & Slicing", desc: "Grundlagen G07 & G08 abgeschlossen (Sequenzen, Listen & Slicing analysiert)." },
|
||||
daten_strategin: { icon: "🗃️", name: "Zertifikat: Assoziative Arrays: Dicts & Sets", desc: "Grundlagen G09 & G10 abgeschlossen (Dicts, Sets & Hash-Lookup O(1))." },
|
||||
bug_jaegerin: { icon: "🔍", name: "Zertifikat: Systematisches Code-Debugging", desc: "Warm-up abgeschlossen (Fehleranalyse & Traceback-Debugging)." },
|
||||
erste_architektin: { icon: "🏗️", name: "Zertifikat: OOP-Grundlagen: Klassen & Kapselung", desc: "OOP 01 bis 03 gelöst (Klassendefinition, Konstruktoren & Instanzmethoden)." },
|
||||
zeit_reisende: { icon: "🌿", name: "Zertifikat: String-Repräsentation & Dunder-Methoden", desc: "OOP 04 gelöst (Spezielle Methoden __str__, __repr__ implementiert)." },
|
||||
tamagotchi_mama: { icon: "📦", name: "Zertifikat: State-Machine & Domänenmodellierung", desc: "OOP Mini-Projekt abgeschlossen (Zustandsverwaltung & Objektinteraktion)." },
|
||||
qualitaets_garantin: { icon: "🧪", name: "Zertifikat: Automatisierte Testsuites & TDD", desc: "OOP 09 abgeschlossen (Eigene professionelle Unit Tests mit Assertions)." },
|
||||
oop_grossmeisterin: { icon: "🧬", name: "Zertifikat: Fortgeschrittene OOP & Robustes Error-Handling", desc: "OOP 10 bis 12 abgeschlossen (Vererbung, Polymorphie & Exceptions)." },
|
||||
software_architektin: { icon: "🏆", name: "Zertifikat: Master-Projekt Software Engineering", desc: "Master-Projekt abgeschlossen & vollwertige Anwendungsarchitektur gebaut!" }
|
||||
};
|
||||
|
||||
function getGamificationData() {
|
||||
|
|
@ -47,8 +47,8 @@
|
|||
spieler_name: "Python-Entwickler",
|
||||
xp: 0,
|
||||
level: 1,
|
||||
titel: "Code-Küken 🐣",
|
||||
rang: "Bronze I",
|
||||
titel: "Junior IT-Trainee I: Syntax & Basistypen",
|
||||
rang: "Stufe 1",
|
||||
geloeste_kapitel: [],
|
||||
bestandene_tests: 0,
|
||||
freigeschaltete_badges: [],
|
||||
|
|
@ -112,7 +112,8 @@
|
|||
const hud = document.getElementById("gamification-hud");
|
||||
if (!hud) return;
|
||||
|
||||
const avatarIcon = currentLevelObj.titel.split(" ").pop() || "🐣";
|
||||
const avatarIcons = ["💻", "🔍", "⚙️", "🏗️", "🌿", "🧪", "🧬", "🛡️", "🖥️", "🏆"];
|
||||
const avatarIcon = avatarIcons[data.level - 1] || "💻";
|
||||
const solvedCount = (data.geloeste_kapitel || []).length;
|
||||
const totalChapters = 27;
|
||||
|
||||
|
|
|
|||
|
|
@ -11,13 +11,13 @@ window.GAMIFICATION_DATA = {
|
|||
"streak_tage": 1,
|
||||
"letzter_aktiver_tag": "2026-09-12",
|
||||
"historie": [],
|
||||
"zuletzt_aktualisiert": "2026-09-12T13:29:30.562924"
|
||||
"zuletzt_aktualisiert": "2026-09-15T15:18:26.060048"
|
||||
};
|
||||
window.GAMIFICATION_BADGES_KATALOG = {
|
||||
"rechenkonegin": {
|
||||
"id": "rechenkonegin",
|
||||
"icon": "🧮",
|
||||
"name": "Rechenkönigin",
|
||||
"name": "Zertifikat: Numerische Typen & Arithmetik",
|
||||
"desc": "Grundlagen 01 & 02 abgeschlossen (Zahlen, Operatoren & Typen gemeistert).",
|
||||
"xp_bonus": 100,
|
||||
"kriterium": [
|
||||
|
|
@ -28,7 +28,7 @@ window.GAMIFICATION_BADGES_KATALOG = {
|
|||
"dialog_profi": {
|
||||
"id": "dialog_profi",
|
||||
"icon": "💬",
|
||||
"name": "Dialog-Profi",
|
||||
"name": "Zertifikat: Interaktive I/O & String-Interpolation",
|
||||
"desc": "Grundlagen 03 abgeschlossen (Interaktive Ein-/Ausgabe mit f-Strings).",
|
||||
"xp_bonus": 75,
|
||||
"kriterium": [
|
||||
|
|
@ -38,8 +38,8 @@ window.GAMIFICATION_BADGES_KATALOG = {
|
|||
"weichenstellerin": {
|
||||
"id": "weichenstellerin",
|
||||
"icon": "🚦",
|
||||
"name": "Weichenstellerin",
|
||||
"desc": "Grundlagen 04 abgeschlossen (Bedingungslogik & Schaltjahre gemeistert).",
|
||||
"name": "Zertifikat: Verzweigungslogik & Boolesche Algebra",
|
||||
"desc": "Grundlagen 04 abgeschlossen (Bedingungslogik & Kontrollfluss gemeistert).",
|
||||
"xp_bonus": 75,
|
||||
"kriterium": [
|
||||
"lehrpfad_1_grundlagen/04_verzweigungen_und_bedingungen"
|
||||
|
|
@ -48,8 +48,8 @@ window.GAMIFICATION_BADGES_KATALOG = {
|
|||
"schleifen_dompteurin": {
|
||||
"id": "schleifen_dompteurin",
|
||||
"icon": "🎡",
|
||||
"name": "Schleifen-Dompteurin",
|
||||
"desc": "Grundlagen 05 abgeschlossen (While- & For-Schleifen gebändigt).",
|
||||
"name": "Zertifikat: Iterationslogik & Sequenzverarbeitung",
|
||||
"desc": "Grundlagen 05 abgeschlossen (While- & For-Schleifen gemeistert).",
|
||||
"xp_bonus": 100,
|
||||
"kriterium": [
|
||||
"lehrpfad_1_grundlagen/05_schleifen_und_wiederholungen"
|
||||
|
|
@ -57,9 +57,9 @@ window.GAMIFICATION_BADGES_KATALOG = {
|
|||
},
|
||||
"funktions_zauberin": {
|
||||
"id": "funktions_zauberin",
|
||||
"icon": "🪄",
|
||||
"name": "Funktions-Zauberin",
|
||||
"desc": "Grundlagen 06 abgeschlossen (Eigene Funktionen & Module gebaut).",
|
||||
"icon": "📐",
|
||||
"name": "Zertifikat: Modulare Funktionsarchitektur",
|
||||
"desc": "Grundlagen 06 abgeschlossen (Modulare Funktionen & Parameterübergabe).",
|
||||
"xp_bonus": 100,
|
||||
"kriterium": [
|
||||
"lehrpfad_1_grundlagen/06_funktionen_und_module"
|
||||
|
|
@ -68,8 +68,8 @@ window.GAMIFICATION_BADGES_KATALOG = {
|
|||
"listen_archivarin": {
|
||||
"id": "listen_archivarin",
|
||||
"icon": "📋",
|
||||
"name": "Listen-Archivarin",
|
||||
"desc": "Grundlagen 07 & 08 abgeschlossen (Sequenzen & Strings analysiert).",
|
||||
"name": "Zertifikat: Datenstrukturen: Listen & Slicing",
|
||||
"desc": "Grundlagen 07 & 08 abgeschlossen (Sequenzen, Listen & Slicing analysiert).",
|
||||
"xp_bonus": 120,
|
||||
"kriterium": [
|
||||
"lehrpfad_1_grundlagen/07_listen_und_sequenzen",
|
||||
|
|
@ -79,8 +79,8 @@ window.GAMIFICATION_BADGES_KATALOG = {
|
|||
"daten_strategin": {
|
||||
"id": "daten_strategin",
|
||||
"icon": "🗃️",
|
||||
"name": "Daten-Strategin",
|
||||
"desc": "Grundlagen 09 & 10 abgeschlossen (Dicts, Sets & Comprehensions gemeistert).",
|
||||
"name": "Zertifikat: Assoziative Arrays: Dicts & Sets",
|
||||
"desc": "Grundlagen 09 & 10 abgeschlossen (Dictionaries, Sets & Hash-Lookup O(1)).",
|
||||
"xp_bonus": 150,
|
||||
"kriterium": [
|
||||
"lehrpfad_1_grundlagen/09_dictionaries_und_sets",
|
||||
|
|
@ -90,8 +90,8 @@ window.GAMIFICATION_BADGES_KATALOG = {
|
|||
"bug_jaegerin": {
|
||||
"id": "bug_jaegerin",
|
||||
"icon": "🔍",
|
||||
"name": "Bug-Jägerin",
|
||||
"desc": "Kapitel 00 gelöst (Alle Fehler-Bugs im Warm-up aufgespürt).",
|
||||
"name": "Zertifikat: Systematisches Code-Debugging",
|
||||
"desc": "Warm-up abgeschlossen (Fehleranalyse & Traceback-Debugging).",
|
||||
"xp_bonus": 100,
|
||||
"kriterium": [
|
||||
"lehrpfad_2_oop_einstieg/00_fehlersuche_und_grundlagen"
|
||||
|
|
@ -100,8 +100,8 @@ window.GAMIFICATION_BADGES_KATALOG = {
|
|||
"erste_architektin": {
|
||||
"id": "erste_architektin",
|
||||
"icon": "🏗️",
|
||||
"name": "Erste Architektin",
|
||||
"desc": "Kapitel 01 bis 03 gelöst (Erste OOP-Klassen, Konstruktoren & Methoden).",
|
||||
"name": "Zertifikat: OOP-Grundlagen: Klassen & Kapselung",
|
||||
"desc": "OOP 01 bis 03 gelöst (Klassendefinition, Konstruktoren & Instanzmethoden).",
|
||||
"xp_bonus": 200,
|
||||
"kriterium": [
|
||||
"lehrpfad_2_oop_einstieg/01_einstieg_klassen",
|
||||
|
|
@ -111,9 +111,9 @@ window.GAMIFICATION_BADGES_KATALOG = {
|
|||
},
|
||||
"tamagotchi_mama": {
|
||||
"id": "tamagotchi_mama",
|
||||
"icon": "🥚",
|
||||
"name": "Tamagotchi-Mama",
|
||||
"desc": "Kapitel 06 Mini-Projekt abgeschlossen (Ein lebendiges Haustier gebaut).",
|
||||
"icon": "📦",
|
||||
"name": "Zertifikat: State-Machine & Domänenmodellierung",
|
||||
"desc": "OOP Mini-Projekt abgeschlossen (Zustandsverwaltung & Objektinteraktion).",
|
||||
"xp_bonus": 250,
|
||||
"kriterium": [
|
||||
"lehrpfad_2_oop_einstieg/06_abschlussprojekt_tamagotchi"
|
||||
|
|
@ -122,8 +122,8 @@ window.GAMIFICATION_BADGES_KATALOG = {
|
|||
"zeit_reisende": {
|
||||
"id": "zeit_reisende",
|
||||
"icon": "🌿",
|
||||
"name": "Git-Zeitreisende",
|
||||
"desc": "Kapitel 04c Git-Versionskontrolle verstanden & Spielstände gesichert.",
|
||||
"name": "Zertifikat: String-Repräsentation & Dunder-Methoden",
|
||||
"desc": "OOP 04 gelöst (Spezielle Methoden __str__, __repr__ implementiert).",
|
||||
"xp_bonus": 100,
|
||||
"kriterium": [
|
||||
"lehrpfad_2_oop_einstieg/04_str_und_darstellung"
|
||||
|
|
@ -132,8 +132,8 @@ window.GAMIFICATION_BADGES_KATALOG = {
|
|||
"qualitaets_garantin": {
|
||||
"id": "qualitaets_garantin",
|
||||
"icon": "🧪",
|
||||
"name": "TDD-Qualitätsgarantin",
|
||||
"desc": "Kapitel 09 abgeschlossen (Eigene professionelle Unit Tests geschrieben).",
|
||||
"name": "Zertifikat: Automatisierte Testsuites & TDD",
|
||||
"desc": "OOP 09 abgeschlossen (Eigene professionelle Unit Tests mit Assertions).",
|
||||
"xp_bonus": 200,
|
||||
"kriterium": [
|
||||
"lehrpfad_3_fortgeschrittenes_oop/09_eigene_unit_tests_schreiben"
|
||||
|
|
@ -142,8 +142,8 @@ window.GAMIFICATION_BADGES_KATALOG = {
|
|||
"oop_grossmeisterin": {
|
||||
"id": "oop_grossmeisterin",
|
||||
"icon": "🧬",
|
||||
"name": "Vererbungs-Koryphäe",
|
||||
"desc": "Kapitel 10 bis 12 abgeschlossen (Vererbung, Polymorphie & Exceptions).",
|
||||
"name": "Zertifikat: Fortgeschrittene OOP & Robustes Error-Handling",
|
||||
"desc": "OOP 10 bis 12 abgeschlossen (Vererbung, Polymorphie & Exceptions).",
|
||||
"xp_bonus": 300,
|
||||
"kriterium": [
|
||||
"lehrpfad_3_fortgeschrittenes_oop/10_vererbung_und_super",
|
||||
|
|
@ -154,8 +154,8 @@ window.GAMIFICATION_BADGES_KATALOG = {
|
|||
"software_architektin": {
|
||||
"id": "software_architektin",
|
||||
"icon": "🏆",
|
||||
"name": "Meister-Entwicklerin",
|
||||
"desc": "Kapitel 16 Master-Projekt abgeschlossen & vollwertige Desktop-App gebaut!",
|
||||
"name": "Zertifikat: Master-Projekt Software Engineering",
|
||||
"desc": "Master-Projekt abgeschlossen & vollwertige Anwendungsarchitektur gebaut!",
|
||||
"xp_bonus": 500,
|
||||
"kriterium": [
|
||||
"lehrpfad_3_fortgeschrittenes_oop/16_master_abschlussprojekt"
|
||||
|
|
@ -167,70 +167,70 @@ window.GAMIFICATION_LEVEL_STUFEN = [
|
|||
"level": 1,
|
||||
"min_xp": 0,
|
||||
"max_xp": 150,
|
||||
"titel": "Code-Küken 🐣",
|
||||
"rang": "Bronze I"
|
||||
"titel": "Junior IT-Trainee I: Syntax & Basistypen",
|
||||
"rang": "Stufe 1"
|
||||
},
|
||||
{
|
||||
"level": 2,
|
||||
"min_xp": 151,
|
||||
"max_xp": 400,
|
||||
"titel": "Code-Detektivin 🔍",
|
||||
"rang": "Bronze II"
|
||||
"titel": "Junior IT-Trainee II: Kontrollfluss & Logik",
|
||||
"rang": "Stufe 2"
|
||||
},
|
||||
{
|
||||
"level": 3,
|
||||
"min_xp": 401,
|
||||
"max_xp": 800,
|
||||
"titel": "Logik-Bastlerin ⚙️",
|
||||
"rang": "Silber I"
|
||||
"titel": "IT-Specialist: Funktionen & Datenstrukturen",
|
||||
"rang": "Stufe 3"
|
||||
},
|
||||
{
|
||||
"level": 4,
|
||||
"min_xp": 801,
|
||||
"max_xp": 1300,
|
||||
"titel": "Klassen-Baumeisterin 🏗️",
|
||||
"rang": "Silber II"
|
||||
"titel": "Software Developer: OOP-Modellierung",
|
||||
"rang": "Stufe 4"
|
||||
},
|
||||
{
|
||||
"level": 5,
|
||||
"min_xp": 1301,
|
||||
"max_xp": 2000,
|
||||
"titel": "Git-Strategin 🌿",
|
||||
"rang": "Gold I"
|
||||
"titel": "DevOps Engineer: Versionskontrolle & Tooling",
|
||||
"rang": "Stufe 5"
|
||||
},
|
||||
{
|
||||
"level": 6,
|
||||
"min_xp": 2001,
|
||||
"max_xp": 2900,
|
||||
"titel": "TDD-Qualitätsprüferin 🧪",
|
||||
"rang": "Gold II"
|
||||
"titel": "Quality Engineer: Test-Driven Development (TDD)",
|
||||
"rang": "Stufe 6"
|
||||
},
|
||||
{
|
||||
"level": 7,
|
||||
"min_xp": 2901,
|
||||
"max_xp": 4000,
|
||||
"titel": "Vererbungs-Meisterin 🧬",
|
||||
"rang": "Platin I"
|
||||
"titel": "Advanced Engineer: Entwurfsmuster & Polymorphie",
|
||||
"rang": "Stufe 7"
|
||||
},
|
||||
{
|
||||
"level": 8,
|
||||
"min_xp": 4001,
|
||||
"max_xp": 5300,
|
||||
"titel": "Exception-Wächterin 🛡️",
|
||||
"rang": "Platin II"
|
||||
"titel": "Systems Engineer: Security & Error Handling",
|
||||
"rang": "Stufe 8"
|
||||
},
|
||||
{
|
||||
"level": 9,
|
||||
"min_xp": 5301,
|
||||
"max_xp": 6800,
|
||||
"titel": "GUI-Entwicklerin 🖥️",
|
||||
"rang": "Diamant"
|
||||
"titel": "Application Engineer: Schnittstellen & UI",
|
||||
"rang": "Stufe 9"
|
||||
},
|
||||
{
|
||||
"level": 10,
|
||||
"min_xp": 6801,
|
||||
"max_xp": 99999,
|
||||
"titel": "Software-Architektin 🏆",
|
||||
"rang": "Großmeisterin"
|
||||
"titel": "Certified IT Systems Professional (IHK AP2)",
|
||||
"rang": "Master-Stufe"
|
||||
}
|
||||
];
|
||||
|
|
|
|||
|
|
@ -184,6 +184,101 @@
|
|||
suggestErrorInterpreter(result.rawOutput);
|
||||
}
|
||||
}
|
||||
|
||||
// Strukturierten visuellen Test-Report mit Diff-Inspektor rendern
|
||||
if (result && (result.total > 0 || result.rawOutput)) {
|
||||
renderVisualTestReport(result);
|
||||
}
|
||||
}
|
||||
|
||||
window.jumpToEditorLine = function(line) {
|
||||
if (monacoEditor && line) {
|
||||
monacoEditor.revealLineInCenter(line);
|
||||
monacoEditor.setPosition({ lineNumber: line, column: 1 });
|
||||
monacoEditor.focus();
|
||||
}
|
||||
};
|
||||
|
||||
function renderVisualTestReport(result) {
|
||||
const term = document.getElementById("workspace-terminal-output");
|
||||
if (!term) return;
|
||||
|
||||
const cases = [];
|
||||
const raw = result.rawOutput || "";
|
||||
|
||||
const lineRegex = /(test_\w+)\s+\([^)]+\)\s+\.\.\.\s+(ok|FAIL|ERROR)/g;
|
||||
let match;
|
||||
while ((match = lineRegex.exec(raw)) !== null) {
|
||||
cases.push({
|
||||
name: match[1],
|
||||
passed: match[2] === "ok",
|
||||
status: match[2]
|
||||
});
|
||||
}
|
||||
|
||||
const fileLineMatch = raw.match(/File\s+["'].*?aufgabe\.\w+["'],\s+line\s+(\d+)/i);
|
||||
const errorLine = fileLineMatch ? parseInt(fileLineMatch[1], 10) : null;
|
||||
|
||||
let expectedVal = null;
|
||||
let actualVal = null;
|
||||
const assertEqualMatch = raw.match(/AssertionError:\s*(.*?)\s*!=\s*(.*)/);
|
||||
if (assertEqualMatch) {
|
||||
actualVal = assertEqualMatch[1].trim();
|
||||
expectedVal = assertEqualMatch[2].trim();
|
||||
}
|
||||
|
||||
const reportCard = document.createElement("div");
|
||||
reportCard.style.marginTop = "14px";
|
||||
|
||||
const total = result.total || cases.length || 1;
|
||||
const passed = result.passed !== undefined ? result.passed : (result.success ? total : 0);
|
||||
const isSuccess = result.success;
|
||||
|
||||
reportCard.innerHTML = `
|
||||
<div class="test-summary-badge ${isSuccess ? 'passed' : 'failed'}">
|
||||
<span>${isSuccess ? '🎉 Alle ' + total + ' Tests bestanden!' : '⚠️ ' + (total - passed) + ' von ' + total + ' Test(s) fehlgeschlagen'}</span>
|
||||
<span>${passed} / ${total} Tests OK</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
if (cases.length > 0) {
|
||||
cases.forEach(c => {
|
||||
const card = document.createElement("div");
|
||||
card.className = "test-case-card";
|
||||
const hasErr = !c.passed;
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="test-case-header" onclick="const d = this.nextElementSibling; if(d) d.style.display = d.style.display === 'none' ? 'block' : 'none';">
|
||||
<span style="color: ${c.passed ? '#4ade80' : '#f87171'}; font-weight: 700;">
|
||||
${c.passed ? '✓' : '✕'} ${escapeHtml(c.name)}
|
||||
</span>
|
||||
<span style="color: #94a3b8; font-size: 0.76rem;">${c.passed ? 'Bestanden' : 'Fehlgeschlagen ▼'}</span>
|
||||
</div>
|
||||
<div class="test-case-details" style="display: ${hasErr ? 'block' : 'none'};">
|
||||
${hasErr ? `
|
||||
<div style="color: #fca5a5; margin-bottom: 6px;">
|
||||
<strong>Meldung:</strong> Test fehlgeschlagen.
|
||||
</div>
|
||||
${expectedVal ? `
|
||||
<div class="test-diff-inspector">
|
||||
<div><span style="color: #94a3b8;">Erwartet: </span><span class="test-diff-expected">${escapeHtml(expectedVal)}</span></div>
|
||||
<div><span style="color: #94a3b8;">Erhalten: </span><span class="test-diff-actual">${escapeHtml(actualVal)}</span></div>
|
||||
</div>
|
||||
` : ''}
|
||||
${errorLine ? `
|
||||
<button type="button" class="btn-jump-code" onclick="window.jumpToEditorLine(${errorLine})">
|
||||
📍 Zu Zeile ${errorLine} in deiner Aufgabe springen
|
||||
</button>
|
||||
` : ''}
|
||||
` : '<div style="color: #4ade80;">Dieser Testfall wurde ohne Fehler bestanden.</div>'}
|
||||
</div>
|
||||
`;
|
||||
reportCard.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
term.appendChild(reportCard);
|
||||
term.scrollTop = term.scrollHeight;
|
||||
}
|
||||
|
||||
// 4. Hilfsfunktionen
|
||||
|
|
|
|||
451
assets/style.css
451
assets/style.css
|
|
@ -9,6 +9,14 @@
|
|||
--primary-light: #eff6ff;
|
||||
--primary-glow: rgba(29, 78, 216, 0.12);
|
||||
|
||||
--brand-50: #eff6ff;
|
||||
--brand-100: #dbeafe;
|
||||
--brand-500: #3b82f6;
|
||||
--brand-600: #2563eb;
|
||||
--brand-700: #1d4ed8;
|
||||
|
||||
/* Neutral Scale (Slate Palette) */
|
||||
--slate-950: #090d16;
|
||||
--slate-900: #0f172a;
|
||||
--slate-800: #1e293b;
|
||||
--slate-700: #334155;
|
||||
|
|
@ -20,6 +28,23 @@
|
|||
--slate-100: #f1f5f9;
|
||||
--slate-50: #f8fafc;
|
||||
|
||||
/* Semantic Status Tokens (WCAG AA & AAA Compliant) */
|
||||
--status-success: #059669;
|
||||
--status-success-bg: rgba(5, 150, 105, 0.08);
|
||||
--status-success-border: rgba(5, 150, 105, 0.20);
|
||||
|
||||
--status-warning: #b45309;
|
||||
--status-warning-bg: rgba(217, 119, 6, 0.08);
|
||||
--status-warning-border: rgba(217, 119, 6, 0.20);
|
||||
|
||||
--status-danger: #dc2626;
|
||||
--status-danger-bg: rgba(220, 38, 38, 0.08);
|
||||
--status-danger-border: rgba(220, 38, 38, 0.20);
|
||||
|
||||
--status-info: #2563eb;
|
||||
--status-info-bg: rgba(37, 99, 235, 0.08);
|
||||
--status-info-border: rgba(37, 99, 235, 0.20);
|
||||
|
||||
--emerald: #059669;
|
||||
--emerald-light: #f0fdf4;
|
||||
|
||||
|
|
@ -36,31 +61,44 @@
|
|||
--success-light: #f0fdf4;
|
||||
|
||||
/* Surfaces & Backgrounds */
|
||||
--bg-canvas: #f8fafc;
|
||||
--bg-body: #f8fafc;
|
||||
--bg-surface: #ffffff;
|
||||
--bg-surface-hover: #f1f5f9;
|
||||
--bg-subtle: #f1f5f9;
|
||||
--bg-sidebar: #0f172a;
|
||||
--bg-code: #0b0f19;
|
||||
--bg-code: #090d16;
|
||||
|
||||
/* Borders */
|
||||
/* Border-First Design System */
|
||||
--border-subtle: rgba(15, 23, 42, 0.08);
|
||||
--border-default: rgba(15, 23, 42, 0.12);
|
||||
--border-hover: rgba(15, 23, 42, 0.24);
|
||||
--border-color: #e2e8f0;
|
||||
--border-dark: #1e293b;
|
||||
|
||||
/* Typography */
|
||||
--text-main: #0f172a;
|
||||
--text-body: #334155;
|
||||
--text-muted: #64748b;
|
||||
/* Typography (WCAG AAA for body text) */
|
||||
--text-main: #0f172a; /* Kontrast > 18:1 auf Weiß */
|
||||
--text-primary: #0f172a;
|
||||
--text-body: #334155; /* Kontrast > 10:1 auf Weiß */
|
||||
--text-secondary: #334155;
|
||||
--text-muted: #64748b; /* Kontrast > 5.4:1 auf Weiß (Sicherer AA Standard) */
|
||||
--text-inverse: #f8fafc;
|
||||
|
||||
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
||||
--font-sans: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
||||
--font-mono: 'JetBrains Mono', 'Fira Code', 'SFMono-Regular', Consolas, Menlo, monospace;
|
||||
|
||||
/* Cognitive Load Limit for Text Containers */
|
||||
--content-max-width: 70ch;
|
||||
|
||||
/* Elevation */
|
||||
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.06), 0 2px 4px -2px rgba(0, 0, 0, 0.04);
|
||||
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.08), 0 4px 6px -4px rgba(0, 0, 0, 0.03);
|
||||
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.08), 0 8px 10px -6px rgba(0, 0, 0, 0.03);
|
||||
--focus-ring: 0 0 0 2px var(--bg-surface), 0 0 0 4px var(--primary);
|
||||
|
||||
--radius-xs: 4px;
|
||||
--radius-sm: 6px;
|
||||
--radius-md: 10px;
|
||||
--radius-lg: 14px;
|
||||
|
|
@ -3111,4 +3149,403 @@ tbody tr:hover {
|
|||
color: #fca5a5 !important;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
ENGINEERING-GRADE COMPONENT SYSTEM & DIDACTIC LAYOUT (WCAG AAA)
|
||||
========================================================================== */
|
||||
|
||||
/* --- Reading Width Limitation (Cognitive Load Reduction) --- */
|
||||
.content-readable,
|
||||
.card p,
|
||||
.box p {
|
||||
max-width: var(--content-max-width);
|
||||
}
|
||||
|
||||
/* --- Standard Buttons --- */
|
||||
.btn-primary {
|
||||
background-color: var(--brand-600);
|
||||
color: #ffffff !important;
|
||||
border: 1px solid var(--brand-700);
|
||||
box-shadow: var(--shadow-xs);
|
||||
font-weight: 600;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background-color: var(--brand-700);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: var(--bg-surface);
|
||||
color: var(--text-primary) !important;
|
||||
border: 1px solid var(--border-default);
|
||||
box-shadow: var(--shadow-xs);
|
||||
}
|
||||
.btn-secondary:hover {
|
||||
background-color: var(--bg-surface-hover);
|
||||
border-color: var(--border-hover);
|
||||
}
|
||||
|
||||
.btn-ghost {
|
||||
background-color: transparent;
|
||||
color: var(--text-secondary) !important;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
.btn-ghost:hover {
|
||||
background-color: var(--bg-subtle);
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background-color: var(--status-danger-bg);
|
||||
color: var(--status-danger) !important;
|
||||
border: 1px solid var(--status-danger-border);
|
||||
}
|
||||
.btn-danger:hover {
|
||||
background-color: var(--status-danger);
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.btn:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: var(--focus-ring);
|
||||
}
|
||||
|
||||
/* --- Badges & Pills (Engineering Grade) --- */
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 3px 8px;
|
||||
font-family: var(--font-sans);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
border-radius: var(--radius-full);
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.badge-neutral {
|
||||
background: var(--bg-subtle);
|
||||
color: var(--text-secondary);
|
||||
border: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
.badge-success {
|
||||
background: var(--status-success-bg);
|
||||
color: var(--status-success);
|
||||
border: 1px solid var(--status-success-border);
|
||||
}
|
||||
|
||||
.badge-warning {
|
||||
background: var(--status-warning-bg);
|
||||
color: var(--status-warning);
|
||||
border: 1px solid var(--status-warning-border);
|
||||
}
|
||||
|
||||
.badge-danger {
|
||||
background: var(--status-danger-bg);
|
||||
color: var(--status-danger);
|
||||
border: 1px solid var(--status-danger-border);
|
||||
}
|
||||
|
||||
.badge-info {
|
||||
background: var(--status-info-bg);
|
||||
color: var(--status-info);
|
||||
border: 1px solid var(--status-info-border);
|
||||
}
|
||||
|
||||
/* --- Code-Block Container (with Copy Action) --- */
|
||||
.code-block {
|
||||
background: var(--bg-code);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: var(--radius-md);
|
||||
margin: 16px 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.code-block-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 14px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
.code-block-title {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.78rem;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.code-copy-btn {
|
||||
font-family: var(--font-sans);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: #cbd5e1;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
padding: 3px 9px;
|
||||
border-radius: var(--radius-xs);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
.code-copy-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* --- Didaktische Boxen: Gotchas (Stolperfallen) & Syntax-Boxen --- */
|
||||
.box-gotcha {
|
||||
background: #fffbeb;
|
||||
border: 1px solid #fde68a;
|
||||
border-left: 4px solid #d97706;
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 14px 18px;
|
||||
margin: 16px 0;
|
||||
color: #92400e;
|
||||
}
|
||||
.box-gotcha h4 {
|
||||
margin: 0 0 6px 0;
|
||||
color: #78350f;
|
||||
font-size: 0.95rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.box-syntax {
|
||||
background: #f8fafc;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 14px 18px;
|
||||
margin: 16px 0;
|
||||
}
|
||||
.box-syntax h4 {
|
||||
margin: 0 0 8px 0;
|
||||
color: var(--text-primary);
|
||||
font-size: 0.92rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
/* --- Subgoal Cards (Cowan Limit 3-4 Steps) --- */
|
||||
.subgoal-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 12px 14px;
|
||||
background: #ffffff;
|
||||
border: 1px solid var(--border-default);
|
||||
border-radius: var(--radius-sm);
|
||||
margin-bottom: 8px;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
.subgoal-item:hover {
|
||||
border-color: var(--primary);
|
||||
box-shadow: var(--shadow-xs);
|
||||
}
|
||||
.subgoal-badge {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
color: var(--brand-700);
|
||||
background: var(--brand-50);
|
||||
border: 1px solid var(--brand-100);
|
||||
padding: 3px 8px;
|
||||
border-radius: var(--radius-xs);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* --- Persistent Sticky Launch Action Bar --- */
|
||||
.sticky-launch-bar {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgba(15, 23, 42, 0.94);
|
||||
backdrop-filter: blur(8px);
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
padding: 12px 24px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
z-index: 50;
|
||||
}
|
||||
.btn-cta-launch {
|
||||
background: #2563eb;
|
||||
color: #ffffff !important;
|
||||
font-weight: 700;
|
||||
font-size: 0.95rem;
|
||||
padding: 10px 20px;
|
||||
border-radius: var(--radius-sm);
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
box-shadow: 0 2px 10px rgba(37, 99, 235, 0.35);
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
.btn-cta-launch:hover {
|
||||
background: #1d4ed8;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* --- Resizable Workspace Splitters (Gutter) --- */
|
||||
.gutter-col {
|
||||
width: 5px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
cursor: col-resize;
|
||||
position: relative;
|
||||
transition: background 0.15s ease;
|
||||
z-index: 20;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.gutter-col::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: -5px;
|
||||
right: -5px;
|
||||
cursor: col-resize;
|
||||
}
|
||||
.gutter-col:hover,
|
||||
.gutter-col.dragging {
|
||||
background: #38bdf8;
|
||||
}
|
||||
|
||||
.gutter-row {
|
||||
height: 5px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
cursor: row-resize;
|
||||
position: relative;
|
||||
transition: background 0.15s ease;
|
||||
z-index: 20;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.gutter-row::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: -5px;
|
||||
bottom: -5px;
|
||||
cursor: row-resize;
|
||||
}
|
||||
.gutter-row:hover,
|
||||
.gutter-row.dragging {
|
||||
background: #38bdf8;
|
||||
}
|
||||
|
||||
/* --- Structured Visual Test Runner Cards --- */
|
||||
.test-summary-badge {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 14px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-weight: 700;
|
||||
font-size: 0.88rem;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.test-summary-badge.passed {
|
||||
background: rgba(16, 185, 129, 0.15);
|
||||
color: #34d399;
|
||||
border: 1px solid rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
.test-summary-badge.failed {
|
||||
background: rgba(239, 68, 68, 0.15);
|
||||
color: #f87171;
|
||||
border: 1px solid rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
|
||||
.test-case-card {
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: var(--radius-sm);
|
||||
background: #090d16;
|
||||
margin-bottom: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.test-case-header {
|
||||
padding: 8px 12px;
|
||||
background: #0f172a;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
font-size: 0.84rem;
|
||||
user-select: none;
|
||||
}
|
||||
.test-case-header:hover {
|
||||
background: #1e293b;
|
||||
}
|
||||
.test-case-details {
|
||||
padding: 10px 14px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.82rem;
|
||||
line-height: 1.5;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
.test-diff-inspector {
|
||||
background: #060911;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 4px;
|
||||
padding: 8px 12px;
|
||||
margin: 6px 0;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.test-diff-expected {
|
||||
color: #4ade80;
|
||||
}
|
||||
.test-diff-actual {
|
||||
color: #f87171;
|
||||
}
|
||||
.btn-jump-code {
|
||||
background: rgba(56, 189, 248, 0.12);
|
||||
color: #38bdf8;
|
||||
border: 1px solid rgba(56, 189, 248, 0.25);
|
||||
padding: 4px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.76rem;
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
margin-top: 6px;
|
||||
}
|
||||
.btn-jump-code:hover {
|
||||
background: rgba(56, 189, 248, 0.25);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
DARK THEME OVERRIDES ([data-theme="dark"])
|
||||
========================================================================== */
|
||||
[data-theme="dark"] {
|
||||
--bg-canvas: #090d16;
|
||||
--bg-body: #090d16;
|
||||
--bg-surface: #0f172a;
|
||||
--bg-surface-hover: #1e293b;
|
||||
--bg-subtle: rgba(255, 255, 255, 0.04);
|
||||
--bg-code: #05080f;
|
||||
|
||||
--border-subtle: rgba(255, 255, 255, 0.08);
|
||||
--border-default: rgba(255, 255, 255, 0.12);
|
||||
--border-hover: rgba(255, 255, 255, 0.25);
|
||||
|
||||
--text-main: #f8fafc;
|
||||
--text-primary: #f8fafc;
|
||||
--text-body: #cbd5e1;
|
||||
--text-secondary: #cbd5e1;
|
||||
--text-muted: #94a3b8;
|
||||
|
||||
--brand-500: #3b82f6;
|
||||
--brand-600: #60a5fa;
|
||||
|
||||
--shadow-xs: none;
|
||||
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.4);
|
||||
--shadow-md: 0 8px 24px rgba(0, 0, 0, 0.5);
|
||||
--focus-ring: 0 0 0 2px #090d16, 0 0 0 4px #3b82f6;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -6,148 +6,167 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Bash 01: Terminal-Navigation & Globbing</title>
|
||||
<link rel="stylesheet" href="../../../assets/style.css">
|
||||
<style>
|
||||
.module-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 7fr 5fr;
|
||||
gap: 24px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
@media (max-width: 992px) {
|
||||
.module-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-layout">
|
||||
<div class="app-main">
|
||||
<header>
|
||||
<div class="header-container">
|
||||
<div class="header-badge">🐧 Linux Bash & Cloud DevOps</div>
|
||||
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 8px;">
|
||||
<span class="badge badge-neutral">🐧 Linux Bash & Cloud DevOps</span>
|
||||
<span class="badge badge-neutral">Modul 01 von 16</span>
|
||||
<span class="badge badge-neutral">⏱️ ca. 15 Min.</span>
|
||||
<span class="badge badge-success">🎓 IHK AP1 & AP2 Relevanz</span>
|
||||
</div>
|
||||
<h1>Bash 01: Terminal-Navigation, Dateioperationen & Globbing</h1>
|
||||
<p>Beherrsche den Linux-Dateibaum, lerne blitzschnelle Pfad-Navigation und automatisiere Dateioperationen mit Wildcards und Brace Expansion.</p>
|
||||
<p class="content-readable" style="color: #cbd5e1; font-size: 1.05rem;">Beherrsche den Linux-Dateibaum, lerne blitzschnelle Pfad-Navigation und automatisiere Dateioperationen mit Wildcards und Brace Expansion.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<!-- 1. THEORIE: PFADE & NAVIGATION -->
|
||||
<section class="card">
|
||||
<h2>🧭 1. Der Unix-Dateibaum & Pfad-Navigation</h2>
|
||||
<p>In Unix gibt es keine Laufwerksbuchstaben (wie <code>C:</code> oder <code>D:</code>). Alles entspringt dem Root-Verzeichnis <code>/</code> (Single Root Tree). Wir unterscheiden strikt zwischen absoluten und relativen Pfaden:</p>
|
||||
<div class="module-grid">
|
||||
<!-- LINKE SPALTE: Konzept & Aufgaben -->
|
||||
<div class="module-main-col">
|
||||
<!-- 1. KERNKONZEPT -->
|
||||
<section class="card">
|
||||
<h2>🧭 1. Der Unix-Dateibaum & Pfad-Navigation</h2>
|
||||
<p class="content-readable">In Unix gibt es keine Laufwerksbuchstaben (wie <code>C:</code>). Alles entspringt dem Root-Verzeichnis <code>/</code> (Single Root Tree). Wir unterscheiden strikt zwischen absoluten Pfaden (beginnen immer mit <code>/</code>) und relativen Pfaden (relativ zum aktuellen Arbeitsverzeichnis <code>pwd</code>).</p>
|
||||
|
||||
<pre><code># Wo bin ich?
|
||||
<div class="code-block">
|
||||
<div class="code-block-header">
|
||||
<span class="code-block-title">bash • Navigation & Pfade</span>
|
||||
<button type="button" class="code-copy-btn" onclick="navigator.clipboard.writeText(this.parentElement.nextElementSibling.innerText); this.innerText='Kopiert! ✓'; setTimeout(()=>this.innerText='Kopieren', 2000);">Kopieren</button>
|
||||
</div>
|
||||
<pre style="padding: 12px; margin: 0; background: transparent; color: #f8fafc; font-family: var(--font-mono); font-size: 0.88rem;"><code># Wo bin ich?
|
||||
pwd # Ausgabe: /home/devops/projects
|
||||
|
||||
# Relative vs. Absolute Navigation:
|
||||
cd /var/log/nginx # Absoluter Pfad: Beginnt immer mit '/'
|
||||
cd /var/log/nginx # Absoluter Pfad
|
||||
cd ../../etc # Relativer Pfad: Zwei Ebenen hoch, dann in 'etc'
|
||||
cd ~ # Wechselt ins Home-Verzeichnis (/home/user)
|
||||
cd - # Wechselt zum vorherigen Verzeichnis zurück
|
||||
cd ~ # Home-Verzeichnis
|
||||
cd - # Vorheriges Verzeichnis</code></pre>
|
||||
</div>
|
||||
|
||||
# Detailliertes Listing mit Berechtigungen und Dateigrößen:
|
||||
ls -lah # -l: Long Format, -a: Versteckte Dateien, -h: Human-readable (KB/MB)</code></pre>
|
||||
</section>
|
||||
<div class="box box-analogie">
|
||||
<div class="box-title">🏢 Didaktische Analogie: Das Hochregallager des Betriebssystems</div>
|
||||
<p><strong>Der Dateibaum:</strong> Stell dir das Linux-Dateisystem wie ein gigantisches Logistikzentrum vor. <code>/</code> ist das Haupttor. Mit <code>mkdir -p</code> stellst du mit einer einzigen Anweisung eine ganze Regalreihe mit Fächern auf.</p>
|
||||
<p><strong>Globbing (<code>*</code> und <code>{a,b}</code>):</strong> Statt jedes Paket einzeln zu scannen, hältst du eine Schablone hoch: <code>*.yaml</code> greift automatisch alle Konfigurationen, während <code>{dev,prod}</code> im Handumdrehen zwei Umgebungen parallel anlegt!</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 2. THEORIE: DATEIOPERATIONEN & GLOBBING -->
|
||||
<section class="card">
|
||||
<h2>📁 2. Dateisystem-Operationen & Mächtiges Globbing</h2>
|
||||
<p>Als DevOps-Ingenieur erstellst, kopierst und reorganisierst du Konfigurationsdateien im Sekundentakt. Die Shell bietet hierfür mächtige Werkzeuge:</p>
|
||||
<!-- 2. SUBGOAL-BOARD -->
|
||||
<section class="card">
|
||||
<h2>🎯 2. Dein Arbeitsauftrag: 4 Subgoals (Teilziele)</h2>
|
||||
<p class="content-readable" style="color: var(--text-muted); margin-bottom: 16px;">Bearbeite die Aufgaben in <code>aufgabe.sh</code> schrittweise:</p>
|
||||
|
||||
<pre><code># 🎯 1. Verzeichnisse verschachtelt anlegen:
|
||||
mkdir -p app/{config,src/{models,views},logs,tests}
|
||||
<div class="subgoal-item">
|
||||
<span class="subgoal-badge">Subgoal 1</span>
|
||||
<div>
|
||||
<strong>Projektverzeichnisse anlegen:</strong>
|
||||
<p style="margin: 4px 0 0 0; font-size: 0.88rem; color: var(--text-secondary);">Implementiere <code>erstelle_projektstruktur(basis_dir)</code> mit <code>mkdir -p</code> für <code>src/components</code>, <code>src/utils</code>, <code>docs/api</code> und <code>backup/logs</code>.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
# 🎯 2. Dateien mit Brace Expansion massenweise erstellen:
|
||||
touch app/src/models/{user,order,payment}.py app/config/{dev,staging,prod}.yaml
|
||||
<div class="subgoal-item">
|
||||
<span class="subgoal-badge">Subgoal 2</span>
|
||||
<div>
|
||||
<strong>Testdateien erzeugen:</strong>
|
||||
<p style="margin: 4px 0 0 0; font-size: 0.88rem; color: var(--text-secondary);">Erstelle mit <code>touch</code> und Brace Expansion Beispieldateien in den angelegten Ordnern.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
# 🎯 3. Shell-Globbing (Wildcards):
|
||||
cp app/config/*.yaml /backup/configs/ # Alle .yaml Dateien kopieren
|
||||
mv app/logs/app_2026-??.log /archive/logs/ # Matcht genau 2 Zeichen für den Monat (z.B. 01..12)
|
||||
rm -rf /tmp/build_cache/ # Rekursives Löschen ohne Bestätigung</code></pre>
|
||||
<div class="subgoal-item">
|
||||
<span class="subgoal-badge">Subgoal 3</span>
|
||||
<div>
|
||||
<strong>Dateien kopieren & Globbing:</strong>
|
||||
<p style="margin: 4px 0 0 0; font-size: 0.88rem; color: var(--text-secondary);">Kopiere mit <code>cp</code> und Wildcards (<code>*.yaml</code>, <code>*.log</code>) alle Konfigurationsdateien in das Backup-Verzeichnis.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box box-analogie">
|
||||
<div class="box-title">🏢 Didaktische Analogie: Das Logistikzentrum des Betriebssystems</div>
|
||||
<p><strong>Der Dateibaum:</strong> Stell dir das Dateisystem wie ein riesiges, strukturiertes Hochregallager vor. <code>/</code> ist der Haupteingang. <code>mkdir -p</code> baut dir sofort eine ganze Gasse mit Fachböden auf.</p>
|
||||
<p><strong>Globbing (<code>*</code> und <code>{a,b}</code>):</strong> Anstatt jedes Paket einzeln mit der Hand zu scannen, hältst du eine Schablone hoch: <code>*.yaml</code> greift automatisch alle blauen Kisten aus dem Regal, während <code>{dev,prod}</code> mit einem Handgriff zwei vorgefertigte Etiketten druckt!</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 3. CHEAT-SHEET -->
|
||||
<section class="card">
|
||||
<h2>📑 3. Cheat-Sheet: Navigation & Wildcards</h2>
|
||||
<div style="overflow-x: auto;">
|
||||
<table style="width: 100%; border-collapse: collapse; font-size: 0.9rem; text-align: left;">
|
||||
<thead>
|
||||
<tr style="border-bottom: 2px solid var(--border-color); background: var(--bg-subtle);">
|
||||
<th style="padding: 10px;">Befehl / Muster</th>
|
||||
<th style="padding: 10px;">Beispiel</th>
|
||||
<th style="padding: 10px;">Bedeutung & DevOps-Einsatz</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>mkdir -p <pfad></code></td>
|
||||
<td style="padding: 10px;"><code>mkdir -p /srv/app/releases</code></td>
|
||||
<td style="padding: 10px;">Erstellt Elternverzeichnisse automatisch ohne Fehler</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>cp -r <src> <dst></code></td>
|
||||
<td style="padding: 10px;"><code>cp -r ./templates /etc/nginx/</code></td>
|
||||
<td style="padding: 10px;">Kopiert ganze Verzeichnisbäume rekursiv</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>mv <src> <dst></code></td>
|
||||
<td style="padding: 10px;"><code>mv old.env .env.backup</code></td>
|
||||
<td style="padding: 10px;">Verschiebt oder benennt Dateien atomar um</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>rm -rf <pfad></code></td>
|
||||
<td style="padding: 10px;"><code>rm -rf ./temp_workspace</code></td>
|
||||
<td style="padding: 10px;">Löscht rekursiv und erzwingt Löschen ohne Prompt</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>*</code> (Wildcard)</td>
|
||||
<td style="padding: 10px;"><code>rm -f *.log</code></td>
|
||||
<td style="padding: 10px;">Matcht 0 oder beliebig viele Zeichen</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>?</code> (Single Char)</td>
|
||||
<td style="padding: 10px;"><code>ls node_v?.log</code></td>
|
||||
<td style="padding: 10px;">Matcht exakt ein einziges Zeichen</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px;"><code>{a,b,c}</code></td>
|
||||
<td style="padding: 10px;"><code>touch {app,db}_{1..3}.conf</code></td>
|
||||
<td style="padding: 10px;">Brace Expansion zur raschen Permutationserstellung</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. AUFGABEN CHECKLISTE -->
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste in <code>aufgabe.sh</code></h2>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t1">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> <code>erstelle_projektstruktur(basis_dir)</code> mit <code>mkdir -p</code> implementieren (src/components, src/utils, docs/api, backup/logs).</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t2">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> <code>erstelle_testdateien(basis_dir)</code> mit <code>touch</code> und Dateimustern umsetzen.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t3">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> <code>kopiere_konfigurationen(src_dir, backup_dir)</code> mit <code>cp</code> und Globbing (<code>*.yaml</code>, <code>*.log</code>) schreiben.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t4">
|
||||
<label class="task-label" for="t4"><strong>TODO 4:</strong> <code>verschiebe_dateien(src_dir, target_dir)</code> für <code>*.md</code> Dokumentationen implementieren.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t5">
|
||||
<label class="task-label" for="t5"><strong>TODO 5:</strong> <code>bereinige_temporaere_dateien(basis_dir)</code> mit <code>rm -f</code> und <code>rm -rf</code> fertigstellen.</label>
|
||||
<div class="subgoal-item">
|
||||
<span class="subgoal-badge">Subgoal 4</span>
|
||||
<div>
|
||||
<strong>Temporäre Dateien bereinigen:</strong>
|
||||
<p style="margin: 4px 0 0 0; font-size: 0.88rem; color: var(--text-secondary);">Nutze <code>rm -f</code> und <code>rm -rf</code>, um Cache- und temporäre Logdateien sauber zu entfernen.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="box box-tipp" style="margin-top: 20px;">
|
||||
<div class="box-title">💡 DevOps Pro-Tipp: Quoting in Shell-Skripten</div>
|
||||
<p>Setze Variablen bei Pfadangaben in Bash-Skripten stets in doppelte Anführungszeichen: <code>"$basis_dir/src/components"</code>. Das verhindert gefürchtete <em>Word Splitting</em> Fehler, falls ein Verzeichnisname einmal Leerzeichen enthält!</p>
|
||||
</div>
|
||||
<!-- RECHTE SPALTE: Referenz, Gotchas & Spickzettel -->
|
||||
<div class="module-side-col">
|
||||
<!-- GOTCHAS (STOLPERFALLEN) -->
|
||||
<div class="box-gotcha">
|
||||
<h4>⚠️ Häufige Stolperfallen (Pre-Mortem)</h4>
|
||||
<p style="margin-bottom: 8px;"><strong>1. Fehlendes <code>-p</code> bei mkdir:</strong> Wenn du <code>mkdir a/b/c</code> aufrufst und Ordner <code>a</code> noch nicht existiert, bricht die Shell mit <em>No such file or directory</em> ab. Verwende immer <code>mkdir -p</code>!</p>
|
||||
<p style="margin: 0;"><strong>2. Word Splitting ohne Quotes:</strong> Setze Variablenpfade in Skripten stets in doppelte Anführungszeichen: <code>"$basis_dir/src"</code>, sonst führen Leerzeichen im Ordnernamen zu fatalen Syntaxfehlern.</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 25px; display: flex; gap: 12px; flex-wrap: wrap;">
|
||||
<a href="../../../workspace.html?course=bash&track=track_1_terminal_grundlagen&chapter=01_navigation_dateien_und_globbing" class="btn" style="background: #10b981; color: #fff; font-weight: 700;">💻 In Web-IDE öffnen →</a>
|
||||
<a href="../02_streams_pipes_und_redirection/index.html" class="btn btn-secondary">Weiter zu Bash 02 →</a>
|
||||
<!-- SYNTAX QUICK REF -->
|
||||
<div class="box-syntax">
|
||||
<h4>📌 Syntax-Spickzettel: Shell-Muster</h4>
|
||||
<table style="width: 100%; border-collapse: collapse; font-size: 0.84rem; text-align: left;">
|
||||
<thead>
|
||||
<tr style="border-bottom: 2px solid var(--border-color); background: var(--bg-subtle);">
|
||||
<th style="padding: 6px 8px;">Muster</th>
|
||||
<th style="padding: 6px 8px;">Bedeutung</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 6px 8px;"><code>*</code></td>
|
||||
<td style="padding: 6px 8px;">Matcht 0 bis unendlich viele Zeichen</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 6px 8px;"><code>?</code></td>
|
||||
<td style="padding: 6px 8px;">Matcht exakt 1 Zeichen</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 6px 8px;"><code>{a,b}</code></td>
|
||||
<td style="padding: 6px 8px;">Brace Expansion (Permutation)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 6px 8px;"><code>mkdir -p</code></td>
|
||||
<td style="padding: 6px 8px;">Erstellt Elternordner automatisch</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- IHK PRÜFUNGSRELEVANZ -->
|
||||
<div class="card" style="background: rgba(37, 99, 235, 0.05); border: 1px solid rgba(37, 99, 235, 0.2);">
|
||||
<h4 style="margin: 0 0 6px 0; color: var(--primary);">💡 IHK-Prüfungstipp (AP1)</h4>
|
||||
<p style="font-size: 0.86rem; color: var(--text-secondary); margin: 0;">In der IHK-Abschlussprüfung Teil 1 wird regelmäßig der Unterschied zwischen relativer und absoluter Pfadangabe sowie die Bedeutung von <code>cd ..</code> und <code>ls -l</code> abgefragt.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- PERSISTENT STICKY LAUNCH ACTION BAR -->
|
||||
<div class="sticky-launch-bar">
|
||||
<div style="display: flex; align-items: center; gap: 12px; color: #f8fafc; font-size: 0.9rem;">
|
||||
<span>🎯 <strong>Status:</strong> Bereit für die Praxis (4 Teilziele definiert)</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 12px;">
|
||||
<a href="../../../dashboard.html" class="btn btn-ghost" style="color: #cbd5e1 !important;">← Dashboard</a>
|
||||
<a href="../../../workspace.html?course=bash&track=track_1_terminal_grundlagen&chapter=01_navigation_dateien_und_globbing" class="btn-cta-launch">
|
||||
💻 In Web-IDE öffnen & Teilziel 1 anpacken →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="../../../assets/courses_manifest.js"></script>
|
||||
|
|
|
|||
|
|
@ -21,26 +21,26 @@ ROOT_DIR = Path(__file__).parent.resolve()
|
|||
DATA_JSON_PATH = ROOT_DIR / ".gamification.json"
|
||||
DATA_JS_PATH = ROOT_DIR / "assets" / "gamification_data.js"
|
||||
|
||||
# 10 Entwickler-Level mit XP-Schwellen und Titeln
|
||||
# 10 Entwickler-Level mit XP-Schwellen und professionellen IT-Kompetenzstufen
|
||||
LEVEL_STUFEN = [
|
||||
{"level": 1, "min_xp": 0, "max_xp": 150, "titel": "Code-Küken 🐣", "rang": "Bronze I"},
|
||||
{"level": 2, "min_xp": 151, "max_xp": 400, "titel": "Code-Detektivin 🔍", "rang": "Bronze II"},
|
||||
{"level": 3, "min_xp": 401, "max_xp": 800, "titel": "Logik-Bastlerin ⚙️", "rang": "Silber I"},
|
||||
{"level": 4, "min_xp": 801, "max_xp": 1300, "titel": "Klassen-Baumeisterin 🏗️", "rang": "Silber II"},
|
||||
{"level": 5, "min_xp": 1301, "max_xp": 2000, "titel": "Git-Strategin 🌿", "rang": "Gold I"},
|
||||
{"level": 6, "min_xp": 2001, "max_xp": 2900, "titel": "TDD-Qualitätsprüferin 🧪", "rang": "Gold II"},
|
||||
{"level": 7, "min_xp": 2901, "max_xp": 4000, "titel": "Vererbungs-Meisterin 🧬", "rang": "Platin I"},
|
||||
{"level": 8, "min_xp": 4001, "max_xp": 5300, "titel": "Exception-Wächterin 🛡️", "rang": "Platin II"},
|
||||
{"level": 9, "min_xp": 5301, "max_xp": 6800, "titel": "GUI-Entwicklerin 🖥️", "rang": "Diamant"},
|
||||
{"level": 10, "min_xp": 6801, "max_xp": 99999, "titel": "Software-Architektin 🏆", "rang": "Großmeisterin"},
|
||||
{"level": 1, "min_xp": 0, "max_xp": 150, "titel": "Junior IT-Trainee I: Syntax & Basistypen", "rang": "Stufe 1"},
|
||||
{"level": 2, "min_xp": 151, "max_xp": 400, "titel": "Junior IT-Trainee II: Kontrollfluss & Logik", "rang": "Stufe 2"},
|
||||
{"level": 3, "min_xp": 401, "max_xp": 800, "titel": "IT-Specialist: Funktionen & Datenstrukturen", "rang": "Stufe 3"},
|
||||
{"level": 4, "min_xp": 801, "max_xp": 1300, "titel": "Software Developer: OOP-Modellierung", "rang": "Stufe 4"},
|
||||
{"level": 5, "min_xp": 1301, "max_xp": 2000, "titel": "DevOps Engineer: Versionskontrolle & Tooling", "rang": "Stufe 5"},
|
||||
{"level": 6, "min_xp": 2001, "max_xp": 2900, "titel": "Quality Engineer: Test-Driven Development (TDD)", "rang": "Stufe 6"},
|
||||
{"level": 7, "min_xp": 2901, "max_xp": 4000, "titel": "Advanced Engineer: Entwurfsmuster & Polymorphie", "rang": "Stufe 7"},
|
||||
{"level": 8, "min_xp": 4001, "max_xp": 5300, "titel": "Systems Engineer: Security & Error Handling", "rang": "Stufe 8"},
|
||||
{"level": 9, "min_xp": 5301, "max_xp": 6800, "titel": "Application Engineer: Schnittstellen & UI", "rang": "Stufe 9"},
|
||||
{"level": 10, "min_xp": 6801, "max_xp": 99999, "titel": "Certified IT Systems Professional (IHK AP2)", "rang": "Master-Stufe"},
|
||||
]
|
||||
|
||||
# 14 freischaltbare Trophäen mit didaktischen Kriterien (abgestimmt auf neue Lehrpfad-Pfade)
|
||||
# 14 freischaltbare IT-Kompetenz-Zertifikate (abgestimmt auf IHK-Rahmenlehrplan)
|
||||
BADGES_KATALOG = {
|
||||
"rechenkonegin": {
|
||||
"id": "rechenkonegin",
|
||||
"icon": "🧮",
|
||||
"name": "Rechenkönigin",
|
||||
"name": "Zertifikat: Numerische Typen & Arithmetik",
|
||||
"desc": "Grundlagen 01 & 02 abgeschlossen (Zahlen, Operatoren & Typen gemeistert).",
|
||||
"xp_bonus": 100,
|
||||
"kriterium": [
|
||||
|
|
@ -51,7 +51,7 @@ BADGES_KATALOG = {
|
|||
"dialog_profi": {
|
||||
"id": "dialog_profi",
|
||||
"icon": "💬",
|
||||
"name": "Dialog-Profi",
|
||||
"name": "Zertifikat: Interaktive I/O & String-Interpolation",
|
||||
"desc": "Grundlagen 03 abgeschlossen (Interaktive Ein-/Ausgabe mit f-Strings).",
|
||||
"xp_bonus": 75,
|
||||
"kriterium": ["lehrpfad_1_grundlagen/03_ein_und_ausgabe"]
|
||||
|
|
@ -59,32 +59,32 @@ BADGES_KATALOG = {
|
|||
"weichenstellerin": {
|
||||
"id": "weichenstellerin",
|
||||
"icon": "🚦",
|
||||
"name": "Weichenstellerin",
|
||||
"desc": "Grundlagen 04 abgeschlossen (Bedingungslogik & Schaltjahre gemeistert).",
|
||||
"name": "Zertifikat: Verzweigungslogik & Boolesche Algebra",
|
||||
"desc": "Grundlagen 04 abgeschlossen (Bedingungslogik & Kontrollfluss gemeistert).",
|
||||
"xp_bonus": 75,
|
||||
"kriterium": ["lehrpfad_1_grundlagen/04_verzweigungen_und_bedingungen"]
|
||||
},
|
||||
"schleifen_dompteurin": {
|
||||
"id": "schleifen_dompteurin",
|
||||
"icon": "🎡",
|
||||
"name": "Schleifen-Dompteurin",
|
||||
"desc": "Grundlagen 05 abgeschlossen (While- & For-Schleifen gebändigt).",
|
||||
"name": "Zertifikat: Iterationslogik & Sequenzverarbeitung",
|
||||
"desc": "Grundlagen 05 abgeschlossen (While- & For-Schleifen gemeistert).",
|
||||
"xp_bonus": 100,
|
||||
"kriterium": ["lehrpfad_1_grundlagen/05_schleifen_und_wiederholungen"]
|
||||
},
|
||||
"funktions_zauberin": {
|
||||
"id": "funktions_zauberin",
|
||||
"icon": "🪄",
|
||||
"name": "Funktions-Zauberin",
|
||||
"desc": "Grundlagen 06 abgeschlossen (Eigene Funktionen & Module gebaut).",
|
||||
"icon": "📐",
|
||||
"name": "Zertifikat: Modulare Funktionsarchitektur",
|
||||
"desc": "Grundlagen 06 abgeschlossen (Modulare Funktionen & Parameterübergabe).",
|
||||
"xp_bonus": 100,
|
||||
"kriterium": ["lehrpfad_1_grundlagen/06_funktionen_und_module"]
|
||||
},
|
||||
"listen_archivarin": {
|
||||
"id": "listen_archivarin",
|
||||
"icon": "📋",
|
||||
"name": "Listen-Archivarin",
|
||||
"desc": "Grundlagen 07 & 08 abgeschlossen (Sequenzen & Strings analysiert).",
|
||||
"name": "Zertifikat: Datenstrukturen: Listen & Slicing",
|
||||
"desc": "Grundlagen 07 & 08 abgeschlossen (Sequenzen, Listen & Slicing analysiert).",
|
||||
"xp_bonus": 120,
|
||||
"kriterium": [
|
||||
"lehrpfad_1_grundlagen/07_listen_und_sequenzen",
|
||||
|
|
@ -94,8 +94,8 @@ BADGES_KATALOG = {
|
|||
"daten_strategin": {
|
||||
"id": "daten_strategin",
|
||||
"icon": "🗃️",
|
||||
"name": "Daten-Strategin",
|
||||
"desc": "Grundlagen 09 & 10 abgeschlossen (Dicts, Sets & Comprehensions gemeistert).",
|
||||
"name": "Zertifikat: Assoziative Arrays: Dicts & Sets",
|
||||
"desc": "Grundlagen 09 & 10 abgeschlossen (Dictionaries, Sets & Hash-Lookup O(1)).",
|
||||
"xp_bonus": 150,
|
||||
"kriterium": [
|
||||
"lehrpfad_1_grundlagen/09_dictionaries_und_sets",
|
||||
|
|
@ -105,16 +105,16 @@ BADGES_KATALOG = {
|
|||
"bug_jaegerin": {
|
||||
"id": "bug_jaegerin",
|
||||
"icon": "🔍",
|
||||
"name": "Bug-Jägerin",
|
||||
"desc": "Kapitel 00 gelöst (Alle Fehler-Bugs im Warm-up aufgespürt).",
|
||||
"name": "Zertifikat: Systematisches Code-Debugging",
|
||||
"desc": "Warm-up abgeschlossen (Fehleranalyse & Traceback-Debugging).",
|
||||
"xp_bonus": 100,
|
||||
"kriterium": ["lehrpfad_2_oop_einstieg/00_fehlersuche_und_grundlagen"]
|
||||
},
|
||||
"erste_architektin": {
|
||||
"id": "erste_architektin",
|
||||
"icon": "🏗️",
|
||||
"name": "Erste Architektin",
|
||||
"desc": "Kapitel 01 bis 03 gelöst (Erste OOP-Klassen, Konstruktoren & Methoden).",
|
||||
"name": "Zertifikat: OOP-Grundlagen: Klassen & Kapselung",
|
||||
"desc": "OOP 01 bis 03 gelöst (Klassendefinition, Konstruktoren & Instanzmethoden).",
|
||||
"xp_bonus": 200,
|
||||
"kriterium": [
|
||||
"lehrpfad_2_oop_einstieg/01_einstieg_klassen",
|
||||
|
|
@ -124,33 +124,33 @@ BADGES_KATALOG = {
|
|||
},
|
||||
"tamagotchi_mama": {
|
||||
"id": "tamagotchi_mama",
|
||||
"icon": "🥚",
|
||||
"name": "Tamagotchi-Mama",
|
||||
"desc": "Kapitel 06 Mini-Projekt abgeschlossen (Ein lebendiges Haustier gebaut).",
|
||||
"icon": "📦",
|
||||
"name": "Zertifikat: State-Machine & Domänenmodellierung",
|
||||
"desc": "OOP Mini-Projekt abgeschlossen (Zustandsverwaltung & Objektinteraktion).",
|
||||
"xp_bonus": 250,
|
||||
"kriterium": ["lehrpfad_2_oop_einstieg/06_abschlussprojekt_tamagotchi"]
|
||||
},
|
||||
"zeit_reisende": {
|
||||
"id": "zeit_reisende",
|
||||
"icon": "🌿",
|
||||
"name": "Git-Zeitreisende",
|
||||
"desc": "Kapitel 04c Git-Versionskontrolle verstanden & Spielstände gesichert.",
|
||||
"name": "Zertifikat: String-Repräsentation & Dunder-Methoden",
|
||||
"desc": "OOP 04 gelöst (Spezielle Methoden __str__, __repr__ implementiert).",
|
||||
"xp_bonus": 100,
|
||||
"kriterium": ["lehrpfad_2_oop_einstieg/04_str_und_darstellung"]
|
||||
},
|
||||
"qualitaets_garantin": {
|
||||
"id": "qualitaets_garantin",
|
||||
"icon": "🧪",
|
||||
"name": "TDD-Qualitätsgarantin",
|
||||
"desc": "Kapitel 09 abgeschlossen (Eigene professionelle Unit Tests geschrieben).",
|
||||
"name": "Zertifikat: Automatisierte Testsuites & TDD",
|
||||
"desc": "OOP 09 abgeschlossen (Eigene professionelle Unit Tests mit Assertions).",
|
||||
"xp_bonus": 200,
|
||||
"kriterium": ["lehrpfad_3_fortgeschrittenes_oop/09_eigene_unit_tests_schreiben"]
|
||||
},
|
||||
"oop_grossmeisterin": {
|
||||
"id": "oop_grossmeisterin",
|
||||
"icon": "🧬",
|
||||
"name": "Vererbungs-Koryphäe",
|
||||
"desc": "Kapitel 10 bis 12 abgeschlossen (Vererbung, Polymorphie & Exceptions).",
|
||||
"name": "Zertifikat: Fortgeschrittene OOP & Robustes Error-Handling",
|
||||
"desc": "OOP 10 bis 12 abgeschlossen (Vererbung, Polymorphie & Exceptions).",
|
||||
"xp_bonus": 300,
|
||||
"kriterium": [
|
||||
"lehrpfad_3_fortgeschrittenes_oop/10_vererbung_und_super",
|
||||
|
|
@ -161,8 +161,8 @@ BADGES_KATALOG = {
|
|||
"software_architektin": {
|
||||
"id": "software_architektin",
|
||||
"icon": "🏆",
|
||||
"name": "Meister-Entwicklerin",
|
||||
"desc": "Kapitel 16 Master-Projekt abgeschlossen & vollwertige Desktop-App gebaut!",
|
||||
"name": "Zertifikat: Master-Projekt Software Engineering",
|
||||
"desc": "Master-Projekt abgeschlossen & vollwertige Anwendungsarchitektur gebaut!",
|
||||
"xp_bonus": 500,
|
||||
"kriterium": ["lehrpfad_3_fortgeschrittenes_oop/16_master_abschlussprojekt"]
|
||||
}
|
||||
|
|
@ -178,11 +178,11 @@ class GamificationManager:
|
|||
|
||||
def _default_state(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"spieler_name": "Python-Entwicklerin",
|
||||
"spieler_name": "IT-Entwickler",
|
||||
"xp": 0,
|
||||
"level": 1,
|
||||
"titel": "Code-Küken 🐣",
|
||||
"rang": "Bronze I",
|
||||
"titel": "Junior IT-Trainee I: Syntax & Basistypen",
|
||||
"rang": "Stufe 1",
|
||||
"geloeste_kapitel": [],
|
||||
"bestandene_tests": 0,
|
||||
"freigeschaltete_badges": [],
|
||||
|
|
|
|||
260
workspace.html
260
workspace.html
|
|
@ -47,20 +47,24 @@
|
|||
|
||||
.workspace-body {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
grid-template-columns: 42% 58%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: calc(100vh - 52px);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.pane-left {
|
||||
width: 40%;
|
||||
min-width: 280px;
|
||||
max-width: 65%;
|
||||
background: #ffffff;
|
||||
color: var(--text-main);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-right: 2px solid #e2e8f0;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.pane-tabs {
|
||||
|
|
@ -101,19 +105,21 @@
|
|||
}
|
||||
|
||||
.pane-right {
|
||||
flex: 1;
|
||||
min-width: 360px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background: #0b0f19;
|
||||
background: #090d16;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.editor-wrapper {
|
||||
flex: 1;
|
||||
height: 65%;
|
||||
min-height: 200px;
|
||||
min-height: 140px;
|
||||
position: relative;
|
||||
background: #0b0f19;
|
||||
background: #090d16;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
#monaco-editor-container {
|
||||
|
|
@ -122,12 +128,23 @@
|
|||
}
|
||||
|
||||
.terminal-wrapper {
|
||||
height: 35%;
|
||||
min-height: 140px;
|
||||
flex: 1;
|
||||
min-height: 80px;
|
||||
background: #060911;
|
||||
border-top: 2px solid rgba(255, 255, 255, 0.08);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
transition: height 0.15s ease;
|
||||
}
|
||||
|
||||
.terminal-wrapper.collapsed {
|
||||
flex: none !important;
|
||||
height: 34px !important;
|
||||
min-height: 34px !important;
|
||||
}
|
||||
|
||||
.terminal-wrapper.collapsed #workspace-terminal-output {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.terminal-header {
|
||||
|
|
@ -286,16 +303,28 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Resizable Gutter (Columns: Didaktik vs Workspace) -->
|
||||
<div class="gutter-col" id="gutter-col" title="Breite anpassen (Doppelklick für Standard 40/60)"></div>
|
||||
|
||||
<!-- Right Pane -->
|
||||
<div class="pane-right">
|
||||
<div class="editor-wrapper">
|
||||
<div class="pane-right" id="pane-right">
|
||||
<div class="editor-wrapper" id="editor-wrapper">
|
||||
<div id="monaco-editor-container"></div>
|
||||
</div>
|
||||
|
||||
<div class="terminal-wrapper">
|
||||
<!-- Resizable Gutter (Rows: Editor vs Terminal) -->
|
||||
<div class="gutter-row" id="gutter-row" title="Höhe anpassen (Doppelklick für Standard 65/35)"></div>
|
||||
|
||||
<div class="terminal-wrapper" id="terminal-wrapper">
|
||||
<div class="terminal-header">
|
||||
<span>🖥️ Terminal / Test-Ausgabe</span>
|
||||
<button type="button" id="btn-clear-terminal" style="background: transparent; border: none; color: #94a3b8; font-size: 0.75rem; cursor: pointer;">🧹 Leeren</button>
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span>🖥️ Terminal / Test-Ausgabe</span>
|
||||
<span id="test-run-indicator" style="display: none; font-size: 0.72rem; color: #38bdf8;">⏳ Läuft...</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<button type="button" id="btn-clear-terminal" style="background: transparent; border: none; color: #94a3b8; font-size: 0.75rem; cursor: pointer;" title="Terminal leeren">🧹 Leeren</button>
|
||||
<button type="button" id="btn-toggle-terminal-collapse" style="background: transparent; border: none; color: #94a3b8; font-size: 0.75rem; cursor: pointer; padding: 2px 6px;" title="Panel einklappen / ausklappen">▼</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="workspace-terminal-output">Klicke oben auf '▶ Code ausführen' (Strg+Enter) oder '🧪 Tests starten' (Strg+Shift+Enter)...</div>
|
||||
</div>
|
||||
|
|
@ -708,7 +737,206 @@
|
|||
}
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", loadWorkspaceData);
|
||||
/* --- Resizable Splitters with State Persistence & Double-Click Reset --- */
|
||||
function initSplitResizers() {
|
||||
const layout = document.querySelector(".workspace-body");
|
||||
const leftPane = document.querySelector(".pane-left");
|
||||
const gutterCol = document.getElementById("gutter-col");
|
||||
const editorWrap = document.getElementById("editor-wrapper");
|
||||
const gutterRow = document.getElementById("gutter-row");
|
||||
const termWrap = document.getElementById("terminal-wrapper");
|
||||
const btnCollapseTerm = document.getElementById("btn-toggle-terminal-collapse");
|
||||
|
||||
if (!layout || !leftPane || !gutterCol || !editorWrap || !gutterRow) return;
|
||||
|
||||
// 1. Aus localStorage wiederherstellen
|
||||
const savedLeft = localStorage.getItem("itp_split_x");
|
||||
const savedEditor = localStorage.getItem("itp_split_y");
|
||||
if (savedLeft && window.innerWidth >= 900) {
|
||||
leftPane.style.width = savedLeft + "%";
|
||||
}
|
||||
if (savedEditor) {
|
||||
editorWrap.style.height = savedEditor + "%";
|
||||
}
|
||||
|
||||
// 2. Horizontaler Resizer (Didaktik links vs. Workspace rechts)
|
||||
gutterCol.addEventListener("mousedown", (e) => {
|
||||
e.preventDefault();
|
||||
gutterCol.classList.add("dragging");
|
||||
document.body.style.cursor = "col-resize";
|
||||
document.body.style.userSelect = "none";
|
||||
|
||||
let animFrame = null;
|
||||
function onMouseMove(moveEvent) {
|
||||
if (animFrame) cancelAnimationFrame(animFrame);
|
||||
animFrame = requestAnimationFrame(() => {
|
||||
const containerWidth = layout.getBoundingClientRect().width;
|
||||
let percent = (moveEvent.clientX / containerWidth) * 100;
|
||||
if (percent < 22) percent = 22;
|
||||
if (percent > 65) percent = 65;
|
||||
leftPane.style.width = percent.toFixed(2) + "%";
|
||||
if (window.monacoEditor) window.monacoEditor.layout();
|
||||
});
|
||||
}
|
||||
|
||||
function onMouseUp() {
|
||||
gutterCol.classList.remove("dragging");
|
||||
document.body.style.cursor = "";
|
||||
document.body.style.userSelect = "";
|
||||
localStorage.setItem("itp_split_x", parseFloat(leftPane.style.width));
|
||||
window.removeEventListener("mousemove", onMouseMove);
|
||||
window.removeEventListener("mouseup", onMouseUp);
|
||||
if (window.monacoEditor) window.monacoEditor.layout();
|
||||
}
|
||||
|
||||
window.addEventListener("mousemove", onMouseMove);
|
||||
window.addEventListener("mouseup", onMouseUp);
|
||||
});
|
||||
|
||||
// Doppelklick auf Spalten-Splitter: Zurücksetzen auf 40/60
|
||||
gutterCol.addEventListener("dblclick", () => {
|
||||
leftPane.style.width = "40%";
|
||||
localStorage.setItem("itp_split_x", "40");
|
||||
if (window.monacoEditor) window.monacoEditor.layout();
|
||||
});
|
||||
|
||||
// 3. Vertikaler Resizer (Editor oben vs. Terminal unten)
|
||||
gutterRow.addEventListener("mousedown", (e) => {
|
||||
e.preventDefault();
|
||||
gutterRow.classList.add("dragging");
|
||||
document.body.style.cursor = "row-resize";
|
||||
document.body.style.userSelect = "none";
|
||||
|
||||
const rightPane = document.getElementById("pane-right");
|
||||
let animFrame = null;
|
||||
|
||||
function onMouseMove(moveEvent) {
|
||||
if (animFrame) cancelAnimationFrame(animFrame);
|
||||
animFrame = requestAnimationFrame(() => {
|
||||
const rightRect = rightPane.getBoundingClientRect();
|
||||
const relY = moveEvent.clientY - rightRect.top;
|
||||
let percent = (relY / rightRect.height) * 100;
|
||||
if (percent < 20) percent = 20;
|
||||
if (percent > 80) percent = 80;
|
||||
editorWrap.style.height = percent.toFixed(2) + "%";
|
||||
if (window.monacoEditor) window.monacoEditor.layout();
|
||||
});
|
||||
}
|
||||
|
||||
function onMouseUp() {
|
||||
gutterRow.classList.remove("dragging");
|
||||
document.body.style.cursor = "";
|
||||
document.body.style.userSelect = "";
|
||||
localStorage.setItem("itp_split_y", parseFloat(editorWrap.style.height));
|
||||
window.removeEventListener("mousemove", onMouseMove);
|
||||
window.removeEventListener("mouseup", onMouseUp);
|
||||
if (window.monacoEditor) window.monacoEditor.layout();
|
||||
}
|
||||
|
||||
window.addEventListener("mousemove", onMouseMove);
|
||||
window.addEventListener("mouseup", onMouseUp);
|
||||
});
|
||||
|
||||
// Doppelklick auf Zeilen-Splitter: Zurücksetzen auf 65/35
|
||||
gutterRow.addEventListener("dblclick", () => {
|
||||
editorWrap.style.height = "65%";
|
||||
localStorage.setItem("itp_split_y", "65");
|
||||
if (window.monacoEditor) window.monacoEditor.layout();
|
||||
});
|
||||
|
||||
// 4. Terminal Einklappen / Ausklappen
|
||||
if (btnCollapseTerm && termWrap) {
|
||||
btnCollapseTerm.addEventListener("click", () => {
|
||||
const isCollapsed = termWrap.classList.toggle("collapsed");
|
||||
btnCollapseTerm.innerText = isCollapsed ? "▲" : "▼";
|
||||
btnCollapseTerm.title = isCollapsed ? "Panel ausklappen" : "Panel einklappen";
|
||||
if (isCollapsed) {
|
||||
editorWrap.style.height = "calc(100% - 34px)";
|
||||
} else {
|
||||
const savedY = localStorage.getItem("itp_split_y") || "65";
|
||||
editorWrap.style.height = savedY + "%";
|
||||
}
|
||||
setTimeout(() => {
|
||||
if (window.monacoEditor) window.monacoEditor.layout();
|
||||
}, 160);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Tastatur-Ergonomie & Global Shortcuts --- */
|
||||
function initWorkspaceShortcuts() {
|
||||
let tabTrapped = true;
|
||||
|
||||
window.addEventListener("keydown", (e) => {
|
||||
// 1. Code ausführen: Ctrl+Enter / Cmd+Enter
|
||||
if ((e.ctrlKey || e.metaKey) && !e.shiftKey && e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
const btnRun = document.getElementById("btn-run-code");
|
||||
if (btnRun) btnRun.click();
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Tests starten: Ctrl+Shift+Enter / Cmd+Shift+Enter
|
||||
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
const btnTests = document.getElementById("btn-run-tests");
|
||||
if (btnTests) btnTests.click();
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Tipps & Lösung: Ctrl+H / Cmd+H
|
||||
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "h") {
|
||||
e.preventDefault();
|
||||
const btnHints = document.getElementById("btn-show-hints");
|
||||
if (btnHints) btnHints.click();
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. Fokus in Editor: Ctrl+1 / Cmd+1
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === "1") {
|
||||
e.preventDefault();
|
||||
if (window.monacoEditor) window.monacoEditor.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. Fokus auf Theorie: Ctrl+2 / Cmd+2
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === "2") {
|
||||
e.preventDefault();
|
||||
const tabTheorie = document.querySelector(".pane-tab-btn");
|
||||
if (tabTheorie) tabTheorie.click();
|
||||
document.getElementById("tab-content-theorie")?.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// 6. Fokus ins Terminal: Ctrl+` / Cmd+`
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === "`") {
|
||||
e.preventDefault();
|
||||
document.getElementById("workspace-terminal-output")?.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// 7. Nächstes Kapitel: Ctrl+Alt+ArrowRight
|
||||
if (e.ctrlKey && e.altKey && e.key === "ArrowRight") {
|
||||
e.preventDefault();
|
||||
document.getElementById("btn-topbar-next-chapter")?.click();
|
||||
return;
|
||||
}
|
||||
|
||||
// 8. Tab-Trapping im Editor lösen mit Esc oder Ctrl+M (WCAG 2.1)
|
||||
if (e.key === "Escape" || ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "m")) {
|
||||
if (window.monaco && window.monaco.editor) {
|
||||
tabTrapped = !tabTrapped;
|
||||
window.monaco.editor.setTabFocusMode(!tabTrapped);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
loadWorkspaceData();
|
||||
initSplitResizers();
|
||||
initWorkspaceShortcuts();
|
||||
});
|
||||
</script>
|
||||
<script src="assets/course_sidebar.js"></script>
|
||||
</body>
|
||||
|
|
|
|||
Loading…
Reference in a new issue