<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>3D Molecule Simulator</title>
   <!-- Tailwind CSS for styling -->
   <script src="https://cdn.tailwindcss.com"></script>
   <!-- three.js for 3D rendering -->
   <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
   <!-- ES6 Import Map for three.js addons -->
   <script type="importmap">
       {
           "imports": {
               "three": "https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.module.js",
               "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.128.0/examples/jsm/"
           }
       }
   </script>
   <link rel="preconnect" href="https://fonts.googleapis.com">
   <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
   <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&family=Fira+Code&display=swap" rel="stylesheet">
   <style>
       body {
           font-family: 'Inter', sans-serif;
       }
       .fira-code {
           font-family: 'Fira Code', monospace;
       }
       /* Custom styles for better UI elements */
       select, input[type="number"] {
           background-color: #374151; /* bg-gray-700 */
           border: 1px solid #4b5563; /* border-gray-600 */
           color: white;
           border-radius: 0.375rem; /* rounded-md */
           padding: 0.5rem 0.75rem;
       }
       select:focus, input[type="number"]:focus {
           outline: none;
           border-color: #3b82f6; /* border-blue-500 */
           box-shadow: 0 0 0 2px #1f2937, 0 0 0 4px #3b82f6;
       }
   </style>
</head>
<body class="bg-gray-900 text-gray-200 flex flex-col items-center justify-center min-h-screen p-4">

   <!-- Main container for the simulation and UI -->
   <div class="flex flex-col items-center">
       <!-- 3D Display element (1000x1000) -->
       <div id="simulation-container" class="w-[1000px] h-[1000px] bg-black rounded-t-lg shadow-2xl overflow-hidden relative border-2 border-gray-700">
           <!-- 3D Renderer will attach its canvas here -->
       </div>

       <!-- UI control element (1000x300) -->
       <div id="ui-container" class="w-[1000px] h-[300px] bg-gray-800/50 backdrop-blur-sm rounded-b-lg shadow-2xl p-6 flex flex-col justify-between border-x-2 border-b-2 border-gray-700">
           
           <!-- Top section: Title and Formula Display -->
           <div class="flex justify-between items-start">
               <div>
                   <h1 class="text-2xl font-bold text-white">Molecule Composer & Simulator</h1>
                   <p class="text-gray-400">Build a molecule by adding elements.</p>
               </div>
               <div class="bg-gray-900 p-4 rounded-lg text-center">
                    <h2 class="text-gray-400 mb-1">Current Formula</h2>
                    <p id="formula-display" class="text-2xl fira-code text-white h-8">H₂O</p>
               </div>
           </div>

           <!-- Middle section: Element Selection & Controls -->
           <div class="flex-grow flex items-center justify-center gap-6">
                <div class="flex flex-col gap-2">
                   <label for="element-select" class="text-sm text-gray-400">Element</label>
                   <select id="element-select" class="w-48"></select>
                </div>
                <div class="flex flex-col gap-2">
                   <label for="element-quantity" class="text-sm text-gray-400">Quantity</label>
                   <input id="element-quantity" type="number" min="1" value="1" class="w-24 text-center">
                </div>
                <button id="add-element-button" class="self-end bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded-lg transition-colors">
                   Add to Molecule
                </button>
           </div>

           <!-- Bottom section: Action Buttons -->
           <div class="flex justify-between items-center">
                <button id="reset-button" class="bg-gray-600 hover:bg-gray-700 text-white font-bold py-2 px-6 rounded-lg transition-colors">
                   Reset
               </button>
               <button id="simulate-button" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded-lg transition-colors shadow-lg text-lg">
                   Simulate Molecule
               </button>
           </div>
       </div>
   </div>

   <script type="module">
       // Import necessary three.js modules
       import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

       // --- Data ---
       const elements = {
           'H': { name: 'Hydrogen', radius: 0.53, color: 0xffffff },
           'He': { name: 'Helium', radius: 0.31, color: 0xd9ffff },
           'C': { name: 'Carbon', radius: 0.91, color: 0x222222 },
           'N': { name: 'Nitrogen', radius: 0.75, color: 0x3050f8 },
           'O': { name: 'Oxygen', radius: 0.65, color: 0xff0d0d },
           'F': { name: 'Fluorine', radius: 0.57, color: 0x90e050 },
           'Na': { name: 'Sodium', radius: 2.23, color: 0xab5cf2 },
           'Si': { name: 'Silicon', radius: 1.46, color: 0xf0c8a0 },
           'P': { name: 'Phosphorus', radius: 1.23, color: 0xff8000 },
           'S': { name: 'Sulfur', radius: 1.16, color: 0xffff30 },
           'Cl': { name: 'Chlorine', radius: 0.99, color: 0x1ff01f },
           'K': { name: 'Potassium', radius: 2.77, color: 0x8f40d4 },
           'Br': { name: 'Bromine', radius: 1.14, color: 0xa62929 },
       };

       // --- State Management ---
       let scene, camera, renderer, controls, moleculeGroup;
       let composition = { 'H': 2, 'O': 1 }; // Default to water

       // --- DOM Element References ---
       const simContainer = document.getElementById('simulation-container');
       const elementSelect = document.getElementById('element-select');
       const quantityInput = document.getElementById('element-quantity');
       const addButton = document.getElementById('add-element-button');
       const simulateButton = document.getElementById('simulate-button');
       const resetButton = document.getElementById('reset-button');
       const formulaDisplay = document.getElementById('formula-display');

       // --- Initializer ---
       function initialize() {
           populateSelect();
           setupEventListeners();
           init3D();
           updateFormulaDisplay();
           buildMolecule();
       }

       // --- UI Functions ---
       function populateSelect() {
           for (const symbol in elements) {
               const option = document.createElement('option');
               option.value = symbol;
               option.textContent = `${symbol} - ${elements[symbol].name}`;
               elementSelect.appendChild(option);
           }
       }
       
       function updateFormulaDisplay() {
           let html = '';
           for(const symbol in composition) {
               const count = composition[symbol];
               html += symbol;
               if (count > 1) {
                   html += `<sub>${count}</sub>`;
               }
           }
           formulaDisplay.innerHTML = html || 'Empty';
       }

       // --- Event Setup ---
       function setupEventListeners() {
           addButton.addEventListener('click', () => {
               const symbol = elementSelect.value;
               const quantity = parseInt(quantityInput.value);
               if (symbol && quantity > 0) {
                   composition[symbol] = (composition[symbol] || 0) + quantity;
                   updateFormulaDisplay();
               }
           });
           
           simulateButton.addEventListener('click', buildMolecule);
           
           resetButton.addEventListener('click', () => {
               composition = {};
               updateFormulaDisplay();
               buildMolecule();
           });
           window.addEventListener('resize', onWindowResize);
       }
       
       // --- 3D Scene Initialization ---
       function init3D() {
           const width = simContainer.clientWidth;
           const height = simContainer.clientHeight;
           
           scene = new THREE.Scene();
           scene.background = new THREE.Color(0x000000);

           camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
           camera.position.set(5, 5, 10);
           
           renderer = new THREE.WebGLRenderer({ antiasias: true });
           renderer.setSize(width, height);
           simContainer.appendChild(renderer.domElement);
           
           scene.add(new THREE.AmbientLight(0xcccccc, 1.0));
           const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5);
           directionalLight.position.set(20, 30, 10);
           scene.add(directionalLight);
           
           controls = new OrbitControls(camera, renderer.domElement);
           controls.enableDamping = true;
           
           animate3D();
       }
       
       function onWindowResize() {
           const width = simContainer.clientWidth;
           const height = simContainer.clientHeight;
           camera.aspect = width / height;
           camera.updateProjectionMatrix();
           renderer.setSize(width, height);
       }

       // --- Molecule Building Logic ---
       function buildMolecule() {
           // Clear previous molecule
           if (moleculeGroup) {
               scene.remove(moleculeGroup);
           }
           moleculeGroup = new THREE.Group();
           
           const atoms = [];
           for (const symbol in composition) {
               for (let i = 0; i < composition[symbol]; i++) {
                   const element = elements[symbol];
                   const geometry = new THREE.SphereGeometry(element.radius, 32, 32);
                   const material = new THREE.MeshStandardMaterial({
                       color: element.color,
                       metalness: 0.3,
                       roughness: 0.5
                   });
                   const atom = new THREE.Mesh(geometry, material);
                   atom.radius = element.radius;
                   atoms.push(atom);
               }
           }
           
           if (atoms.length > 0) {
               positionAtoms(atoms);
               atoms.forEach(atom => moleculeGroup.add(atom));
           }
           
           scene.add(moleculeGroup);
       }
       
       function positionAtoms(atoms) {
            // Simple force-directed placement algorithm
           if (atoms.length === 0) return;

           // Place first atom at origin
           atoms[0].position.set(0, 0, 0);

           for (let i = 1; i < atoms.length; i++) {
               // Place new atom randomly nearby
               atoms[i].position.random().subScalar(0.5).normalize().multiplyScalar(atoms[0].radius + atoms[i].radius);
           }

           // Iteratively push atoms apart to reduce overlap
           for (let iteration = 0; iteration < 100; iteration++) {
               for (let i = 0; i < atoms.length; i++) {
                   for (let j = i + 1; j < atoms.length; j++) {
                       const atomA = atoms[i];
                       const atomB = atoms[j];
                       const vec = new THREE.Vector3().subVectors(atomA.position, atomB.position);
                       const distance = vec.length();
                       const idealDistance = atomA.radius + atomB.radius;
                       if (distance < idealDistance) {
                           const overlap = idealDistance - distance;
                           vec.normalize().multiplyScalar(overlap * 0.5);
                           atomA.position.add(vec);
                           atomB.position.sub(vec);
                       }
                   }
               }
           }
           
           // Center the final molecule
           const center = new THREE.Vector3();
           atoms.forEach(atom => center.add(atom.position));
           center.divideScalar(atoms.length);
           atoms.forEach(atom => atom.position.sub(center));
       }
       
       // --- 3D Animation Loop ---
       function animate3D() {
           requestAnimationFrame(animate3D);
           if (controls) controls.update();
           if (renderer && scene && camera) renderer.render(scene, camera);
       }
       
       // --- Start Application ---
       initialize();

   </script>
</body>
</html>
 

We need your consent to load the translations

We use a third-party service to translate the website content that may collect data about your activity. Please review the details in the privacy policy and accept the service to view the translations.