<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>Interactive 3D DNA 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;
       }
       #simulation-container canvas {
           cursor: pointer;
       }
       .base-button.selected {
           outline: 2px solid #6366F1; /* indigo-500 */
           transform: scale(1.05);
       }
   </style>
</head>
<body class="bg-gray-900 text-gray-200 flex flex-col items-center justify-center min-h-screen p-4">

   <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">
       </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">
           
           <div class="flex justify-between items-start">
               <div>
                   <h1 class="text-2xl font-bold text-white">Interactive DNA Strand Simulator</h1>
                   <p class="text-gray-400">Select a preset or click on a base pair to edit the sequence.</p>
               </div>
               <div>
                   <label for="sample-select" class="text-sm text-gray-400 block mb-1">Load DNA Sample</label>
                   <select id="sample-select" class="bg-gray-700 border border-gray-600 rounded-md p-2 w-72">
                   </select>
               </div>
           </div>

           <!-- Middle section: Editor -->
           <div id="editor-panel" class="flex-grow flex items-center justify-center gap-8 bg-gray-900/50 rounded-lg p-4 border border-gray-700">
               <div class="text-center">
                   <p class="text-gray-400 text-sm">Selected Pair Index</p>
                   <p id="selected-index" class="text-3xl fira-code">-</p>
               </div>
               <div class="w-px h-16 bg-gray-600"></div>
               <div class="text-center">
                    <p class="text-gray-400 text-sm">Current Strand</p>
                    <p id="selected-base" class="text-5xl fira-code font-bold">-</p>
               </div>
                <div class="text-center">
                    <p class="text-gray-400 text-sm">Complementary Strand</p>
                    <p id="complementary-base" class="text-5xl fira-code font-bold">-</p>
               </div>
               <div class="w-px h-16 bg-gray-600"></div>
               <div id="base-editor" class="text-center space-y-2 opacity-25 transition-opacity">
                    <p class="text-gray-400 text-sm">Change Base To</p>
                    <div id="base-buttons" class="flex gap-2">
                       <!-- Buttons will be generated here -->
                    </div>
               </div>
           </div>

           <!-- Bottom section: Legend and Build Button -->
           <div class="flex justify-between items-end h-10">
                <div class="flex gap-4 text-sm items-center">
                   <p class="text-gray-400">Legend:</p>
                   <div class="flex items-center gap-1"><div class="w-3 h-3 rounded-full bg-blue-500"></div>A</div>
                   <div class="flex items-center gap-1"><div class="w-3 h-3 rounded-full bg-green-500"></div>T</div>
                   <div class="flex items-center gap-1"><div class="w-3 h-3 rounded-full bg-yellow-500"></div>C</div>
                   <div class="flex items-center gap-1"><div class="w-3 h-3 rounded-full bg-red-500"></div>G</div>
               </div>
               <button id="build-button" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-8 rounded-lg transition-colors shadow-lg text-lg">
                   Build DNA
               </button>
           </div>
       </div>
   </div>

   <script type="module">
       import * as THREE from 'three';
       import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

       // --- State Management & Constants ---
       let scene, camera, renderer, controls, dnaGroup;
       let dnaSequence = '';
       let interactiveObjects = []; // Stores { base, pair, mesh1, mesh2, index }
       let selectedPair = null;
       let raycaster = new THREE.Raycaster();
       let mouse = new THREE.Vector2();

       const BASE_PAIRS = { 'A': 'T', 'T': 'A', 'C': 'G', 'G': 'C' };
       const BASE_COLORS = { 
           'A': 0x0077ff, // Blue
           'T': 0x00ff00, // Green
           'C': 0xffff00, // Yellow
           'G': 0xff0000, // Red
       };
       const dnaSamples = {
         'sickle-cell': {
           name: 'Sickle Cell Anemia (HBB Gene)',
           // Normal: CCT GAG GAG. Sickle: CCT GTG GAG. The A at index 5 becomes a T.
           sequence: 'CCTGTGGAG', 
         },
         'normal-hemoglobin': {
           name: 'Normal Hemoglobin (HBB Gene)',
           sequence: 'CCTGAGGAG',
         },
         'cystic-fibrosis': {
            name: 'Cystic Fibrosis (CFTR Î"F508)',
            // Normal: ATCATCTTTGGTGTT. Mutated: ATCATCGGTGTT (CTT at index 6 is deleted)
            sequence: 'ATCATCGGTGTT',
         }
       };

       // --- DOM Element References ---
       const simContainer = document.getElementById('simulation-container');
       const sampleSelect = document.getElementById('sample-select');
       const buildButton = document.getElementById('build-button');
       const editorPanel = document.getElementById('editor-panel');
       const selectedIndex = document.getElementById('selected-index');
       const selectedBase = document.getElementById('selected-base');
       const complementaryBase = document.getElementById('complementary-base');
       const baseEditor = document.getElementById('base-editor');
       const baseButtons = document.getElementById('base-buttons');
       
       // --- Initializer ---
       function initialize() {
           init3D();
           setupUI();
           loadSample('sickle-cell'); // Load default sample
           buildDNA();
           animate3D();
       }

       // --- UI Functions ---
       function setupUI() {
           // Populate samples dropdown
           for (const key in dnaSamples) {
               const option = document.createElement('option');
               option.value = key;
               option.textContent = dnaSamples[key].name;
               sampleSelect.appendChild(option);
           }
           sampleSelect.value = 'sickle-cell';
           sampleSelect.addEventListener('change', () => loadSample(sampleSelect.value));
           
           buildButton.addEventListener('click', buildDNA);
           
           // Populate base editor buttons
           ['A', 'T', 'C', 'G'].forEach(base => {
               const button = document.createElement('button');
               button.textContent = base;
               button.dataset.base = base;
               button.className = `base-button w-10 h-10 rounded-full font-bold transition-transform`;
               button.style.backgroundColor = `#${BASE_COLORS[base].toString(16).padStart(6, '0')}`;
               button.style.color = (base === 'C' || base === 'G') ? '#333' : '#fff';
               button.addEventListener('click', () => handleBaseChange(base));
               baseButtons.appendChild(button);
           });
       }

       function loadSample(sampleKey) {
           if (dnaSamples[sampleKey]) {
               dnaSequence = dnaSamples[sampleKey].sequence;
               buildDNA();
           }
       }
       
       function updateEditorPanel() {
           if (selectedPair) {
               baseEditor.classList.remove('opacity-25');
               selectedIndex.textContent = selectedPair.index;
               selectedBase.textContent = selectedPair.base;
               complementaryBase.textContent = selectedPair.pair;

               // Highlight the correct button
               document.querySelectorAll('.base-button').forEach(btn => {
                   btn.classList.toggle('selected', btn.dataset.base === selectedPair.base);
               });

           } else {
               baseEditor.classList.add('opacity-25');
               selectedIndex.textContent = '-';
               selectedBase.textContent = '-';
               complementaryBase.textContent = '-';
               document.querySelectorAll('.base-button').forEach(btn => btn.classList.remove('selected'));
           }
       }

       // --- 3D Scene & DNA Building ---
       function init3D() {
           scene = new THREE.Scene();
           scene.background = new THREE.Color(0x000000);
           camera = new THREE.PerspectiveCamera(75, 1000 / 1000, 0.1, 1000);
           camera.position.set(0, 0, 15);
           renderer = new THREE.WebGLRenderer({ antialias: true });
           renderer.setSize(1000, 1000);
           simContainer.appendChild(renderer.domElement);
           scene.add(new THREE.AmbientLight(0xdddddd));
           const light = new THREE.DirectionalLight(0xffffff, 1);
           light.position.set(5, 5, 10);
           scene.add(light);
           controls = new OrbitControls(camera, renderer.domElement);
           controls.enableDamping = true;
           
           // Add click event listener to canvas
           renderer.domElement.addEventListener('click', onCanvasClick);
       }

       function buildDNA() {
           if (dnaGroup) scene.remove(dnaGroup);
           dnaGroup = new THREE.Group();
           interactiveObjects = [];
           selectedPair = null;
           updateEditorPanel();

           const radius = 2;
           const baseHeight = 0.5;
           const totalHeight = dnaSequence.length * baseHeight;
           const turns = dnaSequence.length / 10;

           const backboneMaterial = new THREE.MeshPhongMaterial({ color: 0x888888 });

           for (let i = 0; i < dnaSequence.length; i++) {
               const y = -totalHeight / 2 + i * baseHeight;
               const angle = (i / dnaSequence.length) * turns * 2 * Math.PI;

               // Backbones
               const b1 = new THREE.Mesh(new THREE.SphereGeometry(0.2), backboneMaterial);
               b1.position.set(radius * Math.cos(angle), y, radius * Math.sin(angle));
               const b2 = new THREE.Mesh(new THREE.SphereGeometry(0.2), backboneMaterial);
               b2.position.set(radius * Math.cos(angle + Math.PI), y, radius * Math.sin(angle + Math.PI));
               dnaGroup.add(b1, b2);

               // Bases
               const base1_char = dnaSequence[i];
               const base2_char = BASE_PAIRS[base1_char];

               const material1 = new THREE.MeshPhongMaterial({ color: BASE_COLORS[base1_char] });
               const material2 = new THREE.MeshPhongMaterial({ color: BASE_COLORS[base2_char] });

               const baseGeo = new THREE.BoxGeometry(radius - 0.2, 0.1, 0.8);

               const mesh1 = new THREE.Mesh(baseGeo, material1);
               mesh1.position.set((radius/2) * Math.cos(angle), y, (radius/2) * Math.sin(angle));
               mesh1.rotation.y = -angle;
               mesh1.userData = { pairIndex: i };
               
               const mesh2 = new THREE.Mesh(baseGeo, material2);
               mesh2.position.set((radius/2) * Math.cos(angle + Math.PI), y, (radius/2) * Math.sin(angle + Math.PI));
               mesh2.rotation.y = -angle;
               mesh2.userData = { pairIndex: i };
               
               dnaGroup.add(mesh1, mesh2);

               interactiveObjects.push({ 
                   base: base1_char, pair: base2_char, 
                   mesh1, mesh2, index: i 
               });
           }
           scene.add(dnaGroup);
       }

       // --- Interactivity ---
       function onCanvasClick(event) {
           event.preventDefault();
           
           const rect = renderer.domElement.getBoundingClientRect();
           mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
           mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;

           raycaster.setFromCamera(mouse, camera);
           
           // Create array of all meshes that can be clicked
           const clickableMeshes = [];
           interactiveObjects.forEach(pair => {
               clickableMeshes.push(pair.mesh1, pair.mesh2);
           });
           
           const intersects = raycaster.intersectObjects(clickableMeshes);

           if (intersects.length > 0) {
               const clickedMesh = intersects[0].object;
               const pairIndex = clickedMesh.userData.pairIndex;
               
               if (pairIndex !== undefined) {
                   selectedPair = interactiveObjects[pairIndex];
                   updateEditorPanel();
               }
           } else {
               selectedPair = null;
               updateEditorPanel();
           }
       }

       function handleBaseChange(newBase) {
           if (!selectedPair) return;

           // Update data model
           const index = selectedPair.index;
           const newPair = BASE_PAIRS[newBase];
           dnaSequence = dnaSequence.substring(0, index) + newBase + dnaSequence.substring(index + 1);
           
           // Update 3D model
           selectedPair.base = newBase;
           selectedPair.pair = newPair;
           selectedPair.mesh1.material.color.setHex(BASE_COLORS[newBase]);
           selectedPair.mesh2.material.color.setHex(BASE_COLORS[newPair]);
           
           // Update UI
           updateEditorPanel();
       }

       // --- Animation Loop ---
       function animate3D() {
           requestAnimationFrame(animate3D);
           if (controls) controls.update();
           if (renderer && scene && camera) renderer.render(scene, camera);
       }
       
       // --- Start Application ---
       initialize();
   </script>
