475 lines
18 KiB
JavaScript
475 lines
18 KiB
JavaScript
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');
|
||
|
||
// 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('.modal-overlay');
|
||
|
||
let elementsOnDesk = [];
|
||
let draggingElement = null;
|
||
let offsetX = 0;
|
||
let offsetY = 0;
|
||
|
||
// Elemente Datenbank für Spawnen (Symbole, Farben, Namen)
|
||
const elementDetails = {
|
||
'H': { color: '#ff7675', name: 'Wasserstoff' },
|
||
'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' }
|
||
};
|
||
|
||
// 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 Millionen Leben.'
|
||
},
|
||
{
|
||
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).'
|
||
}
|
||
];
|
||
|
||
// Geladene Entdeckungen aus dem LocalStorage
|
||
let discoveredMolecules = new Set(JSON.parse(localStorage.getItem('chem_lab_discoveries') || '[]'));
|
||
|
||
// Initiales Laden
|
||
updateStats();
|
||
renderDiscoveryBook();
|
||
|
||
// Button-Klicks registrieren für Spawnen
|
||
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();
|
||
});
|
||
|
||
// Modal Events
|
||
modalCloseBtn.addEventListener('click', closeModal);
|
||
modalOverlay.addEventListener('click', closeModal);
|
||
|
||
function closeModal() {
|
||
infoModal.classList.add('hidden');
|
||
}
|
||
|
||
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 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();
|
||
}
|
||
|
||
// 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 (nach unten gezogen)
|
||
|
||
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('.modal-emoji').innerText = "🎉";
|
||
} else {
|
||
modalTitle.innerText = "Molekül-Lexikon 📖";
|
||
document.querySelector('.modal-emoji').innerText = "📖";
|
||
}
|
||
|
||
// Subscripts für Formel schön rendern
|
||
const prettyFormula = recipe.id.toUpperCase().replace(/\d/g, m => '<sub>' + m + '</sub>');
|
||
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 => '<sub>' + m + '</sub>');
|
||
|
||
if (isDiscovered) {
|
||
item.classList.add('unlocked');
|
||
// Reiner Name aus Rezept
|
||
const cleanName = recipe.result.substring(recipe.result.indexOf('(') + 1, recipe.result.indexOf(')'));
|
||
|
||
item.innerHTML = `
|
||
<span class="book-formula" style="color: ${recipe.color}">${prettyFormula}</span>
|
||
<span class="book-name">${cleanName}</span>
|
||
<span class="book-status">🔓</span>
|
||
`;
|
||
|
||
item.addEventListener('click', () => {
|
||
showModal(recipe, false);
|
||
});
|
||
count++;
|
||
} else {
|
||
item.classList.add('locked');
|
||
item.innerHTML = `
|
||
<span class="book-formula" title="Zutaten: ${recipe.ingredients.sort().join(' + ')}">🧪 ???</span>
|
||
<span class="book-name" style="filter: blur(4px)">Unbekannt</span>
|
||
<span class="book-status">🔒</span>
|
||
`;
|
||
}
|
||
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;
|
||
}
|