Chemistry Lab Simulator

Chemistry Lab Simulator

Chemistry Lab Simulator

Controls

Lab Notes

Lab notes will appear here...

Add, edit, or remove chemicals and their reactions. (Spec #4, #6)

Chemicals

Name Color Action

Combine Reactions (A + B)

Chem A Chem B Result Color Gas? Result Text Action

Heat Reactions

Chemical Result Color Gas? Result Text Action

Lab Notes

'; updateBeakerVisual(null, false); btnHeat.disabled = true; logResult("Beaker reset."); } /** * Simulates combining chemicals */ function handleCombine() { const chemAName = selectA.value; const chemBName = selectB.value; if (chemAName === "Empty" && chemBName === "Empty") { logResult("Both selections are empty. Add a chemical.", "result"); return; } logResult(`Combining ${chemAName} and ${chemBName}...`); // Check for a reaction const reaction = chemData.combineReactions.find( (r) => (r.a === chemAName && r.b === chemBName) || (r.a === chemBName && r.b === chemAName) ); if (reaction) { beakerContents = { name: reaction.text, color: reaction.color }; updateBeakerVisual(reaction.color, reaction.gas); logResult(`Result: ${reaction.text}`, "result"); } else { // No reaction, just mix or add // Simple logic: If one is empty, add the other. If both, mix colors (or default to A) let newContents; if (chemAName === "Empty") { newContents = chemData.chemicals.find(c => c.name === chemBName); } else { newContents = chemData.chemicals.find(c => c.name === chemAName); } if (chemAName !== "Empty" && chemBName !== "Empty") { logResult("No reaction detected. The chemicals formed a mixture.", "result"); // Simple mix: just use color of A } else { logResult(`Added ${newContents.name} to the beaker.`, "result"); } beakerContents = newContents; updateBeakerVisual(newContents.color, false); } btnHeat.disabled = false; } /** * Simulates heating the beaker */ function handleHeat() { if (!beakerContents) { logResult("Beaker is empty. Cannot heat.", "result"); return; } logResult(`Heating ${beakerContents.name}...`); beakerFlame.classList.add("active"); // Check for a heat reaction const reaction = chemData.heatReactions.find(r => r.a === beakerContents.name); if (reaction) { beakerContents = { name: reaction.text, color: reaction.color }; updateBeakerVisual(reaction.color, reaction.gas); logResult(`Result: ${reaction.text}`, "result"); } else { logResult("No specific reaction to heat. The substance is hot.", "result"); // No visual change, just flame } } /** * Downloads the lab notes as a PDF * (Spec II.C) */ function downloadChemPDF() { if ( typeof jspdf === "undefined" || typeof html2canvas === "undefined" ) { console.error("PDF libraries not loaded."); return; } if (logEntries.length === 0) { console.error("No log entries to download."); return; } const { jsPDF } = jspdf; const pdfOutputElement = document.getElementById( "chem-pdf-output-content" ); const pdfLogContainer = document.getElementById("chem-pdf-log"); // 1. Populate PDF log pdfLogContainer.innerHTML = ""; logEntries.forEach(log => { const p = document.createElement("p"); p.className = `log-entry ${log.type}`; p.textContent = log.text; pdfLogContainer.appendChild(p); }); // 2. Temporarily show element pdfOutputElement.style.display = "block"; pdfOutputElement.style.position = "absolute"; pdfOutputElement.style.left = "-9999px"; html2canvas(pdfOutputElement, { scale: 2, backgroundColor: "#ffffff", }).then((canvas) => { // 3. Hide element again pdfOutputElement.style.display = "none"; pdfOutputElement.style.position = "static"; const imgData = canvas.toDataURL("image/png"); const pdf = new jsPDF({ orientation: "p", unit: "px", format: "a4", }); const pdfWidth = pdf.internal.pageSize.getWidth(); const canvasWidth = canvas.width; const canvasHeight = canvas.height; const ratio = canvasWidth / canvasHeight; const imgWidth = pdfWidth - 40; // 20px margin const imgHeight = imgWidth / ratio; pdf.addImage(imgData, "PNG", 20, 20, imgWidth, imgHeight); pdf.save("chemistry-lab-notes.pdf"); }); } // --- 5. Event Listeners (Spec IV.C) --- btnCombine.addEventListener("click", handleCombine); btnHeat.addEventListener("click", handleHeat); btnReset.addEventListener("click", handleReset); pdfDownloadBtn.addEventListener("click", downloadChemPDF); // --- 6. Initialization --- buildDashboard(); buildConfig(); handleReset(); // Set initial empty state showChemTab("chem-tab-dashboard"); // Start on the dashboard tab });