const workspace = document.getElementById('workspace');
const spawnBtns = document.querySelectorAll('.spawn-btn');
const clearDeskBtn = document.getElementById('clear-desk-btn');
const discoveryBook = document.getElementById('discovery-book');
const discoveryProgress = document.getElementById('discovery-progress');
const atomCountEl = document.getElementById('atom-count');
const moleculeCountEl = document.getElementById('molecule-count');
const deskTitle = document.getElementById('desk-title');
const deskSubtitle = document.getElementById('desk-subtitle');
// Main Modal Elements
const infoModal = document.getElementById('info-modal');
const modalTitle = document.getElementById('modal-title');
const modalFormula = document.getElementById('modal-formula-badge');
const modalName = document.getElementById('modal-molecule-name');
const modalDesc = document.getElementById('modal-description');
const modalCloseBtn = document.getElementById('modal-close-btn');
const modalOverlay = document.querySelector('#info-modal .modal-overlay');
const modalCloseIcon = document.querySelector('#info-modal .modal-close-icon');
// Story Success Modal Elements
const storySuccessModal = document.getElementById('story-success-modal');
const storySuccessLevelName = document.getElementById('story-success-level-name');
const storySuccessText = document.getElementById('story-success-text');
const storyNextLevelBtn = document.getElementById('story-next-level-btn');
const storySuccessCloseIcon = document.querySelector('#story-success-modal .modal-close-icon');
const storySuccessOverlay = document.querySelector('#story-success-modal .modal-overlay');
// PSE Modal Elements
const pseToggleBtn = document.getElementById('pse-toggle-btn');
const pseModal = document.getElementById('pse-modal');
const pseCloseBtn = document.getElementById('pse-close-btn');
const pseCloseIcon = document.getElementById('pse-close-icon');
const pseOverlay = document.querySelector('#pse-modal .modal-overlay');
const pseSpawnBtn = document.getElementById('pse-spawn-btn');
let elementsOnDesk = [];
let draggingElement = null;
let offsetX = 0;
let offsetY = 0;
let gameMode = 'sandbox'; // 'sandbox' oder 'story'
let selectedPSEElement = null;
// Geladene Entdeckungen aus dem LocalStorage
let discoveredMolecules = new Set(JSON.parse(localStorage.getItem('chem_lab_discoveries') || '[]'));
let currentLevelIndex = parseInt(localStorage.getItem('chem_lab_story_level') || '0');
let completedLevels = JSON.parse(localStorage.getItem('chem_lab_story_completed') || '[]');
let currentAllowedAtoms = [];
// Elemente Datenbank für Spawnen (Symbole, Farben, Namen)
const elementDetails = {
'H': { color: '#ff7675', name: 'Wasserstoff' },
'He': { color: '#e84393', name: 'Helium' },
'O': { color: '#74b9ff', name: 'Sauerstoff' },
'C': { color: '#2d3436', name: 'Kohlenstoff' },
'N': { color: '#a29bfe', name: 'Stickstoff' },
'Na': { color: '#ffeaa7', name: 'Natrium' },
'Cl': { color: '#55efc4', name: 'Chlor' },
'S': { color: '#fdcb6e', name: 'Schwefel' },
'Fe': { color: '#b2bec3', name: 'Eisen' }
};
// Periodensystem Elemente Datenbank (Wissenschaftliche Details & Schalenmodell)
const pseElements = [
{ number: 1, symbol: 'H', name: 'Wasserstoff', mass: '1.008 u', category: 'nichtmetall', row: 1, col: 1, playable: true, electrons: [1], desc: 'Das häufigste und leichteste Element im Universum. Es bildet den primären Kernbrennstoff für alle leuchtenden Sterne.' },
{ number: 2, symbol: 'He', name: 'Helium', mass: '4.003 u', category: 'edelgas', row: 1, col: 18, playable: true, electrons: [2], desc: 'Ein absolut reaktionsträges Edelgas. Es brennt nicht und ist viel leichter als Luft – perfekt für Ballons und lässt Stimmen lustig hoch klingen!' },
{ number: 3, symbol: 'Li', name: 'Lithium', mass: '6.94 u', category: 'alkalimetall', row: 2, col: 1, playable: false, electrons: [2, 1], desc: 'Ein extrem leichtes, reaktives Metall. Es ist das Herzstück moderner Lithium-Ionen-Akkus für Handys, Laptops und Elektroautos.' },
{ number: 4, symbol: 'Be', name: 'Beryllium', mass: '9.012 u', category: 'erdalkali', row: 2, col: 2, playable: false, electrons: [2, 2], desc: 'Ein seltenes, hochgiftiges Erdalkalimetall. Es ist extrem steif und temperaturbeständig und wird deshalb im Weltraumteleskop-Bau (z.B. James Webb) eingesetzt.' },
{ number: 5, symbol: 'B', name: 'Bor', mass: '10.81 u', category: 'metalloid', row: 2, col: 13, playable: false, electrons: [2, 3], desc: 'Ein hartes, hitzebeständiges Halbmetall. Es wird für extrem widerstandsfähiges Glas (Borosilikatglas) und im Raketentreibstoff-Sektor verwendet.' },
{ number: 6, symbol: 'C', name: 'Kohlenstoff', mass: '12.011 u', category: 'nichtmetall', row: 2, col: 14, playable: true, electrons: [2, 4], desc: 'Die chemische Basis allen Lebens auf der Erde! Kohlenstoff kommt in reiner Form sowohl als weicher Graphit (Bleistift) als auch als extrem harter Diamant vor.' },
{ number: 7, symbol: 'N', name: 'Stickstoff', mass: '14.007 u', category: 'nichtmetall', row: 2, col: 15, playable: true, electrons: [2, 5], desc: 'Stickstoff bildet fast 78% unserer Atemluft! Er reagiert unter normalen Bedingungen kaum und schützt die Erde vor unkontrollierbaren Bränden.' },
{ number: 8, symbol: 'O', name: 'Sauerstoff', mass: '15.999 u', category: 'nichtmetall', row: 2, col: 16, playable: true, electrons: [2, 6], desc: 'Lebensnotwendig für fast alle Tiere. Es ist hochreaktiv und verbindet sich leidenschaftlich gern mit anderen Elementen (Rosten von Eisen, Verbrennung).' },
{ number: 9, symbol: 'F', name: 'Fluor', mass: '18.998 u', category: 'halogen', row: 2, col: 17, playable: false, electrons: [2, 7], desc: 'Das reaktivste aller chemischen Elemente! Fluor-Gas zerfrisst fast jeden Stoff sofort unter Flammenbildung. Spuren von Fluorid schützen unseren Zahnschmelz.' },
{ number: 10, symbol: 'Ne', name: 'Neon', mass: '20.180 u', category: 'edelgas', row: 2, col: 18, playable: false, electrons: [2, 8], desc: 'Ein extrem reaktionsträges Edelgas. Unter Hochspannung leuchtet es intensiv rötlich-orange, weshalb es der Namensgeber für alle Leuchtreklamen ist.' },
{ number: 11, symbol: 'Na', name: 'Natrium', mass: '22.990 u', category: 'alkalimetall', row: 3, col: 1, playable: true, electrons: [2, 8, 1], desc: 'Ein extrem reaktionsfreudiges, weiches Alkalimetall. Es reagiert explosionsartig mit Wasser und muss daher in zähem Öl (Petroleum) gelagert werden.' },
{ number: 12, symbol: 'Mg', name: 'Magnesium', mass: '24.305 u', category: 'erdalkali', row: 3, col: 2, playable: false, electrons: [2, 8, 2], desc: 'Ein leichtes Erdalkalimetall. Es brennt mit einer extrem hellen, blendend weißen Flamme und ist wichtig für die Muskel- und Nervenfunktion im Körper.' },
{ number: 13, symbol: 'Al', name: 'Aluminium', mass: '26.982 u', category: 'metall', row: 3, col: 13, playable: false, electrons: [2, 8, 3], desc: 'Das dritthäufigste Element der Erdkruste. Es ist leicht, rostfrei und wird für Flugzeuge, Alufolie, Getränkedosen und Autoteile verwendet.' },
{ number: 14, symbol: 'Si', name: 'Silicium', mass: '28.085 u', category: 'metalloid', row: 3, col: 14, playable: false, electrons: [2, 8, 4], desc: 'Das Element, das unsere Computertechnologie antreibt! Silicium ist ein Halbleiter und die absolute Basis aller Mikrochips und Solarzellen.' },
{ number: 15, symbol: 'P', name: 'Phosphor', mass: '30.974 u', category: 'nichtmetall', row: 3, col: 15, playable: false, electrons: [2, 8, 5], desc: 'Ein lebenswichtiges Element, das im menschlichen Erbgut (DNA) und in Knochen vorkommt. Roter Phosphor wird auf Streichholz-Reibflächen verwendet.' },
{ number: 16, symbol: 'S', name: 'Schwefel', mass: '32.06 u', category: 'nichtmetall', row: 3, col: 16, playable: true, electrons: [2, 8, 6], desc: 'Ein zitronengelber, geruchloser Feststoff. Seine organischen Verbindungen riechen oft extrem faulig (z.B. faule Eier). Schwefel ist wichtig für Haare und Proteine.' },
{ number: 17, symbol: 'Cl', name: 'Chlor', mass: '35.45 u', category: 'halogen', row: 3, col: 17, playable: true, electrons: [2, 8, 7], desc: 'Ein giftiges, stechend riechendes gelbgrünes Gas. Chlor ist extrem reaktiv und wird weltweit zur Desinfektion von Trinkwasser und Pools genutzt.' },
{ number: 18, symbol: 'Ar', name: 'Argon', mass: '39.948 u', category: 'edelgas', row: 3, col: 18, playable: false, electrons: [2, 8, 8], desc: 'Das dritthäufigste Gas in unserer Atmosphäre. Argon wird als ungiftiges Schutzgas beim Schweißen und in Doppelglasscheiben zur Wärmeisolierung eingesetzt.' },
{ number: 19, symbol: 'K', name: 'Kalium', mass: '39.098 u', category: 'alkalimetall', row: 4, col: 1, playable: false, electrons: [2, 8, 8, 1], desc: 'Ein weiches, hochreaktives Alkalimetall. Es reagiert noch heftiger mit Wasser als Natrium und ist essenziell für die Weiterleitung von Nervensignalen.' },
{ number: 20, symbol: 'Ca', name: 'Calcium', mass: '40.078 u', category: 'erdalkali', row: 4, col: 2, playable: false, electrons: [2, 8, 8, 2], desc: 'Der elementare Baustein für Skelette und Zähne! Calcium bildet Knochen, Eierschalen, Muscheln und Kreide und ist wichtig für die Muskelarbeit.' },
{ number: 26, symbol: 'Fe', name: 'Eisen', mass: '55.845 u', category: 'uebergang', row: 4, col: 8, playable: true, electrons: [2, 8, 14, 2], desc: 'Das am häufigsten genutzte Metall der Menschheit. Eisen bildet das Zentrum unseres Erdkerns (Magnetfeld) und transportiert Sauerstoff in unserem Blut (Hämoglobin).' }
];
// Rezepturenbuch (die Formeln müssen genau den Kombinationen der Symbole entsprechen)
const recipes = [
{
id: 'h2o',
ingredients: ['H', 'H', 'O'], // 2x Wasserstoff, 1x Sauerstoff
result: 'H₂O (Wasser)',
color: '#3498db',
desc: 'Wasser ist die absolute Grundlage allen bekannten Lebens! Über 70% der Erdoberfläche sind mit Wasser bedeckt, und auch unser eigener Körper besteht zu mehr als der Hälfte daraus.'
},
{
id: 'nacl',
ingredients: ['Cl', 'Na'], // 1x Natrium, 1x Chlor
result: 'NaCl (Kochsalz)',
color: '#f5f6fa',
desc: 'Natriumchlorid kennen wir alle als einfaches Speisesalz. Faszinierend: Natrium ist ein explosives, hochreaktives Metall und Chlor ein giftiges Gas – zusammen ergeben sie ein absolut lebenswichtiges und leckeres Gewürz!'
},
{
id: 'o2',
ingredients: ['O', 'O'], // 2x Sauerstoff
result: 'O₂ (Sauerstoff-Molekül)',
color: '#a8dadc',
desc: 'Der molekulare Sauerstoff, den wir jede Sekunde atmen. Pflanzen produzieren ihn als Abfallprodukt bei der Photosynthese, während sie Kohlendioxid abbauen.'
},
{
id: 'co2',
ingredients: ['C', 'O', 'O'], // 1x Kohlenstoff, 2x Sauerstoff
result: 'CO₂ (Kohlendioxid)',
color: '#7f8c8d',
desc: 'Ein farb- und geruchloses Treibhausgas. Wir atmen es ständig aus. Obwohl es die Erderwärmung anheizt, ist es für Pflanzen lebenswichtig, da sie es zur Photosynthese benötigen.'
},
{
id: 'ch4',
ingredients: ['C', 'H', 'H', 'H', 'H'], // 1x Kohlenstoff, 4x Wasserstoff
result: 'CH₄ (Methan)',
color: '#fdcb6e',
desc: 'Methan ist das einfachste Alkan und der Hauptbestandteil von brennbarem Erdgas. Es brennt sauber ab, ist aber als Treibhausgas in der Atmosphäre rund 25-mal klimaschädlicher als CO₂!'
},
{
id: 'nh3',
ingredients: ['N', 'H', 'H', 'H'], // 1x Stickstoff, 3x Wasserstoff
result: 'NH₃ (Ammoniak)',
color: '#9b59b6',
desc: 'Ein stechend riechendes, farbloses Gas. Ammoniak ist extrem wichtig für die chemische Industrie, da es die Grundlage für fast alle Kunstdünger bildet (erfunden durch das Haber-Bosch-Verfahren).'
},
{
id: 'hcl',
ingredients: ['Cl', 'H'], // 1x Chlor, 1x Wasserstoff
result: 'HCl (Salzsäure)',
color: '#e17055',
desc: 'Chlorwasserstoff ist ein stechendes Gas. In Wasser gelöst bildet es die extrem ätzende Salzsäure – diese ist auch ein wichtiger Bestandteil unseres eigenen Magensaftes, um Nahrung zu zersetzen!'
},
{
id: 'naoh',
ingredients: ['H', 'Na', 'O'], // 1x Natrium, 1x Sauerstoff, 1x Wasserstoff
result: 'NaOH (Natronlauge)',
color: '#fd79a8',
desc: 'Natriumhydroxid ist eine extrem starke Base. In Wasser gelöst wird es als Natronlauge bezeichnet, die Haare und Fett auflösen kann, weshalb man sie oft in Abflussreinigern findet. Sie wird auch für echte Seife genutzt.'
},
{
id: 'h2s',
ingredients: ['H', 'H', 'S'], // 2x Wasserstoff, 1x Schwefel
result: 'H₂S (Schwefelwasserstoff)',
color: '#ffeaa7',
desc: 'Dieses hochentzündliche und extrem giftige Gas riecht unverkennbar nach faulen Eiern! Es entsteht bei Fäulnisprozessen von Proteinen, im Darm und in vulkanischen Gasen.'
},
{
id: 'fes',
ingredients: ['Fe', 'S'], // 1x Eisen, 1x Schwefel
result: 'FeS (Eisensulfid)',
color: '#2d3436',
desc: 'Ein dunkelbrauner/schwarzer Feststoff. Das Mischen und Erhitzen von Eisenpulver und Schwefelpulver ist der absolute Klassiker im Chemie-Unterricht, um eine chemische Synthese zu demonstrieren.'
},
{
id: 'so2',
ingredients: ['O', 'O', 'S'], // 1x Schwefel, 2x Sauerstoff
result: 'SO₂ (Schwefeldioxid)',
color: '#b2bec3',
desc: 'Ein stechend riechendes, saures Gas, das beim Verbrennen von schwefelhaltigen fossilen Brennstoffen entsteht. Es schädigt die Atemwege und trägt in Wolken zur Bildung von saurem Regen bei.'
},
{
id: 'co',
ingredients: ['C', 'O'], // 1x Kohlenstoff, 1x Sauerstoff
result: 'CO (Kohlenmonoxid)',
color: '#636e72',
desc: 'Ein unsichtbares, geruchloses und hochgradig tödliches Gas! Es entsteht bei der unvollständigen Verbrennung von Kohle oder Holz und blockiert die Sauerstoffaufnahme im menschlichen Blutkreislauf.'
},
{
id: 'h2o2',
ingredients: ['H', 'H', 'O', 'O'], // 2x Wasserstoff, 2x Sauerstoff
result: 'H₂O₂ (Wasserstoffperoxid)',
color: '#74b9ff',
desc: 'Wasserstoffperoxid ist ein extrem starkes Bleichmittel und Oxidationsmittel. Es wird zum Blondieren von Haaren, zum Desinfizieren von Wunden und industriell zum Bleichen von Papier genutzt.'
},
{
id: 'hcn',
ingredients: ['C', 'H', 'N'], // 1x Wasserstoff, 1x Kohlenstoff, 1x Stickstoff
result: 'HCN (Blausäure)',
color: '#d63031',
desc: 'Cyanwasserstoff ist ein extrem giftiges Gas, das gelöst in Wasser als Blausäure bezeichnet wird. Es riecht für manche Menschen charakteristisch nach bitteren Mandeln und wirkt in Sekunden tödlich.'
},
{
id: 'n2',
ingredients: ['N', 'N'], // 2x Stickstoff
result: 'N₂ (Stickstoff)',
color: '#a29bfe',
desc: 'Stickstoff ist ein extrem reaktionsträges (inertes) Gas und bildet fast 78% der Luft, die wir atmen! Er schützt unseren Planeten vor verheerenden globalen Bränden, die in reinem Sauerstoff wüten würden.'
},
{
id: 'cl2',
ingredients: ['Cl', 'Cl'], // 2x Chlor
result: 'Cl₂ (Chlor)',
color: '#55efc4',
desc: 'Chlor-Gas ist giftig, hat einen stechenden Geruch und eine grüngelbe Farbe. Es wurde im Ersten Weltkrieg als Kampfgas eingesetzt, rettet heute aber als Desinfektionsmittel in Trinkwasser und Pools genutzt.'
},
{
id: 'h2',
ingredients: ['H', 'H'], // 2x Wasserstoff
result: 'H₂ (Wasserstoff)',
color: '#ff7675',
desc: 'Das häufigste, einfachste und leichteste Element im Universum. Wasserstoff ist hochentzündlich (Knallgas-Reaktion) und bildet den primären Kernbrennstoff für alle leuchtenden Sterne.'
},
{
id: 'c2h4',
ingredients: ['C', 'C', 'H', 'H', 'H', 'H'], // 2x Kohlenstoff, 4x Wasserstoff
result: 'C₂H₄ (Ethen)',
color: '#ffeaa7',
desc: 'Ethen (Ethylen) ist ein gasförmiges Pflanzenhormon, das Früchte reifen lässt. Industriell ist es der absolut wichtigste Baustein zur Herstellung des Kunststoffs Polyethylen (PE).'
}
];
// Story-Modus Level-Datenbank
const levels = [
{
id: 1,
title: "Die salzige Suppe",
goalFormula: "nacl",
allowedAtoms: ['Na', 'Cl'],
intro: "Hallo Lehrling! Schön dich im Labor zu sehen. Der herzhafte Eintopf unseres Kantinenkochs schmeckt heute nach gar nichts... Er hat das Salz vergessen! Kannst du ihm helfen und 1x Kochsalz (NaCl) synthetisieren? Ziehe einfach ein Natrium-Atom (Na) und ein Chlor-Atom (Cl) auf dem Tisch zusammen!",
successText: "Großartig! Der Eintopf ist gerettet und schmeckt hervorragend. Du hast soeben gelernt, dass aus zwei gefährlichen Reinstoffen (explosives Metall und giftiges Gas) ein lebenswichtiges Gewürz entstehen kann. Bereit für die nächste Herausforderung?",
reward: "Schaltet Level 2 frei"
},
{
id: 2,
title: "Das Lebenselixier",
goalFormula: "h2o",
allowedAtoms: ['H', 'O'],
intro: "Hervorragend gemacht! Jetzt haben wir aber ein anderes Problem: Der Wasserspender im Labor ist leer und die Hitze heute ist unerträglich. Wir brauchen dringend frisches Wasser (H₂O)! Synthetisiere uns ein Wassermolekül aus zwei Wasserstoff-Atomen (H) und einem Sauerstoff-Atom (O).",
successText: "Ah, erfrischend! Kaltes, sauberes H₂O. Wusstest du, dass Wassermoleküle wegen ihrer Polarität so genial zusammenhalten? Du bist auf dem besten Weg zum Meister-Alchemisten!",
reward: "Schaltet Level 3 frei"
},
{
id: 3,
title: "Der Mülleimerbrand",
goalFormula: "co2",
allowedAtoms: ['C', 'O'],
intro: "Achtung! Ein Missgeschick ist passiert: Jemand hat ein heißes Streichholz in den Mülleimer geworfen und es brennt! Wasser nützt hier nichts, wir brauchen ein erstickendes Gas. Synthetisiere schnell 1x Kohlendioxid (CO₂) aus einem Kohlenstoff-Atom (C) und zwei Sauerstoff-Atomen (O), um die Flammen zu löschen!",
successText: "Puh, gerettet! Das CO₂ hat den Sauerstoff verdrängt und das Feuer im Handumdrehen erstickt. Ein echter Lebensretter-Einsatz! Aber lüfte danach gut durch.",
reward: "Schaltet Level 4 frei"
},
{
id: 4,
title: "Dünger für die Ernte",
goalFormula: "nh3",
allowedAtoms: ['N', 'H'],
intro: "Ein lokaler Bauer hat uns um Hilfe gebeten. Seine Nutzpflanzen wachsen schlecht und er benötigt dringend Dünger. Der wichtigste Rohstoff dafür ist Ammoniak (NH₃). Synthetisiere ein Ammoniak-Molekül aus einem Stickstoff-Atom (N) und drei Wasserstoff-Atomen (H)!",
successText: "Hervorragende Arbeit! Das NH₃ ist fertig. Es riecht zwar extrem stechend nach Urin, aber ohne diesen Kunstdünger (Haber-Bosch-Verfahren) könnten wir heute nicht die gesamte Weltbevölkerung ernähren!",
reward: "Schaltet Level 5 frei"
},
{
id: 5,
title: "Die Verdauungshilfe",
goalFormula: "hcl",
allowedAtoms: ['H', 'Cl'],
intro: "Unser Labor-Maskottchen (ein kleiner gefräßiger Drache) hat Bauchschmerzen, weil er zu viel gefressen hat. Wir müssen seine Verdauung ankurbeln! Synthetisiere Salzsäure (HCl) aus einem Wasserstoff-Atom (H) und einem Chlor-Atom (Cl), um seinen Magensaft zu verstärken!",
successText: "Perfekt! Dem kleinen Drachen geht es schon viel besser. Salzsäure ist extrem sauer und zersetzt selbst zähes Fleisch im Handumdrehen. Du hast den Story-Modus abgeschlossen! Du bist jetzt ein zertifizierter Labor-Meister!",
reward: "Story-Modus abgeschlossen! 🎉"
}
];
// Initiales Laden
updateStats();
renderDiscoveryBook();
// Button-Klicks registrieren für Spawnen in Sidebar
spawnBtns.forEach(btn => {
btn.addEventListener('click', () => {
const symbol = btn.dataset.element;
const color = btn.dataset.color;
spawnAtom(symbol, color);
});
});
// Tisch leeren Button
clearDeskBtn.addEventListener('click', () => {
workspace.innerHTML = '';
elementsOnDesk = [];
updateStats();
});
// =========================================
// MODALS LOGIC
// =========================================
// Info-Modal (Entdeckungen) Schließen
modalCloseBtn.addEventListener('click', closeModal);
modalOverlay.addEventListener('click', closeModal);
modalCloseIcon.addEventListener('click', closeModal);
function closeModal() {
infoModal.classList.add('hidden');
}
// PSE Modal Steuerung
pseToggleBtn.addEventListener('click', () => {
pseModal.classList.remove('hidden');
renderPSEGrid();
});
pseCloseBtn.addEventListener('click', closePSE);
pseCloseIcon.addEventListener('click', closePSE);
pseOverlay.addEventListener('click', closePSE);
function closePSE() {
pseModal.classList.add('hidden');
}
// PSE Atom Spawnen Event
pseSpawnBtn.addEventListener('click', () => {
if (selectedPSEElement) {
spawnAtom(selectedPSEElement.symbol, elementDetails[selectedPSEElement.symbol].color);
closePSE();
}
});
// =========================================
// GAMEPLAY CORE LOGIC
// =========================================
function spawnAtom(symbol, color) {
const el = document.createElement('div');
el.classList.add('atom');
el.innerText = symbol;
el.dataset.symbol = symbol;
el.style.backgroundColor = color;
// Farblich angepasster Text für dunkle/kontrastreiche Elemente
if (symbol === 'C' || symbol === 'Fe') {
el.style.color = '#fff';
} else {
el.style.color = '#333';
}
// Zufällige Position auf dem Tisch
const x = Math.random() * (workspace.clientWidth - 60);
const y = Math.random() * (workspace.clientHeight - 60);
el.style.left = x + 'px';
el.style.top = y + 'px';
makeDraggable(el);
workspace.appendChild(el);
elementsOnDesk.push(el);
updateStats();
// Animation beim Erscheinen
el.animate([
{ transform: 'scale(0)' },
{ transform: 'scale(1.1)' },
{ transform: 'scale(1)' }
], { duration: 250, easing: 'ease-out' });
}
function makeDraggable(el) {
el.addEventListener('mousedown', (e) => {
draggingElement = el;
const rect = el.getBoundingClientRect();
offsetX = e.clientX - rect.left;
offsetY = e.clientY - rect.top;
el.style.zIndex = 1000;
// Füge Schatten beim Ziehen hinzu
el.style.boxShadow = '0 10px 20px rgba(0,0,0,0.4)';
});
}
document.addEventListener('mousemove', (e) => {
if (draggingElement) {
const workspaceRect = workspace.getBoundingClientRect();
let newX = e.clientX - workspaceRect.left - offsetX;
let newY = e.clientY - workspaceRect.top - offsetY;
// Grenzen des Workspaces beachten
newX = Math.max(0, Math.min(newX, workspaceRect.width - draggingElement.offsetWidth));
newY = Math.max(0, Math.min(newY, workspaceRect.height - draggingElement.offsetHeight));
draggingElement.style.left = newX + 'px';
draggingElement.style.top = newY + 'px';
}
});
document.addEventListener('mouseup', () => {
if (draggingElement) {
draggingElement.style.zIndex = '';
draggingElement.style.boxShadow = '';
checkForReactions();
draggingElement = null;
}
});
function checkForReactions() {
if (!draggingElement) return;
// Finde alle Elemente, die sich berühren (inklusive des gezogenen Elements)
let cluster = [draggingElement];
let added = true;
while(added) {
added = false;
for (let el of elementsOnDesk) {
if (cluster.includes(el)) continue;
// Prüfe Kollision mit irgendeinem Element im Cluster
let touches = cluster.some(cEl => {
const r1 = cEl.getBoundingClientRect();
const r2 = el.getBoundingClientRect();
return !(r1.right < r2.left ||
r1.left > r2.right ||
r1.bottom < r2.top ||
r1.top > r2.bottom);
});
if (touches) {
cluster.push(el);
added = true;
}
}
}
if (cluster.length > 1) {
// Ignoriere Cluster, die bereits Moleküle enthalten (nur Atome fusionieren in diesem Prototyp)
const hasMolecule = cluster.some(el => el.classList.contains('molecule'));
if (hasMolecule) return;
const symbols = cluster.map(el => el.dataset.symbol).sort();
// Prüfe gegen alle Rezepte
for (const recipe of recipes) {
const recipeSymbols = [...recipe.ingredients].sort();
if (JSON.stringify(symbols) === JSON.stringify(recipeSymbols)) {
// REAKTION!
const midX = parseFloat(draggingElement.style.left) + draggingElement.offsetWidth / 2;
const midY = parseFloat(draggingElement.style.top) + draggingElement.offsetHeight / 2;
// Lösche fusionierte Atome vom Tisch
cluster.forEach(el => {
if (workspace.contains(el)) {
workspace.removeChild(el);
}
elementsOnDesk = elementsOnDesk.filter(e => e !== el);
});
// Partikel-Explosion erzeugen
triggerExplosion(midX, midY, recipe.color);
// Neues Molekül erstellen
const molX = midX - 60; // Zentrieren
const molY = midY - 20;
createMolecule(recipe, molX, molY);
// Entdeckung registrieren
const isNew = !discoveredMolecules.has(recipe.id);
if (isNew) {
discoveredMolecules.add(recipe.id);
localStorage.setItem('chem_lab_discoveries', JSON.stringify([...discoveredMolecules]));
renderDiscoveryBook();
}
// Story-Modus Ziel prüfen
if (gameMode === 'story') {
const currentLevel = levels[currentLevelIndex];
if (recipe.id === currentLevel.goalFormula) {
handleLevelSuccess(currentLevel, recipe);
break;
}
}
// Info-Modal anzeigen (bei neuer Entdeckung)
showModal(recipe, isNew);
break;
}
}
}
}
function createMolecule(recipe, x, y) {
const el = document.createElement('div');
el.classList.add('molecule');
el.innerText = recipe.result.split(' ')[0]; // Zeigt z.B. H₂O an
el.dataset.symbol = recipe.result;
el.style.backgroundColor = recipe.color;
// Grenzen im Workspace einhalten
const maxLeft = workspace.clientWidth - 120;
const maxTop = workspace.clientHeight - 40;
el.style.left = Math.max(0, Math.min(x, maxLeft)) + 'px';
el.style.top = Math.max(0, Math.min(y, maxTop)) + 'px';
makeDraggable(el);
workspace.appendChild(el);
elementsOnDesk.push(el);
updateStats();
// Entstehungsanimation
el.animate([
{ transform: 'scale(0.5)' },
{ transform: 'scale(1.2)' },
{ transform: 'scale(1)' }
], { duration: 300, easing: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)' });
}
function triggerExplosion(x, y, color) {
const numParticles = 18;
for (let i = 0; i < numParticles; i++) {
const p = document.createElement('div');
p.classList.add('particle');
p.style.backgroundColor = color || '#ff7675';
p.style.left = x + 'px';
p.style.top = y + 'px';
workspace.appendChild(p);
// Zufälliger Winkel und Geschwindigkeit für Explosion
const angle = Math.random() * Math.PI * 2;
const velocity = 40 + Math.random() * 80;
const targetX = Math.cos(angle) * velocity;
const targetY = Math.sin(angle) * velocity + 20; // Leichte Gravitation
p.animate([
{ transform: 'translate(0, 0) scale(1)', opacity: 1 },
{ transform: `translate(${targetX}px, ${targetY}px) scale(0)`, opacity: 0 }
], {
duration: 600 + Math.random() * 300,
easing: 'cubic-bezier(0.1, 0.8, 0.3, 1)',
fill: 'forwards'
});
setTimeout(() => p.remove(), 1000);
}
}
function showModal(recipe, isNew = true) {
if (isNew) {
modalTitle.innerText = "Molekül entdeckt! 🎉";
document.querySelector('#info-modal .modal-emoji').innerText = "🎉";
} else {
modalTitle.innerText = "Molekül-Lexikon 📖";
document.querySelector('#info-modal .modal-emoji').innerText = "📖";
}
// Subscripts für Formel schön rendern
const prettyFormula = recipe.id.toUpperCase().replace(/\d/g, m => '' + m + '');
modalFormula.innerHTML = prettyFormula;
modalFormula.style.backgroundColor = recipe.color;
// Reiner Name (Text in Klammern aus z.B. "H₂O (Wasser)")
const cleanName = recipe.result.substring(recipe.result.indexOf('(') + 1, recipe.result.indexOf(')'));
modalName.innerText = cleanName;
modalDesc.innerText = recipe.desc;
infoModal.classList.remove('hidden');
}
function renderDiscoveryBook() {
discoveryBook.innerHTML = '';
let count = 0;
recipes.forEach(recipe => {
const isDiscovered = discoveredMolecules.has(recipe.id);
const item = document.createElement('div');
item.classList.add('book-item');
// Formel mit tiefgestellten Ziffern
const prettyFormula = recipe.id.toUpperCase().replace(/\d/g, m => '' + m + '');
if (isDiscovered) {
item.classList.add('unlocked');
// Reiner Name aus Rezept
const cleanName = recipe.result.substring(recipe.result.indexOf('(') + 1, recipe.result.indexOf(')'));
item.innerHTML = `
${prettyFormula}
${cleanName}
🔓
`;
item.addEventListener('click', () => {
showModal(recipe, false);
});
count++;
} else {
item.classList.add('locked');
item.innerHTML = `
🧪 ???
Unbekannt
🔒
`;
}
discoveryBook.appendChild(item);
});
discoveryProgress.innerText = `${count}/${recipes.length}`;
}
function updateStats() {
const atoms = elementsOnDesk.filter(el => el.classList.contains('atom')).length;
const molecules = elementsOnDesk.filter(el => el.classList.contains('molecule')).length;
atomCountEl.innerText = atoms;
moleculeCountEl.innerText = molecules;
}
// =========================================
// INTERACTIVE PSE LOGIC
// =========================================
function renderPSEGrid() {
const grid = document.getElementById('pse-grid');
grid.innerHTML = '';
pseElements.forEach(el => {
const div = document.createElement('div');
div.classList.add('pse-el');
div.style.gridRow = el.row;
div.style.gridColumn = el.col;
div.dataset.symbol = el.symbol;
div.innerHTML = `
${el.number}
${el.symbol}
${el.name}
`;
if (el.playable) {
div.classList.add('active-el');
div.classList.add(el.category);
div.addEventListener('click', () => {
selectElementSteckbrief(el);
});
} else {
div.classList.add('locked-el');
div.addEventListener('click', () => {
// Selektiert dekorative Elemente trotzdem für den Steckbrief (Lerneffekt!), aber deaktiviert Spawnen
selectElementSteckbrief(el, false);
});
}
grid.appendChild(div);
});
}
function selectElementSteckbrief(el, canSpawn = true) {
selectedPSEElement = el;
const emptyDiv = document.querySelector('.steckbrief-empty');
const cardDiv = document.querySelector('.steckbrief-card');
emptyDiv.classList.add('hidden');
cardDiv.classList.remove('hidden');
const box = document.getElementById('steckbrief-box');
box.innerText = el.symbol;
box.className = ''; // Zurücksetzen
box.classList.add('active-el', el.category);
// Spezieller Textkontrast für dunkle Elemente im Steckbrief
if (el.symbol === 'C' || el.symbol === 'Fe') {
box.style.color = '#fff';
} else {
box.style.color = '#333';
}
document.getElementById('steckbrief-name').innerText = el.name;
// Kategoriennamen lesbar übersetzen
const categoryNames = {
'nichtmetall': 'Nichtmetall',
'edelgas': 'Edelgas',
'alkalimetall': 'Alkalimetall',
'erdalkali': 'Erdalkalimetall',
'uebergang': 'Übergangsmetall',
'halogen': 'Halogen',
'metalloid': 'Halbmetall',
'metall': 'Metall'
};
document.getElementById('steckbrief-category').innerText = categoryNames[el.category] || el.category;
document.getElementById('steckbrief-category').className = '';
document.getElementById('steckbrief-category').classList.add(el.category);
document.getElementById('steckbrief-number').innerText = el.number;
document.getElementById('steckbrief-mass').innerText = el.mass;
document.getElementById('steckbrief-desc').innerText = el.desc;
// Spawnen-Button steuern
if (canSpawn && (gameMode === 'sandbox' || currentAllowedAtoms.includes(el.symbol))) {
pseSpawnBtn.classList.remove('hidden');
} else {
pseSpawnBtn.classList.add('hidden');
}
// Bohr-Modell Schalen visualisieren
renderBohrModel(el.electrons);
}
function renderBohrModel(electronsArray) {
const shellsContainer = document.getElementById('bohr-shells');
shellsContainer.innerHTML = '';
electronsArray.forEach((numElectrons, shellIndex) => {
const shellDiv = document.createElement('div');
shellDiv.classList.add('bohr-shell');
// Dynamischer Schalendurchmesser basierend auf Schalenindex (Concentric Circles)
const diameter = 40 + (shellIndex * 30);
shellDiv.style.width = diameter + 'px';
shellDiv.style.height = diameter + 'px';
// Geschwindigkeit variieren für coole Dynamik
shellDiv.style.animationDuration = (6 + shellIndex * 4) + 's';
// Elektronenpunkte auf der Kreisbahn berechnen & platzieren
for (let i = 0; i < numElectrons; i++) {
const electron = document.createElement('div');
electron.classList.add('bohr-electron');
// Winkel für Gleichverteilung berechnen
const angle = (i / numElectrons) * Math.PI * 2;
const radius = diameter / 2;
const x = Math.cos(angle) * radius;
const y = Math.sin(angle) * radius;
// Auf der CSS-Kreisbahn zentrieren
electron.style.left = `calc(50% + ${x}px - 3px)`;
electron.style.top = `calc(50% + ${y}px - 3px)`;
shellDiv.appendChild(electron);
}
shellsContainer.appendChild(shellDiv);
});
}
// =========================================
// STORY MODE LOGIC
// =========================================
// Dom Tabs switching
const tabSandboxBtn = document.getElementById('tab-sandbox-btn');
const tabStoryBtn = document.getElementById('tab-story-btn');
const sandboxContentEl = document.getElementById('sandbox-content');
const storyContentEl = document.getElementById('story-content');
tabSandboxBtn.addEventListener('click', () => {
switchMode('sandbox');
});
tabStoryBtn.addEventListener('click', () => {
switchMode('story');
});
function switchMode(mode) {
gameMode = mode;
workspace.innerHTML = '';
elementsOnDesk = [];
updateStats();
closeModal();
closeStorySuccessModal();
if (mode === 'sandbox') {
tabSandboxBtn.classList.add('active');
tabStoryBtn.classList.remove('active');
sandboxContentEl.classList.remove('hidden');
storyContentEl.classList.add('hidden');
deskTitle.innerText = "Labor-Tisch (Sandbox)";
deskSubtitle.innerText = "Ziehe Atome übereinander, um Reaktionen frei auszulösen!";
currentAllowedAtoms = Object.keys(elementDetails);
} else {
tabSandboxBtn.classList.remove('active');
tabStoryBtn.classList.add('active');
sandboxContentEl.classList.add('hidden');
storyContentEl.classList.remove('hidden');
loadStoryLevel(currentLevelIndex);
}
}
function loadStoryLevel(index) {
if (index >= levels.length) {
index = levels.length - 1;
}
currentLevelIndex = index;
localStorage.setItem('chem_lab_story_level', currentLevelIndex);
const level = levels[currentLevelIndex];
currentAllowedAtoms = level.allowedAtoms;
deskTitle.innerText = `Labor-Tisch (Story: ${level.title})`;
const recipe = recipes.find(r => r.id === level.goalFormula);
const cleanName = recipe.result.substring(recipe.result.indexOf('(') + 1, recipe.result.indexOf(')'));
deskSubtitle.innerText = `Ziel: Synthetisiere ${cleanName}`;
// Level Auswahldots zeichnen
renderLevelDots();
// Quest Panel updaten
document.getElementById('quest-level-title').innerText = `Level ${level.id}: ${level.title}`;
document.getElementById('quest-intro-text').innerText = level.intro;
const prettyFormula = recipe.id.toUpperCase().replace(/\d/g, m => '' + m + '');
document.getElementById('quest-goal-text').innerHTML = `Erzeuge 1x ${prettyFormula} (${cleanName})`;
// Checkbox zurücksetzen
const checkbox = document.querySelector('.objective-checkbox');
const objectiveItem = document.querySelector('.objective-item');
if (checkbox) checkbox.innerText = "⬜";
if (objectiveItem) objectiveItem.classList.remove('completed');
// Spawnbare Atome im Story-Gitter rendern
renderStorySpawnButtons();
}
function renderLevelDots() {
const container = document.querySelector('.level-dots-grid');
if (!container) return;
container.innerHTML = '';
levels.forEach((lvl, idx) => {
const dot = document.createElement('div');
dot.classList.add('level-dot');
dot.innerText = lvl.id;
const isCompleted = completedLevels.includes(lvl.id);
const isActive = idx === currentLevelIndex;
const isLocked = idx > 0 && !completedLevels.includes(levels[idx - 1].id);
if (isLocked) {
dot.classList.add('locked');
} else {
if (isCompleted) dot.classList.add('completed');
if (isActive) dot.classList.add('active');
dot.addEventListener('click', () => {
workspace.innerHTML = '';
elementsOnDesk = [];
updateStats();
loadStoryLevel(idx);
});
}
container.appendChild(dot);
});
const completedCount = completedLevels.length;
document.getElementById('story-overall-progress').innerText = `${completedLevels.includes(5) ? 5 : Math.max(1, completedLevels.length + 1)}/5`;
}
function renderStorySpawnButtons() {
const grid = document.getElementById('story-elements-grid');
if (!grid) return;
grid.innerHTML = '';
currentAllowedAtoms.forEach(symbol => {
const details = elementDetails[symbol];
if (details) {
const btn = document.createElement('div');
btn.classList.add('spawn-btn');
btn.dataset.element = symbol;
btn.dataset.color = details.color;
btn.innerText = symbol;
btn.title = details.name;
btn.style.backgroundColor = details.color;
if (symbol === 'C' || symbol === 'Fe') {
btn.style.color = '#fff';
} else {
btn.style.color = '#333';
}
btn.addEventListener('click', () => {
spawnAtom(symbol, details.color);
});
grid.appendChild(btn);
}
});
}
function completedMoleculesAdd(levelId) {
if (!completedLevels.includes(levelId)) {
completedLevels.push(levelId);
localStorage.setItem('chem_lab_story_completed', JSON.stringify(completedLevels));
}
}
function handleLevelSuccess(level, recipe) {
const checkbox = document.querySelector('.objective-checkbox');
const objectiveItem = document.querySelector('.objective-item');
if (checkbox) checkbox.innerText = "✅";
if (objectiveItem) objectiveItem.classList.add('completed');
completedMoleculesAdd(level.id);
renderLevelDots();
// Zeige das große Level-Erfolgs-Modal nach einer kurzen Verzögerung
setTimeout(() => {
showStorySuccessModal(level, recipe);
}, 600);
}
function showStorySuccessModal(level, recipe) {
storySuccessLevelName.innerText = `Level ${level.id} abgeschlossen!`;
storySuccessText.innerText = level.successText;
if (currentLevelIndex === levels.length - 1) {
storyNextLevelBtn.innerText = "🏆 Story-Modus beenden!";
} else {
storyNextLevelBtn.innerText = "Nächstes Level ➡️";
}
storySuccessModal.classList.remove('hidden');
}
function closeStorySuccessModal() {
storySuccessModal.classList.add('hidden');
}
// Story Next Level Button Event
storyNextLevelBtn.addEventListener('click', () => {
closeStorySuccessModal();
workspace.innerHTML = '';
elementsOnDesk = [];
updateStats();
if (currentLevelIndex < levels.length - 1) {
currentLevelIndex++;
localStorage.setItem('chem_lab_story_level', currentLevelIndex);
loadStoryLevel(currentLevelIndex);
} else {
alert("Glückwunsch! Du hast alle Level abgeschlossen. Der Sandkasten-Modus (Sandbox) steht dir weiterhin zur Verfügung!");
switchMode('sandbox');
}
});
// Zusätzliche Modal Close-Events
if (storySuccessCloseIcon) storySuccessCloseIcon.addEventListener('click', closeStorySuccessModal);
if (storySuccessOverlay) storySuccessOverlay.addEventListener('click', closeStorySuccessModal);