Upgrade Chemie-Spiel: add 18 recipes, discovery book, modal, particle effects, stats, and PLAN.md

This commit is contained in:
kreidler90 2026-07-04 12:00:51 +00:00
parent 0b7c186ec0
commit e193638b70
4 changed files with 911 additions and 79 deletions

119
PLAN.md Normal file
View File

@ -0,0 +1,119 @@
# Chemie-Lernspiel Upgrade-Plan
Dieses Dokument beschreibt die geplanten Erweiterungen für das Alchemie-Labor-Chemie-Lernspiel im Verzeichnis `chemie-spiel-dev`.
---
## 1. Ziele des Upgrades
- **Lernwert steigern:** Spieler erhalten spannende und verständliche Fakten über die entdeckten Moleküle.
- **Gamification:** Ein "Entdecker-Buch" (Lexikon/Bibliothek) im Sidebar motiviert dazu, alle Moleküle freizuschalten. Fortschritt wird im Browser (`localStorage`) gespeichert.
- **Visuelles Feedback & Saftigkeit (Juiciness):** Reaktionen erzeugen Partikel-Explosionen, Atome sehen aus wie 3D-Kugeln, und das Interface fühlt sich dynamischer an.
- **Mehr Inhalt:** Zusätzliche Elemente (Schwefel, Eisen) und 12 neue Reaktionen (insgesamt 18 Rezepte).
---
## 2. Detaillierte Features
### A. Element- & Rezept-Erweiterung
Wir führen zwei neue Basiselemente ein:
- **Schwefel (S)** (Gelb, `#f1c40f`)
- **Eisen (Fe)** (Metallisch-Grau, `#95a5a6`)
Damit erweitern wir das Rezeptbuch um folgende spannende Kombinationen:
1. **H₂O (Wasser)**: 2x H + 1x O
2. **NaCl (Kochsalz)**: 1x Na + 1x Cl
3. **O₂ (Sauerstoff)**: 2x O
4. **CO₂ (Kohlendioxid)**: 1x C + 2x O
5. **CH₄ (Methan)**: 1x C + 4x H
6. **NH₃ (Ammoniak)**: 1x N + 3x H
7. **HCl (Salzsäure)**: 1x H + 1x Cl
8. **NaOH (Natronlauge)**: 1x Na + 1x O + 1x H
9. **H₂S (Schwefelwasserstoff)**: 2x H + 1x S
10. **FeS (Eisensulfid)**: 1x Fe + 1x S
11. **SO₂ (Schwefeldioxid)**: 1x S + 2x O
12. **CO (Kohlenmonoxid)**: 1x C + 1x O
13. **H₂O₂ (Wasserstoffperoxid)**: 2x H + 2x O
14. **HCN (Blausäure)**: 1x H + 1x C + 1x N
15. **N₂ (Stickstoff)**: 2x N
16. **Cl₂ (Chlor)**: 2x Cl
17. **H₂ (Wasserstoff)**: 2x H
18. **C₂H₄ (Ethen)**: 2x C + 4x H
### B. Das Entdecker-Buch (Lexikon)
- Befindet sich in der Sidebar unter der Elementen-Auswahl.
- Zeigt den aktuellen Fortschritt an (z. B. "Entdeckt: 2 / 18").
- Ungelöste Rezepte werden als ausgegraute Fragezeichen dargestellt (z. B. `??? (H₂O)` oder komplett anonymisiert).
- Entdeckte Moleküle leuchten auf, zeigen ihren vollen Namen und können angeklickt werden, um die Info-Karte erneut zu öffnen.
- Speicherung des Fortschritts in `localStorage`.
### C. Info-Modal (Pop-up)
- Sobald eine Reaktion stattfindet, öffnet sich ein wunderschönes Modal (verdunkelter, weichgezeichneter Hintergrund).
- Inhalt:
- Großer Titel mit der chemischen Formel (z. B. **H₂O (Wasser)**).
- Ein kurzes, interessantes "Wusstest du schon?"-Faktum (z. B. über die Giftigkeit von Natrium und Chlor einzeln vs. die Harmlosigkeit von Kochsalz).
- Ein "Schließen"-Button, der das Modal schließt und den Spielfluss fortsetzt.
### D. Visuelle Effekte & Animationen
- **Partikel-Explosion:** Bei einer erfolgreichen Reaktion fliegen 10-15 kleine, farbige Funken vom Reaktionszentrum in alle Richtungen weg und verblassen.
- **3D-Atome:** Atome erhalten einen radialen Farbverlauf, der ihnen ein plastisches, rundes Aussehen verleiht.
- **Tisch-Statistiken:** Ein kleiner Counter zeigt an, wie viele Atome und Moleküle sich gerade auf dem Tisch befinden.
- **Tisch leeren:** Ein Papierkorb-Button löscht alle Elemente vom Tisch, um Platz für neue Experimente zu schaffen.
---
## 3. Schritt-für-Schritt-Implementierung
### Schritt 1: HTML-Struktur aktualisieren (`index.html`)
- Neue Buttons für Schwefel (S) und Eisen (Fe) hinzufügen.
- Eine Sektion `#discovery-book` in der Sidebar einbauen.
- Den "Tisch leeren"-Button und die Statistik-Zähler im Labor-Tisch ergänzen.
- Das `#info-modal` Container-Skelett am Ende des Bodys hinzufügen.
### Schritt 2: CSS-Styling verfeinern (`style.css`)
- Sidebar-Scrollbarkeit verbessern, falls das Lexikon lang wird.
- Modernes Styling für das Modal (`position: fixed`, Flexbox-Zentrierung, `backdrop-filter: blur(5px)`).
- 3D-Look für Atome via Radial-Gradient (`background: radial-gradient(circle at 30% 30%, color 0%, darkcolor 100%)`).
- Schönes Design für das Entdecker-Buch (Grid oder List-Layout, Transitionen beim Freischalten).
- Partikel-CSS-Klasse für die Reaktionsexplosion.
### Schritt 3: JavaScript-Logik überarbeiten (`app.js`)
- Rezepte-Array mit allen 18 Rezepten, Farben und detaillierten Texten anlegen.
- Speicherlogik via `localStorage` einrichten (`discoveredMolecules` Set).
- Funktion `updateDiscoveryBook()` schreiben, die das Lexikon-UI aktualisiert.
- Funktion `triggerExplosion(x, y, color)` entwickeln, die DOM-basierte Partikel erzeugt und animiert.
- Modal-Logik integrieren (`showModal(recipe)`), die bei einer Reaktion aufgerufen wird und bei Klick auf das Lexikon.
- Event-Listener für "Tisch leeren" und die Positions-Zähler aktualisieren.
---
## 4. Validierung & Tests
- **Webserver:** Läuft im Hintergrund auf Port 8080.
- **Funktionalität:**
1. Spawnen von Atomen klappt.
2. Drag & Drop funktioniert flüssig.
3. Zusammenführen von 2x H + 1x O öffnet das Wasser-Modal und erzeugt eine Partikel-Explosion.
4. Wasser wird im Entdecker-Buch freigeschaltet und persistiert bei Seiten-Reload.
5. "Tisch leeren" entfernt alle Atome und Moleküle.
---
## 5. Rezept-Daten (Vorschau für `app.js`)
```javascript
const recipes = [
{
id: "h2o",
ingredients: ['H', 'H', 'O'],
result: 'H₂O (Wasser)',
color: '#3498db',
desc: "Wasser ist die Grundlage allen Lebens auf der Erde. Unser Körper besteht zu über 60% daraus, und es bedeckt mehr als 70% unseres Planeten!"
},
{
id: "nacl",
ingredients: ['Cl', 'Na'],
result: 'NaCl (Kochsalz)',
color: '#f5f6fa',
desc: "Natriumchlorid kennen wir alle als Speisesalz. Faszinierend: Natrium ist ein explosives Metall und Chlor ein giftiges Gas zusammen ergeben sie ein lebenswichtiges Gewürz!"
},
// ...weitere 16 Rezepte
];
```

