| import React, { useState, useEffect, useRef } from "react"; | |
| import * as THREE from "three"; | |
| import { Button } from "@/components/ui/button"; | |
| import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; | |
| import { Input } from "@/components/ui/input"; | |
| import { Badge } from "@/components/ui/badge"; | |
| import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; | |
| import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; | |
| import { | |
| Atom, | |
| RotateCcw, | |
| Search, | |
| Play, | |
| Pause, | |
| Info, | |
| Filter | |
| } from "lucide-react"; | |
| // Comprehensive molecules database organized by category | |
| const MOLECULES = [ | |
| // Simple Molecules | |
| { | |
| name: "Hydrogen Gas (H₂)", | |
| formula: "H2", | |
| category: "simple", | |
| atoms: [ | |
| { element: "H", position: [-0.37, 0, 0], color: "#ffffff" }, | |
| { element: "H", position: [0.37, 0, 0], color: "#ffffff" } | |
| ], | |
| bonds: [{ from: 0, to: 1 }], | |
| description: "The simplest and most abundant element in the universe, existing as diatomic molecules under normal conditions." | |
| }, | |
| { | |
| name: "Oxygen Gas (O₂)", | |
| formula: "O2", | |
| category: "simple", | |
| atoms: [ | |
| { element: "O", position: [-0.60, 0, 0], color: "#ff0000" }, | |
| { element: "O", position: [0.60, 0, 0], color: "#ff0000" } | |
| ], | |
| bonds: [{ from: 0, to: 1 }], | |
| description: "Essential for respiration and combustion, making up about 21% of Earth's atmosphere." | |
| }, | |
| { | |
| name: "Nitrogen Gas (N₂)", | |
| formula: "N2", | |
| category: "simple", | |
| atoms: [ | |
| { element: "N", position: [-0.55, 0, 0], color: "#0000ff" }, | |
| { element: "N", position: [0.55, 0, 0], color: "#0000ff" } | |
| ], | |
| bonds: [{ from: 0, to: 1 }], | |
| description: "Makes up about 78% of Earth's atmosphere. The triple bond makes it very stable." | |
| }, | |
| { | |
| name: "Carbon Dioxide (CO₂)", | |
| formula: "CO2", | |
| category: "simple", | |
| atoms: [ | |
| { element: "C", position: [0, 0, 0], color: "#404040" }, | |
| { element: "O", position: [-1.16, 0, 0], color: "#ff0000" }, | |
| { element: "O", position: [1.16, 0, 0], color: "#ff0000" } | |
| ], | |
| bonds: [{ from: 0, to: 1 }, { from: 0, to: 2 }], | |
| description: "A greenhouse gas produced by combustion and respiration, absorbed by plants during photosynthesis." | |
| }, | |
| { | |
| name: "Water (H₂O)", | |
| formula: "H2O", | |
| category: "simple", | |
| atoms: [ | |
| { element: "O", position: [0, 0, 0], color: "#ff0000" }, | |
| { element: "H", position: [0.96, 0, 0], color: "#ffffff" }, | |
| { element: "H", position: [-0.24, 0.93, 0], color: "#ffffff" } | |
| ], | |
| bonds: [{ from: 0, to: 1 }, { from: 0, to: 2 }], | |
| description: "The most essential compound for life, consisting of two hydrogen atoms bonded to one oxygen atom." | |
| }, | |
| { | |
| name: "Ammonia (NH₃)", | |
| formula: "NH3", | |
| category: "simple", | |
| atoms: [ | |
| { element: "N", position: [0, 0, 0], color: "#0000ff" }, | |
| { element: "H", position: [0.94, 0, 0.33], color: "#ffffff" }, | |
| { element: "H", position: [-0.47, 0.82, 0.33], color: "#ffffff" }, | |
| { element: "H", position: [-0.47, -0.82, 0.33], color: "#ffffff" } | |
| ], | |
| bonds: [{ from: 0, to: 1 }, { from: 0, to: 2 }, { from: 0, to: 3 }], | |
| description: "A basic compound used in fertilizers and cleaning products, with a trigonal pyramidal shape." | |
| }, | |
| // Hydrocarbons - Alkanes | |
| { | |
| name: "Methane (CH₄)", | |
| formula: "CH4", | |
| category: "hydrocarbons", | |
| atoms: [ | |
| { element: "C", position: [0, 0, 0], color: "#404040" }, | |
| { element: "H", position: [0.63, 0.63, 0.63], color: "#ffffff" }, | |
| { element: "H", position: [-0.63, -0.63, 0.63], color: "#ffffff" }, | |
| { element: "H", position: [-0.63, 0.63, -0.63], color: "#ffffff" }, | |
| { element: "H", position: [0.63, -0.63, -0.63], color: "#ffffff" } | |
| ], | |
| bonds: [{ from: 0, to: 1 }, { from: 0, to: 2 }, { from: 0, to: 3 }, { from: 0, to: 4 }], | |
| description: "The simplest hydrocarbon and main component of natural gas, featuring a tetrahedral geometry." | |
| }, | |
| { | |
| name: "Ethane (C₂H₆)", | |
| formula: "C2H6", | |
| category: "hydrocarbons", | |
| atoms: [ | |
| { element: "C", position: [-0.77, 0, 0], color: "#404040" }, | |
| { element: "C", position: [0.77, 0, 0], color: "#404040" }, | |
| { element: "H", position: [-1.17, 0.89, 0.51], color: "#ffffff" }, | |
| { element: "H", position: [-1.17, -0.89, 0.51], color: "#ffffff" }, | |
| { element: "H", position: [-1.17, 0, -1.03], color: "#ffffff" }, | |
| { element: "H", position: [1.17, 0.89, 0.51], color: "#ffffff" }, | |
| { element: "H", position: [1.17, -0.89, 0.51], color: "#ffffff" }, | |
| { element: "H", position: [1.17, 0, -1.03], color: "#ffffff" } | |
| ], | |
| bonds: [ | |
| { from: 0, to: 1 }, { from: 0, to: 2 }, { from: 0, to: 3 }, { from: 0, to: 4 }, | |
| { from: 1, to: 5 }, { from: 1, to: 6 }, { from: 1, to: 7 } | |
| ], | |
| description: "A simple alkane used as fuel, with free rotation around the C-C single bond." | |
| }, | |
| { | |
| name: "Propane (C₃H₈)", | |
| formula: "C3H8", | |
| category: "hydrocarbons", | |
| atoms: [ | |
| { element: "C", position: [-1.27, 0, 0], color: "#404040" }, | |
| { element: "C", position: [0, 0, 0], color: "#404040" }, | |
| { element: "C", position: [1.27, 0, 0], color: "#404040" }, | |
| { element: "H", position: [-1.67, 0.89, 0.51], color: "#ffffff" }, | |
| { element: "H", position: [-1.67, -0.89, 0.51], color: "#ffffff" }, | |
| { element: "H", position: [-1.67, 0, -1.03], color: "#ffffff" }, | |
| { element: "H", position: [0, 0.89, 0.51], color: "#ffffff" }, | |
| { element: "H", position: [0, -0.89, 0.51], color: "#ffffff" }, | |
| { element: "H", position: [1.67, 0.89, 0.51], color: "#ffffff" }, | |
| { element: "H", position: [1.67, -0.89, 0.51], color: "#ffffff" }, | |
| { element: "H", position: [1.67, 0, -1.03], color: "#ffffff" } | |
| ], | |
| bonds: [ | |
| { from: 0, to: 1 }, { from: 1, to: 2 }, | |
| { from: 0, to: 3 }, { from: 0, to: 4 }, { from: 0, to: 5 }, | |
| { from: 1, to: 6 }, { from: 1, to: 7 }, | |
| { from: 2, to: 8 }, { from: 2, to: 9 }, { from: 2, to: 10 } | |
| ], | |
| description: "A common fuel gas used for heating, grilling, and as vehicle fuel (LPG)." | |
| }, | |
| // Alkenes | |
| { | |
| name: "Ethene (C₂H₄)", | |
| formula: "C2H4", | |
| category: "hydrocarbons", | |
| atoms: [ | |
| { element: "C", position: [-0.67, 0, 0], color: "#404040" }, | |
| { element: "C", position: [0.67, 0, 0], color: "#404040" }, | |
| { element: "H", position: [-1.23, 0.93, 0], color: "#ffffff" }, | |
| { element: "H", position: [-1.23, -0.93, 0], color: "#ffffff" }, | |
| { element: "H", position: [1.23, 0.93, 0], color: "#ffffff" }, | |
| { element: "H", position: [1.23, -0.93, 0], color: "#ffffff" } | |
| ], | |
| bonds: [ | |
| { from: 0, to: 1 }, { from: 0, to: 2 }, { from: 0, to: 3 }, | |
| { from: 1, to: 4 }, { from: 1, to: 5 } | |
| ], | |
| description: "The simplest alkene with a C=C double bond, used to make polyethylene plastic." | |
| }, | |
| // Aromatic Compounds | |
| { | |
| name: "Benzene (C₆H₆)", | |
| formula: "C6H6", | |
| category: "aromatics", | |
| atoms: [ | |
| { element: "C", position: [1.40, 0, 0], color: "#404040" }, | |
| { element: "C", position: [0.70, 1.21, 0], color: "#404040" }, | |
| { element: "C", position: [-0.70, 1.21, 0], color: "#404040" }, | |
| { element: "C", position: [-1.40, 0, 0], color: "#404040" }, | |
| { element: "C", position: [-0.70, -1.21, 0], color: "#404040" }, | |
| { element: "C", position: [0.70, -1.21, 0], color: "#404040" }, | |
| { element: "H", position: [2.48, 0, 0], color: "#ffffff" }, | |
| { element: "H", position: [1.24, 2.15, 0], color: "#ffffff" }, | |
| { element: "H", position: [-1.24, 2.15, 0], color: "#ffffff" }, | |
| { element: "H", position: [-2.48, 0, 0], color: "#ffffff" }, | |
| { element: "H", position: [-1.24, -2.15, 0], color: "#ffffff" }, | |
| { element: "H", position: [1.24, -2.15, 0], color: "#ffffff" } | |
| ], | |
| bonds: [ | |
| { from: 0, to: 1 }, { from: 1, to: 2 }, { from: 2, to: 3 }, | |
| { from: 3, to: 4 }, { from: 4, to: 5 }, { from: 5, to: 0 }, | |
| { from: 0, to: 6 }, { from: 1, to: 7 }, { from: 2, to: 8 }, | |
| { from: 3, to: 9 }, { from: 4, to: 10 }, { from: 5, to: 11 } | |
| ], | |
| description: "An aromatic hydrocarbon with a ring of six carbon atoms, fundamental in organic chemistry." | |
| }, | |
| { | |
| name: "Toluene (C₇H₈)", | |
| formula: "C7H8", | |
| category: "aromatics", | |
| atoms: [ | |
| { element: "C", position: [1.40, 0, 0], color: "#404040" }, | |
| { element: "C", position: [0.70, 1.21, 0], color: "#404040" }, | |
| { element: "C", position: [-0.70, 1.21, 0], color: "#404040" }, | |
| { element: "C", position: [-1.40, 0, 0], color: "#404040" }, | |
| { element: "C", position: [-0.70, -1.21, 0], color: "#404040" }, | |
| { element: "C", position: [0.70, -1.21, 0], color: "#404040" }, | |
| { element: "C", position: [-2.87, 0, 0], color: "#404040" }, // methyl group | |
| { element: "H", position: [2.48, 0, 0], color: "#ffffff" }, | |
| { element: "H", position: [1.24, 2.15, 0], color: "#ffffff" }, | |
| { element: "H", position: [-1.24, 2.15, 0], color: "#ffffff" }, | |
| { element: "H", position: [-1.24, -2.15, 0], color: "#ffffff" }, | |
| { element: "H", position: [1.24, -2.15, 0], color: "#ffffff" }, | |
| { element: "H", position: [-3.27, 0.89, 0.51], color: "#ffffff" }, | |
| { element: "H", position: [-3.27, -0.89, 0.51], color: "#ffffff" }, | |
| { element: "H", position: [-3.27, 0, -1.03], color: "#ffffff" } | |
| ], | |
| bonds: [ | |
| { from: 0, to: 1 }, { from: 1, to: 2 }, { from: 2, to: 3 }, | |
| { from: 3, to: 4 }, { from: 4, to: 5 }, { from: 5, to: 0 }, | |
| { from: 3, to: 6 }, { from: 0, to: 7 }, { from: 1, to: 8 }, | |
| { from: 2, to: 9 }, { from: 4, to: 10 }, { from: 5, to: 11 }, | |
| { from: 6, to: 12 }, { from: 6, to: 13 }, { from: 6, to: 14 } | |
| ], | |
| description: "Methylbenzene, used as a solvent and in the production of other chemicals." | |
| }, | |
| // Alcohols | |
| { | |
| name: "Methanol (CH₄O)", | |
| formula: "CH4O", | |
| category: "alcohols", | |
| atoms: [ | |
| { element: "C", position: [0, 0, 0], color: "#404040" }, | |
| { element: "O", position: [1.43, 0, 0], color: "#ff0000" }, | |
| { element: "H", position: [-0.51, 0.89, 0.51], color: "#ffffff" }, | |
| { element: "H", position: [-0.51, -0.89, 0.51], color: "#ffffff" }, | |
| { element: "H", position: [-0.51, 0, -1.03], color: "#ffffff" }, | |
| { element: "H", position: [1.83, 0, 0.96], color: "#ffffff" } | |
| ], | |
| bonds: [ | |
| { from: 0, to: 1 }, { from: 0, to: 2 }, { from: 0, to: 3 }, | |
| { from: 0, to: 4 }, { from: 1, to: 5 } | |
| ], | |
| description: "The simplest alcohol, used as fuel additive and solvent. Highly toxic to humans." | |
| }, | |
| { | |
| name: "Ethanol (C₂H₆O)", | |
| formula: "C2H6O", | |
| category: "alcohols", | |
| atoms: [ | |
| { element: "C", position: [-0.77, 0.36, 0], color: "#404040" }, | |
| { element: "C", position: [0.77, 0.36, 0], color: "#404040" }, | |
| { element: "O", position: [1.2, -0.8, 0], color: "#ff0000" }, | |
| { element: "H", position: [-1.1, 0.7, 1], color: "#ffffff" }, | |
| { element: "H", position: [-1.1, 0.7, -1], color: "#ffffff" }, | |
| { element: "H", position: [-1.2, -0.6, 0], color: "#ffffff" }, | |
| { element: "H", position: [1.1, 1.3, 0], color: "#ffffff" }, | |
| { element: "H", position: [1.1, -0.2, 1], color: "#ffffff" }, | |
| { element: "H", position: [2.1, -0.6, 0], color: "#ffffff" } | |
| ], | |
| bonds: [ | |
| { from: 0, to: 1 }, { from: 1, to: 2 }, | |
| { from: 0, to: 3 }, { from: 0, to: 4 }, { from: 0, to: 5 }, | |
| { from: 1, to: 6 }, { from: 1, to: 7 }, | |
| { from: 2, to: 8 } | |
| ], | |
| description: "The alcohol found in beverages, also used as fuel and solvent." | |
| }, | |
| // Acids | |
| { | |
| name: "Formic Acid (HCOOH)", | |
| formula: "HCOOH", | |
| category: "acids", | |
| atoms: [ | |
| { element: "C", position: [0, 0, 0], color: "#404040" }, | |
| { element: "O", position: [1.23, -0.67, 0], color: "#ff0000" }, | |
| { element: "O", position: [0.31, 1.34, 0], color: "#ff0000" }, | |
| { element: "H", position: [-1.08, 0.11, 0], color: "#ffffff" }, | |
| { element: "H", position: [1.19, 1.14, 0], color: "#ffffff" } | |
| ], | |
| bonds: [ | |
| { from: 0, to: 1 }, { from: 0, to: 2 }, { from: 0, to: 3 }, { from: 2, to: 4 } | |
| ], | |
| description: "The simplest carboxylic acid, found in ant stings and used as a preservative." | |
| }, | |
| { | |
| name: "Acetic Acid (CH₃COOH)", | |
| formula: "CH3COOH", | |
| category: "acids", | |
| atoms: [ | |
| { element: "C", position: [-1.27, 0, 0], color: "#404040" }, | |
| { element: "C", position: [0, 0, 0], color: "#404040" }, | |
| { element: "O", position: [1.23, -0.67, 0], color: "#ff0000" }, | |
| { element: "O", position: [0.31, 1.34, 0], color: "#ff0000" }, | |
| { element: "H", position: [-1.67, 0.89, 0.51], color: "#ffffff" }, | |
| { element: "H", position: [-1.67, -0.89, 0.51], color: "#ffffff" }, | |
| { element: "H", position: [-1.67, 0, -1.03], color: "#ffffff" }, | |
| { element: "H", position: [1.19, 1.14, 0], color: "#ffffff" } | |
| ], | |
| bonds: [ | |
| { from: 0, to: 1 }, { from: 1, to: 2 }, { from: 1, to: 3 }, | |
| { from: 0, to: 4 }, { from: 0, to: 5 }, { from: 0, to: 6 }, { from: 3, to: 7 } | |
| ], | |
| description: "Found in vinegar, used as food preservative and in chemical synthesis." | |
| }, | |
| // Biomolecules - Amino Acids | |
| { | |
| name: "Glycine (NH₂CH₂COOH)", | |
| formula: "C2H5NO2", | |
| category: "biomolecules", | |
| atoms: [ | |
| { element: "N", position: [-2.14, 0, 0], color: "#0000ff" }, | |
| { element: "C", position: [-0.67, 0, 0], color: "#404040" }, | |
| { element: "C", position: [0.67, 0, 0], color: "#404040" }, | |
| { element: "O", position: [1.23, -1.15, 0], color: "#ff0000" }, | |
| { element: "O", position: [1.23, 1.15, 0], color: "#ff0000" }, | |
| { element: "H", position: [-2.54, 0.81, 0.41], color: "#ffffff" }, | |
| { element: "H", position: [-2.54, -0.81, 0.41], color: "#ffffff" }, | |
| { element: "H", position: [-0.67, 0.89, 0.51], color: "#ffffff" }, | |
| { element: "H", position: [-0.67, -0.89, 0.51], color: "#ffffff" }, | |
| { element: "H", position: [2.17, 1.08, 0], color: "#ffffff" } | |
| ], | |
| bonds: [ | |
| { from: 0, to: 1 }, { from: 1, to: 2 }, { from: 2, to: 3 }, { from: 2, to: 4 }, | |
| { from: 0, to: 5 }, { from: 0, to: 6 }, { from: 1, to: 7 }, { from: 1, to: 8 }, { from: 4, to: 9 } | |
| ], | |
| description: "The simplest amino acid, essential for protein synthesis and neurotransmitter function." | |
| }, | |
| // Sugars | |
| { | |
| name: "Glucose (C₆H₁₂O₆)", | |
| formula: "C6H12O6", | |
| category: "biomolecules", | |
| atoms: [ | |
| { element: "C", position: [0, 0, 0], color: "#404040" }, | |
| { element: "C", position: [1.25, 0.72, 0], color: "#404040" }, | |
| { element: "C", position: [1.25, 2.17, 0], color: "#404040" }, | |
| { element: "C", position: [0, 2.89, 0], color: "#404040" }, | |
| { element: "C", position: [-1.25, 2.17, 0], color: "#404040" }, | |
| { element: "O", position: [-1.25, 0.72, 0], color: "#ff0000" }, | |
| { element: "O", position: [2.5, 0, 0], color: "#ff0000" }, | |
| { element: "O", position: [2.5, 2.89, 0], color: "#ff0000" }, | |
| { element: "O", position: [0, 4.34, 0], color: "#ff0000" }, | |
| { element: "O", position: [-2.5, 2.89, 0], color: "#ff0000" }, | |
| { element: "C", position: [-2.5, 4.34, 0], color: "#404040" }, | |
| { element: "O", position: [-3.75, 5.06, 0], color: "#ff0000" } | |
| ], | |
| bonds: [ | |
| { from: 0, to: 1 }, { from: 1, to: 2 }, { from: 2, to: 3 }, { from: 3, to: 4 }, | |
| { from: 4, to: 5 }, { from: 5, to: 0 }, { from: 1, to: 6 }, { from: 2, to: 7 }, | |
| { from: 3, to: 8 }, { from: 4, to: 9 }, { from: 9, to: 10 }, { from: 10, to: 11 } | |
| ], | |
| description: "The primary source of energy for cells, essential for metabolism in all living organisms." | |
| }, | |
| // Inorganic Compounds | |
| { | |
| name: "Hydrogen Chloride (HCl)", | |
| formula: "HCl", | |
| category: "inorganic", | |
| atoms: [ | |
| { element: "H", position: [-0.63, 0, 0], color: "#ffffff" }, | |
| { element: "Cl", position: [0.63, 0, 0], color: "#00ff00" } | |
| ], | |
| bonds: [{ from: 0, to: 1 }], | |
| description: "A strong acid when dissolved in water (hydrochloric acid), found in stomach acid." | |
| }, | |
| { | |
| name: "Sodium Chloride (NaCl)", | |
| formula: "NaCl", | |
| category: "inorganic", | |
| atoms: [ | |
| { element: "Na", position: [-1.4, 0, 0], color: "#ab5cf2" }, | |
| { element: "Cl", position: [1.4, 0, 0], color: "#00ff00" } | |
| ], | |
| bonds: [{ from: 0, to: 1 }], | |
| description: "Common table salt, essential for life and widely used in food preservation." | |
| }, | |
| { | |
| name: "Sulfuric Acid (H₂SO₄)", | |
| formula: "H2SO4", | |
| category: "inorganic", | |
| atoms: [ | |
| { element: "S", position: [0, 0, 0], color: "#ffff00" }, | |
| { element: "O", position: [0, 1.5, 0], color: "#ff0000" }, | |
| { element: "O", position: [1.3, -0.75, 0], color: "#ff0000" }, | |
| { element: "O", position: [-1.3, -0.75, 0], color: "#ff0000" }, | |
| { element: "O", position: [0, 0, 1.5], color: "#ff0000" }, | |
| { element: "H", position: [0, 2.4, 0], color: "#ffffff" }, | |
| { element: "H", position: [0, 0, 2.4], color: "#ffffff" } | |
| ], | |
| bonds: [ | |
| { from: 0, to: 1 }, { from: 0, to: 2 }, { from: 0, to: 3 }, { from: 0, to: 4 }, | |
| { from: 1, to: 5 }, { from: 4, to: 6 } | |
| ], | |
| description: "One of the most important industrial chemicals, used in batteries and chemical synthesis." | |
| }, | |
| // Pharmaceuticals | |
| { | |
| name: "Aspirin (C₉H₈O₄)", | |
| formula: "C9H8O4", | |
| category: "pharmaceuticals", | |
| atoms: [ | |
| { element: "C", position: [0, 0, 0], color: "#404040" }, | |
| { element: "C", position: [1.4, 0, 0.8], color: "#404040" }, | |
| { element: "C", position: [1.4, 0, 2.2], color: "#404040" }, | |
| { element: "C", position: [0, 0, 3], color: "#404040" }, | |
| { element: "C", position: [-1.4, 0, 2.2], color: "#404040" }, | |
| { element: "C", position: [-1.4, 0, 0.8], color: "#404040" }, | |
| { element: "C", position: [0, 1.5, 4.5], color: "#404040" }, | |
| { element: "C", position: [-1.5, 1.5, 6], color: "#404040" }, | |
| { element: "O", position: [0, 0, 4.5], color: "#ff0000" }, | |
| { element: "O", position: [1.5, 1.5, 4.5], color: "#ff0000" }, | |
| { element: "O", position: [-3, 1.5, 6], color: "#ff0000" }, | |
| { element: "O", position: [-1.5, 3, 6], color: "#ff0000" } | |
| ], | |
| bonds: [ | |
| { from: 0, to: 1 }, { from: 1, to: 2 }, { from: 2, to: 3 }, { from: 3, to: 4 }, | |
| { from: 4, to: 5 }, { from: 5, to: 0 }, { from: 3, to: 8 }, { from: 8, to: 6 }, | |
| { from: 6, to: 9 }, { from: 6, to: 7 }, { from: 7, to: 10 }, { from: 7, to: 11 } | |
| ], | |
| description: "A widely used pain reliever and anti-inflammatory drug, first synthesized in 1897." | |
| }, | |
| { | |
| name: "Caffeine (C₈H₁₀N₄O₂)", | |
| formula: "C8H10N4O2", | |
| category: "pharmaceuticals", | |
| atoms: [ | |
| { element: "N", position: [0, 0, 0], color: "#0000ff" }, | |
| { element: "C", position: [1.4, 0, 0], color: "#404040" }, | |
| { element: "N", position: [2.1, 1.2, 0], color: "#0000ff" }, | |
| { element: "C", position: [1.4, 2.4, 0], color: "#404040" }, | |
| { element: "C", position: [0, 2.4, 0], color: "#404040" }, | |
| { element: "N", position: [-0.7, 1.2, 0], color: "#0000ff" }, | |
| { element: "C", position: [0, 3.8, 0], color: "#404040" }, | |
| { element: "N", position: [1.4, 3.8, 0], color: "#0000ff" }, | |
| { element: "O", position: [2.1, -1.2, 0], color: "#ff0000" }, | |
| { element: "O", position: [-0.7, 4.6, 0], color: "#ff0000" }, | |
| { element: "C", position: [-2.1, 1.2, 0], color: "#404040" }, | |
| { element: "C", position: [2.8, 4.6, 0], color: "#404040" }, | |
| { element: "C", position: [-0.7, -1.2, 0], color: "#404040" } | |
| ], | |
| bonds: [ | |
| { from: 0, to: 1 }, { from: 1, to: 2 }, { from: 2, to: 3 }, { from: 3, to: 4 }, | |
| { from: 4, to: 5 }, { from: 5, to: 0 }, { from: 4, to: 6 }, { from: 6, to: 7 }, | |
| { from: 7, to: 3 }, { from: 1, to: 8 }, { from: 6, to: 9 }, { from: 5, to: 10 }, | |
| { from: 7, to: 11 }, { from: 0, to: 12 } | |
| ], | |
| description: "A natural stimulant found in coffee, tea, and chocolate, widely consumed worldwide." | |
| }, | |
| // Environmental/Atmospheric | |
| { | |
| name: "Ozone (O₃)", | |
| formula: "O3", | |
| category: "environmental", | |
| atoms: [ | |
| { element: "O", position: [0, 0, 0], color: "#ff0000" }, | |
| { element: "O", position: [1.3, 0, 0], color: "#ff0000" }, | |
| { element: "O", position: [0.65, 1.13, 0], color: "#ff0000" } | |
| ], | |
| bonds: [{ from: 0, to: 1 }, { from: 1, to: 2 }], | |
| description: "An atmospheric molecule that protects Earth from UV radiation but is toxic at ground level." | |
| }, | |
| { | |
| name: "Methyl Chloride (CH₃Cl)", | |
| formula: "CH3Cl", | |
| category: "environmental", | |
| atoms: [ | |
| { element: "C", position: [0, 0, 0], color: "#404040" }, | |
| { element: "Cl", position: [1.78, 0, 0], color: "#00ff00" }, | |
| { element: "H", position: [-0.51, 0.89, 0.51], color: "#ffffff" }, | |
| { element: "H", position: [-0.51, -0.89, 0.51], color: "#ffffff" }, | |
| { element: "H", position: [-0.51, 0, -1.03], color: "#ffffff" } | |
| ], | |
| bonds: [ | |
| { from: 0, to: 1 }, { from: 0, to: 2 }, { from: 0, to: 3 }, { from: 0, to: 4 } | |
| ], | |
| description: "A chlorinated compound that contributes to ozone depletion in the stratosphere." | |
| }, | |
| // Additional Important Molecules | |
| { | |
| name: "Hydrogen Peroxide (H₂O₂)", | |
| formula: "H2O2", | |
| category: "simple", | |
| atoms: [ | |
| { element: "O", position: [-0.74, 0, 0], color: "#ff0000" }, | |
| { element: "O", position: [0.74, 0, 0], color: "#ff0000" }, | |
| { element: "H", position: [-1.21, 0.93, 0], color: "#ffffff" }, | |
| { element: "H", position: [1.21, -0.93, 0], color: "#ffffff" } | |
| ], | |
| bonds: [{ from: 0, to: 1 }, { from: 0, to: 2 }, { from: 1, to: 3 }], | |
| description: "A strong oxidizing agent used as disinfectant and bleaching agent." | |
| }, | |
| { | |
| name: "Nitrous Oxide (N₂O)", | |
| formula: "N2O", | |
| category: "simple", | |
| atoms: [ | |
| { element: "N", position: [-1.13, 0, 0], color: "#0000ff" }, | |
| { element: "N", position: [0, 0, 0], color: "#0000ff" }, | |
| { element: "O", position: [1.19, 0, 0], color: "#ff0000" } | |
| ], | |
| bonds: [{ from: 0, to: 1 }, { from: 1, to: 2 }], | |
| description: "Laughing gas, used as anesthetic and greenhouse gas contributing to climate change." | |
| } | |
| ]; | |
| const CATEGORIES = [ | |
| { value: "all", label: "All Categories", count: MOLECULES.length }, | |
| { value: "simple", label: "Simple Molecules", count: MOLECULES.filter(m => m.category === "simple").length }, | |
| { value: "hydrocarbons", label: "Hydrocarbons", count: MOLECULES.filter(m => m.category === "hydrocarbons").length }, | |
| { value: "aromatics", label: "Aromatics", count: MOLECULES.filter(m => m.category === "aromatics").length }, | |
| { value: "alcohols", label: "Alcohols", count: MOLECULES.filter(m => m.category === "alcohols").length }, | |
| { value: "acids", label: "Acids", count: MOLECULES.filter(m => m.category === "acids").length }, | |
| { value: "biomolecules", label: "Biomolecules", count: MOLECULES.filter(m => m.category === "biomolecules").length }, | |
| { value: "inorganic", label: "Inorganic", count: MOLECULES.filter(m => m.category === "inorganic").length }, | |
| { value: "pharmaceuticals", label: "Pharmaceuticals", count: MOLECULES.filter(m => m.category === "pharmaceuticals").length }, | |
| { value: "environmental", label: "Environmental", count: MOLECULES.filter(m => m.category === "environmental").length } | |
| ]; | |
| const ThreeCanvas = ({ molecule, isAnimating }) => { | |
| const mountRef = useRef(null); | |
| useEffect(() => { | |
| if (!molecule || !mountRef.current) return; | |
| const currentMount = mountRef.current; | |
| // Scene, Camera, Renderer | |
| const scene = new THREE.Scene(); | |
| scene.background = new THREE.Color(0x1e293b); // slate-800 | |
| const camera = new THREE.PerspectiveCamera(75, currentMount.clientWidth / currentMount.clientHeight, 0.1, 1000); | |
| camera.position.z = 5; | |
| const renderer = new THREE.WebGLRenderer({ antialias: true }); | |
| renderer.setSize(currentMount.clientWidth, currentMount.clientHeight); | |
| currentMount.appendChild(renderer.domElement); | |
| // Lights | |
| const ambientLight = new THREE.AmbientLight(0xffffff, 0.7); | |
| scene.add(ambientLight); | |
| const pointLight = new THREE.PointLight(0xffffff, 0.5); | |
| pointLight.position.set(5, 5, 5); | |
| scene.add(pointLight); | |
| // Molecule Group | |
| const moleculeGroup = new THREE.Group(); | |
| scene.add(moleculeGroup); | |
| // Create Atoms | |
| molecule.atoms.forEach(atom => { | |
| const geometry = new THREE.SphereGeometry(atom.element === 'H' ? 0.2 : 0.4, 32, 32); | |
| const material = new THREE.MeshStandardMaterial({ color: atom.color }); | |
| const sphere = new THREE.Mesh(geometry, material); | |
| sphere.position.set(...atom.position); | |
| moleculeGroup.add(sphere); | |
| }); | |
| // Create Bonds | |
| molecule.bonds?.forEach(bond => { | |
| const start = new THREE.Vector3(...molecule.atoms[bond.from].position); | |
| const end = new THREE.Vector3(...molecule.atoms[bond.to].position); | |
| const path = new THREE.LineCurve3(start, end); | |
| const geometry = new THREE.TubeGeometry(path, 1, 0.05, 8, false); | |
| const material = new THREE.MeshStandardMaterial({ color: '#cccccc' }); | |
| const mesh = new THREE.Mesh(geometry, material); | |
| moleculeGroup.add(mesh); | |
| }); | |
| // Center the molecule | |
| new THREE.Box3().setFromObject(moleculeGroup).getCenter(moleculeGroup.position).multiplyScalar(-1); | |
| // Animation loop | |
| let animationFrameId; | |
| const animate = () => { | |
| animationFrameId = requestAnimationFrame(animate); | |
| if (isAnimating) { | |
| moleculeGroup.rotation.y += 0.005; | |
| moleculeGroup.rotation.x += 0.002; | |
| } | |
| renderer.render(scene, camera); | |
| }; | |
| animate(); | |
| // Resize handler | |
| const handleResize = () => { | |
| if (!currentMount) return; | |
| camera.aspect = currentMount.clientWidth / currentMount.clientHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(currentMount.clientWidth, currentMount.clientHeight); | |
| }; | |
| window.addEventListener('resize', handleResize); | |
| // Cleanup | |
| return () => { | |
| window.removeEventListener('resize', handleResize); | |
| if (currentMount.contains(renderer.domElement)) { | |
| currentMount.removeChild(renderer.domElement); | |
| } | |
| cancelAnimationFrame(animationFrameId); | |
| }; | |
| }, [molecule, isAnimating]); | |
| return <div ref={mountRef} className="w-full h-full" />; | |
| }; | |
| export default function MolecularViewer() { | |
| const [selectedMolecule, setSelectedMolecule] = useState(MOLECULES[0]); | |
| const [isAnimating, setIsAnimating] = useState(true); | |
| const [searchTerm, setSearchTerm] = useState(""); | |
| const [selectedCategory, setSelectedCategory] = useState("all"); | |
| const filteredMolecules = MOLECULES.filter(mol => { | |
| const matchesSearch = mol.name.toLowerCase().includes(searchTerm.toLowerCase()) || | |
| mol.formula.toLowerCase().includes(searchTerm.toLowerCase()); | |
| const matchesCategory = selectedCategory === "all" || mol.category === selectedCategory; | |
| return matchesSearch && matchesCategory; | |
| }); | |
| return ( | |
| <div className="min-h-screen bg-gradient-to-br from-slate-50 to-purple-50 p-4 md:p-8"> | |
| <div className="max-w-7xl mx-auto"> | |
| <div className="mb-8"> | |
| <div className="flex items-center gap-3 mb-4"> | |
| <div className="w-12 h-12 bg-gradient-to-br from-purple-600 to-pink-700 rounded-xl flex items-center justify-center"> | |
| <Atom className="w-6 h-6 text-white" /> | |
| </div> | |
| <div> | |
| <h1 className="text-3xl font-bold text-slate-900">3D Molecular Database</h1> | |
| <p className="text-slate-600">Explore {MOLECULES.length} molecular structures in interactive 3D space</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div className="grid lg:grid-cols-4 gap-8"> | |
| {/* Molecule Library */} | |
| <div className="lg:col-span-1"> | |
| <Card className="border-0 shadow-xl bg-white/80 backdrop-blur-sm"> | |
| <CardHeader> | |
| <CardTitle className="flex items-center gap-2 text-lg"> | |
| <Search className="w-5 h-5 text-purple-600" /> | |
| Molecule Library | |
| </CardTitle> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="space-y-4"> | |
| <Input | |
| placeholder="Search molecules..." | |
| value={searchTerm} | |
| onChange={(e) => setSearchTerm(e.target.value)} | |
| className="bg-white" | |
| /> | |
| <div> | |
| <label className="block text-sm font-medium text-slate-700 mb-2">Category</label> | |
| <Select value={selectedCategory} onValueChange={setSelectedCategory}> | |
| <SelectTrigger className="bg-white"> | |
| <SelectValue /> | |
| </SelectTrigger> | |
| <SelectContent> | |
| {CATEGORIES.map((category) => ( | |
| <SelectItem key={category.value} value={category.value}> | |
| {category.label} ({category.count}) | |
| </SelectItem> | |
| ))} | |
| </SelectContent> | |
| </Select> | |
| </div> | |
| <div className="space-y-2 max-h-96 overflow-y-auto pr-2"> | |
| {filteredMolecules.map((molecule, index) => ( | |
| <button | |
| key={index} | |
| onClick={() => setSelectedMolecule(molecule)} | |
| className={`w-full text-left p-3 rounded-lg border transition-all duration-200 ${ | |
| selectedMolecule?.name === molecule.name | |
| ? 'border-purple-500 bg-purple-50' | |
| : 'border-slate-200 hover:border-purple-300 hover:bg-purple-25' | |
| }`} | |
| > | |
| <div className="font-medium text-slate-900 mb-1"> | |
| {molecule.name} | |
| </div> | |
| <div className="flex items-center justify-between"> | |
| <Badge variant="outline" className="text-xs"> | |
| {molecule.formula} | |
| </Badge> | |
| <Badge variant="secondary" className="text-xs capitalize"> | |
| {molecule.category} | |
| </Badge> | |
| </div> | |
| </button> | |
| ))} | |
| {filteredMolecules.length === 0 && ( | |
| <div className="text-center py-8 text-slate-500"> | |
| <Search className="w-12 h-12 mx-auto mb-2 opacity-50" /> | |
| <p>No molecules found</p> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| </CardContent> | |
| </Card> | |
| </div> | |
| {/* Main Viewer */} | |
| <div className="lg:col-span-3"> | |
| <Card className="border-0 shadow-xl bg-white/80 backdrop-blur-sm"> | |
| <CardHeader> | |
| <div className="flex items-center justify-between flex-wrap gap-4"> | |
| <CardTitle className="text-xl"> | |
| {selectedMolecule?.name} | |
| </CardTitle> | |
| <div className="flex items-center gap-2"> | |
| <Button | |
| variant="outline" | |
| size="icon" | |
| onClick={() => setIsAnimating(!isAnimating)} | |
| title={isAnimating ? "Pause Animation" : "Play Animation"} | |
| > | |
| {isAnimating ? ( | |
| <Pause className="w-4 h-4" /> | |
| ) : ( | |
| <Play className="w-4 h-4" /> | |
| )} | |
| </Button> | |
| </div> | |
| </div> | |
| </CardHeader> | |
| <CardContent> | |
| <Tabs defaultValue="viewer" className="w-full"> | |
| <TabsList className="grid w-full grid-cols-2 mb-6"> | |
| <TabsTrigger value="viewer">3D Viewer</TabsTrigger> | |
| <TabsTrigger value="info">Molecule Info</TabsTrigger> | |
| </TabsList> | |
| <TabsContent value="viewer" className="space-y-0"> | |
| <div className="h-96 w-full bg-gradient-to-b from-slate-900 to-slate-800 rounded-xl overflow-hidden relative"> | |
| <ThreeCanvas molecule={selectedMolecule} isAnimating={isAnimating} /> | |
| <div className="absolute top-4 right-4"> | |
| <Badge className="bg-purple-600 text-white"> | |
| {selectedMolecule?.formula} | |
| </Badge> | |
| </div> | |
| </div> | |
| </TabsContent> | |
| <TabsContent value="info" className="space-y-6"> | |
| <div className="grid md:grid-cols-2 gap-6"> | |
| <Card className="bg-gradient-to-br from-blue-50 to-indigo-50 border-blue-200"> | |
| <CardContent className="p-6"> | |
| <div className="flex items-center gap-2 mb-3"> | |
| <Info className="w-5 h-5 text-blue-600" /> | |
| <h3 className="font-semibold text-blue-900">Molecular Formula</h3> | |
| </div> | |
| <p className="text-2xl font-bold text-blue-800 font-mono"> | |
| {selectedMolecule?.formula} | |
| </p> | |
| </CardContent> | |
| </Card> | |
| <Card className="bg-gradient-to-br from-emerald-50 to-teal-50 border-emerald-200"> | |
| <CardContent className="p-6"> | |
| <div className="flex items-center gap-2 mb-3"> | |
| <Atom className="w-5 h-5 text-emerald-600" /> | |
| <h3 className="font-semibold text-emerald-900">Atom Count</h3> | |
| </div> | |
| <p className="text-2xl font-bold text-emerald-800"> | |
| {selectedMolecule?.atoms.length} atoms | |
| </p> | |
| </CardContent> | |
| </Card> | |
| </div> | |
| <Card className="bg-gradient-to-br from-purple-50 to-pink-50 border-purple-200"> | |
| <CardContent className="p-6"> | |
| <div className="flex items-center gap-2 mb-3"> | |
| <Filter className="w-5 h-5 text-purple-600" /> | |
| <h3 className="font-semibold text-purple-900">Category</h3> | |
| </div> | |
| <Badge className="capitalize bg-purple-600 text-white text-lg px-3 py-1"> | |
| {selectedMolecule?.category} | |
| </Badge> | |
| </CardContent> | |
| </Card> | |
| <Card className="bg-slate-50 border-slate-200"> | |
| <CardHeader> | |
| <CardTitle className="text-lg">Description</CardTitle> | |
| </CardHeader> | |
| <CardContent> | |
| <p className="text-slate-700 leading-relaxed"> | |
| {selectedMolecule?.description} | |
| </p> | |
| </CardContent> | |
| </Card> | |
| {selectedMolecule?.atoms && ( | |
| <Card className="bg-slate-50 border-slate-200"> | |
| <CardHeader> | |
| <CardTitle className="text-lg">Atomic Composition</CardTitle> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="space-y-3"> | |
| {Object.entries( | |
| selectedMolecule.atoms.reduce((acc, atom) => { | |
| acc[atom.element] = (acc[atom.element] || 0) + 1; | |
| return acc; | |
| }, {}) | |
| ).map(([element, count]) => ( | |
| <div key={element} className="flex items-center gap-3"> | |
| <div | |
| className="w-8 h-8 rounded-full border-2 border-slate-300 flex items-center justify-center text-xs font-bold" | |
| style={{ | |
| backgroundColor: selectedMolecule.atoms.find(a => a.element === element)?.color, | |
| color: element === 'H' ? '#000' : '#fff' | |
| }} | |
| > | |
| {element} | |
| </div> | |
| <span className="font-medium text-slate-900">{element}</span> | |
| <span className="text-slate-600">× {count}</span> | |
| </div> | |
| ))} | |
| </div> | |
| </CardContent> | |
| </Card> | |
| )} | |
| </TabsContent> | |
| </Tabs> | |
| </CardContent> | |
| </Card> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } |