BIOACADEMY 3D

Anatomical Section

Cellular Inventory

Realistic Physical Rendering (PBR)

SCULPTING CELLULAR STRUCTURE...

Real microscopic sample
REAL MICROSCOPY
Cell in transverse diorama. Spin to explore and click on the organelles to identify them.
Structure

Loading...

Micrograph / Actual Scheme

Structural Anatomy

Physiological Function

Biological Data

// --- 2. GLOBAL STATE --- let activeCellModel = "animal", activeSelectedOrganelleId = "membrane"; let activeDnaSubsection = "helix", currentActiveTab = "celulas", activeSelectedDnaBase = "A"; let cellScene, cellCamera, cellRenderer, cellControls, cellMeshes = [], cellInstancedMesh; let dnaScene, dnaCamera, dnaRenderer, dnaControls, dnaMeshes = []; window.onload = function() { populateOrganellesList(); filterCellTypes("human"); populateChromosomes(); updateCellInfoPanel(); updateDnaInfoPanel(); initGamesHub(); const checkThreeJS = setInterval(() => { if (typeof THREE !== 'undefined' && typeof THREE.OrbitControls !== 'undefined') { clearInterval(checkThreeJS); initCell3D(); initDna3D(); const loader = document.getElementById('cell-loading'); if(loader) loader.classList.add('hidden'); } }, 100); window.addEventListener('resize', onWindowResize); }; // --- 3. UI NAVIGATION --- function switchTab(tabId) { currentActiveTab = tabId; document.querySelectorAll('.tab-content').forEach(el => el.classList.add('hidden')); document.getElementById(`tab-${tabId}`).classList.remove('hidden'); document.querySelectorAll('.tab-btn').forEach(btn => btn.className = "tab-btn px-4 py-2 text-sm font-semibold rounded-lg text-slate-500 hover:text-slate-800 hover:bg-white/50 transition-all duration-300"); document.getElementById(`btn-${tabId}`).className = "tab-btn px-4 py-2 text-sm font-semibold rounded-lg text-blue-700 bg-white border border-slate-200 shadow-sm transition-all duration-300"; onWindowResize(); if (tabId === 'division') { setTimeout(() => { setDivisionType(currentDivisionType); }, 50); } } // --- 4. CELL UI --- function populateOrganellesList() { const list = document.getElementById('organelles-list'); list.innerHTML = ''; cellData[activeCellModel].forEach(item => { const btn = document.createElement('button'); btn.id = `sidebar-cell-${item.id}`; btn.className = `w-full text-left p-3 rounded-xl transition flex items-center gap-3 ${item.id === activeSelectedOrganelleId ? 'selected-list-item bg-white' : 'hover:bg-slate-100 border border-transparent'}`; btn.onclick = () => selectOrganelle(item.id); btn.innerHTML = `
${item.name}${item.tag}
`; list.appendChild(btn); }); } function selectOrganelle(id) { activeSelectedOrganelleId = id; document.querySelectorAll('[id^="sidebar-cell-"]').forEach(el => el.className = `w-full text-left p-3 rounded-xl transition flex items-center gap-3 hover:bg-slate-100 border border-transparent`); document.getElementById(`sidebar-cell-${id}`).className = `w-full text-left p-3 rounded-xl transition flex items-center gap-3 selected-list-item bg-white`; updateCellInfoPanel(); highlightCellMesh(id); } function updateCellInfoPanel() { const item = cellData[activeCellModel].find(o => o.id === activeSelectedOrganelleId) || cellData[activeCellModel][0]; document.getElementById('info-title').innerText = item.name; document.getElementById('info-desc').innerText = item.desc; document.getElementById('info-func').innerText = item.func; document.getElementById('info-extra').innerText = item.extra; const infoRealImg = document.getElementById('info-real-img'); if (infoRealImg && item.realImg) { // Ensure a safe fallback for the UI Panel infoRealImg.onerror = function() { this.src='../../imagenes/biology/photo_1530026405186_ed1f139313f8.jpg'; }; infoRealImg.src = item.realImg; } } function loadCellModel(type) { activeCellModel = type; activeSelectedOrganelleId = cellData[type][0].id; document.getElementById('btn-cell-animal').className = `p-3 rounded-xl border ${type === 'animal' ? 'border-blue-200 bg-blue-50 text-blue-700 shadow-sm hover:bg-blue-100' : 'border-slate-200 bg-white text-slate-500 hover:bg-slate-50'} text-xs font-bold text-center transition flex flex-col items-center gap-1.5`; document.getElementById('btn-cell-general').className = `p-3 rounded-xl border ${type === 'general' ? 'border-blue-200 bg-blue-50 text-blue-700 shadow-sm hover:bg-blue-100' : 'border-slate-200 bg-white text-slate-500 hover:bg-slate-50'} text-xs font-bold text-center transition flex flex-col items-center gap-1.5`; document.querySelector('#btn-cell-animal i').className = `fa-solid fa-paw text-lg ${type === 'animal' ? 'text-blue-500' : 'text-slate-400'}`; document.querySelector('#btn-cell-general i').className = `fa-solid fa-seedling text-lg ${type === 'general' ? 'text-blue-500' : 'text-slate-400'}`; const microImg = document.getElementById('microscope-img'); if (microImg) { microImg.onerror = function() { this.src = type === 'animal' ? '../../imagenes/biology/photo_1530026405186_ed1f139313f8.jpg' : '../../imagenes/biology/photo_1582560475093_ba66accbc424.jpg'; }; microImg.src = type === 'animal' ? '../../imagenes/biology/c0453303_hela_cell_tem.jpg' : '../../imagenes/biology/c0182740_plant_cell_nucleus_tem.jpg'; } populateOrganellesList(); updateCellInfoPanel(); if(typeof THREE !== 'undefined' && typeof THREE.OrbitControls !== 'undefined' && cellScene) { rebuildCell3DScene(); } } // --- 5. CELL TYPES UI --- function filterCellTypes(category) { document.getElementById('btn-filter-human').className = `flex-1 py-2 px-3 text-xs font-bold rounded-lg ${category === 'human' ? 'bg-white text-blue-700 shadow-sm' : 'text-slate-500 hover:text-slate-700'} font-cyber transition`; document.getElementById('btn-filter-unicellular').className = `flex-1 py-2 px-3 text-xs font-bold rounded-lg ${category === 'unicellular' ? 'bg-white text-blue-700 shadow-sm' : 'text-slate-500 hover:text-slate-700'} font-cyber transition`; document.getElementById('btn-filter-pluricellular').className = `flex-1 py-2 px-3 text-xs font-bold rounded-lg ${category === 'multicellular' ? 'bg-white text-blue-700 shadow-sm' : 'text-slate-500 hover:text-slate-700'} font-cyber transition`; const grid = document.getElementById('types-grid'); grid.innerHTML = ''; const filtered = cellTypesData.filter(c => c.category === category); document.getElementById('cell-type-count').innerText = filtered.length; filtered.forEach(cell => { const card = document.createElement('div'); card.className = 'bg-white p-4 rounded-2xl border border-slate-200 hover:border-blue-300 shadow-sm hover:shadow-lg transition-all duration-300 transform hover:-translate-y-1 flex flex-col group'; card.innerHTML = `
${cell.name}
${generateCellSVG(cell.id)}

${cell.name}

${cell.desc}

Function:${cell.func}
`; grid.appendChild(card); }); } // --- 6. DNA UI --- function switchDnaSubSection(sectionId) { activeDnaSubsection = sectionId; ['helix', 'chromosomes', 'rna'].forEach(id => { document.getElementById(`dna-sub-${id}`).classList.add('hidden'); document.getElementById(`btn-sub-${id}`).className = "flex-1 py-2 px-3 text-xs font-bold rounded-lg text-slate-500 hover:text-slate-800 transition"; }); document.getElementById(`dna-sub-${sectionId}`).classList.remove('hidden'); document.getElementById(`btn-sub-${sectionId}`).className = "flex-1 py-2 px-3 text-xs font-bold rounded-lg bg-white text-fuchsia-600 shadow-sm transition"; onWindowResize(); } function selectDnaBase(baseLetter) { activeSelectedDnaBase = baseLetter; updateDnaInfoPanel(); highlightDnaBaseMesh(baseLetter); } function updateDnaInfoPanel() { const data = dnaBasesData[activeSelectedDnaBase]; document.getElementById('dna-desc').innerText = data.desc; document.getElementById('dna-func').innerHTML = `${data.func}`; } function populateChromosomes() { const container = document.getElementById('chromosomes-container'); container.innerHTML = ''; chromosomesData.forEach((chrom) => { const btn = document.createElement('button'); btn.id = `btn-chrom-${chrom.pair}`; btn.onclick = () => selectChromosome(chrom.pair); btn.className = `py-2 rounded-lg border flex items-center justify-center font-bold text-xs transition`; btn.innerHTML = chrom.pair === 23 ? "X/Y" : chrom.pair; container.appendChild(btn); }); selectChromosome(1); } function selectChromosome(num) { const current = chromosomesData[num-1]; document.querySelectorAll('[id^="btn-chrom-"]').forEach(btn => btn.className = "py-2 rounded-lg border border-slate-200 bg-white text-slate-500 hover:bg-slate-50 flex justify-center font-bold text-xs transition"); document.getElementById(`btn-chrom-${num}`).className = "py-2 rounded-lg border border-pink-300 bg-pink-50 text-pink-700 shadow-sm flex justify-center font-bold text-xs transition"; document.getElementById('chrom-title').innerText = num===23 ? "Cromosomas Sexuales" : `Chromosome${num}`; document.getElementById('chrom-badge').innerText = num===23 ? "Par 23" : `Par ${num}`; document.getElementById('chrom-type').innerText = num===23 ? "Alosomas (Determinantes)" : "Homologous autosomes"; document.getElementById('chrom-size').innerText = current.size; document.getElementById('chrom-desc').innerText = current.desc; document.getElementById('chrom-genes').innerText = current.genes; document.getElementById('chrom-diseases').innerText = current.diseases; const vis = document.getElementById('chrom-visual-container'); if(num === 23) { vis.innerHTML = `
`; } else { let h = current.height; vis.innerHTML = `
`; } } // ================= 7. THREE.JS ADVANCED RENDERING ================= function createCellularNoise() { const canvas = document.createElement('canvas'); canvas.width = 512; canvas.height = 512; const context = canvas.getContext('2d'); context.fillStyle = '#000'; context.fillRect(0,0,512,512); for(let i=0; i<3000; i++) { const x = Math.random()*512, y = Math.random()*512, r = Math.random()*15+2; const grad = context.createRadialGradient(x,y,0, x,y,r); const c = Math.floor(Math.random()*200 + 55); grad.addColorStop(0, `rgba(${c},${c},${c},1)`); grad.addColorStop(1, 'rgba(0,0,0,0)'); context.fillStyle = grad; context.beginPath(); context.arc(x,y,r,0,Math.PI*2); context.fill(); } const tex = new THREE.CanvasTexture(canvas); tex.wrapS = THREE.RepeatWrapping; tex.wrapT = THREE.RepeatWrapping; return tex; } function applyProceduralNoise(geometry, amplitude, frequency) { const pos = geometry.attributes.position; const v = new THREE.Vector3(); for(let i=0; i 0) granaGroup.position.z *= -1; group.add(granaGroup); } return group; } function createCentriole() { const group = new THREE.Group(); const tubeMat = new THREE.MeshStandardMaterial({ color: 0x8b5cf6, roughness: 0.6, metalness: 0.1 }); for(let i = 0; i < 9; i++) { const angle = (i / 9) * Math.PI * 2; for(let t = 0; t < 3; t++) { const tube = new THREE.Mesh(new THREE.CylinderGeometry(0.015, 0.015, 0.6, 8), tubeMat); const radius = 0.1 + (t * 0.03); const theta = angle + (t * 0.15); tube.position.set(Math.cos(theta) * radius, 0, Math.sin(theta) * radius); group.add(tube); } } return group; } function initCell3D() { noiseTex = createCellularNoise(); const container = document.getElementById('cell-canvas-container'); cellScene = new THREE.Scene(); cellScene.fog = new THREE.FogExp2(0xf8fafc, 0.03); cellCamera = new THREE.PerspectiveCamera(45, container.clientWidth / container.clientHeight, 0.1, 100); cellCamera.position.set(10, 8, 14); cellRenderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); cellRenderer.setSize(container.clientWidth, container.clientHeight); cellRenderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); cellRenderer.shadowMap.enabled = true; cellRenderer.shadowMap.type = THREE.PCFSoftShadowMap; container.appendChild(cellRenderer.domElement); cellControls = new THREE.OrbitControls(cellCamera, cellRenderer.domElement); cellControls.enableDamping = true; cellControls.dampingFactor = 0.05; const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.6); cellScene.add(hemiLight); const dirLight = new THREE.DirectionalLight(0xffffff, 1.0); dirLight.position.set(10, 20, 10); dirLight.castShadow = true; dirLight.shadow.mapSize.width = 2048; dirLight.shadow.mapSize.height = 2048; dirLight.shadow.bias = -0.001; cellScene.add(dirLight); const fillLight = new THREE.DirectionalLight(0x38bdf8, 0.5); fillLight.position.set(-10, -5, -10); cellScene.add(fillLight); const backLight = new THREE.DirectionalLight(0xdb2777, 0.4); backLight.position.set(0, -10, 10); cellScene.add(backLight); rebuildCell3DScene(); const raycaster = new THREE.Raycaster(); const mouse = new THREE.Vector2(); container.addEventListener('click', (e) => { const rect = cellRenderer.domElement.getBoundingClientRect(); mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1; mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1; raycaster.setFromCamera(mouse, cellCamera); const intersects = raycaster.intersectObjects(cellMeshes, true); if (intersects.length > 0) { let target = intersects[0].object; while (target && !target.userData.id) target = target.parent; if (target && target.userData.id && target.userData.id !== "root") { selectOrganelle(target.userData.id); } } }); requestAnimationFrame(animateCell); } function rebuildCell3DScene() { cellMeshes.forEach(mesh => cellScene.remove(mesh)); cellMeshes = []; const group = new THREE.Group(); group.userData.id = "root"; const isPlant = activeCellModel === 'general'; let memGeom; if (!isPlant) { memGeom = new THREE.SphereGeometry(5.0, 128, 128, 0, Math.PI * 1.5); memGeom = applyProceduralNoise(memGeom, 0.15, 3.0); const memMat = new THREE.MeshPhysicalMaterial({ color: 0xe0f2fe, transmission: 0.85, transparent: true, roughness: 0.3, ior: 1.4, bumpMap: noiseTex, bumpScale: 0.05, clearcoat: 0.8, side: THREE.DoubleSide }); const membrane = new THREE.Mesh(memGeom, memMat); membrane.receiveShadow = true; membrane.userData.id = "membrane"; group.add(membrane); cellMeshes.push(membrane); const tpGeom = new THREE.SphereGeometry(0.12, 8, 8); tpGeom.scale(1, 3, 1); const tpMat = new THREE.MeshStandardMaterial({color: 0xf59e0b, roughness: 0.6}); const tpMesh = new THREE.InstancedMesh(tpGeom, tpMat, 300); const dummy = new THREE.Object3D(); for(let i=0; i<300; i++) { const phi = Math.random() * Math.PI; const theta = Math.random() * Math.PI * 1.5; dummy.position.setFromSphericalCoords(5.0, phi, theta); dummy.lookAt(0,0,0); dummy.updateMatrix(); tpMesh.setMatrixAt(i, dummy.matrix); } group.add(tpMesh); } else { const shape = new THREE.Shape(); for (let i = 0; i < 6; i++) { const angle = (i / 6) * Math.PI * 2; if(i===0) shape.moveTo(Math.cos(angle)*5.5, Math.sin(angle)*5.5); else shape.lineTo(Math.cos(angle)*5.5, Math.sin(angle)*5.5); } shape.lineTo(Math.cos(0)*5.5, Math.sin(0)*5.5); const holePath = new THREE.Path(); holePath.moveTo(0,0); holePath.lineTo(5.0, 0); holePath.lineTo(5.0, 5.0); holePath.lineTo(0, 5.0); holePath.lineTo(0,0); shape.holes.push(holePath); const extrudeSettings = { depth: 9, bevelEnabled: true, bevelSegments: 3, steps: 1, bevelSize: 0.1, bevelThickness: 0.1 }; memGeom = new THREE.ExtrudeGeometry(shape, extrudeSettings); memGeom.rotateX(Math.PI / 2); memGeom.translate(0, 4.5, 0); // CORRECCIÓN: Material cristalino hiperrealista que no bloquea la visión (depthWrite: false) const wallMat = new THREE.MeshPhysicalMaterial({ color: 0x86efac, transmission: 0.9, transparent: true, opacity: 0.45, depthWrite: false, roughness: 0.8, bumpMap: noiseTex, bumpScale: 0.2, side: THREE.DoubleSide }); const wall = new THREE.Mesh(memGeom, wallMat); wall.receiveShadow = true; wall.castShadow = false; // Evita que la pared oscurezca el interior con sus sombras wall.userData.id = "wall"; group.add(wall); cellMeshes.push(wall); const innerMemGeom = new THREE.CylinderGeometry(5.1, 5.1, 8.8, 6, 1, false, 0, Math.PI * 1.33); const innerMemMat = new THREE.MeshPhysicalMaterial({ color: 0xe0f2fe, transmission: 0.9, transparent: true, opacity: 0.35, depthWrite: false, roughness: 0.2, side: THREE.DoubleSide }); const innerMem = new THREE.Mesh(innerMemGeom, innerMemMat); group.add(innerMem); } const nucGroup = new THREE.Group(); nucGroup.userData.id = "nucleus"; let nucOuterGeom = new THREE.SphereGeometry(1.6, 64, 64, 0, Math.PI * 1.5); nucOuterGeom = applyProceduralNoise(nucOuterGeom, 0.05, 5.0); const nucOuterMat = new THREE.MeshPhysicalMaterial({ color: 0x9d174d, roughness: 0.7, bumpMap: noiseTex, bumpScale: 0.1, side: THREE.DoubleSide, clearcoat: 0.3 }); const nucleus = new THREE.Mesh(nucOuterGeom, nucOuterMat); nucleus.castShadow = true; nucleus.receiveShadow = true; nucGroup.add(nucleus); const nucInner = new THREE.Mesh(new THREE.SphereGeometry(1.55, 64, 64, 0, Math.PI * 1.5), new THREE.MeshStandardMaterial({color: 0xfbcfe8, roughness: 1.0, side: THREE.DoubleSide})); nucGroup.add(nucInner); const poreMat = new THREE.MeshStandardMaterial({color: 0x4a044e, roughness: 1.0}); for(let i=0; i<150; i++) { const pore = new THREE.Mesh(new THREE.TorusGeometry(0.06, 0.02, 8, 16), poreMat); const phi = Math.acos(-1 + (2 * i) / 150); const theta = Math.sqrt(150 * Math.PI) * phi; if (theta % (Math.PI*2) < Math.PI * 1.5) { pore.position.setFromSphericalCoords(1.6, phi, theta); pore.lookAt(0,0,0); nucGroup.add(pore); } } let nucleolusGeom = new THREE.SphereGeometry(0.5, 32, 32); nucleolusGeom = applyProceduralNoise(nucleolusGeom, 0.05, 8.0); const nucleolus = new THREE.Mesh(nucleolusGeom, new THREE.MeshStandardMaterial({color: 0x831843, roughness: 0.9})); nucleolus.position.set(0.3, 0.3, -0.3); nucleolus.castShadow = true; nucGroup.add(nucleolus); const chromMat = new THREE.MeshStandardMaterial({color: 0xbe185d, roughness: 0.8}); for(let i=0; i<12; i++) { const curve = new THREE.CatmullRomCurve3(Array.from({length: 8}, () => new THREE.Vector3((Math.random()-0.5)*2.0, (Math.random()-0.5)*2.0, (Math.random()-0.5)*2.0))); const thread = new THREE.Mesh(new THREE.TubeGeometry(curve, 32, 0.015, 8, false), chromMat); nucGroup.add(thread); } nucGroup.position.set(isPlant ? -1.8 : 0, isPlant ? 1 : 0.5, 0); group.add(nucGroup); cellMeshes.push(nucGroup); if(!isPlant) { const erGroup = new THREE.Group(); erGroup.userData.id = "reticulum"; const erMat = new THREE.MeshStandardMaterial({color: 0x2563eb, roughness: 0.8, side: THREE.DoubleSide}); const riboMat = new THREE.MeshStandardMaterial({color: 0xffffff}); for(let i=0; i<4; i++) { const radius = 1.8 + i*0.25; let sheetGeom = new THREE.SphereGeometry(radius, 64, 32, 0, Math.PI * 1.5, Math.PI/4, Math.PI/2); sheetGeom = applyProceduralNoise(sheetGeom, 0.08, 6.0); const sheet = new THREE.Mesh(sheetGeom, erMat); sheet.rotation.set(0, Math.random()*0.4, 0); sheet.castShadow = true; sheet.receiveShadow = true; if(i < 3) { const riboMesh = new THREE.InstancedMesh(new THREE.SphereGeometry(0.015, 4, 4), riboMat, 300); const dummyR = new THREE.Object3D(); for(let r=0; r<300; r++) { const phi = (Math.PI/4) + Math.random() * (Math.PI/2); const theta = Math.random() * Math.PI * 1.5; dummyR.position.setFromSphericalCoords(radius + 0.01, phi, theta); dummyR.updateMatrix(); riboMesh.setMatrixAt(r, dummyR.matrix); } sheet.add(riboMesh); } erGroup.add(sheet); } erGroup.position.copy(nucGroup.position); group.add(erGroup); cellMeshes.push(erGroup); const golgiGroup = new THREE.Group(); golgiGroup.userData.id = "golgi"; const golgiMat = new THREE.MeshStandardMaterial({color: 0x10b981, roughness: 0.5}); for(let i=0; i<6; i++) { let gLayerGeom = new THREE.CylinderGeometry(1.2 - i*0.1, 1.2 - i*0.1, 0.08, 32, 1, false, 0, Math.PI * 0.8); gLayerGeom = applyProceduralNoise(gLayerGeom, 0.05, 8.0); const gLayer = new THREE.Mesh(gLayerGeom, golgiMat); gLayer.position.y = i * 0.15; gLayer.castShadow = true; golgiGroup.add(gLayer); const ves1 = new THREE.Mesh(new THREE.SphereGeometry(0.12, 16, 16), golgiMat); ves1.position.set(1.2 - i*0.1, i*0.15, 0); const ves2 = new THREE.Mesh(new THREE.SphereGeometry(0.12, 16, 16), golgiMat); ves2.position.set(Math.cos(Math.PI*0.8)*(1.2 - i*0.1), i*0.15, Math.sin(Math.PI*0.8)*(1.2 - i*0.1)); golgiGroup.add(ves1); golgiGroup.add(ves2); } golgiGroup.position.set(2.5, -1.0, -1.5); golgiGroup.rotation.set(0.2, -0.8, 0.1); group.add(golgiGroup); cellMeshes.push(golgiGroup); for(let i=0; i<6; i++) { const mito = createMitochondria(); mito.position.set(Math.random()*6-3, Math.random()*6-3, Math.random()*4-2); mito.rotation.set(Math.random()*Math.PI, Math.random()*Math.PI, 0); mito.userData.id = "mitochondria"; group.add(mito); cellMeshes.push(mito); } for(let i=0; i<8; i++) { const ly = new THREE.Mesh(new THREE.SphereGeometry(0.25, 32, 32), new THREE.MeshPhysicalMaterial({color: 0xef4444, roughness: 0.5, clearcoat: 0.8})); ly.position.set(Math.random()*6-3, Math.random()*6-3, Math.random()*4-2); ly.castShadow = true; ly.userData.id = "lysosome"; group.add(ly); cellMeshes.push(ly); } const centGroup = new THREE.Group(); centGroup.userData.id = "centriole"; const c1 = createCentriole(); const c2 = createCentriole(); c2.rotation.x = Math.PI/2; c2.position.set(0, 0.4, 0.4); centGroup.add(c1); centGroup.add(c2); centGroup.position.set(-1.5, 2.5, -1.5); centGroup.rotation.set(0.5, 0.5, 0); group.add(centGroup); cellMeshes.push(centGroup); } else { let vacGeom = new THREE.SphereGeometry(3.5, 64, 64); vacGeom.scale(1.0, 1.2, 0.8); vacGeom = applyProceduralNoise(vacGeom, 0.1, 2.0); // CORRECCIÓN: La vacuola también debe ser translúcida (depthWrite: false) para ver las mitocondrias que tiene detrás const vacMat = new THREE.MeshPhysicalMaterial({ color: 0x38bdf8, transmission: 0.95, transparent: true, opacity: 0.6, depthWrite: false, roughness: 0.0, ior: 1.33 }); const vac = new THREE.Mesh(vacGeom, vacMat); vac.position.set(0.5, -0.5, 0); vac.userData.id = "vacuole"; group.add(vac); cellMeshes.push(vac); for(let i=0; i<8; i++) { const clo = createChloroplast(); clo.position.set(Math.random()*6-3, Math.random()*6-3, Math.random()*4-2); clo.rotation.set(Math.random()*Math.PI, Math.random()*Math.PI, Math.random()*Math.PI); clo.userData.id = "chloroplast"; group.add(clo); cellMeshes.push(clo); } for(let i=0; i<4; i++) { const mito = createMitochondria(); mito.position.set(Math.random()*6-3, Math.random()*6-3, Math.random()*4-2); mito.rotation.set(Math.random()*Math.PI, Math.random()*Math.PI, 0); mito.userData.id = "mitochondria"; group.add(mito); cellMeshes.push(mito); } } const proteinCount = 4000; const protGeom = new THREE.DodecahedronGeometry(0.04, 0); const protMat = new THREE.MeshStandardMaterial({ roughness: 0.7 }); const protMesh = new THREE.InstancedMesh(protGeom, protMat, proteinCount); const dummyP = new THREE.Object3D(); const color = new THREE.Color(); const palettes = [0xf59e0b, 0x0ea5e9, 0xec4899, 0x10b981, 0x8b5cf6, 0xf43f5e, 0x94a3b8]; for(let i = 0; i < proteinCount; i++) { const r = (isPlant ? 5.2 : 4.8) * Math.cbrt(Math.random()); const theta = Math.random() * 2 * Math.PI; const phi = Math.acos(2 * Math.random() - 1); dummyP.position.setFromSphericalCoords(r, phi, theta); if (!isPlant && dummyP.position.x > 0 && dummyP.position.z > 0) continue; if (isPlant && dummyP.position.x > 0 && dummyP.position.z > 0) continue; dummyP.rotation.set(Math.random(), Math.random(), Math.random()); dummyP.scale.setScalar(Math.random() * 1.5 + 0.5); dummyP.updateMatrix(); protMesh.setMatrixAt(i, dummyP.matrix); protMesh.setColorAt(i, color.setHex(palettes[Math.floor(Math.random()*palettes.length)])); } group.add(protMesh); const cytoCount = 300; const cytoMesh = new THREE.InstancedMesh(new THREE.CylinderGeometry(0.008, 0.008, 2, 5), new THREE.MeshStandardMaterial({color: 0xcbd5e1, transparent: true, opacity: 0.6}), cytoCount); for(let i=0; i 4.5) continue; if (!isPlant && dummyP.position.x > 0 && dummyP.position.z > 0) continue; dummyP.rotation.set(Math.random()*Math.PI, Math.random()*Math.PI, 0); dummyP.scale.set(1, Math.random()*3+0.5, 1); dummyP.updateMatrix(); cytoMesh.setMatrixAt(i, dummyP.matrix); } group.add(cytoMesh); cellScene.add(group); cellMeshes.push(group); } function highlightCellMesh(id) { const root = cellMeshes.find(m => m.userData.id === "root"); if (!root) return; root.traverse((child) => { if (child.isMesh) { let target = child; while (target && !target.userData.id) target = target.parent; if (target && target.userData.id === id) { if(child.material && child.material.emissive) child.material.emissive.setHex(0x333333); } else { if(child.material && child.material.emissive) child.material.emissive.setHex(0x000000); } } }); } function animateCell() { requestAnimationFrame(animateCell); cellControls.update(); const root = cellMeshes.find(m => m.userData.id === "root"); if(root) { root.rotation.y += 0.002; } cellRenderer.render(cellScene, cellCamera); } // --- 8. THREE.JS DNA RENDERER --- function initDna3D() { const container = document.getElementById('dna-canvas-container'); dnaScene = new THREE.Scene(); dnaCamera = new THREE.PerspectiveCamera(45, container.clientWidth / container.clientHeight, 0.1, 100); dnaCamera.position.set(0, 0, 25); dnaRenderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); dnaRenderer.setSize(container.clientWidth, container.clientHeight); dnaRenderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); container.appendChild(dnaRenderer.domElement); dnaControls = new THREE.OrbitControls(dnaCamera, dnaRenderer.domElement); dnaControls.enableDamping = true; dnaScene.add(new THREE.AmbientLight(0xffffff, 0.8)); const dl1 = new THREE.DirectionalLight(0xf59e0b, 0.6); dl1.position.set(5, 10, 5); dnaScene.add(dl1); const dl2 = new THREE.DirectionalLight(0xdb2777, 0.6); dl2.position.set(-5, -10, -5); dnaScene.add(dl2); rebuildDna3DScene(); const raycaster = new THREE.Raycaster(); const mouse = new THREE.Vector2(); container.addEventListener('click', (e) => { const rect = dnaRenderer.domElement.getBoundingClientRect(); mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1; mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1; raycaster.setFromCamera(mouse, dnaCamera); const intersects = raycaster.intersectObjects(dnaMeshes, true); if (intersects.length > 0 && intersects[0].object.userData.base) selectDnaBase(intersects[0].object.userData.base); }); requestAnimationFrame(animateDna); } function rebuildDna3DScene() { dnaMeshes.forEach(m => dnaScene.remove(m)); dnaMeshes = []; const group = new THREE.Group(); group.userData.id = "root"; const p1 = [], p2 = []; const totalPairs = 36; const radius = 2.8; const heightFactor = 0.55; for (let i = 0; i <= totalPairs; i++) { const t = (i / totalPairs) * Math.PI * 5; const y = (i - totalPairs/2) * heightFactor; p1.push(new THREE.Vector3(Math.cos(t)*radius, y, Math.sin(t)*radius)); p2.push(new THREE.Vector3(Math.cos(t+Math.PI)*radius, y, Math.sin(t+Math.PI)*radius)); } group.add(new THREE.Mesh(new THREE.TubeGeometry(new THREE.CatmullRomCurve3(p1), 100, 0.4, 12, false), new THREE.MeshPhysicalMaterial({ color: 0x0ea5e9, roughness: 0.1 }))); group.add(new THREE.Mesh(new THREE.TubeGeometry(new THREE.CatmullRomCurve3(p2), 100, 0.4, 12, false), new THREE.MeshPhysicalMaterial({ color: 0xa855f7, roughness: 0.1 }))); const seq = [['A','T'],['G','C'],['C','G'],['T','A'],['A','T'],['C','G']]; const cols = { A: 0xf59e0b, T: 0x3b82f6, G: 0x10b981, C: 0xec4899 }; for(let i=0; i { if(m.userData.base === base) { m.scale.set(1.4,1,1.4); m.material.emissive.setHex(0x444444); } else if(m.userData.base) { m.scale.set(1,1,1); m.material.emissive.setHex(0x000000); } }); } function animateDna() { requestAnimationFrame(animateDna); dnaControls.update(); const root = dnaMeshes.find(m => m.userData.id === "root"); if(root) root.rotation.y += 0.003; dnaRenderer.render(dnaScene, dnaCamera); } function onWindowResize() { const cc = document.getElementById('cell-canvas-container'); if(cellCamera && cc.clientWidth) { cellCamera.aspect = cc.clientWidth/cc.clientHeight; cellCamera.updateProjectionMatrix(); cellRenderer.setSize(cc.clientWidth, cc.clientHeight); } const dc = document.getElementById('dna-canvas-container'); if(dnaCamera && dc.clientWidth && activeDnaSubsection === 'helix' && currentActiveTab === 'dna') { dnaCamera.aspect = dc.clientWidth/dc.clientHeight; dnaCamera.updateProjectionMatrix(); dnaRenderer.setSize(dc.clientWidth, dc.clientHeight); } } // --- 9. GAMES HUB ENGINE --- const gamesList = [ { id: 1, name: "DNA replication", desc: "Drag & Drop mating with visual lace.", icon: "fa-dna", color: "fuchsia" }, { id: 2, name: "Ribosomal Translation", desc: "Synthesize the protein by injecting tRNA.", icon: "fa-stopwatch", color: "emerald" }, { id: 3, name: "Identificador Estructural", desc: "Guess the organelle based on its 3D morphology.", icon: "fa-eye", color: "blue" }, { id: 4, name: "Microscopic Triage", desc: "Clasifica visualmente muestras a contrarreloj.", icon: "fa-microscope", color: "amber" } ]; function initGamesHub() { const hub = document.getElementById('games-hub'); hub.innerHTML = ''; gamesList.forEach(g => { const btn = document.createElement('div'); btn.className = `bg-white p-6 rounded-2xl border border-slate-200 cursor-pointer game-card-hover flex items-center gap-5 shadow-sm text-left`; btn.onclick = () => openGame(g.id); btn.innerHTML = `

${g.name}

${g.desc}

`; hub.appendChild(btn); }); } function openGame(id) { document.getElementById('games-hub').classList.add('hidden'); document.getElementById('active-game-container').classList.remove('hidden'); const canvas = document.getElementById('game-canvas'); const game = gamesList.find(g => g.id === id); let gameInstruction = ""; if(id === 1) gameInstruction = "Instructions: The polymerase advances by building the DNA strand. Drag the correct bases to match them: Adenine (A) only fits with Thymine (T), and Cytosine (C) with Guanine (G). Notice the geometric shape of the hydrogen bonds."; if(id === 2) gameInstruction = "Instructions: The ribosome moves forward reading the mRNA. Select the tRNA whose ANTICODON matches perfectly with the current codon to bring the amino acid and form the protein chain."; if(id === 3) gameInstruction = "Instructions: Observe the cross section of the cell. An organelle will be highlighted in color. Analyze its structure and anatomical location to select the correct name from the list below."; if(id === 4) gameInstruction = "Instructions: Time Trial Mode! Real samples will appear through the lens of the electron microscope. Classify them quickly by clicking on the fabric or domain to which they belong."; canvas.innerHTML = `

${game.name}

${gameInstruction}
`; const root = document.getElementById('game-engine-root'); if(id === 1) renderGame1(root); else if(id === 2) renderGame2(root); else if(id === 3) renderGame3(root); else if(id === 4) renderGame4(root); } function closeGame() { if(window.g4_timerInterval) clearInterval(window.g4_timerInterval); document.getElementById('games-hub').classList.remove('hidden'); document.getElementById('active-game-container').classList.add('hidden'); } // --- GAME 1: REPLICACIÓN (Drag & Drop Interlocking Exact Shapes) --- let g1_target = [], g1_player = [], g1_idx = 0; function getBaseSVG(b, isTop) { const colors = {A:'#f59e0b', T:'#3b82f6', G:'#10b981', C:'#ec4899'}; let path = ''; if (isTop) { if(b==='A') path = "M 0 0 H 50 V 45 L 25 70 L 0 45 Z"; if(b==='T') path = "M 0 0 H 50 V 70 L 25 45 L 0 70 Z"; if(b==='G') path = "M 0 0 H 50 V 45 a 12.5 12.5 0 0 0 -25 0 a 12.5 12.5 0 0 0 -25 0 Z"; if(b==='C') path = "M 0 0 H 50 V 45 a 12.5 12.5 0 0 1 -25 0 a 12.5 12.5 0 0 1 -25 0 Z"; } else { if(b==='A') path = "M 0 25 L 25 0 L 50 25 V 70 H 0 Z"; if(b==='T') path = "M 0 0 L 25 25 L 50 0 V 70 H 0 Z"; if(b==='G') path = "M 0 25 a 12.5 12.5 0 0 0 25 0 a 12.5 12.5 0 0 0 25 0 V 70 H 0 Z"; if(b==='C') path = "M 0 25 a 12.5 12.5 0 0 1 25 0 a 12.5 12.5 0 0 1 25 0 V 70 H 0 Z"; } return `${b}`; } function renderGame1(root) { g1_target = Array.from({length:6}, () => ['A','T','C','G'][Math.floor(Math.random()*4)]); g1_player = []; g1_idx = 0; root.innerHTML = `
${['A','T','C','G'].map(b => `
${getBaseSVG(b, false)}
`).join('')}
`; g1_draw(); } function g1_draw() { document.getElementById('g1-top').innerHTML = g1_target.map((b) => getBaseSVG(b, true)).join(''); document.getElementById('g1-bot').innerHTML = Array.from({length:6}, (_,i) => { if (i < g1_idx) return `
${getBaseSVG(g1_player[i], false)}
`; if (i === g1_idx) return `
`; return `
`; }).join(''); } function g1_drag(ev, b) { ev.dataTransfer.setData("base", b); } function g1_getComp(b) { return {A:'T',T:'A',C:'G',G:'C'}[b]; } function g1_drop(ev) { ev.preventDefault(); const b = ev.dataTransfer.getData("base"); if(b === g1_getComp(g1_target[g1_idx])) { g1_player.push(b); g1_idx++; g1_draw(); if(g1_idx === 6) setTimeout(()=>{ document.getElementById('g1-board').classList.add('anim-pulse-glow'); setTimeout(() => renderGame1(document.getElementById('game-engine-root')), 2000); }, 300); } else { const board = document.getElementById('g1-board'); board.classList.add('anim-shake'); board.classList.replace('border-slate-200', 'border-red-400'); const dropzone = document.getElementById('g1-dropzone'); if(dropzone) dropzone.classList.remove('drop-zone-active'); setTimeout(() => { board.classList.remove('anim-shake'); board.classList.replace('border-red-400', 'border-slate-200'); }, 400); } } // --- GAME 2: TRADUCCIÓN RIBOSOMAL --- const g2_codons = [ {c: "AUG", a: "Met"}, {c: "GGC", a: "Gly"}, {c: "UUA", a: "Leu"}, {c: "CGC", a: "Arg"}, {c: "UAA", a: "STOP"} ]; const anticodonMap = { "Met": "UAC", "Gly": "CCG", "Leu": "AAU", "Arg": "GCG", "STOP": "AUU" }; const colorMap = { "Met": ["#38bdf8", "#0284c7"], "Gly": ["#34d399", "#059669"], "Leu": ["#fbbf24", "#d97706"], "Arg": ["#f472b6", "#be185d"], "STOP": ["#f87171", "#dc2626"] }; let g2_idx = 0; function renderGame2(root) { g2_idx = 0; root.innerHTML = `
${g2_codons.map(c => `
${c.c}
`).join('')}
SITIO A
`; g2_draw(); } function g2_draw() { if(g2_idx >= g2_codons.length) return; document.getElementById('g2-mrna').style.transform = `translateX(calc(-50% + 52px - ${g2_idx * 104}px))`; const currentC = g2_codons[g2_idx]; let opts = [currentC.a]; while(opts.length < 4) { let randomA = g2_codons[Math.floor(Math.random()*g2_codons.length)].a; if(!opts.includes(randomA)) opts.push(randomA); } opts.sort(()=>Math.random()-0.5); document.getElementById('g2-controls').innerHTML = opts.map(o => ` `).join(''); } function g2_play(ans) { if(ans === g2_codons[g2_idx].a) { const isStop = ans === "STOP"; const pContainer = document.getElementById('g2-protein'); if (g2_idx > 0) pContainer.innerHTML += `
`; pContainer.innerHTML += `
${isStop ? '' : ans.substring(0,3).toUpperCase()}
`; g2_idx++; if(isStop) { document.getElementById('g2-ribosome-board').classList.add('opacity-50'); document.getElementById('g2-controls').innerHTML = ``; } else { g2_draw(); } } else { const board = document.getElementById('g2-ribosome-board'); board.classList.add('anim-shake'); board.classList.replace('border-slate-200', 'border-red-400'); setTimeout(() => { board.classList.remove('anim-shake'); board.classList.replace('border-red-400', 'border-slate-200'); }, 400); } } // --- GAME 3: IDENTIFICADOR ESTRUCTURAL --- const g3_pool = [ {id: "nucleus", n: "Core"}, {id: "mitochondria", n: "Mitochondria"}, {id: "golgi", n: "Golgi apparatus"}, {id: "lysosome", n: "Lysosome"}, {id: "reticulum", n: "Endoplasmic Reticulum"} ]; let g3_curr, g3_score = 0; let g3_count = 0, g3_correctList = [], g3_incorrectList = []; function getFullCellHighlightSVG(highlightId) { const isNuc = highlightId === 'nucleus'; const isMito = highlightId === 'mitochondria'; const isGolgi = highlightId === 'golgi'; const isLyso = highlightId === 'lysosome'; const isEr = highlightId === 'reticulum'; return ` `; } function renderGame3(root) { if (g3_count === 0) { g3_correctList = []; g3_incorrectList = []; } g3_curr = g3_pool[Math.floor(Math.random() * g3_pool.length)]; let options = [g3_curr.n]; while(options.length < 4) { let rand = g3_pool[Math.floor(Math.random() * g3_pool.length)].n; if(!options.includes(rand)) options.push(rand); } options.sort(()=>Math.random()-0.5); root.innerHTML = `
Round: ${g3_count+1}/10
POINTS: ${g3_score}
${getFullCellHighlightSVG(g3_curr.id)}
${options.map(opt => ``).join('')}
`; } function g3_check(ans) { const frame = document.getElementById('g3-frame'); g3_count++; if(ans === g3_curr.n) { g3_score += 100; g3_correctList.push(g3_curr); frame.classList.replace('border-slate-300', 'border-emerald-400'); } else { g3_score = Math.max(0, g3_score - 50); g3_incorrectList.push({ expected: g3_curr, chosen: ans }); frame.classList.add('anim-shake'); frame.classList.replace('border-slate-300', 'border-red-500'); } setTimeout(() => { if(g3_count >= 10) { g3_endGame(document.getElementById('game-engine-root')); } else { renderGame3(document.getElementById('game-engine-root')); } }, 600); } function g3_endGame(root) { g3_count = 0; let suggestionsHTML = ""; if (g3_incorrectList.length > 0) { // Remove duplicates in suggestions const uniqueIncorrect = []; const seen = new Set(); g3_incorrectList.forEach(item => { if (!seen.has(item.expected.id)) { seen.add(item.expected.id); uniqueIncorrect.push(item); } }); suggestionsHTML = `

Review Suggestions:

    ${uniqueIncorrect.map(item => { const organelleData = cellData.animal.find(o => o.name === item.expected.n) || cellData.plant.find(o => o.name === item.expected.n); const funcDesc = organelleData ? organelleData.func : ""; return `
  • ${item.expected.n}: Recuerda: ${funcDesc}
  • `; }).join('')}
`; } else { suggestionsHTML = `
Perfect! You identified all the organelles without making mistakes.
`; } root.innerHTML = `

IDENTIFICATION COMPLETE

Successes:${g3_correctList.length}/10

${g3_score} Pts
${suggestionsHTML}
`; } // --- GAME 4: TRIAJE MICROSCÓPICO (REAL IMAGES) --- let g4_pool = [ {n:"Eritrocitos", t:"h", id:"erythrocyte", realImg: "../../imagenes/biology/erythrocyte.jpg"}, {n:"Neurona", t:"h", id:"neuron", realImg: "../../imagenes/biology/neuron.jpg"}, {n:"Miocito", t:"h", id:"myocyte", realImg: "../../imagenes/biology/myocyte.jpg"}, {n:"Osteocito", t:"h", id:"osteocyte", realImg: "../../imagenes/biology/osteocyte.jpg"}, {n:"Adipocito", t:"h", id:"adipocyte", realImg: "../../imagenes/biology/adipocyte.jpg"}, {n:"Linfocito", t:"h", id:"lymphocyte", realImg: "../../imagenes/biology/lymphocyte.jpg"}, {n:"Espermatozoide", t:"h", id:"sperm", realImg: "../../imagenes/biology/sperm.jpg"}, {n:"Ovule", t:"h", id:"egg", realImg: "../../imagenes/biology/egg.jpg"}, {n:"Macrophage", t:"h", id:"macrophage", realImg: "../../imagenes/biology/macrophage.jpg"}, {n:"Epithelial Cell", t:"h", id:"epithelial", realImg: "../../imagenes/biology/epithelial.jpg"}, {n:"Ameba", t:"m", id:"amoeba", realImg: "../../imagenes/biology/amoeba.jpg"}, {n:"Paramecio", t:"m", id:"paramecium", realImg: "../../imagenes/biology/paramecium.jpg"}, {n:"Diatomea", t:"m", id:"diatom", realImg: "../../imagenes/biology/diatom.jpg"}, {n:"Euglena", t:"m", id:"euglena", realImg: "../../imagenes/biology/euglena.jpg"}, {n:"Bacteria", t:"m", id:"bacteria", realImg: "../../imagenes/biology/bacteria.jpg"}, {n:"Levadura", t:"m", id:"yeast", realImg: "../../imagenes/biology/yeast.jpg"}, {n:"Radiolario", t:"m", id:"radiolarian", realImg: "../../imagenes/biology/radiolarian.jpg"}, {n:"Tejido Hoja", t:"v", id:"mesophyll", realImg: "../../imagenes/biology/mesophyll.jpg"}, {n:"Estoma", t:"v", id:"stoma", realImg: "../../imagenes/biology/stoma.jpg"}, {n:"Xilema", t:"v", id:"xylem", realImg: "../../imagenes/biology/xylem.jpg"}, {n:"Root", t:"v", id:"root", realImg: "../../imagenes/biology/root.jpg"}, {n:"Fungal Hypha", t:"v", id:"hypha", realImg: "../../imagenes/biology/hypha.jpg"}, {n:"Polen", t:"v", id:"pollen", realImg: "../../imagenes/biology/pollen.jpg"}, {n:"Onion Cells", t:"v", id:"onion", realImg: "../../imagenes/biology/onion.jpg"} ]; let g4_curr, g4_timeLeft = 30; let g4_correctList = [], g4_incorrectList = []; function renderGame4(root) { window.g4_score = 0; g4_timeLeft = 30; g4_correctList = []; g4_incorrectList = []; root.innerHTML = `
TIME: 30s
POINTS: 0
`; g4_next(); window.g4_timerInterval = setInterval(() => { g4_timeLeft--; document.getElementById('g4-time').innerText = `${g4_timeLeft}s`; if(g4_timeLeft <= 0) g4_endGame(root); }, 1000); } function g4_next() { g4_curr = g4_pool[Math.floor(Math.random()*g4_pool.length)]; const view = document.getElementById('g4-view'); view.classList.remove('anim-focus'); void view.offsetWidth; view.classList.add('anim-focus'); const fallback = `https://images.unsplash.com/photo-1576086213369-97a306d36557?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80`; view.innerHTML = ` `; } function g4_check(t) { const view = document.getElementById('g4-view'); if(t === g4_curr.t) { window.g4_score += 100; g4_correctList.push(g4_curr); document.getElementById('g4-score').innerText = window.g4_score; view.children[0].classList.add('anim-fly-out'); setTimeout(g4_next, 300); } else { window.g4_score = Math.max(0, window.g4_score - 50); g4_incorrectList.push({ expected: g4_curr, chosen: t }); document.getElementById('g4-score').innerText = window.g4_score; view.parentElement.classList.add('anim-shake'); setTimeout(() => view.parentElement.classList.remove('anim-shake'), 400); } } function g4_endGame(root) { clearInterval(window.g4_timerInterval); let suggestionsHTML = ""; if (g4_incorrectList.length > 0) { const typeNames = { h: "Human", m: "Microbio", v: "Vegetable/Mushroom" }; const uniqueIncorrect = []; const seen = new Set(); g4_incorrectList.forEach(item => { if (!seen.has(item.expected.id)) { seen.add(item.expected.id); uniqueIncorrect.push(item); } }); suggestionsHTML = `

Cells you should review:

    ${uniqueIncorrect.map(item => { let advice = ""; if (item.expected.t === 'h') advice = "It is a structure of the human (animal/eukaryote) body."; else if (item.expected.t === 'm') advice = "It is a unicellular microorganism (bacteria or free-living protist)."; else if (item.expected.t === 'v') advice = "It is of plant origin (with cell wall and chloroplasts) or fungal."; return `
  • ${item.expected.n}:Incorrectly classified as "${typeNames[item.chosen]}". Recuerda: ${advice}
  • `; }).join('')}
`; } else { suggestionsHTML = `
Perfect classification! You know all the microscopic samples impeccably.
`; } root.innerHTML = `

OUT OF TIME

Final Ranking Score

${window.g4_score} Pts

Aciertos: ${g4_correctList.length}| Errors:${g4_incorrectList.length}

${suggestionsHTML}
`; } // --- QUIZ FINAL --- const quizQuestions = [ { q: "In which chromosome pair is the alteration (trisomy) that causes Down Syndrome found?", a: ["On Par 23", "On Par 21", "On Par 18"], c: 1, exp: "Down syndrome is due to an extra copy on autosome 21." }, { q: "What nitrogenous base is exclusive to RNA and replaces Thymine?", a: ["Uracilo", "Citosina", "Adenina"], c: 0, exp: "Uracil (U) replaces Thymine (T) during transcription to RNA." }, { q: "Which human cell completely lacks a nucleus in its mature stage?", a: ["Cortex neuron", "Linfocito B", "Erythrocyte (Red Blood Cell)"], c: 2, exp: "Erythrocytes lose their nucleus to maximize hemoglobin transport." }, { q: "What is the organelle responsible for carrying out the photosynthesis process?", a: ["Mitochondria", "Chloroplast", "Golgi apparatus"], c: 1, exp: "The chloroplast, thanks to chlorophyll, converts light into chemical energy." }, { q: "According to the rule of complementarity, Cytokine (C) pairs in DNA with:", a: ["Guanina (G)", "Adenina (A)", "Uracilo (U)"], c: 0, exp: "Cytocine and Guanine are linked by three strong hydrogen bonds." } ]; let quizIdx = 0, quizCorrect = 0, quizDone = false; function startQuiz() { document.getElementById('quiz-setup-card').classList.add('hidden'); document.getElementById('quiz-results-card').classList.add('hidden'); document.getElementById('quiz-play-card').classList.remove('hidden'); quizIdx = 0; quizCorrect = 0; loadQuizQuestion(); } function loadQuizQuestion() { quizDone = false; document.getElementById('quiz-feedback').classList.add('hidden'); const q = quizQuestions[quizIdx]; document.getElementById('quiz-progress-text').innerText = `Question${quizIdx+1}/${quizQuestions.length}`; document.getElementById('quiz-percentage-text').innerText = `${Math.round(((quizIdx)/quizQuestions.length)*100)}%`; document.getElementById('quiz-progress-bar').style.width = `${((quizIdx+1)/quizQuestions.length)*100}%`; document.getElementById('quiz-question').innerText = q.q; document.getElementById('quiz-answers').innerHTML = q.a.map((ans, i) => ``).join(''); } function selectQuizAnswer(sel, btn) { if(quizDone) return; quizDone = true; const q = quizQuestions[quizIdx]; if(sel === q.c) { quizCorrect++; btn.className = "w-full text-left p-5 rounded-xl border-2 border-emerald-500 bg-emerald-50 text-emerald-700 text-sm font-bold shadow-sm"; } else { btn.className = "w-full text-left p-5 rounded-xl border-2 border-red-500 bg-red-50 text-red-700 text-sm font-bold shadow-sm"; } document.getElementById('quiz-feedback').classList.remove('hidden'); document.getElementById('quiz-feedback-text').innerHTML = `${sel===q.c?'Correct!':'Incorrect.'} ${q.exp}`; } function nextQuizQuestion() { quizIdx++; if(quizIdx < quizQuestions.length) loadQuizQuestion(); else showQuizResults(); } function showQuizResults() { document.getElementById('quiz-play-card').classList.add('hidden'); document.getElementById('quiz-results-card').classList.remove('hidden'); document.getElementById('quiz-correct-count').innerText = quizCorrect; const pct = Math.round((quizCorrect/quizQuestions.length)*100); document.getElementById('quiz-score-percentage').innerText = `${pct}%`; document.getElementById('quiz-eval-text').innerText = pct >= 80 ? "You demonstrate a deep mastery of molecular biology! Excellent performance." : "We suggest you review the 3D and Cellular Types sections to strengthen your knowledge."; document.getElementById('quiz-badge-icon').className = `h-24 w-24 rounded-full flex items-center justify-center text-5xl mb-6 shadow-inner bg-${pct>=80?'emerald':'amber'}-50 text-${pct>=80?'emerald':'amber'}-500`; document.getElementById('quiz-badge-icon').innerHTML = ``; } function restartQuiz() { startQuiz(); } // --- DIVISION CELULAR DATA & FUNCTIONS --- let currentDivisionType = 'mitosis'; let currentDivisionPhaseIdx = 0; const divisionPhasesData = { mitosis: [ { name: "Profase", desc: "Chromatin condenses into chromosomes. The centrioles move to the poles and the mitotic spindle begins to form. The nuclear envelope begins to fragment.", molecular: "Mitotic cyclins activate Cdk1, promoting the phosphorylation of nuclear lamins and condensation by condensins of the nuclear complex." }, { name: "Metafase", desc: "Highly condensed chromosomes line up at the equatorial plate of the cell. Spindle fibers attach to the kinetochores of chromosomes.", molecular: "The spindle checkpoint (SAC) checks tension on kinetochores through the APC/C complex to prevent missegregation." }, { name: "Anafase", desc: "Sister chromatids separate synchronously and are dragged by spindle microtubules to opposite poles of the cell.", molecular: "Activation of APC/C destroys segurin, releasing the separase enzyme that cuts the cohesin ring that held the chromatids together." }, { name: "Telofase / Citocinesis", desc: "The chromosomes reach the poles and decondense. The nuclear envelope is rebuilt around each set of chromosomes. A contractile ring of actin and myosin divides the cytoplasm into two daughter cells.", molecular: "Dephosphorylation of nuclear proteins allows nuclear reassembly. The contractile ring strangles the cell cytoplasm by mechanical contraction." } ], meiosis: [ { name: "Profase I", desc: "Synapsis and crossing-over occur between homologous chromosomes, exchanging genetic material.", molecular: "The synaptonemal complex stabilizes chromosome pairing. Homologous recombination is mediated by the endonuclease Spo11." }, { name: "Metafase I", desc: "Pairs of homologous chromosomes (tetrads) line up at the equator. The orientation of the homologs is random (independent segregation).", molecular: "The random alignment of maternal and paternal chromosomes promotes exponential genetic variability in gametes." }, { name: "Anafase I / Telofase I", desc: "Complete homologous chromosomes separate to opposite poles (chromatids remain joined). Two haploid nuclei are formed.", molecular: "Centromeric cohesins are protected by the protein shugoshin, preventing premature separation of chromatids." }, { name: "Metafase II", desc: "In both haploid daughter cells, the individual chromosomes (with two chromatids) align on the equatorial plate.", molecular: "Meiotic spindle II attaches to individual kinetochores. There are half the original number of chromosomes." }, { name: "Anafase II / Telofase II", desc: "The sister chromatids eventually separate and travel to the poles. Cytokinesis generates a total of 4 genetically unique haploid daughter cells.", molecular: "The degradation of centromeric cohesin allows the final segregation of chromatids. It gives rise to male or female gametes." } ] }; function setDivisionType(type) { currentDivisionType = type; currentDivisionPhaseIdx = 0; const mitosisBtn = document.getElementById('btn-div-mitosis'); const meiosisBtn = document.getElementById('btn-div-meiosis'); if (type === 'mitosis') { mitosisBtn.className = "flex-1 py-2 rounded-xl text-xs font-bold text-center border transition-all bg-emerald-600 border-emerald-400 text-white shadow-sm"; meiosisBtn.className = "flex-1 py-2 rounded-xl text-xs font-bold text-center border transition-all bg-slate-100 border-slate-200 text-slate-600 hover:bg-slate-200"; } else { meiosisBtn.className = "flex-1 py-2 rounded-xl text-xs font-bold text-center border transition-all bg-emerald-600 border-emerald-400 text-white shadow-sm"; mitosisBtn.className = "flex-1 py-2 rounded-xl text-xs font-bold text-center border transition-all bg-slate-100 border-slate-200 text-slate-600 hover:bg-slate-200"; } renderDivisionPhasesList(); updateDivisionPhaseView(); } function renderDivisionPhasesList() { const list = document.getElementById('division-phases-list'); const phases = divisionPhasesData[currentDivisionType]; list.innerHTML = phases.map((phase, i) => ` `).join(''); } function setDivisionPhase(idx) { currentDivisionPhaseIdx = idx; renderDivisionPhasesList(); updateDivisionPhaseView(); } function updateDivisionPhaseView() { const phase = divisionPhasesData[currentDivisionType][currentDivisionPhaseIdx]; document.getElementById('division-phase-title').innerText = phase.name; document.getElementById('division-phase-desc').innerText = phase.desc; document.getElementById('division-phase-molecular').innerText = phase.molecular; const canvas = document.getElementById('divisionCanvas'); if (canvas) { const ctx = canvas.getContext('2d'); drawCellDivision(ctx, currentDivisionType, currentDivisionPhaseIdx); } } function drawCellDivision(ctx, type, phaseIndex) { const w = 360; const h = 260; ctx.clearRect(0, 0, w, h); const cx = w / 2; const cy = h / 2; if (type === 'mitosis') { if (phaseIndex === 0) { ctx.strokeStyle = '#94a3b8'; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(cx, cy, 80, 0, Math.PI*2); ctx.stroke(); ctx.strokeStyle = '#db2777'; ctx.lineWidth = 1.5; ctx.setLineDash([4, 4]); ctx.beginPath(); ctx.arc(cx, cy, 45, 0, Math.PI*2); ctx.stroke(); ctx.setLineDash([]); ctx.fillStyle = '#6366f1'; ctx.fillRect(cx - 50, cy - 40, 8, 8); ctx.fillRect(cx - 50, cy - 30, 8, 8); ctx.fillRect(cx + 40, cy - 40, 8, 8); ctx.fillRect(cx + 40, cy - 30, 8, 8); ctx.strokeStyle = 'rgba(99, 102, 241, 0.4)'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(cx - 46, cy - 35); ctx.lineTo(cx - 20, cy - 20); ctx.moveTo(cx + 44, cy - 35); ctx.lineTo(cx + 20, cy - 20); ctx.stroke(); ctx.lineWidth = 3; ctx.strokeStyle = '#ef4444'; ctx.beginPath(); ctx.moveTo(cx - 10, cy - 10); ctx.lineTo(cx + 10, cy + 10); ctx.moveTo(cx + 10, cy - 10); ctx.lineTo(cx - 10, cy + 10); ctx.stroke(); ctx.strokeStyle = '#3b82f6'; ctx.beginPath(); ctx.moveTo(cx - 15, cy + 10); ctx.lineTo(cx + 5, cy + 30); ctx.moveTo(cx + 5, cy + 10); ctx.lineTo(cx - 15, cy + 30); ctx.stroke(); } else if (phaseIndex === 1) { ctx.strokeStyle = '#94a3b8'; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(cx, cy, 80, 0, Math.PI*2); ctx.stroke(); ctx.fillStyle = '#6366f1'; ctx.fillRect(cx - 70, cy - 4, 8, 8); ctx.fillRect(cx + 62, cy - 4, 8, 8); ctx.lineWidth = 3; ctx.strokeStyle = '#ef4444'; ctx.beginPath(); ctx.moveTo(cx - 5, cy - 30); ctx.lineTo(cx + 5, cy - 10); ctx.moveTo(cx + 5, cy - 30); ctx.lineTo(cx - 5, cy - 10); ctx.stroke(); ctx.strokeStyle = '#3b82f6'; ctx.beginPath(); ctx.moveTo(cx - 5, cy + 10); ctx.lineTo(cx + 5, cy + 30); ctx.moveTo(cx + 5, cy + 10); ctx.lineTo(cx - 5, cy + 30); ctx.stroke(); ctx.strokeStyle = 'rgba(99, 102, 241, 0.5)'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(cx - 66, cy); ctx.lineTo(cx, cy - 20); ctx.moveTo(cx - 66, cy); ctx.lineTo(cx, cy + 20); ctx.moveTo(cx + 62, cy); ctx.lineTo(cx, cy - 20); ctx.moveTo(cx + 62, cy); ctx.lineTo(cx, cy + 20); ctx.stroke(); } else if (phaseIndex === 2) { ctx.strokeStyle = '#94a3b8'; ctx.lineWidth = 2; ctx.beginPath(); ctx.ellipse(cx, cy, 95, 75, 0, 0, Math.PI*2); ctx.stroke(); ctx.fillStyle = '#6366f1'; ctx.fillRect(cx - 85, cy - 4, 8, 8); ctx.fillRect(cx + 77, cy - 4, 8, 8); ctx.lineWidth = 2.5; ctx.strokeStyle = '#ef4444'; ctx.beginPath(); ctx.moveTo(cx - 30, cy - 20); ctx.lineTo(cx - 20, cy - 25); ctx.moveTo(cx - 30, cy - 20); ctx.lineTo(cx - 20, cy - 15); ctx.stroke(); ctx.strokeStyle = '#3b82f6'; ctx.beginPath(); ctx.moveTo(cx - 30, cy + 20); ctx.lineTo(cx - 20, cy + 15); ctx.moveTo(cx - 30, cy + 20); ctx.lineTo(cx - 20, cy + 25); ctx.stroke(); ctx.strokeStyle = '#ef4444'; ctx.beginPath(); ctx.moveTo(cx + 30, cy - 20); ctx.lineTo(cx + 20, cy - 25); ctx.moveTo(cx + 30, cy - 20); ctx.lineTo(cx + 20, cy - 15); ctx.stroke(); ctx.strokeStyle = '#3b82f6'; ctx.beginPath(); ctx.moveTo(cx + 30, cy + 20); ctx.lineTo(cx + 20, cy + 15); ctx.moveTo(cx + 30, cy + 20); ctx.lineTo(cx + 20, cy + 25); ctx.stroke(); ctx.strokeStyle = 'rgba(99, 102, 241, 0.4)'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(cx - 81, cy); ctx.lineTo(cx - 30, cy - 20); ctx.moveTo(cx - 81, cy); ctx.lineTo(cx - 30, cy + 20); ctx.moveTo(cx + 77, cy); ctx.lineTo(cx + 30, cy - 20); ctx.moveTo(cx + 77, cy); ctx.lineTo(cx + 30, cy + 20); ctx.stroke(); } else if (phaseIndex === 3) { ctx.strokeStyle = '#94a3b8'; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(cx - 45, cy, 50, -Math.PI/4, Math.PI/4, true); ctx.arc(cx + 45, cy, 50, Math.PI - Math.PI/4, Math.PI + Math.PI/4, true); ctx.closePath(); ctx.stroke(); ctx.strokeStyle = '#db2777'; ctx.lineWidth = 1; ctx.setLineDash([2, 2]); ctx.beginPath(); ctx.arc(cx - 45, cy, 25, 0, Math.PI*2); ctx.stroke(); ctx.beginPath(); ctx.arc(cx + 45, cy, 25, 0, Math.PI*2); ctx.stroke(); ctx.setLineDash([]); ctx.lineWidth = 2; ctx.strokeStyle = '#ef4444'; ctx.beginPath(); ctx.arc(cx - 50, cy - 8, 4, 0, Math.PI*2); ctx.stroke(); ctx.strokeStyle = '#3b82f6'; ctx.beginPath(); ctx.arc(cx - 42, cy + 8, 4, 0, Math.PI*2); ctx.stroke(); ctx.strokeStyle = '#ef4444'; ctx.beginPath(); ctx.arc(cx + 42, cy - 8, 4, 0, Math.PI*2); ctx.stroke(); ctx.strokeStyle = '#3b82f6'; ctx.beginPath(); ctx.arc(cx + 50, cy + 8, 4, 0, Math.PI*2); ctx.stroke(); } } else { if (phaseIndex === 0) { ctx.strokeStyle = '#94a3b8'; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(cx, cy, 80, 0, Math.PI*2); ctx.stroke(); ctx.strokeStyle = '#db2777'; ctx.lineWidth = 1.5; ctx.setLineDash([3, 3]); ctx.beginPath(); ctx.arc(cx, cy, 45, 0, Math.PI*2); ctx.stroke(); ctx.setLineDash([]); ctx.lineWidth = 3; ctx.strokeStyle = '#ef4444'; ctx.beginPath(); ctx.moveTo(cx - 12, cy - 10); ctx.lineTo(cx - 2, cy + 10); ctx.moveTo(cx - 2, cy - 10); ctx.lineTo(cx - 12, cy + 10); ctx.stroke(); ctx.strokeStyle = '#f43f5e'; ctx.beginPath(); ctx.moveTo(cx - 2, cy - 10); ctx.lineTo(cx + 8, cy + 10); ctx.moveTo(cx + 8, cy - 10); ctx.lineTo(cx - 2, cy + 10); ctx.stroke(); ctx.strokeStyle = '#3b82f6'; ctx.beginPath(); ctx.moveTo(cx - 10, cy + 15); ctx.lineTo(cx, cy + 35); ctx.moveTo(cx, cy + 15); ctx.lineTo(cx - 10, cy + 35); ctx.stroke(); ctx.strokeStyle = '#06b6d4'; ctx.beginPath(); ctx.moveTo(cx, cy + 15); ctx.lineTo(cx + 10, cy + 35); ctx.moveTo(cx + 10, cy + 15); ctx.lineTo(cx, cy + 35); ctx.stroke(); } else if (phaseIndex === 1) { ctx.strokeStyle = '#94a3b8'; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(cx, cy, 80, 0, Math.PI*2); ctx.stroke(); ctx.fillStyle = '#6366f1'; ctx.fillRect(cx - 70, cy - 4, 8, 8); ctx.fillRect(cx + 62, cy - 4, 8, 8); ctx.lineWidth = 3; ctx.strokeStyle = '#ef4444'; ctx.beginPath(); ctx.moveTo(cx - 10, cy - 25); ctx.lineTo(cx, cy - 10); ctx.moveTo(cx, cy - 25); ctx.lineTo(cx - 10, cy - 10); ctx.stroke(); ctx.strokeStyle = '#f43f5e'; ctx.beginPath(); ctx.moveTo(cx, cy - 25); ctx.lineTo(cx + 10, cy - 10); ctx.moveTo(cx + 10, cy - 25); ctx.lineTo(cx, cy - 10); ctx.stroke(); ctx.strokeStyle = '#3b82f6'; ctx.beginPath(); ctx.moveTo(cx - 10, cy + 10); ctx.lineTo(cx, cy + 25); ctx.moveTo(cx, cy + 10); ctx.lineTo(cx - 10, cy + 25); ctx.stroke(); ctx.strokeStyle = '#06b6d4'; ctx.beginPath(); ctx.moveTo(cx, cy + 10); ctx.lineTo(cx + 10, cy + 25); ctx.moveTo(cx + 10, cy + 10); ctx.lineTo(cx, cy + 25); ctx.stroke(); ctx.strokeStyle = 'rgba(99, 102, 241, 0.4)'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(cx - 66, cy); ctx.lineTo(cx - 5, cy - 18); ctx.moveTo(cx - 66, cy); ctx.lineTo(cx - 5, cy + 18); ctx.moveTo(cx + 62, cy); ctx.lineTo(cx + 5, cy - 18); ctx.moveTo(cx + 62, cy); ctx.lineTo(cx + 5, cy + 18); ctx.stroke(); } else if (phaseIndex === 2) { ctx.strokeStyle = '#94a3b8'; ctx.lineWidth = 2; ctx.beginPath(); ctx.ellipse(cx, cy, 95, 75, 0, 0, Math.PI*2); ctx.stroke(); ctx.lineWidth = 3; ctx.strokeStyle = '#ef4444'; ctx.beginPath(); ctx.moveTo(cx - 40, cy - 25); ctx.lineTo(cx - 30, cy - 10); ctx.moveTo(cx - 30, cy - 25); ctx.lineTo(cx - 40, cy - 10); ctx.stroke(); ctx.strokeStyle = '#3b82f6'; ctx.beginPath(); ctx.moveTo(cx - 40, cy + 10); ctx.lineTo(cx - 30, cy + 25); ctx.moveTo(cx - 30, cy + 10); ctx.lineTo(cx - 40, cy + 25); ctx.stroke(); ctx.strokeStyle = '#f43f5e'; ctx.beginPath(); ctx.moveTo(cx + 30, cy - 25); ctx.lineTo(cx + 40, cy - 10); ctx.moveTo(cx + 40, cy - 25); ctx.lineTo(cx + 30, cy - 10); ctx.stroke(); ctx.strokeStyle = '#06b6d4'; ctx.beginPath(); ctx.moveTo(cx + 30, cy + 10); ctx.lineTo(cx + 40, cy + 25); ctx.moveTo(cx + 40, cy + 10); ctx.lineTo(cx + 30, cy + 25); ctx.stroke(); } else if (phaseIndex === 3) { ctx.strokeStyle = '#94a3b8'; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(cx - 50, cy, 45, 0, Math.PI*2); ctx.stroke(); ctx.beginPath(); ctx.arc(cx + 50, cy, 45, 0, Math.PI*2); ctx.stroke(); ctx.lineWidth = 2.5; ctx.strokeStyle = '#ef4444'; ctx.beginPath(); ctx.moveTo(cx - 53, cy - 15); ctx.lineTo(cx - 47, cy - 3); ctx.moveTo(cx - 47, cy - 15); ctx.lineTo(cx - 53, cy - 3); ctx.stroke(); ctx.strokeStyle = '#3b82f6'; ctx.beginPath(); ctx.moveTo(cx - 53, cy + 3); ctx.lineTo(cx - 47, cy + 15); ctx.moveTo(cx - 47, cy + 3); ctx.lineTo(cx - 53, cy + 15); ctx.stroke(); ctx.strokeStyle = '#f43f5e'; ctx.beginPath(); ctx.moveTo(cx + 47, cy - 15); ctx.lineTo(cx + 53, cy - 3); ctx.moveTo(cx + 53, cy - 15); ctx.lineTo(cx + 47, cy - 3); ctx.stroke(); ctx.strokeStyle = '#06b6d4'; ctx.beginPath(); ctx.moveTo(cx + 47, cy + 3); ctx.lineTo(cx + 53, cy + 15); ctx.moveTo(cx + 53, cy + 3); ctx.lineTo(cx + 47, cy + 15); ctx.stroke(); } else if (phaseIndex === 4) { ctx.strokeStyle = '#94a3b8'; ctx.lineWidth = 1.5; ctx.beginPath(); ctx.arc(cx - 80, cy - 45, 30, 0, Math.PI*2); ctx.stroke(); ctx.beginPath(); ctx.arc(cx - 80, cy + 45, 30, 0, Math.PI*2); ctx.stroke(); ctx.beginPath(); ctx.arc(cx + 80, cy - 45, 30, 0, Math.PI*2); ctx.stroke(); ctx.beginPath(); ctx.arc(cx + 80, cy + 45, 30, 0, Math.PI*2); ctx.stroke(); ctx.lineWidth = 2; ctx.strokeStyle = '#ef4444'; ctx.beginPath(); ctx.moveTo(cx - 83, cy - 53); ctx.lineTo(cx - 77, cy - 48); ctx.stroke(); ctx.strokeStyle = '#3b82f6'; ctx.beginPath(); ctx.moveTo(cx - 83, cy - 42); ctx.lineTo(cx - 77, cy - 37); ctx.stroke(); ctx.strokeStyle = '#ef4444'; ctx.beginPath(); ctx.moveTo(cx - 83, cy + 37); ctx.lineTo(cx - 77, cy + 42); ctx.stroke(); ctx.strokeStyle = '#3b82f6'; ctx.beginPath(); ctx.moveTo(cx - 83, cy + 48); ctx.lineTo(cx - 77, cy + 53); ctx.stroke(); ctx.strokeStyle = '#f43f5e'; ctx.beginPath(); ctx.moveTo(cx + 77, cy - 53); ctx.lineTo(cx + 83, cy - 48); ctx.stroke(); ctx.strokeStyle = '#06b6d4'; ctx.beginPath(); ctx.moveTo(cx + 77, cy - 42); ctx.lineTo(cx + 83, cy - 37); ctx.stroke(); ctx.strokeStyle = '#f43f5e'; ctx.beginPath(); ctx.moveTo(cx + 77, cy + 37); ctx.lineTo(cx + 83, cy + 42); ctx.stroke(); ctx.strokeStyle = '#06b6d4'; ctx.beginPath(); ctx.moveTo(cx + 77, cy + 48); ctx.lineTo(cx + 83, cy + 53); ctx.stroke(); } } }