344
app.js
View File

@ -1,52 +1,198 @@
const workspace = document.getElementById('workspace'); const workspace = document.getElementById('workspace');
const spawnBtns = document.querySelectorAll('.spawn-btn'); 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 elementsOnDesk = [];
let draggingElement = null; let draggingElement = null;
let offsetX = 0; let offsetX = 0;
let offsetY = 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) // Rezepturenbuch (die Formeln müssen genau den Kombinationen der Symbole entsprechen)
const recipes = [ const recipes = [
{ {
id: 'h2o',
ingredients: ['H', 'H', 'O'], // 2x Wasserstoff, 1x Sauerstoff ingredients: ['H', 'H', 'O'], // 2x Wasserstoff, 1x Sauerstoff
result: 'H₂O (Wasser)', result: 'H₂O (Wasser)',
color: '#3498db' 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 ingredients: ['Cl', 'Na'], // 1x Natrium, 1x Chlor
result: 'NaCl (Kochsalz)', result: 'NaCl (Kochsalz)',
color: '#ecf0f1' 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 ingredients: ['O', 'O'], // 2x Sauerstoff
result: 'O₂ (Sauerstoff-Molekül)', result: 'O₂ (Sauerstoff-Molekül)',
color: '#bdc3c7' 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 ingredients: ['C', 'O', 'O'], // 1x Kohlenstoff, 2x Sauerstoff
result: 'CO₂ (Kohlendioxid)', result: 'CO₂ (Kohlendioxid)',
color: '#7f8c8d' 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 ingredients: ['C', 'H', 'H', 'H', 'H'], // 1x Kohlenstoff, 4x Wasserstoff
result: 'CH₄ (Methan)', result: 'CH₄ (Methan)',
color: '#f39c12' 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 ingredients: ['N', 'H', 'H', 'H'], // 1x Stickstoff, 3x Wasserstoff
result: 'NH₃ (Ammoniak)', result: 'NH₃ (Ammoniak)',
color: '#9b59b6' 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).'
} }
]; ];
// Button-Klicks registrieren // 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 => { spawnBtns.forEach(btn => {
btn.addEventListener('click', () => { btn.addEventListener('click', () => {
spawnAtom(btn.dataset.element, btn.dataset.color); 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) { function spawnAtom(symbol, color) {
const el = document.createElement('div'); const el = document.createElement('div');
el.classList.add('atom'); el.classList.add('atom');
@ -54,9 +200,16 @@ function spawnAtom(symbol, color) {
el.dataset.symbol = symbol; el.dataset.symbol = symbol;
el.style.backgroundColor = color; 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 // Zufällige Position auf dem Tisch
const x = Math.random() * (workspace.clientWidth - 50); const x = Math.random() * (workspace.clientWidth - 60);
const y = Math.random() * (workspace.clientHeight - 50); const y = Math.random() * (workspace.clientHeight - 60);
el.style.left = x + 'px'; el.style.left = x + 'px';
el.style.top = y + 'px'; el.style.top = y + 'px';
@ -64,6 +217,15 @@ function spawnAtom(symbol, color) {
makeDraggable(el); makeDraggable(el);
workspace.appendChild(el); workspace.appendChild(el);
elementsOnDesk.push(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) { function makeDraggable(el) {
@ -74,6 +236,9 @@ function makeDraggable(el) {
offsetX = e.clientX - rect.left; offsetX = e.clientX - rect.left;
offsetY = e.clientY - rect.top; offsetY = e.clientY - rect.top;
el.style.zIndex = 1000; el.style.zIndex = 1000;
// Füge Schatten beim Ziehen hinzu
el.style.boxShadow = '0 10px 20px rgba(0,0,0,0.4)';
}); });
} }
@ -84,7 +249,7 @@ document.addEventListener('mousemove', (e) => {
let newX = e.clientX - workspaceRect.left - offsetX; let newX = e.clientX - workspaceRect.left - offsetX;
let newY = e.clientY - workspaceRect.top - offsetY; let newY = e.clientY - workspaceRect.top - offsetY;
// Grenzen beachten // Grenzen des Workspaces beachten
newX = Math.max(0, Math.min(newX, workspaceRect.width - draggingElement.offsetWidth)); newX = Math.max(0, Math.min(newX, workspaceRect.width - draggingElement.offsetWidth));
newY = Math.max(0, Math.min(newY, workspaceRect.height - draggingElement.offsetHeight)); newY = Math.max(0, Math.min(newY, workspaceRect.height - draggingElement.offsetHeight));
@ -96,6 +261,7 @@ document.addEventListener('mousemove', (e) => {
document.addEventListener('mouseup', () => { document.addEventListener('mouseup', () => {
if (draggingElement) { if (draggingElement) {
draggingElement.style.zIndex = ''; draggingElement.style.zIndex = '';
draggingElement.style.boxShadow = '';
checkForReactions(); checkForReactions();
draggingElement = null; draggingElement = null;
} }
@ -104,14 +270,10 @@ document.addEventListener('mouseup', () => {
function checkForReactions() { function checkForReactions() {
if (!draggingElement) return; if (!draggingElement) return;
const rect1 = draggingElement.getBoundingClientRect();
// Finde alle Elemente, die sich berühren (inklusive des gezogenen Elements) // Finde alle Elemente, die sich berühren (inklusive des gezogenen Elements)
// Einfache Kollisionserkennung: Wir gruppieren alle Elemente, die nahe beieinander liegen.
let cluster = [draggingElement]; let cluster = [draggingElement];
let added = true; let added = true;
// Erweitere den Cluster iterativ, bis keine berührenden Elemente mehr gefunden werden
while(added) { while(added) {
added = false; added = false;
for (let el of elementsOnDesk) { for (let el of elementsOnDesk) {
@ -135,6 +297,10 @@ function checkForReactions() {
} }
if (cluster.length > 1) { 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(); const symbols = cluster.map(el => el.dataset.symbol).sort();
// Prüfe gegen alle Rezepte // Prüfe gegen alle Rezepte
@ -143,10 +309,10 @@ function checkForReactions() {
if (JSON.stringify(symbols) === JSON.stringify(recipeSymbols)) { if (JSON.stringify(symbols) === JSON.stringify(recipeSymbols)) {
// REAKTION! // REAKTION!
const midX = draggingElement.style.left; const midX = parseFloat(draggingElement.style.left) + draggingElement.offsetWidth / 2;
const midY = draggingElement.style.top; const midY = parseFloat(draggingElement.style.top) + draggingElement.offsetHeight / 2;
// Lösche alte Atome // Lösche fusionierte Atome vom Tisch
cluster.forEach(el => { cluster.forEach(el => {
if (workspace.contains(el)) { if (workspace.contains(el)) {
workspace.removeChild(el); workspace.removeChild(el);
@ -154,31 +320,155 @@ function checkForReactions() {
elementsOnDesk = elementsOnDesk.filter(e => e !== el); elementsOnDesk = elementsOnDesk.filter(e => e !== el);
}); });
// Partikel-Explosion erzeugen
triggerExplosion(midX, midY, recipe.color);
// Neues Molekül erstellen // Neues Molekül erstellen
createMolecule(recipe.result, recipe.color, midX, midY); 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; break;
} }
} }
} }
} }
function createMolecule(name, color, x, y) { function createMolecule(recipe, x, y) {
const el = document.createElement('div'); const el = document.createElement('div');
el.classList.add('molecule'); el.classList.add('molecule');
el.innerText = name; el.innerText = recipe.result.split(' ')[0]; // Zeigt z.B. H₂O an
el.dataset.symbol = name; // Für diesen Prototyp können Moleküle noch nicht weiterreagieren el.dataset.symbol = recipe.result;
el.style.backgroundColor = color; el.style.backgroundColor = recipe.color;
el.style.left = x;
el.style.top = y; // 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); makeDraggable(el);
workspace.appendChild(el); workspace.appendChild(el);
elementsOnDesk.push(el); elementsOnDesk.push(el);
// Animation updateStats();
// Entstehungsanimation
el.animate([ el.animate([
{ transform: 'scale(0.5)' }, { transform: 'scale(0.5)' },
{ transform: 'scale(1.2)' }, { transform: 'scale(1.2)' },
{ transform: 'scale(1)' } { transform: 'scale(1)' }
], { duration: 300 }); ], { 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;
} }

View File

@ -3,25 +3,76 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Alchemie Labor</title> <title>Chemisches Alchemie Labor</title>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
</head> </head>
<body> <body>
<div id="sidebar"> <div id="sidebar">
<h2>Elemente</h2> <div class="sidebar-header">
<p>Klicke, um Atome zu spawnen.</p> <h2>🧪 Labor</h2>
<div class="spawn-btn" data-element="H" data-color="#ff9999">Wasserstoff (H)</div> <p>Atome spawnen & fusionieren</p>
<div class="spawn-btn" data-element="O" data-color="#99ccff">Sauerstoff (O)</div>
<div class="spawn-btn" data-element="C" data-color="#bdc3c7">Kohlenstoff (C)</div>
<div class="spawn-btn" data-element="N" data-color="#cc99ff">Stickstoff (N)</div>
<div class="spawn-btn" data-element="Na" data-color="#ffcc99">Natrium (Na)</div>
<div class="spawn-btn" data-element="Cl" data-color="#99ff99">Chlor (Cl)</div>
</div> </div>
<div id="elements-section">
<h3>Atome</h3>
<div class="elements-grid">
<div class="spawn-btn" data-element="H" data-color="#ff7675" title="Wasserstoff">H</div>
<div class="spawn-btn" data-element="O" data-color="#74b9ff" title="Sauerstoff">O</div>
<div class="spawn-btn" data-element="C" data-color="#2d3436" title="Kohlenstoff">C</div>
<div class="spawn-btn" data-element="N" data-color="#a29bfe" title="Stickstoff">N</div>
<div class="spawn-btn" data-element="Na" data-color="#ffeaa7" title="Natrium">Na</div>
<div class="spawn-btn" data-element="Cl" data-color="#55efc4" title="Chlor">Cl</div>
<div class="spawn-btn" data-element="S" data-color="#fdcb6e" title="Schwefel">S</div>
<div class="spawn-btn" data-element="Fe" data-color="#b2bec3" title="Eisen">Fe</div>
</div>
</div>
<div id="book-section">
<div class="book-header">
<h3>📖 Lexikon</h3>
<span id="discovery-progress">0/18</span>
</div>
<div id="discovery-book">
<!-- Dynamisch gefüllt durch app.js -->
</div>
</div>
</div>
<div id="lab-desk"> <div id="lab-desk">
<div class="desk-header">
<div>
<h2>Labor-Tisch</h2> <h2>Labor-Tisch</h2>
<p>Ziehe Atome übereinander (z.B. 2x H und 1x O), um Reaktionen auszulösen!</p> <p>Ziehe Atome übereinander, um Reaktionen auszulösen!</p>
</div>
<div class="desk-controls">
<div id="stats">
Atome: <span id="atom-count">0</span> | Moleküle: <span id="molecule-count">0</span>
</div>
<button id="clear-desk-btn">🧹 Tisch leeren</button>
</div>
</div>
<div id="workspace"></div> <div id="workspace"></div>
</div> </div>
<!-- Info-Modal für Entdeckungen -->
<div id="info-modal" class="modal hidden">
<div class="modal-overlay"></div>
<div class="modal-content">
<div class="modal-header">
<span class="modal-emoji">🎉</span>
<h2 id="modal-title">Molekül entdeckt!</h2>
</div>
<div class="modal-body">
<div id="modal-formula-badge">H₂O</div>
<h3 id="modal-molecule-name">Wasser</h3>
<p id="modal-description">Beschreibung...</p>
</div>
<div class="modal-footer">
<button id="modal-close-btn">Großartig!</button>
</div>
</div>
</div>
<script src="app.js"></script> <script src="app.js"></script>
</body> </body>
</html> </html>

452
style.css
View File

@ -1,76 +1,448 @@
:root {
--bg-dark: #2c3e50;
--bg-sidebar: #1e272e;
--border-color: #3d4f5d;
--workspace-bg: #f8f9fa;
--text-light: #f5f6fa;
--text-muted: #a4b0be;
--primary: #3498db;
--primary-hover: #2980b9;
--shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body { body {
display: flex; display: flex;
margin: 0;
height: 100vh; height: 100vh;
font-family: Arial, sans-serif; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #2c3e50; background-color: var(--bg-dark);
color: white; color: var(--text-light);
overflow: hidden;
} }
/* Sidebar Layout */
#sidebar { #sidebar {
width: 250px; width: 320px;
background-color: #34495e; background-color: var(--bg-sidebar);
padding: 20px; border-right: 1px solid var(--border-color);
box-shadow: 2px 0 5px rgba(0,0,0,0.5);
}
.spawn-btn {
padding: 10px;
margin-bottom: 10px;
background-color: #7f8c8d;
cursor: pointer;
border-radius: 5px;
text-align: center;
user-select: none;
color: #333;
font-weight: bold;
}
.spawn-btn:hover {
filter: brightness(1.1);
}
#lab-desk {
flex-grow: 1;
padding: 20px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%;
} }
.sidebar-header {
padding: 20px;
border-bottom: 1px solid var(--border-color);
}
.sidebar-header h2 {
font-size: 1.5rem;
margin-bottom: 5px;
color: #fff;
}
.sidebar-header p {
font-size: 0.85rem;
color: var(--text-muted);
}
/* Elements Selector */
#elements-section {
padding: 20px;
border-bottom: 1px solid var(--border-color);
}
#elements-section h3, #book-section h3 {
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--text-muted);
margin-bottom: 15px;
}
.elements-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
}
.spawn-btn {
height: 50px;
width: 50px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
font-size: 1.1rem;
cursor: pointer;
user-select: none;
color: #333;
border: 2px solid rgba(0,0,0,0.15);
box-shadow: 0 4px 6px rgba(0,0,0,0.2);
background-image: radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.4) 0%, rgba(0, 0, 0, 0.3) 100%);
transition: transform 0.1s, box-shadow 0.1s;
}
/* Element Colors defined in JS, but base defaults for fallback */
.spawn-btn:hover {
transform: scale(1.1);
box-shadow: 0 6px 10px rgba(0,0,0,0.3);
}
.spawn-btn:active {
transform: scale(0.95);
}
/* Specific button text colors */
.spawn-btn[data-element="C"] {
color: #fff;
}
/* Discovery Book (Lexikon) */
#book-section {
padding: 20px;
flex-grow: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.book-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
#discovery-progress {
background-color: var(--primary);
padding: 3px 8px;
border-radius: 12px;
font-size: 0.8rem;
font-weight: bold;
}
#discovery-book {
flex-grow: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 8px;
padding-right: 5px;
}
/* Scrollbar styles */
#discovery-book::-webkit-scrollbar {
width: 6px;
}
#discovery-book::-webkit-scrollbar-track {
background: transparent;
}
#discovery-book::-webkit-scrollbar-thumb {
background: var(--border-color);
border-radius: 3px;
}
.book-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 15px;
background-color: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 8px;
font-size: 0.9rem;
transition: background-color 0.2s, border-color 0.2s;
user-select: none;
}
.book-item.locked {
color: var(--text-muted);
opacity: 0.6;
}
.book-item.unlocked {
background-color: rgba(52, 152, 219, 0.1);
border-color: rgba(52, 152, 219, 0.2);
cursor: pointer;
}
.book-item.unlocked:hover {
background-color: rgba(52, 152, 219, 0.2);
border-color: rgba(52, 152, 219, 0.4);
}
.book-formula {
font-weight: bold;
}
.book-name {
font-size: 0.85rem;
}
/* Labor Tisch Layout */
#lab-desk {
flex-grow: 1;
display: flex;
flex-direction: column;
height: 100%;
background-color: var(--bg-dark);
}
.desk-header {
padding: 20px;
display: flex;
justify-content: space-between;
align-items: center;
background-color: rgba(0, 0, 0, 0.1);
border-bottom: 1px solid var(--border-color);
}
.desk-header h2 {
font-size: 1.4rem;
color: #fff;
}
.desk-header p {
font-size: 0.85rem;
color: var(--text-muted);
}
.desk-controls {
display: flex;
align-items: center;
gap: 15px;
}
#stats {
font-size: 0.9rem;
color: var(--text-muted);
background-color: rgba(0,0,0,0.2);
padding: 8px 12px;
border-radius: 6px;
border: 1px solid rgba(255,255,255,0.05);
}
#clear-desk-btn {
background-color: #e74c3c;
color: white;
border: none;
padding: 8px 15px;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
font-size: 0.9rem;
transition: background-color 0.2s, transform 0.1s;
}
#clear-desk-btn:hover {
background-color: #c0392b;
}
#clear-desk-btn:active {
transform: scale(0.95);
}
/* Workspace Canvas */
#workspace { #workspace {
flex-grow: 1; flex-grow: 1;
background-color: #ecf0f1; background-color: var(--workspace-bg);
background-image: linear-gradient(rgba(0,0,0,0.05) 1px, transparent 1px), linear-gradient(90deg, rgba(0,0,0,0.05) 1px, transparent 1px); background-image:
background-size: 20px 20px; linear-gradient(rgba(0, 0, 0, 0.05) 1px, transparent 1px),
border-radius: 10px; linear-gradient(90deg, rgba(0, 0, 0, 0.05) 1px, transparent 1px);
background-size: 30px 30px;
margin: 20px;
border-radius: 12px;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
color: #333; box-shadow: inset 0 2px 10px rgba(0, 0, 0, 0.15);
box-shadow: inset 0 0 10px rgba(0,0,0,0.1); border: 1px solid var(--border-color);
} }
/* Atoms and Molecules inside workspace */
.atom { .atom {
width: 50px; width: 55px;
height: 50px; height: 55px;
border-radius: 50%; border-radius: 50%;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
position: absolute; position: absolute;
font-weight: bold; font-weight: bold;
font-size: 1.15rem;
cursor: grab; cursor: grab;
box-shadow: 0 4px 6px rgba(0,0,0,0.3); box-shadow: 0 4px 10px rgba(0,0,0,0.3);
user-select: none; user-select: none;
border: 2px solid rgba(0,0,0,0.2); border: 2px solid rgba(0,0,0,0.2);
background-image: radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.4) 0%, rgba(0, 0, 0, 0.45) 100%);
transition: transform 0.1s;
} }
.atom:active { .atom:active {
cursor: grabbing; cursor: grabbing;
transform: scale(1.05);
} }
.molecule { .molecule {
padding: 10px 20px; padding: 12px 24px;
border-radius: 20px; border-radius: 30px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
position: absolute; position: absolute;
font-weight: bold; font-weight: bold;
font-size: 1.05rem;
cursor: grab; cursor: grab;
box-shadow: 0 4px 6px rgba(0,0,0,0.3); box-shadow: 0 5px 12px rgba(0,0,0,0.3);
background-color: #f1c40f; color: white;
user-select: none; user-select: none;
border: 2px solid rgba(0,0,0,0.2); border: 2px solid rgba(0,0,0,0.25);
background-image: linear-gradient(135deg, rgba(255,255,255,0.2) 0%, rgba(0,0,0,0.2) 100%);
transition: transform 0.1s;
text-shadow: 0 1px 2px rgba(0,0,0,0.5);
}
.molecule:active {
cursor: grabbing;
transform: scale(1.05);
}
/* Particle Explosion Sparks */
.particle {
position: absolute;
width: 8px;
height: 8px;
border-radius: 50%;
pointer-events: none;
z-index: 100;
}
/* Modals */
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
z-index: 2000;
transition: opacity 0.3s ease;
}
.modal.hidden {
opacity: 0;
pointer-events: none;
}
.modal-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(5px);
}
.modal-content {
position: relative;
background-color: #2c3e50;
border: 2px solid var(--border-color);
border-radius: 16px;
width: 450px;
max-width: 90%;
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
overflow: hidden;
transform: scale(0.8);
transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
color: white;
}
.modal:not(.hidden) .modal-content {
transform: scale(1);
}
.modal-header {
background-color: rgba(0, 0, 0, 0.2);
padding: 20px;
display: flex;
align-items: center;
gap: 15px;
border-bottom: 1px solid var(--border-color);
}
.modal-emoji {
font-size: 2rem;
}
.modal-header h2 {
font-size: 1.3rem;
}
.modal-body {
padding: 30px 20px;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
#modal-formula-badge {
background-color: var(--primary);
font-weight: bold;
font-size: 1.8rem;
padding: 10px 25px;
border-radius: 50px;
box-shadow: 0 4px 10px rgba(0,0,0,0.3);
margin-bottom: 15px;
border: 2px solid rgba(255,255,255,0.1);
}
#modal-molecule-name {
font-size: 1.4rem;
margin-bottom: 20px;
color: #ffeaa7;
}
#modal-description {
font-size: 0.95rem;
line-height: 1.5;
color: #dfe6e9;
}
.modal-footer {
padding: 15px 20px;
background-color: rgba(0, 0, 0, 0.1);
border-top: 1px solid var(--border-color);
display: flex;
justify-content: center;
}
#modal-close-btn {
background-color: var(--primary);
color: white;
border: none;
padding: 10px 30px;
border-radius: 8px;
font-size: 1rem;
font-weight: bold;
cursor: pointer;
transition: background-color 0.2s, transform 0.1s;
box-shadow: 0 4px 6px rgba(0,0,0,0.2);
}
#modal-close-btn:hover {
background-color: var(--primary-hover);
}
#modal-close-btn:active {
transform: scale(0.95);
} }