</body>
</html>

The DNA sequence used in the simulator is a scientifically accurate, albeit simplified, representation of the mutation that causes Sickle Cell Anemia. It focuses on the precise location of the genetic change.

The Core Mutation

Sickle Cell Anemia is caused by a single point mutation in the Hemoglobin Subunit Beta (HBB) gene. This means a single nucleotide base is changed at a specific point in the gene's sequence.

DNA Level: The mutation is a substitution where Adenine (A) is replaced by Thymine (T). This changes the DNA codon (a three-base sequence) from GAG to GTG.

Normal Sequence Snippet: CCT GAG GAG

Sickle Cell Snippet: CCT GTG GAG

Protein Level: This change in the DNA has a direct consequence on the protein built from its instructions.

The normal DNA codon GAG is transcribed into the mRNA codon GAG, which codes for the amino acid Glutamic Acid.

The mutated DNA codon GTG is transcribed into the mRNA codon GUG, which codes for the amino acid Valine.

Cellular Level: The change from glutamic acid (which is hydrophilic, or water-loving) to valine (which is hydrophobic, or water-repelling) alters the behavior of the hemoglobin protein. Under low-oxygen conditions, the mutated hemoglobin proteins clump together into long, stiff rods. These rods distort the red blood cells, forcing them from their normal flexible disc shape into a rigid, curved "sickle" shape.

Important Context

The full HBB gene is over 1,600 base pairs long. The 9-base pair sequence shown in the simulator is a small but critical snippet used in genetics to highlight the location of this specific, well-understood mutation. It is an accurate and common way to represent the genetic cause of Sickle Cell Anemia for educational and illustrative purposes.
 

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.